elanous 0.2.6 → 0.2.8
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 +4 -0
- 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/{3995-bee512a7063036ce.js → 2375-cb6debea56cce4fb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +1 -0
- 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/{8d0d9faf-97887ff99713b84e.js → 2ea96958-e8c7e112de50b067.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{5329.8b7e4bca684b6eec.js → 3403.4bc97b4066945a94.js} +2 -2
- 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/{7633.0654fe6ea259c1ee.js → 4323.c5dfb6f503304775.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4836-3084c58c1725a40b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2984-977cfa339fcd65a5.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/{763-7d0e1913b5074e98.js → 5791-bb82743fa6299562.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{4240-7153881b0b377306.js → 6062-73e68343a9814f6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +1 -0
- 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/7032-f4ef698518fba61c.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/{91ff6dda-3b33d42d20657429.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8894-7317998f6cb97d8c.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-8f5ff96c522b5f55.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-9aa47b0bc6d570ff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f4d2ecbbae6088d6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-2053e49930c82748.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-d4b6df905f185055.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a70ba27c33003dd0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-186fc6952f027acc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-18166f885d831d3e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-1c9d6cfe68b121c2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-cbbe03c0da7b081a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c108ff1fdb665c97.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-147a328753d983e2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-799395a9df7d95aa.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-6a8158b71d1fe9d7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-9567560270857531.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-e7ed3d010153d818.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-c0c0d19d51342d3e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2e002ad8faa43291.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-b672e02e4bec8deb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-1ba4a5d857276334.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-9db2226f38c542fb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-70ee65f5f7a151dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-0bb8e43203efb23b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-092e1bcf27d751cc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-eb52580c4b75b905.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-7464c1987dd76e6d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-e573437b0a1c728f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-294300625e49e4d3.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-085847b088675978.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-9fe9d38c36999eb4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-d8ab4d1489223459.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-6141f67f531d8b6a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-295a02b1026b8255.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-1eb9a0e1e054209d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-82efa177a30f3741.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d0be51cec4f62606.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-2e367075ffdbc062.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-647ed2b66c1d49f1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ba058c56e38f2792.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-37b8bf78a3c6eec5.js → webpack-e5bea36f183b25e6.js} +1 -1
- package/apps/pwa/out/_next/static/css/5ef72a618a8994f3.css +1 -0
- package/apps/pwa/out/_next/static/lywc8m_HDzH9MKSDQhyEj/_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/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 +10 -0
- package/graphs/release/release-loop.yaml +10 -3
- package/graphs/test-diet/recipes.yaml +9 -0
- package/graphs/test-diet/test-diet.yaml +22 -0
- package/package.json +6 -5
- package/persona-presets/_index.yaml +10 -0
- package/persona-presets/_schema.json +146 -0
- package/persona-presets/dana.yaml +26 -0
- package/persona-presets/haru.yaml +27 -0
- package/persona-presets/kai.yaml +27 -0
- package/persona-presets/leo.yaml +27 -0
- package/persona-presets/mira.yaml +30 -0
- package/persona-presets/nari.yaml +27 -0
- package/persona-presets/sora.yaml +27 -0
- package/persona-presets/yuna.yaml +27 -0
- package/plugins/card-followup/README.md +13 -4
- package/plugins/card-followup/graphs/card-followup.yaml +3 -1
- package/plugins/card-followup/graphs/recipes.yaml +5 -1
- package/plugins/card-followup/graphs/run-step.ts +122 -18
- package/plugins/card-followup/graphs/sales-strategy.ts +173 -0
- package/plugins/card-followup/plugin.json +1 -1
- 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 +3 -1
- package/plugins/geo-check/graphs/run-step.ts +58 -13
- package/plugins/geo-check/plugin.json +1 -1
- package/plugins/job-coach/.codex-plugin/plugin.json +1 -1
- package/plugins/job-coach/README.md +1 -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/field-feed/deliver-node.ts +57 -0
- package/scripts/field-feed/feed-node.ts +67 -0
- package/scripts/field-feed/lib.ts +164 -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 +109 -21
- package/scripts/landing-heal/clean-checkout.ts +114 -0
- package/scripts/landing-heal/run-mission.ts +38 -3
- package/scripts/lib/pwa-scenarios/scenarios.ts +2 -1
- package/scripts/persona-presets/lint.ts +79 -0
- 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 +2 -2
- package/scripts/release-loop/env-known-failures.ts +31 -0
- package/scripts/release-loop/gate-node.ts +108 -22
- package/scripts/release-loop/known-issues-node.ts +159 -0
- package/scripts/release-loop/npm-publish-node.ts +124 -0
- package/scripts/release-loop/ops-upgrade-node.ts +95 -0
- package/scripts/release-loop/publish-node.ts +23 -1
- package/scripts/release-loop/release-readiness.ts +58 -0
- package/scripts/release-loop/shard-plan.ts +17 -0
- package/scripts/release-loop/unattended-release.ts +93 -0
- package/scripts/storyboard/storyboard.ts +167 -0
- package/scripts/test-diet/lib.ts +88 -0
- package/scripts/test-diet/node.ts +102 -0
- package/scripts/ux/measure-first-text.ts +102 -0
- 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 +12 -9
- package/skills/explainer-video/engine/build.mjs +1 -1
- package/skills/explainer-video/engine/reel.mjs +138 -31
- package/skills/explainer-video/engine/reel.sh +2 -1
- 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/auth-methods.ts +30 -0
- package/src/acp/elanous-aware-peers.ts +30 -0
- package/src/acp/server.ts +96 -20
- 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/track-agent-shadow.ts +151 -0
- package/src/autopilot/track-agent.ts +23 -3
- 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/channel-import/detect.ts +191 -0
- package/src/channel-import/run.ts +111 -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/config-test-sync.ts +4 -0
- package/src/cli/decisions-cli.ts +13 -3
- package/src/cli/dependence-gauge.ts +237 -0
- package/src/cli/doctor-bundle.ts +128 -0
- package/src/cli/doctor-cli.ts +41 -1
- package/src/cli/doctor-local-llm.ts +95 -0
- package/src/cli/doctor-modes.ts +48 -0
- package/src/cli/import-cli.ts +60 -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/mcp-login.ts +13 -4
- package/src/cli/msg-cli.ts +137 -0
- package/src/cli/persona-cli.ts +116 -0
- package/src/cli/plugin-cli.ts +25 -0
- package/src/cli/pr-cli.ts +36 -0
- package/src/cli/provider-cli.ts +5 -0
- package/src/cli/release-cli.ts +50 -8
- 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/skills-cli.ts +173 -0
- package/src/cli/start-cli.ts +13 -4
- package/src/cli/usage-cli.ts +11 -1
- package/src/codex/setup.ts +37 -16
- package/src/dashboard/index.ts +18 -2
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +2 -2
- package/src/dashboard/slash-runtime/help-from-registry.ts +4 -2
- package/src/decisions/decision-cards.ts +291 -0
- package/src/decisions/decision-ledger.ts +5 -2
- package/src/decisions/discord-decision-cards.ts +133 -0
- package/src/decisions/telegram-decision-cards.ts +100 -0
- package/src/directives/version-at.ts +77 -0
- package/src/discord.ts +73 -6
- package/src/domains/core-tools.ts +37 -14
- package/src/domains/outbound-alert.ts +94 -12
- package/src/domains/repeated-failure.ts +1 -1
- package/src/error-report/report.ts +200 -0
- package/src/exec-requests/planner.ts +146 -0
- package/src/exec-requests/runner.ts +378 -0
- package/src/exec-requests/store.ts +78 -0
- package/src/field/field-feed-auto.ts +48 -0
- package/src/field/field-media.ts +8 -3
- package/src/field/field-reel.ts +203 -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/hooks/error-report.ts +210 -0
- package/src/hooks/receiver.ts +41 -1
- package/src/import/apply.ts +128 -0
- package/src/import/plan.ts +197 -0
- package/src/index.ts +168 -21
- package/src/input-core/text-entry.ts +13 -5
- 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-answer.ts +96 -0
- package/src/intake-plane/seat-doc-route.ts +221 -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 +128 -0
- package/src/nexus/api/discord-trigger-bot.ts +3 -0
- package/src/nexus/api/error-reports.ts +108 -0
- package/src/nexus/api/exec-requests.ts +51 -0
- package/src/nexus/api/field-uploads.ts +40 -3
- package/src/nexus/api/graph-approvals-feed.ts +140 -0
- package/src/nexus/api/graph-approvals.ts +71 -4
- package/src/nexus/api/http-server.ts +82 -8
- package/src/nexus/api/outbound-report.ts +7 -0
- package/src/nexus/api/running-runs-background.ts +68 -0
- package/src/nexus/api/running-runs-child.ts +6 -0
- package/src/nexus/api/seat-requests.ts +254 -0
- package/src/nexus/api/share-captures.ts +91 -0
- package/src/nexus/api/skill-problems.ts +56 -0
- package/src/nexus/api/terminals.ts +24 -8
- package/src/nexus/api/trace-run-scan.ts +37 -0
- package/src/nexus/api/trace.ts +19 -3
- package/src/nexus/index.ts +28 -1
- package/src/nexus/setup-status.ts +11 -7
- package/src/notify/llm-budget-alert.ts +16 -10
- package/src/oauth/browser-availability.ts +14 -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-quota-policy.ts +16 -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 +34 -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 +90 -0
- package/src/onboarding.ts +287 -26
- package/src/persona/presets.ts +105 -0
- 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 +19 -7
- 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/relay-cli.ts +20 -0
- package/src/relay/relay-server.ts +220 -0
- package/src/release-loop/checklist.ts +37 -13
- package/src/self-dev/decompose.ts +1 -0
- package/src/self-dev/dev-pipeline.ts +6 -3
- package/src/self-dev/orchestrate.ts +18 -11
- 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/session/chat.ts +17 -5
- package/src/shell-env-bootstrap.ts +44 -7
- package/src/skills/connect-claude-plugin.ts +142 -0
- package/src/skills/connect.ts +70 -0
- package/src/skills/index.ts +29 -1
- package/src/skills/repair.ts +124 -0
- package/src/skills/runner.ts +11 -2
- package/src/skills/shared-agent-skill-roots.ts +25 -0
- package/src/skills/trigger-extract.ts +1 -1
- 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-command-job.ts +16 -8
- 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-commands.ts +13 -0
- package/src/telegram.ts +72 -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 +189 -15
- package/src/version/packed-revision.json +1 -1
- package/src/video-pipeline/recipes/character.ts +42 -5
- 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/apps/pwa/out/_next/static/chunks/1169-696263ac7af8c4d4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1883.c91a2648af4842fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1947-9d64872eeb9cfdec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2808-dc06482994b40f70.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3165-95515ca5f299a120.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3190-e9282753e7ebde85.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4259-99614149577e77cb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4283-329eabeaf233e6cd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4328-cb5058b1acc2de5e.js +0 -25
- package/apps/pwa/out/_next/static/chunks/4801-ecee6ef62b349b18.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5110-8058791dd25a2e14.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5330-c41e9670f594c38f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5423-e95fd125d1794349.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5478-b5eae07dc79ea619.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5787-96ee6642b9a661e5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6448-79d72368589f98bc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6522-9866c002b5531836.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6729-b4a558b7aa891e0c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6969-cb03f9410b7d2e03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7007-3b9861091bb8dd21.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7930-8dc7e20fc6fc19ca.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8071-187edbc22a86741f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8198-d4101ad70cb9a579.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8250.4a2c5553a07cdf47.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8293-123b91e7b0330dc0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8296-d6d5d87dfa911d41.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8305-603963c33ee8eec3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8612-4a0066f20951b8fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9181-cf4e1b61c4783f92.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9262-26c2ecbd5a67e4a2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9328-45abdebce4fbecb7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9355-dbc67205adf8745f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9763.255b90dd69875689.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9933-66d31ec25c0231af.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-bd06fb55f4da0381.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-4e586142da455a25.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-889ad5cddcf45b0d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-47ba401d56572c27.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-0b54f9c83458e038.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-4fcc296ef6c8ed90.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-fe9fda494a87f4e5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-f9642e25ce129590.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-e1063e8ba5f3e2e7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-9d0df30027d8aa9d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-bd06fb55f4da0381.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-d97adf2c9455b8d0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-236b80d202110362.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-21a30f35f1a276ad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-b1a5be357c4fa25d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1705a60303f7f573.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-76b337c11ad0e237.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-7c0f85fbb56f64c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-65ca16b20617b44d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-e7f9b8fc29411c1d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-92ea6213d1aeec04.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a913a005aacb5476.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-2d78804a40dfdef6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-08a99148e780ac00.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-deb674e7ed36f815.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-49f8de0534d192d7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-bd06fb55f4da0381.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-ba162a28d2176e18.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-1ff1a37b550ab41a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-ccaa698f56c0fbdb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-53b944082de1d7db.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-9b70adf1552789bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-30d482aa63241fe8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-aecf786f54651d8a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6ab9b62c7b6e08f2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-08949735455e0c9a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-a1479005234a46c2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3890e90bd232732.js +0 -1
- package/apps/pwa/out/_next/static/chunks/beb5cf10-9adcc10b83930090.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-d68d41c88e05c801.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-11c63971b7a9593b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-d09452353bd0d9cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-184d727cebeca767.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-21bbbbac99b720b1.js +0 -1
- package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +0 -1
- package/apps/pwa/out/_next/static/qMI0jnlZaBjjm4aIDYNGM/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{qMI0jnlZaBjjm4aIDYNGM → lywc8m_HDzH9MKSDQhyEj}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// TD2 graph nodes: pick → measure → record. Shadow only — measures alone on a remote host and drafts a card.
|
|
3
|
+
// bun scripts/test-diet/node.ts pick|measure|record (inside `elanous graph run graphs/test-diet/test-diet.yaml`)
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { debug } from '../../src/debug/log.js';
|
|
8
|
+
import { effectiveInstanceRoot } from '../../src/instance/resolve.js';
|
|
9
|
+
import { appendLedger, costTable, judge, lastLedgerLine, pickRange, writeCardDraft, type Measurement } from './lib.js';
|
|
10
|
+
|
|
11
|
+
type Context = { input: Record<string, unknown>; outputs: Record<string, Record<string, unknown>> };
|
|
12
|
+
function context(): Context {
|
|
13
|
+
const at = process.env.ELANOUS_GRAPH_CONTEXT;
|
|
14
|
+
if (!at) throw new Error('ELANOUS_GRAPH_CONTEXT required');
|
|
15
|
+
const raw = JSON.parse(at.trimStart().startsWith('{') ? at : readFileSync(at, 'utf8')) as Partial<Context>;
|
|
16
|
+
return { input: raw.input ?? {}, outputs: raw.outputs ?? {} };
|
|
17
|
+
}
|
|
18
|
+
function emit(result: Record<string, unknown> & { outcome: 'ok' | 'fail' | 'error'; summary: string }): number {
|
|
19
|
+
console.log(JSON.stringify({ verdict: result.outcome === 'ok' ? 'pass' : 'fail', ...result }));
|
|
20
|
+
return result.outcome === 'ok' ? 0 : result.outcome === 'fail' ? 1 : 2;
|
|
21
|
+
}
|
|
22
|
+
const git = (args: string[]) => spawnSync('git', args, { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }).stdout ?? '';
|
|
23
|
+
|
|
24
|
+
function pick(ctx: Context): number {
|
|
25
|
+
const budgetSecs = typeof ctx.input.budgetSecs === 'number' && ctx.input.budgetSecs > 0 ? ctx.input.budgetSecs : 1800;
|
|
26
|
+
const files = git(['ls-files', '*.test.ts']).split('\n').filter(Boolean).sort();
|
|
27
|
+
const costs = costTable(readFileSync(join(process.cwd(), 'docs/measurements/td1-whole-gate-mechanical-2026-10-01.tsv'), 'utf8'));
|
|
28
|
+
const last = lastLedgerLine(effectiveInstanceRoot());
|
|
29
|
+
for (const r of last?.results ?? []) costs.set(r.file, r.secs);
|
|
30
|
+
// `start` re-looks a slice on purpose; otherwise continue from the ledger cursor.
|
|
31
|
+
const startAt = typeof ctx.input.start === 'number' && ctx.input.start >= 0 ? ctx.input.start : last?.next ?? 0;
|
|
32
|
+
const range = pickRange(files, startAt, costs, budgetSecs);
|
|
33
|
+
return emit({ outcome: 'ok', summary: `test-diet pick #${range.start}~#${range.end} of ${files.length} (≈${Math.round(range.estimatedSecs)}s)`, ...range, total: files.length, budgetSecs });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** One remote run: shallow clone of the mirror, one install, each file alone with its peak memory. */
|
|
37
|
+
function measure(ctx: Context): number {
|
|
38
|
+
const picked = ctx.outputs.pick?.files;
|
|
39
|
+
if (!Array.isArray(picked) || picked.length === 0) return emit({ outcome: 'fail', summary: 'nothing picked' });
|
|
40
|
+
const host = typeof ctx.input.remote === 'string' ? ctx.input.remote : 'node-b';
|
|
41
|
+
const mirror = typeof ctx.input.mirror === 'string' ? ctx.input.mirror : '~/mirror/elanous-agent.git';
|
|
42
|
+
const script = [
|
|
43
|
+
'set -u', 'export PATH="$HOME/.bun/bin:$PATH"',
|
|
44
|
+
'T=$(mktemp -d)', 'trap \'rm -rf "$T"\' EXIT',
|
|
45
|
+
`git clone -q --depth 1 "file://$(eval echo ${mirror})" "$T/repo" || exit 3`,
|
|
46
|
+
// Same installs as the gate: root and apps/pwa (PWA tests import its own dependencies).
|
|
47
|
+
'cd "$T/repo" && bun install --frozen-lockfile >/dev/null 2>&1 || exit 4',
|
|
48
|
+
'(cd apps/pwa && bun install --frozen-lockfile >/dev/null 2>&1) || echo "WARN pwa-install-failed"',
|
|
49
|
+
'echo "COMMIT $(git rev-parse HEAD)"',
|
|
50
|
+
'TO=$(command -v timeout || command -v gtimeout || true)',
|
|
51
|
+
'while IFS= read -r f; do',
|
|
52
|
+
' [ -n "$f" ] || continue',
|
|
53
|
+
' s=$(date +%s)',
|
|
54
|
+
' if [ -n "$TO" ]; then /usr/bin/time -l "$TO" -k 10 300 bun run scripts/test-deterministic.ts "./$f" > "$T/out" 2> "$T/err"; else /usr/bin/time -l bun run scripts/test-deterministic.ts "./$f" > "$T/out" 2> "$T/err"; fi',
|
|
55
|
+
' rc=$?; e=$(( $(date +%s) - s ))',
|
|
56
|
+
' rss=$(awk \'/maximum resident set size/ {printf "%d", $1/1048576}\' "$T/err")',
|
|
57
|
+
// bun test prints its summary on stderr — read both streams.
|
|
58
|
+
' pass=$(cat "$T/out" "$T/err" | grep -E -o "^ *[0-9]+ pass" | grep -E -o "[0-9]+" | tail -1); fail=$(cat "$T/out" "$T/err" | grep -E -o "^ *[0-9]+ fail" | grep -E -o "[0-9]+" | tail -1)',
|
|
59
|
+
// A failing file names its first error, so an environment gap (e.g. no Java on the host) is not read as a test defect.
|
|
60
|
+
' why=""; [ "$rc" -ne 0 ] && why=$(grep -v -E "^\\s*$|^bun test|^ *[0-9]+ \\||^error: *$" "$T/err" | grep -m1 -E "Unable to|Cannot find|not found|ENOENT|EACCES|rror" | tr "\\t" " " | cut -c1-160)',
|
|
61
|
+
' printf "M\\t%s\\t%s\\t%s\\t%s\\t%s\\t%s\\t%s\\n" "$f" "$e" "${rss:-}" "$rc" "${pass:-}" "${fail:-}" "$why"',
|
|
62
|
+
'done',
|
|
63
|
+
].join('\n');
|
|
64
|
+
const run = spawnSync('ssh', ['-o', 'BatchMode=yes', host, 'bash', '-s'], { input: `${script}\n${picked.join('\n')}\n`, encoding: 'utf8', timeout: 4 * 3600_000, maxBuffer: 16 * 1024 * 1024 });
|
|
65
|
+
// The file list follows the script on stdin; `read` in the loop consumes it.
|
|
66
|
+
if (run.status !== 0 && !String(run.stdout).includes('\nM\t')) return emit({ outcome: 'error', summary: `remote measure failed (rc=${run.status}): ${String(run.stderr).trim().slice(0, 200)}` });
|
|
67
|
+
const commit = /^COMMIT (\w+)/m.exec(run.stdout)?.[1] ?? '?';
|
|
68
|
+
const measurements: Measurement[] = String(run.stdout).split('\n').filter((l) => l.startsWith('M\t')).map((l) => {
|
|
69
|
+
const [, file, secs, rss, rc, pass, fail, why] = l.split('\t');
|
|
70
|
+
const num = (v: string | undefined) => (v === undefined || v === '' ? null : Number(v));
|
|
71
|
+
return { file: file!, secs: Number(secs), rssMb: num(rss), rc: num(rc), pass: num(pass), fail: num(fail), ...(why ? { reason: why } : {}) };
|
|
72
|
+
});
|
|
73
|
+
return emit({ outcome: measurements.length ? 'ok' : 'error', summary: `measured ${measurements.length}/${picked.length} on ${host} at ${commit.slice(0, 9)}`, host, commit, measurements });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function record(ctx: Context): number {
|
|
77
|
+
const p = ctx.outputs.pick;
|
|
78
|
+
const m = ctx.outputs.measure;
|
|
79
|
+
const measurements = (m?.measurements ?? []) as Measurement[];
|
|
80
|
+
if (!p || measurements.length === 0) return emit({ outcome: 'fail', summary: 'nothing measured' });
|
|
81
|
+
const results = measurements.map((x) => judge(x, Number(git(['log', '--since=90.days', '--format=%s', '--', x.file]).split('\n').filter((s) => /^fix\b/i.test(s)).length)));
|
|
82
|
+
const line = {
|
|
83
|
+
at: new Date().toISOString(), range: `#${p.start}~#${p.end}`, start: Number(p.start), end: Number(p.end), next: Number(p.next), total: Number(p.total),
|
|
84
|
+
commit: String(m?.commit ?? '?'), budgetSecs: Number(p.budgetSecs), results,
|
|
85
|
+
};
|
|
86
|
+
const root = effectiveInstanceRoot();
|
|
87
|
+
appendLedger(root, line);
|
|
88
|
+
const card = writeCardDraft(root, line);
|
|
89
|
+
const counts = { keep: results.filter((r) => r.verdict === 'keep').length, review: results.filter((r) => r.verdict === 'review').length, failing: results.filter((r) => r.verdict === 'failing').length };
|
|
90
|
+
debug.log('test-diet', 'recorded', { range: line.range, ...counts, card: card !== null });
|
|
91
|
+
return emit({ outcome: 'ok', summary: `test-diet ${line.range} · keep ${counts.keep} · review ${counts.review} · failing ${counts.failing}${card ? ' · card draft' : ''}`, range: line.range, ...counts, ...(card ? { card } : {}) });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (import.meta.main) {
|
|
95
|
+
const step = process.argv[2];
|
|
96
|
+
try {
|
|
97
|
+
const ctx = context();
|
|
98
|
+
process.exitCode = step === 'pick' ? pick(ctx) : step === 'measure' ? measure(ctx) : step === 'record' ? record(ctx) : emit({ outcome: 'error', summary: `unknown step: ${step}` });
|
|
99
|
+
} catch (error) {
|
|
100
|
+
process.exitCode = emit({ outcome: 'error', summary: error instanceof Error ? error.message : String(error) });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Usage: bun scripts/ux/measure-first-text.ts --questions questions.json [--url http://127.0.0.1:3456] [--token-stdin] [--json]
|
|
2
|
+
// questions.json is a nonempty JSON array of nonempty question strings. Each question starts a fresh session.
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
export interface FirstTextMeasurement {
|
|
6
|
+
question: string;
|
|
7
|
+
firstTextMs: number;
|
|
8
|
+
delta: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Stop the clock on the first complete, nonempty text-delta SSE event, not on response headers or turn-begin. */
|
|
12
|
+
export async function measureFirstText(
|
|
13
|
+
question: string,
|
|
14
|
+
url: string,
|
|
15
|
+
options: { token?: string; fetch?: typeof fetch; now?: () => number } = {},
|
|
16
|
+
): Promise<FirstTextMeasurement> {
|
|
17
|
+
const fetchFn = options.fetch ?? fetch;
|
|
18
|
+
const now = options.now ?? (() => performance.now());
|
|
19
|
+
const controller = new AbortController();
|
|
20
|
+
const started = now();
|
|
21
|
+
let reader: ReadableStreamDefaultReader<Uint8Array> | undefined;
|
|
22
|
+
try {
|
|
23
|
+
const response = await fetchFn(new URL('/v1/prompt/stream', url), {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: {
|
|
26
|
+
'content-type': 'application/json',
|
|
27
|
+
accept: 'text/event-stream',
|
|
28
|
+
...(options.token ? { authorization: `Bearer ${options.token}` } : {}),
|
|
29
|
+
},
|
|
30
|
+
body: JSON.stringify({ userText: question }),
|
|
31
|
+
signal: controller.signal,
|
|
32
|
+
});
|
|
33
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
|
|
34
|
+
if (!response.body) throw new Error('SSE response has no body');
|
|
35
|
+
reader = response.body.getReader();
|
|
36
|
+
const decoder = new TextDecoder();
|
|
37
|
+
let buffer = '';
|
|
38
|
+
while (true) {
|
|
39
|
+
const { done, value } = await reader.read();
|
|
40
|
+
if (done) break;
|
|
41
|
+
buffer += decoder.decode(value, { stream: true });
|
|
42
|
+
// SSE separators can straddle arbitrary UTF-8 chunks (including CRLF boundaries).
|
|
43
|
+
let boundary: RegExpExecArray | null;
|
|
44
|
+
while ((boundary = /\r?\n\r?\n/.exec(buffer)) !== null) {
|
|
45
|
+
const frame = buffer.slice(0, boundary.index);
|
|
46
|
+
buffer = buffer.slice(boundary.index + boundary[0].length);
|
|
47
|
+
const event = /^event:\s*(.*)$/m.exec(frame)?.[1]?.trim();
|
|
48
|
+
const data = frame.split(/\r?\n/).filter((line) => line.startsWith('data:')).map((line) => line.slice(5).trimStart()).join('\n');
|
|
49
|
+
if (event === 'text-delta') {
|
|
50
|
+
const payload: unknown = JSON.parse(data);
|
|
51
|
+
const delta = payload && typeof payload === 'object' && 'delta' in payload ? payload.delta : undefined;
|
|
52
|
+
if (typeof delta === 'string' && delta.length > 0) {
|
|
53
|
+
return { question, firstTextMs: now() - started, delta };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (event === 'error') throw new Error(`SSE error: ${data}`);
|
|
57
|
+
if (event === 'turn-end') throw new Error('Turn ended without a text-delta');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
throw new Error('SSE stream ended without a text-delta');
|
|
61
|
+
} finally {
|
|
62
|
+
// Do not leave the daemon generating the rest of a turn after the measurement.
|
|
63
|
+
if (reader) await reader.cancel().catch(() => {});
|
|
64
|
+
controller.abort();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function run(argv: string[], deps: { fetch?: typeof fetch; now?: () => number; stdin?: () => Promise<string> } = {}): Promise<FirstTextMeasurement[]> {
|
|
69
|
+
let url = 'http://127.0.0.1:3456';
|
|
70
|
+
let questionsPath: string | undefined;
|
|
71
|
+
let json = false;
|
|
72
|
+
let tokenStdin = false;
|
|
73
|
+
for (let i = 0; i < argv.length; i++) {
|
|
74
|
+
const arg = argv[i];
|
|
75
|
+
if (arg === '--json') json = true;
|
|
76
|
+
else if (arg === '--token-stdin') tokenStdin = true;
|
|
77
|
+
else if (arg === '--url' || arg === '--questions') {
|
|
78
|
+
const value = argv[++i];
|
|
79
|
+
if (!value || value.startsWith('--')) throw new Error(`${arg} requires a value`);
|
|
80
|
+
if (arg === '--url') url = value;
|
|
81
|
+
else questionsPath = value;
|
|
82
|
+
} else throw new Error(`Unknown argument: ${arg}`);
|
|
83
|
+
}
|
|
84
|
+
if (!questionsPath) throw new Error('Usage: --questions <JSON file> [--url <base URL>] [--token-stdin] [--json]');
|
|
85
|
+
const questions: unknown = JSON.parse(readFileSync(questionsPath, 'utf8'));
|
|
86
|
+
if (!Array.isArray(questions) || !questions.length || !questions.every((q) => typeof q === 'string' && q.trim().length > 0)) {
|
|
87
|
+
throw new Error('--questions must be a nonempty JSON array of nonempty strings');
|
|
88
|
+
}
|
|
89
|
+
const token = tokenStdin ? (await (deps.stdin ?? (() => Bun.stdin.text()))()).trim() : undefined;
|
|
90
|
+
const results: FirstTextMeasurement[] = [];
|
|
91
|
+
for (const question of questions as string[]) {
|
|
92
|
+
results.push(await measureFirstText(question, url, { token, fetch: deps.fetch, now: deps.now }));
|
|
93
|
+
}
|
|
94
|
+
if (json) console.log(JSON.stringify(results));
|
|
95
|
+
else for (const result of results) console.log(`${result.firstTextMs.toFixed(1)} ms\t${result.question}`);
|
|
96
|
+
return results;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (import.meta.main) {
|
|
100
|
+
try { await run(process.argv.slice(2)); }
|
|
101
|
+
catch (error) { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1; }
|
|
102
|
+
}
|
package/scripts/vflow-collect.ts
CHANGED
|
@@ -22,17 +22,30 @@ const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36';
|
|
|
22
22
|
const OUT_DIR = process.env.VFLOW_OUT ?? join(homedir(), 'docs', 'ref', 'vflow');
|
|
23
23
|
const NDJSON = join(OUT_DIR, 'prompts.ndjson');
|
|
24
24
|
const URLS = join(OUT_DIR, 'urls.txt');
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
// ⚠️ 2026-10-01 실측: 4병렬·120ms 로 전수를 돌리면 수백 건 뒤 사이트가 429 를 낸다(재수집 404건 중 158건).
|
|
26
|
+
// 기본을 낮추고, 429 는 «없다»가 아니라 «기다려라»로 읽는다(Retry-After 를 따른다).
|
|
27
|
+
const CONCURRENCY = Number(process.env.VFLOW_CONCURRENCY ?? 2);
|
|
28
|
+
const GAP_MS = Number(process.env.VFLOW_GAP_MS ?? 400);
|
|
29
|
+
const MAX_429_RETRIES = 5;
|
|
27
30
|
|
|
28
31
|
const argv = process.argv.slice(2);
|
|
29
32
|
const limitAt = argv.indexOf('--limit');
|
|
30
33
|
const LIMIT = limitAt >= 0 ? Number(argv[limitAt + 1]) : Infinity;
|
|
31
34
|
|
|
35
|
+
let throttled = 0;
|
|
32
36
|
async function get(url: string): Promise<string> {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
for (let attempt = 0; ; attempt++) {
|
|
38
|
+
const r = await fetch(url, { headers: { 'User-Agent': UA } });
|
|
39
|
+
if (r.status === 429 && attempt < MAX_429_RETRIES) {
|
|
40
|
+
throttled++;
|
|
41
|
+
const after = Number(r.headers.get('retry-after'));
|
|
42
|
+
const waitMs = Number.isFinite(after) && after > 0 ? after * 1000 : 5000 * 2 ** attempt;
|
|
43
|
+
await new Promise((res) => setTimeout(res, waitMs));
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
47
|
+
return r.text();
|
|
48
|
+
}
|
|
36
49
|
}
|
|
37
50
|
|
|
38
51
|
/** 사이트맵 전수 → 영문 `/prompts/` URL. ⛔ 번역판(`/{lang}/prompts/`)은 «같은 내용»이라 뺀다. */
|
|
@@ -166,14 +179,14 @@ async function main(): Promise<void> {
|
|
|
166
179
|
catch { failed++; return null; } // ⛔ 「없다」가 아니라 «못 받았다»
|
|
167
180
|
}));
|
|
168
181
|
for (const r of rows) { if (r) { appendFileSync(NDJSON, JSON.stringify(r) + '\n'); ok++; } else noLd++; }
|
|
169
|
-
if ((i / CONCURRENCY) % 25 === 0) console.log(` … ${ok + noLd}/${todo.length} ok=${ok}
|
|
182
|
+
if ((i / CONCURRENCY) % 25 === 0) console.log(` … ${ok + noLd}/${todo.length} ok=${ok} 못받음=${failed} jsonld없음=${noLd - failed} 429대기=${throttled}`);
|
|
170
183
|
}
|
|
171
184
|
// ⛔ 수를 «도구가» 낸다 — 사람이 세지 않는다.
|
|
172
185
|
writeFileSync(join(OUT_DIR, 'MANIFEST.txt'),
|
|
173
186
|
[`source: https://vflow.live (robots: Allow: / · 2026-09-23 확인)`,
|
|
174
187
|
`fetched: ${new Date().toISOString()}`,
|
|
175
188
|
`urls_total: ${urls.length}`, `rows_ok: ${ok + done.size}`,
|
|
176
|
-
`skipped_no_jsonld: ${noLd - failed}`, `failed_fetch: ${failed}`,
|
|
189
|
+
`skipped_no_jsonld: ${noLd - failed}`, `failed_fetch: ${failed}`, `throttled_429_retries: ${throttled}`,
|
|
177
190
|
`schema: url model category slug name description prompt keywords[] datePublished author authorUrl sourcePost thumb spec{duration,camera[],lighting[],mood[],difficulty,promptLanguage,includes[]} video{url,isoDuration,resolution,aspect}`,
|
|
178
191
|
`refetch: bun scripts/vflow-collect.ts`, ''].join('\n'));
|
|
179
192
|
console.log(`\n ✅ ok ${ok} · 건너뜀 ${noLd - failed} · 못 받음 ${failed} → ${NDJSON}`);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { searchVflowReferences, type VflowQuery } from '../src/video-pipeline/vflow-reference.js';
|
|
4
|
+
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
let file = join(homedir(), 'docs', 'ref', 'vflow', 'prompts.ndjson');
|
|
7
|
+
const query: { -readonly [K in keyof VflowQuery]: VflowQuery[K] } = { limit: 5 };
|
|
8
|
+
let json = false;
|
|
9
|
+
const techniques: string[] = [];
|
|
10
|
+
const terms: string[] = [];
|
|
11
|
+
|
|
12
|
+
for (let i = 0; i < args.length; i++) {
|
|
13
|
+
const arg = args[i]!;
|
|
14
|
+
if (arg === '--json') { json = true; continue; }
|
|
15
|
+
if (['--file', '--model', '--technique', '--category', '--limit', '--camera', '--lighting', '--mood', '--max-seconds'].includes(arg)) {
|
|
16
|
+
const value = args[++i];
|
|
17
|
+
if (!value || value.startsWith('--')) {
|
|
18
|
+
console.error(`값이 필요한 옵션: ${arg}`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
switch (arg) {
|
|
22
|
+
case '--file': file = value; break;
|
|
23
|
+
case '--model': query.model = value; break;
|
|
24
|
+
case '--technique': techniques.push(value); break;
|
|
25
|
+
case '--category': query.category = value; break;
|
|
26
|
+
case '--camera': query.camera = value; break;
|
|
27
|
+
case '--lighting': query.lighting = value; break;
|
|
28
|
+
case '--mood': query.mood = value; break;
|
|
29
|
+
case '--limit': {
|
|
30
|
+
const parsed = Number(value);
|
|
31
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) {
|
|
32
|
+
console.error(`--limit 은 0 이상의 정수여야 합니다: ${value}`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
query.limit = parsed;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
case '--max-seconds': {
|
|
39
|
+
const parsed = Number(value);
|
|
40
|
+
if (!Number.isFinite(parsed) || parsed < 0 || !value.trim()) {
|
|
41
|
+
console.error(`--max-seconds 은 0 이상의 숫자여야 합니다: ${value}`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
query.maxSeconds = parsed;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} else if (arg.startsWith('--')) {
|
|
49
|
+
console.error(`알 수 없는 옵션: ${arg}`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
} else {
|
|
52
|
+
terms.push(arg);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
query.terms = terms;
|
|
56
|
+
query.techniques = techniques;
|
|
57
|
+
const result = searchVflowReferences(file, query);
|
|
58
|
+
if (!result.available) {
|
|
59
|
+
console.error(`입력 파일을 읽을 수 없습니다: ${file} (${result.reason})`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
if (json) {
|
|
63
|
+
console.log(JSON.stringify(result.rows.map(({ url, model, category, name, prompt, keywords, author, authorUrl, video }) =>
|
|
64
|
+
({ url, model, category, name, prompt, keywords, author, authorUrl, video: video ?? null }))));
|
|
65
|
+
if (result.matches === 0) console.error(`일치 0 · 읽은 줄 ${result.read}`);
|
|
66
|
+
} else if (result.matches === 0) {
|
|
67
|
+
console.log(`일치 0 · 읽은 줄 ${result.read}`);
|
|
68
|
+
} else {
|
|
69
|
+
for (const row of result.rows) {
|
|
70
|
+
console.log(`${row.name} · ${row.model}\n${row.url}\n${row.author ?? ''}\n${row.prompt.slice(0, 240)}${row.video?.url ? `\n${row.video.url}` : ''}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (result.skipped) console.error(`건너뛴 줄 ${result.skipped}`);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { parse as parseYaml } from 'yaml';
|
|
3
|
+
import type { GraphSpecLike, WalkerApi } from '../src/video-pipeline/walk-line.js';
|
|
4
|
+
|
|
5
|
+
interface TestGraph extends GraphSpecLike {
|
|
6
|
+
entry_node: string;
|
|
7
|
+
terminal_nodes: string[];
|
|
8
|
+
nodes: ({ node_id: string; recipe?: string; max_visits?: number })[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const readGraphSpec: WalkerApi['readGraphSpec'] = (path) => {
|
|
12
|
+
try { return { spec: parseYaml(readFileSync(path, 'utf8')) as TestGraph }; }
|
|
13
|
+
catch (e) { return { error: (e as Error).message }; }
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const walkGraph: WalkerApi['walkGraph'] = async (graph, step, opts) => {
|
|
17
|
+
const spec = graph as TestGraph;
|
|
18
|
+
const visits = new Map<string, number>();
|
|
19
|
+
const steps: { node: string; visit: number }[] = [];
|
|
20
|
+
let next = spec.entry_node;
|
|
21
|
+
while (steps.length < opts.maxSteps) {
|
|
22
|
+
const node = spec.nodes.find((n) => n.node_id === next);
|
|
23
|
+
if (!node) return { terminal: null, stopReason: 'no-node', steps };
|
|
24
|
+
const visit = (visits.get(next) ?? 0) + 1;
|
|
25
|
+
if (visit > (node.max_visits ?? 1)) return { terminal: null, stopReason: 'budget-exceeded', steps };
|
|
26
|
+
visits.set(next, visit);
|
|
27
|
+
steps.push({ node: next, visit });
|
|
28
|
+
if (spec.terminal_nodes.includes(next)) return { terminal: next, stopReason: 'terminal', steps };
|
|
29
|
+
const outcome = await step(node);
|
|
30
|
+
if (outcome === null) { next = opts.unobservedNode; continue; }
|
|
31
|
+
const edge = spec.edges.find((e) => e.from === next && (e.to !== undefined || e.map?.[outcome] !== undefined));
|
|
32
|
+
if (!edge) return { terminal: null, stopReason: 'no-edge', steps };
|
|
33
|
+
next = edge.to ?? edge.map![outcome]!;
|
|
34
|
+
}
|
|
35
|
+
return { terminal: null, stopReason: 'budget-exceeded', steps };
|
|
36
|
+
};
|
|
@@ -998,9 +998,8 @@ async function plan() {
|
|
|
998
998
|
return where.length ? `${u} → 구간을 «${where.join('/')}» 까지` : `${u} → ⛔ 스핀 결손`;
|
|
999
999
|
});
|
|
1000
1000
|
|
|
1001
|
-
sayUnprobed();
|
|
1002
|
-
|
|
1003
1001
|
if (COMPACT) {
|
|
1002
|
+
sayUnprobed();
|
|
1004
1003
|
// ⛔ 노드별로 찍지 않는다 — encode 가 세 번 나온다. 능력 단위로 «접는다».
|
|
1005
1004
|
const seen = new Map<string, { impl?: Impl; required: boolean }>();
|
|
1006
1005
|
for (const o of out) for (const p of o.picks) {
|
|
@@ -1138,6 +1137,7 @@ async function plan() {
|
|
|
1138
1137
|
return;
|
|
1139
1138
|
}
|
|
1140
1139
|
|
|
1140
|
+
sayUnprobed();
|
|
1141
1141
|
console.log(`\n🧱 추천 스택 — 구간 ${nodes[0].id} → ${nodes[nodes.length - 1].id} · 선호 ${TIER_MARK[prefer]}`);
|
|
1142
1142
|
console.log(` 설정: ${LOADED.source}${MACHINE ? ` · 기계 «${MACHINE_NAME}»` : ''}`);
|
|
1143
1143
|
if (MISSING.size) console.log(` ⚠️ 없다고 «가정»한 것: ${[...MISSING].join(', ')}`);
|
|
@@ -35,7 +35,7 @@ The grammar of a chapter (take it from the reference, don't improvise):
|
|
|
35
35
|
|
|
36
36
|
The outro gives one line that sums everything up, then the CTA.
|
|
37
37
|
|
|
38
|
-
⛔ Every claim must trace to a merged change or a measured run, listed in `sources`. Don't write «fully autonomous», dates you can't promise, or comparisons
|
|
38
|
+
⛔ Every claim must trace to a merged change or a measured run, listed in `sources`. Don't write «fully autonomous», dates you can't promise, or comparisons with other products.
|
|
39
39
|
|
|
40
40
|
## 2. Voice — `bun $SKILL/engine/vo.ts <project>/script.json`
|
|
41
41
|
|
|
@@ -85,7 +85,7 @@ The reference supplies the **grammar** (components and staging), never its colou
|
|
|
85
85
|
- **Text**: `#F2F1EE`.
|
|
86
86
|
- **Motif**: the V6 «엘랑 소용돌이» mark (`engine/brand/`), used in three places — a slow, faint rotation behind the scene, the header, and the chapter cards.
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
The reference is used only for its structure (header, chapter cards, a diagram that moves one step per line, keyword-timed captions). Its colours, artwork and voice are not reproduced.
|
|
89
89
|
|
|
90
90
|
## Field reel — photos from the venue → a vertical video in about a minute
|
|
91
91
|
|
|
@@ -95,13 +95,16 @@ zsh $SKILL/engine/reel.sh <folder> --title "마케터의 밤" --sub "2026.10.02
|
|
|
95
95
|
|
|
96
96
|
- **Input**: a folder of phone photos (jpg · png · heic) and short clips (mp4 · mov). The default upload folder is `~/.elanous/field/<event>/`.
|
|
97
97
|
- **Order**: by capture time (`mdls` creation date), then by file name.
|
|
98
|
-
- **Captions**: optional `<folder>/captions.txt`, one line per item as `파일명 | 자막`.
|
|
98
|
+
- **Captions**: optional `<folder>/captions.txt`, one line per item as `파일명 | 자막`. A photo without a line gets one line from a vision model (`codex exec -i`, all photos in parallel, 30 s cap each); if that fails it shows «현장 N». `FIELD_REEL_VISION=0` turns the vision step off.
|
|
99
|
+
- **Title**: `--title`, or `title.txt` through the daemon. A slug-like title (`field-2026-10-01`) is replaced by a vision headline read from the first three photos (event or company names on screen first). The date line defaults to the first photo's capture date (KST).
|
|
100
|
+
- **Music**: `FIELD_REEL_BGM=<file>` or `~/.cache/elanous-explainer/bgm/field.wav` — cut to length, faded in and out, leveled to about −16 LUFS. No file means no music. Use only licensed tracks (the default cache holds an Epidemic Sound download); the file is never committed.
|
|
99
101
|
- **Output**:
|
|
100
|
-
- `<folder>/reel/reel-9x16.mp4` at 1080×1920,
|
|
101
|
-
-
|
|
102
|
-
-
|
|
103
|
-
-
|
|
104
|
-
- **
|
|
102
|
+
- `<folder>/reel/reel-9x16.mp4` at 1080×1920, 24–60 s.
|
|
103
|
+
- An intro card (kicker, mark, title word by word, red rule, date), then each item: photos for about 4–6 s. A landscape photo is shown whole over a blurred fill of itself; a portrait photo fills the frame. Each move overlaps the next with a slide and a red wipe on the cut.
|
|
104
|
+
- Per item a counter (`01 / 03`), the caption rising word by word, and a red rule. The V6 header, an Icarus Red progress bar, and an end card with elanous.ai.
|
|
105
|
+
- `--part body` renders the photos only (no cards, no music) — for an instant version that stitches pre-rendered cards around it.
|
|
106
|
+
- **Fitting to length**: over 60 s drops items from the middle, keeping the first and last. Under 24 s stretches the photos.
|
|
107
|
+
- **Measured**: 3 photos → 24 s video in 28 s end to end, of which vision 8.7 s and render 15.9 s (M-series Mac, 10-01). Earlier: 7 items → 31.2 s video in 56–83 s.
|
|
105
108
|
- **Before posting**: people's faces need their consent. Run the same 1 fps OCR check as any public video.
|
|
106
109
|
|
|
107
110
|
## Graph
|
|
@@ -114,4 +117,4 @@ zsh $SKILL/engine/reel.sh <folder> --title "마케터의 밤" --sub "2026.10.02
|
|
|
114
117
|
- speech mismatch → vo;
|
|
115
118
|
- bad script → script;
|
|
116
119
|
- public release → needs-human.
|
|
117
|
-
- **
|
|
120
|
+
- **Recipes**: the `explainer-*` recipes are in the elanous core, but `graph run` does not yet accept this graph's terminal nodes. The video-explainer plugin therefore ships the skill and nodes only; run these steps from this skill. The graph comes with plugin 0.2.0.
|
|
@@ -22,7 +22,7 @@ const W = 1920, H = 1080, FPS = 30;
|
|
|
22
22
|
// ── palette (Elanvital CI · RCBC guide) ─────────────────────────────────────────
|
|
23
23
|
const C = { deep: "#1D2751", deep2: "#141C3D", black: "#000000", ink: "#F2F1EE", red: "#E95047", muted: "rgba(242,241,238,0.62)", line: "rgba(242,241,238,0.86)", faint: "rgba(242,241,238,0.10)" };
|
|
24
24
|
|
|
25
|
-
// ── mark (V6 «엘랑 소용돌이»,
|
|
25
|
+
// ── mark (V6 «엘랑 소용돌이», engine/brand/) ─────────────────────────────────────
|
|
26
26
|
const markSrc = readFileSync(process.env.ELANOUS_MARK ?? join(here, "brand", "elanous-mark-on-dark.svg"), "utf8");
|
|
27
27
|
const markPaths = [...markSrc.matchAll(/<path d="([^"]*)"/g)].map((m) => m[1]);
|
|
28
28
|
const mark = (size, blade = C.ink, dot = C.red, cls = "") =>
|