@picahq/cli 1.9.4 → 1.10.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 +145 -0
  2. package/dist/index.js +2222 -71
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -305,8 +305,8 @@ var PicaApi = 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}`;
@@ -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];
@@ -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
  }
@@ -1894,76 +1901,2220 @@ 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
1913
-
1914
- Example \u2014 send an email through Gmail:
1915
- $ pica list
1916
- # Find: gmail operational live::gmail::default::abc123
1917
-
1918
- $ pica actions search gmail "send email" -t execute
1919
- # Find: POST Send Email conn_mod_def::xxx::yyy
1904
+ // src/commands/flow.ts
1905
+ import pc7 from "picocolors";
1920
1906
 
1921
- $ pica actions knowledge gmail conn_mod_def::xxx::yyy
1922
- # Read the docs: required fields are to, subject, body, connectionKey
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
+ }
1923
2224
 
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"}'
2225
+ // src/lib/flow-runner.ts
2226
+ import fs4 from "fs";
2227
+ import path4 from "path";
2228
+ import crypto from "crypto";
1926
2229
 
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);
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
+ }
1933
2260
  }
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
- });
1941
- var connection = program.command("connection").description("Manage connections");
1942
- connection.command("add [platform]").alias("a").description("Add a new connection").action(async (platform) => {
1943
- await connectionAddCommand(platform);
1944
- });
1945
- connection.command("list").alias("ls").description("List your connections").action(async () => {
1946
- await connectionListCommand();
1947
- });
1948
- program.command("platforms").alias("p").description("List available platforms").option("-c, --category <category>", "Filter by category").option("--json", "Output as JSON").action(async (options) => {
1949
- await platformsCommand(options);
1950
- });
1951
- 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) => {
1953
- await actionsSearchCommand(platform, query, options);
1954
- });
1955
- 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
- await actionsKnowledgeCommand(platform, actionId);
1957
- });
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) => {
1959
- await actionsExecuteCommand(platform, actionId, connectionKey, {
1960
- data: options.data,
1961
- pathVars: options.pathVars,
1962
- queryParams: options.queryParams,
1963
- headers: options.headers,
1964
- formData: options.formData,
1965
- formUrlEncoded: options.formUrlEncoded
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()
1966
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
+ }
2611
+
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
+ }
2842
+
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 `pica init` first.");
2849
+ }
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(" Pica 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(`pica flow validate ${flow2.key}`)}
2936
+ Execute: ${pc7.cyan(`pica flow execute ${flow2.key}`)}`);
2937
+ }
2938
+ async function flowExecuteCommand(keyOrPath, options) {
2939
+ intro2(pc7.bgCyan(pc7.black(" Pica Flow ")));
2940
+ const { apiKey, permissions, actionIds } = getConfig2();
2941
+ const api = new PicaApi(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(" Pica 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: pica 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(" Pica 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(" Pica 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 PicaApi(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(" Pica 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 = `# Pica CLI \u2014 Agent Guide
3212
+
3213
+ ## Setup
3214
+
3215
+ 1. Run \`pica init\` to configure your API key
3216
+ 2. Run \`pica add <platform>\` to connect platforms via OAuth
3217
+ 3. Run \`pica --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
+ pica --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. \`pica --agent connection list\` \u2014 See connected platforms and connection keys
3240
+ 2. \`pica --agent actions search <platform> <query>\` \u2014 Find actions
3241
+ 3. \`pica --agent actions knowledge <platform> <actionId>\` \u2014 Read full docs (REQUIRED before execute)
3242
+ 4. \`pica --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. \`pica --agent flow create <key> --definition '<json>'\`
3248
+ 4. \`pica --agent flow execute <key> -i param=value\`
3249
+
3250
+ Platform names are always kebab-case (e.g., \`hub-spot\`, \`google-calendar\`).
3251
+ Run \`pica platforms\` to browse all 200+ available platforms.
3252
+ `;
3253
+ var GUIDE_ACTIONS = `# Pica Actions CLI Workflow
3254
+
3255
+ You have access to the Pica CLI which lets you interact with 200+ third-party platforms through their APIs. The CLI handles authentication, request building, and execution through Pica'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
+ pica --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
+ pica --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
+ pica --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
+ pica --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
+ pica --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
+ pica --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 \`pica 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
+ pica --agent actions execute shopify <actionId> <connectionKey>
3351
+
3352
+ # POST with data
3353
+ pica --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
+ pica --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 \`pica config\` \u2014 if execution is blocked, the user may need to adjust their permissions
3385
+ `;
3386
+ var GUIDE_FLOWS = `# Pica Flow \u2014 Multi-Step API Workflows
3387
+
3388
+ You have access to the Pica 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 \`pica --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
+ pica --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
+ pica --agent actions search <platform> "<query>" -t execute
3414
+
3415
+ # Read the full docs \u2014 REQUIRED before adding to a flow
3416
+ pica --agent actions knowledge <platform> <actionId>
3417
+ \`\`\`
3418
+
3419
+ **CRITICAL:** You MUST call \`pica 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
+ pica --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
+ pica --agent flow validate <key>
3440
+ \`\`\`
3441
+
3442
+ ### Step 6: Execute
3443
+
3444
+ \`\`\`bash
3445
+ pica --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 pica connection list",
3461
+ "connection": { "platform": "stripe" }
3462
+ },
3463
+ "gmailConnectionKey": {
3464
+ "type": "string",
3465
+ "required": true,
3466
+ "description": "Gmail connection key from pica 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 Pica 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 \`pica --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: \`pica --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
+ pica --agent flow create <key> --definition '<json>'
3936
+
3937
+ # List all flows
3938
+ pica --agent flow list
3939
+
3940
+ # Validate a flow
3941
+ pica --agent flow validate <key>
3942
+
3943
+ # Execute a flow
3944
+ pica --agent flow execute <key> -i connectionKey=value -i param=value
3945
+
3946
+ # Execute with dry run (validate only)
3947
+ pica --agent flow execute <key> --dry-run -i connectionKey=value
3948
+
3949
+ # Execute with verbose output
3950
+ pica --agent flow execute <key> -v -i connectionKey=value
3951
+
3952
+ # List flow runs
3953
+ pica --agent flow runs [flowKey]
3954
+
3955
+ # Resume a paused/failed run
3956
+ pica --agent flow resume <runId>
3957
+ \`\`\`
3958
+
3959
+ ## Important Notes
3960
+
3961
+ - **Always use \`--agent\` flag** for structured JSON output
3962
+ - **Always call \`pica 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 \`pica 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: "Pica CLI \u2014 Agent Guide: Overview", content: GUIDE_OVERVIEW };
3978
+ case "actions":
3979
+ return { title: "Pica CLI \u2014 Agent Guide: Actions", content: GUIDE_ACTIONS };
3980
+ case "flows":
3981
+ return { title: "Pica CLI \u2014 Agent Guide: Flows", content: GUIDE_FLOWS };
3982
+ case "all":
3983
+ return {
3984
+ title: "Pica 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(" Pica 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("pica 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("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.
4022
+
4023
+ Setup:
4024
+ pica init Set up API key and install MCP server
4025
+ pica add <platform> Connect a platform via OAuth (e.g. gmail, slack, shopify)
4026
+ pica config Configure access control (permissions, scoping)
4027
+
4028
+ Workflow (use these in order):
4029
+ 1. pica list List your connected platforms and connection keys
4030
+ 2. pica actions search <platform> <q> Search for actions using natural language
4031
+ 3. pica actions knowledge <plat> <id> Get full docs for an action (ALWAYS do this before execute)
4032
+ 4. pica actions execute <p> <id> <key> Execute the action
4033
+
4034
+ Guide:
4035
+ pica guide [topic] Full CLI guide (topics: overview, actions, flows, all)
4036
+
4037
+ Flows (multi-step workflows):
4038
+ pica flow list List saved flows
4039
+ pica flow create [key] Create a flow from JSON
4040
+ pica flow execute <key> Execute a flow
4041
+ pica flow validate <key> Validate a flow
4042
+
4043
+ Example \u2014 send an email through Gmail:
4044
+ $ pica list
4045
+ # Find: gmail operational live::gmail::default::abc123
4046
+
4047
+ $ pica actions search gmail "send email" -t execute
4048
+ # Find: POST Send Email conn_mod_def::xxx::yyy
4049
+
4050
+ $ pica actions knowledge gmail conn_mod_def::xxx::yyy
4051
+ # Read the docs: required fields are to, subject, body, connectionKey
4052
+
4053
+ $ pica 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 'pica 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 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) => {
4065
+ await initCommand(options);
4066
+ });
4067
+ program.command("config").description("Configure MCP access control (permissions, connections, actions)").action(async () => {
4068
+ await configCommand();
4069
+ });
4070
+ var connection = program.command("connection").description("Manage connections");
4071
+ connection.command("add [platform]").alias("a").description("Add a new connection").action(async (platform) => {
4072
+ await connectionAddCommand(platform);
4073
+ });
4074
+ connection.command("list").alias("ls").description("List your connections").action(async () => {
4075
+ await connectionListCommand();
4076
+ });
4077
+ program.command("platforms").alias("p").description("List available platforms").option("-c, --category <category>", "Filter by category").option("--json", "Output as JSON").action(async (options) => {
4078
+ await platformsCommand(options);
4079
+ });
4080
+ var actions = program.command("actions").alias("a").description("Search, explore, and execute platform actions (workflow: search \u2192 knowledge \u2192 execute)");
4081
+ 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) => {
4082
+ await actionsSearchCommand(platform, query, options);
4083
+ });
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) => {
4085
+ await actionsKnowledgeCommand(platform, actionId);
4086
+ });
4087
+ 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) => {
4088
+ await actionsExecuteCommand(platform, actionId, connectionKey, {
4089
+ data: options.data,
4090
+ pathVars: options.pathVars,
4091
+ queryParams: options.queryParams,
4092
+ headers: options.headers,
4093
+ formData: options.formData,
4094
+ formUrlEncoded: options.formUrlEncoded
4095
+ });
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);
1967
4118
  });
1968
4119
  program.command("add [platform]").description("Shortcut for: connection add").action(async (platform) => {
1969
4120
  await connectionAddCommand(platform);