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,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT decode and profile/account extraction.
|
|
3
|
+
* Depends only on lib/constants.js.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { JWT_CLAIM, JWT_PROFILE } from "./constants.js";
|
|
7
|
+
|
|
8
|
+
export function decodeJWT(token) {
|
|
9
|
+
try {
|
|
10
|
+
const parts = token.split(".");
|
|
11
|
+
if (parts.length !== 3) return null;
|
|
12
|
+
const payload = Buffer.from(parts[1], "base64").toString("utf-8");
|
|
13
|
+
return JSON.parse(payload);
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function extractAccountId(accessToken) {
|
|
20
|
+
const payload = decodeJWT(accessToken);
|
|
21
|
+
return payload?.[JWT_CLAIM]?.chatgpt_account_id ?? null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function extractProfile(accessToken) {
|
|
25
|
+
const payload = decodeJWT(accessToken);
|
|
26
|
+
const auth = payload?.[JWT_CLAIM] ?? {};
|
|
27
|
+
const profile = payload?.[JWT_PROFILE] ?? {};
|
|
28
|
+
return {
|
|
29
|
+
email: profile.email ?? null,
|
|
30
|
+
planType: auth.chatgpt_plan_type ?? null,
|
|
31
|
+
userId: auth.chatgpt_user_id ?? null,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI OAuth PKCE flow (shared utilities).
|
|
3
|
+
* Depends on: lib/constants.js, lib/color.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createServer } from "node:net";
|
|
7
|
+
import { createServer as createHttpServer } from "node:http";
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
9
|
+
import { randomBytes, createHash } from "node:crypto";
|
|
10
|
+
import {
|
|
11
|
+
TOKEN_URL,
|
|
12
|
+
AUTHORIZE_URL,
|
|
13
|
+
CLIENT_ID,
|
|
14
|
+
REDIRECT_URI,
|
|
15
|
+
SCOPE,
|
|
16
|
+
OAUTH_TIMEOUT_MS,
|
|
17
|
+
JWT_CLAIM,
|
|
18
|
+
PRIMARY_CMD,
|
|
19
|
+
} from "./constants.js";
|
|
20
|
+
import { decodeJWT, extractAccountId, extractProfile } from "./jwt.js";
|
|
21
|
+
|
|
22
|
+
export function generatePKCE() {
|
|
23
|
+
// Generate 32 random bytes and encode as base64url
|
|
24
|
+
const verifier = randomBytes(32)
|
|
25
|
+
.toString("base64")
|
|
26
|
+
.replace(/\+/g, "-")
|
|
27
|
+
.replace(/\//g, "_")
|
|
28
|
+
.replace(/=/g, "");
|
|
29
|
+
|
|
30
|
+
// Generate SHA256 hash of verifier and encode as base64url
|
|
31
|
+
const challenge = createHash("sha256")
|
|
32
|
+
.update(verifier)
|
|
33
|
+
.digest("base64")
|
|
34
|
+
.replace(/\+/g, "-")
|
|
35
|
+
.replace(/\//g, "_")
|
|
36
|
+
.replace(/=/g, "");
|
|
37
|
+
|
|
38
|
+
return { verifier, challenge };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Generate random state string for OAuth CSRF protection
|
|
43
|
+
* @returns {string} 64-character hex string (32 random bytes)
|
|
44
|
+
*/
|
|
45
|
+
export function generateState() {
|
|
46
|
+
return randomBytes(32).toString("hex");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Build the OAuth authorization URL with all required parameters
|
|
51
|
+
* @param {string} codeChallenge - PKCE code challenge (base64url-encoded SHA256)
|
|
52
|
+
* @param {string} state - Random state string for CSRF protection
|
|
53
|
+
* @returns {string} Complete authorization URL
|
|
54
|
+
*/
|
|
55
|
+
export function buildAuthUrl(codeChallenge, state) {
|
|
56
|
+
const params = new URLSearchParams({
|
|
57
|
+
response_type: "code",
|
|
58
|
+
client_id: CLIENT_ID,
|
|
59
|
+
redirect_uri: REDIRECT_URI,
|
|
60
|
+
scope: SCOPE,
|
|
61
|
+
code_challenge: codeChallenge,
|
|
62
|
+
code_challenge_method: "S256",
|
|
63
|
+
state: state,
|
|
64
|
+
id_token_add_organizations: "true",
|
|
65
|
+
codex_cli_simplified_flow: "true",
|
|
66
|
+
originator: "codex_cli_rs",
|
|
67
|
+
});
|
|
68
|
+
// Use %20 instead of + for spaces (matches official Codex CLI)
|
|
69
|
+
return `${AUTHORIZE_URL}?${params.toString().replace(/\+/g, "%20")}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Check if a port is available for binding
|
|
74
|
+
* @param {number} port - Port number to check
|
|
75
|
+
* @returns {Promise<boolean>} True if port is available, false if in use
|
|
76
|
+
*/
|
|
77
|
+
export function checkPortAvailable(port) {
|
|
78
|
+
return new Promise((resolve) => {
|
|
79
|
+
const server = createServer();
|
|
80
|
+
|
|
81
|
+
server.once("error", (err) => {
|
|
82
|
+
if (err.code === "EADDRINUSE") {
|
|
83
|
+
resolve(false);
|
|
84
|
+
} else {
|
|
85
|
+
// Other errors - treat as unavailable to be safe
|
|
86
|
+
resolve(false);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
server.once("listening", () => {
|
|
91
|
+
// Port is available - close immediately and report success
|
|
92
|
+
server.close(() => {
|
|
93
|
+
resolve(true);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
server.listen(port, "127.0.0.1");
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Detect if running in a headless environment (SSH, no display)
|
|
103
|
+
* Used to determine whether to open browser or print URL for manual copy
|
|
104
|
+
* @returns {boolean} True if headless environment detected
|
|
105
|
+
*/
|
|
106
|
+
export function isHeadlessEnvironment() {
|
|
107
|
+
// Check for SSH session
|
|
108
|
+
if (process.env.SSH_CLIENT || process.env.SSH_TTY) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// On Linux, check for display server
|
|
113
|
+
if (process.platform === "linux") {
|
|
114
|
+
if (!process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Open a URL in the default browser, or print URL if headless/--no-browser
|
|
124
|
+
* @param {string} url - URL to open
|
|
125
|
+
* @param {{ noBrowser?: boolean }} options - Options including --no-browser flag
|
|
126
|
+
* @returns {boolean} True if browser was opened, false if URL was printed
|
|
127
|
+
*/
|
|
128
|
+
export function openBrowser(url, options = {}) {
|
|
129
|
+
// If --no-browser flag or headless environment, only print URL (don't open browser)
|
|
130
|
+
if (options.noBrowser || isHeadlessEnvironment()) {
|
|
131
|
+
console.log("\nOpen this URL in your browser to authenticate:");
|
|
132
|
+
console.log(`\n ${url}\n`);
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Platform-specific browser open commands
|
|
137
|
+
let cmd;
|
|
138
|
+
let args;
|
|
139
|
+
|
|
140
|
+
switch (process.platform) {
|
|
141
|
+
case "darwin":
|
|
142
|
+
cmd = "open";
|
|
143
|
+
args = [url];
|
|
144
|
+
break;
|
|
145
|
+
case "win32":
|
|
146
|
+
cmd = "cmd";
|
|
147
|
+
args = ["/c", "start", "", url];
|
|
148
|
+
break;
|
|
149
|
+
default:
|
|
150
|
+
// Linux and other Unix-like systems
|
|
151
|
+
cmd = "xdg-open";
|
|
152
|
+
args = [url];
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
// Spawn detached process so it doesn't block the CLI
|
|
158
|
+
const child = spawn(cmd, args, {
|
|
159
|
+
detached: true,
|
|
160
|
+
stdio: "ignore",
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Unref to allow the parent process to exit independently
|
|
164
|
+
child.unref();
|
|
165
|
+
|
|
166
|
+
console.log("\nOpening browser for authentication...");
|
|
167
|
+
console.log("\nIf the browser doesn't open, use this URL:");
|
|
168
|
+
console.log(`\n ${url}\n`);
|
|
169
|
+
return true;
|
|
170
|
+
} catch {
|
|
171
|
+
// If spawn fails, fall back to printing URL
|
|
172
|
+
console.log("\nCould not open browser. Open this URL manually:");
|
|
173
|
+
console.log(`\n ${url}\n`);
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* HTML page shown to user after successful OAuth callback
|
|
180
|
+
* Minimal, self-contained page that closes automatically after 3 seconds
|
|
181
|
+
*/
|
|
182
|
+
export const SUCCESS_HTML = `<!DOCTYPE html>
|
|
183
|
+
<html lang="en">
|
|
184
|
+
<head>
|
|
185
|
+
<meta charset="UTF-8">
|
|
186
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
187
|
+
<title>Authentication Successful</title>
|
|
188
|
+
<style>
|
|
189
|
+
body {
|
|
190
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
191
|
+
display: flex;
|
|
192
|
+
justify-content: center;
|
|
193
|
+
align-items: center;
|
|
194
|
+
min-height: 100vh;
|
|
195
|
+
margin: 0;
|
|
196
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
197
|
+
color: white;
|
|
198
|
+
}
|
|
199
|
+
.container {
|
|
200
|
+
text-align: center;
|
|
201
|
+
padding: 2rem;
|
|
202
|
+
}
|
|
203
|
+
h1 { font-size: 2rem; margin-bottom: 0.5rem; }
|
|
204
|
+
p { font-size: 1.1rem; opacity: 0.9; }
|
|
205
|
+
.checkmark {
|
|
206
|
+
font-size: 4rem;
|
|
207
|
+
margin-bottom: 1rem;
|
|
208
|
+
}
|
|
209
|
+
</style>
|
|
210
|
+
</head>
|
|
211
|
+
<body>
|
|
212
|
+
<div class="container">
|
|
213
|
+
<div class="checkmark">✓</div>
|
|
214
|
+
<h1>Authentication Successful</h1>
|
|
215
|
+
<p>You can close this window and return to the terminal.</p>
|
|
216
|
+
</div>
|
|
217
|
+
<script>setTimeout(() => window.close(), 3000);</script>
|
|
218
|
+
</body>
|
|
219
|
+
</html>`;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Error HTML page shown when OAuth callback has an error
|
|
223
|
+
* @param {string} message - Error message to display
|
|
224
|
+
* @returns {string} HTML page content
|
|
225
|
+
*/
|
|
226
|
+
export function getErrorHtml(message) {
|
|
227
|
+
return `<!DOCTYPE html>
|
|
228
|
+
<html lang="en">
|
|
229
|
+
<head>
|
|
230
|
+
<meta charset="UTF-8">
|
|
231
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
232
|
+
<title>Authentication Failed</title>
|
|
233
|
+
<style>
|
|
234
|
+
body {
|
|
235
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
236
|
+
display: flex;
|
|
237
|
+
justify-content: center;
|
|
238
|
+
align-items: center;
|
|
239
|
+
min-height: 100vh;
|
|
240
|
+
margin: 0;
|
|
241
|
+
background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);
|
|
242
|
+
color: white;
|
|
243
|
+
}
|
|
244
|
+
.container {
|
|
245
|
+
text-align: center;
|
|
246
|
+
padding: 2rem;
|
|
247
|
+
}
|
|
248
|
+
h1 { font-size: 2rem; margin-bottom: 0.5rem; }
|
|
249
|
+
p { font-size: 1.1rem; opacity: 0.9; }
|
|
250
|
+
.icon {
|
|
251
|
+
font-size: 4rem;
|
|
252
|
+
margin-bottom: 1rem;
|
|
253
|
+
}
|
|
254
|
+
</style>
|
|
255
|
+
</head>
|
|
256
|
+
<body>
|
|
257
|
+
<div class="container">
|
|
258
|
+
<div class="icon">✗</div>
|
|
259
|
+
<h1>Authentication Failed</h1>
|
|
260
|
+
<p>${message}</p>
|
|
261
|
+
<p>You can close this window and try again.</p>
|
|
262
|
+
</div>
|
|
263
|
+
</body>
|
|
264
|
+
</html>`;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Exchange authorization code for tokens using the OAuth token endpoint
|
|
269
|
+
* @param {string} code - Authorization code from OAuth callback
|
|
270
|
+
* @param {string} codeVerifier - PKCE code verifier used when generating the challenge
|
|
271
|
+
* @returns {Promise<{accessToken: string, refreshToken: string, idToken: string, expires: number, accountId: string, email: string | null}>}
|
|
272
|
+
* @throws {Error} If token exchange fails
|
|
273
|
+
*/
|
|
274
|
+
export async function exchangeCodeForTokens(code, codeVerifier) {
|
|
275
|
+
const body = new URLSearchParams({
|
|
276
|
+
grant_type: "authorization_code",
|
|
277
|
+
code: code,
|
|
278
|
+
client_id: CLIENT_ID,
|
|
279
|
+
redirect_uri: REDIRECT_URI,
|
|
280
|
+
code_verifier: codeVerifier,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const res = await fetch(TOKEN_URL, {
|
|
284
|
+
method: "POST",
|
|
285
|
+
headers: {
|
|
286
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
287
|
+
},
|
|
288
|
+
body: body.toString(),
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
if (!res.ok) {
|
|
292
|
+
let errorMessage = `HTTP ${res.status}`;
|
|
293
|
+
try {
|
|
294
|
+
const errorJson = await res.json();
|
|
295
|
+
if (errorJson.error_description) {
|
|
296
|
+
errorMessage = errorJson.error_description;
|
|
297
|
+
} else if (errorJson.error) {
|
|
298
|
+
errorMessage = errorJson.error;
|
|
299
|
+
}
|
|
300
|
+
} catch {
|
|
301
|
+
// Response not JSON - use HTTP status message
|
|
302
|
+
}
|
|
303
|
+
throw new Error(`Token exchange failed: ${errorMessage}`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const json = await res.json();
|
|
307
|
+
|
|
308
|
+
// Validate required fields
|
|
309
|
+
if (!json.access_token) {
|
|
310
|
+
throw new Error("Token exchange failed: Missing access_token in response");
|
|
311
|
+
}
|
|
312
|
+
if (!json.refresh_token) {
|
|
313
|
+
throw new Error("Token exchange failed: Missing refresh_token in response");
|
|
314
|
+
}
|
|
315
|
+
if (typeof json.expires_in !== "number") {
|
|
316
|
+
throw new Error("Token exchange failed: Missing or invalid expires_in in response");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Calculate expires timestamp (milliseconds since epoch)
|
|
320
|
+
const expires = Date.now() + json.expires_in * 1000;
|
|
321
|
+
|
|
322
|
+
// Extract account_id and email from id_token JWT claims
|
|
323
|
+
const idToken = json.id_token || null;
|
|
324
|
+
let accountId = null;
|
|
325
|
+
let email = null;
|
|
326
|
+
|
|
327
|
+
// Try to get account_id from access_token first (more reliable)
|
|
328
|
+
accountId = extractAccountId(json.access_token);
|
|
329
|
+
|
|
330
|
+
// Extract email from id_token if present
|
|
331
|
+
if (idToken) {
|
|
332
|
+
const idPayload = decodeJWT(idToken);
|
|
333
|
+
if (idPayload) {
|
|
334
|
+
email = idPayload.email || null;
|
|
335
|
+
// Fallback: get account_id from id_token if not in access_token
|
|
336
|
+
if (!accountId) {
|
|
337
|
+
accountId = idPayload[JWT_CLAIM]?.chatgpt_account_id || null;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// If still no account_id, try extracting from access_token profile
|
|
343
|
+
if (!accountId) {
|
|
344
|
+
const profile = extractProfile(json.access_token);
|
|
345
|
+
email = email || profile.email;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (!accountId) {
|
|
349
|
+
throw new Error("Token exchange failed: Could not extract account_id from tokens");
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
accessToken: json.access_token,
|
|
354
|
+
refreshToken: json.refresh_token,
|
|
355
|
+
idToken: idToken,
|
|
356
|
+
expires: expires,
|
|
357
|
+
accountId: accountId,
|
|
358
|
+
email: email,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Start local HTTP server to receive OAuth callback
|
|
364
|
+
* Server listens on port 1455 for /auth/callback path
|
|
365
|
+
* @param {string} expectedState - State string to verify against CSRF attacks
|
|
366
|
+
* @returns {Promise<{code: string, state: string}>} Resolves with auth code and state, rejects on error/timeout
|
|
367
|
+
*/
|
|
368
|
+
export function startCallbackServer(expectedState) {
|
|
369
|
+
return new Promise((resolve, reject) => {
|
|
370
|
+
let serverClosed = false;
|
|
371
|
+
let timeoutId = null;
|
|
372
|
+
let sigintHandler = null;
|
|
373
|
+
|
|
374
|
+
const server = createHttpServer((req, res) => {
|
|
375
|
+
// Only handle /auth/callback path
|
|
376
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
377
|
+
|
|
378
|
+
if (url.pathname !== "/auth/callback") {
|
|
379
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
380
|
+
res.end("Not Found");
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Parse query parameters
|
|
385
|
+
const code = url.searchParams.get("code");
|
|
386
|
+
const state = url.searchParams.get("state");
|
|
387
|
+
const error = url.searchParams.get("error");
|
|
388
|
+
const errorDescription = url.searchParams.get("error_description");
|
|
389
|
+
|
|
390
|
+
// Handle error response from OAuth provider
|
|
391
|
+
if (error) {
|
|
392
|
+
const message = errorDescription || error;
|
|
393
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
394
|
+
res.end(getErrorHtml(message));
|
|
395
|
+
cleanup();
|
|
396
|
+
reject(new Error(`OAuth error: ${message}`));
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Validate required parameters
|
|
401
|
+
if (!code) {
|
|
402
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
403
|
+
res.end(getErrorHtml("Missing authorization code"));
|
|
404
|
+
cleanup();
|
|
405
|
+
reject(new Error("Missing authorization code in callback"));
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (!state) {
|
|
410
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
411
|
+
res.end(getErrorHtml("Missing state parameter"));
|
|
412
|
+
cleanup();
|
|
413
|
+
reject(new Error("Missing state parameter in callback"));
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Verify state matches to prevent CSRF attacks
|
|
418
|
+
if (state !== expectedState) {
|
|
419
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
420
|
+
res.end(getErrorHtml("State mismatch - possible CSRF attack"));
|
|
421
|
+
cleanup();
|
|
422
|
+
reject(new Error("State mismatch. Possible CSRF attack."));
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Success! Serve success page and resolve
|
|
427
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
428
|
+
res.end(SUCCESS_HTML);
|
|
429
|
+
cleanup();
|
|
430
|
+
resolve({ code, state });
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Clean up server resources
|
|
435
|
+
*/
|
|
436
|
+
function cleanup() {
|
|
437
|
+
if (serverClosed) return;
|
|
438
|
+
serverClosed = true;
|
|
439
|
+
|
|
440
|
+
// Clear timeout
|
|
441
|
+
if (timeoutId) {
|
|
442
|
+
clearTimeout(timeoutId);
|
|
443
|
+
timeoutId = null;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Remove SIGINT handler
|
|
447
|
+
if (sigintHandler) {
|
|
448
|
+
process.removeListener("SIGINT", sigintHandler);
|
|
449
|
+
sigintHandler = null;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Close server
|
|
453
|
+
server.close();
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Handle server errors
|
|
457
|
+
server.on("error", (err) => {
|
|
458
|
+
cleanup();
|
|
459
|
+
if (err.code === "EADDRINUSE") {
|
|
460
|
+
reject(new Error(`Port 1455 is in use. Close other ${PRIMARY_CMD} instances and retry.`));
|
|
461
|
+
} else {
|
|
462
|
+
reject(new Error(`Server error: ${err.message}`));
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// Set timeout for authentication (default 2 minutes)
|
|
467
|
+
timeoutId = setTimeout(() => {
|
|
468
|
+
cleanup();
|
|
469
|
+
reject(new Error(`Authentication timed out after 2 minutes. Run '${PRIMARY_CMD} codex add' to try again.`));
|
|
470
|
+
}, OAUTH_TIMEOUT_MS);
|
|
471
|
+
|
|
472
|
+
// Handle Ctrl+C gracefully
|
|
473
|
+
sigintHandler = () => {
|
|
474
|
+
console.log("\nAuthentication cancelled.");
|
|
475
|
+
cleanup();
|
|
476
|
+
reject(new Error("Authentication cancelled by user."));
|
|
477
|
+
};
|
|
478
|
+
process.on("SIGINT", sigintHandler);
|
|
479
|
+
|
|
480
|
+
// Start listening on localhost only (security)
|
|
481
|
+
server.listen(1455, "127.0.0.1", () => {
|
|
482
|
+
// Server is ready - caller will open browser
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth file path resolution for OpenCode, Codex CLI, and pi.
|
|
3
|
+
* Depends only on lib/constants.js.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { DEFAULT_XDG_DATA_HOME, CODEX_CLI_AUTH_PATH, PI_AUTH_PATH } from "./constants.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resolve OpenCode auth.json path using XDG_DATA_HOME
|
|
11
|
+
* @returns {string}
|
|
12
|
+
*/
|
|
13
|
+
export function getOpencodeAuthPath() {
|
|
14
|
+
const dataHome = process.env.XDG_DATA_HOME || DEFAULT_XDG_DATA_HOME;
|
|
15
|
+
return join(dataHome, "opencode", "auth.json");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Resolve Codex CLI auth.json path with optional override.
|
|
20
|
+
* @returns {string}
|
|
21
|
+
*/
|
|
22
|
+
export function getCodexCliAuthPath() {
|
|
23
|
+
const override = process.env.CODEX_AUTH_PATH;
|
|
24
|
+
return override ? override : CODEX_CLI_AUTH_PATH;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Resolve pi auth.json path with optional override.
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
31
|
+
export function getPiAuthPath() {
|
|
32
|
+
const override = process.env.PI_AUTH_PATH;
|
|
33
|
+
return override ? override : PI_AUTH_PATH;
|
|
34
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive prompt helpers.
|
|
3
|
+
* Zero internal dependencies — only uses Node.js built-ins.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createInterface } from "node:readline";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Prompt for confirmation using readline
|
|
10
|
+
* @param {string} message - Message to display
|
|
11
|
+
* @returns {Promise<boolean>} True if user confirms (y/Y), false otherwise
|
|
12
|
+
*/
|
|
13
|
+
export async function promptConfirm(message) {
|
|
14
|
+
const rl = createInterface({
|
|
15
|
+
input: process.stdin,
|
|
16
|
+
output: process.stdout,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
rl.question(`${message} [y/N] `, (answer) => {
|
|
21
|
+
rl.close();
|
|
22
|
+
resolve(answer.toLowerCase() === "y");
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function promptInput(message, options = {}) {
|
|
28
|
+
const { allowEmpty = false } = options;
|
|
29
|
+
const rl = createInterface({
|
|
30
|
+
input: process.stdin,
|
|
31
|
+
output: process.stdout,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return new Promise((resolve) => {
|
|
35
|
+
rl.question(message, (answer) => {
|
|
36
|
+
rl.close();
|
|
37
|
+
if (allowEmpty) {
|
|
38
|
+
resolve(answer);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
resolve(answer.trim());
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|