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.
Files changed (317) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +589 -0
  3. package/SKILL.md +149 -0
  4. package/assets/workflow-catalog.json +57 -0
  5. package/bin/audit-issue-routing.sh +74 -0
  6. package/bin/issue-resource-class.sh +58 -0
  7. package/bin/label-follow-up-issues.sh +114 -0
  8. package/bin/pr-risk.sh +532 -0
  9. package/bin/sync-pr-labels.sh +112 -0
  10. package/hooks/heartbeat-hooks.sh +573 -0
  11. package/hooks/issue-reconcile-hooks.sh +217 -0
  12. package/hooks/pr-reconcile-hooks.sh +225 -0
  13. package/npm/bin/agent-control-plane.js +1984 -0
  14. package/npm/public-bin/agent-control-plane +3 -0
  15. package/package.json +61 -0
  16. package/tools/bin/agent-cleanup-worktree +247 -0
  17. package/tools/bin/agent-github-update-labels +66 -0
  18. package/tools/bin/agent-init-worktree +216 -0
  19. package/tools/bin/agent-project-archive-run +52 -0
  20. package/tools/bin/agent-project-capture-worker +46 -0
  21. package/tools/bin/agent-project-catch-up-merged-prs +137 -0
  22. package/tools/bin/agent-project-cleanup-session +244 -0
  23. package/tools/bin/agent-project-detached-launch +107 -0
  24. package/tools/bin/agent-project-heartbeat-loop +2347 -0
  25. package/tools/bin/agent-project-open-issue-worktree +89 -0
  26. package/tools/bin/agent-project-open-pr-worktree +80 -0
  27. package/tools/bin/agent-project-publish-issue-pr +349 -0
  28. package/tools/bin/agent-project-reconcile-issue-session +1128 -0
  29. package/tools/bin/agent-project-reconcile-pr-session +1005 -0
  30. package/tools/bin/agent-project-retry-state +147 -0
  31. package/tools/bin/agent-project-run-claude-session +657 -0
  32. package/tools/bin/agent-project-run-codex-resilient +718 -0
  33. package/tools/bin/agent-project-run-codex-session +316 -0
  34. package/tools/bin/agent-project-run-kilo-session +27 -0
  35. package/tools/bin/agent-project-run-openclaw-session +984 -0
  36. package/tools/bin/agent-project-run-opencode-session +27 -0
  37. package/tools/bin/agent-project-sync-anchor-repo +128 -0
  38. package/tools/bin/agent-project-worker-status +143 -0
  39. package/tools/bin/audit-agent-worktrees.sh +310 -0
  40. package/tools/bin/audit-issue-routing.sh +11 -0
  41. package/tools/bin/audit-retained-layout.sh +58 -0
  42. package/tools/bin/audit-retained-overlap.sh +135 -0
  43. package/tools/bin/audit-retained-worktrees.sh +228 -0
  44. package/tools/bin/branch-verification-guard.sh +351 -0
  45. package/tools/bin/capture-worker.sh +18 -0
  46. package/tools/bin/check-skill-contracts.sh +324 -0
  47. package/tools/bin/cleanup-worktree.sh +44 -0
  48. package/tools/bin/codex-quota +31 -0
  49. package/tools/bin/create-follow-up-issue.sh +114 -0
  50. package/tools/bin/dashboard-launchd-bootstrap.sh +38 -0
  51. package/tools/bin/flow-config-lib.sh +2127 -0
  52. package/tools/bin/flow-resident-worker-lib.sh +683 -0
  53. package/tools/bin/flow-runtime-doctor.sh +97 -0
  54. package/tools/bin/flow-shell-lib.sh +266 -0
  55. package/tools/bin/heartbeat-recovery-preflight.sh +106 -0
  56. package/tools/bin/heartbeat-safe-auto.sh +551 -0
  57. package/tools/bin/install-dashboard-launchd.sh +152 -0
  58. package/tools/bin/install-project-launchd.sh +219 -0
  59. package/tools/bin/issue-publish-scope-guard.sh +242 -0
  60. package/tools/bin/issue-requires-local-workspace-install.sh +31 -0
  61. package/tools/bin/issue-resource-class.sh +12 -0
  62. package/tools/bin/kick-scheduler.sh +75 -0
  63. package/tools/bin/label-follow-up-issues.sh +14 -0
  64. package/tools/bin/new-pr-worktree.sh +50 -0
  65. package/tools/bin/new-worktree.sh +49 -0
  66. package/tools/bin/pr-risk.sh +12 -0
  67. package/tools/bin/prepare-worktree.sh +140 -0
  68. package/tools/bin/profile-activate.sh +109 -0
  69. package/tools/bin/profile-adopt.sh +219 -0
  70. package/tools/bin/profile-smoke.sh +461 -0
  71. package/tools/bin/project-init.sh +189 -0
  72. package/tools/bin/project-launchd-bootstrap.sh +54 -0
  73. package/tools/bin/project-remove.sh +155 -0
  74. package/tools/bin/project-runtime-supervisor.sh +56 -0
  75. package/tools/bin/project-runtimectl.sh +586 -0
  76. package/tools/bin/provider-cooldown-state.sh +166 -0
  77. package/tools/bin/publish-issue-worker.sh +31 -0
  78. package/tools/bin/reconcile-issue-worker.sh +34 -0
  79. package/tools/bin/reconcile-pr-worker.sh +34 -0
  80. package/tools/bin/record-verification.sh +71 -0
  81. package/tools/bin/render-architecture-infographics.sh +110 -0
  82. package/tools/bin/render-dashboard-demo-media.sh +333 -0
  83. package/tools/bin/render-dashboard-snapshot.py +16 -0
  84. package/tools/bin/render-flow-config.sh +86 -0
  85. package/tools/bin/retry-state.sh +31 -0
  86. package/tools/bin/reuse-issue-worktree.sh +75 -0
  87. package/tools/bin/run-codex-bypass.sh +3 -0
  88. package/tools/bin/run-codex-safe.sh +3 -0
  89. package/tools/bin/run-codex-task.sh +231 -0
  90. package/tools/bin/scaffold-profile.sh +374 -0
  91. package/tools/bin/serve-dashboard.sh +5 -0
  92. package/tools/bin/split-retained-slice.sh +124 -0
  93. package/tools/bin/start-issue-worker.sh +796 -0
  94. package/tools/bin/start-pr-fix-worker.sh +458 -0
  95. package/tools/bin/start-pr-merge-repair-worker.sh +8 -0
  96. package/tools/bin/start-pr-review-worker.sh +227 -0
  97. package/tools/bin/start-resident-issue-loop.sh +908 -0
  98. package/tools/bin/sync-agent-repo.sh +52 -0
  99. package/tools/bin/sync-dependency-baseline.sh +247 -0
  100. package/tools/bin/sync-pr-labels.sh +12 -0
  101. package/tools/bin/sync-recurring-issue-checklist.sh +274 -0
  102. package/tools/bin/sync-shared-agent-home.sh +214 -0
  103. package/tools/bin/sync-vscode-workspace.sh +157 -0
  104. package/tools/bin/test-smoke.sh +63 -0
  105. package/tools/bin/uninstall-project-launchd.sh +55 -0
  106. package/tools/bin/update-github-labels.sh +14 -0
  107. package/tools/bin/worker-status.sh +19 -0
  108. package/tools/bin/workflow-catalog.sh +77 -0
  109. package/tools/dashboard/app.js +286 -0
  110. package/tools/dashboard/dashboard_snapshot.py +466 -0
  111. package/tools/dashboard/index.html +41 -0
  112. package/tools/dashboard/server.py +64 -0
  113. package/tools/dashboard/styles.css +351 -0
  114. package/tools/templates/issue-prompt-template.md +109 -0
  115. package/tools/templates/pr-fix-template.md +120 -0
  116. package/tools/templates/pr-merge-repair-template.md +91 -0
  117. package/tools/templates/pr-review-template.md +62 -0
  118. package/tools/templates/scheduled-issue-prompt-template.md +62 -0
  119. package/tools/tests/test-agent-control-plane-npm-cli.sh +279 -0
  120. package/tools/tests/test-agent-github-update-labels-falls-back-to-repository-id.sh +56 -0
  121. package/tools/tests/test-agent-project-claude-session-wrapper-clears-stale-sandbox-artifacts.sh +89 -0
  122. package/tools/tests/test-agent-project-claude-session-wrapper-does-not-retry-provider-quota.sh +82 -0
  123. package/tools/tests/test-agent-project-claude-session-wrapper-retries-transient-failures.sh +90 -0
  124. package/tools/tests/test-agent-project-claude-session-wrapper-times-out.sh +73 -0
  125. package/tools/tests/test-agent-project-claude-session-wrapper.sh +103 -0
  126. package/tools/tests/test-agent-project-cleanup-session-orphan-fallback.sh +90 -0
  127. package/tools/tests/test-agent-project-cleanup-session-skip-worktree-cleanup.sh +90 -0
  128. package/tools/tests/test-agent-project-codex-live-thread-persist.sh +76 -0
  129. package/tools/tests/test-agent-project-codex-recovery.sh +731 -0
  130. package/tools/tests/test-agent-project-codex-session-wrapper-clears-stale-sandbox-artifacts.sh +105 -0
  131. package/tools/tests/test-agent-project-codex-session-wrapper.sh +97 -0
  132. package/tools/tests/test-agent-project-open-pr-worktree-config-prefix.sh +81 -0
  133. package/tools/tests/test-agent-project-openclaw-session-wrapper-clears-stale-sandbox-artifacts.sh +109 -0
  134. package/tools/tests/test-agent-project-openclaw-session-wrapper-infers-blocked-result-contract.sh +89 -0
  135. package/tools/tests/test-agent-project-openclaw-session-wrapper-recovers-literal-env-artifacts.sh +113 -0
  136. package/tools/tests/test-agent-project-openclaw-session-wrapper-recovers-version-mismatch.sh +135 -0
  137. package/tools/tests/test-agent-project-openclaw-session-wrapper-resident.sh +179 -0
  138. package/tools/tests/test-agent-project-openclaw-session-wrapper-reuses-existing-agent-after-add-race.sh +119 -0
  139. package/tools/tests/test-agent-project-openclaw-session-wrapper-terminates-rate-limit-hang.sh +91 -0
  140. package/tools/tests/test-agent-project-openclaw-session-wrapper.sh +117 -0
  141. package/tools/tests/test-agent-project-publish-issue-pr-prunes-stale-worktree-entry.sh +148 -0
  142. package/tools/tests/test-agent-project-publish-issue-pr-reads-archived-session.sh +146 -0
  143. package/tools/tests/test-agent-project-publish-issue-pr-recovers-final-head.sh +145 -0
  144. package/tools/tests/test-agent-project-publish-issue-pr-reuses-existing-worktree.sh +147 -0
  145. package/tools/tests/test-agent-project-reconcile-failure-reason.sh +456 -0
  146. package/tools/tests/test-agent-project-reconcile-issue-archived-session-fallback.sh +96 -0
  147. package/tools/tests/test-agent-project-reconcile-issue-before-blocked.sh +90 -0
  148. package/tools/tests/test-agent-project-reconcile-issue-host-verification-recovery-uses-recovered-worktree.sh +212 -0
  149. package/tools/tests/test-agent-project-reconcile-issue-host-verification-recovery.sh +207 -0
  150. package/tools/tests/test-agent-project-reconcile-issue-provider-quota-schedules-provider-cooldown.sh +101 -0
  151. package/tools/tests/test-agent-project-reconcile-issue-session-backfills-lane-metadata-from-worker-key.sh +113 -0
  152. package/tools/tests/test-agent-project-reconcile-issue-session-clears-stale-failed-summary.sh +117 -0
  153. package/tools/tests/test-agent-project-reconcile-issue-session-initializes-shared-agent-home.sh +55 -0
  154. package/tools/tests/test-agent-project-reconcile-issue-session-normalizes-runner-state.sh +125 -0
  155. package/tools/tests/test-agent-project-reconcile-issue-session-records-invalid-contract-summary.sh +118 -0
  156. package/tools/tests/test-agent-project-reconcile-issue-session-skips-duplicate-blocked-comment.sh +144 -0
  157. package/tools/tests/test-agent-project-reconcile-issue-session-standardizes-no-commits-blocker.sh +145 -0
  158. package/tools/tests/test-agent-project-reconcile-issue-session-synthesizes-blocked-comment.sh +139 -0
  159. package/tools/tests/test-agent-project-reconcile-pr-blocked-host-recovery.sh +242 -0
  160. package/tools/tests/test-agent-project-reconcile-pr-guard-blocked-no-commit.sh +142 -0
  161. package/tools/tests/test-agent-project-reconcile-pr-provider-quota-schedules-provider-cooldown.sh +106 -0
  162. package/tools/tests/test-agent-project-reconcile-pr-session-initializes-shared-agent-home.sh +66 -0
  163. package/tools/tests/test-agent-project-reconcile-pr-updated-branch-noop.sh +129 -0
  164. package/tools/tests/test-audit-agent-worktrees-active-launch-skips-git-inspection.sh +69 -0
  165. package/tools/tests/test-audit-agent-worktrees-broken-worktree.sh +43 -0
  166. package/tools/tests/test-audit-agent-worktrees-pending-launch-owner.sh +46 -0
  167. package/tools/tests/test-audit-agent-worktrees-unreconciled-owner.sh +79 -0
  168. package/tools/tests/test-audit-issue-routing-managed-branch-globs.sh +56 -0
  169. package/tools/tests/test-branch-verification-guard-generated-artifacts.sh +72 -0
  170. package/tools/tests/test-branch-verification-guard-targeted-coverage.sh +125 -0
  171. package/tools/tests/test-codex-quota-manager-failure-driven-rotation.sh +178 -0
  172. package/tools/tests/test-codex-quota-wrapper.sh +37 -0
  173. package/tools/tests/test-contribution-docs.sh +18 -0
  174. package/tools/tests/test-control-plane-dashboard-runtime-smoke.sh +343 -0
  175. package/tools/tests/test-create-follow-up-issue.sh +73 -0
  176. package/tools/tests/test-dashboard-launchd-bootstrap.sh +55 -0
  177. package/tools/tests/test-flow-export-execution-env-exports-repo-id.sh +30 -0
  178. package/tools/tests/test-flow-export-github-cli-auth-env-prefers-git-credential.sh +48 -0
  179. package/tools/tests/test-flow-github-api-repo-fallback-preserves-input.sh +85 -0
  180. package/tools/tests/test-flow-github-api-repo-prefers-explicit-repository-id.sh +60 -0
  181. package/tools/tests/test-flow-github-issue-list-falls-back-to-repository-id.sh +64 -0
  182. package/tools/tests/test-flow-github-pr-list-falls-back-to-repository-id.sh +77 -0
  183. package/tools/tests/test-flow-resident-can-reuse-does-not-leak-metadata.sh +52 -0
  184. package/tools/tests/test-flow-resident-reap-stale-controllers.sh +63 -0
  185. package/tools/tests/test-flow-resolve-codex-quota-tools.sh +104 -0
  186. package/tools/tests/test-flow-runtime-doctor-profile-selection.sh +27 -0
  187. package/tools/tests/test-heartbeat-codex-pr-linked-issue-exclusion.sh +79 -0
  188. package/tools/tests/test-heartbeat-hooks-enqueue-resident-issue-for-idle-controller.sh +115 -0
  189. package/tools/tests/test-heartbeat-hooks-enqueue-resident-issue-for-live-lane-controller.sh +117 -0
  190. package/tools/tests/test-heartbeat-hooks-start-resident-issue-loop-claude.sh +96 -0
  191. package/tools/tests/test-heartbeat-hooks-start-resident-issue-loop-codex.sh +96 -0
  192. package/tools/tests/test-heartbeat-hooks-start-resident-issue-loop.sh +96 -0
  193. package/tools/tests/test-heartbeat-loop-auth-wait-does-not-consume-capacity.sh +170 -0
  194. package/tools/tests/test-heartbeat-loop-blocked-recovery-lane.sh +201 -0
  195. package/tools/tests/test-heartbeat-loop-blocked-recovery-vs-pr-reservation.sh +201 -0
  196. package/tools/tests/test-heartbeat-loop-idle-resident-controller-does-not-block-launches.sh +160 -0
  197. package/tools/tests/test-heartbeat-loop-pr-launch-dedup.sh +133 -0
  198. package/tools/tests/test-heartbeat-loop-provider-cooldown-suppresses-launches.sh +157 -0
  199. package/tools/tests/test-heartbeat-loop-reaps-stale-resident-controller.sh +181 -0
  200. package/tools/tests/test-heartbeat-loop-waiting-provider-resident-controller-does-not-block-launches.sh +160 -0
  201. package/tools/tests/test-heartbeat-ready-issues-blocked-recovery.sh +134 -0
  202. package/tools/tests/test-heartbeat-safe-auto-dynamic-concurrency.sh +162 -0
  203. package/tools/tests/test-heartbeat-safe-auto-no-tmux-sessions.sh +136 -0
  204. package/tools/tests/test-heartbeat-safe-auto-openclaw-skips-codex-quota.sh +139 -0
  205. package/tools/tests/test-heartbeat-safe-auto-quota-health-signal.sh +119 -0
  206. package/tools/tests/test-heartbeat-safe-auto-stale-shared-loop-pid-does-not-skip.sh +140 -0
  207. package/tools/tests/test-heartbeat-safe-auto-static-capacity-without-quota-cache.sh +142 -0
  208. package/tools/tests/test-heartbeat-safe-auto-zero-healthy-pools.sh +141 -0
  209. package/tools/tests/test-heartbeat-sync-issue-labels-empty-schedule.sh +65 -0
  210. package/tools/tests/test-heartbeat-sync-open-agent-prs-terminal-clears-running.sh +179 -0
  211. package/tools/tests/test-install-dashboard-launchd.sh +78 -0
  212. package/tools/tests/test-install-project-launchd-adds-tool-paths.sh +87 -0
  213. package/tools/tests/test-install-project-launchd.sh +110 -0
  214. package/tools/tests/test-issue-local-workspace-install-policy.sh +81 -0
  215. package/tools/tests/test-issue-publish-scope-guard-docs-signal.sh +70 -0
  216. package/tools/tests/test-issue-reconcile-hooks-success-clears-blocked.sh +36 -0
  217. package/tools/tests/test-kick-scheduler-requires-explicit-profile.sh +47 -0
  218. package/tools/tests/test-label-follow-up-issues-falls-back-to-repository-id.sh +132 -0
  219. package/tools/tests/test-manual-operator-entrypoints-require-explicit-profile.sh +64 -0
  220. package/tools/tests/test-package-funding-metadata.sh +21 -0
  221. package/tools/tests/test-package-public-metadata.sh +62 -0
  222. package/tools/tests/test-placeholder-worker-adapters.sh +38 -0
  223. package/tools/tests/test-pr-reconcile-hooks-refreshes-recurring-issue-checklist.sh +110 -0
  224. package/tools/tests/test-pr-risk-cohesive-mobile-locale-scope.sh +70 -0
  225. package/tools/tests/test-pr-risk-fix-label-semantics.sh +114 -0
  226. package/tools/tests/test-pr-risk-local-first-no-checks.sh +70 -0
  227. package/tools/tests/test-prepare-worktree-simple-repo-baseline.sh +67 -0
  228. package/tools/tests/test-profile-activate.sh +33 -0
  229. package/tools/tests/test-profile-adopt-allow-missing-repo.sh +68 -0
  230. package/tools/tests/test-profile-adopt-skip-workspace-sync-missing-file.sh +61 -0
  231. package/tools/tests/test-profile-adopt-syncs-anchor-and-workspace.sh +90 -0
  232. package/tools/tests/test-profile-smoke-collision.sh +44 -0
  233. package/tools/tests/test-profile-smoke-invalid-claude-config.sh +31 -0
  234. package/tools/tests/test-profile-smoke-invalid-provider-pool.sh +68 -0
  235. package/tools/tests/test-profile-smoke-repo-slug-mismatch.sh +36 -0
  236. package/tools/tests/test-profile-smoke.sh +45 -0
  237. package/tools/tests/test-project-init-force-and-skip-sync.sh +61 -0
  238. package/tools/tests/test-project-init-repo-slug-mismatch.sh +29 -0
  239. package/tools/tests/test-project-init.sh +66 -0
  240. package/tools/tests/test-project-launchd-bootstrap.sh +66 -0
  241. package/tools/tests/test-project-remove.sh +150 -0
  242. package/tools/tests/test-project-runtime-supervisor.sh +47 -0
  243. package/tools/tests/test-project-runtimectl-launchd.sh +115 -0
  244. package/tools/tests/test-project-runtimectl-missing-profile.sh +54 -0
  245. package/tools/tests/test-project-runtimectl-start-falls-back-to-bootstrap.sh +108 -0
  246. package/tools/tests/test-project-runtimectl-status-reports-supervisor-as-heartbeat-parent.sh +95 -0
  247. package/tools/tests/test-project-runtimectl-status-supervisor-running.sh +59 -0
  248. package/tools/tests/test-project-runtimectl-stop-cancels-pending-kick.sh +85 -0
  249. package/tools/tests/test-project-runtimectl-stop-clears-running-labels.sh +78 -0
  250. package/tools/tests/test-project-runtimectl.sh +212 -0
  251. package/tools/tests/test-provider-cooldown-state-prefers-runtime-worker-context.sh +39 -0
  252. package/tools/tests/test-provider-cooldown-state.sh +59 -0
  253. package/tools/tests/test-public-repo-docs.sh +159 -0
  254. package/tools/tests/test-reconcile-pr-worker-acp-config-routing.sh +75 -0
  255. package/tools/tests/test-render-dashboard-snapshot.sh +149 -0
  256. package/tools/tests/test-render-flow-config-demo-profile.sh +36 -0
  257. package/tools/tests/test-render-flow-config-provider-pool-fallback.sh +81 -0
  258. package/tools/tests/test-render-flow-config.sh +52 -0
  259. package/tools/tests/test-run-codex-task-claude-routing.sh +125 -0
  260. package/tools/tests/test-run-codex-task-codex-resident-routing.sh +108 -0
  261. package/tools/tests/test-run-codex-task-kilo-routing.sh +98 -0
  262. package/tools/tests/test-run-codex-task-openclaw-resident-routing.sh +117 -0
  263. package/tools/tests/test-run-codex-task-openclaw-routing.sh +113 -0
  264. package/tools/tests/test-run-codex-task-opencode-routing.sh +98 -0
  265. package/tools/tests/test-run-codex-task-provider-pool-fallback-routing.sh +146 -0
  266. package/tools/tests/test-scaffold-profile.sh +108 -0
  267. package/tools/tests/test-serve-dashboard.sh +93 -0
  268. package/tools/tests/test-start-issue-worker-blocked-context.sh +129 -0
  269. package/tools/tests/test-start-issue-worker-blocks-complete-recurring-checklist.sh +189 -0
  270. package/tools/tests/test-start-issue-worker-local-install-routing.sh +157 -0
  271. package/tools/tests/test-start-issue-worker-profile-template-routing.sh +149 -0
  272. package/tools/tests/test-start-issue-worker-recurring-resident-reuse-codex.sh +212 -0
  273. package/tools/tests/test-start-issue-worker-recurring-resident-reuse.sh +219 -0
  274. package/tools/tests/test-start-issue-worker-renders-verification-snippet.sh +155 -0
  275. package/tools/tests/test-start-issue-worker-resident-reuse-falls-back-to-new-worktree.sh +199 -0
  276. package/tools/tests/test-start-pr-fix-worker-host-blocker-context.sh +275 -0
  277. package/tools/tests/test-start-resident-issue-loop-adopts-next-recurring-issue.sh +185 -0
  278. package/tools/tests/test-start-resident-issue-loop-clears-pending-while-waiting-due.sh +152 -0
  279. package/tools/tests/test-start-resident-issue-loop-consumes-queued-lease.sh +186 -0
  280. package/tools/tests/test-start-resident-issue-loop-fails-over-provider-pool.sh +212 -0
  281. package/tools/tests/test-start-resident-issue-loop-immediate-cycles.sh +148 -0
  282. package/tools/tests/test-start-resident-issue-loop-waits-for-provider.sh +194 -0
  283. package/tools/tests/test-start-resident-issue-loop-waits-for-terminal-reconcile-status.sh +198 -0
  284. package/tools/tests/test-start-resident-issue-loop-yields-to-live-lane-controller.sh +145 -0
  285. package/tools/tests/test-sync-pr-labels-fix-lane-uses-repair-queued.sh +67 -0
  286. package/tools/tests/test-sync-recurring-issue-checklist-backfills-workflow-complete-blocker.sh +70 -0
  287. package/tools/tests/test-sync-recurring-issue-checklist.sh +95 -0
  288. package/tools/tests/test-sync-shared-agent-home-local-source-root.sh +66 -0
  289. package/tools/tests/test-sync-shared-agent-home-preserves-unrelated-workflow-catalog-skill.sh +47 -0
  290. package/tools/tests/test-test-smoke.sh +86 -0
  291. package/tools/tests/test-uninstall-project-launchd.sh +37 -0
  292. package/tools/tests/test-update-github-labels-prefers-sibling-helper.sh +49 -0
  293. package/tools/tests/test-workflow-catalog.sh +43 -0
  294. package/tools/vendor/codex-quota/LICENSE +21 -0
  295. package/tools/vendor/codex-quota/README.md +459 -0
  296. package/tools/vendor/codex-quota/codex-quota.js +261 -0
  297. package/tools/vendor/codex-quota/lib/claude-accounts.js +226 -0
  298. package/tools/vendor/codex-quota/lib/claude-oauth.js +174 -0
  299. package/tools/vendor/codex-quota/lib/claude-tokens.js +471 -0
  300. package/tools/vendor/codex-quota/lib/claude-usage.js +929 -0
  301. package/tools/vendor/codex-quota/lib/codex-accounts.js +205 -0
  302. package/tools/vendor/codex-quota/lib/codex-tokens.js +326 -0
  303. package/tools/vendor/codex-quota/lib/codex-usage.js +32 -0
  304. package/tools/vendor/codex-quota/lib/color.js +72 -0
  305. package/tools/vendor/codex-quota/lib/constants.js +57 -0
  306. package/tools/vendor/codex-quota/lib/container.js +143 -0
  307. package/tools/vendor/codex-quota/lib/display.js +1111 -0
  308. package/tools/vendor/codex-quota/lib/fs.js +63 -0
  309. package/tools/vendor/codex-quota/lib/handlers.js +2060 -0
  310. package/tools/vendor/codex-quota/lib/jwt.js +33 -0
  311. package/tools/vendor/codex-quota/lib/oauth.js +486 -0
  312. package/tools/vendor/codex-quota/lib/paths.js +34 -0
  313. package/tools/vendor/codex-quota/lib/prompts.js +44 -0
  314. package/tools/vendor/codex-quota/lib/sync.js +1438 -0
  315. package/tools/vendor/codex-quota/lib/token-match.js +96 -0
  316. package/tools/vendor/codex-quota-manager/scripts/auto-switch.sh +500 -0
  317. 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
+ }