Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions .github/pr-proof/muse-web-quota-fallback.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Muse Code web quota fallback — production-path proof
# Date: 2026-09-26 03:22–03:39 AWST (UTC+8), macOS arm64, real Muse Code Everyday Usage account.
# Redactions: device token, llama_dev_sess cookie value, email address, tier_id. Nothing else edited.
#
# Setup (no CodexBar Keychain read, no browser automation):
# - The dca: device token was read once with /usr/bin/security (user clicked Allow) into a
# temporary 0600 auth.json in the CLI's file format (storage "file"), selected with MUSE_AUTH_PATH.
# - The llama_dev_sess cookie was copied from the Firefox cookies.sqlite into a temporary
# CODEXBAR_CONFIG with cookieSource "manual". Both files were deleted after the run.
# - Brew build: codexbar 0.66.0 (/opt/homebrew/bin/codexbar).
# - Branch build: swift build of commit 74130071c (.build/debug/CodexBarCLI).

## 1. The 5-hour window is idle (cookie only; 03:22:13)
$ curl -H "Cookie: llama_dev_sess=<redacted>" -H "User-Agent: CodexBar" \
https://dev.meta.ai/api/portal/teams/<team>/subscription-quota
{"subscription_quota":{"tier_id":"<id>","tier":"Muse Code Everyday Usage","as_of":1790364133,
"window_weighted_limit":"20000000000","window_duration_secs":18000,
"weekly_weighted_limit":"60000000000","weekly_resets_at":1790553600,
"window_weighted_used":"0","weekly_weighted_used":"13550839000"}}
# No window_resets_at while idle. Weekly: 13550839000 / 60000000000 = 22.58%.
# The Muse CLI `/usage` shows "Currently unavailable" in this state.

## 2. Same account, same config, both builds (03:38:44)
$ CODEXBAR_CONFIG=<tmp>/config.json MUSE_AUTH_PATH=<tmp>/auth.json codexbar usage --provider muse --format json
== codexbar 0.66.0 (Brew)
{
"source": "oauth",
"primary": null,
"secondary": null,
"dataConfidence": null,
"rows": [
{"label": "Plan", "value": "Muse Code Everyday Usage"},
{"label": "Quota", "value": "Not included in this login response"}
],
"error": null
}
== branch 74130071c
{
"source": "oauth+web",
"primary": {"windowMinutes": 300, "usedPercent": 0},
"secondary": {"windowMinutes": 10080, "usedPercent": 22.584731666666666, "resetsAt": "2026-09-28T00:00:00Z"},
"dataConfidence": "exact",
"rows": [
{"label": "Plan", "value": "Muse Code Everyday Usage"},
{"label": "5 hours", "value": "0%"},
{"label": "Weekly", "value": "23%"}
],
"error": null
}
# Branch weekly value equals step 1 (22.58%); reset equals weekly_resets_at (1790553600).
# Idle 5-hour window: 0% with no invented reset time.

## 3. Default (no cookieSource configured) on the branch stays Off
$ CODEXBAR_CONFIG=<tmp>/off.json MUSE_AUTH_PATH=<tmp>/auth.json CodexBarCLI usage --provider muse --format json
oauth None ['Muse Code Everyday Usage', 'Not included in this login response']
# Unconfigured installs make no dev.meta.ai request and read no browser cookies.

