@withone/cli 1.16.0 → 1.17.1

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.
@@ -1,917 +0,0 @@
1
- ---
2
- name: one-flow
3
- description: |
4
- Build and execute multi-step API workflows that chain actions across platforms — like n8n/Zapier but file-based. Workflows are JSON files stored at `.one/flows/<key>.flow.json`.
5
-
6
- TRIGGER when the user wants to:
7
- - Create a multi-step workflow or automation (e.g., "create a workflow that looks up a customer in Stripe and sends them an email")
8
- - Chain multiple API actions together across platforms
9
- - Build a pipeline or sequence of API calls
10
- - Execute, validate, or manage existing workflows
11
- - Automate a process involving multiple connected platforms
12
- - Schedule or orchestrate a series of actions
13
-
14
- DO NOT TRIGGER for:
15
- - Single action execution (use one-actions skill instead)
16
- - Setting up One or installing MCP (that's `one init`)
17
- - Adding new connections (that's `one connection add`)
18
- ---
19
-
20
- # One Workflows — Multi-Step API Workflows
21
-
22
- <!-- Canonical flow schema: src/lib/flow-schema.ts (drives both validation and guide generation) -->
23
-
24
- You have access to the One CLI's workflow engine, which lets you create and execute multi-step API workflows as JSON files. Workflows chain actions across platforms — e.g., look up a Stripe customer, then send them a welcome email via Gmail.
25
-
26
- ## 1. Overview
27
-
28
- - Workflows are JSON files stored at `.one/flows/<key>.flow.json`
29
- - All dynamic values (including connection keys) are declared as **inputs**
30
- - Each workflow has a unique **key** used to reference and execute it
31
- - Executed via `one --agent flow execute <key> -i name=value`
32
-
33
- ## 2. Building a Workflow — Step-by-Step Process
34
-
35
- **You MUST follow this process to build a correct workflow:**
36
-
37
- ### Step 0: Design the workflow
38
-
39
- Before touching any CLI commands, understand what you are building:
40
-
41
- 1. **Clarify the end goal.** What output does the user actually need? A report? A notification? An enriched dataset? Do not assume — ask if unclear.
42
- 2. **Map the full value chain.** List every step required to deliver that output at production quality. Fetching raw data is never the final step — ask yourself: "If I handed this raw API response to the user, would they be satisfied?" If no, you need analysis or enrichment steps.
43
- 3. **Identify where AI analysis is needed.** Any time raw data needs summarization, scoring, classification, comparison, or natural-language generation, plan a `bash` step using `claude --print`. See the AI-Augmented Patterns section below.
44
- 4. **Write the step sequence as a plain list** before constructing JSON. Example:
45
- - Fetch competitor pricing from API
46
- - Write data to temp file
47
- - Claude analyzes competitive positioning (bash step)
48
- - Parse Claude's JSON output (code step)
49
- - Send formatted report via email
50
-
51
- **Common mistake:** Jumping straight to `one actions search` and building a workflow that only fetches and pipes raw data. The result is a shallow data dump, not a useful workflow. Always design first.
52
-
53
- ### Step 1: Discover connections
54
-
55
- ```bash
56
- one --agent connection list
57
- ```
58
-
59
- Find out which platforms are connected and get their connection keys.
60
-
61
- ### Step 2: For EACH API action needed, get the knowledge
62
-
63
- ```bash
64
- # Find the action ID
65
- one --agent actions search <platform> "<query>" -t execute
66
-
67
- # Read the full docs — REQUIRED before adding to a workflow
68
- one --agent actions knowledge <platform> <actionId>
69
- ```
70
-
71
- **CRITICAL:** You MUST call `one actions knowledge` for every action you include in the workflow. The knowledge output tells you the exact request body structure, required fields, path variables, and query parameters. Without this, your workflow JSON will have incorrect data shapes.
72
-
73
- ### Step 3: Construct the workflow JSON
74
-
75
- Using the knowledge gathered, build the workflow JSON with:
76
- - All inputs declared (connection keys + user parameters)
77
- - Each step with the correct actionId, platform, and data structure (from knowledge)
78
- - Data wired between steps using `$.input.*` and `$.steps.*` selectors
79
-
80
- ### Step 4: Write the workflow file
81
-
82
- ```bash
83
- one --agent flow create <key> --definition '<json>'
84
- ```
85
-
86
- Or write directly to `.one/flows/<key>.flow.json`.
87
-
88
- ### Step 5: Validate
89
-
90
- ```bash
91
- one --agent flow validate <key>
92
- ```
93
-
94
- ### Step 6: Execute
95
-
96
- ```bash
97
- one --agent flow execute <key> -i connectionKey=xxx -i param=value
98
- ```
99
-
100
- ## 3. Workflow JSON Schema Reference
101
-
102
- ```json
103
- {
104
- "key": "welcome-customer",
105
- "name": "Welcome New Customer",
106
- "description": "Look up a Stripe customer and send them a welcome email via Gmail",
107
- "version": "1",
108
- "inputs": {
109
- "stripeConnectionKey": {
110
- "type": "string",
111
- "required": true,
112
- "description": "Stripe connection key from one connection list",
113
- "connection": { "platform": "stripe" }
114
- },
115
- "gmailConnectionKey": {
116
- "type": "string",
117
- "required": true,
118
- "description": "Gmail connection key from one connection list",
119
- "connection": { "platform": "gmail" }
120
- },
121
- "customerEmail": {
122
- "type": "string",
123
- "required": true,
124
- "description": "Customer email to look up"
125
- }
126
- },
127
- "steps": [
128
- {
129
- "id": "stepId",
130
- "name": "Human-readable label",
131
- "type": "action",
132
- "action": {
133
- "platform": "stripe",
134
- "actionId": "the-action-id-from-search",
135
- "connectionKey": "$.input.stripeConnectionKey",
136
- "data": {},
137
- "pathVars": {},
138
- "queryParams": {},
139
- "headers": {}
140
- }
141
- }
142
- ]
143
- }
144
- ```
145
-
146
- ### Input declarations
147
-
148
- | Field | Type | Description |
149
- |---|---|---|
150
- | `type` | string | `string`, `number`, `boolean`, `object`, `array` |
151
- | `required` | boolean | Whether this input must be provided (default: true) |
152
- | `default` | any | Default value if not provided |
153
- | `description` | string | Human-readable description |
154
- | `connection` | object | Connection metadata: `{ "platform": "gmail" }` — enables auto-resolution |
155
-
156
- **Connection inputs** have a `connection` field. If the user has exactly one connection for that platform, the workflow engine auto-resolves it.
157
-
158
- ## 4. Selector Syntax Reference
159
-
160
- | Pattern | Resolves To |
161
- |---|---|
162
- | `$.input.gmailConnectionKey` | Input value (including connection keys) |
163
- | `$.input.customerEmail` | Any input parameter |
164
- | `$.steps.stepId.response` | Full API response from a step |
165
- | `$.steps.stepId.response.data[0].email` | Nested field with array index |
166
- | `$.steps.stepId.response.data[*].id` | Wildcard — maps array to field |
167
- | `$.env.MY_VAR` | Environment variable |
168
- | `$.loop.item` | Current loop item |
169
- | `$.loop.i` | Current loop index |
170
- | `"Hello {{$.steps.getUser.response.data.name}}"` | String interpolation |
171
-
172
- **Rules:**
173
- - A value that is purely `$.xxx` resolves to the raw type (object, array, number)
174
- - A string containing `{{$.xxx}}` does string interpolation (stringifies objects)
175
- - Selectors inside objects/arrays are resolved recursively
176
-
177
- ## 5. Step Types Reference
178
-
179
- ### `action` — Execute a One API action
180
-
181
- ```json
182
- {
183
- "id": "findCustomer",
184
- "name": "Search Stripe customers",
185
- "type": "action",
186
- "action": {
187
- "platform": "stripe",
188
- "actionId": "conn_mod_def::xxx::yyy",
189
- "connectionKey": "$.input.stripeConnectionKey",
190
- "data": {
191
- "query": "email:'{{$.input.customerEmail}}'"
192
- }
193
- }
194
- }
195
- ```
196
-
197
- ### `transform` — Transform data with a JS expression
198
-
199
- ```json
200
- {
201
- "id": "extractNames",
202
- "name": "Extract customer names",
203
- "type": "transform",
204
- "transform": {
205
- "expression": "$.steps.findCustomer.response.data.map(c => c.name)"
206
- }
207
- }
208
- ```
209
-
210
- The expression is evaluated with the full flow context as `$`.
211
-
212
- ### `code` — Run multi-line JavaScript
213
-
214
- Unlike `transform` (single expression, implicit return), `code` runs a full function body with explicit `return`. Use it when you need variables, loops, try/catch, or `await`.
215
-
216
- ```json
217
- {
218
- "id": "processData",
219
- "name": "Process and enrich data",
220
- "type": "code",
221
- "code": {
222
- "source": "const customers = $.steps.listCustomers.response.data;\nconst enriched = customers.map(c => ({\n ...c,\n tier: c.spend > 1000 ? 'gold' : 'silver'\n}));\nreturn enriched;"
223
- }
224
- }
225
- ```
226
-
227
- The `source` field contains a JS function body. The flow context is available as `$`. The function is async, so you can use `await`. The return value is stored as the step result.
228
-
229
- ### `condition` — If/then/else branching
230
-
231
- ```json
232
- {
233
- "id": "checkFound",
234
- "name": "Check if customer was found",
235
- "type": "condition",
236
- "condition": {
237
- "expression": "$.steps.findCustomer.response.data.length > 0",
238
- "then": [
239
- { "id": "sendEmail", "name": "Send welcome email", "type": "action", "action": { "..." : "..." } }
240
- ],
241
- "else": [
242
- { "id": "logNotFound", "name": "Log not found", "type": "transform", "transform": { "expression": "'Customer not found'" } }
243
- ]
244
- }
245
- }
246
- ```
247
-
248
- ### `loop` — Iterate over an array
249
-
250
- ```json
251
- {
252
- "id": "processOrders",
253
- "name": "Process each order",
254
- "type": "loop",
255
- "loop": {
256
- "over": "$.steps.listOrders.response.data",
257
- "as": "order",
258
- "indexAs": "i",
259
- "maxIterations": 1000,
260
- "maxConcurrency": 5,
261
- "steps": [
262
- {
263
- "id": "createInvoice",
264
- "name": "Create invoice for order",
265
- "type": "action",
266
- "action": {
267
- "platform": "quickbooks",
268
- "actionId": "...",
269
- "connectionKey": "$.input.qbConnectionKey",
270
- "data": { "amount": "$.loop.order.total" }
271
- }
272
- }
273
- ]
274
- }
275
- }
276
- ```
277
-
278
- - `maxConcurrency` (optional): When set > 1, loop iterations run in parallel batches of that size. Default is sequential (1).
279
-
280
- ### `parallel` — Run steps concurrently
281
-
282
- ```json
283
- {
284
- "id": "parallelLookups",
285
- "name": "Look up in parallel",
286
- "type": "parallel",
287
- "parallel": {
288
- "maxConcurrency": 5,
289
- "steps": [
290
- { "id": "getStripe", "name": "Get Stripe data", "type": "action", "action": { "...": "..." } },
291
- { "id": "getHubspot", "name": "Get HubSpot data", "type": "action", "action": { "...": "..." } }
292
- ]
293
- }
294
- }
295
- ```
296
-
297
- ### `file-read` — Read from filesystem
298
-
299
- ```json
300
- {
301
- "id": "readConfig",
302
- "name": "Read config file",
303
- "type": "file-read",
304
- "fileRead": { "path": "./data/config.json", "parseJson": true }
305
- }
306
- ```
307
-
308
- ### `file-write` — Write to filesystem
309
-
310
- ```json
311
- {
312
- "id": "writeResults",
313
- "name": "Save results",
314
- "type": "file-write",
315
- "fileWrite": {
316
- "path": "./output/results.json",
317
- "content": "$.steps.transform.output",
318
- "append": false
319
- }
320
- }
321
- ```
322
-
323
- ### `while` — Condition-driven loop (do-while)
324
-
325
- Iterates until a condition becomes falsy. The first iteration always runs (do-while semantics), then the condition is checked before each subsequent iteration. Useful for pagination.
326
-
327
- ```json
328
- {
329
- "id": "paginate",
330
- "name": "Paginate through all pages",
331
- "type": "while",
332
- "while": {
333
- "condition": "$.steps.paginate.output.lastResult.nextPageToken != null",
334
- "maxIterations": 50,
335
- "steps": [
336
- {
337
- "id": "fetchPage",
338
- "name": "Fetch next page",
339
- "type": "action",
340
- "action": {
341
- "platform": "gmail",
342
- "actionId": "GMAIL_LIST_MESSAGES_ACTION_ID",
343
- "connectionKey": "$.input.gmailKey",
344
- "queryParams": {
345
- "pageToken": "$.steps.paginate.output.lastResult.nextPageToken"
346
- }
347
- }
348
- }
349
- ]
350
- }
351
- }
352
- ```
353
-
354
- | Field | Type | Description |
355
- |---|---|---|
356
- | `condition` | string | JS expression evaluated before each iteration (after iteration 0) |
357
- | `maxIterations` | number | Safety cap, default: 100 |
358
- | `steps` | FlowStep[] | Steps to execute each iteration |
359
-
360
- The step output contains `lastResult` (last step's output from most recent iteration), `iteration` (count), and `results` (array of all iteration outputs). Reference via `$.steps.<id>.output.lastResult`.
361
-
362
- ### `flow` — Execute a sub-flow
363
-
364
- Loads and executes another saved flow, enabling flow composition. Circular flows are detected and blocked.
365
-
366
- ```json
367
- {
368
- "id": "processCustomer",
369
- "name": "Run customer enrichment flow",
370
- "type": "flow",
371
- "flow": {
372
- "key": "enrich-customer",
373
- "inputs": {
374
- "email": "$.steps.getCustomer.response.email",
375
- "connectionKey": "$.input.hubspotConnectionKey"
376
- }
377
- }
378
- }
379
- ```
380
-
381
- | Field | Type | Description |
382
- |---|---|---|
383
- | `key` | string | Flow key or path (supports selectors) |
384
- | `inputs` | object | Input values mapped to the sub-flow's declared inputs (supports selectors) |
385
-
386
- The step output contains all sub-flow step results. The full sub-flow context is available via `$.steps.<id>.response`.
387
-
388
- ### `paginate` — Auto-collect paginated API results
389
-
390
- Automatically pages through a paginated API, collecting all results into a single array.
391
-
392
- ```json
393
- {
394
- "id": "allMessages",
395
- "name": "Fetch all Gmail messages",
396
- "type": "paginate",
397
- "paginate": {
398
- "action": {
399
- "platform": "gmail",
400
- "actionId": "GMAIL_LIST_MESSAGES_ACTION_ID",
401
- "connectionKey": "$.input.gmailKey",
402
- "queryParams": { "maxResults": 100 }
403
- },
404
- "pageTokenField": "nextPageToken",
405
- "resultsField": "messages",
406
- "inputTokenParam": "queryParams.pageToken",
407
- "maxPages": 10
408
- }
409
- }
410
- ```
411
-
412
- | Field | Type | Description |
413
- |---|---|---|
414
- | `action` | FlowActionConfig | The API action to call (same format as action steps) |
415
- | `pageTokenField` | string | Dot-path in the API response to the next page token |
416
- | `resultsField` | string | Dot-path in the API response to the results array |
417
- | `inputTokenParam` | string | Dot-path in the action config where the page token is injected |
418
- | `maxPages` | number | Maximum pages to fetch, default: 10 |
419
-
420
- Output is the concatenated results array. Response includes `{ pages, totalResults, results }`.
421
-
422
- ### `bash` — Execute shell commands
423
-
424
- Runs a shell command. **Requires `--allow-bash` flag** for security.
425
-
426
- ```json
427
- {
428
- "id": "analyzeData",
429
- "name": "Analyze data with Claude",
430
- "type": "bash",
431
- "bash": {
432
- "command": "claude --print 'Analyze: {{$.steps.fetchData.response}}' --output-format json",
433
- "timeout": 180000,
434
- "parseJson": true
435
- }
436
- }
437
- ```
438
-
439
- | Field | Type | Description |
440
- |---|---|---|
441
- | `command` | string | Shell command to execute (supports selectors and interpolation) |
442
- | `timeout` | number | Timeout in ms, default: 30000 |
443
- | `parseJson` | boolean | Parse stdout as JSON, default: false |
444
- | `cwd` | string | Working directory (supports selectors) |
445
- | `env` | object | Additional environment variables |
446
-
447
- Output is stdout (trimmed, or parsed as JSON if `parseJson` is true). Response includes `{ stdout, stderr, exitCode }`.
448
-
449
- **Security:** Bash steps are blocked by default. Pass `--allow-bash` to `one flow execute` to enable them.
450
-
451
- ## 6. Error Handling
452
-
453
- ### `onError` strategies
454
-
455
- ```json
456
- {
457
- "id": "riskyStep",
458
- "name": "Might fail",
459
- "type": "action",
460
- "onError": {
461
- "strategy": "retry",
462
- "retries": 3,
463
- "retryDelayMs": 1000
464
- },
465
- "action": { "...": "..." }
466
- }
467
- ```
468
-
469
- | Strategy | Behavior |
470
- |---|---|
471
- | `fail` | Stop the flow immediately (default) |
472
- | `continue` | Mark step as failed, continue to next step |
473
- | `retry` | Retry up to N times with delay |
474
- | `fallback` | On failure, execute a different step |
475
-
476
- ### Conditional execution
477
-
478
- Skip a step based on previous results:
479
-
480
- ```json
481
- {
482
- "id": "sendEmail",
483
- "name": "Send email only if customer found",
484
- "type": "action",
485
- "if": "$.steps.findCustomer.response.data.length > 0",
486
- "action": { "...": "..." }
487
- }
488
- ```
489
-
490
- ## 7. Updating Existing Workflows
491
-
492
- To modify an existing workflow:
493
-
494
- 1. Read the workflow JSON file at `.one/flows/<key>.flow.json`
495
- 2. Understand its current structure
496
- 3. Use `one --agent actions knowledge <platform> <actionId>` for any new actions
497
- 4. Modify the JSON (add/remove/update steps, change data mappings, add inputs)
498
- 5. Write back the updated workflow file
499
- 6. Validate: `one --agent flow validate <key>`
500
-
501
- ## 8. Complete Examples
502
-
503
- ### Example 1: Simple 2-step — Search Stripe customer, send Gmail email
504
-
505
- ```json
506
- {
507
- "key": "welcome-customer",
508
- "name": "Welcome New Customer",
509
- "description": "Look up a Stripe customer and send them a welcome email",
510
- "version": "1",
511
- "inputs": {
512
- "stripeConnectionKey": {
513
- "type": "string",
514
- "required": true,
515
- "description": "Stripe connection key",
516
- "connection": { "platform": "stripe" }
517
- },
518
- "gmailConnectionKey": {
519
- "type": "string",
520
- "required": true,
521
- "description": "Gmail connection key",
522
- "connection": { "platform": "gmail" }
523
- },
524
- "customerEmail": {
525
- "type": "string",
526
- "required": true,
527
- "description": "Customer email to look up"
528
- }
529
- },
530
- "steps": [
531
- {
532
- "id": "findCustomer",
533
- "name": "Search for customer in Stripe",
534
- "type": "action",
535
- "action": {
536
- "platform": "stripe",
537
- "actionId": "STRIPE_SEARCH_CUSTOMERS_ACTION_ID",
538
- "connectionKey": "$.input.stripeConnectionKey",
539
- "data": {
540
- "query": "email:'{{$.input.customerEmail}}'"
541
- }
542
- }
543
- },
544
- {
545
- "id": "sendWelcome",
546
- "name": "Send welcome email via Gmail",
547
- "type": "action",
548
- "if": "$.steps.findCustomer.response.data && $.steps.findCustomer.response.data.length > 0",
549
- "action": {
550
- "platform": "gmail",
551
- "actionId": "GMAIL_SEND_EMAIL_ACTION_ID",
552
- "connectionKey": "$.input.gmailConnectionKey",
553
- "data": {
554
- "to": "{{$.input.customerEmail}}",
555
- "subject": "Welcome, {{$.steps.findCustomer.response.data[0].name}}!",
556
- "body": "Thank you for being a customer. We're glad to have you!"
557
- }
558
- }
559
- }
560
- ]
561
- }
562
- ```
563
-
564
- ### Example 2: Conditional — Check if HubSpot contact exists, create or update
565
-
566
- ```json
567
- {
568
- "key": "sync-hubspot-contact",
569
- "name": "Sync Contact to HubSpot",
570
- "description": "Check if a contact exists in HubSpot, create if new or update if existing",
571
- "version": "1",
572
- "inputs": {
573
- "hubspotConnectionKey": {
574
- "type": "string",
575
- "required": true,
576
- "connection": { "platform": "hub-spot" }
577
- },
578
- "email": { "type": "string", "required": true },
579
- "firstName": { "type": "string", "required": true },
580
- "lastName": { "type": "string", "required": true }
581
- },
582
- "steps": [
583
- {
584
- "id": "searchContact",
585
- "name": "Search for existing contact",
586
- "type": "action",
587
- "action": {
588
- "platform": "hub-spot",
589
- "actionId": "HUBSPOT_SEARCH_CONTACTS_ACTION_ID",
590
- "connectionKey": "$.input.hubspotConnectionKey",
591
- "data": {
592
- "filterGroups": [{ "filters": [{ "propertyName": "email", "operator": "EQ", "value": "$.input.email" }] }]
593
- }
594
- }
595
- },
596
- {
597
- "id": "createOrUpdate",
598
- "name": "Create or update contact",
599
- "type": "condition",
600
- "condition": {
601
- "expression": "$.steps.searchContact.response.total > 0",
602
- "then": [
603
- {
604
- "id": "updateContact",
605
- "name": "Update existing contact",
606
- "type": "action",
607
- "action": {
608
- "platform": "hub-spot",
609
- "actionId": "HUBSPOT_UPDATE_CONTACT_ACTION_ID",
610
- "connectionKey": "$.input.hubspotConnectionKey",
611
- "pathVars": { "contactId": "$.steps.searchContact.response.results[0].id" },
612
- "data": {
613
- "properties": { "firstname": "$.input.firstName", "lastname": "$.input.lastName" }
614
- }
615
- }
616
- }
617
- ],
618
- "else": [
619
- {
620
- "id": "createContact",
621
- "name": "Create new contact",
622
- "type": "action",
623
- "action": {
624
- "platform": "hub-spot",
625
- "actionId": "HUBSPOT_CREATE_CONTACT_ACTION_ID",
626
- "connectionKey": "$.input.hubspotConnectionKey",
627
- "data": {
628
- "properties": { "email": "$.input.email", "firstname": "$.input.firstName", "lastname": "$.input.lastName" }
629
- }
630
- }
631
- }
632
- ]
633
- }
634
- }
635
- ]
636
- }
637
- ```
638
-
639
- ### Example 3: Loop — Iterate over Shopify orders, create invoices
640
-
641
- ```json
642
- {
643
- "key": "shopify-to-invoices",
644
- "name": "Shopify Orders to Invoices",
645
- "description": "Fetch recent Shopify orders and create an invoice for each",
646
- "version": "1",
647
- "inputs": {
648
- "shopifyConnectionKey": {
649
- "type": "string",
650
- "required": true,
651
- "connection": { "platform": "shopify" }
652
- },
653
- "qbConnectionKey": {
654
- "type": "string",
655
- "required": true,
656
- "connection": { "platform": "quick-books" }
657
- }
658
- },
659
- "steps": [
660
- {
661
- "id": "listOrders",
662
- "name": "List recent Shopify orders",
663
- "type": "action",
664
- "action": {
665
- "platform": "shopify",
666
- "actionId": "SHOPIFY_LIST_ORDERS_ACTION_ID",
667
- "connectionKey": "$.input.shopifyConnectionKey",
668
- "queryParams": { "status": "any", "limit": "50" }
669
- }
670
- },
671
- {
672
- "id": "createInvoices",
673
- "name": "Create invoice for each order",
674
- "type": "loop",
675
- "loop": {
676
- "over": "$.steps.listOrders.response.orders",
677
- "as": "order",
678
- "indexAs": "i",
679
- "steps": [
680
- {
681
- "id": "createInvoice",
682
- "name": "Create QuickBooks invoice",
683
- "type": "action",
684
- "onError": { "strategy": "continue" },
685
- "action": {
686
- "platform": "quick-books",
687
- "actionId": "QB_CREATE_INVOICE_ACTION_ID",
688
- "connectionKey": "$.input.qbConnectionKey",
689
- "data": {
690
- "Line": [
691
- {
692
- "Amount": "$.loop.order.total_price",
693
- "Description": "Shopify Order #{{$.loop.order.order_number}}"
694
- }
695
- ]
696
- }
697
- }
698
- }
699
- ]
700
- }
701
- },
702
- {
703
- "id": "summary",
704
- "name": "Generate summary",
705
- "type": "transform",
706
- "transform": {
707
- "expression": "({ totalOrders: $.steps.listOrders.response.orders.length, processed: $.steps.createInvoices.output.length })"
708
- }
709
- }
710
- ]
711
- }
712
- ```
713
-
714
- ### Example 4: AI-Augmented — Fetch CRM data, analyze with Claude, email report
715
-
716
- This example demonstrates the **file-write → bash → code** pattern. Instead of just piping raw data, it uses Claude to perform competitive analysis and delivers an actionable report.
717
-
718
- ```json
719
- {
720
- "key": "competitor-analysis",
721
- "name": "AI Competitor Analysis",
722
- "description": "Fetch deals from HubSpot, analyze competitive landscape with Claude, email the report",
723
- "version": "1",
724
- "inputs": {
725
- "hubspotConnectionKey": {
726
- "type": "string",
727
- "required": true,
728
- "connection": { "platform": "hub-spot" }
729
- },
730
- "gmailConnectionKey": {
731
- "type": "string",
732
- "required": true,
733
- "connection": { "platform": "gmail" }
734
- },
735
- "reportEmail": {
736
- "type": "string",
737
- "required": true,
738
- "description": "Email address to send the analysis report to"
739
- }
740
- },
741
- "steps": [
742
- {
743
- "id": "fetchDeals",
744
- "name": "Fetch recent deals from HubSpot",
745
- "type": "action",
746
- "action": {
747
- "platform": "hub-spot",
748
- "actionId": "HUBSPOT_LIST_DEALS_ACTION_ID",
749
- "connectionKey": "$.input.hubspotConnectionKey",
750
- "queryParams": { "limit": "100" }
751
- }
752
- },
753
- {
754
- "id": "writeDeals",
755
- "name": "Write deals data for Claude analysis",
756
- "type": "file-write",
757
- "fileWrite": {
758
- "path": "/tmp/competitor-analysis-deals.json",
759
- "content": "$.steps.fetchDeals.response"
760
- }
761
- },
762
- {
763
- "id": "analyzeCompetitors",
764
- "name": "Claude analyzes competitive landscape",
765
- "type": "bash",
766
- "bash": {
767
- "command": "cat /tmp/competitor-analysis-deals.json | claude --print 'You are a competitive intelligence analyst. Analyze these CRM deals and return a JSON object with: {\"totalDeals\": number, \"competitorMentions\": [{\"competitor\": \"name\", \"count\": number, \"winRate\": number, \"commonObjections\": [\"...\"]}], \"summary\": \"2-3 paragraph executive summary\", \"recommendations\": [\"actionable items\"]}. Return ONLY valid JSON.' --output-format json",
768
- "timeout": 180000,
769
- "parseJson": true
770
- }
771
- },
772
- {
773
- "id": "formatReport",
774
- "name": "Format analysis into email body",
775
- "type": "code",
776
- "code": {
777
- "source": "const a = $.steps.analyzeCompetitors.output;\nconst competitors = a.competitorMentions.map(c => `- ${c.competitor}: ${c.count} mentions, ${c.winRate}% win rate. Objections: ${c.commonObjections.join(', ')}`).join('\\n');\nreturn {\n subject: `Competitive Analysis — ${a.totalDeals} deals analyzed`,\n body: `${a.summary}\\n\\nCompetitor Breakdown:\\n${competitors}\\n\\nRecommendations:\\n${a.recommendations.map((r, i) => `${i+1}. ${r}`).join('\\n')}`\n};"
778
- }
779
- },
780
- {
781
- "id": "sendReport",
782
- "name": "Email the analysis report",
783
- "type": "action",
784
- "action": {
785
- "platform": "gmail",
786
- "actionId": "GMAIL_SEND_EMAIL_ACTION_ID",
787
- "connectionKey": "$.input.gmailConnectionKey",
788
- "data": {
789
- "to": "{{$.input.reportEmail}}",
790
- "subject": "{{$.steps.formatReport.output.subject}}",
791
- "body": "{{$.steps.formatReport.output.body}}"
792
- }
793
- }
794
- }
795
- ]
796
- }
797
- ```
798
-
799
- Execute with:
800
- ```bash
801
- one --agent flow execute competitor-analysis --allow-bash -i reportEmail=team@company.com
802
- ```
803
-
804
- ## 9. AI-Augmented Workflow Patterns
805
-
806
- Use this pattern whenever raw API data needs analysis, summarization, scoring, classification, or natural-language generation. This is the difference between a shallow data pipe and a workflow that delivers real value.
807
-
808
- ### The file-write → bash → code pattern
809
-
810
- **Step A: `file-write`** — Write raw data to a temp file. API responses are often too large to inline into a shell command.
811
-
812
- ```json
813
- {
814
- "id": "writeData",
815
- "name": "Write raw data for analysis",
816
- "type": "file-write",
817
- "fileWrite": {
818
- "path": "/tmp/{{$.input.flowKey}}-data.json",
819
- "content": "$.steps.fetchData.response"
820
- }
821
- }
822
- ```
823
-
824
- **Step B: `bash`** — Call `claude --print` to analyze the data. This is where intelligence happens.
825
-
826
- ```json
827
- {
828
- "id": "analyze",
829
- "name": "AI analysis",
830
- "type": "bash",
831
- "bash": {
832
- "command": "cat /tmp/{{$.input.flowKey}}-data.json | claude --print 'You are a [domain] analyst. Analyze this data and return JSON with: {\"summary\": \"...\", \"insights\": [...], \"score\": 0-100, \"recommendations\": [...]}. Return ONLY valid JSON, no markdown.' --output-format json",
833
- "timeout": 180000,
834
- "parseJson": true
835
- }
836
- }
837
- ```
838
-
839
- **Step C: `code`** — Parse and structure the AI output for downstream steps.
840
-
841
- ```json
842
- {
843
- "id": "formatResult",
844
- "name": "Structure analysis for output",
845
- "type": "code",
846
- "code": {
847
- "source": "const analysis = $.steps.analyze.output;\nreturn {\n report: `Summary: ${analysis.summary}\\n\\nInsights:\\n${analysis.insights.map((insight, i) => `${i+1}. ${insight}`).join('\\n')}`,\n score: analysis.score\n};"
848
- }
849
- }
850
- ```
851
-
852
- ### When to use this pattern
853
-
854
- - **Use it** when the user expects analysis, not raw data (e.g., "analyze my competitors", "qualify these leads", "summarize these reviews")
855
- - **Use it** when data from one API needs intelligent transformation before being sent to another (e.g., generating a personalized email based on CRM data)
856
- - **Don't use it** for simple field mapping or filtering — use `transform` or `code` steps instead
857
-
858
- ### Prompt engineering tips for bash steps
859
-
860
- - **Request JSON output** so downstream code steps can parse it — include `Return ONLY valid JSON, no markdown.` in the prompt and use `--output-format json`
861
- - **Be specific about the analysis** — "Score each lead 0-100 based on company size, role seniority, and engagement recency" beats "analyze these leads"
862
- - **Include domain context** — "You are a B2B sales analyst" produces better results than a generic prompt
863
- - **Keep prompts focused** — one analysis task per bash step; chain multiple bash steps for multi-stage analysis
864
-
865
- ### Concurrency and timeout guidance
866
-
867
- - **Always set `timeout` to at least `180000` (3 minutes)** for bash steps calling `claude --print`. The default 30s bash timeout will fail on nearly all AI analysis tasks. Claude typically needs 60-90s, and under resource contention this can double.
868
- - **Run Claude-heavy flows sequentially, not in parallel.** Each `claude --print` spawns a separate process. Running multiple flows with bash+Claude steps concurrently causes resource contention and timeout failures — even when individual prompts are small. If orchestrating multiple AI workflows, execute them one at a time.
869
- - **If a bash+Claude step times out**, the cause is almost always the timeout value or concurrent execution — not prompt size. Increase the timeout and ensure no other Claude-heavy flows are running before assuming the prompt needs to be reduced.
870
-
871
- ## 10. CLI Commands Reference
872
-
873
- ```bash
874
- # Create a workflow
875
- one --agent flow create <key> --definition '<json>'
876
-
877
- # List all workflows
878
- one --agent flow list
879
-
880
- # Validate a workflow
881
- one --agent flow validate <key>
882
-
883
- # Execute a workflow
884
- one --agent flow execute <key> -i connectionKey=value -i param=value
885
-
886
- # Execute with dry run (validate only)
887
- one --agent flow execute <key> --dry-run -i connectionKey=value
888
-
889
- # Execute with mock mode (dry-run + mock API responses, runs transforms/code normally)
890
- one --agent flow execute <key> --dry-run --mock -i connectionKey=value
891
-
892
- # Execute with bash steps enabled
893
- one --agent flow execute <key> --allow-bash -i connectionKey=value
894
-
895
- # Execute with verbose output
896
- one --agent flow execute <key> -v -i connectionKey=value
897
-
898
- # List workflow runs
899
- one --agent flow runs [flowKey]
900
-
901
- # Resume a paused/failed run
902
- one --agent flow resume <runId>
903
- ```
904
-
905
- ## Important Notes
906
-
907
- - **Always use `--agent` flag** for structured JSON output
908
- - **Always call `one actions knowledge`** before adding an action step to a workflow
909
- - Platform names are **kebab-case** (e.g., `hub-spot`, not `HubSpot`)
910
- - Connection keys are **inputs**, not hardcoded — makes workflows portable and shareable
911
- - Use `$.input.*` for input values, `$.steps.*` for step results
912
- - Action IDs in examples (like `STRIPE_SEARCH_CUSTOMERS_ACTION_ID`) are placeholders — always use `one actions search` to find the real IDs
913
- - **Parallel step outputs** are accessible both by index (`$.steps.parallelStep.output[0]`) and by substep ID (`$.steps.substepId.response`)
914
- - **Loop step outputs** include iteration details via `$.steps.myLoop.response.iterations[0].innerStepId.response`
915
- - **Code steps** support `await require('crypto')`, `await require('buffer')`, `await require('url')`, `await require('path')` — `fs`, `http`, `child_process`, etc. are blocked
916
- - **Bash steps** require `--allow-bash` flag for security
917
- - **State is persisted** after every step completion — resume picks up where it left off