@plurnk/plurnk-execs-common 3.0.2 → 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
- ```sh (./deploy.sh)
34
- --release "two words"
35
- ```
13
+ Local script form (`{ script, args?, cwd? }`):
36
14
 
37
- ```python3 (transform.py)
38
- --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 (add)` with `scope: "workspace"`): `NODE_OPTIONS` reaches both `js` execution children and Node-based MCP server subprocesses in the workspace.
67
- - **Session scope** (`plurnk_env (add)` 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,18 +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).
3
+ Runs JavaScript via `node -e` (inline `text`) or `node <script>` (`script` with optional `args: string[]`, including `session://` and `skill://` targets).
5
4
 
6
- ```js <!-- the body is the snippet -->
7
- const os = require("node:os");
8
- console.log(JSON.stringify({ platform: os.platform(), cpus: os.cpus().length }));
9
- ```
5
+ plurnk.js({ text: "const os = require(\"node:os\");\nconsole.log(JSON.stringify({ platform: os.platform(), cpus: os.cpus().length }));" })
10
6
 
11
- `js (tool.js)` runs that JavaScript file and passes the body as its arguments
12
- (tokenized by shell quoting), also for `session://` and `skill://` targets.
13
- Relative imports resolve from the script; ordinary relative filesystem paths
14
- resolve from the working directory. Environment, working directory, channels,
15
- output on completion, exit status and lifetime are every subprocess executor's,
16
- as `plurnk:///sh.md` states. Stdout is text, so structured output is serialized
17
- (`console.log(JSON.stringify(value))`); a thrown error exits nonzero with its
18
- stack on stderr.
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,17 +1,9 @@
1
1
  # python3
2
2
 
3
- The body is Python code, run with `python3 -c`. A directory in the `(target)`
4
- slot selects the working directory for that inline code. A script target instead
5
- runs that file and passes the body as its arguments (tokenized by shell
6
- quoting), as for every interpreter (`plurnk:///sh.md`).
3
+ Runs Python 3 via `python3 -c` (inline `text`) or `python3 <script>` (`script` with optional `args: string[]`, including `session://` and `skill://` targets).
7
4
 
8
- ```python3 <!-- the body is the program -->
9
- import json, sys
10
- print(json.dumps({"python": list(sys.version_info[:2])}))
11
- ```
5
+ plurnk.python3({ text: "import json, sys\nprint(json.dumps({\"python\": list(sys.version_info[:2])}))" })
12
6
 
13
- ```python3 (tools/report.py)
14
- --help
15
- ```
7
+ plurnk.python3({ script: "tools/report.py", args: ["--help"] })
16
8
 
17
- 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,66 +1,25 @@
1
1
  # sh
2
2
 
3
- The `sh` fence runs the body with `-c` in the shell its summary names: the
4
- environment's `$SHELL`, or `/bin/sh` when that names no executable file. The body
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
- ```sh <!-- the body is the script itself -->
8
- printf 'hello\n' > hello.txt
9
- wc -l hello.txt
10
- ```
5
+ plurnk.sh({ text: "printf 'hello\\n' > hello.txt\nwc -l hello.txt" })
11
6
 
12
- A script target runs that script: `sh (greet.sh)` runs it with no arguments;
13
- a nonempty body is tokenized by shell quoting and passed as its arguments
14
- (plain words split on whitespace, quoted arguments stay whole, nothing is
15
- expanded). The interpreter reads the script directly, so it needs no executable
16
- bit; a script path authored inside a shell body still follows the kernel's
17
- ordinary executable-bit rules. The same applies to local, `session://`, and
18
- `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). |
19
12
 
20
13
  ## Environment
21
14
 
22
- The command receives a scoped environment: provider keys and every `PLURNK_*`
23
- setting are stripped before the child starts, so `printenv` cannot read
24
- plurnk's credentials, and the project's environment passes through, overlaid
25
- with the `env` registry (`mcp:///plurnk_env.md`).
15
+ Provider credentials and `PLURNK_*` settings are stripped before launch; admitted host variables and `plurnk.env` (`mcp:///plurnk/env.json`) entries pass through.
26
16
 
27
- ## Working directory
17
+ ## Channels and settlement
28
18
 
29
- The working directory is the workspace project root, or in a workspace without
30
- one the directory the shell would run in anyway. A directory in the `(target)`
31
- slot overrides it for an inline body (`sh (packages/api)`); a receipt names the
32
- directory, relative to the project root, only when the command ran elsewhere.
19
+ Output streams under the receipt's `stream` address (e.g. `sh:///ab3d5678`):
33
20
 
34
- ## Channels
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`:
35
24
 
36
- An execution is a host effect, subject to approval. Output streams
37
- under the receipt's `stream` address, such as `sh:///ab3d5678`: `#stdout` is
38
- the default channel and `#stderr` the second; both are `text/stream`. While it
39
- runs, the packet's `## Open Work` streams list reports each channel's size and
40
- growth, and READ can inspect any range. On completion, the harness adds one
41
- `_plurnk` READ per channel: the whole output when it fits the context budget,
42
- otherwise a bodiless receipt naming its size, with the `range` extent and the
43
- terminal exit status. READ a range of the observation's `path` for any part:
44
-
45
- ```READ (sh:///ab3d5678#stdout) <17,40>
46
- ```
47
-
48
- The observation carries the `exitCode`; a nonzero one is the command's answer,
49
- not a failure. Stdout and stderr are separate channels,
50
- and a diagnostic may be on either. The `log:///…/sh` receipt's own body is the
51
- program exactly as sent, never output. A receipt with a non-200 status and no
52
- `stream` address ran nothing; its body is still the program, and its Problem
53
- says why it was refused.
54
-
55
- ## Lifetime
56
-
57
- The heading's `lifetime` metadata bounds the command's total runtime, not idle
58
- time. This command is stopped after 30 seconds if it has not finished:
59
-
60
- ```sh [{"lifetime": "30s"}]
61
- npm test
62
- ```
63
-
64
- Expiry closes the stream with status 504. By contrast, `WAIT [30]` limits one
65
- wait to 30 seconds without stopping the command. Without `lifetime`,
66
- the command is loop-bound; see `mcp:///plurnk_env.md` for other `lifetime` values (`detached`, `turn`).
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.2",
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": "db65532b51408a2014f7ff53a288d834ff6694f2"
68
+ "gitHead": "49f92d9351021ae0729d69e72ab9b1b95e3a974b"
69
69
  }