## 4. Active window (earlier, 2026-09-25 22:45 AWST): subs_usage present, fallback not used
# Brew and branch both returned source "oauth", 5 hours 6%, Weekly 17% — identical output.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
- OpenCode Go: include recorded local token counts in daily and per-model history without inventing costs or treating missing counts as zero (#3995). Thanks @Yuxin-Qiao!
- Security: preserve browser-cookie denial across restarts and CLI configuration, and stage credential writes privately before atomic replacement (reported in #3986). Thanks @bo-vavrik!
- Provider plugins: update bundled QuickJS-NG to 0.17.0 with upstream memory-safety and numeric-correctness fixes (#3987). Thanks @bo-vavrik!
- Muse Code: when the login response omits subscription quotas (Meta leaves them out while the 5-hour window is idle), read the 5-hour and weekly quotas from the `dev.meta.ai` usage page with an opt-in browser session for the same account (Off by default; Automatic reads Chrome or Firefox).
- OpenRouter: explain the required API key field instead of reporting no available fetch strategy, and clarify where regular and Management keys belong (#3966, #3969). Thanks @harjothkhara!
- Antigravity: let menu-bar layouts pin Gemini and Claude/GPT weekly percentages separately when each allowance is available (#3394). Thanks @ksuchoi216!
- Antigravity: preserve decoded local history as a marked lower bound when later databases exhaust the schema budget, while retaining hard scan limits (#3957). Thanks @Niclassslua!
Expand Down
47 changes: 45 additions & 2 deletions Sources/CodexBar/Providers/Muse/MuseProviderImplementation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,56 @@ struct MuseProviderImplementation: ProviderImplementation {
@MainActor
func observeSettings(_: SettingsStore) {}

/// The shared cookie snapshot defaults to Automatic; Muse reads a browser session only after an explicit choice.
@MainActor
func settingsSnapshot(context: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution? {
ProviderSettingsSnapshotContribution(
MuseProviderSettings(
cookieSource: context.settings.museCookieSource,
manualCookieHeader: context.settings.museCookieHeader),
for: MuseProviderSettingsKey.self)
}

@MainActor
func isAvailable(context: ProviderAvailabilityContext) -> Bool {
MuseCredentials.hasLogin(environment: context.environment)
}

/// The dev.meta.ai session only fills quotas that the Muse login response leaves out.
@MainActor
func settingsPickers(context: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {
[
ProviderCookieSourceUI.picker(
id: "muse-cookie-source",
context: context,
source: \.museCookieSource,
allowsOff: true,
subtitles: {
.init(
auto: L("Automatically imports browser cookies."),
manual: L("Paste a Cookie header or cURL capture from %@.", "dev.meta.ai"),
off: L("%@ cookies are disabled.", "Muse Code"))
}),
]
}

@MainActor
func settingsFields(context _: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {
[]
func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {
[
ProviderSettingsFieldDescriptor(
id: "muse-cookie",
title: "",
subtitle: "",
kind: .secure,
placeholder: "Cookie: llama_dev_sess=...",
binding: context.binding(\.museCookieHeader),
actions: [
ProviderSettingsActionDescriptor.openURL(
id: "muse-open-usage",
title: "Open dev.meta.ai",
url: URL(string: "https://dev.meta.ai/usage")),
],
isVisible: { context.settings.museCookieSource == .manual }),
]
}
}
18 changes: 18 additions & 0 deletions Sources/CodexBar/Providers/Muse/MuseSettingsStore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import CodexBarCore
import Foundation

extension SettingsStore {
var museCookieHeader: String {
get { self[providerConfig: .muse, field: .cookieHeader] }
set { self[providerConfig: .muse, field: .cookieHeader] = newValue }
}

var museCookieSource: ProviderCookieSource {
// Browser sessions are opt-in; a pasted header without an explicit source means Manual, as in the CLI.
get {
let header = self.providerConfig(for: .muse)?.sanitizedCookieHeader
return self.resolvedCookieSource(provider: .muse, fallback: header == nil ? .off : .manual)
}
set { self.setCookieSource(newValue, provider: .muse) }
}
}
2 changes: 2 additions & 0 deletions Sources/CodexBar/SettingsStore+MenuObservation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ extension SettingsStore {
_ = self.augmentCookieSource
_ = self.ampCookieSource
_ = self.t3ChatCookieSource
_ = self.museCookieSource
_ = self.zoomMateCookieSource
_ = self.ollamaCookieSource
_ = self.mergeIcons
Expand All @@ -124,6 +125,7 @@ extension SettingsStore {
_ = self.augmentCookieHeader
_ = self.ampCookieHeader
_ = self.t3ChatCookieHeader
_ = self.museCookieHeader
_ = self.zoomMateCookieHeader
_ = self.ollamaCookieHeader
_ = self.copilotAPIToken
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/UsageStore+Logging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ extension UsageStore {
"augmentCookieSource": self.settings.augmentCookieSource.rawValue,
"ampCookieSource": self.settings.ampCookieSource.rawValue,
"t3ChatCookieSource": self.settings.t3ChatCookieSource.rawValue,
"museCookieSource": self.settings.museCookieSource.rawValue,
"ollamaCookieSource": self.settings.ollamaCookieSource.rawValue,
"openAIWebAccess": self.settings.openAIWebAccessEnabled ? "1" : "0",
"openAIWebBatterySaver": self.settings.openAIWebBatterySaverEnabled ? "1" : "0",
Expand Down
40 changes: 37 additions & 3 deletions Sources/CodexBarCore/Providers/Muse/MuseProviderDescriptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,30 @@ public enum MuseProviderDescriptor {
"Muse Code login not found. Run `muse login`, then refresh CodexBar."
})

/// Chrome needs a no-UI Safe Storage grant and Firefox needs none; Safari's store can require Full Disk Access.
private static var browserCookieOrder: BrowserCookieImportOrder? {
#if os(macOS)
[.chrome, .firefox]
#else
nil
#endif
}

static func makeDescriptor() -> ProviderDescriptor {
ProviderDescriptor(
id: .muse,
settingsSection: .init(
MuseProviderSettingsKey.self,
cookieSettings: { settings in
.init(cookieSource: settings.cookieSource, manualCookieHeader: settings.manualCookieHeader)
},
credentialSettings: { context in
// Browser sessions are opt-in for Muse: without an explicit source or pasted header, stay Off.
let header = context.config?.sanitizedCookieHeader
return MuseProviderSettings(
cookieSource: context.config?.cookieSource ?? (header == nil ? .off : .manual),
manualCookieHeader: header)
}),
credentials: self.credentials,
metadata: ProviderMetadata(
id: .muse,
Expand All @@ -32,6 +53,7 @@ public enum MuseProviderDescriptor {
cliName: "muse",
defaultEnabled: false,
widgetSelectable: false,
browserCookieOrder: self.browserCookieOrder,
dashboardURL: "https://dev.meta.ai",
subscriptionDashboardURL: "https://dev.meta.ai",
statusPageURL: nil),
Expand Down Expand Up @@ -75,9 +97,21 @@ struct MuseOAuthFetchStrategy: ProviderFetchStrategy {

func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {
let token = try MuseCredentials.accessToken(environment: context.env)
let runtime = try ProviderPluginRuntime(bundledPlugin: "muse")
let snapshot = try await runtime.fetchUsage(secrets: ["MUSE_DEVICE_TOKEN": token])
return self.makeResult(usage: snapshot, sourceLabel: "oauth")
// The key request (15 s) and the bounded dev.meta.ai fallback (4 × 8 s) fit one 60 s deadline.
let runtime = try ProviderPluginRuntime(bundledPlugin: "muse", timeout: 60)
let cookies = ProviderPluginCookieBroker(
provider: .muse, domains: runtime.manifest.cookieDomains, context: context)
// Reading the browser session is opt-in: an unconfigured Muse provider keeps its CLI-token-only behavior.
let cookieSource = context.settings?[MuseProviderSettingsKey.self]?.cookieSource ?? .off
let result = try await runtime.fetchResult(
secrets: ["MUSE_DEVICE_TOKEN": token],
sourceMode: context.sourceMode,
cookieSource: cookieSource,
cookieInvalidator: { cookies.rejectCookie(domain: $0) },
cookieSessionResolver: { try cookies.nextSession(domain: $0, cachedOnly: $1) },
cookieSessionInvalidator: { cookies.rejectCookie(domain: $0, id: $1) },
cookieResolver: { _, domain in try cookies.cookieHeader(domain: domain) })
return self.makeResult(usage: result.usage, sourceLabel: result.sourceLabel ?? "oauth")
}

func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {
Expand Down
22 changes: 22 additions & 0 deletions Sources/CodexBarCore/Providers/Muse/MuseProviderSettings.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Foundation

public struct MuseProviderSettings: ProviderCookieSettings {
public let cookieSource: ProviderCookieSource
public let manualCookieHeader: String?

public init(cookieSource: ProviderCookieSource, manualCookieHeader: String?) {
self.cookieSource = cookieSource
self.manualCookieHeader = manualCookieHeader
}
}

public enum MuseProviderSettingsKey: ProviderSettingsSectionKey {
public static let providerID = ProviderInstanceID.muse
public typealias Section = MuseProviderSettings
}

extension ProviderSettingsSnapshot {
public static func make(muse: MuseProviderSettings?) -> Self {
self.make(muse, for: MuseProviderSettingsKey.self)
}
}
Loading
Loading