@picahq/cli 1.9.4 → 1.11.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.
Files changed (3) hide show
  1. package/README.md +196 -51
  2. package/dist/index.js +2278 -127
  3. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import pc3 from "picocolors";
12
12
  import fs from "fs";
13
13
  import path from "path";
14
14
  import os from "os";
15
- var CONFIG_DIR = path.join(os.homedir(), ".pica");
15
+ var CONFIG_DIR = path.join(os.homedir(), ".one");
16
16
  var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
17
17
  function getConfigPath() {
18
18
  return CONFIG_FILE;
@@ -37,8 +37,8 @@ function writeConfig(config) {
37
37
  }
38
38
  fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 384 });
39
39
  }
40
- function readPicaRc() {
41
- const rcPath = path.join(process.cwd(), ".picarc");
40
+ function readOneRc() {
41
+ const rcPath = path.join(process.cwd(), ".onerc");
42
42
  if (!fs.existsSync(rcPath)) return {};
43
43
  try {
44
44
  const content = fs.readFileSync(rcPath, "utf-8");
@@ -58,26 +58,26 @@ function readPicaRc() {
58
58
  }
59
59
  }
60
60
  function getApiKey() {
61
- if (process.env.PICA_SECRET) return process.env.PICA_SECRET;
62
- const rc = readPicaRc();
63
- if (rc.PICA_SECRET) return rc.PICA_SECRET;
61
+ if (process.env.ONE_SECRET) return process.env.ONE_SECRET;
62
+ const rc = readOneRc();
63
+ if (rc.ONE_SECRET) return rc.ONE_SECRET;
64
64
  return readConfig()?.apiKey ?? null;
65
65
  }
66
66
  function getAccessControlFromAllSources() {
67
- const rc = readPicaRc();
67
+ const rc = readOneRc();
68
68
  const fileAc = getAccessControl();
69
69
  const merged = { ...fileAc };
70
- if (rc.PICA_PERMISSIONS) {
71
- merged.permissions = rc.PICA_PERMISSIONS;
70
+ if (rc.ONE_PERMISSIONS) {
71
+ merged.permissions = rc.ONE_PERMISSIONS;
72
72
  }
73
- if (rc.PICA_CONNECTION_KEYS) {
74
- merged.connectionKeys = rc.PICA_CONNECTION_KEYS.split(",").map((s) => s.trim()).filter(Boolean);
73
+ if (rc.ONE_CONNECTION_KEYS) {
74
+ merged.connectionKeys = rc.ONE_CONNECTION_KEYS.split(",").map((s) => s.trim()).filter(Boolean);
75
75
  }
76
- if (rc.PICA_ACTION_IDS) {
77
- merged.actionIds = rc.PICA_ACTION_IDS.split(",").map((s) => s.trim()).filter(Boolean);
76
+ if (rc.ONE_ACTION_IDS) {
77
+ merged.actionIds = rc.ONE_ACTION_IDS.split(",").map((s) => s.trim()).filter(Boolean);
78
78
  }
79
- if (rc.PICA_KNOWLEDGE_AGENT) {
80
- merged.knowledgeAgent = rc.PICA_KNOWLEDGE_AGENT === "true";
79
+ if (rc.ONE_KNOWLEDGE_AGENT) {
80
+ merged.knowledgeAgent = rc.ONE_KNOWLEDGE_AGENT === "true";
81
81
  }
82
82
  return merged;
83
83
  }
@@ -247,20 +247,20 @@ function writeAgentConfig(agent, config, scope = "global") {
247
247
  }
248
248
  function getMcpServerConfig(apiKey, accessControl) {
249
249
  const env = {
250
- PICA_SECRET: apiKey
250
+ ONE_SECRET: apiKey
251
251
  };
252
252
  if (accessControl) {
253
253
  if (accessControl.permissions && accessControl.permissions !== "admin") {
254
- env.PICA_PERMISSIONS = accessControl.permissions;
254
+ env.ONE_PERMISSIONS = accessControl.permissions;
255
255
  }
256
256
  if (accessControl.connectionKeys && !(accessControl.connectionKeys.length === 1 && accessControl.connectionKeys[0] === "*")) {
257
- env.PICA_CONNECTION_KEYS = accessControl.connectionKeys.join(",");
257
+ env.ONE_CONNECTION_KEYS = accessControl.connectionKeys.join(",");
258
258
  }
259
259
  if (accessControl.actionIds && !(accessControl.actionIds.length === 1 && accessControl.actionIds[0] === "*")) {
260
- env.PICA_ACTION_IDS = accessControl.actionIds.join(",");
260
+ env.ONE_ACTION_IDS = accessControl.actionIds.join(",");
261
261
  }
262
262
  if (accessControl.knowledgeAgent) {
263
- env.PICA_KNOWLEDGE_AGENT = "true";
263
+ env.ONE_KNOWLEDGE_AGENT = "true";
264
264
  }
265
265
  }
266
266
  return {
@@ -273,7 +273,7 @@ function installMcpConfig(agent, apiKey, scope = "global", accessControl) {
273
273
  const config = readAgentConfig(agent, scope);
274
274
  const configKey = agent.configKey;
275
275
  const mcpServers = config[configKey] || {};
276
- mcpServers["pica"] = getMcpServerConfig(apiKey, accessControl);
276
+ mcpServers["one"] = getMcpServerConfig(apiKey, accessControl);
277
277
  config[configKey] = mcpServers;
278
278
  writeAgentConfig(agent, config, scope);
279
279
  }
@@ -281,7 +281,7 @@ function isMcpInstalled(agent, scope = "global") {
281
281
  const config = readAgentConfig(agent, scope);
282
282
  const configKey = agent.configKey;
283
283
  const mcpServers = config[configKey];
284
- return mcpServers?.["pica"] !== void 0;
284
+ return mcpServers?.["one"] !== void 0;
285
285
  }
286
286
  function getAgentStatuses() {
287
287
  return AGENTS.map((agent) => {
@@ -293,7 +293,7 @@ function getAgentStatuses() {
293
293
  }
294
294
 
295
295
  // src/lib/api.ts
296
- var API_BASE = "https://api.picaos.com/v1";
296
+ var API_BASE = "https://api.withone.ai/v1";
297
297
  var ApiError = class extends Error {
298
298
  constructor(status, message) {
299
299
  super(message);
@@ -301,12 +301,12 @@ var ApiError = class extends Error {
301
301
  this.name = "ApiError";
302
302
  }
303
303
  };
304
- var PicaApi = class {
304
+ var OneApi = class {
305
305
  constructor(apiKey) {
306
306
  this.apiKey = apiKey;
307
307
  }
308
- async request(path3) {
309
- return this.requestFull({ path: path3 });
308
+ async request(path5) {
309
+ return this.requestFull({ path: path5 });
310
310
  }
311
311
  async requestFull(opts) {
312
312
  let url = `${API_BASE}${opts.path}`;
@@ -315,7 +315,7 @@ var PicaApi = class {
315
315
  url += `?${params.toString()}`;
316
316
  }
317
317
  const headers = {
318
- "x-pica-secret": this.apiKey,
318
+ "x-one-secret": this.apiKey,
319
319
  "Content-Type": "application/json",
320
320
  ...opts.headers
321
321
  };
@@ -406,9 +406,9 @@ var PicaApi = class {
406
406
  const method = action.method;
407
407
  const contentType = args.isFormData ? "multipart/form-data" : args.isFormUrlEncoded ? "application/x-www-form-urlencoded" : "application/json";
408
408
  const requestHeaders = {
409
- "x-pica-secret": this.apiKey,
410
- "x-pica-connection-key": args.connectionKey,
411
- "x-pica-action-id": action._id,
409
+ "x-one-secret": this.apiKey,
410
+ "x-one-connection-key": args.connectionKey,
411
+ "x-one-action-id": action._id,
412
412
  "Content-Type": contentType,
413
413
  ...args.headers
414
414
  };
@@ -476,7 +476,7 @@ var PicaApi = class {
476
476
  method,
477
477
  headers: {
478
478
  ...requestHeaders,
479
- "x-pica-secret": "***REDACTED***"
479
+ "x-one-secret": "***REDACTED***"
480
480
  },
481
481
  params: args.queryParams ? Object.fromEntries(
482
482
  Object.entries(args.queryParams).map(([k, v]) => [k, String(v)])
@@ -488,7 +488,8 @@ var PicaApi = class {
488
488
  const text4 = await response.text();
489
489
  throw new ApiError(response.status, text4 || `HTTP ${response.status}`);
490
490
  }
491
- const responseData = await response.json();
491
+ const responseText = await response.text();
492
+ const responseData = responseText ? JSON.parse(responseText) : {};
492
493
  return {
493
494
  requestConfig: sanitizedConfig,
494
495
  responseData
@@ -521,9 +522,9 @@ var TimeoutError = class extends Error {
521
522
  function sleep(ms) {
522
523
  return new Promise((resolve) => setTimeout(resolve, ms));
523
524
  }
524
- function replacePathVariables(path3, variables) {
525
- if (!path3) return path3;
526
- let result = path3;
525
+ function replacePathVariables(path5, variables) {
526
+ if (!path5) return path5;
527
+ let result = path5;
527
528
  result = result.replace(/\{\{([^}]+)\}\}/g, (_match, variable) => {
528
529
  const trimmedVariable = variable.trim();
529
530
  const value = variables[trimmedVariable];
@@ -561,7 +562,7 @@ function isActionAllowed(actionId, allowedActionIds) {
561
562
  return allowedActionIds.includes("*") || allowedActionIds.includes(actionId);
562
563
  }
563
564
  function buildActionKnowledgeWithGuidance(knowledge, method, platform, actionId) {
564
- const baseUrl = "https://api.picaos.com";
565
+ const baseUrl = "https://api.withone.ai";
565
566
  return `${knowledge}
566
567
 
567
568
  API REQUEST STRUCTURE
@@ -578,9 +579,9 @@ Examples:
578
579
  METHOD: ${method}
579
580
 
580
581
  HEADERS:
581
- - x-pica-secret: {{process.env.PICA_SECRET}}
582
- - x-pica-connection-key: {{process.env.PICA_${platform.toUpperCase()}_CONNECTION_KEY}}
583
- - x-pica-action-id: ${actionId}
582
+ - x-one-secret: {{process.env.ONE_SECRET}}
583
+ - x-one-connection-key: {{process.env.ONE_${platform.toUpperCase()}_CONNECTION_KEY}}
584
+ - x-one-action-id: ${actionId}
584
585
  - ... (other headers)
585
586
 
586
587
  BODY: {{BODY}}
@@ -590,12 +591,12 @@ QUERY PARAMS: {{QUERY_PARAMS}}`;
590
591
 
591
592
  // src/lib/browser.ts
592
593
  import open from "open";
593
- var PICA_APP_URL = "https://app.picaos.com";
594
+ var ONE_APP_URL = "https://app.withone.ai";
594
595
  function getConnectionUrl(platform) {
595
- return `${PICA_APP_URL}/connections?#open=${platform}`;
596
+ return `${ONE_APP_URL}/connections?#open=${platform}`;
596
597
  }
597
598
  function getApiKeyUrl() {
598
- return `${PICA_APP_URL}/settings/api-keys`;
599
+ return `${ONE_APP_URL}/settings/api-keys`;
599
600
  }
600
601
  async function openConnectionPage(platform) {
601
602
  const url = getConnectionUrl(platform);
@@ -616,7 +617,7 @@ function setAgentMode(value) {
616
617
  _agentMode = value;
617
618
  }
618
619
  function isAgentMode() {
619
- return _agentMode || process.env.PICA_AGENT === "1";
620
+ return _agentMode || process.env.ONE_AGENT === "1";
620
621
  }
621
622
  function createSpinner() {
622
623
  if (isAgentMode()) {
@@ -629,6 +630,12 @@ function createSpinner() {
629
630
  function intro2(msg) {
630
631
  if (!isAgentMode()) p.intro(msg);
631
632
  }
633
+ function outro2(msg) {
634
+ if (!isAgentMode()) p.outro(msg);
635
+ }
636
+ function note2(msg, title) {
637
+ if (!isAgentMode()) p.note(msg, title);
638
+ }
632
639
  function json(data) {
633
640
  process.stdout.write(JSON.stringify(data) + "\n");
634
641
  }
@@ -648,10 +655,10 @@ async function configCommand() {
648
655
  }
649
656
  const config = readConfig();
650
657
  if (!config) {
651
- p2.log.error(`No Pica config found. Run ${pc.cyan("pica init")} first.`);
658
+ p2.log.error(`No One config found. Run ${pc.cyan("one init")} first.`);
652
659
  return;
653
660
  }
654
- p2.intro(pc.bgCyan(pc.black(" Pica Access Control ")));
661
+ p2.intro(pc.bgCyan(pc.black(" One Access Control ")));
655
662
  const current = getAccessControl();
656
663
  console.log();
657
664
  console.log(` ${pc.bold("Current Access Control")}`);
@@ -694,7 +701,7 @@ async function configCommand() {
694
701
  return;
695
702
  }
696
703
  if (connectionKeys.length === 0) {
697
- p2.log.info(`No connections found. Defaulting to all. Use ${pc.cyan("pica add")} to connect platforms.`);
704
+ p2.log.info(`No connections found. Defaulting to all. Use ${pc.cyan("one add")} to connect platforms.`);
698
705
  connectionKeys = void 0;
699
706
  }
700
707
  }
@@ -765,7 +772,7 @@ async function selectConnections(apiKey) {
765
772
  spinner5.start("Fetching connections...");
766
773
  let connections;
767
774
  try {
768
- const api = new PicaApi(apiKey);
775
+ const api = new OneApi(apiKey);
769
776
  const rawConnections = await api.listConnections();
770
777
  connections = rawConnections.map((c) => ({ platform: c.platform, key: c.key }));
771
778
  spinner5.stop(`Found ${connections.length} connection(s)`);
@@ -848,7 +855,7 @@ async function initCommand(options) {
848
855
  }
849
856
  const existingConfig = readConfig();
850
857
  if (existingConfig) {
851
- p3.intro(pc3.bgCyan(pc3.black(" Pica ")));
858
+ p3.intro(pc3.bgCyan(pc3.black(" One ")));
852
859
  await handleExistingConfig(existingConfig.apiKey, options);
853
860
  return;
854
861
  }
@@ -961,7 +968,7 @@ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
961
968
  await openApiKeyPage();
962
969
  }
963
970
  const newKey = await p3.text({
964
- message: "Enter your new Pica API key:",
971
+ message: "Enter your new One API key:",
965
972
  placeholder: "sk_live_...",
966
973
  validate: (value) => {
967
974
  if (!value) return "API key is required";
@@ -977,7 +984,7 @@ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
977
984
  }
978
985
  const spinner5 = p3.spinner();
979
986
  spinner5.start("Validating API key...");
980
- const api = new PicaApi(newKey);
987
+ const api = new OneApi(newKey);
981
988
  const isValid = await api.validateApiKey();
982
989
  if (!isValid) {
983
990
  spinner5.stop("Invalid API key");
@@ -1014,7 +1021,7 @@ async function handleInstallMore(apiKey, missing) {
1014
1021
  if (missing.length === 1) {
1015
1022
  const agent = missing[0].agent;
1016
1023
  const confirm3 = await p3.confirm({
1017
- message: `Install Pica MCP to ${agent.name}?`,
1024
+ message: `Install One MCP to ${agent.name}?`,
1018
1025
  initialValue: true
1019
1026
  });
1020
1027
  if (p3.isCancel(confirm3) || !confirm3) {
@@ -1106,7 +1113,7 @@ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
1106
1113
  await openApiKeyPage();
1107
1114
  }
1108
1115
  const apiKey = await p3.text({
1109
- message: "Enter your Pica API key:",
1116
+ message: "Enter your One API key:",
1110
1117
  placeholder: "sk_live_...",
1111
1118
  validate: (value) => {
1112
1119
  if (!value) return "API key is required";
@@ -1122,7 +1129,7 @@ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
1122
1129
  }
1123
1130
  const spinner5 = p3.spinner();
1124
1131
  spinner5.start("Validating API key...");
1125
- const api = new PicaApi(apiKey);
1132
+ const api = new OneApi(apiKey);
1126
1133
  const isValid = await api.validateApiKey();
1127
1134
  if (!isValid) {
1128
1135
  spinner5.stop("Invalid API key");
@@ -1234,7 +1241,7 @@ ${globalPaths}
1234
1241
  summary += pc3.yellow("Note: Project config files can be committed to share with your team.\n") + pc3.yellow("Team members will need their own API key.");
1235
1242
  p3.note(summary, "Setup Complete");
1236
1243
  await promptConnectIntegrations(apiKey);
1237
- p3.outro("Your AI agents now have access to Pica integrations!");
1244
+ p3.outro("Your AI agents now have access to One integrations!");
1238
1245
  return;
1239
1246
  }
1240
1247
  const installedAgentIds = [];
@@ -1255,7 +1262,7 @@ ${globalPaths}
1255
1262
  "Setup Complete"
1256
1263
  );
1257
1264
  await promptConnectIntegrations(apiKey);
1258
- p3.outro("Your AI agents now have access to Pica integrations!");
1265
+ p3.outro("Your AI agents now have access to One integrations!");
1259
1266
  }
1260
1267
  function printBanner() {
1261
1268
  console.log();
@@ -1279,7 +1286,7 @@ var TOP_INTEGRATIONS = [
1279
1286
  { value: "notion", label: "Notion", hint: "Access pages, databases, and docs" }
1280
1287
  ];
1281
1288
  async function promptConnectIntegrations(apiKey) {
1282
- const api = new PicaApi(apiKey);
1289
+ const api = new OneApi(apiKey);
1283
1290
  const connected = [];
1284
1291
  try {
1285
1292
  const existing = await api.listConnections();
@@ -1301,7 +1308,7 @@ async function promptConnectIntegrations(apiKey) {
1301
1308
  hint: i.hint
1302
1309
  })),
1303
1310
  { value: "more", label: "Browse all 200+ platforms" },
1304
- { value: "skip", label: "Skip for now", hint: "you can always run pica add later" }
1311
+ { value: "skip", label: "Skip for now", hint: "you can always run one add later" }
1305
1312
  ];
1306
1313
  const message = first ? "Connect your first integration?" : "Connect another?";
1307
1314
  const choice = await p3.select({ message, options });
@@ -1310,12 +1317,12 @@ async function promptConnectIntegrations(apiKey) {
1310
1317
  }
1311
1318
  if (choice === "more") {
1312
1319
  try {
1313
- await open2("https://app.picaos.com/connections");
1314
- p3.log.info("Opened Pica dashboard in browser.");
1320
+ await open2("https://app.withone.ai/connections");
1321
+ p3.log.info("Opened One dashboard in browser.");
1315
1322
  } catch {
1316
- p3.note("https://app.picaos.com/connections", "Open in browser");
1323
+ p3.note("https://app.withone.ai/connections", "Open in browser");
1317
1324
  }
1318
- p3.log.info(`Connect from the dashboard, or use ${pc3.cyan("pica add <platform>")}`);
1325
+ p3.log.info(`Connect from the dashboard, or use ${pc3.cyan("one add <platform>")}`);
1319
1326
  break;
1320
1327
  }
1321
1328
  const platform = choice;
@@ -1340,7 +1347,7 @@ async function promptConnectIntegrations(apiKey) {
1340
1347
  } catch (error2) {
1341
1348
  spinner5.stop("Connection timed out");
1342
1349
  if (error2 instanceof TimeoutError) {
1343
- p3.log.warn(`No worries. Connect later with: ${pc3.cyan(`pica add ${platform}`)}`);
1350
+ p3.log.warn(`No worries. Connect later with: ${pc3.cyan(`one add ${platform}`)}`);
1344
1351
  }
1345
1352
  first = false;
1346
1353
  }
@@ -1407,13 +1414,13 @@ async function connectionAddCommand(platformArg) {
1407
1414
  if (isAgentMode()) {
1408
1415
  error("This command requires interactive input. Run without --agent.");
1409
1416
  }
1410
- p4.intro(pc4.bgCyan(pc4.black(" Pica ")));
1417
+ p4.intro(pc4.bgCyan(pc4.black(" One ")));
1411
1418
  const apiKey = getApiKey();
1412
1419
  if (!apiKey) {
1413
- p4.cancel("Not configured. Run `pica init` first.");
1420
+ p4.cancel("Not configured. Run `one init` first.");
1414
1421
  process.exit(1);
1415
1422
  }
1416
- const api = new PicaApi(apiKey);
1423
+ const api = new OneApi(apiKey);
1417
1424
  const spinner5 = p4.spinner();
1418
1425
  spinner5.start("Loading platforms...");
1419
1426
  let platforms;
@@ -1442,7 +1449,7 @@ async function connectionAddCommand(platformArg) {
1442
1449
  ]
1443
1450
  });
1444
1451
  if (p4.isCancel(suggestion) || suggestion === "__other__") {
1445
- p4.note(`Run ${pc4.cyan("pica platforms")} to see all available platforms.`);
1452
+ p4.note(`Run ${pc4.cyan("one platforms")} to see all available platforms.`);
1446
1453
  p4.cancel("Connection cancelled.");
1447
1454
  process.exit(0);
1448
1455
  }
@@ -1450,7 +1457,7 @@ async function connectionAddCommand(platformArg) {
1450
1457
  } else {
1451
1458
  p4.cancel(`Unknown platform: ${platformArg}
1452
1459
 
1453
- Run ${pc4.cyan("pica platforms")} to see available platforms.`);
1460
+ Run ${pc4.cyan("one platforms")} to see available platforms.`);
1454
1461
  process.exit(1);
1455
1462
  }
1456
1463
  }
@@ -1473,7 +1480,7 @@ Run ${pc4.cyan("pica platforms")} to see available platforms.`);
1473
1480
  } else {
1474
1481
  p4.cancel(`Unknown platform: ${platformInput}
1475
1482
 
1476
- Run ${pc4.cyan("pica platforms")} to see available platforms.`);
1483
+ Run ${pc4.cyan("one platforms")} to see available platforms.`);
1477
1484
  process.exit(1);
1478
1485
  }
1479
1486
  }
@@ -1503,7 +1510,7 @@ ${url}`);
1503
1510
  - Browser popup was blocked
1504
1511
  - Wrong account selected
1505
1512
 
1506
- Try again with: ${pc4.cyan(`pica connection add ${platform}`)}`,
1513
+ Try again with: ${pc4.cyan(`one connection add ${platform}`)}`,
1507
1514
  "Timed Out"
1508
1515
  );
1509
1516
  } else {
@@ -1515,9 +1522,9 @@ Try again with: ${pc4.cyan(`pica connection add ${platform}`)}`,
1515
1522
  async function connectionListCommand() {
1516
1523
  const apiKey = getApiKey();
1517
1524
  if (!apiKey) {
1518
- error("Not configured. Run `pica init` first.");
1525
+ error("Not configured. Run `one init` first.");
1519
1526
  }
1520
- const api = new PicaApi(apiKey);
1527
+ const api = new OneApi(apiKey);
1521
1528
  const spinner5 = createSpinner();
1522
1529
  spinner5.start("Loading connections...");
1523
1530
  try {
@@ -1537,7 +1544,7 @@ async function connectionListCommand() {
1537
1544
  p4.note(
1538
1545
  `No connections yet.
1539
1546
 
1540
- Add one with: ${pc4.cyan("pica connection add gmail")}`,
1547
+ Add one with: ${pc4.cyan("one connection add gmail")}`,
1541
1548
  "No Connections"
1542
1549
  );
1543
1550
  return;
@@ -1559,7 +1566,7 @@ Add one with: ${pc4.cyan("pica connection add gmail")}`,
1559
1566
  rows
1560
1567
  );
1561
1568
  console.log();
1562
- p4.note(`Add more with: ${pc4.cyan("pica connection add <platform>")}`, "Tip");
1569
+ p4.note(`Add more with: ${pc4.cyan("one connection add <platform>")}`, "Tip");
1563
1570
  } catch (error2) {
1564
1571
  spinner5.stop("Failed to load connections");
1565
1572
  error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
@@ -1584,12 +1591,12 @@ import pc5 from "picocolors";
1584
1591
  async function platformsCommand(options) {
1585
1592
  const apiKey = getApiKey();
1586
1593
  if (!apiKey) {
1587
- error("Not configured. Run `pica init` first.");
1594
+ error("Not configured. Run `one init` first.");
1588
1595
  }
1589
1596
  if (isAgentMode()) {
1590
1597
  options.json = true;
1591
1598
  }
1592
- const api = new PicaApi(apiKey);
1599
+ const api = new OneApi(apiKey);
1593
1600
  const spinner5 = createSpinner();
1594
1601
  spinner5.start("Loading platforms...");
1595
1602
  try {
@@ -1648,7 +1655,7 @@ async function platformsCommand(options) {
1648
1655
  );
1649
1656
  }
1650
1657
  console.log();
1651
- p5.note(`Connect with: ${pc5.cyan("pica connection add <platform>")}`, "Tip");
1658
+ p5.note(`Connect with: ${pc5.cyan("one connection add <platform>")}`, "Tip");
1652
1659
  } catch (error2) {
1653
1660
  spinner5.stop("Failed to load platforms");
1654
1661
  error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
@@ -1661,7 +1668,7 @@ import pc6 from "picocolors";
1661
1668
  function getConfig() {
1662
1669
  const apiKey = getApiKey();
1663
1670
  if (!apiKey) {
1664
- error("Not configured. Run `pica init` first.");
1671
+ error("Not configured. Run `one init` first.");
1665
1672
  }
1666
1673
  const ac = getAccessControlFromAllSources();
1667
1674
  const permissions = ac.permissions || "admin";
@@ -1678,9 +1685,9 @@ function parseJsonArg(value, argName) {
1678
1685
  }
1679
1686
  }
1680
1687
  async function actionsSearchCommand(platform, query, options) {
1681
- intro2(pc6.bgCyan(pc6.black(" Pica ")));
1688
+ intro2(pc6.bgCyan(pc6.black(" One ")));
1682
1689
  const { apiKey, permissions, actionIds, knowledgeAgent } = getConfig();
1683
- const api = new PicaApi(apiKey);
1690
+ const api = new OneApi(apiKey);
1684
1691
  const spinner5 = createSpinner();
1685
1692
  spinner5.start(`Searching actions on ${pc6.cyan(platform)} for "${query}"...`);
1686
1693
  try {
@@ -1706,7 +1713,7 @@ async function actionsSearchCommand(platform, query, options) {
1706
1713
  Suggestions:
1707
1714
  - Try a more general query (e.g., 'list', 'get', 'search', 'create')
1708
1715
  - Verify the platform name is correct
1709
- - Check available platforms with ${pc6.cyan("pica platforms")}
1716
+ - Check available platforms with ${pc6.cyan("one platforms")}
1710
1717
 
1711
1718
  Examples of good queries:
1712
1719
  - "search contacts"
@@ -1738,8 +1745,8 @@ Examples of good queries:
1738
1745
  );
1739
1746
  console.log();
1740
1747
  p6.note(
1741
- `Get details: ${pc6.cyan(`pica actions knowledge ${platform} <actionId>`)}
1742
- Execute: ${pc6.cyan(`pica actions execute ${platform} <actionId> <connectionKey>`)}`,
1748
+ `Get details: ${pc6.cyan(`one actions knowledge ${platform} <actionId>`)}
1749
+ Execute: ${pc6.cyan(`one actions execute ${platform} <actionId> <connectionKey>`)}`,
1743
1750
  "Next Steps"
1744
1751
  );
1745
1752
  } catch (error2) {
@@ -1750,9 +1757,9 @@ Execute: ${pc6.cyan(`pica actions execute ${platform} <actionId> <connection
1750
1757
  }
1751
1758
  }
1752
1759
  async function actionsKnowledgeCommand(platform, actionId) {
1753
- intro2(pc6.bgCyan(pc6.black(" Pica ")));
1760
+ intro2(pc6.bgCyan(pc6.black(" One ")));
1754
1761
  const { apiKey, actionIds, connectionKeys } = getConfig();
1755
- const api = new PicaApi(apiKey);
1762
+ const api = new OneApi(apiKey);
1756
1763
  if (!isActionAllowed(actionId, actionIds)) {
1757
1764
  error(`Action "${actionId}" is not in the allowed action list.`);
1758
1765
  }
@@ -1793,7 +1800,7 @@ async function actionsKnowledgeCommand(platform, actionId) {
1793
1800
  console.log(knowledgeWithGuidance);
1794
1801
  console.log();
1795
1802
  p6.note(
1796
- `Execute: ${pc6.cyan(`pica actions execute ${platform} ${actionId} <connectionKey>`)}`,
1803
+ `Execute: ${pc6.cyan(`one actions execute ${platform} ${actionId} <connectionKey>`)}`,
1797
1804
  "Next Step"
1798
1805
  );
1799
1806
  } catch (error2) {
@@ -1804,7 +1811,7 @@ async function actionsKnowledgeCommand(platform, actionId) {
1804
1811
  }
1805
1812
  }
1806
1813
  async function actionsExecuteCommand(platform, actionId, connectionKey, options) {
1807
- intro2(pc6.bgCyan(pc6.black(" Pica ")));
1814
+ intro2(pc6.bgCyan(pc6.black(" One ")));
1808
1815
  const { apiKey, permissions, actionIds, connectionKeys, knowledgeAgent } = getConfig();
1809
1816
  if (knowledgeAgent) {
1810
1817
  error(
@@ -1817,7 +1824,7 @@ async function actionsExecuteCommand(platform, actionId, connectionKey, options)
1817
1824
  if (!connectionKeys.includes("*") && !connectionKeys.includes(connectionKey)) {
1818
1825
  error(`Connection key "${connectionKey}" is not allowed.`);
1819
1826
  }
1820
- const api = new PicaApi(apiKey);
1827
+ const api = new OneApi(apiKey);
1821
1828
  const spinner5 = createSpinner();
1822
1829
  spinner5.start("Loading action details...");
1823
1830
  try {
@@ -1894,50 +1901,2172 @@ function colorMethod(method) {
1894
1901
  }
1895
1902
  }
1896
1903
 
1897
- // src/index.ts
1898
- var require2 = createRequire(import.meta.url);
1899
- var { version } = require2("../package.json");
1900
- var program = new Command();
1901
- program.name("pica").option("--agent", "Machine-readable JSON output (no colors, spinners, or prompts)").description(`Pica CLI \u2014 Connect AI agents to 200+ platforms through one interface.
1902
-
1903
- Setup:
1904
- pica init Set up API key and install MCP server
1905
- pica add <platform> Connect a platform via OAuth (e.g. gmail, slack, shopify)
1906
- pica config Configure access control (permissions, scoping)
1907
-
1908
- Workflow (use these in order):
1909
- 1. pica list List your connected platforms and connection keys
1910
- 2. pica actions search <platform> <q> Search for actions using natural language
1911
- 3. pica actions knowledge <plat> <id> Get full docs for an action (ALWAYS do this before execute)
1912
- 4. pica actions execute <p> <id> <key> Execute the action
1904
+ // src/commands/flow.ts
1905
+ import pc7 from "picocolors";
1913
1906
 
1914
- Example \u2014 send an email through Gmail:
1915
- $ pica list
1916
- # Find: gmail operational live::gmail::default::abc123
1907
+ // src/lib/flow-validator.ts
1908
+ var VALID_STEP_TYPES = [
1909
+ "action",
1910
+ "transform",
1911
+ "code",
1912
+ "condition",
1913
+ "loop",
1914
+ "parallel",
1915
+ "file-read",
1916
+ "file-write"
1917
+ ];
1918
+ var VALID_INPUT_TYPES = ["string", "number", "boolean", "object", "array"];
1919
+ var VALID_ERROR_STRATEGIES = ["fail", "continue", "retry", "fallback"];
1920
+ function validateFlowSchema(flow2) {
1921
+ const errors = [];
1922
+ if (!flow2 || typeof flow2 !== "object") {
1923
+ errors.push({ path: "", message: "Flow must be a JSON object" });
1924
+ return errors;
1925
+ }
1926
+ const f = flow2;
1927
+ if (!f.key || typeof f.key !== "string") {
1928
+ errors.push({ path: "key", message: 'Flow must have a string "key"' });
1929
+ } else if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(f.key) && f.key.length > 1) {
1930
+ errors.push({ path: "key", message: "Flow key must be kebab-case (lowercase letters, numbers, hyphens)" });
1931
+ }
1932
+ if (!f.name || typeof f.name !== "string") {
1933
+ errors.push({ path: "name", message: 'Flow must have a string "name"' });
1934
+ }
1935
+ if (f.description !== void 0 && typeof f.description !== "string") {
1936
+ errors.push({ path: "description", message: '"description" must be a string' });
1937
+ }
1938
+ if (f.version !== void 0 && typeof f.version !== "string") {
1939
+ errors.push({ path: "version", message: '"version" must be a string' });
1940
+ }
1941
+ if (!f.inputs || typeof f.inputs !== "object" || Array.isArray(f.inputs)) {
1942
+ errors.push({ path: "inputs", message: 'Flow must have an "inputs" object' });
1943
+ } else {
1944
+ const inputs = f.inputs;
1945
+ for (const [name, decl] of Object.entries(inputs)) {
1946
+ const prefix = `inputs.${name}`;
1947
+ if (!decl || typeof decl !== "object" || Array.isArray(decl)) {
1948
+ errors.push({ path: prefix, message: "Input declaration must be an object" });
1949
+ continue;
1950
+ }
1951
+ const d = decl;
1952
+ if (!d.type || !VALID_INPUT_TYPES.includes(d.type)) {
1953
+ errors.push({ path: `${prefix}.type`, message: `Input type must be one of: ${VALID_INPUT_TYPES.join(", ")}` });
1954
+ }
1955
+ if (d.connection !== void 0) {
1956
+ if (!d.connection || typeof d.connection !== "object") {
1957
+ errors.push({ path: `${prefix}.connection`, message: "Connection metadata must be an object" });
1958
+ } else {
1959
+ const conn = d.connection;
1960
+ if (!conn.platform || typeof conn.platform !== "string") {
1961
+ errors.push({ path: `${prefix}.connection.platform`, message: 'Connection must have a string "platform"' });
1962
+ }
1963
+ }
1964
+ }
1965
+ }
1966
+ }
1967
+ if (!Array.isArray(f.steps)) {
1968
+ errors.push({ path: "steps", message: 'Flow must have a "steps" array' });
1969
+ } else {
1970
+ validateStepsArray(f.steps, "steps", errors);
1971
+ }
1972
+ return errors;
1973
+ }
1974
+ function validateStepsArray(steps, pathPrefix, errors) {
1975
+ for (let i = 0; i < steps.length; i++) {
1976
+ const step = steps[i];
1977
+ const path5 = `${pathPrefix}[${i}]`;
1978
+ if (!step || typeof step !== "object" || Array.isArray(step)) {
1979
+ errors.push({ path: path5, message: "Step must be an object" });
1980
+ continue;
1981
+ }
1982
+ const s = step;
1983
+ if (!s.id || typeof s.id !== "string") {
1984
+ errors.push({ path: `${path5}.id`, message: 'Step must have a string "id"' });
1985
+ }
1986
+ if (!s.name || typeof s.name !== "string") {
1987
+ errors.push({ path: `${path5}.name`, message: 'Step must have a string "name"' });
1988
+ }
1989
+ if (!s.type || !VALID_STEP_TYPES.includes(s.type)) {
1990
+ errors.push({ path: `${path5}.type`, message: `Step type must be one of: ${VALID_STEP_TYPES.join(", ")}` });
1991
+ }
1992
+ if (s.onError && typeof s.onError === "object") {
1993
+ const oe = s.onError;
1994
+ if (!VALID_ERROR_STRATEGIES.includes(oe.strategy)) {
1995
+ errors.push({ path: `${path5}.onError.strategy`, message: `Error strategy must be one of: ${VALID_ERROR_STRATEGIES.join(", ")}` });
1996
+ }
1997
+ }
1998
+ const type = s.type;
1999
+ if (type === "action") {
2000
+ if (!s.action || typeof s.action !== "object") {
2001
+ errors.push({ path: `${path5}.action`, message: 'Action step must have an "action" config object' });
2002
+ } else {
2003
+ const a = s.action;
2004
+ if (!a.platform) errors.push({ path: `${path5}.action.platform`, message: 'Action must have "platform"' });
2005
+ if (!a.actionId) errors.push({ path: `${path5}.action.actionId`, message: 'Action must have "actionId"' });
2006
+ if (!a.connectionKey) errors.push({ path: `${path5}.action.connectionKey`, message: 'Action must have "connectionKey"' });
2007
+ }
2008
+ } else if (type === "transform") {
2009
+ if (!s.transform || typeof s.transform !== "object") {
2010
+ errors.push({ path: `${path5}.transform`, message: 'Transform step must have a "transform" config object' });
2011
+ } else {
2012
+ const t = s.transform;
2013
+ if (!t.expression || typeof t.expression !== "string") {
2014
+ errors.push({ path: `${path5}.transform.expression`, message: 'Transform must have a string "expression"' });
2015
+ }
2016
+ }
2017
+ } else if (type === "code") {
2018
+ if (!s.code || typeof s.code !== "object") {
2019
+ errors.push({ path: `${path5}.code`, message: 'Code step must have a "code" config object' });
2020
+ } else {
2021
+ const c = s.code;
2022
+ if (!c.source || typeof c.source !== "string") {
2023
+ errors.push({ path: `${path5}.code.source`, message: 'Code must have a string "source"' });
2024
+ }
2025
+ }
2026
+ } else if (type === "condition") {
2027
+ if (!s.condition || typeof s.condition !== "object") {
2028
+ errors.push({ path: `${path5}.condition`, message: 'Condition step must have a "condition" config object' });
2029
+ } else {
2030
+ const c = s.condition;
2031
+ if (!c.expression || typeof c.expression !== "string") {
2032
+ errors.push({ path: `${path5}.condition.expression`, message: 'Condition must have a string "expression"' });
2033
+ }
2034
+ if (!Array.isArray(c.then)) {
2035
+ errors.push({ path: `${path5}.condition.then`, message: 'Condition must have a "then" steps array' });
2036
+ } else {
2037
+ validateStepsArray(c.then, `${path5}.condition.then`, errors);
2038
+ }
2039
+ if (c.else !== void 0) {
2040
+ if (!Array.isArray(c.else)) {
2041
+ errors.push({ path: `${path5}.condition.else`, message: 'Condition "else" must be a steps array' });
2042
+ } else {
2043
+ validateStepsArray(c.else, `${path5}.condition.else`, errors);
2044
+ }
2045
+ }
2046
+ }
2047
+ } else if (type === "loop") {
2048
+ if (!s.loop || typeof s.loop !== "object") {
2049
+ errors.push({ path: `${path5}.loop`, message: 'Loop step must have a "loop" config object' });
2050
+ } else {
2051
+ const l = s.loop;
2052
+ if (!l.over || typeof l.over !== "string") {
2053
+ errors.push({ path: `${path5}.loop.over`, message: 'Loop must have a string "over" selector' });
2054
+ }
2055
+ if (!l.as || typeof l.as !== "string") {
2056
+ errors.push({ path: `${path5}.loop.as`, message: 'Loop must have a string "as" variable name' });
2057
+ }
2058
+ if (!Array.isArray(l.steps)) {
2059
+ errors.push({ path: `${path5}.loop.steps`, message: 'Loop must have a "steps" array' });
2060
+ } else {
2061
+ validateStepsArray(l.steps, `${path5}.loop.steps`, errors);
2062
+ }
2063
+ }
2064
+ } else if (type === "parallel") {
2065
+ if (!s.parallel || typeof s.parallel !== "object") {
2066
+ errors.push({ path: `${path5}.parallel`, message: 'Parallel step must have a "parallel" config object' });
2067
+ } else {
2068
+ const par = s.parallel;
2069
+ if (!Array.isArray(par.steps)) {
2070
+ errors.push({ path: `${path5}.parallel.steps`, message: 'Parallel must have a "steps" array' });
2071
+ } else {
2072
+ validateStepsArray(par.steps, `${path5}.parallel.steps`, errors);
2073
+ }
2074
+ }
2075
+ } else if (type === "file-read") {
2076
+ if (!s.fileRead || typeof s.fileRead !== "object") {
2077
+ errors.push({ path: `${path5}.fileRead`, message: 'File-read step must have a "fileRead" config object' });
2078
+ } else {
2079
+ const fr = s.fileRead;
2080
+ if (!fr.path || typeof fr.path !== "string") {
2081
+ errors.push({ path: `${path5}.fileRead.path`, message: 'File-read must have a string "path"' });
2082
+ }
2083
+ }
2084
+ } else if (type === "file-write") {
2085
+ if (!s.fileWrite || typeof s.fileWrite !== "object") {
2086
+ errors.push({ path: `${path5}.fileWrite`, message: 'File-write step must have a "fileWrite" config object' });
2087
+ } else {
2088
+ const fw = s.fileWrite;
2089
+ if (!fw.path || typeof fw.path !== "string") {
2090
+ errors.push({ path: `${path5}.fileWrite.path`, message: 'File-write must have a string "path"' });
2091
+ }
2092
+ if (fw.content === void 0) {
2093
+ errors.push({ path: `${path5}.fileWrite.content`, message: 'File-write must have "content"' });
2094
+ }
2095
+ }
2096
+ }
2097
+ }
2098
+ }
2099
+ function validateStepIds(flow2) {
2100
+ const errors = [];
2101
+ const seen = /* @__PURE__ */ new Set();
2102
+ function collectIds(steps, pathPrefix) {
2103
+ for (let i = 0; i < steps.length; i++) {
2104
+ const step = steps[i];
2105
+ const path5 = `${pathPrefix}[${i}]`;
2106
+ if (seen.has(step.id)) {
2107
+ errors.push({ path: `${path5}.id`, message: `Duplicate step ID: "${step.id}"` });
2108
+ } else {
2109
+ seen.add(step.id);
2110
+ }
2111
+ if (step.condition) {
2112
+ if (step.condition.then) collectIds(step.condition.then, `${path5}.condition.then`);
2113
+ if (step.condition.else) collectIds(step.condition.else, `${path5}.condition.else`);
2114
+ }
2115
+ if (step.loop?.steps) collectIds(step.loop.steps, `${path5}.loop.steps`);
2116
+ if (step.parallel?.steps) collectIds(step.parallel.steps, `${path5}.parallel.steps`);
2117
+ }
2118
+ }
2119
+ collectIds(flow2.steps, "steps");
2120
+ return errors;
2121
+ }
2122
+ function validateSelectorReferences(flow2) {
2123
+ const errors = [];
2124
+ const inputNames = new Set(Object.keys(flow2.inputs));
2125
+ function getAllStepIds(steps) {
2126
+ const ids = /* @__PURE__ */ new Set();
2127
+ for (const step of steps) {
2128
+ ids.add(step.id);
2129
+ if (step.condition) {
2130
+ for (const id of getAllStepIds(step.condition.then)) ids.add(id);
2131
+ if (step.condition.else) {
2132
+ for (const id of getAllStepIds(step.condition.else)) ids.add(id);
2133
+ }
2134
+ }
2135
+ if (step.loop?.steps) {
2136
+ for (const id of getAllStepIds(step.loop.steps)) ids.add(id);
2137
+ }
2138
+ if (step.parallel?.steps) {
2139
+ for (const id of getAllStepIds(step.parallel.steps)) ids.add(id);
2140
+ }
2141
+ }
2142
+ return ids;
2143
+ }
2144
+ const allStepIds = getAllStepIds(flow2.steps);
2145
+ function extractSelectors(value) {
2146
+ const selectors = [];
2147
+ if (typeof value === "string") {
2148
+ if (value.startsWith("$.")) {
2149
+ selectors.push(value);
2150
+ }
2151
+ const interpolated = value.matchAll(/\{\{(\$\.[^}]+)\}\}/g);
2152
+ for (const match of interpolated) {
2153
+ selectors.push(match[1]);
2154
+ }
2155
+ } else if (Array.isArray(value)) {
2156
+ for (const item of value) {
2157
+ selectors.push(...extractSelectors(item));
2158
+ }
2159
+ } else if (value && typeof value === "object") {
2160
+ for (const v of Object.values(value)) {
2161
+ selectors.push(...extractSelectors(v));
2162
+ }
2163
+ }
2164
+ return selectors;
2165
+ }
2166
+ function checkSelectors(selectors, path5) {
2167
+ for (const selector of selectors) {
2168
+ const parts = selector.split(".");
2169
+ if (parts.length < 3) continue;
2170
+ const root = parts[1];
2171
+ if (root === "input") {
2172
+ const inputName = parts[2];
2173
+ if (!inputNames.has(inputName)) {
2174
+ errors.push({ path: path5, message: `Selector "${selector}" references undefined input "${inputName}"` });
2175
+ }
2176
+ } else if (root === "steps") {
2177
+ const stepId = parts[2];
2178
+ if (!allStepIds.has(stepId)) {
2179
+ errors.push({ path: path5, message: `Selector "${selector}" references undefined step "${stepId}"` });
2180
+ }
2181
+ }
2182
+ }
2183
+ }
2184
+ function checkStep(step, pathPrefix) {
2185
+ if (step.if) checkSelectors(extractSelectors(step.if), `${pathPrefix}.if`);
2186
+ if (step.unless) checkSelectors(extractSelectors(step.unless), `${pathPrefix}.unless`);
2187
+ if (step.action) {
2188
+ checkSelectors(extractSelectors(step.action), `${pathPrefix}.action`);
2189
+ }
2190
+ if (step.transform) {
2191
+ }
2192
+ if (step.condition) {
2193
+ checkStep({ id: "__cond_expr", name: "", type: "transform", transform: { expression: "" } }, pathPrefix);
2194
+ step.condition.then.forEach((s, i) => checkStep(s, `${pathPrefix}.condition.then[${i}]`));
2195
+ step.condition.else?.forEach((s, i) => checkStep(s, `${pathPrefix}.condition.else[${i}]`));
2196
+ }
2197
+ if (step.loop) {
2198
+ checkSelectors(extractSelectors(step.loop.over), `${pathPrefix}.loop.over`);
2199
+ step.loop.steps.forEach((s, i) => checkStep(s, `${pathPrefix}.loop.steps[${i}]`));
2200
+ }
2201
+ if (step.parallel) {
2202
+ step.parallel.steps.forEach((s, i) => checkStep(s, `${pathPrefix}.parallel.steps[${i}]`));
2203
+ }
2204
+ if (step.fileRead) {
2205
+ checkSelectors(extractSelectors(step.fileRead.path), `${pathPrefix}.fileRead.path`);
2206
+ }
2207
+ if (step.fileWrite) {
2208
+ checkSelectors(extractSelectors(step.fileWrite.path), `${pathPrefix}.fileWrite.path`);
2209
+ checkSelectors(extractSelectors(step.fileWrite.content), `${pathPrefix}.fileWrite.content`);
2210
+ }
2211
+ }
2212
+ flow2.steps.forEach((step, i) => checkStep(step, `steps[${i}]`));
2213
+ return errors;
2214
+ }
2215
+ function validateFlow(flow2) {
2216
+ const schemaErrors = validateFlowSchema(flow2);
2217
+ if (schemaErrors.length > 0) return schemaErrors;
2218
+ const f = flow2;
2219
+ return [
2220
+ ...validateStepIds(f),
2221
+ ...validateSelectorReferences(f)
2222
+ ];
2223
+ }
1917
2224
 
1918
- $ pica actions search gmail "send email" -t execute
1919
- # Find: POST Send Email conn_mod_def::xxx::yyy
2225
+ // src/lib/flow-runner.ts
2226
+ import fs4 from "fs";
2227
+ import path4 from "path";
2228
+ import crypto from "crypto";
1920
2229
 
1921
- $ pica actions knowledge gmail conn_mod_def::xxx::yyy
1922
- # Read the docs: required fields are to, subject, body, connectionKey
2230
+ // src/lib/flow-engine.ts
2231
+ import fs3 from "fs";
2232
+ import path3 from "path";
2233
+ function sleep2(ms) {
2234
+ return new Promise((resolve) => setTimeout(resolve, ms));
2235
+ }
2236
+ function resolveSelector(selectorPath, context) {
2237
+ if (!selectorPath.startsWith("$.")) return selectorPath;
2238
+ const parts = selectorPath.slice(2).split(/\.|\[/).map((p7) => p7.replace(/\]$/, ""));
2239
+ let current = context;
2240
+ for (const part of parts) {
2241
+ if (current === null || current === void 0) return void 0;
2242
+ if (part === "*" && Array.isArray(current)) {
2243
+ continue;
2244
+ }
2245
+ if (Array.isArray(current) && part === "*") {
2246
+ continue;
2247
+ }
2248
+ if (Array.isArray(current)) {
2249
+ const idx = Number(part);
2250
+ if (!isNaN(idx)) {
2251
+ current = current[idx];
2252
+ } else {
2253
+ current = current.map((item) => item?.[part]);
2254
+ }
2255
+ } else if (typeof current === "object") {
2256
+ current = current[part];
2257
+ } else {
2258
+ return void 0;
2259
+ }
2260
+ }
2261
+ return current;
2262
+ }
2263
+ function interpolateString(str, context) {
2264
+ return str.replace(/\{\{(\$\.[^}]+)\}\}/g, (_match, selector) => {
2265
+ const value = resolveSelector(selector, context);
2266
+ if (value === void 0 || value === null) return "";
2267
+ if (typeof value === "object") return JSON.stringify(value);
2268
+ return String(value);
2269
+ });
2270
+ }
2271
+ function resolveValue(value, context) {
2272
+ if (typeof value === "string") {
2273
+ if (value.startsWith("$.") && !value.includes("{{")) {
2274
+ return resolveSelector(value, context);
2275
+ }
2276
+ if (value.includes("{{$.")) {
2277
+ return interpolateString(value, context);
2278
+ }
2279
+ return value;
2280
+ }
2281
+ if (Array.isArray(value)) {
2282
+ return value.map((item) => resolveValue(item, context));
2283
+ }
2284
+ if (value && typeof value === "object") {
2285
+ const resolved = {};
2286
+ for (const [k, v] of Object.entries(value)) {
2287
+ resolved[k] = resolveValue(v, context);
2288
+ }
2289
+ return resolved;
2290
+ }
2291
+ return value;
2292
+ }
2293
+ function evaluateExpression(expr, context) {
2294
+ const fn = new Function("$", `return (${expr})`);
2295
+ return fn(context);
2296
+ }
2297
+ async function executeActionStep(step, context, api, permissions, allowedActionIds) {
2298
+ const action = step.action;
2299
+ const platform = resolveValue(action.platform, context);
2300
+ const actionId = resolveValue(action.actionId, context);
2301
+ const connectionKey = resolveValue(action.connectionKey, context);
2302
+ const data = action.data ? resolveValue(action.data, context) : void 0;
2303
+ const pathVars = action.pathVars ? resolveValue(action.pathVars, context) : void 0;
2304
+ const queryParams = action.queryParams ? resolveValue(action.queryParams, context) : void 0;
2305
+ const headers = action.headers ? resolveValue(action.headers, context) : void 0;
2306
+ if (!isActionAllowed(actionId, allowedActionIds)) {
2307
+ throw new Error(`Action "${actionId}" is not in the allowed action list`);
2308
+ }
2309
+ const actionDetails = await api.getActionDetails(actionId);
2310
+ if (!isMethodAllowed(actionDetails.method, permissions)) {
2311
+ throw new Error(`Method "${actionDetails.method}" is not allowed under "${permissions}" permission level`);
2312
+ }
2313
+ const result = await api.executePassthroughRequest({
2314
+ platform,
2315
+ actionId,
2316
+ connectionKey,
2317
+ data,
2318
+ pathVariables: pathVars,
2319
+ queryParams,
2320
+ headers
2321
+ }, actionDetails);
2322
+ return {
2323
+ status: "success",
2324
+ response: result.responseData,
2325
+ output: result.responseData
2326
+ };
2327
+ }
2328
+ function executeTransformStep(step, context) {
2329
+ const output = evaluateExpression(step.transform.expression, context);
2330
+ return { status: "success", output, response: output };
2331
+ }
2332
+ async function executeCodeStep(step, context) {
2333
+ const source = step.code.source;
2334
+ const AsyncFunction = Object.getPrototypeOf(async function() {
2335
+ }).constructor;
2336
+ const fn = new AsyncFunction("$", source);
2337
+ const output = await fn(context);
2338
+ return { status: "success", output, response: output };
2339
+ }
2340
+ async function executeConditionStep(step, context, api, permissions, allowedActionIds, options) {
2341
+ const condition = step.condition;
2342
+ const result = evaluateExpression(condition.expression, context);
2343
+ const branch = result ? condition.then : condition.else || [];
2344
+ const branchResults = await executeSteps(branch, context, api, permissions, allowedActionIds, options);
2345
+ return {
2346
+ status: "success",
2347
+ output: { conditionResult: !!result, stepsExecuted: branchResults },
2348
+ response: { conditionResult: !!result }
2349
+ };
2350
+ }
2351
+ async function executeLoopStep(step, context, api, permissions, allowedActionIds, options) {
2352
+ const loop = step.loop;
2353
+ const items = resolveValue(loop.over, context);
2354
+ if (!Array.isArray(items)) {
2355
+ throw new Error(`Loop "over" must resolve to an array, got ${typeof items}`);
2356
+ }
2357
+ const maxIterations = loop.maxIterations || 1e3;
2358
+ const results = [];
2359
+ const savedLoop = { ...context.loop };
2360
+ for (let i = 0; i < Math.min(items.length, maxIterations); i++) {
2361
+ context.loop = {
2362
+ [loop.as]: items[i],
2363
+ item: items[i],
2364
+ i
2365
+ };
2366
+ if (loop.indexAs) {
2367
+ context.loop[loop.indexAs] = i;
2368
+ }
2369
+ await executeSteps(loop.steps, context, api, permissions, allowedActionIds, options);
2370
+ results.push(context.loop[loop.as]);
2371
+ }
2372
+ context.loop = savedLoop;
2373
+ return { status: "success", output: results, response: results };
2374
+ }
2375
+ async function executeParallelStep(step, context, api, permissions, allowedActionIds, options) {
2376
+ const parallel = step.parallel;
2377
+ const maxConcurrency = parallel.maxConcurrency || 5;
2378
+ const steps = parallel.steps;
2379
+ const results = [];
2380
+ for (let i = 0; i < steps.length; i += maxConcurrency) {
2381
+ const batch = steps.slice(i, i + maxConcurrency);
2382
+ const batchResults = await Promise.all(
2383
+ batch.map((s) => executeSingleStep(s, context, api, permissions, allowedActionIds, options))
2384
+ );
2385
+ results.push(...batchResults);
2386
+ }
2387
+ return { status: "success", output: results, response: results };
2388
+ }
2389
+ function executeFileReadStep(step, context) {
2390
+ const config = step.fileRead;
2391
+ const filePath = resolveValue(config.path, context);
2392
+ const resolvedPath = path3.resolve(filePath);
2393
+ const content = fs3.readFileSync(resolvedPath, "utf-8");
2394
+ const output = config.parseJson ? JSON.parse(content) : content;
2395
+ return { status: "success", output, response: output };
2396
+ }
2397
+ function executeFileWriteStep(step, context) {
2398
+ const config = step.fileWrite;
2399
+ const filePath = resolveValue(config.path, context);
2400
+ const content = resolveValue(config.content, context);
2401
+ const resolvedPath = path3.resolve(filePath);
2402
+ const dir = path3.dirname(resolvedPath);
2403
+ if (!fs3.existsSync(dir)) {
2404
+ fs3.mkdirSync(dir, { recursive: true });
2405
+ }
2406
+ const stringContent = typeof content === "string" ? content : JSON.stringify(content, null, 2);
2407
+ if (config.append) {
2408
+ fs3.appendFileSync(resolvedPath, stringContent);
2409
+ } else {
2410
+ fs3.writeFileSync(resolvedPath, stringContent);
2411
+ }
2412
+ return { status: "success", output: { path: resolvedPath, bytesWritten: stringContent.length }, response: { path: resolvedPath } };
2413
+ }
2414
+ async function executeSingleStep(step, context, api, permissions, allowedActionIds, options) {
2415
+ if (step.if) {
2416
+ const condResult = evaluateExpression(step.if, context);
2417
+ if (!condResult) {
2418
+ const result = { status: "skipped" };
2419
+ context.steps[step.id] = result;
2420
+ return result;
2421
+ }
2422
+ }
2423
+ if (step.unless) {
2424
+ const condResult = evaluateExpression(step.unless, context);
2425
+ if (condResult) {
2426
+ const result = { status: "skipped" };
2427
+ context.steps[step.id] = result;
2428
+ return result;
2429
+ }
2430
+ }
2431
+ const startTime = Date.now();
2432
+ let lastError;
2433
+ const maxAttempts = step.onError?.strategy === "retry" && step.onError.retries ? step.onError.retries + 1 : 1;
2434
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
2435
+ try {
2436
+ if (attempt > 1) {
2437
+ options.onEvent?.({
2438
+ event: "step:retry",
2439
+ stepId: step.id,
2440
+ attempt,
2441
+ maxRetries: step.onError.retries
2442
+ });
2443
+ const delay = step.onError?.retryDelayMs || 1e3;
2444
+ await sleep2(delay);
2445
+ }
2446
+ let result;
2447
+ switch (step.type) {
2448
+ case "action":
2449
+ result = await executeActionStep(step, context, api, permissions, allowedActionIds);
2450
+ break;
2451
+ case "transform":
2452
+ result = executeTransformStep(step, context);
2453
+ break;
2454
+ case "code":
2455
+ result = await executeCodeStep(step, context);
2456
+ break;
2457
+ case "condition":
2458
+ result = await executeConditionStep(step, context, api, permissions, allowedActionIds, options);
2459
+ break;
2460
+ case "loop":
2461
+ result = await executeLoopStep(step, context, api, permissions, allowedActionIds, options);
2462
+ break;
2463
+ case "parallel":
2464
+ result = await executeParallelStep(step, context, api, permissions, allowedActionIds, options);
2465
+ break;
2466
+ case "file-read":
2467
+ result = executeFileReadStep(step, context);
2468
+ break;
2469
+ case "file-write":
2470
+ result = executeFileWriteStep(step, context);
2471
+ break;
2472
+ default:
2473
+ throw new Error(`Unknown step type: ${step.type}`);
2474
+ }
2475
+ result.durationMs = Date.now() - startTime;
2476
+ if (attempt > 1) result.retries = attempt - 1;
2477
+ context.steps[step.id] = result;
2478
+ return result;
2479
+ } catch (err) {
2480
+ lastError = err instanceof Error ? err : new Error(String(err));
2481
+ if (attempt === maxAttempts) {
2482
+ break;
2483
+ }
2484
+ }
2485
+ }
2486
+ const errorMessage = lastError?.message || "Unknown error";
2487
+ const strategy = step.onError?.strategy || "fail";
2488
+ if (strategy === "continue") {
2489
+ const result = {
2490
+ status: "failed",
2491
+ error: errorMessage,
2492
+ durationMs: Date.now() - startTime
2493
+ };
2494
+ context.steps[step.id] = result;
2495
+ return result;
2496
+ }
2497
+ if (strategy === "fallback" && step.onError?.fallbackStepId) {
2498
+ const result = {
2499
+ status: "failed",
2500
+ error: errorMessage,
2501
+ durationMs: Date.now() - startTime
2502
+ };
2503
+ context.steps[step.id] = result;
2504
+ return result;
2505
+ }
2506
+ throw lastError;
2507
+ }
2508
+ async function executeSteps(steps, context, api, permissions, allowedActionIds, options, completedStepIds) {
2509
+ const results = [];
2510
+ for (const step of steps) {
2511
+ if (completedStepIds?.has(step.id)) {
2512
+ results.push(context.steps[step.id] || { status: "success" });
2513
+ continue;
2514
+ }
2515
+ options.onEvent?.({
2516
+ event: "step:start",
2517
+ stepId: step.id,
2518
+ stepName: step.name,
2519
+ type: step.type,
2520
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2521
+ });
2522
+ try {
2523
+ const result = await executeSingleStep(step, context, api, permissions, allowedActionIds, options);
2524
+ results.push(result);
2525
+ options.onEvent?.({
2526
+ event: "step:complete",
2527
+ stepId: step.id,
2528
+ status: result.status,
2529
+ durationMs: result.durationMs,
2530
+ retries: result.retries
2531
+ });
2532
+ } catch (error2) {
2533
+ const errorMsg = error2 instanceof Error ? error2.message : String(error2);
2534
+ options.onEvent?.({
2535
+ event: "step:error",
2536
+ stepId: step.id,
2537
+ error: errorMsg,
2538
+ strategy: step.onError?.strategy || "fail"
2539
+ });
2540
+ throw error2;
2541
+ }
2542
+ }
2543
+ return results;
2544
+ }
2545
+ async function executeFlow(flow2, inputs, api, permissions, allowedActionIds, options = {}, resumeState) {
2546
+ for (const [name, decl] of Object.entries(flow2.inputs)) {
2547
+ if (decl.required !== false && inputs[name] === void 0 && decl.default === void 0) {
2548
+ throw new Error(`Missing required input: "${name}" \u2014 ${decl.description || ""}`);
2549
+ }
2550
+ }
2551
+ const resolvedInputs = {};
2552
+ for (const [name, decl] of Object.entries(flow2.inputs)) {
2553
+ if (inputs[name] !== void 0) {
2554
+ resolvedInputs[name] = inputs[name];
2555
+ } else if (decl.default !== void 0) {
2556
+ resolvedInputs[name] = decl.default;
2557
+ }
2558
+ }
2559
+ const context = resumeState?.context || {
2560
+ input: resolvedInputs,
2561
+ env: process.env,
2562
+ steps: {},
2563
+ loop: {}
2564
+ };
2565
+ const completedStepIds = resumeState ? new Set(resumeState.completedSteps) : void 0;
2566
+ if (options.dryRun) {
2567
+ options.onEvent?.({
2568
+ event: "flow:dry-run",
2569
+ flowKey: flow2.key,
2570
+ resolvedInputs,
2571
+ steps: flow2.steps.map((s) => ({ id: s.id, name: s.name, type: s.type })),
2572
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2573
+ });
2574
+ return context;
2575
+ }
2576
+ options.onEvent?.({
2577
+ event: "flow:start",
2578
+ flowKey: flow2.key,
2579
+ totalSteps: flow2.steps.length,
2580
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2581
+ });
2582
+ const flowStart = Date.now();
2583
+ try {
2584
+ await executeSteps(flow2.steps, context, api, permissions, allowedActionIds, options, completedStepIds);
2585
+ const stepEntries = Object.values(context.steps);
2586
+ const completed = stepEntries.filter((s) => s.status === "success").length;
2587
+ const failed = stepEntries.filter((s) => s.status === "failed").length;
2588
+ const skipped = stepEntries.filter((s) => s.status === "skipped").length;
2589
+ options.onEvent?.({
2590
+ event: "flow:complete",
2591
+ flowKey: flow2.key,
2592
+ status: "success",
2593
+ durationMs: Date.now() - flowStart,
2594
+ stepsCompleted: completed,
2595
+ stepsFailed: failed,
2596
+ stepsSkipped: skipped
2597
+ });
2598
+ } catch (error2) {
2599
+ const errorMsg = error2 instanceof Error ? error2.message : String(error2);
2600
+ options.onEvent?.({
2601
+ event: "flow:error",
2602
+ flowKey: flow2.key,
2603
+ status: "failed",
2604
+ error: errorMsg,
2605
+ durationMs: Date.now() - flowStart
2606
+ });
2607
+ throw error2;
2608
+ }
2609
+ return context;
2610
+ }
1923
2611
 
1924
- $ pica actions execute gmail conn_mod_def::xxx::yyy live::gmail::default::abc123 \\
1925
- -d '{"to":"j@example.com","subject":"Hello","body":"Hi!","connectionKey":"live::gmail::default::abc123"}'
2612
+ // src/lib/flow-runner.ts
2613
+ var FLOWS_DIR = ".one/flows";
2614
+ var RUNS_DIR = ".one/flows/.runs";
2615
+ var LOGS_DIR = ".one/flows/.logs";
2616
+ function ensureDir(dir) {
2617
+ if (!fs4.existsSync(dir)) {
2618
+ fs4.mkdirSync(dir, { recursive: true });
2619
+ }
2620
+ }
2621
+ function generateRunId() {
2622
+ return crypto.randomBytes(6).toString("hex");
2623
+ }
2624
+ var FlowRunner = class _FlowRunner {
2625
+ runId;
2626
+ flowKey;
2627
+ state;
2628
+ logPath;
2629
+ statePath;
2630
+ paused = false;
2631
+ constructor(flow2, inputs, runId) {
2632
+ this.runId = runId || generateRunId();
2633
+ this.flowKey = flow2.key;
2634
+ ensureDir(RUNS_DIR);
2635
+ ensureDir(LOGS_DIR);
2636
+ this.statePath = path4.join(RUNS_DIR, `${flow2.key}-${this.runId}.state.json`);
2637
+ this.logPath = path4.join(LOGS_DIR, `${flow2.key}-${this.runId}.log`);
2638
+ this.state = {
2639
+ runId: this.runId,
2640
+ flowKey: flow2.key,
2641
+ status: "running",
2642
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
2643
+ inputs,
2644
+ completedSteps: [],
2645
+ context: {
2646
+ input: inputs,
2647
+ env: {},
2648
+ steps: {},
2649
+ loop: {}
2650
+ }
2651
+ };
2652
+ }
2653
+ getRunId() {
2654
+ return this.runId;
2655
+ }
2656
+ getLogPath() {
2657
+ return this.logPath;
2658
+ }
2659
+ getStatePath() {
2660
+ return this.statePath;
2661
+ }
2662
+ requestPause() {
2663
+ this.paused = true;
2664
+ }
2665
+ log(level, msg, data) {
2666
+ const entry = {
2667
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2668
+ level,
2669
+ msg,
2670
+ ...data
2671
+ };
2672
+ fs4.appendFileSync(this.logPath, JSON.stringify(entry) + "\n");
2673
+ }
2674
+ saveState() {
2675
+ fs4.writeFileSync(this.statePath, JSON.stringify(this.state, null, 2));
2676
+ }
2677
+ createEventHandler(externalHandler) {
2678
+ return (event) => {
2679
+ this.log(
2680
+ event.event.includes("error") ? "warn" : "info",
2681
+ event.event,
2682
+ event
2683
+ );
2684
+ if (event.event === "step:complete" && event.stepId) {
2685
+ this.state.completedSteps.push(event.stepId);
2686
+ this.state.currentStepId = void 0;
2687
+ }
2688
+ if (event.event === "step:start" && event.stepId) {
2689
+ this.state.currentStepId = event.stepId;
2690
+ }
2691
+ externalHandler?.(event);
2692
+ if (this.paused && event.event === "step:complete") {
2693
+ this.state.status = "paused";
2694
+ this.state.pausedAt = (/* @__PURE__ */ new Date()).toISOString();
2695
+ this.saveState();
2696
+ }
2697
+ };
2698
+ }
2699
+ async execute(flow2, api, permissions, allowedActionIds, options = {}) {
2700
+ this.log("info", "Flow started", { flowKey: this.flowKey, runId: this.runId });
2701
+ this.state.status = "running";
2702
+ this.saveState();
2703
+ const eventHandler = this.createEventHandler(options.onEvent);
2704
+ try {
2705
+ const context = await executeFlow(
2706
+ flow2,
2707
+ this.state.inputs,
2708
+ api,
2709
+ permissions,
2710
+ allowedActionIds,
2711
+ { ...options, onEvent: eventHandler }
2712
+ );
2713
+ this.state.status = "completed";
2714
+ this.state.completedAt = (/* @__PURE__ */ new Date()).toISOString();
2715
+ this.state.context = context;
2716
+ this.saveState();
2717
+ this.log("info", "Flow completed", { status: "success" });
2718
+ return context;
2719
+ } catch (error2) {
2720
+ const errorMsg = error2 instanceof Error ? error2.message : String(error2);
2721
+ this.state.status = "failed";
2722
+ this.state.context.steps = this.state.context.steps || {};
2723
+ this.saveState();
2724
+ this.log("error", "Flow failed", { error: errorMsg });
2725
+ throw error2;
2726
+ }
2727
+ }
2728
+ async resume(flow2, api, permissions, allowedActionIds, options = {}) {
2729
+ this.log("info", "Flow resumed", { flowKey: this.flowKey, runId: this.runId });
2730
+ this.state.status = "running";
2731
+ this.state.pausedAt = void 0;
2732
+ this.saveState();
2733
+ const eventHandler = this.createEventHandler(options.onEvent);
2734
+ try {
2735
+ const context = await executeFlow(
2736
+ flow2,
2737
+ this.state.inputs,
2738
+ api,
2739
+ permissions,
2740
+ allowedActionIds,
2741
+ { ...options, onEvent: eventHandler },
2742
+ {
2743
+ context: this.state.context,
2744
+ completedSteps: this.state.completedSteps
2745
+ }
2746
+ );
2747
+ this.state.status = "completed";
2748
+ this.state.completedAt = (/* @__PURE__ */ new Date()).toISOString();
2749
+ this.state.context = context;
2750
+ this.saveState();
2751
+ this.log("info", "Flow completed after resume", { status: "success" });
2752
+ return context;
2753
+ } catch (error2) {
2754
+ const errorMsg = error2 instanceof Error ? error2.message : String(error2);
2755
+ this.state.status = "failed";
2756
+ this.saveState();
2757
+ this.log("error", "Flow failed after resume", { error: errorMsg });
2758
+ throw error2;
2759
+ }
2760
+ }
2761
+ static loadRunState(runId) {
2762
+ ensureDir(RUNS_DIR);
2763
+ const files = fs4.readdirSync(RUNS_DIR).filter((f) => f.includes(runId) && f.endsWith(".state.json"));
2764
+ if (files.length === 0) return null;
2765
+ try {
2766
+ const content = fs4.readFileSync(path4.join(RUNS_DIR, files[0]), "utf-8");
2767
+ return JSON.parse(content);
2768
+ } catch {
2769
+ return null;
2770
+ }
2771
+ }
2772
+ static fromRunState(state) {
2773
+ const runner = Object.create(_FlowRunner.prototype);
2774
+ runner.runId = state.runId;
2775
+ runner.flowKey = state.flowKey;
2776
+ runner.state = state;
2777
+ runner.paused = false;
2778
+ runner.statePath = path4.join(RUNS_DIR, `${state.flowKey}-${state.runId}.state.json`);
2779
+ runner.logPath = path4.join(LOGS_DIR, `${state.flowKey}-${state.runId}.log`);
2780
+ return runner;
2781
+ }
2782
+ static listRuns(flowKey) {
2783
+ ensureDir(RUNS_DIR);
2784
+ const files = fs4.readdirSync(RUNS_DIR).filter((f) => f.endsWith(".state.json"));
2785
+ const runs = [];
2786
+ for (const file of files) {
2787
+ try {
2788
+ const content = fs4.readFileSync(path4.join(RUNS_DIR, file), "utf-8");
2789
+ const state = JSON.parse(content);
2790
+ if (!flowKey || state.flowKey === flowKey) {
2791
+ runs.push(state);
2792
+ }
2793
+ } catch {
2794
+ }
2795
+ }
2796
+ return runs.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime());
2797
+ }
2798
+ };
2799
+ function resolveFlowPath(keyOrPath) {
2800
+ if (keyOrPath.includes("/") || keyOrPath.includes("\\") || keyOrPath.endsWith(".json")) {
2801
+ return path4.resolve(keyOrPath);
2802
+ }
2803
+ return path4.resolve(FLOWS_DIR, `${keyOrPath}.flow.json`);
2804
+ }
2805
+ function loadFlow(keyOrPath) {
2806
+ const flowPath = resolveFlowPath(keyOrPath);
2807
+ if (!fs4.existsSync(flowPath)) {
2808
+ throw new Error(`Flow not found: ${flowPath}`);
2809
+ }
2810
+ const content = fs4.readFileSync(flowPath, "utf-8");
2811
+ return JSON.parse(content);
2812
+ }
2813
+ function listFlows() {
2814
+ const flowsDir = path4.resolve(FLOWS_DIR);
2815
+ if (!fs4.existsSync(flowsDir)) return [];
2816
+ const files = fs4.readdirSync(flowsDir).filter((f) => f.endsWith(".flow.json"));
2817
+ const flows = [];
2818
+ for (const file of files) {
2819
+ try {
2820
+ const content = fs4.readFileSync(path4.join(flowsDir, file), "utf-8");
2821
+ const flow2 = JSON.parse(content);
2822
+ flows.push({
2823
+ key: flow2.key,
2824
+ name: flow2.name,
2825
+ description: flow2.description,
2826
+ inputCount: Object.keys(flow2.inputs).length,
2827
+ stepCount: flow2.steps.length,
2828
+ path: path4.join(flowsDir, file)
2829
+ });
2830
+ } catch {
2831
+ }
2832
+ }
2833
+ return flows;
2834
+ }
2835
+ function saveFlow(flow2, outputPath) {
2836
+ const flowPath = outputPath ? path4.resolve(outputPath) : path4.resolve(FLOWS_DIR, `${flow2.key}.flow.json`);
2837
+ const dir = path4.dirname(flowPath);
2838
+ ensureDir(dir);
2839
+ fs4.writeFileSync(flowPath, JSON.stringify(flow2, null, 2) + "\n");
2840
+ return flowPath;
2841
+ }
1926
2842
 
1927
- Platform names are always kebab-case (e.g. hub-spot, ship-station, google-calendar).
1928
- Run 'pica platforms' to browse all 200+ available platforms.`).version(version);
1929
- program.hook("preAction", (thisCommand) => {
1930
- const opts = program.opts();
1931
- if (opts.agent) {
1932
- setAgentMode(true);
2843
+ // src/commands/flow.ts
2844
+ import fs5 from "fs";
2845
+ function getConfig2() {
2846
+ const apiKey = getApiKey();
2847
+ if (!apiKey) {
2848
+ error("Not configured. Run `one init` first.");
1933
2849
  }
1934
- });
1935
- program.command("init").description("Set up Pica and install MCP to your AI agents").option("-y, --yes", "Skip confirmations").option("-g, --global", "Install MCP globally (available in all projects)").option("-p, --project", "Install MCP for this project only (creates .mcp.json)").action(async (options) => {
1936
- await initCommand(options);
1937
- });
1938
- program.command("config").description("Configure MCP access control (permissions, connections, actions)").action(async () => {
1939
- await configCommand();
1940
- });
2850
+ const ac = getAccessControlFromAllSources();
2851
+ const permissions = ac.permissions || "admin";
2852
+ const connectionKeys = ac.connectionKeys || ["*"];
2853
+ const actionIds = ac.actionIds || ["*"];
2854
+ return { apiKey, permissions, connectionKeys, actionIds };
2855
+ }
2856
+ function parseInputs(inputArgs) {
2857
+ const inputs = {};
2858
+ for (const arg of inputArgs) {
2859
+ const eqIndex = arg.indexOf("=");
2860
+ if (eqIndex === -1) {
2861
+ error(`Invalid input format: "${arg}" \u2014 expected name=value`);
2862
+ }
2863
+ const key = arg.slice(0, eqIndex);
2864
+ const value = arg.slice(eqIndex + 1);
2865
+ try {
2866
+ inputs[key] = JSON.parse(value);
2867
+ } catch {
2868
+ inputs[key] = value;
2869
+ }
2870
+ }
2871
+ return inputs;
2872
+ }
2873
+ function collect(value, previous) {
2874
+ return previous.concat([value]);
2875
+ }
2876
+ async function autoResolveConnectionInputs(flow2, inputs, api) {
2877
+ const resolved = { ...inputs };
2878
+ const connectionInputs = Object.entries(flow2.inputs).filter(
2879
+ ([, decl]) => decl.connection && !resolved[decl.connection.platform]
2880
+ );
2881
+ if (connectionInputs.length === 0) return resolved;
2882
+ const missing = connectionInputs.filter(([name]) => !resolved[name]);
2883
+ if (missing.length === 0) return resolved;
2884
+ const connections = await api.listConnections();
2885
+ for (const [name, decl] of missing) {
2886
+ const platform = decl.connection.platform;
2887
+ const matching = connections.filter((c) => c.platform.toLowerCase() === platform.toLowerCase());
2888
+ if (matching.length === 1) {
2889
+ resolved[name] = matching[0].key;
2890
+ }
2891
+ }
2892
+ return resolved;
2893
+ }
2894
+ async function flowCreateCommand(key, options) {
2895
+ intro2(pc7.bgCyan(pc7.black(" One Flow ")));
2896
+ let flow2;
2897
+ if (options.definition) {
2898
+ try {
2899
+ flow2 = JSON.parse(options.definition);
2900
+ } catch {
2901
+ error("Invalid JSON in --definition");
2902
+ }
2903
+ } else if (!process.stdin.isTTY) {
2904
+ const chunks = [];
2905
+ for await (const chunk of process.stdin) {
2906
+ chunks.push(chunk);
2907
+ }
2908
+ const raw = Buffer.concat(chunks).toString("utf-8");
2909
+ try {
2910
+ flow2 = JSON.parse(raw);
2911
+ } catch {
2912
+ error("Invalid JSON from stdin");
2913
+ }
2914
+ } else {
2915
+ error("Interactive flow creation not yet supported. Use --definition <json> or pipe JSON via stdin.");
2916
+ }
2917
+ if (key) {
2918
+ flow2.key = key;
2919
+ }
2920
+ const errors = validateFlow(flow2);
2921
+ if (errors.length > 0) {
2922
+ if (isAgentMode()) {
2923
+ json({ error: "Validation failed", errors });
2924
+ process.exit(1);
2925
+ }
2926
+ error(`Validation failed:
2927
+ ${errors.map((e) => ` ${e.path}: ${e.message}`).join("\n")}`);
2928
+ }
2929
+ const flowPath = saveFlow(flow2, options.output);
2930
+ if (isAgentMode()) {
2931
+ json({ created: true, key: flow2.key, path: flowPath });
2932
+ return;
2933
+ }
2934
+ note2(`Flow "${flow2.name}" saved to ${flowPath}`, "Created");
2935
+ outro2(`Validate: ${pc7.cyan(`one flow validate ${flow2.key}`)}
2936
+ Execute: ${pc7.cyan(`one flow execute ${flow2.key}`)}`);
2937
+ }
2938
+ async function flowExecuteCommand(keyOrPath, options) {
2939
+ intro2(pc7.bgCyan(pc7.black(" One Flow ")));
2940
+ const { apiKey, permissions, actionIds } = getConfig2();
2941
+ const api = new OneApi(apiKey);
2942
+ const spinner5 = createSpinner();
2943
+ spinner5.start(`Loading flow "${keyOrPath}"...`);
2944
+ let flow2;
2945
+ try {
2946
+ flow2 = loadFlow(keyOrPath);
2947
+ } catch (err) {
2948
+ spinner5.stop("Flow not found");
2949
+ error(err instanceof Error ? err.message : String(err));
2950
+ return;
2951
+ }
2952
+ spinner5.stop(`Flow: ${flow2.name} (${flow2.steps.length} steps)`);
2953
+ const inputs = parseInputs(options.input || []);
2954
+ const resolvedInputs = await autoResolveConnectionInputs(flow2, inputs, api);
2955
+ const runner = new FlowRunner(flow2, resolvedInputs);
2956
+ const logPath = runner.getLogPath();
2957
+ const runId = runner.getRunId();
2958
+ const sigintHandler = () => {
2959
+ runner.requestPause();
2960
+ if (!isAgentMode()) {
2961
+ console.log(`
2962
+ ${pc7.yellow("Pausing after current step completes...")} (run ID: ${runId})`);
2963
+ }
2964
+ };
2965
+ process.on("SIGINT", sigintHandler);
2966
+ const onEvent = (event) => {
2967
+ if (isAgentMode()) {
2968
+ json(event);
2969
+ } else if (options.verbose) {
2970
+ const ts = (/* @__PURE__ */ new Date()).toISOString().split("T")[1].slice(0, 8);
2971
+ if (event.event === "step:start") {
2972
+ console.log(` ${pc7.dim(ts)} ${pc7.cyan("\u25B6")} ${event.stepName} ${pc7.dim(`(${event.type})`)}`);
2973
+ } else if (event.event === "step:complete") {
2974
+ const status = event.status === "success" ? pc7.green("\u2713") : event.status === "skipped" ? pc7.dim("\u25CB") : pc7.red("\u2717");
2975
+ console.log(` ${pc7.dim(ts)} ${status} ${event.stepId} ${pc7.dim(`${event.durationMs}ms`)}`);
2976
+ } else if (event.event === "step:error") {
2977
+ console.log(` ${pc7.dim(ts)} ${pc7.red("\u2717")} ${event.stepId}: ${event.error}`);
2978
+ }
2979
+ }
2980
+ };
2981
+ const execSpinner = createSpinner();
2982
+ if (!options.verbose && !isAgentMode()) {
2983
+ execSpinner.start("Executing flow...");
2984
+ }
2985
+ try {
2986
+ const context = await runner.execute(flow2, api, permissions, actionIds, {
2987
+ dryRun: options.dryRun,
2988
+ verbose: options.verbose,
2989
+ onEvent
2990
+ });
2991
+ process.off("SIGINT", sigintHandler);
2992
+ if (!options.verbose && !isAgentMode()) {
2993
+ execSpinner.stop("Flow completed");
2994
+ }
2995
+ if (isAgentMode()) {
2996
+ json({
2997
+ event: "flow:result",
2998
+ runId,
2999
+ logFile: logPath,
3000
+ status: "success",
3001
+ steps: context.steps
3002
+ });
3003
+ return;
3004
+ }
3005
+ const stepEntries = Object.entries(context.steps);
3006
+ const succeeded = stepEntries.filter(([, r]) => r.status === "success").length;
3007
+ const failed = stepEntries.filter(([, r]) => r.status === "failed").length;
3008
+ const skipped = stepEntries.filter(([, r]) => r.status === "skipped").length;
3009
+ console.log();
3010
+ console.log(` ${pc7.green("\u2713")} ${succeeded} succeeded ${failed > 0 ? pc7.red(`\u2717 ${failed} failed`) : ""} ${skipped > 0 ? pc7.dim(`\u25CB ${skipped} skipped`) : ""}`);
3011
+ console.log(` ${pc7.dim(`Run ID: ${runId}`)}`);
3012
+ console.log(` ${pc7.dim(`Log: ${logPath}`)}`);
3013
+ if (options.dryRun) {
3014
+ note2("Dry run \u2014 no steps were executed", "Dry Run");
3015
+ }
3016
+ } catch (error2) {
3017
+ process.off("SIGINT", sigintHandler);
3018
+ if (!options.verbose && !isAgentMode()) {
3019
+ execSpinner.stop("Flow failed");
3020
+ }
3021
+ const errorMsg = error2 instanceof Error ? error2.message : String(error2);
3022
+ if (isAgentMode()) {
3023
+ json({
3024
+ event: "flow:result",
3025
+ runId,
3026
+ logFile: logPath,
3027
+ status: "failed",
3028
+ error: errorMsg
3029
+ });
3030
+ process.exit(1);
3031
+ }
3032
+ console.log(` ${pc7.dim(`Run ID: ${runId}`)}`);
3033
+ console.log(` ${pc7.dim(`Log: ${logPath}`)}`);
3034
+ error(`Flow failed: ${errorMsg}`);
3035
+ }
3036
+ }
3037
+ async function flowListCommand() {
3038
+ intro2(pc7.bgCyan(pc7.black(" One Flow ")));
3039
+ const flows = listFlows();
3040
+ if (isAgentMode()) {
3041
+ json({ flows });
3042
+ return;
3043
+ }
3044
+ if (flows.length === 0) {
3045
+ note2("No flows found in .one/flows/\n\nCreate one with: one flow create", "Flows");
3046
+ return;
3047
+ }
3048
+ console.log();
3049
+ printTable(
3050
+ [
3051
+ { key: "key", label: "Key" },
3052
+ { key: "name", label: "Name" },
3053
+ { key: "description", label: "Description" },
3054
+ { key: "inputCount", label: "Inputs" },
3055
+ { key: "stepCount", label: "Steps" }
3056
+ ],
3057
+ flows.map((f) => ({
3058
+ key: f.key,
3059
+ name: f.name,
3060
+ description: f.description || "",
3061
+ inputCount: String(f.inputCount),
3062
+ stepCount: String(f.stepCount)
3063
+ }))
3064
+ );
3065
+ console.log();
3066
+ }
3067
+ async function flowValidateCommand(keyOrPath) {
3068
+ intro2(pc7.bgCyan(pc7.black(" One Flow ")));
3069
+ const spinner5 = createSpinner();
3070
+ spinner5.start(`Validating "${keyOrPath}"...`);
3071
+ let flowData;
3072
+ try {
3073
+ const flowPath = resolveFlowPath(keyOrPath);
3074
+ const content = fs5.readFileSync(flowPath, "utf-8");
3075
+ flowData = JSON.parse(content);
3076
+ } catch (err) {
3077
+ spinner5.stop("Validation failed");
3078
+ error(`Could not read flow: ${err instanceof Error ? err.message : String(err)}`);
3079
+ }
3080
+ const errors = validateFlow(flowData);
3081
+ if (errors.length > 0) {
3082
+ spinner5.stop("Validation failed");
3083
+ if (isAgentMode()) {
3084
+ json({ valid: false, errors });
3085
+ process.exit(1);
3086
+ }
3087
+ console.log();
3088
+ for (const e of errors) {
3089
+ console.log(` ${pc7.red("\u2717")} ${pc7.dim(e.path)}: ${e.message}`);
3090
+ }
3091
+ console.log();
3092
+ error(`${errors.length} validation error(s) found`);
3093
+ }
3094
+ spinner5.stop("Flow is valid");
3095
+ if (isAgentMode()) {
3096
+ json({ valid: true, key: flowData.key });
3097
+ return;
3098
+ }
3099
+ note2(`Flow "${flowData.key}" passed all validation checks`, "Valid");
3100
+ }
3101
+ async function flowResumeCommand(runId) {
3102
+ intro2(pc7.bgCyan(pc7.black(" One Flow ")));
3103
+ const state = FlowRunner.loadRunState(runId);
3104
+ if (!state) {
3105
+ error(`Run "${runId}" not found`);
3106
+ }
3107
+ if (state.status !== "paused" && state.status !== "failed") {
3108
+ error(`Run "${runId}" is ${state.status} \u2014 can only resume paused or failed runs`);
3109
+ }
3110
+ const { apiKey, permissions, actionIds } = getConfig2();
3111
+ const api = new OneApi(apiKey);
3112
+ let flow2;
3113
+ try {
3114
+ flow2 = loadFlow(state.flowKey);
3115
+ } catch (err) {
3116
+ error(`Could not load flow "${state.flowKey}": ${err instanceof Error ? err.message : String(err)}`);
3117
+ return;
3118
+ }
3119
+ const runner = FlowRunner.fromRunState(state);
3120
+ const onEvent = (event) => {
3121
+ if (isAgentMode()) {
3122
+ json(event);
3123
+ }
3124
+ };
3125
+ const spinner5 = createSpinner();
3126
+ spinner5.start(`Resuming run ${runId} (${state.completedSteps.length} steps already completed)...`);
3127
+ try {
3128
+ const context = await runner.resume(flow2, api, permissions, actionIds, { onEvent });
3129
+ spinner5.stop("Flow completed");
3130
+ if (isAgentMode()) {
3131
+ json({
3132
+ event: "flow:result",
3133
+ runId,
3134
+ logFile: runner.getLogPath(),
3135
+ status: "success",
3136
+ steps: context.steps
3137
+ });
3138
+ return;
3139
+ }
3140
+ console.log(` ${pc7.green("\u2713")} Resumed and completed successfully`);
3141
+ console.log(` ${pc7.dim(`Log: ${runner.getLogPath()}`)}`);
3142
+ } catch (error2) {
3143
+ spinner5.stop("Resume failed");
3144
+ const errorMsg = error2 instanceof Error ? error2.message : String(error2);
3145
+ if (isAgentMode()) {
3146
+ json({ event: "flow:result", runId, status: "failed", error: errorMsg });
3147
+ process.exit(1);
3148
+ }
3149
+ error(`Resume failed: ${errorMsg}`);
3150
+ }
3151
+ }
3152
+ async function flowRunsCommand(flowKey) {
3153
+ intro2(pc7.bgCyan(pc7.black(" One Flow ")));
3154
+ const runs = FlowRunner.listRuns(flowKey);
3155
+ if (isAgentMode()) {
3156
+ json({
3157
+ runs: runs.map((r) => ({
3158
+ runId: r.runId,
3159
+ flowKey: r.flowKey,
3160
+ status: r.status,
3161
+ startedAt: r.startedAt,
3162
+ completedAt: r.completedAt,
3163
+ pausedAt: r.pausedAt,
3164
+ completedSteps: r.completedSteps.length
3165
+ }))
3166
+ });
3167
+ return;
3168
+ }
3169
+ if (runs.length === 0) {
3170
+ note2(flowKey ? `No runs found for flow "${flowKey}"` : "No flow runs found", "Runs");
3171
+ return;
3172
+ }
3173
+ console.log();
3174
+ printTable(
3175
+ [
3176
+ { key: "runId", label: "Run ID" },
3177
+ { key: "flowKey", label: "Flow" },
3178
+ { key: "status", label: "Status" },
3179
+ { key: "startedAt", label: "Started" },
3180
+ { key: "steps", label: "Steps Done" }
3181
+ ],
3182
+ runs.map((r) => ({
3183
+ runId: r.runId,
3184
+ flowKey: r.flowKey,
3185
+ status: colorStatus(r.status),
3186
+ startedAt: r.startedAt,
3187
+ steps: String(r.completedSteps.length)
3188
+ }))
3189
+ );
3190
+ console.log();
3191
+ }
3192
+ function colorStatus(status) {
3193
+ switch (status) {
3194
+ case "completed":
3195
+ return pc7.green(status);
3196
+ case "running":
3197
+ return pc7.cyan(status);
3198
+ case "paused":
3199
+ return pc7.yellow(status);
3200
+ case "failed":
3201
+ return pc7.red(status);
3202
+ default:
3203
+ return status;
3204
+ }
3205
+ }
3206
+
3207
+ // src/commands/guide.ts
3208
+ import pc8 from "picocolors";
3209
+
3210
+ // src/lib/guide-content.ts
3211
+ var GUIDE_OVERVIEW = `# One CLI \u2014 Agent Guide
3212
+
3213
+ ## Setup
3214
+
3215
+ 1. Run \`one init\` to configure your API key
3216
+ 2. Run \`one add <platform>\` to connect platforms via OAuth
3217
+ 3. Run \`one --agent connection list\` to verify connections
3218
+
3219
+ ## The --agent Flag
3220
+
3221
+ Always use \`--agent\` for machine-readable JSON output. It disables colors, spinners, and interactive prompts.
3222
+
3223
+ \`\`\`bash
3224
+ one --agent <command>
3225
+ \`\`\`
3226
+
3227
+ All commands return JSON. If an \`error\` key is present, the command failed.
3228
+
3229
+ ## Topics
3230
+
3231
+ This guide has three sections you can request individually:
3232
+
3233
+ - **overview** \u2014 This section. Setup, flag usage, and discovery workflow.
3234
+ - **actions** \u2014 Full workflow for searching, reading docs, and executing platform actions.
3235
+ - **flows** \u2014 Building and executing multi-step API workflows (JSON-based).
3236
+
3237
+ ## Discovery Workflow
3238
+
3239
+ 1. \`one --agent connection list\` \u2014 See connected platforms and connection keys
3240
+ 2. \`one --agent actions search <platform> <query>\` \u2014 Find actions
3241
+ 3. \`one --agent actions knowledge <platform> <actionId>\` \u2014 Read full docs (REQUIRED before execute)
3242
+ 4. \`one --agent actions execute <platform> <actionId> <connectionKey>\` \u2014 Execute the action
3243
+
3244
+ For multi-step workflows, use flows:
3245
+ 1. Discover actions with the workflow above
3246
+ 2. Build a flow JSON definition
3247
+ 3. \`one --agent flow create <key> --definition '<json>'\`
3248
+ 4. \`one --agent flow execute <key> -i param=value\`
3249
+
3250
+ Platform names are always kebab-case (e.g., \`hub-spot\`, \`google-calendar\`).
3251
+ Run \`one platforms\` to browse all 200+ available platforms.
3252
+ `;
3253
+ var GUIDE_ACTIONS = `# One Actions CLI Workflow
3254
+
3255
+ You have access to the One CLI which lets you interact with 200+ third-party platforms through their APIs. The CLI handles authentication, request building, and execution through One's passthrough proxy.
3256
+
3257
+ ## The Workflow
3258
+
3259
+ Always follow this sequence \u2014 each step builds on the previous one:
3260
+
3261
+ 1. **List connections** to see what platforms the user has connected
3262
+ 2. **Search actions** to find the right API action for what the user wants to do
3263
+ 3. **Get knowledge** to understand the action's parameters, requirements, and structure
3264
+ 4. **Execute** the action with the correct parameters
3265
+
3266
+ Never skip the knowledge step before executing \u2014 it contains critical information about required parameters, validation rules, and request structure that you need to build a correct request.
3267
+
3268
+ ## Commands
3269
+
3270
+ ### 1. List Connections
3271
+
3272
+ \`\`\`bash
3273
+ one --agent connection list
3274
+ \`\`\`
3275
+
3276
+ Returns JSON with all connected platforms, their status, and connection keys. You need the **connection key** for executing actions, and the **platform name** (kebab-case) for searching actions.
3277
+
3278
+ Output format:
3279
+ \`\`\`json
3280
+ {"connections": [{"platform": "gmail", "state": "active", "key": "conn_abc123"}, ...]}
3281
+ \`\`\`
3282
+
3283
+ ### 2. Search Actions
3284
+
3285
+ \`\`\`bash
3286
+ one --agent actions search <platform> <query>
3287
+ \`\`\`
3288
+
3289
+ Search for actions on a specific platform using natural language. Returns JSON with up to 5 matching actions including their action IDs, HTTP methods, and paths.
3290
+
3291
+ - \`<platform>\` \u2014 Platform name in kebab-case exactly as shown in the connections list (e.g., \`gmail\`, \`shopify\`, \`hub-spot\`)
3292
+ - \`<query>\` \u2014 Natural language description of what you want to do (e.g., \`"send email"\`, \`"list contacts"\`, \`"create order"\`)
3293
+
3294
+ Options:
3295
+ - \`-t, --type <execute|knowledge>\` \u2014 Use \`execute\` when the user wants to perform an action, \`knowledge\` when they want documentation or want to write code. Defaults to \`knowledge\`.
3296
+
3297
+ Example:
3298
+ \`\`\`bash
3299
+ one --agent actions search gmail "send email" -t execute
3300
+ \`\`\`
3301
+
3302
+ Output format:
3303
+ \`\`\`json
3304
+ {"actions": [{"_id": "abc123", "title": "Send Email", "tags": [...], "method": "POST", "path": "/messages/send"}, ...]}
3305
+ \`\`\`
3306
+
3307
+ ### 3. Get Action Knowledge
3308
+
3309
+ \`\`\`bash
3310
+ one --agent actions knowledge <platform> <actionId>
3311
+ \`\`\`
3312
+
3313
+ Get comprehensive documentation for an action including parameters, requirements, validation rules, request/response structure, and examples. Returns JSON with the full API knowledge and HTTP method.
3314
+
3315
+ Always call this before executing \u2014 it tells you exactly what parameters are required and how to structure the request.
3316
+
3317
+ Example:
3318
+ \`\`\`bash
3319
+ one --agent actions knowledge gmail 67890abcdef
3320
+ \`\`\`
3321
+
3322
+ Output format:
3323
+ \`\`\`json
3324
+ {"knowledge": "...full API documentation and guidance...", "method": "POST"}
3325
+ \`\`\`
3326
+
3327
+ ### 4. Execute Action
3328
+
3329
+ \`\`\`bash
3330
+ one --agent actions execute <platform> <actionId> <connectionKey> [options]
3331
+ \`\`\`
3332
+
3333
+ Execute an action on a connected platform. Returns JSON with the request details and response data. You must have retrieved the knowledge for this action first.
3334
+
3335
+ - \`<platform>\` \u2014 Platform name in kebab-case
3336
+ - \`<actionId>\` \u2014 Action ID from the search results
3337
+ - \`<connectionKey>\` \u2014 Connection key from \`one connection list\`
3338
+
3339
+ Options:
3340
+ - \`-d, --data <json>\` \u2014 Request body as JSON string (for POST, PUT, PATCH)
3341
+ - \`--path-vars <json>\` \u2014 Path variables as JSON (for URLs with \`{id}\` placeholders)
3342
+ - \`--query-params <json>\` \u2014 Query parameters as JSON
3343
+ - \`--headers <json>\` \u2014 Additional headers as JSON
3344
+ - \`--form-data\` \u2014 Send as multipart/form-data instead of JSON
3345
+ - \`--form-url-encoded\` \u2014 Send as application/x-www-form-urlencoded
3346
+
3347
+ Examples:
3348
+ \`\`\`bash
3349
+ # Simple GET request
3350
+ one --agent actions execute shopify <actionId> <connectionKey>
3351
+
3352
+ # POST with data
3353
+ one --agent actions execute hub-spot <actionId> <connectionKey> \\
3354
+ -d '{"properties": {"email": "jane@example.com", "firstname": "Jane"}}'
3355
+
3356
+ # With path variables and query params
3357
+ one --agent actions execute shopify <actionId> <connectionKey> \\
3358
+ --path-vars '{"order_id": "12345"}' \\
3359
+ --query-params '{"limit": "10"}'
3360
+ \`\`\`
3361
+
3362
+ Output format:
3363
+ \`\`\`json
3364
+ {"request": {"method": "POST", "url": "https://..."}, "response": {...}}
3365
+ \`\`\`
3366
+
3367
+ ## Error Handling
3368
+
3369
+ All errors return JSON in agent mode:
3370
+ \`\`\`json
3371
+ {"error": "Error message here"}
3372
+ \`\`\`
3373
+
3374
+ Parse the output as JSON. If the \`error\` key is present, the command failed \u2014 report the error message to the user.
3375
+
3376
+ ## Important Notes
3377
+
3378
+ - **Always use \`--agent\` flag** \u2014 it produces structured JSON output without spinners, colors, or interactive prompts
3379
+ - Platform names are always **kebab-case** (e.g., \`hub-spot\` not \`HubSpot\`, \`ship-station\` not \`ShipStation\`)
3380
+ - Always use the **exact action ID** from search results \u2014 don't guess or construct them
3381
+ - Always read the knowledge output carefully \u2014 it tells you which parameters are required vs optional, what format they need to be in, and any caveats specific to that API
3382
+ - JSON values passed to \`-d\`, \`--path-vars\`, \`--query-params\`, and \`--headers\` must be valid JSON strings (use single quotes around the JSON to avoid shell escaping issues)
3383
+ - If search returns no results, try broader queries (e.g., \`"list"\` instead of \`"list active premium customers"\`)
3384
+ - The execute command respects access control settings configured via \`one config\` \u2014 if execution is blocked, the user may need to adjust their permissions
3385
+ `;
3386
+ var GUIDE_FLOWS = `# One Flow \u2014 Multi-Step API Workflows
3387
+
3388
+ You have access to the One CLI's flow engine, which lets you create and execute multi-step API workflows as JSON files. Flows chain actions across platforms \u2014 e.g., look up a Stripe customer, then send them a welcome email via Gmail.
3389
+
3390
+ ## 1. Overview
3391
+
3392
+ - Flows are JSON files stored at \`.one/flows/<key>.flow.json\`
3393
+ - All dynamic values (including connection keys) are declared as **inputs**
3394
+ - Each flow has a unique **key** used to reference and execute it
3395
+ - Executed via \`one --agent flow execute <key> -i name=value\`
3396
+
3397
+ ## 2. Building a Flow \u2014 Step-by-Step Process
3398
+
3399
+ **You MUST follow this process to build a correct flow:**
3400
+
3401
+ ### Step 1: Discover connections
3402
+
3403
+ \`\`\`bash
3404
+ one --agent connection list
3405
+ \`\`\`
3406
+
3407
+ Find out which platforms are connected and get their connection keys.
3408
+
3409
+ ### Step 2: For EACH API action needed, get the knowledge
3410
+
3411
+ \`\`\`bash
3412
+ # Find the action ID
3413
+ one --agent actions search <platform> "<query>" -t execute
3414
+
3415
+ # Read the full docs \u2014 REQUIRED before adding to a flow
3416
+ one --agent actions knowledge <platform> <actionId>
3417
+ \`\`\`
3418
+
3419
+ **CRITICAL:** You MUST call \`one actions knowledge\` for every action you include in the flow. The knowledge output tells you the exact request body structure, required fields, path variables, and query parameters. Without this, your flow JSON will have incorrect data shapes.
3420
+
3421
+ ### Step 3: Construct the flow JSON
3422
+
3423
+ Using the knowledge gathered, build the flow JSON with:
3424
+ - All inputs declared (connection keys + user parameters)
3425
+ - Each step with the correct actionId, platform, and data structure (from knowledge)
3426
+ - Data wired between steps using \`$.input.*\` and \`$.steps.*\` selectors
3427
+
3428
+ ### Step 4: Write the flow file
3429
+
3430
+ \`\`\`bash
3431
+ one --agent flow create <key> --definition '<json>'
3432
+ \`\`\`
3433
+
3434
+ Or write directly to \`.one/flows/<key>.flow.json\`.
3435
+
3436
+ ### Step 5: Validate
3437
+
3438
+ \`\`\`bash
3439
+ one --agent flow validate <key>
3440
+ \`\`\`
3441
+
3442
+ ### Step 6: Execute
3443
+
3444
+ \`\`\`bash
3445
+ one --agent flow execute <key> -i connectionKey=xxx -i param=value
3446
+ \`\`\`
3447
+
3448
+ ## 3. Flow JSON Schema Reference
3449
+
3450
+ \`\`\`json
3451
+ {
3452
+ "key": "welcome-customer",
3453
+ "name": "Welcome New Customer",
3454
+ "description": "Look up a Stripe customer and send them a welcome email via Gmail",
3455
+ "version": "1",
3456
+ "inputs": {
3457
+ "stripeConnectionKey": {
3458
+ "type": "string",
3459
+ "required": true,
3460
+ "description": "Stripe connection key from one connection list",
3461
+ "connection": { "platform": "stripe" }
3462
+ },
3463
+ "gmailConnectionKey": {
3464
+ "type": "string",
3465
+ "required": true,
3466
+ "description": "Gmail connection key from one connection list",
3467
+ "connection": { "platform": "gmail" }
3468
+ },
3469
+ "customerEmail": {
3470
+ "type": "string",
3471
+ "required": true,
3472
+ "description": "Customer email to look up"
3473
+ }
3474
+ },
3475
+ "steps": [
3476
+ {
3477
+ "id": "stepId",
3478
+ "name": "Human-readable label",
3479
+ "type": "action",
3480
+ "action": {
3481
+ "platform": "stripe",
3482
+ "actionId": "the-action-id-from-search",
3483
+ "connectionKey": "$.input.stripeConnectionKey",
3484
+ "data": {},
3485
+ "pathVars": {},
3486
+ "queryParams": {},
3487
+ "headers": {}
3488
+ }
3489
+ }
3490
+ ]
3491
+ }
3492
+ \`\`\`
3493
+
3494
+ ### Input declarations
3495
+
3496
+ | Field | Type | Description |
3497
+ |---|---|---|
3498
+ | \`type\` | string | \`string\`, \`number\`, \`boolean\`, \`object\`, \`array\` |
3499
+ | \`required\` | boolean | Whether this input must be provided (default: true) |
3500
+ | \`default\` | any | Default value if not provided |
3501
+ | \`description\` | string | Human-readable description |
3502
+ | \`connection\` | object | Connection metadata: \`{ "platform": "gmail" }\` \u2014 enables auto-resolution |
3503
+
3504
+ **Connection inputs** have a \`connection\` field. If the user has exactly one connection for that platform, the engine auto-resolves it.
3505
+
3506
+ ## 4. Selector Syntax Reference
3507
+
3508
+ | Pattern | Resolves To |
3509
+ |---|---|
3510
+ | \`$.input.gmailConnectionKey\` | Input value (including connection keys) |
3511
+ | \`$.input.customerEmail\` | Any input parameter |
3512
+ | \`$.steps.stepId.response\` | Full API response from a step |
3513
+ | \`$.steps.stepId.response.data[0].email\` | Nested field with array index |
3514
+ | \`$.steps.stepId.response.data[*].id\` | Wildcard \u2014 maps array to field |
3515
+ | \`$.env.MY_VAR\` | Environment variable |
3516
+ | \`$.loop.item\` | Current loop item |
3517
+ | \`$.loop.i\` | Current loop index |
3518
+ | \`"Hello {{$.steps.getUser.response.data.name}}"\` | String interpolation |
3519
+
3520
+ **Rules:**
3521
+ - A value that is purely \`$.xxx\` resolves to the raw type (object, array, number)
3522
+ - A string containing \`{{$.xxx}}\` does string interpolation (stringifies objects)
3523
+ - Selectors inside objects/arrays are resolved recursively
3524
+
3525
+ ## 5. Step Types Reference
3526
+
3527
+ ### \`action\` \u2014 Execute a One API action
3528
+
3529
+ \`\`\`json
3530
+ {
3531
+ "id": "findCustomer",
3532
+ "name": "Search Stripe customers",
3533
+ "type": "action",
3534
+ "action": {
3535
+ "platform": "stripe",
3536
+ "actionId": "conn_mod_def::xxx::yyy",
3537
+ "connectionKey": "$.input.stripeConnectionKey",
3538
+ "data": {
3539
+ "query": "email:'{{$.input.customerEmail}}'"
3540
+ }
3541
+ }
3542
+ }
3543
+ \`\`\`
3544
+
3545
+ ### \`transform\` \u2014 Transform data with a JS expression
3546
+
3547
+ \`\`\`json
3548
+ {
3549
+ "id": "extractNames",
3550
+ "name": "Extract customer names",
3551
+ "type": "transform",
3552
+ "transform": {
3553
+ "expression": "$.steps.findCustomer.response.data.map(c => c.name)"
3554
+ }
3555
+ }
3556
+ \`\`\`
3557
+
3558
+ The expression is evaluated with the full flow context as \`$\`.
3559
+
3560
+ ### \`code\` \u2014 Run multi-line JavaScript
3561
+
3562
+ 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\`.
3563
+
3564
+ \`\`\`json
3565
+ {
3566
+ "id": "processData",
3567
+ "name": "Process and enrich data",
3568
+ "type": "code",
3569
+ "code": {
3570
+ "source": "const customers = $.steps.listCustomers.response.data;\\nconst enriched = customers.map(c => ({\\n ...c,\\n tier: c.spend > 1000 ? 'gold' : 'silver'\\n}));\\nreturn enriched;"
3571
+ }
3572
+ }
3573
+ \`\`\`
3574
+
3575
+ 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.
3576
+
3577
+ ### \`condition\` \u2014 If/then/else branching
3578
+
3579
+ \`\`\`json
3580
+ {
3581
+ "id": "checkFound",
3582
+ "name": "Check if customer was found",
3583
+ "type": "condition",
3584
+ "condition": {
3585
+ "expression": "$.steps.findCustomer.response.data.length > 0",
3586
+ "then": [
3587
+ { "id": "sendEmail", "name": "Send welcome email", "type": "action", "action": { "..." : "..." } }
3588
+ ],
3589
+ "else": [
3590
+ { "id": "logNotFound", "name": "Log not found", "type": "transform", "transform": { "expression": "'Customer not found'" } }
3591
+ ]
3592
+ }
3593
+ }
3594
+ \`\`\`
3595
+
3596
+ ### \`loop\` \u2014 Iterate over an array
3597
+
3598
+ \`\`\`json
3599
+ {
3600
+ "id": "processOrders",
3601
+ "name": "Process each order",
3602
+ "type": "loop",
3603
+ "loop": {
3604
+ "over": "$.steps.listOrders.response.data",
3605
+ "as": "order",
3606
+ "indexAs": "i",
3607
+ "maxIterations": 1000,
3608
+ "steps": [
3609
+ {
3610
+ "id": "createInvoice",
3611
+ "name": "Create invoice for order",
3612
+ "type": "action",
3613
+ "action": {
3614
+ "platform": "quickbooks",
3615
+ "actionId": "...",
3616
+ "connectionKey": "$.input.qbConnectionKey",
3617
+ "data": { "amount": "$.loop.order.total" }
3618
+ }
3619
+ }
3620
+ ]
3621
+ }
3622
+ }
3623
+ \`\`\`
3624
+
3625
+ ### \`parallel\` \u2014 Run steps concurrently
3626
+
3627
+ \`\`\`json
3628
+ {
3629
+ "id": "parallelLookups",
3630
+ "name": "Look up in parallel",
3631
+ "type": "parallel",
3632
+ "parallel": {
3633
+ "maxConcurrency": 5,
3634
+ "steps": [
3635
+ { "id": "getStripe", "name": "Get Stripe data", "type": "action", "action": { "...": "..." } },
3636
+ { "id": "getHubspot", "name": "Get HubSpot data", "type": "action", "action": { "...": "..." } }
3637
+ ]
3638
+ }
3639
+ }
3640
+ \`\`\`
3641
+
3642
+ ### \`file-read\` \u2014 Read from filesystem
3643
+
3644
+ \`\`\`json
3645
+ {
3646
+ "id": "readConfig",
3647
+ "name": "Read config file",
3648
+ "type": "file-read",
3649
+ "fileRead": { "path": "./data/config.json", "parseJson": true }
3650
+ }
3651
+ \`\`\`
3652
+
3653
+ ### \`file-write\` \u2014 Write to filesystem
3654
+
3655
+ \`\`\`json
3656
+ {
3657
+ "id": "writeResults",
3658
+ "name": "Save results",
3659
+ "type": "file-write",
3660
+ "fileWrite": {
3661
+ "path": "./output/results.json",
3662
+ "content": "$.steps.transform.output",
3663
+ "append": false
3664
+ }
3665
+ }
3666
+ \`\`\`
3667
+
3668
+ ## 6. Error Handling
3669
+
3670
+ ### \`onError\` strategies
3671
+
3672
+ \`\`\`json
3673
+ {
3674
+ "id": "riskyStep",
3675
+ "name": "Might fail",
3676
+ "type": "action",
3677
+ "onError": {
3678
+ "strategy": "retry",
3679
+ "retries": 3,
3680
+ "retryDelayMs": 1000
3681
+ },
3682
+ "action": { "...": "..." }
3683
+ }
3684
+ \`\`\`
3685
+
3686
+ | Strategy | Behavior |
3687
+ |---|---|
3688
+ | \`fail\` | Stop the flow immediately (default) |
3689
+ | \`continue\` | Mark step as failed, continue to next step |
3690
+ | \`retry\` | Retry up to N times with delay |
3691
+ | \`fallback\` | On failure, execute a different step |
3692
+
3693
+ ### Conditional execution
3694
+
3695
+ Skip a step based on previous results:
3696
+
3697
+ \`\`\`json
3698
+ {
3699
+ "id": "sendEmail",
3700
+ "name": "Send email only if customer found",
3701
+ "type": "action",
3702
+ "if": "$.steps.findCustomer.response.data.length > 0",
3703
+ "action": { "...": "..." }
3704
+ }
3705
+ \`\`\`
3706
+
3707
+ ## 7. Updating Existing Flows
3708
+
3709
+ To modify an existing flow:
3710
+
3711
+ 1. Read the flow JSON file at \`.one/flows/<key>.flow.json\`
3712
+ 2. Understand its current structure
3713
+ 3. Use \`one --agent actions knowledge <platform> <actionId>\` for any new actions
3714
+ 4. Modify the JSON (add/remove/update steps, change data mappings, add inputs)
3715
+ 5. Write back the updated flow file
3716
+ 6. Validate: \`one --agent flow validate <key>\`
3717
+
3718
+ ## 8. Complete Examples
3719
+
3720
+ ### Example 1: Simple 2-step \u2014 Search Stripe customer, send Gmail email
3721
+
3722
+ \`\`\`json
3723
+ {
3724
+ "key": "welcome-customer",
3725
+ "name": "Welcome New Customer",
3726
+ "description": "Look up a Stripe customer and send them a welcome email",
3727
+ "version": "1",
3728
+ "inputs": {
3729
+ "stripeConnectionKey": {
3730
+ "type": "string",
3731
+ "required": true,
3732
+ "description": "Stripe connection key",
3733
+ "connection": { "platform": "stripe" }
3734
+ },
3735
+ "gmailConnectionKey": {
3736
+ "type": "string",
3737
+ "required": true,
3738
+ "description": "Gmail connection key",
3739
+ "connection": { "platform": "gmail" }
3740
+ },
3741
+ "customerEmail": {
3742
+ "type": "string",
3743
+ "required": true,
3744
+ "description": "Customer email to look up"
3745
+ }
3746
+ },
3747
+ "steps": [
3748
+ {
3749
+ "id": "findCustomer",
3750
+ "name": "Search for customer in Stripe",
3751
+ "type": "action",
3752
+ "action": {
3753
+ "platform": "stripe",
3754
+ "actionId": "STRIPE_SEARCH_CUSTOMERS_ACTION_ID",
3755
+ "connectionKey": "$.input.stripeConnectionKey",
3756
+ "data": {
3757
+ "query": "email:'{{$.input.customerEmail}}'"
3758
+ }
3759
+ }
3760
+ },
3761
+ {
3762
+ "id": "sendWelcome",
3763
+ "name": "Send welcome email via Gmail",
3764
+ "type": "action",
3765
+ "if": "$.steps.findCustomer.response.data && $.steps.findCustomer.response.data.length > 0",
3766
+ "action": {
3767
+ "platform": "gmail",
3768
+ "actionId": "GMAIL_SEND_EMAIL_ACTION_ID",
3769
+ "connectionKey": "$.input.gmailConnectionKey",
3770
+ "data": {
3771
+ "to": "{{$.input.customerEmail}}",
3772
+ "subject": "Welcome, {{$.steps.findCustomer.response.data[0].name}}!",
3773
+ "body": "Thank you for being a customer. We're glad to have you!"
3774
+ }
3775
+ }
3776
+ }
3777
+ ]
3778
+ }
3779
+ \`\`\`
3780
+
3781
+ ### Example 2: Conditional \u2014 Check if HubSpot contact exists, create or update
3782
+
3783
+ \`\`\`json
3784
+ {
3785
+ "key": "sync-hubspot-contact",
3786
+ "name": "Sync Contact to HubSpot",
3787
+ "description": "Check if a contact exists in HubSpot, create if new or update if existing",
3788
+ "version": "1",
3789
+ "inputs": {
3790
+ "hubspotConnectionKey": {
3791
+ "type": "string",
3792
+ "required": true,
3793
+ "connection": { "platform": "hub-spot" }
3794
+ },
3795
+ "email": { "type": "string", "required": true },
3796
+ "firstName": { "type": "string", "required": true },
3797
+ "lastName": { "type": "string", "required": true }
3798
+ },
3799
+ "steps": [
3800
+ {
3801
+ "id": "searchContact",
3802
+ "name": "Search for existing contact",
3803
+ "type": "action",
3804
+ "action": {
3805
+ "platform": "hub-spot",
3806
+ "actionId": "HUBSPOT_SEARCH_CONTACTS_ACTION_ID",
3807
+ "connectionKey": "$.input.hubspotConnectionKey",
3808
+ "data": {
3809
+ "filterGroups": [{ "filters": [{ "propertyName": "email", "operator": "EQ", "value": "$.input.email" }] }]
3810
+ }
3811
+ }
3812
+ },
3813
+ {
3814
+ "id": "createOrUpdate",
3815
+ "name": "Create or update contact",
3816
+ "type": "condition",
3817
+ "condition": {
3818
+ "expression": "$.steps.searchContact.response.total > 0",
3819
+ "then": [
3820
+ {
3821
+ "id": "updateContact",
3822
+ "name": "Update existing contact",
3823
+ "type": "action",
3824
+ "action": {
3825
+ "platform": "hub-spot",
3826
+ "actionId": "HUBSPOT_UPDATE_CONTACT_ACTION_ID",
3827
+ "connectionKey": "$.input.hubspotConnectionKey",
3828
+ "pathVars": { "contactId": "$.steps.searchContact.response.results[0].id" },
3829
+ "data": {
3830
+ "properties": { "firstname": "$.input.firstName", "lastname": "$.input.lastName" }
3831
+ }
3832
+ }
3833
+ }
3834
+ ],
3835
+ "else": [
3836
+ {
3837
+ "id": "createContact",
3838
+ "name": "Create new contact",
3839
+ "type": "action",
3840
+ "action": {
3841
+ "platform": "hub-spot",
3842
+ "actionId": "HUBSPOT_CREATE_CONTACT_ACTION_ID",
3843
+ "connectionKey": "$.input.hubspotConnectionKey",
3844
+ "data": {
3845
+ "properties": { "email": "$.input.email", "firstname": "$.input.firstName", "lastname": "$.input.lastName" }
3846
+ }
3847
+ }
3848
+ }
3849
+ ]
3850
+ }
3851
+ }
3852
+ ]
3853
+ }
3854
+ \`\`\`
3855
+
3856
+ ### Example 3: Loop \u2014 Iterate over Shopify orders, create invoices
3857
+
3858
+ \`\`\`json
3859
+ {
3860
+ "key": "shopify-to-invoices",
3861
+ "name": "Shopify Orders to Invoices",
3862
+ "description": "Fetch recent Shopify orders and create an invoice for each",
3863
+ "version": "1",
3864
+ "inputs": {
3865
+ "shopifyConnectionKey": {
3866
+ "type": "string",
3867
+ "required": true,
3868
+ "connection": { "platform": "shopify" }
3869
+ },
3870
+ "qbConnectionKey": {
3871
+ "type": "string",
3872
+ "required": true,
3873
+ "connection": { "platform": "quick-books" }
3874
+ }
3875
+ },
3876
+ "steps": [
3877
+ {
3878
+ "id": "listOrders",
3879
+ "name": "List recent Shopify orders",
3880
+ "type": "action",
3881
+ "action": {
3882
+ "platform": "shopify",
3883
+ "actionId": "SHOPIFY_LIST_ORDERS_ACTION_ID",
3884
+ "connectionKey": "$.input.shopifyConnectionKey",
3885
+ "queryParams": { "status": "any", "limit": "50" }
3886
+ }
3887
+ },
3888
+ {
3889
+ "id": "createInvoices",
3890
+ "name": "Create invoice for each order",
3891
+ "type": "loop",
3892
+ "loop": {
3893
+ "over": "$.steps.listOrders.response.orders",
3894
+ "as": "order",
3895
+ "indexAs": "i",
3896
+ "steps": [
3897
+ {
3898
+ "id": "createInvoice",
3899
+ "name": "Create QuickBooks invoice",
3900
+ "type": "action",
3901
+ "onError": { "strategy": "continue" },
3902
+ "action": {
3903
+ "platform": "quick-books",
3904
+ "actionId": "QB_CREATE_INVOICE_ACTION_ID",
3905
+ "connectionKey": "$.input.qbConnectionKey",
3906
+ "data": {
3907
+ "Line": [
3908
+ {
3909
+ "Amount": "$.loop.order.total_price",
3910
+ "Description": "Shopify Order #{{$.loop.order.order_number}}"
3911
+ }
3912
+ ]
3913
+ }
3914
+ }
3915
+ }
3916
+ ]
3917
+ }
3918
+ },
3919
+ {
3920
+ "id": "summary",
3921
+ "name": "Generate summary",
3922
+ "type": "transform",
3923
+ "transform": {
3924
+ "expression": "({ totalOrders: $.steps.listOrders.response.orders.length, processed: $.steps.createInvoices.output.length })"
3925
+ }
3926
+ }
3927
+ ]
3928
+ }
3929
+ \`\`\`
3930
+
3931
+ ## CLI Commands Reference
3932
+
3933
+ \`\`\`bash
3934
+ # Create a flow
3935
+ one --agent flow create <key> --definition '<json>'
3936
+
3937
+ # List all flows
3938
+ one --agent flow list
3939
+
3940
+ # Validate a flow
3941
+ one --agent flow validate <key>
3942
+
3943
+ # Execute a flow
3944
+ one --agent flow execute <key> -i connectionKey=value -i param=value
3945
+
3946
+ # Execute with dry run (validate only)
3947
+ one --agent flow execute <key> --dry-run -i connectionKey=value
3948
+
3949
+ # Execute with verbose output
3950
+ one --agent flow execute <key> -v -i connectionKey=value
3951
+
3952
+ # List flow runs
3953
+ one --agent flow runs [flowKey]
3954
+
3955
+ # Resume a paused/failed run
3956
+ one --agent flow resume <runId>
3957
+ \`\`\`
3958
+
3959
+ ## Important Notes
3960
+
3961
+ - **Always use \`--agent\` flag** for structured JSON output
3962
+ - **Always call \`one actions knowledge\`** before adding an action step to a flow
3963
+ - Platform names are **kebab-case** (e.g., \`hub-spot\`, not \`HubSpot\`)
3964
+ - Connection keys are **inputs**, not hardcoded \u2014 makes flows portable and shareable
3965
+ - Use \`$.input.*\` for input values, \`$.steps.*\` for step results
3966
+ - Action IDs in examples (like \`STRIPE_SEARCH_CUSTOMERS_ACTION_ID\`) are placeholders \u2014 always use \`one actions search\` to find the real IDs
3967
+ `;
3968
+ var TOPICS = [
3969
+ { topic: "overview", description: "Setup, --agent flag, discovery workflow" },
3970
+ { topic: "actions", description: "Search, read docs, and execute platform actions" },
3971
+ { topic: "flows", description: "Build and execute multi-step API workflows" },
3972
+ { topic: "all", description: "Complete guide (all topics combined)" }
3973
+ ];
3974
+ function getGuideContent(topic) {
3975
+ switch (topic) {
3976
+ case "overview":
3977
+ return { title: "One CLI \u2014 Agent Guide: Overview", content: GUIDE_OVERVIEW };
3978
+ case "actions":
3979
+ return { title: "One CLI \u2014 Agent Guide: Actions", content: GUIDE_ACTIONS };
3980
+ case "flows":
3981
+ return { title: "One CLI \u2014 Agent Guide: Flows", content: GUIDE_FLOWS };
3982
+ case "all":
3983
+ return {
3984
+ title: "One CLI \u2014 Agent Guide: Complete",
3985
+ content: [GUIDE_OVERVIEW, GUIDE_ACTIONS, GUIDE_FLOWS].join("\n---\n\n")
3986
+ };
3987
+ }
3988
+ }
3989
+ function getAvailableTopics() {
3990
+ return TOPICS;
3991
+ }
3992
+
3993
+ // src/commands/guide.ts
3994
+ var VALID_TOPICS = ["overview", "actions", "flows", "all"];
3995
+ async function guideCommand(topic = "all") {
3996
+ if (!VALID_TOPICS.includes(topic)) {
3997
+ error(
3998
+ `Unknown topic "${topic}". Available topics: ${VALID_TOPICS.join(", ")}`
3999
+ );
4000
+ }
4001
+ const { title, content } = getGuideContent(topic);
4002
+ const availableTopics = getAvailableTopics();
4003
+ if (isAgentMode()) {
4004
+ json({ topic, title, content, availableTopics });
4005
+ return;
4006
+ }
4007
+ intro2(pc8.bgCyan(pc8.black(" One Guide ")));
4008
+ console.log();
4009
+ console.log(content);
4010
+ console.log(pc8.dim("\u2500".repeat(60)));
4011
+ console.log(
4012
+ pc8.dim("Available topics: ") + availableTopics.map((t) => pc8.cyan(t.topic)).join(", ")
4013
+ );
4014
+ console.log(pc8.dim(`Run ${pc8.cyan("one guide <topic>")} for a specific section.`));
4015
+ }
4016
+
4017
+ // src/index.ts
4018
+ var require2 = createRequire(import.meta.url);
4019
+ var { version } = require2("../package.json");
4020
+ var program = new Command();
4021
+ program.name("one").option("--agent", "Machine-readable JSON output (no colors, spinners, or prompts)").description(`One CLI \u2014 Connect AI agents to 200+ platforms through one interface.
4022
+
4023
+ Setup:
4024
+ one init Set up API key and install MCP server
4025
+ one add <platform> Connect a platform via OAuth (e.g. gmail, slack, shopify)
4026
+ one config Configure access control (permissions, scoping)
4027
+
4028
+ Workflow (use these in order):
4029
+ 1. one list List your connected platforms and connection keys
4030
+ 2. one actions search <platform> <q> Search for actions using natural language
4031
+ 3. one actions knowledge <plat> <id> Get full docs for an action (ALWAYS do this before execute)
4032
+ 4. one actions execute <p> <id> <key> Execute the action
4033
+
4034
+ Guide:
4035
+ one guide [topic] Full CLI guide (topics: overview, actions, flows, all)
4036
+
4037
+ Flows (multi-step workflows):
4038
+ one flow list List saved flows
4039
+ one flow create [key] Create a flow from JSON
4040
+ one flow execute <key> Execute a flow
4041
+ one flow validate <key> Validate a flow
4042
+
4043
+ Example \u2014 send an email through Gmail:
4044
+ $ one list
4045
+ # Find: gmail operational live::gmail::default::abc123
4046
+
4047
+ $ one actions search gmail "send email" -t execute
4048
+ # Find: POST Send Email conn_mod_def::xxx::yyy
4049
+
4050
+ $ one actions knowledge gmail conn_mod_def::xxx::yyy
4051
+ # Read the docs: required fields are to, subject, body, connectionKey
4052
+
4053
+ $ one actions execute gmail conn_mod_def::xxx::yyy live::gmail::default::abc123 \\
4054
+ -d '{"to":"j@example.com","subject":"Hello","body":"Hi!","connectionKey":"live::gmail::default::abc123"}'
4055
+
4056
+ Platform names are always kebab-case (e.g. hub-spot, ship-station, google-calendar).
4057
+ Run 'one platforms' to browse all 200+ available platforms.`).version(version);
4058
+ program.hook("preAction", (thisCommand) => {
4059
+ const opts = program.opts();
4060
+ if (opts.agent) {
4061
+ setAgentMode(true);
4062
+ }
4063
+ });
4064
+ program.command("init").description("Set up One and install MCP to your AI agents").option("-y, --yes", "Skip confirmations").option("-g, --global", "Install MCP globally (available in all projects)").option("-p, --project", "Install MCP for this project only (creates .mcp.json)").action(async (options) => {
4065
+ await initCommand(options);
4066
+ });
4067
+ program.command("config").description("Configure MCP access control (permissions, connections, actions)").action(async () => {
4068
+ await configCommand();
4069
+ });
1941
4070
  var connection = program.command("connection").description("Manage connections");
1942
4071
  connection.command("add [platform]").alias("a").description("Add a new connection").action(async (platform) => {
1943
4072
  await connectionAddCommand(platform);
@@ -1949,13 +4078,13 @@ program.command("platforms").alias("p").description("List available platforms").
1949
4078
  await platformsCommand(options);
1950
4079
  });
1951
4080
  var actions = program.command("actions").alias("a").description("Search, explore, and execute platform actions (workflow: search \u2192 knowledge \u2192 execute)");
1952
- actions.command("search <platform> <query>").description('Search for actions on a platform (e.g. pica actions search gmail "send email")').option("-t, --type <type>", "execute (to run it) or knowledge (to learn about it). Default: knowledge").action(async (platform, query, options) => {
4081
+ actions.command("search <platform> <query>").description('Search for actions on a platform (e.g. one actions search gmail "send email")').option("-t, --type <type>", "execute (to run it) or knowledge (to learn about it). Default: knowledge").action(async (platform, query, options) => {
1953
4082
  await actionsSearchCommand(platform, query, options);
1954
4083
  });
1955
4084
  actions.command("knowledge <platform> <actionId>").alias("k").description("Get full docs for an action \u2014 MUST call before execute to know required params").action(async (platform, actionId) => {
1956
4085
  await actionsKnowledgeCommand(platform, actionId);
1957
4086
  });
1958
- actions.command("execute <platform> <actionId> <connectionKey>").alias("x").description('Execute an action \u2014 pass connectionKey from "pica list", actionId from "actions search"').option("-d, --data <json>", "Request body as JSON").option("--path-vars <json>", "Path variables as JSON").option("--query-params <json>", "Query parameters as JSON").option("--headers <json>", "Additional headers as JSON").option("--form-data", "Send as multipart/form-data").option("--form-url-encoded", "Send as application/x-www-form-urlencoded").action(async (platform, actionId, connectionKey, options) => {
4087
+ actions.command("execute <platform> <actionId> <connectionKey>").alias("x").description('Execute an action \u2014 pass connectionKey from "one list", actionId from "actions search"').option("-d, --data <json>", "Request body as JSON").option("--path-vars <json>", "Path variables as JSON").option("--query-params <json>", "Query parameters as JSON").option("--headers <json>", "Additional headers as JSON").option("--form-data", "Send as multipart/form-data").option("--form-url-encoded", "Send as application/x-www-form-urlencoded").action(async (platform, actionId, connectionKey, options) => {
1959
4088
  await actionsExecuteCommand(platform, actionId, connectionKey, {
1960
4089
  data: options.data,
1961
4090
  pathVars: options.pathVars,
@@ -1965,6 +4094,28 @@ actions.command("execute <platform> <actionId> <connectionKey>").alias("x").desc
1965
4094
  formUrlEncoded: options.formUrlEncoded
1966
4095
  });
1967
4096
  });
4097
+ var flow = program.command("flow").alias("f").description("Create, execute, and manage multi-step API workflows");
4098
+ flow.command("create [key]").description("Create a new flow from JSON definition").option("--definition <json>", "Flow definition as JSON string").option("-o, --output <path>", "Custom output path (default .one/flows/<key>.flow.json)").action(async (key, options) => {
4099
+ await flowCreateCommand(key, options);
4100
+ });
4101
+ flow.command("execute <keyOrPath>").alias("x").description("Execute a flow 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("-v, --verbose", "Show full request/response for each step").action(async (keyOrPath, options) => {
4102
+ await flowExecuteCommand(keyOrPath, options);
4103
+ });
4104
+ flow.command("list").alias("ls").description("List all flows in .one/flows/").action(async () => {
4105
+ await flowListCommand();
4106
+ });
4107
+ flow.command("validate <keyOrPath>").description("Validate a flow JSON file").action(async (keyOrPath) => {
4108
+ await flowValidateCommand(keyOrPath);
4109
+ });
4110
+ flow.command("resume <runId>").description("Resume a paused or failed flow run").action(async (runId) => {
4111
+ await flowResumeCommand(runId);
4112
+ });
4113
+ flow.command("runs [flowKey]").description("List flow runs (optionally filtered by flow key)").action(async (flowKey) => {
4114
+ await flowRunsCommand(flowKey);
4115
+ });
4116
+ program.command("guide [topic]").description("Full CLI usage guide for agents (topics: overview, actions, flows, all)").action(async (topic) => {
4117
+ await guideCommand(topic);
4118
+ });
1968
4119
  program.command("add [platform]").description("Shortcut for: connection add").action(async (platform) => {
1969
4120
  await connectionAddCommand(platform);
1970
4121
  });