@withone/cli 1.21.0 → 1.23.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withone/cli",
3
- "version": "1.21.0",
3
+ "version": "1.23.0",
4
4
  "description": "CLI for managing One",
5
5
  "type": "module",
6
6
  "files": [
@@ -95,6 +95,21 @@ process.stdout.write(JSON.stringify(items.filter(i => i.active)));
95
95
 
96
96
  The module runs as a child `node` process: the flow context `$` is piped to stdin as JSON, and stdout is parsed as JSON and used as the step's output. Modules have full Node APIs available (unlike inline `code.source`, which is sandboxed). Use `code.module` for anything non-trivial; keep `code.source` for one-liners.
97
97
 
98
+ #### Inline `code.source` sandbox
99
+
100
+ Inline `code.source` runs inside an async function with a restricted `require`. Only the following Node built-ins are importable:
101
+
102
+ - `node:buffer`
103
+ - `node:crypto`
104
+ - `node:url`
105
+ - `node:path`
106
+
107
+ Everything else — `fs`, `http`, `https`, `net`, `child_process`, `process`, `os`, `cluster`, `dgram`, `tls`, `vm`, `worker_threads` — is **blocked** and will throw `Module "<name>" is blocked in code steps`. The runtime also does not expose `process`, `__dirname`, `__filename`, `setTimeout`, or `fetch`.
108
+
109
+ If you need any of those (filesystem reads, network calls, timers, etc.), use a `code.module` step instead — modules run as a real child `node` process and have the full Node API surface.
110
+
111
+ When an inline `code.source` step throws at runtime, the error message reports the user-relative line and column plus the offending line of source — e.g. `Code step "blowup" failed at line 3:34\n const c = $.steps.mk.output.data.score;\n Cannot read properties of null (reading 'score')`. No need to bisect the step manually.
112
+
98
113
  Whatever JSON a module writes to stdout becomes both `$.steps.<id>.output` and `$.steps.<id>.response` (aliases). Downstream steps can reference either.
99
114
 
100
115
  ### Migrating a legacy single-file flow
@@ -130,6 +145,8 @@ Two rules: (1) prepend the stdin-read line, (2) replace `return X` with `process
130
145
  one --agent flow validate <key>
131
146
  ```
132
147
 
148
+ `flow validate` parses every inline `code.source` and runs `node --check` on every `code.module` file, so syntax errors (brace/paren mismatches, duplicate `let`, etc.) surface here instead of after upstream steps have already run. It also extracts `$.steps.X` and `$.input.X` references from inside `code.source` and `transform.expression` and reports any reference to an undefined step/input or to a step declared **after** the current one (forward references resolve to `undefined` at runtime — silent data loss). The same checks run automatically at the start of `flow execute` so a broken step in position 15 fails the run immediately rather than 15 minutes in.
149
+
133
150
  ### Step 6: Execute
134
151
 
135
152
  ```bash
@@ -187,6 +204,16 @@ Connection inputs with a `connection` field auto-resolve if the user has exactly
187
204
 
188
205
  A pure `$.xxx` value resolves to the raw type. A string containing `{{$.xxx}}` does string interpolation.
189
206
 
207
+ **Passing objects and arrays:** `{{ }}` interpolation always produces a string — if the resolved value is an object or array it will be JSON-stringified and the engine will log a warning. To pass an object/array as a native value to the next step, use a **direct selector without `{{ }}`**:
208
+
209
+ ```json
210
+ // ✗ Wrong — becomes a JSON string, triggers a runtime warning
211
+ "files": "{{$.steps.extract.output.allFiles}}"
212
+
213
+ // ✓ Right — passes the array as an array
214
+ "files": "$.steps.extract.output.allFiles"
215
+ ```
216
+
190
217
  ### Selectors vs expressions
191
218
 
192
219
  Selectors in data fields (`data`, `queryParams`, `pathVars`, `connectionKey`) are **dot-path lookups only** — they do not support JavaScript operators like `||` or `&&`. For default values, use the `default` field on the input definition:
@@ -343,6 +370,14 @@ After a parallel step, access each substep's output by its `id`: `$.steps.fetchE
343
370
  }
