elanous 0.2.8 → 0.2.10
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/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/2DsiDfDCks44g1cPUMhiW/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/24-e6d28380efab7faf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3615-740dd270764ffa24.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3734-1015fe991eb9b7e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5012-a7fe6f3c8b6fe45a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5783-ef40ebfc91d55230.js +1 -0
- package/apps/pwa/out/_next/static/chunks/62-ff227c3edb8b248b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7633-1b3709b0854c9412.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7951-8b03dbb10b24cd06.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8067-87ce253ec2f23697.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8195-6fde3d7a39859206.js} +5 -5
- package/apps/pwa/out/_next/static/chunks/8894-b5fb36bab837cd29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-84d0d5d77446bd92.js +1 -0
- package/apps/pwa/out/_next/static/chunks/988-5f92e7ab7ccea3dd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9989-5582582f06429840.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d2253d7e58ef1890.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5441d4a9ab35e518.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-d9b12d680922d980.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-d4b6df905f185055.js → page-54b32bd27726588d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e268d6d145827a22.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-186fc6952f027acc.js → page-f8116559c7424da7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-1085399fd0f32112.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-1df686a14f65895a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-cbbe03c0da7b081a.js → page-54e1f966ab60a4dc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7ed95f296f5ee8e9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-11edac668d77e5c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-59e2b9a22cd5a6d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-2976df21ad74ab48.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-34d211a67f16d510.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-8ff652d5d950639b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-c0c0d19d51342d3e.js → page-b31610deda76337b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-2e002ad8faa43291.js → page-6cb0e1bbefe5fb3d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-a286a152cf584576.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-d9237581e7003bcb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-83274e761643fdd2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ceee6cfb1d4e6d68.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-9c9f8bb3e4a64091.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c17217603ea3426a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-efb6177cbf1e8a51.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-d307523703bc4df3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-462f4e4bab45ce7d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8990e96f33af3837.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-7464c1987dd76e6d.js → page-e168fa1030af0548.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-1233e05a4068bc9c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-13a1840b1fac337a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-64a29fc80b11ab55.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-68e787339a16b3dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-d8ab4d1489223459.js → page-4fddc90b23feb215.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-886ba8260dc79c79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-0f26507a1722e40b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-109d4ccd42259c9d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-f5c2a4e510726774.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-b2e0039416f42522.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-26102fd7350f171e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-708948bc8a802794.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-5e5611620f78fb0b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-139ba5c367bbf60a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-202b4e81f33895ea.js +1 -0
- package/apps/pwa/out/_next/static/css/43feca5fd387dcbd.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +30 -29
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +30 -29
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +30 -29
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +27 -26
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +27 -26
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +27 -26
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +30 -29
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +30 -29
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +30 -29
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +30 -29
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +30 -29
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +30 -29
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +27 -26
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +30 -29
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +30 -29
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +27 -26
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +27 -26
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +30 -29
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +30 -29
- package/apps/pwa/out/ops/checklist/index.html +1 -0
- package/apps/pwa/out/ops/checklist/index.txt +30 -0
- package/apps/pwa/out/ops/release/index.html +1 -0
- package/apps/pwa/out/ops/release/index.txt +30 -0
- package/apps/pwa/out/ops/seats/index.html +1 -0
- package/apps/pwa/out/ops/seats/index.txt +30 -0
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +28 -27
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +30 -29
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +27 -26
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +30 -29
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +30 -29
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +30 -29
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +30 -29
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +30 -29
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +27 -26
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +30 -29
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +30 -29
- package/apps/pwa/out/today/index.html +1 -0
- package/apps/pwa/out/today/index.txt +33 -0
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +30 -29
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +30 -29
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +30 -29
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +30 -29
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +30 -29
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +30 -29
- package/graphs/field/field-feed.yaml +2 -0
- package/graphs/implement-loop.yaml +1 -1
- package/graphs/release/recipes.yaml +3 -0
- package/graphs/release/release-loop.yaml +3 -1
- package/graphs/test-diet/nightly-audit.yaml +18 -0
- package/graphs/test-diet/recipes.yaml +3 -0
- package/package.json +1 -1
- package/plugins/card-followup/graphs/run-step.ts +48 -13
- package/plugins/doc-draft/graphs/doc-draft.yaml +3 -3
- package/plugins/doc-draft/graphs/run-step.ts +14 -7
- package/scripts/ci-pwa-build-gate.ts +160 -0
- package/scripts/coord-channel-watch.sh +42 -8
- package/scripts/coord-digest.ts +199 -0
- package/scripts/coord-post.sh +60 -3
- package/scripts/install.ps1 +68 -9
- package/scripts/install.sh +39 -12
- package/scripts/lib/pwa-scenarios/scenarios.ts +20 -0
- package/scripts/lib/tui-regress-checks.ts +146 -0
- package/scripts/memory-lifecycle-cycle.ts +2 -2
- package/scripts/ops-now-probe.ts +327 -0
- package/scripts/publish-internal-dist.ts +11 -1
- package/scripts/release-loop/cutoff.ts +31 -2
- package/scripts/release-loop/export-check-node.ts +85 -0
- package/scripts/release-loop/gate-node.ts +18 -4
- package/scripts/release-loop/node-verdict.ts +8 -1
- package/scripts/release-loop/ops-upgrade-node.ts +72 -16
- package/scripts/release-loop/release-readiness.ts +9 -0
- package/scripts/release-loop/version-node.ts +58 -10
- package/scripts/repo-watch-cycle.ts +1 -1
- package/scripts/retro-cycle.ts +2 -2
- package/scripts/schedule-health-report.ts +1 -1
- package/scripts/storyboard/storyboard.ts +5 -161
- package/scripts/test-diet/lib.ts +31 -1
- package/scripts/test-diet/node.ts +134 -17
- package/scripts/test-timeout-audit.ts +114 -0
- package/scripts/tree-sync-alert.ts +1 -1
- package/scripts/tui-regress.ts +71 -0
- package/scripts/waitlist-daily-digest.ts +1 -1
- package/skills/explainer-video/SKILL.md +4 -0
- package/skills/explainer-video/engine/reel.mjs +18 -13
- package/skills/explainer-video/engine/reel.sh +24 -5
- package/skills/explainer-video/engine/stitch.mjs +145 -0
- package/skills/pitch-deck/SKILL.md +36 -0
- package/skills/pitch-deck/template.html +80 -0
- package/src/acp/codex-approval-adapter.ts +9 -3
- package/src/acp/core-turn-bridge.ts +117 -18
- package/src/acp/fact-question.ts +11 -0
- package/src/acp/server.ts +48 -7
- package/src/acp/tool-approval.ts +126 -0
- package/src/acp/turn-runner.ts +36 -0
- package/src/agent/monad-agent-turn.ts +8 -0
- package/src/ask-user-question/pending-questions.ts +25 -10
- package/src/ask-user-question/types.ts +9 -0
- package/src/auth/setup-link-tokens.fixture.ts +19 -0
- package/src/auth/setup-link-tokens.ts +164 -0
- package/src/autopilot/build/integrity-gate.ts +12 -0
- package/src/boot/daemon-prompt-turn.ts +3 -0
- package/src/boot/daemon-tools/index.ts +8 -0
- package/src/boot/daemon-tools/types.ts +2 -0
- package/src/boot/daemon-tools/web-search.ts +1 -0
- package/src/budget/unified-usage.ts +75 -0
- package/src/channel-bot-token.ts +37 -10
- package/src/chat/ctrl-c-in-input.ts +14 -0
- package/src/chat/index.ts +61 -9
- package/src/chat/pickers/state.ts +6 -2
- package/src/cli/agent-cli.ts +7 -0
- package/src/cli/decisions-cli.ts +25 -3
- package/src/cli/doctor-bundle-upload.ts +68 -0
- package/src/cli/doctor-cli.ts +38 -4
- package/src/cli/op-proxy-sync.ts +83 -0
- package/src/cli/ops-cli.ts +20 -0
- package/src/cli/pod-cli.ts +49 -0
- package/src/cli/pr-cli.ts +37 -4
- package/src/cli/pwa-stop.ts +163 -13
- package/src/cli/pwa-test.ts +3 -0
- package/src/cli/release-cli.ts +98 -15
- package/src/cli/self-update.ts +14 -0
- package/src/cli/storyboard-cli.ts +37 -0
- package/src/cli/test-state-dir-flag.ts +11 -4
- package/src/connectors/linear.ts +146 -0
- package/src/context-bus/coord-events.ts +115 -0
- package/src/dashboard/dashboard-boot-fixture.ts +23 -0
- package/src/dashboard/dashboard-status-lines.ts +45 -0
- package/src/dashboard/first-screen-band.ts +35 -0
- package/src/dashboard/index.ts +128 -34
- package/src/dashboard/provider-slash-runtime.ts +28 -11
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +17 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
- package/src/dashboard/turn-stream-formatter.ts +1 -1
- package/src/decisions/decision-cards.ts +50 -2
- package/src/decisions/decision-ledger.ts +75 -4
- package/src/decisions/decision-linear-projection.ts +157 -0
- package/src/decisions/web-push-decision-cards.ts +50 -0
- package/src/directive/interpretation-card.ts +36 -0
- package/src/discord.ts +2 -1
- package/src/domains/coo-admin-tool.ts +62 -0
- package/src/domains/core-tools.ts +11 -3
- package/src/domains/elanous-card.ts +29 -0
- package/src/domains/ops-facts-tool.ts +76 -0
- package/src/domains/outbound-alert.ts +41 -13
- package/src/domains/release-tool.ts +168 -0
- package/src/domains/telegram-kind-route.ts +19 -1
- package/src/exec-requests/planner.ts +98 -28
- package/src/exec-requests/runner.ts +107 -10
- package/src/exec-requests/store.ts +8 -2
- package/src/field/field-media.ts +10 -7
- package/src/field/field-reel.ts +14 -4
- package/src/git-fs/worktree.ts +45 -6
- package/src/graph-runner/graph-approval-card.ts +113 -0
- package/src/graph-runner/graph-cli.ts +129 -4
- package/src/graph-runner/graph-notify.ts +25 -3
- package/src/graph-runner/runner.ts +142 -16
- package/src/harness/browser-verify.ts +25 -2
- package/src/harness/harness-cli-command.ts +231 -35
- package/src/harness/harness-incidents-writer.fixture.ts +24 -0
- package/src/harness/harness-incidents.ts +129 -0
- package/src/harness/harness-stop.ts +53 -3
- package/src/hitl/clarification-policy.ts +3 -1
- package/src/index.ts +172 -41
- package/src/intake-plane/author-goals.ts +37 -4
- package/src/intake-plane/check.ts +3 -0
- package/src/intake-plane/discord-seat-work.ts +47 -2
- package/src/intake-plane/seat-doc-route.ts +170 -6
- package/src/intake-plane/telegram-seat-work.ts +44 -2
- package/src/intake-plane/telegram-work.ts +1 -0
- package/src/maturity/cli-maturity.ts +34 -0
- package/src/maturity/feature-maturity.ts +100 -1
- package/src/maturity/tui-slash-maturity.ts +31 -0
- package/src/memory.ts +32 -4
- package/src/nexus/api/exec-requests.ts +33 -3
- package/src/nexus/api/http-server.ts +73 -1
- package/src/nexus/api/meta-api.ts +38 -0
- package/src/nexus/api/operator.ts +61 -0
- package/src/nexus/api/ops-api.ts +148 -0
- package/src/nexus/api/ops-seats-sources.ts +95 -0
- package/src/nexus/api/ops-seats.ts +124 -0
- package/src/nexus/api/public-routes.ts +14 -1
- package/src/nexus/api/seat-requests.ts +88 -8
- package/src/nexus/api/sessions-store.ts +63 -0
- package/src/nexus/api/setup-claim.ts +21 -0
- package/src/nexus/api/setup-codex-login.ts +160 -0
- package/src/nexus/api/usage-api.ts +102 -0
- package/src/nexus/config/secrets/file-backend.ts +56 -18
- package/src/nexus/index.ts +45 -4
- package/src/onboarding/first-run-doctor.ts +54 -0
- package/src/onboarding/first-run.ts +89 -0
- package/src/onboarding/lan-address.ts +45 -0
- package/src/onboarding/web-first.ts +119 -24
- package/src/onboarding.ts +4 -0
- package/src/ops-now/ops-now-refresh.ts +100 -0
- package/src/project/project-store.ts +115 -0
- package/src/release-loop/checklist.ts +76 -61
- package/src/release-loop/feature-store.ts +460 -0
- package/src/release-loop/manifest.ts +59 -5
- package/src/release-loop/release-schedule.ts +53 -0
- package/src/repl/index.ts +7 -0
- package/src/seat-dispatch/ceo-commands.ts +98 -0
- package/src/seat-dispatch/ceo-intent.ts +38 -0
- package/src/seat-loop/seat-loop.ts +231 -0
- package/src/seat-loop/seat-report.ts +64 -0
- package/src/self-dev/decompose.ts +66 -11
- package/src/self-dev/run-store.ts +10 -1
- package/src/self-implement/execution-clarification.ts +115 -0
- package/src/self-implement/gate-cli.ts +6 -3
- package/src/self-implement/orchestrator.ts +37 -3
- package/src/self-implement/pipeline-shape.ts +1 -1
- package/src/self-implement/run-status-mapping.ts +5 -3
- package/src/session/chat.ts +2 -0
- package/src/session/index.ts +9 -0
- package/src/shell-env-bootstrap.ts +17 -0
- package/src/skills/tools/usage-slash.ts +8 -7
- package/src/steward/directive.ts +51 -2
- package/src/steward/launch.ts +250 -0
- package/src/steward/outcome-notify.ts +115 -0
- package/src/steward/steward-cards.ts +8 -2
- package/src/steward/triage-plan.ts +32 -4
- package/src/steward/triage.ts +93 -9
- package/src/steward/working-backwards.ts +64 -0
- package/src/storyboard/storyboard.ts +141 -0
- package/src/task-cards/card-store.ts +2 -2
- package/src/task-orchestrator/surfaces/pod-lease.ts +260 -0
- package/src/telegram-card-followup.ts +233 -0
- package/src/telegram-commands.ts +16 -0
- package/src/telegram-report.ts +15 -9
- package/src/telegram-run.ts +90 -13
- package/src/telegram.ts +29 -8
- package/src/thinking-line.ts +28 -20
- package/src/tool-runtime/types.ts +4 -0
- package/src/tui/chat/execution-badge.ts +1 -1
- package/src/tui-client/dashboard-session.ts +2 -0
- package/src/user-config.ts +118 -11
- package/src/ux-sim/turn-states.ts +1 -1
- package/src/version/packed-revision.json +1 -1
- package/src/web-push/notify-exec-request.ts +32 -0
- package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4836-3084c58c1725a40b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8894-7317998f6cb97d8c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-9aa47b0bc6d570ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f4d2ecbbae6088d6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-2053e49930c82748.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a70ba27c33003dd0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-18166f885d831d3e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-1c9d6cfe68b121c2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c108ff1fdb665c97.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-147a328753d983e2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-799395a9df7d95aa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-6a8158b71d1fe9d7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-9567560270857531.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-e7ed3d010153d818.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-b672e02e4bec8deb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-1ba4a5d857276334.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-9db2226f38c542fb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-70ee65f5f7a151dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-0bb8e43203efb23b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-092e1bcf27d751cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-eb52580c4b75b905.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-e573437b0a1c728f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-294300625e49e4d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-085847b088675978.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-9fe9d38c36999eb4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-6141f67f531d8b6a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-295a02b1026b8255.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-1eb9a0e1e054209d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-82efa177a30f3741.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d0be51cec4f62606.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-2e367075ffdbc062.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-647ed2b66c1d49f1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ba058c56e38f2792.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
- package/apps/pwa/out/_next/static/css/5ef72a618a8994f3.css +0 -1
- package/apps/pwa/out/_next/static/lywc8m_HDzH9MKSDQhyEj/_buildManifest.js +0 -1
- package/src/dashboard/turn-typeahead-echo.ts +0 -23
- /package/apps/pwa/out/_next/static/{lywc8m_HDzH9MKSDQhyEj → 2DsiDfDCks44g1cPUMhiW}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { collectUnifiedUsage } from '../../budget/unified-usage.js';
|
|
2
|
+
import type { UnifiedUsageReport } from '../../budget/types.js';
|
|
3
|
+
import { jsonResponse } from './json-response.js';
|
|
4
|
+
|
|
5
|
+
const CACHE_MS = 30_000;
|
|
6
|
+
|
|
7
|
+
// TC 조건(10-03 01:21): 계정 이름·이메일·토큰은 응답에 «하나도» 싣지 않는다 — 공급자·사용률·리셋만.
|
|
8
|
+
// 계정이 여럿이면 공급자 안 순번(«계정 1»)으로만 가른다(이름에서 비밀을 걸러 내는 방식은 쓰지 않는다).
|
|
9
|
+
function accountLabel(index: number, count: number): string {
|
|
10
|
+
return count > 1 ? `계정 ${index + 1}` : '계정';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const SAFE_REASONS = new Set(['query-does-not-supply', 'not-a-subscription']);
|
|
14
|
+
function safeReason(reason: unknown): string {
|
|
15
|
+
return typeof reason === 'string' && SAFE_REASONS.has(reason) ? reason : 'unknown';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function periodType(value: string | null): string | null {
|
|
19
|
+
return value && /^(?:session|weekly|monthly|daily|prepaid-usd|USAGE_PERIOD_TYPE_(?:SESSION|WEEKLY|MONTHLY|DAILY))$/.test(value) ? value : null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isoDate(value: string | null): string | null {
|
|
23
|
+
if (!value) return null;
|
|
24
|
+
const epoch = Date.parse(value);
|
|
25
|
+
return Number.isFinite(epoch) && epoch > 0 ? new Date(epoch).toISOString() : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function resetsInMs(resetsAt: number, nowMs: number): number | null {
|
|
29
|
+
return Number.isFinite(resetsAt) && resetsAt > 0 ? Math.max(0, resetsAt - nowMs) : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function projectUsage(report: UnifiedUsageReport, nowMs: number) {
|
|
33
|
+
return {
|
|
34
|
+
ok: true as const,
|
|
35
|
+
accountCounts: {
|
|
36
|
+
codex: report.accountCounts.codex,
|
|
37
|
+
grok: report.accountCounts.grok,
|
|
38
|
+
openrouter: report.accountCounts.openrouter,
|
|
39
|
+
},
|
|
40
|
+
rows: report.rows.map((row, _i, rows) => ({
|
|
41
|
+
provider: row.provider,
|
|
42
|
+
account: accountLabel(rows.filter((r) => r.provider === row.provider).indexOf(row), rows.filter((r) => r.provider === row.provider).length),
|
|
43
|
+
accountCount: row.accountCount,
|
|
44
|
+
soleAccount: row.soleAccount,
|
|
45
|
+
credits: row.credits.status === 'ok' ? {
|
|
46
|
+
status: 'ok' as const,
|
|
47
|
+
usedPercent: row.credits.usedPercent,
|
|
48
|
+
periodType: periodType(row.credits.periodType),
|
|
49
|
+
periodStart: isoDate(row.credits.periodStart),
|
|
50
|
+
periodEnd: isoDate(row.credits.periodEnd),
|
|
51
|
+
resetsInMs: resetsInMs(row.credits.periodEnd ? Date.parse(row.credits.periodEnd) : 0, nowMs),
|
|
52
|
+
monthlyLimit: row.credits.monthlyLimit,
|
|
53
|
+
used: row.credits.used,
|
|
54
|
+
onDemandCap: row.credits.onDemandCap,
|
|
55
|
+
onDemandUsed: row.credits.onDemandUsed,
|
|
56
|
+
prepaidBalance: row.credits.prepaidBalance,
|
|
57
|
+
balance: row.credits.balance,
|
|
58
|
+
hasCredits: row.credits.hasCredits,
|
|
59
|
+
unlimited: row.credits.unlimited,
|
|
60
|
+
} : { status: row.credits.status },
|
|
61
|
+
subscription: row.subscription.status === 'available' ? {
|
|
62
|
+
status: 'available' as const,
|
|
63
|
+
remainingPercent: row.subscription.remainingPercent,
|
|
64
|
+
resetsAt: row.subscription.resetsAt,
|
|
65
|
+
resetsInMs: resetsInMs(row.subscription.resetsAt, nowMs),
|
|
66
|
+
windowKind: row.subscription.windowKind,
|
|
67
|
+
} : { status: 'unavailable' as const, reason: safeReason(row.subscription.reason) },
|
|
68
|
+
resetCredits: row.resetCredits.status === 'available' || row.resetCredits.status === 'expiring-soon' || row.resetCredits.status === 'expired' ? {
|
|
69
|
+
status: row.resetCredits.status,
|
|
70
|
+
expiresAt: isoDate(row.resetCredits.expiresAt),
|
|
71
|
+
hasUnknownExpiry: row.resetCredits.hasUnknownExpiry,
|
|
72
|
+
} : { status: row.resetCredits.status },
|
|
73
|
+
})),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** A per-daemon 30-second snapshot; concurrent requests share the same collection. */
|
|
78
|
+
export function createUsageHandler(
|
|
79
|
+
collect: () => Promise<UnifiedUsageReport> = collectUnifiedUsage,
|
|
80
|
+
now: () => number = Date.now,
|
|
81
|
+
): () => Promise<Response> {
|
|
82
|
+
let cached: ReturnType<typeof projectUsage> | undefined;
|
|
83
|
+
let cachedAt = 0;
|
|
84
|
+
let pending: Promise<ReturnType<typeof projectUsage>> | undefined;
|
|
85
|
+
return async () => {
|
|
86
|
+
if (cached && now() - cachedAt < CACHE_MS) return jsonResponse(cached);
|
|
87
|
+
pending ??= Promise.resolve().then(collect).then((report) => {
|
|
88
|
+
const value = projectUsage(report, now());
|
|
89
|
+
cached = value;
|
|
90
|
+
cachedAt = now();
|
|
91
|
+
return value;
|
|
92
|
+
}).finally(() => { pending = undefined; });
|
|
93
|
+
try {
|
|
94
|
+
return jsonResponse(await pending);
|
|
95
|
+
} catch {
|
|
96
|
+
// Upstream exceptions can contain credentials; never echo their messages.
|
|
97
|
+
return jsonResponse({ ok: false, reason: 'usage-collection-failed' });
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const handleUsageGet = createUsageHandler();
|
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
// without touching caller code. The legacy module re-exports this
|
|
6
6
|
// backend's accessor functions to preserve backwards compatibility.
|
|
7
7
|
|
|
8
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, copyFileSync, unlinkSync, chmodSync } from 'node:fs';
|
|
9
|
+
import { randomUUID } from 'node:crypto';
|
|
9
10
|
import { dirname } from 'node:path';
|
|
11
|
+
import { acquireLockSync } from '../../../storage/file-lock.js';
|
|
10
12
|
import { SECRETS_VERSION, type SecretsFile } from '../types.js';
|
|
11
13
|
import { secretsPath } from '../paths.js';
|
|
12
14
|
import type { SecretBackend, SecretBackendAvailability, SyncReadableBackend } from './types.js';
|
|
@@ -21,20 +23,47 @@ function readSecretsFile(): SecretsFile {
|
|
|
21
23
|
try {
|
|
22
24
|
const raw = readFileSync(path, 'utf-8');
|
|
23
25
|
const parsed = JSON.parse(raw) as Partial<SecretsFile>;
|
|
24
|
-
if (parsed
|
|
25
|
-
|
|
26
|
-
version
|
|
27
|
-
|
|
28
|
-
};
|
|
29
|
-
} catch {
|
|
30
|
-
|
|
26
|
+
if (parsed?.version !== SECRETS_VERSION || !parsed.secrets || typeof parsed.secrets !== 'object'
|
|
27
|
+
|| Array.isArray(parsed.secrets) || Object.values(parsed.secrets).some(value => typeof value !== 'string')) {
|
|
28
|
+
throw new Error('invalid secrets file version or contents');
|
|
29
|
+
}
|
|
30
|
+
return { version: SECRETS_VERSION, secrets: parsed.secrets };
|
|
31
|
+
} catch (error) {
|
|
32
|
+
throw new Error(`cannot read secrets file at ${path}`, { cause: error });
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
function writeSecretsFile(s: SecretsFile): void {
|
|
35
37
|
const path = secretsPath();
|
|
36
38
|
mkdirSync(dirname(path), { recursive: true });
|
|
37
|
-
|
|
39
|
+
const tmp = `${path}.${randomUUID()}.tmp`;
|
|
40
|
+
const backupTmp = `${path}.${randomUUID()}.bak.tmp`;
|
|
41
|
+
try {
|
|
42
|
+
writeFileSync(tmp, JSON.stringify(s, null, 2), { mode: 0o600, flag: 'wx' });
|
|
43
|
+
if (existsSync(path)) {
|
|
44
|
+
copyFileSync(path, backupTmp);
|
|
45
|
+
chmodSync(backupTmp, 0o600);
|
|
46
|
+
renameSync(backupTmp, `${path}.bak`);
|
|
47
|
+
}
|
|
48
|
+
renameSync(tmp, path);
|
|
49
|
+
} finally {
|
|
50
|
+
if (existsSync(tmp)) unlinkSync(tmp);
|
|
51
|
+
if (existsSync(backupTmp)) unlinkSync(backupTmp);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function modifySecretsFile(change: (s: SecretsFile) => boolean): boolean {
|
|
56
|
+
const path = secretsPath();
|
|
57
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
58
|
+
const release = acquireLockSync(`${path}.lock`);
|
|
59
|
+
try {
|
|
60
|
+
const s = readSecretsFile();
|
|
61
|
+
if (!change(s)) return false;
|
|
62
|
+
writeSecretsFile(s);
|
|
63
|
+
return true;
|
|
64
|
+
} finally {
|
|
65
|
+
release();
|
|
66
|
+
}
|
|
38
67
|
}
|
|
39
68
|
|
|
40
69
|
const syncReadable: SyncReadableBackend = {
|
|
@@ -46,16 +75,14 @@ export const fileBackend: SecretBackend = {
|
|
|
46
75
|
id: 'file',
|
|
47
76
|
async get(id) { return syncReadable.getSync(id); },
|
|
48
77
|
async set(id, value) {
|
|
49
|
-
|
|
50
|
-
s.secrets[id] = value;
|
|
51
|
-
writeSecretsFile(s);
|
|
78
|
+
modifySecretsFile(s => { s.secrets[id] = value; return true; });
|
|
52
79
|
},
|
|
53
80
|
async delete(id) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
81
|
+
return modifySecretsFile(s => {
|
|
82
|
+
if (!Object.hasOwn(s.secrets, id)) return false;
|
|
83
|
+
delete s.secrets[id];
|
|
84
|
+
return true;
|
|
85
|
+
});
|
|
59
86
|
},
|
|
60
87
|
async list() { return syncReadable.listSync(); },
|
|
61
88
|
async isAvailable(): Promise<SecretBackendAvailability> {
|
|
@@ -74,5 +101,16 @@ export function readSecretsFileRaw(): SecretsFile {
|
|
|
74
101
|
}
|
|
75
102
|
|
|
76
103
|
export function writeSecretsFileRaw(s: SecretsFile): void {
|
|
77
|
-
|
|
104
|
+
if (s.version !== SECRETS_VERSION || !s.secrets || typeof s.secrets !== 'object'
|
|
105
|
+
|| Array.isArray(s.secrets) || Object.values(s.secrets).some(value => typeof value !== 'string')) {
|
|
106
|
+
throw new Error('invalid secrets file version or contents');
|
|
107
|
+
}
|
|
108
|
+
const path = secretsPath();
|
|
109
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
110
|
+
const release = acquireLockSync(`${path}.lock`);
|
|
111
|
+
try {
|
|
112
|
+
writeSecretsFile(s);
|
|
113
|
+
} finally {
|
|
114
|
+
release();
|
|
115
|
+
}
|
|
78
116
|
}
|
package/src/nexus/index.ts
CHANGED
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
// In PR α `elanous nexus` is opt-in only; `elanous` continues to launch
|
|
16
16
|
// the legacy dashboard.
|
|
17
17
|
|
|
18
|
+
import { getElanousConfigDir } from '../elanous-config-dir.js';
|
|
18
19
|
import { hostname } from 'node:os';
|
|
20
|
+
import { request as httpRequest } from 'node:http';
|
|
21
|
+
import { isIP } from 'node:net';
|
|
19
22
|
import { resolveToolCwd } from '../boot/tool-cwd.js';
|
|
20
23
|
import { acquireNexusLock, NexusLockError, readNexusLock, findNexusLifecycleState } from './supervisor/lock.js';
|
|
21
24
|
import { classifyNexusStatus, type NexusHttpHealth } from './status-line.js';
|
|
@@ -1267,6 +1270,7 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
|
|
|
1267
1270
|
let coordinatorPushWatcher: { stop(): void } | undefined;
|
|
1268
1271
|
// V2.2-3 (2026-05-12) — NEXUS-hosted Discord workflow trigger bot.
|
|
1269
1272
|
// Wired below once the workflow daemon is constructed.
|
|
1273
|
+
let webPushDecisionTimer: ReturnType<typeof setInterval> | undefined;
|
|
1270
1274
|
let workflowDiscordBot: NexusDiscordTriggerBotHandle | undefined;
|
|
1271
1275
|
// V2.2-4 (2026-05-12) — NEXUS-hosted Telegram workflow trigger bot.
|
|
1272
1276
|
let workflowTelegramBot: NexusTelegramTriggerBotHandle | undefined;
|
|
@@ -2108,7 +2112,9 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
|
|
|
2108
2112
|
},
|
|
2109
2113
|
} : {}),
|
|
2110
2114
|
voiceAdapter,
|
|
2111
|
-
|
|
2115
|
+
// The verifier reads the token envelope from the same config dir the boot token was written to
|
|
2116
|
+
// (ensureAuthToken) — default ~/.elanous would make an isolated daemon check the production envelope.
|
|
2117
|
+
...buildNexusWsBridgeAuth(bearerToken, { configDir: getElanousConfigDir() }),
|
|
2112
2118
|
};
|
|
2113
2119
|
|
|
2114
2120
|
metaApiOpts = {
|
|
@@ -2729,6 +2735,28 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
|
|
|
2729
2735
|
console.warn(`[nexus] workflow daemon boot skipped: ${msg}`);
|
|
2730
2736
|
}
|
|
2731
2737
|
|
|
2738
|
+
// Decision cards use the same ledger and first-start baseline as the messaging transports.
|
|
2739
|
+
if ((getUserConfig().raw?.decisions as { cards?: unknown } | undefined)?.cards !== false && !isAutonomousRunContext()) {
|
|
2740
|
+
try {
|
|
2741
|
+
const { DecisionCardService } = await import('../decisions/decision-cards.js');
|
|
2742
|
+
const { webPushDecisionTransport } = await import('../decisions/web-push-decision-cards.js');
|
|
2743
|
+
const { listSubscriptions } = await import('../web-push/subscriptions.js');
|
|
2744
|
+
const { DecisionLedger } = await import('../decisions/decision-ledger.js');
|
|
2745
|
+
const ledger = new DecisionLedger();
|
|
2746
|
+
const service = new DecisionCardService({ transport: webPushDecisionTransport(ledger), ownerIds: [], ledger });
|
|
2747
|
+
await service.tick();
|
|
2748
|
+
webPushDecisionTimer = setInterval(() => {
|
|
2749
|
+
void service.tick().catch((error: unknown) => {
|
|
2750
|
+
debug.log('decisions.webpush', 'tick-failed', { reason: error instanceof Error ? error.message.slice(0, 80) : 'unknown' }, { level: 'warn' });
|
|
2751
|
+
});
|
|
2752
|
+
}, 20_000);
|
|
2753
|
+
webPushDecisionTimer.unref();
|
|
2754
|
+
debug.log('decisions.webpush', 'attached', { subscribers: listSubscriptions().length });
|
|
2755
|
+
} catch (error) {
|
|
2756
|
+
debug.log('decisions.webpush', 'attach-failed', { reason: error instanceof Error ? error.message.slice(0, 80) : 'unknown' }, { level: 'warn' });
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2732
2760
|
// W7-후속 (2026-05-12) — Outbound substrate boot. Builds the
|
|
2733
2761
|
// OutboundRouter + iOS push channel + token store from user-config
|
|
2734
2762
|
// `notifications.apns.*`. The token store is passed to the http
|
|
@@ -3339,6 +3367,7 @@ export async function runNexus(opts: RunNexusOptions = {}): Promise<RunNexusHand
|
|
|
3339
3367
|
console.warn(`[nexus] continuation scheduler stop failed: ${(err as Error).message}`);
|
|
3340
3368
|
}
|
|
3341
3369
|
}
|
|
3370
|
+
if (webPushDecisionTimer) { clearInterval(webPushDecisionTimer); webPushDecisionTimer = undefined; }
|
|
3342
3371
|
// R5 — stop the dig-goal armer poller (no-op when never armed).
|
|
3343
3372
|
if (digArmerHandle) { clearInterval(digArmerHandle); digArmerHandle = undefined; }
|
|
3344
3373
|
if (replayArmerHandle) { clearInterval(replayArmerHandle); replayArmerHandle = undefined; }
|
|
@@ -3876,9 +3905,21 @@ async function probeNexusStatusHealth(runtime: NexusRuntimeMeta | null): Promise
|
|
|
3876
3905
|
const healthUrl = new URL('http://localhost/v1/health');
|
|
3877
3906
|
healthUrl.hostname = host.includes(':') && !host.startsWith('[') ? `[${host}]` : host;
|
|
3878
3907
|
healthUrl.port = String(runtime.httpPort);
|
|
3879
|
-
const
|
|
3880
|
-
|
|
3881
|
-
|
|
3908
|
+
const signal = AbortSignal.timeout(NEXUS_STATUS_HEALTH_TIMEOUT_MS);
|
|
3909
|
+
if (healthUrl.hostname === 'localhost' || healthUrl.hostname === '[::1]'
|
|
3910
|
+
|| (isIP(healthUrl.hostname) === 4 && healthUrl.hostname.startsWith('127.'))) {
|
|
3911
|
+
// Bun fetch inherits HTTP_PROXY even for loopback; node:http opens a direct socket.
|
|
3912
|
+
const ok = await new Promise<boolean>((resolve, reject) => {
|
|
3913
|
+
const req = httpRequest(healthUrl, { signal }, (response) => {
|
|
3914
|
+
response.resume();
|
|
3915
|
+
resolve(response.statusCode !== undefined && response.statusCode >= 200 && response.statusCode < 300);
|
|
3916
|
+
});
|
|
3917
|
+
req.on('error', reject);
|
|
3918
|
+
req.end();
|
|
3919
|
+
});
|
|
3920
|
+
return ok ? 'responsive' : 'silent';
|
|
3921
|
+
}
|
|
3922
|
+
const response = await fetch(healthUrl.href, { signal });
|
|
3882
3923
|
return response.ok ? 'responsive' : 'silent';
|
|
3883
3924
|
} catch {
|
|
3884
3925
|
// The request could not complete, so retain unknown rather than infer listener silence.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { delimiter, join, win32 } from 'node:path';
|
|
2
|
+
import { applyDoctorFixes, planDoctorFixes, type DoctorFixDeps, type DoctorFixItem } from '../cli/doctor-fix.js';
|
|
3
|
+
import { staticToolBinDir } from '../cli/doctor-static-tools.js';
|
|
4
|
+
import { debug } from '../debug/log.js';
|
|
5
|
+
|
|
6
|
+
const AUTOMATIC = new Set<DoctorFixItem['id']>(['private-files', 'key-cache-permissions', 'static-tools']);
|
|
7
|
+
const DEFERRED = new Set<DoctorFixItem['id']>(['install-path', 'bun-tmpdir']);
|
|
8
|
+
const LATER_LINE = '나중에: elanous doctor --fix (셸 시작 파일 수정)';
|
|
9
|
+
|
|
10
|
+
export interface FirstRunDoctorResult {
|
|
11
|
+
fixed: string[];
|
|
12
|
+
skipped: string[];
|
|
13
|
+
failed: string[];
|
|
14
|
+
later: string[];
|
|
15
|
+
lines: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** 첫 실행에서는 동의가 필요 없는 수리만 실행하고, 셸 시작 파일은 손대지 않는다. */
|
|
19
|
+
export function runFirstRunDoctor(deps: DoctorFixDeps): FirstRunDoctorResult {
|
|
20
|
+
const result: FirstRunDoctorResult = { fixed: [], skipped: [], failed: [], later: [], lines: [] };
|
|
21
|
+
const record = (id: string, outcome: 'fixed' | 'skipped' | 'failed', reason?: string) => {
|
|
22
|
+
result[outcome].push(id);
|
|
23
|
+
if (outcome === 'failed') result.lines.push(`doctor: ${id} 수리 실패 — ${reason || '원인 불명'}`);
|
|
24
|
+
try { debug.log('doctor.first-run', outcome, { id }); } catch { /* 관측 실패가 첫 실행을 막지 않는다. */ }
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const windows = deps.readiness?.platform === 'win32';
|
|
29
|
+
const prefix = deps.readiness?.installPrefix;
|
|
30
|
+
const bin = typeof prefix === 'string' && prefix.trim()
|
|
31
|
+
? windows ? win32.join(prefix.trim(), 'bin') : join(prefix.trim(), 'bin')
|
|
32
|
+
: staticToolBinDir(deps.env ?? process.env, deps.home);
|
|
33
|
+
const separator = windows ? ';' : delimiter;
|
|
34
|
+
const entries = (process.env.PATH ?? '').split(separator);
|
|
35
|
+
if (!entries.some((entry) => windows ? entry.toLowerCase() === bin.toLowerCase() : entry === bin)) {
|
|
36
|
+
process.env.PATH = [bin, process.env.PATH].filter(Boolean).join(separator);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const plan = planDoctorFixes(deps);
|
|
40
|
+
for (const item of plan.items) {
|
|
41
|
+
if (DEFERRED.has(item.id) && item.status === 'fixable') result.later.push(item.id);
|
|
42
|
+
}
|
|
43
|
+
const applied = applyDoctorFixes(deps, true, AUTOMATIC);
|
|
44
|
+
for (const item of applied.items) {
|
|
45
|
+
if (AUTOMATIC.has(item.id) && item.result === 'failed') record(item.id, 'failed', item.reason);
|
|
46
|
+
else if (AUTOMATIC.has(item.id) && item.result === 'fixed') record(item.id, 'fixed');
|
|
47
|
+
else record(item.id, 'skipped');
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
record('doctor', 'failed', '계획 또는 적용 중 예외');
|
|
51
|
+
}
|
|
52
|
+
if (result.later.length) result.lines.push(LATER_LINE);
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import * as readline from 'node:readline/promises';
|
|
2
|
+
import { detectProviders, type DetectedProvider } from '../llm/provider-detect.js';
|
|
3
|
+
import { debug } from '../debug/log.js';
|
|
4
|
+
import { resolveProviderCredential } from '../llm/provider-credentials.js';
|
|
5
|
+
import {
|
|
6
|
+
getUserConfig, saveUserConfig, userConfigPath,
|
|
7
|
+
type LLMProviderName, type UserConfig,
|
|
8
|
+
} from '../user-config.js';
|
|
9
|
+
|
|
10
|
+
export interface FirstRunDeps {
|
|
11
|
+
config?: UserConfig;
|
|
12
|
+
path?: string;
|
|
13
|
+
detectProviders?: () => Promise<DetectedProvider[]>;
|
|
14
|
+
isTTY?: boolean;
|
|
15
|
+
chooseProvider?: () => Promise<LLMProviderName | null>;
|
|
16
|
+
print?: (message: string) => void;
|
|
17
|
+
saveConfig?: (config: UserConfig, path: string) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function chooseProvider(): Promise<LLMProviderName | null> {
|
|
21
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
22
|
+
try {
|
|
23
|
+
const answer = (await rl.question('Choose LLM (1 Codex, 2 Grok, 3 Claude; Enter to cancel): ')).trim();
|
|
24
|
+
return ({ '1': 'openai-codex', '2': 'grok', '3': 'anthropic' } as Record<string, LLMProviderName>)[answer] ?? null;
|
|
25
|
+
} finally {
|
|
26
|
+
rl.close();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function runFirstRun(deps: FirstRunDeps = {}): Promise<{
|
|
31
|
+
outcome: 'ready' | 'needs-llm' | 'cancelled'; provider?: string; inputs: number;
|
|
32
|
+
}> {
|
|
33
|
+
const path = deps.path ?? userConfigPath();
|
|
34
|
+
const cfg = deps.config ?? getUserConfig(path);
|
|
35
|
+
const print = deps.print ?? console.log;
|
|
36
|
+
let detected: DetectedProvider[] = [];
|
|
37
|
+
try { detected = await (deps.detectProviders ?? detectProviders)(); }
|
|
38
|
+
catch { /* Detection is advisory; a TTY can still choose. */ }
|
|
39
|
+
const subscriptions = detected.filter((d) => d.available && (d.auth === 'oauth' || d.auth === 'agent-cli'));
|
|
40
|
+
const providers = subscriptions.map((d) => d.provider);
|
|
41
|
+
const observe = (event: 'detected' | 'chosen' | 'ready' | 'needs-llm', inputs: number) =>
|
|
42
|
+
debug.log('onboarding.first-run', event, { providers, inputs });
|
|
43
|
+
observe('detected', 0);
|
|
44
|
+
// An incomplete onboarding marker does not mean the user's LLM is unconfigured.
|
|
45
|
+
// Keep an already usable provider, key and model rather than replacing them with a detected subscription.
|
|
46
|
+
const existing = cfg.llm.provider !== 'auto'
|
|
47
|
+
? resolveProviderCredential({ provider: cfg.llm.provider, rotation: cfg.llm.rotation,
|
|
48
|
+
baseProvider: cfg.llm.provider, baseApiKey: cfg.llm.apiKey })
|
|
49
|
+
: undefined;
|
|
50
|
+
const configured = existing && existing.source !== 'none'
|
|
51
|
+
? cfg.llm.provider : undefined;
|
|
52
|
+
const picked = (['openai-codex', 'grok', 'claude-code', 'anthropic', 'gemini', 'openai', 'openrouter'] as const)
|
|
53
|
+
.find((name) => providers.includes(name));
|
|
54
|
+
let provider: LLMProviderName | null | undefined = configured ??
|
|
55
|
+
(picked === 'claude-code' ? 'anthropic' : picked as LLMProviderName | undefined);
|
|
56
|
+
let inputs = 0;
|
|
57
|
+
if (!provider) {
|
|
58
|
+
if ((deps.isTTY ?? process.stdin.isTTY) !== true) {
|
|
59
|
+
print('eln setup llm 으로 고르세요');
|
|
60
|
+
observe('needs-llm', 0);
|
|
61
|
+
return { outcome: 'needs-llm', inputs: 0 };
|
|
62
|
+
}
|
|
63
|
+
inputs = 1;
|
|
64
|
+
provider = await (deps.chooseProvider ?? chooseProvider)();
|
|
65
|
+
if (!provider) return { outcome: 'cancelled', inputs };
|
|
66
|
+
observe('chosen', inputs);
|
|
67
|
+
}
|
|
68
|
+
const credential = inputs === 1 || provider !== cfg.llm.provider
|
|
69
|
+
? resolveProviderCredential({
|
|
70
|
+
provider, rotation: cfg.llm.rotation,
|
|
71
|
+
baseProvider: cfg.llm.provider, baseApiKey: cfg.llm.apiKey,
|
|
72
|
+
})
|
|
73
|
+
: undefined;
|
|
74
|
+
if (inputs === 1 && credential?.source === 'none') {
|
|
75
|
+
print('eln setup llm 으로 고르세요');
|
|
76
|
+
observe('needs-llm', inputs);
|
|
77
|
+
return { outcome: 'needs-llm', inputs };
|
|
78
|
+
}
|
|
79
|
+
(deps.saveConfig ?? saveUserConfig)({
|
|
80
|
+
...cfg,
|
|
81
|
+
llm: { ...cfg.llm, provider,
|
|
82
|
+
...(provider !== cfg.llm.provider ? { apiKey: credential?.apiKey, baseUrl: credential?.baseUrl, model: undefined } : {}),
|
|
83
|
+
...(credential?.apiKey ? { apiKey: credential.apiKey } : {}),
|
|
84
|
+
...(credential?.baseUrl ? { baseUrl: credential.baseUrl } : {}) },
|
|
85
|
+
onboarding: { ...cfg.onboarding, ready: true },
|
|
86
|
+
}, path);
|
|
87
|
+
observe('ready', inputs);
|
|
88
|
+
return { outcome: 'ready', provider, inputs };
|
|
89
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { networkInterfaces } from 'node:os';
|
|
3
|
+
|
|
4
|
+
export function isPrivateIpv4(addr: string): boolean {
|
|
5
|
+
const parts = addr.split('.');
|
|
6
|
+
if (parts.length !== 4 || parts.some(part => !/^(0|[1-9]\d{0,2})$/.test(part) || Number(part) > 255)) return false;
|
|
7
|
+
const [a, b] = parts.map(Number);
|
|
8
|
+
return a === 10 || (a === 172 && b! >= 16 && b! <= 31) || (a === 192 && b === 168);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function defaultRouteInterface(platform: NodeJS.Platform = process.platform): string | undefined {
|
|
12
|
+
try {
|
|
13
|
+
if (platform === 'darwin') {
|
|
14
|
+
return /^\s*interface:\s*(\S+)/m.exec(execFileSync('route', ['-n', 'get', 'default'], { encoding: 'utf8', timeout: 1000, stdio: ['ignore', 'pipe', 'ignore'] }))?.[1];
|
|
15
|
+
}
|
|
16
|
+
if (platform === 'linux') {
|
|
17
|
+
return /^default\s+.*?\bdev\s+(\S+)/m.exec(execFileSync('ip', ['route', 'show', 'default'], { encoding: 'utf8', timeout: 1000, stdio: ['ignore', 'pipe', 'ignore'] }))?.[1];
|
|
18
|
+
}
|
|
19
|
+
} catch { /* Route lookup is optional. */ }
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function pickLanAddress({ interfaces, defaultRouteInterface: route }: {
|
|
24
|
+
interfaces: ReturnType<typeof networkInterfaces>;
|
|
25
|
+
defaultRouteInterface?: string | (() => string | undefined);
|
|
26
|
+
}): string | null {
|
|
27
|
+
const candidates: { name: string; address: string }[] = [];
|
|
28
|
+
for (const [name, addresses] of Object.entries(interfaces)) {
|
|
29
|
+
for (const info of addresses ?? []) {
|
|
30
|
+
if (info.family === 'IPv4' && !info.internal && isPrivateIpv4(info.address)) {
|
|
31
|
+
candidates.push({ name, address: info.address });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (candidates.length === 0) return null;
|
|
36
|
+
let preferred: string | undefined;
|
|
37
|
+
try { preferred = typeof route === 'function' ? route() : route ?? defaultRouteInterface(); } catch { /* Use sorted candidates. */ }
|
|
38
|
+
candidates.sort((a, b) => a.name.localeCompare(b.name) || a.address.localeCompare(b.address));
|
|
39
|
+
return (candidates.find(candidate => candidate.name === preferred) ?? candidates[0])?.address ?? null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function isLanReachableBind(httpHost: string | undefined): boolean {
|
|
43
|
+
const host = httpHost?.trim() ?? '';
|
|
44
|
+
return host === '' || host === '0.0.0.0' || host === '::' || host === '[::]' || isPrivateIpv4(host);
|
|
45
|
+
}
|