elanous 0.2.4 → 0.2.6
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/README.md +7 -6
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/1169-696263ac7af8c4d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1883.c91a2648af4842fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1947-9d64872eeb9cfdec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2808-dc06482994b40f70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 2984-977cfa339fcd65a5.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/3165-95515ca5f299a120.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8142-011e1f474efb8b33.js → 3190-e9282753e7ebde85.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3995-bee512a7063036ce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 4240-7153881b0b377306.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4259-99614149577e77cb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4283-329eabeaf233e6cd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4328-cb5058b1acc2de5e.js +25 -0
- package/apps/pwa/out/_next/static/chunks/4801-ecee6ef62b349b18.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5110-8058791dd25a2e14.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3403.4bc97b4066945a94.js → 5329.8b7e4bca684b6eec.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/5330-c41e9670f594c38f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5423-e95fd125d1794349.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5478-b5eae07dc79ea619.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5787-96ee6642b9a661e5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6448-79d72368589f98bc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6522-9866c002b5531836.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6729-b4a558b7aa891e0c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6969-cb03f9410b7d2e03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7007-3b9861091bb8dd21.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5791-bb82743fa6299562.js → 763-7d0e1913b5074e98.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 7633.0654fe6ea259c1ee.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7930-8dc7e20fc6fc19ca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8071-187edbc22a86741f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8198-d4101ad70cb9a579.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8250.4a2c5553a07cdf47.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8293-123b91e7b0330dc0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8296-d6d5d87dfa911d41.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8305-603963c33ee8eec3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8612-4a0066f20951b8fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2ea96958-e8c7e112de50b067.js → 8d0d9faf-97887ff99713b84e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9181-cf4e1b61c4783f92.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → 91ff6dda-3b33d42d20657429.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9262-26c2ecbd5a67e4a2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9328-45abdebce4fbecb7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9355-dbc67205adf8745f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9763.255b90dd69875689.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9933-66d31ec25c0231af.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-bd06fb55f4da0381.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-4e586142da455a25.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-889ad5cddcf45b0d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-47ba401d56572c27.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-0b54f9c83458e038.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-4fcc296ef6c8ed90.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-fe9fda494a87f4e5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-f9642e25ce129590.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-e1063e8ba5f3e2e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-9d0df30027d8aa9d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-bd06fb55f4da0381.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-d97adf2c9455b8d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-236b80d202110362.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-21a30f35f1a276ad.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-b1a5be357c4fa25d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1705a60303f7f573.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-76b337c11ad0e237.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-7c0f85fbb56f64c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-65ca16b20617b44d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-e7f9b8fc29411c1d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-92ea6213d1aeec04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a913a005aacb5476.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-2d78804a40dfdef6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-08a99148e780ac00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-deb674e7ed36f815.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-49f8de0534d192d7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-bd06fb55f4da0381.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-ba162a28d2176e18.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-1ff1a37b550ab41a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-ccaa698f56c0fbdb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-53b944082de1d7db.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-9b70adf1552789bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-30d482aa63241fe8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-aecf786f54651d8a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6ab9b62c7b6e08f2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-08949735455e0c9a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-a1479005234a46c2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3890e90bd232732.js +1 -0
- package/apps/pwa/out/_next/static/chunks/beb5cf10-9adcc10b83930090.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-d68d41c88e05c801.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-11c63971b7a9593b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-d09452353bd0d9cc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-184d727cebeca767.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-21bbbbac99b720b1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{webpack-e5bea36f183b25e6.js → webpack-37b8bf78a3c6eec5.js} +1 -1
- package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +1 -0
- package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → qMI0jnlZaBjjm4aIDYNGM}/_buildManifest.js +1 -1
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +29 -27
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +29 -27
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +29 -27
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +26 -24
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +26 -24
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +26 -24
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +29 -27
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +29 -27
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +29 -27
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/icons/elanous-16.png +0 -0
- package/apps/pwa/out/icons/elanous-180.png +0 -0
- package/apps/pwa/out/icons/elanous-192.png +0 -0
- package/apps/pwa/out/icons/elanous-32.png +0 -0
- package/apps/pwa/out/icons/elanous-512.png +0 -0
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +26 -24
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +29 -27
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +29 -27
- package/apps/pwa/out/manifest.webmanifest +5 -2
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +26 -24
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +26 -24
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +29 -27
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +29 -27
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +27 -25
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +29 -27
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +26 -24
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +29 -27
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +29 -27
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +29 -27
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +29 -27
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +29 -27
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +26 -24
- package/apps/pwa/out/sw-strategies.js +44 -0
- package/apps/pwa/out/sw.js +6 -44
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +29 -27
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +29 -27
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +29 -27
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +29 -27
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +29 -27
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +29 -27
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +29 -27
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +29 -27
- package/catalog/resources.yaml +6 -6
- package/docs/goal-context/steward-directives-2026-09-28.md +35 -37
- package/graphs/landing/landing-heal.yaml +1 -1
- package/graphs/release/recipes.yaml +11 -1
- package/graphs/release/release-loop.yaml +9 -2
- package/graphs/video/README.md +1 -0
- package/graphs/video/explainer-line.yaml +58 -0
- package/package.json +3 -1
- package/plugins/card-followup/README.md +33 -0
- package/plugins/card-followup/examples/card-sample.json +7 -0
- package/plugins/card-followup/graphs/card-followup.yaml +16 -0
- package/plugins/card-followup/graphs/recipes.yaml +12 -0
- package/plugins/card-followup/graphs/run-step.ts +172 -0
- package/plugins/card-followup/plugin.json +25 -0
- package/plugins/geo-check/README.md +13 -0
- package/plugins/geo-check/examples/brand-sample.json +8 -0
- package/plugins/geo-check/graphs/geo-check.yaml +20 -0
- package/plugins/geo-check/graphs/recipes.yaml +18 -0
- package/plugins/geo-check/graphs/run-step.ts +177 -0
- package/plugins/geo-check/plugin.json +24 -0
- package/plugins/job-coach/.codex-plugin/plugin.json +18 -1
- package/plugins/job-coach/README.md +42 -0
- package/plugins/job-coach/connectors/ncs/server.ts +4 -3
- package/plugins/job-coach/graphs/gap-judge.ts +44 -0
- package/plugins/job-coach/graphs/recipes.yaml +27 -0
- package/plugins/job-coach/graphs/report-enterprise.yaml +28 -0
- package/plugins/job-coach/graphs/run-step.ts +211 -17
- package/plugins/job-coach/plugin.json +16 -3
- package/plugins/job-coach/skills/enterprise-ai-fit/SKILL.md +8 -0
- package/plugins/job-coach/skills/enterprise-competency/SKILL.md +8 -0
- package/plugins/job-coach/skills/enterprise-needs/SKILL.md +8 -0
- package/plugins/job-coach/skills/enterprise-roadmap/SKILL.md +8 -0
- package/plugins/job-coach/skills/enterprise-task-analysis/SKILL.md +8 -0
- package/plugins/job-coach/skills/interview-to-profile/SKILL.md +18 -1
- package/scripts/backup/elanous-backup.sh +4 -1
- package/scripts/build-docs-site.ts +4 -3
- package/scripts/coord-channel-watch.sh +23 -4
- package/scripts/coord-post.sh +49 -12
- package/scripts/coord-tracks.json +5 -5
- package/scripts/daemon-port-baseline.txt +1 -2
- package/scripts/eval-bot-vs-grok.ts +79 -0
- package/scripts/eval-grok-live-baseline.ts +36 -0
- package/scripts/fixtures/front-route-corpus.sample.json +17 -0
- package/scripts/fleet/node-setup.sh +46 -7
- package/scripts/landing-heal/must-fix-match.ts +26 -0
- package/scripts/landing-heal/run-mission.ts +114 -20
- package/scripts/lib/android-test-device.ts +32 -0
- package/scripts/lib/front-route-measurement.ts +131 -0
- package/scripts/lib/tui-sim-keys.ts +42 -0
- package/scripts/measure-front-route-classifier.ts +56 -0
- package/scripts/media/ocr-leak-scan.sh +21 -0
- package/scripts/public-export.ts +2 -3
- package/scripts/public-import-pr.ts +80 -6
- package/scripts/publish-internal-dist.ts +2 -3
- package/scripts/red-sweep-diff.ts +1 -1
- package/scripts/release-loop/auto-approve-node.ts +72 -0
- package/scripts/release-loop/checklist-gate-node.ts +35 -0
- package/scripts/release-loop/gate-diff.ts +29 -0
- package/scripts/release-loop/gate-node.ts +366 -45
- package/scripts/release-loop/publish-node.ts +7 -3
- package/scripts/release-loop/release-docs.ts +20 -13
- package/scripts/release-loop/shard-plan.ts +47 -0
- package/scripts/release-loop/test-durations.ts +63 -0
- package/scripts/release-loop/upgrade-node.ts +3 -2
- package/scripts/test-audit-coverage-per-file.sh +16 -0
- package/scripts/test-audit-meaningless.ts +88 -0
- package/scripts/test-audit-overlap-and-weak.ts +73 -0
- package/scripts/test-deterministic.ts +27 -5
- package/scripts/tui-sim.ts +59 -14
- package/scripts/ux/bump-installed.sh +20 -0
- package/scripts/ux/lib/cdp.ts +72 -0
- package/scripts/ux/record-page.ts +53 -0
- package/skills/explainer-video/SKILL.md +117 -0
- package/skills/explainer-video/engine/brand/elanous-mark-on-dark.svg +1 -0
- package/skills/explainer-video/engine/build.mjs +431 -0
- package/skills/explainer-video/engine/reel.mjs +128 -0
- package/skills/explainer-video/engine/reel.sh +8 -0
- package/skills/explainer-video/engine/vo.ts +79 -0
- package/skills/explainer-video/examples/elanous-0.2.5/script.json +155 -0
- package/src/agent/nested-agent-env.ts +1 -0
- package/src/agent-mission/driver.ts +95 -16
- package/src/agent-mission/mission-cli.ts +6 -0
- package/src/agent-mission/resource-ladder.ts +73 -25
- package/src/auth/acp-token.ts +16 -0
- package/src/auth/github-app-token.ts +112 -0
- package/src/autopilot/gh-rest-fallback.ts +177 -0
- package/src/autopilot/mission-origin.ts +20 -4
- package/src/autopilot/mission-phase-track.ts +11 -1
- package/src/autopilot/pr-manager.ts +51 -4
- package/src/autopilot/track-agent.ts +3 -3
- package/src/autopilot/track-courier.ts +2 -2
- package/src/boot/acp-server.ts +2 -16
- package/src/boot/daemon-tools/web-search.ts +2 -1
- package/src/boot/ws-bridge.ts +11 -0
- package/src/chat/index.ts +37 -8
- package/src/chat/log-scroll-key.ts +12 -0
- package/src/chat/submit-trace.ts +30 -0
- package/src/cli/agent-cli.ts +7 -2
- package/src/cli/decisions-cli.ts +94 -0
- package/src/cli/doctor-cli.ts +40 -54
- package/src/cli/doctor-readiness.ts +16 -24
- package/src/cli/intake-cli.ts +1 -1
- package/src/cli/mcp-call.ts +9 -0
- package/src/cli/mcp-gateway-cli.ts +7 -3
- package/src/cli/nexus-ios-bind.ts +25 -8
- package/src/cli/phone-link.ts +117 -0
- package/src/cli/plugin-cli.ts +136 -11
- package/src/cli/pr-lineage.ts +2 -1
- package/src/cli/private-ledger-commands.ts +31 -0
- package/src/cli/pwa-start.ts +3 -0
- package/src/cli/release-cli.ts +97 -7
- package/src/cli/self-update.ts +7 -4
- package/src/cli/start-cli.ts +33 -14
- package/src/dashboard/acp-boot.ts +3 -0
- package/src/dashboard/index.ts +62 -13
- package/src/dashboard/input/no-provider-reply.ts +13 -0
- package/src/dashboard/slash-runtime/unknown-slash-reply.ts +78 -0
- package/src/dashboard/stale-install-check.ts +77 -0
- package/src/decisions/decision-ledger.ts +404 -0
- package/src/domains/outbound-alert.ts +13 -7
- package/src/domains/telegram-kind-route.ts +21 -0
- package/src/execution-loop/dispatch-task.ts +139 -0
- package/src/execution-loop/gate-decision-summary.ts +29 -0
- package/src/execution-loop/gates.ts +197 -0
- package/src/field/field-media.ts +245 -0
- package/src/graph-kinds/installed-plugin-nodes.ts +95 -0
- package/src/graph-kinds/plugin-nodes.ts +10 -2
- package/src/graph-runner/runner.ts +35 -5
- package/src/grounding/token.ts +12 -6
- package/src/harness/harness-cli-command.ts +68 -9
- package/src/harness/harness-pod-dispatch.ts +8 -2
- package/src/harness/harness-stop.ts +147 -18
- package/src/harness/self-send-decide.ts +26 -17
- package/src/harness/self-send-target.ts +22 -1
- package/src/harness/worktree-branch-prefix.ts +4 -3
- package/src/index.ts +50 -13
- package/src/instance/resolve.ts +6 -0
- package/src/intake-plane/collect-telegram-saved.ts +5 -0
- package/src/intake-plane/digest.ts +11 -2
- package/src/live/detail-switch.ts +8 -3
- package/src/llm/usable-llm.ts +32 -0
- package/src/loops/registry.ts +27 -5
- package/src/market/publish.ts +59 -3
- package/src/native-tool-catalog.ts +2 -2
- package/src/nexus/api/field-uploads.ts +143 -0
- package/src/nexus/api/graph-approvals.ts +174 -0
- package/src/nexus/api/harness-api.ts +219 -5
- package/src/nexus/api/http-server.ts +69 -6
- package/src/nexus/api/log-fabric.ts +10 -1
- package/src/nexus/api/mcp-oauth-hint.ts +10 -9
- package/src/nexus/api/merge-approvals.ts +2 -1
- package/src/nexus/api/meta-api.ts +14 -0
- package/src/nexus/api/plugins-credentials-api.ts +47 -0
- package/src/nexus/api/plugins-market.ts +87 -3
- package/src/nexus/api/pod-credential-api.ts +68 -14
- package/src/nexus/api/public-routes.ts +7 -0
- package/src/nexus/api/trace.ts +57 -6
- package/src/nexus/boot/register-mcp-clients.ts +99 -14
- package/src/nexus/index.ts +33 -32
- package/src/nexus/outbound/router.ts +1 -1
- package/src/notify/llm-budget-alert.ts +119 -0
- package/src/onboarding.ts +25 -2
- package/src/plugins/core/host.ts +14 -0
- package/src/plugins/install/market-fetch.ts +4 -3
- package/src/plugins/install/plugin-credentials.ts +106 -0
- package/src/plugins/install/plugin-install.ts +163 -32
- package/src/plugins/maker/plugin-maker.ts +208 -0
- package/src/prompt-library/universal-preamble.ts +8 -2
- package/src/relay/channel.ts +184 -0
- package/src/relay/mux.ts +182 -0
- package/src/relay/test-vectors.json +29 -0
- package/src/release-loop/checklist.ts +217 -0
- package/src/release-loop/release-note.ts +6 -3
- package/src/seat-address/seat-address.ts +34 -0
- package/src/self-dev/dev-cli.ts +40 -20
- package/src/self-dev/dev-pipeline.ts +77 -1
- package/src/self-implement/gate-baseline.ts +3 -1
- package/src/self-implement/goal-author.ts +18 -1
- package/src/self-implement/host-regate.ts +6 -2
- package/src/self-implement/orchestrator.ts +7 -2
- package/src/self-implement/run-ledger-compact.ts +1 -1
- package/src/self-implement/seams.ts +4 -2
- package/src/session/index.ts +23 -10
- package/src/skills/tools/dev-harness.ts +42 -0
- package/src/steward/steward-cards.ts +45 -0
- package/src/steward/triage.ts +50 -14
- package/src/task-orchestrator/surfaces/pod-artifact-return.ts +61 -3
- package/src/task-orchestrator/surfaces/pod-bun-cache.ts +18 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +36 -6
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +39 -9
- package/src/task-orchestrator/surfaces/pod-source-bundle.ts +16 -1
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +51 -13
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +172 -28
- package/src/telegram-report.ts +12 -6
- package/src/telegram.ts +108 -1
- package/src/tui-client/acp-transport-ws-client.ts +72 -5
- package/src/tui.ts +3 -2
- package/src/user-config.ts +32 -2
- package/src/util/spawn-sync-output.ts +15 -0
- package/src/version/code-revision.ts +1 -1
- package/src/version/packed-revision.json +1 -1
- package/src/video-pipeline/recipes/explainer.ts +213 -0
- package/src/video-pipeline/walk-line.ts +3 -2
- package/src/web-search/free.ts +77 -0
- package/src/web-search/index.ts +2 -0
- package/src/workflow-runtime/deps-bridge.ts +4 -3
- package/src/workflow-runtime/plugin-kind-node.ts +32 -4
- package/src/workflow-runtime/types.ts +2 -1
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1191-7b13a24117a689fa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2375-3fcae5186c64fe6b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +0 -25
- package/apps/pwa/out/_next/static/chunks/319-d37df1b6e0cbeff4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4836-3c93795ab834381e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6090-1bd6077591603947.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7457-f5cb1cc8db9f7453.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8067-f74bf14d67ad98f1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8519-0059d82bdb4812bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9740-52467f244755917f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9804-64c02c3c127d5014.js +0 -1
- package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-21c3261af6ab377c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-1991445b8ea86274.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-4ebfaee2369fe248.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-9fa879ebf5959096.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-ca7c3cc305c3285a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-9951930add3d2051.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-4472ddf1e2458e69.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-4de08d4b766f134b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7a975e2b9db1dfb7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-49270714ef35bb96.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-33fbdf353e2a5982.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-259f122e2dd64317.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-65b82726753658e1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-4b8df3748dc06285.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-112f7e477bda77fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-542a5c0006da637c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-63ffb347831262ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-98e4e359d97263b4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-97e24ac675ce576e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-ac6391f26391d89d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-9f45c9d45ffc43bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-36995b9cb5d98218.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-0c795b577988d63f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b4311e2f71c6735a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-eb2381074df20dba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-b01336e64eba01ae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-7af7f21c1f2858ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-a98ecd364586c89b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-57f0502dfbfa089b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-3467d2e1abd8a14a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-7842ca23d53ba682.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-ae9ac882912b0198.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-3a0d72e3f783bcef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-f3b39fe8358c2a97.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-5a22af3181ac2afb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-1457627bb302a22c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
- package/apps/pwa/out/_next/static/css/e66b7f5efc8df3f3.css +0 -1
- /package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → qMI0jnlZaBjjm4aIDYNGM}/_ssgManifest.js +0 -0
package/src/plugins/core/host.ts
CHANGED
|
@@ -233,6 +233,14 @@ export const BUILTIN_DIR = resolve(import.meta.dir, '..', '..', '..', 'plugins')
|
|
|
233
233
|
export const PACKS_DIR = resolve(import.meta.dir, '..', '..', '..', 'packs');
|
|
234
234
|
export const USER_DIR = join(homedir(), '.claude', 'plugins');
|
|
235
235
|
|
|
236
|
+
/** 코드 없이 스킬·그래프만 싣는 팩인가 — 진입점이 기본값(`./plugin.ts`)이고 파일이 없으며, 스킬·그래프를 선언하거나 `skills/` 폴더가 있다. */
|
|
237
|
+
export function isSkillOnlyPack(pluginDir: string, manifest: { main: string; contributes?: { skills?: unknown[]; graphs?: unknown[] } }, hasLegacyEntry: boolean): boolean {
|
|
238
|
+
if (hasLegacyEntry) return false;
|
|
239
|
+
if (manifest.main !== './plugin.ts') return false; // 명시한 진입점이 없으면 그건 결함이다 — 경고를 유지한다.
|
|
240
|
+
const declares = (manifest.contributes?.skills?.length ?? 0) > 0 || (manifest.contributes?.graphs?.length ?? 0) > 0;
|
|
241
|
+
return declares || existsSync(join(pluginDir, 'skills'));
|
|
242
|
+
}
|
|
243
|
+
|
|
236
244
|
export class PluginHost {
|
|
237
245
|
private available = new Map<string, PluginEntry>();
|
|
238
246
|
private discoveredNodeKinds = new Map<string, NodeKindEntry>();
|
|
@@ -614,6 +622,12 @@ export class PluginHost {
|
|
|
614
622
|
// ~/.claude/plugins/{cache,data,marketplaces} internal state)
|
|
615
623
|
// are silently skipped: they never intended to be plugins.
|
|
616
624
|
const looksLikePlugin = hasLegacyEntry || !manifestLoad.inferred;
|
|
625
|
+
// 스킬·그래프만 싣는 팩(공식 `elanous-basics` 등)은 코드 진입점이 없다 — 매니페스트가 `main` 을
|
|
626
|
+
// 적지 않아 기본값 `./plugin.ts` 가 들어온 것뿐이다. 부팅마다 «main not found» 경고를 내지 않는다.
|
|
627
|
+
if (looksLikePlugin && isSkillOnlyPack(pluginDir, manifestLoad.manifest, hasLegacyEntry)) {
|
|
628
|
+
debug.log('plugin.discovery', 'skill-only', { plugin: manifestLoad.manifest.id });
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
617
631
|
if (looksLikePlugin) {
|
|
618
632
|
this.hooks.log(`⚠ plugin "${manifestLoad.manifest.id}" main not found: ${manifestLoad.manifest.main}`);
|
|
619
633
|
debug.log('plugin.discovery', 'error', {
|
|
@@ -186,9 +186,10 @@ async function fetchMarketIndex(name: string, opts: MarketFetchOptions): Promise
|
|
|
186
186
|
const load = async (file: string): Promise<Uint8Array> => {
|
|
187
187
|
const url = new URL(file, market.url);
|
|
188
188
|
let response: Response;
|
|
189
|
-
try { response = await fetcher(url.href); }
|
|
190
|
-
catch
|
|
189
|
+
try { response = await fetcher(url.href, { redirect: 'error' }); }
|
|
190
|
+
catch { throw new MarketFetchError('io', `market fetch failed (${file}): network error`); }
|
|
191
191
|
if (!response.ok) throw new MarketFetchError('io', `market fetch failed (${file}): HTTP ${response.status}`);
|
|
192
|
+
if (response.redirected || (response.url && response.url !== url.href)) throw new MarketFetchError('io', `market fetch was redirected (${file})`);
|
|
192
193
|
if (!response.body) throw new MarketFetchError('io', `market fetch failed (${file}): missing response body`);
|
|
193
194
|
const reader = response.body.getReader();
|
|
194
195
|
const chunks: Uint8Array[] = [];
|
|
@@ -203,7 +204,7 @@ async function fetchMarketIndex(name: string, opts: MarketFetchOptions): Promise
|
|
|
203
204
|
}
|
|
204
205
|
} catch (error) {
|
|
205
206
|
if (error instanceof MarketFetchError) throw error;
|
|
206
|
-
throw new MarketFetchError('io', `market fetch failed (${file}):
|
|
207
|
+
throw new MarketFetchError('io', `market fetch failed (${file}): network error`);
|
|
207
208
|
} finally {
|
|
208
209
|
void reader.cancel().catch(() => {});
|
|
209
210
|
reader.releaseLock();
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { closeSync, existsSync, fchmodSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { elanousStateRoot } from '../../autopilot/state-paths.js';
|
|
5
|
+
import { loadPluginManifestFromDir } from '../core/manifest.js';
|
|
6
|
+
import { listInstalledPlugins, withLedgerLock } from './plugin-install.js';
|
|
7
|
+
|
|
8
|
+
const PLUGIN_NAME = /^[a-z0-9][a-z0-9-]{1,39}$/;
|
|
9
|
+
const FIELD_NAME = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
10
|
+
const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
11
|
+
|
|
12
|
+
type FieldStatus = { name: string; env: string; set: boolean };
|
|
13
|
+
|
|
14
|
+
function credentialPath(name: string, root: string): string {
|
|
15
|
+
if (!PLUGIN_NAME.test(name)) throw new Error('invalid plugin name');
|
|
16
|
+
return join(root, 'plugins', 'credentials', `${name}.json`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function declaredFields(name: string, root: string): Array<{ name: string; env: string }> {
|
|
20
|
+
if (!PLUGIN_NAME.test(name)) throw new Error('invalid plugin name');
|
|
21
|
+
const installed = listInstalledPlugins(root).filter(plugin => plugin.name === name);
|
|
22
|
+
if (installed.length === 0) throw new Error(`plugin not installed: ${name}`);
|
|
23
|
+
const fields = new Map<string, string>();
|
|
24
|
+
for (const plugin of installed) {
|
|
25
|
+
const manifest = loadPluginManifestFromDir(plugin.path, { id: plugin.name }).manifest;
|
|
26
|
+
for (const connector of manifest.contributes.connectors ?? []) {
|
|
27
|
+
for (const field of [
|
|
28
|
+
...(connector.fields ?? []).map(item => ({ name: item.name, env: item.env })),
|
|
29
|
+
...(connector.userConfig ?? []).map(item => ({ name: item.key, env: item.env })),
|
|
30
|
+
]) {
|
|
31
|
+
if (!FIELD_NAME.test(field.name)) throw new Error('invalid plugin credential field name');
|
|
32
|
+
const env = field.env === undefined
|
|
33
|
+
? `ELANOUS_PLUGIN_${name.replaceAll('-', '_').toUpperCase()}_${field.name.replaceAll('-', '_').toUpperCase()}`
|
|
34
|
+
: field.env;
|
|
35
|
+
if (typeof env !== 'string' || !ENV_NAME.test(env)) throw new Error('invalid plugin credential environment name');
|
|
36
|
+
if (fields.has(field.name) && fields.get(field.name) !== env) throw new Error('conflicting plugin credential environment name');
|
|
37
|
+
fields.set(field.name, env);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (new Set(fields.values()).size !== fields.size) throw new Error('duplicate plugin credential environment name');
|
|
42
|
+
return [...fields].map(([fieldName, env]) => ({ name: fieldName, env })).sort((a, b) => a.name.localeCompare(b.name));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function readCredentials(path: string): Record<string, string> {
|
|
46
|
+
if (!existsSync(path)) return Object.create(null) as Record<string, string>;
|
|
47
|
+
if (!lstatSync(path).isFile()) throw new Error('invalid plugin credential file');
|
|
48
|
+
let parsed: unknown;
|
|
49
|
+
try { parsed = JSON.parse(readFileSync(path, 'utf8')); }
|
|
50
|
+
catch { throw new Error('invalid plugin credential file'); }
|
|
51
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)
|
|
52
|
+
|| Object.entries(parsed).some(([key, value]) => !FIELD_NAME.test(key) || typeof value !== 'string')) {
|
|
53
|
+
throw new Error('invalid plugin credential file');
|
|
54
|
+
}
|
|
55
|
+
return Object.assign(Object.create(null) as Record<string, string>, parsed);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Update one installed plugin's declared fields. null removes a field; omitted fields remain intact. */
|
|
59
|
+
export function setPluginCredentials(name: string, fields: Record<string, string | null>, root = elanousStateRoot()): { set: string[] } {
|
|
60
|
+
const path = credentialPath(name, root);
|
|
61
|
+
if (!fields || typeof fields !== 'object' || Array.isArray(fields)) throw new Error('invalid plugin credential fields');
|
|
62
|
+
const allowed = new Set(declaredFields(name, root).map(field => field.name));
|
|
63
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
64
|
+
if (!allowed.has(key) || (typeof value !== 'string' && value !== null) || (typeof value === 'string' && value.includes('\0'))) {
|
|
65
|
+
throw new Error('invalid plugin credential field');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return withLedgerLock(root, () => {
|
|
69
|
+
const current = readCredentials(path);
|
|
70
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
71
|
+
if (value === null) delete current[key];
|
|
72
|
+
else current[key] = value;
|
|
73
|
+
}
|
|
74
|
+
if (Object.keys(fields).length === 0) return { set: [] };
|
|
75
|
+
mkdirSync(join(root, 'plugins', 'credentials'), { recursive: true, mode: 0o700 });
|
|
76
|
+
const temp = `${path}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`;
|
|
77
|
+
try {
|
|
78
|
+
const fd = openSync(temp, 'wx', 0o600);
|
|
79
|
+
try {
|
|
80
|
+
fchmodSync(fd, 0o600);
|
|
81
|
+
writeFileSync(fd, JSON.stringify(current));
|
|
82
|
+
fsyncSync(fd);
|
|
83
|
+
} finally { closeSync(fd); }
|
|
84
|
+
renameSync(temp, path);
|
|
85
|
+
} finally { rmSync(temp, { force: true }); }
|
|
86
|
+
return { set: Object.keys(fields).filter(key => Object.hasOwn(current, key)).sort() };
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Public metadata only: values never leave the credential store. */
|
|
91
|
+
export function credentialStatus(name: string, root = elanousStateRoot()): { fields: FieldStatus[] } {
|
|
92
|
+
const declared = declaredFields(name, root);
|
|
93
|
+
const saved = readCredentials(credentialPath(name, root));
|
|
94
|
+
return { fields: declared.map(field => ({ ...field, set: Object.hasOwn(saved, field.name) })) };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Environment entries intended only for this plugin's child process. */
|
|
98
|
+
export function pluginEnv(name: string, root = elanousStateRoot()): Record<string, string> {
|
|
99
|
+
const declared = declaredFields(name, root);
|
|
100
|
+
const saved = readCredentials(credentialPath(name, root));
|
|
101
|
+
const env = Object.create(null) as Record<string, string>;
|
|
102
|
+
for (const field of declared) {
|
|
103
|
+
if (Object.hasOwn(saved, field.name)) env[field.env] = saved[field.name]!;
|
|
104
|
+
}
|
|
105
|
+
return env;
|
|
106
|
+
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { spawnSync } from 'node:child_process';
|
|
3
3
|
import { closeSync, cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { basename, dirname, isAbsolute, join, resolve, sep } from 'node:path';
|
|
6
6
|
import { dlopen, FFIType } from 'bun:ffi';
|
|
7
7
|
import { loadPluginManifestFromDir, type PluginManifest } from '../core/manifest.js';
|
|
8
|
+
import { loadPluginNodes } from '../../graph-kinds/plugin-nodes.js';
|
|
9
|
+
import { getNodeKindRegistration, listNodeKinds, unregisterPluginNodeKind } from '../../graph-kinds/registry.js';
|
|
8
10
|
import { verifyIndex, type MarketplaceIndex, type VerifyIndexResult } from '../../market/signed-index.js';
|
|
9
11
|
import { elanousStateRoot } from '../../autopilot/state-paths.js';
|
|
12
|
+
import { ensureMarketIndex, MarketFetchError, type MarketFetchOptions } from './market-fetch.js';
|
|
10
13
|
|
|
11
14
|
const NAME = /^[a-z0-9][a-z0-9-]{1,39}$/;
|
|
12
15
|
const VERSION = /^[0-9]+\.[0-9]+\.[0-9]+(?:[-+][a-zA-Z0-9.-]+)?$/;
|
|
@@ -18,7 +21,7 @@ export type InstallEvent =
|
|
|
18
21
|
| { event: 'verify'; signature: 'ok' | 'missing' | 'bad'; scan: 'safe' | 'caution' | 'dangerous' }
|
|
19
22
|
| { event: 'consent'; capabilities: string[]; required: boolean }
|
|
20
23
|
| { event: 'credentials'; connectors: Array<{ id: string; fields: Array<{ name: string; secret: boolean }> }> }
|
|
21
|
-
| { event: 'registered'; kinds: string[]; graphs: string[]; skills: string[] }
|
|
24
|
+
| { event: 'registered'; /** Vocab file paths, not graph node kinds. */ kinds: string[]; nodes: string[]; nodeErrors: number; graphs: string[]; skills: string[] }
|
|
22
25
|
| { event: 'done'; plugin: string; version: string; path: string };
|
|
23
26
|
|
|
24
27
|
export interface InstalledPlugin {
|
|
@@ -27,6 +30,8 @@ export interface InstalledPlugin {
|
|
|
27
30
|
market: string;
|
|
28
31
|
path: string;
|
|
29
32
|
sha256: string | null;
|
|
33
|
+
/** Absent for installations recorded before timestamps were introduced. */
|
|
34
|
+
installedAt?: string;
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
export interface InstallOptions {
|
|
@@ -34,12 +39,15 @@ export interface InstallOptions {
|
|
|
34
39
|
marketDir?: string;
|
|
35
40
|
cwd?: string;
|
|
36
41
|
allowUnsigned?: boolean;
|
|
42
|
+
refresh?: boolean;
|
|
37
43
|
yes?: boolean;
|
|
38
44
|
trustedKeys?: ReadonlyArray<{ keyId: string; publicKey: string }>;
|
|
39
45
|
verifySignature?: typeof verifyIndex;
|
|
40
46
|
hashArtifact?: (bytes: Uint8Array) => string;
|
|
41
47
|
onEvent?: (event: InstallEvent) => void;
|
|
42
48
|
consent?: (capabilities: string[]) => boolean | Promise<boolean>;
|
|
49
|
+
fetcher?: MarketFetchOptions['fetcher'];
|
|
50
|
+
configPath?: string;
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
export class PluginInstallError extends Error {
|
|
@@ -115,28 +123,8 @@ function marketEntry(spec: string, opts: InstallOptions): { market: string; entr
|
|
|
115
123
|
return { market, entry: matches[0]!, signature };
|
|
116
124
|
}
|
|
117
125
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
writeFileSync(archive, bytes);
|
|
121
|
-
try {
|
|
122
|
-
const entries = child('tar', ['-tzf', archive]).split('\n');
|
|
123
|
-
for (const item of entries) {
|
|
124
|
-
if (item === '.' || item === './') continue;
|
|
125
|
-
inside(target, item.replace(/^\.\//, ''));
|
|
126
|
-
}
|
|
127
|
-
// Links and special entries can write outside the destination before a post-extract scan.
|
|
128
|
-
const types = child('tar', ['-tvzf', archive]).split('\n');
|
|
129
|
-
if (types.some(line => line && !['-', 'd'].includes(line[0]!))) fail('scan', 'artifact contains links or special files');
|
|
130
|
-
mkdirSync(target);
|
|
131
|
-
child('tar', ['-xzf', archive, '-C', target, '--no-same-owner', '--no-same-permissions']);
|
|
132
|
-
safeTree(target);
|
|
133
|
-
} finally {
|
|
134
|
-
rmSync(archive, { force: true });
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export function resolvePluginSource(spec: string, target: string, opts: InstallOptions = {}): { market: string; sha256: string | null; signature: 'ok' | 'missing'; expectedName?: string; expectedVersion?: string } {
|
|
139
|
-
if (/^[a-z0-9][a-z0-9-]{1,39}@[a-z0-9][a-z0-9-]{1,39}$/.test(spec) && !existsSync(resolve(opts.cwd ?? process.cwd(), spec))) {
|
|
126
|
+
/** Offline: a signed index placed in `<markets>/<name>/` without a configured URL (pre-fetch behaviour — kept). */
|
|
127
|
+
function localMarketSource(spec: string, target: string, opts: InstallOptions): ResolvedPluginSource {
|
|
140
128
|
const { market, entry, signature } = marketEntry(spec, opts);
|
|
141
129
|
const source = entry.source;
|
|
142
130
|
const sourceRoot = dirname(indexPath(market, opts));
|
|
@@ -159,7 +147,100 @@ export function resolvePluginSource(spec: string, target: string, opts: InstallO
|
|
|
159
147
|
fail('io', `market entry has no verifiable artifact: ${entry.name}`);
|
|
160
148
|
}
|
|
161
149
|
return { market, sha256: entry.artifact?.sha256 ?? null, signature, expectedName: entry.name, expectedVersion: entry.version };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function hasLocalMarketIndex(market: string, opts: InstallOptions): boolean {
|
|
153
|
+
const base = resolve(opts.marketDir ?? join(opts.root ?? elanousStateRoot(), 'markets'));
|
|
154
|
+
return [join(base, market, 'marketplace.json'), join(base, market, '.agents', 'plugins', 'marketplace.json')].some(existsSync);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function unpackArchive(bytes: Uint8Array, target: string): void {
|
|
158
|
+
const archive = join(dirname(target), 'artifact.tgz');
|
|
159
|
+
writeFileSync(archive, bytes);
|
|
160
|
+
try {
|
|
161
|
+
const entries = child('tar', ['-tzf', archive]).split('\n');
|
|
162
|
+
for (const item of entries) {
|
|
163
|
+
if (item === '.' || item === './') continue;
|
|
164
|
+
inside(target, item.replace(/^\.\//, ''));
|
|
165
|
+
}
|
|
166
|
+
// Links and special entries can write outside the destination before a post-extract scan.
|
|
167
|
+
const types = child('tar', ['-tvzf', archive]).split('\n');
|
|
168
|
+
if (types.some(line => line && !['-', 'd'].includes(line[0]!))) fail('scan', 'artifact contains links or special files');
|
|
169
|
+
mkdirSync(target);
|
|
170
|
+
child('tar', ['-xzf', archive, '-C', target, '--no-same-owner', '--no-same-permissions']);
|
|
171
|
+
safeTree(target);
|
|
172
|
+
} finally {
|
|
173
|
+
rmSync(archive, { force: true });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
type ResolvedPluginSource = { market: string; sha256: string | null; signature: 'ok' | 'missing'; expectedName?: string; expectedVersion?: string };
|
|
178
|
+
|
|
179
|
+
function marketSpec(spec: string, opts: InstallOptions): boolean {
|
|
180
|
+
return /^[a-z0-9][a-z0-9-]{1,39}@[a-z0-9][a-z0-9-]{1,39}$/.test(spec) && !existsSync(resolve(opts.cwd ?? process.cwd(), spec));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function fetchedMarketSource(spec: string, target: string, opts: InstallOptions): Promise<ResolvedPluginSource> {
|
|
184
|
+
const [name, marketName] = spec.split('@') as [string, string];
|
|
185
|
+
let marketIndex: Awaited<ReturnType<typeof ensureMarketIndex>>;
|
|
186
|
+
try {
|
|
187
|
+
marketIndex = await ensureMarketIndex(marketName, {
|
|
188
|
+
root: opts.root, marketDir: opts.marketDir, configPath: opts.configPath, fetcher: opts.fetcher,
|
|
189
|
+
refresh: opts.refresh, verifySignature: opts.verifySignature, trustedKeys: opts.trustedKeys,
|
|
190
|
+
});
|
|
191
|
+
} catch (error) {
|
|
192
|
+
if (error instanceof MarketFetchError && /^market not configured: /.test(error.message) && hasLocalMarketIndex(marketName, opts)) {
|
|
193
|
+
return localMarketSource(spec, target, opts);
|
|
194
|
+
}
|
|
195
|
+
if (error instanceof MarketFetchError) throw new PluginInstallError(error.reason, error.message);
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
const matches = marketIndex.index.plugins.filter(entry => entry.name === name);
|
|
199
|
+
if (matches.length !== 1) fail('io', `plugin not found or ambiguous in ${marketName}: ${name}`);
|
|
200
|
+
const entry = matches[0]!;
|
|
201
|
+
const artifact = entry.artifact;
|
|
202
|
+
if (!artifact || typeof artifact.key !== 'string' || !artifact.key || !HASH.test(artifact.sha256)
|
|
203
|
+
|| !Number.isSafeInteger(artifact.bytes) || artifact.bytes < 0) fail('io', 'invalid artifact key, hash or size');
|
|
204
|
+
// Check the key as written, before URL resolution folds `..`/`.` or percent-encoded separators away.
|
|
205
|
+
if (!/^[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)*$/.test(artifact.key) || artifact.key.split('/').some(part => part === '.' || part === '..')) fail('io', 'unsafe artifact key');
|
|
206
|
+
const base = new URL(marketIndex.market.url);
|
|
207
|
+
const url = new URL(artifact.key, base);
|
|
208
|
+
if (url.protocol !== 'https:' || url.origin !== base.origin || !url.pathname.startsWith(base.pathname)
|
|
209
|
+
|| url.username || url.password || url.search || url.hash || /[\\?#]/.test(artifact.key)) fail('io', 'unsafe artifact key');
|
|
210
|
+
let response: Response;
|
|
211
|
+
// The URL checks above hold only if no redirect moves the download elsewhere.
|
|
212
|
+
try { response = await (opts.fetcher ?? fetch)(url.href, { redirect: 'error' }); }
|
|
213
|
+
catch { fail('io', 'artifact fetch failed: network error'); }
|
|
214
|
+
if (!response.ok || !response.body) fail('io', `artifact fetch failed: HTTP ${response.status}`);
|
|
215
|
+
if (response.redirected || (response.url && response.url !== url.href)) fail('io', 'artifact fetch was redirected');
|
|
216
|
+
const reader = response.body.getReader();
|
|
217
|
+
const chunks: Uint8Array[] = [];
|
|
218
|
+
let length = 0;
|
|
219
|
+
try {
|
|
220
|
+
for (;;) {
|
|
221
|
+
const { done, value } = await reader.read();
|
|
222
|
+
if (done) break;
|
|
223
|
+
length += value.byteLength;
|
|
224
|
+
if (length > artifact.bytes) fail('scan', 'artifact sha256 or size mismatch');
|
|
225
|
+
chunks.push(value);
|
|
226
|
+
}
|
|
227
|
+
} finally {
|
|
228
|
+
void reader.cancel().catch(() => {});
|
|
229
|
+
reader.releaseLock();
|
|
230
|
+
}
|
|
231
|
+
const bytes = new Uint8Array(length);
|
|
232
|
+
let offset = 0;
|
|
233
|
+
for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.byteLength; }
|
|
234
|
+
if (length !== artifact.bytes || (opts.hashArtifact ?? (data => createHash('sha256').update(data).digest('hex')))(bytes).toLowerCase() !== artifact.sha256.toLowerCase()) {
|
|
235
|
+
fail('scan', 'artifact sha256 or size mismatch');
|
|
162
236
|
}
|
|
237
|
+
unpackArchive(bytes, target);
|
|
238
|
+
return { market: marketName, sha256: artifact.sha256, signature: marketIndex.signature, expectedName: name, expectedVersion: entry.version };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function resolvePluginSource(spec: string, target: string, opts: InstallOptions = {}): ResolvedPluginSource {
|
|
242
|
+
// Synchronous callers keep the offline contract: a signed index already placed under `<markets>/<name>/`.
|
|
243
|
+
if (marketSpec(spec, opts)) return localMarketSource(spec, target, opts);
|
|
163
244
|
const local = resolve(opts.cwd ?? process.cwd(), spec);
|
|
164
245
|
if (existsSync(local)) {
|
|
165
246
|
copyPlugin(realpathSync(local), target);
|
|
@@ -187,19 +268,63 @@ function gitSource(url: string, destination: string, path?: unknown, sha?: unkno
|
|
|
187
268
|
}
|
|
188
269
|
}
|
|
189
270
|
|
|
271
|
+
function hasSkill(dir: string): boolean {
|
|
272
|
+
if (!existsSync(dir)) return false;
|
|
273
|
+
return readdirSync(dir, { withFileTypes: true }).some(entry => entry.isDirectory() && existsSync(join(dir, entry.name, 'SKILL.md')));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function inspectPluginNodes(packageRoot: string, manifest: PluginManifest): { nodes: string[]; nodeErrors: number } {
|
|
277
|
+
if (!manifest.contributes.nodes?.length) return { nodes: [], nodeErrors: 0 };
|
|
278
|
+
// Registration validates each staged definition. Use a private identity so existing installations
|
|
279
|
+
// cannot reject an otherwise valid kind as a duplicate; only this inspection's entries are removed.
|
|
280
|
+
const inspectionId = `inspect-${randomUUID().replaceAll('-', '')}`;
|
|
281
|
+
let nodeErrors = 0;
|
|
282
|
+
const nodes: string[] = [];
|
|
283
|
+
try {
|
|
284
|
+
nodeErrors = loadPluginNodes(packageRoot, { ...manifest, id: inspectionId }).errors.length;
|
|
285
|
+
} finally {
|
|
286
|
+
for (const entry of listNodeKinds()) {
|
|
287
|
+
if (entry.plugin !== inspectionId) continue;
|
|
288
|
+
nodes.push(`${manifest.id}:${entry.kind.slice(inspectionId.length + 1)}`);
|
|
289
|
+
const registered = getNodeKindRegistration(entry.graph, entry.kind);
|
|
290
|
+
if (registered) unregisterPluginNodeKind(entry.graph, entry.kind, inspectionId, registered);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return { nodes, nodeErrors };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function declaresMain(packageRoot: string): boolean {
|
|
297
|
+
for (const file of ['plugin.json', join('.codex-plugin', 'plugin.json')]) {
|
|
298
|
+
const path = join(packageRoot, file);
|
|
299
|
+
if (!existsSync(path)) continue;
|
|
300
|
+
try {
|
|
301
|
+
const raw: unknown = JSON.parse(readFileSync(path, 'utf8'));
|
|
302
|
+
if (raw && typeof raw === 'object' && 'main' in raw) return true;
|
|
303
|
+
} catch { return true; }
|
|
304
|
+
}
|
|
305
|
+
return !existsSync(join(packageRoot, 'plugin.json')) && !existsSync(join(packageRoot, '.codex-plugin', 'plugin.json'));
|
|
306
|
+
}
|
|
307
|
+
|
|
190
308
|
export async function installPlugin(spec: string, opts: InstallOptions = {}): Promise<InstalledPlugin> {
|
|
191
309
|
const root = resolve(opts.root ?? elanousStateRoot());
|
|
192
310
|
const stageRoot = join(root, 'plugins', '.staging');
|
|
193
311
|
mkdirSync(stageRoot, { recursive: true });
|
|
194
312
|
const stage = mkdtempSync(join(stageRoot, 'plugin-'));
|
|
195
313
|
try {
|
|
196
|
-
const source =
|
|
314
|
+
const source = marketSpec(spec, opts)
|
|
315
|
+
? await fetchedMarketSource(spec, join(stage, 'package'), opts)
|
|
316
|
+
: resolvePluginSource(spec, join(stage, 'package'), opts);
|
|
197
317
|
const manifest: PluginManifest = loadPluginManifestFromDir(join(stage, 'package'), { id: source.expectedName ?? basename(spec) }).manifest;
|
|
198
318
|
if (!NAME.test(manifest.id) || !VERSION.test(manifest.version)) fail('io', 'invalid plugin name or version');
|
|
199
319
|
if (source.expectedName && (manifest.id !== source.expectedName || manifest.version !== source.expectedVersion)) fail('conflict', 'market name or version differs from manifest');
|
|
200
320
|
const packageRoot = join(stage, 'package');
|
|
201
321
|
const mainPath = inside(packageRoot, manifest.main);
|
|
202
|
-
|
|
322
|
+
// A skills/graphs-only pack (official `elanous-basics`) declares no `main` — the default `./plugin.ts` is not a promise.
|
|
323
|
+
// …but only a pack that ships something to install (skills or graphs) may omit it.
|
|
324
|
+
const shipsAssets = hasSkill(join(packageRoot, 'skills')) || (manifest.contributes.graphs?.length ?? 0) > 0;
|
|
325
|
+
if (existsSync(mainPath) || declaresMain(packageRoot) || !shipsAssets) {
|
|
326
|
+
if (!existsSync(mainPath) || !realpathSync(mainPath).startsWith(`${realpathSync(packageRoot)}${sep}`)) fail('io', `plugin main not found or unsafe: ${manifest.main}`);
|
|
327
|
+
}
|
|
203
328
|
const name = manifest.id;
|
|
204
329
|
const version = manifest.version;
|
|
205
330
|
for (const asset of [...(manifest.contributes.graphs ?? []), ...(manifest.contributes.vocab ?? [])]) {
|
|
@@ -228,12 +353,13 @@ export async function installPlugin(spec: string, opts: InstallOptions = {}): Pr
|
|
|
228
353
|
.map(entry => entry.name).sort()
|
|
229
354
|
: []
|
|
230
355
|
: manifest.contributes.skills.map(skill => skill.name ?? skill.id ?? '').filter(Boolean);
|
|
231
|
-
|
|
356
|
+
const { nodes, nodeErrors } = inspectPluginNodes(packageRoot, manifest);
|
|
357
|
+
emit({ event: 'registered', kinds, nodes, nodeErrors, graphs, skills });
|
|
232
358
|
const installed = withLedgerLock(root, () => {
|
|
233
359
|
if (existsSync(destination)) fail('conflict', `plugin already installed: ${name}@${version}`);
|
|
234
360
|
mkdirSync(dirname(destination), { recursive: true });
|
|
235
361
|
renameSync(join(stage, 'package'), destination);
|
|
236
|
-
const item = { name, version, market: source.market, path: destination, sha256: source.sha256 };
|
|
362
|
+
const item = { name, version, market: source.market, path: destination, sha256: source.sha256, installedAt: new Date().toISOString() };
|
|
237
363
|
try {
|
|
238
364
|
writeLedger(root, [...listInstalledPlugins(root), item]);
|
|
239
365
|
} catch (error) {
|
|
@@ -323,7 +449,8 @@ function writeLedger(root: string, entries: InstalledPlugin[]): void {
|
|
|
323
449
|
const unique = new Map(entries.map(entry => [`${entry.market}/${entry.name}/${entry.version}`, entry]));
|
|
324
450
|
const temp = `${ledger}.${process.pid}.tmp`;
|
|
325
451
|
try {
|
|
326
|
-
writeFileSync(temp, JSON.stringify([...unique.values()].map(({ name, version, market, sha256
|
|
452
|
+
writeFileSync(temp, JSON.stringify([...unique.values()].map(({ name, version, market, sha256, installedAt }) =>
|
|
453
|
+
({ name, version, market, sha256, ...(installedAt === undefined ? {} : { installedAt }) })), null, 2));
|
|
327
454
|
renameSync(temp, ledger);
|
|
328
455
|
} finally {
|
|
329
456
|
rmSync(temp, { force: true });
|
|
@@ -334,7 +461,7 @@ export function listInstalledPlugins(root = elanousStateRoot()): InstalledPlugin
|
|
|
334
461
|
const base = join(root, 'plugins');
|
|
335
462
|
if (!existsSync(base)) return [];
|
|
336
463
|
const result: InstalledPlugin[] = [];
|
|
337
|
-
let ledger: Array<Pick<InstalledPlugin, 'name' | 'version' | 'market' | 'sha256'>> = [];
|
|
464
|
+
let ledger: Array<Pick<InstalledPlugin, 'name' | 'version' | 'market' | 'sha256' | 'installedAt'>> = [];
|
|
338
465
|
try {
|
|
339
466
|
ledger = JSON.parse(readFileSync(join(base, 'installed.json'), 'utf8'));
|
|
340
467
|
if (!Array.isArray(ledger)) ledger = [];
|
|
@@ -348,8 +475,12 @@ export function listInstalledPlugins(root = elanousStateRoot()): InstalledPlugin
|
|
|
348
475
|
const path = join(base, market.name, name.name, version.name);
|
|
349
476
|
try {
|
|
350
477
|
const manifest = loadPluginManifestFromDir(path, { id: name.name }).manifest;
|
|
351
|
-
if (manifest.id === name.name && manifest.version === version.name)
|
|
352
|
-
|
|
478
|
+
if (manifest.id === name.name && manifest.version === version.name) {
|
|
479
|
+
const recorded = ledger.find(entry => entry.name === name.name && entry.version === version.name && entry.market === market.name);
|
|
480
|
+
result.push({ name: name.name, version: version.name, market: market.name, path,
|
|
481
|
+
sha256: recorded?.sha256 ?? null,
|
|
482
|
+
...(typeof recorded?.installedAt === 'string' ? { installedAt: recorded.installedAt } : {}) });
|
|
483
|
+
}
|
|
353
484
|
} catch { /* malformed packages are not installed plugins */ }
|
|
354
485
|
}
|
|
355
486
|
}
|