@phnx-labs/agents-cli 1.22.120 → 1.22.121
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +227 -0
- package/README.md +19 -2
- package/dist/bootstrap.d.ts +0 -15
- package/dist/bootstrap.js +10 -409
- package/dist/cli/command-registry.d.ts +0 -58
- package/dist/cli/command-registry.js +2 -74
- package/dist/commands/accounts.d.ts +0 -17
- package/dist/commands/accounts.js +1 -52
- package/dist/commands/alias.d.ts +0 -12
- package/dist/commands/alias.js +0 -6
- package/dist/commands/apply.d.ts +0 -15
- package/dist/commands/apply.js +0 -63
- package/dist/commands/audit.d.ts +0 -12
- package/dist/commands/audit.js +0 -13
- package/dist/commands/auth.js +4 -12
- package/dist/commands/beta.d.ts +0 -6
- package/dist/commands/beta.js +2 -6
- package/dist/commands/browser-sessions-picker.d.ts +0 -8
- package/dist/commands/browser-sessions-picker.js +0 -26
- package/dist/commands/browser.d.ts +0 -57
- package/dist/commands/browser.js +0 -80
- package/dist/commands/budget.d.ts +0 -13
- package/dist/commands/budget.js +0 -2
- package/dist/commands/cli.d.ts +0 -12
- package/dist/commands/cli.js +0 -4
- package/dist/commands/cloud.d.ts +0 -9
- package/dist/commands/cloud.js +0 -43
- package/dist/commands/commands.d.ts +0 -9
- package/dist/commands/commands.js +0 -18
- package/dist/commands/computer-sessions-picker.d.ts +0 -11
- package/dist/commands/computer-sessions-picker.js +0 -29
- package/dist/commands/computer.d.ts +0 -62
- package/dist/commands/computer.js +3 -114
- package/dist/commands/config.d.ts +0 -14
- package/dist/commands/config.js +21 -60
- package/dist/commands/cost.d.ts +0 -11
- package/dist/commands/cost.js +1 -17
- package/dist/commands/daemon-test-harness.d.ts +0 -32
- package/dist/commands/daemon-test-harness.js +1 -36
- package/dist/commands/daemon.d.ts +0 -46
- package/dist/commands/daemon.js +8 -255
- package/dist/commands/detach-core.d.ts +0 -28
- package/dist/commands/detach-core.js +0 -30
- package/dist/commands/detach.d.ts +1 -12
- package/dist/commands/detach.js +16 -52
- package/dist/commands/device-config-test-harness.js +0 -12
- package/dist/commands/doctor.d.ts +0 -103
- package/dist/commands/doctor.js +3 -279
- package/dist/commands/events.d.ts +0 -29
- package/dist/commands/events.js +1 -55
- package/dist/commands/exec.d.ts +0 -163
- package/dist/commands/exec.js +15 -964
- package/dist/commands/factory.d.ts +0 -6
- package/dist/commands/factory.js +0 -2
- package/dist/commands/feed-watch.js +0 -20
- package/dist/commands/feed.d.ts +0 -38
- package/dist/commands/feed.js +1 -151
- package/dist/commands/feedback.d.ts +0 -5
- package/dist/commands/feedback.js +0 -5
- package/dist/commands/fleet-capture.d.ts +0 -12
- package/dist/commands/fleet-capture.js +1 -39
- package/dist/commands/focus.d.ts +6 -94
- package/dist/commands/focus.js +13 -183
- package/dist/commands/fork.d.ts +0 -21
- package/dist/commands/fork.js +0 -45
- package/dist/commands/funnel.d.ts +0 -3
- package/dist/commands/go.d.ts +2 -71
- package/dist/commands/go.js +5 -99
- package/dist/commands/harness-hooks.d.ts +0 -41
- package/dist/commands/harness-hooks.js +0 -48
- package/dist/commands/harness-wizard.d.ts +0 -137
- package/dist/commands/harness-wizard.js +0 -109
- package/dist/commands/harness.d.ts +0 -63
- package/dist/commands/harness.js +0 -103
- package/dist/commands/hooks.d.ts +0 -9
- package/dist/commands/hooks.js +0 -26
- package/dist/commands/humans.d.ts +0 -8
- package/dist/commands/humans.js +0 -8
- package/dist/commands/import.d.ts +0 -22
- package/dist/commands/import.js +0 -79
- package/dist/commands/insights.d.ts +0 -37
- package/dist/commands/insights.js +3 -115
- package/dist/commands/inspect.d.ts +0 -117
- package/dist/commands/inspect.js +2 -448
- package/dist/commands/lease.d.ts +0 -28
- package/dist/commands/lease.js +0 -43
- package/dist/commands/logs.d.ts +0 -23
- package/dist/commands/logs.js +0 -30
- package/dist/commands/mailboxes.d.ts +0 -18
- package/dist/commands/mailboxes.js +0 -29
- package/dist/commands/mcp.d.ts +0 -9
- package/dist/commands/mcp.js +0 -53
- package/dist/commands/memory.d.ts +0 -7
- package/dist/commands/memory.js +0 -7
- package/dist/commands/menubar.d.ts +0 -8
- package/dist/commands/menubar.js +0 -24
- package/dist/commands/message.d.ts +0 -23
- package/dist/commands/message.js +0 -50
- package/dist/commands/mine.d.ts +0 -22
- package/dist/commands/mine.js +0 -21
- package/dist/commands/models.d.ts +0 -8
- package/dist/commands/models.js +0 -33
- package/dist/commands/modes.d.ts +0 -10
- package/dist/commands/modes.js +0 -13
- package/dist/commands/monitors.d.ts +0 -9
- package/dist/commands/monitors.js +3 -160
- package/dist/commands/open.d.ts +0 -26
- package/dist/commands/open.js +0 -10
- package/dist/commands/output.d.ts +0 -18
- package/dist/commands/output.js +1 -34
- package/dist/commands/packages-materialize.d.ts +0 -11
- package/dist/commands/packages-materialize.js +0 -3
- package/dist/commands/packages.d.ts +0 -10
- package/dist/commands/packages.js +0 -50
- package/dist/commands/perf.d.ts +0 -41
- package/dist/commands/perf.js +0 -55
- package/dist/commands/permissions.d.ts +0 -9
- package/dist/commands/permissions.js +0 -43
- package/dist/commands/plugins.d.ts +0 -14
- package/dist/commands/plugins.js +2 -55
- package/dist/commands/profiles.d.ts +0 -25
- package/dist/commands/profiles.js +0 -27
- package/dist/commands/projects-todo.d.ts +2 -0
- package/dist/commands/projects-todo.js +132 -0
- package/dist/commands/projects.d.ts +0 -85
- package/dist/commands/projects.js +344 -291
- package/dist/commands/prune.js +0 -30
- package/dist/commands/ps-roster.d.ts +92 -0
- package/dist/commands/ps-roster.js +744 -0
- package/dist/commands/ps.d.ts +6 -0
- package/dist/commands/ps.js +98 -0
- package/dist/commands/refresh-rules.d.ts +0 -13
- package/dist/commands/refresh-rules.js +0 -14
- package/dist/commands/reminders.d.ts +0 -7
- package/dist/commands/repo.d.ts +0 -89
- package/dist/commands/repo.js +7 -205
- package/dist/commands/resource-view.d.ts +0 -33
- package/dist/commands/resource-view.js +0 -49
- package/dist/commands/resume.d.ts +1 -19
- package/dist/commands/resume.js +0 -50
- package/dist/commands/route.d.ts +0 -10
- package/dist/commands/route.js +0 -19
- package/dist/commands/routines.d.ts +0 -42
- package/dist/commands/routines.js +5 -290
- package/dist/commands/routines.test-fixture.d.ts +0 -34
- package/dist/commands/routines.test-fixture.js +4 -100
- package/dist/commands/rules.d.ts +0 -9
- package/dist/commands/rules.js +1 -6
- package/dist/commands/run-account-picker.d.ts +0 -59
- package/dist/commands/run-account-picker.js +0 -71
- package/dist/commands/run-broadcast.d.ts +0 -9
- package/dist/commands/run-broadcast.js +0 -7
- package/dist/commands/run-cloud.d.ts +0 -15
- package/dist/commands/run-cloud.js +0 -37
- package/dist/commands/run-device-picker.d.ts +0 -25
- package/dist/commands/run-device-picker.js +1 -53
- package/dist/commands/secrets-passthrough.d.ts +0 -31
- package/dist/commands/secrets-passthrough.js +0 -10
- package/dist/commands/send.d.ts +0 -20
- package/dist/commands/send.js +14 -18
- package/dist/commands/sessions-backfill.d.ts +0 -7
- package/dist/commands/sessions-backfill.js +1 -16
- package/dist/commands/sessions-backup-setup.js +0 -11
- package/dist/commands/sessions-bookmark.d.ts +0 -8
- package/dist/commands/sessions-bookmark.js +0 -28
- package/dist/commands/sessions-browser.d.ts +2 -117
- package/dist/commands/sessions-browser.js +5 -243
- package/dist/commands/sessions-export.d.ts +1 -26
- package/dist/commands/sessions-export.js +3 -81
- package/dist/commands/sessions-import.d.ts +0 -14
- package/dist/commands/sessions-import.js +0 -59
- package/dist/commands/sessions-inject.d.ts +0 -60
- package/dist/commands/sessions-inject.js +3 -96
- package/dist/commands/sessions-migrate.d.ts +2 -37
- package/dist/commands/sessions-migrate.js +7 -159
- package/dist/commands/sessions-optimize.d.ts +0 -12
- package/dist/commands/sessions-picker-factory.d.ts +0 -7
- package/dist/commands/sessions-picker-factory.js +0 -8
- package/dist/commands/sessions-picker.d.ts +4 -103
- package/dist/commands/sessions-picker.js +13 -352
- package/dist/commands/sessions-render.d.ts +1 -2
- package/dist/commands/sessions-render.js +3 -7
- package/dist/commands/sessions-resume.d.ts +1 -24
- package/dist/commands/sessions-resume.js +2 -65
- package/dist/commands/sessions-share.d.ts +1 -9
- package/dist/commands/sessions-share.js +1 -45
- package/dist/commands/sessions-stats.d.ts +0 -24
- package/dist/commands/sessions-stats.js +0 -49
- package/dist/commands/sessions-stop.d.ts +1 -20
- package/dist/commands/sessions-stop.js +4 -9
- package/dist/commands/sessions-tail.d.ts +1 -19
- package/dist/commands/sessions-tail.js +3 -36
- package/dist/commands/sessions-trace.d.ts +5 -36
- package/dist/commands/sessions-trace.js +7 -82
- package/dist/commands/sessions.d.ts +1 -666
- package/dist/commands/sessions.js +30 -2279
- package/dist/commands/sessions.test-fixture.d.ts +0 -9
- package/dist/commands/sessions.test-fixture.js +8 -23
- package/dist/commands/setup-accounts.d.ts +0 -6
- package/dist/commands/setup-browser.d.ts +0 -23
- package/dist/commands/setup-browser.js +0 -28
- package/dist/commands/setup-computer.d.ts +0 -16
- package/dist/commands/setup-computer.js +0 -28
- package/dist/commands/setup-fleet.d.ts +0 -8
- package/dist/commands/setup-fleet.js +0 -11
- package/dist/commands/setup-mine.d.ts +0 -14
- package/dist/commands/setup-mine.js +0 -25
- package/dist/commands/setup-preferences.d.ts +0 -47
- package/dist/commands/setup-preferences.js +0 -53
- package/dist/commands/setup-secrets.d.ts +2 -24
- package/dist/commands/setup-secrets.js +0 -24
- package/dist/commands/setup-term.d.ts +0 -21
- package/dist/commands/setup-term.js +0 -21
- package/dist/commands/setup-terminal.d.ts +0 -1
- package/dist/commands/setup-terminal.js +0 -1
- package/dist/commands/setup-tools.d.ts +2 -0
- package/dist/commands/setup-tools.js +73 -0
- package/dist/commands/setup.d.ts +0 -20
- package/dist/commands/setup.js +4 -57
- package/dist/commands/skills.d.ts +0 -9
- package/dist/commands/skills.js +1 -22
- package/dist/commands/snapshot.d.ts +0 -9
- package/dist/commands/snapshot.js +0 -10
- package/dist/commands/ssh.d.ts +0 -64
- package/dist/commands/ssh.js +9 -420
- package/dist/commands/status.d.ts +0 -12
- package/dist/commands/status.js +0 -26
- package/dist/commands/subagents.d.ts +0 -8
- package/dist/commands/subagents.js +0 -31
- package/dist/commands/sync.d.ts +0 -37
- package/dist/commands/sync.js +0 -256
- package/dist/commands/teams-picker.d.ts +0 -3
- package/dist/commands/teams-picker.js +0 -34
- package/dist/commands/teams.d.ts +1 -76
- package/dist/commands/teams.js +9 -477
- package/dist/commands/tmux.d.ts +0 -25
- package/dist/commands/tmux.js +0 -46
- package/dist/commands/traces.js +0 -19
- package/dist/commands/trash.d.ts +0 -17
- package/dist/commands/trash.js +1 -11
- package/dist/commands/uninstall.d.ts +0 -9
- package/dist/commands/uninstall.js +0 -6
- package/dist/commands/update.d.ts +0 -22
- package/dist/commands/update.js +0 -44
- package/dist/commands/upgrade.d.ts +0 -1
- package/dist/commands/upgrade.js +0 -1
- package/dist/commands/utils.d.ts +0 -85
- package/dist/commands/utils.js +1 -95
- package/dist/commands/versions.d.ts +0 -16
- package/dist/commands/versions.js +0 -134
- package/dist/commands/view.d.ts +0 -90
- package/dist/commands/view.js +4 -343
- package/dist/commands/watchdog.d.ts +0 -19
- package/dist/commands/watchdog.js +3 -43
- package/dist/commands/webhook.d.ts +0 -8
- package/dist/commands/webhook.js +0 -14
- package/dist/commands/workflows.d.ts +0 -8
- package/dist/commands/workflows.js +0 -6
- package/dist/index.d.ts +0 -25
- package/dist/index.js +5 -119
- package/dist/lib/account-capabilities.d.ts +0 -34
- package/dist/lib/account-capabilities.js +0 -32
- package/dist/lib/account-catalog.d.ts +0 -128
- package/dist/lib/account-catalog.js +8 -154
- package/dist/lib/account-provider-registry.d.ts +0 -14
- package/dist/lib/account-provider-registry.js +0 -8
- package/dist/lib/account-registry.d.ts +0 -74
- package/dist/lib/account-registry.js +14 -189
- package/dist/lib/account-schema.d.ts +0 -19
- package/dist/lib/account-schema.js +0 -43
- package/dist/lib/accounting/account-launch.d.ts +0 -15
- package/dist/lib/accounting/account-launch.js +1 -3
- package/dist/lib/accounting/account-pool-collect.d.ts +0 -26
- package/dist/lib/accounting/account-pool-collect.js +0 -42
- package/dist/lib/accounting/account-pool.d.ts +0 -41
- package/dist/lib/accounting/account-pool.js +0 -17
- package/dist/lib/accounting/capacity.d.ts +0 -32
- package/dist/lib/accounting/capacity.js +0 -32
- package/dist/lib/accounting/rotate.d.ts +0 -518
- package/dist/lib/accounting/rotate.js +4 -598
- package/dist/lib/accounting/usage-ingest.d.ts +0 -1
- package/dist/lib/accounting/usage-ingest.js +0 -44
- package/dist/lib/accounting/usage-sync.d.ts +0 -60
- package/dist/lib/accounting/usage-sync.js +4 -51
- package/dist/lib/accounting/usage.d.ts +0 -632
- package/dist/lib/accounting/usage.js +14 -1055
- package/dist/lib/accounts/add.d.ts +0 -60
- package/dist/lib/accounts/add.js +0 -127
- package/dist/lib/accounts/auth-operation-lock.d.ts +0 -1
- package/dist/lib/accounts/auth-operation-lock.js +0 -4
- package/dist/lib/accounts/migrate.d.ts +0 -9
- package/dist/lib/accounts/migrate.js +3 -42
- package/dist/lib/accounts/slots.d.ts +0 -25
- package/dist/lib/accounts/slots.js +1 -49
- package/dist/lib/acp/client.d.ts +0 -16
- package/dist/lib/acp/client.js +0 -29
- package/dist/lib/acp/harnesses.d.ts +0 -15
- package/dist/lib/acp/harnesses.js +0 -16
- package/dist/lib/acp/run.d.ts +0 -8
- package/dist/lib/acp/run.js +0 -8
- package/dist/lib/actor.d.ts +1 -71
- package/dist/lib/actor.js +1 -99
- package/dist/lib/add-dir.d.ts +0 -46
- package/dist/lib/add-dir.js +1 -70
- package/dist/lib/address.d.ts +0 -17
- package/dist/lib/address.js +0 -15
- package/dist/lib/agent-cli-commands.d.ts +0 -10
- package/dist/lib/agent-cli-commands.js +0 -10
- package/dist/lib/agent-modes.d.ts +0 -29
- package/dist/lib/agent-modes.js +0 -31
- package/dist/lib/agent-spec/agents.d.ts +0 -426
- package/dist/lib/agent-spec/agents.js +37 -924
- package/dist/lib/agent-spec/index.d.ts +0 -5
- package/dist/lib/agent-spec/index.js +0 -14
- package/dist/lib/agent-spec/materialize.d.ts +0 -10
- package/dist/lib/agent-spec/materialize.js +2 -139
- package/dist/lib/agent-spec/package-resolve.d.ts +0 -8
- package/dist/lib/agent-spec/package-resolve.js +1 -44
- package/dist/lib/agent-spec/package-schema.d.ts +0 -2
- package/dist/lib/agent-spec/package-schema.js +0 -11
- package/dist/lib/agent-spec/package-types.d.ts +0 -27
- package/dist/lib/agent-spec/package-types.js +0 -1
- package/dist/lib/agent-spec/primitives.d.ts +0 -18
- package/dist/lib/agent-spec/primitives.js +0 -27
- package/dist/lib/agent-spec/provider.js +0 -2
- package/dist/lib/agent-spec/resolve.d.ts +0 -23
- package/dist/lib/agent-spec/resolve.js +0 -41
- package/dist/lib/agent-spec/types.d.ts +0 -34
- package/dist/lib/agent-spec/types.js +0 -6
- package/dist/lib/agents.d.ts +0 -1
- package/dist/lib/agents.js +0 -1
- package/dist/lib/analytics/mix-commands.d.ts +0 -23
- package/dist/lib/analytics/mix-commands.js +0 -26
- package/dist/lib/analytics/recipes.d.ts +0 -1
- package/dist/lib/analytics/recipes.js +5 -10
- package/dist/lib/analytics/usage-db.js +4 -6
- package/dist/lib/answer-router.d.ts +0 -61
- package/dist/lib/answer-router.js +0 -57
- package/dist/lib/app-bundle-install.d.ts +0 -13
- package/dist/lib/app-bundle-install.js +0 -50
- package/dist/lib/artifact-actions.d.ts +0 -15
- package/dist/lib/artifact-actions.js +0 -18
- package/dist/lib/artifacts-client.d.ts +0 -10
- package/dist/lib/artifacts-client.js +0 -17
- package/dist/lib/ask-classifier.d.ts +0 -38
- package/dist/lib/ask-classifier.js +0 -27
- package/dist/lib/audit/log.d.ts +0 -47
- package/dist/lib/audit/log.js +1 -59
- package/dist/lib/auth-health.d.ts +0 -213
- package/dist/lib/auth-health.js +16 -266
- package/dist/lib/auth-mint.d.ts +0 -83
- package/dist/lib/auth-mint.js +8 -85
- package/dist/lib/auto-pull-worker.d.ts +0 -10
- package/dist/lib/auto-pull-worker.js +3 -48
- package/dist/lib/auto-pull.d.ts +0 -64
- package/dist/lib/auto-pull.js +0 -79
- package/dist/lib/beta.d.ts +0 -9
- package/dist/lib/beta.js +0 -9
- package/dist/lib/binary-shadow.d.ts +0 -22
- package/dist/lib/binary-shadow.js +4 -34
- package/dist/lib/boot-profile.d.ts +0 -10
- package/dist/lib/boot-profile.js +1 -31
- package/dist/lib/brand.d.ts +0 -20
- package/dist/lib/brand.js +0 -50
- package/dist/lib/browser/context.d.ts +0 -54
- package/dist/lib/browser/context.js +0 -49
- package/dist/lib/browser/paths.d.ts +0 -21
- package/dist/lib/browser/paths.js +0 -26
- package/dist/lib/browser/record.d.ts +0 -34
- package/dist/lib/browser/record.js +0 -38
- package/dist/lib/browser/sessions-list.d.ts +1 -79
- package/dist/lib/browser/sessions-list.js +2 -98
- package/dist/lib/browser-client.d.ts +0 -112
- package/dist/lib/browser-client.js +1 -90
- package/dist/lib/budget/config.d.ts +0 -6
- package/dist/lib/budget/config.js +0 -29
- package/dist/lib/budget/enforce.d.ts +0 -50
- package/dist/lib/budget/enforce.js +0 -27
- package/dist/lib/budget/ledger.d.ts +0 -23
- package/dist/lib/budget/ledger.js +0 -28
- package/dist/lib/budget/live-cloud.d.ts +0 -26
- package/dist/lib/budget/live-cloud.js +0 -12
- package/dist/lib/budget/live-team.d.ts +0 -17
- package/dist/lib/budget/live-team.js +1 -35
- package/dist/lib/budget/preflight.d.ts +0 -51
- package/dist/lib/budget/preflight.js +0 -36
- package/dist/lib/byok-usage.d.ts +0 -20
- package/dist/lib/byok-usage.js +1 -24
- package/dist/lib/capabilities.d.ts +0 -24
- package/dist/lib/capabilities.js +0 -29
- package/dist/lib/catchup.d.ts +0 -87
- package/dist/lib/catchup.js +1 -81
- package/dist/lib/channels/owner-forward.d.ts +0 -70
- package/dist/lib/channels/owner-forward.js +1 -43
- package/dist/lib/channels/owner-sink.js +0 -21
- package/dist/lib/channels/providers/desktop.d.ts +0 -37
- package/dist/lib/channels/providers/desktop.js +0 -72
- package/dist/lib/channels/providers/index.d.ts +0 -1
- package/dist/lib/channels/providers/index.js +2 -6
- package/dist/lib/channels/providers/mailbox.js +0 -7
- package/dist/lib/channels/providers/openclaw-telegram.js +0 -8
- package/dist/lib/channels/providers/rush.d.ts +0 -2
- package/dist/lib/channels/providers/rush.js +0 -26
- package/dist/lib/channels/providers/session.d.ts +2 -0
- package/dist/lib/channels/providers/session.js +26 -0
- package/dist/lib/channels/registry.d.ts +0 -27
- package/dist/lib/channels/registry.js +0 -10
- package/dist/lib/channels/resolve.d.ts +0 -19
- package/dist/lib/channels/resolve.js +0 -2
- package/dist/lib/channels/send.d.ts +0 -50
- package/dist/lib/channels/send.js +0 -25
- package/dist/lib/checkpoint.d.ts +0 -36
- package/dist/lib/checkpoint.js +0 -28
- package/dist/lib/claude-account-token.d.ts +0 -98
- package/dist/lib/claude-account-token.js +7 -169
- package/dist/lib/claude-statusline.d.ts +0 -41
- package/dist/lib/claude-statusline.js +0 -68
- package/dist/lib/cli-entry.d.ts +0 -26
- package/dist/lib/cli-entry.js +1 -61
- package/dist/lib/cli-resources.d.ts +31 -89
- package/dist/lib/cli-resources.js +116 -152
- package/dist/lib/cloud/antigravity.d.ts +0 -29
- package/dist/lib/cloud/antigravity.js +0 -36
- package/dist/lib/cloud/codex.d.ts +0 -8
- package/dist/lib/cloud/codex.js +0 -41
- package/dist/lib/cloud/cursor.d.ts +0 -5
- package/dist/lib/cloud/cursor.js +2 -7
- package/dist/lib/cloud/dispatch.d.ts +0 -13
- package/dist/lib/cloud/dispatch.js +1 -48
- package/dist/lib/cloud/factory.d.ts +0 -52
- package/dist/lib/cloud/factory.js +3 -67
- package/dist/lib/cloud/host.d.ts +0 -39
- package/dist/lib/cloud/host.js +5 -48
- package/dist/lib/cloud/registry.d.ts +0 -25
- package/dist/lib/cloud/registry.js +0 -29
- package/dist/lib/cloud/rush.d.ts +0 -47
- package/dist/lib/cloud/rush.js +1 -82
- package/dist/lib/cloud/session-index.d.ts +0 -27
- package/dist/lib/cloud/session-index.js +1 -32
- package/dist/lib/cloud/store.d.ts +0 -18
- package/dist/lib/cloud/store.js +0 -28
- package/dist/lib/cloud/stream.d.ts +0 -14
- package/dist/lib/cloud/stream.js +0 -25
- package/dist/lib/cloud/types.d.ts +0 -168
- package/dist/lib/cloud/types.js +0 -46
- package/dist/lib/cloudflare/creds.d.ts +0 -2
- package/dist/lib/cloudflare/creds.js +0 -14
- package/dist/lib/cloudflare/provision.d.ts +0 -8
- package/dist/lib/cloudflare/provision.js +1 -19
- package/dist/lib/codex-home.d.ts +0 -60
- package/dist/lib/codex-home.js +2 -72
- package/dist/lib/codex-policy.d.ts +0 -10
- package/dist/lib/codex-policy.js +0 -13
- package/dist/lib/command-skills.d.ts +0 -10
- package/dist/lib/command-skills.js +0 -10
- package/dist/lib/commands.d.ts +0 -74
- package/dist/lib/commands.js +0 -112
- package/dist/lib/comms-render.d.ts +0 -5
- package/dist/lib/comms-render.js +0 -5
- package/dist/lib/computer/context.d.ts +0 -23
- package/dist/lib/computer/context.js +0 -14
- package/dist/lib/computer/policy.d.ts +0 -20
- package/dist/lib/computer/policy.js +0 -53
- package/dist/lib/computer/record.d.ts +0 -33
- package/dist/lib/computer/record.js +1 -48
- package/dist/lib/computer/sessions-list.d.ts +1 -128
- package/dist/lib/computer/sessions-list.js +6 -165
- package/dist/lib/computer-client.d.ts +0 -106
- package/dist/lib/computer-client.js +5 -86
- package/dist/lib/concurrency.d.ts +0 -14
- package/dist/lib/concurrency.js +0 -3
- package/dist/lib/config-drift.d.ts +0 -31
- package/dist/lib/config-drift.js +0 -32
- package/dist/lib/config-keys.d.ts +1 -65
- package/dist/lib/config-keys.js +6 -52
- package/dist/lib/config-transfer.d.ts +0 -14
- package/dist/lib/config-transfer.js +0 -26
- package/dist/lib/convert.d.ts +0 -17
- package/dist/lib/convert.js +0 -18
- package/dist/lib/crabbox/cli.d.ts +0 -156
- package/dist/lib/crabbox/cli.js +1 -188
- package/dist/lib/crabbox/config.d.ts +0 -18
- package/dist/lib/crabbox/config.js +1 -19
- package/dist/lib/crabbox/lease.d.ts +0 -93
- package/dist/lib/crabbox/lease.js +4 -148
- package/dist/lib/crabbox/progress.d.ts +0 -56
- package/dist/lib/crabbox/progress.js +2 -49
- package/dist/lib/crabbox/runtimes.d.ts +0 -94
- package/dist/lib/crabbox/runtimes.js +1 -111
- package/dist/lib/crabbox/setup-copy.d.ts +0 -64
- package/dist/lib/crabbox/setup-copy.js +1 -53
- package/dist/lib/daemon/account-state-daemon-service.d.ts +0 -48
- package/dist/lib/daemon/account-state-daemon-service.js +2 -63
- package/dist/lib/daemon/attention-notify-service.d.ts +0 -12
- package/dist/lib/daemon/attention-notify-service.js +1 -24
- package/dist/lib/daemon/auth-sync-service.d.ts +0 -28
- package/dist/lib/daemon/auth-sync-service.js +0 -56
- package/dist/lib/daemon/catchup-service.d.ts +0 -34
- package/dist/lib/daemon/catchup-service.js +0 -30
- package/dist/lib/daemon/daemon.d.ts +0 -374
- package/dist/lib/daemon/daemon.js +44 -1049
- package/dist/lib/daemon/daemon.test-fixture.d.ts +0 -19
- package/dist/lib/daemon/daemon.test-fixture.js +1 -24
- package/dist/lib/daemon/device-probe-service.d.ts +0 -16
- package/dist/lib/daemon/device-probe-service.js +0 -20
- package/dist/lib/daemon/feed-stream-service.d.ts +0 -15
- package/dist/lib/daemon/feed-stream-service.js +0 -20
- package/dist/lib/daemon/harness-update-service.d.ts +2 -61
- package/dist/lib/daemon/harness-update-service.js +6 -86
- package/dist/lib/daemon/heartbeat-service.d.ts +0 -1
- package/dist/lib/daemon/heartbeat-service.js +0 -1
- package/dist/lib/daemon/leaked-daemons.d.ts +0 -46
- package/dist/lib/daemon/leaked-daemons.js +2 -78
- package/dist/lib/daemon/monitor-engine-service.d.ts +0 -13
- package/dist/lib/daemon/monitor-engine-service.js +3 -15
- package/dist/lib/daemon/runner.d.ts +0 -194
- package/dist/lib/daemon/runner.js +39 -760
- package/dist/lib/daemon/self-heal-service.d.ts +18 -17
- package/dist/lib/daemon/self-heal-service.js +83 -37
- package/dist/lib/daemon/self-update-service.d.ts +1 -131
- package/dist/lib/daemon/self-update-service.js +26 -189
- package/dist/lib/daemon/service.d.ts +0 -73
- package/dist/lib/daemon/service.js +0 -34
- package/dist/lib/daemon/session-index-service.d.ts +0 -12
- package/dist/lib/daemon/session-index-service.js +0 -23
- package/dist/lib/daemon/session-state-service.d.ts +0 -8
- package/dist/lib/daemon/session-state-service.js +0 -8
- package/dist/lib/daemon/session-summarizer-service.d.ts +0 -14
- package/dist/lib/daemon/session-summarizer-service.js +1 -19
- package/dist/lib/daemon/session-title-service.d.ts +0 -25
- package/dist/lib/daemon/session-title-service.js +0 -32
- package/dist/lib/daemon/state-dir-check-service.d.ts +0 -24
- package/dist/lib/daemon/state-dir-check-service.js +0 -29
- package/dist/lib/daemon/supervisor.d.ts +0 -112
- package/dist/lib/daemon/supervisor.js +4 -147
- package/dist/lib/daemon/testdata/process-view-daemon.js +0 -2
- package/dist/lib/daemon/testdata/process-view-start.js +0 -5
- package/dist/lib/daemon/tmux-reap-service.d.ts +0 -1
- package/dist/lib/daemon/tmux-reap-service.js +0 -1
- package/dist/lib/daemon/usage-sync-service.d.ts +0 -23
- package/dist/lib/daemon/usage-sync-service.js +0 -26
- package/dist/lib/daemon/watchdog-service.d.ts +0 -9
- package/dist/lib/daemon/watchdog-service.js +0 -13
- package/dist/lib/daemon/webhook-receiver-service.d.ts +0 -1
- package/dist/lib/daemon/webhook-receiver-service.js +0 -1
- package/dist/lib/daemon-health.d.ts +0 -55
- package/dist/lib/daemon-health.js +4 -77
- package/dist/lib/daemon-services.d.ts +0 -36
- package/dist/lib/daemon-services.js +0 -45
- package/dist/lib/daemon-ticks.d.ts +0 -126
- package/dist/lib/daemon-ticks.js +1 -156
- package/dist/lib/daemon-webhooks.d.ts +0 -57
- package/dist/lib/daemon-webhooks.js +2 -54
- package/dist/lib/deeplink/register.d.ts +0 -15
- package/dist/lib/deeplink/register.js +0 -60
- package/dist/lib/deeplink/url.d.ts +0 -24
- package/dist/lib/deeplink/url.js +1 -33
- package/dist/lib/device-config.d.ts +1 -192
- package/dist/lib/device-config.js +86 -240
- package/dist/lib/devices/config-migration.d.ts +0 -43
- package/dist/lib/devices/config-migration.js +7 -127
- package/dist/lib/devices/connect.d.ts +0 -168
- package/dist/lib/devices/connect.js +6 -254
- package/dist/lib/devices/device-docs.d.ts +0 -24
- package/dist/lib/devices/device-docs.js +0 -46
- package/dist/lib/devices/discovery-policy.d.ts +0 -30
- package/dist/lib/devices/discovery-policy.js +3 -34
- package/dist/lib/devices/doctor-findings.d.ts +0 -109
- package/dist/lib/devices/doctor-findings.js +30 -326
- package/dist/lib/devices/doctor-overview-cache.d.ts +0 -33
- package/dist/lib/devices/doctor-overview-cache.js +0 -81
- package/dist/lib/devices/fleet-divergence.d.ts +0 -89
- package/dist/lib/devices/fleet-divergence.js +1 -60
- package/dist/lib/devices/fleet-inventory.d.ts +0 -27
- package/dist/lib/devices/fleet-inventory.js +0 -38
- package/dist/lib/devices/fleet.d.ts +0 -95
- package/dist/lib/devices/fleet.js +0 -79
- package/dist/lib/devices/harness-inventory.d.ts +0 -75
- package/dist/lib/devices/harness-inventory.js +0 -101
- package/dist/lib/devices/health-report.d.ts +0 -37
- package/dist/lib/devices/health-report.js +0 -73
- package/dist/lib/devices/health.d.ts +0 -71
- package/dist/lib/devices/health.js +2 -82
- package/dist/lib/devices/interactive-host.d.ts +0 -10
- package/dist/lib/devices/interactive-host.js +0 -34
- package/dist/lib/devices/known-hosts.d.ts +0 -45
- package/dist/lib/devices/known-hosts.js +0 -77
- package/dist/lib/devices/pending.d.ts +0 -31
- package/dist/lib/devices/pending.js +7 -67
- package/dist/lib/devices/pool.d.ts +0 -36
- package/dist/lib/devices/pool.js +0 -30
- package/dist/lib/devices/reachability.d.ts +0 -25
- package/dist/lib/devices/reachability.js +0 -14
- package/dist/lib/devices/registry.d.ts +0 -141
- package/dist/lib/devices/registry.js +4 -151
- package/dist/lib/devices/resolve-profile.d.ts +0 -18
- package/dist/lib/devices/resolve-profile.js +0 -18
- package/dist/lib/devices/resolve-target.d.ts +0 -20
- package/dist/lib/devices/resolve-target.js +1 -51
- package/dist/lib/devices/rollout-verify.d.ts +1 -81
- package/dist/lib/devices/rollout-verify.js +0 -77
- package/dist/lib/devices/self-host.d.ts +0 -7
- package/dist/lib/devices/self-host.js +1 -29
- package/dist/lib/devices/ssh-config.d.ts +0 -18
- package/dist/lib/devices/ssh-config.js +0 -9
- package/dist/lib/devices/stats-cache.d.ts +0 -48
- package/dist/lib/devices/stats-cache.js +0 -80
- package/dist/lib/devices/sync.d.ts +0 -97
- package/dist/lib/devices/sync.js +0 -88
- package/dist/lib/devices/tailscale.d.ts +0 -18
- package/dist/lib/devices/tailscale.js +1 -38
- package/dist/lib/devices/terminfo.d.ts +0 -27
- package/dist/lib/devices/terminfo.js +2 -64
- package/dist/lib/devices/worker-pick.d.ts +0 -15
- package/dist/lib/devices/worker-pick.js +0 -48
- package/dist/lib/diff-text.d.ts +0 -18
- package/dist/lib/diff-text.js +0 -15
- package/dist/lib/doctor-diff.d.ts +0 -79
- package/dist/lib/doctor-diff.js +2 -181
- package/dist/lib/drift-sync.d.ts +0 -29
- package/dist/lib/drift-sync.js +0 -40
- package/dist/lib/drift.d.ts +0 -43
- package/dist/lib/drift.js +0 -31
- package/dist/lib/event-families.d.ts +0 -17
- package/dist/lib/event-families.js +0 -33
- package/dist/lib/event-provenance.d.ts +0 -4
- package/dist/lib/event-provenance.js +0 -5
- package/dist/lib/event-stream.d.ts +0 -29
- package/dist/lib/event-stream.js +0 -36
- package/dist/lib/events-ingest.d.ts +0 -27
- package/dist/lib/events-ingest.js +0 -54
- package/dist/lib/exec-account-home.d.ts +0 -24
- package/dist/lib/exec-account-home.js +0 -41
- package/dist/lib/exec-bounded.d.ts +1 -37
- package/dist/lib/exec-bounded.js +10 -46
- package/dist/lib/exec.d.ts +3 -583
- package/dist/lib/exec.js +18 -1025
- package/dist/lib/factory/snapshot.js +1 -15
- package/dist/lib/feed/activity-stream.d.ts +0 -27
- package/dist/lib/feed/activity-stream.js +4 -80
- package/dist/lib/feed/activity.d.ts +1 -243
- package/dist/lib/feed/activity.js +8 -273
- package/dist/lib/feed/answer.d.ts +0 -103
- package/dist/lib/feed/answer.js +0 -267
- package/dist/lib/feed/attention.d.ts +0 -56
- package/dist/lib/feed/attention.js +0 -161
- package/dist/lib/feed/envelope.d.ts +2 -17
- package/dist/lib/feed/envelope.js +0 -14
- package/dist/lib/feed/events.d.ts +0 -118
- package/dist/lib/feed/events.js +13 -213
- package/dist/lib/feed/feed.d.ts +1 -250
- package/dist/lib/feed/feed.js +24 -334
- package/dist/lib/feed/hub-server.d.ts +0 -100
- package/dist/lib/feed/hub-server.js +1 -191
- package/dist/lib/feed/hub.d.ts +0 -53
- package/dist/lib/feed/hub.js +0 -89
- package/dist/lib/feed/pr-status.d.ts +1 -19
- package/dist/lib/feed/pr-status.js +0 -18
- package/dist/lib/feed/tool-activity.d.ts +0 -59
- package/dist/lib/feed/tool-activity.js +1 -119
- package/dist/lib/feed/tools.d.ts +0 -100
- package/dist/lib/feed/tools.js +0 -89
- package/dist/lib/feed/watch.d.ts +0 -33
- package/dist/lib/feed/watch.js +2 -94
- package/dist/lib/feed-broadcast.d.ts +0 -148
- package/dist/lib/feed-broadcast.js +1 -283
- package/dist/lib/feed-outcome.d.ts +0 -50
- package/dist/lib/feed-outcome.js +1 -67
- package/dist/lib/feed-policy.d.ts +0 -6
- package/dist/lib/feed-policy.js +1 -27
- package/dist/lib/feed-post.d.ts +0 -61
- package/dist/lib/feed-post.js +4 -61
- package/dist/lib/feed-ranking.d.ts +1 -1
- package/dist/lib/fleet/apply.d.ts +0 -64
- package/dist/lib/fleet/apply.js +0 -125
- package/dist/lib/fleet/auth-sync.d.ts +0 -48
- package/dist/lib/fleet/auth-sync.js +4 -49
- package/dist/lib/fleet/capture.d.ts +0 -26
- package/dist/lib/fleet/capture.js +1 -36
- package/dist/lib/fleet/manifest.d.ts +0 -38
- package/dist/lib/fleet/manifest.js +0 -40
- package/dist/lib/fleet/types.d.ts +1 -138
- package/dist/lib/fleet/types.js +0 -12
- package/dist/lib/fleet-cache.d.ts +0 -12
- package/dist/lib/fleet-cache.js +0 -24
- package/dist/lib/fleet-shared-state.d.ts +6 -59
- package/dist/lib/fleet-shared-state.js +5 -51
- package/dist/lib/fleet-status.d.ts +0 -27
- package/dist/lib/fleet-status.js +0 -48
- package/dist/lib/format.d.ts +0 -74
- package/dist/lib/format.js +3 -77
- package/dist/lib/friction-heuristics.d.ts +0 -20
- package/dist/lib/friction-heuristics.js +0 -8
- package/dist/lib/fs-atomic.d.ts +0 -51
- package/dist/lib/fs-atomic.js +8 -66
- package/dist/lib/fs-walk.d.ts +0 -13
- package/dist/lib/fs-walk.js +0 -22
- package/dist/lib/fuzzy.d.ts +0 -29
- package/dist/lib/fuzzy.js +0 -31
- package/dist/lib/gemini-settings.js +0 -12
- package/dist/lib/git.d.ts +0 -366
- package/dist/lib/git.js +7 -574
- package/dist/lib/github/ci-failure.d.ts +30 -0
- package/dist/lib/github/ci-failure.js +95 -0
- package/dist/lib/github/gh-overload.d.ts +0 -38
- package/dist/lib/github/gh-overload.js +3 -55
- package/dist/lib/github/pr-mergeable.d.ts +3 -34
- package/dist/lib/github/pr-mergeable.js +2 -42
- package/dist/lib/github/pr-verdict.d.ts +0 -44
- package/dist/lib/github/pr-verdict.js +0 -36
- package/dist/lib/github/project-prs.d.ts +135 -105
- package/dist/lib/github/project-prs.js +530 -170
- package/dist/lib/github/release-drift.d.ts +45 -0
- package/dist/lib/github/release-drift.js +91 -0
- package/dist/lib/github/rest.d.ts +0 -50
- package/dist/lib/github/rest.js +0 -49
- package/dist/lib/github/viewer.d.ts +27 -0
- package/dist/lib/github/viewer.js +101 -0
- package/dist/lib/goose-commands.d.ts +0 -29
- package/dist/lib/goose-commands.js +3 -48
- package/dist/lib/harness/adapter.d.ts +0 -151
- package/dist/lib/harness/adapter.js +0 -41
- package/dist/lib/harness/adapters/claude.d.ts +0 -8
- package/dist/lib/harness/adapters/claude.js +0 -89
- package/dist/lib/harness/adapters/codex.js +0 -18
- package/dist/lib/harness/adapters/copilot.js +0 -3
- package/dist/lib/harness/adapters/cursor.js +0 -16
- package/dist/lib/harness/adapters/droid.js +0 -3
- package/dist/lib/harness/adapters/kimi.js +0 -19
- package/dist/lib/harness/adapters/muse.js +0 -9
- package/dist/lib/harness/adapters/opencode.js +0 -2
- package/dist/lib/harness/exec-config-version.d.ts +0 -2
- package/dist/lib/harness/exec-config-version.js +0 -13
- package/dist/lib/harness/index.js +0 -10
- package/dist/lib/harness-auth-capabilities.d.ts +0 -26
- package/dist/lib/harness-auth-capabilities.js +0 -18
- package/dist/lib/harness-connection-test.d.ts +0 -27
- package/dist/lib/harness-connection-test.js +2 -20
- package/dist/lib/heal.d.ts +0 -45
- package/dist/lib/heal.js +2 -82
- package/dist/lib/help.d.ts +0 -32
- package/dist/lib/help.js +0 -41
- package/dist/lib/helper-download.d.ts +0 -85
- package/dist/lib/helper-download.js +0 -95
- package/dist/lib/helper-versions.d.ts +0 -51
- package/dist/lib/helper-versions.js +1 -45
- package/dist/lib/hooks/cache.d.ts +0 -40
- package/dist/lib/hooks/cache.js +8 -122
- package/dist/lib/hooks/install.d.ts +0 -253
- package/dist/lib/hooks/install.js +49 -822
- package/dist/lib/hooks/match.d.ts +0 -24
- package/dist/lib/hooks/match.js +0 -31
- package/dist/lib/hooks/profile.d.ts +0 -21
- package/dist/lib/hooks/profile.js +0 -28
- package/dist/lib/hosts/credential-transport.d.ts +0 -6
- package/dist/lib/hosts/credential-transport.js +0 -17
- package/dist/lib/hosts/credentials.d.ts +0 -35
- package/dist/lib/hosts/credentials.js +0 -38
- package/dist/lib/hosts/dispatch.d.ts +0 -140
- package/dist/lib/hosts/dispatch.js +0 -176
- package/dist/lib/hosts/logs.d.ts +0 -25
- package/dist/lib/hosts/logs.js +1 -51
- package/dist/lib/hosts/option.d.ts +0 -12
- package/dist/lib/hosts/option.js +0 -12
- package/dist/lib/hosts/passthrough.d.ts +0 -129
- package/dist/lib/hosts/passthrough.js +12 -249
- package/dist/lib/hosts/progress.d.ts +0 -90
- package/dist/lib/hosts/progress.js +5 -115
- package/dist/lib/hosts/providers/devices.d.ts +0 -19
- package/dist/lib/hosts/providers/devices.js +1 -34
- package/dist/lib/hosts/providers/local.d.ts +0 -14
- package/dist/lib/hosts/providers/local.js +0 -22
- package/dist/lib/hosts/ready.d.ts +0 -99
- package/dist/lib/hosts/ready.js +0 -101
- package/dist/lib/hosts/reconcile.d.ts +0 -38
- package/dist/lib/hosts/reconcile.js +1 -50
- package/dist/lib/hosts/reconnect.d.ts +0 -75
- package/dist/lib/hosts/reconnect.js +0 -98
- package/dist/lib/hosts/registry.d.ts +0 -74
- package/dist/lib/hosts/registry.js +0 -110
- package/dist/lib/hosts/remote-cmd.d.ts +1 -228
- package/dist/lib/hosts/remote-cmd.js +19 -294
- package/dist/lib/hosts/remote-cmd.test-fixture.d.ts +0 -1
- package/dist/lib/hosts/remote-cmd.test-fixture.js +0 -12
- package/dist/lib/hosts/remote-os.d.ts +0 -19
- package/dist/lib/hosts/remote-os.js +0 -22
- package/dist/lib/hosts/remote-session-id.d.ts +0 -22
- package/dist/lib/hosts/remote-session-id.js +0 -24
- package/dist/lib/hosts/routing-flag.d.ts +0 -21
- package/dist/lib/hosts/routing-flag.js +0 -21
- package/dist/lib/hosts/run-target.d.ts +0 -53
- package/dist/lib/hosts/run-target.js +0 -56
- package/dist/lib/hosts/session-index.d.ts +1 -53
- package/dist/lib/hosts/session-index.js +2 -59
- package/dist/lib/hosts/session-marker.d.ts +0 -31
- package/dist/lib/hosts/session-marker.js +0 -32
- package/dist/lib/hosts/ssh-config.d.ts +0 -25
- package/dist/lib/hosts/ssh-config.js +1 -32
- package/dist/lib/hosts/tasks.d.ts +0 -54
- package/dist/lib/hosts/tasks.js +0 -35
- package/dist/lib/hosts/types.d.ts +0 -39
- package/dist/lib/hosts/types.js +0 -22
- package/dist/lib/humans.d.ts +0 -21
- package/dist/lib/humans.js +0 -30
- package/dist/lib/identity/client.d.ts +1 -35
- package/dist/lib/identity/client.js +3 -29
- package/dist/lib/identity/index.d.ts +3 -27
- package/dist/lib/identity/index.js +8 -24
- package/dist/lib/import.d.ts +0 -104
- package/dist/lib/import.js +1 -137
- package/dist/lib/installations/active-check.d.ts +0 -53
- package/dist/lib/installations/active-check.js +0 -49
- package/dist/lib/installations/index.d.ts +0 -9
- package/dist/lib/installations/index.js +0 -9
- package/dist/lib/installations/installation-lock.js +0 -8
- package/dist/lib/installations/launch-gate.d.ts +0 -56
- package/dist/lib/installations/launch-gate.js +0 -83
- package/dist/lib/installations/migrate.d.ts +0 -100
- package/dist/lib/installations/migrate.js +105 -572
- package/dist/lib/installations/native-command.d.ts +0 -1
- package/dist/lib/installations/native-command.js +0 -6
- package/dist/lib/installations/resolve.d.ts +0 -24
- package/dist/lib/installations/resolve.js +0 -24
- package/dist/lib/installations/shims.d.ts +0 -395
- package/dist/lib/installations/shims.js +30 -957
- package/dist/lib/installations/store.d.ts +0 -199
- package/dist/lib/installations/store.js +1 -297
- package/dist/lib/installations/strategies.d.ts +0 -44
- package/dist/lib/installations/strategies.js +0 -87
- package/dist/lib/installations/types.d.ts +1 -67
- package/dist/lib/installations/types.js +0 -6
- package/dist/lib/installations/update-cancellation.d.ts +0 -74
- package/dist/lib/installations/update-cancellation.js +0 -74
- package/dist/lib/installations/update-policy.d.ts +0 -57
- package/dist/lib/installations/update-policy.js +0 -62
- package/dist/lib/installations/update-runtime.d.ts +0 -88
- package/dist/lib/installations/update-runtime.js +1 -127
- package/dist/lib/installations/update.d.ts +0 -53
- package/dist/lib/installations/update.js +0 -95
- package/dist/lib/installations/versions.d.ts +0 -310
- package/dist/lib/installations/versions.js +14 -837
- package/dist/lib/isolation-boundary-report.d.ts +0 -7
- package/dist/lib/isolation-boundary-report.js +0 -7
- package/dist/lib/launch-identity.d.ts +0 -8
- package/dist/lib/launch-identity.js +0 -8
- package/dist/lib/linear-autoclose.d.ts +0 -25
- package/dist/lib/linear-autoclose.js +0 -19
- package/dist/lib/linear-cache.d.ts +0 -57
- package/dist/lib/linear-cache.js +1 -81
- package/dist/lib/linear-project-counts.d.ts +0 -83
- package/dist/lib/linear-project-counts.js +2 -64
- package/dist/lib/linear-projects.d.ts +0 -62
- package/dist/lib/linear-projects.js +0 -85
- package/dist/lib/linear-rate-limit.d.ts +0 -21
- package/dist/lib/linear-rate-limit.js +1 -93
- package/dist/lib/linux-userns.d.ts +1 -42
- package/dist/lib/linux-userns.js +0 -47
- package/dist/lib/lock-compromise.d.ts +0 -7
- package/dist/lib/lock-compromise.js +0 -7
- package/dist/lib/log-follow.d.ts +0 -2
- package/dist/lib/log-follow.js +5 -15
- package/dist/lib/loop.d.ts +0 -106
- package/dist/lib/loop.js +0 -135
- package/dist/lib/machine-id.d.ts +0 -19
- package/dist/lib/machine-id.js +0 -19
- package/dist/lib/mailbox-gc.d.ts +0 -7
- package/dist/lib/mailbox-gc.js +0 -25
- package/dist/lib/mailbox-target.d.ts +0 -31
- package/dist/lib/mailbox-target.js +0 -27
- package/dist/lib/mailbox.d.ts +0 -77
- package/dist/lib/mailbox.js +5 -105
- package/dist/lib/manifest.d.ts +0 -4
- package/dist/lib/manifest.js +0 -36
- package/dist/lib/markdown.d.ts +0 -3
- package/dist/lib/markdown.js +0 -9
- package/dist/lib/mcp-registry.d.ts +1 -50
- package/dist/lib/mcp-registry.js +0 -64
- package/dist/lib/mcp.d.ts +0 -115
- package/dist/lib/mcp.js +3 -199
- package/dist/lib/memory-cache.d.ts +0 -11
- package/dist/lib/memory-cache.js +0 -9
- package/dist/lib/memory.d.ts +0 -52
- package/dist/lib/memory.js +12 -74
- package/dist/lib/menubar/download-menubar.d.ts +0 -35
- package/dist/lib/menubar/download-menubar.js +0 -38
- package/dist/lib/menubar/install-menubar.d.ts +0 -405
- package/dist/lib/menubar/install-menubar.js +25 -667
- package/dist/lib/menubar/migrate-prefs.d.ts +0 -47
- package/dist/lib/menubar/migrate-prefs.js +1 -70
- package/dist/lib/menubar/notify-desktop.d.ts +0 -82
- package/dist/lib/menubar/notify-desktop.js +0 -68
- package/dist/lib/menubar/resolve-version.d.ts +0 -41
- package/dist/lib/menubar/resolve-version.js +2 -44
- package/dist/lib/menubar/snapshot.d.ts +13 -66
- package/dist/lib/menubar/snapshot.js +45 -39
- package/dist/lib/merged-resources.d.ts +0 -10
- package/dist/lib/merged-resources.js +0 -10
- package/dist/lib/migrate-fold.d.ts +0 -31
- package/dist/lib/migrate-fold.js +6 -38
- package/dist/lib/model-tier-overrides.d.ts +0 -30
- package/dist/lib/model-tier-overrides.js +0 -14
- package/dist/lib/model-tiers.d.ts +0 -43
- package/dist/lib/model-tiers.js +5 -78
- package/dist/lib/models.d.ts +0 -145
- package/dist/lib/models.js +3 -333
- package/dist/lib/monitors/config.d.ts +0 -187
- package/dist/lib/monitors/config.js +4 -101
- package/dist/lib/monitors/dispatch.d.ts +0 -22
- package/dist/lib/monitors/dispatch.js +0 -35
- package/dist/lib/monitors/engine.d.ts +1 -53
- package/dist/lib/monitors/engine.js +17 -106
- package/dist/lib/monitors/fingerprint.d.ts +0 -43
- package/dist/lib/monitors/fingerprint.js +0 -57
- package/dist/lib/monitors/pid-watch.d.ts +0 -31
- package/dist/lib/monitors/pid-watch.js +0 -31
- package/dist/lib/monitors/remote.d.ts +0 -52
- package/dist/lib/monitors/remote.js +0 -42
- package/dist/lib/monitors/sources/command.d.ts +2 -9
- package/dist/lib/monitors/sources/command.js +13 -34
- package/dist/lib/monitors/sources/device.d.ts +0 -10
- package/dist/lib/monitors/sources/device.js +0 -14
- package/dist/lib/monitors/sources/failure.d.ts +0 -27
- package/dist/lib/monitors/sources/failure.js +0 -27
- package/dist/lib/monitors/sources/file.d.ts +0 -10
- package/dist/lib/monitors/sources/file.js +0 -11
- package/dist/lib/monitors/sources/http.d.ts +0 -7
- package/dist/lib/monitors/sources/http.js +0 -9
- package/dist/lib/monitors/sources/index.d.ts +0 -9
- package/dist/lib/monitors/sources/index.js +0 -9
- package/dist/lib/monitors/sources/poll.d.ts +0 -8
- package/dist/lib/monitors/sources/poll.js +0 -8
- package/dist/lib/monitors/sources/types.d.ts +0 -18
- package/dist/lib/monitors/sources/types.js +0 -8
- package/dist/lib/monitors/sources/webhook.d.ts +0 -7
- package/dist/lib/monitors/sources/webhook.js +0 -7
- package/dist/lib/monitors/sources/ws.d.ts +0 -10
- package/dist/lib/monitors/sources/ws.js +3 -13
- package/dist/lib/monitors/state.d.ts +0 -132
- package/dist/lib/monitors/state.js +2 -86
- package/dist/lib/native-accounts.d.ts +0 -32
- package/dist/lib/native-accounts.js +0 -17
- package/dist/lib/net-close.d.ts +0 -9
- package/dist/lib/net-close.js +0 -5
- package/dist/lib/notify.d.ts +0 -47
- package/dist/lib/notify.js +0 -30
- package/dist/lib/onepassword.d.ts +0 -14
- package/dist/lib/onepassword.js +0 -12
- package/dist/lib/open-url.d.ts +0 -38
- package/dist/lib/open-url.js +1 -69
- package/dist/lib/openclaw-keychain.js +2 -7
- package/dist/lib/operator.d.ts +0 -9
- package/dist/lib/operator.js +0 -34
- package/dist/lib/origin-machine.d.ts +0 -16
- package/dist/lib/origin-machine.js +0 -16
- package/dist/lib/output/git-output.d.ts +0 -31
- package/dist/lib/output/git-output.js +4 -43
- package/dist/lib/overdue.d.ts +0 -35
- package/dist/lib/overdue.js +0 -60
- package/dist/lib/owner-message.d.ts +0 -20
- package/dist/lib/owner-message.js +1 -40
- package/dist/lib/packages/output-home.d.ts +0 -28
- package/dist/lib/packages/output-home.js +0 -114
- package/dist/lib/paths.d.ts +0 -26
- package/dist/lib/paths.js +1 -27
- package/dist/lib/percentile.d.ts +0 -11
- package/dist/lib/percentile.js +0 -11
- package/dist/lib/perf/db.d.ts +0 -16
- package/dist/lib/perf/db.js +3 -32
- package/dist/lib/perf/spool.d.ts +0 -12
- package/dist/lib/perf/spool.js +0 -13
- package/dist/lib/perf/types.d.ts +0 -26
- package/dist/lib/perf/types.js +0 -1
- package/dist/lib/permissions-registry.d.ts +0 -48
- package/dist/lib/permissions-registry.js +0 -108
- package/dist/lib/permissions.d.ts +0 -215
- package/dist/lib/permissions.js +4 -407
- package/dist/lib/picker.d.ts +0 -124
- package/dist/lib/picker.js +0 -140
- package/dist/lib/placement.d.ts +0 -57
- package/dist/lib/placement.js +0 -47
- package/dist/lib/platform/exec.d.ts +0 -73
- package/dist/lib/platform/exec.js +0 -80
- package/dist/lib/platform/index.d.ts +0 -14
- package/dist/lib/platform/index.js +0 -14
- package/dist/lib/platform/ipc.d.ts +0 -10
- package/dist/lib/platform/ipc.js +0 -13
- package/dist/lib/platform/links.d.ts +0 -14
- package/dist/lib/platform/links.js +0 -25
- package/dist/lib/platform/paths.d.ts +0 -48
- package/dist/lib/platform/paths.js +0 -52
- package/dist/lib/platform/posixpath.d.ts +0 -18
- package/dist/lib/platform/posixpath.js +0 -62
- package/dist/lib/platform/process.d.ts +0 -67
- package/dist/lib/platform/process.js +5 -99
- package/dist/lib/platform/winpath.d.ts +0 -50
- package/dist/lib/platform/winpath.js +0 -81
- package/dist/lib/plugins/plugin-marketplace.d.ts +0 -165
- package/dist/lib/plugins/plugin-marketplace.js +10 -238
- package/dist/lib/plugins/plugins.d.ts +0 -238
- package/dist/lib/plugins/plugins.js +20 -454
- package/dist/lib/plugins/skills.d.ts +0 -86
- package/dist/lib/plugins/skills.js +1 -139
- package/dist/lib/pr-land-detach.d.ts +0 -17
- package/dist/lib/pr-land-detach.js +0 -30
- package/dist/lib/pricing/cost.d.ts +0 -29
- package/dist/lib/pricing/cost.js +0 -38
- package/dist/lib/pricing/index.d.ts +0 -6
- package/dist/lib/pricing/index.js +0 -6
- package/dist/lib/pricing/table.d.ts +0 -8
- package/dist/lib/pricing/table.js +3 -56
- package/dist/lib/probe.d.ts +0 -5
- package/dist/lib/probe.js +0 -40
- package/dist/lib/profiles-presets.d.ts +0 -20
- package/dist/lib/profiles-presets.js +1 -36
- package/dist/lib/profiles.d.ts +0 -185
- package/dist/lib/profiles.js +1 -158
- package/dist/lib/project-doctor.d.ts +0 -29
- package/dist/lib/project-doctor.js +0 -26
- package/dist/lib/project-focus.d.ts +0 -41
- package/dist/lib/project-focus.js +0 -49
- package/dist/lib/project-import.d.ts +0 -41
- package/dist/lib/project-import.js +0 -36
- package/dist/lib/project-key.d.ts +0 -65
- package/dist/lib/project-key.js +0 -70
- package/dist/lib/project-launch.d.ts +0 -49
- package/dist/lib/project-launch.js +2 -91
- package/dist/lib/project-probe.d.ts +0 -86
- package/dist/lib/project-probe.js +1 -88
- package/dist/lib/project-pull.d.ts +0 -96
- package/dist/lib/project-pull.js +0 -122
- package/dist/lib/project-resources.d.ts +0 -44
- package/dist/lib/project-resources.js +7 -171
- package/dist/lib/project-root.d.ts +0 -95
- package/dist/lib/project-root.js +0 -101
- package/dist/lib/project-schedule.d.ts +0 -43
- package/dist/lib/project-schedule.js +0 -52
- package/dist/lib/project-status.d.ts +0 -108
- package/dist/lib/project-status.js +0 -117
- package/dist/lib/projects.d.ts +0 -197
- package/dist/lib/projects.js +1 -213
- package/dist/lib/pwsh.d.ts +0 -41
- package/dist/lib/pwsh.js +0 -51
- package/dist/lib/quick-todo.d.ts +84 -0
- package/dist/lib/quick-todo.js +243 -0
- package/dist/lib/reconcile-and-repair.d.ts +0 -78
- package/dist/lib/reconcile-and-repair.js +3 -66
- package/dist/lib/redact.d.ts +0 -29
- package/dist/lib/redact.js +0 -54
- package/dist/lib/refresh-coordinator.d.ts +0 -15
- package/dist/lib/refresh-coordinator.js +0 -10
- package/dist/lib/refresh.d.ts +0 -39
- package/dist/lib/refresh.js +0 -45
- package/dist/lib/registry.d.ts +0 -65
- package/dist/lib/registry.js +0 -127
- package/dist/lib/reminders.d.ts +0 -21
- package/dist/lib/reminders.js +0 -30
- package/dist/lib/remote-agents-json.d.ts +0 -44
- package/dist/lib/remote-agents-json.js +1 -44
- package/dist/lib/reserved-stores.d.ts +0 -28
- package/dist/lib/reserved-stores.js +2 -28
- package/dist/lib/resource-aliases.d.ts +0 -12
- package/dist/lib/resource-aliases.js +0 -12
- package/dist/lib/resource-content-diff.d.ts +0 -28
- package/dist/lib/resource-content-diff.js +7 -34
- package/dist/lib/resource-inventory.d.ts +0 -51
- package/dist/lib/resource-inventory.js +0 -45
- package/dist/lib/resource-patterns.d.ts +0 -32
- package/dist/lib/resource-patterns.js +0 -34
- package/dist/lib/resource-profiles.d.ts +0 -7
- package/dist/lib/resource-profiles.js +0 -11
- package/dist/lib/resources/types.d.ts +0 -50
- package/dist/lib/resources/types.js +0 -7
- package/dist/lib/resources/workflows.d.ts +0 -7
- package/dist/lib/resources/workflows.js +0 -13
- package/dist/lib/resources.d.ts +0 -65
- package/dist/lib/resources.js +0 -78
- package/dist/lib/routers.d.ts +0 -67
- package/dist/lib/routers.js +0 -60
- package/dist/lib/routine-activation.d.ts +0 -23
- package/dist/lib/routine-activation.js +0 -24
- package/dist/lib/routine-context.d.ts +1 -91
- package/dist/lib/routine-context.js +0 -107
- package/dist/lib/routine-notify-owner.d.ts +0 -79
- package/dist/lib/routine-notify-owner.js +1 -103
- package/dist/lib/routine-notify.d.ts +0 -70
- package/dist/lib/routine-notify.js +1 -75
- package/dist/lib/routine-process-cleanup.d.ts +0 -1
- package/dist/lib/routine-process-cleanup.js +1 -4
- package/dist/lib/routine-readiness.d.ts +0 -63
- package/dist/lib/routine-readiness.js +4 -86
- package/dist/lib/routines-format.d.ts +0 -40
- package/dist/lib/routines-format.js +0 -67
- package/dist/lib/routines-placement.d.ts +0 -33
- package/dist/lib/routines-placement.js +0 -40
- package/dist/lib/routines-project.d.ts +0 -57
- package/dist/lib/routines-project.js +6 -80
- package/dist/lib/rules/compile.d.ts +0 -66
- package/dist/lib/rules/compile.js +6 -118
- package/dist/lib/rules/compose.d.ts +0 -56
- package/dist/lib/rules/compose.js +2 -83
- package/dist/lib/rules/rules.d.ts +0 -23
- package/dist/lib/rules/rules.js +0 -35
- package/dist/lib/rules/run-sync.d.ts +0 -16
- package/dist/lib/rules/run-sync.js +1 -44
- package/dist/lib/run-config.d.ts +0 -7
- package/dist/lib/run-config.js +0 -8
- package/dist/lib/run-defaults.d.ts +0 -14
- package/dist/lib/run-defaults.js +0 -14
- package/dist/lib/run-notify.d.ts +0 -18
- package/dist/lib/run-notify.js +0 -36
- package/dist/lib/run-trace-sync.d.ts +0 -21
- package/dist/lib/run-trace-sync.js +2 -66
- package/dist/lib/rush-session.d.ts +0 -26
- package/dist/lib/rush-session.js +0 -28
- package/dist/lib/sandbox.d.ts +0 -45
- package/dist/lib/sandbox.js +5 -93
- package/dist/lib/scheduler.d.ts +0 -28
- package/dist/lib/scheduler.js +0 -45
- package/dist/lib/scheduling/routines.d.ts +0 -652
- package/dist/lib/scheduling/routines.js +8 -475
- package/dist/lib/secrets-cli.d.ts +3 -8
- package/dist/lib/secrets-cli.js +1 -19
- package/dist/lib/secrets-client.d.ts +0 -117
- package/dist/lib/secrets-client.js +4 -275
- package/dist/lib/secrets-policy.d.ts +0 -142
- package/dist/lib/secrets-policy.js +4 -237
- package/dist/lib/secrets-types.d.ts +0 -87
- package/dist/lib/secrets-types.js +0 -13
- package/dist/lib/self-heal/checks/hook-manifest.js +0 -25
- package/dist/lib/self-heal/checks/hook-runtime.js +0 -5
- package/dist/lib/self-heal/checks/install-staging.d.ts +0 -1
- package/dist/lib/self-heal/checks/install-staging.js +1 -28
- package/dist/lib/self-heal/checks/menubar-helper.js +0 -5
- package/dist/lib/self-heal/checks/path.js +0 -9
- package/dist/lib/self-heal/checks/resources.js +0 -5
- package/dist/lib/self-heal/checks/shadowing.js +2 -16
- package/dist/lib/self-heal/checks/shims.js +0 -14
- package/dist/lib/self-heal/child.d.ts +12 -0
- package/dist/lib/self-heal/child.js +23 -0
- package/dist/lib/self-heal/registry.d.ts +0 -9
- package/dist/lib/self-heal/registry.js +0 -16
- package/dist/lib/self-heal/types.d.ts +0 -12
- package/dist/lib/self-heal/types.js +0 -15
- package/dist/lib/self-update.d.ts +0 -312
- package/dist/lib/self-update.js +9 -360
- package/dist/lib/service-manifest.d.ts +0 -39
- package/dist/lib/service-manifest.js +0 -72
- package/dist/lib/session/active.d.ts +13 -869
- package/dist/lib/session/active.js +17 -976
- package/dist/lib/session/actor-sidecar.d.ts +1 -32
- package/dist/lib/session/actor-sidecar.js +1 -41
- package/dist/lib/session/bookmarks.d.ts +0 -33
- package/dist/lib/session/bookmarks.js +0 -37
- package/dist/lib/session/bundle.d.ts +0 -86
- package/dist/lib/session/bundle.js +0 -54
- package/dist/lib/session/claude-accounts.d.ts +0 -40
- package/dist/lib/session/claude-accounts.js +0 -49
- package/dist/lib/session/cloud.d.ts +1 -23
- package/dist/lib/session/cloud.js +0 -26
- package/dist/lib/session/db.d.ts +5 -658
- package/dist/lib/session/db.js +22 -1443
- package/dist/lib/session/detached.d.ts +0 -21
- package/dist/lib/session/detached.js +0 -29
- package/dist/lib/session/discover.d.ts +3 -420
- package/dist/lib/session/discover.js +30 -1123
- package/dist/lib/session/fork.d.ts +1 -41
- package/dist/lib/session/fork.js +0 -19
- package/dist/lib/session/ghostty-tabs.d.ts +0 -24
- package/dist/lib/session/ghostty-tabs.js +0 -37
- package/dist/lib/session/glance-files.d.ts +19 -0
- package/dist/lib/session/glance-files.js +258 -0
- package/dist/lib/session/hook-sessions.d.ts +0 -33
- package/dist/lib/session/hook-sessions.js +4 -99
- package/dist/lib/session/host-link.d.ts +1 -94
- package/dist/lib/session/host-link.js +1 -81
- package/dist/lib/session/inject-target.d.ts +8 -0
- package/dist/lib/session/inject-target.js +32 -0
- package/dist/lib/session/inject.d.ts +0 -15
- package/dist/lib/session/inject.js +0 -15
- package/dist/lib/session/live-metadata.d.ts +1 -34
- package/dist/lib/session/live-metadata.js +1 -73
- package/dist/lib/session/local-tmux-attach.d.ts +0 -37
- package/dist/lib/session/local-tmux-attach.js +1 -76
- package/dist/lib/session/migrate-targets.d.ts +0 -46
- package/dist/lib/session/migrate-targets.js +0 -24
- package/dist/lib/session/migrations.d.ts +0 -12
- package/dist/lib/session/migrations.js +1 -20
- package/dist/lib/session/mirror.d.ts +0 -32
- package/dist/lib/session/mirror.js +0 -77
- package/dist/lib/session/origin-machine.js +0 -3
- package/dist/lib/session/pid-registry.d.ts +0 -80
- package/dist/lib/session/pid-registry.js +2 -81
- package/dist/lib/session/presence.d.ts +0 -39
- package/dist/lib/session/presence.js +2 -52
- package/dist/lib/session/process-view.d.ts +0 -11
- package/dist/lib/session/process-view.js +0 -25
- package/dist/lib/session/projection.d.ts +0 -3
- package/dist/lib/session/projection.js +0 -10
- package/dist/lib/session/provenance.d.ts +1 -37
- package/dist/lib/session/provenance.js +0 -69
- package/dist/lib/session/recovery.d.ts +1 -40
- package/dist/lib/session/recovery.js +2 -80
- package/dist/lib/session/relative-time.js +0 -3
- package/dist/lib/session/remote/peer-stream.d.ts +0 -42
- package/dist/lib/session/remote/peer-stream.js +0 -64
- package/dist/lib/session/remote/remote-bundle.d.ts +0 -6
- package/dist/lib/session/remote/remote-bundle.js +0 -21
- package/dist/lib/session/remote/remote-list.d.ts +1 -110
- package/dist/lib/session/remote/remote-list.js +5 -139
- package/dist/lib/session/remote/remote.d.ts +0 -98
- package/dist/lib/session/remote/remote.js +3 -119
- package/dist/lib/session/remote/watch.d.ts +1 -34
- package/dist/lib/session/remote/watch.js +12 -72
- package/dist/lib/session/remote-active.d.ts +0 -35
- package/dist/lib/session/remote-active.js +0 -48
- package/dist/lib/session/remote-bundle.js +0 -2
- package/dist/lib/session/remote-list.js +0 -2
- package/dist/lib/session/remote-preview-cache.d.ts +0 -7
- package/dist/lib/session/remote-preview-cache.js +1 -76
- package/dist/lib/session/remote.js +0 -2
- package/dist/lib/session/resume-command.d.ts +0 -6
- package/dist/lib/session/resume-command.js +0 -6
- package/dist/lib/session/resume-owner.d.ts +1 -52
- package/dist/lib/session/resume-owner.js +0 -56
- package/dist/lib/session/run-names.d.ts +0 -24
- package/dist/lib/session/run-names.js +0 -26
- package/dist/lib/session/session-cache.d.ts +2 -146
- package/dist/lib/session/session-cache.js +26 -203
- package/dist/lib/session/short-id.js +0 -3
- package/dist/lib/session/sync/agents.d.ts +0 -68
- package/dist/lib/session/sync/agents.js +0 -0
- package/dist/lib/session/sync/backend.d.ts +0 -39
- package/dist/lib/session/sync/backend.js +0 -45
- package/dist/lib/session/sync/config.d.ts +0 -27
- package/dist/lib/session/sync/config.js +0 -56
- package/dist/lib/session/sync/managed-config.d.ts +0 -17
- package/dist/lib/session/sync/managed-config.js +0 -17
- package/dist/lib/session/sync/managed-key.d.ts +0 -38
- package/dist/lib/session/sync/managed-key.js +1 -42
- package/dist/lib/session/sync/net-client.d.ts +0 -36
- package/dist/lib/session/sync/net-client.js +0 -30
- package/dist/lib/session/sync/provision.d.ts +0 -2
- package/dist/lib/session/sync/provision.js +0 -13
- package/dist/lib/session/sync/r2.d.ts +0 -20
- package/dist/lib/session/sync/r2.js +0 -20
- package/dist/lib/session/sync/transcript-crypto.d.ts +0 -37
- package/dist/lib/session/sync/transcript-crypto.js +5 -37
- package/dist/lib/session/sync/worker-template.d.ts +0 -5
- package/dist/lib/session/sync/worker-template.js +0 -40
- package/dist/lib/session/testdata/pid-registry-namespace.js +0 -2
- package/dist/lib/session/testdata/process-view-private.js +0 -1
- package/dist/lib/session/throughput.d.ts +0 -24
- package/dist/lib/session/throughput.js +1 -25
- package/dist/lib/session/timeline-pass.d.ts +1 -63
- package/dist/lib/session/timeline-pass.js +161 -148
- package/dist/lib/session/title.d.ts +0 -182
- package/dist/lib/session/title.js +0 -139
- package/dist/lib/session/tool-index.d.ts +2 -21
- package/dist/lib/session/tool-index.js +3 -75
- package/dist/lib/session/tool-store.d.ts +2 -48
- package/dist/lib/session/tool-store.js +1 -33
- package/dist/lib/session/viewing-in.d.ts +0 -67
- package/dist/lib/session/viewing-in.js +1 -71
- package/dist/lib/session/watch.js +0 -2
- package/dist/lib/session/width.js +0 -3
- package/dist/lib/sessions-client.d.ts +0 -46
- package/dist/lib/sessions-client.js +36 -113
- package/dist/lib/settings-manifest.d.ts +1 -17
- package/dist/lib/settings-manifest.js +4 -57
- package/dist/lib/setup-tool-install.d.ts +0 -1
- package/dist/lib/setup-tool-install.js +2 -7
- package/dist/lib/setup-tool-status.d.ts +0 -3
- package/dist/lib/setup-tool-status.js +4 -14
- package/dist/lib/sha256-asset.d.ts +0 -20
- package/dist/lib/sha256-asset.js +0 -20
- package/dist/lib/share-runtime.d.ts +0 -10
- package/dist/lib/share-runtime.js +1 -27
- package/dist/lib/shim-heal.d.ts +0 -15
- package/dist/lib/shim-heal.js +3 -29
- package/dist/lib/signin-badge.d.ts +0 -72
- package/dist/lib/signin-badge.js +0 -86
- package/dist/lib/sink-format.d.ts +0 -32
- package/dist/lib/sink-format.js +0 -14
- package/dist/lib/smart-launch.d.ts +0 -75
- package/dist/lib/smart-launch.js +1 -83
- package/dist/lib/snapshot.d.ts +0 -46
- package/dist/lib/snapshot.js +5 -31
- package/dist/lib/spinner.d.ts +0 -37
- package/dist/lib/spinner.js +0 -37
- package/dist/lib/sqlite.d.ts +0 -13
- package/dist/lib/sqlite.js +3 -71
- package/dist/lib/ssh-exec.d.ts +0 -209
- package/dist/lib/ssh-exec.js +11 -248
- package/dist/lib/ssh-tunnel.d.ts +0 -43
- package/dist/lib/ssh-tunnel.js +2 -48
- package/dist/lib/staleness/checkers/commands.d.ts +0 -4
- package/dist/lib/staleness/checkers/commands.js +0 -4
- package/dist/lib/staleness/checkers/hooks.d.ts +0 -10
- package/dist/lib/staleness/checkers/hooks.js +0 -15
- package/dist/lib/staleness/checkers/mcp.d.ts +0 -9
- package/dist/lib/staleness/checkers/mcp.js +0 -9
- package/dist/lib/staleness/checkers/permissions.d.ts +0 -12
- package/dist/lib/staleness/checkers/permissions.js +0 -13
- package/dist/lib/staleness/checkers/plugins.d.ts +0 -8
- package/dist/lib/staleness/checkers/plugins.js +0 -8
- package/dist/lib/staleness/checkers/rules.d.ts +0 -15
- package/dist/lib/staleness/checkers/rules.js +0 -26
- package/dist/lib/staleness/checkers/skills.d.ts +0 -4
- package/dist/lib/staleness/checkers/skills.js +0 -4
- package/dist/lib/staleness/checkers/subagents.d.ts +0 -9
- package/dist/lib/staleness/checkers/subagents.js +0 -9
- package/dist/lib/staleness/checkers/types.d.ts +0 -34
- package/dist/lib/staleness/checkers/types.js +0 -19
- package/dist/lib/staleness/checkers/workflows.d.ts +0 -7
- package/dist/lib/staleness/checkers/workflows.js +0 -7
- package/dist/lib/staleness/detectors/commands.js +0 -17
- package/dist/lib/staleness/detectors/hooks.js +1 -6
- package/dist/lib/staleness/detectors/mcp.js +0 -4
- package/dist/lib/staleness/detectors/permissions.js +10 -23
- package/dist/lib/staleness/detectors/rules.js +0 -6
- package/dist/lib/staleness/detectors/skills.d.ts +0 -1
- package/dist/lib/staleness/detectors/skills.js +1 -12
- package/dist/lib/staleness/detectors/types.d.ts +0 -10
- package/dist/lib/staleness/fingerprint.d.ts +0 -19
- package/dist/lib/staleness/fingerprint.js +0 -35
- package/dist/lib/staleness/index.d.ts +0 -28
- package/dist/lib/staleness/index.js +1 -45
- package/dist/lib/staleness/layers.d.ts +0 -23
- package/dist/lib/staleness/layers.js +0 -34
- package/dist/lib/staleness/prune.d.ts +0 -71
- package/dist/lib/staleness/prune.js +2 -27
- package/dist/lib/staleness/registry.d.ts +0 -10
- package/dist/lib/staleness/registry.js +3 -33
- package/dist/lib/staleness/types.d.ts +0 -36
- package/dist/lib/staleness/types.js +0 -5
- package/dist/lib/staleness/writers/commands.js +1 -48
- package/dist/lib/staleness/writers/hooks.js +1 -18
- package/dist/lib/staleness/writers/kinds.d.ts +0 -12
- package/dist/lib/staleness/writers/kinds.js +0 -1
- package/dist/lib/staleness/writers/lazy-map.d.ts +0 -11
- package/dist/lib/staleness/writers/mcp.js +0 -13
- package/dist/lib/staleness/writers/plugins.js +0 -4
- package/dist/lib/staleness/writers/rules.d.ts +0 -1
- package/dist/lib/staleness/writers/rules.js +1 -19
- package/dist/lib/staleness/writers/skills.js +3 -14
- package/dist/lib/staleness/writers/sources.d.ts +0 -11
- package/dist/lib/staleness/writers/sources.js +0 -32
- package/dist/lib/staleness/writers/subagents.js +0 -16
- package/dist/lib/staleness/writers/types.d.ts +0 -58
- package/dist/lib/staleness/writers/workflows.js +1 -1
- package/dist/lib/standalone-tools.d.ts +24 -0
- package/dist/lib/standalone-tools.js +58 -0
- package/dist/lib/star-nudge.d.ts +0 -32
- package/dist/lib/star-nudge.js +2 -36
- package/dist/lib/startup/command-registry.d.ts +0 -45
- package/dist/lib/startup/command-registry.js +1 -46
- package/dist/lib/startup/dev-build.d.ts +0 -9
- package/dist/lib/startup/dev-build.js +1 -42
- package/dist/lib/startup/root-command.d.ts +0 -19
- package/dist/lib/startup/root-command.js +0 -19
- package/dist/lib/startup/spellcheck.d.ts +0 -13
- package/dist/lib/startup/spellcheck.js +0 -13
- package/dist/lib/state.d.ts +0 -376
- package/dist/lib/state.js +19 -737
- package/dist/lib/storage/selection.d.ts +0 -40
- package/dist/lib/storage/selection.js +0 -29
- package/dist/lib/subagents-registry.d.ts +0 -56
- package/dist/lib/subagents-registry.js +0 -99
- package/dist/lib/subagents.d.ts +0 -128
- package/dist/lib/subagents.js +1 -158
- package/dist/lib/summarizer/config.d.ts +0 -37
- package/dist/lib/summarizer/config.js +0 -37
- package/dist/lib/summarizer/pass.d.ts +0 -20
- package/dist/lib/summarizer/pass.js +1 -34
- package/dist/lib/summarizer/summarize.d.ts +1 -49
- package/dist/lib/summarizer/summarize.js +0 -35
- package/dist/lib/sync-passphrase.d.ts +0 -16
- package/dist/lib/sync-passphrase.js +1 -18
- package/dist/lib/sync-status.d.ts +0 -72
- package/dist/lib/sync-status.js +0 -55
- package/dist/lib/sync-umbrella.d.ts +0 -54
- package/dist/lib/sync-umbrella.js +0 -48
- package/dist/lib/system-run-sync.d.ts +0 -5
- package/dist/lib/system-run-sync.js +1 -31
- package/dist/lib/teams/agents.d.ts +1 -444
- package/dist/lib/teams/agents.js +11 -871
- package/dist/lib/teams/api.d.ts +2 -52
- package/dist/lib/teams/api.js +4 -63
- package/dist/lib/teams/debug.d.ts +0 -6
- package/dist/lib/teams/debug.js +0 -6
- package/dist/lib/teams/delivery.d.ts +0 -41
- package/dist/lib/teams/delivery.js +0 -32
- package/dist/lib/teams/file_ops.d.ts +0 -11
- package/dist/lib/teams/file_ops.js +0 -11
- package/dist/lib/teams/forEach.d.ts +0 -76
- package/dist/lib/teams/forEach.js +1 -77
- package/dist/lib/teams/index.d.ts +1 -7
- package/dist/lib/teams/index.js +1 -7
- package/dist/lib/teams/parsers.d.ts +0 -4
- package/dist/lib/teams/parsers.js +0 -81
- package/dist/lib/teams/persistence.d.ts +0 -2
- package/dist/lib/teams/persistence.js +0 -12
- package/dist/lib/teams/placement-probe.d.ts +0 -12
- package/dist/lib/teams/placement-probe.js +1 -51
- package/dist/lib/teams/pr-watch.d.ts +0 -107
- package/dist/lib/teams/pr-watch.js +1 -107
- package/dist/lib/teams/registry.d.ts +0 -36
- package/dist/lib/teams/registry.js +0 -47
- package/dist/lib/teams/remoteWorktree.d.ts +0 -50
- package/dist/lib/teams/remoteWorktree.js +1 -96
- package/dist/lib/teams/scheduler.d.ts +0 -111
- package/dist/lib/teams/scheduler.js +2 -125
- package/dist/lib/teams/summarizer.d.ts +0 -22
- package/dist/lib/teams/summarizer.js +6 -50
- package/dist/lib/teams/supervisor.d.ts +0 -38
- package/dist/lib/teams/supervisor.js +0 -21
- package/dist/lib/teams/worktree.d.ts +0 -95
- package/dist/lib/teams/worktree.js +3 -110
- package/dist/lib/template.d.ts +0 -21
- package/dist/lib/template.js +0 -23
- package/dist/lib/term-client.d.ts +0 -26
- package/dist/lib/term-client.js +0 -48
- package/dist/lib/term-driver.d.ts +0 -5
- package/dist/lib/term-driver.js +0 -8
- package/dist/lib/terminal/backends/ghostty.d.ts +0 -2
- package/dist/lib/terminal/backends/ghostty.js +0 -12
- package/dist/lib/terminal/backends/index.d.ts +0 -17
- package/dist/lib/terminal/backends/index.js +0 -15
- package/dist/lib/terminal/backends/iterm.d.ts +0 -2
- package/dist/lib/terminal/backends/iterm.js +0 -10
- package/dist/lib/terminal/backends/terminal-app.d.ts +0 -10
- package/dist/lib/terminal/backends/terminal-app.js +0 -30
- package/dist/lib/terminal/backends/tmux.d.ts +0 -10
- package/dist/lib/terminal/backends/tmux.js +0 -2
- package/dist/lib/terminal/backends/vscodium-agent.d.ts +0 -25
- package/dist/lib/terminal/backends/vscodium-agent.js +0 -36
- package/dist/lib/terminal/engine.d.ts +0 -17
- package/dist/lib/terminal/engine.js +0 -10
- package/dist/lib/terminal/index.d.ts +0 -8
- package/dist/lib/terminal/inject.d.ts +1 -173
- package/dist/lib/terminal/inject.js +0 -146
- package/dist/lib/terminal/policy.d.ts +0 -8
- package/dist/lib/terminal/policy.js +0 -1
- package/dist/lib/terminal/preferred.d.ts +0 -66
- package/dist/lib/terminal/preferred.js +0 -30
- package/dist/lib/terminal/quote.d.ts +0 -8
- package/dist/lib/terminal/quote.js +0 -8
- package/dist/lib/terminal/resolve.d.ts +0 -46
- package/dist/lib/terminal/resolve.js +0 -30
- package/dist/lib/terminal/run-surface.d.ts +0 -56
- package/dist/lib/terminal/run-surface.js +0 -46
- package/dist/lib/terminal/shell.d.ts +0 -3
- package/dist/lib/terminal/shell.js +0 -13
- package/dist/lib/terminal/transport.d.ts +0 -14
- package/dist/lib/terminal/transport.js +1 -30
- package/dist/lib/terminal/types.d.ts +0 -40
- package/dist/lib/terminal/types.js +0 -10
- package/dist/lib/text/relative-time.d.ts +0 -36
- package/dist/lib/text/relative-time.js +0 -43
- package/dist/lib/text/short-id.d.ts +0 -16
- package/dist/lib/text/short-id.js +0 -16
- package/dist/lib/text/width.d.ts +0 -24
- package/dist/lib/text/width.js +10 -52
- package/dist/lib/tmux/binary.d.ts +0 -36
- package/dist/lib/tmux/binary.js +0 -28
- package/dist/lib/tmux/index.d.ts +0 -5
- package/dist/lib/tmux/index.js +0 -5
- package/dist/lib/tmux/orphan-reap.d.ts +0 -253
- package/dist/lib/tmux/orphan-reap.js +3 -299
- package/dist/lib/tmux/paths.d.ts +0 -14
- package/dist/lib/tmux/paths.js +0 -14
- package/dist/lib/tmux/session.d.ts +0 -263
- package/dist/lib/tmux/session.js +6 -344
- package/dist/lib/traces/backend.d.ts +0 -20
- package/dist/lib/traces/backend.js +0 -23
- package/dist/lib/traces/classify.d.ts +0 -20
- package/dist/lib/traces/classify.js +0 -7
- package/dist/lib/traces/insights.d.ts +1 -84
- package/dist/lib/traces/insights.js +0 -126
- package/dist/lib/traces/phenotype.d.ts +0 -69
- package/dist/lib/traces/phenotype.js +0 -150
- package/dist/lib/traces/provision.d.ts +0 -1
- package/dist/lib/traces/provision.js +0 -5
- package/dist/lib/traces/schema2-build.d.ts +3 -60
- package/dist/lib/traces/schema2-build.js +8 -149
- package/dist/lib/traces/schema2-danger.d.ts +0 -30
- package/dist/lib/traces/schema2-danger.js +1 -63
- package/dist/lib/traces/schema2.d.ts +0 -11
- package/dist/lib/traces/segments.d.ts +0 -44
- package/dist/lib/traces/segments.js +0 -65
- package/dist/lib/traces/sync.d.ts +2 -239
- package/dist/lib/traces/sync.js +15 -237
- package/dist/lib/traces/worker-template.d.ts +0 -3
- package/dist/lib/traces/worker-template.js +0 -29
- package/dist/lib/triggers/handlers.d.ts +0 -82
- package/dist/lib/triggers/handlers.js +0 -75
- package/dist/lib/triggers/webhook.d.ts +0 -158
- package/dist/lib/triggers/webhook.js +0 -143
- package/dist/lib/types.d.ts +0 -736
- package/dist/lib/types.js +0 -50
- package/dist/lib/uninstall.d.ts +0 -17
- package/dist/lib/uninstall.js +0 -75
- package/dist/lib/usage-backoff.d.ts +0 -20
- package/dist/lib/usage-backoff.js +3 -60
- package/dist/lib/usage-refresh.d.ts +0 -154
- package/dist/lib/usage-refresh.js +8 -212
- package/dist/lib/verbs.d.ts +0 -12
- package/dist/lib/verbs.js +0 -12
- package/dist/lib/version-duplicates.d.ts +0 -6
- package/dist/lib/version-duplicates.js +0 -6
- package/dist/lib/version.d.ts +0 -55
- package/dist/lib/version.js +1 -58
- package/dist/lib/view-types.d.ts +0 -48
- package/dist/lib/warn-unpushed.d.ts +0 -27
- package/dist/lib/warn-unpushed.js +1 -45
- package/dist/lib/watchdog/history.d.ts +0 -1
- package/dist/lib/watchdog/history.js +0 -3
- package/dist/lib/watchdog/index.js +0 -2
- package/dist/lib/watchdog/log.d.ts +0 -19
- package/dist/lib/watchdog/log.js +0 -34
- package/dist/lib/watchdog/read.d.ts +0 -27
- package/dist/lib/watchdog/read.js +3 -58
- package/dist/lib/watchdog/rotate.d.ts +0 -139
- package/dist/lib/watchdog/rotate.js +2 -151
- package/dist/lib/watchdog/runner.d.ts +0 -125
- package/dist/lib/watchdog/runner.js +3 -223
- package/dist/lib/watchdog/service.d.ts +0 -1
- package/dist/lib/watchdog/service.js +0 -2
- package/dist/lib/watchdog/watchdog-agent.d.ts +0 -31
- package/dist/lib/watchdog/watchdog-agent.js +0 -27
- package/dist/lib/watchdog/watchdog.d.ts +0 -7
- package/dist/lib/watchdog/watchdog.js +0 -10
- package/dist/lib/watchdog/watchdogTail.js +0 -7
- package/dist/lib/whats-new.d.ts +0 -10
- package/dist/lib/whats-new.js +0 -21
- package/dist/lib/workflows-registry.d.ts +0 -45
- package/dist/lib/workflows-registry.js +0 -60
- package/dist/lib/workflows.d.ts +0 -282
- package/dist/lib/workflows.js +3 -213
- package/dist/lib/worktree/held.d.ts +0 -89
- package/dist/lib/worktree/held.js +1 -103
- package/dist/lib/wrap.d.ts +0 -32
- package/dist/lib/wrap.js +0 -32
- package/dist/lib/yaml-io.d.ts +0 -45
- package/dist/lib/yaml-io.js +0 -47
- package/dist/session-tracker/dist/adapters/claude.js +0 -2
- package/dist/session-tracker/dist/hook.sh +4 -37
- package/dist/session-tracker/dist/index.d.ts +0 -7
- package/dist/session-tracker/dist/index.js +0 -5
- package/dist/session-tracker/dist/install-hook.d.ts +0 -5
- package/dist/session-tracker/dist/install-hook.js +1 -15
- package/dist/session-tracker/dist/prune-state.js +0 -4
- package/dist/session-tracker/dist/reader.js +0 -4
- package/dist/session-tracker/dist/state-file.d.ts +0 -3
- package/dist/session-tracker/dist/state-file.js +3 -8
- package/package.json +3 -4
- package/scripts/postinstall.js +8 -116
- package/dist/lib/session/artifacts.d.ts +0 -14
- package/dist/lib/session/artifacts.js +0 -101
- package/dist/lib/session/bash-command.d.ts +0 -63
- package/dist/lib/session/bash-command.js +0 -490
- package/dist/lib/session/digest.d.ts +0 -66
- package/dist/lib/session/digest.js +0 -328
- package/dist/lib/session/highlights.d.ts +0 -151
- package/dist/lib/session/highlights.js +0 -391
- package/dist/lib/session/insights.d.ts +0 -165
- package/dist/lib/session/insights.js +0 -563
- package/dist/lib/session/linear.d.ts +0 -19
- package/dist/lib/session/linear.js +0 -99
- package/dist/lib/session/parse.d.ts +0 -224
- package/dist/lib/session/parse.js +0 -2408
- package/dist/lib/session/prompt.d.ts +0 -213
- package/dist/lib/session/prompt.js +0 -601
- package/dist/lib/session/render.d.ts +0 -133
- package/dist/lib/session/render.js +0 -1047
- package/dist/lib/session/share-html.d.ts +0 -35
- package/dist/lib/session/share-html.js +0 -319
- package/dist/lib/session/shell-programs.d.ts +0 -33
- package/dist/lib/session/shell-programs.js +0 -380
- package/dist/lib/session/state.d.ts +0 -269
- package/dist/lib/session/state.js +0 -777
- package/dist/lib/session/stream-render.d.ts +0 -4
- package/dist/lib/session/stream-render.js +0 -131
- package/dist/lib/session/tail.d.ts +0 -88
- package/dist/lib/session/tail.js +0 -294
- package/dist/lib/session/team-filter.d.ts +0 -129
- package/dist/lib/session/team-filter.js +0 -269
- package/dist/lib/session/timeline.d.ts +0 -172
- package/dist/lib/session/timeline.js +0 -636
- package/dist/lib/session/tool-calls.d.ts +0 -132
- package/dist/lib/session/tool-calls.js +0 -674
- package/dist/lib/session/trajectory-compare.d.ts +0 -48
- package/dist/lib/session/trajectory-compare.js +0 -116
- package/dist/lib/session/trajectory-html.d.ts +0 -19
- package/dist/lib/session/trajectory-html.js +0 -771
- package/dist/lib/session/trajectory-lineage.d.ts +0 -93
- package/dist/lib/session/trajectory-lineage.js +0 -195
- package/dist/lib/session/trajectory-text.d.ts +0 -38
- package/dist/lib/session/trajectory-text.js +0 -284
- package/dist/lib/session/trajectory.d.ts +0 -145
- package/dist/lib/session/trajectory.js +0 -394
- package/dist/lib/session/types.d.ts +0 -627
- package/dist/lib/session/types.js +0 -43
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Usage and rate-limit tracking for Claude, Codex, Kimi, Droid, Grok, Cursor,
|
|
3
|
-
* and Antigravity agents.
|
|
4
|
-
*
|
|
5
|
-
* Fetches live usage data from each agent's usage API (Anthropic OAuth for
|
|
6
|
-
* Claude, Kimi Code /usages, Factory billing limits for Droid, Google Code
|
|
7
|
-
* Assist :retrieveUserQuota for Antigravity) or parses rate-limit events from
|
|
8
|
-
* Codex session logs. Results are normalized into a common UsageSnapshot
|
|
9
|
-
* shape, cached to disk, and rendered as terminal progress bars for the
|
|
10
|
-
* `agents view` command.
|
|
11
|
-
*/
|
|
12
1
|
import { execFile } from 'child_process';
|
|
13
2
|
import { createHash } from 'crypto';
|
|
14
3
|
import * as fs from 'fs';
|
|
@@ -33,37 +22,12 @@ const CLAUDE_TOKEN_URL = 'https://platform.claude.com/v1/oauth/token';
|
|
|
33
22
|
const CLAUDE_CLIENT_ID = '9d1c250a-e61b-44d9-88ed-5944d1962f5e';
|
|
34
23
|
const CLAUDE_OAUTH_BETA_HEADER = 'oauth-2025-04-20';
|
|
35
24
|
const CLAUDE_REFRESH_LEEWAY_MS = 5 * 60 * 1000;
|
|
36
|
-
/**
|
|
37
|
-
* Why a usage read produced no snapshot, when the cause is the credential or the
|
|
38
|
-
* server rather than the payload. Every provider used to return `error: null`
|
|
39
|
-
* for all three, which made an account nobody can read indistinguishable from a
|
|
40
|
-
* healthy one: the caller fell back to whatever was in the SWR cache and
|
|
41
|
-
* rendered its bars as fact. On `yosemite-s1` that hid five Claude accounts
|
|
42
|
-
* whose stored access token had expired — one of them eleven days earlier —
|
|
43
|
-
* behind a cache frozen for 26h, and balanced routing launched into an account
|
|
44
|
-
* that was actually at its weekly cap.
|
|
45
|
-
*
|
|
46
|
-
* No usage read ever refreshes a token (RUSH-1822 for Claude; the same rule for
|
|
47
|
-
* Kimi/Droid/Cursor, whose own CLIs rotate on their next launch), so an expired
|
|
48
|
-
* credential cannot heal on its own — the account stays unreadable until that
|
|
49
|
-
* agent actually runs, or a long-lived token is provisioned for it.
|
|
50
|
-
*
|
|
51
|
-
* Shared across all four networked providers on purpose: the failure shape is
|
|
52
|
-
* identical, and wiring only Claude would leave `agents view --refresh`
|
|
53
|
-
* reporting Claude accounts while silently presenting stale Kimi, Droid, and
|
|
54
|
-
* Cursor readings as confirmed.
|
|
55
|
-
*/
|
|
56
25
|
export function usageNoCredentialError(agent) {
|
|
57
26
|
return `No readable ${agent} credential — sign in, or provision a long-lived token for this account.`;
|
|
58
27
|
}
|
|
59
28
|
export function usageExpiredCredentialError(agent) {
|
|
60
29
|
return `${agent} credential expired — re-auth this account (a usage read never refreshes it).`;
|
|
61
30
|
}
|
|
62
|
-
/**
|
|
63
|
-
* Kimi-specific expired-credential wording. A normal Kimi launch refreshes its
|
|
64
|
-
* own OAuth access token, so the recovery action for an expired Kimi credential
|
|
65
|
-
* is to run Kimi once — not to re-auth through agents-cli (RUSH-3198).
|
|
66
|
-
*/
|
|
67
31
|
export function usageExpiredKimiCredentialError() {
|
|
68
32
|
return `Kimi credential expired — run Kimi once to refresh it (a usage read never refreshes it).`;
|
|
69
33
|
}
|
|
@@ -72,31 +36,13 @@ export function usageRejectedError(agent, status) {
|
|
|
72
36
|
? `${agent} is rate-limiting the usage endpoint for this machine (HTTP 429).`
|
|
73
37
|
: `${agent} rejected the usage read (HTTP ${status}).`;
|
|
74
38
|
}
|
|
75
|
-
/**
|
|
76
|
-
* Canonical phrase for the Anthropic setup-token scope gap (RUSH-2392).
|
|
77
|
-
* `claude setup-token` mints `user:inference` only; the usage endpoint requires
|
|
78
|
-
* `user:profile`. The account can still run; usage bars cannot populate via
|
|
79
|
-
* that token. Callers detect this string with {@link isUsageHeadlessScopeError}
|
|
80
|
-
* so the UI can render it distinctly from a generic "unverified" failure.
|
|
81
|
-
*/
|
|
82
39
|
export const USAGE_HEADLESS_SCOPE_MARKER = 'usage unavailable (headless)';
|
|
83
|
-
/**
|
|
84
|
-
* Distinct error when Claude's usage API returns 403 because the setup-token
|
|
85
|
-
* lacks `user:profile` (RUSH-2392). Not a revocation, not a missing mint —
|
|
86
|
-
* a permanent tradeoff of the headless credential.
|
|
87
|
-
*/
|
|
88
40
|
export function usageHeadlessScopeError(agent = 'Claude') {
|
|
89
41
|
return `${agent} ${USAGE_HEADLESS_SCOPE_MARKER} — setup-token lacks user:profile; account can still run.`;
|
|
90
42
|
}
|
|
91
|
-
/** True when an error string is the setup-token scope gap (RUSH-2392). */
|
|
92
43
|
export function isUsageHeadlessScopeError(error) {
|
|
93
44
|
return typeof error === 'string' && error.includes(USAGE_HEADLESS_SCOPE_MARKER);
|
|
94
45
|
}
|
|
95
|
-
/**
|
|
96
|
-
* Canonical phrase for a Claude account the usage reader holds no usable
|
|
97
|
-
* credential for. Distinct from {@link USAGE_HEADLESS_SCOPE_MARKER}, which
|
|
98
|
-
* means a setup-token WAS read and the endpoint refused its scope.
|
|
99
|
-
*/
|
|
100
46
|
export const USAGE_NO_USAGE_CREDENTIAL_MARKER = 'usage unavailable (no usage credential)';
|
|
101
47
|
/**
|
|
102
48
|
* Claude's own no-credential message. The shared
|
|
@@ -113,16 +59,9 @@ export function usageNoClaudeUsageCredentialError() {
|
|
|
113
59
|
return (`Claude ${USAGE_NO_USAGE_CREDENTIAL_MARKER} — a usage read never uses your login ` +
|
|
114
60
|
'(RUSH-1822); a setup-token cannot read usage (RUSH-2392). The account still runs.');
|
|
115
61
|
}
|
|
116
|
-
/** True when an error string is the Claude no-usage-credential state (#2987). */
|
|
117
62
|
function isUsageNoUsageCredentialError(error) {
|
|
118
63
|
return typeof error === 'string' && error.includes(USAGE_NO_USAGE_CREDENTIAL_MARKER);
|
|
119
64
|
}
|
|
120
|
-
/**
|
|
121
|
-
* Detect Anthropic's usage-endpoint scope denial: HTTP 403 whose body names
|
|
122
|
-
* `user:profile` (or "scope requirement"). A bare 403 without that body stays
|
|
123
|
-
* classified as a real rejection — only the known setup-token shape is special
|
|
124
|
-
* (RUSH-2392).
|
|
125
|
-
*/
|
|
126
65
|
export function isClaudeUsageScopeDenied(status, bodyText) {
|
|
127
66
|
if (status !== 403)
|
|
128
67
|
return false;
|
|
@@ -131,18 +70,6 @@ export function isClaudeUsageScopeDenied(status, bodyText) {
|
|
|
131
70
|
const lower = bodyText.toLowerCase();
|
|
132
71
|
return lower.includes('user:profile') || lower.includes('scope requirement');
|
|
133
72
|
}
|
|
134
|
-
/**
|
|
135
|
-
* The read threw rather than answering — a timeout, DNS/TLS failure, a payload
|
|
136
|
-
* that would not parse, a credential that would not decrypt. Every provider
|
|
137
|
-
* swallowed these into `error: null`, which is the same silence as an expired
|
|
138
|
-
* token: the caller renders a stale snapshot as confirmed. The cause is carried
|
|
139
|
-
* verbatim because these are the failures a user cannot otherwise see.
|
|
140
|
-
*/
|
|
141
|
-
/**
|
|
142
|
-
* The provider told us to back off and we are still inside that window, so this
|
|
143
|
-
* read made no request at all. Distinct from `usageRejectedError(agent, 429)`,
|
|
144
|
-
* which is the 429 itself: this one says we are *honouring* it.
|
|
145
|
-
*/
|
|
146
73
|
export function usageThrottledError(agent, untilMs) {
|
|
147
74
|
return `${agent} rate-limited this machine — not retrying for ${formatBackoffRemaining(untilMs)}.`;
|
|
148
75
|
}
|
|
@@ -152,38 +79,9 @@ export function usageUnreachableError(agent, cause) {
|
|
|
152
79
|
? `${agent} usage read failed: ${detail}`
|
|
153
80
|
: `${agent} usage read failed.`;
|
|
154
81
|
}
|
|
155
|
-
/**
|
|
156
|
-
* Marker for a log-based (`network: false`) provider — Codex, Grok — that has
|
|
157
|
-
* simply never recorded a rate-limit event on this machine yet: no session
|
|
158
|
-
* log exists, or no session in it carries usage data. Distinct on purpose from
|
|
159
|
-
* `usageUnreachableError`: that one means the local log COULDN'T be read (a
|
|
160
|
-
* real failure worth surfacing distinctly); this one means there is nothing to
|
|
161
|
-
* read because the account has not run here, which is expected for a fresh
|
|
162
|
-
* install and should render as a benign state, not an error (RUSH-3040).
|
|
163
|
-
*/
|
|
164
82
|
const USAGE_NO_RECENT_USAGE_MARKER = 'no usage recorded yet';
|
|
165
83
|
export const USAGE_BENIGN_STATE = Symbol('usageBenignState');
|
|
166
|
-
/**
|
|
167
|
-
* Sentinel `UsageInfo.error` for a read-only lookup whose cache held nothing
|
|
168
|
-
* (`getUsageInfoForIdentity`). No request was made and nothing failed — the
|
|
169
|
-
* daemon simply has not collected this account yet. It was an unclassified
|
|
170
|
-
* literal, so `classifyUsageErrorKind` fell through to `'rejected'` and
|
|
171
|
-
* `agents view` printed the generic "usage unavailable" for a cold cache,
|
|
172
|
-
* which reads as a failure the operator should chase (#2987). The string value
|
|
173
|
-
* is unchanged; callers that already compare against `'stale'` keep working.
|
|
174
|
-
*/
|
|
175
84
|
export const USAGE_NOT_COLLECTED_MARKER = 'stale';
|
|
176
|
-
/**
|
|
177
|
-
* Human-facing form of a `UsageInfo.error` for a machine/JSON consumer
|
|
178
|
-
* (`agents view --json`'s `usageError`). Every error string this module
|
|
179
|
-
* constructs is already a full human sentence EXCEPT the internal
|
|
180
|
-
* {@link USAGE_NOT_COLLECTED_MARKER} (`'stale'`) sentinel, which a read-only
|
|
181
|
-
* lookup returns for a never-cached account when `--refresh` was not passed. That
|
|
182
|
-
* value is an internal cache signal, not an error message, and leaking it verbatim
|
|
183
|
-
* contradicts the field's "human-readable" contract (PHNX-3348). Map it to a
|
|
184
|
-
* plain-language, actionable string and pass every genuine error through
|
|
185
|
-
* unchanged. Returns `null` when there is no error.
|
|
186
|
-
*/
|
|
187
85
|
export function usageErrorForDisplay(error) {
|
|
188
86
|
if (!error)
|
|
189
87
|
return null;
|
|
@@ -192,18 +90,6 @@ export function usageErrorForDisplay(error) {
|
|
|
192
90
|
}
|
|
193
91
|
return error;
|
|
194
92
|
}
|
|
195
|
-
/**
|
|
196
|
-
* Shared error-classification + 429 backoff for a networked usage fetch whose
|
|
197
|
-
* only signal is an HTTP status (or none at all, on a network failure) —
|
|
198
|
-
* Antigravity's :retrieveUserQuota and Muse's Meta Model API probe are both
|
|
199
|
-
* this shape. They were added to `USAGE_SOURCES` after the four original
|
|
200
|
-
* `usageXError` constructors and did not get their own scheme (usage.ts's
|
|
201
|
-
* error handling was written for "four networked providers"; RUSH-3040).
|
|
202
|
-
* Route every no-snapshot outcome for either through this one function so a
|
|
203
|
-
* future entry cannot be added second-class again — it owns noting the 429
|
|
204
|
-
* backoff, so callers must NOT also call {@link noteUsageRateLimited} for the
|
|
205
|
-
* same response.
|
|
206
|
-
*/
|
|
207
93
|
export function classifyUsageFetchFailure(agent, agentId, status, retryAfterHeader, usageScope) {
|
|
208
94
|
if (status === 429) {
|
|
209
95
|
noteUsageRateLimited(agentId, retryAfterHeader ?? null, { account: usageScope });
|
|
@@ -213,7 +99,6 @@ export function classifyUsageFetchFailure(agent, agentId, status, retryAfterHead
|
|
|
213
99
|
return usageRejectedError(agent, status);
|
|
214
100
|
return usageUnreachableError(agent);
|
|
215
101
|
}
|
|
216
|
-
/** Classify a `UsageInfo.error` string into its {@link UsageErrorKind}, or null when there is no error. */
|
|
217
102
|
export function classifyUsageErrorKind(error) {
|
|
218
103
|
if (!error)
|
|
219
104
|
return null;
|
|
@@ -236,17 +121,6 @@ export function classifyUsageErrorKind(error) {
|
|
|
236
121
|
return 'unreachable';
|
|
237
122
|
return 'rejected';
|
|
238
123
|
}
|
|
239
|
-
/**
|
|
240
|
-
* True when a Claude OAuth access token is within the refresh leeway of expiry
|
|
241
|
-
* (or already expired) — i.e. it "would need a refresh" before the next use.
|
|
242
|
-
*
|
|
243
|
-
* Single source of truth for the expiry gate, shared by the two callers that
|
|
244
|
-
* must agree on it but act differently: the run/usage hot path
|
|
245
|
-
* (`getClaudeAccessToken`) refreshes when this is true; the health probe
|
|
246
|
-
* (`probeClaudeStatus`) must NOT refresh and instead reports the non-fatal
|
|
247
|
-
* `expired` state (RUSH-1822). A missing `expiresAt` is treated as "still
|
|
248
|
-
* fresh" (never force a refresh on a token with no known expiry).
|
|
249
|
-
*/
|
|
250
124
|
export function claudeAccessTokenNeedsRefresh(expiresAt, nowMs = Date.now()) {
|
|
251
125
|
if (expiresAt == null)
|
|
252
126
|
return false;
|
|
@@ -260,12 +134,6 @@ const CLAUDE_SCOPES = [
|
|
|
260
134
|
'user:file_upload',
|
|
261
135
|
];
|
|
262
136
|
const CLAUDE_KEYCHAIN_SERVICE = 'Claude Code-credentials';
|
|
263
|
-
/**
|
|
264
|
-
* Test seam for the usage cache path, mirroring `setUsageBackoffDirForTest`.
|
|
265
|
-
* `getCacheDir()` resolves from a module-level constant captured at import, so
|
|
266
|
-
* overriding `HOME` in a test does NOT redirect this cache — it would write into
|
|
267
|
-
* the developer's real `~/.agents/.cache/`. Point it at a tmpdir instead.
|
|
268
|
-
*/
|
|
269
137
|
let claudeUsageCachePathOverride = null;
|
|
270
138
|
export function setClaudeUsageCachePathForTest(cachePath) {
|
|
271
139
|
const prev = claudeUsageCachePathOverride;
|
|
@@ -284,21 +152,13 @@ const USAGE_BAR_LEN = 10;
|
|
|
284
152
|
const FULL = '\u2588';
|
|
285
153
|
const EMPTY = '\u2591';
|
|
286
154
|
const PARTIAL_BLOCKS = ['', '\u258F', '\u258E', '\u258D', '\u258C', '\u258B', '\u258A', '\u2589'];
|
|
287
|
-
// A window we EXPECTED but have no reading for \u2014 e.g. Claude's 5h "session"
|
|
288
|
-
// window when the account has no usage in the current rolling window, so the
|
|
289
|
-
// usage API returns five_hour.utilization = null and no session bar is written.
|
|
290
|
-
// It must read as neither 0% (EMPTY '\u2591') nor 100% (FULL '\u2588') \u2014 a full block was
|
|
291
|
-
// alarming and looked maxed-out \u2014 so use a dashed row that says "no data".
|
|
292
155
|
const NO_DATA = '\u2504';
|
|
293
|
-
/** Construct the benign no-local-log result without overloading `error`. */
|
|
294
156
|
function usageNoRecentUsageInfo() {
|
|
295
157
|
return { snapshot: null, error: null, [USAGE_BENIGN_STATE]: 'no-recent-usage' };
|
|
296
158
|
}
|
|
297
|
-
/** Read a benign state for the human renderer; symbols are omitted by JSON serialization. */
|
|
298
159
|
export function getUsageBenignState(info) {
|
|
299
160
|
return info[USAGE_BENIGN_STATE] ?? null;
|
|
300
161
|
}
|
|
301
|
-
/** The single registry of agent usage sources and their transport. */
|
|
302
162
|
const USAGE_SOURCES = {
|
|
303
163
|
claude: { fetch: getClaudeUsageInfo, network: true },
|
|
304
164
|
codex: { fetch: getCodexUsageInfo, network: false },
|
|
@@ -313,29 +173,17 @@ export const USAGE_SOURCE_AGENT_IDS = Object.keys(USAGE_SOURCES);
|
|
|
313
173
|
function getUsageSource(agentId) {
|
|
314
174
|
return USAGE_SOURCES[agentId];
|
|
315
175
|
}
|
|
316
|
-
/** Fetch usage info for a given agent through the canonical source registry. */
|
|
317
176
|
export async function getUsageInfo(agentId, options) {
|
|
318
177
|
const source = getUsageSource(agentId);
|
|
319
178
|
return source ? source.fetch(options) : { snapshot: null, error: null };
|
|
320
179
|
}
|
|
321
|
-
/**
|
|
322
|
-
* Combine a caller-supplied abort signal (the daemon tick deadline) with a
|
|
323
|
-
* per-fetch timeout, so a provider fetch is bounded by whichever fires first
|
|
324
|
-
* (PHNX-3608). With no caller signal it degrades to the timeout alone —
|
|
325
|
-
* byte-identical to the previous `AbortSignal.timeout(ms)` behaviour.
|
|
326
|
-
*/
|
|
327
180
|
function usageFetchSignal(signal, timeoutMs) {
|
|
328
181
|
const timeout = AbortSignal.timeout(timeoutMs);
|
|
329
182
|
return signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
330
183
|
}
|
|
331
|
-
/** Derive a stable lookup key from account info for usage deduplication. */
|
|
332
184
|
export function getUsageLookupKey(info) {
|
|
333
185
|
return info?.usageKey || info?.accountKey || null;
|
|
334
186
|
}
|
|
335
|
-
/**
|
|
336
|
-
* Deduplicate identity inputs into canonical (most-recently-active) accounts
|
|
337
|
-
* and build the corresponding fetch inputs for each unique usage key.
|
|
338
|
-
*/
|
|
339
187
|
export function buildCanonicalUsageContext(inputs) {
|
|
340
188
|
const canonicalByUsageKey = new Map();
|
|
341
189
|
const usageFetchInputs = new Map();
|
|
@@ -359,31 +207,12 @@ export function buildCanonicalUsageContext(inputs) {
|
|
|
359
207
|
}
|
|
360
208
|
return { canonicalByUsageKey, usageFetchInputs };
|
|
361
209
|
}
|
|
362
|
-
/**
|
|
363
|
-
* Whether an agent exposes usage/limit data we can render — Claude/Kimi/Droid/
|
|
364
|
-
* Cursor/Antigravity via a live API, Codex/Grok via local session logs.
|
|
365
|
-
* Everything else has no usage concept, so callers use this to decide whether
|
|
366
|
-
* a missing snapshot is worth flagging as "usage unavailable" (a signed-in
|
|
367
|
-
* Claude account with no data) versus simply not applicable (OpenCode).
|
|
368
|
-
*/
|
|
369
210
|
export function agentReportsUsage(agentId) {
|
|
370
211
|
return getUsageSource(agentId) !== undefined;
|
|
371
212
|
}
|
|
372
|
-
/**
|
|
373
|
-
* Whether an agent's usage source makes a live NETWORK call (Claude/Kimi/Droid/
|
|
374
|
-
* Cursor/Antigravity) versus reading local session logs (Codex/Grok). Both
|
|
375
|
-
* kinds publish through the shared cache; callers use this only to distinguish
|
|
376
|
-
* provider I/O from local collection.
|
|
377
|
-
*/
|
|
378
213
|
export function agentUsesNetworkUsage(agentId) {
|
|
379
214
|
return getUsageSource(agentId)?.network === true;
|
|
380
215
|
}
|
|
381
|
-
/**
|
|
382
|
-
* Concurrent live usage fetches for a single `agents view` / rotation pass.
|
|
383
|
-
* High enough to finish a multi-account refresh in one round-trip window; low
|
|
384
|
-
* enough that a cold cache of 10+ accounts cannot open 10+ HTTP calls at once
|
|
385
|
-
* (and cannot stack behind delayed responses until the process is pegged).
|
|
386
|
-
*/
|
|
387
216
|
export const USAGE_FETCH_CONCURRENCY = 3;
|
|
388
217
|
export async function getUsageInfoByIdentity(inputs, opts) {
|
|
389
218
|
const { canonicalByUsageKey, usageFetchInputs } = buildCanonicalUsageContext(inputs);
|
|
@@ -402,26 +231,11 @@ export async function getUsageInfoByIdentity(inputs, opts) {
|
|
|
402
231
|
usageByKey: new Map(usageResults.map(({ key, usage }) => [key, usage])),
|
|
403
232
|
};
|
|
404
233
|
}
|
|
405
|
-
/**
|
|
406
|
-
* In-process dedup complements the device-wide lease. It avoids lock contention
|
|
407
|
-
* when several callers in one process explicitly request the same refresh.
|
|
408
|
-
*/
|
|
409
234
|
const inFlightLiveFetches = new Map();
|
|
410
|
-
/**
|
|
411
|
-
* Fetch usage for one identity. Ordinary callers always read the shared cache;
|
|
412
|
-
* the daemon and explicit `--refresh` calls collect through one device lease.
|
|
413
|
-
*/
|
|
414
235
|
export async function getUsageInfoForIdentity(input, opts) {
|
|
415
236
|
const usageKey = getUsageLookupKey(input.info);
|
|
416
237
|
const forceRefresh = opts?.forceRefresh === true;
|
|
417
|
-
// Reading is the default. Only an explicit forceRefresh is authorized to
|
|
418
|
-
// collect provider/local-log state; callers cannot accidentally turn a
|
|
419
|
-
// display or routing path into a collector by omitting an option.
|
|
420
238
|
const readOnly = !forceRefresh;
|
|
421
|
-
// The on-disk cache is shared for both provider and local-log sources and is
|
|
422
|
-
// keyed by usageKey, which is namespaced per agent (`claude:org=…`,
|
|
423
|
-
// `kimi:user=…`, `droid:org=…`, `cursor:user=…`, `antigravity:sub=…`), so one
|
|
424
|
-
// cache file holds every account without collision.
|
|
425
239
|
if (!usageKey) {
|
|
426
240
|
if (readOnly)
|
|
427
241
|
return { snapshot: null, error: USAGE_NOT_COLLECTED_MARKER };
|
|
@@ -436,49 +250,20 @@ export async function getUsageInfoForIdentity(input, opts) {
|
|
|
436
250
|
});
|
|
437
251
|
}
|
|
438
252
|
const cached = readClaudeUsageCache(usageKey);
|
|
439
|
-
// `readOnly` (the `agents run` routing hot path): serve the cache and NEVER
|
|
440
|
-
// touch the network — not even a background refresh. `collectRunCandidates`
|
|
441
|
-
// used to pass a 5-minute `maxAgeMs`, which made a snapshot older than that
|
|
442
|
-
// fall through to the blocking live fetch below (getUsageInfo → provider HTTP),
|
|
443
|
-
// adding one round trip per account to `agents run` cold-start on a box whose
|
|
444
|
-
// cache had gone stale. The daemon now owns keeping this cache fresh
|
|
445
|
-
// (`runUsageRefresh`, adaptive + rate-capped), so the router only ever reads
|
|
446
|
-
// it. A stale-or-absent snapshot is handled downstream by the router's own
|
|
447
|
-
// freshness guard (`isUsageVerified` in rotate.ts), which routes around a
|
|
448
|
-
// number it can't confirm rather than trusting an old one — so returning a
|
|
449
|
-
// stale snapshot here is safe, and an absent one reports
|
|
450
|
-
// {@link USAGE_NOT_COLLECTED_MARKER}.
|
|
451
253
|
if (readOnly) {
|
|
452
|
-
// A row carries a CONFIRMED reading when it has a fresh window, a
|
|
453
|
-
// subscription plan (meterless-healthy, e.g. Grok's tier), or a live refusal
|
|
454
|
-
// (out_of_credits / session_limit). Those report `usageError: null`.
|
|
455
254
|
if (cached && (cached.windows.length > 0 || cached.plan || cached.unavailable)) {
|
|
456
255
|
return { snapshot: cached, error: null };
|
|
457
256
|
}
|
|
458
|
-
// A row whose ONLY content is last-known stale readings — the all-expired
|
|
459
|
-
// Claude case, its windows moved to `staleWindows` (view-only) so the
|
|
460
|
-
// TERMINAL view still renders the number with its age — must NOT read as a
|
|
461
|
-
// healthy account. `staleWindows` is deliberately excluded from `--json`
|
|
462
|
-
// (which projects only `windows`), so keep `usageError` non-null: a consumer
|
|
463
|
-
// polling `agents view --json` must still see the staleness signal, the exact
|
|
464
|
-
// RUSH-2858 weeks-stale case this marker exists for. The snapshot is still
|
|
465
|
-
// returned, so the human view is unaffected.
|
|
466
257
|
if (cached)
|
|
467
258
|
return { snapshot: cached, error: USAGE_NOT_COLLECTED_MARKER };
|
|
468
259
|
return { snapshot: null, error: USAGE_NOT_COLLECTED_MARKER };
|
|
469
260
|
}
|
|
470
|
-
// Explicit refresh: block on the shared device collector.
|
|
471
261
|
return fetchLiveUsageDeduped(input, usageKey, cached, opts?.fileOnly === true, {
|
|
472
262
|
allowInteractiveLogin: opts?.allowInteractiveLogin === true,
|
|
473
263
|
nativeFileLogin: opts?.nativeFileLogin === true,
|
|
474
264
|
signal: opts?.signal,
|
|
475
265
|
});
|
|
476
266
|
}
|
|
477
|
-
/**
|
|
478
|
-
* Single-flight live usage fetch per usage key. Concurrent callers (view +
|
|
479
|
-
* rotation, or two rows sharing an account) await the same promise rather than
|
|
480
|
-
* opening duplicate HTTP requests that then time out and pile up.
|
|
481
|
-
*/
|
|
482
267
|
async function fetchLiveUsageDeduped(input, usageKey, cached, fileOnly, opts) {
|
|
483
268
|
const existing = inFlightLiveFetches.get(usageKey);
|
|
484
269
|
if (existing)
|
|
@@ -498,8 +283,6 @@ async function fetchLiveUsageDeduped(input, usageKey, cached, fileOnly, opts) {
|
|
|
498
283
|
home: input.home,
|
|
499
284
|
cliVersion: input.cliVersion,
|
|
500
285
|
organizationId: input.info.organizationId,
|
|
501
|
-
// Scope this fetch's 429 backoff to the account being fetched, so one
|
|
502
|
-
// throttled account cannot park the whole provider (RUSH-3036).
|
|
503
286
|
usageScope: usageKey,
|
|
504
287
|
fileOnly,
|
|
505
288
|
allowInteractiveLogin: opts?.allowInteractiveLogin === true,
|
|
@@ -513,7 +296,6 @@ async function fetchLiveUsageDeduped(input, usageKey, cached, fileOnly, opts) {
|
|
|
513
296
|
writeClaudeUsageCache(usageKey, usage.snapshot);
|
|
514
297
|
return usage;
|
|
515
298
|
}
|
|
516
|
-
// Live fetch failed — last-resort fallback to whatever cache we had.
|
|
517
299
|
if (latestCached)
|
|
518
300
|
return { snapshot: latestCached, error: usage.error };
|
|
519
301
|
return usage;
|
|
@@ -527,23 +309,11 @@ async function fetchLiveUsageDeduped(input, usageKey, cached, fileOnly, opts) {
|
|
|
527
309
|
inFlightLiveFetches.delete(usageKey);
|
|
528
310
|
}
|
|
529
311
|
}
|
|
530
|
-
/**
|
|
531
|
-
* Pick which usage windows to render in a compact one-line summary.
|
|
532
|
-
*
|
|
533
|
-
* Overview rows (`agents view` all agents) must stay narrow enough that one
|
|
534
|
-
* multi-window agent (Antigravity's four model quotas, Droid's three buckets)
|
|
535
|
-
* does not force every other row to pad to ~200 columns and wrap. Prefer the
|
|
536
|
-
* canonical session + week windows when present; otherwise take the highest
|
|
537
|
-
* utilization remaining. Returns the full set when `maxWindows` is unset.
|
|
538
|
-
*/
|
|
539
312
|
export function pickCompactUsageWindows(windows, maxWindows) {
|
|
540
313
|
const filtered = windows.filter((window) => window.key !== 'sonnet_week');
|
|
541
314
|
if (maxWindows === undefined || maxWindows <= 0 || filtered.length <= maxWindows) {
|
|
542
315
|
return filtered;
|
|
543
316
|
}
|
|
544
|
-
// Pick by object identity, not by key. Antigravity normalizes every model
|
|
545
|
-
// quota as key: 'session', so a key-set filter would keep only the first and
|
|
546
|
-
// drop the rest even when maxWindows > 1.
|
|
547
317
|
const chosen = [];
|
|
548
318
|
const take = (w) => {
|
|
549
319
|
if (!w || chosen.includes(w) || chosen.length >= maxWindows)
|
|
@@ -562,12 +332,6 @@ export function pickCompactUsageWindows(windows, maxWindows) {
|
|
|
562
332
|
}
|
|
563
333
|
return chosen;
|
|
564
334
|
}
|
|
565
|
-
/**
|
|
566
|
-
* Shared builder for {@link formatUsageSummary} options in `agents view` and
|
|
567
|
-
* account-catalog rows. One builder, no copy — captures `headless`,
|
|
568
|
-
* `unverified`, `expectedWindows`, `errorKind`, `benignState`, and the grok
|
|
569
|
-
* `noRecentUsageLabel` consistently.
|
|
570
|
-
*/
|
|
571
335
|
export function viewUsageSummaryOptions(agentId, signedIn, usageInfo, maxWindows, version) {
|
|
572
336
|
const headless = isUsageHeadlessScopeError(usageInfo?.error);
|
|
573
337
|
const benignState = usageInfo ? getUsageBenignState(usageInfo) : null;
|
|
@@ -587,22 +351,15 @@ export function viewUsageSummaryOptions(agentId, signedIn, usageInfo, maxWindows
|
|
|
587
351
|
: null,
|
|
588
352
|
};
|
|
589
353
|
}
|
|
590
|
-
/** Human label for a classified usage error, for the no-bars branch of {@link formatUsageSummary}. */
|
|
591
354
|
function formatUsageErrorKindLabel(kind, detail) {
|
|
592
355
|
switch (kind) {
|
|
593
356
|
case 'no-credential':
|
|
594
357
|
return 'sign in / provision token';
|
|
595
|
-
// Both of these are permanent for the account as configured, and both used
|
|
596
|
-
// to render as the generic bucket — which reads as a transient failure and
|
|
597
|
-
// sends operators back to `claude setup-token` for a remedy that cannot
|
|
598
|
-
// work (#2987). Name the state instead.
|
|
599
358
|
case 'no-usage-credential':
|
|
600
359
|
return USAGE_NO_USAGE_CREDENTIAL_MARKER;
|
|
601
360
|
case 'headless-scope':
|
|
602
361
|
return USAGE_HEADLESS_SCOPE_MARKER;
|
|
603
362
|
case 'expired-credential':
|
|
604
|
-
// Kimi refreshes its own credential on a normal launch; the recovery hint
|
|
605
|
-
// is embedded in the error string so the label matches the exact action.
|
|
606
363
|
if (detail?.includes('run Kimi once'))
|
|
607
364
|
return 'run Kimi once';
|
|
608
365
|
return 're-auth for usage';
|
|
@@ -620,35 +377,19 @@ function formatUsageErrorKindLabel(kind, detail) {
|
|
|
620
377
|
return 'usage unavailable';
|
|
621
378
|
}
|
|
622
379
|
}
|
|
623
|
-
/** Format a one-line usage summary with compact bars for inline display. */
|
|
624
380
|
export function formatUsageSummary(plan, snapshot, planWidth = 3, opts) {
|
|
625
381
|
const parts = [];
|
|
626
382
|
if (plan) {
|
|
627
383
|
parts.push(chalk.gray(plan.padEnd(planWidth)));
|
|
628
384
|
}
|
|
629
385
|
if (snapshot) {
|
|
630
|
-
// A blocking marker renders AFTER the bars: leading it pushed every gauge
|
|
631
|
-
// out of its column, so one throttled account misaligned the whole table.
|
|
632
386
|
const blocked = snapshot.unavailable?.reason === 'out_of_credits'
|
|
633
387
|
? chalk.red('out of credits')
|
|
634
388
|
: snapshot.unavailable?.reason === 'session_limit' && snapshot.unavailable.resetsAt
|
|
635
389
|
? chalk.yellow(`session-limited (${formatResetHint(snapshot.unavailable.resetsAt)})`)
|
|
636
390
|
: null;
|
|
637
|
-
// Compact rows show BLOCKING windows — the same set
|
|
638
|
-
// deriveUsageStatusFromSnapshot uses for the rate-limited badge — so an
|
|
639
|
-
// account throttled by its month window (Droid meters on 5h/week/month)
|
|
640
|
-
// shows the bar that explains why. Claude's Sonnet week is a per-model
|
|
641
|
-
// sub-limit, not a blocking window; it renders only in the full
|
|
642
|
-
// per-version usage section. Each window reads "S: ███░░ 58% (3d)" — the
|
|
643
|
-
// gauge, the exact percentage, and a compact hint of when it resets.
|
|
644
|
-
//
|
|
645
|
-
// Overview caps the window count (see pickCompactUsageWindows) so one
|
|
646
|
-
// multi-meter agent cannot force the whole table to wrap.
|
|
647
391
|
const selected = pickCompactUsageWindows(snapshot.windows, opts?.maxWindows);
|
|
648
392
|
const hidden = Math.max(0, snapshot.windows.filter((w) => w.key !== 'sonnet_week').length - selected.length);
|
|
649
|
-
// Last-known windows the freshness gate dropped (see UsageSnapshot.staleWindows).
|
|
650
|
-
// Rendered with an age suffix so a stale reading stays visible instead of a
|
|
651
|
-
// bare "unavailable"; never in `snapshot.windows`, so routing never sees them.
|
|
652
393
|
const now = new Date();
|
|
653
394
|
const staleWindows = snapshot.staleWindows ?? [];
|
|
654
395
|
const staleByKey = new Map(staleWindows.map((w) => [w.key, w]));
|
|
@@ -658,8 +399,6 @@ export function formatUsageSummary(plan, snapshot, planWidth = 3, opts) {
|
|
|
658
399
|
: selected.map((window) => ({ key: window.key, window, shortLabel: window.shortLabel }));
|
|
659
400
|
const windowParts = windowsToRender.map(({ key, window, shortLabel }, index) => {
|
|
660
401
|
if (!window) {
|
|
661
|
-
// A window we expected but have no fresh reading for: render the
|
|
662
|
-
// last-known value with its age if we still have it, else "unavailable".
|
|
663
402
|
const stale = staleByKey.get(key);
|
|
664
403
|
const rendered = stale
|
|
665
404
|
? renderStaleUsageWindow(stale, snapshot.capturedAt, shortLabel, now)
|
|
@@ -679,9 +418,6 @@ export function formatUsageSummary(plan, snapshot, planWidth = 3, opts) {
|
|
|
679
418
|
parts.push(windowParts.join(' '));
|
|
680
419
|
}
|
|
681
420
|
else if (staleWindows.length > 0) {
|
|
682
|
-
// No fresh bars, but we have last-known readings (e.g. Grok's weekly bar
|
|
683
|
-
// from an ended billing period): show them with an age suffix rather than
|
|
684
|
-
// the "run once to refresh" hint, which hid a number we actually had.
|
|
685
421
|
const cap = opts?.maxWindows ?? staleWindows.length;
|
|
686
422
|
const rendered = staleWindows
|
|
687
423
|
.slice(0, cap)
|
|
@@ -691,11 +427,6 @@ export function formatUsageSummary(plan, snapshot, planWidth = 3, opts) {
|
|
|
691
427
|
else if (snapshot.refreshHint) {
|
|
692
428
|
parts.push(chalk.dim(snapshot.refreshHint));
|
|
693
429
|
}
|
|
694
|
-
// The bars came from the cache and the live read that should have confirmed
|
|
695
|
-
// them failed, so they are the last thing we saw — not the current state.
|
|
696
|
-
// Drawing them unmarked is what let a 26h-old "48% used" read as fact.
|
|
697
|
-
// Headless-scope (RUSH-2392) is a known permanent gap, not a flaky cache:
|
|
698
|
-
// prefer that label over the generic "unverified" so operators do not re-mint.
|
|
699
430
|
if (opts?.headless) {
|
|
700
431
|
parts.push(chalk.dim(USAGE_HEADLESS_SCOPE_MARKER));
|
|
701
432
|
}
|
|
@@ -706,61 +437,23 @@ export function formatUsageSummary(plan, snapshot, planWidth = 3, opts) {
|
|
|
706
437
|
parts.push(blocked);
|
|
707
438
|
}
|
|
708
439
|
else if (opts?.headless) {
|
|
709
|
-
// No bars at all: still name the scope gap so "usage pending" is not
|
|
710
|
-
// mistaken for a missing setup-token or seeding failure (RUSH-2392).
|
|
711
440
|
parts.push(chalk.dim(USAGE_HEADLESS_SCOPE_MARKER));
|
|
712
441
|
}
|
|
713
442
|
else if (opts?.benignState === 'no-recent-usage') {
|
|
714
443
|
parts.push(chalk.dim(opts.noRecentUsageLabel || USAGE_NO_RECENT_USAGE_MARKER));
|
|
715
444
|
}
|
|
716
445
|
else if (opts?.unavailable) {
|
|
717
|
-
// Signed-in account we could NOT fetch usage for (no live token in a reachable
|
|
718
|
-
// home / org mismatch / fetch error). Say so explicitly instead of drawing a
|
|
719
|
-
// blank gauge that reads like "0% used" — and name the SPECIFIC cause when
|
|
720
|
-
// the caller passed one, rather than the generic bucket that used to cover
|
|
721
|
-
// ~6 different failures (RUSH-3040).
|
|
722
446
|
parts.push(chalk.dim(formatUsageErrorKindLabel(opts.errorKind, opts.errorDetail)));
|
|
723
447
|
}
|
|
724
448
|
return parts.join(' ');
|
|
725
449
|
}
|
|
726
|
-
/**
|
|
727
|
-
* The snapshot's windows that are still LIVE — reset time not yet passed. A
|
|
728
|
-
* window past its `resetsAt` has rolled over: its `usedPercent` is a reading of
|
|
729
|
-
* the PREVIOUS period, not a live throttle (PHNX-4116). Both the throttle
|
|
730
|
-
* verdict ({@link deriveUsageStatusFromSnapshot}) and the displayed
|
|
731
|
-
* `usedPercent` (`devices/harness-inventory.ts`) MUST select from this SAME set,
|
|
732
|
-
* or a maxed-then-reset account reads `available` yet shows a stale 99% next to
|
|
733
|
-
* its "ready" verdict (#3705). Callers still narrow further (dropping
|
|
734
|
-
* `sonnet_week`, the model sub-limit); this is only the reset-rollover cut.
|
|
735
|
-
*/
|
|
736
450
|
export function liveUsageWindows(snapshot, now = Date.now()) {
|
|
737
451
|
return snapshot.windows.filter((window) => !(window.resetsAt && window.resetsAt.getTime() <= now));
|
|
738
452
|
}
|
|
739
|
-
/**
|
|
740
|
-
* Derive an account's real throttle state from its live usage windows — the
|
|
741
|
-
* single signal both the `agents view` badge and run-rotation eligibility share
|
|
742
|
-
* (`hasUsageAvailable` in rotate.ts treats a `rate_limited` verdict here as
|
|
743
|
-
* ineligible). A window at 100% utilization means the account is throttled until
|
|
744
|
-
* that window resets. Rotation *weighting* still ranks eligible accounts by
|
|
745
|
-
* weekly headroom (`getRoutingUsedPercent`); this function is the yes/no gate.
|
|
746
|
-
*
|
|
747
|
-
* Returns `null` when there is no snapshot, so callers render no badge rather
|
|
748
|
-
* than a misleading one. This deliberately never consults
|
|
749
|
-
* `cachedExtraUsageDisabledReason`: that field describes why pay-as-you-go
|
|
750
|
-
* overage is disabled (`out_of_credits` = no overage credits purchased,
|
|
751
|
-
* `org_level_disabled` = an admin turned overage off), NOT whether the account
|
|
752
|
-
* can do work right now. A Pro account at 5% weekly usage with overage disabled
|
|
753
|
-
* is fully usable, yet that flag would mislabel it "out of credits".
|
|
754
|
-
*
|
|
755
|
-
* The model-specific `sonnet_week` sub-limit is excluded: hitting it throttles
|
|
756
|
-
* one model, not the account, so it shouldn't flip the whole row to throttled.
|
|
757
|
-
*/
|
|
758
453
|
export function deriveUsageStatusFromSnapshot(snapshot, now = Date.now()) {
|
|
759
454
|
if (!snapshot)
|
|
760
455
|
return null;
|
|
761
456
|
if (snapshot.unavailable) {
|
|
762
|
-
// out_of_credits has no clock — it stays blocking until a successful run
|
|
763
|
-
// clears it. session_limit blocks only until its reset time.
|
|
764
457
|
if (snapshot.unavailable.reason === 'out_of_credits')
|
|
765
458
|
return 'rate_limited';
|
|
766
459
|
if (snapshot.unavailable.resetsAt && snapshot.unavailable.resetsAt.getTime() > now) {
|
|
@@ -790,30 +483,12 @@ export function deriveUsageHeadroom(snapshot, prev) {
|
|
|
790
483
|
}
|
|
791
484
|
const deltaPercent = session.usedPercent - prev.usedPercent;
|
|
792
485
|
const deltaMinutes = (capturedAt - prev.capturedAt) / 60_000;
|
|
793
|
-
// Flat, falling (a window reset), or a zero/negative time delta: no live burn
|
|
794
|
-
// to project from, so this account is not "projected to cap".
|
|
795
486
|
if (deltaPercent <= 0 || deltaMinutes <= 0)
|
|
796
487
|
return { status, minutesToLimit: null };
|
|
797
488
|
const burnPerMinute = deltaPercent / deltaMinutes;
|
|
798
489
|
const remaining = Math.max(0, 100 - session.usedPercent);
|
|
799
490
|
return { status, minutesToLimit: remaining / burnPerMinute };
|
|
800
491
|
}
|
|
801
|
-
/**
|
|
802
|
-
* Compact colored badge for the account's overall usage status. Renders only
|
|
803
|
-
* when the account is throttled — `available` and `null` return ''.
|
|
804
|
-
*
|
|
805
|
-
* - `out_of_credits` → red "out of credits" (terminal account, all buckets dry)
|
|
806
|
-
* - `rate_limited` → yellow "rate-limited" (transient throttling)
|
|
807
|
-
*
|
|
808
|
-
* The badge sits between the usage bars and `lastActive` in `agents view`, so
|
|
809
|
-
* a glance at the row tells the user whether the version can do useful work.
|
|
810
|
-
* The same signal is exposed as `usageStatus` in `agents view --json` for
|
|
811
|
-
* programmatic consumers (e.g. the swarmify panel's "resume in healthy agent").
|
|
812
|
-
*
|
|
813
|
-
* The switch is exhaustive on purpose — adding a new `AccountInfo.usageStatus`
|
|
814
|
-
* value without updating the cases here is a build error at `_exhaustive`,
|
|
815
|
-
* which is exactly the bug class this PR is fixing.
|
|
816
|
-
*/
|
|
817
492
|
export function formatUsageStatusBadge(usageStatus) {
|
|
818
493
|
if (usageStatus === null || usageStatus === undefined)
|
|
819
494
|
return '';
|
|
@@ -828,7 +503,6 @@ export function formatUsageStatusBadge(usageStatus) {
|
|
|
828
503
|
}
|
|
829
504
|
}
|
|
830
505
|
}
|
|
831
|
-
/** Format a multi-line usage section for detailed agent views. */
|
|
832
506
|
export function formatUsageSection(usage) {
|
|
833
507
|
if (!usage.snapshot && !usage.error) {
|
|
834
508
|
return [];
|
|
@@ -853,25 +527,8 @@ export function formatUsageSection(usage) {
|
|
|
853
527
|
lines.push(` ${chalk.dim(`Source: ${usage.snapshot.sourceLabel}`)}`);
|
|
854
528
|
return lines;
|
|
855
529
|
}
|
|
856
|
-
/** Fetch Codex usage by scanning the most recent session files for rate-limit events. */
|
|
857
530
|
async function getCodexUsageInfo(options) {
|
|
858
531
|
try {
|
|
859
|
-
// Codex usage is read from on-disk session transcripts, which carry no
|
|
860
|
-
// account identity and are not removed on logout. To keep the bar scoped to
|
|
861
|
-
// the account signed in NOW, floor the scan at the current login time: the
|
|
862
|
-
// id_token's `auth_time` claim — the OIDC time-of-authentication. A session
|
|
863
|
-
// written before that login belongs to whoever was signed in before (e.g.
|
|
864
|
-
// after `codex logout` + login into a different account), and showing its
|
|
865
|
-
// rate_limits is the "wrong usage after switch" bug.
|
|
866
|
-
//
|
|
867
|
-
// `auth_time` — not the auth.json file mtime — is the correct floor: Codex
|
|
868
|
-
// rewrites auth.json on every token refresh (advancing its mtime), but a
|
|
869
|
-
// refresh_token grant does not re-authenticate the user, so `auth_time`
|
|
870
|
-
// stays at the real login. Flooring on mtime would blank the bar after each
|
|
871
|
-
// background refresh; flooring on `auth_time` does not. No readable
|
|
872
|
-
// credential means the version is signed out — report no usage. A credential
|
|
873
|
-
// that carries no `auth_time` falls back to no floor (prior behavior) rather
|
|
874
|
-
// than hide a signed-in account's usage.
|
|
875
532
|
const base = options?.home || os.homedir();
|
|
876
533
|
let sinceMs;
|
|
877
534
|
try {
|
|
@@ -889,11 +546,6 @@ async function getCodexUsageInfo(options) {
|
|
|
889
546
|
const match = await readLatestCodexRateLimits(filePath);
|
|
890
547
|
if (!match)
|
|
891
548
|
continue;
|
|
892
|
-
// Same freshness filter Grok already applies (RUSH-3040): a window whose
|
|
893
|
-
// reset time or windowMinutes-derived expiry has passed is a STALE read,
|
|
894
|
-
// not a current one — rendering it as-is is how a codex bar kept showing
|
|
895
|
-
// "100% used" past its own reset. Try the next-older session file rather
|
|
896
|
-
// than surfacing a stale bar.
|
|
897
549
|
const windows = normalizeCodexWindows(match.rateLimits).filter((window) => isCachedUsageWindowFresh(window, match.capturedAt, now));
|
|
898
550
|
if (windows.length === 0)
|
|
899
551
|
continue;
|
|
@@ -907,52 +559,21 @@ async function getCodexUsageInfo(options) {
|
|
|
907
559
|
error: null,
|
|
908
560
|
};
|
|
909
561
|
}
|
|
910
|
-
// No session ever recorded a rate-limit event on this machine (or none of
|
|
911
|
-
// the ones found were still fresh) — a benign "nothing to show yet", not a
|
|
912
|
-
// failure (RUSH-3040). Distinct from the outer catch below, which is a
|
|
913
|
-
// genuine read/parse failure.
|
|
914
562
|
return usageNoRecentUsageInfo();
|
|
915
563
|
}
|
|
916
564
|
catch (err) {
|
|
917
565
|
return { snapshot: null, error: usageUnreachableError('Codex', err) };
|
|
918
566
|
}
|
|
919
567
|
}
|
|
920
|
-
|
|
921
|
-
* The access token to use for a READ-ONLY Claude usage fetch, or null when the
|
|
922
|
-
* stored token is within the refresh leeway.
|
|
923
|
-
*
|
|
924
|
-
* Returns null instead of refreshing on purpose. Claude's refresh token is
|
|
925
|
-
* single-use and rotates server-side on every refresh; with one account signed
|
|
926
|
-
* into several machines, refreshing here would stampede that one token and
|
|
927
|
-
* silently invalidate every other holder — the RUSH-1822 failure, except in the
|
|
928
|
-
* usage path (fired in the background by the SWR cache and by `agents run`'s
|
|
929
|
-
* default "balanced" rotation on every unpinned run) rather than the health
|
|
930
|
-
* probe. So a usage read must never rotate: a near-expiry token yields "no usage
|
|
931
|
-
* right now" instead of a fleet-wide logout. Mirrors {@link probeClaudeStatus};
|
|
932
|
-
* the single legitimate refresh belongs to the actual claude run, never a read.
|
|
933
|
-
* Pure — unit-tested.
|
|
934
|
-
*/
|
|
568
|
+
// Usage probes never refresh credentials or fall into interactive OAuth; foreground headed reads must opt in explicitly.
|
|
935
569
|
export function claudeUsageAccessTokenNoRefresh(oauth) {
|
|
936
570
|
if (claudeAccessTokenNeedsRefresh(oauth.expiresAt ?? null))
|
|
937
571
|
return null;
|
|
938
572
|
const token = oauth.accessToken?.trim();
|
|
939
573
|
return token ? token : null;
|
|
940
574
|
}
|
|
941
|
-
/** Fetch Claude usage via the Anthropic OAuth usage API. */
|
|
942
575
|
async function getClaudeUsageInfo(options) {
|
|
943
576
|
try {
|
|
944
|
-
// accessTokenCache: this is the every-60s watchdog hot path and usage needs
|
|
945
|
-
// only the access token, so it reads ONLY the file-based setup-token and never
|
|
946
|
-
// the interactive login (reading that ACL-bound token and firing it at the
|
|
947
|
-
// usage API is what got it revoked — RUSH-1822). No setup-token => null =>
|
|
948
|
-
// "usage pending". fileOnly additionally forbids the ACL keychain path.
|
|
949
|
-
//
|
|
950
|
-
// allowInteractiveLogin is the one sanctioned exception (USAGE-READ-1/2): a
|
|
951
|
-
// foreground human `agents view` on a `personal` device MAY fall through to
|
|
952
|
-
// the interactive login when no setup-token exists, because that login is the
|
|
953
|
-
// only credential carrying the `user:profile` scope the usage endpoint
|
|
954
|
-
// requires (the setup-token is user:inference → 403, RUSH-2392). It is unset
|
|
955
|
-
// for every background caller, so the RUSH-1822 guarantee is untouched there.
|
|
956
577
|
const oauth = await loadClaudeOauth(options?.home, {
|
|
957
578
|
accessTokenCache: options?.nativeFileLogin !== true,
|
|
958
579
|
fileOnly: options?.fileOnly === true || options?.nativeFileLogin === true,
|
|
@@ -960,26 +581,17 @@ async function getClaudeUsageInfo(options) {
|
|
|
960
581
|
nativeFileLogin: options?.nativeFileLogin === true,
|
|
961
582
|
});
|
|
962
583
|
if (!oauth?.accessToken) {
|
|
963
|
-
// NOT the shared no-credential message: "sign in" is not a remedy here.
|
|
964
|
-
// The account this reads for is usually signed in already — the reader is
|
|
965
|
-
// forbidden from touching that login (RUSH-1822) — so the shared wording
|
|
966
|
-
// asked the operator to redo the one thing they had already done (#2987).
|
|
967
584
|
return { snapshot: null, error: usageNoClaudeUsageCredentialError() };
|
|
968
585
|
}
|
|
969
586
|
const requestedOrgId = normalizeString(options?.organizationId);
|
|
970
587
|
const liveOrgId = normalizeString(oauth.organizationUuid);
|
|
971
588
|
if (!isClaudeUsageOrgMatch(requestedOrgId, liveOrgId)) {
|
|
972
|
-
// Not a fault: this home is signed into a different org than the identity
|
|
973
|
-
// being read, so there is nothing to report for it.
|
|
974
589
|
return { snapshot: null, error: null };
|
|
975
590
|
}
|
|
976
|
-
// Read-only: never refresh a single-use token just to read usage (RUSH-1822).
|
|
977
591
|
const accessToken = claudeUsageAccessTokenNoRefresh(oauth);
|
|
978
592
|
if (!accessToken) {
|
|
979
593
|
return { snapshot: null, error: usageExpiredCredentialError('Claude') };
|
|
980
594
|
}
|
|
981
|
-
// Honour a live Retry-After rather than re-arming the penalty (see
|
|
982
|
-
// usage-backoff.ts). No request at all while the window is open.
|
|
983
595
|
const throttledUntil = usageRateLimitedUntil('claude', Date.now(), options?.usageScope);
|
|
984
596
|
if (throttledUntil) {
|
|
985
597
|
return { snapshot: null, error: usageThrottledError('Claude', throttledUntil) };
|
|
@@ -998,16 +610,12 @@ async function getClaudeUsageInfo(options) {
|
|
|
998
610
|
if (response.status === 429) {
|
|
999
611
|
noteUsageRateLimited('claude', response.headers.get('retry-after'), { account: options?.usageScope });
|
|
1000
612
|
}
|
|
1001
|
-
// Setup-token is user:inference only; usage needs user:profile → 403
|
|
1002
|
-
// with a scope-requirement body. Distinct from a real rejection so the
|
|
1003
|
-
// UI does not say "unverified" / re-mint (RUSH-2392).
|
|
1004
613
|
if (response.status === 403) {
|
|
1005
614
|
let bodyText = '';
|
|
1006
615
|
try {
|
|
1007
616
|
bodyText = await response.text();
|
|
1008
617
|
}
|
|
1009
618
|
catch {
|
|
1010
|
-
// ignore — fall through to generic rejection
|
|
1011
619
|
}
|
|
1012
620
|
if (isClaudeUsageScopeDenied(response.status, bodyText)) {
|
|
1013
621
|
return { snapshot: null, error: usageHeadlessScopeError('Claude') };
|
|
@@ -1031,19 +639,9 @@ async function getClaudeUsageInfo(options) {
|
|
|
1031
639
|
};
|
|
1032
640
|
}
|
|
1033
641
|
catch (err) {
|
|
1034
|
-
// A thrown request (timeout, DNS, TLS, a malformed payload) is a failed
|
|
1035
|
-
// read like any other — staying silent here would hand the caller a stale
|
|
1036
|
-
// snapshot to render as confirmed, which is the bug this file just closed.
|
|
1037
642
|
return { snapshot: null, error: usageUnreachableError('Claude', err) };
|
|
1038
643
|
}
|
|
1039
644
|
}
|
|
1040
|
-
/**
|
|
1041
|
-
* Resolve Kimi's OAuth credential file. Sign-in is account-global but each
|
|
1042
|
-
* installed version has an isolated home; the file physically lives only in the
|
|
1043
|
-
* home the user logged in under. Check the per-version home first, then the
|
|
1044
|
-
* active location under the real HOME — mirrors resolveAccountCredentialPath in
|
|
1045
|
-
* agents.ts so every version reflects the true account state.
|
|
1046
|
-
*/
|
|
1047
645
|
function resolveKimiCredentialPath(home) {
|
|
1048
646
|
const rel = ['.kimi-code', 'credentials', 'kimi-code.json'];
|
|
1049
647
|
const perVersion = path.join(home || os.homedir(), ...rel);
|
|
@@ -1051,14 +649,14 @@ function resolveKimiCredentialPath(home) {
|
|
|
1051
649
|
if (fs.existsSync(perVersion))
|
|
1052
650
|
return perVersion;
|
|
1053
651
|
}
|
|
1054
|
-
catch {
|
|
652
|
+
catch { }
|
|
1055
653
|
const active = path.join(process.env.AGENTS_REAL_HOME || os.homedir(), ...rel);
|
|
1056
654
|
if (active !== perVersion) {
|
|
1057
655
|
try {
|
|
1058
656
|
if (fs.existsSync(active))
|
|
1059
657
|
return active;
|
|
1060
658
|
}
|
|
1061
|
-
catch {
|
|
659
|
+
catch { }
|
|
1062
660
|
}
|
|
1063
661
|
return null;
|
|
1064
662
|
}
|
|
@@ -1087,8 +685,6 @@ async function getKimiUsageInfo(options) {
|
|
|
1087
685
|
if (expiresAt !== null && Date.now() / 1000 >= expiresAt) {
|
|
1088
686
|
return { snapshot: null, error: usageExpiredKimiCredentialError() };
|
|
1089
687
|
}
|
|
1090
|
-
// Honour a live Retry-After rather than re-arming the penalty (see
|
|
1091
|
-
// usage-backoff.ts). No request at all while the window is open.
|
|
1092
688
|
const throttledUntil = usageRateLimitedUntil('kimi', Date.now(), options?.usageScope);
|
|
1093
689
|
if (throttledUntil) {
|
|
1094
690
|
return { snapshot: null, error: usageThrottledError('Kimi', throttledUntil) };
|
|
@@ -1101,8 +697,6 @@ async function getKimiUsageInfo(options) {
|
|
|
1101
697
|
},
|
|
1102
698
|
signal: usageFetchSignal(options?.signal, 5000),
|
|
1103
699
|
});
|
|
1104
|
-
// 401/403 => expired token, 404 => no Kimi For Coding subscription. Either
|
|
1105
|
-
// way there are no bars to draw, and the status is what tells them apart.
|
|
1106
700
|
if (!response.ok) {
|
|
1107
701
|
if (response.status === 429) {
|
|
1108
702
|
noteUsageRateLimited('kimi', response.headers.get('retry-after'), { account: options?.usageScope });
|
|
@@ -1126,29 +720,22 @@ async function getKimiUsageInfo(options) {
|
|
|
1126
720
|
};
|
|
1127
721
|
}
|
|
1128
722
|
catch (err) {
|
|
1129
|
-
// A thrown request (timeout, DNS, TLS, a malformed payload) is a failed
|
|
1130
|
-
// read like any other — staying silent here would hand the caller a stale
|
|
1131
|
-
// snapshot to render as confirmed, which is the bug this file just closed.
|
|
1132
723
|
return { snapshot: null, error: usageUnreachableError('Kimi', err) };
|
|
1133
724
|
}
|
|
1134
725
|
}
|
|
1135
|
-
/** Normalize the Kimi /usages payload into the common UsageWindow shape. */
|
|
1136
726
|
export function normalizeKimiWindows(data) {
|
|
1137
727
|
const windows = [];
|
|
1138
|
-
// Per-window rate limit (e.g. a 300-minute bucket) -> "session".
|
|
1139
728
|
const shortLimit = Array.isArray(data.limits)
|
|
1140
729
|
? data.limits.find((entry) => entry?.detail)
|
|
1141
730
|
: null;
|
|
1142
731
|
const session = normalizeKimiWindow(shortLimit?.detail, 'session', 'Current session', 'S', kimiWindowMinutes(shortLimit?.window));
|
|
1143
732
|
if (session)
|
|
1144
733
|
windows.push(session);
|
|
1145
|
-
// Rolling account quota -> "week".
|
|
1146
734
|
const period = normalizeKimiWindow(data.usage, 'week', 'Current period', 'W', null);
|
|
1147
735
|
if (period)
|
|
1148
736
|
windows.push(period);
|
|
1149
737
|
return windows;
|
|
1150
738
|
}
|
|
1151
|
-
/** Normalize a single Kimi quota bucket (used/limit strings) into a UsageWindow. */
|
|
1152
739
|
function normalizeKimiWindow(quota, key, label, shortLabel, windowMinutes) {
|
|
1153
740
|
const limit = kimiNumber(quota?.limit);
|
|
1154
741
|
const used = kimiNumber(quota?.used);
|
|
@@ -1166,7 +753,6 @@ function normalizeKimiWindow(quota, key, label, shortLabel, windowMinutes) {
|
|
|
1166
753
|
windowMinutes: windowMinutes ?? inferWindowMinutes(key),
|
|
1167
754
|
};
|
|
1168
755
|
}
|
|
1169
|
-
/** Parse a numeric field that Kimi serializes as a string (e.g. "100"). */
|
|
1170
756
|
function kimiNumber(value) {
|
|
1171
757
|
if (typeof value === 'number' && Number.isFinite(value))
|
|
1172
758
|
return value;
|
|
@@ -1175,7 +761,6 @@ function kimiNumber(value) {
|
|
|
1175
761
|
}
|
|
1176
762
|
return null;
|
|
1177
763
|
}
|
|
1178
|
-
/** Convert a Kimi limit window (duration + timeUnit enum) to minutes. */
|
|
1179
764
|
function kimiWindowMinutes(window) {
|
|
1180
765
|
const duration = typeof window?.duration === 'number' ? window.duration : null;
|
|
1181
766
|
if (duration === null || duration <= 0)
|
|
@@ -1183,14 +768,13 @@ function kimiWindowMinutes(window) {
|
|
|
1183
768
|
switch (window?.timeUnit) {
|
|
1184
769
|
case 'TIME_UNIT_HOUR': return duration * 60;
|
|
1185
770
|
case 'TIME_UNIT_SECOND': return duration / 60;
|
|
1186
|
-
default: return duration;
|
|
771
|
+
default: return duration;
|
|
1187
772
|
}
|
|
1188
773
|
}
|
|
1189
|
-
/** Derive a display plan label from Kimi's membership tier or subscription type. */
|
|
1190
774
|
export function formatKimiPlan(data) {
|
|
1191
775
|
const level = data.user?.membership?.level;
|
|
1192
776
|
const raw = (typeof level === 'string' && level) || (typeof data.subType === 'string' && data.subType) || '';
|
|
1193
|
-
const tail = raw.split('_').pop() || '';
|
|
777
|
+
const tail = raw.split('_').pop() || '';
|
|
1194
778
|
if (!tail)
|
|
1195
779
|
return null;
|
|
1196
780
|
return tail.charAt(0).toUpperCase() + tail.slice(1).toLowerCase();
|
|
@@ -1221,8 +805,6 @@ async function getDroidUsageInfo(options) {
|
|
|
1221
805
|
if (typeof exp === 'number' && Date.now() / 1000 >= exp) {
|
|
1222
806
|
return { snapshot: null, error: usageExpiredCredentialError('Droid') };
|
|
1223
807
|
}
|
|
1224
|
-
// Honour a live Retry-After rather than re-arming the penalty (see
|
|
1225
|
-
// usage-backoff.ts). No request at all while the window is open.
|
|
1226
808
|
const throttledUntil = usageRateLimitedUntil('droid', Date.now(), options?.usageScope);
|
|
1227
809
|
if (throttledUntil) {
|
|
1228
810
|
return { snapshot: null, error: usageThrottledError('Droid', throttledUntil) };
|
|
@@ -1235,7 +817,6 @@ async function getDroidUsageInfo(options) {
|
|
|
1235
817
|
},
|
|
1236
818
|
signal: usageFetchSignal(options?.signal, 5000),
|
|
1237
819
|
});
|
|
1238
|
-
// 401 => revoked/expired token. No bars to draw, and the status says why.
|
|
1239
820
|
if (!response.ok) {
|
|
1240
821
|
if (response.status === 429) {
|
|
1241
822
|
noteUsageRateLimited('droid', response.headers.get('retry-after'), { account: options?.usageScope });
|
|
@@ -1258,38 +839,17 @@ async function getDroidUsageInfo(options) {
|
|
|
1258
839
|
};
|
|
1259
840
|
}
|
|
1260
841
|
catch (err) {
|
|
1261
|
-
// A thrown request (timeout, DNS, TLS, a malformed payload) is a failed
|
|
1262
|
-
// read like any other — staying silent here would hand the caller a stale
|
|
1263
|
-
// snapshot to render as confirmed, which is the bug this file just closed.
|
|
1264
842
|
return { snapshot: null, error: usageUnreachableError('Droid', err) };
|
|
1265
843
|
}
|
|
1266
844
|
}
|
|
1267
|
-
/** Probe Claude's OAuth token against the usage endpoint. Never refreshes — reports `expired` for a near-expiry token; see the comment below (RUSH-1822). */
|
|
1268
845
|
export async function probeClaudeStatus(home, cliVersion, usageScope, signal) {
|
|
1269
|
-
// accessTokenCache: the daemon warms this probe every ~3 min per account, so it
|
|
1270
|
-
// reads ONLY the file-based setup-token and never the interactive login —
|
|
1271
|
-
// transmitting that ACL-bound token to the usage API from a background loop is
|
|
1272
|
-
// what got it revoked (RUSH-1822). No setup-token => token 'missing' below.
|
|
1273
846
|
const oauth = await loadClaudeOauth(home, { accessTokenCache: true });
|
|
1274
847
|
const accessToken = oauth?.accessToken?.trim();
|
|
1275
848
|
if (!accessToken)
|
|
1276
849
|
return { status: null, token: 'missing' };
|
|
1277
|
-
// Never refresh from a health probe. Claude's refresh token is single-use and
|
|
1278
|
-
// rotates on every refresh; with one account signed into several machines the
|
|
1279
|
-
// daemon's every-3-min fleet-cache warm (probeLocalFleetAuth -> here) would
|
|
1280
|
-
// stampede that one rotating token and silently invalidate every other
|
|
1281
|
-
// holder, dropping the fleet to "run /login" (RUSH-1822). Mirror the sibling
|
|
1282
|
-
// Kimi/Droid probes, which never refresh: if the stored token is within the
|
|
1283
|
-
// refresh leeway of expiry, report the non-fatal `expired` state ("would need
|
|
1284
|
-
// a refresh") instead of rotating it, and leave the single legitimate refresh
|
|
1285
|
-
// to the run/usage hot path (getClaudeAccessToken).
|
|
1286
850
|
if (claudeAccessTokenNeedsRefresh(oauth?.expiresAt ?? null)) {
|
|
1287
851
|
return { status: null, token: 'expired' };
|
|
1288
852
|
}
|
|
1289
|
-
// A probe is a request like any other: while the provider's Retry-After
|
|
1290
|
-
// window is open, report the throttle from the recorded state instead of
|
|
1291
|
-
// firing again and re-arming it (usage-backoff.ts). This 3-min-cadence
|
|
1292
|
-
// probe is what created the loop it now respects.
|
|
1293
853
|
if (usageRateLimitedUntil('claude', Date.now(), usageScope))
|
|
1294
854
|
return { status: 429, token: 'present' };
|
|
1295
855
|
try {
|
|
@@ -1306,15 +866,12 @@ export async function probeClaudeStatus(home, cliVersion, usageScope, signal) {
|
|
|
1306
866
|
if (response.status === 429) {
|
|
1307
867
|
noteUsageRateLimited('claude', response.headers.get('retry-after'), { account: usageScope });
|
|
1308
868
|
}
|
|
1309
|
-
// Setup-token is user:inference only; usage needs user:profile → 403.
|
|
1310
|
-
// That is NOT a revocation — the account still runs (RUSH-2392).
|
|
1311
869
|
if (response.status === 403) {
|
|
1312
870
|
let bodyText = '';
|
|
1313
871
|
try {
|
|
1314
872
|
bodyText = await response.text();
|
|
1315
873
|
}
|
|
1316
874
|
catch {
|
|
1317
|
-
// Body unreadable: fall through to a bare 403 (classified as revoked).
|
|
1318
875
|
}
|
|
1319
876
|
if (isClaudeUsageScopeDenied(response.status, bodyText)) {
|
|
1320
877
|
return {
|
|
@@ -1331,7 +888,6 @@ export async function probeClaudeStatus(home, cliVersion, usageScope, signal) {
|
|
|
1331
888
|
return { status: null, token: 'present', error: err instanceof Error ? err.message : String(err) };
|
|
1332
889
|
}
|
|
1333
890
|
}
|
|
1334
|
-
/** Probe Kimi's OAuth token against the /usages endpoint. Never refreshes (single-use rotation — see getKimiUsageInfo). */
|
|
1335
891
|
export async function probeKimiStatus(home, usageScope, signal) {
|
|
1336
892
|
const credPath = resolveKimiCredentialPath(home);
|
|
1337
893
|
if (!credPath)
|
|
@@ -1350,12 +906,6 @@ export async function probeKimiStatus(home, usageScope, signal) {
|
|
|
1350
906
|
return { status: null, token: 'missing' };
|
|
1351
907
|
if (expiresAt !== null && Date.now() / 1000 >= expiresAt)
|
|
1352
908
|
return { status: null, token: 'expired' };
|
|
1353
|
-
// A probe is a request like any other: while the provider's Retry-After
|
|
1354
|
-
// window is open, report the throttle from the recorded state instead of
|
|
1355
|
-
// firing again and re-arming it (usage-backoff.ts). This 3-min-cadence probe
|
|
1356
|
-
// is what created the loop it now respects. It sits AFTER the local
|
|
1357
|
-
// missing/expired checks — as in probeClaudeStatus and probeDroidStatus — so
|
|
1358
|
-
// a genuinely broken credential is never misreported as merely throttled.
|
|
1359
909
|
if (usageRateLimitedUntil('kimi', Date.now(), usageScope))
|
|
1360
910
|
return { status: 429, token: 'present' };
|
|
1361
911
|
try {
|
|
@@ -1373,7 +923,6 @@ export async function probeKimiStatus(home, usageScope, signal) {
|
|
|
1373
923
|
return { status: null, token: 'present', error: err instanceof Error ? err.message : String(err) };
|
|
1374
924
|
}
|
|
1375
925
|
}
|
|
1376
|
-
/** Probe Droid's WorkOS token against the billing-limits endpoint. Never refreshes (single-use rotation — see getDroidUsageInfo). */
|
|
1377
926
|
export async function probeDroidStatus(home, usageScope, signal) {
|
|
1378
927
|
const cred = decryptDroidAuthPayload(home || os.homedir());
|
|
1379
928
|
const accessToken = cred?.access_token;
|
|
@@ -1382,10 +931,6 @@ export async function probeDroidStatus(home, usageScope, signal) {
|
|
|
1382
931
|
const exp = decodeJwtPayload(accessToken)?.exp;
|
|
1383
932
|
if (typeof exp === 'number' && Date.now() / 1000 >= exp)
|
|
1384
933
|
return { status: null, token: 'expired' };
|
|
1385
|
-
// A probe is a request like any other: while the provider's Retry-After
|
|
1386
|
-
// window is open, report the throttle from the recorded state instead of
|
|
1387
|
-
// firing again and re-arming it (usage-backoff.ts). This 3-min-cadence
|
|
1388
|
-
// probe is what created the loop it now respects.
|
|
1389
934
|
if (usageRateLimitedUntil('droid', Date.now(), usageScope))
|
|
1390
935
|
return { status: 429, token: 'present' };
|
|
1391
936
|
try {
|
|
@@ -1403,12 +948,6 @@ export async function probeDroidStatus(home, usageScope, signal) {
|
|
|
1403
948
|
return { status: null, token: 'present', error: err instanceof Error ? err.message : String(err) };
|
|
1404
949
|
}
|
|
1405
950
|
}
|
|
1406
|
-
/**
|
|
1407
|
-
* Normalize the Factory billing-limits payload into the common UsageWindow
|
|
1408
|
-
* shape. Orgs on the legacy (non token-rate-limit) billing model have no
|
|
1409
|
-
* meaningful windows, so they render nothing — mirrors droid's own gate on
|
|
1410
|
-
* `usesTokenRateLimitsBilling` before it reads `limits.standard`.
|
|
1411
|
-
*/
|
|
1412
951
|
export function normalizeDroidWindows(data) {
|
|
1413
952
|
if (data.usesTokenRateLimitsBilling !== true)
|
|
1414
953
|
return [];
|
|
@@ -1422,7 +961,6 @@ export function normalizeDroidWindows(data) {
|
|
|
1422
961
|
];
|
|
1423
962
|
return windows.filter((window) => window !== null);
|
|
1424
963
|
}
|
|
1425
|
-
/** Normalize a single Droid billing-limits window. */
|
|
1426
964
|
function normalizeDroidWindow(window, key, label, shortLabel) {
|
|
1427
965
|
const usedPercent = normalizePercent(window?.usedPercent);
|
|
1428
966
|
if (usedPercent === null)
|
|
@@ -1436,14 +974,6 @@ function normalizeDroidWindow(window, key, label, shortLabel) {
|
|
|
1436
974
|
windowMinutes: inferWindowMinutes(key),
|
|
1437
975
|
};
|
|
1438
976
|
}
|
|
1439
|
-
/**
|
|
1440
|
-
* Collect Codex JSONL session files sorted newest-first.
|
|
1441
|
-
*
|
|
1442
|
-
* `sinceMs` drops files modified before it. Codex session transcripts are not
|
|
1443
|
-
* tagged with the account that wrote them, so this mtime floor is how usage is
|
|
1444
|
-
* kept account-scoped: a session older than the current login belongs to a
|
|
1445
|
-
* prior account (see {@link getCodexUsageInfo}).
|
|
1446
|
-
*/
|
|
1447
977
|
function collectCodexSessionFiles(home, sinceMs) {
|
|
1448
978
|
const base = home || os.homedir();
|
|
1449
979
|
const dir = path.join(base, '.codex', 'sessions');
|
|
@@ -1466,7 +996,6 @@ function collectCodexSessionFiles(home, sinceMs) {
|
|
|
1466
996
|
files.sort((a, b) => b.mtime - a.mtime);
|
|
1467
997
|
return files.map((file) => file.path);
|
|
1468
998
|
}
|
|
1469
|
-
/** Stream a Codex JSONL file and return the last rate_limits payload found. */
|
|
1470
999
|
async function readLatestCodexRateLimits(filePath) {
|
|
1471
1000
|
return new Promise((resolve) => {
|
|
1472
1001
|
let latest = null;
|
|
@@ -1486,21 +1015,18 @@ async function readLatestCodexRateLimits(filePath) {
|
|
|
1486
1015
|
};
|
|
1487
1016
|
}
|
|
1488
1017
|
catch {
|
|
1489
|
-
/* malformed session line */
|
|
1490
1018
|
}
|
|
1491
1019
|
});
|
|
1492
1020
|
rl.on('close', () => resolve(latest));
|
|
1493
1021
|
rl.on('error', () => resolve(latest));
|
|
1494
1022
|
});
|
|
1495
1023
|
}
|
|
1496
|
-
/** Normalize Codex rate-limit windows into the common UsageWindow shape. */
|
|
1497
1024
|
function normalizeCodexWindows(rateLimits) {
|
|
1498
1025
|
return [rateLimits.primary, rateLimits.secondary]
|
|
1499
1026
|
.map(normalizeCodexWindow)
|
|
1500
1027
|
.filter((window) => window !== null)
|
|
1501
1028
|
.sort((a, b) => (a.windowMinutes ?? 0) - (b.windowMinutes ?? 0));
|
|
1502
1029
|
}
|
|
1503
|
-
/** Normalize a single Codex rate-limit window. */
|
|
1504
1030
|
function normalizeCodexWindow(window) {
|
|
1505
1031
|
const usedPercent = normalizePercent(window?.used_percent);
|
|
1506
1032
|
if (usedPercent === null)
|
|
@@ -1516,7 +1042,6 @@ function normalizeCodexWindow(window) {
|
|
|
1516
1042
|
windowMinutes,
|
|
1517
1043
|
};
|
|
1518
1044
|
}
|
|
1519
|
-
/** Codex assigns quota windows to primary/secondary by plan, so duration carries their meaning. */
|
|
1520
1045
|
function classifyCodexWindow(windowMinutes) {
|
|
1521
1046
|
if (windowMinutes !== null && windowMinutes >= 28 * 24 * 60) {
|
|
1522
1047
|
return { key: 'month', label: 'Current month', shortLabel: 'M' };
|
|
@@ -1526,7 +1051,6 @@ function classifyCodexWindow(windowMinutes) {
|
|
|
1526
1051
|
}
|
|
1527
1052
|
return { key: 'session', label: 'Current session', shortLabel: 'S' };
|
|
1528
1053
|
}
|
|
1529
|
-
/** Normalize Claude API usage windows into the common UsageWindow shape. */
|
|
1530
1054
|
function normalizeClaudeWindows(data) {
|
|
1531
1055
|
const windows = [
|
|
1532
1056
|
normalizeClaudeWindow(data.five_hour, 'session', 'Current session', 'S'),
|
|
@@ -1535,7 +1059,6 @@ function normalizeClaudeWindows(data) {
|
|
|
1535
1059
|
];
|
|
1536
1060
|
return windows.filter((window) => window !== null);
|
|
1537
1061
|
}
|
|
1538
|
-
/** Normalize a single Claude API usage window. */
|
|
1539
1062
|
function normalizeClaudeWindow(window, key, label, shortLabel) {
|
|
1540
1063
|
const usedPercent = normalizePercent(window?.utilization);
|
|
1541
1064
|
if (usedPercent === null)
|
|
@@ -1549,12 +1072,6 @@ function normalizeClaudeWindow(window, key, label, shortLabel) {
|
|
|
1549
1072
|
windowMinutes: inferWindowMinutes(key),
|
|
1550
1073
|
};
|
|
1551
1074
|
}
|
|
1552
|
-
/**
|
|
1553
|
-
* Parse a wrapped Claude OAuth payload — the `{ claudeAiOauth, organizationUuid }`
|
|
1554
|
-
* shape written by BOTH the macOS Keychain item and the Linux `.credentials.json`
|
|
1555
|
-
* file — into our credential struct. Returns null when there is no usable access
|
|
1556
|
-
* token. Never throws (malformed JSON => null).
|
|
1557
|
-
*/
|
|
1558
1075
|
function parseClaudeOauthPayload(raw) {
|
|
1559
1076
|
try {
|
|
1560
1077
|
const payload = JSON.parse(raw.trim());
|
|
@@ -1570,69 +1087,18 @@ function parseClaudeOauthPayload(raw) {
|
|
|
1570
1087
|
return null;
|
|
1571
1088
|
}
|
|
1572
1089
|
}
|
|
1573
|
-
// ── Stale no-ACL Claude OAuth cache eviction (retired subsystem) ──
|
|
1574
|
-
//
|
|
1575
|
-
// Earlier versions cached Claude's OAuth ACCESS token in a device-local no-ACL
|
|
1576
|
-
// keychain item so a read-only usage/probe read wouldn't pop the macOS Touch ID
|
|
1577
|
-
// prompt that the ACL-bound source item (`Claude Code-credentials-<hash>`) forces.
|
|
1578
|
-
// That cache is retired: read-only probes now authenticate ONLY with a file-based
|
|
1579
|
-
// setup-token and never read the interactive login (see loadClaudeOauth), so
|
|
1580
|
-
// nothing populates the cache anymore. deleteCachedClaudeOauth remains — a
|
|
1581
|
-
// credential rotation still evicts a stale item an earlier version may have
|
|
1582
|
-
// written, so an old no-ACL copy of the interactive token can't linger.
|
|
1583
1090
|
const CLAUDE_OAUTH_CACHE_PREFIX = 'agents-cli.claude-oauth-cache.';
|
|
1584
|
-
/** The no-ACL cache item name for a Claude keychain service (hashed to stay tidy). */
|
|
1585
1091
|
function claudeOauthCacheItem(service) {
|
|
1586
1092
|
const hash = createHash('sha256').update(service).digest('hex').slice(0, 16);
|
|
1587
1093
|
return `${CLAUDE_OAUTH_CACHE_PREFIX}${hash}`;
|
|
1588
1094
|
}
|
|
1589
|
-
/**
|
|
1590
|
-
* Evict any no-ACL access-token cache item so a source rotation or sign-out is
|
|
1591
|
-
* reflected immediately. The cache itself is retired — read-only probes no longer
|
|
1592
|
-
* read or write it (loadClaudeOauth returns a file-based setup-token or nothing) —
|
|
1593
|
-
* but this eviction remains so a credential rotation still clears a stale cache
|
|
1594
|
-
* item that an earlier agents-cli version may have written no-ACL.
|
|
1595
|
-
*/
|
|
1596
1095
|
function deleteCachedClaudeOauth(service) {
|
|
1597
1096
|
try {
|
|
1598
1097
|
deleteKeychainTokenSync(claudeOauthCacheItem(service));
|
|
1599
1098
|
}
|
|
1600
1099
|
catch {
|
|
1601
|
-
/* best-effort — cache is an optimization */
|
|
1602
1100
|
}
|
|
1603
1101
|
}
|
|
1604
|
-
/**
|
|
1605
|
-
* Load a version home's Claude OAuth credential from the two stores Claude Code
|
|
1606
|
-
* uses, tried in order:
|
|
1607
|
-
*
|
|
1608
|
-
* 1. The OS keychain (`getKeychainToken`). Canonical on macOS — Claude Code
|
|
1609
|
-
* writes the token to the login keychain and we read it via `/usr/bin/security`.
|
|
1610
|
-
* 2. `<home>/.claude/.credentials.json`. On a headless Linux box (the
|
|
1611
|
-
* `agents view --device <linux>` case) there is no reachable Secret Service, so
|
|
1612
|
-
* the Claude CLI stores its OAuth token in this plaintext file instead. The
|
|
1613
|
-
* keychain read above finds nothing on that platform, so we fall back to the
|
|
1614
|
-
* file. Same wrapped `{ claudeAiOauth }` shape, so one parser handles both.
|
|
1615
|
-
*
|
|
1616
|
-
* Without step 2 the live usage fetch got no token on Linux, so `agents view`
|
|
1617
|
-
* (run remotely over SSH by `--device`) rendered no usage bars even though the
|
|
1618
|
-
* account + plan — read from the plaintext `.claude.json` — showed fine.
|
|
1619
|
-
*
|
|
1620
|
-
* `opts.accessTokenCache` marks a read-only, access-token-only consumer (the
|
|
1621
|
-
* usage fetch and the auth-health probe). Such a caller authenticates ONLY with
|
|
1622
|
-
* a file-based setup-token and, when none is provisioned, gets `null` — it never
|
|
1623
|
-
* reads Claude Code's interactive login (transmitting that ACL-bound OAuth token
|
|
1624
|
-
* to Anthropic's API is what gets it revoked; see the branch body and
|
|
1625
|
-
* docs/secrets.md). It is OFF by default so full-credential
|
|
1626
|
-
* callers that refresh (`isClaudeAuthValid` -> `getClaudeAccessToken`) still
|
|
1627
|
-
* read the interactive login. Rush Cloud dispatch does not call this helper
|
|
1628
|
-
* at all (SING-1b: the account manifest is email-only; RUSH-2359 removed the
|
|
1629
|
-
* leftover blob reader that used to send the interactive login).
|
|
1630
|
-
*
|
|
1631
|
-
* `opts.fileOnly` skips the ACL keychain read entirely — setup-token and
|
|
1632
|
-
* `.credentials.json` only. Used by the daemon usage refresher so a background
|
|
1633
|
-
* tick can never pop Touch ID.
|
|
1634
|
-
*/
|
|
1635
|
-
/** True when `<home>/.claude/.credentials.json` is a native rotating OAuth blob. */
|
|
1636
1102
|
export function claudeHomeHasNativeOauthFile(home) {
|
|
1637
1103
|
return readClaudeNativeCredentialsFile(home) !== null;
|
|
1638
1104
|
}
|
|
@@ -1642,8 +1108,6 @@ function readClaudeNativeCredentialsFile(home) {
|
|
|
1642
1108
|
if (!fs.existsSync(credsPath))
|
|
1643
1109
|
return null;
|
|
1644
1110
|
const parsed = parseClaudeOauthPayload(fs.readFileSync(credsPath, 'utf-8'));
|
|
1645
|
-
// A native rotating blob has a refresh token. A setup-token-shaped file
|
|
1646
|
-
// (access only) is not a native login and must not be polled as one.
|
|
1647
1111
|
if (!parsed?.accessToken?.trim() || !parsed.refreshToken?.trim())
|
|
1648
1112
|
return null;
|
|
1649
1113
|
return parsed;
|
|
@@ -1653,55 +1117,18 @@ function readClaudeNativeCredentialsFile(home) {
|
|
|
1653
1117
|
}
|
|
1654
1118
|
}
|
|
1655
1119
|
export async function loadClaudeOauth(home, opts) {
|
|
1656
|
-
// Headed usage poller: the native rotating blob in `.credentials.json` is
|
|
1657
|
-
// the only credential that carries `user:profile` without opening the ACL
|
|
1658
|
-
// keychain (Touch ID) or firing a setup-token that 403s on /oauth/usage.
|
|
1659
1120
|
if (opts?.nativeFileLogin === true) {
|
|
1660
1121
|
return readClaudeNativeCredentialsFile(home);
|
|
1661
1122
|
}
|
|
1662
|
-
// Read-only usage/probe callers (accessTokenCache) authenticate ONLY with a
|
|
1663
|
-
// file-based setup-token from the `auth` bundle — never Claude Code's
|
|
1664
|
-
// interactive login. The usage endpoint accepts any sk-ant-oat01 bearer, and
|
|
1665
|
-
// the file-based token never pops Touch ID. When no setup-token is provisioned
|
|
1666
|
-
// the probe reports unprovisioned rather than reading the interactive
|
|
1667
|
-
// credential (see below) — that is the whole point of this branch.
|
|
1668
1123
|
if (opts?.accessTokenCache === true) {
|
|
1669
1124
|
const setupToken = resolveClaudeSetupToken(home);
|
|
1670
1125
|
if (setupToken) {
|
|
1671
|
-
// No expiresAt: a setup-token is long-lived and non-rotating, and a null
|
|
1672
|
-
// expiry reads as "still fresh" (claudeAccessTokenNeedsRefresh) so the
|
|
1673
|
-
// probe never reports it expired or tries to refresh it. The endpoint is
|
|
1674
|
-
// the source of truth if it has actually been revoked.
|
|
1675
1126
|
return { accessToken: setupToken };
|
|
1676
1127
|
}
|
|
1677
|
-
// No provisioned setup-token. A read-only usage/health probe MUST NOT fall
|
|
1678
|
-
// through to Claude Code's interactive login credential. The daemon's usage
|
|
1679
|
-
// (~60s) and auth-health (~3min) warms would otherwise read the ACL-bound
|
|
1680
|
-
// OAuth token and transmit it to api.anthropic.com/api/oauth/usage — an
|
|
1681
|
-
// interactive credential used programmatically, which Anthropic flags and
|
|
1682
|
-
// revokes (the fleet-wide-logout class, RUSH-1822), and which violates the
|
|
1683
|
-
// invariant that the interactive/rotating login is untouchable
|
|
1684
|
-
// (docs/secrets.md). Report unprovisioned (-> probe
|
|
1685
|
-
// token 'missing' -> auth-health 'unconfigured', benign for rotation); seed a
|
|
1686
|
-
// setup-token via the mint-auth path to restore usage/probe for the account.
|
|
1687
|
-
//
|
|
1688
|
-
// The single sanctioned exception (USAGE-READ-1/2): a foreground human
|
|
1689
|
-
// `agents view` on a headed device (personal or desktop) sets allowInteractiveLogin, and only
|
|
1690
|
-
// then do we fall through to the interactive-login read below — the one
|
|
1691
|
-
// credential carrying `user:profile`, which the usage endpoint requires. This
|
|
1692
|
-
// is a human running one command, not an unattended loop, so it is not the
|
|
1693
|
-
// revocation risk RUSH-1822 fixed. Every background caller leaves the flag
|
|
1694
|
-
// unset and still returns null here.
|
|
1695
1128
|
if (opts?.allowInteractiveLogin !== true) {
|
|
1696
1129
|
return null;
|
|
1697
1130
|
}
|
|
1698
1131
|
}
|
|
1699
|
-
// Full-credential callers (isClaudeAuthValid -> getClaudeAccessToken)
|
|
1700
|
-
// legitimately read the interactive login to run/refresh Claude. Rush Cloud
|
|
1701
|
-
// dispatch does not (SING-1b / RUSH-2359). The OS keychain/keyring step is
|
|
1702
|
-
// macOS/Linux-only; Windows and any
|
|
1703
|
-
// fileOnly caller skip to the .credentials.json read below (the Claude CLI
|
|
1704
|
-
// stores its OAuth token in that file too).
|
|
1705
1132
|
if (!opts?.fileOnly && (process.platform === 'darwin' || process.platform === 'linux')) {
|
|
1706
1133
|
const service = getClaudeKeychainService(home);
|
|
1707
1134
|
try {
|
|
@@ -1710,7 +1137,6 @@ export async function loadClaudeOauth(home, opts) {
|
|
|
1710
1137
|
return fromKeychain;
|
|
1711
1138
|
}
|
|
1712
1139
|
catch {
|
|
1713
|
-
// No keychain item, or no reachable keyring (headless Linux) — fall through.
|
|
1714
1140
|
}
|
|
1715
1141
|
}
|
|
1716
1142
|
const credsPath = path.join(home ?? os.homedir(), '.claude', '.credentials.json');
|
|
@@ -1720,33 +1146,22 @@ export async function loadClaudeOauth(home, opts) {
|
|
|
1720
1146
|
}
|
|
1721
1147
|
}
|
|
1722
1148
|
catch {
|
|
1723
|
-
// Unreadable file — treat as not signed in.
|
|
1724
1149
|
}
|
|
1725
1150
|
return null;
|
|
1726
1151
|
}
|
|
1727
|
-
/**
|
|
1728
|
-
* Save Claude OAuth credentials to the system keychain/keyring.
|
|
1729
|
-
* Reads the existing payload, merges the new OAuth fields, and writes back.
|
|
1730
|
-
* Exported for regression tests; not part of the public command surface.
|
|
1731
|
-
*/
|
|
1732
1152
|
async function saveClaudeOauth(home, credentials) {
|
|
1733
|
-
// Windows not yet supported: Claude Code keeps its credential in the file
|
|
1734
|
-
// there, so the rotated credential is written by the harness, not here.
|
|
1735
1153
|
if (process.platform !== 'darwin' && process.platform !== 'linux') {
|
|
1736
1154
|
return false;
|
|
1737
1155
|
}
|
|
1738
1156
|
try {
|
|
1739
1157
|
const service = getClaudeKeychainService(home);
|
|
1740
|
-
// Read existing payload to preserve other fields
|
|
1741
1158
|
let existingPayload = {};
|
|
1742
1159
|
try {
|
|
1743
1160
|
const stdout = getKeychainTokenSync(service);
|
|
1744
1161
|
existingPayload = JSON.parse(stdout.trim());
|
|
1745
1162
|
}
|
|
1746
1163
|
catch {
|
|
1747
|
-
// No existing entry, start fresh
|
|
1748
1164
|
}
|
|
1749
|
-
// Merge new credentials into existing payload
|
|
1750
1165
|
const newPayload = {
|
|
1751
1166
|
...existingPayload,
|
|
1752
1167
|
claudeAiOauth: {
|
|
@@ -1758,15 +1173,12 @@ async function saveClaudeOauth(home, credentials) {
|
|
|
1758
1173
|
},
|
|
1759
1174
|
};
|
|
1760
1175
|
const payloadJson = JSON.stringify(newPayload);
|
|
1761
|
-
// Delete existing entry first, then add updated entry
|
|
1762
1176
|
try {
|
|
1763
1177
|
await deleteKeychainToken(service);
|
|
1764
1178
|
}
|
|
1765
1179
|
catch {
|
|
1766
|
-
// Entry might not exist, ignore
|
|
1767
1180
|
}
|
|
1768
1181
|
await setKeychainToken(service, payloadJson);
|
|
1769
|
-
// A new credential rotation means any cached access token is stale.
|
|
1770
1182
|
deleteCachedClaudeOauth(service);
|
|
1771
1183
|
return true;
|
|
1772
1184
|
}
|
|
@@ -1774,10 +1186,6 @@ async function saveClaudeOauth(home, credentials) {
|
|
|
1774
1186
|
return false;
|
|
1775
1187
|
}
|
|
1776
1188
|
}
|
|
1777
|
-
/**
|
|
1778
|
-
* Derive the Keychain service name for a Claude home directory.
|
|
1779
|
-
* Managed (non-default) homes get a hash suffix for isolation.
|
|
1780
|
-
*/
|
|
1781
1189
|
export function getClaudeKeychainService(home) {
|
|
1782
1190
|
if (!home) {
|
|
1783
1191
|
return CLAUDE_KEYCHAIN_SERVICE;
|
|
@@ -1786,16 +1194,12 @@ export function getClaudeKeychainService(home) {
|
|
|
1786
1194
|
const hash = createHash('sha256').update(configDir).digest('hex').slice(0, 8);
|
|
1787
1195
|
return `${CLAUDE_KEYCHAIN_SERVICE}-${hash}`;
|
|
1788
1196
|
}
|
|
1789
|
-
|
|
1790
|
-
* Check whether a requested org ID matches the live OAuth org ID.
|
|
1791
|
-
* Returns true when either is absent (no filtering) or when they match.
|
|
1792
|
-
*/
|
|
1197
|
+
// Organization identity guards attribution because different accounts can share email and home labels.
|
|
1793
1198
|
export function isClaudeUsageOrgMatch(requestedOrgId, liveOrgId) {
|
|
1794
1199
|
const requested = normalizeString(requestedOrgId);
|
|
1795
1200
|
const live = normalizeString(liveOrgId);
|
|
1796
1201
|
return !requested || !live || requested === live;
|
|
1797
1202
|
}
|
|
1798
|
-
/** Read a cached usage snapshot for a given usage key. Returns null if absent or stale. */
|
|
1799
1203
|
export function readClaudeUsageCache(usageKey, cachePath = getClaudeUsageCachePath(), now = new Date()) {
|
|
1800
1204
|
const cache = readClaudeUsageCacheFile(cachePath);
|
|
1801
1205
|
const cached = cache[usageKey];
|
|
@@ -1808,13 +1212,10 @@ export function readClaudeUsageCache(usageKey, cachePath = getClaudeUsageCachePa
|
|
|
1808
1212
|
}
|
|
1809
1213
|
return snapshot;
|
|
1810
1214
|
}
|
|
1811
|
-
/** Delete an expired cache row only if it is still expired under the write lock. */
|
|
1812
1215
|
export function pruneExpiredClaudeUsageCacheEntry(usageKey, cachePath = getClaudeUsageCachePath(), now = new Date()) {
|
|
1813
1216
|
try {
|
|
1814
1217
|
ensureLockTarget(cachePath, '{}');
|
|
1815
1218
|
withFileLock(cachePath, () => {
|
|
1816
|
-
// The row may have been refreshed after readClaudeUsageCache observed it.
|
|
1817
|
-
// Re-read under the lock so stale cleanup cannot erase that newer write.
|
|
1818
1219
|
const latest = readClaudeUsageCacheFile(cachePath);
|
|
1819
1220
|
const current = latest[usageKey];
|
|
1820
1221
|
if (!current || deserializeClaudeUsageSnapshot(current, now))
|
|
@@ -1824,16 +1225,12 @@ export function pruneExpiredClaudeUsageCacheEntry(usageKey, cachePath = getClaud
|
|
|
1824
1225
|
});
|
|
1825
1226
|
}
|
|
1826
1227
|
catch {
|
|
1827
|
-
/* best-effort cache cleanup — lock busy or disk full */
|
|
1828
1228
|
}
|
|
1829
1229
|
}
|
|
1830
|
-
/** Write a usage snapshot to the on-disk cache. */
|
|
1831
1230
|
export function writeClaudeUsageCache(usageKey, snapshot, cachePath = getClaudeUsageCachePath()) {
|
|
1832
1231
|
try {
|
|
1833
1232
|
ensureLockTarget(cachePath, '{}');
|
|
1834
1233
|
withFileLock(cachePath, () => {
|
|
1835
|
-
// Re-read under the lock so a concurrent daemon tick / agents view
|
|
1836
|
-
// refresh cannot drop another account's row (lost update).
|
|
1837
1234
|
const cache = readClaudeUsageCacheFile(cachePath);
|
|
1838
1235
|
const prior = cache[usageKey];
|
|
1839
1236
|
cache[usageKey] = {
|
|
@@ -1841,20 +1238,14 @@ export function writeClaudeUsageCache(usageKey, snapshot, cachePath = getClaudeU
|
|
|
1841
1238
|
...snapshot,
|
|
1842
1239
|
unavailable: carryForwardUnavailable(prior?.unavailable, snapshot.unavailable),
|
|
1843
1240
|
}),
|
|
1844
|
-
// A per-model refusal has its own independent lifecycle (see
|
|
1845
|
-
// noteClaudeModelRefusal / clearClaudeModelRefusal) — a global usage
|
|
1846
|
-
// write must never drop it, the same reason `unavailable` is carried
|
|
1847
|
-
// forward above rather than overwritten.
|
|
1848
1241
|
modelRefusals: prior?.modelRefusals,
|
|
1849
1242
|
};
|
|
1850
1243
|
atomicWriteFileSync(cachePath, JSON.stringify(cache, null, 2), 'utf-8');
|
|
1851
1244
|
});
|
|
1852
1245
|
}
|
|
1853
1246
|
catch {
|
|
1854
|
-
/* best-effort cache write — lock busy or disk full */
|
|
1855
1247
|
}
|
|
1856
1248
|
}
|
|
1857
|
-
/** Atomically merge partial native Claude windows into the current fresh row. */
|
|
1858
1249
|
export function mergeClaudeUsageCacheWindows(usageKey, snapshot, cachePath = getClaudeUsageCachePath()) {
|
|
1859
1250
|
try {
|
|
1860
1251
|
ensureLockTarget(cachePath, '{}');
|
|
@@ -1874,22 +1265,14 @@ export function mergeClaudeUsageCacheWindows(usageKey, snapshot, cachePath = get
|
|
|
1874
1265
|
plan: snapshot.plan ?? priorSnapshot?.plan ?? null,
|
|
1875
1266
|
unavailable: carryForwardUnavailable(prior?.unavailable, snapshot.unavailable),
|
|
1876
1267
|
}),
|
|
1877
|
-
// See writeClaudeUsageCache: a per-model refusal survives a windows-only merge too.
|
|
1878
1268
|
modelRefusals: prior?.modelRefusals,
|
|
1879
1269
|
};
|
|
1880
1270
|
atomicWriteFileSync(cachePath, JSON.stringify(cache, null, 2), 'utf-8');
|
|
1881
1271
|
});
|
|
1882
1272
|
}
|
|
1883
1273
|
catch {
|
|
1884
|
-
/* best-effort cache write — lock busy or disk full */
|
|
1885
1274
|
}
|
|
1886
1275
|
}
|
|
1887
|
-
/**
|
|
1888
|
-
* Export the local usage cache rows worth publishing to fleet peers (PHNX-3392
|
|
1889
|
-
* usage-sync). Returns the raw serialized rows keyed by usage identity, filtered
|
|
1890
|
-
* to those carrying at least one window — an empty row has nothing to teach a
|
|
1891
|
-
* worker. The transport is the on-disk cache form, so there is no Date round-trip.
|
|
1892
|
-
*/
|
|
1893
1276
|
export function exportClaudeUsageCacheRows(cachePath = getClaudeUsageCachePath()) {
|
|
1894
1277
|
const cache = readClaudeUsageCacheFile(cachePath);
|
|
1895
1278
|
const out = {};
|
|
@@ -1905,26 +1288,6 @@ function parseCapturedAtMs(capturedAt) {
|
|
|
1905
1288
|
const ms = Date.parse(capturedAt);
|
|
1906
1289
|
return Number.isFinite(ms) ? ms : null;
|
|
1907
1290
|
}
|
|
1908
|
-
/**
|
|
1909
|
-
* Merge usage rows received from a fleet peer into the local cache, NEWEST-WINS
|
|
1910
|
-
* per identity by `capturedAt` (PHNX-3392 usage-sync). A worker has no local
|
|
1911
|
-
* usage writer, so an incoming row is almost always the freshest it will get; the
|
|
1912
|
-
* timestamp guard exists so a stale push from one headed peer can never overwrite
|
|
1913
|
-
* a fresher row another peer (or, on a headed receiver, the local status-line)
|
|
1914
|
-
* already wrote. An incoming row with no `capturedAt` cannot prove it is newer, so
|
|
1915
|
-
* it never displaces an existing timestamped row. Returns the count updated.
|
|
1916
|
-
* Locked + atomic like every other cache writer, but through the ASYNC lock: it
|
|
1917
|
-
* runs on the daemon's usage-sync tick once per peer reply (PHNX-4116), where
|
|
1918
|
-
* the sync lock's `sleepSync` would freeze every other service under contention.
|
|
1919
|
-
*
|
|
1920
|
-
* Deliberately NOT role-gated on the receiver. "Consume only on worker/unmarked"
|
|
1921
|
-
* is a SENDER-side optimization (don't waste a push on a headed peer that reads
|
|
1922
|
-
* its own usage), not a safety invariant — the actual safety property is this
|
|
1923
|
-
* newest-wins guard. Receiving on a headed box is harmless (its fresher local
|
|
1924
|
-
* status-line row survives) or helpful (an account it is signed into but never
|
|
1925
|
-
* runs now shows a usage bar), so gating here on the receiver's own — laggier —
|
|
1926
|
-
* view of its role would only reject legitimate data.
|
|
1927
|
-
*/
|
|
1928
1291
|
export async function ingestPeerClaudeUsageRows(rows, cachePath = getClaudeUsageCachePath()) {
|
|
1929
1292
|
const incoming = Object.entries(rows).filter(([, row]) => row && Array.isArray(row.windows) && row.windows.length > 0);
|
|
1930
1293
|
if (incoming.length === 0)
|
|
@@ -1939,7 +1302,6 @@ export async function ingestPeerClaudeUsageRows(rows, cachePath = getClaudeUsage
|
|
|
1939
1302
|
if (prior) {
|
|
1940
1303
|
const priorMs = parseCapturedAtMs(prior.capturedAt);
|
|
1941
1304
|
const incomingMs = parseCapturedAtMs(row.capturedAt);
|
|
1942
|
-
// Keep local unless the incoming row PROVES it is strictly newer.
|
|
1943
1305
|
if (incomingMs === null)
|
|
1944
1306
|
continue;
|
|
1945
1307
|
if (priorMs !== null && priorMs >= incomingMs)
|
|
@@ -1953,11 +1315,9 @@ export async function ingestPeerClaudeUsageRows(rows, cachePath = getClaudeUsage
|
|
|
1953
1315
|
});
|
|
1954
1316
|
}
|
|
1955
1317
|
catch {
|
|
1956
|
-
/* best-effort cache write — lock busy or disk full */
|
|
1957
1318
|
}
|
|
1958
1319
|
return merged;
|
|
1959
1320
|
}
|
|
1960
|
-
/** Read the entire usage cache file from disk. */
|
|
1961
1321
|
function readClaudeUsageCacheFile(cachePath) {
|
|
1962
1322
|
if (!fs.existsSync(cachePath)) {
|
|
1963
1323
|
return {};
|
|
@@ -1970,28 +1330,15 @@ function readClaudeUsageCacheFile(cachePath) {
|
|
|
1970
1330
|
return {};
|
|
1971
1331
|
}
|
|
1972
1332
|
}
|
|
1973
|
-
/** Write the entire usage cache to disk. Best-effort; failures are silent. */
|
|
1974
1333
|
function writeClaudeUsageCacheFile(cache, cachePath) {
|
|
1975
1334
|
try {
|
|
1976
1335
|
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
|
|
1977
1336
|
atomicWriteFileSync(cachePath, JSON.stringify(cache, null, 2), 'utf-8');
|
|
1978
1337
|
}
|
|
1979
1338
|
catch {
|
|
1980
|
-
/* best-effort cache write */
|
|
1981
1339
|
}
|
|
1982
1340
|
}
|
|
1983
|
-
/** Convert a live UsageSnapshot to its JSON-serializable cached form. */
|
|
1984
1341
|
function serializeClaudeUsageSnapshot(snapshot) {
|
|
1985
|
-
// Persist the union of fresh `windows` and last-known `staleWindows`.
|
|
1986
|
-
// `deserializeClaudeUsageSnapshot` re-runs the freshness gate on read and
|
|
1987
|
-
// re-partitions the serialized windows into fresh vs. stale, so what matters
|
|
1988
|
-
// is that every last-known reading reaches disk. Claude's collector returns
|
|
1989
|
-
// raw windows (no `staleWindows`) and relies on that read-side partition. But
|
|
1990
|
-
// Grok's collector pre-partitions in the fetch, moving an ended-period reading
|
|
1991
|
-
// onto `staleWindows` — serializing only `windows` dropped it, so the very
|
|
1992
|
-
// number a daemon `--refresh` just captured was gone from the next cached
|
|
1993
|
-
// `agents view grok`, which rendered the plan alone (no bar). Include the
|
|
1994
|
-
// stale windows here so the round-trip preserves them for any collector.
|
|
1995
1342
|
const persistedWindows = [...snapshot.windows, ...(snapshot.staleWindows ?? [])];
|
|
1996
1343
|
return {
|
|
1997
1344
|
capturedAt: snapshot.capturedAt?.toISOString() || null,
|
|
@@ -2015,24 +1362,6 @@ function serializeClaudeUsageSnapshot(snapshot) {
|
|
|
2015
1362
|
pollerDevice: snapshot.freshness?.poller,
|
|
2016
1363
|
};
|
|
2017
1364
|
}
|
|
2018
|
-
/**
|
|
2019
|
-
* Deserialize a cached snapshot, dropping windows whose reset time has passed.
|
|
2020
|
-
*
|
|
2021
|
-
* An expired window is UNKNOWN, not 0%: the counter reset, and anything may
|
|
2022
|
-
* have burned since. Zeroing-but-keeping it (the previous behavior) rendered a
|
|
2023
|
-
* weeks-frozen cache as "S: 0% (now)" with `deriveUsageStatusFromSnapshot` →
|
|
2024
|
-
* 'available', so a genuinely rate-limited account read as an idle dispatch
|
|
2025
|
-
* candidate (RUSH-2858). Dropping keeps them out of `windows` (routing stays
|
|
2026
|
-
* blind), but they are preserved on `staleWindows` so the view can render the
|
|
2027
|
-
* last-known number with its age instead of a bare "unavailable" — a row that
|
|
2028
|
-
* carries only stale windows therefore survives (it is worth showing), and only
|
|
2029
|
-
* a row with NOTHING to show — no fresh window, no stale window, no plan, no
|
|
2030
|
-
* refusal — deserializes to null so `readClaudeUsageCache` deletes it.
|
|
2031
|
-
*
|
|
2032
|
-
* A row that carries a plan survives even with no fresh windows: the plan is a
|
|
2033
|
-
* truthful reading in its own right, and losing it is what made the cached view
|
|
2034
|
-
* contradict the refreshed one for meterless harnesses. See the guard below.
|
|
2035
|
-
*/
|
|
2036
1365
|
function deserializeClaudeUsageSnapshot(snapshot, now) {
|
|
2037
1366
|
const capturedAt = parseDateValue(snapshot.capturedAt);
|
|
2038
1367
|
const deserialized = snapshot.windows.map((window) => ({
|
|
@@ -2044,23 +1373,9 @@ function deserializeClaudeUsageSnapshot(snapshot, now) {
|
|
|
2044
1373
|
windowMinutes: window.windowMinutes,
|
|
2045
1374
|
}));
|
|
2046
1375
|
const windows = deserialized.filter((window) => isCachedUsageWindowFresh(window, capturedAt, now));
|
|
2047
|
-
// The dropped windows are still the LAST reading we saw for those meters —
|
|
2048
|
-
// routing must not trust them (they stay out of `windows`), but the view
|
|
2049
|
-
// renders them with an age suffix rather than a bare "unavailable" (see
|
|
2050
|
-
// UsageSnapshot.staleWindows). Skip any meter that already has a fresh row.
|
|
2051
1376
|
const freshKeys = new Set(windows.map((window) => window.key));
|
|
2052
1377
|
const staleWindows = deserialized.filter((window) => !freshKeys.has(window.key) && !isCachedUsageWindowFresh(window, capturedAt, now));
|
|
2053
1378
|
const unavailable = deserializeUnavailable(snapshot.unavailable, now);
|
|
2054
|
-
// A windowless row is not automatically worthless. Grok's collector reports
|
|
2055
|
-
// the subscription tier and no meters at all, so treating "no fresh windows"
|
|
2056
|
-
// as "nothing cached" deleted the only truthful thing we knew about the
|
|
2057
|
-
// account: `--refresh` wrote {plan: 'SuperGrok Heavy', windows: []}, the very
|
|
2058
|
-
// next plain `agents view` deserialized it to null, `readClaudeUsageCache`
|
|
2059
|
-
// pruned the row, and the row rendered "usage unavailable" one read after a
|
|
2060
|
-
// successful refresh. Keep a plan-bearing row — it renders as the plan alone,
|
|
2061
|
-
// and `deriveUsageStatusFromSnapshot` still returns null for zero windows, so
|
|
2062
|
-
// it can never read as a 0% bar or an "available" badge (the RUSH-2858
|
|
2063
|
-
// property that made expired windows drop in the first place).
|
|
2064
1379
|
if (windows.length === 0 &&
|
|
2065
1380
|
staleWindows.length === 0 &&
|
|
2066
1381
|
!unavailable &&
|
|
@@ -2083,13 +1398,6 @@ function deserializeClaudeUsageSnapshot(snapshot, now) {
|
|
|
2083
1398
|
: undefined,
|
|
2084
1399
|
};
|
|
2085
1400
|
}
|
|
2086
|
-
/**
|
|
2087
|
-
* Carry a prior refusal marker forward across a daemon usage refresh, and drop
|
|
2088
|
-
* an expired one. A live `snapshot.unavailable` (a refusal just observed) wins.
|
|
2089
|
-
* `out_of_credits` survives refreshes with no reset — only a successful run
|
|
2090
|
-
* clears it (clearClaudeAccountRefusal). A `session_limit` survives only while
|
|
2091
|
-
* its reset time is still in the future.
|
|
2092
|
-
*/
|
|
2093
1401
|
function carryForwardUnavailable(prior, live) {
|
|
2094
1402
|
if (live)
|
|
2095
1403
|
return live;
|
|
@@ -2102,10 +1410,6 @@ function carryForwardUnavailable(prior, live) {
|
|
|
2102
1410
|
? { reason: 'session_limit', resetsAt: reset }
|
|
2103
1411
|
: undefined;
|
|
2104
1412
|
}
|
|
2105
|
-
/**
|
|
2106
|
-
* Deserialize a cached `unavailable` marker, dropping an expired session_limit
|
|
2107
|
-
* but keeping a clock-less out_of_credits.
|
|
2108
|
-
*/
|
|
2109
1413
|
function deserializeUnavailable(cached, now) {
|
|
2110
1414
|
if (!cached)
|
|
2111
1415
|
return undefined;
|
|
@@ -2116,23 +1420,16 @@ function deserializeUnavailable(cached, now) {
|
|
|
2116
1420
|
? { reason: 'session_limit', resetsAt: reset }
|
|
2117
1421
|
: undefined;
|
|
2118
1422
|
}
|
|
2119
|
-
/** Whether any per-model refusal in the row is still live (not clock-expired). */
|
|
2120
1423
|
function hasLiveModelRefusal(modelRefusals, now) {
|
|
2121
1424
|
if (!modelRefusals)
|
|
2122
1425
|
return false;
|
|
2123
1426
|
return Object.values(modelRefusals).some((entry) => {
|
|
2124
1427
|
if (!entry.resetsAt)
|
|
2125
|
-
return true;
|
|
1428
|
+
return true;
|
|
2126
1429
|
const reset = parseDateValue(entry.resetsAt);
|
|
2127
1430
|
return !reset || reset.getTime() > now.getTime();
|
|
2128
1431
|
});
|
|
2129
1432
|
}
|
|
2130
|
-
/**
|
|
2131
|
-
* Persist a Claude tokens/credits exhaustion (`out of usage credits` / `monthly
|
|
2132
|
-
* spend limit`) from a real run. Unlike a rate/session limit this does NOT reset
|
|
2133
|
-
* on a clock, so no reset time is stored — rotation excludes the account until a
|
|
2134
|
-
* later successful run clears it via {@link clearClaudeAccountRefusal}.
|
|
2135
|
-
*/
|
|
2136
1433
|
export function noteClaudeOutOfCredits(usageKey, cachePath = getClaudeUsageCachePath()) {
|
|
2137
1434
|
try {
|
|
2138
1435
|
ensureLockTarget(cachePath, '{}');
|
|
@@ -2144,14 +1441,8 @@ export function noteClaudeOutOfCredits(usageKey, cachePath = getClaudeUsageCache
|
|
|
2144
1441
|
});
|
|
2145
1442
|
}
|
|
2146
1443
|
catch {
|
|
2147
|
-
/* best-effort cache write — lock busy or disk full */
|
|
2148
1444
|
}
|
|
2149
1445
|
}
|
|
2150
|
-
/**
|
|
2151
|
-
* Clear any persisted refusal marker for an account after a run SUCCEEDS on it.
|
|
2152
|
-
* This is the recovery path for `out_of_credits` (which has no clock) and also
|
|
2153
|
-
* proactively clears a stale `session_limit` the moment the account serves again.
|
|
2154
|
-
*/
|
|
2155
1446
|
export function clearClaudeAccountRefusal(usageKey, cachePath = getClaudeUsageCachePath()) {
|
|
2156
1447
|
try {
|
|
2157
1448
|
if (!fs.existsSync(cachePath))
|
|
@@ -2167,13 +1458,8 @@ export function clearClaudeAccountRefusal(usageKey, cachePath = getClaudeUsageCa
|
|
|
2167
1458
|
});
|
|
2168
1459
|
}
|
|
2169
1460
|
catch {
|
|
2170
|
-
/* best-effort cache write */
|
|
2171
1461
|
}
|
|
2172
1462
|
}
|
|
2173
|
-
/**
|
|
2174
|
-
* Persist a Claude session-limit refusal from a real run until its stated reset.
|
|
2175
|
-
* This quota is not part of Anthropic's five-hour/weekly usage response.
|
|
2176
|
-
*/
|
|
2177
1463
|
export function noteClaudeSessionLimit(usageKey, resetsAt, cachePath = getClaudeUsageCachePath()) {
|
|
2178
1464
|
try {
|
|
2179
1465
|
ensureLockTarget(cachePath, '{}');
|
|
@@ -2188,23 +1474,8 @@ export function noteClaudeSessionLimit(usageKey, resetsAt, cachePath = getClaude
|
|
|
2188
1474
|
});
|
|
2189
1475
|
}
|
|
2190
1476
|
catch {
|
|
2191
|
-
/* best-effort cache write — lock busy or disk full */
|
|
2192
1477
|
}
|
|
2193
1478
|
}
|
|
2194
|
-
/**
|
|
2195
|
-
* Persist a Claude MODEL-specific refusal — "You've reached your Fable limit.
|
|
2196
|
-
* Run /usage-credits to continue or switch models with /model." — a distinct
|
|
2197
|
-
* class from {@link noteClaudeOutOfCredits} / {@link noteClaudeSessionLimit}:
|
|
2198
|
-
* those exclude the whole account, this excludes only ONE model on it (an
|
|
2199
|
-
* organization quota group can meter models separately). `accountKey` MUST be
|
|
2200
|
-
* the candidate's stable native-account key (see `candidateAccountKey` in
|
|
2201
|
-
* rotate.ts), never the org-shared `usageKey` — using the org key here would
|
|
2202
|
-
* poison every sibling account under that org for a limit that named one
|
|
2203
|
-
* model on one login. No invented reset: `resetsAt` is written only when the
|
|
2204
|
-
* refusal text carried one; otherwise the marker is sticky until a later
|
|
2205
|
-
* successful run on this exact (account, model) clears it via
|
|
2206
|
-
* {@link clearClaudeModelRefusal}.
|
|
2207
|
-
*/
|
|
2208
1479
|
export function claudeModelRefusalKey(accountId, home) {
|
|
2209
1480
|
if (accountId)
|
|
2210
1481
|
return `account:${accountId}`;
|
|
@@ -2237,16 +1508,8 @@ export function noteClaudeModelRefusal(accountKey, model, refusal, cachePath = g
|
|
|
2237
1508
|
});
|
|
2238
1509
|
}
|
|
2239
1510
|
catch {
|
|
2240
|
-
/* best-effort cache write — lock busy or disk full */
|
|
2241
1511
|
}
|
|
2242
1512
|
}
|
|
2243
|
-
/**
|
|
2244
|
-
* Clear a persisted model-refusal marker for exactly ONE (account, model)
|
|
2245
|
-
* pair after a run SUCCEEDS on that same account+model. Never clears a
|
|
2246
|
-
* sibling model on the same account, and never fires for an interactive
|
|
2247
|
-
* detach or an unknown outcome — the caller must have demonstrated an actual
|
|
2248
|
-
* completed success on this exact model before calling this.
|
|
2249
|
-
*/
|
|
2250
1513
|
export function clearClaudeModelRefusal(accountKey, model, cachePath = getClaudeUsageCachePath()) {
|
|
2251
1514
|
model = modelFamily(model);
|
|
2252
1515
|
try {
|
|
@@ -2267,15 +1530,8 @@ export function clearClaudeModelRefusal(accountKey, model, cachePath = getClaude
|
|
|
2267
1530
|
});
|
|
2268
1531
|
}
|
|
2269
1532
|
catch {
|
|
2270
|
-
/* best-effort cache write */
|
|
2271
1533
|
}
|
|
2272
1534
|
}
|
|
2273
|
-
/**
|
|
2274
|
-
* Read a live (non-expired) model-refusal marker for (accountKey, model), or
|
|
2275
|
-
* null when none is recorded or the recorded one has passed its clock. A
|
|
2276
|
-
* marker with no `resetsAt` never expires here — it is sticky until
|
|
2277
|
-
* {@link clearClaudeModelRefusal} observes a real success.
|
|
2278
|
-
*/
|
|
2279
1535
|
export function getClaudeModelRefusal(accountKey, model, nowMs = Date.now(), cachePath = getClaudeUsageCachePath()) {
|
|
2280
1536
|
const cache = readClaudeUsageCacheFile(cachePath);
|
|
2281
1537
|
const entry = cache[accountKey]?.modelRefusals?.[modelFamily(model)];
|
|
@@ -2289,14 +1545,6 @@ export function getClaudeModelRefusal(accountKey, model, nowMs = Date.now(), cac
|
|
|
2289
1545
|
}
|
|
2290
1546
|
return { family: entry.family, resetsAt: null };
|
|
2291
1547
|
}
|
|
2292
|
-
/**
|
|
2293
|
-
* Parse Claude's model-specific refusal — the CLI's own phrasing when ONE
|
|
2294
|
-
* model's quota is exhausted while the account otherwise keeps serving:
|
|
2295
|
-
* "You've reached your Fable limit. Run /usage-credits to continue or switch
|
|
2296
|
-
* models with /model." Deliberately narrow (unlike the broad RATE_LIMIT_PATTERNS
|
|
2297
|
-
* scan) so a session that merely discusses `/usage-credits` cannot false-positive.
|
|
2298
|
-
* Tolerates both a straight and curly apostrophe.
|
|
2299
|
-
*/
|
|
2300
1548
|
export function parseClaudeModelRefusal(text) {
|
|
2301
1549
|
const candidates = [text];
|
|
2302
1550
|
for (const line of text.split('\n')) {
|
|
@@ -2308,7 +1556,7 @@ export function parseClaudeModelRefusal(text) {
|
|
|
2308
1556
|
candidates.push(row.message.content.filter((block) => block.type === 'text').map((block) => block.text ?? '').join('\n'));
|
|
2309
1557
|
}
|
|
2310
1558
|
}
|
|
2311
|
-
catch {
|
|
1559
|
+
catch { }
|
|
2312
1560
|
}
|
|
2313
1561
|
for (const candidate of candidates) {
|
|
2314
1562
|
const match = /(?:^|\n)\s*You['’]ve reached your ([^\n.]+) limit\.\s*Run \/usage-credits to continue or switch models with \/model\./i.exec(candidate);
|
|
@@ -2317,7 +1565,6 @@ export function parseClaudeModelRefusal(text) {
|
|
|
2317
1565
|
}
|
|
2318
1566
|
return null;
|
|
2319
1567
|
}
|
|
2320
|
-
/** Parse Claude's `hit your session limit · resets …` refusal. */
|
|
2321
1568
|
export function parseClaudeSessionLimitReset(text, nowMs = Date.now()) {
|
|
2322
1569
|
if (!/hit your session limit/i.test(text))
|
|
2323
1570
|
return null;
|
|
@@ -2360,7 +1607,6 @@ export function parseClaudeSessionLimitReset(text, nowMs = Date.now()) {
|
|
|
2360
1607
|
return null;
|
|
2361
1608
|
}
|
|
2362
1609
|
}
|
|
2363
|
-
/** Check whether a cached usage window is still relevant (not expired or reset). */
|
|
2364
1610
|
function isCachedUsageWindowFresh(window, capturedAt, now) {
|
|
2365
1611
|
if (window.resetsAt && window.resetsAt.getTime() <= now.getTime()) {
|
|
2366
1612
|
return false;
|
|
@@ -2373,7 +1619,6 @@ function isCachedUsageWindowFresh(window, capturedAt, now) {
|
|
|
2373
1619
|
}
|
|
2374
1620
|
return true;
|
|
2375
1621
|
}
|
|
2376
|
-
/** Obtain a valid access token, refreshing if expired. Saves refreshed tokens to Keychain. */
|
|
2377
1622
|
async function getClaudeAccessToken(oauth, home) {
|
|
2378
1623
|
const accessToken = oauth.accessToken?.trim();
|
|
2379
1624
|
if (!accessToken) {
|
|
@@ -2389,11 +1634,9 @@ async function getClaudeAccessToken(oauth, home) {
|
|
|
2389
1634
|
if (!refreshed?.accessToken) {
|
|
2390
1635
|
return null;
|
|
2391
1636
|
}
|
|
2392
|
-
// Persist refreshed credentials to Keychain so they survive across runs
|
|
2393
1637
|
await saveClaudeOauth(home, refreshed);
|
|
2394
1638
|
return refreshed.accessToken.trim();
|
|
2395
1639
|
}
|
|
2396
|
-
/** Refresh an expired Claude OAuth access token using the refresh token. */
|
|
2397
1640
|
async function refreshClaudeToken(oauth) {
|
|
2398
1641
|
const response = await fetch(CLAUDE_TOKEN_URL, {
|
|
2399
1642
|
method: 'POST',
|
|
@@ -2422,11 +1665,6 @@ async function refreshClaudeToken(oauth) {
|
|
|
2422
1665
|
scopes: data.scope ? data.scope.split(/\s+/).filter(Boolean) : (oauth.scopes || CLAUDE_SCOPES),
|
|
2423
1666
|
};
|
|
2424
1667
|
}
|
|
2425
|
-
/**
|
|
2426
|
-
* Check whether the Claude OAuth credentials for a given home are usable.
|
|
2427
|
-
* Attempts a token refresh if the access token is expired.
|
|
2428
|
-
* Returns true only when a valid access token can be obtained.
|
|
2429
|
-
*/
|
|
2430
1668
|
export async function isClaudeAuthValid(home) {
|
|
2431
1669
|
const oauth = await loadClaudeOauth(home);
|
|
2432
1670
|
if (!oauth)
|
|
@@ -2434,25 +1672,21 @@ export async function isClaudeAuthValid(home) {
|
|
|
2434
1672
|
const token = await getClaudeAccessToken(oauth, home);
|
|
2435
1673
|
return token !== null;
|
|
2436
1674
|
}
|
|
2437
|
-
/** Build a User-Agent string for Claude API requests. */
|
|
2438
1675
|
function getClaudeUserAgent(cliVersion) {
|
|
2439
1676
|
return cliVersion ? `claude-code/${cliVersion}` : 'claude-code';
|
|
2440
1677
|
}
|
|
2441
|
-
/** Clamp a numeric value to 0..100, returning null for non-finite values. */
|
|
2442
1678
|
function normalizePercent(value) {
|
|
2443
1679
|
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
2444
1680
|
return null;
|
|
2445
1681
|
}
|
|
2446
1682
|
return Math.max(0, Math.min(100, value));
|
|
2447
1683
|
}
|
|
2448
|
-
/** Validate and return a positive window duration, or null. */
|
|
2449
1684
|
function normalizeWindowMinutes(value) {
|
|
2450
1685
|
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
|
|
2451
1686
|
return null;
|
|
2452
1687
|
}
|
|
2453
1688
|
return value;
|
|
2454
1689
|
}
|
|
2455
|
-
/** Infer the window duration in minutes from a well-known window key. */
|
|
2456
1690
|
function inferWindowMinutes(key) {
|
|
2457
1691
|
switch (key) {
|
|
2458
1692
|
case 'session':
|
|
@@ -2464,7 +1698,6 @@ function inferWindowMinutes(key) {
|
|
|
2464
1698
|
return 43200;
|
|
2465
1699
|
}
|
|
2466
1700
|
}
|
|
2467
|
-
/** Parse a date value from a number (epoch seconds or ms) or ISO string. */
|
|
2468
1701
|
function parseDateValue(value) {
|
|
2469
1702
|
if (value === null || value === undefined || value === '') {
|
|
2470
1703
|
return null;
|
|
@@ -2482,22 +1715,18 @@ function parseDateValue(value) {
|
|
|
2482
1715
|
}
|
|
2483
1716
|
return null;
|
|
2484
1717
|
}
|
|
2485
|
-
/** Trim and return a string, or null if empty/non-string. */
|
|
2486
1718
|
function normalizeString(value) {
|
|
2487
1719
|
if (typeof value !== 'string')
|
|
2488
1720
|
return null;
|
|
2489
1721
|
const trimmed = value.trim();
|
|
2490
1722
|
return trimmed || null;
|
|
2491
1723
|
}
|
|
2492
|
-
/** Render a full-width usage bar for detailed views. */
|
|
2493
1724
|
function renderUsageBar(usedPercent) {
|
|
2494
1725
|
return renderBar(usedPercent, USAGE_BAR_LEN);
|
|
2495
1726
|
}
|
|
2496
|
-
/** Render a compact usage bar for inline summaries. */
|
|
2497
1727
|
function renderCompactUsageBar(usedPercent) {
|
|
2498
1728
|
return renderBar(usedPercent, COMPACT_BAR_LEN);
|
|
2499
1729
|
}
|
|
2500
|
-
/** Render a proportional colored progress bar at one-eighth-cell resolution. */
|
|
2501
1730
|
export function renderBar(usedPercent, length) {
|
|
2502
1731
|
const clamped = Math.max(0, Math.min(100, usedPercent));
|
|
2503
1732
|
const eighths = Math.round((clamped / 100) * length * 8);
|
|
@@ -2507,11 +1736,9 @@ export function renderBar(usedPercent, length) {
|
|
|
2507
1736
|
const gauge = FULL.repeat(filled) + PARTIAL_BLOCKS[partial];
|
|
2508
1737
|
return color(gauge) + chalk.dim(EMPTY.repeat(length - filled - (partial > 0 ? 1 : 0)));
|
|
2509
1738
|
}
|
|
2510
|
-
/** Apply the appropriate color to a text string based on usage percentage. */
|
|
2511
1739
|
function colorUsage(text, usedPercent) {
|
|
2512
1740
|
return getUsageColor(usedPercent)(text);
|
|
2513
1741
|
}
|
|
2514
|
-
/** Return a chalk color function based on the usage percentage threshold. */
|
|
2515
1742
|
export function getUsageColor(usedPercent) {
|
|
2516
1743
|
if (usedPercent >= 100)
|
|
2517
1744
|
return chalk.red;
|
|
@@ -2519,17 +1746,10 @@ export function getUsageColor(usedPercent) {
|
|
|
2519
1746
|
return chalk.yellow;
|
|
2520
1747
|
return chalk.cyan;
|
|
2521
1748
|
}
|
|
2522
|
-
/** Format a percentage value with at most one decimal place. */
|
|
2523
1749
|
function formatPercent(value) {
|
|
2524
1750
|
const rounded = Math.round(value * 10) / 10;
|
|
2525
1751
|
return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1);
|
|
2526
1752
|
}
|
|
2527
|
-
/**
|
|
2528
|
-
* Compact "time until reset" hint for the inline usage bars: "5m", "2h", "3d",
|
|
2529
|
-
* or "now" once elapsed. Deliberately coarse (single unit, whole numbers) so it
|
|
2530
|
-
* fits after a bar without wrapping the row — the detailed section
|
|
2531
|
-
* (`formatResetAt`) carries the precise clock time.
|
|
2532
|
-
*/
|
|
2533
1753
|
function formatResetHint(date) {
|
|
2534
1754
|
const diffMs = date.getTime() - Date.now();
|
|
2535
1755
|
if (diffMs <= 0)
|
|
@@ -2543,11 +1763,6 @@ function formatResetHint(date) {
|
|
|
2543
1763
|
const days = Math.round(hours / 24);
|
|
2544
1764
|
return `${days}d`;
|
|
2545
1765
|
}
|
|
2546
|
-
/**
|
|
2547
|
-
* Compact elapsed-time label for a stale reading's age: "30m", "6h", "2d".
|
|
2548
|
-
* Coarse single-unit like {@link formatResetHint}, floored at "1m" so a
|
|
2549
|
-
* just-expired window never reads "0m".
|
|
2550
|
-
*/
|
|
2551
1766
|
function formatAgeShort(diffMs) {
|
|
2552
1767
|
const mins = Math.max(1, Math.round(diffMs / 60000));
|
|
2553
1768
|
if (mins < 60)
|
|
@@ -2558,15 +1773,6 @@ function formatAgeShort(diffMs) {
|
|
|
2558
1773
|
const days = Math.round(hours / 24);
|
|
2559
1774
|
return `${days}d`;
|
|
2560
1775
|
}
|
|
2561
|
-
/**
|
|
2562
|
-
* Staleness suffix for a last-known window the freshness gate dropped. A window
|
|
2563
|
-
* whose reset/period boundary passed while the sample itself is still inside its
|
|
2564
|
-
* `windowMinutes` rolled OVER — the number describes a period that is done, so
|
|
2565
|
-
* name it ("period ended 1h", e.g. Grok's weekly billing period). A window
|
|
2566
|
-
* that aged past its own `windowMinutes` (Claude's 5h session read never
|
|
2567
|
-
* refreshed in time) is a stale sample of a still-rolling window, so report the
|
|
2568
|
-
* capture age ("6h old"). Falls back to the reset age, then a bare "stale".
|
|
2569
|
-
*/
|
|
2570
1776
|
function formatStaleWindowSuffix(window, capturedAt, now) {
|
|
2571
1777
|
const resetPassed = !!window.resetsAt && window.resetsAt.getTime() <= now.getTime();
|
|
2572
1778
|
const captureExpired = !!capturedAt &&
|
|
@@ -2581,20 +1787,12 @@ function formatStaleWindowSuffix(window, capturedAt, now) {
|
|
|
2581
1787
|
return `period ended ${formatAgeShort(now.getTime() - window.resetsAt.getTime())}`;
|
|
2582
1788
|
return 'stale';
|
|
2583
1789
|
}
|
|
2584
|
-
/**
|
|
2585
|
-
* Render a dropped-but-last-known window as "S: ▍░░░░ 30%* (6h old)": the gauge
|
|
2586
|
-
* and percentage exactly as a live bar, then the `*` stale marker the listing
|
|
2587
|
-
* legend explains and a dim age, so the number is always visible and
|
|
2588
|
-
* unmistakably not current. VIEW-ONLY — these windows are never in
|
|
2589
|
-
* `snapshot.windows`, so routing never sees them.
|
|
2590
|
-
*/
|
|
2591
1790
|
function renderStaleUsageWindow(window, capturedAt, shortLabel, now) {
|
|
2592
1791
|
const bar = renderCompactUsageBar(window.usedPercent);
|
|
2593
1792
|
const pct = colorUsage(`${Math.round(window.usedPercent)}%`, window.usedPercent);
|
|
2594
1793
|
const suffix = formatStaleWindowSuffix(window, capturedAt, now);
|
|
2595
1794
|
return `${chalk.gray(`${shortLabel}:`)} ${bar} ${pct}${chalk.dim('*')} ${chalk.dim(`(${suffix})`)}`;
|
|
2596
1795
|
}
|
|
2597
|
-
/** Format a reset timestamp as a human-readable relative or absolute time. */
|
|
2598
1796
|
function formatResetAt(date) {
|
|
2599
1797
|
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
2600
1798
|
const now = new Date();
|
|
@@ -2619,7 +1817,6 @@ function formatResetAt(date) {
|
|
|
2619
1817
|
}
|
|
2620
1818
|
return `${date.toLocaleString('en-US', options)} (${timezone})`;
|
|
2621
1819
|
}
|
|
2622
|
-
/** Safe wrapper around fs.realpathSync that returns null on error. */
|
|
2623
1820
|
function safeRealpathSync(filePath) {
|
|
2624
1821
|
try {
|
|
2625
1822
|
return fs.realpathSync(filePath);
|
|
@@ -2628,7 +1825,6 @@ function safeRealpathSync(filePath) {
|
|
|
2628
1825
|
return null;
|
|
2629
1826
|
}
|
|
2630
1827
|
}
|
|
2631
|
-
/** Safe wrapper around fs.statSync that returns null on error. */
|
|
2632
1828
|
function safeStatSync(filePath) {
|
|
2633
1829
|
try {
|
|
2634
1830
|
return fs.statSync(filePath);
|
|
@@ -2637,19 +1833,6 @@ function safeStatSync(filePath) {
|
|
|
2637
1833
|
return null;
|
|
2638
1834
|
}
|
|
2639
1835
|
}
|
|
2640
|
-
/**
|
|
2641
|
-
* Resolve the Grok billing log to read usage from.
|
|
2642
|
-
*
|
|
2643
|
-
* `agents view grok` reads usage per INSTALLED VERSION, passing each version's
|
|
2644
|
-
* isolated home (`~/.agents/.history/versions/grok/<ver>`). Grok writes
|
|
2645
|
-
* `unified.jsonl` only to the shared real home `~/.grok/logs/unified.jsonl`
|
|
2646
|
-
* even though GROK_HOME isolates auth/config/models per version. A per-version
|
|
2647
|
-
* log, if one ever appears, still wins; otherwise we return the shared path
|
|
2648
|
-
* marked `shared: true` so the caller can attribute it to at most one identity.
|
|
2649
|
-
* Grok accounts are version-scoped (`NATIVE_ACCOUNT_CAPABILITIES.grok.scope ===
|
|
2650
|
-
* 'version'`) — the shared file has no owner, so it must not be stamped onto
|
|
2651
|
-
* every version home.
|
|
2652
|
-
*/
|
|
2653
1836
|
function resolveGrokBillingLogPath(home) {
|
|
2654
1837
|
const rel = ['.grok', 'logs', 'unified.jsonl'];
|
|
2655
1838
|
const perVersion = path.join(home || os.homedir(), ...rel);
|
|
@@ -2657,21 +1840,17 @@ function resolveGrokBillingLogPath(home) {
|
|
|
2657
1840
|
if (fs.existsSync(perVersion))
|
|
2658
1841
|
return { logPath: perVersion, shared: false };
|
|
2659
1842
|
}
|
|
2660
|
-
catch {
|
|
2661
|
-
// `AGENTS_REAL_HOME` is the seam every version-home consumer honors: a
|
|
2662
|
-
// daemon/service-manager child's HOME can be baked to something other than
|
|
2663
|
-
// the account's real home, so os.homedir() alone is not a reliable stand-in.
|
|
1843
|
+
catch { }
|
|
2664
1844
|
const shared = path.join(process.env.AGENTS_REAL_HOME || os.homedir(), ...rel);
|
|
2665
1845
|
if (shared !== perVersion) {
|
|
2666
1846
|
try {
|
|
2667
1847
|
if (fs.existsSync(shared))
|
|
2668
1848
|
return { logPath: shared, shared: true };
|
|
2669
1849
|
}
|
|
2670
|
-
catch {
|
|
1850
|
+
catch { }
|
|
2671
1851
|
}
|
|
2672
1852
|
return null;
|
|
2673
1853
|
}
|
|
2674
|
-
/** This home's own `.grok/auth.json` only — never the shared-home fallback. */
|
|
2675
1854
|
function readGrokAuthIdentity(home) {
|
|
2676
1855
|
const authPath = path.join(home, '.grok', 'auth.json');
|
|
2677
1856
|
try {
|
|
@@ -2730,11 +1909,7 @@ function grokIdentityFromBillingPayload(parsed) {
|
|
|
2730
1909
|
function sameHomePath(a, b) {
|
|
2731
1910
|
return (safeRealpathSync(a) ?? path.resolve(a)) === (safeRealpathSync(b) ?? path.resolve(b));
|
|
2732
1911
|
}
|
|
2733
|
-
|
|
2734
|
-
* Whether the shared `~/.grok` billing log may be attached to this home.
|
|
2735
|
-
* Grok logins are per version home; the shared last line is one account's
|
|
2736
|
-
* meter. Fail loud: never copy it onto every installed identity.
|
|
2737
|
-
*/
|
|
1912
|
+
// A real-HOME Grok billing log serves a version home only when payload/auth identities match; unknown identity belongs only to real HOME.
|
|
2738
1913
|
function sharedGrokLogAppliesToHome(home, match) {
|
|
2739
1914
|
const realHome = process.env.AGENTS_REAL_HOME || os.homedir();
|
|
2740
1915
|
const requestedHome = home || os.homedir();
|
|
@@ -2742,18 +1917,13 @@ function sharedGrokLogAppliesToHome(home, match) {
|
|
|
2742
1917
|
if (match.identity) {
|
|
2743
1918
|
return grokIdentitiesMatch(match.identity, thisId);
|
|
2744
1919
|
}
|
|
2745
|
-
// No identity on the line (the live Grok shape). Attribute the reading to
|
|
2746
|
-
// exactly one canonical identity: the real home itself, or the version home
|
|
2747
|
-
// whose own auth.json matches the shared `~/.grok/auth.json`.
|
|
2748
1920
|
if (sameHomePath(requestedHome, realHome))
|
|
2749
1921
|
return true;
|
|
2750
1922
|
return grokIdentitiesMatch(thisId, readGrokAuthIdentity(realHome));
|
|
2751
1923
|
}
|
|
2752
|
-
/** Parse the latest billing info from Grok's unified log. */
|
|
2753
1924
|
async function getGrokUsageInfo(options) {
|
|
2754
1925
|
try {
|
|
2755
1926
|
const resolved = resolveGrokBillingLogPath(options?.home);
|
|
2756
|
-
// No log yet: a benign "nothing recorded here", not a failure (RUSH-3040).
|
|
2757
1927
|
if (!resolved)
|
|
2758
1928
|
return usageNoRecentUsageInfo();
|
|
2759
1929
|
const match = await readLatestGrokBilling(resolved.logPath);
|
|
@@ -2762,18 +1932,8 @@ async function getGrokUsageInfo(options) {
|
|
|
2762
1932
|
if (resolved.shared && !sharedGrokLogAppliesToHome(options?.home, match)) {
|
|
2763
1933
|
return usageNoRecentUsageInfo();
|
|
2764
1934
|
}
|
|
2765
|
-
// Grok has no live usage API (`network: false`) — bars are last-seen from
|
|
2766
|
-
// this machine's unified.jsonl only. Drop windows whose billing period has
|
|
2767
|
-
// already ended so a stale 100% does not paint "rate-limited" after reset,
|
|
2768
|
-
// and so an expired 92% on one box cannot disagree with a fresh reading on
|
|
2769
|
-
// another. Missing `creditUsagePercent` never reaches here as a 0% bar
|
|
2770
|
-
// (see readLatestGrokBilling).
|
|
2771
1935
|
const now = new Date();
|
|
2772
1936
|
const windows = match.windows.filter((window) => isCachedUsageWindowFresh(window, match.capturedAt, now));
|
|
2773
|
-
// A window from an ended billing period is the LAST reading we saw — routing
|
|
2774
|
-
// must not trust it (kept out of `windows`), but the view renders it with a
|
|
2775
|
-
// "period ended Xh ago" suffix instead of the bare refresh hint. Only when
|
|
2776
|
-
// there is nothing at all to show does the refresh hint stand alone.
|
|
2777
1937
|
const staleWindows = match.windows.filter((window) => !isCachedUsageWindowFresh(window, match.capturedAt, now));
|
|
2778
1938
|
const version = options?.cliVersion;
|
|
2779
1939
|
const refreshHint = windows.length === 0 && staleWindows.length === 0
|
|
@@ -2796,21 +1956,9 @@ async function getGrokUsageInfo(options) {
|
|
|
2796
1956
|
return { snapshot: null, error: usageUnreachableError('Grok', err) };
|
|
2797
1957
|
}
|
|
2798
1958
|
}
|
|
2799
|
-
/**
|
|
2800
|
-
* Muse Code usage.
|
|
2801
|
-
*
|
|
2802
|
-
* Prefer live Meta Model API rate-limit headers when a key is available
|
|
2803
|
-
* (META_API_KEY / MODEL_API_KEY / ~/.config/muse/auth.json). Fall back to
|
|
2804
|
-
* aggregating `model_completed.usage` from local session.jsonl logs under
|
|
2805
|
-
* ~/.local/share/muse/sessions for a last-7-days token window.
|
|
2806
|
-
*/
|
|
2807
1959
|
async function getMuseUsageInfo(options) {
|
|
2808
1960
|
try {
|
|
2809
1961
|
const base = options?.home || os.homedir();
|
|
2810
|
-
// Honour a live Retry-After rather than re-arming the penalty (see
|
|
2811
|
-
// usage-backoff.ts). The local log fallback still works while the live
|
|
2812
|
-
// probe is throttled — only report the throttle when there is truly
|
|
2813
|
-
// nothing else to show.
|
|
2814
1962
|
const throttledUntil = usageRateLimitedUntil('muse', Date.now(), options?.usageScope);
|
|
2815
1963
|
if (throttledUntil) {
|
|
2816
1964
|
const local = await readMuseLocalSessionUsage(base);
|
|
@@ -2824,9 +1972,6 @@ async function getMuseUsageInfo(options) {
|
|
|
2824
1972
|
const local = await readMuseLocalSessionUsage(base);
|
|
2825
1973
|
if (local)
|
|
2826
1974
|
return { snapshot: local, error: null };
|
|
2827
|
-
// No live snapshot and no local log — every source came up empty. Only
|
|
2828
|
-
// now does the probe's own outcome become the reported error, mirroring
|
|
2829
|
-
// Cursor's "surface the last resort's failure" pattern above.
|
|
2830
1975
|
if (!probe.hasKey)
|
|
2831
1976
|
return { snapshot: null, error: usageNoCredentialError('Muse') };
|
|
2832
1977
|
if (probe.noHeaders)
|
|
@@ -2837,13 +1982,9 @@ async function getMuseUsageInfo(options) {
|
|
|
2837
1982
|
};
|
|
2838
1983
|
}
|
|
2839
1984
|
catch (err) {
|
|
2840
|
-
// A thrown request (timeout, DNS, TLS, a malformed payload) is a failed
|
|
2841
|
-
// read like any other — staying silent here would hand the caller a stale
|
|
2842
|
-
// snapshot to render as confirmed (RUSH-3040).
|
|
2843
1985
|
return { snapshot: null, error: usageUnreachableError('Muse', err) };
|
|
2844
1986
|
}
|
|
2845
1987
|
}
|
|
2846
|
-
/** Resolve a Muse API key from env or auth.json without logging the value. */
|
|
2847
1988
|
function resolveMuseApiKey(base) {
|
|
2848
1989
|
const envKey = process.env.META_API_KEY?.trim() || process.env.MODEL_API_KEY?.trim();
|
|
2849
1990
|
if (envKey)
|
|
@@ -2868,16 +2009,9 @@ function resolveMuseApiKey(base) {
|
|
|
2868
2009
|
}
|
|
2869
2010
|
}
|
|
2870
2011
|
catch {
|
|
2871
|
-
/* unreadable auth */
|
|
2872
2012
|
}
|
|
2873
2013
|
return null;
|
|
2874
2014
|
}
|
|
2875
|
-
/**
|
|
2876
|
-
* Probe Meta Model API for rate-limit headers. Uses GET /v1/models (no token
|
|
2877
|
-
* spend). The 429 backoff is noted by the CALLER via
|
|
2878
|
-
* {@link classifyUsageFetchFailure}, not here, so a throttled read is recorded
|
|
2879
|
-
* exactly once regardless of which branch of `getMuseUsageInfo` observes it.
|
|
2880
|
-
*/
|
|
2881
2015
|
async function probeMuseRateLimits(base) {
|
|
2882
2016
|
const key = resolveMuseApiKey(base);
|
|
2883
2017
|
if (!key)
|
|
@@ -2951,11 +2085,6 @@ function headerNumber(headers, name) {
|
|
|
2951
2085
|
const n = Number(raw);
|
|
2952
2086
|
return Number.isFinite(n) ? n : null;
|
|
2953
2087
|
}
|
|
2954
|
-
/**
|
|
2955
|
-
* Aggregate Muse session token usage from local session.jsonl files for the
|
|
2956
|
-
* last 7 days. Scales the bar against 10M tokens (soft visibility scale — Meta
|
|
2957
|
-
* is pay-as-you-go with no hard local cap).
|
|
2958
|
-
*/
|
|
2959
2088
|
async function readMuseLocalSessionUsage(base) {
|
|
2960
2089
|
const root = path.join(base, '.local', 'share', 'muse', 'sessions');
|
|
2961
2090
|
if (!fs.existsSync(root))
|
|
@@ -3015,7 +2144,6 @@ async function readMuseLocalSessionUsage(base) {
|
|
|
3015
2144
|
}
|
|
3016
2145
|
}
|
|
3017
2146
|
catch {
|
|
3018
|
-
/* skip unreadable session */
|
|
3019
2147
|
}
|
|
3020
2148
|
}
|
|
3021
2149
|
};
|
|
@@ -3023,7 +2151,6 @@ async function readMuseLocalSessionUsage(base) {
|
|
|
3023
2151
|
const total = inputTokens + outputTokens + cachedTokens;
|
|
3024
2152
|
if (total === 0 && files === 0)
|
|
3025
2153
|
return null;
|
|
3026
|
-
// Soft 10M-token scale for the bar (pay-as-you-go has no hard local cap).
|
|
3027
2154
|
const softCap = 10_000_000;
|
|
3028
2155
|
const usedPercent = Math.max(0, Math.min(100, (total / softCap) * 100));
|
|
3029
2156
|
const formatK = (n) => n >= 1_000_000 ? `${(n / 1_000_000).toFixed(1)}M` : n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n);
|
|
@@ -3064,11 +2191,6 @@ async function readLatestGrokBilling(filePath) {
|
|
|
3064
2191
|
? config.currentPeriod
|
|
3065
2192
|
: null;
|
|
3066
2193
|
if (currentPeriod?.end && typeof config.creditUsagePercent === 'number') {
|
|
3067
|
-
// `creditUsagePercent` is Grok's weekly credit consumption (0-100);
|
|
3068
|
-
// the billing period's `end` is when that window resets.
|
|
3069
|
-
// Do NOT coerce a missing percent to 0 — a new period often lands a
|
|
3070
|
-
// billing line before the gauge is populated, and inventing 0% makes
|
|
3071
|
-
// `agents view` disagree across devices (and looks like a fresh week).
|
|
3072
2194
|
const rawPercent = config.creditUsagePercent;
|
|
3073
2195
|
windows.push({
|
|
3074
2196
|
key: 'week',
|
|
@@ -3088,22 +2210,12 @@ async function readLatestGrokBilling(filePath) {
|
|
|
3088
2210
|
}
|
|
3089
2211
|
}
|
|
3090
2212
|
catch {
|
|
3091
|
-
/* malformed session line */
|
|
3092
2213
|
}
|
|
3093
2214
|
});
|
|
3094
2215
|
rl.on('close', () => resolve(latest));
|
|
3095
2216
|
rl.on('error', () => resolve(latest));
|
|
3096
2217
|
});
|
|
3097
2218
|
}
|
|
3098
|
-
/**
|
|
3099
|
-
* Normalize Cursor's /api/usage payload into the common UsageWindow shape.
|
|
3100
|
-
*
|
|
3101
|
-
* Only free / legacy request-capped plans carry a `maxRequestUsage` on the
|
|
3102
|
-
* premium ("gpt-4") bucket — that's the fast-request cap the plan meters, and it
|
|
3103
|
-
* maps cleanly to a monthly window. Usage-based plans report `maxRequestUsage:
|
|
3104
|
-
* null` (no request cap — spend is metered in dollars instead), so they have no
|
|
3105
|
-
* bar to draw here and return no windows rather than a misleading empty gauge.
|
|
3106
|
-
*/
|
|
3107
2219
|
export function normalizeCursorUsage(data) {
|
|
3108
2220
|
const premium = data['gpt-4'];
|
|
3109
2221
|
if (!premium || typeof premium !== 'object')
|
|
@@ -3113,10 +2225,6 @@ export function normalizeCursorUsage(data) {
|
|
|
3113
2225
|
return [];
|
|
3114
2226
|
const used = typeof premium.numRequests === 'number' ? premium.numRequests : 0;
|
|
3115
2227
|
const startOfMonth = typeof data.startOfMonth === 'string' ? parseDateValue(data.startOfMonth) : null;
|
|
3116
|
-
// The request quota resets one calendar month after the period start. Guard the
|
|
3117
|
-
// month-end overflow: setMonth on a day the target month lacks (Jan 31 -> Feb 31)
|
|
3118
|
-
// rolls forward into the month after (Mar 3), so clamp back to the intended
|
|
3119
|
-
// month's last day.
|
|
3120
2228
|
let resetsAt = null;
|
|
3121
2229
|
if (startOfMonth) {
|
|
3122
2230
|
resetsAt = new Date(startOfMonth);
|
|
@@ -3136,25 +2244,12 @@ export function normalizeCursorUsage(data) {
|
|
|
3136
2244
|
},
|
|
3137
2245
|
];
|
|
3138
2246
|
}
|
|
3139
|
-
/**
|
|
3140
|
-
* Normalize a single Cursor percent-based window (auto/api/total), or null when
|
|
3141
|
-
* the percent is not a finite number — the "no empty gauges" rule.
|
|
3142
|
-
* `windowMinutes` stays null: every window shares one billing-cycle reset
|
|
3143
|
-
* (`resetsAt`, from the explicit `billingCycleEnd`), not an inferred cadence, so
|
|
3144
|
-
* inferring one from the (repurposed) `session`/`week`/`month` key would let the
|
|
3145
|
-
* SWR cache zero the bar out long before the real reset.
|
|
3146
|
-
*/
|
|
3147
2247
|
function normalizeCursorPercentWindow(percent, key, label, shortLabel, resetsAt) {
|
|
3148
2248
|
const usedPercent = normalizePercent(percent);
|
|
3149
2249
|
if (usedPercent === null)
|
|
3150
2250
|
return null;
|
|
3151
2251
|
return { key, label, shortLabel, usedPercent, resetsAt, windowMinutes: null };
|
|
3152
2252
|
}
|
|
3153
|
-
/**
|
|
3154
|
-
* Normalize Cursor's dashboard `get-current-period-usage` payload — the
|
|
3155
|
-
* primary usage source, giving the same Auto+Composer / API / Total breakdown
|
|
3156
|
-
* the web dashboard shows.
|
|
3157
|
-
*/
|
|
3158
2253
|
export function normalizeCursorPeriodUsage(data) {
|
|
3159
2254
|
const resetsAt = parseDateValue(data.billingCycleEnd);
|
|
3160
2255
|
const plan = data.planUsage;
|
|
@@ -3165,14 +2260,6 @@ export function normalizeCursorPeriodUsage(data) {
|
|
|
3165
2260
|
];
|
|
3166
2261
|
return windows.filter((window) => window !== null);
|
|
3167
2262
|
}
|
|
3168
|
-
/**
|
|
3169
|
-
* Normalize Cursor's `usage-summary` fallback payload — the same Auto/API/Total
|
|
3170
|
-
* breakdown nested under `individualUsage.plan`, used when the primary
|
|
3171
|
-
* dashboard endpoint returns no usable `planUsage` (seen on some
|
|
3172
|
-
* enterprise/team accounts). An unlimited plan (`isUnlimited: true`) with no
|
|
3173
|
-
* usable percent has nothing to draw and returns no windows, rather than a
|
|
3174
|
-
* misleading empty gauge.
|
|
3175
|
-
*/
|
|
3176
2263
|
export function normalizeCursorUsageSummary(data) {
|
|
3177
2264
|
const resetsAt = parseDateValue(data.billingCycleEnd);
|
|
3178
2265
|
const plan = data.individualUsage?.plan;
|
|
@@ -3183,7 +2270,6 @@ export function normalizeCursorUsageSummary(data) {
|
|
|
3183
2270
|
];
|
|
3184
2271
|
return windows.filter((window) => window !== null);
|
|
3185
2272
|
}
|
|
3186
|
-
/** Read Cursor's OAuth access token + config-file subject from the local CLI config/auth files. */
|
|
3187
2273
|
function readCursorCredentials(base) {
|
|
3188
2274
|
try {
|
|
3189
2275
|
const cfgPath = path.join(base, '.cursor', 'cli-config.json');
|
|
@@ -3202,22 +2288,10 @@ function readCursorCredentials(base) {
|
|
|
3202
2288
|
return null;
|
|
3203
2289
|
}
|
|
3204
2290
|
}
|
|
3205
|
-
/**
|
|
3206
|
-
* Resolve the OAuth subject Cursor expects in the `WorkosCursorSessionToken`
|
|
3207
|
-
* cookie: the access token's own JWT `sub` claim first (the subject that
|
|
3208
|
-
* actually signed the token in hand), falling back to the subject
|
|
3209
|
-
* `cli-config.json` recorded at login when the token carries no usable `sub`.
|
|
3210
|
-
*/
|
|
3211
2291
|
function resolveCursorSubject(accessToken, cfgSub) {
|
|
3212
2292
|
const jwtSub = normalizeString(decodeJwtPayload(accessToken)?.sub);
|
|
3213
2293
|
return jwtSub || cfgSub;
|
|
3214
2294
|
}
|
|
3215
|
-
/**
|
|
3216
|
-
* POST the dashboard current-period-usage endpoint and normalize its windows.
|
|
3217
|
-
* Returns null on any network/auth failure so the caller falls through to the
|
|
3218
|
-
* next source — only a genuine empty-windows response distinguishes "no usage
|
|
3219
|
-
* to report" from "couldn't reach this source".
|
|
3220
|
-
*/
|
|
3221
2295
|
async function fetchCursorPeriodWindows(cookie) {
|
|
3222
2296
|
try {
|
|
3223
2297
|
const response = await fetch(CURSOR_PERIOD_USAGE_URL, {
|
|
@@ -3244,10 +2318,6 @@ async function fetchCursorPeriodWindows(cookie) {
|
|
|
3244
2318
|
return null;
|
|
3245
2319
|
}
|
|
3246
2320
|
}
|
|
3247
|
-
/**
|
|
3248
|
-
* GET the usage-summary fallback endpoint and normalize its windows. Same
|
|
3249
|
-
* null-on-failure contract as {@link fetchCursorPeriodWindows}.
|
|
3250
|
-
*/
|
|
3251
2321
|
async function fetchCursorUsageSummaryWindows(cookie) {
|
|
3252
2322
|
try {
|
|
3253
2323
|
const response = await fetch(CURSOR_USAGE_SUMMARY_URL, {
|
|
@@ -3271,30 +2341,6 @@ async function fetchCursorUsageSummaryWindows(cookie) {
|
|
|
3271
2341
|
return null;
|
|
3272
2342
|
}
|
|
3273
2343
|
}
|
|
3274
|
-
/**
|
|
3275
|
-
* Fetch Cursor usage. Cursor authenticates every one of these requests with a
|
|
3276
|
-
* `WorkosCursorSessionToken` cookie of the form `<oauth-subject>::<access-token>`
|
|
3277
|
-
* (the same pair the web dashboard sends), not a bearer header, so all three
|
|
3278
|
-
* sources below share one resolved cookie.
|
|
3279
|
-
*
|
|
3280
|
-
* Three sources, tried in order, because no single endpoint carries usable data
|
|
3281
|
-
* for every plan shape:
|
|
3282
|
-
*
|
|
3283
|
-
* 1. `get-current-period-usage` — the primary source, and the richest: the
|
|
3284
|
-
* Auto+Composer / API / Total percent breakdown the dashboard itself shows.
|
|
3285
|
-
* 2. `usage-summary` — some enterprise/team accounts return no usable
|
|
3286
|
-
* `planUsage` from (1); this nests the same three percentages under
|
|
3287
|
-
* `individualUsage.plan` instead.
|
|
3288
|
-
* 3. The legacy `/api/usage` request-cap endpoint — the original source,
|
|
3289
|
-
* kept as the final fallback for free/legacy plans that predate the
|
|
3290
|
-
* percent-based breakdown above and only ever exposed a monthly request cap.
|
|
3291
|
-
*
|
|
3292
|
-
* The first source to yield a non-empty window list wins; a source that errors
|
|
3293
|
-
* or returns no usable numbers falls through to the next rather than surfacing
|
|
3294
|
-
* an error — only the last resort's own response/error is surfaced when every
|
|
3295
|
-
* source comes up empty, so a plan enrolled in exactly one billing model still
|
|
3296
|
-
* renders instead of reporting three swallowed failures.
|
|
3297
|
-
*/
|
|
3298
2344
|
async function getCursorUsageInfo(options) {
|
|
3299
2345
|
try {
|
|
3300
2346
|
const base = options?.home || os.homedir();
|
|
@@ -3336,8 +2382,6 @@ async function getCursorUsageInfo(options) {
|
|
|
3336
2382
|
},
|
|
3337
2383
|
signal: usageFetchSignal(options?.signal, 5000),
|
|
3338
2384
|
});
|
|
3339
|
-
// 401/redirect => revoked/expired session. No bars to draw, and the status
|
|
3340
|
-
// says why.
|
|
3341
2385
|
if (!response.ok) {
|
|
3342
2386
|
if (response.status === 429) {
|
|
3343
2387
|
noteUsageRateLimited('cursor', response.headers.get('retry-after'));
|
|
@@ -3356,38 +2400,17 @@ async function getCursorUsageInfo(options) {
|
|
|
3356
2400
|
};
|
|
3357
2401
|
}
|
|
3358
2402
|
catch (err) {
|
|
3359
|
-
// A thrown request (timeout, DNS, TLS, a malformed payload) is a failed
|
|
3360
|
-
// read like any other — staying silent here would hand the caller a stale
|
|
3361
|
-
// snapshot to render as confirmed, which is the bug this file just closed.
|
|
3362
2403
|
return { snapshot: null, error: usageUnreachableError('Cursor', err) };
|
|
3363
2404
|
}
|
|
3364
2405
|
}
|
|
3365
|
-
// ---------------------------------------------------------------------------
|
|
3366
|
-
// Antigravity (`agy`) usage — Google Code Assist per-model quota buckets
|
|
3367
|
-
// ---------------------------------------------------------------------------
|
|
3368
2406
|
const ANTIGRAVITY_TOKEN_URL = 'https://oauth2.googleapis.com/token';
|
|
3369
|
-
// Production Code Assist endpoint first; the daily track is where `agy` itself
|
|
3370
|
-
// points when the account is enrolled in the daily channel (its log shows
|
|
3371
|
-
// daily-cloudcode-pa), so fall back to it when prod rejects the call.
|
|
3372
2407
|
const ANTIGRAVITY_QUOTA_URLS = [
|
|
3373
2408
|
'https://cloudcode-pa.googleapis.com/v1internal:retrieveUserQuota',
|
|
3374
2409
|
'https://daily-cloudcode-pa.googleapis.com/v1internal:retrieveUserQuota',
|
|
3375
2410
|
];
|
|
3376
|
-
// The public installed-app OAuth client the released `agy` binary itself
|
|
3377
|
-
// ships (Google installed-app clients are non-confidential by design — the
|
|
3378
|
-
// same client community tooling uses). Needed because a Google token refresh
|
|
3379
|
-
// requires the client id/secret pair the login was minted under.
|
|
3380
2411
|
const ANTIGRAVITY_CLIENT_ID = '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com';
|
|
3381
2412
|
const ANTIGRAVITY_CLIENT_SECRET = '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';
|
|
3382
|
-
/** Refresh leeway — treat an access token expiring within a minute as expired. */
|
|
3383
2413
|
const ANTIGRAVITY_REFRESH_LEEWAY_MS = 60 * 1000;
|
|
3384
|
-
/**
|
|
3385
|
-
* Parse a stored `agy` OAuth payload into its token. Handles both on-disk
|
|
3386
|
-
* shapes: the raw `{ token: {…} }` JSON (Linux file fallback) and the
|
|
3387
|
-
* `go-keyring-base64:<base64>` wrapper zalando/go-keyring writes into the
|
|
3388
|
-
* macOS Keychain item (service `gemini`, account `antigravity`). Never throws
|
|
3389
|
-
* (malformed input => null).
|
|
3390
|
-
*/
|
|
3391
2414
|
export function parseAntigravityOauthPayload(raw) {
|
|
3392
2415
|
try {
|
|
3393
2416
|
let text = raw.trim();
|
|
@@ -3406,12 +2429,6 @@ export function parseAntigravityOauthPayload(raw) {
|
|
|
3406
2429
|
return null;
|
|
3407
2430
|
}
|
|
3408
2431
|
}
|
|
3409
|
-
/**
|
|
3410
|
-
* True when the stored access token is expired (or inside the refresh leeway).
|
|
3411
|
-
* A missing/unparseable expiry is treated as still-fresh — the quota call
|
|
3412
|
-
* below is the source of truth if the token is actually dead (401 => render
|
|
3413
|
-
* nothing), and we never want to force a refresh without evidence.
|
|
3414
|
-
*/
|
|
3415
2432
|
export function antigravityTokenNeedsRefresh(expiry, nowMs = Date.now()) {
|
|
3416
2433
|
if (!expiry)
|
|
3417
2434
|
return false;
|
|
@@ -3420,13 +2437,6 @@ export function antigravityTokenNeedsRefresh(expiry, nowMs = Date.now()) {
|
|
|
3420
2437
|
return false;
|
|
3421
2438
|
return nowMs + ANTIGRAVITY_REFRESH_LEEWAY_MS >= ms;
|
|
3422
2439
|
}
|
|
3423
|
-
/**
|
|
3424
|
-
* Resolve the `agy` OAuth credential file. agy is a self-updating global
|
|
3425
|
-
* install (no per-version homes), but check the passed home first and then the
|
|
3426
|
-
* active location under the real HOME — mirrors resolveKimiCredentialPath.
|
|
3427
|
-
* Present only on Linux without a Secret Service daemon; macOS logins live in
|
|
3428
|
-
* the Keychain instead.
|
|
3429
|
-
*/
|
|
3430
2440
|
function resolveAntigravityCredentialPath(home) {
|
|
3431
2441
|
const rel = ['.gemini', 'antigravity-cli', 'antigravity-oauth-token'];
|
|
3432
2442
|
const perHome = path.join(home || os.homedir(), ...rel);
|
|
@@ -3434,25 +2444,17 @@ function resolveAntigravityCredentialPath(home) {
|
|
|
3434
2444
|
if (fs.existsSync(perHome))
|
|
3435
2445
|
return perHome;
|
|
3436
2446
|
}
|
|
3437
|
-
catch {
|
|
2447
|
+
catch { }
|
|
3438
2448
|
const active = path.join(process.env.AGENTS_REAL_HOME || os.homedir(), ...rel);
|
|
3439
2449
|
if (active !== perHome) {
|
|
3440
2450
|
try {
|
|
3441
2451
|
if (fs.existsSync(active))
|
|
3442
2452
|
return active;
|
|
3443
2453
|
}
|
|
3444
|
-
catch {
|
|
2454
|
+
catch { }
|
|
3445
2455
|
}
|
|
3446
2456
|
return null;
|
|
3447
2457
|
}
|
|
3448
|
-
/**
|
|
3449
|
-
* Load the stored `agy` OAuth token: the file fallback first, then the OS
|
|
3450
|
-
* keyring (macOS Keychain / Linux Secret Service — go-keyring's two stores;
|
|
3451
|
-
* the probe command pair mirrors antigravityOsKeyringProbe in agents.ts, with
|
|
3452
|
-
* `-w` on macOS to read the secret value, not just metadata). Returns null on
|
|
3453
|
-
* Windows or when no readable credential exists. Honors the
|
|
3454
|
-
* AGENTS_NO_KEYCHAIN_PROBE=1 test guard.
|
|
3455
|
-
*/
|
|
3456
2458
|
async function loadAntigravityOauth(home) {
|
|
3457
2459
|
const credPath = resolveAntigravityCredentialPath(home);
|
|
3458
2460
|
if (credPath) {
|
|
@@ -3461,7 +2463,7 @@ async function loadAntigravityOauth(home) {
|
|
|
3461
2463
|
if (parsed)
|
|
3462
2464
|
return parsed;
|
|
3463
2465
|
}
|
|
3464
|
-
catch {
|
|
2466
|
+
catch { }
|
|
3465
2467
|
}
|
|
3466
2468
|
if (process.env.AGENTS_NO_KEYCHAIN_PROBE === '1')
|
|
3467
2469
|
return null;
|
|
@@ -3480,16 +2482,6 @@ async function loadAntigravityOauth(home) {
|
|
|
3480
2482
|
return null;
|
|
3481
2483
|
}
|
|
3482
2484
|
}
|
|
3483
|
-
/**
|
|
3484
|
-
* Refresh an `agy` access token against Google's token endpoint. This is safe
|
|
3485
|
-
* from a read path in a way Claude/WorkOS refreshes are NOT: Google's OAuth
|
|
3486
|
-
* refresh tokens are stable and non-rotating — a refresh mints a new access
|
|
3487
|
-
* token and leaves the refresh token (and every other live access token)
|
|
3488
|
-
* valid, so refreshing here cannot invalidate a concurrently running `agy`.
|
|
3489
|
-
* We still never write the refreshed token back: `agy` rewrites its own
|
|
3490
|
-
* keychain item on launch, and a read-only usage fetch must not mutate the
|
|
3491
|
-
* user's credential.
|
|
3492
|
-
*/
|
|
3493
2485
|
async function refreshAntigravityAccessToken(refreshToken) {
|
|
3494
2486
|
try {
|
|
3495
2487
|
const response = await fetch(ANTIGRAVITY_TOKEN_URL, {
|
|
@@ -3512,13 +2504,6 @@ async function refreshAntigravityAccessToken(refreshToken) {
|
|
|
3512
2504
|
return null;
|
|
3513
2505
|
}
|
|
3514
2506
|
}
|
|
3515
|
-
/**
|
|
3516
|
-
* POST :retrieveUserQuota against the Code Assist endpoints in order, returning
|
|
3517
|
-
* the first successful bucket list. `buckets: null` when every endpoint rejects
|
|
3518
|
-
* (expired token, no quota API for the account) or the network fails — `status`
|
|
3519
|
-
* carries the LAST rejection's HTTP status (or null when every attempt threw)
|
|
3520
|
-
* so the caller can classify the failure instead of it reading as silence.
|
|
3521
|
-
*/
|
|
3522
2507
|
async function fetchAntigravityQuota(accessToken) {
|
|
3523
2508
|
let lastStatus = null;
|
|
3524
2509
|
let lastRetryAfter = null;
|
|
@@ -3548,7 +2533,6 @@ async function fetchAntigravityQuota(accessToken) {
|
|
|
3548
2533
|
}
|
|
3549
2534
|
return { buckets: null, status: lastStatus, retryAfter: lastRetryAfter };
|
|
3550
2535
|
}
|
|
3551
|
-
/** Compact model tag for the inline bar — 'gemini-2.5-flash-lite' => '2.5FL'. */
|
|
3552
2536
|
export function antigravityModelShortLabel(modelId) {
|
|
3553
2537
|
const stripped = modelId.replace(/^gemini-/i, '');
|
|
3554
2538
|
const parts = stripped.split('-').filter(Boolean);
|
|
@@ -3557,16 +2541,6 @@ export function antigravityModelShortLabel(modelId) {
|
|
|
3557
2541
|
const [version, ...rest] = parts;
|
|
3558
2542
|
return version + rest.map((part) => (part[0] ? part[0].toUpperCase() : '')).join('');
|
|
3559
2543
|
}
|
|
3560
|
-
/**
|
|
3561
|
-
* Normalize the per-model quota buckets into the common UsageWindow shape —
|
|
3562
|
-
* one window per model (`gemini-3.1-pro`, `gemini-2.5-flash`, …), keyed
|
|
3563
|
-
* `session` since each bucket is a short-cycle quota with its own reset time.
|
|
3564
|
-
* Duplicate buckets for one model keep the LOWEST remaining fraction (the
|
|
3565
|
-
* most conservative read). Sorted most-used first so the bar closest to
|
|
3566
|
-
* throttling leads the row. `windowMinutes` stays null: the API reports only
|
|
3567
|
-
* the reset timestamp, not the window length, and an inferred 5h session
|
|
3568
|
-
* length would wrongly zero the SWR cache between resets.
|
|
3569
|
-
*/
|
|
3570
2544
|
export function normalizeAntigravityWindows(buckets) {
|
|
3571
2545
|
const byModel = new Map();
|
|
3572
2546
|
for (const bucket of buckets) {
|
|
@@ -3596,14 +2570,6 @@ export function normalizeAntigravityWindows(buckets) {
|
|
|
3596
2570
|
windows.sort((a, b) => b.usedPercent - a.usedPercent);
|
|
3597
2571
|
return windows;
|
|
3598
2572
|
}
|
|
3599
|
-
/**
|
|
3600
|
-
* Fetch Antigravity usage via Google Code Assist's :retrieveUserQuota — the
|
|
3601
|
-
* quota API `agy` itself talks to (its log shows the sibling :loadCodeAssist
|
|
3602
|
-
* and :fetchAvailableModels calls on the same host). Auth is the stored `agy`
|
|
3603
|
-
* OAuth token (OS keyring on macOS, file fallback on Linux), refreshed
|
|
3604
|
-
* in-memory when expired — safe because Google's refresh tokens are
|
|
3605
|
-
* non-rotating (see refreshAntigravityAccessToken).
|
|
3606
|
-
*/
|
|
3607
2573
|
async function getAntigravityUsageInfo(options) {
|
|
3608
2574
|
try {
|
|
3609
2575
|
const token = await loadAntigravityOauth(options?.home);
|
|
@@ -3615,10 +2581,6 @@ async function getAntigravityUsageInfo(options) {
|
|
|
3615
2581
|
}
|
|
3616
2582
|
if (!accessToken)
|
|
3617
2583
|
return { snapshot: null, error: usageExpiredCredentialError('Antigravity') };
|
|
3618
|
-
// Honour a live Retry-After rather than re-arming the penalty (see
|
|
3619
|
-
// usage-backoff.ts). No request at all while the window is open. Antigravity
|
|
3620
|
-
// previously had NO rate-limit backoff at all (RUSH-3040) — every refresh
|
|
3621
|
-
// re-hit a throttled endpoint.
|
|
3622
2584
|
const throttledUntil = usageRateLimitedUntil('antigravity', Date.now(), options?.usageScope);
|
|
3623
2585
|
if (throttledUntil) {
|
|
3624
2586
|
return { snapshot: null, error: usageThrottledError('Antigravity', throttledUntil) };
|
|
@@ -3644,9 +2606,6 @@ async function getAntigravityUsageInfo(options) {
|
|
|
3644
2606
|
};
|
|
3645
2607
|
}
|
|
3646
2608
|
catch (err) {
|
|
3647
|
-
// A thrown request (timeout, DNS, TLS, a malformed payload) is a failed
|
|
3648
|
-
// read like any other — staying silent here would hand the caller a stale
|
|
3649
|
-
// snapshot to render as confirmed (RUSH-3040).
|
|
3650
2609
|
return { snapshot: null, error: usageUnreachableError('Antigravity', err) };
|
|
3651
2610
|
}
|
|
3652
2611
|
}
|