@plurnk/plurnk-execs-common 1.20.0 → 1.21.1

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/.env.defaults ADDED
@@ -0,0 +1,11 @@
1
+ # @plurnk/plurnk-execs-common — secondary interpreters are opt-in.
2
+ # Set a runtime to 1 to register it; its interpreter must also be on PATH.
3
+ # sh, node, and python3 remain enabled through the executor framework's default.
4
+ PLURNK_EXECS_PERL=0
5
+ PLURNK_EXECS_RUBY=0
6
+ PLURNK_EXECS_LUA=0
7
+ PLURNK_EXECS_DENO=0
8
+ PLURNK_EXECS_BUN=0
9
+ PLURNK_EXECS_TCL=0
10
+ PLURNK_EXECS_BC=0
11
+ PLURNK_EXECS_AWK=0
package/.env.example CHANGED
@@ -1,15 +1,13 @@
1
1
  # @plurnk/plurnk-execs-common — environment
2
2
 
3
- # Operator kill-switch per runtime tag. Set to 0 (or false) to disable a tag
4
- # even when its interpreter is installed on the host. Absent = enabled (the tag
5
- # is offered when its binary is on PATH). Disabling drops it from the model's
6
- # available-runtimes list with a "disabled" reason on the 501.
7
- #
8
- # PLURNK_EXECS_PERL=0
9
- # PLURNK_EXECS_RUBY=0
10
- # PLURNK_EXECS_LUA=0
11
- # PLURNK_EXECS_DENO=0
12
- # PLURNK_EXECS_BUN=0
13
- # PLURNK_EXECS_TCL=0
14
- # PLURNK_EXECS_BC=0
15
- # PLURNK_EXECS_AWK=0
3
+ # sh, node, and python3 are enabled when their interpreter is available.
4
+ # Uncomment any optional runtime to enable it at the next service start.
5
+ # An enabled runtime still requires its interpreter on PATH.
6
+ # PLURNK_EXECS_PERL=1
7
+ # PLURNK_EXECS_RUBY=1
8
+ # PLURNK_EXECS_LUA=1
9
+ # PLURNK_EXECS_DENO=1
10
+ # PLURNK_EXECS_BUN=1
11
+ # PLURNK_EXECS_TCL=1
12
+ # PLURNK_EXECS_BC=1
13
+ # PLURNK_EXECS_AWK=1
package/README.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  The **universal subprocess executor** for
4
4
  [plurnk-service](https://github.com/plurnk/plurnk-service)'s `exec` scheme. One
5
- package covers the shell, Node.js, Python 3, and whichever supported host
6
- interpreters are present. Node is guaranteed; the rest are detected.
5
+ package offers the shell, Node.js, and Python 3 by default, with other supported
6
+ host interpreters available as opt-ins. Node is guaranteed; the rest are detected.
7
7
 
8
8
  A `@plurnk/plurnk-execs-*` sibling built on the [plurnk-execs](https://github.com/plurnk/plurnk-service/tree/main/plurnk-execs) framework.
9
9
 
@@ -25,7 +25,7 @@ detects every other interpreter, so one executor adapts to the host.
25
25
  | `bc` 🧮 | bc | stdin (for example, `6 * 7`) |
26
26
  | `awk` 🪄 | awk | program arg, empty stdin (`BEGIN { … }`) |
27
27
 
28
- Each runtime's catalog Summary includes a short executable inline body, with
28
+ Each enabled runtime's catalog Summary includes a short executable inline body, with
29
29
  literal `\n` separators keeping the invocation on one discovery line.
30
30
 
31
31
  ### A script — the `(target)` slot
@@ -37,19 +37,19 @@ selects the working directory. Script arguments use `[{"args": ["arg",...]}]`;
37
37
  each string is passed literally, without shell expansion ({§executor-metadata}).
38
38
  These options work for local, Worker, and Skill script targets alike.
39
39
 
40
- ````sh (./deploy.sh)
40
+ ```sh (./deploy.sh)
41
41
  yes
42
42
  yes
43
43
  no
44
- ````
44
+ ```
45
45
 
46
- ````python3 (transform.py) [{"args": ["--format","json"]}]
46
+ ```python3 (transform.py) [{"args": ["--format","json"]}]
47
47
  3
48
48
  1
49
49
  4
50
50
  1
51
51
  5
52
- ````
52
+ ```
53
53
 
54
54
  The first operation answers a shell script's prompts through stdin. The second
55
55
  feeds records to a Python script.
@@ -62,6 +62,13 @@ separate input channel; script targets receive stdin from the body.
62
62
 
63
63
  ## Configuration
64
64
 
65
+ The shipped [`.env.defaults`](./.env.defaults) disables `perl`, `ruby`, `lua`,
66
+ `deno`, `bun`, `tcl`, `bc`, and `awk`. To expose one, set its switch to `1`
67
+ in the ordinary environment cascade and restart the service; commented opt-ins
68
+ are in [`.env.example`](./.env.example). For example, `PLURNK_EXECS_PERL=1`
69
+ enables the Perl executor when Perl is installed. These switches do not prevent
70
+ calling an interpreter through `sh`.
71
+
65
72
  Per-tag kill-switches (`PLURNK_EXECS_<TAG>=0`) and the
66
73
  `PLURNK_EXECS_ONLY` allowlist are honored by framework discovery, uniformly
67
74
  across every plugin ({§executor-policy}). A disabled tag is not registered and
package/docs/awk.md CHANGED
@@ -1,30 +1,21 @@
1
1
  # awk
2
2
 
3
- For continuing input, launch with `[{"stdin": "open"}]`, then SEND exact text to the
4
- returned execution address; `[{"eof": true}]` closes stdin. See the
5
- [live-input example](node.md#live-input), including newline framing.
6
-
7
3
  The body is the AWK program, passed as the one positional argument with an
8
- empty stdin: with no input file it only runs `BEGIN` blocks. To process data,
9
- name the file(s) in `[{"args": [...]}]`, or run a script target and feed it the body
10
- as stdin.
4
+ empty stdin: with no input file it only runs `BEGIN` blocks. Input files are
5
+ named in `[{"args": [...]}]`; a script target runs that file and receives the
6
+ body as stdin. Working directory, environment, channels and exit status are the
7
+ executor family's (`sh.md`); `exit 1` in the program closes with status 500.
11
8
 
12
- ````awk [{"args": ["data.csv"]}] <!-- the body is the program -->
9
+ ```awk [{"args": ["data.csv"]}] <!-- the body is the program -->
13
10
  BEGIN { FS = "," }
14
11
  NR > 1 { total += $3 }
15
12
  END { printf "rows=%d total=%.2f\n", NR - 1, total }
16
- ````
13
+ ```
17
14
 
18
- ````awk (tools/summarize.awk) <!-- the script runs; the body is its stdin -->
15
+ ```awk (tools/summarize.awk) <!-- the script runs; the body is its stdin -->
19
16
  alpha,1
20
17
  beta,2
21
- ````
22
-
23
- Output goes to `#stdout`, diagnostics to `#stderr`; `exit 1` in the program
24
- closes with status 500. `[{"cwd": "<directory>"}]` selects the working directory
25
- for relative file arguments. AWK is the right tool for column arithmetic and
26
- line reshaping over text; for JSON use `jq`, for anything else `node` or `sh`.
18
+ ```
27
19
 
28
- `[{"env": {"NAME": "value"}}]` on the same fence line sets variables for this run
29
- alone, over the entries in your `env` registry; names are the shell's, and plurnk's own
30
- (`PLURNK_*`, provider credentials) are refused by name.
20
+ Live input (`[{"stdin": "open"}]`, SEND, `[{"eof": true}]`) is as `node.md`
21
+ shows.
package/docs/bc.md CHANGED
@@ -1,22 +1,17 @@
1
1
  # bc
2
2
 
3
- For continuing input, launch with `[{"stdin": "open"}]`, then SEND exact text to the
4
- returned execution address; `[{"eof": true}]` closes stdin. See the
5
- [live-input example](node.md#live-input), including newline framing.
3
+ The body is a `bc` program fed on stdin (a trailing newline is added, so the
4
+ last line evaluates). Arbitrary precision: `scale` is set before dividing, or
5
+ every quotient is truncated to an integer.
6
6
 
7
- The body is a `bc` program fed on stdin (a trailing newline is added, so the last
8
- line evaluates). Arbitrary precision: set `scale` before dividing, or every
9
- quotient is truncated to an integer.
10
-
11
- ````bc <!-- the body is the program -->
7
+ ```bc <!-- the body is the program -->
12
8
  scale=6
13
9
  22/7
14
10
  2^64
15
- ````
11
+ ```
16
12
 
17
13
  Each expression prints its value on its own line to `#stdout`; `#stderr`
18
- carries parse errors such as `syntax error`. The exit status is 0 even when
19
- a line failed to parse, so read `#stderr` when a result is missing. A script
20
- target (`bc (rates.bc)`) runs that file with the body as stdin.
21
- Use bc for exact decimal or big-integer arithmetic; for anything with strings,
22
- loops over data, or JSON, reach for `awk`, `node`, or `sh`.
14
+ carries parse errors such as `syntax error`, and the exit status is 0 even when
15
+ a line failed to parse, so a missing result is explained on `#stderr`. A script
16
+ target (`bc (rates.bc)`) runs that file with the body as stdin. Live input
17
+ (`[{"stdin": "open"}]`, SEND, `[{"eof": true}]`) is as `node.md` shows.
package/docs/bun.md CHANGED
@@ -1,22 +1,18 @@
1
1
  # bun
2
2
 
3
- For continuing input, launch with `[{"stdin": "open"}]`, then SEND exact text to the
4
- returned execution address; `[{"eof": true}]` closes stdin. See the
5
- [live-input example](node.md#live-input), including newline framing.
6
-
7
3
  The body is TypeScript or JavaScript, run with `bun -e`. A script target runs
8
4
  that file and receives the body as stdin; `[{"args": [...]}]` passes literal
9
- arguments, readable as `Bun.argv` or `process.argv`.
5
+ arguments, readable as `Bun.argv` or `process.argv`. Node is always present;
6
+ bun only when the host has it.
10
7
 
11
- ````bun <!-- the body is the program -->
8
+ ```bun <!-- the body is the program -->
12
9
  const file = Bun.file("package.json");
13
10
  console.log((await file.json()).name);
14
- ````
11
+ ```
15
12
 
16
- ````bun (scripts/build.ts) [{"args": ["--watch=false"]}]
17
- ````
13
+ ```bun (scripts/build.ts) [{"args": ["--watch=false"]}]
14
+ ```
18
15
 
19
16
  `console.log` streams to `#stdout`, `console.error` to `#stderr`; an uncaught
20
- error or `process.exit(1)` closes with status 500. Prefer `node` unless the
21
- project is a Bun project: node is always present, bun only when the host
22
- has it.
17
+ error or `process.exit(1)` closes with status 500. Live input
18
+ (`[{"stdin": "open"}]`, SEND, `[{"eof": true}]`) is as `node.md` shows.
package/docs/deno.md CHANGED
@@ -1,23 +1,19 @@
1
1
  # deno
2
2
 
3
- For continuing input, launch with `[{"stdin": "open"}]`, then SEND exact text to the
4
- returned execution address; `[{"eof": true}]` closes stdin. See the
5
- [live-input example](node.md#live-input), including newline framing.
6
-
7
3
  The body is TypeScript or JavaScript, run with `deno eval` (permissions are
8
- Deno's defaults for `eval`; import what you need from the project or a URL).
9
- A script target runs that file and receives the body as stdin;
10
- `[{"args": [...]}]` passes literal arguments, readable as `Deno.args`.
4
+ Deno's defaults for `eval`; imports come from the project or a URL). A script
5
+ target runs that file and receives the body as stdin; `[{"args": [...]}]` passes
6
+ literal arguments, readable as `Deno.args`. Node is always present; deno only
7
+ when the host has it.
11
8
 
12
- ````deno <!-- the body is the program -->
9
+ ```deno <!-- the body is the program -->
13
10
  const versions: Record<string, string> = Deno.version;
14
11
  console.log(JSON.stringify(versions));
15
- ````
12
+ ```
16
13
 
17
- ````deno (scripts/check.ts) [{"args": ["--strict"]}]
18
- ````
14
+ ```deno (scripts/check.ts) [{"args": ["--strict"]}]
15
+ ```
19
16
 
20
17
  `console.log` streams to `#stdout`, `console.error` to `#stderr`; an uncaught
21
- error or `Deno.exit(1)` closes with status 500. Prefer `node` when the task
22
- does not need Deno specifically: node is always present, deno only when the
23
- host has it.
18
+ error or `Deno.exit(1)` closes with status 500. Live input
19
+ (`[{"stdin": "open"}]`, SEND, `[{"eof": true}]`) is as `node.md` shows.
package/docs/lua.md CHANGED
@@ -1,23 +1,20 @@
1
1
  # lua
2
2
 
3
- For continuing input, launch with `[{"stdin": "open"}]`, then SEND exact text to the
4
- returned execution address; `[{"eof": true}]` closes stdin. See the
5
- [live-input example](node.md#live-input), including newline framing.
6
-
7
3
  The body is Lua code, run with `lua -e`. A script target runs that file and
8
- receives the body as stdin; `[{"args": [...]}]` passes literal script arguments,
9
- readable through the `arg` table.
4
+ receives the body as stdin; `[{"args": [...]}]` passes literal script
5
+ arguments, readable through the `arg` table.
10
6
 
11
- ````lua <!-- the body is the program -->
7
+ ```lua <!-- the body is the program -->
12
8
  local t = { 3, 1, 2 }
13
9
  table.sort(t)
14
10
  print(table.concat(t, ","))
15
- ````
11
+ ```
16
12
 
17
- ````lua (scripts/lint.lua) [{"args": ["src/main.lua"]}]
18
- ````
13
+ ```lua (scripts/lint.lua) [{"args": ["src/main.lua"]}]
14
+ ```
19
15
 
20
- `print` streams to `#stdout`, `io.stderr:write` to `#stderr`; `error(...)`
21
- or `os.exit(1)` closes with status 500. The standalone interpreter is the
22
- host's `lua` on PATH; project modules resolve through its ordinary
23
- `package.path` from the working directory.
16
+ `print` streams to `#stdout`, `io.stderr:write` to `#stderr`; `error(...)` or
17
+ `os.exit(1)` closes with status 500. The standalone interpreter is the host's
18
+ `lua` on PATH; project modules resolve through its ordinary `package.path` from
19
+ the working directory. Live input (`[{"stdin": "open"}]`, SEND,
20
+ `[{"eof": true}]`) is as `node.md` shows.
package/docs/node.md CHANGED
@@ -1,64 +1,44 @@
1
1
  # node
2
2
 
3
- A JavaScript snippet, run via `node -e`. Node is the daemon's own runtime, so it's always available (no PATH probe).
3
+ A JavaScript snippet, run via `node -e`. Node is the daemon's own runtime, so it
4
+ is always available (no PATH probe).
4
5
 
5
- ````node <!-- the body is the snippet -->
6
+ ```node <!-- the body is the snippet -->
6
7
  const os = require("node:os");
7
8
  console.log(JSON.stringify({ platform: os.platform(), cpus: os.cpus().length }));
8
- ````
9
+ ```
10
+
11
+ `node (tool.js)` runs that JavaScript file and receives the body as stdin;
12
+ `[{"args": ["--format","json"]}]` passes literal script arguments, also for
13
+ `worker://` and `skill://` targets. Relative imports resolve from the script;
14
+ ordinary relative filesystem paths resolve from the working directory.
15
+ Environment, working directory, channels, first page, exit status and lifetime
16
+ are the executor family's, as `sh.md` states. Stdout is text, so structured
17
+ output is serialized (`console.log(JSON.stringify(value))`); a thrown error
18
+ exits nonzero (status 500) with its stack on stderr.
9
19
 
10
20
  ## Live input
11
21
 
12
22
  `[{"stdin": "open"}]` keeps stdin open for later SENDs to the receipt's execution
13
23
  address. Without it, initial input ends with EOF as usual.
14
24
 
15
- ````node [{"stdin": "open"}]
25
+ ```node [{"stdin": "open"}]
16
26
  process.stdin.on("data", chunk => process.stdout.write(chunk));
17
- ````
27
+ ```
18
28
 
19
29
  Using the address returned by that invocation (here `node:///ab3d5678`):
20
30
 
21
- ````SEND (node:///ab3d5678)
31
+ ```SEND (node:///ab3d5678)
22
32
  hello
23
33
 
24
- ````
34
+ ```
25
35
 
26
36
  The blank line before the closing fence supplies a newline after `hello`.
27
37
  SEND adds no newline of its own. Its receipt acknowledges pipe delivery, not
28
38
  program completion. READ that same address to inspect stdout while it runs.
29
39
 
30
- ````SEND (node:///ab3d5678) [{"eof": true}]
31
- ````
40
+ ```SEND (node:///ab3d5678) [{"eof": true}]
41
+ ```
32
42
 
33
43
  EOF closes stdin, not the process. KILL terminates the execution. Any worker
34
44
  in the workspace can send input to the same execution address.
35
-
36
- ## Environment
37
-
38
- The same scoped environment as `sh`: the daemon's own secrets (`PLURNK_*`, provider keys) are stripped, so `process.env` inside the snippet sees the project's environment, not plurnk's.
39
-
40
- ## Output
41
-
42
- Whatever the snippet writes to stdout streams to `#stdout`; stderr streams to
43
- `#stderr`. Both are text under the receipt's `stream` address, such as
44
- `node:///ab3d5678`. On completion, the harness adds a READ of each channel's
45
- first page (up to 16 lines). READ the stream address for additional lines;
46
- the log READ holds only its recorded page. To return structured data, use
47
- `console.log(JSON.stringify(value))`. A thrown error exits nonzero (status 500)
48
- with its stack on stderr. The receipt's own body is the snippet as you sent
49
- it, never output; a non-200 receipt with no `stream` address ran nothing.
50
-
51
- ## Working directory
52
-
53
- Runs in the workspace project root by default, or the daemon's own cwd in a
54
- workspace without one; a `[{"cwd": "<directory>"}]` block on the opening fence line selects
55
- another. The target is a script, never a command or a directory:
56
- `node (tool.js)` runs that JavaScript file and receives the body as
57
- stdin. `[{"args": ["--format","json"]}]` passes literal script arguments, also for
58
- `worker://` and `skill://` targets. Relative imports resolve from the script;
59
- ordinary relative filesystem paths resolve from cwd. The receipt names cwd
60
- when it differs from the project root.
61
-
62
- `[{"env": {"NAME": "value"}}]` on the same fence line sets variables for this run
63
- alone, over the entries in your `env` registry; names are the shell's, and plurnk's own
64
- (`PLURNK_*`, provider credentials) are refused by name.
package/docs/perl.md CHANGED
@@ -1,23 +1,19 @@
1
1
  # perl
2
2
 
3
- For continuing input, launch with `[{"stdin": "open"}]`, then SEND exact text to the
4
- returned execution address; `[{"eof": true}]` closes stdin. See the
5
- [live-input example](node.md#live-input), including newline framing.
3
+ The body is Perl code, run with `perl -e`; `-n`/`-p` have no equivalent here,
4
+ so a line loop is written in the body. A script target runs that file and
5
+ receives the body as stdin; `[{"args": [...]}]` passes literal arguments.
6
+ Environment, channels and exit status are the executor family's (`sh.md`):
7
+ `die` or a nonzero `exit` closes with status 500.
6
8
 
7
- The body is Perl code, run with `perl -e`. A script target runs that file and
8
- receives the body as stdin; `[{"args": [...]}]` passes literal script arguments.
9
-
10
- ````perl <!-- the body is the program -->
9
+ ```perl <!-- the body is the program -->
11
10
  use strict; use warnings;
12
11
  my %count; $count{$_}++ for qw(a b a c a);
13
12
  printf "%s=%d\n", $_, $count{$_} for sort keys %count;
14
- ````
13
+ ```
15
14
 
16
- ````perl (tools/rename.pl) [{"args": ["--dry-run"]}]
17
- ````
15
+ ```perl (tools/rename.pl) [{"args": ["--dry-run"]}]
16
+ ```
18
17
 
19
- stdout streams to `#stdout`, stderr to `#stderr`; `die` or a nonzero `exit`
20
- closes with status 500. The environment is scoped exactly as for `sh`
21
- (plurnk's own settings and provider keys are stripped). Use `-n`/`-p`-style
22
- one-liners by writing the loop yourself, or run a script target over the
23
- files you name in `{args}`.
18
+ Live input (`[{"stdin": "open"}]`, SEND, `[{"eof": true}]`) is as `node.md`
19
+ shows.
package/docs/python3.md CHANGED
@@ -1,26 +1,18 @@
1
1
  # python3
2
2
 
3
- For continuing input, launch with `[{"stdin": "open"}]`, then SEND exact text to the
4
- returned execution address; `[{"eof": true}]` closes stdin. See the
5
- [live-input example](node.md#live-input), including newline framing.
6
-
7
3
  The body is Python code, run with `python3 -c`. A script target instead runs
8
- that file and receives the body as stdin. Script arguments and working directory
9
- are optional header metadata:
4
+ that file and receives the body as stdin; `[{"args": [...]}]` passes literal
5
+ arguments and `[{"cwd": "<directory>"}]` selects the working directory, as for
6
+ every interpreter (`sh.md`).
10
7
 
11
- ````python3 <!-- the body is the program -->
8
+ ```python3 <!-- the body is the program -->
12
9
  import json, sys
13
10
  print(json.dumps({"python": list(sys.version_info[:2])}))
14
- ````
15
-
16
- ````python3 (tools/report.py) [{"args": ["--help"]}]
17
- ````
11
+ ```
18
12
 
19
- Each argument is a literal string, without shell expansion. `[{"cwd": "<directory>"}]`
20
- selects the working directory; otherwise it remains the workspace root. The
21
- same options apply to local and `worker://` script targets. Native skill files
22
- retain their sibling imports and source-relative assets.
13
+ ```python3 (tools/report.py) [{"args": ["--help"]}]
14
+ ```
23
15
 
24
- `[{"env": {"NAME": "value"}}]` on the same fence line sets variables for this run
25
- alone, over the entries in your `env` registry; names are the shell's, and plurnk's own
26
- (`PLURNK_*`, provider credentials) are refused by name.
16
+ Native skill files retain their sibling imports and source-relative assets.
17
+ Live input (`[{"stdin": "open"}]`, SEND, `[{"eof": true}]`) is as `node.md`
18
+ shows.
package/docs/ruby.md CHANGED
@@ -1,26 +1,20 @@
1
1
  # ruby
2
2
 
3
- For continuing input, launch with `[{"stdin": "open"}]`, then SEND exact text to the
4
- returned execution address; `[{"eof": true}]` closes stdin. See the
5
- [live-input example](node.md#live-input), including newline framing.
6
-
7
3
  The body is Ruby code, run with `ruby -e`. A script target runs that file and
8
4
  receives the body as stdin; `[{"args": [...]}]` passes literal `ARGV` entries.
9
5
 
10
- ````ruby <!-- the body is the program -->
6
+ ```ruby <!-- the body is the program -->
11
7
  require "json"
12
8
  words = %w[alpha beta alpha]
13
9
  puts JSON.generate(words.tally)
14
- ````
10
+ ```
15
11
 
16
- ````ruby (bin/migrate.rb) [{"args": ["--check"]}]
17
- ````
12
+ ```ruby (bin/migrate.rb) [{"args": ["--check"]}]
13
+ ```
18
14
 
19
15
  `puts` and `print` stream to `#stdout`, `warn` to `#stderr`; an unrescued
20
- exception or `exit 1` closes with status 500 with the backtrace on stderr.
21
- Gems resolve from the project's ordinary Ruby environment; `[{"cwd": "<directory>"}]`
16
+ exception or `exit 1` closes with status 500 with the backtrace on stderr. Gems
17
+ resolve from the project's ordinary Ruby environment; `[{"cwd": "<directory>"}]`
22
18
  selects the working directory when the project's `Gemfile` lives elsewhere.
23
-
24
- `[{"env": {"NAME": "value"}}]` on the same fence line sets variables for this run
25
- alone, over the entries in your `env` registry; names are the shell's, and plurnk's own
26
- (`PLURNK_*`, provider credentials) are refused by name.
19
+ Environment metadata is the executor family's (`sh.md`). Live input
20
+ (`[{"stdin": "open"}]`, SEND, `[{"eof": true}]`) is as `node.md` shows.
package/docs/sh.md CHANGED
@@ -1,92 +1,77 @@
1
1
  # sh
2
2
 
3
- For continuing input, launch with `[{"stdin": "open"}]`, then SEND exact text to the
4
- returned execution address; `[{"eof": true}]` closes stdin. See the
5
- [live-input example](node.md#live-input), including newline framing.
6
-
7
3
  The `sh` fence runs the body via `sh -c`, character-perfect including whitespace.
8
4
 
9
- ````sh <!-- the body is the script itself -->
5
+ ```sh <!-- the body is the script itself -->
10
6
  printf 'hello\n' > hello.txt
11
7
  wc -l hello.txt
12
- ````
8
+ ```
9
+
10
+ A script target runs that script: `sh (greet.sh)` runs it with an empty stdin;
11
+ a nonempty body becomes its stdin. The interpreter reads the script directly,
12
+ so it needs no executable bit; a script path authored inside a shell body still
13
+ follows the kernel's ordinary executable-bit rules. Script arguments go in
14
+ `[{"args": ["--release","two words"]}]`: each string is one literal argument,
15
+ without shell expansion. The target is a program, never a command and never a
16
+ directory; a target that is not a script is refused before anything runs. The
17
+ same options apply to local, `worker://`, and `skill://` script targets across
18
+ every interpreter.
13
19
 
14
20
  ## Environment
15
21
 
16
- The command receives a **scoped** environment. Provider keys and every
17
- `PLURNK_*` setting are stripped before the child starts, so `printenv` cannot
18
- read plurnk's credentials. The project's environment passes through.
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.
25
+ `[{"env": {"LC_ALL": "C"}}]` on the fence line sets variables for this run
26
+ alone, over the entries in the `env` registry (`env.md`); plurnk's own names
27
+ are refused by name.
19
28
 
20
29
  ## Working directory
21
30
 
22
- The working directory is the workspace project root — where file operations
23
- write — or, in a workspace without one, the directory the shell would run in
24
- anyway. A `[{"cwd": "<directory>"}]` block on the opening fence line overrides it for its body:
25
-
26
- ````sh [{"cwd": "./dir"}]
27
- pwd
28
- ````
29
-
30
- The receipt always names the directory the command ran in.
31
-
32
- `env` on the same line sets variables for this run alone, over the entries in
33
- your `env` registry; plurnk's own names (`PLURNK_*`, provider credentials) are
34
- refused by name:
35
-
36
- ````sh [{"env": {"LC_ALL": "C"}}]
37
- sort names.txt
38
- ````
39
-
40
- A script target runs that script: `sh (greet.sh)` runs it with an empty
41
- stdin; a nonempty body becomes its stdin. The interpreter reads the script
42
- directly, so it needs no executable bit; a script path authored inside a shell
43
- body still follows the kernel's ordinary executable-bit rules.
44
-
45
- Script arguments go in `[{"args": ["--release","two words"]}]`: each string is
46
- one literal argument, without shell expansion. The same options apply to
47
- local, `worker://`, and `skill://` script targets across the common interpreters.
48
-
49
- The target is a program — a script — never a command and never a directory. A
50
- target that is not a script is refused before anything runs.
31
+ The working directory is the workspace project root, or in a workspace without
32
+ one the directory the shell would run in anyway. `[{"cwd": "<directory>"}]` on
33
+ the fence line overrides it for its body; the receipt always names the
34
+ directory the command ran in.
51
35
 
52
36
  ## Channels
53
37
 
54
- Every shell invocation is host-effecting and proposes for review before it
55
- runs. Output then streams under the receipt's `stream` address, such as
56
- `sh:///ab3d5678`: `#stdout` is the default channel and
57
- `#stderr` is the second; both are `text/stream`. While it runs, the packet's
58
- `## Delegation` streams list reports each channel's size and growth and READ can
59
- inspect any range. On
60
- completion, the harness adds one `_plurnk` READ per channel: its first page
61
- (up to 16 lines), `range` extent, and terminal exit status. READ that observation's
62
- `path` for more; the `log:///…/READ` item holds only its recorded page:
63
-
64
- ````READ (sh:///ab3d5678#stdout) <17,40>
65
- ````
38
+ An execution is a host effect, admitted under the loop's policy. Output streams
39
+ under the receipt's `stream` address, such as `sh:///ab3d5678`: `#stdout` is
40
+ the default channel and `#stderr` the second; both are `text/stream`. While it
41
+ runs, the packet's `## Delegation` streams list reports each channel's size and
42
+ growth, and READ can inspect any range. On completion, the harness adds one
43
+ `_plurnk` READ per channel: its first page, `range` extent, and terminal exit
44
+ status. READ the observation's `path` for more; the `log:///…/READ` item holds
45
+ only its recorded page:
66
46
 
67
- A nonzero exit closes with status 500; inspect both channels because either
68
- may carry the useful diagnostic.
47
+ ```READ (sh:///ab3d5678#stdout) <17,40>
48
+ ```
69
49
 
70
- The `log:///…/sh` receipt's own body is the program exactly as you sent it,
71
- never output: output lives on the stream and in the harness READs. A receipt
72
- with a non-200 status and no `stream` address ran nothing; its body is still
73
- your program, and its Problem says why it was refused.
50
+ A nonzero exit closes with status 500; stdout and stderr are separate channels,
51
+ and a diagnostic may be on either. The `log:///…/sh` receipt's own body is the
52
+ program exactly as sent, never output. A receipt with a non-200 status and no
53
+ `stream` address ran nothing; its body is still the program, and its Problem
54
+ says why it was refused.
74
55
 
75
56
  ## Lifetime
76
57
 
77
58
  How long a command may run is one metadata field; absent, it ends with the loop.
78
59
 
79
- ````sh [{"lifetime": "30m"}]
60
+ ```sh [{"lifetime": "30m"}]
80
61
  npm run e2e
81
- ````
62
+ ```
82
63
 
83
- ````sh [{"lifetime": "detached"}]
64
+ ```sh [{"lifetime": "detached"}]
84
65
  npm run dev
85
- ````
66
+ ```
86
67
 
87
68
  A duration (`30s`, `30m`, `2h`) kills the command at that deadline. `detached`
88
- outlives the loop — it runs until it exits or you KILL it, so a server you must
89
- leave running takes it. `turn` keeps the command only through the current turn.
90
- While you wait on a stream, the service wakes you to inspect it; you never ask
91
- for that, and you never poll from inside a loop. To act again later with nothing
92
- in flight, add a rule with the `schedule` family targeting yourself.
69
+ outlives the loop: it runs until it exits or is KILLed. `turn` keeps the command
70
+ only through the current turn. While a stream is live, observation wakes arrive
71
+ on the daemon's cadence and present it for inspection.
72
+
73
+ ## Live input
74
+
75
+ `[{"stdin": "open"}]` keeps stdin open for later SENDs to the returned execution
76
+ address; `[{"eof": true}]` closes it. The worked example, including newline
77
+ framing, is on `node.md`.
package/docs/tcl.md CHANGED
@@ -1,23 +1,20 @@
1
1
  # tcl
2
2
 
3
- For continuing input, launch with `[{"stdin": "open"}]`, then SEND exact text to the
4
- returned execution address; `[{"eof": true}]` closes stdin. See the
5
- [live-input example](node.md#live-input), including newline framing.
6
-
7
3
  The body is a Tcl script fed to `tclsh` on stdin (a trailing newline is added).
8
- A script target runs that file and receives the body as stdin; `[{"args": [...]}]`
9
- passes literal arguments, readable as `$argv`.
4
+ A script target runs that file and receives the body as stdin;
5
+ `[{"args": [...]}]` passes literal arguments, readable as `$argv`.
10
6
 
11
- ````tcl <!-- the body is the program -->
7
+ ```tcl <!-- the body is the program -->
12
8
  set words {alpha beta alpha}
13
9
  foreach w $words { dict incr count $w }
14
10
  puts [dict get $count alpha]
15
- ````
11
+ ```
16
12
 
17
- ````tcl (tests/all.tcl) [{"args": ["-verbose","bps"]}]
18
- ````
13
+ ```tcl (tests/all.tcl) [{"args": ["-verbose","bps"]}]
14
+ ```
19
15
 
20
16
  `puts` streams to `#stdout`, `puts stderr ...` to `#stderr`; an uncaught error
21
17
  or `exit 1` closes with status 500 with the Tcl error info on stderr. tclsh
22
18
  evaluates the script line by line, so an incomplete command at the end is an
23
- error, not a silent no-op.
19
+ error, not a silent no-op. Live input (`[{"stdin": "open"}]`, SEND,
20
+ `[{"eof": true}]`) is as `node.md` shows.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plurnk/plurnk-execs-common",
3
- "version": "1.20.0",
3
+ "version": "1.21.1",
4
4
  "description": "Universal subprocess executor for plurnk-service's exec scheme — one package exposing the shell, Node.js, Python 3, and supported host interpreters.",
5
5
  "keywords": [
6
6
  "plurnk",
@@ -255,12 +255,13 @@
255
255
  "files": [
256
256
  "dist/**/*",
257
257
  "README.md",
258
+ ".env.defaults",
258
259
  ".env.example",
259
260
  "docs/**/*"
260
261
  ],
261
262
  "scripts": {
262
263
  "test:lint": "tsc --noEmit",
263
- "test:unit": "node --conditions=plurnk-dev --env-file-if-exists=../plurnk-execs/.env.defaults --test \"src/**/*.test.ts\"",
264
+ "test:unit": "node --conditions=plurnk-dev --env-file-if-exists=.env.defaults --env-file-if-exists=../plurnk-execs/.env.defaults --test \"src/**/*.test.ts\"",
264
265
  "test": "npm run test:lint && npm run test:unit",
265
266
  "build:clean": "rm -rf dist",
266
267
  "build:dist": "tsc -p tsconfig.build.json",
@@ -269,6 +270,6 @@
269
270
  "prepublishOnly": "npm audit --audit-level=moderate && npm test"
270
271
  },
271
272
  "peerDependencies": {
272
- "@plurnk/plurnk-execs": "^1.20.0"
273
+ "@plurnk/plurnk-execs": "^1.21.1"
273
274
  }
274
275
  }