@svenroth-ai/shipwright 0.25.0 → 0.27.0
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/client/dist/assets/{EmbeddedTerminal-5QvKe3_a.js → EmbeddedTerminal-CqCQUDtp.js} +1 -1
- package/client/dist/assets/{abnfDiagram-VRR7QNED-CXi9HegQ.js → abnfDiagram-VRR7QNED-DX2objTP.js} +1 -1
- package/client/dist/assets/{arc-DLh4GiqR.js → arc-DQmjbsFU.js} +1 -1
- package/client/dist/assets/{architectureDiagram-ZJ3FMSHR-DUypQ8dQ.js → architectureDiagram-ZJ3FMSHR-DAvCsPCW.js} +1 -1
- package/client/dist/assets/{blockDiagram-677ZJIJ3-D2cVMKJG.js → blockDiagram-677ZJIJ3-DC-dlyYI.js} +1 -1
- package/client/dist/assets/{c4Diagram-LMCZKHZV-DtruBGJw.js → c4Diagram-LMCZKHZV-DSlmXfhE.js} +1 -1
- package/client/dist/assets/channel-BfmIcrp7.js +1 -0
- package/client/dist/assets/{chunk-2Q5K7J3B-CAfQkFw2.js → chunk-2Q5K7J3B-C111EHL6.js} +1 -1
- package/client/dist/assets/{chunk-32BRIVSS-CPWXoLnQ.js → chunk-32BRIVSS-DrCYs2XG.js} +1 -1
- package/client/dist/assets/{chunk-5VM5RSS4-xVc3oppQ.js → chunk-5VM5RSS4-DIejoaXS.js} +1 -1
- package/client/dist/assets/{chunk-EX3LRPZG-CUpl6fRR.js → chunk-EX3LRPZG-CGWNeH2g.js} +1 -1
- package/client/dist/assets/{chunk-JWPE2WC7-NejUp56x.js → chunk-JWPE2WC7-CCH0SRFY.js} +1 -1
- package/client/dist/assets/{chunk-MOJQB5TN-C9-2lLg0.js → chunk-MOJQB5TN-B3OpZGRv.js} +1 -1
- package/client/dist/assets/{chunk-RYQCIY6F-BAPtwABy.js → chunk-RYQCIY6F-BKhdp_8v.js} +1 -1
- package/client/dist/assets/{chunk-V7JOEXUC-Dwzihw26.js → chunk-V7JOEXUC-B6Bu8x1y.js} +1 -1
- package/client/dist/assets/{chunk-VR4S4FIN-9-iArFJM.js → chunk-VR4S4FIN-OgFi2_iB.js} +1 -1
- package/client/dist/assets/{chunk-XXDRQBXY-DiWHOrqe.js → chunk-XXDRQBXY-xyjuuhxl.js} +1 -1
- package/client/dist/assets/classDiagram-OUVF2IWQ-HfUQZ6Uc.js +1 -0
- package/client/dist/assets/classDiagram-v2-EOCWNBFH-HfUQZ6Uc.js +1 -0
- package/client/dist/assets/{cose-bilkent-JH36ORCC-Cp04EOcH.js → cose-bilkent-JH36ORCC-BQIH3SCK.js} +1 -1
- package/client/dist/assets/{cynefin-VYW2F7L2-D--GN1v8.js → cynefin-VYW2F7L2-oyLAz8te.js} +1 -1
- package/client/dist/assets/{cynefinDiagram-TSTJHNR4-BGH0o_qp.js → cynefinDiagram-TSTJHNR4-DoLCsH0l.js} +1 -1
- package/client/dist/assets/{dagre-VKFMJZFB-DSKkIKlj.js → dagre-VKFMJZFB-D7djx1_Y.js} +1 -1
- package/client/dist/assets/{diagram-FQU43EPY-Cs_C4lb-.js → diagram-FQU43EPY-DFuYE0Vf.js} +1 -1
- package/client/dist/assets/{diagram-G47NLZAW-oDtAA8O_.js → diagram-G47NLZAW-CffCZVXk.js} +1 -1
- package/client/dist/assets/{diagram-NH7WQ7WH-BiFMjqSH.js → diagram-NH7WQ7WH-BhrHtUlt.js} +1 -1
- package/client/dist/assets/{diagram-OA4YK3LP-Ccs3thCj.js → diagram-OA4YK3LP-CwQPbOn0.js} +1 -1
- package/client/dist/assets/{diagram-WEI45ONY-CdZNqgO3.js → diagram-WEI45ONY-abtYnYqH.js} +1 -1
- package/client/dist/assets/{ebnfDiagram-CCIWWBDH-CapTA0VL.js → ebnfDiagram-CCIWWBDH-DfV7gT4m.js} +1 -1
- package/client/dist/assets/{erDiagram-Q63AITRT-BcRNEP8s.js → erDiagram-Q63AITRT-vtbCprgH.js} +1 -1
- package/client/dist/assets/{flowDiagram-23GEKE2U-CJ_zF7e_.js → flowDiagram-23GEKE2U-CVCgcyDF.js} +1 -1
- package/client/dist/assets/{ganttDiagram-NO4QXBWP-CnJdcg9A.js → ganttDiagram-NO4QXBWP-BclyOgMb.js} +1 -1
- package/client/dist/assets/{gitGraphDiagram-IHSO6WYX-YTUQ_ELk.js → gitGraphDiagram-IHSO6WYX-BXT4rIKj.js} +1 -1
- package/client/dist/assets/index-CKU9dY18.css +1 -0
- package/client/dist/assets/index-DOMl6atu.js +847 -0
- package/client/dist/assets/{infoDiagram-FWYZ7A6U-CLOP_ZWD.js → infoDiagram-FWYZ7A6U-Dr4mN24J.js} +1 -1
- package/client/dist/assets/{ishikawaDiagram-FXEZZL3T-aFTIzr7o.js → ishikawaDiagram-FXEZZL3T-BBUFWq5C.js} +1 -1
- package/client/dist/assets/{journeyDiagram-5HDEW3XC-CbPJB4xd.js → journeyDiagram-5HDEW3XC-CmclipcQ.js} +1 -1
- package/client/dist/assets/{kanban-definition-HUTT4EX6-BoyhytIS.js → kanban-definition-HUTT4EX6-eIzUeZ03.js} +1 -1
- package/client/dist/assets/{linear-1VXA-uBg.js → linear-DGiT7uKK.js} +1 -1
- package/client/dist/assets/{mermaid.core-D_lO2yK9.js → mermaid.core-BcLNGbxW.js} +4 -4
- package/client/dist/assets/{mindmap-definition-LN4V7U3C-iFK7LXMx.js → mindmap-definition-LN4V7U3C-C4ANnNiP.js} +1 -1
- package/client/dist/assets/{pegDiagram-2B236MQR-DNmcmfA-.js → pegDiagram-2B236MQR-DnMeh3Yh.js} +1 -1
- package/client/dist/assets/{pieDiagram-ENE6RG2P-ekx83O5b.js → pieDiagram-ENE6RG2P-CKV_rkhF.js} +1 -1
- package/client/dist/assets/{quadrantDiagram-ABIIQ3AL-DayyD1l8.js → quadrantDiagram-ABIIQ3AL-BuUsGwHh.js} +1 -1
- package/client/dist/assets/{railroadDiagram-RFXS5EU6-Bdgz7S6G.js → railroadDiagram-RFXS5EU6-BwwdKnFL.js} +1 -1
- package/client/dist/assets/{requirementDiagram-TGXJPOKE-szP3Muld.js → requirementDiagram-TGXJPOKE-CpQZouJV.js} +1 -1
- package/client/dist/assets/{sankeyDiagram-HTMAVEWB-Ct3rzgUj.js → sankeyDiagram-HTMAVEWB-4fnDUNVa.js} +1 -1
- package/client/dist/assets/{sequenceDiagram-DBY2YBRQ-_69EfmOU.js → sequenceDiagram-DBY2YBRQ-BOcaWlM0.js} +1 -1
- package/client/dist/assets/{sizeCapture-X5ZJPWSS-BSOU06J2.js → sizeCapture-X5ZJPWSS-_pv-9R0c.js} +1 -1
- package/client/dist/assets/{stateDiagram-2N3HPSRC-HmOmFwQm.js → stateDiagram-2N3HPSRC-CFwCOonY.js} +1 -1
- package/client/dist/assets/stateDiagram-v2-6OUMAXLB-srkiu3Ib.js +1 -0
- package/client/dist/assets/{swimlanes-5IMT3BWC-CVg_4oT7.js → swimlanes-5IMT3BWC-DFa2tzxu.js} +2 -2
- package/client/dist/assets/swimlanesDiagram-G3AALYLV-Dq-0K9Zm.js +8 -0
- package/client/dist/assets/{timeline-definition-FHXFAJF6-CBBaDKXm.js → timeline-definition-FHXFAJF6-rhGYElgn.js} +1 -1
- package/client/dist/assets/{vennDiagram-L72KCM5P-CBXiWPMc.js → vennDiagram-L72KCM5P-BoGjRxyA.js} +1 -1
- package/client/dist/assets/{wardleyDiagram-EHGQE667-uhBCZoW0.js → wardleyDiagram-EHGQE667-D-IGv5bH.js} +1 -1
- package/client/dist/assets/{xychartDiagram-FW5EYKEG-xxE_wY5O.js → xychartDiagram-FW5EYKEG-D9PFTCfm.js} +1 -1
- package/client/dist/index.html +27 -27
- package/lib/claude-cli.mjs +173 -173
- package/lib/preflight.mjs +252 -252
- package/lib/probe-path.mjs +84 -84
- package/lib/probes.mjs +10 -4
- package/lib/server.mjs +506 -369
- package/lib/util.mjs +157 -157
- package/lib/win32-spawn.mjs +253 -253
- package/package.json +68 -68
- package/scripts/deploy-procs.mjs +208 -208
- package/scripts/deploy-swap.mjs +276 -276
- package/scripts/kill-targets.js +140 -140
- package/scripts/repair-claude-json.mjs +295 -295
- package/server/dist/config/default-actions.json +90 -90
- package/server/dist/core/mission-context/association.d.ts +46 -0
- package/server/dist/core/mission-context/association.d.ts.map +1 -1
- package/server/dist/core/mission-context/association.js +69 -0
- package/server/dist/core/mission-context/association.js.map +1 -1
- package/server/dist/core/mission-context/resolver-iterate.d.ts +6 -0
- package/server/dist/core/mission-context/resolver-iterate.d.ts.map +1 -0
- package/server/dist/core/mission-context/resolver-iterate.js +202 -0
- package/server/dist/core/mission-context/resolver-iterate.js.map +1 -0
- package/server/dist/core/mission-context/resolver-parts.d.ts +20 -0
- package/server/dist/core/mission-context/resolver-parts.d.ts.map +1 -1
- package/server/dist/core/mission-context/resolver-parts.js +92 -0
- package/server/dist/core/mission-context/resolver-parts.js.map +1 -1
- package/server/dist/core/mission-context/resolver.d.ts +1 -1
- package/server/dist/core/mission-context/resolver.d.ts.map +1 -1
- package/server/dist/core/mission-context/resolver.js +17 -199
- package/server/dist/core/mission-context/resolver.js.map +1 -1
- package/server/dist/core/mission-context/run-id-recovery.d.ts +21 -5
- package/server/dist/core/mission-context/run-id-recovery.d.ts.map +1 -1
- package/server/dist/core/mission-context/run-id-recovery.js +32 -5
- package/server/dist/core/mission-context/run-id-recovery.js.map +1 -1
- package/server/dist/core/mission-context/scenario.d.ts +18 -11
- package/server/dist/core/mission-context/scenario.d.ts.map +1 -1
- package/server/dist/core/mission-context/scenario.js +40 -3
- package/server/dist/core/mission-context/scenario.js.map +1 -1
- package/server/dist/core/path-guard.d.ts +1 -1
- package/server/dist/core/path-guard.d.ts.map +1 -1
- package/server/dist/core/path-guard.js +9 -2
- package/server/dist/core/path-guard.js.map +1 -1
- package/server/dist/core/sdk-sessions-merge.d.ts +19 -0
- package/server/dist/core/sdk-sessions-merge.d.ts.map +1 -1
- package/server/dist/core/sdk-sessions-merge.js +19 -5
- package/server/dist/core/sdk-sessions-merge.js.map +1 -1
- package/server/dist/core/sdk-sessions-refresh.d.ts +50 -0
- package/server/dist/core/sdk-sessions-refresh.d.ts.map +1 -0
- package/server/dist/core/sdk-sessions-refresh.js +69 -0
- package/server/dist/core/sdk-sessions-refresh.js.map +1 -0
- package/server/dist/core/sdk-sessions-store.d.ts +2 -0
- package/server/dist/core/sdk-sessions-store.d.ts.map +1 -1
- package/server/dist/core/sdk-sessions-store.js +7 -9
- package/server/dist/core/sdk-sessions-store.js.map +1 -1
- package/server/dist/core/shipslog-docs-reader.d.ts +4 -0
- package/server/dist/core/shipslog-docs-reader.d.ts.map +1 -0
- package/server/dist/core/shipslog-docs-reader.js +181 -0
- package/server/dist/core/shipslog-docs-reader.js.map +1 -0
- package/server/dist/core/shipslog-docs-types.d.ts +15 -0
- package/server/dist/core/shipslog-docs-types.d.ts.map +1 -0
- package/server/dist/core/shipslog-docs-types.js +14 -0
- package/server/dist/core/shipslog-docs-types.js.map +1 -0
- package/server/dist/core/shipwright-plugin-version-reader.d.ts +15 -0
- package/server/dist/core/shipwright-plugin-version-reader.d.ts.map +1 -0
- package/server/dist/core/shipwright-plugin-version-reader.js +49 -0
- package/server/dist/core/shipwright-plugin-version-reader.js.map +1 -0
- package/server/dist/external/design-review/serve.js +27 -27
- package/server/dist/external/inbox/_derive.d.ts +2 -25
- package/server/dist/external/inbox/_derive.d.ts.map +1 -1
- package/server/dist/external/inbox/_derive.js.map +1 -1
- package/server/dist/external/inbox/_lead.d.ts +23 -0
- package/server/dist/external/inbox/_lead.d.ts.map +1 -0
- package/server/dist/external/inbox/_lead.js +83 -0
- package/server/dist/external/inbox/_lead.js.map +1 -0
- package/server/dist/external/inbox/_types.d.ts +32 -0
- package/server/dist/external/inbox/_types.d.ts.map +1 -0
- package/server/dist/external/inbox/_types.js +10 -0
- package/server/dist/external/inbox/_types.js.map +1 -0
- package/server/dist/external/inbox/routes.d.ts.map +1 -1
- package/server/dist/external/inbox/routes.js +29 -1
- package/server/dist/external/inbox/routes.js.map +1 -1
- package/server/dist/external/launch/claim-holder-gate.d.ts +42 -0
- package/server/dist/external/launch/claim-holder-gate.d.ts.map +1 -0
- package/server/dist/external/launch/claim-holder-gate.js +87 -0
- package/server/dist/external/launch/claim-holder-gate.js.map +1 -0
- package/server/dist/external/launch/routes.d.ts.map +1 -1
- package/server/dist/external/launch/routes.js +23 -19
- package/server/dist/external/launch/routes.js.map +1 -1
- package/server/dist/external/mission-context/association-write.d.ts +22 -0
- package/server/dist/external/mission-context/association-write.d.ts.map +1 -0
- package/server/dist/external/mission-context/association-write.js +90 -0
- package/server/dist/external/mission-context/association-write.js.map +1 -0
- package/server/dist/external/mission-context/routes.d.ts.map +1 -1
- package/server/dist/external/mission-context/routes.js +18 -31
- package/server/dist/external/mission-context/routes.js.map +1 -1
- package/server/dist/external/org/lead-question-threads.d.ts +46 -0
- package/server/dist/external/org/lead-question-threads.d.ts.map +1 -0
- package/server/dist/external/org/lead-question-threads.js +132 -0
- package/server/dist/external/org/lead-question-threads.js.map +1 -0
- package/server/dist/external/org/mount.d.ts +3 -0
- package/server/dist/external/org/mount.d.ts.map +1 -1
- package/server/dist/external/org/mount.js +2 -1
- package/server/dist/external/org/mount.js.map +1 -1
- package/server/dist/external/org/usage.d.ts.map +1 -1
- package/server/dist/external/org/usage.js +20 -2
- package/server/dist/external/org/usage.js.map +1 -1
- package/server/dist/external/routes.d.ts.map +1 -1
- package/server/dist/external/routes.js +10 -13
- package/server/dist/external/routes.js.map +1 -1
- package/server/dist/external/shipslog-docs/routes.d.ts +10 -0
- package/server/dist/external/shipslog-docs/routes.d.ts.map +1 -0
- package/server/dist/external/shipslog-docs/routes.js +41 -0
- package/server/dist/external/shipslog-docs/routes.js.map +1 -0
- package/server/dist/routes/diagnostics.d.ts +12 -0
- package/server/dist/routes/diagnostics.d.ts.map +1 -1
- package/server/dist/routes/diagnostics.js +4 -1
- package/server/dist/routes/diagnostics.js.map +1 -1
- package/server/dist/routes/org-threads-composite.d.ts +24 -0
- package/server/dist/routes/org-threads-composite.d.ts.map +1 -0
- package/server/dist/routes/org-threads-composite.js +51 -0
- package/server/dist/routes/org-threads-composite.js.map +1 -0
- package/server/dist/routes/org.d.ts +5 -0
- package/server/dist/routes/org.d.ts.map +1 -1
- package/server/dist/routes/org.js +13 -0
- package/server/dist/routes/org.js.map +1 -1
- package/server/dist/terminal/pty-manager.d.ts +23 -1
- package/server/dist/terminal/pty-manager.d.ts.map +1 -1
- package/server/dist/terminal/pty-manager.js +22 -3
- package/server/dist/terminal/pty-manager.js.map +1 -1
- package/server/dist/terminal/pty-write-chunker.d.ts +109 -0
- package/server/dist/terminal/pty-write-chunker.d.ts.map +1 -0
- package/server/dist/terminal/pty-write-chunker.js +234 -0
- package/server/dist/terminal/pty-write-chunker.js.map +1 -0
- package/server/dist/types/org.d.ts +19 -0
- package/server/dist/types/org.d.ts.map +1 -1
- package/server/package.json +1 -1
- package/server/profiles/PROFILE_SCHEMA_VERSION +1 -1
- package/server/profiles/README.md +27 -27
- package/server/profiles/python-plugin-monorepo.json +35 -35
- package/server/profiles/supabase-nextjs.json +253 -253
- package/server/profiles/vite-hono.json +92 -92
- package/client/dist/assets/channel-C_k-9jsa.js +0 -1
- package/client/dist/assets/classDiagram-OUVF2IWQ-CsEPq9Ac.js +0 -1
- package/client/dist/assets/classDiagram-v2-EOCWNBFH-CsEPq9Ac.js +0 -1
- package/client/dist/assets/index-6nZ1bt0a.css +0 -1
- package/client/dist/assets/index-irQhGNpn.js +0 -828
- package/client/dist/assets/stateDiagram-v2-6OUMAXLB-BW8H33dv.js +0 -1
- package/client/dist/assets/swimlanesDiagram-G3AALYLV-BaRlxcPD.js +0 -8
package/lib/claude-cli.mjs
CHANGED
|
@@ -1,173 +1,173 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* claude-cli.mjs — the OS-touching default seams for plugins.mjs.
|
|
3
|
-
*
|
|
4
|
-
* Split out (like deploy-procs.mjs vs kill-targets.js) so plugins.mjs stays
|
|
5
|
-
* pure and fully unit-testable. Everything here talks to the real `claude`
|
|
6
|
-
* binary, the real `~/.claude/plugins/` tree, or the network — and every one
|
|
7
|
-
* of them is injectable, so tests never touch any of it.
|
|
8
|
-
*
|
|
9
|
-
* ARCHITECTURE FENCE: this drives the `claude` PLUGIN CLI as an installer. It
|
|
10
|
-
* NEVER starts a `claude` session and never writes `~/.claude/projects/**`,
|
|
11
|
-
* `shipwright_run_config.json`, or `run_loop_state.json` (CLAUDE.md rule 1 /
|
|
12
|
-
* DO-NOT #12). The only verbs used are `plugin marketplace …` and `plugin
|
|
13
|
-
* install|update …`.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { spawnSync } from "node:child_process";
|
|
17
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
18
|
-
import os from "node:os";
|
|
19
|
-
import path from "node:path";
|
|
20
|
-
|
|
21
|
-
import { MANIFEST_RAW_URL, buildInstalledMap } from "./plugins.mjs";
|
|
22
|
-
import { resolveSpawn } from "./win32-spawn.mjs";
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Argument charset gate for the `claude` CLI. Exported so BOTH directions can be
|
|
26
|
-
* asserted without starting a process (a gate that refuses everything would pass
|
|
27
|
-
* a refusal-only test). See `defaultRunClaude` for why it is still load-bearing
|
|
28
|
-
* after the shell:true removal.
|
|
29
|
-
*/
|
|
30
|
-
export const SAFE_ARG = /^[A-Za-z0-9@._/:-]+$/;
|
|
31
|
-
|
|
32
|
-
/** The one "we could not run claude at all" verdict, identical on every platform. */
|
|
33
|
-
const notFound = () => ({
|
|
34
|
-
ok: false,
|
|
35
|
-
code: null,
|
|
36
|
-
stdout: "",
|
|
37
|
-
stderr: "claude not found on PATH",
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Run the real `claude` CLI, always with `shell: false`.
|
|
42
|
-
*
|
|
43
|
-
* On Windows `claude` is often a `.cmd` shim, which a bare `shell: false` spawn
|
|
44
|
-
* cannot reach: `CreateProcess` only ever appends `.exe`, and Node's
|
|
45
|
-
* CVE-2024-27980 hardening EINVAL-blocks a direct `.cmd`. That used to be
|
|
46
|
-
* answered with `shell: true`; it is now answered by `resolveSpawn`, which finds
|
|
47
|
-
* the real file via PATHEXT and invokes a shim through an explicit
|
|
48
|
-
* `cmd.exe /d /s /c` with discrete argv (see lib/win32-spawn.mjs).
|
|
49
|
-
*
|
|
50
|
-
* SAFE_ARG STAYS, and is still load-bearing — do not remove it as newly
|
|
51
|
-
* redundant. A `.cmd` target is still ultimately parsed by cmd.exe, and Node
|
|
52
|
-
* quotes an argument for spaces, not for `&` / `|` / `^` / `%`. The charset gate
|
|
53
|
-
* is what guarantees no token can carry one. What changed is that an `.exe`
|
|
54
|
-
* target (the common case — `claude.exe` from the native installer) now involves
|
|
55
|
-
* no cmd.exe at all.
|
|
56
|
-
* @param {string[]} args
|
|
57
|
-
*/
|
|
58
|
-
export function defaultRunClaude(args) {
|
|
59
|
-
for (const a of args) {
|
|
60
|
-
if (!SAFE_ARG.test(a)) {
|
|
61
|
-
return { ok: false, code: null, stdout: "", stderr: `refused unsafe claude arg: ${a}` };
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
const plan = resolveSpawn(["claude", ...args]);
|
|
65
|
-
if (!plan) {
|
|
66
|
-
// UNREACHABLE as of iterate-2026-07-31: `resolveSpawn` no longer returns
|
|
67
|
-
// null — an unresolvable bare name comes back as the bare name for
|
|
68
|
-
// CreateProcess to resolve PATH-only (win32-spawn divergence 5), and the
|
|
69
|
-
// ENOENT that follows is turned into `notFound()` below. What actually
|
|
70
|
-
// prevents cmd.exe's cwd-first lookup is that fallback, NOT this branch.
|
|
71
|
-
// Kept as contract insurance: if the resolver ever regains a null path,
|
|
72
|
-
// this is already the right answer. Verified 2026-08-01.
|
|
73
|
-
return notFound();
|
|
74
|
-
}
|
|
75
|
-
const r = spawnSync(plan.command, plan.args, {
|
|
76
|
-
encoding: "utf-8",
|
|
77
|
-
shell: false,
|
|
78
|
-
timeout: 120_000,
|
|
79
|
-
windowsVerbatimArguments: plan.windowsVerbatimArguments,
|
|
80
|
-
});
|
|
81
|
-
// POSIX takes `resolveSpawn`'s pass-through branch, so an absent `claude`
|
|
82
|
-
// arrives here as an ENOENT spawn error with EMPTY stderr instead of the
|
|
83
|
-
// win32 resolver's null. Report the SAME verdict on both platforms — a caller
|
|
84
|
-
// should not have to know which OS swallowed the message.
|
|
85
|
-
if (r.error && r.error.code === "ENOENT") return notFound();
|
|
86
|
-
return {
|
|
87
|
-
ok: r.status === 0 && !r.error,
|
|
88
|
-
code: r.status ?? null,
|
|
89
|
-
stdout: (r.stdout ?? "").toString(),
|
|
90
|
-
stderr: (r.stderr ?? "").toString(),
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** `~/.claude/plugins` root (override via CLAUDE_PLUGINS_ROOT for tests). */
|
|
95
|
-
export function pluginsRoot(env = process.env, home = os.homedir()) {
|
|
96
|
-
return env.CLAUDE_PLUGINS_ROOT || path.join(home, ".claude", "plugins");
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function installedPluginsPath(env, home) {
|
|
100
|
-
return path.join(pluginsRoot(env, home), "installed_plugins.json");
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export function marketplaceDir(env, home) {
|
|
104
|
-
return path.join(pluginsRoot(env, home), "marketplaces", "shipwright");
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function cacheRoot(env, home) {
|
|
108
|
-
return path.join(pluginsRoot(env, home), "cache", "shipwright");
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** Read + parse installed_plugins.json into a `{ name: version }` map (empty on any error). */
|
|
112
|
-
export function defaultSnapshotInstalled(env = process.env, home = os.homedir()) {
|
|
113
|
-
try {
|
|
114
|
-
const raw = readFileSync(installedPluginsPath(env, home), "utf-8");
|
|
115
|
-
return buildInstalledMap(JSON.parse(raw));
|
|
116
|
-
} catch {
|
|
117
|
-
return {};
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/** Precedence 1 — the local marketplace clone materialised by `marketplace add`. */
|
|
122
|
-
export function defaultReadLocalManifest(env = process.env, home = os.homedir()) {
|
|
123
|
-
const p = path.join(marketplaceDir(env, home), ".claude-plugin", "marketplace.json");
|
|
124
|
-
try {
|
|
125
|
-
if (!existsSync(p)) return null;
|
|
126
|
-
return { text: readFileSync(p, "utf-8"), source: p };
|
|
127
|
-
} catch {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/** Precedence 2 — GitHub raw. Offline-safe: any failure resolves to null. */
|
|
133
|
-
export function makeFetchRemoteManifest(deps = {}) {
|
|
134
|
-
const {
|
|
135
|
-
fetchImpl = typeof fetch === "function" ? fetch : undefined,
|
|
136
|
-
url = MANIFEST_RAW_URL,
|
|
137
|
-
timeoutMs = 4000,
|
|
138
|
-
} = deps;
|
|
139
|
-
return async () => {
|
|
140
|
-
if (!fetchImpl) return null;
|
|
141
|
-
const ctrl = new AbortController();
|
|
142
|
-
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
143
|
-
try {
|
|
144
|
-
const res = await fetchImpl(url, { signal: ctrl.signal });
|
|
145
|
-
if (!res || !res.ok) return null;
|
|
146
|
-
return { text: await res.text(), source: url };
|
|
147
|
-
} catch {
|
|
148
|
-
return null;
|
|
149
|
-
} finally {
|
|
150
|
-
clearTimeout(timer);
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/** Precedence 3 — explicit path override (also the test seam). */
|
|
156
|
-
export function defaultReadOverrideManifest(env = process.env) {
|
|
157
|
-
const p = env.SHIPWRIGHT_MARKETPLACE_MANIFEST;
|
|
158
|
-
if (!p) return null;
|
|
159
|
-
// A set-but-unreadable override is an OPERATOR error, not a fall-through:
|
|
160
|
-
// surface it loudly rather than silently sliding to "no list".
|
|
161
|
-
return { text: readFileSync(p, "utf-8"), source: p };
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/** Bundle the three resolver seams with their real implementations. */
|
|
165
|
-
export function defaultResolverSeams(deps = {}) {
|
|
166
|
-
const env = deps.env ?? process.env;
|
|
167
|
-
const home = deps.home ?? os.homedir();
|
|
168
|
-
return {
|
|
169
|
-
readLocalManifest: () => defaultReadLocalManifest(env, home),
|
|
170
|
-
fetchRemoteManifest: makeFetchRemoteManifest(deps),
|
|
171
|
-
readOverrideManifest: () => defaultReadOverrideManifest(env),
|
|
172
|
-
};
|
|
173
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* claude-cli.mjs — the OS-touching default seams for plugins.mjs.
|
|
3
|
+
*
|
|
4
|
+
* Split out (like deploy-procs.mjs vs kill-targets.js) so plugins.mjs stays
|
|
5
|
+
* pure and fully unit-testable. Everything here talks to the real `claude`
|
|
6
|
+
* binary, the real `~/.claude/plugins/` tree, or the network — and every one
|
|
7
|
+
* of them is injectable, so tests never touch any of it.
|
|
8
|
+
*
|
|
9
|
+
* ARCHITECTURE FENCE: this drives the `claude` PLUGIN CLI as an installer. It
|
|
10
|
+
* NEVER starts a `claude` session and never writes `~/.claude/projects/**`,
|
|
11
|
+
* `shipwright_run_config.json`, or `run_loop_state.json` (CLAUDE.md rule 1 /
|
|
12
|
+
* DO-NOT #12). The only verbs used are `plugin marketplace …` and `plugin
|
|
13
|
+
* install|update …`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { spawnSync } from "node:child_process";
|
|
17
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
18
|
+
import os from "node:os";
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
|
|
21
|
+
import { MANIFEST_RAW_URL, buildInstalledMap } from "./plugins.mjs";
|
|
22
|
+
import { resolveSpawn } from "./win32-spawn.mjs";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Argument charset gate for the `claude` CLI. Exported so BOTH directions can be
|
|
26
|
+
* asserted without starting a process (a gate that refuses everything would pass
|
|
27
|
+
* a refusal-only test). See `defaultRunClaude` for why it is still load-bearing
|
|
28
|
+
* after the shell:true removal.
|
|
29
|
+
*/
|
|
30
|
+
export const SAFE_ARG = /^[A-Za-z0-9@._/:-]+$/;
|
|
31
|
+
|
|
32
|
+
/** The one "we could not run claude at all" verdict, identical on every platform. */
|
|
33
|
+
const notFound = () => ({
|
|
34
|
+
ok: false,
|
|
35
|
+
code: null,
|
|
36
|
+
stdout: "",
|
|
37
|
+
stderr: "claude not found on PATH",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Run the real `claude` CLI, always with `shell: false`.
|
|
42
|
+
*
|
|
43
|
+
* On Windows `claude` is often a `.cmd` shim, which a bare `shell: false` spawn
|
|
44
|
+
* cannot reach: `CreateProcess` only ever appends `.exe`, and Node's
|
|
45
|
+
* CVE-2024-27980 hardening EINVAL-blocks a direct `.cmd`. That used to be
|
|
46
|
+
* answered with `shell: true`; it is now answered by `resolveSpawn`, which finds
|
|
47
|
+
* the real file via PATHEXT and invokes a shim through an explicit
|
|
48
|
+
* `cmd.exe /d /s /c` with discrete argv (see lib/win32-spawn.mjs).
|
|
49
|
+
*
|
|
50
|
+
* SAFE_ARG STAYS, and is still load-bearing — do not remove it as newly
|
|
51
|
+
* redundant. A `.cmd` target is still ultimately parsed by cmd.exe, and Node
|
|
52
|
+
* quotes an argument for spaces, not for `&` / `|` / `^` / `%`. The charset gate
|
|
53
|
+
* is what guarantees no token can carry one. What changed is that an `.exe`
|
|
54
|
+
* target (the common case — `claude.exe` from the native installer) now involves
|
|
55
|
+
* no cmd.exe at all.
|
|
56
|
+
* @param {string[]} args
|
|
57
|
+
*/
|
|
58
|
+
export function defaultRunClaude(args) {
|
|
59
|
+
for (const a of args) {
|
|
60
|
+
if (!SAFE_ARG.test(a)) {
|
|
61
|
+
return { ok: false, code: null, stdout: "", stderr: `refused unsafe claude arg: ${a}` };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const plan = resolveSpawn(["claude", ...args]);
|
|
65
|
+
if (!plan) {
|
|
66
|
+
// UNREACHABLE as of iterate-2026-07-31: `resolveSpawn` no longer returns
|
|
67
|
+
// null — an unresolvable bare name comes back as the bare name for
|
|
68
|
+
// CreateProcess to resolve PATH-only (win32-spawn divergence 5), and the
|
|
69
|
+
// ENOENT that follows is turned into `notFound()` below. What actually
|
|
70
|
+
// prevents cmd.exe's cwd-first lookup is that fallback, NOT this branch.
|
|
71
|
+
// Kept as contract insurance: if the resolver ever regains a null path,
|
|
72
|
+
// this is already the right answer. Verified 2026-08-01.
|
|
73
|
+
return notFound();
|
|
74
|
+
}
|
|
75
|
+
const r = spawnSync(plan.command, plan.args, {
|
|
76
|
+
encoding: "utf-8",
|
|
77
|
+
shell: false,
|
|
78
|
+
timeout: 120_000,
|
|
79
|
+
windowsVerbatimArguments: plan.windowsVerbatimArguments,
|
|
80
|
+
});
|
|
81
|
+
// POSIX takes `resolveSpawn`'s pass-through branch, so an absent `claude`
|
|
82
|
+
// arrives here as an ENOENT spawn error with EMPTY stderr instead of the
|
|
83
|
+
// win32 resolver's null. Report the SAME verdict on both platforms — a caller
|
|
84
|
+
// should not have to know which OS swallowed the message.
|
|
85
|
+
if (r.error && r.error.code === "ENOENT") return notFound();
|
|
86
|
+
return {
|
|
87
|
+
ok: r.status === 0 && !r.error,
|
|
88
|
+
code: r.status ?? null,
|
|
89
|
+
stdout: (r.stdout ?? "").toString(),
|
|
90
|
+
stderr: (r.stderr ?? "").toString(),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** `~/.claude/plugins` root (override via CLAUDE_PLUGINS_ROOT for tests). */
|
|
95
|
+
export function pluginsRoot(env = process.env, home = os.homedir()) {
|
|
96
|
+
return env.CLAUDE_PLUGINS_ROOT || path.join(home, ".claude", "plugins");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function installedPluginsPath(env, home) {
|
|
100
|
+
return path.join(pluginsRoot(env, home), "installed_plugins.json");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function marketplaceDir(env, home) {
|
|
104
|
+
return path.join(pluginsRoot(env, home), "marketplaces", "shipwright");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function cacheRoot(env, home) {
|
|
108
|
+
return path.join(pluginsRoot(env, home), "cache", "shipwright");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Read + parse installed_plugins.json into a `{ name: version }` map (empty on any error). */
|
|
112
|
+
export function defaultSnapshotInstalled(env = process.env, home = os.homedir()) {
|
|
113
|
+
try {
|
|
114
|
+
const raw = readFileSync(installedPluginsPath(env, home), "utf-8");
|
|
115
|
+
return buildInstalledMap(JSON.parse(raw));
|
|
116
|
+
} catch {
|
|
117
|
+
return {};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Precedence 1 — the local marketplace clone materialised by `marketplace add`. */
|
|
122
|
+
export function defaultReadLocalManifest(env = process.env, home = os.homedir()) {
|
|
123
|
+
const p = path.join(marketplaceDir(env, home), ".claude-plugin", "marketplace.json");
|
|
124
|
+
try {
|
|
125
|
+
if (!existsSync(p)) return null;
|
|
126
|
+
return { text: readFileSync(p, "utf-8"), source: p };
|
|
127
|
+
} catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Precedence 2 — GitHub raw. Offline-safe: any failure resolves to null. */
|
|
133
|
+
export function makeFetchRemoteManifest(deps = {}) {
|
|
134
|
+
const {
|
|
135
|
+
fetchImpl = typeof fetch === "function" ? fetch : undefined,
|
|
136
|
+
url = MANIFEST_RAW_URL,
|
|
137
|
+
timeoutMs = 4000,
|
|
138
|
+
} = deps;
|
|
139
|
+
return async () => {
|
|
140
|
+
if (!fetchImpl) return null;
|
|
141
|
+
const ctrl = new AbortController();
|
|
142
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
143
|
+
try {
|
|
144
|
+
const res = await fetchImpl(url, { signal: ctrl.signal });
|
|
145
|
+
if (!res || !res.ok) return null;
|
|
146
|
+
return { text: await res.text(), source: url };
|
|
147
|
+
} catch {
|
|
148
|
+
return null;
|
|
149
|
+
} finally {
|
|
150
|
+
clearTimeout(timer);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Precedence 3 — explicit path override (also the test seam). */
|
|
156
|
+
export function defaultReadOverrideManifest(env = process.env) {
|
|
157
|
+
const p = env.SHIPWRIGHT_MARKETPLACE_MANIFEST;
|
|
158
|
+
if (!p) return null;
|
|
159
|
+
// A set-but-unreadable override is an OPERATOR error, not a fall-through:
|
|
160
|
+
// surface it loudly rather than silently sliding to "no list".
|
|
161
|
+
return { text: readFileSync(p, "utf-8"), source: p };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Bundle the three resolver seams with their real implementations. */
|
|
165
|
+
export function defaultResolverSeams(deps = {}) {
|
|
166
|
+
const env = deps.env ?? process.env;
|
|
167
|
+
const home = deps.home ?? os.homedir();
|
|
168
|
+
return {
|
|
169
|
+
readLocalManifest: () => defaultReadLocalManifest(env, home),
|
|
170
|
+
fetchRemoteManifest: makeFetchRemoteManifest(deps),
|
|
171
|
+
readOverrideManifest: () => defaultReadOverrideManifest(env),
|
|
172
|
+
};
|
|
173
|
+
}
|