elanous 0.2.5 → 0.2.7
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/.env.example +10 -7
- package/README.md +9 -4
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5391-8793f88b03b30161.js → 2375-cb6debea56cce4fb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{1751.50b96ea9c37b49b6.js → 2415.ed6428ff334c7ce2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +25 -0
- package/apps/pwa/out/_next/static/chunks/{4daa2798-1363d725c79f3593.js → 2ea96958-e8c7e112de50b067.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +5 -0
- package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +1 -0
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8617.465ca22a319c95b3.js → 4323.c5dfb6f503304775.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4836-2eba6079f31b88d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8918-482b30ce2a8ebf4c.js → 5080-1613752891fcadb4.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5348-bbf838e119661eee.js → 5791-bb82743fa6299562.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{9704-3221c102623c8d73.js → 6062-73e68343a9814f6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{4440-18bb160d95819f4e.js → 6090-85a3f05f954e5697.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{f6f3850d-7f324c6566002113.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8067-f74bf14d67ad98f1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8519-f6b5d8a5833a7603.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8894-e92491a164192cc1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9740-67051141115ce775.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9804-64c02c3c127d5014.js +1 -0
- package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d5ebde04844689e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-f89e9a303515a033.js → page-10ed24f413ed66e9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-4f403cd060442c95.js → page-211810b6c93ab66a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-0ac5d053f0444d21.js → page-624f7a2f89b60c8f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-5a5fb385a4df0ffe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-80497e93a878ddc5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-7457f52683562161.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-cee86394de407aa6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-4ff8edab44196b8c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-92e4f44428d5e290.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-b7dcf5d48a854b76.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-51480757e7d68b92.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-2a05dfe80c8bdf16.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-d9979633aa82f8e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-30533117beb53625.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-f7857826c2f97ad7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-07267f1391b96aba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-bccce0babceae30f.js → page-55d50e9b0006a91d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-9699b1786be6171d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-43dd772aee8af75f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-a7e37af7d0399cb8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-973ab7af59c718b7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a9f8d3865b91e436.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-15e5a15db84c56aa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a2e572e4511f2a16.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-dd107bf1c7d52a90.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-d42b99cdfb9f27c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-2764ff40dad5a995.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-c5f1152db116ff96.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-f933bca81ea38a6f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-c1c95739c0bff3b8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-a7aaffcf771662d8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-71d18eec33b9c667.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-56adbe1a1f1379bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-2c4e59079ee357ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-a81f113f3ef89d72.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-5d422f38c052967e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-fc27b00e20733146.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{webpack-2bf59c720fe9542c.js → webpack-e5bea36f183b25e6.js} +1 -1
- package/apps/pwa/out/_next/static/css/f537bdb19c69306a.css +1 -0
- package/apps/pwa/out/_next/static/keajW7DDyghEs1y8oJe6T/_buildManifest.js +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +22 -22
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +22 -22
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +22 -22
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +21 -21
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +21 -21
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +21 -21
- package/apps/pwa/out/consult/index.html +1 -0
- package/apps/pwa/out/consult/index.txt +32 -0
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +22 -22
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +22 -22
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +22 -22
- package/apps/pwa/out/exec/index.html +1 -0
- package/apps/pwa/out/exec/index.txt +32 -0
- package/apps/pwa/out/field/index.html +1 -0
- package/apps/pwa/out/field/index.txt +32 -0
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/icons/elanous-16.png +0 -0
- package/apps/pwa/out/icons/elanous-180.png +0 -0
- package/apps/pwa/out/icons/elanous-192.png +0 -0
- package/apps/pwa/out/icons/elanous-32.png +0 -0
- package/apps/pwa/out/icons/elanous-512.png +0 -0
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +21 -21
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +22 -22
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +22 -22
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +21 -21
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +21 -21
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +22 -22
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +22 -22
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +22 -22
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +22 -22
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +21 -21
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +22 -22
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +22 -22
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +22 -22
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +22 -22
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +22 -22
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +21 -21
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +22 -22
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +22 -22
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +22 -22
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +22 -22
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +22 -22
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +22 -22
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +22 -22
- package/bin/elanous.cjs +107 -0
- package/graphs/field/field-feed.yaml +24 -0
- package/graphs/field/recipes.yaml +12 -0
- package/graphs/landing/landing-heal.yaml +3 -1
- package/graphs/landing/recipes.yaml +3 -0
- package/graphs/release/recipes.yaml +13 -0
- package/graphs/release/release-loop.yaml +13 -4
- package/package.json +5 -5
- package/plugins/card-followup/README.md +42 -0
- package/plugins/card-followup/examples/card-sample.json +7 -0
- package/plugins/card-followup/graphs/card-followup.yaml +18 -0
- package/plugins/card-followup/graphs/recipes.yaml +16 -0
- package/plugins/card-followup/graphs/run-step.ts +276 -0
- package/plugins/card-followup/graphs/sales-strategy.ts +173 -0
- package/plugins/card-followup/plugin.json +25 -0
- package/plugins/doc-draft/README.md +13 -0
- package/plugins/doc-draft/examples/brief-sample.json +8 -0
- package/plugins/doc-draft/graphs/doc-draft.yaml +14 -0
- package/plugins/doc-draft/graphs/recipes.yaml +9 -0
- package/plugins/doc-draft/graphs/run-step.ts +113 -0
- package/plugins/doc-draft/plugin.json +24 -0
- package/plugins/geo-check/README.md +15 -0
- package/plugins/geo-check/examples/brand-sample.json +8 -0
- package/plugins/geo-check/graphs/geo-check.yaml +20 -0
- package/plugins/geo-check/graphs/recipes.yaml +18 -0
- package/plugins/geo-check/graphs/run-step.ts +198 -0
- package/plugins/geo-check/plugin.json +24 -0
- package/plugins/job-coach/.codex-plugin/plugin.json +1 -1
- package/plugins/job-coach/README.md +31 -1
- package/plugins/job-coach/graphs/run-step.ts +2 -2
- package/plugins/job-coach/plugin.json +1 -1
- package/plugins/job-coach/plugin.ts +1 -1
- package/scripts/affinity/endcard.sh +3 -2
- package/scripts/affinity/reframe.sh +3 -1
- package/scripts/ci-public-export-leak-gate.ts +48 -0
- package/scripts/codex-quota-alert.ts +122 -258
- package/scripts/coord-tracks.json +4 -4
- package/scripts/daemon-port-baseline.txt +1 -2
- package/scripts/field-feed/deliver-node.ts +52 -0
- package/scripts/field-feed/feed-node.ts +67 -0
- package/scripts/field-feed/lib.ts +162 -0
- package/scripts/field-feed/reel-node.ts +24 -0
- package/scripts/field-feed/start.ts +62 -0
- package/scripts/fixtures/vflow-sample.ndjson +6 -0
- package/scripts/install.sh +92 -13
- package/scripts/landing-heal/clean-checkout.ts +114 -0
- package/scripts/landing-heal/run-mission.ts +38 -3
- package/scripts/lib/android-test-device.ts +32 -0
- package/scripts/lib/pwa-scenarios/scenarios.ts +2 -1
- package/scripts/public-export.ts +8 -4
- package/scripts/release-loop/baseline-from-logs.ts +92 -0
- package/scripts/release-loop/checklist-gate-node.ts +35 -0
- package/scripts/release-loop/env-known-failures.ts +31 -0
- package/scripts/release-loop/gate-diff.ts +29 -0
- package/scripts/release-loop/gate-node.ts +124 -29
- package/scripts/release-loop/known-issues-node.ts +159 -0
- package/scripts/release-loop/npm-publish-node.ts +118 -0
- package/scripts/release-loop/ops-upgrade-node.ts +87 -0
- package/scripts/release-loop/release-readiness.ts +58 -0
- package/scripts/release-loop/shard-plan.ts +47 -0
- package/scripts/release-loop/unattended-release.ts +93 -0
- package/scripts/ux/measure-first-text.ts +102 -0
- package/scripts/ux/record-page.ts +4 -1
- package/scripts/vflow-collect.ts +20 -7
- package/scripts/vflow-search.ts +73 -0
- package/scripts/video-free-line-test-walker.ts +36 -0
- package/scripts/video-pipeline.ts +2 -2
- package/skills/explainer-video/SKILL.md +24 -3
- package/skills/explainer-video/engine/brand/elanous-mark-on-dark.svg +1 -1
- package/skills/explainer-video/engine/build.mjs +2 -2
- package/skills/explainer-video/engine/reel.mjs +235 -0
- package/skills/explainer-video/engine/reel.sh +9 -0
- package/skills/explainer-video/engine/vo.ts +58 -16
- package/skills/explainer-video/examples/elanous-0.2.5/script.json +1 -1
- package/skills/grill-me/SKILL.md +1 -1
- package/skills/omni-digest/docs/diagrams/noncode-terminal-promotion-path-2026-08-13.mmd +1 -1
- package/src/a2a/server.ts +2 -0
- package/src/acp/elanous-aware-peers.ts +30 -0
- package/src/acp/server.ts +91 -15
- package/src/agent/monad-agent-turn.ts +25 -6
- package/src/agent/shared-app-tools.ts +5 -3
- package/src/agent-substrate/review-intent.ts +6 -2
- package/src/agent-substrate/shard-boundary-intent.ts +70 -0
- package/src/auth/github-app-token.ts +4 -1
- package/src/autopilot/mission-origin.ts +20 -4
- package/src/autopilot/mission-phase-track.ts +11 -1
- package/src/autopilot/track-agent-shadow.ts +151 -0
- package/src/autopilot/track-agent.ts +26 -6
- package/src/autopilot/track-courier.ts +2 -2
- package/src/boot/acp-server-help.ts +16 -0
- package/src/boot/daemon-prompt-turn.ts +31 -10
- package/src/boot/daemon-tools/index.ts +7 -1
- package/src/budget/codex-credit-pace.ts +84 -0
- package/src/budget/codex-credit-plan.ts +154 -0
- package/src/budget/codex-reset-credit-state.ts +2 -0
- package/src/chat/chat-cli.ts +4 -1
- package/src/chat/short-question.ts +40 -0
- package/src/cli/agent-cli.ts +24 -15
- package/src/cli/ask-bare.ts +42 -0
- package/src/cli/cli-user-error.ts +25 -0
- package/src/cli/codex-credit-plan-view.ts +25 -0
- package/src/cli/dependence-gauge.ts +237 -0
- package/src/cli/doctor-cli.ts +5 -0
- package/src/cli/doctor-modes.ts +48 -0
- package/src/cli/intake-cli.ts +12 -2
- package/src/cli/intake-source-cli.ts +58 -0
- package/src/cli/machine-cli.ts +23 -0
- package/src/cli/msg-cli.ts +137 -0
- package/src/cli/nexus-ios-bind.ts +25 -8
- package/src/cli/phone-link.ts +117 -0
- package/src/cli/plugin-cli.ts +52 -0
- package/src/cli/pr-cli.ts +36 -0
- package/src/cli/private-ledger-commands.ts +31 -0
- package/src/cli/provider-cli.ts +5 -0
- package/src/cli/release-cli.ts +56 -12
- package/src/cli/release-run-if-ready.ts +48 -0
- package/src/cli/setup-cli.ts +27 -6
- package/src/cli/setup-recommend.ts +8 -0
- package/src/cli/usage-cli.ts +11 -1
- package/src/codex/setup.ts +27 -8
- package/src/dashboard/index.ts +1 -1
- package/src/discord.ts +56 -6
- package/src/domains/core-tools.ts +37 -14
- package/src/domains/outbound-alert.ts +55 -16
- package/src/exec-requests/planner.ts +140 -0
- package/src/exec-requests/runner.ts +338 -0
- package/src/exec-requests/store.ts +76 -0
- package/src/field/field-media.ts +250 -0
- package/src/field/field-reel.ts +196 -0
- package/src/grok/credential.ts +7 -5
- package/src/harness/aside-run-status.ts +33 -0
- package/src/harness/browser-verify.ts +48 -17
- package/src/harness/harness-cli-command.ts +25 -3
- package/src/harness/image-load-state.ts +21 -0
- package/src/harness/process-garbage.ts +45 -0
- package/src/index.ts +104 -38
- package/src/instance/leader.ts +13 -1
- package/src/intake-plane/collect-github.ts +5 -3
- package/src/intake-plane/collect-telegram-saved.ts +15 -3
- package/src/intake-plane/discord-seat-work.ts +56 -0
- package/src/intake-plane/intake-sources.ts +202 -0
- package/src/intake-plane/items.ts +28 -12
- package/src/intake-plane/seat-doc-route.ts +207 -0
- package/src/intake-plane/submit-intake-work.ts +9 -1
- package/src/intake-plane/telegram-seat-work.ts +72 -0
- package/src/llm/model-defaults.ts +24 -1
- package/src/llm.ts +5 -4
- package/src/machines/machine-ledger.ts +127 -0
- package/src/market/pack-internal-refs.ts +28 -0
- package/src/market/publish.ts +31 -4
- package/src/maturity/feature-maturity.ts +65 -0
- package/src/model-tier/turn-tier.ts +24 -0
- package/src/msg/msg-store.ts +149 -0
- package/src/native-tool-catalog.ts +2 -1
- package/src/nexus/api/chat-fast-path-config.ts +28 -0
- package/src/nexus/api/consult-requests.ts +117 -0
- package/src/nexus/api/discord-trigger-bot.ts +3 -0
- package/src/nexus/api/exec-requests.ts +51 -0
- package/src/nexus/api/field-uploads.ts +180 -0
- package/src/nexus/api/graph-approvals-feed.ts +140 -0
- package/src/nexus/api/graph-approvals.ts +120 -4
- package/src/nexus/api/harness-api.ts +219 -5
- package/src/nexus/api/http-server.ts +68 -5
- package/src/nexus/api/seat-requests.ts +254 -0
- package/src/nexus/api/trace-run-scan.ts +37 -0
- package/src/nexus/api/trace.ts +19 -3
- package/src/nexus/index.ts +3 -0
- package/src/nexus/setup-status.ts +4 -5
- package/src/notify/llm-budget-alert.ts +125 -0
- package/src/oauth/codex-account-rotation.ts +26 -4
- package/src/oauth/codex-account-store.ts +20 -2
- package/src/oauth/codex-browser-login.ts +173 -0
- package/src/oauth/codex.ts +4 -1
- package/src/oauth/fallback-chain.ts +5 -5
- package/src/oauth/store.ts +42 -5
- package/src/onboarding/continue-here.ts +29 -0
- package/src/onboarding/entry-hints.ts +8 -0
- package/src/onboarding/full-screen-io.ts +35 -3
- package/src/onboarding/help/skills.md +1 -1
- package/src/onboarding/non-interactive.ts +1 -0
- package/src/onboarding/subscription-hint.ts +27 -0
- package/src/onboarding/web-first.ts +83 -0
- package/src/onboarding.ts +230 -23
- package/src/plugins/maker/node-install.ts +92 -0
- package/src/plugins/maker/node-maker.ts +139 -0
- package/src/plugins/maker/plugin-maker.ts +220 -0
- package/src/policy/llm-policy.ts +268 -0
- package/src/prompt-library/session-guidance.ts +7 -1
- package/src/prompt-library/universal-preamble.ts +2 -2
- package/src/relay/channel.ts +184 -0
- package/src/relay/mux.ts +182 -0
- package/src/relay/relay-cli.ts +20 -0
- package/src/relay/relay-server.ts +220 -0
- package/src/relay/test-vectors.json +29 -0
- package/src/release-loop/checklist.ts +36 -9
- package/src/seat-address/seat-address.ts +34 -0
- package/src/self-dev/decompose.ts +1 -0
- package/src/self-dev/dev-pipeline.ts +6 -3
- package/src/self-dev/run-record-mirror.ts +44 -0
- package/src/self-dev/run-store.ts +12 -3
- package/src/self-implement/harness-policy-gates.ts +4 -1
- package/src/self-implement/report-origin.ts +12 -1
- package/src/self-implement/run-ledger-compact.ts +1 -1
- package/src/session/chat.ts +17 -5
- package/src/session/index.ts +23 -10
- package/src/skills/shared-agent-skill-roots.ts +25 -0
- package/src/steward/failure-streak.ts +78 -0
- package/src/steward/steward-cards.ts +24 -0
- package/src/steward/triage-budget.ts +127 -0
- package/src/steward/triage-plan.ts +75 -0
- package/src/steward/triage.ts +244 -42
- package/src/task-cards/card-store.ts +1 -1
- package/src/task-orchestrator/surfaces/pod-bun-cache.ts +18 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +12 -3
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
- package/src/task-orchestrator/surfaces/pod-ledger-events.ts +68 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +78 -4
- package/src/telegram.ts +145 -2
- package/src/tool-runtime/elanous-skills-list-runtime.ts +3 -3
- package/src/tool-runtime/self-cognition-runtimes.ts +160 -2
- package/src/user-config.ts +84 -4
- package/src/version/packed-revision.json +1 -1
- package/src/video-pipeline/recipes/character.ts +42 -5
- package/src/video-pipeline/recipes/explainer.ts +213 -0
- package/src/video-pipeline/recipes/ffmpeg.ts +1 -1
- package/src/video-pipeline/recipes/free-line.ts +16 -3
- package/src/video-pipeline/recipes/remote.ts +7 -1
- package/src/video-pipeline/vflow-reference.ts +109 -0
- package/src/video-pipeline/walk-line.ts +3 -2
- package/apps/pwa/out/_next/static/chunks/06ef2eba-d1b99ac6d2ea663c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1413-7179a49332db5a80.js +0 -1
- package/apps/pwa/out/_next/static/chunks/150-25c277137a9638ab.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1640-bc99ec23bbe29d12.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2300-de059d0ab2a16630.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2684-05b0a81e75298c16.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3026-2ed7e317d9b02102.js +0 -25
- package/apps/pwa/out/_next/static/chunks/3611-77496ada38cd6358.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3717-68957c4dc9ceec90.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3929-46ba9af6f9d895cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4015-62c5a3744698a8da.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4217-3c4317a7905f9eeb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4240-3230f230fae2d7d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4271-e53e075ebd48593b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4377.3b125fef79576dfd.js +0 -5
- package/apps/pwa/out/_next/static/chunks/4853-35fdbe6bdbcdc0fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5007-6a3b30e23ff68a6e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5244-12b424c42070f5ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5611-df00af3e4e4a61bb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5890-dca2321773be829a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6123-37c9dc57ddaa7ab0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6588-b7975304a302ab91.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6604-c03961962b6273b6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7109-cf8c87e968e83a65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7178-30f77877525a2efb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7262-871563fc9663730b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7294-f0eeebb92aa93459.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7429-881af44deed6958f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7527.943753b6ed829f09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/764-a01e858525a190c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7868-e35d5f7985ecd3f1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8320-51484af7b01fdfa5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9131-52f5fad51cceca04.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9238.2cd5f47f4bf7a2c3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-70c404c7bb6d7f0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-3ffd80166b1050aa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-4bcc63e272ae8205.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-880734bd07d62882.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-72bd384898d88955.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-1018bbc9586ecae5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-de86f8c38076dd7b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-70c404c7bb6d7f0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-cd6447196d8bafe6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-e733630c47fa102b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-d1c1a8357b161875.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-60c514128e81d787.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8b637b7e325e13c1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-63301d11dbe73836.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-a8cf0dc472060db2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-02fba54881ccfa49.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-d5e8e90f0038f8de.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-b1ef6824f494466a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-d589bb0f233b1009.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c9bbaebef4cda059.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-3639ce7916b92fed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7450ae7478955653.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-70c404c7bb6d7f0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-b41ca4abfd618452.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-b333dd79ebee3fa9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-8be1227492793655.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-6e03e9371b552e0d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-911411e9fca18d03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-792170e649b37088.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-c40463466937bc91.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-509c798b80384536.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-1cac2aa21d77c417.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-9626799d9b4a2cd1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-84cd419494e16b0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-361487d2da603917.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-607f5beb94cb2e07.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-d0222d219e29dddf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-a26980a31126decc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-edb2b37decf23e4d.js +0 -1
- package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +0 -1
- package/apps/pwa/out/_next/static/ktMqfrBbf8BMGac0jqLrb/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{ktMqfrBbf8BMGac0jqLrb → keajW7DDyghEs1y8oJe6T}/_ssgManifest.js +0 -0
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
} from './codex-account.js';
|
|
14
14
|
import { authStorePath, defaultCodexHome, listProviders, loadTokens, saveTokens } from './store.js';
|
|
15
15
|
import { readFreshAvailabilityState, quotaSignalDir, readQuotaSignal, readQuotaSignalCredits, readQuotaSignalUsedPercent, readQuotaSignalObservedAt, readQuotaSignalObservedAtRaw } from '../budget/codex-reset-credit-state.js';
|
|
16
|
+
import { creditPaceStatus } from '../budget/codex-credit-pace.js';
|
|
17
|
+
import { loadLlmPolicy } from '../policy/llm-policy.js';
|
|
16
18
|
import { getUserConfig } from '../user-config.js';
|
|
17
19
|
import {
|
|
18
20
|
decideCodexRotation, observeRotation, applyRotation, readCodexAccountRotationConfig, rotatedChildEnv,
|
|
@@ -278,6 +280,13 @@ function buildRotationCandidates(path: string, now: number, env: NodeJS.ProcessE
|
|
|
278
280
|
.filter((c): c is RotationCandidate => c !== null);
|
|
279
281
|
}
|
|
280
282
|
|
|
283
|
+
function rotationCreditPace(candidates: readonly RotationCandidate[], now: number) {
|
|
284
|
+
const policy = loadLlmPolicy({ now: new Date(now) }).policy;
|
|
285
|
+
const balances = Object.fromEntries(candidates.filter((c) => c.creditBalance !== undefined)
|
|
286
|
+
.map((c) => [c.name, c.creditBalance!]));
|
|
287
|
+
return creditPaceStatus({ policy, balances, now: new Date(now) });
|
|
288
|
+
}
|
|
289
|
+
|
|
281
290
|
function formatUsedPercent(value: number | undefined): string {
|
|
282
291
|
return value === undefined ? 'unknown' : `${value}%`;
|
|
283
292
|
}
|
|
@@ -517,7 +526,7 @@ export function inspectCodexRotation(
|
|
|
517
526
|
const rawThreshold = rotationThresholdFromConfig();
|
|
518
527
|
const thresholdPercentByAccount = rotationThresholdsByAccountFromConfig();
|
|
519
528
|
const decision = decideCodexRotation({
|
|
520
|
-
current, explicit, enabled, disabledProvenance: rotationConfig.state, currentReached, currentUsedPercent, accountOrder: accountOrderFromConfig(), resetCreditAvailability: resetCreditAvailability(currentHome, now), thresholdPercent: rawThreshold, thresholdPercentByAccount, creditsAllowed: codexCreditsAllowedFromConfig(), ...currentCredits, candidates,
|
|
529
|
+
current, explicit, enabled, disabledProvenance: rotationConfig.state, currentReached, currentUsedPercent, accountOrder: accountOrderFromConfig(), resetCreditAvailability: resetCreditAvailability(currentHome, now), thresholdPercent: rawThreshold, thresholdPercentByAccount, creditsAllowed: codexCreditsAllowedFromConfig(), ...currentCredits, candidates, creditPace: rotationCreditPace(candidates, now),
|
|
521
530
|
});
|
|
522
531
|
// ⛔⭐ 나이는 «만료돼도» 낸다 — 「65분 전」과 「3일 전」은 다른 진단이다(리뷰 must-fix).
|
|
523
532
|
const observedAtByHome: Record<string, number> = {};
|
|
@@ -603,6 +612,7 @@ export function resolveCodexAccountForRun(
|
|
|
603
612
|
const candidates = buildRotationCandidates(path, now, env);
|
|
604
613
|
const currentSignalAttributable = signalAttributableToAccount(currentHomeInfo);
|
|
605
614
|
|
|
615
|
+
const creditPace = rotationCreditPace(candidates, now);
|
|
606
616
|
const decision = decideCodexRotation({
|
|
607
617
|
current,
|
|
608
618
|
explicit,
|
|
@@ -617,6 +627,13 @@ export function resolveCodexAccountForRun(
|
|
|
617
627
|
creditsAllowed: codexCreditsAllowedFromConfig(),
|
|
618
628
|
...currentCreditsInput(currentSignalAttributable ? currentHome : undefined, now),
|
|
619
629
|
candidates,
|
|
630
|
+
creditPace,
|
|
631
|
+
});
|
|
632
|
+
debug.log('codex.rotation', 'credit-pace', {
|
|
633
|
+
active: creditPace.active,
|
|
634
|
+
todaySpent: creditPace.todaySpent,
|
|
635
|
+
target: creditPace.target,
|
|
636
|
+
to: decision.reason === 'credit-pace' ? decision.to?.name ?? current.name : decision.to?.name,
|
|
620
637
|
});
|
|
621
638
|
observeRotation(decision, current.name);
|
|
622
639
|
const resolved = applyRotation(current, decision);
|
|
@@ -726,7 +743,7 @@ export function resolveRunFallback(
|
|
|
726
743
|
try {
|
|
727
744
|
const snapshot = inspectCodexRotation(env, deps.storePath ? { storePath: deps.storePath } : {});
|
|
728
745
|
let rotation: RotationOutcome;
|
|
729
|
-
if ((snapshot.reason === 'rotated' || snapshot.reason === 'reset-credit-unknown') && snapshot.to) {
|
|
746
|
+
if ((snapshot.reason === 'rotated' || snapshot.reason === 'reset-credit-unknown' || snapshot.reason === 'credit-pace') && snapshot.to) {
|
|
730
747
|
// ⛔ 스냅샷은 이름만 준다 — 후보 목록에서 «그 객체»를 되찾는다(홈까지 필요하다).
|
|
731
748
|
const to = snapshot.candidates.find((c) => c.name === snapshot.to);
|
|
732
749
|
rotation = to ? { reason: snapshot.reason, to } : { reason: 'no-candidate' };
|
|
@@ -734,6 +751,7 @@ export function resolveRunFallback(
|
|
|
734
751
|
snapshot.reason === 'explicit'
|
|
735
752
|
|| snapshot.reason === 'disabled'
|
|
736
753
|
|| snapshot.reason === 'not-reached'
|
|
754
|
+
|| snapshot.reason === 'credit-pace'
|
|
737
755
|
|| snapshot.reason === 'reset-credit-available'
|
|
738
756
|
|| snapshot.reason === 'no-candidate'
|
|
739
757
|
) {
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { createServer, type Server } from 'node:http';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { debug } from '../debug/log.js';
|
|
5
|
+
import { CODEX_OAUTH_CLIENT_ID, CODEX_OAUTH_TOKEN_URL } from './codex.js';
|
|
6
|
+
import { expiresAtFromSeconds, saveTokens, type ProviderAuthState } from './store.js';
|
|
7
|
+
|
|
8
|
+
const AUTHORIZE_URL = 'https://auth.openai.com/oauth/authorize';
|
|
9
|
+
const DEFAULT_PORT = 1455;
|
|
10
|
+
const TIMEOUT_MS = 5 * 60 * 1000;
|
|
11
|
+
|
|
12
|
+
export interface CodexBrowserLoginOpts {
|
|
13
|
+
fetchImpl?: typeof fetch;
|
|
14
|
+
openBrowser?: (url: string) => boolean | Promise<boolean>;
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
preferredPort?: number;
|
|
17
|
+
mirrorCodex?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function browserLaunchCommand(url: string, platform: NodeJS.Platform = process.platform): { command: string; args: string[] } {
|
|
21
|
+
if (platform === 'win32') {
|
|
22
|
+
// Use the Windows URL handler directly; cmd.exe expands & and % even inside OAuth URLs.
|
|
23
|
+
return { command: 'rundll32.exe', args: ['url.dll,FileProtocolHandler', url] };
|
|
24
|
+
}
|
|
25
|
+
return { command: platform === 'darwin' ? 'open' : 'xdg-open', args: [url] };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A launcher that exits non-zero this soon has failed; one still running (or exiting 0) handed the URL off. */
|
|
29
|
+
const LAUNCH_GRACE_MS = 1_000;
|
|
30
|
+
|
|
31
|
+
function openBrowser(url: string): Promise<boolean> {
|
|
32
|
+
const { command, args } = browserLaunchCommand(url);
|
|
33
|
+
return new Promise(resolve => {
|
|
34
|
+
let settled = false;
|
|
35
|
+
const settle = (ok: boolean) => { if (!settled) { settled = true; resolve(ok); } };
|
|
36
|
+
try {
|
|
37
|
+
const child = spawn(command, args, { stdio: 'ignore', detached: true });
|
|
38
|
+
child.once('error', () => settle(false));
|
|
39
|
+
child.once('exit', code => settle(code === 0));
|
|
40
|
+
child.once('spawn', () => {
|
|
41
|
+
const grace = setTimeout(() => { child.unref(); settle(true); }, LAUNCH_GRACE_MS);
|
|
42
|
+
grace.unref?.();
|
|
43
|
+
child.once('exit', () => clearTimeout(grace));
|
|
44
|
+
});
|
|
45
|
+
} catch { settle(false); }
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function listen(server: Server, port: number): Promise<void> {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
server.once('error', reject);
|
|
52
|
+
server.listen(port, '127.0.0.1', () => {
|
|
53
|
+
server.off('error', reject);
|
|
54
|
+
resolve();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Local OAuth callback, independent of a bundled or PATH Codex executable. */
|
|
60
|
+
export async function loginWithCodexBrowser(opts: CodexBrowserLoginOpts = {}): Promise<ProviderAuthState> {
|
|
61
|
+
const verifier = randomBytes(32).toString('base64url');
|
|
62
|
+
const challenge = createHash('sha256').update(verifier).digest('base64url');
|
|
63
|
+
const state = randomBytes(32).toString('base64url');
|
|
64
|
+
let port = opts.preferredPort ?? DEFAULT_PORT;
|
|
65
|
+
let resolveCallback!: (value: ProviderAuthState) => void;
|
|
66
|
+
let rejectCallback!: (reason: Error) => void;
|
|
67
|
+
const callback = new Promise<ProviderAuthState>((resolve, reject) => {
|
|
68
|
+
resolveCallback = resolve;
|
|
69
|
+
rejectCallback = reject;
|
|
70
|
+
});
|
|
71
|
+
void callback.catch(() => {});
|
|
72
|
+
let settled = false;
|
|
73
|
+
let handling = false;
|
|
74
|
+
const tokenAbort = new AbortController();
|
|
75
|
+
const server = createServer((req, res) => {
|
|
76
|
+
const url = new URL(req.url ?? '/', `http://localhost:${port}`);
|
|
77
|
+
if (req.method !== 'GET' || url.pathname !== '/auth/callback') {
|
|
78
|
+
res.writeHead(404).end();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
debug.log('oauth.codex-browser', 'callback', { port });
|
|
82
|
+
if (handling || settled) { res.writeHead(409).end(); return; }
|
|
83
|
+
if (url.searchParams.get('state') !== state) {
|
|
84
|
+
res.writeHead(400).end('Invalid login state.');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const code = url.searchParams.get('code');
|
|
88
|
+
if (!code || url.searchParams.has('error')) {
|
|
89
|
+
res.writeHead(400).end('Login failed. Return to the terminal.');
|
|
90
|
+
rejectCallback(new Error('authorization-denied'));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
handling = true;
|
|
94
|
+
void (async () => {
|
|
95
|
+
try {
|
|
96
|
+
const response = await (opts.fetchImpl ?? fetch)(CODEX_OAUTH_TOKEN_URL, {
|
|
97
|
+
method: 'POST',
|
|
98
|
+
signal: tokenAbort.signal,
|
|
99
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
|
|
100
|
+
body: new URLSearchParams({
|
|
101
|
+
grant_type: 'authorization_code', code, redirect_uri: `http://localhost:${port}/auth/callback`,
|
|
102
|
+
client_id: CODEX_OAUTH_CLIENT_ID, code_verifier: verifier,
|
|
103
|
+
}).toString(),
|
|
104
|
+
});
|
|
105
|
+
if (!response.ok) throw new Error('token-exchange');
|
|
106
|
+
const token = await response.json() as {
|
|
107
|
+
access_token?: string; refresh_token?: string; id_token?: string;
|
|
108
|
+
expires_in?: number; scope?: string; token_type?: string;
|
|
109
|
+
};
|
|
110
|
+
if (!token.access_token || !token.refresh_token) throw new Error('token-exchange');
|
|
111
|
+
if (settled) { res.writeHead(410).end(); return; }
|
|
112
|
+
const saved = saveTokens('openai-codex', {
|
|
113
|
+
accessToken: token.access_token,
|
|
114
|
+
refreshToken: token.refresh_token,
|
|
115
|
+
...(token.id_token ? { idToken: token.id_token } : {}),
|
|
116
|
+
expiresAt: expiresAtFromSeconds(token.expires_in),
|
|
117
|
+
scope: token.scope,
|
|
118
|
+
tokenType: token.token_type ?? 'Bearer',
|
|
119
|
+
}, { authMode: 'chatgpt', mirrorCodex: opts.mirrorCodex });
|
|
120
|
+
debug.log('oauth.codex-browser', 'exchanged', { port });
|
|
121
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
|
|
122
|
+
.end('<!doctype html><html lang="ko"><meta charset="utf-8"><body>로그인됨 — 터미널로 돌아가세요</body></html>');
|
|
123
|
+
resolveCallback(saved);
|
|
124
|
+
} catch {
|
|
125
|
+
res.writeHead(400).end('Login failed. Return to the terminal.');
|
|
126
|
+
rejectCallback(new Error('token-exchange'));
|
|
127
|
+
}
|
|
128
|
+
})();
|
|
129
|
+
});
|
|
130
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
131
|
+
const cancel = () => rejectCallback(new Error('cancelled'));
|
|
132
|
+
const onExit = () => server.close();
|
|
133
|
+
const onServerError = () => rejectCallback(new Error('port-unavailable'));
|
|
134
|
+
let listening = false;
|
|
135
|
+
try {
|
|
136
|
+
try { await listen(server, port); }
|
|
137
|
+
catch (err) {
|
|
138
|
+
if ((err as NodeJS.ErrnoException).code !== 'EADDRINUSE' || port === 0) throw err;
|
|
139
|
+
await listen(server, 0);
|
|
140
|
+
}
|
|
141
|
+
const address = server.address();
|
|
142
|
+
if (!address || typeof address === 'string') throw new Error('port-unavailable');
|
|
143
|
+
listening = true;
|
|
144
|
+
port = address.port;
|
|
145
|
+
server.on('error', onServerError);
|
|
146
|
+
process.once('SIGINT', cancel);
|
|
147
|
+
process.once('exit', onExit);
|
|
148
|
+
timer = setTimeout(() => rejectCallback(new Error('timeout')), opts.timeoutMs ?? TIMEOUT_MS);
|
|
149
|
+
const authorize = new URL(AUTHORIZE_URL);
|
|
150
|
+
for (const [key, value] of Object.entries({
|
|
151
|
+
client_id: CODEX_OAUTH_CLIENT_ID, redirect_uri: `http://localhost:${port}/auth/callback`,
|
|
152
|
+
response_type: 'code', code_challenge: challenge, code_challenge_method: 'S256', state,
|
|
153
|
+
scope: 'openid profile email offline_access', id_token_add_organizations: 'true',
|
|
154
|
+
codex_cli_simplified_flow: 'true', originator: 'codex_cli_rs',
|
|
155
|
+
})) authorize.searchParams.set(key, value);
|
|
156
|
+
debug.log('oauth.codex-browser', 'started', { port });
|
|
157
|
+
if (!await (opts.openBrowser ?? openBrowser)(authorize.toString())) throw new Error('browser-unavailable');
|
|
158
|
+
return await callback;
|
|
159
|
+
} catch (err) {
|
|
160
|
+
const reason = err instanceof Error && ['timeout', 'cancelled', 'browser-unavailable', 'authorization-denied', 'token-exchange'].includes(err.message)
|
|
161
|
+
? err.message : 'port-unavailable';
|
|
162
|
+
debug.log('oauth.codex-browser', 'failed', { port, reason });
|
|
163
|
+
throw new Error(`codex browser login failed: ${reason}`);
|
|
164
|
+
} finally {
|
|
165
|
+
settled = true;
|
|
166
|
+
tokenAbort.abort();
|
|
167
|
+
if (timer) clearTimeout(timer);
|
|
168
|
+
process.off('SIGINT', cancel);
|
|
169
|
+
process.off('exit', onExit);
|
|
170
|
+
if (listening) server.close();
|
|
171
|
+
server.off('error', onServerError);
|
|
172
|
+
}
|
|
173
|
+
}
|
package/src/oauth/codex.ts
CHANGED
|
@@ -121,6 +121,8 @@ interface UserCodeResponse {
|
|
|
121
121
|
device_auth_id: string;
|
|
122
122
|
interval?: number;
|
|
123
123
|
expires_in?: number;
|
|
124
|
+
verification_uri_complete?: string;
|
|
125
|
+
verification_uri?: string;
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
interface PollSuccessResponse {
|
|
@@ -278,7 +280,8 @@ export async function loginWithCodex(opts: CodexLoginOpts = {}): Promise<Provide
|
|
|
278
280
|
const userCode = await requestDeviceCode({ fetchImpl });
|
|
279
281
|
const serverInterval = typeof userCode.interval === 'number' ? userCode.interval * 1000 : 0;
|
|
280
282
|
const pollInterval = Math.max(reportedInterval, serverInterval || 0);
|
|
281
|
-
onProgress({ type: 'user_code', userCode: userCode.user_code,
|
|
283
|
+
onProgress({ type: 'user_code', userCode: userCode.user_code,
|
|
284
|
+
loginUrl: userCode.verification_uri_complete || userCode.verification_uri || CODEX_DEVICE_LOGIN_URL });
|
|
282
285
|
|
|
283
286
|
// 2) Poll until ready or timeout.
|
|
284
287
|
const startedAt = Date.now();
|
|
@@ -69,8 +69,8 @@ export function normalizeFallbackChain(
|
|
|
69
69
|
/** 회전 판정이 낸 이유 — `codex-account-rotation.ts` 의 `RotationReason` 과 같은 어휘.
|
|
70
70
|
* ⛔ 그쪽 타입이 비공개라 구조적으로 받는다(그 파일의 리뷰 규율: dead export 금지). */
|
|
71
71
|
export type RotationOutcome =
|
|
72
|
-
| { readonly reason: 'rotated' | 'reset-credit-unknown'; readonly to: RotationCandidate }
|
|
73
|
-
| { readonly reason: 'explicit' | 'disabled' | 'not-reached' | 'reset-credit-available' | 'no-candidate' };
|
|
72
|
+
| { readonly reason: 'rotated' | 'reset-credit-unknown' | 'credit-pace'; readonly to: RotationCandidate }
|
|
73
|
+
| { readonly reason: 'explicit' | 'disabled' | 'not-reached' | 'credit-pace' | 'reset-credit-available' | 'no-candidate' };
|
|
74
74
|
|
|
75
75
|
export type FallbackDecision =
|
|
76
76
|
/** codex 계정을 갈아탄다(종전 동작). */
|
|
@@ -156,14 +156,14 @@ export function decideFallback(input: FallbackInput): FallbackDecision {
|
|
|
156
156
|
return stepAfter(chain, currentStep, grokAvailable, grokQuota, 'chain-exhausted');
|
|
157
157
|
}
|
|
158
158
|
if (rotation.reason === 'explicit') return { action: 'stay', why: 'explicit' };
|
|
159
|
-
if (rotation.reason === 'not-reached') {
|
|
159
|
+
if (rotation.reason === 'not-reached' || (rotation.reason === 'credit-pace' && !('to' in rotation))) {
|
|
160
160
|
return { action: 'stay', why: 'not-reached' };
|
|
161
161
|
}
|
|
162
162
|
if (rotation.reason === 'reset-credit-available' && stayOnResetCreditAvailable) {
|
|
163
163
|
return { action: 'stay', why: 'reset-credit-available' };
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
if (rotation.reason === 'rotated' || rotation.reason === 'reset-credit-unknown') {
|
|
166
|
+
if (rotation.reason === 'rotated' || rotation.reason === 'reset-credit-unknown' || (rotation.reason === 'credit-pace' && 'to' in rotation)) {
|
|
167
167
|
// 회전이 답을 냈는데 체인이 codex-rotate 를 «빼» 놨다면 그 뜻을 존중하고
|
|
168
168
|
// 다음 칸으로 간다(회전을 원치 않는 구성).
|
|
169
169
|
if (chain.includes('codex-rotate')) return { action: 'codex-rotate', to: rotation.to };
|
|
@@ -172,7 +172,7 @@ export function decideFallback(input: FallbackInput): FallbackDecision {
|
|
|
172
172
|
|
|
173
173
|
// no-candidate | disabled | reset-credit-available(기본) — codex 축이 끝났다. 다음 칸을 본다.
|
|
174
174
|
// ⛔ 다음 칸이 «없을 때»의 이름만 고른다. 진행(stepAfterCodex)은 그대로다.
|
|
175
|
-
const fallbackWhy = stayReasonWhenCodexAxisEnds(rotation.reason);
|
|
175
|
+
const fallbackWhy = stayReasonWhenCodexAxisEnds(rotation.reason === 'credit-pace' ? 'no-candidate' : rotation.reason);
|
|
176
176
|
return stepAfterCodex(chain, grokAvailable, grokQuota, fallbackWhy);
|
|
177
177
|
}
|
|
178
178
|
|
package/src/oauth/store.ts
CHANGED
|
@@ -51,7 +51,9 @@ export interface OAuthTokens {
|
|
|
51
51
|
tokenType?: string;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
export type CodexMirrorResult = 'written' | 'not-created-missing-cli-fields' | 'write-failed'
|
|
54
|
+
export type CodexMirrorResult = 'written' | 'not-created-missing-cli-fields' | 'write-failed'
|
|
55
|
+
// AUTH1 — ~/.codex is the Codex CLI's file: never mix another account's tokens into it.
|
|
56
|
+
| 'skipped-different-account' | 'skipped-unknown-account';
|
|
55
57
|
|
|
56
58
|
export interface ProviderAuthState {
|
|
57
59
|
tokens: OAuthTokens;
|
|
@@ -245,6 +247,9 @@ export interface WriteTokensOpts {
|
|
|
245
247
|
/** Default true for 'openai-codex'. Set false to skip the ~/.codex
|
|
246
248
|
* mirror (e.g. in tests that shouldn't touch the user's home). */
|
|
247
249
|
mirrorCodex?: boolean;
|
|
250
|
+
/** AUTH1 — when the Codex CLI file belongs to a different account, replace it wholly with this
|
|
251
|
+
* login (id_token · account_id · tokens) instead of leaving it. Only on explicit user consent. */
|
|
252
|
+
replaceDifferentCodexAccount?: boolean;
|
|
248
253
|
/** Explicit ChatGPT claims override. Normally left undefined — for
|
|
249
254
|
* 'openai-codex' with OAuth tokens, saveTokens auto-decodes the
|
|
250
255
|
* access-token JWT so every refresh site picks up fresh claims
|
|
@@ -296,7 +301,10 @@ export function saveTokens(
|
|
|
296
301
|
?? (provider === 'openai-codex' ? defaultCodexHome(process.env) : undefined);
|
|
297
302
|
if (home) {
|
|
298
303
|
// loginWithCodex → saveTokens reaches the Codex CLI mirror on each token save.
|
|
299
|
-
state.codexMirrorResult = mirrorCodexAuth(tokens, state.lastRefresh, join(home, 'auth.json')
|
|
304
|
+
state.codexMirrorResult = mirrorCodexAuth(tokens, state.lastRefresh, join(home, 'auth.json'), {
|
|
305
|
+
knownAccountId: state.chatGPT?.accountId,
|
|
306
|
+
replaceDifferentAccount: opts.replaceDifferentCodexAccount === true,
|
|
307
|
+
});
|
|
300
308
|
}
|
|
301
309
|
// ⛔ 이름 계정인데 홈을 모르면 «미러하지 않는다» — 모르는 곳에 토큰을 쓰지 않는다
|
|
302
310
|
}
|
|
@@ -377,7 +385,12 @@ export function assertMirrorTargetIsSafeUnderTest(mirrorPath: string): void {
|
|
|
377
385
|
* under `oauth.codex-mirror`. ⛔ The 2026-08-05 incident had to be traced by
|
|
378
386
|
* guessing from the file's `last_refresh` FORMAT, which is deduction, not
|
|
379
387
|
* observation. Token values are never logged — path + error kind only. */
|
|
380
|
-
function mirrorCodexAuth(
|
|
388
|
+
function mirrorCodexAuth(
|
|
389
|
+
tokens: OAuthTokens,
|
|
390
|
+
lastRefresh: string,
|
|
391
|
+
mirrorPath: string = codexAuthPath(),
|
|
392
|
+
account: { knownAccountId?: string; replaceDifferentAccount?: boolean } = {},
|
|
393
|
+
): CodexMirrorResult {
|
|
381
394
|
assertMirrorTargetIsSafeUnderTest(mirrorPath);
|
|
382
395
|
try {
|
|
383
396
|
const path = mirrorPath;
|
|
@@ -400,21 +413,45 @@ function mirrorCodexAuth(tokens: OAuthTokens, lastRefresh: string, mirrorPath: s
|
|
|
400
413
|
&& tokens.idToken.length > 0
|
|
401
414
|
&& typeof accountId === 'string'
|
|
402
415
|
&& accountId.length > 0;
|
|
416
|
+
// AUTH1 — the file is the Codex CLI's. Same account: rotate tokens as before. Different account
|
|
417
|
+
// (or ours unknown): leave it untouched unless the user asked to replace it — the old «hasBase»
|
|
418
|
+
// path kept the file's id_token/account_id and wrote our tokens next to them (two accounts mixed).
|
|
419
|
+
// Our account: id_token claim → stored claim → the access token's own claim. Refresh responses
|
|
420
|
+
// carry no id_token, and named rotation accounts keep their CLI homes in sync through this path —
|
|
421
|
+
// so an unknown account keeps the old rotation behaviour; only «both known and different» is refused.
|
|
422
|
+
const ourAccount = (typeof accountId === 'string' && accountId) || account.knownAccountId
|
|
423
|
+
|| extractChatGPTClaims(tokens.accessToken)?.accountId || undefined;
|
|
424
|
+
const baseTokens = (existing.tokens && typeof existing.tokens === 'object') ? existing.tokens as Record<string, unknown> : {};
|
|
425
|
+
const baseAccount = typeof baseTokens.account_id === 'string' && baseTokens.account_id ? baseTokens.account_id : undefined;
|
|
426
|
+
let replaceWholly = false;
|
|
427
|
+
if (hasBase && baseAccount && !ourAccount) debug.log('oauth.codex-mirror', 'account-unknown', { path });
|
|
428
|
+
if (hasBase && baseAccount && ourAccount) {
|
|
429
|
+
if (ourAccount !== baseAccount) {
|
|
430
|
+
if (!account.replaceDifferentAccount || !canCreateUsableMirror) {
|
|
431
|
+
debug.log('oauth.codex-mirror', 'skipped-different-account', { path, replaceRequested: account.replaceDifferentAccount === true });
|
|
432
|
+
return 'skipped-different-account';
|
|
433
|
+
}
|
|
434
|
+
replaceWholly = true;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
403
438
|
if (!hasBase && !canCreateUsableMirror) {
|
|
404
439
|
debug.log('oauth.codex-mirror', 'not-created-missing-cli-fields', { path });
|
|
405
440
|
return 'not-created-missing-cli-fields';
|
|
406
441
|
}
|
|
407
442
|
|
|
408
443
|
mkdirSync(dirname(path), { recursive: true });
|
|
444
|
+
if (replaceWholly) existing = {};
|
|
445
|
+
const fresh = !hasBase || replaceWholly;
|
|
409
446
|
const priorTokens = (existing.tokens && typeof existing.tokens === 'object')
|
|
410
447
|
? existing.tokens as Record<string, unknown> : {};
|
|
411
448
|
existing.tokens = {
|
|
412
449
|
...priorTokens,
|
|
413
|
-
...(
|
|
450
|
+
...(fresh ? { id_token: tokens.idToken, account_id: accountId } : {}),
|
|
414
451
|
access_token: tokens.accessToken,
|
|
415
452
|
refresh_token: tokens.refreshToken,
|
|
416
453
|
};
|
|
417
|
-
if (
|
|
454
|
+
if (fresh) {
|
|
418
455
|
existing.OPENAI_API_KEY = null;
|
|
419
456
|
existing.auth_mode = 'chatgpt';
|
|
420
457
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface ContinueHereOptions {
|
|
2
|
+
daemonRunning: boolean;
|
|
3
|
+
/** Resolved PWA URL for the running daemon, if available. */
|
|
4
|
+
pwaUrl?: string;
|
|
5
|
+
/** Already-issued short-lived phone connection link; never pass its token separately. */
|
|
6
|
+
phoneLink?: string;
|
|
7
|
+
telegramEnabled: boolean;
|
|
8
|
+
/** Non-interactive output must not include links (or QR payloads). */
|
|
9
|
+
interactive: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Render post-setup directions without inspecting the daemon, issuing credentials, or performing IO. */
|
|
13
|
+
export function continueHereLines(opts: ContinueHereOptions): string[] {
|
|
14
|
+
const lines = [' Continue here:'];
|
|
15
|
+
if (!opts.daemonRunning) lines.push(' Start the daemon: `elanous nexus run`');
|
|
16
|
+
if (opts.interactive && opts.daemonRunning && opts.pwaUrl) {
|
|
17
|
+
lines.push(` Browser: ${opts.pwaUrl}`);
|
|
18
|
+
} else {
|
|
19
|
+
lines.push(' Browser: `elanous nexus show`');
|
|
20
|
+
}
|
|
21
|
+
if (opts.interactive && opts.daemonRunning && opts.phoneLink) {
|
|
22
|
+
lines.push(` Phone: ${opts.phoneLink}`);
|
|
23
|
+
lines.push(' ⚠ This link contains a connection token — do not share it or include it in a public screenshot.');
|
|
24
|
+
} else {
|
|
25
|
+
lines.push(' Phone: `elanous phone link --temp --ttl 24h`');
|
|
26
|
+
}
|
|
27
|
+
if (opts.telegramEnabled) lines.push(' Telegram: `elanous telegram`');
|
|
28
|
+
return lines;
|
|
29
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const UNATTENDED_SETUP_COMMAND = 'elanous onboarding --non-interactive --config <answers.json>';
|
|
2
|
+
export const SKILLS_STEP_COMMAND = 'elanous onboarding skills';
|
|
3
|
+
|
|
4
|
+
export function unattendedSetupHint(): string {
|
|
5
|
+
return `unattended: \`${UNATTENDED_SETUP_COMMAND}\``;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const SKILLS_LIST_DESCRIPTION = 'List elanous skills from configured skill directories (skills.activeSet / skills.dirs and packaged skills): subdirectories and the first non-heading line of each SKILL.md. Read-only.';
|
|
@@ -147,6 +147,28 @@ export function fullScreenIO(opts: FullScreenIOOpts = {}): WizardIO {
|
|
|
147
147
|
out.write(composeFullPaint(spec));
|
|
148
148
|
};
|
|
149
149
|
|
|
150
|
+
// OB4(2026-10-01 · 0.2.6 실물): print() 는 «다음 질문 화면»에 묻어 그린다 — 그런데 기기 코드 로그인처럼
|
|
151
|
+
// 질문 없이 오래 기다리는 구간에선 다음 화면이 오지 않아 코드·URL 이 끝내 안 보이고, 원시 모드라 Ctrl-C 도
|
|
152
|
+
// 신호가 안 돼 «멈춤»이 됐다. 질문이 없을 때 print 는 곧바로 메시지 화면을 그리고 Ctrl-C 감시를 붙인다.
|
|
153
|
+
let questionDepth = 0;
|
|
154
|
+
let idleWatcher: ((chunk: string | Buffer) => void) | null = null;
|
|
155
|
+
const detachIdleWatcher = (): void => {
|
|
156
|
+
if (idleWatcher) { inp.off('data', idleWatcher); idleWatcher = null; }
|
|
157
|
+
};
|
|
158
|
+
const attachIdleWatcher = (): void => {
|
|
159
|
+
if (idleWatcher || !inp.isTTY) return;
|
|
160
|
+
idleWatcher = (chunk) => {
|
|
161
|
+
const s = typeof chunk === 'string' ? chunk : chunk.toString('utf8');
|
|
162
|
+
if (s.includes('\x03')) handleSigInt();
|
|
163
|
+
};
|
|
164
|
+
inp.on('data', idleWatcher);
|
|
165
|
+
};
|
|
166
|
+
const inQuestion = async <R>(fn: () => Promise<R>): Promise<R> => {
|
|
167
|
+
detachIdleWatcher();
|
|
168
|
+
questionDepth++;
|
|
169
|
+
try { return await fn(); } finally { questionDepth--; }
|
|
170
|
+
};
|
|
171
|
+
|
|
150
172
|
// ── Raw key reader ──────────────────────────────────────────────
|
|
151
173
|
const readKey = (): Promise<MiniKey> =>
|
|
152
174
|
new Promise((resolve) => {
|
|
@@ -159,7 +181,12 @@ export function fullScreenIO(opts: FullScreenIOOpts = {}): WizardIO {
|
|
|
159
181
|
});
|
|
160
182
|
|
|
161
183
|
// ── choose ─────────────────────────────────────────────────────
|
|
162
|
-
const choose =
|
|
184
|
+
const choose = <T>(
|
|
185
|
+
prompt: string,
|
|
186
|
+
options: ChoiceOption<T>[],
|
|
187
|
+
opts: ChooseOpts = {},
|
|
188
|
+
): Promise<T> => inQuestion(() => chooseInner(prompt, options, opts));
|
|
189
|
+
const chooseInner = async <T>(
|
|
163
190
|
prompt: string,
|
|
164
191
|
options: ChoiceOption<T>[],
|
|
165
192
|
opts: ChooseOpts = {},
|
|
@@ -343,11 +370,11 @@ export function fullScreenIO(opts: FullScreenIOOpts = {}): WizardIO {
|
|
|
343
370
|
|
|
344
371
|
// ── ask ────────────────────────────────────────────────────────
|
|
345
372
|
const ask = async (prompt: string): Promise<string> => {
|
|
346
|
-
return askInternal(prompt, false);
|
|
373
|
+
return inQuestion(() => askInternal(prompt, false));
|
|
347
374
|
};
|
|
348
375
|
|
|
349
376
|
const askSecret = async (prompt: string): Promise<string> => {
|
|
350
|
-
return askInternal(prompt, true);
|
|
377
|
+
return inQuestion(() => askInternal(prompt, true));
|
|
351
378
|
};
|
|
352
379
|
|
|
353
380
|
const askInternal = async (prompt: string, mask: boolean): Promise<string> => {
|
|
@@ -407,6 +434,10 @@ export function fullScreenIO(opts: FullScreenIOOpts = {}): WizardIO {
|
|
|
407
434
|
// box — the wizard frame already contains the same info.
|
|
408
435
|
if (isBannerLine(text)) return;
|
|
409
436
|
messages.push({ text, kind: 'plain' });
|
|
437
|
+
if (questionDepth === 0) {
|
|
438
|
+
paint(buildScreenSpec({ kind: 'message', lines: [] }, 'Ctrl-C cancel'));
|
|
439
|
+
attachIdleWatcher();
|
|
440
|
+
}
|
|
410
441
|
};
|
|
411
442
|
|
|
412
443
|
return {
|
|
@@ -414,6 +445,7 @@ export function fullScreenIO(opts: FullScreenIOOpts = {}): WizardIO {
|
|
|
414
445
|
askSecret,
|
|
415
446
|
print,
|
|
416
447
|
close: () => {
|
|
448
|
+
detachIdleWatcher();
|
|
417
449
|
exitRaw();
|
|
418
450
|
// Final clear so any leftover screen does not interfere with
|
|
419
451
|
// the post-wizard CLI banner / dashboard.
|
|
@@ -128,6 +128,7 @@ export function nonInteractiveIO(opts: NonInteractiveOptions = {}): WizardIO {
|
|
|
128
128
|
section = STEP_SECTIONS[spec.index];
|
|
129
129
|
},
|
|
130
130
|
print,
|
|
131
|
+
nonInteractive: true,
|
|
131
132
|
complete: reportUnusedAnswers,
|
|
132
133
|
close: () => { /* no resources to release */ },
|
|
133
134
|
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Onboarding step 1 — turn detected subscription logins into «Found» lines and a default provider (OB1 · 10-01).
|
|
2
|
+
// A fresh user with a ChatGPT/Codex or Grok login was shown «Auto-detect (env vars)» preselected and nothing found.
|
|
3
|
+
import type { DetectedProvider } from '../llm/provider-detect.js';
|
|
4
|
+
|
|
5
|
+
export interface SubscriptionHint {
|
|
6
|
+
found: Array<{ provider: 'openai-codex' | 'grok'; line: string }>;
|
|
7
|
+
/** Provider to preselect when the user has not chosen one yet. */
|
|
8
|
+
preferred: 'openai-codex' | 'grok';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const SOURCE_LABEL: Record<string, string> = {
|
|
12
|
+
'codex-auth': '~/.codex/auth.json',
|
|
13
|
+
'elanous-auth': 'elanous login',
|
|
14
|
+
'grok-auth': '~/.grok/auth.json',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** Subscriptions first: codex, then grok. With none found, codex is still the default (most users have ChatGPT). */
|
|
18
|
+
export function subscriptionHint(detected: readonly DetectedProvider[]): SubscriptionHint {
|
|
19
|
+
const found: SubscriptionHint['found'] = [];
|
|
20
|
+
for (const provider of ['openai-codex', 'grok'] as const) {
|
|
21
|
+
const hit = detected.find(d => d.provider === provider && d.auth === 'oauth');
|
|
22
|
+
if (!hit) continue;
|
|
23
|
+
const name = provider === 'openai-codex' ? 'ChatGPT / Codex subscription' : 'Grok subscription';
|
|
24
|
+
found.push({ provider, line: ` Found ${name} (${SOURCE_LABEL[hit.source] ?? hit.source})` });
|
|
25
|
+
}
|
|
26
|
+
return { found, preferred: found[0]?.provider ?? 'openai-codex' };
|
|
27
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { isHeadlessEnv } from '../acp/codex-auth.js';
|
|
3
|
+
import { runNexusShow, type NexusShowResult } from '../cli/nexus-show.js';
|
|
4
|
+
import { debug } from '../debug/log.js';
|
|
5
|
+
import type { UserConfig } from '../user-config.js';
|
|
6
|
+
|
|
7
|
+
export interface WebFirstSetupDeps {
|
|
8
|
+
config: Pick<UserConfig, 'onboarding'>;
|
|
9
|
+
isTty?: boolean;
|
|
10
|
+
terminal?: boolean;
|
|
11
|
+
showNexus?: () => Promise<NexusShowResult>;
|
|
12
|
+
issueSetupLinkToken?: () => Promise<string | { token: string }>;
|
|
13
|
+
renderQr?: (link: string) => string | undefined;
|
|
14
|
+
openBrowser?: (link: string) => boolean;
|
|
15
|
+
isHeadless?: () => boolean;
|
|
16
|
+
print?: (line: string) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function setupUrl(pwa: string, token: string): string {
|
|
20
|
+
return `${pwa.replace(/\/app\/?$/, '').replace(/\/$/, '')}/setup#t=${encodeURIComponent(token)}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function runWebFirstSetup(deps: WebFirstSetupDeps): Promise<'link-shown' | 'fallback'> {
|
|
24
|
+
const observe = (event: 'link-shown' | 'fallback', reason?: string, qr = false, browserOpened = false, remote: 'loopback' | 'tailnet' = 'loopback') => {
|
|
25
|
+
try { debug.log('onboarding.web-first', event, { ...(reason ? { reason } : {}), qr, browserOpened, remote }); }
|
|
26
|
+
catch { /* Observability must not block onboarding. */ }
|
|
27
|
+
};
|
|
28
|
+
if (deps.config.onboarding.webFirst !== true) { observe('fallback', 'disabled'); return 'fallback'; }
|
|
29
|
+
if ((deps.isTty ?? (process.stdin.isTTY === true)) !== true) { observe('fallback', 'non-tty'); return 'fallback'; }
|
|
30
|
+
if (deps.terminal) { observe('fallback', 'terminal'); return 'fallback'; }
|
|
31
|
+
|
|
32
|
+
let shown: NexusShowResult;
|
|
33
|
+
try {
|
|
34
|
+
shown = await (deps.showNexus ?? (() => runNexusShow({ format: 'json', out: { log: () => {}, error: () => {} } })))();
|
|
35
|
+
} catch { observe('fallback', 'daemon-unavailable'); return 'fallback'; }
|
|
36
|
+
const running = shown.status === 'unregistered' || (shown.status === 'registered' && shown.instance?.alive === true);
|
|
37
|
+
if (!running || !shown.urls?.pwa.loopback) { observe('fallback', 'daemon-unavailable'); return 'fallback'; }
|
|
38
|
+
|
|
39
|
+
let token: string;
|
|
40
|
+
try {
|
|
41
|
+
const issue = deps.issueSetupLinkToken ?? (async () => {
|
|
42
|
+
const modulePath = '../auth/setup-link-token.js';
|
|
43
|
+
const mod = await import(modulePath) as { issueSetupLinkToken?: (options: Record<string, never>) => Promise<string | { token: string }> };
|
|
44
|
+
if (typeof mod.issueSetupLinkToken !== 'function') throw new Error('issuer-missing');
|
|
45
|
+
return mod.issueSetupLinkToken({});
|
|
46
|
+
});
|
|
47
|
+
const issued = await issue();
|
|
48
|
+
token = typeof issued === 'string' ? issued : issued?.token;
|
|
49
|
+
if (!token) throw new Error('issuer-missing');
|
|
50
|
+
} catch { observe('fallback', 'issuer-missing'); return 'fallback'; }
|
|
51
|
+
|
|
52
|
+
const print = deps.print ?? console.log;
|
|
53
|
+
const link = setupUrl(shown.urls.pwa.loopback, token);
|
|
54
|
+
const phoneLink = shown.urls.pwa.tailnet ? setupUrl(shown.urls.pwa.tailnet, token) : undefined;
|
|
55
|
+
print('브라우저에서 셋업을 이어가세요');
|
|
56
|
+
print(link);
|
|
57
|
+
let qr = false;
|
|
58
|
+
if (phoneLink) {
|
|
59
|
+
try {
|
|
60
|
+
const rendered = (deps.renderQr ?? ((url: string) => {
|
|
61
|
+
const result = spawnSync('qrencode', ['-t', 'ANSIUTF8', url], { encoding: 'utf8' });
|
|
62
|
+
return result.status === 0 ? result.stdout : undefined;
|
|
63
|
+
}))(phoneLink);
|
|
64
|
+
if (rendered) {
|
|
65
|
+
print(rendered);
|
|
66
|
+
print('같은 와이파이·tailnet 의 폰 카메라로 스캔하세요');
|
|
67
|
+
qr = true;
|
|
68
|
+
}
|
|
69
|
+
} catch { /* QR is optional. */ }
|
|
70
|
+
}
|
|
71
|
+
print('터미널에서 하려면: `elanous setup --terminal`');
|
|
72
|
+
let browserOpened = false;
|
|
73
|
+
if (!(deps.isHeadless ?? (() => isHeadlessEnv(process.env)))()) {
|
|
74
|
+
try {
|
|
75
|
+
browserOpened = (deps.openBrowser ?? ((url: string) => {
|
|
76
|
+
const command = process.platform === 'darwin' ? 'open' : process.platform === 'linux' || process.platform === 'freebsd' || process.platform === 'openbsd' ? 'xdg-open' : undefined;
|
|
77
|
+
return command ? spawnSync(command, [url], { stdio: 'ignore' }).status === 0 : false;
|
|
78
|
+
}))(link);
|
|
79
|
+
} catch { /* The displayed link remains usable. */ }
|
|
80
|
+
}
|
|
81
|
+
observe('link-shown', undefined, qr, browserOpened, phoneLink ? 'tailnet' : 'loopback');
|
|
82
|
+
return 'link-shown';
|
|
83
|
+
}
|