@repo-os/repoos 0.5.45
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/LICENSE.md +110 -0
- package/README.md +272 -0
- package/dist/.build-info.json +4 -0
- package/dist/.build-stamp.json +3 -0
- package/dist/cli/colors.d.ts +13 -0
- package/dist/cli/colors.js +37 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +221 -0
- package/dist/commands/check.d.ts +170 -0
- package/dist/commands/check.js +1227 -0
- package/dist/commands/docs.d.ts +2 -0
- package/dist/commands/docs.js +70 -0
- package/dist/commands/gc.d.ts +1 -0
- package/dist/commands/gc.js +57 -0
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.js +699 -0
- package/dist/commands/screenshots.d.ts +7 -0
- package/dist/commands/screenshots.js +77 -0
- package/dist/commands/serve.d.ts +55 -0
- package/dist/commands/serve.js +204 -0
- package/dist/commands/status.d.ts +140 -0
- package/dist/commands/status.js +569 -0
- package/dist/commands/stop.d.ts +1 -0
- package/dist/commands/stop.js +59 -0
- package/dist/commands/tasks.d.ts +47 -0
- package/dist/commands/tasks.js +334 -0
- package/dist/commands/tunnel.d.ts +1 -0
- package/dist/commands/tunnel.js +1271 -0
- package/dist/commands/ui-harness.d.ts +53 -0
- package/dist/commands/ui-harness.js +62 -0
- package/dist/commands/ui-smoke.d.ts +6 -0
- package/dist/commands/ui-smoke.js +220 -0
- package/dist/commands/upgrade.d.ts +15 -0
- package/dist/commands/upgrade.js +188 -0
- package/dist/core/auth-store.d.ts +81 -0
- package/dist/core/auth-store.js +530 -0
- package/dist/core/auth.d.ts +75 -0
- package/dist/core/auth.js +124 -0
- package/dist/core/bootstrap.d.ts +41 -0
- package/dist/core/bootstrap.js +236 -0
- package/dist/core/build.d.ts +92 -0
- package/dist/core/build.js +249 -0
- package/dist/core/canary.d.ts +10 -0
- package/dist/core/canary.js +14 -0
- package/dist/core/check-failure-summary.d.ts +10 -0
- package/dist/core/check-failure-summary.js +34 -0
- package/dist/core/config.d.ts +160 -0
- package/dist/core/config.js +1288 -0
- package/dist/core/context-pack.d.ts +74 -0
- package/dist/core/context-pack.js +778 -0
- package/dist/core/db.d.ts +236 -0
- package/dist/core/db.js +747 -0
- package/dist/core/detect.d.ts +69 -0
- package/dist/core/detect.js +251 -0
- package/dist/core/docs.d.ts +76 -0
- package/dist/core/docs.js +225 -0
- package/dist/core/frontmatter.d.ts +29 -0
- package/dist/core/frontmatter.js +215 -0
- package/dist/core/git.d.ts +505 -0
- package/dist/core/git.js +1453 -0
- package/dist/core/indexer.d.ts +33 -0
- package/dist/core/indexer.js +282 -0
- package/dist/core/input.d.ts +66 -0
- package/dist/core/input.js +222 -0
- package/dist/core/logger.d.ts +70 -0
- package/dist/core/logger.js +138 -0
- package/dist/core/models.d.ts +45 -0
- package/dist/core/models.js +291 -0
- package/dist/core/net-probe.d.ts +4 -0
- package/dist/core/net-probe.js +20 -0
- package/dist/core/providers/deepinfra.d.ts +23 -0
- package/dist/core/providers/deepinfra.js +44 -0
- package/dist/core/providers/index.d.ts +24 -0
- package/dist/core/providers/index.js +59 -0
- package/dist/core/providers/openrouter.d.ts +28 -0
- package/dist/core/providers/openrouter.js +82 -0
- package/dist/core/providers/spend.d.ts +86 -0
- package/dist/core/providers/spend.js +286 -0
- package/dist/core/providers/types.d.ts +56 -0
- package/dist/core/providers/types.js +39 -0
- package/dist/core/repoos.d.ts +59 -0
- package/dist/core/repoos.js +221 -0
- package/dist/core/runtime.d.ts +35 -0
- package/dist/core/runtime.js +167 -0
- package/dist/core/skills-registry.d.ts +39 -0
- package/dist/core/skills-registry.js +148 -0
- package/dist/core/skills-registry.test.d.ts +1 -0
- package/dist/core/skills-registry.test.js +52 -0
- package/dist/core/sysmem.d.ts +7 -0
- package/dist/core/sysmem.js +55 -0
- package/dist/core/tailscale.d.ts +2 -0
- package/dist/core/tailscale.js +19 -0
- package/dist/core/task.d.ts +53 -0
- package/dist/core/task.js +310 -0
- package/dist/core/toml-line.d.ts +20 -0
- package/dist/core/toml-line.js +52 -0
- package/dist/core/toml-validate.d.ts +44 -0
- package/dist/core/toml-validate.js +335 -0
- package/dist/core/tunnel-assistant.d.ts +30 -0
- package/dist/core/tunnel-assistant.js +65 -0
- package/dist/core/tunnel-registry.d.ts +96 -0
- package/dist/core/tunnel-registry.js +267 -0
- package/dist/core/tunnel-registry.test.d.ts +1 -0
- package/dist/core/tunnel-registry.test.js +309 -0
- package/dist/core/tunnel.d.ts +103 -0
- package/dist/core/tunnel.js +320 -0
- package/dist/core/types.d.ts +811 -0
- package/dist/core/types.js +27 -0
- package/dist/core/worktree-gc.d.ts +57 -0
- package/dist/core/worktree-gc.js +194 -0
- package/dist/server/__tests__/integration-job.test.d.ts +1 -0
- package/dist/server/__tests__/integration-job.test.js +129 -0
- package/dist/server/agents.d.ts +1020 -0
- package/dist/server/agents.js +4070 -0
- package/dist/server/attachments.d.ts +51 -0
- package/dist/server/attachments.js +130 -0
- package/dist/server/auto-engineering.d.ts +45 -0
- package/dist/server/auto-engineering.js +275 -0
- package/dist/server/branchless-release.d.ts +22 -0
- package/dist/server/branchless-release.js +67 -0
- package/dist/server/built-in-agents.d.ts +305 -0
- package/dist/server/built-in-agents.js +1838 -0
- package/dist/server/cto-monitor.d.ts +35 -0
- package/dist/server/cto-monitor.js +281 -0
- package/dist/server/cto.d.ts +125 -0
- package/dist/server/cto.js +590 -0
- package/dist/server/deployments.d.ts +119 -0
- package/dist/server/deployments.js +325 -0
- package/dist/server/diff-snapshot.d.ts +20 -0
- package/dist/server/diff-snapshot.js +73 -0
- package/dist/server/done.d.ts +160 -0
- package/dist/server/done.js +488 -0
- package/dist/server/freeform.d.ts +55 -0
- package/dist/server/freeform.js +179 -0
- package/dist/server/handoff.d.ts +99 -0
- package/dist/server/handoff.js +575 -0
- package/dist/server/hetzner.d.ts +45 -0
- package/dist/server/hetzner.js +117 -0
- package/dist/server/icons.d.ts +8 -0
- package/dist/server/icons.js +195 -0
- package/dist/server/integration-job.d.ts +75 -0
- package/dist/server/integration-job.js +144 -0
- package/dist/server/integration-orchestrator.d.ts +211 -0
- package/dist/server/integration-orchestrator.js +1299 -0
- package/dist/server/integration-status.d.ts +48 -0
- package/dist/server/integration-status.js +71 -0
- package/dist/server/live-index.d.ts +295 -0
- package/dist/server/live-index.js +452 -0
- package/dist/server/model-test.d.ts +20 -0
- package/dist/server/model-test.js +100 -0
- package/dist/server/ntfy.d.ts +57 -0
- package/dist/server/ntfy.js +112 -0
- package/dist/server/pm-attachments.d.ts +35 -0
- package/dist/server/pm-attachments.js +181 -0
- package/dist/server/pm-runs.d.ts +58 -0
- package/dist/server/pm-runs.js +80 -0
- package/dist/server/preview.d.ts +174 -0
- package/dist/server/preview.js +641 -0
- package/dist/server/release.d.ts +88 -0
- package/dist/server/release.js +449 -0
- package/dist/server/reload.d.ts +194 -0
- package/dist/server/reload.js +586 -0
- package/dist/server/remote-validation.d.ts +132 -0
- package/dist/server/remote-validation.js +478 -0
- package/dist/server/repo-lock.d.ts +36 -0
- package/dist/server/repo-lock.js +141 -0
- package/dist/server/review-guard.d.ts +14 -0
- package/dist/server/review-guard.js +159 -0
- package/dist/server/review.d.ts +243 -0
- package/dist/server/review.js +1211 -0
- package/dist/server/routes/agents.d.ts +6 -0
- package/dist/server/routes/agents.js +28 -0
- package/dist/server/routes/auth.d.ts +60 -0
- package/dist/server/routes/auth.js +755 -0
- package/dist/server/routes/config.d.ts +20 -0
- package/dist/server/routes/config.js +347 -0
- package/dist/server/routes/debugger.d.ts +33 -0
- package/dist/server/routes/debugger.js +360 -0
- package/dist/server/routes/deployments.d.ts +3 -0
- package/dist/server/routes/deployments.js +16 -0
- package/dist/server/routes/docs.d.ts +8 -0
- package/dist/server/routes/docs.js +135 -0
- package/dist/server/routes/helpers.d.ts +21 -0
- package/dist/server/routes/helpers.js +171 -0
- package/dist/server/routes/index.d.ts +19 -0
- package/dist/server/routes/index.js +18 -0
- package/dist/server/routes/info.d.ts +17 -0
- package/dist/server/routes/info.js +177 -0
- package/dist/server/routes/inputs.d.ts +7 -0
- package/dist/server/routes/inputs.js +125 -0
- package/dist/server/routes/model-providers.d.ts +27 -0
- package/dist/server/routes/model-providers.js +94 -0
- package/dist/server/routes/models.d.ts +3 -0
- package/dist/server/routes/models.js +36 -0
- package/dist/server/routes/notify.d.ts +2 -0
- package/dist/server/routes/notify.js +17 -0
- package/dist/server/routes/playground.d.ts +29 -0
- package/dist/server/routes/playground.js +81 -0
- package/dist/server/routes/release.d.ts +20 -0
- package/dist/server/routes/release.js +86 -0
- package/dist/server/routes/router.d.ts +12 -0
- package/dist/server/routes/router.js +32 -0
- package/dist/server/routes/skill-registry.d.ts +5 -0
- package/dist/server/routes/skill-registry.js +51 -0
- package/dist/server/routes/tasks.d.ts +58 -0
- package/dist/server/routes/tasks.js +1442 -0
- package/dist/server/routes/transcribe.d.ts +2 -0
- package/dist/server/routes/transcribe.js +124 -0
- package/dist/server/routes/types.d.ts +68 -0
- package/dist/server/routes/types.js +1 -0
- package/dist/server/routes/ui.d.ts +5 -0
- package/dist/server/routes/ui.js +81 -0
- package/dist/server/routes/utils.d.ts +3 -0
- package/dist/server/routes/utils.js +23 -0
- package/dist/server/serve-reaper.d.ts +135 -0
- package/dist/server/serve-reaper.js +491 -0
- package/dist/server/server.d.ts +49 -0
- package/dist/server/server.js +2171 -0
- package/dist/server/supervisor.d.ts +89 -0
- package/dist/server/supervisor.js +231 -0
- package/dist/server/system.d.ts +198 -0
- package/dist/server/system.js +480 -0
- package/dist/server/task-check.d.ts +45 -0
- package/dist/server/task-check.js +71 -0
- package/dist/server/task-transitions.d.ts +51 -0
- package/dist/server/task-transitions.js +78 -0
- package/dist/server/task-watchdog.d.ts +158 -0
- package/dist/server/task-watchdog.js +681 -0
- package/dist/server/test-run.d.ts +28 -0
- package/dist/server/test-run.js +81 -0
- package/dist/server/watcher.d.ts +21 -0
- package/dist/server/watcher.js +194 -0
- package/dist/server/write.d.ts +94 -0
- package/dist/server/write.js +345 -0
- package/dist/ui/assets/AgentsView-CF4wRvHh.css +1 -0
- package/dist/ui/assets/AgentsView-D-myL-kA.js +1 -0
- package/dist/ui/assets/ContextView-D3TC-RSp.css +1 -0
- package/dist/ui/assets/ContextView-DsOqAdyE.js +4 -0
- package/dist/ui/assets/DashboardView-BOhsIJqO.js +1 -0
- package/dist/ui/assets/DashboardView-Ck9o0RP2.css +1 -0
- package/dist/ui/assets/DeploymentsView--iIUVfEH.css +1 -0
- package/dist/ui/assets/DeploymentsView-DXu_FYR2.js +1 -0
- package/dist/ui/assets/InputsView-24HVbVPk.js +1 -0
- package/dist/ui/assets/InputsView-BLb7GHtx.css +1 -0
- package/dist/ui/assets/LoginView-6QlGg6cx.css +1 -0
- package/dist/ui/assets/LoginView-BqupI0Lt.js +1 -0
- package/dist/ui/assets/ReleasesView-BEG1A0YH.css +1 -0
- package/dist/ui/assets/ReleasesView-CAgMuxpe.js +3 -0
- package/dist/ui/assets/SettingsView-CA-NYSMK.css +1 -0
- package/dist/ui/assets/SettingsView-O3iZSnjg.js +3 -0
- package/dist/ui/assets/WorkView-D8Tl6qwy.js +1 -0
- package/dist/ui/assets/WorkView-DMYL2s3W.css +1 -0
- package/dist/ui/assets/abnfDiagram-VCTEODGH-J5lkSnEE.js +1 -0
- package/dist/ui/assets/arc-BHZnmhhM.js +1 -0
- package/dist/ui/assets/architecture-7GRP2DOG-pLdFt-xS.js +1 -0
- package/dist/ui/assets/architectureDiagram-5GKGNRK7-C3k89uKX.js +36 -0
- package/dist/ui/assets/array-BifhSqXX.js +1 -0
- package/dist/ui/assets/blockDiagram-I7D4REHJ-AWNoBzop.js +129 -0
- package/dist/ui/assets/button-lbikqSph.js +1 -0
- package/dist/ui/assets/c4Diagram-7LVT6UL2-DKG1D5gd.js +38 -0
- package/dist/ui/assets/channel-gA4qBv3A.js +1 -0
- package/dist/ui/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- package/dist/ui/assets/chunk-4HAMMTFA-D1YR8lF_.js +62 -0
- package/dist/ui/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- package/dist/ui/assets/chunk-75Z2AOVW-5GuBxfsL.js +2 -0
- package/dist/ui/assets/chunk-DU6HZSFF-DfdNNqwa.js +127 -0
- package/dist/ui/assets/chunk-F27PBJKO-BPZzinQ4.js +1 -0
- package/dist/ui/assets/chunk-FOHPRMQF-l2z4K2jD.js +161 -0
- package/dist/ui/assets/chunk-GMAD6QVW-DGWvSKj5.js +72 -0
- package/dist/ui/assets/chunk-GVQU2GXP-D6Aw75wJ.js +1 -0
- package/dist/ui/assets/chunk-IMKFNOWR-B8mMBKCW.js +231 -0
- package/dist/ui/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- package/dist/ui/assets/chunk-L3NEJ4N5-D5OJuXYE.js +1 -0
- package/dist/ui/assets/chunk-OSK3NFVY-B7r9w2UW.js +10 -0
- package/dist/ui/assets/chunk-P2QGCYS3-C74X4AQX.js +1 -0
- package/dist/ui/assets/chunk-POPQ4Y6H-C5_u1gUS.js +1 -0
- package/dist/ui/assets/chunk-PWAF6VOD-DfoTLtxn.js +1 -0
- package/dist/ui/assets/chunk-SHT3W25Y-D40m2eqz.js +168 -0
- package/dist/ui/assets/chunk-SVP7TREG-euxi0Kap.js +88 -0
- package/dist/ui/assets/chunk-TICWLB2K-fkdxngzN.js +206 -0
- package/dist/ui/assets/chunk-XXDRQBXY-DFBUG-OT.js +1 -0
- package/dist/ui/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- package/dist/ui/assets/classDiagram-ZZMXUADV-BzW8bq2P.js +1 -0
- package/dist/ui/assets/classDiagram-v2-VYDZK3BY-BzW8bq2P.js +1 -0
- package/dist/ui/assets/cose-bilkent-JH36ORCC-BTcgMon1.js +1 -0
- package/dist/ui/assets/cynefin-OW5HDTMX-B5m_COsS.js +1 -0
- package/dist/ui/assets/cynefinDiagram-5FMLGOSQ-C-AG6CGe.js +62 -0
- package/dist/ui/assets/cytoscape.esm-FBntH-9t.js +321 -0
- package/dist/ui/assets/dagre-GXQ25YYZ-DDfDnp8L.js +4 -0
- package/dist/ui/assets/dagre-PrKaheQc.js +1 -0
- package/dist/ui/assets/defaultLocale-BFoDCU3G.js +1 -0
- package/dist/ui/assets/diagram-S7CK7UJ4-DjQZAlEY.js +30 -0
- package/dist/ui/assets/diagram-UQ7AKVKN-DAZ9yp1B.js +41 -0
- package/dist/ui/assets/diagram-VSXAHHWV-BWepUKJD.js +3 -0
- package/dist/ui/assets/diagram-VX7I27RA-EcV1J81b.js +24 -0
- package/dist/ui/assets/diagram-Z3DM3KII-Dbz7XU_K.js +24 -0
- package/dist/ui/assets/dist-DTg6UBE_.js +1 -0
- package/dist/ui/assets/ebnfDiagram-PWID7BFC-BUyQw8Mm.js +1 -0
- package/dist/ui/assets/erDiagram-RLTQ6QDP-WVf8ukKt.js +99 -0
- package/dist/ui/assets/eventmodeling-NTZA5JFV-DHqyLOAn.js +1 -0
- package/dist/ui/assets/flowDiagram-HODETNUW-CYk_ze5K.js +1 -0
- package/dist/ui/assets/ganttDiagram-EL5Y4UJY-rB4MZuB-.js +292 -0
- package/dist/ui/assets/gitGraph-4MIJSDKK-BOTWM8dY.js +1 -0
- package/dist/ui/assets/gitGraphDiagram-WWUBYQGX-eYscuhtP.js +106 -0
- package/dist/ui/assets/graphlib-DS17s2tU.js +1 -0
- package/dist/ui/assets/index-BACBfnga.css +1 -0
- package/dist/ui/assets/index-BOQ-ust6.js +26 -0
- package/dist/ui/assets/info-A6RAGUB7-_67wOORe.js +1 -0
- package/dist/ui/assets/infoDiagram-27XIBGKW-XVXbddTA.js +2 -0
- package/dist/ui/assets/init-C-OQMol4.js +1 -0
- package/dist/ui/assets/ishikawaDiagram-5VMMS53U-ClLNiEru.js +70 -0
- package/dist/ui/assets/journeyDiagram-3NMN7TZE-NRawjR6i.js +139 -0
- package/dist/ui/assets/kanban-definition-UXKFOSKX-BgSzBfIx.js +89 -0
- package/dist/ui/assets/katex-DolUETbr.js +257 -0
- package/dist/ui/assets/line-KAyfuul4.js +1 -0
- package/dist/ui/assets/linear-cCCPOpu7.js +1 -0
- package/dist/ui/assets/mermaid-parser.core-CgXZk1iN.js +7 -0
- package/dist/ui/assets/mermaid.core-BUUkChHW.js +44 -0
- package/dist/ui/assets/mindmap-definition-YA3MSWOX-Cpn7vW4r.js +96 -0
- package/dist/ui/assets/ordinal-BDEzSJ7C.js +1 -0
- package/dist/ui/assets/packet-AYTQ26CC-0lZwM-d6.js +1 -0
- package/dist/ui/assets/path-COt_16Va.js +1 -0
- package/dist/ui/assets/pegDiagram-XKGWAZYB-impy0inn.js +1 -0
- package/dist/ui/assets/pie-WAS4IAKB-CqfdGHYg.js +1 -0
- package/dist/ui/assets/pieDiagram-E7YTZNPT-CxjL4I1h.js +39 -0
- package/dist/ui/assets/quadrantDiagram-AXDQQJYC-Cjd1QMIe.js +7 -0
- package/dist/ui/assets/radar-RG4KPBEZ-CRkQIwiv.js +1 -0
- package/dist/ui/assets/railroad-74A4TZTK-i67XSWTB.js +1 -0
- package/dist/ui/assets/railroad-abnf-HS5TGJTU-Cv9m5aS3.js +1 -0
- package/dist/ui/assets/railroad-ebnf-LZEXJU2U-C5ZIn-Jh.js +1 -0
- package/dist/ui/assets/railroad-peg-WCYAUIDC-CGra9Oq-.js +1 -0
- package/dist/ui/assets/railroadDiagram-O6MQD6OU-_pK0qyaY.js +1 -0
- package/dist/ui/assets/releases-B0WNV-SY.js +1 -0
- package/dist/ui/assets/repoos-cto-square.webp +0 -0
- package/dist/ui/assets/repoos-helpful-pm-square.webp +0 -0
- package/dist/ui/assets/repoos-orchestrator-square.webp +0 -0
- package/dist/ui/assets/repoos-performance-agent-square.webp +0 -0
- package/dist/ui/assets/repoos-ross-from-friends-square.webp +0 -0
- package/dist/ui/assets/repoos-tech-debt-agent-square.webp +0 -0
- package/dist/ui/assets/requirementDiagram-BXWQKSXE-CRrFX-yw.js +84 -0
- package/dist/ui/assets/rough.esm-By172zw-.js +1 -0
- package/dist/ui/assets/sankeyDiagram-P5KCCOFB-X_xFRj9E.js +40 -0
- package/dist/ui/assets/sequenceDiagram-WJ2MYXX4-Byb49lKf.js +162 -0
- package/dist/ui/assets/sizeCapture-INFHLROL-B0uUizjq.js +1 -0
- package/dist/ui/assets/src-BH-TyZbA.js +1 -0
- package/dist/ui/assets/stateDiagram-D77RDMKH-CzaPrqYF.js +1 -0
- package/dist/ui/assets/stateDiagram-v2-MP3YSRHH-BCXckz7K.js +1 -0
- package/dist/ui/assets/swimlanes-42K2YHIH-Cx_1lnqr.js +1 -0
- package/dist/ui/assets/swimlanesDiagram-VR7AAH4N-54qsKi6A.js +8 -0
- package/dist/ui/assets/timeline-definition-24CTP7MA-B1IbZPQ-.js +120 -0
- package/dist/ui/assets/treeView-Q6P3EWNA-DFWhVE3d.js +1 -0
- package/dist/ui/assets/treemap-WGGIJYW6-BmbYPThn.js +1 -0
- package/dist/ui/assets/vendor-ui-DPm7zLxi.js +3 -0
- package/dist/ui/assets/vendor-vue-CobgyEJp.js +1 -0
- package/dist/ui/assets/vennDiagram-4TSXK5OY-Ae2AtRpt.js +34 -0
- package/dist/ui/assets/wardley-WFR3VGLG-BtD4X4tL.js +1 -0
- package/dist/ui/assets/wardleyDiagram-VM6X3IG4-B3YN2fTd.js +78 -0
- package/dist/ui/assets/xychartDiagram-S5SC5T6Z-CIzJfMKA.js +7 -0
- package/dist/ui/favicon.svg +11 -0
- package/dist/ui/index.html +49 -0
- package/dist/ui/sw.js +48 -0
- package/package.json +91 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** A coding agent RepoOS knows about, regardless of whether it is installed. */
|
|
2
|
+
export interface KnownAgent {
|
|
3
|
+
/** Stable id, e.g. "opencode", "qwen-code". */
|
|
4
|
+
id: string;
|
|
5
|
+
/** Display name, e.g. "claude code". */
|
|
6
|
+
name: string;
|
|
7
|
+
/** The binary searched on PATH. */
|
|
8
|
+
binary: string;
|
|
9
|
+
/** True when RepoOS has a driver for this CLI (can start headless runs). */
|
|
10
|
+
drivable: boolean;
|
|
11
|
+
/** Copyable install hint shown when the CLI is missing. */
|
|
12
|
+
installHint: string;
|
|
13
|
+
}
|
|
14
|
+
/** One row of the detection result. */
|
|
15
|
+
export interface DetectedAgent extends KnownAgent {
|
|
16
|
+
/** Whether the binary resolves on PATH. */
|
|
17
|
+
installed: boolean;
|
|
18
|
+
/** Absolute path of the resolved binary, or null when not found. */
|
|
19
|
+
path: string | null;
|
|
20
|
+
/** First line of `--version` output, or null when the probe failed. */
|
|
21
|
+
version: string | null;
|
|
22
|
+
/**
|
|
23
|
+
* Whether the installed binary is headless-drivable:
|
|
24
|
+
* - `false` — a desktop-only install (e.g. a macOS `.app` bundle shadows PATH)
|
|
25
|
+
* - `true` — headless-capable
|
|
26
|
+
* - `null` — not installed (headless is unknown)
|
|
27
|
+
*/
|
|
28
|
+
headless: boolean | null;
|
|
29
|
+
}
|
|
30
|
+
/** Default ceiling on the `--version` probe, ms. A hung binary is SIGKILLed. */
|
|
31
|
+
export declare const VERSION_TIMEOUT_MS = 1500;
|
|
32
|
+
/** The known agent list. `drivable` mirrors the drivers in src/server/agents.ts. */
|
|
33
|
+
export declare const KNOWN_AGENTS: KnownAgent[];
|
|
34
|
+
/** True when `p` exists and is executable (X_OK; F_OK on Windows). */
|
|
35
|
+
export declare function isExecutable(p: string): boolean;
|
|
36
|
+
/** Candidate filenames for a binary name (Windows adds `.cmd` / `.exe` / `.bat`). */
|
|
37
|
+
export declare function binaryCandidates(binary: string): string[];
|
|
38
|
+
/**
|
|
39
|
+
* Resolve a binary name against a PATH string. Returns the first existing,
|
|
40
|
+
* executable candidate, or null. PATH search order is preserved; empty entries
|
|
41
|
+
* are skipped.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveBinary(binary: string, pathEnv?: string): string | null;
|
|
44
|
+
/** True when a resolved binary path lives inside a macOS `.app` bundle. */
|
|
45
|
+
export declare function isAppBundleBinary(p: string): boolean;
|
|
46
|
+
/** True when `--version` output carries a desktop-app relaunch signature. */
|
|
47
|
+
export declare function isDesktopOutputSignature(version: string | null): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Capture the first line of `<binaryPath> --version` output. Never throws and
|
|
50
|
+
* never hangs: a spawn failure, error event, or the timeout all resolve null.
|
|
51
|
+
*/
|
|
52
|
+
export declare function captureVersion(binaryPath: string, timeoutMs: number): Promise<string | null>;
|
|
53
|
+
export interface DetectOptions {
|
|
54
|
+
/** PATH string to search. Defaults to `process.env.PATH`. */
|
|
55
|
+
pathEnv?: string;
|
|
56
|
+
/** `--version` probe timeout in ms. Defaults to VERSION_TIMEOUT_MS. */
|
|
57
|
+
versionTimeoutMs?: number;
|
|
58
|
+
/** Agent list to probe (tests inject a narrowed list). */
|
|
59
|
+
agents?: readonly KnownAgent[];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Probe every known coding agent: resolve its binary on PATH and, when found,
|
|
63
|
+
* capture a version. Rows for missing binaries carry `installed: false`.
|
|
64
|
+
*
|
|
65
|
+
* A binary inside a macOS `.app` bundle is not probed for a version — spawning
|
|
66
|
+
* it could relaunch the desktop app on the user's screen — and is reported as
|
|
67
|
+
* desktop-only (`headless: false`).
|
|
68
|
+
*/
|
|
69
|
+
export declare function detectAgents(opts?: DetectOptions): Promise<DetectedAgent[]>;
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coding-agent detection — tells the Agents page what is actually installed,
|
|
3
|
+
* headless-drivable, and missing on the machine.
|
|
4
|
+
*
|
|
5
|
+
* Zero runtime deps: `node:fs` / `node:path` for PATH resolution (no `which`),
|
|
6
|
+
* `node:child_process` for a best-effort `--version` probe. Everything here is
|
|
7
|
+
* fail-soft: a missing binary, a broken PATH entry, or a hung version probe
|
|
8
|
+
* must never throw — callers (the HTTP endpoint, the UI) always get a row.
|
|
9
|
+
*/
|
|
10
|
+
import { spawn } from "node:child_process";
|
|
11
|
+
import { accessSync, constants } from "node:fs";
|
|
12
|
+
import { delimiter, join } from "node:path";
|
|
13
|
+
/** Default ceiling on the `--version` probe, ms. A hung binary is SIGKILLed. */
|
|
14
|
+
export const VERSION_TIMEOUT_MS = 1500;
|
|
15
|
+
/** Cap on the version string kept for display. */
|
|
16
|
+
const VERSION_MAX_LEN = 200;
|
|
17
|
+
/** The known agent list. `drivable` mirrors the drivers in src/server/agents.ts. */
|
|
18
|
+
export const KNOWN_AGENTS = [
|
|
19
|
+
{
|
|
20
|
+
id: "opencode",
|
|
21
|
+
name: "opencode",
|
|
22
|
+
binary: "opencode",
|
|
23
|
+
drivable: true,
|
|
24
|
+
installHint: "npm i -g opencode-ai",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "claude-code",
|
|
28
|
+
name: "claude code",
|
|
29
|
+
binary: "claude",
|
|
30
|
+
drivable: true,
|
|
31
|
+
installHint: "npm i -g @anthropic-ai/claude-code",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "qwen-code",
|
|
35
|
+
name: "qwen code",
|
|
36
|
+
binary: "qwen",
|
|
37
|
+
drivable: true,
|
|
38
|
+
installHint: "npm i -g @qwen-code/qwen-code",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "codex",
|
|
42
|
+
name: "codex",
|
|
43
|
+
binary: "codex",
|
|
44
|
+
drivable: true,
|
|
45
|
+
installHint: "npm i -g @openai/codex",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "gemini",
|
|
49
|
+
name: "gemini",
|
|
50
|
+
binary: "gemini",
|
|
51
|
+
drivable: false,
|
|
52
|
+
installHint: "npm i -g @google/gemini-cli",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: "copilot",
|
|
56
|
+
name: "github copilot",
|
|
57
|
+
binary: "copilot",
|
|
58
|
+
drivable: true,
|
|
59
|
+
installHint: "npm i -g @github/copilot",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: "aider",
|
|
63
|
+
name: "aider",
|
|
64
|
+
binary: "aider",
|
|
65
|
+
drivable: false,
|
|
66
|
+
installHint: "pipx install aider-chat",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "goose",
|
|
70
|
+
name: "goose",
|
|
71
|
+
binary: "goose",
|
|
72
|
+
drivable: false,
|
|
73
|
+
installHint: "curl -fsSL https://github.com/block/goose/releases/download/stable/download_cli.sh | bash",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: "antigravity",
|
|
77
|
+
name: "antigravity",
|
|
78
|
+
binary: "agy",
|
|
79
|
+
drivable: false,
|
|
80
|
+
installHint: "npm i -g @google/antigravity",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "kiro",
|
|
84
|
+
name: "kiro",
|
|
85
|
+
binary: "kiro-cli",
|
|
86
|
+
drivable: true,
|
|
87
|
+
installHint: "npm i -g kiro-cli",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: "pi",
|
|
91
|
+
name: "pi",
|
|
92
|
+
binary: "pi",
|
|
93
|
+
drivable: false,
|
|
94
|
+
installHint: "npm i -g @earendil-works/pi-coding-agent",
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
const WIN32_SCRIPT_RE = /\.(cmd|bat)$/i;
|
|
98
|
+
/** A binary inside a macOS `.app` bundle: `Foo.app/Contents/MacOS/<bin>`. */
|
|
99
|
+
const APP_BUNDLE_RE = /\.app[\/\\]Contents[\/\\]MacOS[\/\\]/i;
|
|
100
|
+
/**
|
|
101
|
+
* `--version` output signatures of a desktop app that re-opens its GUI instead
|
|
102
|
+
* of answering on stdout. Deliberately narrow and opencode-only to avoid false
|
|
103
|
+
* positives from headless CLIs that embed Electron.
|
|
104
|
+
*/
|
|
105
|
+
const DESKTOP_OUTPUT_SIGNATURES = [
|
|
106
|
+
/relaunch/i,
|
|
107
|
+
/desktop application/i,
|
|
108
|
+
/launching the desktop/i,
|
|
109
|
+
/this app is a desktop/i,
|
|
110
|
+
];
|
|
111
|
+
/** True when `p` exists and is executable (X_OK; F_OK on Windows). */
|
|
112
|
+
export function isExecutable(p) {
|
|
113
|
+
try {
|
|
114
|
+
accessSync(p, constants.X_OK);
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** Candidate filenames for a binary name (Windows adds `.cmd` / `.exe` / `.bat`). */
|
|
122
|
+
export function binaryCandidates(binary) {
|
|
123
|
+
const out = [binary];
|
|
124
|
+
if (process.platform === "win32") {
|
|
125
|
+
out.push(`${binary}.cmd`, `${binary}.exe`, `${binary}.bat`);
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Resolve a binary name against a PATH string. Returns the first existing,
|
|
131
|
+
* executable candidate, or null. PATH search order is preserved; empty entries
|
|
132
|
+
* are skipped.
|
|
133
|
+
*/
|
|
134
|
+
export function resolveBinary(binary, pathEnv = process.env.PATH ?? "") {
|
|
135
|
+
for (const dir of pathEnv.split(delimiter)) {
|
|
136
|
+
if (!dir)
|
|
137
|
+
continue;
|
|
138
|
+
for (const name of binaryCandidates(binary)) {
|
|
139
|
+
const full = join(dir, name);
|
|
140
|
+
if (isExecutable(full))
|
|
141
|
+
return full;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
/** True when a resolved binary path lives inside a macOS `.app` bundle. */
|
|
147
|
+
export function isAppBundleBinary(p) {
|
|
148
|
+
return APP_BUNDLE_RE.test(p.replace(/\\/g, "/"));
|
|
149
|
+
}
|
|
150
|
+
/** True when `--version` output carries a desktop-app relaunch signature. */
|
|
151
|
+
export function isDesktopOutputSignature(version) {
|
|
152
|
+
if (!version)
|
|
153
|
+
return false;
|
|
154
|
+
return DESKTOP_OUTPUT_SIGNATURES.some((re) => re.test(version));
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Capture the first line of `<binaryPath> --version` output. Never throws and
|
|
158
|
+
* never hangs: a spawn failure, error event, or the timeout all resolve null.
|
|
159
|
+
*/
|
|
160
|
+
export function captureVersion(binaryPath, timeoutMs) {
|
|
161
|
+
return new Promise((resolve) => {
|
|
162
|
+
const needsShell = process.platform === "win32" && WIN32_SCRIPT_RE.test(binaryPath);
|
|
163
|
+
let proc;
|
|
164
|
+
try {
|
|
165
|
+
proc = spawn(binaryPath, ["--version"], {
|
|
166
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
167
|
+
shell: needsShell,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
resolve(null);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
let out = "";
|
|
175
|
+
let errOut = "";
|
|
176
|
+
let settled = false;
|
|
177
|
+
const done = (v) => {
|
|
178
|
+
if (settled)
|
|
179
|
+
return;
|
|
180
|
+
settled = true;
|
|
181
|
+
resolve(v);
|
|
182
|
+
};
|
|
183
|
+
const timer = setTimeout(() => {
|
|
184
|
+
try {
|
|
185
|
+
proc.kill("SIGKILL");
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
/* already gone */
|
|
189
|
+
}
|
|
190
|
+
done(null);
|
|
191
|
+
}, timeoutMs);
|
|
192
|
+
proc.stdout?.on("data", (c) => {
|
|
193
|
+
out += c.toString("utf8");
|
|
194
|
+
});
|
|
195
|
+
proc.stderr?.on("data", (c) => {
|
|
196
|
+
errOut += c.toString("utf8");
|
|
197
|
+
});
|
|
198
|
+
proc.on("error", () => {
|
|
199
|
+
clearTimeout(timer);
|
|
200
|
+
done(null);
|
|
201
|
+
});
|
|
202
|
+
proc.on("exit", () => {
|
|
203
|
+
clearTimeout(timer);
|
|
204
|
+
const text = `${out}${errOut ? ` ${errOut.trim()}` : ""}`.trim();
|
|
205
|
+
if (!text) {
|
|
206
|
+
done(null);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const first = text.split(/\r?\n/)[0].trim();
|
|
210
|
+
done(first.length > VERSION_MAX_LEN ? first.slice(0, VERSION_MAX_LEN) : first);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Probe every known coding agent: resolve its binary on PATH and, when found,
|
|
216
|
+
* capture a version. Rows for missing binaries carry `installed: false`.
|
|
217
|
+
*
|
|
218
|
+
* A binary inside a macOS `.app` bundle is not probed for a version — spawning
|
|
219
|
+
* it could relaunch the desktop app on the user's screen — and is reported as
|
|
220
|
+
* desktop-only (`headless: false`).
|
|
221
|
+
*/
|
|
222
|
+
export async function detectAgents(opts = {}) {
|
|
223
|
+
const pathEnv = opts.pathEnv ?? process.env.PATH ?? "";
|
|
224
|
+
const timeoutMs = opts.versionTimeoutMs ?? VERSION_TIMEOUT_MS;
|
|
225
|
+
const list = opts.agents ?? KNOWN_AGENTS;
|
|
226
|
+
const rows = await Promise.all(list.map(async (agent) => {
|
|
227
|
+
const resolved = resolveBinary(agent.binary, pathEnv);
|
|
228
|
+
if (!resolved) {
|
|
229
|
+
return { ...agent, installed: false, path: null, version: null, headless: null };
|
|
230
|
+
}
|
|
231
|
+
const appBundle = isAppBundleBinary(resolved);
|
|
232
|
+
let version = null;
|
|
233
|
+
if (!appBundle) {
|
|
234
|
+
try {
|
|
235
|
+
version = await captureVersion(resolved, timeoutMs);
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
version = null;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const desktopOnly = appBundle || (agent.id === "opencode" && isDesktopOutputSignature(version));
|
|
242
|
+
return {
|
|
243
|
+
...agent,
|
|
244
|
+
installed: true,
|
|
245
|
+
path: resolved,
|
|
246
|
+
version,
|
|
247
|
+
headless: !desktopOnly,
|
|
248
|
+
};
|
|
249
|
+
}));
|
|
250
|
+
return rows;
|
|
251
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { RepoOSConfig } from "./types.js";
|
|
2
|
+
export interface CreateDocumentInput {
|
|
3
|
+
path: string;
|
|
4
|
+
/** File body. Decoded from base64 to raw bytes when `encoding` is "base64". */
|
|
5
|
+
content: string;
|
|
6
|
+
/** "utf8" (default) writes `content` as text; "base64" writes decoded bytes — for image/PDF/font assets. */
|
|
7
|
+
encoding?: "utf8" | "base64";
|
|
8
|
+
}
|
|
9
|
+
export interface CreateDocumentResult {
|
|
10
|
+
path: string;
|
|
11
|
+
absPath: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Callback for running a prompt via PM agent (e.g. from server/agents.ts).
|
|
15
|
+
* Takes description, returns { path, content } or throws.
|
|
16
|
+
*/
|
|
17
|
+
export type FreeformDocumentGenerator = (description: string) => Promise<{
|
|
18
|
+
path: string;
|
|
19
|
+
content: string;
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* Validates a document path to ensure it stays within docsDir.
|
|
23
|
+
* Rejects absolute paths and paths with ".." traversal.
|
|
24
|
+
*/
|
|
25
|
+
export declare function validateDocPath(config: RepoOSConfig, path: string): {
|
|
26
|
+
valid: boolean;
|
|
27
|
+
reason?: string;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Turn a human skill name into a directory slug: lowercase, non-alphanumerics
|
|
31
|
+
* collapsed to single dashes, leading/trailing dashes trimmed.
|
|
32
|
+
*/
|
|
33
|
+
export declare function skillSlug(name: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* Validates a skill path — must be exactly `<skillsDir>/<slug>/SKILL.md` with a
|
|
36
|
+
* slug of lowercase alphanumerics and dashes. Skills live in their own dir, not
|
|
37
|
+
* under docsDir, so this is a separate check from validateDocPath.
|
|
38
|
+
*/
|
|
39
|
+
export declare function validateSkillPath(config: RepoOSConfig, path: string): {
|
|
40
|
+
valid: boolean;
|
|
41
|
+
reason?: string;
|
|
42
|
+
};
|
|
43
|
+
/** Assemble a SKILL.md body with the required `name` / `description` frontmatter. */
|
|
44
|
+
export declare function buildSkillMarkdown(name: string, description: string, body: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* Create a skill: `<skillsDir>/<slug>/SKILL.md`. When `content` is given it is
|
|
47
|
+
* written verbatim (upload); otherwise frontmatter is assembled from `name` and
|
|
48
|
+
* `description` and prepended to `body` (manual).
|
|
49
|
+
*/
|
|
50
|
+
export declare function createSkill(config: RepoOSConfig, input: {
|
|
51
|
+
name: string;
|
|
52
|
+
description?: string;
|
|
53
|
+
body?: string;
|
|
54
|
+
content?: string;
|
|
55
|
+
}): CreateDocumentResult;
|
|
56
|
+
/** Create a skill via the PM agent (freeform), mirroring createFreeformDocument. */
|
|
57
|
+
export declare function createFreeformSkill(config: RepoOSConfig, description: string, generator: FreeformDocumentGenerator): Promise<CreateDocumentResult>;
|
|
58
|
+
/** Build the PM agent's prompt for authoring a new skill. */
|
|
59
|
+
export declare function skillFreeformPrompt(description: string): string;
|
|
60
|
+
/**
|
|
61
|
+
* Create a document with explicit path and content.
|
|
62
|
+
* Validates path to stay within config.docsDir.
|
|
63
|
+
*/
|
|
64
|
+
export declare function createDocument(config: RepoOSConfig, input: CreateDocumentInput): CreateDocumentResult;
|
|
65
|
+
/**
|
|
66
|
+
* Create a document via PM agent (freeform).
|
|
67
|
+
* Calls the provided generator with description, validates result, writes file.
|
|
68
|
+
*/
|
|
69
|
+
export declare function createFreeformDocument(config: RepoOSConfig, description: string, generator: FreeformDocumentGenerator): Promise<CreateDocumentResult>;
|
|
70
|
+
/** Build the PM agent's prompt from a document description. */
|
|
71
|
+
export declare function docFreeformPrompt(description: string): string;
|
|
72
|
+
/** Parse the PM agent's generated document response (frontmatter `path` + markdown body). */
|
|
73
|
+
export declare function parseGeneratedDocument(output: string): {
|
|
74
|
+
path: string;
|
|
75
|
+
content: string;
|
|
76
|
+
};
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document creation core functions. Mirrors the task creation pattern
|
|
3
|
+
* from src/core/repoos.ts — manual path+content and PM-agent-backed freeform.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, mkdirSync, statSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { dirname, join, resolve } from "node:path";
|
|
7
|
+
import { parseDocument } from "./frontmatter.js";
|
|
8
|
+
/**
|
|
9
|
+
* Validates a document path to ensure it stays within docsDir.
|
|
10
|
+
* Rejects absolute paths and paths with ".." traversal.
|
|
11
|
+
*/
|
|
12
|
+
export function validateDocPath(config, path) {
|
|
13
|
+
if (!path)
|
|
14
|
+
return { valid: false, reason: "path is required" };
|
|
15
|
+
if (path.includes("..") || path.startsWith("/")) {
|
|
16
|
+
return { valid: false, reason: "invalid path: no .. or absolute paths" };
|
|
17
|
+
}
|
|
18
|
+
if (/[/\\]\s*$/.test(path)) {
|
|
19
|
+
return {
|
|
20
|
+
valid: false,
|
|
21
|
+
reason: "path must be a file, not a directory — include the filename (e.g. docs/design/assets/logo.svg)",
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const absPath = join(config.root, path);
|
|
25
|
+
const docsBase = join(config.root, config.docsDir);
|
|
26
|
+
const resolvedPath = resolve(absPath);
|
|
27
|
+
const resolvedBase = resolve(docsBase);
|
|
28
|
+
if (!resolvedPath.startsWith(resolvedBase + "/") && resolvedPath !== resolvedBase) {
|
|
29
|
+
return { valid: false, reason: `path must be under ${config.docsDir}` };
|
|
30
|
+
}
|
|
31
|
+
if (existsSync(resolvedPath) && statSync(resolvedPath).isDirectory()) {
|
|
32
|
+
return { valid: false, reason: `${path} is an existing directory — include the filename` };
|
|
33
|
+
}
|
|
34
|
+
return { valid: true };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Turn a human skill name into a directory slug: lowercase, non-alphanumerics
|
|
38
|
+
* collapsed to single dashes, leading/trailing dashes trimmed.
|
|
39
|
+
*/
|
|
40
|
+
export function skillSlug(name) {
|
|
41
|
+
return name
|
|
42
|
+
.trim()
|
|
43
|
+
.toLowerCase()
|
|
44
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
45
|
+
.replace(/^-+|-+$/g, "");
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Validates a skill path — must be exactly `<skillsDir>/<slug>/SKILL.md` with a
|
|
49
|
+
* slug of lowercase alphanumerics and dashes. Skills live in their own dir, not
|
|
50
|
+
* under docsDir, so this is a separate check from validateDocPath.
|
|
51
|
+
*/
|
|
52
|
+
export function validateSkillPath(config, path) {
|
|
53
|
+
if (!path)
|
|
54
|
+
return { valid: false, reason: "path is required" };
|
|
55
|
+
if (path.includes("..") || path.startsWith("/")) {
|
|
56
|
+
return { valid: false, reason: "invalid path: no .. or absolute paths" };
|
|
57
|
+
}
|
|
58
|
+
const parts = path.split("/");
|
|
59
|
+
if (parts.length !== 3 || parts[0] !== config.skillsDir || parts[2] !== "SKILL.md") {
|
|
60
|
+
return { valid: false, reason: `path must be ${config.skillsDir}/<name>/SKILL.md` };
|
|
61
|
+
}
|
|
62
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(parts[1])) {
|
|
63
|
+
return { valid: false, reason: "skill name must be lowercase letters, digits and dashes" };
|
|
64
|
+
}
|
|
65
|
+
return { valid: true };
|
|
66
|
+
}
|
|
67
|
+
/** Double-quote a YAML scalar so colons, quotes and `#` in the value stay safe. */
|
|
68
|
+
function yamlQuote(v) {
|
|
69
|
+
return `"${v.trim().replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
70
|
+
}
|
|
71
|
+
/** Assemble a SKILL.md body with the required `name` / `description` frontmatter. */
|
|
72
|
+
export function buildSkillMarkdown(name, description, body) {
|
|
73
|
+
const fm = [
|
|
74
|
+
"---",
|
|
75
|
+
`name: ${yamlQuote(name)}`,
|
|
76
|
+
`description: ${yamlQuote(description)}`,
|
|
77
|
+
"---",
|
|
78
|
+
"",
|
|
79
|
+
].join("\n");
|
|
80
|
+
return `${fm}\n${body.trim()}\n`;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Create a skill: `<skillsDir>/<slug>/SKILL.md`. When `content` is given it is
|
|
84
|
+
* written verbatim (upload); otherwise frontmatter is assembled from `name` and
|
|
85
|
+
* `description` and prepended to `body` (manual).
|
|
86
|
+
*/
|
|
87
|
+
export function createSkill(config, input) {
|
|
88
|
+
const slug = skillSlug(input.name);
|
|
89
|
+
const path = `${config.skillsDir}/${slug}/SKILL.md`;
|
|
90
|
+
const validation = validateSkillPath(config, path);
|
|
91
|
+
if (!validation.valid) {
|
|
92
|
+
throw new Error(validation.reason || "invalid skill name");
|
|
93
|
+
}
|
|
94
|
+
const content = typeof input.content === "string" && input.content.length > 0
|
|
95
|
+
? input.content
|
|
96
|
+
: buildSkillMarkdown(input.name, input.description ?? "", input.body ?? "");
|
|
97
|
+
const absPath = join(config.root, path);
|
|
98
|
+
mkdirSync(dirname(absPath), { recursive: true });
|
|
99
|
+
writeFileSync(absPath, content, "utf8");
|
|
100
|
+
return { path, absPath };
|
|
101
|
+
}
|
|
102
|
+
/** Create a skill via the PM agent (freeform), mirroring createFreeformDocument. */
|
|
103
|
+
export async function createFreeformSkill(config, description, generator) {
|
|
104
|
+
const { path, content } = await generator(description);
|
|
105
|
+
if (!path || !content) {
|
|
106
|
+
throw new Error("freeform generator returned unusable output (missing path or content)");
|
|
107
|
+
}
|
|
108
|
+
const validation = validateSkillPath(config, path);
|
|
109
|
+
if (!validation.valid) {
|
|
110
|
+
throw new Error(validation.reason || "invalid skill path from freeform generator");
|
|
111
|
+
}
|
|
112
|
+
const absPath = join(config.root, path);
|
|
113
|
+
mkdirSync(dirname(absPath), { recursive: true });
|
|
114
|
+
writeFileSync(absPath, content, "utf8");
|
|
115
|
+
return { path, absPath };
|
|
116
|
+
}
|
|
117
|
+
/** Build the PM agent's prompt for authoring a new skill. */
|
|
118
|
+
export function skillFreeformPrompt(description) {
|
|
119
|
+
return [
|
|
120
|
+
"You are the PM agent for RepoOS. Turn the user's description into a complete",
|
|
121
|
+
"Claude skill: a single SKILL.md file with YAML frontmatter.",
|
|
122
|
+
"",
|
|
123
|
+
"The user's description:",
|
|
124
|
+
"",
|
|
125
|
+
"```",
|
|
126
|
+
description.trim(),
|
|
127
|
+
"```",
|
|
128
|
+
"",
|
|
129
|
+
"Respond with a frontmatter block giving the destination path, then the SKILL.md",
|
|
130
|
+
"content, like:",
|
|
131
|
+
"---",
|
|
132
|
+
"path: skills/<name>/SKILL.md # <name> is lowercase letters, digits and dashes",
|
|
133
|
+
"---",
|
|
134
|
+
"---",
|
|
135
|
+
"name: <name>",
|
|
136
|
+
"description: <one-line description of when to use this skill>",
|
|
137
|
+
"---",
|
|
138
|
+
"",
|
|
139
|
+
"# <Title>",
|
|
140
|
+
"Instructions the agent should follow when this skill is active.",
|
|
141
|
+
"",
|
|
142
|
+
"Respond with ONLY the outer frontmatter block and the file content, starting with",
|
|
143
|
+
"the opening '---' line and with no preamble, commentary, or code fences.",
|
|
144
|
+
].join("\n");
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Create a document with explicit path and content.
|
|
148
|
+
* Validates path to stay within config.docsDir.
|
|
149
|
+
*/
|
|
150
|
+
export function createDocument(config, input) {
|
|
151
|
+
const validation = validateDocPath(config, input.path);
|
|
152
|
+
if (!validation.valid) {
|
|
153
|
+
throw new Error(validation.reason || "invalid document path");
|
|
154
|
+
}
|
|
155
|
+
const absPath = join(config.root, input.path);
|
|
156
|
+
const dir = dirname(absPath);
|
|
157
|
+
mkdirSync(dir, { recursive: true });
|
|
158
|
+
if (input.encoding === "base64") {
|
|
159
|
+
writeFileSync(absPath, Buffer.from(input.content, "base64"));
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
writeFileSync(absPath, input.content, "utf8");
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
path: input.path,
|
|
166
|
+
absPath,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Create a document via PM agent (freeform).
|
|
171
|
+
* Calls the provided generator with description, validates result, writes file.
|
|
172
|
+
*/
|
|
173
|
+
export async function createFreeformDocument(config, description, generator) {
|
|
174
|
+
const { path, content } = await generator(description);
|
|
175
|
+
if (!path || !content) {
|
|
176
|
+
throw new Error("freeform generator returned unusable output (missing path or content)");
|
|
177
|
+
}
|
|
178
|
+
const validation = validateDocPath(config, path);
|
|
179
|
+
if (!validation.valid) {
|
|
180
|
+
throw new Error(validation.reason || "invalid document path from freeform generator");
|
|
181
|
+
}
|
|
182
|
+
const absPath = join(config.root, path);
|
|
183
|
+
const dir = dirname(absPath);
|
|
184
|
+
mkdirSync(dir, { recursive: true });
|
|
185
|
+
writeFileSync(absPath, content, "utf8");
|
|
186
|
+
return {
|
|
187
|
+
path,
|
|
188
|
+
absPath,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/** Build the PM agent's prompt from a document description. */
|
|
192
|
+
export function docFreeformPrompt(description) {
|
|
193
|
+
return [
|
|
194
|
+
"You are the PM agent for RepoOS. Turn the user's description into a well-formatted,",
|
|
195
|
+
"complete Markdown document.",
|
|
196
|
+
"",
|
|
197
|
+
"The user's description:",
|
|
198
|
+
"",
|
|
199
|
+
"```",
|
|
200
|
+
description.trim(),
|
|
201
|
+
"```",
|
|
202
|
+
"",
|
|
203
|
+
"Respond with a frontmatter block giving the destination path, then the document",
|
|
204
|
+
"body, like:",
|
|
205
|
+
"---",
|
|
206
|
+
"path: docs/my-doc.md # or e.g. docs/adr/0001-title.md — relative to the repo root",
|
|
207
|
+
"---",
|
|
208
|
+
"",
|
|
209
|
+
"# Title",
|
|
210
|
+
"The rest of the markdown document content goes here.",
|
|
211
|
+
"",
|
|
212
|
+
"Respond with ONLY the frontmatter block and the document content, starting with the",
|
|
213
|
+
"opening '---' line and with no preamble, commentary, or code fences.",
|
|
214
|
+
].join("\n");
|
|
215
|
+
}
|
|
216
|
+
/** Parse the PM agent's generated document response (frontmatter `path` + markdown body). */
|
|
217
|
+
export function parseGeneratedDocument(output) {
|
|
218
|
+
const { data, body, hadFrontmatter } = parseDocument(output.trim());
|
|
219
|
+
const path = hadFrontmatter && typeof data.path === "string" ? data.path.trim() : "";
|
|
220
|
+
const content = body.trim();
|
|
221
|
+
if (path && content) {
|
|
222
|
+
return { path, content };
|
|
223
|
+
}
|
|
224
|
+
return { path: "", content: "" };
|
|
225
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal, dependency-free frontmatter handling.
|
|
3
|
+
*
|
|
4
|
+
* We deliberately avoid pulling in gray-matter + a YAML engine so the package
|
|
5
|
+
* has ZERO runtime dependencies and installs instantly via bunx/npx. We only
|
|
6
|
+
* support the subset of YAML that task frontmatter realistically uses:
|
|
7
|
+
* - key: scalar (string / number / bool / null)
|
|
8
|
+
* - key: [a, b, c] (inline list)
|
|
9
|
+
* - key: (block list with "- item" lines)
|
|
10
|
+
* - quoted strings (".." / '..')
|
|
11
|
+
*
|
|
12
|
+
* If a repo needs richer YAML, that frontmatter still round-trips: unknown
|
|
13
|
+
* shapes are preserved as raw strings rather than dropped.
|
|
14
|
+
*/
|
|
15
|
+
export interface ParsedDocument {
|
|
16
|
+
data: Record<string, unknown>;
|
|
17
|
+
body: string;
|
|
18
|
+
/** True if a frontmatter block was actually present. */
|
|
19
|
+
hadFrontmatter: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare const FM_DELIM = "---";
|
|
22
|
+
/** Parse a document string into {data, body}. Never throws on malformed YAML. */
|
|
23
|
+
export declare function parseDocument(content: string): ParsedDocument;
|
|
24
|
+
/**
|
|
25
|
+
* Serialize {data, body} back to a document, preserving a stable key order so
|
|
26
|
+
* git diffs stay small. Known keys come first in a sensible order; unknown
|
|
27
|
+
* keys follow in insertion order.
|
|
28
|
+
*/
|
|
29
|
+
export declare function serializeDocument(data: Record<string, unknown>, body: string, keyOrder?: string[]): string;
|