@withone/cli 1.20.2 → 1.21.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/README.md +3 -3
- package/dist/{chunk-DPOG6BQ5.js → chunk-KZOFPEHD.js} +806 -19
- package/dist/flow-runner-CXZ6AWXT.js +28 -0
- package/dist/index.js +90 -549
- package/package.json +1 -1
- package/skills/one/references/flows.md +101 -2
- package/dist/flow-runner-UWZL2FPJ.js +0 -14
package/dist/index.js
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
FLOW_SCHEMA,
|
|
3
4
|
FlowRunner,
|
|
4
5
|
OneApi,
|
|
5
6
|
TimeoutError,
|
|
6
7
|
buildActionKnowledgeWithGuidance,
|
|
7
8
|
filterByPermissions,
|
|
9
|
+
flowRequiresBash,
|
|
10
|
+
generateFlowGuide,
|
|
11
|
+
getNestedStepsKeys,
|
|
12
|
+
getStepTypeDescriptor,
|
|
8
13
|
isActionAllowed,
|
|
9
14
|
isMethodAllowed,
|
|
10
15
|
listFlows,
|
|
11
|
-
|
|
16
|
+
loadFlowWithMeta,
|
|
12
17
|
resolveFlowPath,
|
|
13
18
|
saveFlow
|
|
14
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-KZOFPEHD.js";
|
|
15
20
|
|
|
16
21
|
// src/index.ts
|
|
17
22
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -2028,541 +2033,6 @@ function colorMethod(method) {
|
|
|
2028
2033
|
// src/commands/flow.ts
|
|
2029
2034
|
import pc7 from "picocolors";
|
|
2030
2035
|
|
|
2031
|
-
// src/lib/flow-schema.ts
|
|
2032
|
-
var FLOW_SCHEMA = {
|
|
2033
|
-
errorStrategies: ["fail", "continue", "retry", "fallback"],
|
|
2034
|
-
validInputTypes: ["string", "number", "boolean", "object", "array"],
|
|
2035
|
-
flowFields: {
|
|
2036
|
-
key: { type: "string", required: true, description: "Unique kebab-case identifier", pattern: /^[a-z0-9][a-z0-9-]*[a-z0-9]$/ },
|
|
2037
|
-
name: { type: "string", required: true, description: "Human-readable flow name" },
|
|
2038
|
-
description: { type: "string", required: false, description: "What this flow does" },
|
|
2039
|
-
version: { type: "string", required: false, description: "Semver or arbitrary version string" },
|
|
2040
|
-
inputs: { type: "object", required: true, description: "Input declarations (Record<string, InputDeclaration>)" },
|
|
2041
|
-
steps: { type: "array", required: true, description: "Ordered array of steps", stepsArray: true }
|
|
2042
|
-
},
|
|
2043
|
-
inputFields: {
|
|
2044
|
-
type: { type: "string", required: true, description: "Data type: string, number, boolean, object, array", enum: ["string", "number", "boolean", "object", "array"] },
|
|
2045
|
-
required: { type: "boolean", required: false, description: "Whether this input must be provided" },
|
|
2046
|
-
default: { type: "unknown", required: false, description: "Default value if not provided" },
|
|
2047
|
-
description: { type: "string", required: false, description: "Human-readable description" },
|
|
2048
|
-
connection: { type: "object", required: false, description: 'Connection metadata: { platform: "gmail" } \u2014 enables auto-resolution' }
|
|
2049
|
-
},
|
|
2050
|
-
stepCommonFields: {
|
|
2051
|
-
id: { type: "string", required: true, description: "Unique step identifier (used in selectors)" },
|
|
2052
|
-
name: { type: "string", required: true, description: "Human-readable step label" },
|
|
2053
|
-
type: { type: "string", required: true, description: "Step type (determines which config object is required)" },
|
|
2054
|
-
if: { type: "string", required: false, description: "JS expression \u2014 skip step if falsy" },
|
|
2055
|
-
unless: { type: "string", required: false, description: "JS expression \u2014 skip step if truthy" }
|
|
2056
|
-
},
|
|
2057
|
-
stepTypes: [
|
|
2058
|
-
{
|
|
2059
|
-
type: "action",
|
|
2060
|
-
configKey: "action",
|
|
2061
|
-
description: "Execute a platform API action",
|
|
2062
|
-
fields: {
|
|
2063
|
-
platform: { type: "string", required: true, description: "Platform name (kebab-case)" },
|
|
2064
|
-
actionId: { type: "string", required: true, description: "Action ID from `actions search`" },
|
|
2065
|
-
connectionKey: { type: "string", required: true, description: "Connection key (use $.input selector)" },
|
|
2066
|
-
data: { type: "object", required: false, description: "Request body (POST/PUT/PATCH)" },
|
|
2067
|
-
pathVars: { type: "object", required: false, description: "URL path variables" },
|
|
2068
|
-
queryParams: { type: "object", required: false, description: "Query parameters" },
|
|
2069
|
-
headers: { type: "object", required: false, description: "Additional headers" }
|
|
2070
|
-
},
|
|
2071
|
-
example: {
|
|
2072
|
-
id: "findCustomer",
|
|
2073
|
-
name: "Search Stripe customers",
|
|
2074
|
-
type: "action",
|
|
2075
|
-
action: {
|
|
2076
|
-
platform: "stripe",
|
|
2077
|
-
actionId: "conn_mod_def::xxx::yyy",
|
|
2078
|
-
connectionKey: "$.input.stripeConnectionKey",
|
|
2079
|
-
data: { query: "email:'{{$.input.customerEmail}}'" }
|
|
2080
|
-
}
|
|
2081
|
-
}
|
|
2082
|
-
},
|
|
2083
|
-
{
|
|
2084
|
-
type: "transform",
|
|
2085
|
-
configKey: "transform",
|
|
2086
|
-
description: "Single JS expression with implicit return",
|
|
2087
|
-
fields: {
|
|
2088
|
-
expression: { type: "string", required: true, description: "JS expression evaluated with flow context as $" }
|
|
2089
|
-
},
|
|
2090
|
-
example: {
|
|
2091
|
-
id: "extractNames",
|
|
2092
|
-
name: "Extract customer names",
|
|
2093
|
-
type: "transform",
|
|
2094
|
-
transform: { expression: "$.steps.findCustomer.response.data.map(c => c.name)" }
|
|
2095
|
-
}
|
|
2096
|
-
},
|
|
2097
|
-
{
|
|
2098
|
-
type: "code",
|
|
2099
|
-
configKey: "code",
|
|
2100
|
-
description: "Multi-line async JS with explicit return",
|
|
2101
|
-
fields: {
|
|
2102
|
-
source: { type: "string", required: true, description: "JS function body (flow context as $, supports await)" }
|
|
2103
|
-
},
|
|
2104
|
-
example: {
|
|
2105
|
-
id: "processData",
|
|
2106
|
-
name: "Process and enrich data",
|
|
2107
|
-
type: "code",
|
|
2108
|
-
code: { source: "const items = $.steps.fetch.response.data;\nreturn items.filter(i => i.active);" }
|
|
2109
|
-
}
|
|
2110
|
-
},
|
|
2111
|
-
{
|
|
2112
|
-
type: "condition",
|
|
2113
|
-
configKey: "condition",
|
|
2114
|
-
description: "If/then/else branching",
|
|
2115
|
-
fields: {
|
|
2116
|
-
expression: { type: "string", required: true, description: "JS expression \u2014 truthy runs then, falsy runs else" },
|
|
2117
|
-
then: { type: "array", required: true, description: "Steps to run when true", stepsArray: true },
|
|
2118
|
-
else: { type: "array", required: false, description: "Steps to run when false", stepsArray: true }
|
|
2119
|
-
},
|
|
2120
|
-
example: {
|
|
2121
|
-
id: "checkFound",
|
|
2122
|
-
name: "Check if customer exists",
|
|
2123
|
-
type: "condition",
|
|
2124
|
-
condition: {
|
|
2125
|
-
expression: "$.steps.search.response.data.length > 0",
|
|
2126
|
-
then: [{ id: "notify", name: "Send notification", type: "action", action: { platform: "slack", actionId: "...", connectionKey: "$.input.slackKey", data: { text: "Found!" } } }],
|
|
2127
|
-
else: [{ id: "logMiss", name: "Log not found", type: "transform", transform: { expression: "'Not found'" } }]
|
|
2128
|
-
}
|
|
2129
|
-
}
|
|
2130
|
-
},
|
|
2131
|
-
{
|
|
2132
|
-
type: "loop",
|
|
2133
|
-
configKey: "loop",
|
|
2134
|
-
description: "Iterate over an array with optional concurrency",
|
|
2135
|
-
fields: {
|
|
2136
|
-
over: { type: "string", required: true, description: "Selector resolving to an array" },
|
|
2137
|
-
as: { type: "string", required: true, description: "Variable name for current item ($.loop.<as>)" },
|
|
2138
|
-
indexAs: { type: "string", required: false, description: "Variable name for index" },
|
|
2139
|
-
steps: { type: "array", required: true, description: "Steps to run per iteration", stepsArray: true },
|
|
2140
|
-
maxIterations: { type: "number", required: false, description: "Safety cap (default: no limit)" },
|
|
2141
|
-
maxConcurrency: { type: "number", required: false, description: "Parallel batch size (default: 1 = sequential)" }
|
|
2142
|
-
},
|
|
2143
|
-
example: {
|
|
2144
|
-
id: "processOrders",
|
|
2145
|
-
name: "Process each order",
|
|
2146
|
-
type: "loop",
|
|
2147
|
-
loop: {
|
|
2148
|
-
over: "$.steps.listOrders.response.data",
|
|
2149
|
-
as: "order",
|
|
2150
|
-
steps: [{ id: "createInvoice", name: "Create invoice", type: "action", action: { platform: "stripe", actionId: "...", connectionKey: "$.input.stripeKey", data: { amount: "$.loop.order.total" } } }]
|
|
2151
|
-
}
|
|
2152
|
-
}
|
|
2153
|
-
},
|
|
2154
|
-
{
|
|
2155
|
-
type: "parallel",
|
|
2156
|
-
configKey: "parallel",
|
|
2157
|
-
description: "Run steps concurrently",
|
|
2158
|
-
fields: {
|
|
2159
|
-
steps: { type: "array", required: true, description: "Steps to run in parallel", stepsArray: true },
|
|
2160
|
-
maxConcurrency: { type: "number", required: false, description: "Max concurrent steps (default: 5)" }
|
|
2161
|
-
},
|
|
2162
|
-
example: {
|
|
2163
|
-
id: "lookups",
|
|
2164
|
-
name: "Parallel data lookups",
|
|
2165
|
-
type: "parallel",
|
|
2166
|
-
parallel: {
|
|
2167
|
-
steps: [
|
|
2168
|
-
{ id: "getStripe", name: "Get Stripe data", type: "action", action: { platform: "stripe", actionId: "...", connectionKey: "$.input.stripeKey" } },
|
|
2169
|
-
{ id: "getSlack", name: "Get Slack data", type: "action", action: { platform: "slack", actionId: "...", connectionKey: "$.input.slackKey" } }
|
|
2170
|
-
]
|
|
2171
|
-
}
|
|
2172
|
-
}
|
|
2173
|
-
},
|
|
2174
|
-
{
|
|
2175
|
-
type: "file-read",
|
|
2176
|
-
configKey: "fileRead",
|
|
2177
|
-
description: "Read a file (optional JSON parse)",
|
|
2178
|
-
fields: {
|
|
2179
|
-
path: { type: "string", required: true, description: "File path to read" },
|
|
2180
|
-
parseJson: { type: "boolean", required: false, description: "Parse contents as JSON (default: false)" }
|
|
2181
|
-
},
|
|
2182
|
-
example: {
|
|
2183
|
-
id: "readConfig",
|
|
2184
|
-
name: "Read config file",
|
|
2185
|
-
type: "file-read",
|
|
2186
|
-
fileRead: { path: "./data/config.json", parseJson: true }
|
|
2187
|
-
}
|
|
2188
|
-
},
|
|
2189
|
-
{
|
|
2190
|
-
type: "file-write",
|
|
2191
|
-
configKey: "fileWrite",
|
|
2192
|
-
description: "Write or append to a file",
|
|
2193
|
-
fields: {
|
|
2194
|
-
path: { type: "string", required: true, description: "File path to write" },
|
|
2195
|
-
content: { type: "unknown", required: true, description: "Content to write (supports selectors)" },
|
|
2196
|
-
append: { type: "boolean", required: false, description: "Append instead of overwrite (default: false)" }
|
|
2197
|
-
},
|
|
2198
|
-
example: {
|
|
2199
|
-
id: "writeResults",
|
|
2200
|
-
name: "Save results",
|
|
2201
|
-
type: "file-write",
|
|
2202
|
-
fileWrite: { path: "./output/results.json", content: "$.steps.transform.output" }
|
|
2203
|
-
}
|
|
2204
|
-
},
|
|
2205
|
-
{
|
|
2206
|
-
type: "while",
|
|
2207
|
-
configKey: "while",
|
|
2208
|
-
description: "Do-while loop with condition check",
|
|
2209
|
-
fields: {
|
|
2210
|
-
condition: { type: "string", required: true, description: "JS expression checked before each iteration (after first)" },
|
|
2211
|
-
steps: { type: "array", required: true, description: "Steps to run each iteration", stepsArray: true },
|
|
2212
|
-
maxIterations: { type: "number", required: false, description: "Safety cap (default: 100)" }
|
|
2213
|
-
},
|
|
2214
|
-
example: {
|
|
2215
|
-
id: "paginate",
|
|
2216
|
-
name: "Paginate through pages",
|
|
2217
|
-
type: "while",
|
|
2218
|
-
while: {
|
|
2219
|
-
condition: "$.steps.paginate.output.lastResult.nextPageToken != null",
|
|
2220
|
-
maxIterations: 50,
|
|
2221
|
-
steps: [{ id: "fetchPage", name: "Fetch next page", type: "action", action: { platform: "gmail", actionId: "...", connectionKey: "$.input.gmailKey" } }]
|
|
2222
|
-
}
|
|
2223
|
-
}
|
|
2224
|
-
},
|
|
2225
|
-
{
|
|
2226
|
-
type: "flow",
|
|
2227
|
-
configKey: "flow",
|
|
2228
|
-
description: "Execute a sub-flow (supports composition)",
|
|
2229
|
-
fields: {
|
|
2230
|
-
key: { type: "string", required: true, description: "Flow key or path of the sub-flow" },
|
|
2231
|
-
inputs: { type: "object", required: false, description: "Inputs to pass to the sub-flow (supports selectors)" }
|
|
2232
|
-
},
|
|
2233
|
-
example: {
|
|
2234
|
-
id: "enrich",
|
|
2235
|
-
name: "Run enrichment sub-flow",
|
|
2236
|
-
type: "flow",
|
|
2237
|
-
flow: { key: "enrich-customer", inputs: { email: "$.steps.getCustomer.response.email" } }
|
|
2238
|
-
}
|
|
2239
|
-
},
|
|
2240
|
-
{
|
|
2241
|
-
type: "paginate",
|
|
2242
|
-
configKey: "paginate",
|
|
2243
|
-
description: "Auto-paginate API results into a single array",
|
|
2244
|
-
fields: {
|
|
2245
|
-
action: { type: "object", required: true, description: "Action config (same shape as action step: platform, actionId, connectionKey)" },
|
|
2246
|
-
pageTokenField: { type: "string", required: true, description: "Dot-path in response to next page token" },
|
|
2247
|
-
resultsField: { type: "string", required: true, description: "Dot-path in response to results array" },
|
|
2248
|
-
inputTokenParam: { type: "string", required: true, description: "Dot-path in action config where page token is injected" },
|
|
2249
|
-
maxPages: { type: "number", required: false, description: "Max pages to fetch (default: 10)" }
|
|
2250
|
-
},
|
|
2251
|
-
example: {
|
|
2252
|
-
id: "allMessages",
|
|
2253
|
-
name: "Fetch all Gmail messages",
|
|
2254
|
-
type: "paginate",
|
|
2255
|
-
paginate: {
|
|
2256
|
-
action: { platform: "gmail", actionId: "...", connectionKey: "$.input.gmailKey", queryParams: { maxResults: 100 } },
|
|
2257
|
-
pageTokenField: "nextPageToken",
|
|
2258
|
-
resultsField: "messages",
|
|
2259
|
-
inputTokenParam: "queryParams.pageToken",
|
|
2260
|
-
maxPages: 10
|
|
2261
|
-
}
|
|
2262
|
-
}
|
|
2263
|
-
},
|
|
2264
|
-
{
|
|
2265
|
-
type: "bash",
|
|
2266
|
-
configKey: "bash",
|
|
2267
|
-
description: "Shell command (requires --allow-bash)",
|
|
2268
|
-
fields: {
|
|
2269
|
-
command: { type: "string", required: true, description: "Shell command to execute (supports selectors)" },
|
|
2270
|
-
timeout: { type: "number", required: false, description: "Timeout in ms (default: 30000)" },
|
|
2271
|
-
parseJson: { type: "boolean", required: false, description: "Parse stdout as JSON (default: false)" },
|
|
2272
|
-
cwd: { type: "string", required: false, description: "Working directory (supports selectors)" },
|
|
2273
|
-
env: { type: "object", required: false, description: "Additional environment variables" }
|
|
2274
|
-
},
|
|
2275
|
-
example: {
|
|
2276
|
-
id: "analyze",
|
|
2277
|
-
name: "Analyze with Claude",
|
|
2278
|
-
type: "bash",
|
|
2279
|
-
bash: {
|
|
2280
|
-
command: "cat /tmp/data.json | claude --print 'Analyze this data' --output-format json",
|
|
2281
|
-
timeout: 18e4,
|
|
2282
|
-
parseJson: true
|
|
2283
|
-
}
|
|
2284
|
-
}
|
|
2285
|
-
}
|
|
2286
|
-
]
|
|
2287
|
-
};
|
|
2288
|
-
var _coveredTypes = Object.fromEntries(
|
|
2289
|
-
FLOW_SCHEMA.stepTypes.map((st) => [st.type, true])
|
|
2290
|
-
);
|
|
2291
|
-
var _stepTypeMap = new Map(
|
|
2292
|
-
FLOW_SCHEMA.stepTypes.map((st) => [st.type, st])
|
|
2293
|
-
);
|
|
2294
|
-
function getStepTypeDescriptor(type) {
|
|
2295
|
-
return _stepTypeMap.get(type);
|
|
2296
|
-
}
|
|
2297
|
-
function getValidStepTypes() {
|
|
2298
|
-
return FLOW_SCHEMA.stepTypes.map((st) => st.type);
|
|
2299
|
-
}
|
|
2300
|
-
function getNestedStepsKeys() {
|
|
2301
|
-
const result = [];
|
|
2302
|
-
for (const st of FLOW_SCHEMA.stepTypes) {
|
|
2303
|
-
for (const [fieldName, fd] of Object.entries(st.fields)) {
|
|
2304
|
-
if (fd.stepsArray) {
|
|
2305
|
-
result.push({ configKey: st.configKey, fieldName });
|
|
2306
|
-
}
|
|
2307
|
-
}
|
|
2308
|
-
}
|
|
2309
|
-
return result;
|
|
2310
|
-
}
|
|
2311
|
-
function generateFlowGuide() {
|
|
2312
|
-
const validTypes = getValidStepTypes();
|
|
2313
|
-
const sections = [];
|
|
2314
|
-
sections.push(`# One Flows \u2014 Reference
|
|
2315
|
-
|
|
2316
|
-
## Overview
|
|
2317
|
-
|
|
2318
|
-
Workflows are JSON files at \`.one/flows/<key>.flow.json\` that chain actions across platforms.
|
|
2319
|
-
|
|
2320
|
-
## Commands
|
|
2321
|
-
|
|
2322
|
-
\`\`\`bash
|
|
2323
|
-
one --agent flow create <key> --definition '<json>' # Create (or --definition @file.json)
|
|
2324
|
-
one --agent flow create <key> --definition @flow.json # Create from file
|
|
2325
|
-
one --agent flow list # List
|
|
2326
|
-
one --agent flow validate <key> # Validate
|
|
2327
|
-
one --agent flow execute <key> -i name=value # Execute
|
|
2328
|
-
one --agent flow execute <key> --dry-run --mock # Test with mock data
|
|
2329
|
-
one --agent flow execute <key> --allow-bash # Enable bash steps
|
|
2330
|
-
one --agent flow runs [flowKey] # List past runs
|
|
2331
|
-
one --agent flow resume <runId> # Resume failed run
|
|
2332
|
-
one --agent flow scaffold [template] # Generate a starter template
|
|
2333
|
-
\`\`\`
|
|
2334
|
-
|
|
2335
|
-
You can also write the JSON file directly to \`.one/flows/<key>.flow.json\` \u2014 this is often easier than passing large JSON via --definition.
|
|
2336
|
-
|
|
2337
|
-
## Building a Workflow
|
|
2338
|
-
|
|
2339
|
-
1. **Design first** \u2014 clarify the end goal, map the full value chain, identify where AI analysis is needed
|
|
2340
|
-
2. **Discover connections** \u2014 \`one --agent connection list\`
|
|
2341
|
-
3. **Get knowledge** for every action \u2014 \`one --agent actions knowledge <platform> <actionId>\`
|
|
2342
|
-
4. **Construct JSON** \u2014 declare inputs, wire steps with selectors
|
|
2343
|
-
5. **Validate** \u2014 \`one --agent flow validate <key>\`
|
|
2344
|
-
6. **Execute** \u2014 \`one --agent flow execute <key> -i param=value\``);
|
|
2345
|
-
sections.push(`## Flow JSON Schema
|
|
2346
|
-
|
|
2347
|
-
\`\`\`json
|
|
2348
|
-
{
|
|
2349
|
-
"key": "my-workflow",
|
|
2350
|
-
"name": "My Workflow",
|
|
2351
|
-
"description": "What this flow does",
|
|
2352
|
-
"version": "1",
|
|
2353
|
-
"inputs": {
|
|
2354
|
-
"connectionKey": {
|
|
2355
|
-
"type": "string",
|
|
2356
|
-
"required": true,
|
|
2357
|
-
"description": "Platform connection key",
|
|
2358
|
-
"connection": { "platform": "stripe" }
|
|
2359
|
-
},
|
|
2360
|
-
"param": {
|
|
2361
|
-
"type": "string",
|
|
2362
|
-
"required": true,
|
|
2363
|
-
"description": "A user parameter"
|
|
2364
|
-
}
|
|
2365
|
-
},
|
|
2366
|
-
"steps": [
|
|
2367
|
-
{
|
|
2368
|
-
"id": "stepId",
|
|
2369
|
-
"name": "Human-readable step name",
|
|
2370
|
-
"type": "action",
|
|
2371
|
-
"action": {
|
|
2372
|
-
"platform": "stripe",
|
|
2373
|
-
"actionId": "conn_mod_def::xxx::yyy",
|
|
2374
|
-
"connectionKey": "$.input.connectionKey",
|
|
2375
|
-
"data": { "query": "{{$.input.param}}" }
|
|
2376
|
-
}
|
|
2377
|
-
}
|
|
2378
|
-
]
|
|
2379
|
-
}
|
|
2380
|
-
\`\`\`
|
|
2381
|
-
|
|
2382
|
-
### Top-level fields
|
|
2383
|
-
|
|
2384
|
-
| Field | Type | Required | Description |
|
|
2385
|
-
|-------|------|----------|-------------|`);
|
|
2386
|
-
for (const [name, fd] of Object.entries(FLOW_SCHEMA.flowFields)) {
|
|
2387
|
-
sections.push(`| \`${name}\` | ${fd.type} | ${fd.required ? "yes" : "no"} | ${fd.description} |`);
|
|
2388
|
-
}
|
|
2389
|
-
sections.push(`
|
|
2390
|
-
### Input declarations
|
|
2391
|
-
|
|
2392
|
-
| Field | Type | Required | Description |
|
|
2393
|
-
|-------|------|----------|-------------|`);
|
|
2394
|
-
for (const [name, fd] of Object.entries(FLOW_SCHEMA.inputFields)) {
|
|
2395
|
-
sections.push(`| \`${name}\` | ${fd.type} | ${fd.required ? "yes" : "no"} | ${fd.description} |`);
|
|
2396
|
-
}
|
|
2397
|
-
sections.push(`
|
|
2398
|
-
### Step fields (all steps)
|
|
2399
|
-
|
|
2400
|
-
Every step MUST have \`id\`, \`name\`, and \`type\`. The \`type\` determines which config object is required.
|
|
2401
|
-
|
|
2402
|
-
| Field | Type | Required | Description |
|
|
2403
|
-
|-------|------|----------|-------------|`);
|
|
2404
|
-
for (const [name, fd] of Object.entries(FLOW_SCHEMA.stepCommonFields)) {
|
|
2405
|
-
sections.push(`| \`${name}\` | ${fd.type} | ${fd.required ? "yes" : "no"} | ${fd.description} |`);
|
|
2406
|
-
}
|
|
2407
|
-
sections.push(`| \`onError\` | object | no | Error handling: \`{ "strategy": "${FLOW_SCHEMA.errorStrategies.join(" | ")}", "retries": 3, "retryDelayMs": 1000 }\` |`);
|
|
2408
|
-
sections.push(`
|
|
2409
|
-
## Step Types
|
|
2410
|
-
|
|
2411
|
-
**IMPORTANT:** Each step type requires a config object nested under a specific key. The type name and config key differ for some types (noted below).
|
|
2412
|
-
|
|
2413
|
-
| Type | Config Key | Description |
|
|
2414
|
-
|------|-----------|-------------|`);
|
|
2415
|
-
for (const st of FLOW_SCHEMA.stepTypes) {
|
|
2416
|
-
const keyNote = st.type !== st.configKey ? ` \u26A0\uFE0F` : "";
|
|
2417
|
-
sections.push(`| \`${st.type}\` | \`${st.configKey}\`${keyNote} | ${st.description} |`);
|
|
2418
|
-
}
|
|
2419
|
-
sections.push(`
|
|
2420
|
-
## Step Type Reference`);
|
|
2421
|
-
for (const st of FLOW_SCHEMA.stepTypes) {
|
|
2422
|
-
sections.push(`
|
|
2423
|
-
### \`${st.type}\` \u2014 ${st.description}`);
|
|
2424
|
-
if (st.type !== st.configKey) {
|
|
2425
|
-
sections.push(`
|
|
2426
|
-
> **Note:** Type is \`"${st.type}"\` but config key is \`"${st.configKey}"\` (camelCase).`);
|
|
2427
|
-
}
|
|
2428
|
-
sections.push(`
|
|
2429
|
-
| Field | Type | Required | Description |
|
|
2430
|
-
|-------|------|----------|-------------|`);
|
|
2431
|
-
for (const [name, fd] of Object.entries(st.fields)) {
|
|
2432
|
-
sections.push(`| \`${name}\` | ${fd.type} | ${fd.required ? "yes" : "no"} | ${fd.description} |`);
|
|
2433
|
-
}
|
|
2434
|
-
sections.push(`
|
|
2435
|
-
\`\`\`json
|
|
2436
|
-
${JSON.stringify(st.example, null, 2)}
|
|
2437
|
-
\`\`\``);
|
|
2438
|
-
}
|
|
2439
|
-
sections.push(`
|
|
2440
|
-
## Selectors
|
|
2441
|
-
|
|
2442
|
-
| Pattern | Resolves To |
|
|
2443
|
-
|---------|-------------|
|
|
2444
|
-
| \`$.input.paramName\` | Input value |
|
|
2445
|
-
| \`$.steps.stepId.response\` | Full API response |
|
|
2446
|
-
| \`$.steps.stepId.response.data[0].email\` | Nested field |
|
|
2447
|
-
| \`$.steps.stepId.response.data[*].id\` | Wildcard array map |
|
|
2448
|
-
| \`$.env.MY_VAR\` | Environment variable |
|
|
2449
|
-
| \`$.loop.item\` / \`$.loop.i\` | Loop iteration |
|
|
2450
|
-
| \`"Hello {{$.steps.getUser.response.name}}"\` | String interpolation |
|
|
2451
|
-
|
|
2452
|
-
### When to use bare selectors vs \`{{...}}\` interpolation
|
|
2453
|
-
|
|
2454
|
-
- **Bare selectors** (\`$.input.x\`): Use for fields the engine resolves directly \u2014 \`connectionKey\`, \`over\`, \`path\`, \`expression\`, \`condition\`, and any field where the entire value is a single selector. The resolved value keeps its original type (object, array, number).
|
|
2455
|
-
- **Interpolation** (\`{{$.input.x}}\`): Use inside string values where the selector is embedded in text \u2014 e.g., \`"Hello {{$.steps.getUser.response.name}}"\`. The resolved value is always stringified. Use this in \`data\`, \`pathVars\`, and \`queryParams\` when mixing selectors with literal text.
|
|
2456
|
-
- **Rule of thumb**: If the value is purely a selector, use bare. If it's a string containing a selector, use \`{{...}}\`.
|
|
2457
|
-
|
|
2458
|
-
### \`output\` vs \`response\` on step results
|
|
2459
|
-
|
|
2460
|
-
Every completed step produces both \`output\` and \`response\`:
|
|
2461
|
-
- **Action steps**: \`response\` is the raw API response. \`output\` is the same as \`response\`.
|
|
2462
|
-
- **Code/transform steps**: \`output\` is the return value. \`response\` is an alias for \`output\`.
|
|
2463
|
-
- **In practice**: Use \`$.steps.stepId.response\` for action steps (API data) and \`$.steps.stepId.output\` for code/transform steps (computed data). Both work interchangeably, but using the semantically correct one makes flows easier to read.
|
|
2464
|
-
|
|
2465
|
-
## Error Handling
|
|
2466
|
-
|
|
2467
|
-
\`\`\`json
|
|
2468
|
-
{"onError": {"strategy": "retry", "retries": 3, "retryDelayMs": 1000}}
|
|
2469
|
-
\`\`\`
|
|
2470
|
-
|
|
2471
|
-
Strategies: \`${FLOW_SCHEMA.errorStrategies.join("`, `")}\`
|
|
2472
|
-
|
|
2473
|
-
Conditional execution: \`"if": "$.steps.prev.response.data.length > 0"\`
|
|
2474
|
-
|
|
2475
|
-
## Input Connection Auto-Resolution
|
|
2476
|
-
|
|
2477
|
-
When an input has \`"connection": { "platform": "stripe" }\`, the flow engine can automatically resolve the connection key at execution time. If the user has exactly one connection for that platform, the engine fills in the key without requiring \`-i connectionKey=...\`. If multiple connections exist, the user must specify which one. This is metadata for tooling \u2014 it does not affect the flow JSON structure, but it makes execution more convenient.
|
|
2478
|
-
|
|
2479
|
-
## Complete Example: Fetch Data, Transform, Notify
|
|
2480
|
-
|
|
2481
|
-
\`\`\`json
|
|
2482
|
-
{
|
|
2483
|
-
"key": "contacts-to-slack",
|
|
2484
|
-
"name": "CRM Contacts Summary to Slack",
|
|
2485
|
-
"description": "Fetch recent contacts from CRM, build a summary, post to Slack",
|
|
2486
|
-
"version": "1",
|
|
2487
|
-
"inputs": {
|
|
2488
|
-
"crmConnectionKey": {
|
|
2489
|
-
"type": "string",
|
|
2490
|
-
"required": true,
|
|
2491
|
-
"description": "CRM platform connection key",
|
|
2492
|
-
"connection": { "platform": "attio" }
|
|
2493
|
-
},
|
|
2494
|
-
"slackConnectionKey": {
|
|
2495
|
-
"type": "string",
|
|
2496
|
-
"required": true,
|
|
2497
|
-
"description": "Slack connection key",
|
|
2498
|
-
"connection": { "platform": "slack" }
|
|
2499
|
-
},
|
|
2500
|
-
"slackChannel": {
|
|
2501
|
-
"type": "string",
|
|
2502
|
-
"required": true,
|
|
2503
|
-
"description": "Slack channel name or ID"
|
|
2504
|
-
}
|
|
2505
|
-
},
|
|
2506
|
-
"steps": [
|
|
2507
|
-
{
|
|
2508
|
-
"id": "fetchContacts",
|
|
2509
|
-
"name": "Fetch recent contacts",
|
|
2510
|
-
"type": "action",
|
|
2511
|
-
"action": {
|
|
2512
|
-
"platform": "attio",
|
|
2513
|
-
"actionId": "ATTIO_LIST_PEOPLE_ACTION_ID",
|
|
2514
|
-
"connectionKey": "$.input.crmConnectionKey",
|
|
2515
|
-
"queryParams": { "limit": "10" }
|
|
2516
|
-
}
|
|
2517
|
-
},
|
|
2518
|
-
{
|
|
2519
|
-
"id": "buildSummary",
|
|
2520
|
-
"name": "Build formatted summary",
|
|
2521
|
-
"type": "code",
|
|
2522
|
-
"code": {
|
|
2523
|
-
"source": "const contacts = $.steps.fetchContacts.response.data || [];\\nconst lines = contacts.map((c, i) => \`\${i+1}. \${c.name || 'Unknown'} \u2014 \${c.email || 'no email'}\`);\\nreturn { summary: \`Found \${contacts.length} contacts:\\n\${lines.join('\\n')}\` };"
|
|
2524
|
-
}
|
|
2525
|
-
},
|
|
2526
|
-
{
|
|
2527
|
-
"id": "notifySlack",
|
|
2528
|
-
"name": "Post summary to Slack",
|
|
2529
|
-
"type": "action",
|
|
2530
|
-
"action": {
|
|
2531
|
-
"platform": "slack",
|
|
2532
|
-
"actionId": "SLACK_SEND_MESSAGE_ACTION_ID",
|
|
2533
|
-
"connectionKey": "$.input.slackConnectionKey",
|
|
2534
|
-
"data": {
|
|
2535
|
-
"channel": "$.input.slackChannel",
|
|
2536
|
-
"text": "{{$.steps.buildSummary.output.summary}}"
|
|
2537
|
-
}
|
|
2538
|
-
}
|
|
2539
|
-
}
|
|
2540
|
-
]
|
|
2541
|
-
}
|
|
2542
|
-
\`\`\`
|
|
2543
|
-
|
|
2544
|
-
Note: Action IDs above are placeholders. Always use \`one --agent actions search <platform> "<query>"\` to find real IDs.
|
|
2545
|
-
|
|
2546
|
-
## AI-Augmented Pattern
|
|
2547
|
-
|
|
2548
|
-
For workflows that need analysis/summarization, use the file-write \u2192 bash \u2192 code pattern:
|
|
2549
|
-
|
|
2550
|
-
1. \`file-write\` \u2014 save data to temp file
|
|
2551
|
-
2. \`bash\` \u2014 \`claude --print\` analyzes it (\`parseJson: true\`, \`timeout: 180000\`)
|
|
2552
|
-
3. \`code\` \u2014 parse and structure the output
|
|
2553
|
-
|
|
2554
|
-
Set timeout to at least 180000ms (3 min). Run Claude-heavy flows sequentially, not in parallel.
|
|
2555
|
-
|
|
2556
|
-
## Notes
|
|
2557
|
-
|
|
2558
|
-
- Connection keys are **inputs**, not hardcoded
|
|
2559
|
-
- Action IDs in examples are placeholders \u2014 always use \`actions search\`
|
|
2560
|
-
- Code steps allow \`crypto\`, \`buffer\`, \`url\`, \`path\` \u2014 \`fs\`, \`http\`, \`child_process\` are blocked
|
|
2561
|
-
- Bash steps require \`--allow-bash\` flag
|
|
2562
|
-
- State is persisted after every step \u2014 resume picks up where it left off`);
|
|
2563
|
-
return sections.join("\n");
|
|
2564
|
-
}
|
|
2565
|
-
|
|
2566
2036
|
// src/lib/flow-validator.ts
|
|
2567
2037
|
function validateFlowSchema(flow2) {
|
|
2568
2038
|
const errors = [];
|
|
@@ -2690,6 +2160,25 @@ function validateStepsArray(steps, pathPrefix, errors) {
|
|
|
2690
2160
|
}
|
|
2691
2161
|
}
|
|
2692
2162
|
}
|
|
2163
|
+
if (descriptor.type === "code") {
|
|
2164
|
+
const hasSource = typeof config.source === "string" && config.source.length > 0;
|
|
2165
|
+
const hasModule = typeof config.module === "string" && config.module.length > 0;
|
|
2166
|
+
if (!hasSource && !hasModule) {
|
|
2167
|
+
errors.push({ path: `${path5}.${configKey}`, message: 'Code step must define either "source" (inline JS) or "module" (path to .mjs file)' });
|
|
2168
|
+
} else if (hasSource && hasModule) {
|
|
2169
|
+
errors.push({ path: `${path5}.${configKey}`, message: 'Code step cannot define both "source" and "module" \u2014 pick one' });
|
|
2170
|
+
}
|
|
2171
|
+
if (hasModule) {
|
|
2172
|
+
const m = config.module;
|
|
2173
|
+
if (m.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(m)) {
|
|
2174
|
+
errors.push({ path: `${path5}.${configKey}.module`, message: "Code module path must be relative to the flow folder (no absolute paths)" });
|
|
2175
|
+
} else if (m.split(/[\\/]/).includes("..")) {
|
|
2176
|
+
errors.push({ path: `${path5}.${configKey}.module`, message: 'Code module path must not escape the flow folder ("..")' });
|
|
2177
|
+
} else if (!m.endsWith(".mjs")) {
|
|
2178
|
+
errors.push({ path: `${path5}.${configKey}.module`, message: "Code module must be a .mjs file" });
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2693
2182
|
}
|
|
2694
2183
|
}
|
|
2695
2184
|
function detectFlatConfigHint(step, descriptor) {
|
|
@@ -2745,11 +2234,12 @@ function validateSelectorReferences(flow2) {
|
|
|
2745
2234
|
return ids;
|
|
2746
2235
|
}
|
|
2747
2236
|
const allStepIds = getAllStepIds(flow2.steps);
|
|
2237
|
+
const SELECTOR_TOKEN_RE = /\$\.[a-zA-Z_][\w.\[\]*]*/g;
|
|
2748
2238
|
function extractSelectors(value) {
|
|
2749
2239
|
const selectors = [];
|
|
2750
2240
|
if (typeof value === "string") {
|
|
2751
|
-
|
|
2752
|
-
selectors.push(
|
|
2241
|
+
for (const match of value.matchAll(SELECTOR_TOKEN_RE)) {
|
|
2242
|
+
selectors.push(match[0]);
|
|
2753
2243
|
}
|
|
2754
2244
|
const interpolated = value.matchAll(/\{\{(\$\.[^}]+)\}\}/g);
|
|
2755
2245
|
for (const match of interpolated) {
|
|
@@ -2784,6 +2274,24 @@ function validateSelectorReferences(flow2) {
|
|
|
2784
2274
|
}
|
|
2785
2275
|
}
|
|
2786
2276
|
}
|
|
2277
|
+
const EXPRESSION_FIELDS = /* @__PURE__ */ new Set(["condition.expression", "while.condition"]);
|
|
2278
|
+
function checkOperatorsInSelectorField(value, path5) {
|
|
2279
|
+
if (typeof value === "string" && value.startsWith("$.")) {
|
|
2280
|
+
if (value.includes("||")) {
|
|
2281
|
+
errors.push({ path: path5, message: `Selector "${value}" contains unsupported operator "||". Selectors in data fields use dot-path resolution, not JS evaluation. Use the "default" field on the input definition instead, or use a "code" step for complex expressions.` });
|
|
2282
|
+
} else if (value.includes("&&")) {
|
|
2283
|
+
errors.push({ path: path5, message: `Selector "${value}" contains unsupported operator "&&". Selectors in data fields use dot-path resolution, not JS evaluation. Use a "condition" step or "code" step for complex expressions.` });
|
|
2284
|
+
}
|
|
2285
|
+
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
2286
|
+
for (const [k, v] of Object.entries(value)) {
|
|
2287
|
+
checkOperatorsInSelectorField(v, `${path5}.${k}`);
|
|
2288
|
+
}
|
|
2289
|
+
} else if (Array.isArray(value)) {
|
|
2290
|
+
for (let i = 0; i < value.length; i++) {
|
|
2291
|
+
checkOperatorsInSelectorField(value[i], `${path5}[${i}]`);
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2787
2295
|
function checkStep(step, pathPrefix) {
|
|
2788
2296
|
if (step.if) checkSelectors(extractSelectors(step.if), `${pathPrefix}.if`);
|
|
2789
2297
|
if (step.unless) checkSelectors(extractSelectors(step.unless), `${pathPrefix}.unless`);
|
|
@@ -2796,7 +2304,12 @@ function validateSelectorReferences(flow2) {
|
|
|
2796
2304
|
if (fd.stepsArray) continue;
|
|
2797
2305
|
const value = config[fieldName];
|
|
2798
2306
|
if (value !== void 0) {
|
|
2799
|
-
|
|
2307
|
+
const fieldKey = `${descriptor.configKey}.${fieldName}`;
|
|
2308
|
+
const fieldPath = `${pathPrefix}.${fieldKey}`;
|
|
2309
|
+
checkSelectors(extractSelectors(value), fieldPath);
|
|
2310
|
+
if (!EXPRESSION_FIELDS.has(fieldKey)) {
|
|
2311
|
+
checkOperatorsInSelectorField(value, fieldPath);
|
|
2312
|
+
}
|
|
2800
2313
|
}
|
|
2801
2314
|
}
|
|
2802
2315
|
}
|
|
@@ -2937,14 +2450,35 @@ async function flowExecuteCommand(keyOrPath, options) {
|
|
|
2937
2450
|
const spinner5 = createSpinner();
|
|
2938
2451
|
spinner5.start(`Loading workflow "${keyOrPath}"...`);
|
|
2939
2452
|
let flow2;
|
|
2453
|
+
let rootDir;
|
|
2454
|
+
let flowFilePath;
|
|
2940
2455
|
try {
|
|
2941
|
-
|
|
2456
|
+
const loaded = loadFlowWithMeta(keyOrPath);
|
|
2457
|
+
flow2 = loaded.flow;
|
|
2458
|
+
rootDir = loaded.rootDir;
|
|
2459
|
+
flowFilePath = loaded.filePath;
|
|
2942
2460
|
} catch (err) {
|
|
2943
2461
|
spinner5.stop("Workflow not found");
|
|
2944
2462
|
error(err instanceof Error ? err.message : String(err));
|
|
2945
2463
|
return;
|
|
2946
2464
|
}
|
|
2947
2465
|
spinner5.stop(`Workflow: ${flow2.name} (${flow2.steps.length} steps)`);
|
|
2466
|
+
if (flowFilePath.endsWith(".flow.json")) {
|
|
2467
|
+
const msg = `Workflow "${flow2.key}" uses the deprecated single-file layout. Migrate to .one/flows/${flow2.key}/flow.json (see: one guide flows).`;
|
|
2468
|
+
if (isAgentMode()) {
|
|
2469
|
+
json({ event: "flow:deprecation", flowKey: flow2.key, warning: msg });
|
|
2470
|
+
} else {
|
|
2471
|
+
console.error(pc7.yellow(`\u26A0 ${msg}`));
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
if (!options.allowBash && flowRequiresBash(flow2)) {
|
|
2475
|
+
const msg = `Workflow "${flow2.key}" contains bash steps. Re-run with --allow-bash to permit shell execution.`;
|
|
2476
|
+
if (isAgentMode()) {
|
|
2477
|
+
json({ error: msg, requiresBash: true, flowKey: flow2.key });
|
|
2478
|
+
process.exit(1);
|
|
2479
|
+
}
|
|
2480
|
+
error(msg);
|
|
2481
|
+
}
|
|
2948
2482
|
const inputs = parseInputs(options.input || []);
|
|
2949
2483
|
const resolvedInputs = await autoResolveConnectionInputs(flow2, inputs, api);
|
|
2950
2484
|
const runner = new FlowRunner(flow2, resolvedInputs);
|
|
@@ -2983,6 +2517,7 @@ ${pc7.yellow("Pausing after current step completes...")} (run ID: ${runId})`);
|
|
|
2983
2517
|
mock: options.mock,
|
|
2984
2518
|
verbose: options.verbose,
|
|
2985
2519
|
allowBash: options.allowBash,
|
|
2520
|
+
rootDir,
|
|
2986
2521
|
onEvent
|
|
2987
2522
|
});
|
|
2988
2523
|
process.off("SIGINT", sigintHandler);
|
|
@@ -3047,16 +2582,18 @@ async function flowListCommand() {
|
|
|
3047
2582
|
[
|
|
3048
2583
|
{ key: "key", label: "Key" },
|
|
3049
2584
|
{ key: "name", label: "Name" },
|
|
3050
|
-
{ key: "
|
|
2585
|
+
{ key: "layout", label: "Layout" },
|
|
3051
2586
|
{ key: "inputCount", label: "Inputs" },
|
|
3052
|
-
{ key: "stepCount", label: "Steps" }
|
|
2587
|
+
{ key: "stepCount", label: "Steps" },
|
|
2588
|
+
{ key: "flags", label: "Requires" }
|
|
3053
2589
|
],
|
|
3054
2590
|
flows.map((f) => ({
|
|
3055
2591
|
key: f.key,
|
|
3056
2592
|
name: f.name,
|
|
3057
|
-
|
|
2593
|
+
layout: f.layout,
|
|
3058
2594
|
inputCount: String(f.inputCount),
|
|
3059
|
-
stepCount: String(f.stepCount)
|
|
2595
|
+
stepCount: String(f.stepCount),
|
|
2596
|
+
flags: f.requiresBash ? "--allow-bash" : ""
|
|
3060
2597
|
}))
|
|
3061
2598
|
);
|
|
3062
2599
|
console.log();
|
|
@@ -3107,8 +2644,11 @@ async function flowResumeCommand(runId) {
|
|
|
3107
2644
|
const { apiKey, permissions, actionIds } = getConfig2();
|
|
3108
2645
|
const api = new OneApi(apiKey, getApiBase());
|
|
3109
2646
|
let flow2;
|
|
2647
|
+
let rootDir;
|
|
3110
2648
|
try {
|
|
3111
|
-
|
|
2649
|
+
const loaded = loadFlowWithMeta(state.flowKey);
|
|
2650
|
+
flow2 = loaded.flow;
|
|
2651
|
+
rootDir = loaded.rootDir;
|
|
3112
2652
|
} catch (err) {
|
|
3113
2653
|
error(`Could not load workflow "${state.flowKey}": ${err instanceof Error ? err.message : String(err)}`);
|
|
3114
2654
|
return;
|
|
@@ -3122,7 +2662,7 @@ async function flowResumeCommand(runId) {
|
|
|
3122
2662
|
const spinner5 = createSpinner();
|
|
3123
2663
|
spinner5.start(`Resuming run ${runId} (${state.completedSteps.length} steps already completed)...`);
|
|
3124
2664
|
try {
|
|
3125
|
-
const context = await runner.resume(flow2, api, permissions, actionIds, { onEvent });
|
|
2665
|
+
const context = await runner.resume(flow2, api, permissions, actionIds, { onEvent, rootDir });
|
|
3126
2666
|
spinner5.stop("Workflow completed");
|
|
3127
2667
|
if (isAgentMode()) {
|
|
3128
2668
|
json({
|
|
@@ -3912,7 +3452,8 @@ one --agent flow list # List all workflows
|
|
|
3912
3452
|
\`\`\`
|
|
3913
3453
|
|
|
3914
3454
|
**Key concepts:**
|
|
3915
|
-
- Workflows
|
|
3455
|
+
- Workflows live at \`.one/flows/<key>/flow.json\` (folder layout \u2014 REQUIRED for new flows). The legacy \`.one/flows/<key>.flow.json\` single-file layout is DEPRECATED but still loads for backward compatibility
|
|
3456
|
+
- Code steps can reference an external \`.mjs\` module under the flow's \`lib/\` folder (stdin JSON in, stdout JSON out) \u2014 keeps JS out of JSON strings and makes flows shareable
|
|
3916
3457
|
- 12 step types: action, transform, code, condition, loop, parallel, file-read, file-write, while, flow, paginate, bash
|
|
3917
3458
|
- Data wiring via selectors: \`$.input.param\`, \`$.steps.stepId.response\`, \`$.loop.item\`
|
|
3918
3459
|
- AI analysis via bash steps: \`claude --print\` with \`parseJson: true\`
|
|
@@ -4781,7 +4322,7 @@ actions.command("execute <platform> <actionId> <connectionKey>").alias("x").desc
|
|
|
4781
4322
|
});
|
|
4782
4323
|
});
|
|
4783
4324
|
var flow = program.command("flow").alias("f").description("Create, execute, and manage multi-step workflows");
|
|
4784
|
-
flow.command("create [key]").description("Create a new workflow from JSON definition").option("--definition <json>", "Workflow definition as JSON string").option("-o, --output <path>", "Custom output path (default .one/flows/<key
|
|
4325
|
+
flow.command("create [key]").description("Create a new workflow from JSON definition").option("--definition <json>", "Workflow definition as JSON string").option("-o, --output <path>", "Custom output path (default .one/flows/<key>/flow.json)").action(async (key, options) => {
|
|
4785
4326
|
await flowCreateCommand(key, options);
|
|
4786
4327
|
});
|
|
4787
4328
|
flow.command("execute <keyOrPath>").alias("x").description("Execute a workflow by key or file path").option("-i, --input <name=value>", "Input parameter (repeatable)", collect, []).option("--dry-run", "Validate and show execution plan without running").option("--mock", "With --dry-run: execute transforms/code with mock API responses").option("--allow-bash", "Allow bash step execution (disabled by default for security)").option("-v, --verbose", "Show full request/response for each step").action(async (keyOrPath, options) => {
|