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
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// Field feed — shared helpers for the graph nodes (graphs/field/field-feed.yaml · EV10c/EV10d).
|
|
2
|
+
// The draft file `<folder>/feed/feed-draft.json` is the single source of truth: the feed node writes it, the PWA
|
|
3
|
+
// «게시 대기» card overwrites it when a person edits, and the deliver node reads it «after» approval.
|
|
4
|
+
import { existsSync, readFileSync, readdirSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
|
|
5
|
+
import { basename, dirname, extname, join, resolve } from 'node:path';
|
|
6
|
+
import { homedir } from 'node:os';
|
|
7
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
8
|
+
|
|
9
|
+
export type FeedSlide = { image: string; source: string; caption: string; include: boolean; renderedCaption?: string /* `N/M:caption` as last drawn */ };
|
|
10
|
+
export type FeedDraft = {
|
|
11
|
+
kind: 'feed-draft'; version: 1; revision: number; updatedAt: string; updatedBy: 'graph' | 'human';
|
|
12
|
+
graphId: 'field-feed'; runId: string | null; folder: string;
|
|
13
|
+
brand: { name: string; handle: string; avatar: string | null };
|
|
14
|
+
event: { title: string; date: string };
|
|
15
|
+
cover: { text: string; sub: string; image: string; renderedText?: string; renderedSub?: string; renderedSource?: string };
|
|
16
|
+
slides: FeedSlide[];
|
|
17
|
+
caption: { hook: string; body: string };
|
|
18
|
+
hashtags: string[];
|
|
19
|
+
location: string | null;
|
|
20
|
+
reel: string | null;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const ENGINE = resolve(import.meta.dir, '../../skills/explainer-video/engine');
|
|
24
|
+
export const CACHE = process.env.EXPLAINER_CACHE ?? join(homedir(), '.cache', 'elanous-explainer');
|
|
25
|
+
const IMG = ['.jpg', '.jpeg', '.png', '.heic', '.webp'];
|
|
26
|
+
|
|
27
|
+
/** Graph context: `ELANOUS_GRAPH_CONTEXT` is a JSON string or a path to one. CLI `--folder` wins for manual runs. */
|
|
28
|
+
export function readContext(): { folder: string; runId: string | null } {
|
|
29
|
+
const at = process.argv.indexOf('--folder');
|
|
30
|
+
let folder = at >= 0 ? process.argv[at + 1] : undefined;
|
|
31
|
+
let runId: string | null = null;
|
|
32
|
+
const raw = process.env.ELANOUS_GRAPH_CONTEXT;
|
|
33
|
+
if (raw) {
|
|
34
|
+
const ctx = JSON.parse(raw.trim().startsWith('{') ? raw : readFileSync(raw, 'utf8')) as { input?: { folder?: unknown }; runId?: unknown };
|
|
35
|
+
if (!folder && typeof ctx.input?.folder === 'string') folder = ctx.input.folder;
|
|
36
|
+
if (typeof ctx.runId === 'string') runId = ctx.runId;
|
|
37
|
+
}
|
|
38
|
+
if (!folder) throw new Error('input.folder 필요(현장 폴더 절대 경로)');
|
|
39
|
+
folder = resolve(folder.replace(/^~(?=\/)/, homedir()));
|
|
40
|
+
if (!existsSync(folder)) throw new Error(`현장 폴더 없음: ${folder}`);
|
|
41
|
+
return { folder, runId: runId ?? process.env.ELANOUS_GRAPH_RUN_ID ?? null };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const draftPath = (folder: string) => join(folder, 'feed', 'feed-draft.json');
|
|
45
|
+
|
|
46
|
+
export function readDraft(folder: string): FeedDraft | null {
|
|
47
|
+
try { return JSON.parse(readFileSync(draftPath(folder), 'utf8')) as FeedDraft; } catch { return null; }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function writeDraft(folder: string, draft: FeedDraft): void {
|
|
51
|
+
const path = draftPath(folder);
|
|
52
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
53
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
54
|
+
writeFileSync(tmp, JSON.stringify(draft, null, 2) + '\n');
|
|
55
|
+
renameSync(tmp, path);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Field uploads are named `20261002T193012Z-<device>-<name>` (capture time, UTC) — same order as the reel. */
|
|
59
|
+
export function listPhotos(folder: string): string[] {
|
|
60
|
+
const stamp = (f: string) => { const m = /^(\d{8}T\d{6}Z)-/.exec(f); return m ? m[1]! : `~${f}`; };
|
|
61
|
+
return readdirSync(folder).filter((f) => IMG.includes(extname(f).toLowerCase()))
|
|
62
|
+
.sort((a, b) => (stamp(a) < stamp(b) ? -1 : stamp(a) > stamp(b) ? 1 : a < b ? -1 : a > b ? 1 : 0));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function kstDate(photos: string[]): string {
|
|
66
|
+
const m = /^(\d{4})(\d{2})(\d{2})T(\d{2})/.exec(photos[0] ?? '');
|
|
67
|
+
if (!m) return '';
|
|
68
|
+
const d = new Date(Date.UTC(+m[1]!, +m[2]! - 1, +m[3]!, +m[4]!) + 9 * 3600e3);
|
|
69
|
+
return `${d.getUTCFullYear()}.${String(d.getUTCMonth() + 1).padStart(2, '0')}.${String(d.getUTCDate()).padStart(2, '0')}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** One codex vision/text call — the same CLI the reel and card-followup use. Empty string on any failure. */
|
|
73
|
+
export function ask(prompt: string, images: string[], ms = 45000): Promise<string> {
|
|
74
|
+
const bin = process.env.FIELD_REEL_CODEX_BIN || 'codex';
|
|
75
|
+
const args = ['exec', '--skip-git-repo-check', '-c', 'model_reasoning_effort=low', prompt, ...images.flatMap((im) => ['-i', im])];
|
|
76
|
+
return new Promise((done) => {
|
|
77
|
+
let buf = ''; let settled = false;
|
|
78
|
+
const finish = (v: string) => { if (!settled) { settled = true; done(v); } };
|
|
79
|
+
let child;
|
|
80
|
+
try { child = spawn(bin, args, { stdio: ['ignore', 'pipe', 'ignore'] }); } catch { finish(''); return; }
|
|
81
|
+
const timer = setTimeout(() => { child.kill('SIGKILL'); finish(''); }, ms);
|
|
82
|
+
child.stdout.on('data', (d) => { buf += d; });
|
|
83
|
+
child.on('error', () => { clearTimeout(timer); finish(''); });
|
|
84
|
+
child.on('close', (code) => { clearTimeout(timer); finish(code === 0 ? buf : ''); });
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const CHROME = process.env.FIELD_FEED_CHROME
|
|
89
|
+
?? ['/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', '/Applications/Chromium.app/Contents/MacOS/Chromium', '/usr/bin/chromium', '/usr/bin/google-chrome']
|
|
90
|
+
.find((p) => existsSync(p));
|
|
91
|
+
|
|
92
|
+
/** HTML page → PNG at 1080×1350 with headless Chrome. */
|
|
93
|
+
export function screenshot(html: string, out: string): void {
|
|
94
|
+
if (!CHROME) throw new Error('Chrome 없음 — FIELD_FEED_CHROME 로 지정');
|
|
95
|
+
const page = out.replace(/\.png$/, '.html');
|
|
96
|
+
writeFileSync(page, html);
|
|
97
|
+
const r = spawnSync(CHROME, ['--headless=new', '--disable-gpu', '--hide-scrollbars', '--force-device-scale-factor=1',
|
|
98
|
+
'--window-size=1080,1350', '--virtual-time-budget=3000', '--allow-file-access-from-files', `--screenshot=${out}`, `file://${page}`],
|
|
99
|
+
{ encoding: 'utf8', timeout: 60000 });
|
|
100
|
+
if (r.status !== 0 || !existsSync(out)) throw new Error(`screenshot ${basename(out)}: ${(r.stderr || '').slice(0, 200)}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function markSvg(size: number): string {
|
|
104
|
+
const paths = [...readFileSync(join(ENGINE, 'brand', 'elanous-mark-on-dark.svg'), 'utf8').matchAll(/<path d="([^"]*)"/g)].map((m) => m[1]);
|
|
105
|
+
return `<svg width="${size}" height="${size}" viewBox="150 150 724 724">${paths.map((d) => `<path d="${d}" fill="#F2F1EE"/>`).join('')}<circle cx="512" cy="512" r="76" fill="#E95047"/></svg>`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const esc = (s: string) => s.replace(/&/g, '&').replace(/</g, '<').replace(/"/g, '"');
|
|
109
|
+
const fonts = () => ['Bold:700', 'ExtraBold:800', 'Black:900'].map((w) => {
|
|
110
|
+
const [name, weight] = w.split(':');
|
|
111
|
+
return `@font-face{font-family:"Pretendard";src:url("file://${join(CACHE, 'fonts', `Pretendard-${name}.woff2`)}") format("woff2");font-weight:${weight}}`;
|
|
112
|
+
}).join('');
|
|
113
|
+
const base = `*{margin:0;padding:0;box-sizing:border-box}html,body{width:1080px;height:1350px;overflow:hidden;background:#000}
|
|
114
|
+
body{position:relative;font-family:"Pretendard",sans-serif;color:#F2F1EE;word-break:keep-all}
|
|
115
|
+
.ph{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
|
|
116
|
+
.top{position:absolute;left:56px;top:52px;display:flex;align-items:center;gap:16px;font-weight:800;font-size:30px;text-shadow:0 2px 10px rgba(0,0,0,.6)}
|
|
117
|
+
.top b{color:#E95047}
|
|
118
|
+
.url{position:absolute;right:56px;bottom:48px;font-weight:700;font-size:26px;color:rgba(242,241,238,.7)}`;
|
|
119
|
+
|
|
120
|
+
export function coverHtml(photo: string, title: string, sub: string): string {
|
|
121
|
+
return `<!doctype html><html lang="ko"><head><meta charset="utf-8"><style>${fonts()}${base}
|
|
122
|
+
.shade{position:absolute;inset:0;background:linear-gradient(to top,rgba(0,0,0,.92) 0%,rgba(0,0,0,.55) 45%,rgba(0,0,0,.25) 100%)}
|
|
123
|
+
.box{position:absolute;left:72px;right:72px;bottom:150px}
|
|
124
|
+
.kick{font-weight:800;font-size:30px;letter-spacing:.26em;color:#E95047}
|
|
125
|
+
h1{margin-top:22px;font-weight:900;font-size:104px;line-height:1.1;letter-spacing:-.03em}
|
|
126
|
+
.rule{margin-top:34px;width:150px;height:9px;background:#E95047}
|
|
127
|
+
p{margin-top:26px;font-weight:700;font-size:40px;color:rgba(242,241,238,.75)}
|
|
128
|
+
</style></head><body><img class="ph" src="file://${esc(photo)}"/><div class="shade"></div>
|
|
129
|
+
<div class="top">${markSvg(54)}<span>Elanous <b>·</b> 현장 스케치</span></div>
|
|
130
|
+
<div class="box"><div class="kick">현장 스케치</div><h1>${esc(title)}</h1><div class="rule"></div><p>${esc(sub)}</p></div>
|
|
131
|
+
<div class="url">elanous.ai</div></body></html>`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function slideHtml(photo: string, caption: string, no: number, total: number): string {
|
|
135
|
+
return `<!doctype html><html lang="ko"><head><meta charset="utf-8"><style>${fonts()}${base}
|
|
136
|
+
.shade{position:absolute;left:0;right:0;bottom:0;height:46%;background:linear-gradient(to top,rgba(0,0,0,.86),rgba(0,0,0,0))}
|
|
137
|
+
.box{position:absolute;left:72px;right:72px;bottom:120px}
|
|
138
|
+
.no{font-weight:800;font-size:32px;letter-spacing:.12em;color:#E95047}.no i{font-style:normal;color:rgba(242,241,238,.6)}
|
|
139
|
+
.cap{margin-top:16px;font-weight:900;font-size:68px;line-height:1.2;letter-spacing:-.03em;text-shadow:0 3px 16px rgba(0,0,0,.6)}
|
|
140
|
+
.rule{margin-top:26px;width:110px;height:8px;background:#E95047}
|
|
141
|
+
</style></head><body><img class="ph" src="file://${esc(photo)}"/><div class="shade"></div>
|
|
142
|
+
<div class="top">${markSvg(48)}<span>Elanous</span></div>
|
|
143
|
+
<div class="box"><div class="no">${String(no).padStart(2, '0')} <i>/ ${String(total).padStart(2, '0')}</i></div><div class="cap">${esc(caption)}</div><div class="rule"></div></div>
|
|
144
|
+
<div class="url">elanous.ai</div></body></html>`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Last JSON object in a model reply, or null. */
|
|
148
|
+
export function lastJson(text: string): Record<string, unknown> | null {
|
|
149
|
+
// Try objects from the last «{» backwards; for each, the shortest closing «}» that parses.
|
|
150
|
+
for (let start = text.lastIndexOf('{'); start >= 0; start = text.lastIndexOf('{', start - 1)) {
|
|
151
|
+
for (let end = text.indexOf('}', start); end >= 0; end = text.indexOf('}', end + 1)) {
|
|
152
|
+
try {
|
|
153
|
+
const value = JSON.parse(text.slice(start, end + 1)) as unknown;
|
|
154
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) return value as Record<string, unknown>;
|
|
155
|
+
} catch { /* longer */ }
|
|
156
|
+
}
|
|
157
|
+
if (start === 0) break;
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export const result = (data: Record<string, unknown>) => console.log(JSON.stringify({ outcome: 'ok', ...data }));
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// reel node — 데몬이 업로드 뒤 이미 렌더했으면(.reel-status.json done ⊕ 파일 있음) 그대로 쓰고, 아니면 같은 엔진(reel.sh)으로 한 번.
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { ENGINE, readContext, result } from './lib.js';
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const { folder } = readContext();
|
|
10
|
+
const file = join(folder, 'reel', 'reel-9x16.mp4');
|
|
11
|
+
let state = '';
|
|
12
|
+
try { state = (JSON.parse(readFileSync(join(folder, '.reel-status.json'), 'utf8')) as { state?: string }).state ?? ''; } catch { /* 없음 */ }
|
|
13
|
+
if (state === 'rendering') throw new Error('데몬이 아직 렌더 중이다 — 끝난 뒤 다시');
|
|
14
|
+
if (state === 'done' && existsSync(file) && existsSync(join(folder, 'reel', 'timeline.json'))) {
|
|
15
|
+
result({ reel: file, reused: true });
|
|
16
|
+
} else {
|
|
17
|
+
const started = Date.now();
|
|
18
|
+
const r = spawnSync('zsh', [join(ENGINE, 'reel.sh'), folder], { encoding: 'utf8', timeout: 540000 });
|
|
19
|
+
if (r.status !== 0 || !existsSync(file)) throw new Error(`reel.sh 실패: ${(r.stderr || r.stdout || '').slice(-300)}`);
|
|
20
|
+
result({ reel: file, reused: false, ms: Date.now() - started });
|
|
21
|
+
}
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.log(JSON.stringify({ outcome: 'fail', reason: error instanceof Error ? error.message : String(error) }));
|
|
24
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// 현장 피드 시작 — `bun scripts/field-feed/start.ts <현장폴더>`
|
|
3
|
+
// ① graphs/field/field-feed.yaml 런을 띄운다(reel → feed → post 에서 «게시 대기»로 멈춘다)
|
|
4
|
+
// ② 멈췄으면 «결정 대기자»를 떼어 띄운다: 15초마다 `graph run --resume <runId>` — 결정이 기록되기 전엔 같은 자리에 다시 멈추고,
|
|
5
|
+
// «최종 게시»(approved)가 기록되면 deliver 까지, 거절이면 failed 로 끝난다. 48시간 뒤 스스로 그만둔다.
|
|
6
|
+
// `--wait <runId>` 는 그 대기자 자신(직접 부르지 않는다).
|
|
7
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
8
|
+
import { existsSync, mkdirSync, openSync } from 'node:fs';
|
|
9
|
+
import { homedir } from 'node:os';
|
|
10
|
+
import { join, resolve } from 'node:path';
|
|
11
|
+
import { debug } from '../../src/debug/log.js';
|
|
12
|
+
|
|
13
|
+
const ROOT = resolve(import.meta.dir, '../..');
|
|
14
|
+
const GRAPH = join(ROOT, 'graphs', 'field', 'field-feed.yaml');
|
|
15
|
+
const CLI = join(ROOT, 'bin', 'elanous.mjs');
|
|
16
|
+
type State = { runId?: string; status?: string; pending?: { decision?: string } };
|
|
17
|
+
|
|
18
|
+
function graphRun(args: string[]): State {
|
|
19
|
+
const r = spawnSync(process.execPath, [CLI, 'graph', 'run', GRAPH, '--json', ...args], { cwd: ROOT, encoding: 'utf8', maxBuffer: 20 * 1024 * 1024 });
|
|
20
|
+
const line = r.stdout.trim().split('\n').reverse().find((l) => l.startsWith('{'));
|
|
21
|
+
if (!line) throw new Error(`graph run: 결과 없음 rc=${r.status} ${r.stderr.slice(-300)}`);
|
|
22
|
+
return JSON.parse(line) as State;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const argv = process.argv.slice(2);
|
|
26
|
+
const waitAt = argv.indexOf('--wait');
|
|
27
|
+
if (waitAt >= 0) {
|
|
28
|
+
const runId = argv[waitAt + 1]!;
|
|
29
|
+
const deadline = Date.now() + 48 * 3600e3;
|
|
30
|
+
let state: State = { status: 'awaiting-approval' };
|
|
31
|
+
while (Date.now() < deadline) {
|
|
32
|
+
await Bun.sleep(15000);
|
|
33
|
+
try { state = graphRun(['--resume', runId]); } catch (error) {
|
|
34
|
+
debug.log('field.feed', 'resume-error', { runId, error: error instanceof Error ? error.message : String(error) });
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (state.status !== 'awaiting-approval') break;
|
|
38
|
+
}
|
|
39
|
+
debug.log('field.feed', 'wait-ended', { runId, status: state.status ?? 'unknown', timedOut: state.status === 'awaiting-approval' });
|
|
40
|
+
console.log(`field-feed ${runId}: ${state.status}`);
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const folderArg = argv.find((a) => !a.startsWith('--'));
|
|
45
|
+
if (!folderArg) { console.error('usage: bun scripts/field-feed/start.ts <현장폴더>'); process.exit(2); }
|
|
46
|
+
const folder = resolve(folderArg.replace(/^~(?=\/)/, homedir()));
|
|
47
|
+
if (!existsSync(folder)) { console.error(`현장 폴더 없음: ${folder}`); process.exit(2); }
|
|
48
|
+
const started = Date.now();
|
|
49
|
+
const state = graphRun(['--input', JSON.stringify({ folder })]);
|
|
50
|
+
const seconds = Math.round((Date.now() - started) / 1000);
|
|
51
|
+
debug.log('field.feed', 'started', { runId: state.runId, status: state.status, seconds });
|
|
52
|
+
if (state.status === 'awaiting-approval' && state.runId) {
|
|
53
|
+
const logDir = join(folder, 'feed');
|
|
54
|
+
mkdirSync(logDir, { recursive: true });
|
|
55
|
+
const out = openSync(join(logDir, 'wait.log'), 'a');
|
|
56
|
+
const child = spawn(process.execPath, [import.meta.path, '--wait', state.runId], { cwd: ROOT, detached: true, stdio: ['ignore', out, out] });
|
|
57
|
+
child.unref();
|
|
58
|
+
console.log(`field-feed ${state.runId}: 게시 대기 (${seconds}s) · 초안 ${join(folder, 'feed', 'feed-draft.json')} · 결정 대기자 pid ${child.pid}`);
|
|
59
|
+
} else {
|
|
60
|
+
console.log(`field-feed ${state.runId}: ${state.status} (${seconds}s)`);
|
|
61
|
+
if (state.status !== 'done') process.exitCode = 1;
|
|
62
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{"url":"https://example.test/shot-a","model":"seedance-2-0","category":"camera","name":"Crash Zoom Macro","description":"A toy car approaches the lens","prompt":"Crash zoom into a miniature car, then hold a macro detail on the painted door.","keywords":["crash zoom","macro"],"author":"Aster","authorUrl":"https://example.test/aster"}
|
|
2
|
+
{"url":"https://example.test/shot-b","model":"seedance-2-0","category":"camera","name":"Quiet Crash Zoom","description":"A bell at a crossing","prompt":"A crash zoom toward a distant bell in the rain.","keywords":["crash zoom"],"author":"Beryl","authorUrl":"https://example.test/beryl"}
|
|
3
|
+
{"url":"https://example.test/shot-c","model":"seedance-2-0","category":"motion","name":"Wide Speed Ramp","description":"A paper kite travels across the sky","prompt":"A paper kite turns slowly before a speed ramp across the horizon.","keywords":["speed ramp"],"author":"Cedar","authorUrl":"https://example.test/cedar"}
|
|
4
|
+
{"url":"https://example.test/shot-d","model":"grok-imagine","category":"camera","name":"Crash Zoom Macro","description":"A tiny clock on the desk","prompt":"Crash zoom onto a tiny clock, revealing macro gears.","keywords":["crash zoom","macro"],"author":"Dara","authorUrl":"https://example.test/dara"}
|
|
5
|
+
{"url":"https://example.test/shot-e","model":"grok-imagine","category":"motion","name":"Whip Pan","description":"A painted room","prompt":"Whip pan past the window to reveal a blue wall. The illustrated cardboard room is full of handmade clocks, chalk drawings, paper boats, folded maps, model trees, painted stones, colorful blocks, and strings of origami stars. The camera circles the toy table slowly while the paper figures stand still. ENDING_SENTINEL","keywords":["whip pan"],"author":"Eden","authorUrl":"https://example.test/eden"}
|
|
6
|
+
{broken json
|
package/scripts/install.sh
CHANGED
|
@@ -11,7 +11,7 @@ set -euo pipefail
|
|
|
11
11
|
|
|
12
12
|
usage() {
|
|
13
13
|
cat <<'EOF'
|
|
14
|
-
Usage: bash scripts/install.sh [--prefix PATH] [--source PATH.tgz|URL] [--no-modify-path] [--no-bootstrap-bun] [--help]
|
|
14
|
+
Usage: bash scripts/install.sh [--prefix PATH] [--source PATH.tgz|URL] [--no-modify-path] [--no-bootstrap-bun] [--no-setup] [--yes] [--allow-downgrade] [--help]
|
|
15
15
|
curl -fsSL https://github.com/ElanvitalAI/elanous/releases/latest/download/install.sh | bash
|
|
16
16
|
|
|
17
17
|
Install elanous without contacting a package registry.
|
|
@@ -23,6 +23,9 @@ Install elanous without contacting a package registry.
|
|
|
23
23
|
(or set $ELANOUS_INSTALL_SOURCE; without either, fetch the verified latest release)
|
|
24
24
|
--no-modify-path do not append the elanous PATH block to a shell startup file
|
|
25
25
|
--no-bootstrap-bun fail instead of installing bun with its official installer when bun is missing
|
|
26
|
+
--no-setup do not start first-time setup in this window after installing (interactive terminals only)
|
|
27
|
+
--yes do not ask before updating an existing installation
|
|
28
|
+
--allow-downgrade allow installing a version older than the one already installed
|
|
26
29
|
--help, -h show this help
|
|
27
30
|
EOF
|
|
28
31
|
}
|
|
@@ -35,6 +38,9 @@ PREFIX="${ELANOUS_INSTALL_PREFIX:-${XDG_DATA_HOME:-${HOME:?HOME is required}/.lo
|
|
|
35
38
|
SOURCE="${ELANOUS_INSTALL_SOURCE:-}"
|
|
36
39
|
MODIFY_PATH=1
|
|
37
40
|
BOOTSTRAP_BUN=1
|
|
41
|
+
RUN_SETUP=1
|
|
42
|
+
ASSUME_YES=0
|
|
43
|
+
ALLOW_DOWNGRADE=0
|
|
38
44
|
|
|
39
45
|
while [ "$#" -gt 0 ]; do
|
|
40
46
|
case "$1" in
|
|
@@ -47,10 +53,30 @@ while [ "$#" -gt 0 ]; do
|
|
|
47
53
|
SOURCE="$2"; shift 2 ;;
|
|
48
54
|
--no-modify-path) MODIFY_PATH=0; shift ;;
|
|
49
55
|
--no-bootstrap-bun) BOOTSTRAP_BUN=0; shift ;;
|
|
56
|
+
--no-setup) RUN_SETUP=0; shift ;;
|
|
57
|
+
--yes|-y) ASSUME_YES=1; shift ;;
|
|
58
|
+
--allow-downgrade) ALLOW_DOWNGRADE=1; shift ;;
|
|
50
59
|
*) echo "⛔ unknown argument: $1" >&2; usage >&2; exit 2 ;;
|
|
51
60
|
esac
|
|
52
61
|
done
|
|
53
62
|
|
|
63
|
+
# INST1 — one language per run: English by default, Korean when the locale is Korean (override: ELANOUS_INSTALL_LANG=en|ko).
|
|
64
|
+
case "${ELANOUS_INSTALL_LANG:-${LC_ALL:-${LC_MESSAGES:-${LANG:-}}}}" in
|
|
65
|
+
ko*) INSTALL_LANG=ko ;;
|
|
66
|
+
*) INSTALL_LANG=en ;;
|
|
67
|
+
esac
|
|
68
|
+
t() { if [ "$INSTALL_LANG" = ko ]; then printf '%s' "$2"; else printf '%s' "$1"; fi; }
|
|
69
|
+
|
|
70
|
+
# Interactive = a person is at a terminal. `curl … | bash` gives us the script on stdin, so look at
|
|
71
|
+
# stdout and /dev/tty, not stdin (rustup does the same). CI never counts as interactive.
|
|
72
|
+
# ELANOUS_INSTALL_INTERACTIVE=0|1 overrides the probe (tests, wrappers).
|
|
73
|
+
INTERACTIVE=0
|
|
74
|
+
if [ -n "${ELANOUS_INSTALL_INTERACTIVE:-}" ]; then
|
|
75
|
+
[ "$ELANOUS_INSTALL_INTERACTIVE" = 1 ] && INTERACTIVE=1
|
|
76
|
+
elif [ -z "${CI:-}" ] && [ -t 1 ] && { : </dev/tty; } 2>/dev/null; then
|
|
77
|
+
INTERACTIVE=1
|
|
78
|
+
fi
|
|
79
|
+
|
|
54
80
|
# bun 이 없으면 공식 설치기로 먼저 깐다 — «sh 한 번이면 알아서» (claude·grok 설치기와 같은 기대).
|
|
55
81
|
# ⛔ 끄면(--no-bootstrap-bun) 공식 설치 명령을 안내하고 rc 127 로 멈춘다.
|
|
56
82
|
# 🩸 2026-09-24 빈 VM 실측: 비대화 셸(ssh 명령·크론)은 ~/.bun/bin 이 PATH 에 없어 «이미 깐» bun 을 못 보고 또 설치했다.
|
|
@@ -295,6 +321,30 @@ fi
|
|
|
295
321
|
PACKAGE_VERSION="$(tar -xzOf "$INSTALL_TARBALL" package/package.json 2>/dev/null | bun -e 'const t=await Bun.stdin.text(); try { process.stdout.write(String(JSON.parse(t).version ?? "")) } catch {}' || true)"
|
|
296
322
|
[ -n "$PACKAGE_VERSION" ] || { echo "⛔ package version missing in tarball: $INSTALL_TARBALL" >&2; exit 1; }
|
|
297
323
|
case "$PACKAGE_VERSION" in */*|*..*) echo "⛔ unsafe package version: $PACKAGE_VERSION" >&2; exit 1 ;; esac
|
|
324
|
+
|
|
325
|
+
# INST1 — an existing installation is never moved silently: no downgrade without --allow-downgrade
|
|
326
|
+
# (10-01: a running mbp went 0.2.7 → 0.2.6 unnoticed), and an interactive re-run asks before updating.
|
|
327
|
+
INSTALLED_VERSION=""
|
|
328
|
+
if [ -f "$PREFIX/install.json" ]; then
|
|
329
|
+
INSTALLED_VERSION="$(bun -e 'try { const j=JSON.parse(await Bun.file(process.argv.at(-1)).text()); process.stdout.write(String(j.version ?? "")) } catch {}' "$PREFIX/install.json" || true)"
|
|
330
|
+
fi
|
|
331
|
+
if [ -n "$INSTALLED_VERSION" ]; then
|
|
332
|
+
ORDER="$(bun -e 'const [a,b]=process.argv.slice(-2); try { process.stdout.write(String(Bun.semver.order(a,b))) } catch { process.stdout.write("0") }' "$PACKAGE_VERSION" "$INSTALLED_VERSION" || echo 0)"
|
|
333
|
+
if [ "$ORDER" = "-1" ] && [ "$ALLOW_DOWNGRADE" -eq 0 ]; then
|
|
334
|
+
echo "⛔ $(t "elanous $INSTALLED_VERSION is installed; $PACKAGE_VERSION is older, so nothing was changed. To go back on purpose, rerun with --allow-downgrade." "elanous $INSTALLED_VERSION 이(가) 설치돼 있고 $PACKAGE_VERSION 은(는) 더 낮은 판이라 아무것도 바꾸지 않았습니다. 일부러 내리려면 --allow-downgrade 를 붙여 다시 실행하세요.")" >&2
|
|
335
|
+
exit 3
|
|
336
|
+
fi
|
|
337
|
+
if [ "$INTERACTIVE" -eq 1 ] && [ "$ASSUME_YES" -eq 0 ]; then
|
|
338
|
+
printf '%s ' "$(t "elanous $INSTALLED_VERSION is already installed. Update to $PACKAGE_VERSION? [Y/n]" "elanous $INSTALLED_VERSION 이(가) 이미 설치돼 있습니다. $PACKAGE_VERSION 로 업데이트할까요? [Y/n]")"
|
|
339
|
+
if [ -n "${ELANOUS_INSTALL_ANSWER+x}" ]; then ANSWER="$ELANOUS_INSTALL_ANSWER"; else ANSWER=""; read -r ANSWER </dev/tty || ANSWER=""; fi
|
|
340
|
+
echo ""
|
|
341
|
+
case "$ANSWER" in
|
|
342
|
+
n|N|no|NO|No|아니오|아니요)
|
|
343
|
+
echo "$(t "Left elanous $INSTALLED_VERSION as it is." "elanous $INSTALLED_VERSION 을(를) 그대로 두었습니다.")"
|
|
344
|
+
exit 0 ;;
|
|
345
|
+
esac
|
|
346
|
+
fi
|
|
347
|
+
fi
|
|
298
348
|
VERSION_NAME="$PACKAGE_VERSION${VERSION_SUFFIX:-}"
|
|
299
349
|
VERSION_DIR="$PREFIX/versions/$VERSION_NAME"
|
|
300
350
|
mkdir -p "$VERSION_DIR"
|
|
@@ -312,15 +362,25 @@ printf '#!/bin/sh\nexec "%s" x node-gyp@latest "$@"\n' "$BUN_EXEC" > "$NATIVE_SH
|
|
|
312
362
|
chmod +x "$NATIVE_SHIM/node-gyp"
|
|
313
363
|
if ! (cd "$VERSION_DIR" && PATH="$NATIVE_SHIM:$PATH" bun add --no-save --offline "$INSTALL_TARBALL" >/dev/null 2>&1); then
|
|
314
364
|
echo "dependencies not in the local bun cache — fetching them from the npm registry" >&2
|
|
315
|
-
|
|
365
|
+
# bun prints «Blocked N postinstalls» and progress noise here — keep it out of the screen unless this step fails.
|
|
366
|
+
ADD_LOG="$(mktemp "${TMPDIR:-/tmp}/elanous-bun-add.XXXXXX")"
|
|
367
|
+
ADD_RC=0
|
|
368
|
+
(cd "$VERSION_DIR" && PATH="$NATIVE_SHIM:$PATH" bun add --no-save "$INSTALL_TARBALL") >"$ADD_LOG" 2>&1 || ADD_RC=$?
|
|
369
|
+
if [ "$ADD_RC" -ne 0 ]; then
|
|
370
|
+
tail -n 20 "$ADD_LOG" >&2
|
|
371
|
+
rm -f "$ADD_LOG"; rm -rf "$NATIVE_SHIM"
|
|
372
|
+
exit "$ADD_RC"
|
|
373
|
+
fi
|
|
374
|
+
rm -f "$ADD_LOG"
|
|
316
375
|
fi
|
|
317
376
|
rm -rf "$NATIVE_SHIM"
|
|
318
377
|
ln -sfn "versions/$VERSION_NAME" "$PREFIX/current"
|
|
319
378
|
mkdir -p "$PREFIX/bin"
|
|
320
379
|
# 절대 bun 경로를 설치 때 박고 엔트리는 current 를 거친다 — cron/systemd 의 짧은 PATH 와 판 전환 모두 지원.
|
|
321
380
|
WRAPPER="$PREFIX/bin/.elanous-$$"
|
|
322
|
-
|
|
323
|
-
|
|
381
|
+
BUN_MISSING_MSG="$(t 'bun not found: %s — run the installer again' 'bun 을 찾을 수 없습니다: %s — 설치기를 다시 실행하세요')"
|
|
382
|
+
printf '#!/bin/sh\n# elanous-wrapper\nif [ ! -x %s ]; then\n printf '\''%s\\n'\'' %s >&2\n exit 127\nfi\nexec %s %s "$@"\n' \
|
|
383
|
+
"$(shell_quote "$BUN_EXEC")" "$BUN_MISSING_MSG" "$(shell_quote "$BUN_EXEC")" "$(shell_quote "$BUN_EXEC")" \
|
|
324
384
|
"$(shell_quote "$PREFIX/current/node_modules/elanous/bin/elanous.mjs")" > "$WRAPPER"
|
|
325
385
|
chmod +x "$WRAPPER"
|
|
326
386
|
mv -f "$WRAPPER" "$PREFIX/bin/elanous"
|
|
@@ -329,7 +389,7 @@ ELN_ON_PATH="$(command -v eln 2>/dev/null || true)"
|
|
|
329
389
|
if { [ -n "$ELN_ON_PATH" ] && [ "$ELN_ON_PATH" != "$ELN" ]; } ||
|
|
330
390
|
{ { [ -e "$ELN" ] || [ -L "$ELN" ]; } &&
|
|
331
391
|
{ [ -L "$ELN" ] || [ ! -f "$ELN" ] || [ "$(sed -n '2p' "$ELN")" != '# elanous-wrapper' ]; }; }; then
|
|
332
|
-
echo
|
|
392
|
+
echo "⚠ eln: $(t 'already used by another program, so it was not created — run elanous instead.' '이미 다른 프로그램이 eln 을 써서 만들지 않았습니다 — elanous 로 실행하세요.')"
|
|
333
393
|
ELN_AVAILABLE=0
|
|
334
394
|
else
|
|
335
395
|
cp "$PREFIX/bin/elanous" "$ELN"
|
|
@@ -368,21 +428,26 @@ if [ "$MODIFY_PATH" -eq 1 ] && [ -n "${LOGIN_STARTUP:-}" ] && ! grep -Fqx "$MARK
|
|
|
368
428
|
printf '\n%s\n%s\n%s\n' "$MARKER_START" "$PATH_LINE" "$MARKER_END" >> "$LOGIN_STARTUP"
|
|
369
429
|
fi
|
|
370
430
|
|
|
371
|
-
echo "Installed elanous $VERSION at $PREFIX/bin/elanous"
|
|
431
|
+
echo "$(t "Installed elanous $VERSION at $PREFIX/bin/elanous" "elanous $VERSION 을(를) 설치했습니다: $PREFIX/bin/elanous")"
|
|
372
432
|
|
|
373
433
|
# ── 다음 걸음 (2026-09-23 · Phase 3 「사람 손」) ─────────────────────────
|
|
374
434
|
# ⛔ 설치가 끝나도 «무엇을 더 쳐야 하나»를 안 말하면, 빠뜨린 손이 나중에 «다른 원인의 얼굴»로 나타난다
|
|
375
435
|
# (예: 로그인 누락이 quota-exhausted 로 분류됐다 — 09-21 실측). 그래서 «지금 상태»로 계산해 말한다.
|
|
376
436
|
# provider 설정은 «안» 적는다 — 빈 config(auto)는 로그인만 있으면 런타임이 codex 로 고른다(#19950).
|
|
437
|
+
# --no-setup keeps the prompt above but never starts setup.
|
|
438
|
+
START_SETUP=$INTERACTIVE
|
|
439
|
+
[ "$RUN_SETUP" -eq 1 ] || START_SETUP=0
|
|
440
|
+
|
|
377
441
|
echo ""
|
|
378
|
-
echo "Next:"
|
|
442
|
+
echo "$(t 'Next:' '다음:')"
|
|
379
443
|
STEP=1
|
|
444
|
+
# INST1 order: new shell → elanous (first-time setup, which also signs in) → missing tools → harness say.
|
|
380
445
|
case ":$PATH:" in
|
|
381
446
|
*":$PREFIX/bin:"*) ;;
|
|
382
|
-
*) if [ "$MODIFY_PATH" -eq 1 ]; then echo " $STEP) open a new shell (or: source $STARTUP)"; else echo " $STEP) add $PREFIX/bin to PATH"; fi; STEP=$((STEP + 1)) ;;
|
|
447
|
+
*) if [ "$MODIFY_PATH" -eq 1 ]; then echo " $STEP) $(t "open a new shell (or: source $STARTUP)" "새 셸을 여세요(또는: source $STARTUP)")"; else echo " $STEP) $(t "add $PREFIX/bin to PATH" "$PREFIX/bin 을 PATH 에 넣으세요")"; fi; STEP=$((STEP + 1)) ;;
|
|
383
448
|
esac
|
|
384
|
-
if
|
|
385
|
-
echo " $STEP)
|
|
449
|
+
if [ "$START_SETUP" -eq 0 ]; then
|
|
450
|
+
echo " $STEP) $(t "Run: $PREFIX/bin/elanous # first-time setup (in a new shell: elanous)" "실행: $PREFIX/bin/elanous # 첫 설정(새 셸에서는: elanous)")"; STEP=$((STEP + 1))
|
|
386
451
|
fi
|
|
387
452
|
# 🆕 2026-09-24 빈 VM 실측: 하니스(codex 백엔드)는 Codex CLI 를 자식으로 띄우는데 빈 기계엔 codex·node 가 «둘 다» 없었다.
|
|
388
453
|
# ⚠️ 아래 설치 줄은 빈 기계에서 아직 «안 쟀다» — `elanous doctor` 의 codex 줄이 판정한다.
|
|
@@ -440,8 +505,22 @@ elif ! gh auth status >/dev/null 2>&1; then
|
|
|
440
505
|
echo " $STEP) gh auth login # the harness opens pull requests with it"; STEP=$((STEP + 1))
|
|
441
506
|
fi
|
|
442
507
|
if [ "$ELN_AVAILABLE" -eq 1 ]; then
|
|
443
|
-
echo " $STEP) eln harness say
|
|
508
|
+
echo " $STEP) $(t 'eln harness say "<one line of what you want>" (or: elanous harness say)' 'eln harness say "<하고 싶은 일 한 줄>" (또는: elanous harness say)')"
|
|
444
509
|
else
|
|
445
|
-
echo " $STEP) elanous harness say
|
|
510
|
+
echo " $STEP) $(t 'elanous harness say "<one line of what you want>"' 'elanous harness say "<하고 싶은 일 한 줄>"')"
|
|
511
|
+
fi
|
|
512
|
+
echo " $(t 'check anytime: elanous setup --non-interactive' '언제든 점검: elanous setup --non-interactive')"
|
|
513
|
+
|
|
514
|
+
# INST2 — finish in the same window: start first-time setup with the absolute wrapper (no source, no new shell).
|
|
515
|
+
if [ "$START_SETUP" -eq 1 ]; then
|
|
516
|
+
echo ""
|
|
517
|
+
echo "$(t 'Starting first-time setup… (skip it next time with --no-setup)' '첫 설정을 시작합니다… (다음에 건너뛰려면 --no-setup)')"
|
|
518
|
+
case ":$PATH:" in
|
|
519
|
+
*":$PREFIX/bin:"*) ;;
|
|
520
|
+
*) echo " $(t "(new shells will find elanous on PATH; this window uses $PREFIX/bin/elanous)" "(새 셸에서는 PATH 로 elanous 를 찾습니다 · 이 창은 $PREFIX/bin/elanous 를 씁니다)")" ;;
|
|
521
|
+
esac
|
|
522
|
+
if [ -n "${ELANOUS_INSTALL_SETUP_EXEC:-}" ]; then
|
|
523
|
+
exec "$ELANOUS_INSTALL_SETUP_EXEC" "$PREFIX/bin/elanous"
|
|
524
|
+
fi
|
|
525
|
+
exec "$PREFIX/bin/elanous" </dev/tty
|
|
446
526
|
fi
|
|
447
|
-
echo " check anytime: elanous setup --non-interactive"
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, mkdtempSync, rmSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { repoRoot } from '../../src/domains/schedule-registry.js';
|
|
6
|
+
|
|
7
|
+
export interface CheckoutStep { name: string; ok: boolean; seconds: number; tail: string }
|
|
8
|
+
export interface CheckoutResult { pr: number; mergeSha: string; ok: boolean; steps: CheckoutStep[] }
|
|
9
|
+
export interface CheckoutDeps {
|
|
10
|
+
git: (args: string[], options: { cwd: string; timeout: number }) => string;
|
|
11
|
+
run: (args: string[], options: { cwd: string; timeout: number; env?: NodeJS.ProcessEnv }) => string;
|
|
12
|
+
now: () => number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const LIMIT_MS = 600_000;
|
|
16
|
+
const tail = (text: string): string => text.trimEnd().split('\n').slice(-20).join('\n');
|
|
17
|
+
const validPath = (file: string): boolean => file.length > 0 && !file.startsWith('/') && !file.startsWith('-') && !file.split('/').includes('..');
|
|
18
|
+
const errorOutput = (error: unknown): string => {
|
|
19
|
+
if (!error || typeof error !== 'object') return String(error);
|
|
20
|
+
const e = error as { stdout?: string | Buffer; stderr?: string | Buffer; message?: string };
|
|
21
|
+
return [e.stdout, e.stderr, e.message].filter(Boolean).map(String).join('\n') || String(error);
|
|
22
|
+
};
|
|
23
|
+
const defaultDeps: CheckoutDeps = {
|
|
24
|
+
git: (args, options) => execFileSync('git', args, { ...options, encoding: 'utf8', maxBuffer: 4 * 1024 * 1024 }),
|
|
25
|
+
run: (args, options) => {
|
|
26
|
+
const result = spawnSync('bun', args, { ...options, encoding: 'utf8', maxBuffer: 4 * 1024 * 1024, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
27
|
+
if (result.error || result.status !== 0) throw { stdout: result.stdout, stderr: result.stderr, message: result.error?.message ?? `bun exit ${result.status}` };
|
|
28
|
+
return `${result.stdout}${result.stderr}`;
|
|
29
|
+
},
|
|
30
|
+
now: Date.now,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** A single merge commit, with installed dependencies owned by its detached worktree. */
|
|
34
|
+
export function verifyCleanCheckout({ pr, mergeSha, files, deps = defaultDeps }: {
|
|
35
|
+
pr: number; mergeSha: string; files: string[]; deps?: CheckoutDeps;
|
|
36
|
+
}): CheckoutResult {
|
|
37
|
+
const steps: CheckoutStep[] = [];
|
|
38
|
+
const root = repoRoot();
|
|
39
|
+
const start = deps.now();
|
|
40
|
+
const deadline = start + LIMIT_MS;
|
|
41
|
+
const workspace = mkdtempSync(join(tmpdir(), 'landing-heal-clean-'));
|
|
42
|
+
const checkout = join(workspace, 'checkout');
|
|
43
|
+
let ok = true;
|
|
44
|
+
const step = (name: string, command: (timeout: number) => string): boolean => {
|
|
45
|
+
const began = deps.now();
|
|
46
|
+
try {
|
|
47
|
+
const remaining = deadline - began;
|
|
48
|
+
if (remaining <= 0) throw new Error('clean checkout 600s timeout');
|
|
49
|
+
const out = command(remaining);
|
|
50
|
+
steps.push({ name, ok: true, seconds: (deps.now() - began) / 1000, tail: tail(out) });
|
|
51
|
+
return true;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
steps.push({ name, ok: false, seconds: (deps.now() - began) / 1000, tail: tail(errorOutput(error)) });
|
|
54
|
+
ok = false;
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
try {
|
|
59
|
+
if (step('worktree add', (timeout) => {
|
|
60
|
+
const commit = `${mergeSha}^{commit}`;
|
|
61
|
+
const git = (args: string[]) => {
|
|
62
|
+
const remaining = Math.min(timeout, deadline - deps.now());
|
|
63
|
+
if (remaining <= 0) throw new Error('clean checkout 600s timeout');
|
|
64
|
+
return deps.git(args, { cwd: root, timeout: remaining });
|
|
65
|
+
};
|
|
66
|
+
try {
|
|
67
|
+
git(['cat-file', '-e', commit]);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (deadline - deps.now() <= 0) throw error;
|
|
70
|
+
git(['fetch', '--no-tags', 'origin', mergeSha]);
|
|
71
|
+
git(['cat-file', '-e', commit]);
|
|
72
|
+
}
|
|
73
|
+
return git(['worktree', 'add', '--detach', checkout, mergeSha]);
|
|
74
|
+
})) {
|
|
75
|
+
if (step('bun install --frozen-lockfile', (timeout) => deps.run(['install', '--frozen-lockfile'], { cwd: checkout, timeout }))) {
|
|
76
|
+
const changedTests = files.filter((file) => /\.(?:test|spec)\.[^/]+$/.test(file));
|
|
77
|
+
const unsupported = changedTests.filter((file) => !validPath(file) || !/\.(?:test|spec)\.[cm]?[jt]sx?$/.test(file));
|
|
78
|
+
const tests = [...new Set(changedTests.filter((file) => validPath(file)))];
|
|
79
|
+
if (unsupported.length) {
|
|
80
|
+
step('select changed tests', () => { throw new Error(`unsupported changed test files: ${unsupported.join(', ')}`); });
|
|
81
|
+
} else if (tests.length) {
|
|
82
|
+
step('bun test', (timeout) => {
|
|
83
|
+
const out = deps.run(['test', ...tests], { cwd: checkout, timeout });
|
|
84
|
+
const count = /Ran (\d+) tests? across (\d+) files?/.exec(out);
|
|
85
|
+
if (!count || Number(count[1]) === 0) throw new Error(`bun test did not run any tests: ${tail(out)}`);
|
|
86
|
+
if (Number(count[2]) !== tests.length) throw new Error(`bun test ran ${count[2]} of ${tests.length} changed test files: ${tail(out)}`);
|
|
87
|
+
return out;
|
|
88
|
+
});
|
|
89
|
+
} else if (files.some((file) => validPath(file) && /\.tsx?$/.test(file))) {
|
|
90
|
+
// ci-typecheck-changed.ts rejects --base: its supported base selector is TSC_BASE_REF.
|
|
91
|
+
step('typecheck changed', (timeout) => deps.run(['run', 'scripts/ci-typecheck-changed.ts'], {
|
|
92
|
+
cwd: checkout, timeout, env: { ...process.env, TSC_BASE_REF: `${mergeSha}^`, GITHUB_BASE_REF: '' },
|
|
93
|
+
}));
|
|
94
|
+
} else {
|
|
95
|
+
step('select changed TypeScript', () => { throw new Error('no existing changed .ts files to verify'); });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} finally {
|
|
100
|
+
// Cleanup has its own budget: exhausting the PR deadline must not skip removal.
|
|
101
|
+
const cleanupStart = deps.now();
|
|
102
|
+
try {
|
|
103
|
+
const out = deps.git(['worktree', 'remove', '--force', checkout], { cwd: root, timeout: 30_000 });
|
|
104
|
+
steps.push({ name: 'worktree remove', ok: true, seconds: (deps.now() - cleanupStart) / 1000, tail: tail(out) });
|
|
105
|
+
} catch (error) {
|
|
106
|
+
steps.push({ name: 'worktree remove', ok: false, seconds: (deps.now() - cleanupStart) / 1000, tail: tail(errorOutput(error)) });
|
|
107
|
+
ok = false;
|
|
108
|
+
}
|
|
109
|
+
try { rmSync(workspace, { recursive: true, force: true }); }
|
|
110
|
+
catch (error) { steps.push({ name: 'remove temporary folder', ok: false, seconds: 0, tail: tail(errorOutput(error)) }); ok = false; }
|
|
111
|
+
if (existsSync(workspace)) { steps.push({ name: 'remove temporary folder', ok: false, seconds: 0, tail: 'temporary folder still exists' }); ok = false; }
|
|
112
|
+
}
|
|
113
|
+
return { pr, mergeSha, ok, steps };
|
|
114
|
+
}
|