@tbrandenburg/node-red-cli 0.2.12 → 0.2.13
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 +43 -1
- package/bin/node-red-cli.js +13 -5
- package/package.json +1 -1
- package/src/docker-image.js +7 -0
- package/src/docker-run.js +2 -1
package/README.md
CHANGED
|
@@ -54,6 +54,7 @@ make install-global
|
|
|
54
54
|
- [Passing flow JSON inline](#passing-flow-json-inline)
|
|
55
55
|
- [Installing additional Node-RED node packages](#installing-additional-node-red-node-packages)
|
|
56
56
|
- [Running sandboxed in Docker](#running-sandboxed-in-docker-)
|
|
57
|
+
- [Agentic workflows](#agentic-workflows-)
|
|
57
58
|
- [Host API](#host-api-)
|
|
58
59
|
- [Technical approach](#technical-approach-)
|
|
59
60
|
- [Preflight and limitations](#preflight-and-limitations-)
|
|
@@ -277,7 +278,9 @@ Sandboxing defaults applied to every `--docker` run:
|
|
|
277
278
|
|
|
278
279
|
- `--rm -i` (always disposable)
|
|
279
280
|
- `--network none`, unless `--node-modules` is also given (needs registry
|
|
280
|
-
access)
|
|
281
|
+
access) or `--network` is passed explicitly (enables network access for a
|
|
282
|
+
flow that needs to call out, independent of installing any package) —
|
|
283
|
+
narrowest network exposure by default
|
|
281
284
|
- `--read-only` root filesystem + a `/tmp` tmpfs mount
|
|
282
285
|
- `--cap-drop=ALL`
|
|
283
286
|
- `--security-opt=no-new-privileges`
|
|
@@ -293,6 +296,45 @@ failed: ...` if the image build fails (e.g. the local version isn't yet
|
|
|
293
296
|
published to npm — use `--docker <image>` or `--docker @path` as an
|
|
294
297
|
escape hatch in that case).
|
|
295
298
|
|
|
299
|
+
### Agentic workflows 🤖
|
|
300
|
+
|
|
301
|
+
Node-RED nodes such as [`agent`](https://www.npmjs.com/package/@tbrandenburg/node-red-agents)
|
|
302
|
+
(OpenCode, `pi`) turn a `link in -> agent -> link out (return)` flow into a
|
|
303
|
+
callable AI step, invoked like any other target — a JSON flow with an
|
|
304
|
+
inline multiline prompt, in one command:
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
echo '{"payload":"Summarize this repo in one sentence.","cwd":"/repo"}' \
|
|
308
|
+
| node-red-cli --flow-json '[
|
|
309
|
+
{"id":"tab","type":"tab","label":"Agent"},
|
|
310
|
+
{"id":"ask","type":"link in","z":"tab","name":"ask","wires":[["agent"]]},
|
|
311
|
+
{"id":"agent","type":"agent","z":"tab","name":"opencode","agent":"opencode",
|
|
312
|
+
"runtime":"direct","prompt":"payload","promptType":"msg",
|
|
313
|
+
"cwd":"cwd","cwdType":"msg","wires":[["return"],[]]},
|
|
314
|
+
{"id":"return","type":"link out","z":"tab","name":"return","mode":"return"}
|
|
315
|
+
]' ask --node-modules @tbrandenburg/node-red-agents --user-dir --timeout=120000 --format=json
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
The same flow runs sandboxed via `--docker <image>` against an image that
|
|
319
|
+
already ships `opencode` + `node-red-agents`, e.g.
|
|
320
|
+
[`ghcr.io/tbrandenburg/agentic-workflow-dev-env`](https://github.com/tbrandenburg/agentic-workflow-dev-env)
|
|
321
|
+
(`--network` is required for network access, since the agent calls out to
|
|
322
|
+
its own API — the package is already in the image, so `--node-modules` is
|
|
323
|
+
not needed here):
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
echo '{"payload":"Summarize this repo in one sentence.","cwd":"/repo"}' \
|
|
327
|
+
| node-red-cli --flow-json '[
|
|
328
|
+
{"id":"tab","type":"tab","label":"Agent"},
|
|
329
|
+
{"id":"ask","type":"link in","z":"tab","name":"ask","wires":[["agent"]]},
|
|
330
|
+
{"id":"agent","type":"agent","z":"tab","name":"opencode","agent":"opencode",
|
|
331
|
+
"runtime":"direct","prompt":"payload","promptType":"msg",
|
|
332
|
+
"cwd":"cwd","cwdType":"msg","wires":[["return"],[]]},
|
|
333
|
+
{"id":"return","type":"link out","z":"tab","name":"return","mode":"return"}
|
|
334
|
+
]' ask --docker ghcr.io/tbrandenburg/agentic-workflow-dev-env:latest \
|
|
335
|
+
--network --timeout=120000 --format=json
|
|
336
|
+
```
|
|
337
|
+
|
|
296
338
|
## Host API 🛠️
|
|
297
339
|
|
|
298
340
|
The core interface is intentionally small:
|
package/bin/node-red-cli.js
CHANGED
|
@@ -70,10 +70,11 @@ const HELP_TEXT = [
|
|
|
70
70
|
" - '@<path>' or an http(s) URL: build from a Dockerfile (local file or",
|
|
71
71
|
" fetched URL), cached by content hash.",
|
|
72
72
|
"Sandboxing defaults: --network none (unless --node-modules is also set,",
|
|
73
|
-
"which needs registry access
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"
|
|
73
|
+
"which needs registry access, or --network is passed explicitly to enable",
|
|
74
|
+
"network access independent of installing any package), --read-only",
|
|
75
|
+
"rootfs with a /tmp tmpfs, --cap-drop=ALL, --security-opt=no-new-privileges.",
|
|
76
|
+
"When combined with --user-dir, persistence uses a named Docker volume,",
|
|
77
|
+
"never a host bind mount.",
|
|
77
78
|
"",
|
|
78
79
|
"Example:",
|
|
79
80
|
' echo \'{"payload":{"x":4,"y":5}}\' | node-red-cli flows.json calculate',
|
|
@@ -231,7 +232,10 @@ async function run(args, options) {
|
|
|
231
232
|
let result;
|
|
232
233
|
try {
|
|
233
234
|
image = await resolveImage(options.docker, { version });
|
|
234
|
-
result = await runContainer(image, envelope, {
|
|
235
|
+
result = await runContainer(image, envelope, {
|
|
236
|
+
networkNeeded: nodeModules.length > 0 || options.network,
|
|
237
|
+
volumeName
|
|
238
|
+
});
|
|
235
239
|
} catch (error) {
|
|
236
240
|
console.error(error.message);
|
|
237
241
|
process.exitCode = 1;
|
|
@@ -296,6 +300,10 @@ program
|
|
|
296
300
|
"run the invocation sandboxed in a disposable Docker container; bare = cached default image, " +
|
|
297
301
|
"'<image[:tag]>' = explicit image (installed into if missing), '@path'/URL = build from a Dockerfile"
|
|
298
302
|
)
|
|
303
|
+
.option(
|
|
304
|
+
"--network",
|
|
305
|
+
"enable network access in --docker mode, independent of --node-modules (default: --network none)"
|
|
306
|
+
)
|
|
299
307
|
.addHelpText("after", HELP_TEXT)
|
|
300
308
|
.version(version, "-v, --version", "print the installed node-red-cli version and exit")
|
|
301
309
|
.action(run);
|
package/package.json
CHANGED
package/src/docker-image.js
CHANGED
|
@@ -87,9 +87,16 @@ function derivedDockerfile(image, version) {
|
|
|
87
87
|
// The derived image is left running as root - restoring the original
|
|
88
88
|
// image's default user would require inspecting the base image at build
|
|
89
89
|
// time, which is out of scope here.
|
|
90
|
+
//
|
|
91
|
+
// Force the npm global prefix to /usr/local regardless of what the base
|
|
92
|
+
// image's own NPM_CONFIG_PREFIX/.npmrc sets: SANDBOX_ENTRY_PATH is
|
|
93
|
+
// hardcoded to /usr/local, so an install that lands elsewhere would
|
|
94
|
+
// silently succeed at build time but fail with MODULE_NOT_FOUND at
|
|
95
|
+
// `docker run`.
|
|
90
96
|
return [
|
|
91
97
|
`FROM ${image}`,
|
|
92
98
|
"USER root",
|
|
99
|
+
"ENV NPM_CONFIG_PREFIX=/usr/local",
|
|
93
100
|
`RUN npm install -g @tbrandenburg/node-red-cli@${version}`,
|
|
94
101
|
`ENTRYPOINT ["node", "${SANDBOX_ENTRY_PATH}"]`,
|
|
95
102
|
""
|
package/src/docker-run.js
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Sandboxing defaults (always applied):
|
|
10
10
|
* - `--rm -i` (always disposable, interactive stdin)
|
|
11
|
-
* - `--network none`, unless `networkNeeded` (i.e. `--node-modules`
|
|
11
|
+
* - `--network none`, unless `networkNeeded` (i.e. `--node-modules` and/or
|
|
12
|
+
* the CLI's `--network` flag is set)
|
|
12
13
|
* - `--read-only` root filesystem + a `/tmp` tmpfs mount
|
|
13
14
|
* - `-e HOME=/tmp`, so tools needing a writable `$HOME` (config/cache dirs)
|
|
14
15
|
* land on the writable `/tmp` tmpfs instead of the read-only rootfs
|