@controllerai/cli 0.2.18 → 0.2.21
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 +6 -4
- package/skills/controllerai-agents/SKILL.md +66 -152
- package/skills/controllerai-agents/references/instructions-and-versioning.md +32 -34
- package/skills/controllerai-ai-nodes/SKILL.md +57 -65
- package/skills/controllerai-ai-nodes/references/selection-and-testing.md +44 -26
- package/skills/controllerai-data/SKILL.md +65 -118
- package/skills/controllerai-data/references/data-rails.md +24 -26
- package/skills/controllerai-expressions/SKILL.md +48 -117
- package/skills/controllerai-expressions/references/dsl-and-recipes.md +42 -40
- package/skills/controllerai-flow-control/SKILL.md +66 -122
- package/skills/controllerai-flow-control/references/patterns.md +23 -60
- package/skills/controllerai-integration-actions/SKILL.md +47 -75
- package/skills/controllerai-node-config/SKILL.md +60 -130
- package/skills/controllerai-node-config/references/prop-and-readiness.md +28 -78
- package/skills/controllerai-runs/SKILL.md +50 -85
- package/skills/controllerai-runs/references/debugging.md +24 -22
- package/skills/controllerai-start/SKILL.md +53 -58
- package/skills/controllerai-start/references/mcp-tool-map.md +21 -16
- package/skills/controllerai-triggers/SKILL.md +62 -159
- package/skills/controllerai-triggers/references/lifecycle.md +36 -48
- package/skills/controllerai-workflows/SKILL.md +70 -70
- package/skills/controllerai-workflows/references/command-map.md +19 -44
- package/skills/manifest.json +80 -77
- package/skills-mcp/controllerai-agents/SKILL.md +121 -0
- package/skills-mcp/controllerai-agents/references/instructions-and-versioning.md +76 -0
- package/skills-mcp/controllerai-ai-nodes/SKILL.md +92 -0
- package/skills-mcp/controllerai-ai-nodes/references/selection-and-testing.md +66 -0
- package/skills-mcp/controllerai-data/SKILL.md +119 -0
- package/skills-mcp/controllerai-data/references/data-rails.md +45 -0
- package/skills-mcp/controllerai-expressions/SKILL.md +98 -0
- package/skills-mcp/controllerai-expressions/references/dsl-and-recipes.md +136 -0
- package/skills-mcp/controllerai-flow-control/SKILL.md +114 -0
- package/skills-mcp/controllerai-flow-control/references/patterns.md +83 -0
- package/skills-mcp/controllerai-integration-actions/SKILL.md +89 -0
- package/skills-mcp/controllerai-node-config/SKILL.md +106 -0
- package/skills-mcp/controllerai-node-config/references/prop-and-readiness.md +49 -0
- package/skills-mcp/controllerai-runs/SKILL.md +88 -0
- package/skills-mcp/controllerai-runs/references/debugging.md +53 -0
- package/skills-mcp/controllerai-start/SKILL.md +80 -0
- package/skills-mcp/controllerai-start/references/mcp-tool-map.md +175 -0
- package/skills-mcp/controllerai-triggers/SKILL.md +112 -0
- package/skills-mcp/controllerai-triggers/references/lifecycle.md +59 -0
- package/skills-mcp/controllerai-workflows/SKILL.md +109 -0
- package/skills-mcp/controllerai-workflows/references/command-map.md +1 -0
- package/skills-mcp/manifest.json +189 -0
|
@@ -1,173 +1,104 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: controllerai-expressions
|
|
3
|
-
description: Author, validate, persist, and debug Controller AI expressions for node props and conditions. Use when a value comes from a flow input, upstream node, loop item, workflow data, constant, calculation, template
|
|
3
|
+
description: Author, validate, persist, and debug Controller AI expressions for node props and conditions. Use when a value comes from a flow input, an upstream node, a loop item, workflow data, a constant, a calculation, a template or a boolean rule, and when an expression compiles but resolves to null or the wrong input.
|
|
4
4
|
---
|
|
5
|
+
<!-- generated from .claude/skills by cli/scripts/render-skills.mjs; edit the source -->
|
|
5
6
|
|
|
6
|
-
#
|
|
7
|
+
# Expressions
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
## Use this when
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
- A node prop or condition needs a dynamic value: a flow input, a wired upstream output, the current loop item, workflow records, a date calculation, a text template, a boolean guard.
|
|
12
|
+
- An expression compiles but the run shows null, an empty value, or the wrong source.
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
Node discovery, connections and static props belong to `controllerai-node-config`; Router, If/Else and loop structure to `controllerai-flow-control`; execution evidence to `controllerai-runs`.
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
- `--value` is static; dynamic values use `--js`. See Typed values and templates.
|
|
16
|
-
- Copy current bindings, not labels or stale names. See Read the destination's exact scope.
|
|
17
|
-
- Never use raw ASTs, general JavaScript, or private APIs. See Use only advertised syntax.
|
|
18
|
-
- Keep ternaries in the destination type. See Typed values and templates.
|
|
19
|
-
- Prove resolution in a containing-flow run. See Verify runtime resolution.
|
|
16
|
+
## How it works
|
|
20
17
|
|
|
21
|
-
|
|
18
|
+
Expressions are written in a restricted JavaScript-like language and compiled into the engine's expression tree. It is not JavaScript: no loops, assignments, `new`, regular expressions, optional chaining, object literals (the `{ ignoreEmptyConstraints: true }` filter options object is the one accepted object) or library methods beyond what the compiler catalog advertises. A program is optional `const` declarations followed by one final expression.
|
|
22
19
|
|
|
23
|
-
|
|
20
|
+
**Everything is typed.** An expression can only be written against a known type. An integration node's output type is usually learned from a representative development run, or from a supported output selector or a declared provider sample, and `node status` says which route applies; `custom.*` types describe data-store records and AI structured outputs; `dataRecord.custom.x` is a stored record of that shape with its identity (`id`, `createdAt`, `updatedAt`), while bare `custom.x` is the shape alone; `list.` prefixes a list of any type. When an expression cannot be authored, the cause is usually a missing upstream type.
|
|
24
21
|
|
|
25
|
-
|
|
22
|
+
**Scope** comes from the destination's expression context: `flow.<name>` is a flow input, `inputs.<name>` a wired node input (usually already unwrapped), `item` the current loop item where the context exposes it, `workflowData("custom.type")` the records of an active collection on the current rail, and `now` and `isLiveVersion` constants. Bindings are display-derived and de-duplicated by the context; display labels and old spellings are not bindings.
|
|
26
23
|
|
|
27
|
-
An
|
|
24
|
+
**Empty values.** An omitted input arrives in its typed empty form: `""` for text, null for objects, and it is compared like any other value: `=== ""` matches only rows whose field is empty, `!==` passes every other row including rows with a missing value, `includes("")` matches every text row, and a negative membership check against a missing list passes everything. An omitted optional input therefore does not close a filter by itself: equality usually returns nothing, a negation or containment returns everything, and an omitted lookup key can turn a create-if-missing step into a duplicate. With `ignoreEmptyConstraints` on, a constraint whose value is null, empty text or an empty list is skipped; `0`, `false`, whitespace and dates stay real constraints. `.every()` takes the same predicates as `.filter()` and compiles to a single-pass "all match"; a constraint on a field the item does not have compiles, makes `.every()` false and is dropped by `.filter()`, which then matches every item. Predicates compare a field of the item, so a list of numbers or text cannot be filtered by its values: count or classify primitives with a per-item loop that returns a flag, then sum the flags. Comparators decide what missing, null or unparseable values do, and negative comparisons can pass rows with missing values, so a guard states presence explicitly.
|
|
28
25
|
|
|
29
|
-
-
|
|
30
|
-
- **An omitted optional TEXT input arrives as `""`.** Equality then matches nothing and can make a create-if-missing step duplicate data.
|
|
26
|
+
**Dates** travel as ISO-8601 text. A `date`-typed input or field takes date operations directly; text needs `.toDate()` first, and epoch text needs an explicit unit, `.toDate("epoch_seconds")` or `.toDate("epoch_millis")`. Format with Moment tokens such as `format("YYYY-MM-DD")`.
|
|
31
27
|
|
|
32
|
-
|
|
28
|
+
**Templates and ternaries.** A backtick template with `${...}` returns text; keep non-text values as direct typed expressions. A ternary keeps its type when both branches are numbers or both are booleans; any other pair renders as text, so give both branches the same type when a number or boolean is needed, and never quote or coerce a branch to hide a type mismatch.
|
|
33
29
|
|
|
34
|
-
|
|
30
|
+
**Channels.** A literal value and an expression are stored on different channels; the setters pick the channel from the prop's metadata. On a value-channel text array, a scalar expression replaces the whole array with one dynamic element and a list expression is rejected; a static list is written as a literal array, and a list expression only where the prop exposes whole-array expression mode.
|
|
35
31
|
|
|
36
|
-
|
|
32
|
+
## Rules
|
|
37
33
|
|
|
38
|
-
An upstream output
|
|
34
|
+
1. **Wire first, then read the context, then write.** An upstream output listed under `unwiredUpstreamOutputs` is discovery, not scope. Re-read the context after rewiring, renaming an input, changing a schema, changing a dynamic selector, or resolving an output type.
|
|
35
|
+
2. **Copy bindings exactly.** Never derive one from a label, never add `.data` on your own, and when a diagnostic advertises a typeable accessor, use it. For duplicate names, use the de-duplicated binding, input ID and source node the context gives.
|
|
36
|
+
3. **Use only advertised operations.** Read the catalog for the type; never author a raw expression tree or probe a private API. When an idiom is rejected, use the advertised alternative, or express the logic through routing, typed inputs or a native action.
|
|
37
|
+
4. **A literal is a literal.** `flow.name`, `{{name}}` and `${name}` inside a literal stay text. Dynamic values go through the expression setter.
|
|
38
|
+
5. **Preserve `0` and `false`.** Fallbacks are same-type literals (`flow.count ?? 0`, `flow.enabled ?? false`) and never swallow a real zero or false.
|
|
39
|
+
6. **Optional filters are a decision, not a default.** An omitted value is a real comparison, not a skipped constraint; turn on `ignoreEmptyConstraints` only when omitting that constraint is what the user wants, and test both the supplied and the omitted case.
|
|
40
|
+
7. **Compiler success proves syntax and types, not the source.** When names are ambiguous, check the source node and input IDs, then look at runtime values.
|
|
41
|
+
8. **An expression that reads `inputs.<name>` is verified only by running the containing flow.** A node run never executes upstream nodes.
|
|
42
|
+
9. **Empty results need a reason.** An intentional empty (a closed filter, an absent optional) is valid; an unexpected null or partial value in `resolvedConfigs` is a failure to trace, not something to fix by rewriting valid syntax.
|
|
39
43
|
|
|
40
|
-
|
|
41
|
-
cai node connect --flow <flowId> --from <sourceNodeId> --to <targetNodeId> --json
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
Use `cai branch connect` for Router or If/Else outputs.
|
|
44
|
+
## Recipes
|
|
45
45
|
|
|
46
|
-
###
|
|
46
|
+
### Discover the scope and the operations
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
49
|
cai expr context <nodeId> --prop <propName> --json
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
Copy binding and field names exactly. Important sources include:
|
|
53
|
-
|
|
54
|
-
- `flow.<name>`: flow input
|
|
55
|
-
- `inputs.<name>`: wired node input; the payload is usually already unwrapped
|
|
56
|
-
- `item`: injected current item, only where context exposes it
|
|
57
|
-
- `workflowData("custom.type")`: records from an active workflow collection
|
|
58
|
-
- `now`, `isLiveVersion`: constants shown by context
|
|
59
|
-
|
|
60
|
-
Do not derive a binding from a display label. Context flags duplicate display names and supplies the de-duplicated JS name, input ID, and source node.
|
|
61
|
-
|
|
62
|
-
When compilation reports an unknown field, copy a typeable display-derived accessor it advertises; field IDs appear second for disambiguation.
|
|
63
|
-
|
|
64
|
-
### 3. Use only advertised syntax
|
|
65
|
-
|
|
66
|
-
Get the current operation catalog when needed:
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
50
|
cai expr ops --workflow <workflowId> --type <text|number|date|list|boolean|file|custom> --json
|
|
70
51
|
```
|
|
71
52
|
|
|
72
|
-
The catalog is workflow
|
|
53
|
+
The catalog is read from the workflow's expression context, so the workflow needs at least one node, and an existing pin (`cai use` or `CAI_WORKFLOW`) can replace `--workflow`.
|
|
73
54
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
Date values are ISO-8601 strings on the wire (`"2026-06-05"` or `"2026-06-05T12:30:00Z"`). A `date`-typed flow input or field is used directly with date operations; only `text` values need `.toDate()` first. For epoch text, pass the unit literal explicitly: `flow.timestamp.toDate("epoch_seconds")` or `flow.timestamp.toDate("epoch_millis")`. The ops table renders that argument generically; never guess the unit from digit count.
|
|
77
|
-
|
|
78
|
-
#### Typed values and templates
|
|
79
|
-
|
|
80
|
-
`--value` stores a literal, so `flow.name`, `{inputs.Name}`, `{{name}}`, and `${name}` stay static. `--js` and `--js-file` compile typed expressions. `${...}` interpolates only inside a backtick template expression, which returns text. Keep non-text values as direct typed expressions.
|
|
81
|
-
|
|
82
|
-
Ternaries preserve compatible scalar branch types: number and boolean branches stay number and boolean. A different inferred destination type means the write is incomplete. Report the compiler error; do not quote or coerce branches to text.
|
|
83
|
-
|
|
84
|
-
Read [references/dsl-and-recipes.md](references/dsl-and-recipes.md) for canonical patterns.
|
|
85
|
-
|
|
86
|
-
### 4. Compile, write, and read back
|
|
87
|
-
|
|
88
|
-
For a node prop, prefer:
|
|
55
|
+
If the source is not wired yet:
|
|
89
56
|
|
|
90
57
|
```bash
|
|
91
|
-
cai
|
|
58
|
+
cai node connect --flow <flowId> --from <sourceNodeId> --to <targetNodeId> --json
|
|
92
59
|
```
|
|
93
60
|
|
|
94
|
-
|
|
61
|
+
Router and If/Else outputs connect through `cai branch connect`.
|
|
95
62
|
|
|
96
|
-
|
|
63
|
+
### Validate, write, read back
|
|
97
64
|
|
|
98
65
|
```bash
|
|
99
|
-
cai expr
|
|
66
|
+
cai expr validate <nodeId> --prop <propName> --js '<expression>' --json
|
|
67
|
+
cai expr set <nodeId> --prop <propName> --js '<expression>' --json
|
|
68
|
+
cai expr decompile <nodeId> --prop <propName> --json
|
|
100
69
|
```
|
|
101
70
|
|
|
102
|
-
|
|
71
|
+
The setter fetches the prop's metadata, enables a catalog-only optional prop when needed, compiles, writes the right channel and verifies the readback. If the write fails after enabling, the prop stays enabled: disable it with `cai node disable-prop <nodeId> --prop <propName>` and re-read. Decompile returns the stored source and channel, not a type: validate the decompiled source against the current destination to confirm the type still holds.
|
|
103
72
|
|
|
104
|
-
|
|
73
|
+
For a multiline or shell-sensitive expression, keep the exact UTF-8 source in a file and pass `--js-file <path>` (or `--js-file -` for stdin) instead of `--js`; `expr validate`, `expr set`, `node set`, `node only-when`, `branch add` and `branch update` all accept it.
|
|
105
74
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
Array channels are not interchangeable. On a value-channel `SHORT_TEXT_ARRAY`, a scalar expression replaces the whole array with one dynamic element; it does not append. List-typed expressions on value-channel array props are rejected. Use `cai node set <nodeId> --prop <propName> --value '["a","b"]'` for a static list, or use a list-typed expression only where prop metadata exposes whole-array expression mode.
|
|
109
|
-
|
|
110
|
-
For a side-effect-free check:
|
|
111
|
-
|
|
112
|
-
```bash
|
|
113
|
-
cai expr validate <nodeId> --prop <propName> --js '<expression>' --json
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
For conditions:
|
|
75
|
+
### Conditions
|
|
117
76
|
|
|
118
77
|
```bash
|
|
119
78
|
cai expr validate <nodeId> --mode onlyWhen --js '<boolean expression>' --json
|
|
120
79
|
cai node only-when <nodeId> --js '<boolean expression>' --json
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
Node `only-when` takes one restricted boolean through `--js` or `--js-file`, or grouped conditions through `--groups-json '[["a","b"],["c"]]'` (inner arrays are AND, outer rows are OR). Root scalar `&&` or `||` inside one expression is unsupported; use `--groups-json` for compound guards. Branch add/update accepts `string[][]`: outer rows are OR, inner entries are AND, and each entry compiles separately.
|
|
124
|
-
|
|
125
|
-
```bash
|
|
80
|
+
cai node only-when <nodeId> --groups-json '[["flow.email.isNotEmpty()","flow.units < 5"],["flow.override === true"]]' --json
|
|
126
81
|
cai branch update <nodeId> --route <routeId> --condition-json '[["flow.email.isNotEmpty()","flow.units < 5"],["flow.override === true"]]' --json
|
|
127
82
|
```
|
|
128
83
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
Router and If/Else conditions are written with `cai branch add` or `cai branch update` after the same context/validation discipline.
|
|
84
|
+
A node guard takes one boolean expression, or groups where each inner list is AND and the outer list is OR; a root `&&` or `||` inside one expression is not supported, so compound guards use groups. Branch conditions use the same grouped shape, each string compiled separately. Make a missing number fail a comparison with a same-type boundary: `(flow.unitsLeft ?? 5) < 5`.
|
|
132
85
|
|
|
133
|
-
###
|
|
86
|
+
### Prove the value at runtime
|
|
134
87
|
|
|
135
88
|
```bash
|
|
136
|
-
cai expr decompile <nodeId> --prop <propName> --json
|
|
137
89
|
cai run node <nodeId> --inputs <values.json> --json
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
MCP: `run_node` takes the parsed `inputs` object and `waitSeconds` (default 60, max 120).
|
|
141
|
-
|
|
142
|
-
Use `run node` to isolate the destination's own configuration. It never executes wired upstream nodes. If the expression consumes `inputs.<name>`, also run its containing flow:
|
|
143
|
-
|
|
144
|
-
```bash
|
|
145
90
|
cai run flow <flowId> --inputs <values.json> --json
|
|
91
|
+
cai exec node <nodeExecutionId> --full --json
|
|
146
92
|
```
|
|
147
93
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
Inspect `runScope` and `resolvedConfigs` in the run result. A null or missing dynamic config is an expression failure, not success; a partially empty value is equally suspect. Only the full flow run proves an upstream-derived value. Use `cai exec node <nodeExecutionId> --full --json` to inspect the final status, resolved configs, and untruncated output.
|
|
94
|
+
The node run isolates the destination's own configuration. The flow run proves an upstream-derived value. Inspect `runScope` and `resolvedConfigs`; when a search unexpectedly returns nothing, look at the resolved comparison values first.
|
|
151
95
|
|
|
152
|
-
##
|
|
96
|
+
## Verify
|
|
153
97
|
|
|
154
|
-
|
|
155
|
-
- Never author against an unwired output.
|
|
156
|
-
- Never add `.data` automatically. Use the exact path shown by context.
|
|
157
|
-
- Re-read context after rewiring, renaming inputs, changing schemas, changing dynamic selectors, or resolving an output type.
|
|
158
|
-
- Preserve `0` and `false`; they are values, not missing data.
|
|
159
|
-
- Optional filters need an explicit behavior decision. Do not silently change fail-closed filtering to match-all.
|
|
160
|
-
- A compiler success proves syntax and types, not correct source selection. Verify the source node/input IDs when names are ambiguous and inspect runtime values.
|
|
98
|
+
The current context shows the intended source IDs and bindings; the decompiled source validates against the current destination; the run's `resolvedConfigs` hold the intended value, with intentional empties explained; the edge cases the task implies were exercised: omitted input, `0`, `false`, empty text or list, missing record.
|
|
161
99
|
|
|
162
|
-
##
|
|
100
|
+
## Report
|
|
163
101
|
|
|
164
|
-
|
|
165
|
-
cai expr context <nodeId> --prop <propName> --json
|
|
166
|
-
cai expr validate <nodeId> --prop <propName> --js '<expression>' --json
|
|
167
|
-
cai expr decompile <nodeId> --prop <propName> --json
|
|
168
|
-
cai run flow <flowId> --inputs <values.json> --json
|
|
169
|
-
```
|
|
102
|
+
Which values are dynamic and what they read from, any optional-filter or fallback decision you made, and what the run showed.
|
|
170
103
|
|
|
171
|
-
-
|
|
172
|
-
- Validation and decompile must agree on source and type.
|
|
173
|
-
- The flow run must show the intended value in `runScope` and `resolvedConfigs`.
|
|
104
|
+
Read [references/dsl-and-recipes.md](references/dsl-and-recipes.md) for the canonical patterns: templates, prefix tests, HTTP JSON bodies, fallbacks, dates, workflow records, optional filters, loop items and outputs, and the troubleshooting order.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
<!-- generated from .claude/skills by cli/scripts/render-skills.mjs; edit the source -->
|
|
1
2
|
# Expression DSL and recipes
|
|
2
3
|
|
|
3
|
-
The live compiler
|
|
4
|
+
The live compiler, `cai expr context` and `cai expr ops` are authoritative. These examples show structure; take every binding, field and operation name from the context.
|
|
4
5
|
|
|
5
6
|
## Text template
|
|
6
7
|
|
|
@@ -8,36 +9,25 @@ The live compiler and `cai expr context`/`cai expr ops` are authoritative. These
|
|
|
8
9
|
`Hello ${flow.firstName.trim()}, order ${inputs.order.id} is ready`
|
|
9
10
|
```
|
|
10
11
|
|
|
11
|
-
Use the exact bindings and fields returned for the destination node.
|
|
12
|
-
|
|
13
12
|
## Text prefix
|
|
14
13
|
|
|
15
|
-
When `.startsWith()` is
|
|
14
|
+
When `.startsWith()` is not advertised, test a prefix with a guarded split:
|
|
16
15
|
|
|
17
16
|
```js
|
|
18
17
|
flow.subject.isNotEmpty() ? flow.subject.split("urgent")[0] === "" : false
|
|
19
18
|
```
|
|
20
19
|
|
|
21
|
-
The
|
|
20
|
+
The guard keeps empty text from matching. Prefix matching is not first-word matching: do not split on whitespace unless first-word equality is the requirement, and test a value with punctuation right after the prefix (`urgent-alpha`) plus a near miss.
|
|
22
21
|
|
|
23
22
|
## HTTP JSON body
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
A JSON body prop is JSON text. The language has no constructed object literals and no `JSON.stringify`, so build the document as a template of complete JSON tokens:
|
|
26
25
|
|
|
27
26
|
```js
|
|
28
27
|
`{"message":${flow.name.jsonEncode()},"payload":${flow.user.stringify()}}`
|
|
29
28
|
```
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
- Use `.stringify()` for a custom object or list.
|
|
33
|
-
- Keep JSON punctuation outside the interpolation and validate against the destination's exact prop before writing.
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
cai expr validate <httpNodeId> --prop <jsonBodyProp> --js-file body-expression.js --json
|
|
37
|
-
cai expr set <httpNodeId> --prop <jsonBodyProp> --js-file body-expression.js --json
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
MCP: pass the file contents inline as `js`; the set call uses `node_prop_set`.
|
|
30
|
+
`.jsonEncode()` returns a JSON-safe token: quoted for text, bare `true`/`false` for a boolean, so never add a second pair of quotes. `.stringify()` serializes a custom object or list. Keep the JSON punctuation outside the interpolations and validate against the exact body prop before writing.
|
|
41
31
|
|
|
42
32
|
## Same-type fallback
|
|
43
33
|
|
|
@@ -47,7 +37,7 @@ flow.label ?? "unknown"
|
|
|
47
37
|
flow.enabled ?? false
|
|
48
38
|
```
|
|
49
39
|
|
|
50
|
-
|
|
40
|
+
`0` and `false` are values; a fallback never replaces them.
|
|
51
41
|
|
|
52
42
|
## Boolean condition
|
|
53
43
|
|
|
@@ -56,7 +46,7 @@ flow.email.isNotEmpty()
|
|
|
56
46
|
inputs.score >= 80
|
|
57
47
|
```
|
|
58
48
|
|
|
59
|
-
Validate
|
|
49
|
+
Validate a condition with `--mode onlyWhen` and no prop name.
|
|
60
50
|
|
|
61
51
|
## Dates
|
|
62
52
|
|
|
@@ -65,23 +55,23 @@ inputs.createdAt.toDate() > now.plusDays(-7)
|
|
|
65
55
|
inputs.createdAt.toDate().format("YYYY-MM-DD")
|
|
66
56
|
```
|
|
67
57
|
|
|
68
|
-
Convert text to a date before date math. Use
|
|
58
|
+
Convert text to a date before date math; a negative amount moves backward. Use Moment format tokens, never JavaScript `Date` constructors or getters.
|
|
69
59
|
|
|
70
60
|
## Workflow records
|
|
71
61
|
|
|
72
|
-
Filter immediately after `workflowData` so
|
|
62
|
+
Filter immediately after `workflowData` so supported predicates are pushed into storage, then sort by an advertised field:
|
|
73
63
|
|
|
74
64
|
```js
|
|
75
65
|
workflowData("custom.lead")
|
|
76
66
|
.filter(row => row.status === "active")
|
|
77
|
-
.sortBy("
|
|
67
|
+
.sortBy("createdAt", "desc")
|
|
78
68
|
```
|
|
79
69
|
|
|
80
|
-
The collection must exist on the current
|
|
70
|
+
The collection must exist on the current rail; a schema declaration alone does not put rows in it. Record identity fields are `id`, `createdAt`, `updatedAt`.
|
|
81
71
|
|
|
82
72
|
## Optional filter inputs
|
|
83
73
|
|
|
84
|
-
|
|
74
|
+
An empty right-hand value is a real comparison: equality matches only empty fields, a negation or containment matches every row. Skip an omitted constraint only when the requirement allows it:
|
|
85
75
|
|
|
86
76
|
```js
|
|
87
77
|
workflowData("custom.lead").filter(
|
|
@@ -90,45 +80,57 @@ workflowData("custom.lead").filter(
|
|
|
90
80
|
)
|
|
91
81
|
```
|
|
92
82
|
|
|
93
|
-
|
|
83
|
+
With the option on, null, empty text and an empty list skip the constraint; `0`, `false`, whitespace and dates remain real constraints. Test the supplied and the omitted case.
|
|
84
|
+
|
|
85
|
+
## List membership
|
|
86
|
+
|
|
87
|
+
For a list field such as tags, membership is a single element: `row.tags.includes(flow.tag)`. `filter`, `some`, `find` and `every` share the same predicate grammar; `map` projects fields rather than building objects.
|
|
88
|
+
|
|
89
|
+
## Primitive lists
|
|
90
|
+
|
|
91
|
+
A predicate compares a field of the item, so `flow.weights.filter(w => w >= flow.threshold)` is rejected, and `w.value` on a number compiles as a missing field whose constraint is dropped, matching every item. Aggregate a primitive list with the advertised list operations (`.sum()`, `.count()`, `.max()`), and classify or count its items with a per-item loop whose body returns a flag:
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
inputs.runFlowOnList.filter(row => row.status === "completed").map(row => row.outputs).map(row => row.heavyFlag).sum()
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`.map()` picks one field per call, so nested picks are chained; the `status` filter keeps failed iterations out of the total.
|
|
94
98
|
|
|
95
99
|
## Loop item
|
|
96
100
|
|
|
97
|
-
Where context exposes `item`, primitive
|
|
101
|
+
Where the context exposes `item`, a primitive list uses it directly:
|
|
98
102
|
|
|
99
103
|
```js
|
|
100
104
|
item
|
|
101
105
|
```
|
|
102
106
|
|
|
103
|
-
|
|
107
|
+
An object list uses an advertised field:
|
|
104
108
|
|
|
105
109
|
```js
|
|
106
110
|
item.email
|
|
107
111
|
```
|
|
108
112
|
|
|
109
|
-
Inside the child flow
|
|
113
|
+
Inside the child flow the item arrives as a flow input and is `flow.<name>`, never `item`. A root routing node can read flow inputs directly; no producer node is needed to expose them.
|
|
110
114
|
|
|
111
115
|
## Loop output
|
|
112
116
|
|
|
113
|
-
Run-on-list output is a list of iteration records
|
|
117
|
+
Run-on-list output is a list of iteration records; child outputs sit under `outputs`:
|
|
114
118
|
|
|
115
119
|
```js
|
|
116
120
|
inputs.enrichEach[0].outputs.score
|
|
117
121
|
```
|
|
118
122
|
|
|
119
|
-
Confirm the
|
|
123
|
+
Confirm the binding and the path in the context.
|
|
120
124
|
|
|
121
|
-
## Troubleshooting
|
|
125
|
+
## Troubleshooting order
|
|
122
126
|
|
|
123
|
-
1. `cai expr context <
|
|
124
|
-
2. Confirm the intended source is wired and its `sourceNodeId` is
|
|
125
|
-
3. `cai expr validate
|
|
126
|
-
4. `cai expr set
|
|
127
|
-
5. `cai expr decompile
|
|
128
|
-
6. `cai run node
|
|
129
|
-
7. `cai run flow <flowId>
|
|
127
|
+
1. `cai expr context <nodeId> --prop <propName> --json`
|
|
128
|
+
2. Confirm the intended source is wired and its `sourceNodeId` is right.
|
|
129
|
+
3. `cai expr validate <nodeId> --prop <propName> --js '<expression>' --json`
|
|
130
|
+
4. `cai expr set <nodeId> --prop <propName> --js '<expression>' --json`
|
|
131
|
+
5. `cai expr decompile <nodeId> --prop <propName> --json`
|
|
132
|
+
6. `cai run node <nodeId> --inputs <values.json> --json` for the destination alone
|
|
133
|
+
7. `cai run flow <flowId> --inputs <values.json> --json` when the expression consumes a wired input
|
|
130
134
|
8. `cai exec node <nodeExecutionId> --full --json`
|
|
131
135
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
If compilation succeeds but runtime is null or partially empty, inspect `runScope`, the actual input value, and `resolvedConfigs`; do not keep rewriting syntax blindly. A node-only run cannot prove an `inputs.<name>` binding because it does not execute upstream nodes.
|
|
136
|
+
If compilation succeeds but the runtime value is empty or partial, inspect `runScope`, the actual input value and `resolvedConfigs` before touching the syntax again. A node-only run cannot prove an `inputs.<name>` binding because it does not execute upstream nodes.
|