diff --git a/README.md b/README.md index 7b727ad..ceb67cd 100644 --- a/README.md +++ b/README.md @@ -193,3 +193,40 @@ steps: stdin: $categorize.stdout condition: $approve.approved ``` + +## Calling OpenClaw tools from workflows + +Workflow `steps[].command` runs in `/bin/sh`, so *tool calls must be real executables*. + +If you install Lobster via npm/pnpm, it installs a small shim executable named: + +- `openclaw.invoke` (preferred) +- `clawd.invoke` (alias) + +These shims forward to the Lobster pipeline command of the same name. + +### Example: invoke llm-task + +Prereqs: + +- `OPENCLAW_URL` points at a running OpenClaw gateway +- optionally `OPENCLAW_TOKEN` if auth is enabled + +```bash +export OPENCLAW_URL=http://127.0.0.1:18789 +# export OPENCLAW_TOKEN=... +``` + +In a workflow: + +```yaml +name: hello-world +steps: + - id: greeting + command: > + openclaw.invoke --tool llm-task --action json --args-json '{"prompt":"Hello"}' +``` + +### Passing data between steps (no temp files) + +Use `stdin: $stepId.stdout` to pipe output from one step into the next. diff --git a/bin/clawd.invoke.js b/bin/clawd.invoke.js new file mode 100755 index 0000000..6d8b22e --- /dev/null +++ b/bin/clawd.invoke.js @@ -0,0 +1,18 @@ +#!/usr/bin/env node + +import { spawnSync } from 'node:child_process'; + +function shellQuote(arg) { + if (/^[A-Za-z0-9_\-./:=@]+$/.test(arg)) return arg; + return `'${String(arg).replace(/'/g, `'\\''`)}'`; +} + +const argv = process.argv.slice(2); +const pipeline = ['clawd.invoke', ...argv.map(shellQuote)].join(' '); + +const res = spawnSync('lobster', [pipeline], { + stdio: 'inherit', + env: process.env, +}); + +process.exit(res.status ?? 1); diff --git a/bin/openclaw.invoke.js b/bin/openclaw.invoke.js new file mode 100755 index 0000000..bcd3d71 --- /dev/null +++ b/bin/openclaw.invoke.js @@ -0,0 +1,21 @@ +#!/usr/bin/env node + +import { spawnSync } from 'node:child_process'; + +function shellQuote(arg) { + // Conservative POSIX-ish quoting for embedding argv into a single pipeline string. + // Lobster's pipeline parser preserves quoted substrings. + if (/^[A-Za-z0-9_\-./:=@]+$/.test(arg)) return arg; + // single-quote, escaping embedded single quotes: ' -> '\'' + return `'${String(arg).replace(/'/g, `'\\''`)}'`; +} + +const argv = process.argv.slice(2); +const pipeline = ['openclaw.invoke', ...argv.map(shellQuote)].join(' '); + +const res = spawnSync('lobster', [pipeline], { + stdio: 'inherit', + env: process.env, +}); + +process.exit(res.status ?? 1); diff --git a/package.json b/package.json index 42bb57b..228c86d 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,9 @@ "description": "Workflow runtime for AI agents - deterministic pipelines with approval gates", "type": "module", "bin": { - "lobster": "bin/lobster.js" + "lobster": "bin/lobster.js", + "openclaw.invoke": "bin/openclaw.invoke.js", + "clawd.invoke": "bin/clawd.invoke.js" }, "files": [ "bin",