elanous 0.2.6 → 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 +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-2eba6079f31b88d4.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/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-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-10ed24f413ed66e9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-211810b6c93ab66a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-624f7a2f89b60c8f.js +1 -0
- 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-55d50e9b0006a91d.js +1 -0
- 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-37b8bf78a3c6eec5.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/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/package.json +5 -5
- 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 +24 -3
- 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 +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/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/env-known-failures.ts +31 -0
- package/scripts/release-loop/gate-node.ts +81 -19
- 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/unattended-release.ts +93 -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/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/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/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/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 +42 -1
- 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 +50 -11
- 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 +8 -3
- 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 +86 -14
- 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 +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 +61 -7
- 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 +16 -10
- 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 +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 +9 -8
- 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/session/chat.ts +17 -5
- 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-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 +38 -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 +80 -4
- 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 → keajW7DDyghEs1y8oJe6T}/_ssgManifest.js +0 -0
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 = "") =>
|
|
@@ -1,22 +1,29 @@
|
|
|
1
|
-
// Field reel — a folder of phone photos / short clips → one vertical
|
|
2
|
-
// Usage: node reel.mjs <folder> [--title "마케터의 밤"] [--sub "2026.10.02 · 서울"] [--max 60] [--min
|
|
1
|
+
// Field reel — a folder of phone photos / short clips → one vertical HyperFrames composition (EV10 · EV10b).
|
|
2
|
+
// Usage: node reel.mjs <folder> [--title "마케터의 밤"] [--sub "2026.10.02 · 서울"] [--max 60] [--min 24] [--part full|body]
|
|
3
3
|
// <folder>/captions.txt (optional): one line per item — `파일명 | 자막` (a line without « | » is ignored).
|
|
4
|
+
// Photos without a caption get one line from a vision model (codex · parallel · FIELD_REEL_VISION=0 turns it off);
|
|
5
|
+
// if that fails the caption is «현장 N». A slug-like title (`field-2026-10-01`) is replaced by a vision headline.
|
|
6
|
+
// BGM: FIELD_REEL_BGM=<wav/mp3> or ~/.cache/elanous-explainer/bgm/field.wav — cut to length, faded and leveled here.
|
|
7
|
+
// --part body: photos only, no intro/end cards and no music (the instant version stitches cached cards around it).
|
|
4
8
|
// Output: <folder>/reel/hf/ (HyperFrames project) ⊕ reel/timeline.json. Render: npx hyperframes render --fps 30 in reel/hf.
|
|
5
9
|
// Look = Elanvital CI: Real Black ground · Icarus Red single accent · V6 mark · Pretendard. Order = capture time (then name).
|
|
6
10
|
import { readFileSync, writeFileSync, mkdirSync, copyFileSync, existsSync, readdirSync, statSync } from "node:fs";
|
|
7
11
|
import { join, dirname, resolve, extname, basename } from "node:path";
|
|
8
12
|
import { homedir } from "node:os";
|
|
9
|
-
import { spawnSync } from "node:child_process";
|
|
13
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
10
14
|
import { fileURLToPath } from "node:url";
|
|
11
15
|
|
|
12
16
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
13
17
|
const argv = process.argv.slice(2);
|
|
14
18
|
const opt = (k, d) => { const i = argv.indexOf(`--${k}`); return i >= 0 ? argv[i + 1] : d; };
|
|
15
19
|
const folder = resolve(argv.find((a, i) => !a.startsWith("--") && !(i > 0 && argv[i - 1].startsWith("--"))) ?? ".");
|
|
16
|
-
const
|
|
17
|
-
const
|
|
20
|
+
const PART = opt("part", "full");
|
|
21
|
+
const MAX = Number(opt("max", 60)), MIN = Number(opt("min", 24));
|
|
22
|
+
const W = 1080, H = 1920, PHOTO = 4.2, CLIP_MAX = 5, XF = 0.5;
|
|
23
|
+
const INTRO = PART === "body" ? 0 : 3.0, END = PART === "body" ? 0 : 3.4;
|
|
18
24
|
const out = join(folder, "reel"), hf = join(out, "hf"), media = join(hf, "assets", "media");
|
|
19
25
|
mkdirSync(media, { recursive: true }); mkdirSync(join(hf, "assets", "fonts"), { recursive: true });
|
|
26
|
+
const T0 = Date.now(), lap = (s) => console.log(`reel: ${s} · ${((Date.now() - T0) / 1000).toFixed(1)}s`);
|
|
20
27
|
|
|
21
28
|
// ── collect & order ────────────────────────────────────────────────────────────
|
|
22
29
|
const IMG = [".jpg", ".jpeg", ".png", ".heic", ".webp"], VID = [".mp4", ".mov", ".m4v"];
|
|
@@ -41,9 +48,9 @@ if (!items.length) throw new Error(`no photos or clips in ${folder}`);
|
|
|
41
48
|
for (const it of items) it.dur = it.video ? Math.min(CLIP_MAX, Math.max(1.5, probeDur(it.p))) : PHOTO;
|
|
42
49
|
const body = () => items.reduce((a, it) => a + it.dur, 0);
|
|
43
50
|
while (INTRO + body() + END > MAX && items.length > 1) items.splice(Math.floor(items.length / 2), 1); // drop from the middle, keep first & last
|
|
44
|
-
if (INTRO + body() + END < MIN) { const photos = items.filter((i) => !i.video); const need = MIN - (INTRO + body() + END); for (const p of photos) p.dur += need / Math.max(1, photos.length); }
|
|
51
|
+
if (PART !== "body" && INTRO + body() + END < MIN) { const photos = items.filter((i) => !i.video); const need = MIN - (INTRO + body() + END); for (const p of photos) p.dur += need / Math.max(1, photos.length); }
|
|
45
52
|
|
|
46
|
-
// ── media prep: HEIC → jpg · big photos down to 2160 · clips re-encoded short & muted ──
|
|
53
|
+
// ── media prep: HEIC → jpg · big photos down to 2160 · a small copy for the vision model · clips re-encoded short & muted ──
|
|
47
54
|
let t = INTRO;
|
|
48
55
|
items.forEach((it, i) => {
|
|
49
56
|
it.start = +t.toFixed(3); t += it.dur;
|
|
@@ -55,10 +62,49 @@ items.forEach((it, i) => {
|
|
|
55
62
|
it.src = `m${i}.jpg`;
|
|
56
63
|
const r = run("sips", ["-s", "format", "jpeg", "-Z", "2160", it.p, "--out", join(media, it.src)]);
|
|
57
64
|
if (r.status !== 0) throw new Error(`photo ${it.f}: ${r.stderr.slice(0, 200)}`);
|
|
65
|
+
const dim = run("sips", ["-g", "pixelWidth", "-g", "pixelHeight", join(media, it.src)]).stdout;
|
|
66
|
+
const pw = Number(/pixelWidth: (\d+)/.exec(dim)?.[1]), ph = Number(/pixelHeight: (\d+)/.exec(dim)?.[1]);
|
|
67
|
+
it.wide = pw > 0 && ph > 0 && pw / ph > 0.75; // anything wider than 3:4 is shown whole over a blurred fill
|
|
68
|
+
run("sips", ["-s", "format", "jpeg", "-Z", "1024", it.p, "--out", join(out, `v${i}.jpg`)]);
|
|
58
69
|
}
|
|
59
|
-
it.cap = caps[it.f] ?? "";
|
|
60
70
|
});
|
|
61
71
|
const TOTAL = +(t + END).toFixed(3);
|
|
72
|
+
lap(`${items.length} items prepared`);
|
|
73
|
+
|
|
74
|
+
// ── vision: one caption line per photo, in parallel; a headline when the title is only a slug ──
|
|
75
|
+
const CODEX = process.env.FIELD_REEL_CODEX_BIN || "codex";
|
|
76
|
+
const VISION = process.env.FIELD_REEL_VISION !== "0";
|
|
77
|
+
const ask = (prompt, images, ms = 30000) => new Promise((done) => {
|
|
78
|
+
const args = ["exec", "--skip-git-repo-check", "-c", "model_reasoning_effort=low", prompt, ...images.flatMap((im) => ["-i", im])];
|
|
79
|
+
let buf = "", settled = false;
|
|
80
|
+
const finish = (v) => { if (!settled) { settled = true; done(v); } };
|
|
81
|
+
let child;
|
|
82
|
+
try { child = spawn(CODEX, args, { stdio: ["ignore", "pipe", "ignore"] }); } catch { return finish(""); }
|
|
83
|
+
const timer = setTimeout(() => { child.kill("SIGKILL"); finish(""); }, ms);
|
|
84
|
+
child.stdout.on("data", (d) => { buf += d; });
|
|
85
|
+
child.on("error", () => { clearTimeout(timer); finish(""); });
|
|
86
|
+
child.on("close", (code) => { clearTimeout(timer); finish(code === 0 ? buf : ""); });
|
|
87
|
+
});
|
|
88
|
+
const oneLine = (s, max) => {
|
|
89
|
+
const line = s.split("\n").map((l) => l.trim().replace(/^["'«“]|["'»”]$/g, "")).filter(Boolean).pop() ?? "";
|
|
90
|
+
return line.length >= 2 && line.length <= max ? line.replace(/[.。]$/, "") : "";
|
|
91
|
+
};
|
|
92
|
+
const CAP_PROMPT = "행사 현장 사진이다. 세로 영상 자막으로 쓸 한국어 한 줄을 출력하라 — 18자 이내 · 마침표 없이 · 보이는 장면만 · 사람 이름·외모 묘사·추측 금지 · 화면 속 글자(행사명·회사명)는 읽히면 써도 된다. 그 한 줄만.";
|
|
93
|
+
const HEAD_PROMPT = "같은 행사에서 찍은 사진들이다. 영상 첫 화면 제목으로 쓸 한국어 행사 이름 한 줄을 출력하라 — 16자 이내 · 화면 속 글자에서 읽히는 행사·회사 이름을 우선 · 읽히지 않으면 장면을 짧게(예: «상장 기념식 현장») · 추측 금지. 그 한 줄만.";
|
|
94
|
+
const slugTitle = (s) => !s || /^[a-z0-9]+(?:-[a-z0-9]+)*$/i.test(s);
|
|
95
|
+
let TITLE = opt("title", ""), SUB = opt("sub", "");
|
|
96
|
+
const photos = items.filter((it) => !it.video);
|
|
97
|
+
const VT0 = Date.now();
|
|
98
|
+
const jobs = VISION ? [
|
|
99
|
+
...photos.filter((it) => !caps[it.f]).map(async (it) => { it.vcap = oneLine(await ask(CAP_PROMPT, [join(out, `v${items.indexOf(it)}.jpg`)]), 22); }),
|
|
100
|
+
...(PART !== "body" && slugTitle(TITLE) && photos.length ? [(async () => { TITLE = oneLine(await ask(HEAD_PROMPT, photos.slice(0, 3).map((it) => join(out, `v${items.indexOf(it)}.jpg`))), 20) || TITLE; })()] : []),
|
|
101
|
+
] : [];
|
|
102
|
+
await Promise.all(jobs);
|
|
103
|
+
const visionMs = Date.now() - VT0;
|
|
104
|
+
items.forEach((it, i) => { it.cap = caps[it.f] || it.vcap || (it.video ? "" : `현장 ${i + 1}`); });
|
|
105
|
+
if (slugTitle(TITLE)) TITLE = "현장 스케치";
|
|
106
|
+
if (!SUB) { const d = new Date(items[0].t + 9 * 3600e3); SUB = `${d.getUTCFullYear()}.${String(d.getUTCMonth() + 1).padStart(2, "0")}.${String(d.getUTCDate()).padStart(2, "0")}`; }
|
|
107
|
+
lap(`vision ${jobs.length ? `${items.filter((i) => i.vcap).length}/${photos.filter((p) => !caps[p.f]).length} captions` : "off"} · title «${TITLE}»`);
|
|
62
108
|
|
|
63
109
|
// ── assets (same cache as build.mjs) ───────────────────────────────────────────
|
|
64
110
|
const CACHE = process.env.EXPLAINER_CACHE ?? join(homedir(), ".cache", "elanous-explainer");
|
|
@@ -69,54 +115,114 @@ for (const [rel, url] of Object.entries(ASSETS)) {
|
|
|
69
115
|
if (!existsSync(c)) { const res = await fetch(url); if (!res.ok) throw new Error(`asset ${rel}: HTTP ${res.status}`); mkdirSync(dirname(c), { recursive: true }); writeFileSync(c, Buffer.from(await res.arrayBuffer())); }
|
|
70
116
|
copyFileSync(c, join(hf, "assets", rel));
|
|
71
117
|
}
|
|
118
|
+
|
|
119
|
+
// ── music: cut to length · fade in/out · level to about -16 LUFS (the whole mix is music) ──
|
|
120
|
+
const BGM = process.env.FIELD_REEL_BGM || join(CACHE, "bgm", "field.wav");
|
|
121
|
+
let music = "";
|
|
122
|
+
if (PART !== "body" && existsSync(BGM)) {
|
|
123
|
+
const r = run("ffmpeg", ["-v", "error", "-y", "-i", BGM, "-t", String(TOTAL), "-af", `afade=t=in:d=0.4,afade=t=out:st=${f(TOTAL - 1.6)}:d=1.6,loudnorm=I=-16:TP=-1.5:LRA=9`, "-ar", "48000", "-ac", "2", join(media, "bgm.wav")]);
|
|
124
|
+
if (r.status === 0) music = "bgm.wav"; else console.error(`reel: music skipped — ${r.stderr.slice(0, 160)}`);
|
|
125
|
+
}
|
|
126
|
+
function f(x) { return +x.toFixed(3); }
|
|
127
|
+
|
|
72
128
|
const markPaths = [...readFileSync(join(here, "brand", "elanous-mark-on-dark.svg"), "utf8").matchAll(/<path d="([^"]*)"/g)].map((m) => m[1]);
|
|
73
129
|
const mark = (s, cls = "") => `<svg class="${cls}" width="${s}" height="${s}" viewBox="150 150 724 724">${markPaths.map((d) => `<path d="${d}" fill="#F2F1EE"/>`).join("")}<circle cx="512" cy="512" r="76" fill="#E95047"/></svg>`;
|
|
74
130
|
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<");
|
|
75
|
-
const
|
|
131
|
+
const words = (s, cls) => s.split(/\s+/).filter(Boolean).map((w) => `<span class="${cls}">${esc(w)}</span>`).join(" ");
|
|
76
132
|
|
|
77
133
|
// ── composition ────────────────────────────────────────────────────────────────
|
|
134
|
+
// Each photo: blurred fill + the whole photo (wide) or a full-bleed crop (tall) · slow push · a cross-move into the next ·
|
|
135
|
+
// a red wipe on the cut · counter «0N / 0M» · caption words rising one by one.
|
|
78
136
|
const tw = [];
|
|
137
|
+
const n = items.length;
|
|
79
138
|
const clips = items.map((it, i) => {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (
|
|
84
|
-
|
|
85
|
-
if (it.
|
|
139
|
+
const s = it.start, d = it.dur, last = i === n - 1;
|
|
140
|
+
const span = last ? d : d + XF; // overlap the next item so the move reads as one motion
|
|
141
|
+
let vis;
|
|
142
|
+
if (it.video) {
|
|
143
|
+
vis = `<video id="v${i}" class="clip med" src="assets/media/${it.src}" muted playsinline data-start="${f(s)}" data-duration="${f(d)}" data-track-index="1"></video>`;
|
|
144
|
+
} else if (it.wide) {
|
|
145
|
+
vis = `<div class="clip med" data-start="${f(s)}" data-duration="${f(span)}" data-track-index="${1 + (i % 2)}"><div class="g g${i}">`
|
|
146
|
+
+ `<img class="fill" src="assets/media/${it.src}"/><div class="dim"></div>`
|
|
147
|
+
+ `<div class="frame fr${i}"><img src="assets/media/${it.src}"/></div></div></div>`;
|
|
148
|
+
tw.push(`tl.fromTo(".fr${i}", {scale:1.0, x:${i % 2 ? 30 : -30}}, {scale:1.12, x:${i % 2 ? -30 : 30}, duration:${f(span)}, ease:"none"}, ${f(s)});`);
|
|
149
|
+
tw.push(`tl.fromTo(".g${i} .fill", {scale:1.25}, {scale:1.4, duration:${f(span)}, ease:"none"}, ${f(s)});`);
|
|
150
|
+
} else {
|
|
151
|
+
vis = `<div class="clip med" data-start="${f(s)}" data-duration="${f(span)}" data-track-index="${1 + (i % 2)}"><div class="g g${i}"><img class="kb k${i}" src="assets/media/${it.src}"/></div></div>`;
|
|
152
|
+
tw.push(`tl.fromTo(".k${i}", {scale:1.02, x:0}, {scale:1.12, x:${i % 2 ? -28 : 28}, duration:${f(span)}, ease:"none"}, ${f(s)});`);
|
|
153
|
+
}
|
|
154
|
+
if (!it.video && i > 0) tw.push(`tl.fromTo(".g${i}", {opacity:0, xPercent:${i % 2 ? 14 : -14}}, {opacity:1, xPercent:0, duration:${XF}, ease:"power3.out"}, ${f(s - XF)});`);
|
|
155
|
+
if (i > 0) tw.push(`tl.fromTo(".wipe", {scaleY:0, transformOrigin:"50% 100%"}, {scaleY:1, duration:${XF / 2}, ease:"power2.in", immediateRender:false}, ${f(s - XF / 2)});`
|
|
156
|
+
+ `tl.to(".wipe", {scaleY:0, transformOrigin:"50% 0%", duration:${XF / 2}, ease:"power2.out"}, ${f(s)});`);
|
|
157
|
+
const no = `${String(i + 1).padStart(2, "0")} <i>/ ${String(n).padStart(2, "0")}</i>`;
|
|
158
|
+
const cap = it.cap ? `<div class="clip capw" data-start="${f(s)}" data-duration="${f(d)}" data-track-index="4"><div class="no n${i}">${no}</div><div class="cap c${i}">${words(it.cap, `w w${i}`)}</div><div class="rule r${i}"></div></div>` : "";
|
|
159
|
+
if (it.cap) {
|
|
160
|
+
tw.push(`tl.fromTo(".n${i}", {opacity:0, x:-24}, {opacity:1, x:0, duration:0.35, ease:"power3.out"}, ${f(s + 0.2)});`);
|
|
161
|
+
tw.push(`tl.fromTo(".r${i}", {scaleX:0}, {scaleX:1, duration:0.5, ease:"power3.out"}, ${f(s + 0.25)});`);
|
|
162
|
+
tw.push(`tl.fromTo(".w${i}", {opacity:0, y:46}, {opacity:1, y:0, duration:0.45, stagger:0.09, ease:"power3.out"}, ${f(s + 0.35)});`);
|
|
163
|
+
if (!last) tw.push(`tl.to(".c${i}, .n${i}, .r${i}", {opacity:0, duration:0.25}, ${f(s + d - 0.3)});`);
|
|
164
|
+
}
|
|
86
165
|
return vis + cap;
|
|
87
166
|
}).join("\n");
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
167
|
+
|
|
168
|
+
const intro = PART === "body" ? "" : `<section class="clip card" data-start="0" data-duration="${INTRO}" data-track-index="6">`
|
|
169
|
+
+ `<div class="kick ik">현장 스케치</div>${mark(200, "im")}<h1>${words(TITLE, "it")}</h1><div class="line il"></div><p class="is">${esc(SUB)}</p></section>`;
|
|
170
|
+
const end = PART === "body" ? "" : `<section class="clip card" data-start="${f(t)}" data-duration="${END}" data-track-index="6">`
|
|
171
|
+
+ `${mark(230, "em")}<h1 class="et">Elanous</h1><p class="et">사진을 올리면, 영상이 됩니다</p><p class="et red">elanous.ai</p></section>`;
|
|
172
|
+
if (PART !== "body") {
|
|
173
|
+
tw.push(`tl.fromTo(".ik", {opacity:0, letterSpacing:"0.6em"}, {opacity:1, letterSpacing:"0.28em", duration:0.8, ease:"power3.out"}, 0.1);`);
|
|
174
|
+
tw.push(`tl.fromTo(".im", {rotation:-160, scale:0.6, opacity:0}, {rotation:0, scale:1, opacity:1, duration:1.0, ease:"power3.out"}, 0.0);`);
|
|
175
|
+
tw.push(`tl.fromTo(".it", {opacity:0, y:50}, {opacity:1, y:0, duration:0.55, stagger:0.1, ease:"power3.out"}, 0.45);`);
|
|
176
|
+
tw.push(`tl.fromTo(".il", {scaleX:0}, {scaleX:1, duration:0.6, ease:"power3.inOut"}, 0.9);`);
|
|
177
|
+
tw.push(`tl.fromTo(".is", {opacity:0, y:16}, {opacity:1, y:0, duration:0.45, ease:"power3.out"}, 1.15);`);
|
|
178
|
+
tw.push(`tl.fromTo(".em", {rotation:-200, scale:0.5}, {rotation:0, scale:1, duration:1.2, ease:"power3.out"}, ${f(t)});`);
|
|
179
|
+
tw.push(`tl.fromTo(".et", {opacity:0, y:20}, {opacity:1, y:0, duration:0.5, stagger:0.14, ease:"power3.out"}, ${f(t + 0.5)});`);
|
|
180
|
+
}
|
|
181
|
+
tw.unshift(`tl.set(".wipe", {scaleY:0}, 0);`);
|
|
92
182
|
tw.push(`tl.fromTo(".bar i", {scaleX:0}, {scaleX:1, duration:${TOTAL}, ease:"none"}, 0);`);
|
|
183
|
+
const audio = music ? `<audio id="bgm" src="assets/media/${music}" data-start="0" data-duration="${TOTAL}" data-track-index="9"></audio>` : "";
|
|
93
184
|
const html = `<!doctype html><html lang="ko"><head><meta charset="UTF-8"/><meta name="viewport" content="width=${W}, height=${H}"/><title>${esc(TITLE)}</title><script src="assets/gsap.min.js"></script><style>
|
|
94
185
|
@font-face{font-family:"Pretendard";src:url("assets/fonts/Pretendard-Bold.woff2") format("woff2");font-weight:700}
|
|
95
186
|
@font-face{font-family:"Pretendard";src:url("assets/fonts/Pretendard-ExtraBold.woff2") format("woff2");font-weight:800}
|
|
96
187
|
@font-face{font-family:"Pretendard";src:url("assets/fonts/Pretendard-Black.woff2") format("woff2");font-weight:900}
|
|
97
188
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
98
189
|
#root{position:relative;width:100%;height:100%;overflow:hidden;background:#000;font-family:"Pretendard",sans-serif;color:#F2F1EE;word-break:keep-all}
|
|
99
|
-
.med{position:absolute;inset:0;width:100%;height:100%;
|
|
190
|
+
.med{position:absolute;inset:0;width:100%;height:100%;overflow:hidden}
|
|
191
|
+
.g{position:absolute;inset:0;overflow:hidden}
|
|
100
192
|
.kb{width:100%;height:100%;object-fit:cover;display:block}
|
|
101
|
-
.
|
|
193
|
+
.fill{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;filter:blur(42px) saturate(1.2);opacity:.85}
|
|
194
|
+
.dim{position:absolute;inset:0;background:rgba(0,0,0,.42)}
|
|
195
|
+
.frame{position:absolute;left:0;right:0;top:420px;height:960px;overflow:hidden;box-shadow:0 30px 80px rgba(0,0,0,.55)}
|
|
196
|
+
.frame img{width:100%;height:100%;object-fit:cover;display:block}
|
|
197
|
+
.shade{z-index:3;position:absolute;left:0;right:0;bottom:0;height:42%;background:linear-gradient(to top,rgba(0,0,0,.82),rgba(0,0,0,0));pointer-events:none}
|
|
102
198
|
.top{z-index:5;position:absolute;left:56px;right:56px;top:72px;display:flex;align-items:center;gap:18px;font-weight:800;font-size:34px;text-shadow:0 2px 12px rgba(0,0,0,.6)}
|
|
103
199
|
.top b{color:#E95047;font-weight:800}
|
|
104
|
-
.capw{z-index:4;position:absolute;left:64px;right:64px;bottom:
|
|
105
|
-
.
|
|
106
|
-
.
|
|
107
|
-
.
|
|
108
|
-
.
|
|
200
|
+
.capw{z-index:4;position:absolute;left:64px;right:64px;bottom:210px}
|
|
201
|
+
.no{font-weight:800;font-size:34px;letter-spacing:.12em;color:#E95047}
|
|
202
|
+
.no i{font-style:normal;color:rgba(242,241,238,.55)}
|
|
203
|
+
.cap{margin-top:18px;font-weight:900;font-size:76px;line-height:1.18;letter-spacing:-0.03em;text-shadow:0 3px 18px rgba(0,0,0,.7)}
|
|
204
|
+
.w{display:inline-block}
|
|
205
|
+
.rule{margin-top:26px;width:120px;height:8px;background:#E95047;transform-origin:0 50%}
|
|
206
|
+
.wipe{z-index:8;position:absolute;inset:0;background:#E95047}
|
|
207
|
+
.card{z-index:7;position:absolute;inset:0;background:#000;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:0 80px}
|
|
208
|
+
.kick{margin-bottom:56px;font-weight:800;font-size:32px;letter-spacing:.28em;color:#E95047}
|
|
209
|
+
.card h1{margin-top:56px;font-weight:900;font-size:100px;line-height:1.12;letter-spacing:-0.03em}
|
|
210
|
+
.card h1 span{display:inline-block}
|
|
211
|
+
.line{margin-top:40px;width:160px;height:8px;background:#E95047}
|
|
212
|
+
.card p{margin-top:28px;font-weight:700;font-size:42px;color:rgba(242,241,238,.66)}
|
|
109
213
|
.card .red{color:#E95047}
|
|
110
|
-
.bar{z-index:
|
|
214
|
+
.bar{z-index:9;position:absolute;left:0;right:0;bottom:0;height:10px;background:rgba(242,241,238,.12)}
|
|
111
215
|
.bar i{position:absolute;inset:0;background:#E95047;transform-origin:0 50%;display:block}
|
|
112
216
|
</style></head><body>
|
|
113
217
|
<div id="root" data-composition-id="main" data-start="0" data-width="${W}" data-height="${H}" data-duration="${TOTAL}">
|
|
114
|
-
|
|
218
|
+
${intro}
|
|
115
219
|
${clips}
|
|
116
220
|
<div class="shade"></div>
|
|
117
221
|
<div class="top">${mark(60)}<span>Elanous <b>·</b> ${esc(TITLE)}</span></div>
|
|
118
|
-
<
|
|
222
|
+
<div class="wipe"></div>
|
|
223
|
+
${end}
|
|
119
224
|
<div class="bar"><i></i></div>
|
|
225
|
+
${audio}
|
|
120
226
|
</div>
|
|
121
227
|
<script>const tl = gsap.timeline({ paused: true });
|
|
122
228
|
${tw.join("\n")}
|
|
@@ -124,5 +230,6 @@ window.__timelines["main"] = tl;</script></body></html>`;
|
|
|
124
230
|
writeFileSync(join(hf, "index.html"), html);
|
|
125
231
|
writeFileSync(join(hf, "hyperframes.json"), JSON.stringify({ $schema: "https://hyperframes.heygen.com/schema/hyperframes.json", paths: { assets: "assets" }, media: { autoProxy: true } }, null, 2) + "\n");
|
|
126
232
|
writeFileSync(join(hf, "meta.json"), JSON.stringify({ id: "reel", name: "reel" }, null, 2) + "\n");
|
|
127
|
-
writeFileSync(join(out, "timeline.json"), JSON.stringify({ total: TOTAL,
|
|
128
|
-
|
|
233
|
+
writeFileSync(join(out, "timeline.json"), JSON.stringify({ total: TOTAL, part: PART, title: TITLE, sub: SUB, music: Boolean(music),
|
|
234
|
+
vision: { enabled: VISION, calls: jobs.length, captioned: items.filter((i) => i.vcap).length, fallback: items.filter((i) => !i.video && !caps[i.f] && !i.vcap).length, ms: visionMs }, items: items.map(({ f: file, video, start, dur, cap, vcap }) => ({ file, video, start, dur: f(dur), cap, vision: Boolean(vcap) })) }, null, 2));
|
|
235
|
+
console.log(`reel: ${items.length} items · ${TOTAL}s · ${items.filter((i) => i.vcap).length} vision captions · music ${music ? "on" : "off"} → ${hf}`);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/bin/zsh
|
|
2
2
|
# Field reel in one command: folder of phone photos/clips → <folder>/reel/<name>-9x16.mp4 · prints the elapsed seconds.
|
|
3
|
+
# The layout/contrast check is advisory: phone photos behind captions often miss 3:1, and a failed check must not stop the reel.
|
|
3
4
|
# Usage: zsh reel.sh <folder> [--title "…"] [--sub "…"] (captions: <folder>/captions.txt — `파일 | 자막`)
|
|
4
5
|
E=${0:A:h}; F=${1:A}; shift
|
|
5
6
|
[[ -d $F ]] || { print -u2 "reel: no folder $F"; exit 2 }
|
|
6
7
|
T0=$(date +%s)
|
|
7
|
-
rm -rf $F/reel && node $E/reel.mjs $F "$@" && (cd $F/reel/hf && npx -y hyperframes@${HYPERFRAMES_VERSION:-0.8.95} check >../render.log 2>&1
|
|
8
|
+
rm -rf $F/reel && node $E/reel.mjs $F "$@" && (cd $F/reel/hf && { npx -y hyperframes@${HYPERFRAMES_VERSION:-0.8.95} check >../render.log 2>&1 || print "reel: check reported issues (continuing — user photos vary; see above)" >>../render.log }; npx -y hyperframes@${HYPERFRAMES_VERSION:-0.8.95} render --fps 30 --workers 4 --output ../reel-9x16.mp4 >>../render.log 2>&1) || { print -u2 "reel: FAILED — see $F/reel/render.log"; exit 1 }
|
|
8
9
|
print "reel: $F/reel/reel-9x16.mp4 · $(( $(date +%s) - T0 ))s"
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"name": "Seulki",
|
|
6
6
|
"model": "eleven_v3"
|
|
7
7
|
},
|
|
8
|
-
"sources": "
|
|
8
|
+
"sources": "elanous 0.2.5 release notes — https://github.com/ElanvitalAI/elanous/releases/tag/v0.2.5",
|
|
9
9
|
"intro": {
|
|
10
10
|
"beats": [
|
|
11
11
|
{
|
package/skills/grill-me/SKILL.md
CHANGED
|
@@ -123,7 +123,7 @@ requires: []
|
|
|
123
123
|
| 다음 | 언제 | 어떻게 |
|
|
124
124
|
|---|---|---|
|
|
125
125
|
| 🧹 **`first-principles`** | 요구가 많이 나왔을 때 | ⭐ ***캐낸 다음엔 «지운다»*** — 5단계 ①의심 ②삭제 가 순서상 여기다 |
|
|
126
|
-
| 🎯 **골 문면** (
|
|
126
|
+
| 🎯 **골 문면** (골 파일) | 하니스가 구현할 일일 때 | 카드의 `S`·`C`·`Q` 가 그대로 ask 의 세 칸이 된다 |
|
|
127
127
|
| 📄 **문서** (`docs/`) | 결정을 남길 일일 때 | `## ✅ 정해진 것` 표가 그대로 결정 기록이다 |
|
|
128
128
|
| 🧑🤝🧑 **팀 짜기** | 일이 여럿으로 갈릴 때 | ⭐ 목표를 심은 뒤 *"이 일을 할 팀을 짜 보라"* — 인터뷰의 «응용»이다 |
|
|
129
129
|
|
|
@@ -14,7 +14,7 @@ flowchart TD
|
|
|
14
14
|
H{nonCodeOutcome exists?}:::gate
|
|
15
15
|
I[observe deploy-no-changes<br/>return { ok:false, kind:none }]:::fail
|
|
16
16
|
J{Evidence is artifact-created?}:::gate
|
|
17
|
-
K{
|
|
17
|
+
K{2026-08-13<br/>existsSync(resolve(cwd, ref))?}:::gate
|
|
18
18
|
L[observe deploy-noncode-blocked<br/>reason: artifact-created-ref-missing<br/>then deploy-no-changes]:::fail
|
|
19
19
|
M[Do not treat ref as a file<br/>published-url / execution-completed]:::data
|
|
20
20
|
N[observe deploy-noncode<br/>return terminal:<br/>published or executed]:::pass
|
package/src/a2a/server.ts
CHANGED
|
@@ -91,6 +91,8 @@ export function startA2AServer(opts: A2AServerOptions): Bun.Server<undefined> {
|
|
|
91
91
|
history ??= new DaemonSessionHistory();
|
|
92
92
|
const result = await (opts.runDaemonTurn ?? runDaemonPromptTurn)({
|
|
93
93
|
history,
|
|
94
|
+
// A peer agent delegates work, not small talk: never drop its tools for a short message (#22430 fast path).
|
|
95
|
+
fastPathEnabled: false,
|
|
94
96
|
request: {
|
|
95
97
|
sessionId: contextId,
|
|
96
98
|
userText: text,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ClientCapabilities } from '@agentclientprotocol/sdk';
|
|
2
|
+
|
|
3
|
+
/** The server's sessionPeers registry remains the source of truth for which
|
|
4
|
+
* connections are attached to a session. Awareness belongs to the connection,
|
|
5
|
+
* not the session: a plain client can load an aware client's session. */
|
|
6
|
+
export class ElanousAwarePeers<Peer extends object> {
|
|
7
|
+
private readonly aware = new WeakSet<Peer>();
|
|
8
|
+
|
|
9
|
+
constructor(private readonly sessionPeers: ReadonlyMap<string, ReadonlySet<Peer>>) {}
|
|
10
|
+
|
|
11
|
+
markPeer(connectionOrSessionId: Peer, clientCapabilities: ClientCapabilities | undefined | null): void {
|
|
12
|
+
const elanous = (clientCapabilities?._meta as Record<string, unknown> | undefined)?.elanous;
|
|
13
|
+
if (elanous !== null && typeof elanous === 'object' && !Array.isArray(elanous)) {
|
|
14
|
+
this.aware.add(connectionOrSessionId);
|
|
15
|
+
} else {
|
|
16
|
+
this.aware.delete(connectionOrSessionId);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
isPeerAware(peer: Peer): boolean {
|
|
21
|
+
return this.aware.has(peer);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
isElanousAware(sessionId: string): boolean {
|
|
25
|
+
for (const peer of this.sessionPeers.get(sessionId) ?? []) {
|
|
26
|
+
if (this.isPeerAware(peer)) return true;
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|