344
371
  ```
345
372
 
373
+ A sub-flow step exposes the sub-flow's **step results map** at both `.output` and `.response` (they are aliases — pick whichever reads better). Access a specific sub-step's data with:
374
+
375
+ ```
376
+ $.steps.<parent>.output.<subStepId>.output.<field>
377
+ ```
378
+
379
+ e.g. if sub-flow `enrich-customer` has a step `load` that returns `{ TEAM: "acme" }`, the caller reads it as `$.steps.enrich.output.load.output.TEAM`. There is no longer any `.response.<subStepId>` vs `.output.<subStepId>` ambiguity.
380
+
346
381
  ### `paginate` — Auto-collect paginated results
347
382
 
348
383
  ```json
@@ -369,6 +404,26 @@ After a parallel step, access each substep's output by its `id`: `$.steps.fetchE
369
404
  }
370
405
  ```
371
406
 
407
+ **Safe interpolation.** Plain `{{$.input.x}}` does string substitution and is **unsafe** for bash — values containing quotes, `$`, backticks, `&`, etc. will break the command (or worse). Use the `q` helper to POSIX-shell-quote the value:
408
+
409
+ ```json
410
+ { "command": "echo {{q $.input.companyName}} | tr '[:upper:]' '[:lower:]'" }
411
+ ```
412
+
413
+ `{{q $.input.companyName}}` resolves `O'Reilly Media & Co` to `'O'\''Reilly Media & Co'` — a single argv token bash will parse cleanly. Use `{{q ...}}` for **every** interpolation of user-controlled data into a bash command.
414
+
415
+ Alternatively, pass values as environment variables (also shell-safe) and reference them with `$VAR`:
416
+
417
+ ```json
418
+ {
419
+ "type": "bash",
420
+ "bash": {
421
+ "env": { "COMPANY": "$.input.companyName" },
422
+ "command": "echo \"$COMPANY\" | tr '[:upper:]' '[:lower:]'"
423
+ }
424
+ }
425
+ ```
426
+
372
427
  ## Error Handling
373
428
 
374
429
  ```json
@@ -377,6 +432,30 @@ After a parallel step, access each substep's output by its `id`: `$.steps.fetchE
377
432
 
378
433
  Strategies: `fail` (default), `continue`, `retry`, `fallback`.
379
434
 
435
+ **Retry backoff.** By default each retry waits exactly `retryDelayMs`. For rate-limited APIs add `"backoff": "exponential"` (or `"exponential-jitter"`) and an optional `"maxDelayMs"` cap (defaults to 30000):
436
+
437
+ ```json
438
+ {
439
+ "onError": {
440
+ "strategy": "retry",
441
+ "retries": 4,
442
+ "retryDelayMs": 1000,
443
+ "backoff": "exponential-jitter",
444
+ "maxDelayMs": 10000
445
+ }
446
+ }
447
+ ```
448
+
449
+ `exponential` waits `retryDelayMs * 2^(retryIndex)` (1s, 2s, 4s, 8s…) capped at `maxDelayMs`. `exponential-jitter` multiplies each wait by a random factor in [0.5, 1.0) so concurrent retries spread out.
450
+
451
+ **Inspecting retry outcomes.** Every retried step exposes how it ended on its `StepResult`:
452
+
453
+ - `$.steps.<id>.status` — `"success"` or `"failed"`
454
+ - `$.steps.<id>.retries` — number of retries actually performed (0 if first attempt succeeded)
455
+ - `$.steps.<id>.error` — last error message (only set when `status === "failed"` under `continue`/`fallback` strategies)
456
+
457
+ A successful-after-retry step also emits a `step:retry-success` event with the retry count, so you can distinguish a clean first-attempt success from a recovered one in logs.
458
+
380
459
  Conditional execution: `"if": "$.steps.find.response.data.length > 0"`
381
460
 
382
461
  ## AI-Augmented Patterns