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