agent-control-plane 0.1.0
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/LICENSE +21 -0
- package/README.md +589 -0
- package/SKILL.md +149 -0
- package/assets/workflow-catalog.json +57 -0
- package/bin/audit-issue-routing.sh +74 -0
- package/bin/issue-resource-class.sh +58 -0
- package/bin/label-follow-up-issues.sh +114 -0
- package/bin/pr-risk.sh +532 -0
- package/bin/sync-pr-labels.sh +112 -0
- package/hooks/heartbeat-hooks.sh +573 -0
- package/hooks/issue-reconcile-hooks.sh +217 -0
- package/hooks/pr-reconcile-hooks.sh +225 -0
- package/npm/bin/agent-control-plane.js +1984 -0
- package/npm/public-bin/agent-control-plane +3 -0
- package/package.json +61 -0
- package/tools/bin/agent-cleanup-worktree +247 -0
- package/tools/bin/agent-github-update-labels +66 -0
- package/tools/bin/agent-init-worktree +216 -0
- package/tools/bin/agent-project-archive-run +52 -0
- package/tools/bin/agent-project-capture-worker +46 -0
- package/tools/bin/agent-project-catch-up-merged-prs +137 -0
- package/tools/bin/agent-project-cleanup-session +244 -0
- package/tools/bin/agent-project-detached-launch +107 -0
- package/tools/bin/agent-project-heartbeat-loop +2347 -0
- package/tools/bin/agent-project-open-issue-worktree +89 -0
- package/tools/bin/agent-project-open-pr-worktree +80 -0
- package/tools/bin/agent-project-publish-issue-pr +349 -0
- package/tools/bin/agent-project-reconcile-issue-session +1128 -0
- package/tools/bin/agent-project-reconcile-pr-session +1005 -0
- package/tools/bin/agent-project-retry-state +147 -0
- package/tools/bin/agent-project-run-claude-session +657 -0
- package/tools/bin/agent-project-run-codex-resilient +718 -0
- package/tools/bin/agent-project-run-codex-session +316 -0
- package/tools/bin/agent-project-run-kilo-session +27 -0
- package/tools/bin/agent-project-run-openclaw-session +984 -0
- package/tools/bin/agent-project-run-opencode-session +27 -0
- package/tools/bin/agent-project-sync-anchor-repo +128 -0
- package/tools/bin/agent-project-worker-status +143 -0
- package/tools/bin/audit-agent-worktrees.sh +310 -0
- package/tools/bin/audit-issue-routing.sh +11 -0
- package/tools/bin/audit-retained-layout.sh +58 -0
- package/tools/bin/audit-retained-overlap.sh +135 -0
- package/tools/bin/audit-retained-worktrees.sh +228 -0
- package/tools/bin/branch-verification-guard.sh +351 -0
- package/tools/bin/capture-worker.sh +18 -0
- package/tools/bin/check-skill-contracts.sh +324 -0
- package/tools/bin/cleanup-worktree.sh +44 -0
- package/tools/bin/codex-quota +31 -0
- package/tools/bin/create-follow-up-issue.sh +114 -0
- package/tools/bin/dashboard-launchd-bootstrap.sh +38 -0
- package/tools/bin/flow-config-lib.sh +2127 -0
- package/tools/bin/flow-resident-worker-lib.sh +683 -0
- package/tools/bin/flow-runtime-doctor.sh +97 -0
- package/tools/bin/flow-shell-lib.sh +266 -0
- package/tools/bin/heartbeat-recovery-preflight.sh +106 -0
- package/tools/bin/heartbeat-safe-auto.sh +551 -0
- package/tools/bin/install-dashboard-launchd.sh +152 -0
- package/tools/bin/install-project-launchd.sh +219 -0
- package/tools/bin/issue-publish-scope-guard.sh +242 -0
- package/tools/bin/issue-requires-local-workspace-install.sh +31 -0
- package/tools/bin/issue-resource-class.sh +12 -0
- package/tools/bin/kick-scheduler.sh +75 -0
- package/tools/bin/label-follow-up-issues.sh +14 -0
- package/tools/bin/new-pr-worktree.sh +50 -0
- package/tools/bin/new-worktree.sh +49 -0
- package/tools/bin/pr-risk.sh +12 -0
- package/tools/bin/prepare-worktree.sh +140 -0
- package/tools/bin/profile-activate.sh +109 -0
- package/tools/bin/profile-adopt.sh +219 -0
- package/tools/bin/profile-smoke.sh +461 -0
- package/tools/bin/project-init.sh +189 -0
- package/tools/bin/project-launchd-bootstrap.sh +54 -0
- package/tools/bin/project-remove.sh +155 -0
- package/tools/bin/project-runtime-supervisor.sh +56 -0
- package/tools/bin/project-runtimectl.sh +586 -0
- package/tools/bin/provider-cooldown-state.sh +166 -0
- package/tools/bin/publish-issue-worker.sh +31 -0
- package/tools/bin/reconcile-issue-worker.sh +34 -0
- package/tools/bin/reconcile-pr-worker.sh +34 -0
- package/tools/bin/record-verification.sh +71 -0
- package/tools/bin/render-architecture-infographics.sh +110 -0
- package/tools/bin/render-dashboard-demo-media.sh +333 -0
- package/tools/bin/render-dashboard-snapshot.py +16 -0
- package/tools/bin/render-flow-config.sh +86 -0
- package/tools/bin/retry-state.sh +31 -0
- package/tools/bin/reuse-issue-worktree.sh +75 -0
- package/tools/bin/run-codex-bypass.sh +3 -0
- package/tools/bin/run-codex-safe.sh +3 -0
- package/tools/bin/run-codex-task.sh +231 -0
- package/tools/bin/scaffold-profile.sh +374 -0
- package/tools/bin/serve-dashboard.sh +5 -0
- package/tools/bin/split-retained-slice.sh +124 -0
- package/tools/bin/start-issue-worker.sh +796 -0
- package/tools/bin/start-pr-fix-worker.sh +458 -0
- package/tools/bin/start-pr-merge-repair-worker.sh +8 -0
- package/tools/bin/start-pr-review-worker.sh +227 -0
- package/tools/bin/start-resident-issue-loop.sh +908 -0
- package/tools/bin/sync-agent-repo.sh +52 -0
- package/tools/bin/sync-dependency-baseline.sh +247 -0
- package/tools/bin/sync-pr-labels.sh +12 -0
- package/tools/bin/sync-recurring-issue-checklist.sh +274 -0
- package/tools/bin/sync-shared-agent-home.sh +214 -0
- package/tools/bin/sync-vscode-workspace.sh +157 -0
- package/tools/bin/test-smoke.sh +63 -0
- package/tools/bin/uninstall-project-launchd.sh +55 -0
- package/tools/bin/update-github-labels.sh +14 -0
- package/tools/bin/worker-status.sh +19 -0
- package/tools/bin/workflow-catalog.sh +77 -0
- package/tools/dashboard/app.js +286 -0
- package/tools/dashboard/dashboard_snapshot.py +466 -0
- package/tools/dashboard/index.html +41 -0
- package/tools/dashboard/server.py +64 -0
- package/tools/dashboard/styles.css +351 -0
- package/tools/templates/issue-prompt-template.md +109 -0
- package/tools/templates/pr-fix-template.md +120 -0
- package/tools/templates/pr-merge-repair-template.md +91 -0
- package/tools/templates/pr-review-template.md +62 -0
- package/tools/templates/scheduled-issue-prompt-template.md +62 -0
- package/tools/tests/test-agent-control-plane-npm-cli.sh +279 -0
- package/tools/tests/test-agent-github-update-labels-falls-back-to-repository-id.sh +56 -0
- package/tools/tests/test-agent-project-claude-session-wrapper-clears-stale-sandbox-artifacts.sh +89 -0
- package/tools/tests/test-agent-project-claude-session-wrapper-does-not-retry-provider-quota.sh +82 -0
- package/tools/tests/test-agent-project-claude-session-wrapper-retries-transient-failures.sh +90 -0
- package/tools/tests/test-agent-project-claude-session-wrapper-times-out.sh +73 -0
- package/tools/tests/test-agent-project-claude-session-wrapper.sh +103 -0
- package/tools/tests/test-agent-project-cleanup-session-orphan-fallback.sh +90 -0
- package/tools/tests/test-agent-project-cleanup-session-skip-worktree-cleanup.sh +90 -0
- package/tools/tests/test-agent-project-codex-live-thread-persist.sh +76 -0
- package/tools/tests/test-agent-project-codex-recovery.sh +731 -0
- package/tools/tests/test-agent-project-codex-session-wrapper-clears-stale-sandbox-artifacts.sh +105 -0
- package/tools/tests/test-agent-project-codex-session-wrapper.sh +97 -0
- package/tools/tests/test-agent-project-open-pr-worktree-config-prefix.sh +81 -0
- package/tools/tests/test-agent-project-openclaw-session-wrapper-clears-stale-sandbox-artifacts.sh +109 -0
- package/tools/tests/test-agent-project-openclaw-session-wrapper-infers-blocked-result-contract.sh +89 -0
- package/tools/tests/test-agent-project-openclaw-session-wrapper-recovers-literal-env-artifacts.sh +113 -0
- package/tools/tests/test-agent-project-openclaw-session-wrapper-recovers-version-mismatch.sh +135 -0
- package/tools/tests/test-agent-project-openclaw-session-wrapper-resident.sh +179 -0
- package/tools/tests/test-agent-project-openclaw-session-wrapper-reuses-existing-agent-after-add-race.sh +119 -0
- package/tools/tests/test-agent-project-openclaw-session-wrapper-terminates-rate-limit-hang.sh +91 -0
- package/tools/tests/test-agent-project-openclaw-session-wrapper.sh +117 -0
- package/tools/tests/test-agent-project-publish-issue-pr-prunes-stale-worktree-entry.sh +148 -0
- package/tools/tests/test-agent-project-publish-issue-pr-reads-archived-session.sh +146 -0
- package/tools/tests/test-agent-project-publish-issue-pr-recovers-final-head.sh +145 -0
- package/tools/tests/test-agent-project-publish-issue-pr-reuses-existing-worktree.sh +147 -0
- package/tools/tests/test-agent-project-reconcile-failure-reason.sh +456 -0
- package/tools/tests/test-agent-project-reconcile-issue-archived-session-fallback.sh +96 -0
- package/tools/tests/test-agent-project-reconcile-issue-before-blocked.sh +90 -0
- package/tools/tests/test-agent-project-reconcile-issue-host-verification-recovery-uses-recovered-worktree.sh +212 -0
- package/tools/tests/test-agent-project-reconcile-issue-host-verification-recovery.sh +207 -0
- package/tools/tests/test-agent-project-reconcile-issue-provider-quota-schedules-provider-cooldown.sh +101 -0
- package/tools/tests/test-agent-project-reconcile-issue-session-backfills-lane-metadata-from-worker-key.sh +113 -0
- package/tools/tests/test-agent-project-reconcile-issue-session-clears-stale-failed-summary.sh +117 -0
- package/tools/tests/test-agent-project-reconcile-issue-session-initializes-shared-agent-home.sh +55 -0
- package/tools/tests/test-agent-project-reconcile-issue-session-normalizes-runner-state.sh +125 -0
- package/tools/tests/test-agent-project-reconcile-issue-session-records-invalid-contract-summary.sh +118 -0
- package/tools/tests/test-agent-project-reconcile-issue-session-skips-duplicate-blocked-comment.sh +144 -0
- package/tools/tests/test-agent-project-reconcile-issue-session-standardizes-no-commits-blocker.sh +145 -0
- package/tools/tests/test-agent-project-reconcile-issue-session-synthesizes-blocked-comment.sh +139 -0
- package/tools/tests/test-agent-project-reconcile-pr-blocked-host-recovery.sh +242 -0
- package/tools/tests/test-agent-project-reconcile-pr-guard-blocked-no-commit.sh +142 -0
- package/tools/tests/test-agent-project-reconcile-pr-provider-quota-schedules-provider-cooldown.sh +106 -0
- package/tools/tests/test-agent-project-reconcile-pr-session-initializes-shared-agent-home.sh +66 -0
- package/tools/tests/test-agent-project-reconcile-pr-updated-branch-noop.sh +129 -0
- package/tools/tests/test-audit-agent-worktrees-active-launch-skips-git-inspection.sh +69 -0
- package/tools/tests/test-audit-agent-worktrees-broken-worktree.sh +43 -0
- package/tools/tests/test-audit-agent-worktrees-pending-launch-owner.sh +46 -0
- package/tools/tests/test-audit-agent-worktrees-unreconciled-owner.sh +79 -0
- package/tools/tests/test-audit-issue-routing-managed-branch-globs.sh +56 -0
- package/tools/tests/test-branch-verification-guard-generated-artifacts.sh +72 -0
- package/tools/tests/test-branch-verification-guard-targeted-coverage.sh +125 -0
- package/tools/tests/test-codex-quota-manager-failure-driven-rotation.sh +178 -0
- package/tools/tests/test-codex-quota-wrapper.sh +37 -0
- package/tools/tests/test-contribution-docs.sh +18 -0
- package/tools/tests/test-control-plane-dashboard-runtime-smoke.sh +343 -0
- package/tools/tests/test-create-follow-up-issue.sh +73 -0
- package/tools/tests/test-dashboard-launchd-bootstrap.sh +55 -0
- package/tools/tests/test-flow-export-execution-env-exports-repo-id.sh +30 -0
- package/tools/tests/test-flow-export-github-cli-auth-env-prefers-git-credential.sh +48 -0
- package/tools/tests/test-flow-github-api-repo-fallback-preserves-input.sh +85 -0
- package/tools/tests/test-flow-github-api-repo-prefers-explicit-repository-id.sh +60 -0
- package/tools/tests/test-flow-github-issue-list-falls-back-to-repository-id.sh +64 -0
- package/tools/tests/test-flow-github-pr-list-falls-back-to-repository-id.sh +77 -0
- package/tools/tests/test-flow-resident-can-reuse-does-not-leak-metadata.sh +52 -0
- package/tools/tests/test-flow-resident-reap-stale-controllers.sh +63 -0
- package/tools/tests/test-flow-resolve-codex-quota-tools.sh +104 -0
- package/tools/tests/test-flow-runtime-doctor-profile-selection.sh +27 -0
- package/tools/tests/test-heartbeat-codex-pr-linked-issue-exclusion.sh +79 -0
- package/tools/tests/test-heartbeat-hooks-enqueue-resident-issue-for-idle-controller.sh +115 -0
- package/tools/tests/test-heartbeat-hooks-enqueue-resident-issue-for-live-lane-controller.sh +117 -0
- package/tools/tests/test-heartbeat-hooks-start-resident-issue-loop-claude.sh +96 -0
- package/tools/tests/test-heartbeat-hooks-start-resident-issue-loop-codex.sh +96 -0
- package/tools/tests/test-heartbeat-hooks-start-resident-issue-loop.sh +96 -0
- package/tools/tests/test-heartbeat-loop-auth-wait-does-not-consume-capacity.sh +170 -0
- package/tools/tests/test-heartbeat-loop-blocked-recovery-lane.sh +201 -0
- package/tools/tests/test-heartbeat-loop-blocked-recovery-vs-pr-reservation.sh +201 -0
- package/tools/tests/test-heartbeat-loop-idle-resident-controller-does-not-block-launches.sh +160 -0
- package/tools/tests/test-heartbeat-loop-pr-launch-dedup.sh +133 -0
- package/tools/tests/test-heartbeat-loop-provider-cooldown-suppresses-launches.sh +157 -0
- package/tools/tests/test-heartbeat-loop-reaps-stale-resident-controller.sh +181 -0
- package/tools/tests/test-heartbeat-loop-waiting-provider-resident-controller-does-not-block-launches.sh +160 -0
- package/tools/tests/test-heartbeat-ready-issues-blocked-recovery.sh +134 -0
- package/tools/tests/test-heartbeat-safe-auto-dynamic-concurrency.sh +162 -0
- package/tools/tests/test-heartbeat-safe-auto-no-tmux-sessions.sh +136 -0
- package/tools/tests/test-heartbeat-safe-auto-openclaw-skips-codex-quota.sh +139 -0
- package/tools/tests/test-heartbeat-safe-auto-quota-health-signal.sh +119 -0
- package/tools/tests/test-heartbeat-safe-auto-stale-shared-loop-pid-does-not-skip.sh +140 -0
- package/tools/tests/test-heartbeat-safe-auto-static-capacity-without-quota-cache.sh +142 -0
- package/tools/tests/test-heartbeat-safe-auto-zero-healthy-pools.sh +141 -0
- package/tools/tests/test-heartbeat-sync-issue-labels-empty-schedule.sh +65 -0
- package/tools/tests/test-heartbeat-sync-open-agent-prs-terminal-clears-running.sh +179 -0
- package/tools/tests/test-install-dashboard-launchd.sh +78 -0
- package/tools/tests/test-install-project-launchd-adds-tool-paths.sh +87 -0
- package/tools/tests/test-install-project-launchd.sh +110 -0
- package/tools/tests/test-issue-local-workspace-install-policy.sh +81 -0
- package/tools/tests/test-issue-publish-scope-guard-docs-signal.sh +70 -0
- package/tools/tests/test-issue-reconcile-hooks-success-clears-blocked.sh +36 -0
- package/tools/tests/test-kick-scheduler-requires-explicit-profile.sh +47 -0
- package/tools/tests/test-label-follow-up-issues-falls-back-to-repository-id.sh +132 -0
- package/tools/tests/test-manual-operator-entrypoints-require-explicit-profile.sh +64 -0
- package/tools/tests/test-package-funding-metadata.sh +21 -0
- package/tools/tests/test-package-public-metadata.sh +62 -0
- package/tools/tests/test-placeholder-worker-adapters.sh +38 -0
- package/tools/tests/test-pr-reconcile-hooks-refreshes-recurring-issue-checklist.sh +110 -0
- package/tools/tests/test-pr-risk-cohesive-mobile-locale-scope.sh +70 -0
- package/tools/tests/test-pr-risk-fix-label-semantics.sh +114 -0
- package/tools/tests/test-pr-risk-local-first-no-checks.sh +70 -0
- package/tools/tests/test-prepare-worktree-simple-repo-baseline.sh +67 -0
- package/tools/tests/test-profile-activate.sh +33 -0
- package/tools/tests/test-profile-adopt-allow-missing-repo.sh +68 -0
- package/tools/tests/test-profile-adopt-skip-workspace-sync-missing-file.sh +61 -0
- package/tools/tests/test-profile-adopt-syncs-anchor-and-workspace.sh +90 -0
- package/tools/tests/test-profile-smoke-collision.sh +44 -0
- package/tools/tests/test-profile-smoke-invalid-claude-config.sh +31 -0
- package/tools/tests/test-profile-smoke-invalid-provider-pool.sh +68 -0
- package/tools/tests/test-profile-smoke-repo-slug-mismatch.sh +36 -0
- package/tools/tests/test-profile-smoke.sh +45 -0
- package/tools/tests/test-project-init-force-and-skip-sync.sh +61 -0
- package/tools/tests/test-project-init-repo-slug-mismatch.sh +29 -0
- package/tools/tests/test-project-init.sh +66 -0
- package/tools/tests/test-project-launchd-bootstrap.sh +66 -0
- package/tools/tests/test-project-remove.sh +150 -0
- package/tools/tests/test-project-runtime-supervisor.sh +47 -0
- package/tools/tests/test-project-runtimectl-launchd.sh +115 -0
- package/tools/tests/test-project-runtimectl-missing-profile.sh +54 -0
- package/tools/tests/test-project-runtimectl-start-falls-back-to-bootstrap.sh +108 -0
- package/tools/tests/test-project-runtimectl-status-reports-supervisor-as-heartbeat-parent.sh +95 -0
- package/tools/tests/test-project-runtimectl-status-supervisor-running.sh +59 -0
- package/tools/tests/test-project-runtimectl-stop-cancels-pending-kick.sh +85 -0
- package/tools/tests/test-project-runtimectl-stop-clears-running-labels.sh +78 -0
- package/tools/tests/test-project-runtimectl.sh +212 -0
- package/tools/tests/test-provider-cooldown-state-prefers-runtime-worker-context.sh +39 -0
- package/tools/tests/test-provider-cooldown-state.sh +59 -0
- package/tools/tests/test-public-repo-docs.sh +159 -0
- package/tools/tests/test-reconcile-pr-worker-acp-config-routing.sh +75 -0
- package/tools/tests/test-render-dashboard-snapshot.sh +149 -0
- package/tools/tests/test-render-flow-config-demo-profile.sh +36 -0
- package/tools/tests/test-render-flow-config-provider-pool-fallback.sh +81 -0
- package/tools/tests/test-render-flow-config.sh +52 -0
- package/tools/tests/test-run-codex-task-claude-routing.sh +125 -0
- package/tools/tests/test-run-codex-task-codex-resident-routing.sh +108 -0
- package/tools/tests/test-run-codex-task-kilo-routing.sh +98 -0
- package/tools/tests/test-run-codex-task-openclaw-resident-routing.sh +117 -0
- package/tools/tests/test-run-codex-task-openclaw-routing.sh +113 -0
- package/tools/tests/test-run-codex-task-opencode-routing.sh +98 -0
- package/tools/tests/test-run-codex-task-provider-pool-fallback-routing.sh +146 -0
- package/tools/tests/test-scaffold-profile.sh +108 -0
- package/tools/tests/test-serve-dashboard.sh +93 -0
- package/tools/tests/test-start-issue-worker-blocked-context.sh +129 -0
- package/tools/tests/test-start-issue-worker-blocks-complete-recurring-checklist.sh +189 -0
- package/tools/tests/test-start-issue-worker-local-install-routing.sh +157 -0
- package/tools/tests/test-start-issue-worker-profile-template-routing.sh +149 -0
- package/tools/tests/test-start-issue-worker-recurring-resident-reuse-codex.sh +212 -0
- package/tools/tests/test-start-issue-worker-recurring-resident-reuse.sh +219 -0
- package/tools/tests/test-start-issue-worker-renders-verification-snippet.sh +155 -0
- package/tools/tests/test-start-issue-worker-resident-reuse-falls-back-to-new-worktree.sh +199 -0
- package/tools/tests/test-start-pr-fix-worker-host-blocker-context.sh +275 -0
- package/tools/tests/test-start-resident-issue-loop-adopts-next-recurring-issue.sh +185 -0
- package/tools/tests/test-start-resident-issue-loop-clears-pending-while-waiting-due.sh +152 -0
- package/tools/tests/test-start-resident-issue-loop-consumes-queued-lease.sh +186 -0
- package/tools/tests/test-start-resident-issue-loop-fails-over-provider-pool.sh +212 -0
- package/tools/tests/test-start-resident-issue-loop-immediate-cycles.sh +148 -0
- package/tools/tests/test-start-resident-issue-loop-waits-for-provider.sh +194 -0
- package/tools/tests/test-start-resident-issue-loop-waits-for-terminal-reconcile-status.sh +198 -0
- package/tools/tests/test-start-resident-issue-loop-yields-to-live-lane-controller.sh +145 -0
- package/tools/tests/test-sync-pr-labels-fix-lane-uses-repair-queued.sh +67 -0
- package/tools/tests/test-sync-recurring-issue-checklist-backfills-workflow-complete-blocker.sh +70 -0
- package/tools/tests/test-sync-recurring-issue-checklist.sh +95 -0
- package/tools/tests/test-sync-shared-agent-home-local-source-root.sh +66 -0
- package/tools/tests/test-sync-shared-agent-home-preserves-unrelated-workflow-catalog-skill.sh +47 -0
- package/tools/tests/test-test-smoke.sh +86 -0
- package/tools/tests/test-uninstall-project-launchd.sh +37 -0
- package/tools/tests/test-update-github-labels-prefers-sibling-helper.sh +49 -0
- package/tools/tests/test-workflow-catalog.sh +43 -0
- package/tools/vendor/codex-quota/LICENSE +21 -0
- package/tools/vendor/codex-quota/README.md +459 -0
- package/tools/vendor/codex-quota/codex-quota.js +261 -0
- package/tools/vendor/codex-quota/lib/claude-accounts.js +226 -0
- package/tools/vendor/codex-quota/lib/claude-oauth.js +174 -0
- package/tools/vendor/codex-quota/lib/claude-tokens.js +471 -0
- package/tools/vendor/codex-quota/lib/claude-usage.js +929 -0
- package/tools/vendor/codex-quota/lib/codex-accounts.js +205 -0
- package/tools/vendor/codex-quota/lib/codex-tokens.js +326 -0
- package/tools/vendor/codex-quota/lib/codex-usage.js +32 -0
- package/tools/vendor/codex-quota/lib/color.js +72 -0
- package/tools/vendor/codex-quota/lib/constants.js +57 -0
- package/tools/vendor/codex-quota/lib/container.js +143 -0
- package/tools/vendor/codex-quota/lib/display.js +1111 -0
- package/tools/vendor/codex-quota/lib/fs.js +63 -0
- package/tools/vendor/codex-quota/lib/handlers.js +2060 -0
- package/tools/vendor/codex-quota/lib/jwt.js +33 -0
- package/tools/vendor/codex-quota/lib/oauth.js +486 -0
- package/tools/vendor/codex-quota/lib/paths.js +34 -0
- package/tools/vendor/codex-quota/lib/prompts.js +44 -0
- package/tools/vendor/codex-quota/lib/sync.js +1438 -0
- package/tools/vendor/codex-quota/lib/token-match.js +96 -0
- package/tools/vendor/codex-quota-manager/scripts/auto-switch.sh +500 -0
- package/tools/vendor/codex-quota-manager/scripts/batch-add.sh +123 -0
|
@@ -0,0 +1,1111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bars, boxes, usage lines, help text.
|
|
3
|
+
* Depends on: lib/constants.js, lib/color.js, lib/jwt.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { PRIMARY_CMD } from "./constants.js";
|
|
7
|
+
import { GREEN, RED, YELLOW, colorize, getPackageVersion } from "./color.js";
|
|
8
|
+
import { extractProfile } from "./jwt.js";
|
|
9
|
+
import { normalizeClaudeOrgId } from "./claude-usage.js";
|
|
10
|
+
|
|
11
|
+
export function parseWindow(window) {
|
|
12
|
+
if (!window) return null;
|
|
13
|
+
const used = window.used_percent ?? window.usedPercent ?? window.percent_used;
|
|
14
|
+
const remaining = window.remaining_percent ?? window.remainingPercent;
|
|
15
|
+
const resets = window.resets_at ?? window.resetsAt ?? window.reset_at;
|
|
16
|
+
const resetAfterSeconds = window.reset_after_seconds ?? window.resetAfterSeconds;
|
|
17
|
+
return { used, remaining, resets, resetAfterSeconds };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function formatPercent(used, remaining) {
|
|
21
|
+
// Prefer showing remaining (matches Codex CLI /status display)
|
|
22
|
+
if (remaining !== undefined) return `${Math.round(remaining)}% left`;
|
|
23
|
+
if (used !== undefined) return `${Math.round(100 - used)}% left`;
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// normalizeClaudeOrgId and isClaudeAuthError are imported from ./claude-usage.js
|
|
28
|
+
|
|
29
|
+
export function formatResetTime(seconds, style = "parentheses") {
|
|
30
|
+
if (!seconds) return "";
|
|
31
|
+
|
|
32
|
+
const resetDate = new Date(Date.now() + seconds * 1000);
|
|
33
|
+
const hours = Math.floor(seconds / 3600);
|
|
34
|
+
const mins = Math.floor((seconds % 3600) / 60);
|
|
35
|
+
|
|
36
|
+
// Format time as HH:MM
|
|
37
|
+
const timeStr = resetDate.toLocaleTimeString("en-US", {
|
|
38
|
+
hour: "2-digit",
|
|
39
|
+
minute: "2-digit",
|
|
40
|
+
hour12: false
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// For display matching Codex CLI style
|
|
44
|
+
if (style === "inline") {
|
|
45
|
+
if (hours >= 24) {
|
|
46
|
+
// Show date for weekly+ resets: "resets 20:26 on 19 Jan"
|
|
47
|
+
const day = resetDate.getDate();
|
|
48
|
+
const month = resetDate.toLocaleDateString("en-US", { month: "short" });
|
|
49
|
+
return `(resets ${timeStr} on ${day} ${month})`;
|
|
50
|
+
}
|
|
51
|
+
// Same day: "resets 23:14"
|
|
52
|
+
return `(resets ${timeStr})`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Legacy parentheses style for JSON/other uses
|
|
56
|
+
if (hours > 24) {
|
|
57
|
+
const days = Math.floor(hours / 24);
|
|
58
|
+
return `(resets in ${days}d ${hours % 24}h)`;
|
|
59
|
+
}
|
|
60
|
+
if (hours > 0) {
|
|
61
|
+
return `(resets in ${hours}h ${mins}m)`;
|
|
62
|
+
}
|
|
63
|
+
return `(resets in ${mins}m)`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function formatUsage(payload) {
|
|
67
|
+
const usage = payload?.usage ?? payload;
|
|
68
|
+
|
|
69
|
+
// Handle new API format: rate_limit.primary_window / secondary_window
|
|
70
|
+
const rateLimit = usage?.rate_limit;
|
|
71
|
+
const primaryWindow = rateLimit?.primary_window ?? usage?.primary ?? usage?.session ?? usage?.fiveHour;
|
|
72
|
+
const secondaryWindow = rateLimit?.secondary_window ?? usage?.secondary ?? usage?.weekly ?? usage?.week;
|
|
73
|
+
const tertiaryWindow = usage?.tertiary ?? usage?.monthly ?? usage?.month;
|
|
74
|
+
|
|
75
|
+
const session = parseWindow(primaryWindow);
|
|
76
|
+
const weekly = parseWindow(secondaryWindow);
|
|
77
|
+
const monthly = parseWindow(tertiaryWindow);
|
|
78
|
+
|
|
79
|
+
const lines = [];
|
|
80
|
+
|
|
81
|
+
if (session) {
|
|
82
|
+
const pct = formatPercent(session.used, session.remaining);
|
|
83
|
+
const reset = session.resetAfterSeconds ? formatResetTime(session.resetAfterSeconds) :
|
|
84
|
+
session.resets ? `(resets ${session.resets})` : "";
|
|
85
|
+
lines.push(` Session: ${pct || "?"} ${reset}`);
|
|
86
|
+
}
|
|
87
|
+
if (weekly) {
|
|
88
|
+
const pct = formatPercent(weekly.used, weekly.remaining);
|
|
89
|
+
const reset = weekly.resetAfterSeconds ? formatResetTime(weekly.resetAfterSeconds) :
|
|
90
|
+
weekly.resets ? `(resets ${weekly.resets})` : "";
|
|
91
|
+
lines.push(` Weekly: ${pct || "?"} ${reset}`);
|
|
92
|
+
}
|
|
93
|
+
if (monthly) {
|
|
94
|
+
const pct = formatPercent(monthly.used, monthly.remaining);
|
|
95
|
+
const reset = monthly.resetAfterSeconds ? formatResetTime(monthly.resetAfterSeconds) :
|
|
96
|
+
monthly.resets ? `(resets ${monthly.resets})` : "";
|
|
97
|
+
lines.push(` Monthly: ${pct || "?"} ${reset}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Handle credits
|
|
101
|
+
const credits = usage?.credits;
|
|
102
|
+
if (credits) {
|
|
103
|
+
const balance = credits.balance ?? credits.remaining;
|
|
104
|
+
if (balance !== undefined) {
|
|
105
|
+
lines.push(` Credits: ${parseFloat(balance).toFixed(2)} remaining`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Plan type
|
|
110
|
+
const planType = usage?.plan_type;
|
|
111
|
+
if (planType) {
|
|
112
|
+
lines.push(` Plan: ${planType}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return lines.length ? lines : [" (no usage data)"];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function printBar(remaining, width = 20) {
|
|
119
|
+
// Bar shows remaining quota: full = 100% left, empty = 0% left (matches Codex CLI)
|
|
120
|
+
const filled = Math.round((remaining / 100) * width);
|
|
121
|
+
const empty = width - filled;
|
|
122
|
+
const bar = "█".repeat(filled) + "░".repeat(empty);
|
|
123
|
+
return `[${bar}]`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Box drawing characters
|
|
127
|
+
const BOX = {
|
|
128
|
+
topLeft: "╭",
|
|
129
|
+
topRight: "╮",
|
|
130
|
+
bottomLeft: "╰",
|
|
131
|
+
bottomRight: "╯",
|
|
132
|
+
horizontal: "─",
|
|
133
|
+
vertical: "│",
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Draw a box around content lines
|
|
138
|
+
* @param {string[]} lines - Lines to display inside the box
|
|
139
|
+
* @param {number} minWidth - Minimum box width (default 70)
|
|
140
|
+
* @returns {string[]} Lines with box characters
|
|
141
|
+
*/
|
|
142
|
+
export function drawBox(lines, minWidth = 70) {
|
|
143
|
+
// Calculate content width (max line length)
|
|
144
|
+
const contentWidth = Math.max(minWidth, ...lines.map(l => l.length)) + 2;
|
|
145
|
+
|
|
146
|
+
const output = [];
|
|
147
|
+
|
|
148
|
+
// Top border
|
|
149
|
+
output.push(BOX.topLeft + BOX.horizontal.repeat(contentWidth) + BOX.topRight);
|
|
150
|
+
|
|
151
|
+
// Content lines with padding
|
|
152
|
+
for (const line of lines) {
|
|
153
|
+
const padding = contentWidth - line.length - 1;
|
|
154
|
+
output.push(BOX.vertical + " " + line + " ".repeat(padding) + BOX.vertical);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Bottom border
|
|
158
|
+
output.push(BOX.bottomLeft + BOX.horizontal.repeat(contentWidth) + BOX.bottomRight);
|
|
159
|
+
|
|
160
|
+
return output;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Build usage lines for an account (for box display)
|
|
165
|
+
* @param {object} account - Account object
|
|
166
|
+
* @param {object} payload - Usage payload from API
|
|
167
|
+
* @returns {string[]} Lines to display
|
|
168
|
+
*/
|
|
169
|
+
export function buildAccountUsageLines(account, payload) {
|
|
170
|
+
const lines = [];
|
|
171
|
+
const usage = payload?.usage ?? payload;
|
|
172
|
+
const rateLimit = usage?.rate_limit;
|
|
173
|
+
const primaryWindow = rateLimit?.primary_window ?? usage?.primary ?? usage?.session ?? usage?.fiveHour;
|
|
174
|
+
const secondaryWindow = rateLimit?.secondary_window ?? usage?.secondary ?? usage?.weekly ?? usage?.week;
|
|
175
|
+
const session = parseWindow(primaryWindow);
|
|
176
|
+
const weekly = parseWindow(secondaryWindow);
|
|
177
|
+
|
|
178
|
+
// Extract profile info from token
|
|
179
|
+
const profile = extractProfile(account.access);
|
|
180
|
+
const planType = usage?.plan_type ?? profile.planType;
|
|
181
|
+
const planDisplay = planType ? ` (${planType})` : "";
|
|
182
|
+
|
|
183
|
+
// Header: Codex (label) <email> (plan) — matches Claude format
|
|
184
|
+
const labelDisplay = account.label ? ` (${account.label})` : "";
|
|
185
|
+
const emailDisplay = profile.email ? ` <${profile.email}>` : "";
|
|
186
|
+
lines.push(`Codex${labelDisplay}${emailDisplay}${planDisplay}`);
|
|
187
|
+
lines.push("");
|
|
188
|
+
|
|
189
|
+
if (payload.error) {
|
|
190
|
+
lines.push(`Error: ${payload.error}`);
|
|
191
|
+
if (account.source) {
|
|
192
|
+
lines.push(` Source: ${shortenPath(account.source)}`);
|
|
193
|
+
}
|
|
194
|
+
return lines;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 5h limit bar (session/primary window)
|
|
198
|
+
if (session) {
|
|
199
|
+
const remaining = session.remaining ?? (session.used !== undefined ? 100 - session.used : null);
|
|
200
|
+
if (remaining !== null) {
|
|
201
|
+
const reset = session.resetAfterSeconds ? formatResetTime(session.resetAfterSeconds, "inline") : "";
|
|
202
|
+
lines.push(`5h limit: ${printBar(remaining)} ${Math.round(remaining)}% left ${reset}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Weekly limit bar (secondary window)
|
|
207
|
+
if (weekly) {
|
|
208
|
+
const remaining = weekly.remaining ?? (weekly.used !== undefined ? 100 - weekly.used : null);
|
|
209
|
+
if (remaining !== null) {
|
|
210
|
+
const reset = weekly.resetAfterSeconds ? formatResetTime(weekly.resetAfterSeconds, "inline") : "";
|
|
211
|
+
lines.push(`Weekly limit: ${printBar(remaining)} ${Math.round(remaining)}% left ${reset}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (account.source) {
|
|
216
|
+
lines.push(` Source: ${shortenPath(account.source)}`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return lines;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function formatClaudePercentLeft(percentLeft) {
|
|
223
|
+
if (percentLeft === null || percentLeft === undefined || Number.isNaN(percentLeft)) {
|
|
224
|
+
return "?";
|
|
225
|
+
}
|
|
226
|
+
return `${Math.round(percentLeft)}% left`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function normalizePercentUsed(value) {
|
|
230
|
+
if (value === null || value === undefined) return null;
|
|
231
|
+
let used = Number(value);
|
|
232
|
+
if (!Number.isFinite(used)) return null;
|
|
233
|
+
|
|
234
|
+
// Claude OAuth usage now reports percentage points (0-100).
|
|
235
|
+
// Keep integer values like 1 as 1% used; only treat fractional values
|
|
236
|
+
// in (0, 1) as ratio form for backward compatibility.
|
|
237
|
+
if (used > 0 && used < 1) {
|
|
238
|
+
used *= 100;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return Math.min(100, Math.max(0, used));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function parseClaudeUtilizationWindow(window) {
|
|
245
|
+
if (!window || typeof window !== "object") return null;
|
|
246
|
+
const utilization = window.utilization ?? window.used_percent ?? window.usedPercent ?? window.percent_used;
|
|
247
|
+
const remainingPercent = window.remaining_percent ?? window.remainingPercent ?? window.percent_remaining;
|
|
248
|
+
const resetsAt = window.resets_at ?? window.resetsAt ?? window.reset_at ?? window.resetAt;
|
|
249
|
+
let remaining = null;
|
|
250
|
+
if (remainingPercent !== undefined) {
|
|
251
|
+
remaining = Number(remainingPercent);
|
|
252
|
+
} else {
|
|
253
|
+
const used = normalizePercentUsed(utilization);
|
|
254
|
+
if (used !== null) {
|
|
255
|
+
remaining = 100 - used;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (remaining !== null && Number.isFinite(remaining)) {
|
|
259
|
+
remaining = Math.min(100, Math.max(0, remaining));
|
|
260
|
+
}
|
|
261
|
+
return { remaining, resetsAt };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function formatResetAt(dateString) {
|
|
265
|
+
if (!dateString) return "";
|
|
266
|
+
const date = new Date(dateString);
|
|
267
|
+
if (Number.isNaN(date.getTime())) return "";
|
|
268
|
+
const seconds = Math.max(0, Math.floor((date.getTime() - Date.now()) / 1000));
|
|
269
|
+
return formatResetTime(seconds, "inline");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function parseClaudeWindow(window) {
|
|
273
|
+
if (!window || typeof window !== "object") return null;
|
|
274
|
+
const usedPercent = window.used_percent ?? window.usedPercent ?? window.percent_used ?? window.percentUsed;
|
|
275
|
+
const remainingPercent = window.remaining_percent ?? window.remainingPercent ?? window.percent_remaining ?? window.percentRemaining;
|
|
276
|
+
const used = window.used ?? window.used_units ?? window.usedUnits ?? window.used_tokens ?? window.usedTokens;
|
|
277
|
+
const remaining = window.remaining ?? window.remaining_units ?? window.remainingUnits ?? window.remaining_tokens ?? window.remainingTokens;
|
|
278
|
+
const limit = window.limit ?? window.quota ?? window.total ?? window.max ?? window.maximum;
|
|
279
|
+
const resets = window.resets_at ?? window.resetsAt ?? window.reset_at ?? window.resetAt ?? window.reset;
|
|
280
|
+
const resetAfterSeconds = window.reset_after_seconds ?? window.resetAfterSeconds;
|
|
281
|
+
|
|
282
|
+
let percentLeft = null;
|
|
283
|
+
if (remainingPercent !== undefined) {
|
|
284
|
+
percentLeft = remainingPercent;
|
|
285
|
+
} else if (usedPercent !== undefined) {
|
|
286
|
+
percentLeft = 100 - usedPercent;
|
|
287
|
+
} else if (remaining !== undefined && Number.isFinite(limit) && limit > 0) {
|
|
288
|
+
percentLeft = (remaining / limit) * 100;
|
|
289
|
+
} else if (used !== undefined && Number.isFinite(limit) && limit > 0) {
|
|
290
|
+
percentLeft = (1 - used / limit) * 100;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return { percentLeft, used, remaining, limit, resets, resetAfterSeconds };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function formatClaudeLabel(label) {
|
|
297
|
+
if (!label) return "";
|
|
298
|
+
return label
|
|
299
|
+
.replace(/_/g, " ")
|
|
300
|
+
.replace(/(^|\s)\S/g, (m) => m.toUpperCase())
|
|
301
|
+
.trim();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function getClaudeUsageWindows(usage) {
|
|
305
|
+
if (!usage || typeof usage !== "object") return [];
|
|
306
|
+
const root = usage.usage ?? usage.quotas ?? usage.quota ?? usage;
|
|
307
|
+
const windows = [];
|
|
308
|
+
|
|
309
|
+
const seen = new Set();
|
|
310
|
+
const pushWindow = (label, window) => {
|
|
311
|
+
if (!window || typeof window !== "object") return;
|
|
312
|
+
if (seen.has(label)) return;
|
|
313
|
+
seen.add(label);
|
|
314
|
+
windows.push({ label, window });
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
pushWindow("Session", root.session ?? root.sessions ?? root.fiveHour ?? root.five_hour ?? root.primary);
|
|
318
|
+
pushWindow("Weekly", root.weekly ?? root.week ?? root.secondary);
|
|
319
|
+
|
|
320
|
+
const modelContainer = root.models ?? root.model ?? root.usage_by_model ?? root.model_usage;
|
|
321
|
+
if (modelContainer && typeof modelContainer === "object" && !Array.isArray(modelContainer)) {
|
|
322
|
+
for (const [key, value] of Object.entries(modelContainer)) {
|
|
323
|
+
pushWindow(formatClaudeLabel(key), value);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
pushWindow("Opus", root.opus ?? root.model_opus ?? root.claude_opus);
|
|
328
|
+
|
|
329
|
+
return windows;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export function formatClaudeOverageLine(overage) {
|
|
333
|
+
if (!overage || typeof overage !== "object") return null;
|
|
334
|
+
const limit = overage.limit ?? overage.spend_limit ?? overage.spendLimit ?? overage.overage_spend_limit;
|
|
335
|
+
const used = overage.used ?? overage.spent ?? overage.spend ?? overage.amount_used;
|
|
336
|
+
const remaining = overage.remaining ?? (limit !== undefined && used !== undefined ? limit - used : undefined);
|
|
337
|
+
const enabled = overage.enabled ?? overage.is_enabled ?? overage.active;
|
|
338
|
+
|
|
339
|
+
const parts = [];
|
|
340
|
+
if (enabled !== undefined) {
|
|
341
|
+
parts.push(enabled ? "enabled" : "disabled");
|
|
342
|
+
}
|
|
343
|
+
if (limit !== undefined) {
|
|
344
|
+
parts.push(`limit ${limit}`);
|
|
345
|
+
}
|
|
346
|
+
if (remaining !== undefined) {
|
|
347
|
+
parts.push(`remaining ${remaining}`);
|
|
348
|
+
}
|
|
349
|
+
if (!parts.length) return null;
|
|
350
|
+
return `Overage: ${parts.join(", ")}`;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function buildClaudeUsageLines(payload) {
|
|
354
|
+
const lines = [];
|
|
355
|
+
|
|
356
|
+
const account = payload?.account ?? {};
|
|
357
|
+
const email = account.email ?? account.email_address ?? account?.user?.email ?? account?.account?.email ?? null;
|
|
358
|
+
const membership = Array.isArray(account.memberships)
|
|
359
|
+
? account.memberships.find(m => normalizeClaudeOrgId(m?.organization?.uuid) === normalizeClaudeOrgId(payload?.orgId))
|
|
360
|
+
: null;
|
|
361
|
+
// Support both old format (from account API) and new OAuth format (from credentials)
|
|
362
|
+
const plan = payload?.subscriptionType
|
|
363
|
+
?? payload?.rateLimitTier
|
|
364
|
+
?? account.plan
|
|
365
|
+
?? account.plan_type
|
|
366
|
+
?? account.planType
|
|
367
|
+
?? account?.subscription?.plan
|
|
368
|
+
?? membership?.organization?.rate_limit_tier
|
|
369
|
+
?? (membership?.organization?.capabilities?.includes("claude_max") ? "claude_max" : null);
|
|
370
|
+
let planDisplay = null;
|
|
371
|
+
if (plan) {
|
|
372
|
+
planDisplay = formatClaudeLabel(
|
|
373
|
+
String(plan)
|
|
374
|
+
.replace(/^default_/, "")
|
|
375
|
+
.replace(/_\d+x$/i, "")
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
const label = payload?.label ? ` (${payload.label})` : "";
|
|
379
|
+
const header = `Claude${label}${email ? ` <${email}>` : ""}${planDisplay ? ` (${planDisplay})` : ""}`;
|
|
380
|
+
|
|
381
|
+
lines.push(header);
|
|
382
|
+
lines.push("");
|
|
383
|
+
|
|
384
|
+
if (!payload || payload.success === false) {
|
|
385
|
+
lines.push(`Error: ${payload?.error ?? "Claude usage unavailable"}`);
|
|
386
|
+
return lines;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const usage = payload?.usage;
|
|
390
|
+
let renderedUsage = false;
|
|
391
|
+
if (usage && typeof usage === "object") {
|
|
392
|
+
const fiveHour = parseClaudeUtilizationWindow(usage.five_hour ?? usage.fiveHour);
|
|
393
|
+
if (fiveHour && fiveHour.remaining !== null) {
|
|
394
|
+
const reset = formatResetAt(fiveHour.resetsAt);
|
|
395
|
+
lines.push(`5h limit: ${printBar(fiveHour.remaining)} ${Math.round(fiveHour.remaining)}% left ${reset}`.trimEnd());
|
|
396
|
+
renderedUsage = true;
|
|
397
|
+
}
|
|
398
|
+
const weekly = parseClaudeUtilizationWindow(usage.seven_day ?? usage.sevenDay);
|
|
399
|
+
if (weekly && weekly.remaining !== null) {
|
|
400
|
+
const reset = formatResetAt(weekly.resetsAt);
|
|
401
|
+
lines.push(`Weekly limit: ${printBar(weekly.remaining)} ${Math.round(weekly.remaining)}% left ${reset}`.trimEnd());
|
|
402
|
+
renderedUsage = true;
|
|
403
|
+
}
|
|
404
|
+
const opus = parseClaudeUtilizationWindow(usage.seven_day_opus ?? usage.sevenDayOpus);
|
|
405
|
+
if (opus && opus.remaining !== null) {
|
|
406
|
+
const reset = formatResetAt(opus.resetsAt);
|
|
407
|
+
lines.push(`Opus weekly: ${printBar(opus.remaining)} ${Math.round(opus.remaining)}% left ${reset}`.trimEnd());
|
|
408
|
+
renderedUsage = true;
|
|
409
|
+
}
|
|
410
|
+
const sonnet = parseClaudeUtilizationWindow(usage.seven_day_sonnet ?? usage.sevenDaySonnet);
|
|
411
|
+
if (sonnet && sonnet.remaining !== null) {
|
|
412
|
+
const reset = formatResetAt(sonnet.resetsAt);
|
|
413
|
+
lines.push(`Sonnet weekly: ${printBar(sonnet.remaining)} ${Math.round(sonnet.remaining)}% left ${reset}`.trimEnd());
|
|
414
|
+
renderedUsage = true;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (!renderedUsage) {
|
|
419
|
+
const windows = getClaudeUsageWindows(payload.usage);
|
|
420
|
+
if (windows.length) {
|
|
421
|
+
for (const { label, window } of windows) {
|
|
422
|
+
const parsed = parseClaudeWindow(window);
|
|
423
|
+
if (!parsed) continue;
|
|
424
|
+
const reset = parsed.resetAfterSeconds
|
|
425
|
+
? formatResetTime(parsed.resetAfterSeconds)
|
|
426
|
+
: parsed.resets ? `(resets ${parsed.resets})` : "";
|
|
427
|
+
lines.push(` ${label}: ${formatClaudePercentLeft(parsed.percentLeft)} ${reset}`.trimEnd());
|
|
428
|
+
}
|
|
429
|
+
} else {
|
|
430
|
+
lines.push(" Usage: (no usage data)");
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const overageLine = formatClaudeOverageLine(payload.overage);
|
|
435
|
+
if (overageLine) {
|
|
436
|
+
lines.push(` ${overageLine}`);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (payload.orgId) {
|
|
440
|
+
lines.push(` Org: ${payload.orgId}`);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (payload.source) {
|
|
444
|
+
lines.push(` Source: ${shortenPath(payload.source)}`);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (payload.errors) {
|
|
448
|
+
const parts = Object.entries(payload.errors).map(([key, value]) => `${key}=${value}`);
|
|
449
|
+
lines.push(` Partial errors: ${parts.join(", ")}`);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return lines;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
export function printHelp() {
|
|
456
|
+
console.log(`${PRIMARY_CMD} - Manage and monitor OpenAI Codex and Claude accounts
|
|
457
|
+
Version: ${getPackageVersion()}
|
|
458
|
+
|
|
459
|
+
Usage:
|
|
460
|
+
${PRIMARY_CMD} <namespace> [command] [options]
|
|
461
|
+
${PRIMARY_CMD} [label] Check quota for all accounts (Codex + Claude)
|
|
462
|
+
|
|
463
|
+
Namespaces:
|
|
464
|
+
codex Manage OpenAI Codex accounts
|
|
465
|
+
claude Manage Claude accounts
|
|
466
|
+
|
|
467
|
+
Options:
|
|
468
|
+
--json Output in JSON format
|
|
469
|
+
--local Use only stored account files; skip harness token checks
|
|
470
|
+
--dry-run Preview sync without writing files
|
|
471
|
+
--no-browser Print auth URL instead of opening browser
|
|
472
|
+
--no-color Disable colored output
|
|
473
|
+
--version, -v Show version number
|
|
474
|
+
--help, -h Show this help
|
|
475
|
+
|
|
476
|
+
Examples:
|
|
477
|
+
${PRIMARY_CMD} Check quota for all accounts (Codex + Claude)
|
|
478
|
+
${PRIMARY_CMD} codex Show Codex command help
|
|
479
|
+
${PRIMARY_CMD} claude Show Claude command help
|
|
480
|
+
${PRIMARY_CMD} codex quota Check quota for Codex accounts
|
|
481
|
+
${PRIMARY_CMD} claude quota Check quota for Claude accounts
|
|
482
|
+
${PRIMARY_CMD} codex add work Add Codex account with label "work"
|
|
483
|
+
${PRIMARY_CMD} claude add work Add Claude credential with label "work"
|
|
484
|
+
${PRIMARY_CMD} codex reauth work Re-authenticate existing "work" account
|
|
485
|
+
${PRIMARY_CMD} claude reauth work Re-authenticate existing "work" account
|
|
486
|
+
${PRIMARY_CMD} codex switch work Switch Codex/OpenCode/pi to "work"
|
|
487
|
+
${PRIMARY_CMD} claude switch work Switch Claude Code/OpenCode/pi to "work"
|
|
488
|
+
${PRIMARY_CMD} codex sync Sync active Codex account to CLI auth files
|
|
489
|
+
${PRIMARY_CMD} codex sync --dry-run Preview Codex sync without writing
|
|
490
|
+
${PRIMARY_CMD} claude sync --dry-run Preview Claude sync without writing
|
|
491
|
+
|
|
492
|
+
Account sources (checked in order):
|
|
493
|
+
1. CODEX_ACCOUNTS env var (JSON array)
|
|
494
|
+
2. ~/.codex-accounts.json
|
|
495
|
+
3. ~/.opencode/openai-codex-auth-accounts.json
|
|
496
|
+
4. ~/.codex/auth.json (Codex CLI format)
|
|
497
|
+
|
|
498
|
+
OpenCode & pi Integration:
|
|
499
|
+
The 'switch' and 'sync' commands update Codex CLI (~/.codex/auth.json) plus
|
|
500
|
+
OpenCode (~/.local/share/opencode/auth.json) and pi (~/.pi/agent/auth.json)
|
|
501
|
+
authentication files when they exist, enabling seamless account switching.
|
|
502
|
+
The activeLabel marker in multi-account files is used for sync and divergence
|
|
503
|
+
warnings in list/quota output.
|
|
504
|
+
|
|
505
|
+
Run '${PRIMARY_CMD} <namespace> <command> --help' for help on a specific command.
|
|
506
|
+
`);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
export function printHelpCodex() {
|
|
510
|
+
console.log(`${PRIMARY_CMD} codex - Manage OpenAI Codex accounts
|
|
511
|
+
|
|
512
|
+
Usage:
|
|
513
|
+
${PRIMARY_CMD} codex [command] [options]
|
|
514
|
+
|
|
515
|
+
Commands:
|
|
516
|
+
quota [label] Check usage quota (default command)
|
|
517
|
+
add [label] Add a new account via OAuth browser flow
|
|
518
|
+
reauth <label> Re-authenticate an existing account via OAuth
|
|
519
|
+
switch <label> Switch active account for Codex CLI, OpenCode, and pi
|
|
520
|
+
sync Sync activeLabel to Codex CLI, OpenCode, and pi
|
|
521
|
+
list List all accounts from all sources
|
|
522
|
+
remove <label> Remove an account from storage
|
|
523
|
+
|
|
524
|
+
Options:
|
|
525
|
+
--json Output in JSON format
|
|
526
|
+
--dry-run Preview sync without writing files
|
|
527
|
+
--no-browser Print auth URL instead of opening browser
|
|
528
|
+
--no-color Disable colored output
|
|
529
|
+
--help, -h Show this help
|
|
530
|
+
|
|
531
|
+
Examples:
|
|
532
|
+
${PRIMARY_CMD} codex Check quota for Codex accounts
|
|
533
|
+
${PRIMARY_CMD} codex personal Check quota for "personal" account
|
|
534
|
+
${PRIMARY_CMD} codex add work Add new account with label "work"
|
|
535
|
+
${PRIMARY_CMD} codex reauth work Re-authenticate "work" account
|
|
536
|
+
${PRIMARY_CMD} codex switch personal Switch to "personal" account
|
|
537
|
+
${PRIMARY_CMD} codex list List all configured accounts
|
|
538
|
+
${PRIMARY_CMD} codex remove old Remove "old" account
|
|
539
|
+
${PRIMARY_CMD} codex sync Sync the activeLabel account
|
|
540
|
+
${PRIMARY_CMD} codex sync --dry-run Preview sync without writing
|
|
541
|
+
|
|
542
|
+
Notes:
|
|
543
|
+
- switch and sync update activeLabel in ~/.codex-accounts.json when available
|
|
544
|
+
- list/quota warn when CLI auth diverges (use '${PRIMARY_CMD} codex sync')
|
|
545
|
+
`);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
export function printHelpClaude() {
|
|
549
|
+
console.log(`${PRIMARY_CMD} claude - Manage Claude credentials
|
|
550
|
+
|
|
551
|
+
Usage:
|
|
552
|
+
${PRIMARY_CMD} claude [command] [options]
|
|
553
|
+
|
|
554
|
+
Commands:
|
|
555
|
+
quota [label] Check Claude usage (default command)
|
|
556
|
+
add [label] Add a Claude credential (via OAuth or manual entry)
|
|
557
|
+
reauth <label> Re-authenticate an existing Claude account via OAuth
|
|
558
|
+
switch <label> Switch Claude Code, OpenCode, and pi credentials
|
|
559
|
+
sync Sync activeLabel to Claude Code, OpenCode, and pi
|
|
560
|
+
list List Claude credentials
|
|
561
|
+
remove <label> Remove a Claude credential from storage
|
|
562
|
+
|
|
563
|
+
Options:
|
|
564
|
+
--json Output result in JSON format
|
|
565
|
+
--dry-run Preview sync without writing files
|
|
566
|
+
--oauth Use OAuth browser authentication (recommended)
|
|
567
|
+
--manual Use manual token entry
|
|
568
|
+
--no-browser Print OAuth URL instead of opening browser
|
|
569
|
+
--help, -h Show this help
|
|
570
|
+
|
|
571
|
+
Examples:
|
|
572
|
+
${PRIMARY_CMD} claude Check Claude usage
|
|
573
|
+
${PRIMARY_CMD} claude quota work Check Claude usage for "work"
|
|
574
|
+
${PRIMARY_CMD} claude add Add Claude credential (prompts for method)
|
|
575
|
+
${PRIMARY_CMD} claude add work --oauth Add via OAuth browser flow
|
|
576
|
+
${PRIMARY_CMD} claude reauth work Re-authenticate "work" account
|
|
577
|
+
${PRIMARY_CMD} claude switch work Switch Claude Code/OpenCode/pi to "work"
|
|
578
|
+
${PRIMARY_CMD} claude list List Claude credentials
|
|
579
|
+
${PRIMARY_CMD} claude remove old Remove Claude credential "old"
|
|
580
|
+
${PRIMARY_CMD} claude sync Sync the activeLabel account
|
|
581
|
+
${PRIMARY_CMD} claude sync --dry-run Preview sync without writing
|
|
582
|
+
|
|
583
|
+
Notes:
|
|
584
|
+
- switch and sync update activeLabel in ~/.claude-accounts.json when available
|
|
585
|
+
- session-key-only accounts cannot be synced (OAuth required)
|
|
586
|
+
`);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
export function printHelpClaudeAdd() {
|
|
590
|
+
console.log(`${PRIMARY_CMD} claude add - Add a Claude credential
|
|
591
|
+
|
|
592
|
+
Usage:
|
|
593
|
+
${PRIMARY_CMD} claude add [label] [options]
|
|
594
|
+
|
|
595
|
+
Arguments:
|
|
596
|
+
label Optional label for the Claude credential (e.g., "work", "personal")
|
|
597
|
+
|
|
598
|
+
Options:
|
|
599
|
+
--oauth Use OAuth browser authentication (recommended)
|
|
600
|
+
Opens browser for secure authentication
|
|
601
|
+
--manual Use manual token entry
|
|
602
|
+
Paste sessionKey or OAuth token directly
|
|
603
|
+
--no-browser Print OAuth URL instead of opening browser
|
|
604
|
+
Use this in headless/SSH environments
|
|
605
|
+
--json Output result in JSON format
|
|
606
|
+
--help, -h Show this help
|
|
607
|
+
|
|
608
|
+
Description:
|
|
609
|
+
Adds a Claude credential to ~/.claude-accounts.json.
|
|
610
|
+
|
|
611
|
+
OAuth flow (recommended):
|
|
612
|
+
1. Opens browser for authentication at claude.ai
|
|
613
|
+
2. User copies code#state from browser
|
|
614
|
+
3. Tool exchanges code for tokens automatically
|
|
615
|
+
|
|
616
|
+
Manual flow:
|
|
617
|
+
Prompts for sessionKey or OAuth token (one is required).
|
|
618
|
+
|
|
619
|
+
Examples:
|
|
620
|
+
${PRIMARY_CMD} claude add Interactive (prompts for method)
|
|
621
|
+
${PRIMARY_CMD} claude add work --oauth OAuth browser flow
|
|
622
|
+
${PRIMARY_CMD} claude add work --manual Manual token entry
|
|
623
|
+
${PRIMARY_CMD} claude add work --oauth --no-browser OAuth without opening browser
|
|
624
|
+
${PRIMARY_CMD} claude add work --json JSON output for scripting
|
|
625
|
+
`);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
export function printHelpClaudeReauth() {
|
|
629
|
+
console.log(`${PRIMARY_CMD} claude reauth - Re-authenticate an existing Claude account
|
|
630
|
+
|
|
631
|
+
Usage:
|
|
632
|
+
${PRIMARY_CMD} claude reauth <label> [options]
|
|
633
|
+
|
|
634
|
+
Arguments:
|
|
635
|
+
label Required. Label of the Claude account to re-authenticate
|
|
636
|
+
|
|
637
|
+
Options:
|
|
638
|
+
--no-browser Print the OAuth URL instead of opening browser
|
|
639
|
+
Use this in headless/SSH environments
|
|
640
|
+
--json Output result in JSON format
|
|
641
|
+
--help, -h Show this help
|
|
642
|
+
|
|
643
|
+
Description:
|
|
644
|
+
Re-authenticates an existing Claude account via the OAuth browser flow.
|
|
645
|
+
This is useful when your tokens have expired and cannot be refreshed,
|
|
646
|
+
or when you need to reset your authentication.
|
|
647
|
+
|
|
648
|
+
Unlike 'add', this command:
|
|
649
|
+
- Requires an existing account with the specified label
|
|
650
|
+
- Updates the existing entry instead of creating a new one
|
|
651
|
+
- Preserves any extra fields in the account configuration
|
|
652
|
+
- Always uses OAuth (no manual token entry)
|
|
653
|
+
|
|
654
|
+
If the re-authenticated account is the active account, CLI auth files
|
|
655
|
+
(Claude Code, OpenCode, pi) will also be updated automatically.
|
|
656
|
+
|
|
657
|
+
Examples:
|
|
658
|
+
${PRIMARY_CMD} claude reauth work Re-authenticate "work" account
|
|
659
|
+
${PRIMARY_CMD} claude reauth work --no-browser Print URL for manual browser auth
|
|
660
|
+
${PRIMARY_CMD} claude reauth work --json JSON output for scripting
|
|
661
|
+
|
|
662
|
+
See also:
|
|
663
|
+
${PRIMARY_CMD} claude add Add a new Claude account
|
|
664
|
+
${PRIMARY_CMD} claude list Show all configured Claude accounts
|
|
665
|
+
`);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
export function printHelpClaudeSwitch() {
|
|
669
|
+
console.log(`${PRIMARY_CMD} claude switch - Switch Claude credentials
|
|
670
|
+
|
|
671
|
+
Usage:
|
|
672
|
+
${PRIMARY_CMD} claude switch <label> [options]
|
|
673
|
+
|
|
674
|
+
Arguments:
|
|
675
|
+
label Required. Label of the Claude credential to switch to
|
|
676
|
+
|
|
677
|
+
Options:
|
|
678
|
+
--json Output result in JSON format
|
|
679
|
+
--help, -h Show this help
|
|
680
|
+
|
|
681
|
+
Description:
|
|
682
|
+
Updates Claude Code (~/.claude/.credentials.json) and, when available,
|
|
683
|
+
OpenCode (~/.local/share/opencode/auth.json) plus pi (~/.pi/agent/auth.json).
|
|
684
|
+
|
|
685
|
+
Requires an OAuth-based Claude credential (add with --oauth).
|
|
686
|
+
Also updates activeLabel in ~/.claude-accounts.json when available.
|
|
687
|
+
|
|
688
|
+
Examples:
|
|
689
|
+
${PRIMARY_CMD} claude switch work
|
|
690
|
+
${PRIMARY_CMD} claude switch work --json
|
|
691
|
+
|
|
692
|
+
See also:
|
|
693
|
+
${PRIMARY_CMD} claude sync
|
|
694
|
+
`);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
export function printHelpClaudeSync() {
|
|
698
|
+
console.log(`${PRIMARY_CMD} claude sync - Sync activeLabel to Claude auth files
|
|
699
|
+
|
|
700
|
+
Usage:
|
|
701
|
+
${PRIMARY_CMD} claude sync [options]
|
|
702
|
+
|
|
703
|
+
Options:
|
|
704
|
+
--dry-run Preview what would be synced without writing files
|
|
705
|
+
--json Output result in JSON format
|
|
706
|
+
--help, -h Show this help
|
|
707
|
+
|
|
708
|
+
Description:
|
|
709
|
+
Pushes the activeLabel Claude account from ~/.claude-accounts.json to:
|
|
710
|
+
- Claude Code (~/.claude/.credentials.json)
|
|
711
|
+
- OpenCode (~/.local/share/opencode/auth.json) when present
|
|
712
|
+
- pi (~/.pi/agent/auth.json) when present
|
|
713
|
+
|
|
714
|
+
Only OAuth-based accounts can be synced. Session-key-only accounts are
|
|
715
|
+
skipped with a warning.
|
|
716
|
+
|
|
717
|
+
Examples:
|
|
718
|
+
${PRIMARY_CMD} claude sync
|
|
719
|
+
${PRIMARY_CMD} claude sync --dry-run
|
|
720
|
+
${PRIMARY_CMD} claude sync --json
|
|
721
|
+
|
|
722
|
+
See also:
|
|
723
|
+
${PRIMARY_CMD} claude switch <label>
|
|
724
|
+
${PRIMARY_CMD} claude list
|
|
725
|
+
`);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
export function printHelpClaudeList() {
|
|
729
|
+
console.log(`${PRIMARY_CMD} claude list - List Claude credentials
|
|
730
|
+
|
|
731
|
+
Usage:
|
|
732
|
+
${PRIMARY_CMD} claude list [options]
|
|
733
|
+
|
|
734
|
+
Options:
|
|
735
|
+
--json Output in JSON format
|
|
736
|
+
--local Skip harness token checks and divergence warnings
|
|
737
|
+
--help, -h Show this help
|
|
738
|
+
|
|
739
|
+
Description:
|
|
740
|
+
Lists Claude credentials stored in CLAUDE_ACCOUNTS or ~/.claude-accounts.json.
|
|
741
|
+
The activeLabel account is marked with '*'.
|
|
742
|
+
OAuth-based accounts are checked for divergence in Claude CLI stores.
|
|
743
|
+
Use --local to suppress harness checks and only use stored account files.
|
|
744
|
+
|
|
745
|
+
Examples:
|
|
746
|
+
${PRIMARY_CMD} claude list
|
|
747
|
+
${PRIMARY_CMD} claude list --json
|
|
748
|
+
`);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
export function printHelpClaudeRemove() {
|
|
752
|
+
console.log(`${PRIMARY_CMD} claude remove - Remove a Claude credential
|
|
753
|
+
|
|
754
|
+
Usage:
|
|
755
|
+
${PRIMARY_CMD} claude remove <label> [options]
|
|
756
|
+
|
|
757
|
+
Arguments:
|
|
758
|
+
label Required. Label of the Claude credential to remove
|
|
759
|
+
|
|
760
|
+
Options:
|
|
761
|
+
--json Output result in JSON format (skips confirmation)
|
|
762
|
+
--help, -h Show this help
|
|
763
|
+
|
|
764
|
+
Description:
|
|
765
|
+
Removes a Claude credential from ~/.claude-accounts.json.
|
|
766
|
+
Credentials stored in CLAUDE_ACCOUNTS env var cannot be removed via CLI.
|
|
767
|
+
|
|
768
|
+
Examples:
|
|
769
|
+
${PRIMARY_CMD} claude remove old
|
|
770
|
+
${PRIMARY_CMD} claude remove work --json
|
|
771
|
+
`);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
export function printHelpClaudeQuota() {
|
|
775
|
+
console.log(`${PRIMARY_CMD} claude quota - Check Claude usage quota
|
|
776
|
+
|
|
777
|
+
Usage:
|
|
778
|
+
${PRIMARY_CMD} claude quota [label] [options]
|
|
779
|
+
|
|
780
|
+
Arguments:
|
|
781
|
+
label Optional. Check quota for a specific Claude credential
|
|
782
|
+
|
|
783
|
+
Options:
|
|
784
|
+
--json Output in JSON format
|
|
785
|
+
--local Skip harness token checks and divergence warnings
|
|
786
|
+
--help, -h Show this help
|
|
787
|
+
|
|
788
|
+
Description:
|
|
789
|
+
Displays usage statistics for Claude accounts. Tokens are refreshed when
|
|
790
|
+
available. Uses OAuth credentials when possible and falls back to legacy
|
|
791
|
+
session credentials.
|
|
792
|
+
OAuth-based accounts are checked for divergence in Claude CLI stores.
|
|
793
|
+
Use --local to suppress harness checks and only use stored account files.
|
|
794
|
+
|
|
795
|
+
Examples:
|
|
796
|
+
${PRIMARY_CMD} claude quota
|
|
797
|
+
${PRIMARY_CMD} claude quota work
|
|
798
|
+
${PRIMARY_CMD} claude quota --json
|
|
799
|
+
`);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
export function printHelpAdd() {
|
|
803
|
+
console.log(`${PRIMARY_CMD} codex add - Add a new account via OAuth browser flow
|
|
804
|
+
|
|
805
|
+
Usage:
|
|
806
|
+
${PRIMARY_CMD} codex add [label] [options]
|
|
807
|
+
|
|
808
|
+
Arguments:
|
|
809
|
+
label Optional label for the account (e.g., "work", "personal")
|
|
810
|
+
If not provided, derived from email address
|
|
811
|
+
|
|
812
|
+
Options:
|
|
813
|
+
--no-browser Print the auth URL instead of opening browser
|
|
814
|
+
Use this in headless/SSH environments
|
|
815
|
+
--json Output result in JSON format
|
|
816
|
+
--help, -h Show this help
|
|
817
|
+
|
|
818
|
+
Description:
|
|
819
|
+
Authenticates with OpenAI via OAuth in your browser and saves the
|
|
820
|
+
account credentials to ~/.codex-accounts.json.
|
|
821
|
+
|
|
822
|
+
The OAuth flow uses PKCE for security. A local server is started on
|
|
823
|
+
port 1455 to receive the authentication callback.
|
|
824
|
+
|
|
825
|
+
Examples:
|
|
826
|
+
${PRIMARY_CMD} codex add Add account (label from email)
|
|
827
|
+
${PRIMARY_CMD} codex add work Add account with label "work"
|
|
828
|
+
${PRIMARY_CMD} codex add --no-browser Print URL for manual browser auth
|
|
829
|
+
|
|
830
|
+
Environment:
|
|
831
|
+
SSH/headless environments are auto-detected. The URL will be printed
|
|
832
|
+
instead of opening a browser when SSH_CLIENT or SSH_TTY is set, or
|
|
833
|
+
when DISPLAY/WAYLAND_DISPLAY is missing on Linux.
|
|
834
|
+
`);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
export function printHelpCodexReauth() {
|
|
838
|
+
console.log(`${PRIMARY_CMD} codex reauth - Re-authenticate an existing account
|
|
839
|
+
|
|
840
|
+
Usage:
|
|
841
|
+
${PRIMARY_CMD} codex reauth <label> [options]
|
|
842
|
+
|
|
843
|
+
Arguments:
|
|
844
|
+
label Required. Label of the account to re-authenticate
|
|
845
|
+
|
|
846
|
+
Options:
|
|
847
|
+
--no-browser Print the auth URL instead of opening browser
|
|
848
|
+
Use this in headless/SSH environments
|
|
849
|
+
--json Output result in JSON format
|
|
850
|
+
--help, -h Show this help
|
|
851
|
+
|
|
852
|
+
Description:
|
|
853
|
+
Re-authenticates an existing Codex account via the OAuth browser flow.
|
|
854
|
+
This is useful when your tokens have expired and cannot be refreshed,
|
|
855
|
+
or when you need to reset your authentication.
|
|
856
|
+
|
|
857
|
+
Unlike 'add', this command:
|
|
858
|
+
- Requires an existing account with the specified label
|
|
859
|
+
- Updates the existing entry instead of creating a new one
|
|
860
|
+
- Preserves any extra fields in the account configuration
|
|
861
|
+
|
|
862
|
+
If the re-authenticated account is the active account, CLI auth files
|
|
863
|
+
(Codex CLI, OpenCode, pi) will also be updated automatically.
|
|
864
|
+
|
|
865
|
+
Examples:
|
|
866
|
+
${PRIMARY_CMD} codex reauth work Re-authenticate "work" account
|
|
867
|
+
${PRIMARY_CMD} codex reauth work --no-browser Print URL for manual browser auth
|
|
868
|
+
${PRIMARY_CMD} codex reauth work --json JSON output for scripting
|
|
869
|
+
|
|
870
|
+
See also:
|
|
871
|
+
${PRIMARY_CMD} codex add Add a new account
|
|
872
|
+
${PRIMARY_CMD} codex list Show all configured accounts
|
|
873
|
+
`);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
export function printHelpSwitch() {
|
|
877
|
+
console.log(`${PRIMARY_CMD} codex switch - Switch the active account
|
|
878
|
+
|
|
879
|
+
Usage:
|
|
880
|
+
${PRIMARY_CMD} codex switch <label> [options]
|
|
881
|
+
|
|
882
|
+
Arguments:
|
|
883
|
+
label Required. Label of the account to switch to
|
|
884
|
+
|
|
885
|
+
Options:
|
|
886
|
+
--json Output result in JSON format
|
|
887
|
+
--help, -h Show this help
|
|
888
|
+
|
|
889
|
+
Description:
|
|
890
|
+
Switches the active OpenAI account for Codex CLI, OpenCode, and pi.
|
|
891
|
+
|
|
892
|
+
This command updates authentication files when they exist:
|
|
893
|
+
1. ~/.codex/auth.json - Used by Codex CLI
|
|
894
|
+
2. ~/.local/share/opencode/auth.json - Used by OpenCode (if exists)
|
|
895
|
+
3. ~/.pi/agent/auth.json - Used by pi (if exists)
|
|
896
|
+
|
|
897
|
+
The OpenCode auth file location respects XDG_DATA_HOME if set.
|
|
898
|
+
If the optional auth files don't exist, only the Codex CLI file is updated.
|
|
899
|
+
|
|
900
|
+
Also updates activeLabel in your multi-account file when available.
|
|
901
|
+
|
|
902
|
+
If the token is expired, it will be refreshed before switching.
|
|
903
|
+
Any existing OPENAI_API_KEY in auth.json is preserved.
|
|
904
|
+
|
|
905
|
+
Examples:
|
|
906
|
+
${PRIMARY_CMD} codex switch personal Switch to "personal" account
|
|
907
|
+
${PRIMARY_CMD} codex switch work --json Switch to "work" with JSON output
|
|
908
|
+
|
|
909
|
+
See also:
|
|
910
|
+
${PRIMARY_CMD} codex list Show all available accounts and their labels
|
|
911
|
+
${PRIMARY_CMD} codex sync Re-sync activeLabel to CLI auth files
|
|
912
|
+
`);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
export function printHelpCodexSync() {
|
|
916
|
+
console.log(`${PRIMARY_CMD} codex sync - Sync activeLabel to CLI auth files
|
|
917
|
+
|
|
918
|
+
Usage:
|
|
919
|
+
${PRIMARY_CMD} codex sync [options]
|
|
920
|
+
|
|
921
|
+
Options:
|
|
922
|
+
--dry-run Preview what would be synced without writing files
|
|
923
|
+
--json Output result in JSON format
|
|
924
|
+
--help, -h Show this help
|
|
925
|
+
|
|
926
|
+
Description:
|
|
927
|
+
Pushes the activeLabel account from your multi-account file to:
|
|
928
|
+
- Codex CLI (~/.codex/auth.json)
|
|
929
|
+
- OpenCode (~/.local/share/opencode/auth.json) when present
|
|
930
|
+
- pi (~/.pi/agent/auth.json) when present
|
|
931
|
+
|
|
932
|
+
This is useful after a native CLI login has diverged from the tracked
|
|
933
|
+
activeLabel account.
|
|
934
|
+
|
|
935
|
+
Examples:
|
|
936
|
+
${PRIMARY_CMD} codex sync
|
|
937
|
+
${PRIMARY_CMD} codex sync --dry-run
|
|
938
|
+
${PRIMARY_CMD} codex sync --json
|
|
939
|
+
|
|
940
|
+
See also:
|
|
941
|
+
${PRIMARY_CMD} codex switch <label>
|
|
942
|
+
${PRIMARY_CMD} codex list
|
|
943
|
+
`);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
export function printHelpList() {
|
|
947
|
+
console.log(`${PRIMARY_CMD} codex list - List all configured accounts
|
|
948
|
+
|
|
949
|
+
Usage:
|
|
950
|
+
${PRIMARY_CMD} codex list [options]
|
|
951
|
+
|
|
952
|
+
Options:
|
|
953
|
+
--json Output in JSON format
|
|
954
|
+
--local Skip harness token checks and divergence warnings
|
|
955
|
+
--help, -h Show this help
|
|
956
|
+
|
|
957
|
+
Description:
|
|
958
|
+
Lists all accounts from all configured sources with details:
|
|
959
|
+
- Label and email address
|
|
960
|
+
- Plan type (plus, free, etc.)
|
|
961
|
+
- Token expiry status
|
|
962
|
+
- Source file location
|
|
963
|
+
- Active indicator (* for the activeLabel account)
|
|
964
|
+
Accounts are deduplicated by email for display and prefer the
|
|
965
|
+
activeLabel account when duplicates exist.
|
|
966
|
+
If CLI auth diverges from activeLabel, a warning is shown with a sync hint.
|
|
967
|
+
Use --local to suppress harness checks and only use stored account files.
|
|
968
|
+
|
|
969
|
+
Output columns:
|
|
970
|
+
* = active Active account from activeLabel
|
|
971
|
+
~ = CLI auth CLI account when it diverges from activeLabel
|
|
972
|
+
label Account identifier
|
|
973
|
+
<email> Email address from token
|
|
974
|
+
Plan ChatGPT plan type
|
|
975
|
+
Expires Token expiry (e.g., "9d 17h", "Expired")
|
|
976
|
+
Source File path where account is stored
|
|
977
|
+
|
|
978
|
+
Examples:
|
|
979
|
+
${PRIMARY_CMD} codex list Show all accounts
|
|
980
|
+
${PRIMARY_CMD} codex list --json Get JSON output for scripting
|
|
981
|
+
`);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
export function printHelpRemove() {
|
|
985
|
+
console.log(`${PRIMARY_CMD} codex remove - Remove an account from storage
|
|
986
|
+
|
|
987
|
+
Usage:
|
|
988
|
+
${PRIMARY_CMD} codex remove <label> [options]
|
|
989
|
+
|
|
990
|
+
Arguments:
|
|
991
|
+
label Required. Label of the account to remove
|
|
992
|
+
|
|
993
|
+
Options:
|
|
994
|
+
--json Output result in JSON format (skips confirmation)
|
|
995
|
+
--help, -h Show this help
|
|
996
|
+
|
|
997
|
+
Description:
|
|
998
|
+
Removes an account from the multi-account storage file.
|
|
999
|
+
|
|
1000
|
+
- For accounts in ~/.codex-accounts.json: removes from the file
|
|
1001
|
+
- For the codex-cli account (~/.codex/auth.json): deletes the file
|
|
1002
|
+
- For accounts in CODEX_ACCOUNTS env var: shows error (modify env directly)
|
|
1003
|
+
|
|
1004
|
+
Safety:
|
|
1005
|
+
- Prompts for confirmation before removing (unless --json)
|
|
1006
|
+
- Warns when removing the last account in a file
|
|
1007
|
+
- Warns when removing the codex-cli account (clears authentication)
|
|
1008
|
+
|
|
1009
|
+
Examples:
|
|
1010
|
+
${PRIMARY_CMD} codex remove old Remove "old" account with confirmation
|
|
1011
|
+
${PRIMARY_CMD} codex remove work --json Remove "work" account (no prompt)
|
|
1012
|
+
|
|
1013
|
+
See also:
|
|
1014
|
+
${PRIMARY_CMD} codex list Show all accounts and their sources
|
|
1015
|
+
`);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
export function printHelpQuota() {
|
|
1019
|
+
console.log(`${PRIMARY_CMD} codex quota - Check usage quota for accounts
|
|
1020
|
+
|
|
1021
|
+
Usage:
|
|
1022
|
+
${PRIMARY_CMD} codex quota [label] [options]
|
|
1023
|
+
|
|
1024
|
+
Arguments:
|
|
1025
|
+
label Optional. Check quota for a specific account only
|
|
1026
|
+
If not provided, shows quota for all accounts
|
|
1027
|
+
|
|
1028
|
+
Options:
|
|
1029
|
+
--json Output in JSON format
|
|
1030
|
+
--local Skip harness token checks and divergence warnings
|
|
1031
|
+
--help, -h Show this help
|
|
1032
|
+
|
|
1033
|
+
Description:
|
|
1034
|
+
Displays usage statistics for OpenAI Codex and Claude accounts:
|
|
1035
|
+
- Session usage (queries per session)
|
|
1036
|
+
- Weekly usage (queries per 7-day period)
|
|
1037
|
+
- Available credits
|
|
1038
|
+
|
|
1039
|
+
This command shows Codex usage only. Use '${PRIMARY_CMD} claude quota' for Claude.
|
|
1040
|
+
|
|
1041
|
+
Accounts are deduplicated by ID to avoid showing the same account
|
|
1042
|
+
multiple times when sourced from different files.
|
|
1043
|
+
|
|
1044
|
+
Tokens are automatically refreshed if expired.
|
|
1045
|
+
If CLI auth diverges from activeLabel, a warning is shown with a sync hint.
|
|
1046
|
+
Use --local to suppress these checks and only use stored account files.
|
|
1047
|
+
|
|
1048
|
+
Examples:
|
|
1049
|
+
${PRIMARY_CMD} codex quota Check all Codex accounts
|
|
1050
|
+
${PRIMARY_CMD} codex quota personal Check "personal" account only
|
|
1051
|
+
${PRIMARY_CMD} codex quota --json JSON output for all Codex accounts
|
|
1052
|
+
${PRIMARY_CMD} codex quota work --json JSON output for "work" account
|
|
1053
|
+
${PRIMARY_CMD} claude quota Check Claude accounts
|
|
1054
|
+
`);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
|
|
1058
|
+
import { homedir } from "node:os";
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Format expiry time as human-readable duration
|
|
1062
|
+
* @param {number | undefined} expires - Expiry timestamp in milliseconds
|
|
1063
|
+
* @returns {{ status: string, display: string }} Status and display string
|
|
1064
|
+
*/
|
|
1065
|
+
export function formatExpiryStatus(expires) {
|
|
1066
|
+
if (!expires) {
|
|
1067
|
+
return { status: "unknown", display: "Unknown" };
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
const now = Date.now();
|
|
1071
|
+
const diff = expires - now;
|
|
1072
|
+
|
|
1073
|
+
if (diff <= 0) {
|
|
1074
|
+
return { status: "expired", display: "Expired" };
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// Warn if expiring within 5 minutes
|
|
1078
|
+
if (diff < 5 * 60 * 1000) {
|
|
1079
|
+
const mins = Math.ceil(diff / 60000);
|
|
1080
|
+
return { status: "expiring", display: `Expiring in ${mins}m` };
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// Format remaining time
|
|
1084
|
+
const hours = Math.floor(diff / (60 * 60 * 1000));
|
|
1085
|
+
const mins = Math.floor((diff % (60 * 60 * 1000)) / 60000);
|
|
1086
|
+
|
|
1087
|
+
if (hours > 24) {
|
|
1088
|
+
const days = Math.floor(hours / 24);
|
|
1089
|
+
const remainingHours = hours % 24;
|
|
1090
|
+
return { status: "valid", display: `${days}d ${remainingHours}h` };
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
if (hours > 0) {
|
|
1094
|
+
return { status: "valid", display: `${hours}h ${mins}m` };
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
return { status: "valid", display: `${mins}m` };
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
/**
|
|
1101
|
+
* Shorten a path for display (replace home directory with ~)
|
|
1102
|
+
* @param {string} filePath - Full file path
|
|
1103
|
+
* @returns {string} Shortened path
|
|
1104
|
+
*/
|
|
1105
|
+
export function shortenPath(filePath) {
|
|
1106
|
+
const home = homedir();
|
|
1107
|
+
if (filePath.startsWith(home)) {
|
|
1108
|
+
return "~" + filePath.slice(home.length);
|
|
1109
|
+
}
|
|
1110
|
+
return filePath;
|
|
1111
|
+
}
|