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,929 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude usage API fetch (session + OAuth).
|
|
3
|
+
* Depends on: lib/constants.js, lib/paths.js, lib/claude-accounts.js, lib/claude-tokens.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync, readFileSync, copyFileSync, unlinkSync } from "node:fs";
|
|
7
|
+
import { spawnSync } from "node:child_process";
|
|
8
|
+
import { randomBytes, pbkdf2Sync, createDecipheriv } from "node:crypto";
|
|
9
|
+
import { homedir, tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import {
|
|
12
|
+
CLAUDE_CREDENTIALS_PATH,
|
|
13
|
+
CLAUDE_MULTI_ACCOUNT_PATHS,
|
|
14
|
+
CLAUDE_API_BASE,
|
|
15
|
+
CLAUDE_ORIGIN,
|
|
16
|
+
CLAUDE_ORGS_URL,
|
|
17
|
+
CLAUDE_ACCOUNT_URL,
|
|
18
|
+
CLAUDE_TIMEOUT_MS,
|
|
19
|
+
CLAUDE_USER_AGENT,
|
|
20
|
+
CLAUDE_OAUTH_USAGE_URL,
|
|
21
|
+
CLAUDE_OAUTH_VERSION,
|
|
22
|
+
CLAUDE_OAUTH_BETA,
|
|
23
|
+
} from "./constants.js";
|
|
24
|
+
import { getOpencodeAuthPath } from "./paths.js";
|
|
25
|
+
import {
|
|
26
|
+
findClaudeSessionKey,
|
|
27
|
+
loadClaudeAccountsFromFile,
|
|
28
|
+
loadClaudeSessionFromCredentials,
|
|
29
|
+
loadClaudeOAuthToken,
|
|
30
|
+
} from "./claude-accounts.js";
|
|
31
|
+
import { ensureFreshClaudeOAuthToken } from "./claude-tokens.js";
|
|
32
|
+
|
|
33
|
+
export function normalizeClaudeOrgId(orgId) {
|
|
34
|
+
if (!orgId || typeof orgId !== "string") return orgId;
|
|
35
|
+
if (/^[0-9a-f-]{36}$/i.test(orgId)) {
|
|
36
|
+
return orgId.replace(/-/g, "");
|
|
37
|
+
}
|
|
38
|
+
return orgId;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isClaudeAuthError(error) {
|
|
42
|
+
if (!error) return false;
|
|
43
|
+
return /account_session_invalid|invalid authorization|http 401|http 403/i.test(String(error));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function loadClaudeOAuthFromClaudeCode() {
|
|
47
|
+
const credentialsPath = process.env.CLAUDE_CREDENTIALS_PATH || CLAUDE_CREDENTIALS_PATH;
|
|
48
|
+
if (!existsSync(credentialsPath)) return [];
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const raw = readFileSync(credentialsPath, "utf-8");
|
|
52
|
+
const parsed = JSON.parse(raw);
|
|
53
|
+
const oauth = parsed?.claudeAiOauth ?? parsed?.claude_ai_oauth;
|
|
54
|
+
|
|
55
|
+
if (!oauth?.accessToken) return [];
|
|
56
|
+
|
|
57
|
+
// Check if token has user:profile scope (required for usage API)
|
|
58
|
+
const scopes = oauth.scopes ?? [];
|
|
59
|
+
if (!scopes.includes("user:profile")) {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return [{
|
|
64
|
+
label: "claude-code",
|
|
65
|
+
accessToken: oauth.accessToken,
|
|
66
|
+
refreshToken: oauth.refreshToken,
|
|
67
|
+
expiresAt: oauth.expiresAt,
|
|
68
|
+
subscriptionType: oauth.subscriptionType,
|
|
69
|
+
rateLimitTier: oauth.rateLimitTier,
|
|
70
|
+
scopes,
|
|
71
|
+
source: credentialsPath,
|
|
72
|
+
}];
|
|
73
|
+
} catch {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Load Claude OAuth account from OpenCode auth.json
|
|
80
|
+
* @returns {Array<{ label: string, accessToken: string, refreshToken?: string, expiresAt?: number, source: string }>}
|
|
81
|
+
*/
|
|
82
|
+
export function loadClaudeOAuthFromOpenCode() {
|
|
83
|
+
const authPath = getOpencodeAuthPath();
|
|
84
|
+
if (!existsSync(authPath)) return [];
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const raw = readFileSync(authPath, "utf-8");
|
|
88
|
+
const parsed = JSON.parse(raw);
|
|
89
|
+
const anthropic = parsed?.anthropic;
|
|
90
|
+
|
|
91
|
+
if (!anthropic?.access) return [];
|
|
92
|
+
|
|
93
|
+
return [{
|
|
94
|
+
label: "opencode",
|
|
95
|
+
accessToken: anthropic.access,
|
|
96
|
+
refreshToken: anthropic.refresh,
|
|
97
|
+
expiresAt: anthropic.expires,
|
|
98
|
+
source: authPath,
|
|
99
|
+
}];
|
|
100
|
+
} catch {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Load Claude OAuth accounts from environment variable
|
|
107
|
+
* Format: JSON array with { label, accessToken, refreshToken?, ... }
|
|
108
|
+
* @returns {Array<{ label: string, accessToken: string, ... }>}
|
|
109
|
+
*/
|
|
110
|
+
export function loadClaudeOAuthFromEnv() {
|
|
111
|
+
const envAccounts = process.env.CLAUDE_OAUTH_ACCOUNTS;
|
|
112
|
+
if (!envAccounts) return [];
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const parsed = JSON.parse(envAccounts);
|
|
116
|
+
const accounts = Array.isArray(parsed) ? parsed : parsed?.accounts ?? [];
|
|
117
|
+
return accounts
|
|
118
|
+
.filter(a => a?.label && a?.accessToken)
|
|
119
|
+
.map(a => ({ ...a, source: "env:CLAUDE_OAUTH_ACCOUNTS" }));
|
|
120
|
+
} catch {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Deduplicate Claude OAuth accounts by refresh token
|
|
127
|
+
* This handles the case where the same Claude account is sourced from multiple files
|
|
128
|
+
* (e.g., claude-code and opencode both storing the same credentials)
|
|
129
|
+
*
|
|
130
|
+
* We use refreshToken because:
|
|
131
|
+
* - Access tokens change on refresh, but refresh tokens stay constant
|
|
132
|
+
* - Two entries with same refresh token are the same underlying account
|
|
133
|
+
* @param {Array<{accessToken: string, refreshToken?: string, ...}>} accounts - Array of accounts
|
|
134
|
+
* @returns {Array<{accessToken: string, ...}>} Deduplicated accounts
|
|
135
|
+
*/
|
|
136
|
+
export function deduplicateClaudeOAuthAccounts(accounts) {
|
|
137
|
+
const seenTokens = new Set();
|
|
138
|
+
return accounts.filter(account => {
|
|
139
|
+
if (!account.accessToken) return true; // Keep accounts without token (shouldn't happen)
|
|
140
|
+
// Use refresh token if available (stays constant), otherwise fall back to access token
|
|
141
|
+
const tokenKey = account.refreshToken
|
|
142
|
+
? account.refreshToken.substring(0, 50)
|
|
143
|
+
: account.accessToken.substring(0, 50);
|
|
144
|
+
if (seenTokens.has(tokenKey)) return false;
|
|
145
|
+
seenTokens.add(tokenKey);
|
|
146
|
+
return true;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Deduplicate Claude usage results by comparing usage fingerprints
|
|
152
|
+
* This catches cases where the same account has different OAuth tokens
|
|
153
|
+
* (e.g., claude-code and opencode both logged into the same Claude account)
|
|
154
|
+
*
|
|
155
|
+
* We consider two results identical if they have the same utilization values.
|
|
156
|
+
* Reset times are NOT included since they can differ by milliseconds between calls.
|
|
157
|
+
*
|
|
158
|
+
* @param {Array<{usage: object, ...}>} results - Array of fetched usage results
|
|
159
|
+
* @returns {Array<{usage: object, ...}>} Deduplicated results
|
|
160
|
+
*/
|
|
161
|
+
export function deduplicateClaudeResultsByUsage(results) {
|
|
162
|
+
const seen = new Set();
|
|
163
|
+
return results.filter(result => {
|
|
164
|
+
if (!result.success || !result.usage) return true; // Keep errors/failures
|
|
165
|
+
|
|
166
|
+
// Create a fingerprint from utilization values only (not reset times)
|
|
167
|
+
const usage = result.usage;
|
|
168
|
+
const fiveHour = usage.five_hour?.utilization ?? "null";
|
|
169
|
+
const sevenDay = usage.seven_day?.utilization ?? "null";
|
|
170
|
+
const sevenDayOpus = usage.seven_day_opus?.utilization ?? "null";
|
|
171
|
+
const sevenDaySonnet = usage.seven_day_sonnet?.utilization ?? "null";
|
|
172
|
+
|
|
173
|
+
// Fingerprint: all utilization values concatenated
|
|
174
|
+
// Same account will have identical utilization regardless of which OAuth token is used
|
|
175
|
+
const fingerprint = `${fiveHour}|${sevenDay}|${sevenDayOpus}|${sevenDaySonnet}`;
|
|
176
|
+
|
|
177
|
+
if (seen.has(fingerprint)) return false;
|
|
178
|
+
seen.add(fingerprint);
|
|
179
|
+
return true;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Load all Claude OAuth accounts from all sources
|
|
185
|
+
* Sources (in priority order):
|
|
186
|
+
* 1. CLAUDE_OAUTH_ACCOUNTS env var
|
|
187
|
+
* 2. ~/.claude-accounts.json (accounts with oauthToken field)
|
|
188
|
+
* 3. ~/.claude/.credentials.json (Claude Code) [skipped when local=true]
|
|
189
|
+
* 4. ~/.local/share/opencode/auth.json (OpenCode) [skipped when local=true]
|
|
190
|
+
* Deduplicates by accessToken to prevent showing same account twice
|
|
191
|
+
* @param {{ local?: boolean }} [options] - When local=true, skip harness auth files
|
|
192
|
+
* @returns {Array<{ label: string, accessToken: string, refreshToken?: string, expiresAt?: number, subscriptionType?: string, rateLimitTier?: string, source: string }>}
|
|
193
|
+
*/
|
|
194
|
+
export function loadAllClaudeOAuthAccounts(options = {}) {
|
|
195
|
+
const all = [];
|
|
196
|
+
const seenLabels = new Set();
|
|
197
|
+
|
|
198
|
+
// 1. Environment variable
|
|
199
|
+
for (const account of loadClaudeOAuthFromEnv()) {
|
|
200
|
+
if (!seenLabels.has(account.label)) {
|
|
201
|
+
seenLabels.add(account.label);
|
|
202
|
+
all.push(account);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// 2. Multi-account file (accounts with oauthToken)
|
|
207
|
+
for (const path of CLAUDE_MULTI_ACCOUNT_PATHS) {
|
|
208
|
+
const accounts = loadClaudeAccountsFromFile(path);
|
|
209
|
+
for (const account of accounts) {
|
|
210
|
+
if (account.oauthToken && !seenLabels.has(account.label)) {
|
|
211
|
+
seenLabels.add(account.label);
|
|
212
|
+
all.push({
|
|
213
|
+
label: account.label,
|
|
214
|
+
accessToken: account.oauthToken,
|
|
215
|
+
// Pass through new OAuth metadata fields (optional, may be null for legacy accounts)
|
|
216
|
+
refreshToken: account.oauthRefreshToken || null,
|
|
217
|
+
expiresAt: account.oauthExpiresAt || null,
|
|
218
|
+
scopes: account.oauthScopes || null,
|
|
219
|
+
source: account.source,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// 3. Claude Code credentials (skip in local mode)
|
|
226
|
+
if (!options.local) {
|
|
227
|
+
for (const account of loadClaudeOAuthFromClaudeCode()) {
|
|
228
|
+
if (!seenLabels.has(account.label)) {
|
|
229
|
+
seenLabels.add(account.label);
|
|
230
|
+
all.push(account);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 4. OpenCode credentials (skip in local mode)
|
|
236
|
+
if (!options.local) {
|
|
237
|
+
for (const account of loadClaudeOAuthFromOpenCode()) {
|
|
238
|
+
if (!seenLabels.has(account.label)) {
|
|
239
|
+
seenLabels.add(account.label);
|
|
240
|
+
all.push(account);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// 5. Deduplicate by accessToken (same account from multiple sources with different labels)
|
|
246
|
+
return deduplicateClaudeOAuthAccounts(all);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Fetch Claude usage via OAuth API (new official endpoint)
|
|
251
|
+
* Endpoint: GET https://api.anthropic.com/api/oauth/usage
|
|
252
|
+
* Required headers:
|
|
253
|
+
* - Authorization: Bearer <access_token>
|
|
254
|
+
* - anthropic-version: 2023-06-01
|
|
255
|
+
* - anthropic-beta: oauth-2025-04-20
|
|
256
|
+
* @param {string} accessToken - OAuth access token with user:profile scope
|
|
257
|
+
* @returns {Promise<{ success: boolean, data?: object, error?: string }>}
|
|
258
|
+
*/
|
|
259
|
+
export async function fetchClaudeOAuthUsage(accessToken) {
|
|
260
|
+
const controller = new AbortController();
|
|
261
|
+
const timeout = setTimeout(() => controller.abort(), CLAUDE_TIMEOUT_MS);
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
const res = await fetch(CLAUDE_OAUTH_USAGE_URL, {
|
|
265
|
+
method: "GET",
|
|
266
|
+
headers: {
|
|
267
|
+
Authorization: `Bearer ${accessToken}`,
|
|
268
|
+
"anthropic-version": CLAUDE_OAUTH_VERSION,
|
|
269
|
+
"anthropic-beta": CLAUDE_OAUTH_BETA,
|
|
270
|
+
},
|
|
271
|
+
signal: controller.signal,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
if (!res.ok) {
|
|
275
|
+
const body = await res.text().catch(() => "");
|
|
276
|
+
return {
|
|
277
|
+
success: false,
|
|
278
|
+
error: `HTTP ${res.status}: ${body.slice(0, 200) || res.statusText}`,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const data = await res.json();
|
|
283
|
+
return { success: true, data };
|
|
284
|
+
} catch (err) {
|
|
285
|
+
const message = err.name === "AbortError" ? "Request timed out" : err.message;
|
|
286
|
+
return { success: false, error: message };
|
|
287
|
+
} finally {
|
|
288
|
+
clearTimeout(timeout);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Fetch usage for a Claude OAuth account
|
|
294
|
+
* @param {{ label: string, accessToken: string, ... }} account - OAuth account
|
|
295
|
+
* @returns {Promise<{ success: boolean, label: string, source: string, usage?: object, ... }>}
|
|
296
|
+
*/
|
|
297
|
+
export async function fetchClaudeOAuthUsageForAccount(account) {
|
|
298
|
+
const refreshed = await ensureFreshClaudeOAuthToken(account);
|
|
299
|
+
if (!refreshed) {
|
|
300
|
+
const message = account.refreshToken
|
|
301
|
+
? "OAuth token expired and refresh failed - run 'claude /login'"
|
|
302
|
+
: "OAuth token expired - refresh token missing, run 'claude /login'";
|
|
303
|
+
return {
|
|
304
|
+
success: false,
|
|
305
|
+
label: account.label,
|
|
306
|
+
source: account.source,
|
|
307
|
+
error: message,
|
|
308
|
+
subscriptionType: account.subscriptionType,
|
|
309
|
+
rateLimitTier: account.rateLimitTier,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const result = await fetchClaudeOAuthUsage(account.accessToken);
|
|
314
|
+
|
|
315
|
+
if (!result.success) {
|
|
316
|
+
return {
|
|
317
|
+
success: false,
|
|
318
|
+
label: account.label,
|
|
319
|
+
source: account.source,
|
|
320
|
+
error: result.error,
|
|
321
|
+
subscriptionType: account.subscriptionType,
|
|
322
|
+
rateLimitTier: account.rateLimitTier,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
success: true,
|
|
328
|
+
label: account.label,
|
|
329
|
+
source: account.source,
|
|
330
|
+
usage: result.data,
|
|
331
|
+
subscriptionType: account.subscriptionType,
|
|
332
|
+
rateLimitTier: account.rateLimitTier,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export function getChromeSafeStoragePassword() {
|
|
337
|
+
const candidates = ["chromium", "chrome", "google-chrome", "google-chrome-canary"];
|
|
338
|
+
for (const app of candidates) {
|
|
339
|
+
try {
|
|
340
|
+
const result = spawnSync("secret-tool", ["lookup", "application", app], {
|
|
341
|
+
encoding: "utf-8",
|
|
342
|
+
});
|
|
343
|
+
if (result.status === 0) {
|
|
344
|
+
const value = (result.stdout || "").trim();
|
|
345
|
+
if (value) return value;
|
|
346
|
+
}
|
|
347
|
+
} catch {
|
|
348
|
+
// ignore
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return "peanuts";
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export function decryptChromeCookie(encryptedValue, password) {
|
|
355
|
+
if (!encryptedValue || encryptedValue.length < 4) return null;
|
|
356
|
+
const prefix = encryptedValue.slice(0, 3).toString("utf-8");
|
|
357
|
+
if (prefix !== "v10" && prefix !== "v11") {
|
|
358
|
+
try {
|
|
359
|
+
return encryptedValue.toString("utf-8");
|
|
360
|
+
} catch {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
try {
|
|
366
|
+
const ciphertext = encryptedValue.slice(3);
|
|
367
|
+
const key = pbkdf2Sync(password, "saltysalt", 1, 16, "sha1");
|
|
368
|
+
const iv = Buffer.alloc(16, " ");
|
|
369
|
+
const decipher = createDecipheriv("aes-128-cbc", key, iv);
|
|
370
|
+
let decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
371
|
+
const pad = decrypted[decrypted.length - 1];
|
|
372
|
+
if (pad > 0 && pad <= 16) {
|
|
373
|
+
decrypted = decrypted.slice(0, -pad);
|
|
374
|
+
}
|
|
375
|
+
return decrypted.toString("utf-8");
|
|
376
|
+
} catch {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function stripNonPrintable(value) {
|
|
382
|
+
if (!value) return value;
|
|
383
|
+
return value.replace(/^[^\x20-\x7E]+/, "").replace(/[^\x20-\x7E]+$/, "");
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export function extractClaudeCookieValue(value, name = null) {
|
|
387
|
+
const cleaned = stripNonPrintable(value);
|
|
388
|
+
if (!cleaned) return null;
|
|
389
|
+
const asciiOnly = cleaned.replace(/[^\x20-\x7E]/g, "");
|
|
390
|
+
if (!asciiOnly) return null;
|
|
391
|
+
if (name === "sessionKey") {
|
|
392
|
+
const match = asciiOnly.match(/sk-ant-[a-z0-9_-]+/i);
|
|
393
|
+
return match ? match[0] : null;
|
|
394
|
+
}
|
|
395
|
+
if (name === "cf_clearance") {
|
|
396
|
+
const match = asciiOnly.match(/[A-Za-z0-9._-]{20,}/);
|
|
397
|
+
return match ? match[0] : null;
|
|
398
|
+
}
|
|
399
|
+
if (name === "lastActiveOrg") {
|
|
400
|
+
const match = asciiOnly.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i);
|
|
401
|
+
return match ? match[0] : null;
|
|
402
|
+
}
|
|
403
|
+
return asciiOnly;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export function readClaudeCookiesFromDb(cookiePath) {
|
|
407
|
+
const tempPath = join(tmpdir(), `cq-claude-cookies-${randomBytes(6).toString("hex")}.db`);
|
|
408
|
+
try {
|
|
409
|
+
copyFileSync(cookiePath, tempPath);
|
|
410
|
+
const query = [
|
|
411
|
+
"select name, value, hex(encrypted_value)",
|
|
412
|
+
"from cookies",
|
|
413
|
+
"where host_key like '%claude.ai%'",
|
|
414
|
+
";",
|
|
415
|
+
].join(" ");
|
|
416
|
+
const result = spawnSync("sqlite3", ["-readonly", "-separator", "\t", tempPath, query], {
|
|
417
|
+
encoding: "utf-8",
|
|
418
|
+
});
|
|
419
|
+
if (result.status !== 0) {
|
|
420
|
+
return { error: result.stderr?.trim() || "Failed to read cookie DB" };
|
|
421
|
+
}
|
|
422
|
+
const lines = (result.stdout || "").trim().split("\n").filter(Boolean);
|
|
423
|
+
if (!lines.length) {
|
|
424
|
+
return { error: "No Claude cookies found in DB" };
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const password = getChromeSafeStoragePassword();
|
|
428
|
+
const cookies = {};
|
|
429
|
+
|
|
430
|
+
for (const line of lines) {
|
|
431
|
+
const [name, plainValue, hexValue] = line.split("\t");
|
|
432
|
+
if (!name) continue;
|
|
433
|
+
if (plainValue) {
|
|
434
|
+
const value = extractClaudeCookieValue(plainValue, name);
|
|
435
|
+
if (value) cookies[name] = value;
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
if (hexValue) {
|
|
439
|
+
const buffer = Buffer.from(hexValue, "hex");
|
|
440
|
+
const decrypted = decryptChromeCookie(buffer, password);
|
|
441
|
+
const value = extractClaudeCookieValue(decrypted, name);
|
|
442
|
+
if (value) cookies[name] = value;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return {
|
|
447
|
+
sessionKey: cookies.sessionKey ?? null,
|
|
448
|
+
cfClearance: cookies.cf_clearance ?? null,
|
|
449
|
+
cookies,
|
|
450
|
+
};
|
|
451
|
+
} catch (err) {
|
|
452
|
+
return { error: err?.message ?? String(err) };
|
|
453
|
+
} finally {
|
|
454
|
+
try {
|
|
455
|
+
unlinkSync(tempPath);
|
|
456
|
+
} catch {
|
|
457
|
+
// ignore
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export function loadClaudeCookieCandidates() {
|
|
463
|
+
const overridePath = process.env.CLAUDE_COOKIE_DB_PATH;
|
|
464
|
+
const candidates = overridePath
|
|
465
|
+
? [overridePath]
|
|
466
|
+
: [
|
|
467
|
+
join(homedir(), ".config", "chromium", "Default", "Cookies"),
|
|
468
|
+
join(homedir(), ".config", "google-chrome", "Default", "Cookies"),
|
|
469
|
+
join(homedir(), ".config", "google-chrome-canary", "Default", "Cookies"),
|
|
470
|
+
join(homedir(), ".config", "google-chrome-for-testing", "Default", "Cookies"),
|
|
471
|
+
];
|
|
472
|
+
|
|
473
|
+
const sessions = [];
|
|
474
|
+
|
|
475
|
+
for (const cookiePath of candidates) {
|
|
476
|
+
if (!existsSync(cookiePath)) continue;
|
|
477
|
+
const result = readClaudeCookiesFromDb(cookiePath);
|
|
478
|
+
if (result.sessionKey) {
|
|
479
|
+
sessions.push({
|
|
480
|
+
sessionKey: result.sessionKey,
|
|
481
|
+
cfClearance: result.cfClearance ?? null,
|
|
482
|
+
cookies: result.cookies ?? null,
|
|
483
|
+
source: cookiePath,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return sessions;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export function loadClaudeSessionCandidates() {
|
|
492
|
+
const sessions = [];
|
|
493
|
+
const cookieSessions = loadClaudeCookieCandidates();
|
|
494
|
+
const oauth = loadClaudeOAuthToken();
|
|
495
|
+
for (const session of cookieSessions) {
|
|
496
|
+
sessions.push({
|
|
497
|
+
...session,
|
|
498
|
+
oauthToken: oauth.token ?? null,
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const credentialsSession = loadClaudeSessionFromCredentials();
|
|
503
|
+
if (credentialsSession.sessionKey) {
|
|
504
|
+
sessions.push({
|
|
505
|
+
...credentialsSession,
|
|
506
|
+
oauthToken: oauth.token ?? credentialsSession.sessionKey,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return sessions;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
export function buildClaudeHeaders(sessionKey, cfClearance, bearerToken, mode, cookies) {
|
|
514
|
+
const headers = {
|
|
515
|
+
accept: "application/json, text/plain, */*",
|
|
516
|
+
"accept-language": "en-US,en;q=0.9",
|
|
517
|
+
"cache-control": "no-cache",
|
|
518
|
+
pragma: "no-cache",
|
|
519
|
+
origin: CLAUDE_ORIGIN,
|
|
520
|
+
referer: `${CLAUDE_ORIGIN}/`,
|
|
521
|
+
"user-agent": CLAUDE_USER_AGENT,
|
|
522
|
+
"sec-fetch-dest": "empty",
|
|
523
|
+
"sec-fetch-mode": "cors",
|
|
524
|
+
"sec-fetch-site": "same-origin",
|
|
525
|
+
"x-requested-with": "XMLHttpRequest",
|
|
526
|
+
};
|
|
527
|
+
if (mode.includes("cookie")) {
|
|
528
|
+
if (!sessionKey && !(cookies && typeof cookies === "object")) {
|
|
529
|
+
return headers;
|
|
530
|
+
}
|
|
531
|
+
let parts = [];
|
|
532
|
+
if (cookies && typeof cookies === "object") {
|
|
533
|
+
parts = Object.entries(cookies)
|
|
534
|
+
.filter(([, value]) => typeof value === "string" && value.length)
|
|
535
|
+
.map(([name, value]) => `${name}=${value}`);
|
|
536
|
+
} else {
|
|
537
|
+
parts = [`sessionKey=${sessionKey}`];
|
|
538
|
+
if (cfClearance) {
|
|
539
|
+
parts.push(`cf_clearance=${cfClearance}`);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
headers.Cookie = parts.join("; ");
|
|
543
|
+
}
|
|
544
|
+
if (mode.includes("bearer")) {
|
|
545
|
+
if (bearerToken) {
|
|
546
|
+
headers.Authorization = `Bearer ${bearerToken}`;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return headers;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
export async function fetchClaudeJson(url, sessionKey, cfClearance, oauthToken, cookies) {
|
|
553
|
+
const controller = new AbortController();
|
|
554
|
+
const timeout = setTimeout(() => controller.abort(), CLAUDE_TIMEOUT_MS);
|
|
555
|
+
|
|
556
|
+
try {
|
|
557
|
+
const attempts = [];
|
|
558
|
+
const hasCookie = Boolean(sessionKey || (cookies && typeof cookies === "object"));
|
|
559
|
+
const hasSessionBearer = Boolean(sessionKey);
|
|
560
|
+
const hasOauthBearer = Boolean(oauthToken);
|
|
561
|
+
|
|
562
|
+
if (hasCookie) attempts.push({ mode: "cookie", bearer: null });
|
|
563
|
+
if (hasSessionBearer) attempts.push({ mode: "bearer", bearer: sessionKey });
|
|
564
|
+
if (hasOauthBearer) attempts.push({ mode: "bearer", bearer: oauthToken });
|
|
565
|
+
if (hasCookie && hasSessionBearer) attempts.push({ mode: "cookie+bearer", bearer: sessionKey });
|
|
566
|
+
if (hasCookie && hasOauthBearer) attempts.push({ mode: "cookie+bearer", bearer: oauthToken });
|
|
567
|
+
let lastError = null;
|
|
568
|
+
|
|
569
|
+
for (const attempt of attempts) {
|
|
570
|
+
const res = await fetch(url, {
|
|
571
|
+
method: "GET",
|
|
572
|
+
headers: buildClaudeHeaders(
|
|
573
|
+
sessionKey,
|
|
574
|
+
cfClearance,
|
|
575
|
+
attempt.bearer,
|
|
576
|
+
attempt.mode,
|
|
577
|
+
cookies
|
|
578
|
+
),
|
|
579
|
+
signal: controller.signal,
|
|
580
|
+
});
|
|
581
|
+
if (res.ok) {
|
|
582
|
+
const text = await res.text();
|
|
583
|
+
if (!text) {
|
|
584
|
+
return { data: null };
|
|
585
|
+
}
|
|
586
|
+
try {
|
|
587
|
+
return { data: JSON.parse(text) };
|
|
588
|
+
} catch {
|
|
589
|
+
return { error: "Invalid JSON response" };
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
let detail = "";
|
|
594
|
+
try {
|
|
595
|
+
const text = await res.text();
|
|
596
|
+
if (text) {
|
|
597
|
+
detail = text.trim().slice(0, 200);
|
|
598
|
+
}
|
|
599
|
+
} catch {
|
|
600
|
+
// ignore body parse errors
|
|
601
|
+
}
|
|
602
|
+
const error = {
|
|
603
|
+
status: res.status,
|
|
604
|
+
error: detail ? `HTTP ${res.status}: ${detail}` : `HTTP ${res.status}`,
|
|
605
|
+
};
|
|
606
|
+
lastError = error;
|
|
607
|
+
if (res.status !== 401 && res.status !== 403) {
|
|
608
|
+
return error;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
return lastError ?? { error: "HTTP 403" };
|
|
613
|
+
} catch (err) {
|
|
614
|
+
const message = err?.name === "AbortError" ? "Request timed out" : err?.message ?? String(err);
|
|
615
|
+
return { error: message };
|
|
616
|
+
} finally {
|
|
617
|
+
clearTimeout(timeout);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
export function extractClaudeOrgId(payload) {
|
|
622
|
+
if (!payload) return null;
|
|
623
|
+
if (typeof payload === "string") return payload;
|
|
624
|
+
|
|
625
|
+
const isUuidLike = (value) => {
|
|
626
|
+
if (typeof value !== "string") return false;
|
|
627
|
+
if (/^[0-9a-f]{32}$/i.test(value)) return true;
|
|
628
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)) {
|
|
629
|
+
return true;
|
|
630
|
+
}
|
|
631
|
+
return false;
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
const searchUuid = (root) => {
|
|
635
|
+
const stack = [root];
|
|
636
|
+
const seen = new Set();
|
|
637
|
+
while (stack.length) {
|
|
638
|
+
const current = stack.pop();
|
|
639
|
+
if (!current || typeof current !== "object") continue;
|
|
640
|
+
if (seen.has(current)) continue;
|
|
641
|
+
seen.add(current);
|
|
642
|
+
if (Array.isArray(current)) {
|
|
643
|
+
for (const item of current) {
|
|
644
|
+
if (isUuidLike(item)) return item;
|
|
645
|
+
if (item && typeof item === "object") stack.push(item);
|
|
646
|
+
}
|
|
647
|
+
} else {
|
|
648
|
+
for (const value of Object.values(current)) {
|
|
649
|
+
if (isUuidLike(value)) return value;
|
|
650
|
+
if (value && typeof value === "object") stack.push(value);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return null;
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
const uuidCandidate = searchUuid(payload);
|
|
658
|
+
if (uuidCandidate) return uuidCandidate;
|
|
659
|
+
|
|
660
|
+
const direct = payload.id ?? payload.uuid ?? payload.organizationId ?? payload.orgId ?? payload.org_id;
|
|
661
|
+
if (direct) return direct;
|
|
662
|
+
if (payload.current_organization_uuid) return payload.current_organization_uuid;
|
|
663
|
+
|
|
664
|
+
const orgs = Array.isArray(payload)
|
|
665
|
+
? payload
|
|
666
|
+
: payload.organizations ?? payload.orgs ?? payload.items ?? payload.data;
|
|
667
|
+
|
|
668
|
+
if (!Array.isArray(orgs) || orgs.length === 0) return null;
|
|
669
|
+
const first = orgs[0];
|
|
670
|
+
if (typeof first === "string") return first;
|
|
671
|
+
return first?.id ?? first?.uuid ?? first?.organizationId ?? first?.orgId ?? first?.org_id ?? null;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
export async function fetchClaudeUsageForCredentials(credentials) {
|
|
675
|
+
const sessionKey = credentials.sessionKey ?? findClaudeSessionKey(credentials.cookies);
|
|
676
|
+
const oauthToken = credentials.oauthToken ?? null;
|
|
677
|
+
const cfClearance = credentials.cfClearance ?? credentials.cookies?.cf_clearance ?? credentials.cookies?.cfClearance ?? null;
|
|
678
|
+
const cookies = credentials.cookies ?? null;
|
|
679
|
+
|
|
680
|
+
if (!sessionKey && !oauthToken && !cookies) {
|
|
681
|
+
return {
|
|
682
|
+
success: false,
|
|
683
|
+
label: credentials.label ?? null,
|
|
684
|
+
source: credentials.source ?? null,
|
|
685
|
+
error: "Missing Claude session key or OAuth token",
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
let lastAuthError = null;
|
|
690
|
+
const tryUsageForOrg = async (orgId) => {
|
|
691
|
+
const normalizedOrgId = normalizeClaudeOrgId(orgId);
|
|
692
|
+
const usageUrl = `${CLAUDE_API_BASE}/organizations/${normalizedOrgId}/usage`;
|
|
693
|
+
const overageUrl = `${CLAUDE_API_BASE}/organizations/${normalizedOrgId}/overage_spend_limit`;
|
|
694
|
+
|
|
695
|
+
const [usageResponse, overageResponse, accountResponse] = await Promise.all([
|
|
696
|
+
fetchClaudeJson(
|
|
697
|
+
usageUrl,
|
|
698
|
+
sessionKey,
|
|
699
|
+
cfClearance,
|
|
700
|
+
oauthToken,
|
|
701
|
+
cookies
|
|
702
|
+
),
|
|
703
|
+
fetchClaudeJson(
|
|
704
|
+
overageUrl,
|
|
705
|
+
sessionKey,
|
|
706
|
+
cfClearance,
|
|
707
|
+
oauthToken,
|
|
708
|
+
cookies
|
|
709
|
+
),
|
|
710
|
+
fetchClaudeJson(
|
|
711
|
+
CLAUDE_ACCOUNT_URL,
|
|
712
|
+
sessionKey,
|
|
713
|
+
cfClearance,
|
|
714
|
+
oauthToken,
|
|
715
|
+
cookies
|
|
716
|
+
),
|
|
717
|
+
]);
|
|
718
|
+
|
|
719
|
+
const errors = {};
|
|
720
|
+
if (usageResponse.error) errors.usage = usageResponse.error;
|
|
721
|
+
if (overageResponse.error) errors.overage = overageResponse.error;
|
|
722
|
+
if (accountResponse.error) errors.account = accountResponse.error;
|
|
723
|
+
|
|
724
|
+
return { usageResponse, overageResponse, accountResponse, errors, orgId };
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
const cookieOrg = credentials.cookies?.lastActiveOrg;
|
|
728
|
+
const configuredOrg = credentials.orgId ?? null;
|
|
729
|
+
|
|
730
|
+
if (configuredOrg || cookieOrg) {
|
|
731
|
+
const orgAttempt = await tryUsageForOrg(configuredOrg ?? cookieOrg);
|
|
732
|
+
const authErrors = Object.values(orgAttempt.errors).some(isClaudeAuthError);
|
|
733
|
+
if (!authErrors) {
|
|
734
|
+
return {
|
|
735
|
+
success: true,
|
|
736
|
+
label: credentials.label ?? null,
|
|
737
|
+
source: credentials.source ?? null,
|
|
738
|
+
orgId: orgAttempt.orgId,
|
|
739
|
+
usage: orgAttempt.usageResponse.data ?? null,
|
|
740
|
+
overage: orgAttempt.overageResponse.data ?? null,
|
|
741
|
+
account: orgAttempt.accountResponse.data ?? null,
|
|
742
|
+
errors: Object.keys(orgAttempt.errors).length ? orgAttempt.errors : null,
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
lastAuthError = orgAttempt.errors.usage || orgAttempt.errors.overage || lastAuthError;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const orgsResponse = await fetchClaudeJson(
|
|
749
|
+
CLAUDE_ORGS_URL,
|
|
750
|
+
sessionKey,
|
|
751
|
+
cfClearance,
|
|
752
|
+
oauthToken,
|
|
753
|
+
cookies
|
|
754
|
+
);
|
|
755
|
+
if (orgsResponse.error) {
|
|
756
|
+
const errorText = String(orgsResponse.error);
|
|
757
|
+
const isAuthError = /account_session_invalid|invalid authorization|http 401|http 403/i.test(errorText);
|
|
758
|
+
if (isAuthError) {
|
|
759
|
+
lastAuthError = orgsResponse.error;
|
|
760
|
+
return {
|
|
761
|
+
success: false,
|
|
762
|
+
label: credentials.label ?? null,
|
|
763
|
+
source: credentials.source ?? null,
|
|
764
|
+
error: `Organizations request failed: ${lastAuthError}`,
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
return {
|
|
768
|
+
success: false,
|
|
769
|
+
label: credentials.label ?? null,
|
|
770
|
+
source: credentials.source ?? null,
|
|
771
|
+
error: `Organizations request failed: ${orgsResponse.error}`,
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const orgId = extractClaudeOrgId(orgsResponse.data);
|
|
776
|
+
if (!orgId) {
|
|
777
|
+
return {
|
|
778
|
+
success: false,
|
|
779
|
+
label: credentials.label ?? null,
|
|
780
|
+
source: credentials.source ?? null,
|
|
781
|
+
error: "No Claude organization ID found",
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
const orgAttempt = await tryUsageForOrg(orgId);
|
|
786
|
+
const authErrors = Object.values(orgAttempt.errors).some(isClaudeAuthError);
|
|
787
|
+
if (!authErrors) {
|
|
788
|
+
return {
|
|
789
|
+
success: true,
|
|
790
|
+
label: credentials.label ?? null,
|
|
791
|
+
source: credentials.source ?? null,
|
|
792
|
+
orgId,
|
|
793
|
+
usage: orgAttempt.usageResponse.data ?? null,
|
|
794
|
+
overage: orgAttempt.overageResponse.data ?? null,
|
|
795
|
+
account: orgAttempt.accountResponse.data ?? null,
|
|
796
|
+
errors: Object.keys(orgAttempt.errors).length ? orgAttempt.errors : null,
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
return {
|
|
801
|
+
success: false,
|
|
802
|
+
label: credentials.label ?? null,
|
|
803
|
+
source: credentials.source ?? null,
|
|
804
|
+
error: `Organizations request failed: ${lastAuthError || "Invalid authorization"}`,
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
export async function fetchClaudeUsage() {
|
|
809
|
+
const candidates = loadClaudeSessionCandidates();
|
|
810
|
+
if (!candidates.length) {
|
|
811
|
+
const credentials = loadClaudeSessionFromCredentials();
|
|
812
|
+
return {
|
|
813
|
+
success: false,
|
|
814
|
+
source: credentials.source,
|
|
815
|
+
error: credentials.error ?? "Missing Claude session key",
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
let lastAuthError = null;
|
|
820
|
+
|
|
821
|
+
for (const credentials of candidates) {
|
|
822
|
+
const tryUsageForOrg = async (orgId) => {
|
|
823
|
+
const normalizedOrgId = normalizeClaudeOrgId(orgId);
|
|
824
|
+
const usageUrl = `${CLAUDE_API_BASE}/organizations/${normalizedOrgId}/usage`;
|
|
825
|
+
const overageUrl = `${CLAUDE_API_BASE}/organizations/${normalizedOrgId}/overage_spend_limit`;
|
|
826
|
+
|
|
827
|
+
const [usageResponse, overageResponse, accountResponse] = await Promise.all([
|
|
828
|
+
fetchClaudeJson(
|
|
829
|
+
usageUrl,
|
|
830
|
+
credentials.sessionKey,
|
|
831
|
+
credentials.cfClearance,
|
|
832
|
+
credentials.oauthToken,
|
|
833
|
+
credentials.cookies
|
|
834
|
+
),
|
|
835
|
+
fetchClaudeJson(
|
|
836
|
+
overageUrl,
|
|
837
|
+
credentials.sessionKey,
|
|
838
|
+
credentials.cfClearance,
|
|
839
|
+
credentials.oauthToken,
|
|
840
|
+
credentials.cookies
|
|
841
|
+
),
|
|
842
|
+
fetchClaudeJson(
|
|
843
|
+
CLAUDE_ACCOUNT_URL,
|
|
844
|
+
credentials.sessionKey,
|
|
845
|
+
credentials.cfClearance,
|
|
846
|
+
credentials.oauthToken,
|
|
847
|
+
credentials.cookies
|
|
848
|
+
),
|
|
849
|
+
]);
|
|
850
|
+
|
|
851
|
+
const errors = {};
|
|
852
|
+
if (usageResponse.error) errors.usage = usageResponse.error;
|
|
853
|
+
if (overageResponse.error) errors.overage = overageResponse.error;
|
|
854
|
+
if (accountResponse.error) errors.account = accountResponse.error;
|
|
855
|
+
|
|
856
|
+
return { usageResponse, overageResponse, accountResponse, errors, orgId };
|
|
857
|
+
};
|
|
858
|
+
|
|
859
|
+
const cookieOrg = credentials.cookies?.lastActiveOrg;
|
|
860
|
+
if (cookieOrg) {
|
|
861
|
+
const cookieAttempt = await tryUsageForOrg(cookieOrg);
|
|
862
|
+
const authErrors = Object.values(cookieAttempt.errors).some(isClaudeAuthError);
|
|
863
|
+
if (!authErrors) {
|
|
864
|
+
return {
|
|
865
|
+
success: true,
|
|
866
|
+
source: credentials.source,
|
|
867
|
+
orgId: cookieAttempt.orgId,
|
|
868
|
+
usage: cookieAttempt.usageResponse.data ?? null,
|
|
869
|
+
overage: cookieAttempt.overageResponse.data ?? null,
|
|
870
|
+
account: cookieAttempt.accountResponse.data ?? null,
|
|
871
|
+
errors: Object.keys(cookieAttempt.errors).length ? cookieAttempt.errors : null,
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
lastAuthError = cookieAttempt.errors.usage || cookieAttempt.errors.overage || lastAuthError;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
const orgsResponse = await fetchClaudeJson(
|
|
878
|
+
CLAUDE_ORGS_URL,
|
|
879
|
+
credentials.sessionKey,
|
|
880
|
+
credentials.cfClearance,
|
|
881
|
+
credentials.oauthToken,
|
|
882
|
+
credentials.cookies
|
|
883
|
+
);
|
|
884
|
+
if (orgsResponse.error) {
|
|
885
|
+
const errorText = String(orgsResponse.error);
|
|
886
|
+
const isAuthError = /account_session_invalid|invalid authorization|http 401|http 403/i.test(errorText);
|
|
887
|
+
if (isAuthError) {
|
|
888
|
+
lastAuthError = orgsResponse.error;
|
|
889
|
+
continue;
|
|
890
|
+
}
|
|
891
|
+
return {
|
|
892
|
+
success: false,
|
|
893
|
+
source: credentials.source,
|
|
894
|
+
error: `Organizations request failed: ${orgsResponse.error}`,
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
const orgId = extractClaudeOrgId(orgsResponse.data);
|
|
899
|
+
if (!orgId) {
|
|
900
|
+
return {
|
|
901
|
+
success: false,
|
|
902
|
+
source: credentials.source,
|
|
903
|
+
error: "No Claude organization ID found",
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
const orgAttempt = await tryUsageForOrg(orgId);
|
|
908
|
+
const authErrors = Object.values(orgAttempt.errors).some(isClaudeAuthError);
|
|
909
|
+
if (!authErrors) {
|
|
910
|
+
return {
|
|
911
|
+
success: true,
|
|
912
|
+
source: credentials.source,
|
|
913
|
+
orgId,
|
|
914
|
+
usage: orgAttempt.usageResponse.data ?? null,
|
|
915
|
+
overage: orgAttempt.overageResponse.data ?? null,
|
|
916
|
+
account: orgAttempt.accountResponse.data ?? null,
|
|
917
|
+
errors: Object.keys(orgAttempt.errors).length ? orgAttempt.errors : null,
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
lastAuthError = orgAttempt.errors.usage || orgAttempt.errors.overage || lastAuthError;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
return {
|
|
924
|
+
success: false,
|
|
925
|
+
source: candidates[0]?.source ?? null,
|
|
926
|
+
error: `Organizations request failed: ${lastAuthError || "Invalid authorization"}`,
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
|