@plurnk/plurnk-execs-common 3.0.3 → 3.0.4

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 CHANGED
@@ -1,73 +1,38 @@
1
1
  # @plurnk/plurnk-execs-common
2
2
 
3
- The **subprocess executor** for
4
- [plurnk-service](https://github.com/plurnk/plurnk-service)'s runtime-tag execution (`sh`, `js`, `python3`). One
5
- package offers the shell, Node.js, and Python 3. Node is guaranteed; the rest
6
- are detected.
3
+ Subprocess executor for [`@plurnk/plurnk-service`](https://github.com/plurnk/plurnk-service) (`sh`, `js`, `python3`), built on [`@plurnk/plurnk-execs`](https://github.com/plurnk/plurnk-service/tree/main/plurnk-execs). `js` (`node`) is always present; `sh` and `python3` register when `command -v` finds them on `PATH`.
7
4
 
8
- A `@plurnk/plurnk-execs-*` sibling built on the [plurnk-execs](https://github.com/plurnk/plurnk-service/tree/main/plurnk-execs) framework.
5
+ ## Runtimes & invocation ({§executor-subprocess}, {§executor-contract})
9
6
 
10
- ## How it works
11
-
12
- The manifest claims the subprocess tags. Per-tag `probe()` reports Node
13
- unconditionally because the daemon already runs on it. A cheap `command -v`
14
- detects `sh` and `python3`, so one executor adapts to the host.
15
-
16
- | Tag | Binary | Inline body via |
17
- | ------------ | ------- | ------------------------------ |
18
- | `sh` 🐚 | sh | `-c <body>` |
19
- | `js` ⬢ | node | `-e <body>` (always available) |
20
- | `python3` 🐍 | python3 | `-c <body>` |
21
-
22
- Each enabled runtime's catalog Summary includes a short executable inline body, with
23
- literal `\n` separators keeping the invocation on one discovery line.
24
-
25
- ### The `(target)` slot — a script or working directory
26
-
27
- The table above is the **inline** form: the body is the program. A directory in
28
- the `(target)` slot selects the working directory for that inline program. A
29
- file in the `(target)` slot is instead the script each interpreter reads
30
- directly, and the body is tokenized by shell quoting (`tokenizeArgv`) into the
31
- script's arguments ({§executor-subprocess}, {§executor-contract}).
7
+ | Tag | Binary | Inline `{ text, cwd? }` |
8
+ | --- | --- | --- |
9
+ | `sh` 🐚 | `sh` (`-c`) | `plurnk.sh({ text: "uname -s" })` |
10
+ | `js` ⬢ | `node` (`-e`) | `plurnk.js({ text: "console.log(process.version)" })` |
11
+ | `python3` 🐍 | `python3` (`-c`) | `plurnk.python3({ text: "import sys; print(sys.version)" })` |
32
12
 
33
- ```ts
34
- await plurnk.sh({ script: "./deploy.sh", args: '--release "two words"' });
35
- ```
13
+ Local script form (`{ script, args?, cwd? }`):
36
14
 
37
- ```ts
38
- await plurnk.python3({ script: "transform.py", args: "--format json" });
15
+ ```js
16
+ plurnk.sh({ script: "./deploy.sh", args: ["--release", "two words"] })
17
+ plurnk.python3({ script: "transform.py", args: ["--format", "json"] })
39
18
  ```
40
19
 
41
- All declared tags run host code, so every invocation is proposal-gated ({§executor-results}). Inline programs have no
42
- separate input channel; script targets receive their arguments from the body.
20
+ - `cwd` selects the working directory for inline or script runs; `script` executes the file directly with `args` (`string[]`, lowered via `SubprocessExecutor` / `tokenizeArgv`).
21
+ - All three runtimes declare `effect: "host"` and are proposal-gated ({§executor-results}), streaming output to `<tag>:///<id>#{stdout,stderr}`.
22
+ - Subtractive boot policy: `PLURNK_EXECS_<TAG>=0` disables one tag; `PLURNK_EXECS_ONLY=<list>` restricts registration ({§executor-policy}).
43
23
 
44
- ## Configuration
24
+ ## Node `--permission` confinement
45
25
 
46
- Per-tag kill-switches (`PLURNK_EXECS_<TAG>=0`) and the
47
- `PLURNK_EXECS_ONLY` allowlist are honored by framework discovery, uniformly
48
- across every extension ({§executor-policy}). A disabled tag is not registered and
49
- never reaches this executor.
26
+ Passing `--permission` via `NODE_OPTIONS` confines `js` child processes (`ERR_ACCESS_DENIED`) with zero PLURNK-specific flags ({§execution-output-identity}):
50
27
 
51
- ## Alignment with Node's permission framework
52
-
53
- Node's `--permission` model is natively conditional: unflagged `js` invocations run
54
- with normal Node semantics, while passing `--permission` flags through
55
- `NODE_OPTIONS` confines `js` child processes at access time (`ERR_ACCESS_DENIED`)
56
- without adding any PLURNK-specific knob or altering default behavior.
57
-
58
- | Step | Existing surface | Effect |
28
+ | Scope | Surface | Effect |
59
29
  | --- | --- | --- |
60
- | 1. Remove out-of-model interpreters | `PLURNK_EXECS_ONLY=js` | Unregisters `sh` and `python3` at discovery ({§executor-policy}); downstream layers cannot re-enable them. |
61
- | 2. Confine `js` children | `NODE_OPTIONS="--permission --allow-fs-read=<workspace> --allow-fs-write=<workspace>"` | Confines filesystem reads and writes to `<workspace>` and denies `child_process` spawning (`ERR_ACCESS_DENIED`). |
62
- | 3. Discover daemon envelope (optional) | `node --permission-audit ...` | Audits paths the daemon itself touches (run root, checkout, `node_modules`) before launching the daemon under `--permission`. |
63
-
64
- **Scope choices** ({§execution-output-identity}):
65
-
66
- - **Workspace scope** (`plurnk.env` with `scope: "workspace"`): `NODE_OPTIONS` reaches both `js` execution children and Node-based MCP server subprocesses in the workspace.
67
- - **Session scope** (`plurnk.env` with `scope: "session"`): `NODE_OPTIONS` reaches `js` execution children of that session only.
68
-
69
- **Honest ledger:** Node's permission model provides same-UID accident containment, not adversarial security boundaries. PLURNK owns authority, consent, and audit, and never claims containment; for OS-account separation, combine with `PLURNK_EXECS_SPAWN_USER` ({§executor-policy}) or an external container/VM sandbox.
30
+ | Unregister shell/Python | `PLURNK_EXECS_ONLY=js` | Restricts registered subprocess tags to `js` ({§executor-policy}). |
31
+ | Confine `js` children | `NODE_OPTIONS="--permission --allow-fs-read=<dir> --allow-fs-write=<dir>"` | Applies to `js` + Node MCP stdio servers at `scope: "workspace"`, or `js` children only at `scope: "session"` (`plurnk.env`). |
32
+ | OS boundary | `PLURNK_EXECS_SPAWN_USER` / container / VM | Required for adversarial isolation (Node `--permission` is same-UID accident containment). |
70
33
 
71
34
  ## Tests
72
35
 
73
- `test:lint`, `test:unit`. Live-eval tests auto-skip where the interpreter is absent.
36
+ ```sh
37
+ npm test -w @plurnk/plurnk-execs-common
38
+ ```
@@ -1 +1 @@
1
- {"version":3,"file":"Common.js","sourceRoot":"","sources":["../src/Common.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Common.js","sourceRoot":"","sources":["../src/Common.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from \"node:child_process\";\nimport { isExecutionOp, type RuntimeTag } from \"@plurnk/plurnk-contracts\";\nimport { SubprocessExecutor, SpawnAccounts, Shell, tokenizeArgv } from \"@plurnk/plurnk-execs\";\nimport type { RuntimeAvailability, SpawnArgs } from \"@plurnk/plurnk-execs\";\n\n// Per-interpreter spawn recipe. `bin` is the executable (probed for presence).\ninterface Recipe {\n bin: string;\n arg: (command: string) => string[];\n // node is guaranteed (the daemon IS node) — reported available without a\n // PATH probe. Everything else is detected.\n alwaysAvailable?: boolean;\n}\n\nconst RECIPES: Readonly<Record<string, Recipe>> = Object.freeze({\n // Subprocess floor. `sh`'s bin is the shell the environment names ({§executor-subprocess}), resolved at use.\n sh: { bin: \"sh\", arg: (c) => [\"-c\", c] },\n js: { bin: \"node\", arg: (c) => [\"-e\", c], alwaysAvailable: true },\n python3: { bin: \"python3\", arg: (c) => [\"-c\", c] },\n});\n\n// Exposed for tests / consumers wanting the candidate set.\nexport const RUNTIME_TAGS: readonly RuntimeTag[] = Object.freeze(Object.keys(RECIPES).filter(isExecutionOp));\n\nconst onPath = (bin: string): boolean =>\n spawnSync(\"sh\", [\"-c\", `command -v \"$1\"`, \"sh\", bin], { ...SpawnAccounts.options() }).status === 0; // {§executor-policy}\n\nexport default class Common extends SubprocessExecutor {\n protected override spawnArgs(runtime: string, command: string, target: string | null = null): SpawnArgs {\n const r = Common.#recipe(runtime);\n if (r === undefined) throw new Error(`plurnk-execs-common received unclaimed runtime tag '${runtime}'`);\n // With a target the program IS the target and the body is its arguments,\n // tokenized by shell quoting ({§executor-subprocess}, {§executor-contract}).\n // The interpreter reads the file; no executable bit is consulted or changed.\n if (target !== null) {\n return { cmd: r.bin, args: [target, ...tokenizeArgv(command)], useShell: false };\n }\n return { cmd: r.bin, args: r.arg(command), useShell: false };\n }\n\n // {§executor-subprocess} `sh` runs the environment's shell; every other recipe is fixed.\n static #recipe(runtime: string): Recipe | undefined {\n const r = RECIPES[runtime];\n return runtime === \"sh\" && r !== undefined ? { ...r, bin: Shell.current().path } : r;\n }\n\n override async probe(): Promise<RuntimeAvailability> {\n const r = Common.#recipe(this.runtime);\n if (r === undefined) return { available: false, detail: `unknown runtime '${this.runtime}'` };\n if (r.alwaysAvailable) return { available: true, detail: r.bin === \"node\" ? process.version : r.bin };\n return onPath(r.bin)\n ? { available: true, detail: r.bin }\n : { available: false, detail: `${r.bin} not on PATH` };\n }\n}\n"]}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC","sourcesContent":["export { default as Common, RUNTIME_TAGS } from \"./Common.ts\";\nexport { default } from \"./Common.ts\";\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"runtimes.d.ts","sourceRoot":"","sources":["../src/runtimes.ts"],"names":[],"mappings":"AAEA,OAAO,EAAS,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AA8D/D,eAAO,MAAM,QAAQ,QAAO,WAAW,EAAoE,CAAC"}
1
+ {"version":3,"file":"runtimes.d.ts","sourceRoot":"","sources":["../src/runtimes.ts"],"names":[],"mappings":"AAEA,OAAO,EAAS,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAoE/D,eAAO,MAAM,QAAQ,QAAO,WAAW,EAItC,CAAC"}
package/dist/runtimes.js CHANGED
@@ -10,7 +10,7 @@ const SH = {
10
10
  required: false,
11
11
  },
12
12
  target: {
13
- role: "script, or working directory for an inline program",
13
+ role: "script, or working directory for an inline program (defaults to the workspace root)",
14
14
  required: false,
15
15
  kind: "script",
16
16
  },
@@ -19,37 +19,40 @@ const SH = {
19
19
  },
20
20
  },
21
21
  };
22
- const FIXED = [
23
- {
24
- name: "js",
25
- glyph: "⬢",
26
- summary: "Run JavaScript code or scripts with Node.js.",
27
- invocation: {
28
- body: {
29
- role: "JavaScript program; arguments when the target is a script",
30
- required: false,
31
- },
32
- target: {
33
- role: "script, or working directory for an inline program",
34
- required: false,
35
- kind: "script",
36
- },
37
- example: {
38
- body: "console.log(42)",
39
- },
22
+ let python3Version;
23
+ const currentPython3Version = () => python3Version === undefined ? (python3Version = Shell.version("python3")) : python3Version;
24
+ const jsDecl = () => ({
25
+ name: "js",
26
+ glyph: "⬢",
27
+ summary: `Run JavaScript code or scripts in the workspace with node (${process.version}).`,
28
+ invocation: {
29
+ body: {
30
+ role: "JavaScript program; arguments when the target is a script",
31
+ required: false,
32
+ },
33
+ target: {
34
+ role: "script, or working directory for an inline program (defaults to the workspace root)",
35
+ required: false,
36
+ kind: "script",
37
+ },
38
+ example: {
39
+ body: "console.log(42)",
40
40
  },
41
41
  },
42
- {
42
+ });
43
+ const python3Decl = () => {
44
+ const version = currentPython3Version();
45
+ return {
43
46
  name: "python3",
44
47
  glyph: "🐍",
45
- summary: "Run Python 3 code or scripts.",
48
+ summary: `Run Python 3 code or scripts in the workspace with python3${version === null ? "" : ` (v${version})`}.`,
46
49
  invocation: {
47
50
  body: {
48
51
  role: "Python program; arguments when the target is a script",
49
52
  required: false,
50
53
  },
51
54
  target: {
52
- role: "script, or working directory for an inline program",
55
+ role: "script, or working directory for an inline program (defaults to the workspace root)",
53
56
  required: false,
54
57
  kind: "script",
55
58
  },
@@ -57,7 +60,11 @@ const FIXED = [
57
60
  body: "print(42)",
58
61
  },
59
62
  },
60
- },
63
+ };
64
+ };
65
+ export const runtimes = () => [
66
+ { ...SH, summary: Shell.summary(Shell.current()) },
67
+ jsDecl(),
68
+ python3Decl(),
61
69
  ];
62
- export const runtimes = () => [{ ...SH, summary: Shell.summary(Shell.current()) }, ...FIXED];
63
70
  //# sourceMappingURL=runtimes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtimes.js","sourceRoot":"","sources":["../src/runtimes.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,qFAAqF;AACrF,OAAO,EAAE,KAAK,EAAoB,MAAM,sBAAsB,CAAC;AAE/D,MAAM,EAAE,GAAiC;IACrC,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,IAAI;IACX,UAAU,EAAE;QACR,IAAI,EAAE;YACF,IAAI,EAAE,0DAA0D;YAChE,QAAQ,EAAE,KAAK;SAClB;QACD,MAAM,EAAE;YACJ,IAAI,EAAE,oDAAoD;YAC1D,QAAQ,EAAE,KAAK;YACf,IAAI,EAAE,QAAQ;SACjB;QACD,OAAO,EAAE;YACL,IAAI,EAAE,oBAAoB;SAC7B;KACJ;CACJ,CAAC;AAEF,MAAM,KAAK,GAA2B;IAClC;QACI,IAAI,EAAE,IAAI;QACV,KAAK,EAAE,GAAG;QACV,OAAO,EAAE,8CAA8C;QACvD,UAAU,EAAE;YACR,IAAI,EAAE;gBACF,IAAI,EAAE,2DAA2D;gBACjE,QAAQ,EAAE,KAAK;aAClB;YACD,MAAM,EAAE;gBACJ,IAAI,EAAE,oDAAoD;gBAC1D,QAAQ,EAAE,KAAK;gBACf,IAAI,EAAE,QAAQ;aACjB;YACD,OAAO,EAAE;gBACL,IAAI,EAAE,iBAAiB;aAC1B;SACJ;KACJ;IACD;QACI,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,IAAI;QACX,OAAO,EAAE,+BAA+B;QACxC,UAAU,EAAE;YACR,IAAI,EAAE;gBACF,IAAI,EAAE,uDAAuD;gBAC7D,QAAQ,EAAE,KAAK;aAClB;YACD,MAAM,EAAE;gBACJ,IAAI,EAAE,oDAAoD;gBAC1D,QAAQ,EAAE,KAAK;gBACf,IAAI,EAAE,QAAQ;aACjB;YACD,OAAO,EAAE;gBACL,IAAI,EAAE,WAAW;aACpB;SACJ;KACJ;CACJ,CAAC;AAEF,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAkB,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,CAAC,CAAC"}
1
+ {"version":3,"file":"runtimes.js","sourceRoot":"","sources":["../src/runtimes.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,qFAAqF;AACrF,OAAO,EAAE,KAAK,EAAoB,MAAM,sBAAsB,CAAC;AAE/D,MAAM,EAAE,GAAiC;IACrC,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,IAAI;IACX,UAAU,EAAE;QACR,IAAI,EAAE;YACF,IAAI,EAAE,0DAA0D;YAChE,QAAQ,EAAE,KAAK;SAClB;QACD,MAAM,EAAE;YACJ,IAAI,EAAE,qFAAqF;YAC3F,QAAQ,EAAE,KAAK;YACf,IAAI,EAAE,QAAQ;SACjB;QACD,OAAO,EAAE;YACL,IAAI,EAAE,oBAAoB;SAC7B;KACJ;CACJ,CAAC;AAEF,IAAI,cAAyC,CAAC;AAC9C,MAAM,qBAAqB,GAAG,GAAkB,EAAE,CAC9C,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,cAAc,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC;AAEhG,MAAM,MAAM,GAAG,GAAgB,EAAE,CAAC,CAAC;IAC/B,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,GAAG;IACV,OAAO,EAAE,8DAA8D,OAAO,CAAC,OAAO,IAAI;IAC1F,UAAU,EAAE;QACR,IAAI,EAAE;YACF,IAAI,EAAE,2DAA2D;YACjE,QAAQ,EAAE,KAAK;SAClB;QACD,MAAM,EAAE;YACJ,IAAI,EAAE,qFAAqF;YAC3F,QAAQ,EAAE,KAAK;YACf,IAAI,EAAE,QAAQ;SACjB;QACD,OAAO,EAAE;YACL,IAAI,EAAE,iBAAiB;SAC1B;KACJ;CACJ,CAAC,CAAC;AAEH,MAAM,WAAW,GAAG,GAAgB,EAAE;IAClC,MAAM,OAAO,GAAG,qBAAqB,EAAE,CAAC;IACxC,OAAO;QACH,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,IAAI;QACX,OAAO,EAAE,6DAA6D,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,OAAO,GAAG,GAAG;QACjH,UAAU,EAAE;YACR,IAAI,EAAE;gBACF,IAAI,EAAE,uDAAuD;gBAC7D,QAAQ,EAAE,KAAK;aAClB;YACD,MAAM,EAAE;gBACJ,IAAI,EAAE,qFAAqF;gBAC3F,QAAQ,EAAE,KAAK;gBACf,IAAI,EAAE,QAAQ;aACjB;YACD,OAAO,EAAE;gBACL,IAAI,EAAE,WAAW;aACpB;SACJ;KACJ,CAAC;AACN,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAkB,EAAE,CAAC;IACzC,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,EAAE;IAClD,MAAM,EAAE;IACR,WAAW,EAAE;CAChB,CAAC","sourcesContent":["// {§executor-discovery} The common package's declarations. `sh` names the shell it runs\n// ({§executor-subprocess}), resolved on this host; every other declaration is fixed.\nimport { Shell, type RuntimeDecl } from \"@plurnk/plurnk-execs\";\n\nconst SH: Omit<RuntimeDecl, \"summary\"> = {\n name: \"sh\",\n glyph: \"🐚\",\n invocation: {\n body: {\n role: \"the shell program; arguments when the target is a script\",\n required: false,\n },\n target: {\n role: \"script, or working directory for an inline program (defaults to the workspace root)\",\n required: false,\n kind: \"script\",\n },\n example: {\n body: \"git status --short\",\n },\n },\n};\n\nlet python3Version: string | null | undefined;\nconst currentPython3Version = (): string | null =>\n python3Version === undefined ? (python3Version = Shell.version(\"python3\")) : python3Version;\n\nconst jsDecl = (): RuntimeDecl => ({\n name: \"js\",\n glyph: \"⬢\",\n summary: `Run JavaScript code or scripts in the workspace with node (${process.version}).`,\n invocation: {\n body: {\n role: \"JavaScript program; arguments when the target is a script\",\n required: false,\n },\n target: {\n role: \"script, or working directory for an inline program (defaults to the workspace root)\",\n required: false,\n kind: \"script\",\n },\n example: {\n body: \"console.log(42)\",\n },\n },\n});\n\nconst python3Decl = (): RuntimeDecl => {\n const version = currentPython3Version();\n return {\n name: \"python3\",\n glyph: \"🐍\",\n summary: `Run Python 3 code or scripts in the workspace with python3${version === null ? \"\" : ` (v${version})`}.`,\n invocation: {\n body: {\n role: \"Python program; arguments when the target is a script\",\n required: false,\n },\n target: {\n role: \"script, or working directory for an inline program (defaults to the workspace root)\",\n required: false,\n kind: \"script\",\n },\n example: {\n body: \"print(42)\",\n },\n },\n };\n};\n\nexport const runtimes = (): RuntimeDecl[] => [\n { ...SH, summary: Shell.summary(Shell.current()) },\n jsDecl(),\n python3Decl(),\n];\n"]}
package/docs/js.md CHANGED
@@ -1,15 +1,10 @@
1
1
  # js
2
2
 
3
- A JavaScript snippet, run via `node -e`. Node is the daemon's own runtime, so it
4
- is always available (no PATH probe).
5
-
6
- await plurnk.js({ code: "const os = require(\"node:os\");\nconsole.log(JSON.stringify({ platform: os.platform(), cpus: os.cpus().length }));" }); // the code is the snippet
7
-
8
- `await plurnk.js({ script: "tool.js", args: ["--flag"] });` runs that JavaScript file and passes `args` as its arguments,
9
- also for `session://` and `skill://` targets.
10
- Relative imports resolve from the script; ordinary relative filesystem paths
11
- resolve from the working directory. Environment, working directory, channels,
12
- output on completion, and exit status are every subprocess executor's,
13
- as `mcp:///plurnk/sh.json` states. Stdout is text, so structured output is serialized
14
- (`console.log(JSON.stringify(value))`); a thrown error exits nonzero with its
15
- stack on stderr.
3
+ Runs JavaScript via `node -e` (inline `text`) or `node <script>` (`script` with optional `args: string[]`, including `session://` and `skill://` targets).
4
+
5
+ plurnk.js({ text: "const os = require(\"node:os\");\nconsole.log(JSON.stringify({ platform: os.platform(), cpus: os.cpus().length }));" })
6
+
7
+ plurnk.js({ script: "tool.js", args: ["--flag"] })
8
+
9
+ - Relative `import` / `require` resolves from the script; relative filesystem paths resolve from `cwd` (project root by default).
10
+ - Environment, `cwd`, `#stdout`/`#stderr` channels, and `exitCode` follow `mcp:///plurnk/sh.json`.
package/docs/python3.md CHANGED
@@ -1,11 +1,9 @@
1
1
  # python3
2
2
 
3
- The `code` is Python code, run with `python3 -c`. Setting `cwd` selects the
4
- working directory for that inline code. A `script` instead runs that file and
5
- passes `args` as its arguments, as for every interpreter (`mcp:///plurnk/sh.json`).
3
+ Runs Python 3 via `python3 -c` (inline `text`) or `python3 <script>` (`script` with optional `args: string[]`, including `session://` and `skill://` targets).
6
4
 
7
- await plurnk.python3({ code: "import json, sys\nprint(json.dumps({\"python\": list(sys.version_info[:2])}))" }); // the code is the program
5
+ plurnk.python3({ text: "import json, sys\nprint(json.dumps({\"python\": list(sys.version_info[:2])}))" })
8
6
 
9
- await plurnk.python3({ script: "tools/report.py", args: ["--help"] });
7
+ plurnk.python3({ script: "tools/report.py", args: ["--help"] })
10
8
 
11
- Native skill files retain their sibling imports and source-relative assets.
9
+ Native skill scripts retain sibling imports and source-relative assets; environment, `cwd`, `#stdout`/`#stderr` channels, and `exitCode` follow `mcp:///plurnk/sh.json`.
package/docs/sh.md CHANGED
@@ -1,49 +1,25 @@
1
1
  # sh
2
2
 
3
- `plurnk.sh` runs the `command` with `-c` in the shell its summary names: the
4
- environment's `$SHELL`, or `/bin/sh` when that names no executable file. The command
5
- reaches it character-perfect, whitespace included; write for that shell.
3
+ `plurnk.sh` runs `text` with `-c` in the shell its summary names (`$SHELL`, or `/bin/sh` when unset).
6
4
 
7
- await plurnk.sh({ command: "printf 'hello\\n' > hello.txt\nwc -l hello.txt" }); // the command is the script itself
5
+ plurnk.sh({ text: "printf 'hello\\n' > hello.txt\nwc -l hello.txt" })
8
6
 
9
- A `script` runs that script: `await plurnk.sh({ script: "greet.sh" });` runs it with no arguments;
10
- `args` (an array of argument strings or a shell-quoted string) is passed as its arguments
11
- (plain words split on whitespace, quoted arguments stay whole, nothing is
12
- expanded). The interpreter reads the script directly, so it needs no executable
13
- bit; a script path authored inside a shell `command` still follows the kernel's
14
- ordinary executable-bit rules. The same applies to local, `session://`, and
15
- `skill://` script targets across every interpreter.
7
+ | Input | Behavior |
8
+ | --- | --- |
9
+ | `{ text, cwd? }` | Run `text` inline via `<shell> -c`. |
10
+ | `{ script, args?, cwd? }` | Run a local, `session://`, or `skill://` script directly in the interpreter with `args` (`string[]`); no executable bit is required. |
11
+ | `cwd` | Working directory (defaults to the workspace project root). |
16
12
 
17
13
  ## Environment
18
14
 
19
- The command receives a scoped environment: provider keys and every `PLURNK_*`
20
- setting are stripped before the child starts, so `printenv` cannot read
21
- plurnk's credentials, and the project's environment passes through, overlaid
22
- with the `env` registry (`mcp:///plurnk/env.json`).
23
-
24
- ## Working directory
25
-
26
- The working directory is the workspace project root, or in a workspace without
27
- one the directory the shell would run in anyway. Setting `cwd` overrides it for
28
- an inline command (`await plurnk.sh({ command: "npm test", cwd: "packages/api" });`); a receipt names the
29
- directory, relative to the project root, only when the command ran elsewhere.
30
-
31
- ## Channels
32
-
33
- An execution is a host effect, subject to approval. Output streams
34
- under the receipt's `stream` address, such as `sh:///ab3d5678`: `#stdout` is
35
- the default channel and `#stderr` the second; both are `text/stream`. While it
36
- runs, the packet's `## Open Work` streams list reports each channel's size and
37
- growth, and `plurnk.read` can inspect any range. On completion, the harness adds one
38
- `_plurnk` `read` per channel: the whole output when it fits the context budget,
39
- otherwise a bodiless receipt naming its size, with the `range` extent and the
40
- terminal exit status. Read a range of the observation's `path` for any part:
41
-
42
- await plurnk.read({ path: "sh:///ab3d5678#stdout", range: "17,40" });
43
-
44
- The observation carries the `exitCode`; a nonzero one is the command's answer,
45
- not a failure. Stdout and stderr are separate channels,
46
- and a diagnostic may be on either. The `log:///…/sh` receipt's own body is the
47
- program exactly as sent, never output. A receipt with a non-200 status and no
48
- `stream` address ran nothing; its body is still the program, and its Problem
49
- says why it was refused.
15
+ Provider credentials and `PLURNK_*` settings are stripped before launch; admitted host variables and `plurnk.env` (`mcp:///plurnk/env.json`) entries pass through.
16
+
17
+ ## Channels and settlement
18
+
19
+ Output streams under the receipt's `stream` address (e.g. `sh:///ab3d5678`):
20
+
21
+ - `#stdout` (default) and `#stderr` (`text/stream`): while running, `## Open Work` reports each channel's size and growth; on completion, `_plurnk` emits one `read` per non-empty channel (full output when it fits the budget, or a bodiless size/range receipt with `exitCode`).
22
+ - Nonzero `exitCode` is the command's exit status, not an operation failure.
23
+ - Read a slice of a running or settled stream with `range`:
24
+
25
+ plurnk.read({ path: "sh:///ab3d5678#stdout", range: "17,40" })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plurnk/plurnk-execs-common",
3
- "version": "3.0.3",
3
+ "version": "3.0.4",
4
4
  "description": "Subprocess executor for plurnk-service's exec scheme — exposing the shell, Node.js, and Python 3.",
5
5
  "keywords": [
6
6
  "plurnk",
@@ -15,7 +15,7 @@
15
15
  ],
16
16
  "homepage": "https://github.com/plurnk/plurnk-service/tree/main/plurnk-execs-common#readme",
17
17
  "bugs": {
18
- "url": "https://repo.possumtech.com/plurnk/plurnk-service/issues"
18
+ "url": "https://github.com/plurnk/plurnk-service/issues"
19
19
  },
20
20
  "repository": {
21
21
  "type": "git",
@@ -65,5 +65,5 @@
65
65
  "@plurnk/plurnk-contracts": "^7.0.0",
66
66
  "@plurnk/plurnk-execs": "^4.0.0"
67
67
  },
68
- "gitHead": "9d234b81e14a1d3fe88b9be3f7c643a0eb929533"
68
+ "gitHead": "49f92d9351021ae0729d69e72ab9b1b95e3a974b"
69
69
  }