@operator-labs/operator-cli 0.1.0

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.
@@ -0,0 +1,37 @@
1
+ # Safety Rules
2
+
3
+ `invoke()` prepends safety rules to every message by default. This prevents hook-spawned agent sessions from creating persistent autonomous behavior.
4
+
5
+ ## What it does
6
+
7
+ `invoke()` prepends a short block of text before your message:
8
+
9
+ ```
10
+ OPERATOR SAFETY RULES:
11
+ You MUST NOT write to the following paths:
12
+ - ~/.openclaw/skills
13
+ - ~/.openclaw/hooks
14
+ - openclaw.json
15
+ ...
16
+
17
+ ---
18
+
19
+ <your actual message>
20
+ ```
21
+
22
+ The agent still has full tool access. The rules tell it not to use write tools on specific paths.
23
+
24
+ ## What it prevents
25
+
26
+ An agent that writes a new skill to `~/.openclaw/skills/` containing its own `operator invoke` calls creates a self-replicating loop. The safety rules prevent this by instructing the agent to skip writes to the skills directory, hooks directory, and gateway config.
27
+
28
+ ## Limitations
29
+
30
+ This is a prompt-level boundary. The agent is told not to write to protected paths, not mechanically prevented. This is the same trust model OpenClaw uses for external content wrapping. It works for non-adversarial workflows processing structured data. It does not prevent a crafted prompt injection designed to override the rules.
31
+
32
+ ## Disabling
33
+
34
+ ```js
35
+ // Per invocation
36
+ await invoke("your message", { safety: false });
37
+ ```
@@ -0,0 +1,60 @@
1
+ # Manual Setup
2
+
3
+ If you prefer to configure manually instead of running `operator-cli setup`,
4
+ the following must be present in `openclaw.json` (typically at
5
+ `~/.openclaw/openclaw.json` or `$OPENCLAW_CONFIG_PATH`):
6
+
7
+ ## 1. Hooks enabled with a token and the operator mapping
8
+
9
+ - `hooks.enabled`: `true`
10
+ - `hooks.token`: any non-empty string (generate with `require("crypto").randomBytes(32).toString("hex")`)
11
+ - `hooks.mappings`: must include:
12
+
13
+ ```json
14
+ {
15
+ "id": "operator",
16
+ "match": { "path": "operator" },
17
+ "action": "agent",
18
+ "messageTemplate": "{{message}}"
19
+ }
20
+ ```
21
+
22
+ ## 2. llm-task plugin enabled (required for think())
23
+
24
+ - `plugins.entries.llm-task.enabled`: `true`
25
+
26
+ ## 3. llm-task in the tool allowlist
27
+
28
+ - `tools.allow` must include `"llm-task"`
29
+
30
+ ## 4. Gateway auth token (required for think(), not for invoke())
31
+
32
+ - `gateway.auth.token` in config, or `OPENCLAW_GATEWAY_TOKEN` env var
33
+ - Must differ from `hooks.token` -- OpenClaw rejects startup if they match
34
+
35
+ ## Full example
36
+
37
+ ```json
38
+ {
39
+ "hooks": {
40
+ "enabled": true,
41
+ "token": "<generated 64-char hex>",
42
+ "mappings": [
43
+ {
44
+ "id": "operator",
45
+ "match": { "path": "operator" },
46
+ "action": "agent",
47
+ "messageTemplate": "{{message}}"
48
+ }
49
+ ]
50
+ },
51
+ "plugins": {
52
+ "entries": {
53
+ "llm-task": { "enabled": true }
54
+ }
55
+ },
56
+ "tools": {
57
+ "allow": ["llm-task"]
58
+ }
59
+ }
60
+ ```
@@ -0,0 +1,150 @@
1
+ # Writing Workflow Scripts
2
+
3
+ Workflows are Node.js scripts that import `invoke` and `think` from
4
+ `@operator-labs/operator-cli`. After `operator-cli setup`, imports resolve
5
+ from any script in the agent's skill directory or from a project with the
6
+ package installed.
7
+
8
+ A workflow is a skill. It lives in the agent's skill directory alongside a
9
+ `SKILL.md` so the agent can discover and run it on future sessions.
10
+
11
+ ## invokeAndWait
12
+
13
+ `invoke()` returns immediately. Poll for the result:
14
+
15
+ ```js
16
+ async function invokeAndWait(message, done, { interval = 5000, maxWait = 300000 } = {}) {
17
+ await invoke(message);
18
+ const start = Date.now();
19
+ while (Date.now() - start < maxWait) {
20
+ if (await done()) return;
21
+ await new Promise(r => setTimeout(r, interval));
22
+ }
23
+ throw new Error("timed out waiting for task to complete");
24
+ }
25
+ ```
26
+
27
+ The `done()` check is yours -- file, database row, API call, anything:
28
+
29
+ ```js
30
+ const fileReady = (path) => () => existsSync(path) && readFileSync(path, "utf-8").trim();
31
+ const apiReady = (url) => async () => (await fetch(url)).ok;
32
+ const dbRow = (query) => async () => (await db.query(query)).rows.length > 0;
33
+ ```
34
+
35
+ ## Full example
36
+
37
+ ```js
38
+ import { readFileSync, existsSync, mkdirSync } from "fs";
39
+ import { join } from "path";
40
+ import { invoke, think } from "@operator-labs/operator-cli";
41
+
42
+ const WORK_DIR = join(process.cwd(), "workflows", "my-workflow");
43
+ for (const dir of ["results", "state", "logs"]) {
44
+ mkdirSync(join(WORK_DIR, dir), { recursive: true });
45
+ }
46
+
47
+ // Classify with think (synchronous request/response)
48
+ const classification = await think(JSON.stringify({
49
+ prompt: "Classify this URL.",
50
+ input: "https://example.com",
51
+ schema: {
52
+ type: "object",
53
+ properties: { type: { type: "string", enum: ["blog", "product", "docs"] } },
54
+ required: ["type"],
55
+ },
56
+ }));
57
+
58
+ // Invoke and wait for output file
59
+ const analysisPath = join(WORK_DIR, "results/analysis.md");
60
+ await invoke(
61
+ "Analyze https://example.com (type: " + classification.output?.type + "). " +
62
+ "Write findings to " + analysisPath,
63
+ );
64
+ while (!existsSync(analysisPath) || !readFileSync(analysisPath, "utf-8").trim()) {
65
+ await new Promise(r => setTimeout(r, 5000));
66
+ }
67
+ ```
68
+
69
+ ## State management
70
+
71
+ Workflow data lives under `workflows/<name>/` relative to cwd. On OpenClaw
72
+ this resolves to `~/.openclaw/workspace/workflows/<name>/`.
73
+
74
+ Standard layout:
75
+
76
+ ```
77
+ workflows/<name>/
78
+ runs/<runId>.jsonl # one file per invoke/think call
79
+ state/ # checkpoints, progress, intermediate data
80
+ results/ # final output files
81
+ ```
82
+
83
+ ### Run tracking
84
+
85
+ Each `invoke()` or `think()` call gets its own JSONL file in `runs/`,
86
+ matching how OpenClaw stores sessions in `sessions/<sessionId>.jsonl`:
87
+
88
+ ```js
89
+ function logRun(runId, entry) {
90
+ mkdirSync(join(WORK_DIR, "runs"), { recursive: true });
91
+ appendFileSync(join(WORK_DIR, "runs", runId + ".jsonl"),
92
+ JSON.stringify({ ts: new Date().toISOString(), ...entry }) + "\n");
93
+ }
94
+
95
+ const run = await invoke(message);
96
+ logRun(run.runId, { type: "invoke", message: message.slice(0, 200), ok: run.ok });
97
+ // append more events to the same file as the run progresses
98
+ logRun(run.runId, { type: "poll", path: outputPath, found: true });
99
+ ```
100
+
101
+ The `runId` correlates with OpenClaw's session logs for debugging.
102
+
103
+ ### Checkpointing
104
+
105
+ For workflows that benefit from resume (batches, long pipelines), write
106
+ progress to `state/` after each step so the workflow can pick up where it
107
+ left off.
108
+
109
+ ## Where workflows live
110
+
111
+ A workflow is a skill. The agent creates it in its own skill directory:
112
+
113
+ - `~/.openclaw/workspace/skills/<workflow-name>/`
114
+
115
+ Each workflow follows the agentskills.io format:
116
+
117
+ ```
118
+ <workflow-name>/
119
+ ├── SKILL.md # frontmatter (name, description) + instructions
120
+ ├── scripts/ # executable workflow code
121
+ │ └── workflow.mjs
122
+ ├── references/ # docs loaded on demand (optional)
123
+ │ └── *.md
124
+ └── assets/ # templates, config files (optional)
125
+ ```
126
+
127
+ SKILL.md frontmatter requires `name` (lowercase, hyphens, max 64 chars)
128
+ and `description` (what it does + when to use it). The description is how the
129
+ agent decides to activate the skill, so include specific triggers. The body
130
+ contains usage instructions, loaded only after activation.
131
+
132
+ On future sessions the workflow appears in the skill catalog and can be
133
+ reused or modified.
134
+
135
+ Pre-built workflows from the monorepo can be installed with
136
+ `operator-cli install <name>`, which copies the skill into the skill directory.
137
+
138
+ ## Constraints
139
+
140
+ - Each `invoke` spawns a full agent session. Parallel invokes without a concurrency cap can exhaust resources.
141
+ - `invoke()` returns a `runId`. Log it -- it's the only link between your workflow log and the agent session.
142
+ - Each invoke and think call consumes tokens. Prefer `think` over `invoke` when the step only needs reasoning.
143
+
144
+ ## Examples
145
+
146
+ Before building a workflow, read the bundled examples:
147
+
148
+ - `examples/batch-analyze.mjs` — checkpointing, resume, progress tracking
149
+ - `examples/research-pipeline.mjs` — sequential steps with validation
150
+ - `examples/monitor-and-act.mjs` — conditional branching, CSV state