@withone/cli 1.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,4180 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { createRequire } from "module";
5
+ import { Command } from "commander";
6
+
7
+ // src/commands/init.ts
8
+ import * as p3 from "@clack/prompts";
9
+ import pc3 from "picocolors";
10
+
11
+ // src/lib/config.ts
12
+ import fs from "fs";
13
+ import path from "path";
14
+ import os from "os";
15
+ var CONFIG_DIR = path.join(os.homedir(), ".one");
16
+ var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
17
+ function getConfigPath() {
18
+ return CONFIG_FILE;
19
+ }
20
+ function configExists() {
21
+ return fs.existsSync(CONFIG_FILE);
22
+ }
23
+ function readConfig() {
24
+ if (!configExists()) {
25
+ return null;
26
+ }
27
+ try {
28
+ const content = fs.readFileSync(CONFIG_FILE, "utf-8");
29
+ return JSON.parse(content);
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+ function writeConfig(config) {
35
+ if (!fs.existsSync(CONFIG_DIR)) {
36
+ fs.mkdirSync(CONFIG_DIR, { mode: 448 });
37
+ }
38
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 384 });
39
+ }
40
+ function readOneRc() {
41
+ const rcPath = path.join(process.cwd(), ".onerc");
42
+ if (!fs.existsSync(rcPath)) return {};
43
+ try {
44
+ const content = fs.readFileSync(rcPath, "utf-8");
45
+ const result = {};
46
+ for (const line of content.split("\n")) {
47
+ const trimmed = line.trim();
48
+ if (!trimmed || trimmed.startsWith("#")) continue;
49
+ const eqIndex = trimmed.indexOf("=");
50
+ if (eqIndex === -1) continue;
51
+ const key = trimmed.slice(0, eqIndex).trim();
52
+ const value = trimmed.slice(eqIndex + 1).trim();
53
+ result[key] = value;
54
+ }
55
+ return result;
56
+ } catch {
57
+ return {};
58
+ }
59
+ }
60
+ function getApiKey() {
61
+ if (process.env.ONE_SECRET) return process.env.ONE_SECRET;
62
+ const rc = readOneRc();
63
+ if (rc.ONE_SECRET) return rc.ONE_SECRET;
64
+ return readConfig()?.apiKey ?? null;
65
+ }
66
+ function getAccessControlFromAllSources() {
67
+ const rc = readOneRc();
68
+ const fileAc = getAccessControl();
69
+ const merged = { ...fileAc };
70
+ if (rc.ONE_PERMISSIONS) {
71
+ merged.permissions = rc.ONE_PERMISSIONS;
72
+ }
73
+ if (rc.ONE_CONNECTION_KEYS) {
74
+ merged.connectionKeys = rc.ONE_CONNECTION_KEYS.split(",").map((s) => s.trim()).filter(Boolean);
75
+ }
76
+ if (rc.ONE_ACTION_IDS) {
77
+ merged.actionIds = rc.ONE_ACTION_IDS.split(",").map((s) => s.trim()).filter(Boolean);
78
+ }
79
+ if (rc.ONE_KNOWLEDGE_AGENT) {
80
+ merged.knowledgeAgent = rc.ONE_KNOWLEDGE_AGENT === "true";
81
+ }
82
+ return merged;
83
+ }
84
+ function getAccessControl() {
85
+ return readConfig()?.accessControl ?? {};
86
+ }
87
+ function updateAccessControl(settings) {
88
+ const config = readConfig();
89
+ if (!config) return;
90
+ const cleaned = {};
91
+ if (settings.permissions && settings.permissions !== "admin") {
92
+ cleaned.permissions = settings.permissions;
93
+ }
94
+ if (settings.connectionKeys && !(settings.connectionKeys.length === 1 && settings.connectionKeys[0] === "*")) {
95
+ cleaned.connectionKeys = settings.connectionKeys;
96
+ }
97
+ if (settings.actionIds && !(settings.actionIds.length === 1 && settings.actionIds[0] === "*")) {
98
+ cleaned.actionIds = settings.actionIds;
99
+ }
100
+ if (settings.knowledgeAgent) {
101
+ cleaned.knowledgeAgent = true;
102
+ }
103
+ if (Object.keys(cleaned).length === 0) {
104
+ delete config.accessControl;
105
+ } else {
106
+ config.accessControl = cleaned;
107
+ }
108
+ writeConfig(config);
109
+ }
110
+
111
+ // src/lib/agents.ts
112
+ import fs2 from "fs";
113
+ import path2 from "path";
114
+ import os2 from "os";
115
+ import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
116
+ function expandPath(p7) {
117
+ if (p7.startsWith("~/")) {
118
+ return path2.join(os2.homedir(), p7.slice(2));
119
+ }
120
+ return p7;
121
+ }
122
+ function getClaudeDesktopConfigPath() {
123
+ switch (process.platform) {
124
+ case "darwin":
125
+ return "~/Library/Application Support/Claude/claude_desktop_config.json";
126
+ case "win32":
127
+ return path2.join(process.env.APPDATA || "", "Claude", "claude_desktop_config.json");
128
+ default:
129
+ return "~/.config/Claude/claude_desktop_config.json";
130
+ }
131
+ }
132
+ function getClaudeDesktopDetectDir() {
133
+ switch (process.platform) {
134
+ case "darwin":
135
+ return "~/Library/Application Support/Claude";
136
+ case "win32":
137
+ return path2.join(process.env.APPDATA || "", "Claude");
138
+ default:
139
+ return "~/.config/Claude";
140
+ }
141
+ }
142
+ function getWindsurfConfigPath() {
143
+ if (process.platform === "win32") {
144
+ return path2.join(process.env.USERPROFILE || os2.homedir(), ".codeium", "windsurf", "mcp_config.json");
145
+ }
146
+ return "~/.codeium/windsurf/mcp_config.json";
147
+ }
148
+ function getWindsurfDetectDir() {
149
+ if (process.platform === "win32") {
150
+ return path2.join(process.env.USERPROFILE || os2.homedir(), ".codeium", "windsurf");
151
+ }
152
+ return "~/.codeium/windsurf";
153
+ }
154
+ function getCursorConfigPath() {
155
+ if (process.platform === "win32") {
156
+ return path2.join(process.env.USERPROFILE || os2.homedir(), ".cursor", "mcp.json");
157
+ }
158
+ return "~/.cursor/mcp.json";
159
+ }
160
+ var AGENTS = [
161
+ {
162
+ id: "claude-code",
163
+ name: "Claude Code",
164
+ configPath: "~/.claude.json",
165
+ configKey: "mcpServers",
166
+ detectDir: "~/.claude",
167
+ projectConfigPath: ".mcp.json"
168
+ },
169
+ {
170
+ id: "claude-desktop",
171
+ name: "Claude Desktop",
172
+ configPath: getClaudeDesktopConfigPath(),
173
+ configKey: "mcpServers",
174
+ detectDir: getClaudeDesktopDetectDir()
175
+ },
176
+ {
177
+ id: "cursor",
178
+ name: "Cursor",
179
+ configPath: getCursorConfigPath(),
180
+ configKey: "mcpServers",
181
+ detectDir: "~/.cursor",
182
+ projectConfigPath: ".cursor/mcp.json"
183
+ },
184
+ {
185
+ id: "windsurf",
186
+ name: "Windsurf",
187
+ configPath: getWindsurfConfigPath(),
188
+ configKey: "mcpServers",
189
+ detectDir: getWindsurfDetectDir()
190
+ },
191
+ {
192
+ id: "codex",
193
+ name: "Codex",
194
+ configPath: "~/.codex/config.toml",
195
+ configKey: "mcp_servers",
196
+ detectDir: "~/.codex",
197
+ projectConfigPath: ".codex/config.toml",
198
+ configFormat: "toml"
199
+ },
200
+ {
201
+ id: "kiro",
202
+ name: "Kiro",
203
+ configPath: "~/.kiro/settings/mcp.json",
204
+ configKey: "mcpServers",
205
+ detectDir: "~/.kiro",
206
+ projectConfigPath: ".kiro/settings/mcp.json"
207
+ }
208
+ ];
209
+ function getAllAgents() {
210
+ return AGENTS;
211
+ }
212
+ function supportsProjectScope(agent) {
213
+ return agent.projectConfigPath !== void 0;
214
+ }
215
+ function getAgentConfigPath(agent, scope = "global") {
216
+ if (scope === "project" && agent.projectConfigPath) {
217
+ return path2.join(process.cwd(), agent.projectConfigPath);
218
+ }
219
+ return expandPath(agent.configPath);
220
+ }
221
+ function readAgentConfig(agent, scope = "global") {
222
+ const configPath = getAgentConfigPath(agent, scope);
223
+ if (!fs2.existsSync(configPath)) {
224
+ return {};
225
+ }
226
+ try {
227
+ const content = fs2.readFileSync(configPath, "utf-8");
228
+ if (agent.configFormat === "toml") {
229
+ return parseToml(content);
230
+ }
231
+ return JSON.parse(content);
232
+ } catch {
233
+ return {};
234
+ }
235
+ }
236
+ function writeAgentConfig(agent, config, scope = "global") {
237
+ const configPath = getAgentConfigPath(agent, scope);
238
+ const configDir = path2.dirname(configPath);
239
+ if (!fs2.existsSync(configDir)) {
240
+ fs2.mkdirSync(configDir, { recursive: true });
241
+ }
242
+ if (agent.configFormat === "toml") {
243
+ fs2.writeFileSync(configPath, stringifyToml(config));
244
+ } else {
245
+ fs2.writeFileSync(configPath, JSON.stringify(config, null, 2));
246
+ }
247
+ }
248
+ function getMcpServerConfig(apiKey, accessControl) {
249
+ const env = {
250
+ ONE_SECRET: apiKey
251
+ };
252
+ if (accessControl) {
253
+ if (accessControl.permissions && accessControl.permissions !== "admin") {
254
+ env.ONE_PERMISSIONS = accessControl.permissions;
255
+ }
256
+ if (accessControl.connectionKeys && !(accessControl.connectionKeys.length === 1 && accessControl.connectionKeys[0] === "*")) {
257
+ env.ONE_CONNECTION_KEYS = accessControl.connectionKeys.join(",");
258
+ }
259
+ if (accessControl.actionIds && !(accessControl.actionIds.length === 1 && accessControl.actionIds[0] === "*")) {
260
+ env.ONE_ACTION_IDS = accessControl.actionIds.join(",");
261
+ }
262
+ if (accessControl.knowledgeAgent) {
263
+ env.ONE_KNOWLEDGE_AGENT = "true";
264
+ }
265
+ }
266
+ return {
267
+ command: "npx",
268
+ args: ["-y", "@picahq/mcp"],
269
+ env
270
+ };
271
+ }
272
+ function installMcpConfig(agent, apiKey, scope = "global", accessControl) {
273
+ const config = readAgentConfig(agent, scope);
274
+ const configKey = agent.configKey;
275
+ const mcpServers = config[configKey] || {};
276
+ mcpServers["one"] = getMcpServerConfig(apiKey, accessControl);
277
+ config[configKey] = mcpServers;
278
+ writeAgentConfig(agent, config, scope);
279
+ }
280
+ function isMcpInstalled(agent, scope = "global") {
281
+ const config = readAgentConfig(agent, scope);
282
+ const configKey = agent.configKey;
283
+ const mcpServers = config[configKey];
284
+ return mcpServers?.["one"] !== void 0;
285
+ }
286
+ function getAgentStatuses() {
287
+ return AGENTS.map((agent) => {
288
+ const detected = fs2.existsSync(expandPath(agent.detectDir));
289
+ const globalMcp = detected && isMcpInstalled(agent, "global");
290
+ const projectMcp = agent.projectConfigPath ? isMcpInstalled(agent, "project") : null;
291
+ return { agent, detected, globalMcp, projectMcp };
292
+ });
293
+ }
294
+
295
+ // src/lib/api.ts
296
+ var API_BASE = "https://api.withone.ai/v1";
297
+ var ApiError = class extends Error {
298
+ constructor(status, message) {
299
+ super(message);
300
+ this.status = status;
301
+ this.name = "ApiError";
302
+ }
303
+ };
304
+ var OneApi = class {
305
+ constructor(apiKey) {
306
+ this.apiKey = apiKey;
307
+ }
308
+ async request(path5) {
309
+ return this.requestFull({ path: path5 });
310
+ }
311
+ async requestFull(opts) {
312
+ let url = `${API_BASE}${opts.path}`;
313
+ if (opts.queryParams && Object.keys(opts.queryParams).length > 0) {
314
+ const params = new URLSearchParams(opts.queryParams);
315
+ url += `?${params.toString()}`;
316
+ }
317
+ const headers = {
318
+ "x-one-secret": this.apiKey,
319
+ "Content-Type": "application/json",
320
+ ...opts.headers
321
+ };
322
+ const fetchOpts = {
323
+ method: opts.method || "GET",
324
+ headers
325
+ };
326
+ if (opts.body !== void 0) {
327
+ fetchOpts.body = JSON.stringify(opts.body);
328
+ }
329
+ const response = await fetch(url, fetchOpts);
330
+ if (!response.ok) {
331
+ const text4 = await response.text();
332
+ throw new ApiError(response.status, text4 || `HTTP ${response.status}`);
333
+ }
334
+ return response.json();
335
+ }
336
+ async validateApiKey() {
337
+ try {
338
+ await this.listConnections();
339
+ return true;
340
+ } catch (error2) {
341
+ if (error2 instanceof ApiError && error2.status === 401) {
342
+ return false;
343
+ }
344
+ throw error2;
345
+ }
346
+ }
347
+ async listConnections() {
348
+ const response = await this.request("/vault/connections");
349
+ return response.rows || [];
350
+ }
351
+ async listPlatforms() {
352
+ const allPlatforms = [];
353
+ let page = 1;
354
+ let totalPages = 1;
355
+ do {
356
+ const response = await this.request(`/available-connectors?page=${page}&limit=100`);
357
+ allPlatforms.push(...response.rows || []);
358
+ totalPages = response.pages || 1;
359
+ page++;
360
+ } while (page <= totalPages);
361
+ return allPlatforms;
362
+ }
363
+ async searchActions(platform, query, agentType) {
364
+ const isKnowledgeAgent = !agentType || agentType === "knowledge";
365
+ const queryParams = {
366
+ query,
367
+ limit: "5"
368
+ };
369
+ if (isKnowledgeAgent) {
370
+ queryParams.knowledgeAgent = "true";
371
+ } else {
372
+ queryParams.executeAgent = "true";
373
+ }
374
+ const response = await this.requestFull({
375
+ path: `/available-actions/search/${platform}`,
376
+ queryParams
377
+ });
378
+ return response || [];
379
+ }
380
+ async getActionDetails(actionId) {
381
+ const response = await this.requestFull({
382
+ path: "/knowledge",
383
+ queryParams: { _id: actionId }
384
+ });
385
+ const actions2 = response?.rows || [];
386
+ if (actions2.length === 0) {
387
+ throw new ApiError(404, `Action with ID ${actionId} not found`);
388
+ }
389
+ return actions2[0];
390
+ }
391
+ async getActionKnowledge(actionId) {
392
+ const action = await this.getActionDetails(actionId);
393
+ if (!action.knowledge || !action.method) {
394
+ return {
395
+ knowledge: "No knowledge was found",
396
+ method: "No method was found"
397
+ };
398
+ }
399
+ return {
400
+ knowledge: action.knowledge,
401
+ method: action.method
402
+ };
403
+ }
404
+ async executePassthroughRequest(args, preloadedAction) {
405
+ const action = preloadedAction ?? await this.getActionDetails(args.actionId);
406
+ const method = action.method;
407
+ const contentType = args.isFormData ? "multipart/form-data" : args.isFormUrlEncoded ? "application/x-www-form-urlencoded" : "application/json";
408
+ const requestHeaders = {
409
+ "x-one-secret": this.apiKey,
410
+ "x-one-connection-key": args.connectionKey,
411
+ "x-one-action-id": action._id,
412
+ "Content-Type": contentType,
413
+ ...args.headers
414
+ };
415
+ const finalActionPath = args.pathVariables ? replacePathVariables(action.path, args.pathVariables) : action.path;
416
+ const normalizedPath = finalActionPath.startsWith("/") ? finalActionPath : `/${finalActionPath}`;
417
+ const url = `${API_BASE.replace("/v1", "")}/v1/passthrough${normalizedPath}`;
418
+ const isCustomAction = action.tags?.includes("custom");
419
+ let requestData = args.data;
420
+ if (isCustomAction && method?.toLowerCase() !== "get") {
421
+ requestData = {
422
+ ...args.data,
423
+ connectionKey: args.connectionKey
424
+ };
425
+ }
426
+ let queryString = "";
427
+ if (args.queryParams && Object.keys(args.queryParams).length > 0) {
428
+ const params = new URLSearchParams(
429
+ Object.entries(args.queryParams).map(([k, v]) => [k, String(v)])
430
+ );
431
+ queryString = `?${params.toString()}`;
432
+ }
433
+ const fullUrl = `${url}${queryString}`;
434
+ const fetchOpts = {
435
+ method,
436
+ headers: requestHeaders
437
+ };
438
+ if (method?.toLowerCase() !== "get" && requestData !== void 0) {
439
+ if (args.isFormUrlEncoded) {
440
+ const params = new URLSearchParams();
441
+ if (requestData && typeof requestData === "object" && !Array.isArray(requestData)) {
442
+ Object.entries(requestData).forEach(([key, value]) => {
443
+ if (typeof value === "object") {
444
+ params.append(key, JSON.stringify(value));
445
+ } else {
446
+ params.append(key, String(value));
447
+ }
448
+ });
449
+ }
450
+ fetchOpts.body = params.toString();
451
+ } else if (args.isFormData) {
452
+ const boundary = `----FormBoundary${Date.now()}`;
453
+ requestHeaders["Content-Type"] = `multipart/form-data; boundary=${boundary}`;
454
+ let body = "";
455
+ if (requestData && typeof requestData === "object" && !Array.isArray(requestData)) {
456
+ Object.entries(requestData).forEach(([key, value]) => {
457
+ body += `--${boundary}\r
458
+ `;
459
+ body += `Content-Disposition: form-data; name="${key}"\r
460
+ \r
461
+ `;
462
+ body += typeof value === "object" ? JSON.stringify(value) : String(value);
463
+ body += "\r\n";
464
+ });
465
+ }
466
+ body += `--${boundary}--\r
467
+ `;
468
+ fetchOpts.body = body;
469
+ fetchOpts.headers = requestHeaders;
470
+ } else {
471
+ fetchOpts.body = JSON.stringify(requestData);
472
+ }
473
+ }
474
+ const sanitizedConfig = {
475
+ url: fullUrl,
476
+ method,
477
+ headers: {
478
+ ...requestHeaders,
479
+ "x-one-secret": "***REDACTED***"
480
+ },
481
+ params: args.queryParams ? Object.fromEntries(
482
+ Object.entries(args.queryParams).map(([k, v]) => [k, String(v)])
483
+ ) : void 0,
484
+ data: requestData
485
+ };
486
+ if (args.dryRun) {
487
+ return {
488
+ requestConfig: sanitizedConfig,
489
+ responseData: null
490
+ };
491
+ }
492
+ const response = await fetch(fullUrl, fetchOpts);
493
+ if (!response.ok) {
494
+ const text4 = await response.text();
495
+ throw new ApiError(response.status, text4 || `HTTP ${response.status}`);
496
+ }
497
+ const responseText = await response.text();
498
+ const responseData = responseText ? JSON.parse(responseText) : {};
499
+ return {
500
+ requestConfig: sanitizedConfig,
501
+ responseData
502
+ };
503
+ }
504
+ async waitForConnection(platform, timeoutMs = 5 * 60 * 1e3, pollIntervalMs = 5e3, onPoll) {
505
+ const startTime = Date.now();
506
+ const existingConnections = await this.listConnections();
507
+ const existingIds = new Set(existingConnections.map((c) => c.id));
508
+ while (Date.now() - startTime < timeoutMs) {
509
+ await sleep(pollIntervalMs);
510
+ onPoll?.();
511
+ const currentConnections = await this.listConnections();
512
+ const newConnection = currentConnections.find(
513
+ (c) => c.platform.toLowerCase() === platform.toLowerCase() && !existingIds.has(c.id)
514
+ );
515
+ if (newConnection) {
516
+ return newConnection;
517
+ }
518
+ }
519
+ throw new TimeoutError(`Timed out waiting for ${platform} connection`);
520
+ }
521
+ };
522
+ var TimeoutError = class extends Error {
523
+ constructor(message) {
524
+ super(message);
525
+ this.name = "TimeoutError";
526
+ }
527
+ };
528
+ function sleep(ms) {
529
+ return new Promise((resolve) => setTimeout(resolve, ms));
530
+ }
531
+ function replacePathVariables(path5, variables) {
532
+ if (!path5) return path5;
533
+ let result = path5;
534
+ result = result.replace(/\{\{([^}]+)\}\}/g, (_match, variable) => {
535
+ const trimmedVariable = variable.trim();
536
+ const value = variables[trimmedVariable];
537
+ if (value === void 0 || value === null || value === "") {
538
+ throw new Error(`Missing value for path variable: ${trimmedVariable}`);
539
+ }
540
+ return encodeURIComponent(value.toString()).replace(/%3A/gi, ":");
541
+ });
542
+ result = result.replace(/\{([^}]+)\}/g, (_match, variable) => {
543
+ const trimmedVariable = variable.trim();
544
+ const value = variables[trimmedVariable];
545
+ if (value === void 0 || value === null || value === "") {
546
+ throw new Error(`Missing value for path variable: ${trimmedVariable}`);
547
+ }
548
+ return encodeURIComponent(value.toString()).replace(/%3A/gi, ":");
549
+ });
550
+ return result;
551
+ }
552
+ var PERMISSION_METHODS = {
553
+ read: ["GET"],
554
+ write: ["GET", "POST", "PUT", "PATCH"],
555
+ admin: null
556
+ };
557
+ function filterByPermissions(actions2, permissions) {
558
+ const allowed = PERMISSION_METHODS[permissions];
559
+ if (allowed === null) return actions2;
560
+ return actions2.filter((a) => allowed.includes(a.method.toUpperCase()));
561
+ }
562
+ function isMethodAllowed(method, permissions) {
563
+ const allowed = PERMISSION_METHODS[permissions];
564
+ if (allowed === null) return true;
565
+ return allowed.includes(method.toUpperCase());
566
+ }
567
+ function isActionAllowed(actionId, allowedActionIds) {
568
+ return allowedActionIds.includes("*") || allowedActionIds.includes(actionId);
569
+ }
570
+ function buildActionKnowledgeWithGuidance(knowledge, method, platform, actionId) {
571
+ const baseUrl = "https://api.withone.ai";
572
+ return `${knowledge}
573
+
574
+ API REQUEST STRUCTURE
575
+ ======================
576
+ URL: ${baseUrl}/v1/passthrough/{{PATH}}
577
+
578
+ IMPORTANT: When constructing the URL, only include the API endpoint path after the base URL.
579
+ Do NOT include the full third-party API URL.
580
+
581
+ Examples:
582
+ Correct: ${baseUrl}/v1/passthrough/crm/v3/objects/contacts/search
583
+ Incorrect: ${baseUrl}/v1/passthrough/https://api.hubapi.com/crm/v3/objects/contacts/search
584
+
585
+ METHOD: ${method}
586
+
587
+ HEADERS:
588
+ - x-one-secret: {{process.env.ONE_SECRET}}
589
+ - x-one-connection-key: {{process.env.ONE_${platform.toUpperCase()}_CONNECTION_KEY}}
590
+ - x-one-action-id: ${actionId}
591
+ - ... (other headers)
592
+
593
+ BODY: {{BODY}}
594
+
595
+ QUERY PARAMS: {{QUERY_PARAMS}}`;
596
+ }
597
+
598
+ // src/lib/browser.ts
599
+ import open from "open";
600
+ var ONE_APP_URL = "https://app.withone.ai";
601
+ function getConnectionUrl(platform) {
602
+ return `${ONE_APP_URL}/?#open=${platform}`;
603
+ }
604
+ function getApiKeyUrl() {
605
+ return `${ONE_APP_URL}/settings/api-keys`;
606
+ }
607
+ async function openConnectionPage(platform) {
608
+ const url = getConnectionUrl(platform);
609
+ await open(url);
610
+ }
611
+ async function openApiKeyPage() {
612
+ await open(getApiKeyUrl());
613
+ }
614
+
615
+ // src/commands/config.ts
616
+ import * as p2 from "@clack/prompts";
617
+ import pc from "picocolors";
618
+
619
+ // src/lib/output.ts
620
+ import * as p from "@clack/prompts";
621
+ var _agentMode = false;
622
+ function setAgentMode(value) {
623
+ _agentMode = value;
624
+ }
625
+ function isAgentMode() {
626
+ return _agentMode || process.env.ONE_AGENT === "1";
627
+ }
628
+ function createSpinner() {
629
+ if (isAgentMode()) {
630
+ return { start() {
631
+ }, stop() {
632
+ } };
633
+ }
634
+ return p.spinner();
635
+ }
636
+ function intro2(msg) {
637
+ if (!isAgentMode()) p.intro(msg);
638
+ }
639
+ function outro2(msg) {
640
+ if (!isAgentMode()) p.outro(msg);
641
+ }
642
+ function note2(msg, title) {
643
+ if (!isAgentMode()) p.note(msg, title);
644
+ }
645
+ function json(data) {
646
+ process.stdout.write(JSON.stringify(data) + "\n");
647
+ }
648
+ function error(message, exitCode = 1) {
649
+ if (isAgentMode()) {
650
+ json({ error: message });
651
+ } else {
652
+ p.cancel(message);
653
+ }
654
+ process.exit(exitCode);
655
+ }
656
+
657
+ // src/commands/config.ts
658
+ async function configCommand() {
659
+ if (isAgentMode()) {
660
+ error("This command requires interactive input. Run without --agent.");
661
+ }
662
+ const config = readConfig();
663
+ if (!config) {
664
+ p2.log.error(`No One config found. Run ${pc.cyan("one init")} first.`);
665
+ return;
666
+ }
667
+ p2.intro(pc.bgCyan(pc.black(" One Access Control ")));
668
+ const current = getAccessControl();
669
+ console.log();
670
+ console.log(` ${pc.bold("Current Access Control")}`);
671
+ console.log(` ${pc.dim("\u2500".repeat(42))}`);
672
+ console.log(` ${pc.dim("Permissions:")} ${current.permissions ?? "admin"}`);
673
+ console.log(` ${pc.dim("Connections:")} ${formatList(current.connectionKeys)}`);
674
+ console.log(` ${pc.dim("Action IDs:")} ${formatList(current.actionIds)}`);
675
+ console.log(` ${pc.dim("Knowledge only:")} ${current.knowledgeAgent ? "yes" : "no"}`);
676
+ console.log();
677
+ const permissions = await p2.select({
678
+ message: "Permission level",
679
+ options: [
680
+ { value: "admin", label: "Admin", hint: "Full access (GET, POST, PUT, PATCH, DELETE)" },
681
+ { value: "write", label: "Write", hint: "GET, POST, PUT, PATCH" },
682
+ { value: "read", label: "Read", hint: "GET only" }
683
+ ],
684
+ initialValue: current.permissions ?? "admin"
685
+ });
686
+ if (p2.isCancel(permissions)) {
687
+ p2.outro("No changes made.");
688
+ return;
689
+ }
690
+ const connectionMode = await p2.select({
691
+ message: "Connection scope",
692
+ options: [
693
+ { value: "all", label: "All connections" },
694
+ { value: "specific", label: "Select specific connections" }
695
+ ],
696
+ initialValue: current.connectionKeys ? "specific" : "all"
697
+ });
698
+ if (p2.isCancel(connectionMode)) {
699
+ p2.outro("No changes made.");
700
+ return;
701
+ }
702
+ let connectionKeys;
703
+ if (connectionMode === "specific") {
704
+ connectionKeys = await selectConnections(config.apiKey);
705
+ if (connectionKeys === void 0) {
706
+ p2.outro("No changes made.");
707
+ return;
708
+ }
709
+ if (connectionKeys.length === 0) {
710
+ p2.log.info(`No connections found. Defaulting to all. Use ${pc.cyan("one add")} to connect platforms.`);
711
+ connectionKeys = void 0;
712
+ }
713
+ }
714
+ const actionMode = await p2.select({
715
+ message: "Action scope",
716
+ options: [
717
+ { value: "all", label: "All actions" },
718
+ { value: "specific", label: "Restrict to specific action IDs" }
719
+ ],
720
+ initialValue: current.actionIds ? "specific" : "all"
721
+ });
722
+ if (p2.isCancel(actionMode)) {
723
+ p2.outro("No changes made.");
724
+ return;
725
+ }
726
+ let actionIds;
727
+ if (actionMode === "specific") {
728
+ const actionInput = await p2.text({
729
+ message: "Enter action IDs (comma-separated):",
730
+ placeholder: "action-id-1, action-id-2",
731
+ initialValue: current.actionIds?.join(", ") ?? "",
732
+ validate: (value) => {
733
+ if (!value.trim()) return "At least one action ID is required";
734
+ return void 0;
735
+ }
736
+ });
737
+ if (p2.isCancel(actionInput)) {
738
+ p2.outro("No changes made.");
739
+ return;
740
+ }
741
+ actionIds = actionInput.split(",").map((s) => s.trim()).filter(Boolean);
742
+ }
743
+ const knowledgeAgent = await p2.confirm({
744
+ message: "Enable knowledge-only mode? (disables action execution)",
745
+ initialValue: current.knowledgeAgent ?? false
746
+ });
747
+ if (p2.isCancel(knowledgeAgent)) {
748
+ p2.outro("No changes made.");
749
+ return;
750
+ }
751
+ const settings = {
752
+ permissions,
753
+ connectionKeys: connectionKeys ?? ["*"],
754
+ actionIds: actionIds ?? ["*"],
755
+ knowledgeAgent
756
+ };
757
+ updateAccessControl(settings);
758
+ const ac = getAccessControl();
759
+ const statuses = getAgentStatuses();
760
+ const reinstalled = [];
761
+ for (const s of statuses) {
762
+ if (s.globalMcp) {
763
+ installMcpConfig(s.agent, config.apiKey, "global", ac);
764
+ reinstalled.push(`${s.agent.name} (global)`);
765
+ }
766
+ if (s.projectMcp) {
767
+ installMcpConfig(s.agent, config.apiKey, "project", ac);
768
+ reinstalled.push(`${s.agent.name} (project)`);
769
+ }
770
+ }
771
+ if (reinstalled.length > 0) {
772
+ p2.log.success(`Updated MCP configs: ${reinstalled.join(", ")}`);
773
+ }
774
+ p2.outro("Access control updated.");
775
+ }
776
+ async function selectConnections(apiKey) {
777
+ const spinner5 = p2.spinner();
778
+ spinner5.start("Fetching connections...");
779
+ let connections;
780
+ try {
781
+ const api = new OneApi(apiKey);
782
+ const rawConnections = await api.listConnections();
783
+ connections = rawConnections.map((c) => ({ platform: c.platform, key: c.key }));
784
+ spinner5.stop(`Found ${connections.length} connection(s)`);
785
+ } catch {
786
+ spinner5.stop("Could not fetch connections");
787
+ const manual = await p2.text({
788
+ message: "Enter connection keys manually (comma-separated):",
789
+ placeholder: "conn_key_1, conn_key_2",
790
+ validate: (value) => {
791
+ if (!value.trim()) return "At least one connection key is required";
792
+ return void 0;
793
+ }
794
+ });
795
+ if (p2.isCancel(manual)) return void 0;
796
+ return manual.split(",").map((s) => s.trim()).filter(Boolean);
797
+ }
798
+ if (connections.length === 0) {
799
+ return [];
800
+ }
801
+ const selected = await p2.multiselect({
802
+ message: "Select connections:",
803
+ options: connections.map((c) => ({
804
+ value: c.key,
805
+ label: `${c.platform}`,
806
+ hint: c.key
807
+ }))
808
+ });
809
+ if (p2.isCancel(selected)) return void 0;
810
+ return selected;
811
+ }
812
+ function formatList(list) {
813
+ if (!list || list.length === 0) return "all";
814
+ if (list.length === 1 && list[0] === "*") return "all";
815
+ return list.join(", ");
816
+ }
817
+
818
+ // src/commands/init.ts
819
+ import open2 from "open";
820
+
821
+ // src/lib/table.ts
822
+ import pc2 from "picocolors";
823
+ function printTable(columns, rows) {
824
+ if (rows.length === 0) return;
825
+ const gap = " ";
826
+ const indent = " ";
827
+ const widths = columns.map((col) => {
828
+ const headerLen = col.label.length;
829
+ const maxValueLen = Math.max(...rows.map((row) => stripAnsi(row[col.key] || "").length));
830
+ return Math.max(headerLen, maxValueLen);
831
+ });
832
+ const header = columns.map((col, i) => {
833
+ const padded = col.align === "right" ? col.label.padStart(widths[i]) : col.label.padEnd(widths[i]);
834
+ return pc2.dim(padded);
835
+ }).join(gap);
836
+ console.log(`${indent}${header}`);
837
+ const separator = columns.map((_, i) => pc2.dim("\u2500".repeat(widths[i]))).join(gap);
838
+ console.log(`${indent}${separator}`);
839
+ for (const row of rows) {
840
+ const line = columns.map((col, i) => {
841
+ const raw = row[col.key] || "";
842
+ const rawLen = stripAnsi(raw).length;
843
+ const padding = widths[i] - rawLen;
844
+ const colored = col.color ? col.color(raw) : raw;
845
+ if (col.align === "right") {
846
+ return " ".repeat(Math.max(0, padding)) + colored;
847
+ }
848
+ return colored + " ".repeat(Math.max(0, padding));
849
+ }).join(gap);
850
+ console.log(`${indent}${line}`);
851
+ }
852
+ }
853
+ function stripAnsi(str) {
854
+ return str.replace(/\x1b\[[0-9;]*m/g, "");
855
+ }
856
+
857
+ // src/commands/init.ts
858
+ async function initCommand(options) {
859
+ if (isAgentMode()) {
860
+ error("This command requires interactive input. Run without --agent.");
861
+ }
862
+ const existingConfig = readConfig();
863
+ if (existingConfig) {
864
+ p3.intro(pc3.bgCyan(pc3.black(" One ")));
865
+ await handleExistingConfig(existingConfig.apiKey, options);
866
+ return;
867
+ }
868
+ printBanner();
869
+ await freshSetup(options);
870
+ }
871
+ async function handleExistingConfig(apiKey, options) {
872
+ const statuses = getAgentStatuses();
873
+ const masked = maskApiKey(apiKey);
874
+ console.log();
875
+ console.log(` ${pc3.bold("Current Setup")}`);
876
+ console.log(` ${pc3.dim("\u2500".repeat(42))}`);
877
+ console.log(` ${pc3.dim("API Key:")} ${masked}`);
878
+ console.log(` ${pc3.dim("Config:")} ${getConfigPath()}`);
879
+ console.log();
880
+ printTable(
881
+ [
882
+ { key: "agent", label: "Agent" },
883
+ { key: "global", label: "Global" },
884
+ { key: "project", label: "Project" }
885
+ ],
886
+ statuses.map((s) => ({
887
+ agent: s.agent.name,
888
+ global: !s.detected ? pc3.dim("-") : s.globalMcp ? pc3.green("\u25CF yes") : pc3.yellow("\u25CB no"),
889
+ project: s.projectMcp === null ? pc3.dim("-") : s.projectMcp ? pc3.green("\u25CF yes") : pc3.yellow("\u25CB no")
890
+ }))
891
+ );
892
+ const notDetected = statuses.filter((s) => !s.detected);
893
+ if (notDetected.length > 0) {
894
+ console.log(` ${pc3.dim("- = not detected on this machine")}`);
895
+ }
896
+ const ac = getAccessControl();
897
+ if (Object.keys(ac).length > 0) {
898
+ console.log(` ${pc3.bold("Access Control")}`);
899
+ console.log(` ${pc3.dim("\u2500".repeat(42))}`);
900
+ if (ac.permissions) console.log(` ${pc3.dim("Permissions:")} ${ac.permissions}`);
901
+ if (ac.connectionKeys) console.log(` ${pc3.dim("Connections:")} ${ac.connectionKeys.join(", ")}`);
902
+ if (ac.actionIds) console.log(` ${pc3.dim("Action IDs:")} ${ac.actionIds.join(", ")}`);
903
+ if (ac.knowledgeAgent) console.log(` ${pc3.dim("Knowledge only:")} yes`);
904
+ console.log();
905
+ }
906
+ const actionOptions = [];
907
+ actionOptions.push({
908
+ value: "update-key",
909
+ label: "Update API key"
910
+ });
911
+ const agentsMissingGlobal = statuses.filter((s) => s.detected && !s.globalMcp);
912
+ if (agentsMissingGlobal.length > 0) {
913
+ actionOptions.push({
914
+ value: "install-more",
915
+ label: "Install MCP to more agents",
916
+ hint: agentsMissingGlobal.map((s) => s.agent.name).join(", ")
917
+ });
918
+ }
919
+ const agentsMissingProject = statuses.filter((s) => s.projectMcp === false);
920
+ if (agentsMissingProject.length > 0) {
921
+ actionOptions.push({
922
+ value: "install-project",
923
+ label: "Install MCP for this project",
924
+ hint: agentsMissingProject.map((s) => s.agent.name).join(", ")
925
+ });
926
+ }
927
+ actionOptions.push({
928
+ value: "access-control",
929
+ label: "Configure access control",
930
+ hint: "permissions, connections, actions"
931
+ });
932
+ actionOptions.push({
933
+ value: "start-fresh",
934
+ label: "Start fresh (reconfigure everything)"
935
+ });
936
+ const action = await p3.select({
937
+ message: "What would you like to do?",
938
+ options: actionOptions
939
+ });
940
+ if (p3.isCancel(action)) {
941
+ p3.outro("No changes made.");
942
+ return;
943
+ }
944
+ switch (action) {
945
+ case "update-key":
946
+ await handleUpdateKey(statuses);
947
+ break;
948
+ case "install-more":
949
+ await handleInstallMore(apiKey, agentsMissingGlobal);
950
+ break;
951
+ case "install-project":
952
+ await handleInstallProject(apiKey, agentsMissingProject);
953
+ break;
954
+ case "access-control":
955
+ await configCommand();
956
+ break;
957
+ case "start-fresh":
958
+ await freshSetup({ yes: true });
959
+ break;
960
+ }
961
+ }
962
+ async function handleUpdateKey(statuses) {
963
+ p3.note(`Get your API key at:
964
+ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
965
+ const openBrowser = await p3.confirm({
966
+ message: "Open browser to get API key?",
967
+ initialValue: true
968
+ });
969
+ if (p3.isCancel(openBrowser)) {
970
+ p3.cancel("Cancelled.");
971
+ process.exit(0);
972
+ }
973
+ if (openBrowser) {
974
+ await openApiKeyPage();
975
+ }
976
+ const newKey = await p3.text({
977
+ message: "Enter your new One API key:",
978
+ placeholder: "sk_live_...",
979
+ validate: (value) => {
980
+ if (!value) return "API key is required";
981
+ if (!value.startsWith("sk_live_") && !value.startsWith("sk_test_")) {
982
+ return "API key should start with sk_live_ or sk_test_";
983
+ }
984
+ return void 0;
985
+ }
986
+ });
987
+ if (p3.isCancel(newKey)) {
988
+ p3.cancel("Cancelled.");
989
+ process.exit(0);
990
+ }
991
+ const spinner5 = p3.spinner();
992
+ spinner5.start("Validating API key...");
993
+ const api = new OneApi(newKey);
994
+ const isValid = await api.validateApiKey();
995
+ if (!isValid) {
996
+ spinner5.stop("Invalid API key");
997
+ p3.cancel(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
998
+ process.exit(1);
999
+ }
1000
+ spinner5.stop("API key validated");
1001
+ const ac = getAccessControl();
1002
+ const reinstalled = [];
1003
+ for (const s of statuses) {
1004
+ if (s.globalMcp) {
1005
+ installMcpConfig(s.agent, newKey, "global", ac);
1006
+ reinstalled.push(`${s.agent.name} (global)`);
1007
+ }
1008
+ if (s.projectMcp) {
1009
+ installMcpConfig(s.agent, newKey, "project", ac);
1010
+ reinstalled.push(`${s.agent.name} (project)`);
1011
+ }
1012
+ }
1013
+ const config = readConfig();
1014
+ writeConfig({
1015
+ apiKey: newKey,
1016
+ installedAgents: config?.installedAgents ?? [],
1017
+ createdAt: config?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1018
+ accessControl: config?.accessControl
1019
+ });
1020
+ if (reinstalled.length > 0) {
1021
+ p3.log.success(`Updated MCP configs: ${reinstalled.join(", ")}`);
1022
+ }
1023
+ p3.outro("API key updated.");
1024
+ }
1025
+ async function handleInstallMore(apiKey, missing) {
1026
+ const ac = getAccessControl();
1027
+ if (missing.length === 1) {
1028
+ const agent = missing[0].agent;
1029
+ const confirm3 = await p3.confirm({
1030
+ message: `Install One MCP to ${agent.name}?`,
1031
+ initialValue: true
1032
+ });
1033
+ if (p3.isCancel(confirm3) || !confirm3) {
1034
+ p3.outro("No changes made.");
1035
+ return;
1036
+ }
1037
+ installMcpConfig(agent, apiKey, "global", ac);
1038
+ updateConfigAgents(agent.id);
1039
+ p3.log.success(`${agent.name}: MCP installed`);
1040
+ p3.outro("Done.");
1041
+ return;
1042
+ }
1043
+ const selected = await p3.multiselect({
1044
+ message: "Select agents to install MCP:",
1045
+ options: missing.map((s) => ({
1046
+ value: s.agent.id,
1047
+ label: s.agent.name
1048
+ }))
1049
+ });
1050
+ if (p3.isCancel(selected)) {
1051
+ p3.outro("No changes made.");
1052
+ return;
1053
+ }
1054
+ const agents = missing.filter((s) => selected.includes(s.agent.id));
1055
+ for (const s of agents) {
1056
+ installMcpConfig(s.agent, apiKey, "global", ac);
1057
+ updateConfigAgents(s.agent.id);
1058
+ p3.log.success(`${s.agent.name}: MCP installed`);
1059
+ }
1060
+ p3.outro("Done.");
1061
+ }
1062
+ async function handleInstallProject(apiKey, missing) {
1063
+ const ac = getAccessControl();
1064
+ if (missing.length === 1) {
1065
+ const agent = missing[0].agent;
1066
+ const confirm3 = await p3.confirm({
1067
+ message: `Install project-level MCP for ${agent.name}?`,
1068
+ initialValue: true
1069
+ });
1070
+ if (p3.isCancel(confirm3) || !confirm3) {
1071
+ p3.outro("No changes made.");
1072
+ return;
1073
+ }
1074
+ installMcpConfig(agent, apiKey, "project", ac);
1075
+ const configPath = getAgentConfigPath(agent, "project");
1076
+ p3.log.success(`${agent.name}: ${configPath} created`);
1077
+ p3.note(
1078
+ pc3.yellow("Project config files can be committed to share with your team.\n") + pc3.yellow("Team members will need their own API key."),
1079
+ "Tip"
1080
+ );
1081
+ p3.outro("Done.");
1082
+ return;
1083
+ }
1084
+ const selected = await p3.multiselect({
1085
+ message: "Select agents for project-level MCP:",
1086
+ options: missing.map((s) => ({
1087
+ value: s.agent.id,
1088
+ label: s.agent.name
1089
+ }))
1090
+ });
1091
+ if (p3.isCancel(selected)) {
1092
+ p3.outro("No changes made.");
1093
+ return;
1094
+ }
1095
+ const agents = missing.filter((s) => selected.includes(s.agent.id));
1096
+ for (const s of agents) {
1097
+ installMcpConfig(s.agent, apiKey, "project", ac);
1098
+ const configPath = getAgentConfigPath(s.agent, "project");
1099
+ p3.log.success(`${s.agent.name}: ${configPath} created`);
1100
+ }
1101
+ p3.note(
1102
+ pc3.yellow("Project config files can be committed to share with your team.\n") + pc3.yellow("Team members will need their own API key."),
1103
+ "Tip"
1104
+ );
1105
+ p3.outro("Done.");
1106
+ }
1107
+ async function freshSetup(options) {
1108
+ p3.note(`Get your API key at:
1109
+ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
1110
+ const openBrowser = await p3.confirm({
1111
+ message: "Open browser to get API key?",
1112
+ initialValue: true
1113
+ });
1114
+ if (p3.isCancel(openBrowser)) {
1115
+ p3.cancel("Setup cancelled.");
1116
+ process.exit(0);
1117
+ }
1118
+ if (openBrowser) {
1119
+ await openApiKeyPage();
1120
+ }
1121
+ const apiKey = await p3.text({
1122
+ message: "Enter your One API key:",
1123
+ placeholder: "sk_live_...",
1124
+ validate: (value) => {
1125
+ if (!value) return "API key is required";
1126
+ if (!value.startsWith("sk_live_") && !value.startsWith("sk_test_")) {
1127
+ return "API key should start with sk_live_ or sk_test_";
1128
+ }
1129
+ return void 0;
1130
+ }
1131
+ });
1132
+ if (p3.isCancel(apiKey)) {
1133
+ p3.cancel("Setup cancelled.");
1134
+ process.exit(0);
1135
+ }
1136
+ const spinner5 = p3.spinner();
1137
+ spinner5.start("Validating API key...");
1138
+ const api = new OneApi(apiKey);
1139
+ const isValid = await api.validateApiKey();
1140
+ if (!isValid) {
1141
+ spinner5.stop("Invalid API key");
1142
+ p3.cancel(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
1143
+ process.exit(1);
1144
+ }
1145
+ spinner5.stop("API key validated");
1146
+ writeConfig({
1147
+ apiKey,
1148
+ installedAgents: [],
1149
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1150
+ });
1151
+ const allAgents = getAllAgents();
1152
+ const agentChoice = await p3.select({
1153
+ message: "Where do you want to install the MCP?",
1154
+ options: [
1155
+ {
1156
+ value: "all",
1157
+ label: "All agents",
1158
+ hint: allAgents.map((a) => a.name).join(", ")
1159
+ },
1160
+ ...allAgents.map((agent) => ({
1161
+ value: agent.id,
1162
+ label: agent.name
1163
+ }))
1164
+ ]
1165
+ });
1166
+ if (p3.isCancel(agentChoice)) {
1167
+ p3.cancel("Setup cancelled.");
1168
+ process.exit(0);
1169
+ }
1170
+ const selectedAgents = agentChoice === "all" ? allAgents : allAgents.filter((a) => a.id === agentChoice);
1171
+ let scope = "global";
1172
+ const hasProjectScopeAgent = selectedAgents.some((a) => supportsProjectScope(a));
1173
+ if (options.global) {
1174
+ scope = "global";
1175
+ } else if (options.project) {
1176
+ scope = "project";
1177
+ } else if (hasProjectScopeAgent) {
1178
+ const scopeChoice = await p3.select({
1179
+ message: "How do you want to install it?",
1180
+ options: [
1181
+ {
1182
+ value: "global",
1183
+ label: "Global (Recommended)",
1184
+ hint: "Available in all your projects"
1185
+ },
1186
+ {
1187
+ value: "project",
1188
+ label: "Project only",
1189
+ hint: "Creates config files in current directory"
1190
+ }
1191
+ ]
1192
+ });
1193
+ if (p3.isCancel(scopeChoice)) {
1194
+ p3.cancel("Setup cancelled.");
1195
+ process.exit(0);
1196
+ }
1197
+ scope = scopeChoice;
1198
+ }
1199
+ if (scope === "project") {
1200
+ const projectAgents = selectedAgents.filter((a) => supportsProjectScope(a));
1201
+ const nonProjectAgents = selectedAgents.filter((a) => !supportsProjectScope(a));
1202
+ if (projectAgents.length === 0) {
1203
+ const supported = allAgents.filter((a) => supportsProjectScope(a)).map((a) => a.name).join(", ");
1204
+ p3.note(
1205
+ `${selectedAgents.map((a) => a.name).join(", ")} does not support project-level MCP.
1206
+ Project scope is supported by: ${supported}`,
1207
+ "Not Supported"
1208
+ );
1209
+ p3.cancel("Run again and choose global scope or a different agent.");
1210
+ process.exit(1);
1211
+ }
1212
+ for (const agent of projectAgents) {
1213
+ const wasInstalled = isMcpInstalled(agent, "project");
1214
+ installMcpConfig(agent, apiKey, "project");
1215
+ const configPath = getAgentConfigPath(agent, "project");
1216
+ const status = wasInstalled ? "updated" : "created";
1217
+ p3.log.success(`${agent.name}: ${configPath} ${status}`);
1218
+ }
1219
+ if (nonProjectAgents.length > 0) {
1220
+ p3.log.info(`Installing globally for agents without project scope support:`);
1221
+ for (const agent of nonProjectAgents) {
1222
+ const wasInstalled = isMcpInstalled(agent, "global");
1223
+ installMcpConfig(agent, apiKey, "global");
1224
+ const status = wasInstalled ? "updated" : "installed";
1225
+ p3.log.success(`${agent.name}: MCP ${status} (global)`);
1226
+ }
1227
+ }
1228
+ const allInstalled = [...projectAgents, ...nonProjectAgents];
1229
+ writeConfig({
1230
+ apiKey,
1231
+ installedAgents: allInstalled.map((a) => a.id),
1232
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1233
+ });
1234
+ const configPaths = projectAgents.map((a) => ` ${a.name}: ${pc3.dim(getAgentConfigPath(a, "project"))}`).join("\n");
1235
+ let summary = `Config saved to: ${pc3.dim(getConfigPath())}
1236
+ MCP configs:
1237
+ ${configPaths}
1238
+
1239
+ `;
1240
+ if (nonProjectAgents.length > 0) {
1241
+ const globalPaths = nonProjectAgents.map((a) => ` ${a.name}: ${pc3.dim(getAgentConfigPath(a, "global"))}`).join("\n");
1242
+ summary += `Global configs:
1243
+ ${globalPaths}
1244
+
1245
+ `;
1246
+ }
1247
+ summary += pc3.yellow("Note: Project config files can be committed to share with your team.\n") + pc3.yellow("Team members will need their own API key.");
1248
+ p3.note(summary, "Setup Complete");
1249
+ await promptConnectIntegrations(apiKey);
1250
+ p3.outro("Your AI agents now have access to One integrations!");
1251
+ return;
1252
+ }
1253
+ const installedAgentIds = [];
1254
+ for (const agent of selectedAgents) {
1255
+ const wasInstalled = isMcpInstalled(agent, "global");
1256
+ installMcpConfig(agent, apiKey, "global");
1257
+ installedAgentIds.push(agent.id);
1258
+ const status = wasInstalled ? "updated" : "installed";
1259
+ p3.log.success(`${agent.name}: MCP ${status}`);
1260
+ }
1261
+ writeConfig({
1262
+ apiKey,
1263
+ installedAgents: installedAgentIds,
1264
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1265
+ });
1266
+ p3.note(
1267
+ `Config saved to: ${pc3.dim(getConfigPath())}`,
1268
+ "Setup Complete"
1269
+ );
1270
+ await promptConnectIntegrations(apiKey);
1271
+ p3.outro("Your AI agents now have access to One integrations!");
1272
+ }
1273
+ function printBanner() {
1274
+ console.log();
1275
+ console.log(pc3.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588"));
1276
+ console.log(pc3.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588"));
1277
+ console.log(pc3.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 "));
1278
+ console.log(pc3.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 "));
1279
+ console.log(pc3.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 "));
1280
+ console.log(pc3.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 "));
1281
+ console.log(pc3.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 "));
1282
+ console.log(pc3.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 "));
1283
+ console.log(pc3.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588"));
1284
+ console.log(pc3.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588"));
1285
+ console.log();
1286
+ console.log(pc3.dim(" U N I V E R S A L I N T E G R A T I O N S F O R A I"));
1287
+ console.log();
1288
+ }
1289
+ var TOP_INTEGRATIONS = [
1290
+ { value: "gmail", label: "Gmail", hint: "Read and send emails" },
1291
+ { value: "google-calendar", label: "Google Calendar", hint: "Manage events and schedules" },
1292
+ { value: "notion", label: "Notion", hint: "Access pages, databases, and docs" }
1293
+ ];
1294
+ async function promptConnectIntegrations(apiKey) {
1295
+ const api = new OneApi(apiKey);
1296
+ const connected = [];
1297
+ try {
1298
+ const existing = await api.listConnections();
1299
+ for (const conn of existing) {
1300
+ const match = TOP_INTEGRATIONS.find(
1301
+ (i) => i.value === conn.platform.toLowerCase()
1302
+ );
1303
+ if (match) connected.push(match.value);
1304
+ }
1305
+ } catch {
1306
+ }
1307
+ let first = true;
1308
+ while (true) {
1309
+ const available = TOP_INTEGRATIONS.filter((i) => !connected.includes(i.value));
1310
+ const options = [
1311
+ ...available.map((i) => ({
1312
+ value: i.value,
1313
+ label: i.label,
1314
+ hint: i.hint
1315
+ })),
1316
+ { value: "more", label: "Browse all 200+ platforms" },
1317
+ { value: "skip", label: "Skip for now", hint: "you can always run one add later" }
1318
+ ];
1319
+ const message = first ? "Connect your first integration?" : "Connect another?";
1320
+ const choice = await p3.select({ message, options });
1321
+ if (p3.isCancel(choice) || choice === "skip") {
1322
+ break;
1323
+ }
1324
+ if (choice === "more") {
1325
+ try {
1326
+ await open2("https://app.withone.ai/connections");
1327
+ p3.log.info("Opened One dashboard in browser.");
1328
+ } catch {
1329
+ p3.note("https://app.withone.ai/connections", "Open in browser");
1330
+ }
1331
+ p3.log.info(`Connect from the dashboard, or use ${pc3.cyan("one add <platform>")}`);
1332
+ break;
1333
+ }
1334
+ const platform = choice;
1335
+ const integration = TOP_INTEGRATIONS.find((i) => i.value === platform);
1336
+ const label = integration?.label ?? platform;
1337
+ p3.log.info(`Opening browser to connect ${pc3.cyan(label)}...`);
1338
+ try {
1339
+ await openConnectionPage(platform);
1340
+ } catch {
1341
+ const url = getConnectionUrl(platform);
1342
+ p3.log.warn("Could not open browser automatically.");
1343
+ p3.note(url, "Open manually");
1344
+ }
1345
+ const spinner5 = p3.spinner();
1346
+ spinner5.start("Waiting for connection... (complete auth in browser)");
1347
+ try {
1348
+ await api.waitForConnection(platform, 5 * 60 * 1e3, 5e3);
1349
+ spinner5.stop(`${label} connected!`);
1350
+ p3.log.success(`${pc3.green("\u2713")} ${label} is now available to your AI agents`);
1351
+ connected.push(platform);
1352
+ first = false;
1353
+ } catch (error2) {
1354
+ spinner5.stop("Connection timed out");
1355
+ if (error2 instanceof TimeoutError) {
1356
+ p3.log.warn(`No worries. Connect later with: ${pc3.cyan(`one add ${platform}`)}`);
1357
+ }
1358
+ first = false;
1359
+ }
1360
+ if (TOP_INTEGRATIONS.every((i) => connected.includes(i.value))) {
1361
+ p3.log.success("All top integrations connected!");
1362
+ break;
1363
+ }
1364
+ }
1365
+ }
1366
+ function maskApiKey(key) {
1367
+ if (key.length <= 12) return key.slice(0, 8) + "...";
1368
+ return key.slice(0, 8) + "..." + key.slice(-4);
1369
+ }
1370
+ function updateConfigAgents(agentId) {
1371
+ const config = readConfig();
1372
+ if (!config) return;
1373
+ if (!config.installedAgents.includes(agentId)) {
1374
+ config.installedAgents.push(agentId);
1375
+ writeConfig(config);
1376
+ }
1377
+ }
1378
+
1379
+ // src/commands/connection.ts
1380
+ import * as p4 from "@clack/prompts";
1381
+ import pc4 from "picocolors";
1382
+
1383
+ // src/lib/platforms.ts
1384
+ function findPlatform(platforms, query) {
1385
+ const normalizedQuery = query.toLowerCase().trim();
1386
+ const exact = platforms.find(
1387
+ (p7) => p7.platform.toLowerCase() === normalizedQuery || p7.name.toLowerCase() === normalizedQuery
1388
+ );
1389
+ if (exact) return exact;
1390
+ return null;
1391
+ }
1392
+ function findSimilarPlatforms(platforms, query, limit = 3) {
1393
+ const normalizedQuery = query.toLowerCase().trim();
1394
+ const scored = platforms.map((p7) => {
1395
+ const name = p7.name.toLowerCase();
1396
+ const slug = p7.platform.toLowerCase();
1397
+ let score = 0;
1398
+ if (name.includes(normalizedQuery) || slug.includes(normalizedQuery)) {
1399
+ score = 10;
1400
+ } else if (normalizedQuery.includes(name) || normalizedQuery.includes(slug)) {
1401
+ score = 5;
1402
+ } else {
1403
+ score = countMatchingChars(normalizedQuery, slug);
1404
+ }
1405
+ return { platform: p7, score };
1406
+ }).filter((item) => item.score > 0).sort((a, b) => b.score - a.score).slice(0, limit);
1407
+ return scored.map((item) => item.platform);
1408
+ }
1409
+ function countMatchingChars(a, b) {
1410
+ let count = 0;
1411
+ const bChars = new Set(b.split(""));
1412
+ for (const char of a) {
1413
+ if (bChars.has(char)) count++;
1414
+ }
1415
+ return count;
1416
+ }
1417
+
1418
+ // src/commands/connection.ts
1419
+ async function connectionAddCommand(platformArg) {
1420
+ if (isAgentMode()) {
1421
+ error("This command requires interactive input. Run without --agent.");
1422
+ }
1423
+ p4.intro(pc4.bgCyan(pc4.black(" One ")));
1424
+ const apiKey = getApiKey();
1425
+ if (!apiKey) {
1426
+ p4.cancel("Not configured. Run `one init` first.");
1427
+ process.exit(1);
1428
+ }
1429
+ const api = new OneApi(apiKey);
1430
+ const spinner5 = p4.spinner();
1431
+ spinner5.start("Loading platforms...");
1432
+ let platforms;
1433
+ try {
1434
+ platforms = await api.listPlatforms();
1435
+ spinner5.stop(`${platforms.length} platforms available`);
1436
+ } catch (error2) {
1437
+ spinner5.stop("Failed to load platforms");
1438
+ p4.cancel(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
1439
+ process.exit(1);
1440
+ }
1441
+ let platform;
1442
+ if (platformArg) {
1443
+ const found = findPlatform(platforms, platformArg);
1444
+ if (found) {
1445
+ platform = found.platform;
1446
+ } else {
1447
+ const similar = findSimilarPlatforms(platforms, platformArg);
1448
+ if (similar.length > 0) {
1449
+ p4.log.warn(`Unknown platform: ${platformArg}`);
1450
+ const suggestion = await p4.select({
1451
+ message: "Did you mean:",
1452
+ options: [
1453
+ ...similar.map((s) => ({ value: s.platform, label: `${s.name} (${s.platform})` })),
1454
+ { value: "__other__", label: "None of these" }
1455
+ ]
1456
+ });
1457
+ if (p4.isCancel(suggestion) || suggestion === "__other__") {
1458
+ p4.note(`Run ${pc4.cyan("one platforms")} to see all available platforms.`);
1459
+ p4.cancel("Connection cancelled.");
1460
+ process.exit(0);
1461
+ }
1462
+ platform = suggestion;
1463
+ } else {
1464
+ p4.cancel(`Unknown platform: ${platformArg}
1465
+
1466
+ Run ${pc4.cyan("one platforms")} to see available platforms.`);
1467
+ process.exit(1);
1468
+ }
1469
+ }
1470
+ } else {
1471
+ const platformInput = await p4.text({
1472
+ message: "Which platform do you want to connect?",
1473
+ placeholder: "gmail, slack, hubspot...",
1474
+ validate: (value) => {
1475
+ if (!value.trim()) return "Platform name is required";
1476
+ return void 0;
1477
+ }
1478
+ });
1479
+ if (p4.isCancel(platformInput)) {
1480
+ p4.cancel("Connection cancelled.");
1481
+ process.exit(0);
1482
+ }
1483
+ const found = findPlatform(platforms, platformInput);
1484
+ if (found) {
1485
+ platform = found.platform;
1486
+ } else {
1487
+ p4.cancel(`Unknown platform: ${platformInput}
1488
+
1489
+ Run ${pc4.cyan("one platforms")} to see available platforms.`);
1490
+ process.exit(1);
1491
+ }
1492
+ }
1493
+ const url = getConnectionUrl(platform);
1494
+ p4.log.info(`Opening browser to connect ${pc4.cyan(platform)}...`);
1495
+ p4.note(pc4.dim(url), "URL");
1496
+ try {
1497
+ await openConnectionPage(platform);
1498
+ } catch {
1499
+ p4.log.warn("Could not open browser automatically.");
1500
+ p4.note(`Open this URL manually:
1501
+ ${url}`);
1502
+ }
1503
+ const pollSpinner = p4.spinner();
1504
+ pollSpinner.start("Waiting for connection... (complete auth in browser)");
1505
+ try {
1506
+ const connection2 = await api.waitForConnection(platform, 5 * 60 * 1e3, 5e3);
1507
+ pollSpinner.stop(`${platform} connected!`);
1508
+ p4.log.success(`${pc4.green("\u2713")} ${connection2.platform} is now available to your AI agents.`);
1509
+ p4.outro("Connection complete!");
1510
+ } catch (error2) {
1511
+ pollSpinner.stop("Connection timed out");
1512
+ if (error2 instanceof TimeoutError) {
1513
+ p4.note(
1514
+ `Possible issues:
1515
+ - OAuth flow was not completed in the browser
1516
+ - Browser popup was blocked
1517
+ - Wrong account selected
1518
+
1519
+ Try again with: ${pc4.cyan(`one connection add ${platform}`)}`,
1520
+ "Timed Out"
1521
+ );
1522
+ } else {
1523
+ p4.log.error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
1524
+ }
1525
+ process.exit(1);
1526
+ }
1527
+ }
1528
+ async function connectionListCommand() {
1529
+ const apiKey = getApiKey();
1530
+ if (!apiKey) {
1531
+ error("Not configured. Run `one init` first.");
1532
+ }
1533
+ const api = new OneApi(apiKey);
1534
+ const spinner5 = createSpinner();
1535
+ spinner5.start("Loading connections...");
1536
+ try {
1537
+ const connections = await api.listConnections();
1538
+ if (isAgentMode()) {
1539
+ json({
1540
+ connections: connections.map((conn) => ({
1541
+ platform: conn.platform,
1542
+ state: conn.state,
1543
+ key: conn.key
1544
+ }))
1545
+ });
1546
+ return;
1547
+ }
1548
+ spinner5.stop(`${connections.length} connection${connections.length === 1 ? "" : "s"} found`);
1549
+ if (connections.length === 0) {
1550
+ p4.note(
1551
+ `No connections yet.
1552
+
1553
+ Add one with: ${pc4.cyan("one connection add gmail")}`,
1554
+ "No Connections"
1555
+ );
1556
+ return;
1557
+ }
1558
+ console.log();
1559
+ const rows = connections.map((conn) => ({
1560
+ status: getStatusIndicator(conn.state),
1561
+ platform: conn.platform,
1562
+ state: conn.state,
1563
+ key: conn.key
1564
+ }));
1565
+ printTable(
1566
+ [
1567
+ { key: "status", label: "" },
1568
+ { key: "platform", label: "Platform" },
1569
+ { key: "state", label: "Status" },
1570
+ { key: "key", label: "Connection Key", color: pc4.dim }
1571
+ ],
1572
+ rows
1573
+ );
1574
+ console.log();
1575
+ p4.note(`Add more with: ${pc4.cyan("one connection add <platform>")}`, "Tip");
1576
+ } catch (error2) {
1577
+ spinner5.stop("Failed to load connections");
1578
+ error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
1579
+ }
1580
+ }
1581
+ function getStatusIndicator(state) {
1582
+ switch (state) {
1583
+ case "operational":
1584
+ return pc4.green("\u25CF");
1585
+ case "degraded":
1586
+ return pc4.yellow("\u25CF");
1587
+ case "failed":
1588
+ return pc4.red("\u25CF");
1589
+ default:
1590
+ return pc4.dim("\u25CB");
1591
+ }
1592
+ }
1593
+
1594
+ // src/commands/platforms.ts
1595
+ import * as p5 from "@clack/prompts";
1596
+ import pc5 from "picocolors";
1597
+ async function platformsCommand(options) {
1598
+ const apiKey = getApiKey();
1599
+ if (!apiKey) {
1600
+ error("Not configured. Run `one init` first.");
1601
+ }
1602
+ if (isAgentMode()) {
1603
+ options.json = true;
1604
+ }
1605
+ const api = new OneApi(apiKey);
1606
+ const spinner5 = createSpinner();
1607
+ spinner5.start("Loading platforms...");
1608
+ try {
1609
+ const platforms = await api.listPlatforms();
1610
+ spinner5.stop(`${platforms.length} platforms available`);
1611
+ if (options.json) {
1612
+ if (isAgentMode()) {
1613
+ json({ platforms });
1614
+ } else {
1615
+ console.log(JSON.stringify(platforms, null, 2));
1616
+ }
1617
+ return;
1618
+ }
1619
+ const byCategory = /* @__PURE__ */ new Map();
1620
+ for (const plat of platforms) {
1621
+ const category = plat.category || "Other";
1622
+ if (!byCategory.has(category)) {
1623
+ byCategory.set(category, []);
1624
+ }
1625
+ byCategory.get(category).push(plat);
1626
+ }
1627
+ console.log();
1628
+ if (options.category) {
1629
+ const categoryPlatforms = byCategory.get(options.category);
1630
+ if (!categoryPlatforms) {
1631
+ const categories = [...byCategory.keys()].sort();
1632
+ p5.note(`Available categories:
1633
+ ${categories.join(", ")}`, "Unknown Category");
1634
+ process.exit(1);
1635
+ }
1636
+ const rows = categoryPlatforms.sort((a, b) => a.platform.localeCompare(b.platform)).map((plat) => ({
1637
+ platform: plat.platform,
1638
+ name: plat.name,
1639
+ category: plat.category || "Other"
1640
+ }));
1641
+ printTable(
1642
+ [
1643
+ { key: "platform", label: "Platform" },
1644
+ { key: "name", label: "Name" }
1645
+ ],
1646
+ rows
1647
+ );
1648
+ } else {
1649
+ const rows = platforms.sort((a, b) => a.category.localeCompare(b.category) || a.platform.localeCompare(b.platform)).map((plat) => ({
1650
+ platform: plat.platform,
1651
+ name: plat.name,
1652
+ category: plat.category || "Other"
1653
+ }));
1654
+ printTable(
1655
+ [
1656
+ { key: "category", label: "Category" },
1657
+ { key: "platform", label: "Platform" },
1658
+ { key: "name", label: "Name" }
1659
+ ],
1660
+ rows
1661
+ );
1662
+ }
1663
+ console.log();
1664
+ p5.note(`Connect with: ${pc5.cyan("one connection add <platform>")}`, "Tip");
1665
+ } catch (error2) {
1666
+ spinner5.stop("Failed to load platforms");
1667
+ error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
1668
+ }
1669
+ }
1670
+
1671
+ // src/commands/actions.ts
1672
+ import * as p6 from "@clack/prompts";
1673
+ import pc6 from "picocolors";
1674
+ function getConfig() {
1675
+ const apiKey = getApiKey();
1676
+ if (!apiKey) {
1677
+ error("Not configured. Run `one init` first.");
1678
+ }
1679
+ const ac = getAccessControlFromAllSources();
1680
+ const permissions = ac.permissions || "admin";
1681
+ const connectionKeys = ac.connectionKeys || ["*"];
1682
+ const actionIds = ac.actionIds || ["*"];
1683
+ const knowledgeAgent = ac.knowledgeAgent || false;
1684
+ return { apiKey, permissions, connectionKeys, actionIds, knowledgeAgent };
1685
+ }
1686
+ function parseJsonArg(value, argName) {
1687
+ try {
1688
+ return JSON.parse(value);
1689
+ } catch {
1690
+ error(`Invalid JSON for ${argName}: ${value}`);
1691
+ }
1692
+ }
1693
+ async function actionsSearchCommand(platform, query, options) {
1694
+ intro2(pc6.bgCyan(pc6.black(" One ")));
1695
+ const { apiKey, permissions, actionIds, knowledgeAgent } = getConfig();
1696
+ const api = new OneApi(apiKey);
1697
+ const spinner5 = createSpinner();
1698
+ spinner5.start(`Searching actions on ${pc6.cyan(platform)} for "${query}"...`);
1699
+ try {
1700
+ const agentType = knowledgeAgent ? "knowledge" : options.type;
1701
+ let actions2 = await api.searchActions(platform, query, agentType);
1702
+ actions2 = filterByPermissions(actions2, permissions);
1703
+ actions2 = actions2.filter((a) => isActionAllowed(a.systemId, actionIds));
1704
+ const cleanedActions = actions2.map((action) => ({
1705
+ actionId: action.systemId,
1706
+ title: action.title,
1707
+ method: action.method,
1708
+ path: action.path
1709
+ }));
1710
+ if (isAgentMode()) {
1711
+ json({ actions: cleanedActions });
1712
+ return;
1713
+ }
1714
+ if (cleanedActions.length === 0) {
1715
+ spinner5.stop("No actions found");
1716
+ p6.note(
1717
+ `No actions found for platform '${platform}' matching query '${query}'.
1718
+
1719
+ Suggestions:
1720
+ - Try a more general query (e.g., 'list', 'get', 'search', 'create')
1721
+ - Verify the platform name is correct
1722
+ - Check available platforms with ${pc6.cyan("one platforms")}
1723
+
1724
+ Examples of good queries:
1725
+ - "search contacts"
1726
+ - "send email"
1727
+ - "create customer"
1728
+ - "list orders"`,
1729
+ "No Results"
1730
+ );
1731
+ return;
1732
+ }
1733
+ spinner5.stop(
1734
+ `Found ${cleanedActions.length} action(s) for '${platform}' matching '${query}'`
1735
+ );
1736
+ console.log();
1737
+ const rows = cleanedActions.map((a) => ({
1738
+ method: colorMethod(a.method),
1739
+ title: a.title,
1740
+ actionId: a.actionId,
1741
+ path: a.path
1742
+ }));
1743
+ printTable(
1744
+ [
1745
+ { key: "method", label: "Method" },
1746
+ { key: "title", label: "Title" },
1747
+ { key: "actionId", label: "Action ID", color: pc6.dim },
1748
+ { key: "path", label: "Path", color: pc6.dim }
1749
+ ],
1750
+ rows
1751
+ );
1752
+ console.log();
1753
+ p6.note(
1754
+ `Get details: ${pc6.cyan(`one actions knowledge ${platform} <actionId>`)}
1755
+ Execute: ${pc6.cyan(`one actions execute ${platform} <actionId> <connectionKey>`)}`,
1756
+ "Next Steps"
1757
+ );
1758
+ } catch (error2) {
1759
+ spinner5.stop("Search failed");
1760
+ error(
1761
+ `Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`
1762
+ );
1763
+ }
1764
+ }
1765
+ async function actionsKnowledgeCommand(platform, actionId) {
1766
+ intro2(pc6.bgCyan(pc6.black(" One ")));
1767
+ const { apiKey, actionIds, connectionKeys } = getConfig();
1768
+ const api = new OneApi(apiKey);
1769
+ if (!isActionAllowed(actionId, actionIds)) {
1770
+ error(`Action "${actionId}" is not in the allowed action list.`);
1771
+ }
1772
+ if (!connectionKeys.includes("*")) {
1773
+ const spinner6 = createSpinner();
1774
+ spinner6.start("Checking connections...");
1775
+ try {
1776
+ const connections = await api.listConnections();
1777
+ const connectedPlatforms = connections.map((c) => c.platform);
1778
+ if (!connectedPlatforms.includes(platform)) {
1779
+ spinner6.stop("Platform not connected");
1780
+ error(`Platform "${platform}" has no allowed connections.`);
1781
+ }
1782
+ spinner6.stop("Connection verified");
1783
+ } catch (error2) {
1784
+ spinner6.stop("Failed to check connections");
1785
+ error(
1786
+ `Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`
1787
+ );
1788
+ }
1789
+ }
1790
+ const spinner5 = createSpinner();
1791
+ spinner5.start(`Loading knowledge for action ${pc6.dim(actionId)}...`);
1792
+ try {
1793
+ const { knowledge, method } = await api.getActionKnowledge(actionId);
1794
+ const knowledgeWithGuidance = buildActionKnowledgeWithGuidance(
1795
+ knowledge,
1796
+ method,
1797
+ platform,
1798
+ actionId
1799
+ );
1800
+ if (isAgentMode()) {
1801
+ json({ knowledge: knowledgeWithGuidance, method });
1802
+ return;
1803
+ }
1804
+ spinner5.stop("Knowledge loaded");
1805
+ console.log();
1806
+ console.log(knowledgeWithGuidance);
1807
+ console.log();
1808
+ p6.note(
1809
+ `Execute: ${pc6.cyan(`one actions execute ${platform} ${actionId} <connectionKey>`)}`,
1810
+ "Next Step"
1811
+ );
1812
+ } catch (error2) {
1813
+ spinner5.stop("Failed to load knowledge");
1814
+ error(
1815
+ `Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`
1816
+ );
1817
+ }
1818
+ }
1819
+ async function actionsExecuteCommand(platform, actionId, connectionKey, options) {
1820
+ intro2(pc6.bgCyan(pc6.black(" One ")));
1821
+ const { apiKey, permissions, actionIds, connectionKeys, knowledgeAgent } = getConfig();
1822
+ if (knowledgeAgent) {
1823
+ error(
1824
+ "Action execution is disabled (knowledge-only mode)."
1825
+ );
1826
+ }
1827
+ if (!isActionAllowed(actionId, actionIds)) {
1828
+ error(`Action "${actionId}" is not in the allowed action list.`);
1829
+ }
1830
+ if (!connectionKeys.includes("*") && !connectionKeys.includes(connectionKey)) {
1831
+ error(`Connection key "${connectionKey}" is not allowed.`);
1832
+ }
1833
+ const api = new OneApi(apiKey);
1834
+ const spinner5 = createSpinner();
1835
+ spinner5.start("Loading action details...");
1836
+ try {
1837
+ const actionDetails = await api.getActionDetails(actionId);
1838
+ if (!isMethodAllowed(actionDetails.method, permissions)) {
1839
+ spinner5.stop("Permission denied");
1840
+ error(
1841
+ `Method "${actionDetails.method}" is not allowed under "${permissions}" permission level.`
1842
+ );
1843
+ }
1844
+ spinner5.stop(`Action: ${actionDetails.title} [${actionDetails.method}]`);
1845
+ const data = options.data ? parseJsonArg(options.data, "--data") : void 0;
1846
+ const pathVariables = options.pathVars ? parseJsonArg(options.pathVars, "--path-vars") : void 0;
1847
+ const queryParams = options.queryParams ? parseJsonArg(options.queryParams, "--query-params") : void 0;
1848
+ const headers = options.headers ? parseJsonArg(options.headers, "--headers") : void 0;
1849
+ const execSpinner = createSpinner();
1850
+ execSpinner.start(options.dryRun ? "Building request..." : "Executing action...");
1851
+ const result = await api.executePassthroughRequest(
1852
+ {
1853
+ platform,
1854
+ actionId,
1855
+ connectionKey,
1856
+ data,
1857
+ pathVariables,
1858
+ queryParams,
1859
+ headers,
1860
+ isFormData: options.formData,
1861
+ isFormUrlEncoded: options.formUrlEncoded,
1862
+ dryRun: options.dryRun
1863
+ },
1864
+ actionDetails
1865
+ );
1866
+ execSpinner.stop(options.dryRun ? "Dry run \u2014 request not sent" : "Action executed successfully");
1867
+ if (isAgentMode()) {
1868
+ json({
1869
+ dryRun: options.dryRun || false,
1870
+ request: {
1871
+ method: result.requestConfig.method,
1872
+ url: result.requestConfig.url,
1873
+ headers: options.dryRun ? result.requestConfig.headers : void 0,
1874
+ data: options.dryRun ? result.requestConfig.data : void 0
1875
+ },
1876
+ response: options.dryRun ? void 0 : result.responseData
1877
+ });
1878
+ return;
1879
+ }
1880
+ console.log();
1881
+ console.log(pc6.dim("Request:"));
1882
+ console.log(
1883
+ pc6.dim(
1884
+ ` ${result.requestConfig.method} ${result.requestConfig.url}`
1885
+ )
1886
+ );
1887
+ if (options.dryRun) {
1888
+ if (result.requestConfig.data) {
1889
+ console.log();
1890
+ console.log(pc6.dim("Body:"));
1891
+ console.log(pc6.dim(JSON.stringify(result.requestConfig.data, null, 2)));
1892
+ }
1893
+ console.log();
1894
+ note2("Dry run \u2014 request was not sent", "Dry Run");
1895
+ } else {
1896
+ console.log();
1897
+ console.log(pc6.bold("Response:"));
1898
+ console.log(JSON.stringify(result.responseData, null, 2));
1899
+ }
1900
+ } catch (error2) {
1901
+ spinner5.stop("Execution failed");
1902
+ error(
1903
+ `Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`
1904
+ );
1905
+ }
1906
+ }
1907
+ function colorMethod(method) {
1908
+ switch (method.toUpperCase()) {
1909
+ case "GET":
1910
+ return pc6.green(method);
1911
+ case "POST":
1912
+ return pc6.yellow(method);
1913
+ case "PUT":
1914
+ return pc6.blue(method);
1915
+ case "PATCH":
1916
+ return pc6.magenta(method);
1917
+ case "DELETE":
1918
+ return pc6.red(method);
1919
+ default:
1920
+ return method;
1921
+ }
1922
+ }
1923
+
1924
+ // src/commands/flow.ts
1925
+ import pc7 from "picocolors";
1926
+
1927
+ // src/lib/flow-validator.ts
1928
+ var VALID_STEP_TYPES = [
1929
+ "action",
1930
+ "transform",
1931
+ "code",
1932
+ "condition",
1933
+ "loop",
1934
+ "parallel",
1935
+ "file-read",
1936
+ "file-write"
1937
+ ];
1938
+ var VALID_INPUT_TYPES = ["string", "number", "boolean", "object", "array"];
1939
+ var VALID_ERROR_STRATEGIES = ["fail", "continue", "retry", "fallback"];
1940
+ function validateFlowSchema(flow2) {
1941
+ const errors = [];
1942
+ if (!flow2 || typeof flow2 !== "object") {
1943
+ errors.push({ path: "", message: "Flow must be a JSON object" });
1944
+ return errors;
1945
+ }
1946
+ const f = flow2;
1947
+ if (!f.key || typeof f.key !== "string") {
1948
+ errors.push({ path: "key", message: 'Flow must have a string "key"' });
1949
+ } else if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(f.key) && f.key.length > 1) {
1950
+ errors.push({ path: "key", message: "Flow key must be kebab-case (lowercase letters, numbers, hyphens)" });
1951
+ }
1952
+ if (!f.name || typeof f.name !== "string") {
1953
+ errors.push({ path: "name", message: 'Flow must have a string "name"' });
1954
+ }
1955
+ if (f.description !== void 0 && typeof f.description !== "string") {
1956
+ errors.push({ path: "description", message: '"description" must be a string' });
1957
+ }
1958
+ if (f.version !== void 0 && typeof f.version !== "string") {
1959
+ errors.push({ path: "version", message: '"version" must be a string' });
1960
+ }
1961
+ if (!f.inputs || typeof f.inputs !== "object" || Array.isArray(f.inputs)) {
1962
+ errors.push({ path: "inputs", message: 'Flow must have an "inputs" object' });
1963
+ } else {
1964
+ const inputs = f.inputs;
1965
+ for (const [name, decl] of Object.entries(inputs)) {
1966
+ const prefix = `inputs.${name}`;
1967
+ if (!decl || typeof decl !== "object" || Array.isArray(decl)) {
1968
+ errors.push({ path: prefix, message: "Input declaration must be an object" });
1969
+ continue;
1970
+ }
1971
+ const d = decl;
1972
+ if (!d.type || !VALID_INPUT_TYPES.includes(d.type)) {
1973
+ errors.push({ path: `${prefix}.type`, message: `Input type must be one of: ${VALID_INPUT_TYPES.join(", ")}` });
1974
+ }
1975
+ if (d.connection !== void 0) {
1976
+ if (!d.connection || typeof d.connection !== "object") {
1977
+ errors.push({ path: `${prefix}.connection`, message: "Connection metadata must be an object" });
1978
+ } else {
1979
+ const conn = d.connection;
1980
+ if (!conn.platform || typeof conn.platform !== "string") {
1981
+ errors.push({ path: `${prefix}.connection.platform`, message: 'Connection must have a string "platform"' });
1982
+ }
1983
+ }
1984
+ }
1985
+ }
1986
+ }
1987
+ if (!Array.isArray(f.steps)) {
1988
+ errors.push({ path: "steps", message: 'Flow must have a "steps" array' });
1989
+ } else {
1990
+ validateStepsArray(f.steps, "steps", errors);
1991
+ }
1992
+ return errors;
1993
+ }
1994
+ function validateStepsArray(steps, pathPrefix, errors) {
1995
+ for (let i = 0; i < steps.length; i++) {
1996
+ const step = steps[i];
1997
+ const path5 = `${pathPrefix}[${i}]`;
1998
+ if (!step || typeof step !== "object" || Array.isArray(step)) {
1999
+ errors.push({ path: path5, message: "Step must be an object" });
2000
+ continue;
2001
+ }
2002
+ const s = step;
2003
+ if (!s.id || typeof s.id !== "string") {
2004
+ errors.push({ path: `${path5}.id`, message: 'Step must have a string "id"' });
2005
+ }
2006
+ if (!s.name || typeof s.name !== "string") {
2007
+ errors.push({ path: `${path5}.name`, message: 'Step must have a string "name"' });
2008
+ }
2009
+ if (!s.type || !VALID_STEP_TYPES.includes(s.type)) {
2010
+ errors.push({ path: `${path5}.type`, message: `Step type must be one of: ${VALID_STEP_TYPES.join(", ")}` });
2011
+ }
2012
+ if (s.onError && typeof s.onError === "object") {
2013
+ const oe = s.onError;
2014
+ if (!VALID_ERROR_STRATEGIES.includes(oe.strategy)) {
2015
+ errors.push({ path: `${path5}.onError.strategy`, message: `Error strategy must be one of: ${VALID_ERROR_STRATEGIES.join(", ")}` });
2016
+ }
2017
+ }
2018
+ const type = s.type;
2019
+ if (type === "action") {
2020
+ if (!s.action || typeof s.action !== "object") {
2021
+ errors.push({ path: `${path5}.action`, message: 'Action step must have an "action" config object' });
2022
+ } else {
2023
+ const a = s.action;
2024
+ if (!a.platform) errors.push({ path: `${path5}.action.platform`, message: 'Action must have "platform"' });
2025
+ if (!a.actionId) errors.push({ path: `${path5}.action.actionId`, message: 'Action must have "actionId"' });
2026
+ if (!a.connectionKey) errors.push({ path: `${path5}.action.connectionKey`, message: 'Action must have "connectionKey"' });
2027
+ }
2028
+ } else if (type === "transform") {
2029
+ if (!s.transform || typeof s.transform !== "object") {
2030
+ errors.push({ path: `${path5}.transform`, message: 'Transform step must have a "transform" config object' });
2031
+ } else {
2032
+ const t = s.transform;
2033
+ if (!t.expression || typeof t.expression !== "string") {
2034
+ errors.push({ path: `${path5}.transform.expression`, message: 'Transform must have a string "expression"' });
2035
+ }
2036
+ }
2037
+ } else if (type === "code") {
2038
+ if (!s.code || typeof s.code !== "object") {
2039
+ errors.push({ path: `${path5}.code`, message: 'Code step must have a "code" config object' });
2040
+ } else {
2041
+ const c = s.code;
2042
+ if (!c.source || typeof c.source !== "string") {
2043
+ errors.push({ path: `${path5}.code.source`, message: 'Code must have a string "source"' });
2044
+ }
2045
+ }
2046
+ } else if (type === "condition") {
2047
+ if (!s.condition || typeof s.condition !== "object") {
2048
+ errors.push({ path: `${path5}.condition`, message: 'Condition step must have a "condition" config object' });
2049
+ } else {
2050
+ const c = s.condition;
2051
+ if (!c.expression || typeof c.expression !== "string") {
2052
+ errors.push({ path: `${path5}.condition.expression`, message: 'Condition must have a string "expression"' });
2053
+ }
2054
+ if (!Array.isArray(c.then)) {
2055
+ errors.push({ path: `${path5}.condition.then`, message: 'Condition must have a "then" steps array' });
2056
+ } else {
2057
+ validateStepsArray(c.then, `${path5}.condition.then`, errors);
2058
+ }
2059
+ if (c.else !== void 0) {
2060
+ if (!Array.isArray(c.else)) {
2061
+ errors.push({ path: `${path5}.condition.else`, message: 'Condition "else" must be a steps array' });
2062
+ } else {
2063
+ validateStepsArray(c.else, `${path5}.condition.else`, errors);
2064
+ }
2065
+ }
2066
+ }
2067
+ } else if (type === "loop") {
2068
+ if (!s.loop || typeof s.loop !== "object") {
2069
+ errors.push({ path: `${path5}.loop`, message: 'Loop step must have a "loop" config object' });
2070
+ } else {
2071
+ const l = s.loop;
2072
+ if (!l.over || typeof l.over !== "string") {
2073
+ errors.push({ path: `${path5}.loop.over`, message: 'Loop must have a string "over" selector' });
2074
+ }
2075
+ if (!l.as || typeof l.as !== "string") {
2076
+ errors.push({ path: `${path5}.loop.as`, message: 'Loop must have a string "as" variable name' });
2077
+ }
2078
+ if (!Array.isArray(l.steps)) {
2079
+ errors.push({ path: `${path5}.loop.steps`, message: 'Loop must have a "steps" array' });
2080
+ } else {
2081
+ validateStepsArray(l.steps, `${path5}.loop.steps`, errors);
2082
+ }
2083
+ }
2084
+ } else if (type === "parallel") {
2085
+ if (!s.parallel || typeof s.parallel !== "object") {
2086
+ errors.push({ path: `${path5}.parallel`, message: 'Parallel step must have a "parallel" config object' });
2087
+ } else {
2088
+ const par = s.parallel;
2089
+ if (!Array.isArray(par.steps)) {
2090
+ errors.push({ path: `${path5}.parallel.steps`, message: 'Parallel must have a "steps" array' });
2091
+ } else {
2092
+ validateStepsArray(par.steps, `${path5}.parallel.steps`, errors);
2093
+ }
2094
+ }
2095
+ } else if (type === "file-read") {
2096
+ if (!s.fileRead || typeof s.fileRead !== "object") {
2097
+ errors.push({ path: `${path5}.fileRead`, message: 'File-read step must have a "fileRead" config object' });
2098
+ } else {
2099
+ const fr = s.fileRead;
2100
+ if (!fr.path || typeof fr.path !== "string") {
2101
+ errors.push({ path: `${path5}.fileRead.path`, message: 'File-read must have a string "path"' });
2102
+ }
2103
+ }
2104
+ } else if (type === "file-write") {
2105
+ if (!s.fileWrite || typeof s.fileWrite !== "object") {
2106
+ errors.push({ path: `${path5}.fileWrite`, message: 'File-write step must have a "fileWrite" config object' });
2107
+ } else {
2108
+ const fw = s.fileWrite;
2109
+ if (!fw.path || typeof fw.path !== "string") {
2110
+ errors.push({ path: `${path5}.fileWrite.path`, message: 'File-write must have a string "path"' });
2111
+ }
2112
+ if (fw.content === void 0) {
2113
+ errors.push({ path: `${path5}.fileWrite.content`, message: 'File-write must have "content"' });
2114
+ }
2115
+ }
2116
+ }
2117
+ }
2118
+ }
2119
+ function validateStepIds(flow2) {
2120
+ const errors = [];
2121
+ const seen = /* @__PURE__ */ new Set();
2122
+ function collectIds(steps, pathPrefix) {
2123
+ for (let i = 0; i < steps.length; i++) {
2124
+ const step = steps[i];
2125
+ const path5 = `${pathPrefix}[${i}]`;
2126
+ if (seen.has(step.id)) {
2127
+ errors.push({ path: `${path5}.id`, message: `Duplicate step ID: "${step.id}"` });
2128
+ } else {
2129
+ seen.add(step.id);
2130
+ }
2131
+ if (step.condition) {
2132
+ if (step.condition.then) collectIds(step.condition.then, `${path5}.condition.then`);
2133
+ if (step.condition.else) collectIds(step.condition.else, `${path5}.condition.else`);
2134
+ }
2135
+ if (step.loop?.steps) collectIds(step.loop.steps, `${path5}.loop.steps`);
2136
+ if (step.parallel?.steps) collectIds(step.parallel.steps, `${path5}.parallel.steps`);
2137
+ }
2138
+ }
2139
+ collectIds(flow2.steps, "steps");
2140
+ return errors;
2141
+ }
2142
+ function validateSelectorReferences(flow2) {
2143
+ const errors = [];
2144
+ const inputNames = new Set(Object.keys(flow2.inputs));
2145
+ function getAllStepIds(steps) {
2146
+ const ids = /* @__PURE__ */ new Set();
2147
+ for (const step of steps) {
2148
+ ids.add(step.id);
2149
+ if (step.condition) {
2150
+ for (const id of getAllStepIds(step.condition.then)) ids.add(id);
2151
+ if (step.condition.else) {
2152
+ for (const id of getAllStepIds(step.condition.else)) ids.add(id);
2153
+ }
2154
+ }
2155
+ if (step.loop?.steps) {
2156
+ for (const id of getAllStepIds(step.loop.steps)) ids.add(id);
2157
+ }
2158
+ if (step.parallel?.steps) {
2159
+ for (const id of getAllStepIds(step.parallel.steps)) ids.add(id);
2160
+ }
2161
+ }
2162
+ return ids;
2163
+ }
2164
+ const allStepIds = getAllStepIds(flow2.steps);
2165
+ function extractSelectors(value) {
2166
+ const selectors = [];
2167
+ if (typeof value === "string") {
2168
+ if (value.startsWith("$.")) {
2169
+ selectors.push(value);
2170
+ }
2171
+ const interpolated = value.matchAll(/\{\{(\$\.[^}]+)\}\}/g);
2172
+ for (const match of interpolated) {
2173
+ selectors.push(match[1]);
2174
+ }
2175
+ } else if (Array.isArray(value)) {
2176
+ for (const item of value) {
2177
+ selectors.push(...extractSelectors(item));
2178
+ }
2179
+ } else if (value && typeof value === "object") {
2180
+ for (const v of Object.values(value)) {
2181
+ selectors.push(...extractSelectors(v));
2182
+ }
2183
+ }
2184
+ return selectors;
2185
+ }
2186
+ function checkSelectors(selectors, path5) {
2187
+ for (const selector of selectors) {
2188
+ const parts = selector.split(".");
2189
+ if (parts.length < 3) continue;
2190
+ const root = parts[1];
2191
+ if (root === "input") {
2192
+ const inputName = parts[2];
2193
+ if (!inputNames.has(inputName)) {
2194
+ errors.push({ path: path5, message: `Selector "${selector}" references undefined input "${inputName}"` });
2195
+ }
2196
+ } else if (root === "steps") {
2197
+ const stepId = parts[2];
2198
+ if (!allStepIds.has(stepId)) {
2199
+ errors.push({ path: path5, message: `Selector "${selector}" references undefined step "${stepId}"` });
2200
+ }
2201
+ }
2202
+ }
2203
+ }
2204
+ function checkStep(step, pathPrefix) {
2205
+ if (step.if) checkSelectors(extractSelectors(step.if), `${pathPrefix}.if`);
2206
+ if (step.unless) checkSelectors(extractSelectors(step.unless), `${pathPrefix}.unless`);
2207
+ if (step.action) {
2208
+ checkSelectors(extractSelectors(step.action), `${pathPrefix}.action`);
2209
+ }
2210
+ if (step.transform) {
2211
+ }
2212
+ if (step.condition) {
2213
+ checkStep({ id: "__cond_expr", name: "", type: "transform", transform: { expression: "" } }, pathPrefix);
2214
+ step.condition.then.forEach((s, i) => checkStep(s, `${pathPrefix}.condition.then[${i}]`));
2215
+ step.condition.else?.forEach((s, i) => checkStep(s, `${pathPrefix}.condition.else[${i}]`));
2216
+ }
2217
+ if (step.loop) {
2218
+ checkSelectors(extractSelectors(step.loop.over), `${pathPrefix}.loop.over`);
2219
+ step.loop.steps.forEach((s, i) => checkStep(s, `${pathPrefix}.loop.steps[${i}]`));
2220
+ }
2221
+ if (step.parallel) {
2222
+ step.parallel.steps.forEach((s, i) => checkStep(s, `${pathPrefix}.parallel.steps[${i}]`));
2223
+ }
2224
+ if (step.fileRead) {
2225
+ checkSelectors(extractSelectors(step.fileRead.path), `${pathPrefix}.fileRead.path`);
2226
+ }
2227
+ if (step.fileWrite) {
2228
+ checkSelectors(extractSelectors(step.fileWrite.path), `${pathPrefix}.fileWrite.path`);
2229
+ checkSelectors(extractSelectors(step.fileWrite.content), `${pathPrefix}.fileWrite.content`);
2230
+ }
2231
+ }
2232
+ flow2.steps.forEach((step, i) => checkStep(step, `steps[${i}]`));
2233
+ return errors;
2234
+ }
2235
+ function validateFlow(flow2) {
2236
+ const schemaErrors = validateFlowSchema(flow2);
2237
+ if (schemaErrors.length > 0) return schemaErrors;
2238
+ const f = flow2;
2239
+ return [
2240
+ ...validateStepIds(f),
2241
+ ...validateSelectorReferences(f)
2242
+ ];
2243
+ }
2244
+
2245
+ // src/lib/flow-runner.ts
2246
+ import fs4 from "fs";
2247
+ import path4 from "path";
2248
+ import crypto from "crypto";
2249
+
2250
+ // src/lib/flow-engine.ts
2251
+ import fs3 from "fs";
2252
+ import path3 from "path";
2253
+ function sleep2(ms) {
2254
+ return new Promise((resolve) => setTimeout(resolve, ms));
2255
+ }
2256
+ function resolveSelector(selectorPath, context) {
2257
+ if (!selectorPath.startsWith("$.")) return selectorPath;
2258
+ const parts = selectorPath.slice(2).split(/\.|\[/).map((p7) => p7.replace(/\]$/, ""));
2259
+ let current = context;
2260
+ for (const part of parts) {
2261
+ if (current === null || current === void 0) return void 0;
2262
+ if (part === "*" && Array.isArray(current)) {
2263
+ continue;
2264
+ }
2265
+ if (Array.isArray(current) && part === "*") {
2266
+ continue;
2267
+ }
2268
+ if (Array.isArray(current)) {
2269
+ const idx = Number(part);
2270
+ if (!isNaN(idx)) {
2271
+ current = current[idx];
2272
+ } else {
2273
+ current = current.map((item) => item?.[part]);
2274
+ }
2275
+ } else if (typeof current === "object") {
2276
+ current = current[part];
2277
+ } else {
2278
+ return void 0;
2279
+ }
2280
+ }
2281
+ return current;
2282
+ }
2283
+ function interpolateString(str, context) {
2284
+ return str.replace(/\{\{(\$\.[^}]+)\}\}/g, (_match, selector) => {
2285
+ const value = resolveSelector(selector, context);
2286
+ if (value === void 0 || value === null) return "";
2287
+ if (typeof value === "object") return JSON.stringify(value);
2288
+ return String(value);
2289
+ });
2290
+ }
2291
+ function resolveValue(value, context) {
2292
+ if (typeof value === "string") {
2293
+ if (value.startsWith("$.") && !value.includes("{{")) {
2294
+ return resolveSelector(value, context);
2295
+ }
2296
+ if (value.includes("{{$.")) {
2297
+ return interpolateString(value, context);
2298
+ }
2299
+ return value;
2300
+ }
2301
+ if (Array.isArray(value)) {
2302
+ return value.map((item) => resolveValue(item, context));
2303
+ }
2304
+ if (value && typeof value === "object") {
2305
+ const resolved = {};
2306
+ for (const [k, v] of Object.entries(value)) {
2307
+ resolved[k] = resolveValue(v, context);
2308
+ }
2309
+ return resolved;
2310
+ }
2311
+ return value;
2312
+ }
2313
+ function evaluateExpression(expr, context) {
2314
+ const fn = new Function("$", `return (${expr})`);
2315
+ return fn(context);
2316
+ }
2317
+ async function executeActionStep(step, context, api, permissions, allowedActionIds) {
2318
+ const action = step.action;
2319
+ const platform = resolveValue(action.platform, context);
2320
+ const actionId = resolveValue(action.actionId, context);
2321
+ const connectionKey = resolveValue(action.connectionKey, context);
2322
+ const data = action.data ? resolveValue(action.data, context) : void 0;
2323
+ const pathVars = action.pathVars ? resolveValue(action.pathVars, context) : void 0;
2324
+ const queryParams = action.queryParams ? resolveValue(action.queryParams, context) : void 0;
2325
+ const headers = action.headers ? resolveValue(action.headers, context) : void 0;
2326
+ if (!isActionAllowed(actionId, allowedActionIds)) {
2327
+ throw new Error(`Action "${actionId}" is not in the allowed action list`);
2328
+ }
2329
+ const actionDetails = await api.getActionDetails(actionId);
2330
+ if (!isMethodAllowed(actionDetails.method, permissions)) {
2331
+ throw new Error(`Method "${actionDetails.method}" is not allowed under "${permissions}" permission level`);
2332
+ }
2333
+ const result = await api.executePassthroughRequest({
2334
+ platform,
2335
+ actionId,
2336
+ connectionKey,
2337
+ data,
2338
+ pathVariables: pathVars,
2339
+ queryParams,
2340
+ headers
2341
+ }, actionDetails);
2342
+ return {
2343
+ status: "success",
2344
+ response: result.responseData,
2345
+ output: result.responseData
2346
+ };
2347
+ }
2348
+ function executeTransformStep(step, context) {
2349
+ const output = evaluateExpression(step.transform.expression, context);
2350
+ return { status: "success", output, response: output };
2351
+ }
2352
+ async function executeCodeStep(step, context) {
2353
+ const source = step.code.source;
2354
+ const AsyncFunction = Object.getPrototypeOf(async function() {
2355
+ }).constructor;
2356
+ const fn = new AsyncFunction("$", source);
2357
+ const output = await fn(context);
2358
+ return { status: "success", output, response: output };
2359
+ }
2360
+ async function executeConditionStep(step, context, api, permissions, allowedActionIds, options) {
2361
+ const condition = step.condition;
2362
+ const result = evaluateExpression(condition.expression, context);
2363
+ const branch = result ? condition.then : condition.else || [];
2364
+ const branchResults = await executeSteps(branch, context, api, permissions, allowedActionIds, options);
2365
+ return {
2366
+ status: "success",
2367
+ output: { conditionResult: !!result, stepsExecuted: branchResults },
2368
+ response: { conditionResult: !!result }
2369
+ };
2370
+ }
2371
+ async function executeLoopStep(step, context, api, permissions, allowedActionIds, options) {
2372
+ const loop = step.loop;
2373
+ const items = resolveValue(loop.over, context);
2374
+ if (!Array.isArray(items)) {
2375
+ throw new Error(`Loop "over" must resolve to an array, got ${typeof items}`);
2376
+ }
2377
+ const maxIterations = loop.maxIterations || 1e3;
2378
+ const bounded = items.slice(0, maxIterations);
2379
+ const savedLoop = { ...context.loop };
2380
+ if (loop.maxConcurrency && loop.maxConcurrency > 1) {
2381
+ const results2 = new Array(bounded.length);
2382
+ for (let batchStart = 0; batchStart < bounded.length; batchStart += loop.maxConcurrency) {
2383
+ const batch = bounded.slice(batchStart, batchStart + loop.maxConcurrency);
2384
+ const batchResults = await Promise.all(
2385
+ batch.map(async (item, batchIdx) => {
2386
+ const i = batchStart + batchIdx;
2387
+ const iterContext = {
2388
+ ...context,
2389
+ loop: {
2390
+ [loop.as]: item,
2391
+ item,
2392
+ i,
2393
+ ...loop.indexAs ? { [loop.indexAs]: i } : {}
2394
+ },
2395
+ steps: { ...context.steps }
2396
+ };
2397
+ await executeSteps(loop.steps, iterContext, api, permissions, allowedActionIds, options);
2398
+ Object.assign(context.steps, iterContext.steps);
2399
+ return iterContext.loop[loop.as];
2400
+ })
2401
+ );
2402
+ for (let j = 0; j < batchResults.length; j++) {
2403
+ results2[batchStart + j] = batchResults[j];
2404
+ }
2405
+ }
2406
+ context.loop = savedLoop;
2407
+ return { status: "success", output: results2, response: results2 };
2408
+ }
2409
+ const results = [];
2410
+ for (let i = 0; i < bounded.length; i++) {
2411
+ context.loop = {
2412
+ [loop.as]: bounded[i],
2413
+ item: bounded[i],
2414
+ i
2415
+ };
2416
+ if (loop.indexAs) {
2417
+ context.loop[loop.indexAs] = i;
2418
+ }
2419
+ await executeSteps(loop.steps, context, api, permissions, allowedActionIds, options);
2420
+ results.push(context.loop[loop.as]);
2421
+ }
2422
+ context.loop = savedLoop;
2423
+ return { status: "success", output: results, response: results };
2424
+ }
2425
+ async function executeParallelStep(step, context, api, permissions, allowedActionIds, options) {
2426
+ const parallel = step.parallel;
2427
+ const maxConcurrency = parallel.maxConcurrency || 5;
2428
+ const steps = parallel.steps;
2429
+ const results = [];
2430
+ for (let i = 0; i < steps.length; i += maxConcurrency) {
2431
+ const batch = steps.slice(i, i + maxConcurrency);
2432
+ const batchResults = await Promise.all(
2433
+ batch.map((s) => executeSingleStep(s, context, api, permissions, allowedActionIds, options))
2434
+ );
2435
+ results.push(...batchResults);
2436
+ }
2437
+ return { status: "success", output: results, response: results };
2438
+ }
2439
+ function executeFileReadStep(step, context) {
2440
+ const config = step.fileRead;
2441
+ const filePath = resolveValue(config.path, context);
2442
+ const resolvedPath = path3.resolve(filePath);
2443
+ const content = fs3.readFileSync(resolvedPath, "utf-8");
2444
+ const output = config.parseJson ? JSON.parse(content) : content;
2445
+ return { status: "success", output, response: output };
2446
+ }
2447
+ function executeFileWriteStep(step, context) {
2448
+ const config = step.fileWrite;
2449
+ const filePath = resolveValue(config.path, context);
2450
+ const content = resolveValue(config.content, context);
2451
+ const resolvedPath = path3.resolve(filePath);
2452
+ const dir = path3.dirname(resolvedPath);
2453
+ if (!fs3.existsSync(dir)) {
2454
+ fs3.mkdirSync(dir, { recursive: true });
2455
+ }
2456
+ const stringContent = typeof content === "string" ? content : JSON.stringify(content, null, 2);
2457
+ if (config.append) {
2458
+ fs3.appendFileSync(resolvedPath, stringContent);
2459
+ } else {
2460
+ fs3.writeFileSync(resolvedPath, stringContent);
2461
+ }
2462
+ return { status: "success", output: { path: resolvedPath, bytesWritten: stringContent.length }, response: { path: resolvedPath } };
2463
+ }
2464
+ async function executeSingleStep(step, context, api, permissions, allowedActionIds, options) {
2465
+ if (step.if) {
2466
+ const condResult = evaluateExpression(step.if, context);
2467
+ if (!condResult) {
2468
+ const result = { status: "skipped" };
2469
+ context.steps[step.id] = result;
2470
+ return result;
2471
+ }
2472
+ }
2473
+ if (step.unless) {
2474
+ const condResult = evaluateExpression(step.unless, context);
2475
+ if (condResult) {
2476
+ const result = { status: "skipped" };
2477
+ context.steps[step.id] = result;
2478
+ return result;
2479
+ }
2480
+ }
2481
+ const startTime = Date.now();
2482
+ let lastError;
2483
+ const maxAttempts = step.onError?.strategy === "retry" && step.onError.retries ? step.onError.retries + 1 : 1;
2484
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
2485
+ try {
2486
+ if (attempt > 1) {
2487
+ options.onEvent?.({
2488
+ event: "step:retry",
2489
+ stepId: step.id,
2490
+ attempt,
2491
+ maxRetries: step.onError.retries
2492
+ });
2493
+ const delay = step.onError?.retryDelayMs || 1e3;
2494
+ await sleep2(delay);
2495
+ }
2496
+ let result;
2497
+ switch (step.type) {
2498
+ case "action":
2499
+ result = await executeActionStep(step, context, api, permissions, allowedActionIds);
2500
+ break;
2501
+ case "transform":
2502
+ result = executeTransformStep(step, context);
2503
+ break;
2504
+ case "code":
2505
+ result = await executeCodeStep(step, context);
2506
+ break;
2507
+ case "condition":
2508
+ result = await executeConditionStep(step, context, api, permissions, allowedActionIds, options);
2509
+ break;
2510
+ case "loop":
2511
+ result = await executeLoopStep(step, context, api, permissions, allowedActionIds, options);
2512
+ break;
2513
+ case "parallel":
2514
+ result = await executeParallelStep(step, context, api, permissions, allowedActionIds, options);
2515
+ break;
2516
+ case "file-read":
2517
+ result = executeFileReadStep(step, context);
2518
+ break;
2519
+ case "file-write":
2520
+ result = executeFileWriteStep(step, context);
2521
+ break;
2522
+ default:
2523
+ throw new Error(`Unknown step type: ${step.type}`);
2524
+ }
2525
+ result.durationMs = Date.now() - startTime;
2526
+ if (attempt > 1) result.retries = attempt - 1;
2527
+ context.steps[step.id] = result;
2528
+ return result;
2529
+ } catch (err) {
2530
+ lastError = err instanceof Error ? err : new Error(String(err));
2531
+ if (attempt === maxAttempts) {
2532
+ break;
2533
+ }
2534
+ }
2535
+ }
2536
+ const errorMessage = lastError?.message || "Unknown error";
2537
+ const strategy = step.onError?.strategy || "fail";
2538
+ if (strategy === "continue") {
2539
+ const result = {
2540
+ status: "failed",
2541
+ error: errorMessage,
2542
+ durationMs: Date.now() - startTime
2543
+ };
2544
+ context.steps[step.id] = result;
2545
+ return result;
2546
+ }
2547
+ if (strategy === "fallback" && step.onError?.fallbackStepId) {
2548
+ const result = {
2549
+ status: "failed",
2550
+ error: errorMessage,
2551
+ durationMs: Date.now() - startTime
2552
+ };
2553
+ context.steps[step.id] = result;
2554
+ return result;
2555
+ }
2556
+ throw lastError;
2557
+ }
2558
+ async function executeSteps(steps, context, api, permissions, allowedActionIds, options, completedStepIds) {
2559
+ const results = [];
2560
+ for (const step of steps) {
2561
+ if (completedStepIds?.has(step.id)) {
2562
+ results.push(context.steps[step.id] || { status: "success" });
2563
+ continue;
2564
+ }
2565
+ options.onEvent?.({
2566
+ event: "step:start",
2567
+ stepId: step.id,
2568
+ stepName: step.name,
2569
+ type: step.type,
2570
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2571
+ });
2572
+ try {
2573
+ const result = await executeSingleStep(step, context, api, permissions, allowedActionIds, options);
2574
+ results.push(result);
2575
+ options.onEvent?.({
2576
+ event: "step:complete",
2577
+ stepId: step.id,
2578
+ status: result.status,
2579
+ durationMs: result.durationMs,
2580
+ retries: result.retries
2581
+ });
2582
+ } catch (error2) {
2583
+ const errorMsg = error2 instanceof Error ? error2.message : String(error2);
2584
+ options.onEvent?.({
2585
+ event: "step:error",
2586
+ stepId: step.id,
2587
+ error: errorMsg,
2588
+ strategy: step.onError?.strategy || "fail"
2589
+ });
2590
+ throw error2;
2591
+ }
2592
+ }
2593
+ return results;
2594
+ }
2595
+ async function executeFlow(flow2, inputs, api, permissions, allowedActionIds, options = {}, resumeState) {
2596
+ for (const [name, decl] of Object.entries(flow2.inputs)) {
2597
+ if (decl.required !== false && inputs[name] === void 0 && decl.default === void 0) {
2598
+ throw new Error(`Missing required input: "${name}" \u2014 ${decl.description || ""}`);
2599
+ }
2600
+ }
2601
+ const resolvedInputs = {};
2602
+ for (const [name, decl] of Object.entries(flow2.inputs)) {
2603
+ if (inputs[name] !== void 0) {
2604
+ resolvedInputs[name] = inputs[name];
2605
+ } else if (decl.default !== void 0) {
2606
+ resolvedInputs[name] = decl.default;
2607
+ }
2608
+ }
2609
+ const context = resumeState?.context || {
2610
+ input: resolvedInputs,
2611
+ env: process.env,
2612
+ steps: {},
2613
+ loop: {}
2614
+ };
2615
+ const completedStepIds = resumeState ? new Set(resumeState.completedSteps) : void 0;
2616
+ if (options.dryRun) {
2617
+ options.onEvent?.({
2618
+ event: "flow:dry-run",
2619
+ flowKey: flow2.key,
2620
+ resolvedInputs,
2621
+ steps: flow2.steps.map((s) => ({ id: s.id, name: s.name, type: s.type })),
2622
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2623
+ });
2624
+ return context;
2625
+ }
2626
+ options.onEvent?.({
2627
+ event: "flow:start",
2628
+ flowKey: flow2.key,
2629
+ totalSteps: flow2.steps.length,
2630
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2631
+ });
2632
+ const flowStart = Date.now();
2633
+ try {
2634
+ await executeSteps(flow2.steps, context, api, permissions, allowedActionIds, options, completedStepIds);
2635
+ const stepEntries = Object.values(context.steps);
2636
+ const completed = stepEntries.filter((s) => s.status === "success").length;
2637
+ const failed = stepEntries.filter((s) => s.status === "failed").length;
2638
+ const skipped = stepEntries.filter((s) => s.status === "skipped").length;
2639
+ options.onEvent?.({
2640
+ event: "flow:complete",
2641
+ flowKey: flow2.key,
2642
+ status: "success",
2643
+ durationMs: Date.now() - flowStart,
2644
+ stepsCompleted: completed,
2645
+ stepsFailed: failed,
2646
+ stepsSkipped: skipped
2647
+ });
2648
+ } catch (error2) {
2649
+ const errorMsg = error2 instanceof Error ? error2.message : String(error2);
2650
+ options.onEvent?.({
2651
+ event: "flow:error",
2652
+ flowKey: flow2.key,
2653
+ status: "failed",
2654
+ error: errorMsg,
2655
+ durationMs: Date.now() - flowStart
2656
+ });
2657
+ throw error2;
2658
+ }
2659
+ return context;
2660
+ }
2661
+
2662
+ // src/lib/flow-runner.ts
2663
+ var FLOWS_DIR = ".one/flows";
2664
+ var RUNS_DIR = ".one/flows/.runs";
2665
+ var LOGS_DIR = ".one/flows/.logs";
2666
+ function ensureDir(dir) {
2667
+ if (!fs4.existsSync(dir)) {
2668
+ fs4.mkdirSync(dir, { recursive: true });
2669
+ }
2670
+ }
2671
+ function generateRunId() {
2672
+ return crypto.randomBytes(6).toString("hex");
2673
+ }
2674
+ var FlowRunner = class _FlowRunner {
2675
+ runId;
2676
+ flowKey;
2677
+ state;
2678
+ logPath;
2679
+ statePath;
2680
+ paused = false;
2681
+ constructor(flow2, inputs, runId) {
2682
+ this.runId = runId || generateRunId();
2683
+ this.flowKey = flow2.key;
2684
+ ensureDir(RUNS_DIR);
2685
+ ensureDir(LOGS_DIR);
2686
+ this.statePath = path4.join(RUNS_DIR, `${flow2.key}-${this.runId}.state.json`);
2687
+ this.logPath = path4.join(LOGS_DIR, `${flow2.key}-${this.runId}.log`);
2688
+ this.state = {
2689
+ runId: this.runId,
2690
+ flowKey: flow2.key,
2691
+ status: "running",
2692
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
2693
+ inputs,
2694
+ completedSteps: [],
2695
+ context: {
2696
+ input: inputs,
2697
+ env: {},
2698
+ steps: {},
2699
+ loop: {}
2700
+ }
2701
+ };
2702
+ }
2703
+ getRunId() {
2704
+ return this.runId;
2705
+ }
2706
+ getLogPath() {
2707
+ return this.logPath;
2708
+ }
2709
+ getStatePath() {
2710
+ return this.statePath;
2711
+ }
2712
+ requestPause() {
2713
+ this.paused = true;
2714
+ }
2715
+ log(level, msg, data) {
2716
+ const entry = {
2717
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2718
+ level,
2719
+ msg,
2720
+ ...data
2721
+ };
2722
+ fs4.appendFileSync(this.logPath, JSON.stringify(entry) + "\n");
2723
+ }
2724
+ saveState() {
2725
+ fs4.writeFileSync(this.statePath, JSON.stringify(this.state, null, 2));
2726
+ }
2727
+ createEventHandler(externalHandler) {
2728
+ return (event) => {
2729
+ this.log(
2730
+ event.event.includes("error") ? "warn" : "info",
2731
+ event.event,
2732
+ event
2733
+ );
2734
+ if (event.event === "step:complete" && event.stepId) {
2735
+ this.state.completedSteps.push(event.stepId);
2736
+ this.state.currentStepId = void 0;
2737
+ }
2738
+ if (event.event === "step:start" && event.stepId) {
2739
+ this.state.currentStepId = event.stepId;
2740
+ }
2741
+ externalHandler?.(event);
2742
+ if (this.paused && event.event === "step:complete") {
2743
+ this.state.status = "paused";
2744
+ this.state.pausedAt = (/* @__PURE__ */ new Date()).toISOString();
2745
+ this.saveState();
2746
+ }
2747
+ };
2748
+ }
2749
+ async execute(flow2, api, permissions, allowedActionIds, options = {}) {
2750
+ this.log("info", "Flow started", { flowKey: this.flowKey, runId: this.runId });
2751
+ this.state.status = "running";
2752
+ this.saveState();
2753
+ const eventHandler = this.createEventHandler(options.onEvent);
2754
+ try {
2755
+ const context = await executeFlow(
2756
+ flow2,
2757
+ this.state.inputs,
2758
+ api,
2759
+ permissions,
2760
+ allowedActionIds,
2761
+ { ...options, onEvent: eventHandler }
2762
+ );
2763
+ this.state.status = "completed";
2764
+ this.state.completedAt = (/* @__PURE__ */ new Date()).toISOString();
2765
+ this.state.context = context;
2766
+ this.saveState();
2767
+ this.log("info", "Flow completed", { status: "success" });
2768
+ return context;
2769
+ } catch (error2) {
2770
+ const errorMsg = error2 instanceof Error ? error2.message : String(error2);
2771
+ this.state.status = "failed";
2772
+ this.state.context.steps = this.state.context.steps || {};
2773
+ this.saveState();
2774
+ this.log("error", "Flow failed", { error: errorMsg });
2775
+ throw error2;
2776
+ }
2777
+ }
2778
+ async resume(flow2, api, permissions, allowedActionIds, options = {}) {
2779
+ this.log("info", "Flow resumed", { flowKey: this.flowKey, runId: this.runId });
2780
+ this.state.status = "running";
2781
+ this.state.pausedAt = void 0;
2782
+ this.saveState();
2783
+ const eventHandler = this.createEventHandler(options.onEvent);
2784
+ try {
2785
+ const context = await executeFlow(
2786
+ flow2,
2787
+ this.state.inputs,
2788
+ api,
2789
+ permissions,
2790
+ allowedActionIds,
2791
+ { ...options, onEvent: eventHandler },
2792
+ {
2793
+ context: this.state.context,
2794
+ completedSteps: this.state.completedSteps
2795
+ }
2796
+ );
2797
+ this.state.status = "completed";
2798
+ this.state.completedAt = (/* @__PURE__ */ new Date()).toISOString();
2799
+ this.state.context = context;
2800
+ this.saveState();
2801
+ this.log("info", "Flow completed after resume", { status: "success" });
2802
+ return context;
2803
+ } catch (error2) {
2804
+ const errorMsg = error2 instanceof Error ? error2.message : String(error2);
2805
+ this.state.status = "failed";
2806
+ this.saveState();
2807
+ this.log("error", "Flow failed after resume", { error: errorMsg });
2808
+ throw error2;
2809
+ }
2810
+ }
2811
+ static loadRunState(runId) {
2812
+ ensureDir(RUNS_DIR);
2813
+ const files = fs4.readdirSync(RUNS_DIR).filter((f) => f.includes(runId) && f.endsWith(".state.json"));
2814
+ if (files.length === 0) return null;
2815
+ try {
2816
+ const content = fs4.readFileSync(path4.join(RUNS_DIR, files[0]), "utf-8");
2817
+ return JSON.parse(content);
2818
+ } catch {
2819
+ return null;
2820
+ }
2821
+ }
2822
+ static fromRunState(state) {
2823
+ const runner = Object.create(_FlowRunner.prototype);
2824
+ runner.runId = state.runId;
2825
+ runner.flowKey = state.flowKey;
2826
+ runner.state = state;
2827
+ runner.paused = false;
2828
+ runner.statePath = path4.join(RUNS_DIR, `${state.flowKey}-${state.runId}.state.json`);
2829
+ runner.logPath = path4.join(LOGS_DIR, `${state.flowKey}-${state.runId}.log`);
2830
+ return runner;
2831
+ }
2832
+ static listRuns(flowKey) {
2833
+ ensureDir(RUNS_DIR);
2834
+ const files = fs4.readdirSync(RUNS_DIR).filter((f) => f.endsWith(".state.json"));
2835
+ const runs = [];
2836
+ for (const file of files) {
2837
+ try {
2838
+ const content = fs4.readFileSync(path4.join(RUNS_DIR, file), "utf-8");
2839
+ const state = JSON.parse(content);
2840
+ if (!flowKey || state.flowKey === flowKey) {
2841
+ runs.push(state);
2842
+ }
2843
+ } catch {
2844
+ }
2845
+ }
2846
+ return runs.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime());
2847
+ }
2848
+ };
2849
+ function resolveFlowPath(keyOrPath) {
2850
+ if (keyOrPath.includes("/") || keyOrPath.includes("\\") || keyOrPath.endsWith(".json")) {
2851
+ return path4.resolve(keyOrPath);
2852
+ }
2853
+ return path4.resolve(FLOWS_DIR, `${keyOrPath}.flow.json`);
2854
+ }
2855
+ function loadFlow(keyOrPath) {
2856
+ const flowPath = resolveFlowPath(keyOrPath);
2857
+ if (!fs4.existsSync(flowPath)) {
2858
+ throw new Error(`Flow not found: ${flowPath}`);
2859
+ }
2860
+ const content = fs4.readFileSync(flowPath, "utf-8");
2861
+ return JSON.parse(content);
2862
+ }
2863
+ function listFlows() {
2864
+ const flowsDir = path4.resolve(FLOWS_DIR);
2865
+ if (!fs4.existsSync(flowsDir)) return [];
2866
+ const files = fs4.readdirSync(flowsDir).filter((f) => f.endsWith(".flow.json"));
2867
+ const flows = [];
2868
+ for (const file of files) {
2869
+ try {
2870
+ const content = fs4.readFileSync(path4.join(flowsDir, file), "utf-8");
2871
+ const flow2 = JSON.parse(content);
2872
+ flows.push({
2873
+ key: flow2.key,
2874
+ name: flow2.name,
2875
+ description: flow2.description,
2876
+ inputCount: Object.keys(flow2.inputs).length,
2877
+ stepCount: flow2.steps.length,
2878
+ path: path4.join(flowsDir, file)
2879
+ });
2880
+ } catch {
2881
+ }
2882
+ }
2883
+ return flows;
2884
+ }
2885
+ function saveFlow(flow2, outputPath) {
2886
+ const flowPath = outputPath ? path4.resolve(outputPath) : path4.resolve(FLOWS_DIR, `${flow2.key}.flow.json`);
2887
+ const dir = path4.dirname(flowPath);
2888
+ ensureDir(dir);
2889
+ fs4.writeFileSync(flowPath, JSON.stringify(flow2, null, 2) + "\n");
2890
+ return flowPath;
2891
+ }
2892
+
2893
+ // src/commands/flow.ts
2894
+ import fs5 from "fs";
2895
+ function getConfig2() {
2896
+ const apiKey = getApiKey();
2897
+ if (!apiKey) {
2898
+ error("Not configured. Run `one init` first.");
2899
+ }
2900
+ const ac = getAccessControlFromAllSources();
2901
+ const permissions = ac.permissions || "admin";
2902
+ const connectionKeys = ac.connectionKeys || ["*"];
2903
+ const actionIds = ac.actionIds || ["*"];
2904
+ return { apiKey, permissions, connectionKeys, actionIds };
2905
+ }
2906
+ function parseInputs(inputArgs) {
2907
+ const inputs = {};
2908
+ for (const arg of inputArgs) {
2909
+ const eqIndex = arg.indexOf("=");
2910
+ if (eqIndex === -1) {
2911
+ error(`Invalid input format: "${arg}" \u2014 expected name=value`);
2912
+ }
2913
+ const key = arg.slice(0, eqIndex);
2914
+ const value = arg.slice(eqIndex + 1);
2915
+ try {
2916
+ inputs[key] = JSON.parse(value);
2917
+ } catch {
2918
+ inputs[key] = value;
2919
+ }
2920
+ }
2921
+ return inputs;
2922
+ }
2923
+ function collect(value, previous) {
2924
+ return previous.concat([value]);
2925
+ }
2926
+ async function autoResolveConnectionInputs(flow2, inputs, api) {
2927
+ const resolved = { ...inputs };
2928
+ const connectionInputs = Object.entries(flow2.inputs).filter(
2929
+ ([, decl]) => decl.connection && !resolved[decl.connection.platform]
2930
+ );
2931
+ if (connectionInputs.length === 0) return resolved;
2932
+ const missing = connectionInputs.filter(([name]) => !resolved[name]);
2933
+ if (missing.length === 0) return resolved;
2934
+ const connections = await api.listConnections();
2935
+ for (const [name, decl] of missing) {
2936
+ const platform = decl.connection.platform;
2937
+ const matching = connections.filter((c) => c.platform.toLowerCase() === platform.toLowerCase());
2938
+ if (matching.length === 1) {
2939
+ resolved[name] = matching[0].key;
2940
+ }
2941
+ }
2942
+ return resolved;
2943
+ }
2944
+ async function flowCreateCommand(key, options) {
2945
+ intro2(pc7.bgCyan(pc7.black(" One Workflow ")));
2946
+ let flow2;
2947
+ if (options.definition) {
2948
+ try {
2949
+ flow2 = JSON.parse(options.definition);
2950
+ } catch {
2951
+ error("Invalid JSON in --definition");
2952
+ }
2953
+ } else if (!process.stdin.isTTY) {
2954
+ const chunks = [];
2955
+ for await (const chunk of process.stdin) {
2956
+ chunks.push(chunk);
2957
+ }
2958
+ const raw = Buffer.concat(chunks).toString("utf-8");
2959
+ try {
2960
+ flow2 = JSON.parse(raw);
2961
+ } catch {
2962
+ error("Invalid JSON from stdin");
2963
+ }
2964
+ } else {
2965
+ error("Interactive workflow creation not yet supported. Use --definition <json> or pipe JSON via stdin.");
2966
+ }
2967
+ if (key) {
2968
+ flow2.key = key;
2969
+ }
2970
+ const errors = validateFlow(flow2);
2971
+ if (errors.length > 0) {
2972
+ if (isAgentMode()) {
2973
+ json({ error: "Validation failed", errors });
2974
+ process.exit(1);
2975
+ }
2976
+ error(`Validation failed:
2977
+ ${errors.map((e) => ` ${e.path}: ${e.message}`).join("\n")}`);
2978
+ }
2979
+ const flowPath = saveFlow(flow2, options.output);
2980
+ if (isAgentMode()) {
2981
+ json({ created: true, key: flow2.key, path: flowPath });
2982
+ return;
2983
+ }
2984
+ note2(`Workflow "${flow2.name}" saved to ${flowPath}`, "Created");
2985
+ outro2(`Validate: ${pc7.cyan(`one flow validate ${flow2.key}`)}
2986
+ Execute: ${pc7.cyan(`one flow execute ${flow2.key}`)}`);
2987
+ }
2988
+ async function flowExecuteCommand(keyOrPath, options) {
2989
+ intro2(pc7.bgCyan(pc7.black(" One Workflow ")));
2990
+ const { apiKey, permissions, actionIds } = getConfig2();
2991
+ const api = new OneApi(apiKey);
2992
+ const spinner5 = createSpinner();
2993
+ spinner5.start(`Loading workflow "${keyOrPath}"...`);
2994
+ let flow2;
2995
+ try {
2996
+ flow2 = loadFlow(keyOrPath);
2997
+ } catch (err) {
2998
+ spinner5.stop("Workflow not found");
2999
+ error(err instanceof Error ? err.message : String(err));
3000
+ return;
3001
+ }
3002
+ spinner5.stop(`Workflow: ${flow2.name} (${flow2.steps.length} steps)`);
3003
+ const inputs = parseInputs(options.input || []);
3004
+ const resolvedInputs = await autoResolveConnectionInputs(flow2, inputs, api);
3005
+ const runner = new FlowRunner(flow2, resolvedInputs);
3006
+ const logPath = runner.getLogPath();
3007
+ const runId = runner.getRunId();
3008
+ const sigintHandler = () => {
3009
+ runner.requestPause();
3010
+ if (!isAgentMode()) {
3011
+ console.log(`
3012
+ ${pc7.yellow("Pausing after current step completes...")} (run ID: ${runId})`);
3013
+ }
3014
+ };
3015
+ process.on("SIGINT", sigintHandler);
3016
+ const onEvent = (event) => {
3017
+ if (isAgentMode()) {
3018
+ json(event);
3019
+ } else if (options.verbose) {
3020
+ const ts = (/* @__PURE__ */ new Date()).toISOString().split("T")[1].slice(0, 8);
3021
+ if (event.event === "step:start") {
3022
+ console.log(` ${pc7.dim(ts)} ${pc7.cyan("\u25B6")} ${event.stepName} ${pc7.dim(`(${event.type})`)}`);
3023
+ } else if (event.event === "step:complete") {
3024
+ const status = event.status === "success" ? pc7.green("\u2713") : event.status === "skipped" ? pc7.dim("\u25CB") : pc7.red("\u2717");
3025
+ console.log(` ${pc7.dim(ts)} ${status} ${event.stepId} ${pc7.dim(`${event.durationMs}ms`)}`);
3026
+ } else if (event.event === "step:error") {
3027
+ console.log(` ${pc7.dim(ts)} ${pc7.red("\u2717")} ${event.stepId}: ${event.error}`);
3028
+ }
3029
+ }
3030
+ };
3031
+ const execSpinner = createSpinner();
3032
+ if (!options.verbose && !isAgentMode()) {
3033
+ execSpinner.start("Executing workflow...");
3034
+ }
3035
+ try {
3036
+ const context = await runner.execute(flow2, api, permissions, actionIds, {
3037
+ dryRun: options.dryRun,
3038
+ verbose: options.verbose,
3039
+ onEvent
3040
+ });
3041
+ process.off("SIGINT", sigintHandler);
3042
+ if (!options.verbose && !isAgentMode()) {
3043
+ execSpinner.stop("Workflow completed");
3044
+ }
3045
+ if (isAgentMode()) {
3046
+ json({
3047
+ event: "workflow:result",
3048
+ runId,
3049
+ logFile: logPath,
3050
+ status: "success",
3051
+ steps: context.steps
3052
+ });
3053
+ return;
3054
+ }
3055
+ const stepEntries = Object.entries(context.steps);
3056
+ const succeeded = stepEntries.filter(([, r]) => r.status === "success").length;
3057
+ const failed = stepEntries.filter(([, r]) => r.status === "failed").length;
3058
+ const skipped = stepEntries.filter(([, r]) => r.status === "skipped").length;
3059
+ console.log();
3060
+ console.log(` ${pc7.green("\u2713")} ${succeeded} succeeded ${failed > 0 ? pc7.red(`\u2717 ${failed} failed`) : ""} ${skipped > 0 ? pc7.dim(`\u25CB ${skipped} skipped`) : ""}`);
3061
+ console.log(` ${pc7.dim(`Run ID: ${runId}`)}`);
3062
+ console.log(` ${pc7.dim(`Log: ${logPath}`)}`);
3063
+ if (options.dryRun) {
3064
+ note2("Dry run \u2014 no steps were executed", "Dry Run");
3065
+ }
3066
+ } catch (error2) {
3067
+ process.off("SIGINT", sigintHandler);
3068
+ if (!options.verbose && !isAgentMode()) {
3069
+ execSpinner.stop("Workflow failed");
3070
+ }
3071
+ const errorMsg = error2 instanceof Error ? error2.message : String(error2);
3072
+ if (isAgentMode()) {
3073
+ json({
3074
+ event: "workflow:result",
3075
+ runId,
3076
+ logFile: logPath,
3077
+ status: "failed",
3078
+ error: errorMsg
3079
+ });
3080
+ process.exit(1);
3081
+ }
3082
+ console.log(` ${pc7.dim(`Run ID: ${runId}`)}`);
3083
+ console.log(` ${pc7.dim(`Log: ${logPath}`)}`);
3084
+ error(`Workflow failed: ${errorMsg}`);
3085
+ }
3086
+ }
3087
+ async function flowListCommand() {
3088
+ intro2(pc7.bgCyan(pc7.black(" One Workflow ")));
3089
+ const flows = listFlows();
3090
+ if (isAgentMode()) {
3091
+ json({ workflows: flows });
3092
+ return;
3093
+ }
3094
+ if (flows.length === 0) {
3095
+ note2("No workflows found in .one/flows/\n\nCreate one with: one flow create", "Workflows");
3096
+ return;
3097
+ }
3098
+ console.log();
3099
+ printTable(
3100
+ [
3101
+ { key: "key", label: "Key" },
3102
+ { key: "name", label: "Name" },
3103
+ { key: "description", label: "Description" },
3104
+ { key: "inputCount", label: "Inputs" },
3105
+ { key: "stepCount", label: "Steps" }
3106
+ ],
3107
+ flows.map((f) => ({
3108
+ key: f.key,
3109
+ name: f.name,
3110
+ description: f.description || "",
3111
+ inputCount: String(f.inputCount),
3112
+ stepCount: String(f.stepCount)
3113
+ }))
3114
+ );
3115
+ console.log();
3116
+ }
3117
+ async function flowValidateCommand(keyOrPath) {
3118
+ intro2(pc7.bgCyan(pc7.black(" One Workflow ")));
3119
+ const spinner5 = createSpinner();
3120
+ spinner5.start(`Validating "${keyOrPath}"...`);
3121
+ let flowData;
3122
+ try {
3123
+ const flowPath = resolveFlowPath(keyOrPath);
3124
+ const content = fs5.readFileSync(flowPath, "utf-8");
3125
+ flowData = JSON.parse(content);
3126
+ } catch (err) {
3127
+ spinner5.stop("Validation failed");
3128
+ error(`Could not read workflow: ${err instanceof Error ? err.message : String(err)}`);
3129
+ }
3130
+ const errors = validateFlow(flowData);
3131
+ if (errors.length > 0) {
3132
+ spinner5.stop("Validation failed");
3133
+ if (isAgentMode()) {
3134
+ json({ valid: false, errors });
3135
+ process.exit(1);
3136
+ }
3137
+ console.log();
3138
+ for (const e of errors) {
3139
+ console.log(` ${pc7.red("\u2717")} ${pc7.dim(e.path)}: ${e.message}`);
3140
+ }
3141
+ console.log();
3142
+ error(`${errors.length} validation error(s) found`);
3143
+ }
3144
+ spinner5.stop("Workflow is valid");
3145
+ if (isAgentMode()) {
3146
+ json({ valid: true, key: flowData.key });
3147
+ return;
3148
+ }
3149
+ note2(`Workflow "${flowData.key}" passed all validation checks`, "Valid");
3150
+ }
3151
+ async function flowResumeCommand(runId) {
3152
+ intro2(pc7.bgCyan(pc7.black(" One Workflow ")));
3153
+ const state = FlowRunner.loadRunState(runId);
3154
+ if (!state) {
3155
+ error(`Run "${runId}" not found`);
3156
+ }
3157
+ if (state.status !== "paused" && state.status !== "failed") {
3158
+ error(`Run "${runId}" is ${state.status} \u2014 can only resume paused or failed runs`);
3159
+ }
3160
+ const { apiKey, permissions, actionIds } = getConfig2();
3161
+ const api = new OneApi(apiKey);
3162
+ let flow2;
3163
+ try {
3164
+ flow2 = loadFlow(state.flowKey);
3165
+ } catch (err) {
3166
+ error(`Could not load workflow "${state.flowKey}": ${err instanceof Error ? err.message : String(err)}`);
3167
+ return;
3168
+ }
3169
+ const runner = FlowRunner.fromRunState(state);
3170
+ const onEvent = (event) => {
3171
+ if (isAgentMode()) {
3172
+ json(event);
3173
+ }
3174
+ };
3175
+ const spinner5 = createSpinner();
3176
+ spinner5.start(`Resuming run ${runId} (${state.completedSteps.length} steps already completed)...`);
3177
+ try {
3178
+ const context = await runner.resume(flow2, api, permissions, actionIds, { onEvent });
3179
+ spinner5.stop("Workflow completed");
3180
+ if (isAgentMode()) {
3181
+ json({
3182
+ event: "workflow:result",
3183
+ runId,
3184
+ logFile: runner.getLogPath(),
3185
+ status: "success",
3186
+ steps: context.steps
3187
+ });
3188
+ return;
3189
+ }
3190
+ console.log(` ${pc7.green("\u2713")} Resumed and completed successfully`);
3191
+ console.log(` ${pc7.dim(`Log: ${runner.getLogPath()}`)}`);
3192
+ } catch (error2) {
3193
+ spinner5.stop("Resume failed");
3194
+ const errorMsg = error2 instanceof Error ? error2.message : String(error2);
3195
+ if (isAgentMode()) {
3196
+ json({ event: "workflow:result", runId, status: "failed", error: errorMsg });
3197
+ process.exit(1);
3198
+ }
3199
+ error(`Resume failed: ${errorMsg}`);
3200
+ }
3201
+ }
3202
+ async function flowRunsCommand(flowKey) {
3203
+ intro2(pc7.bgCyan(pc7.black(" One Workflow ")));
3204
+ const runs = FlowRunner.listRuns(flowKey);
3205
+ if (isAgentMode()) {
3206
+ json({
3207
+ runs: runs.map((r) => ({
3208
+ runId: r.runId,
3209
+ flowKey: r.flowKey,
3210
+ status: r.status,
3211
+ startedAt: r.startedAt,
3212
+ completedAt: r.completedAt,
3213
+ pausedAt: r.pausedAt,
3214
+ completedSteps: r.completedSteps.length
3215
+ }))
3216
+ });
3217
+ return;
3218
+ }
3219
+ if (runs.length === 0) {
3220
+ note2(flowKey ? `No runs found for workflow "${flowKey}"` : "No workflow runs found", "Runs");
3221
+ return;
3222
+ }
3223
+ console.log();
3224
+ printTable(
3225
+ [
3226
+ { key: "runId", label: "Run ID" },
3227
+ { key: "flowKey", label: "Workflow" },
3228
+ { key: "status", label: "Status" },
3229
+ { key: "startedAt", label: "Started" },
3230
+ { key: "steps", label: "Steps Done" }
3231
+ ],
3232
+ runs.map((r) => ({
3233
+ runId: r.runId,
3234
+ flowKey: r.flowKey,
3235
+ status: colorStatus(r.status),
3236
+ startedAt: r.startedAt,
3237
+ steps: String(r.completedSteps.length)
3238
+ }))
3239
+ );
3240
+ console.log();
3241
+ }
3242
+ function colorStatus(status) {
3243
+ switch (status) {
3244
+ case "completed":
3245
+ return pc7.green(status);
3246
+ case "running":
3247
+ return pc7.cyan(status);
3248
+ case "paused":
3249
+ return pc7.yellow(status);
3250
+ case "failed":
3251
+ return pc7.red(status);
3252
+ default:
3253
+ return status;
3254
+ }
3255
+ }
3256
+
3257
+ // src/commands/guide.ts
3258
+ import pc8 from "picocolors";
3259
+
3260
+ // src/lib/guide-content.ts
3261
+ var GUIDE_OVERVIEW = `# One CLI \u2014 Agent Guide
3262
+
3263
+ ## Setup
3264
+
3265
+ 1. Run \`one init\` to configure your API key
3266
+ 2. Run \`one add <platform>\` to connect platforms via OAuth
3267
+ 3. Run \`one --agent connection list\` to verify connections
3268
+
3269
+ ## The --agent Flag
3270
+
3271
+ Always use \`--agent\` for machine-readable JSON output. It disables colors, spinners, and interactive prompts.
3272
+
3273
+ \`\`\`bash
3274
+ one --agent <command>
3275
+ \`\`\`
3276
+
3277
+ All commands return JSON. If an \`error\` key is present, the command failed.
3278
+
3279
+ ## Topics
3280
+
3281
+ This guide has three sections you can request individually:
3282
+
3283
+ - **overview** \u2014 This section. Setup, flag usage, and discovery workflow.
3284
+ - **actions** \u2014 Full workflow for searching, reading docs, and executing platform actions.
3285
+ - **workflows** \u2014 Building and executing multi-step API workflows (JSON-based).
3286
+
3287
+ ## Discovery Workflow
3288
+
3289
+ 1. \`one --agent connection list\` \u2014 See connected platforms and connection keys
3290
+ 2. \`one --agent actions search <platform> <query>\` \u2014 Find actions
3291
+ 3. \`one --agent actions knowledge <platform> <actionId>\` \u2014 Read full docs (REQUIRED before execute)
3292
+ 4. \`one --agent actions execute <platform> <actionId> <connectionKey>\` \u2014 Execute the action
3293
+
3294
+ For multi-step workflows:
3295
+ 1. Discover actions with the workflow above
3296
+ 2. Build a workflow JSON definition
3297
+ 3. \`one --agent flow create <key> --definition '<json>'\`
3298
+ 4. \`one --agent flow execute <key> -i param=value\`
3299
+
3300
+ Platform names are always kebab-case (e.g., \`hub-spot\`, \`google-calendar\`).
3301
+ Run \`one platforms\` to browse all 200+ available platforms.
3302
+ `;
3303
+ var GUIDE_ACTIONS = `# One Actions CLI Workflow
3304
+
3305
+ You have access to the One CLI which lets you interact with 200+ third-party platforms through their APIs. The CLI handles authentication, request building, and execution through One's passthrough proxy.
3306
+
3307
+ ## The Workflow
3308
+
3309
+ Always follow this sequence \u2014 each step builds on the previous one:
3310
+
3311
+ 1. **List connections** to see what platforms the user has connected
3312
+ 2. **Search actions** to find the right API action for what the user wants to do
3313
+ 3. **Get knowledge** to understand the action's parameters, requirements, and structure
3314
+ 4. **Execute** the action with the correct parameters
3315
+
3316
+ 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.
3317
+
3318
+ ## Commands
3319
+
3320
+ ### 1. List Connections
3321
+
3322
+ \`\`\`bash
3323
+ one --agent connection list
3324
+ \`\`\`
3325
+
3326
+ 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.
3327
+
3328
+ Output format:
3329
+ \`\`\`json
3330
+ {"connections": [{"platform": "gmail", "state": "active", "key": "conn_abc123"}, ...]}
3331
+ \`\`\`
3332
+
3333
+ ### 2. Search Actions
3334
+
3335
+ \`\`\`bash
3336
+ one --agent actions search <platform> <query>
3337
+ \`\`\`
3338
+
3339
+ 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.
3340
+
3341
+ - \`<platform>\` \u2014 Platform name in kebab-case exactly as shown in the connections list (e.g., \`gmail\`, \`shopify\`, \`hub-spot\`)
3342
+ - \`<query>\` \u2014 Natural language description of what you want to do (e.g., \`"send email"\`, \`"list contacts"\`, \`"create order"\`)
3343
+
3344
+ Options:
3345
+ - \`-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\`.
3346
+
3347
+ Example:
3348
+ \`\`\`bash
3349
+ one --agent actions search gmail "send email" -t execute
3350
+ \`\`\`
3351
+
3352
+ Output format:
3353
+ \`\`\`json
3354
+ {"actions": [{"_id": "abc123", "title": "Send Email", "tags": [...], "method": "POST", "path": "/messages/send"}, ...]}
3355
+ \`\`\`
3356
+
3357
+ ### 3. Get Action Knowledge
3358
+
3359
+ \`\`\`bash
3360
+ one --agent actions knowledge <platform> <actionId>
3361
+ \`\`\`
3362
+
3363
+ 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.
3364
+
3365
+ Always call this before executing \u2014 it tells you exactly what parameters are required and how to structure the request.
3366
+
3367
+ Example:
3368
+ \`\`\`bash
3369
+ one --agent actions knowledge gmail 67890abcdef
3370
+ \`\`\`
3371
+
3372
+ Output format:
3373
+ \`\`\`json
3374
+ {"knowledge": "...full API documentation and guidance...", "method": "POST"}
3375
+ \`\`\`
3376
+
3377
+ ### 4. Execute Action
3378
+
3379
+ \`\`\`bash
3380
+ one --agent actions execute <platform> <actionId> <connectionKey> [options]
3381
+ \`\`\`
3382
+
3383
+ 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.
3384
+
3385
+ - \`<platform>\` \u2014 Platform name in kebab-case
3386
+ - \`<actionId>\` \u2014 Action ID from the search results
3387
+ - \`<connectionKey>\` \u2014 Connection key from \`one connection list\`
3388
+
3389
+ Options:
3390
+ - \`-d, --data <json>\` \u2014 Request body as JSON string (for POST, PUT, PATCH)
3391
+ - \`--path-vars <json>\` \u2014 Path variables as JSON (for URLs with \`{id}\` placeholders)
3392
+ - \`--query-params <json>\` \u2014 Query parameters as JSON
3393
+ - \`--headers <json>\` \u2014 Additional headers as JSON
3394
+ - \`--form-data\` \u2014 Send as multipart/form-data instead of JSON
3395
+ - \`--form-url-encoded\` \u2014 Send as application/x-www-form-urlencoded
3396
+ - \`--dry-run\` \u2014 Show the request that would be sent without executing it
3397
+
3398
+ Examples:
3399
+ \`\`\`bash
3400
+ # Simple GET request
3401
+ one --agent actions execute shopify <actionId> <connectionKey>
3402
+
3403
+ # POST with data
3404
+ one --agent actions execute hub-spot <actionId> <connectionKey> \\
3405
+ -d '{"properties": {"email": "jane@example.com", "firstname": "Jane"}}'
3406
+
3407
+ # With path variables and query params
3408
+ one --agent actions execute shopify <actionId> <connectionKey> \\
3409
+ --path-vars '{"order_id": "12345"}' \\
3410
+ --query-params '{"limit": "10"}'
3411
+ \`\`\`
3412
+
3413
+ Output format:
3414
+ \`\`\`json
3415
+ {"request": {"method": "POST", "url": "https://..."}, "response": {...}}
3416
+ \`\`\`
3417
+
3418
+ ## Error Handling
3419
+
3420
+ All errors return JSON in agent mode:
3421
+ \`\`\`json
3422
+ {"error": "Error message here"}
3423
+ \`\`\`
3424
+
3425
+ Parse the output as JSON. If the \`error\` key is present, the command failed \u2014 report the error message to the user.
3426
+
3427
+ ## Important Notes
3428
+
3429
+ - **Always use \`--agent\` flag** \u2014 it produces structured JSON output without spinners, colors, or interactive prompts
3430
+ - Platform names are always **kebab-case** (e.g., \`hub-spot\` not \`HubSpot\`, \`ship-station\` not \`ShipStation\`)
3431
+ - Always use the **exact action ID** from search results \u2014 don't guess or construct them
3432
+ - 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
3433
+ - 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)
3434
+ - If search returns no results, try broader queries (e.g., \`"list"\` instead of \`"list active premium customers"\`)
3435
+ - The execute command respects access control settings configured via \`one config\` \u2014 if execution is blocked, the user may need to adjust their permissions
3436
+ `;
3437
+ var GUIDE_FLOWS = `# One Workflows \u2014 Multi-Step API Workflows
3438
+
3439
+ You have access to the One CLI's workflow engine, which lets you create and execute multi-step API workflows as JSON files. Workflows chain actions across platforms \u2014 e.g., look up a Stripe customer, then send them a welcome email via Gmail.
3440
+
3441
+ ## 1. Overview
3442
+
3443
+ - Workflows are JSON files stored at \`.one/flows/<key>.flow.json\`
3444
+ - All dynamic values (including connection keys) are declared as **inputs**
3445
+ - Each workflow has a unique **key** used to reference and execute it
3446
+ - Executed via \`one --agent flow execute <key> -i name=value\`
3447
+
3448
+ ## 2. Building a Workflow \u2014 Step-by-Step Process
3449
+
3450
+ **You MUST follow this process to build a correct workflow:**
3451
+
3452
+ ### Step 1: Discover connections
3453
+
3454
+ \`\`\`bash
3455
+ one --agent connection list
3456
+ \`\`\`
3457
+
3458
+ Find out which platforms are connected and get their connection keys.
3459
+
3460
+ ### Step 2: For EACH API action needed, get the knowledge
3461
+
3462
+ \`\`\`bash
3463
+ # Find the action ID
3464
+ one --agent actions search <platform> "<query>" -t execute
3465
+
3466
+ # Read the full docs \u2014 REQUIRED before adding to a workflow
3467
+ one --agent actions knowledge <platform> <actionId>
3468
+ \`\`\`
3469
+
3470
+ **CRITICAL:** You MUST call \`one actions knowledge\` for every action you include in the workflow. The knowledge output tells you the exact request body structure, required fields, path variables, and query parameters. Without this, your workflow JSON will have incorrect data shapes.
3471
+
3472
+ ### Step 3: Construct the workflow JSON
3473
+
3474
+ Using the knowledge gathered, build the workflow JSON with:
3475
+ - All inputs declared (connection keys + user parameters)
3476
+ - Each step with the correct actionId, platform, and data structure (from knowledge)
3477
+ - Data wired between steps using \`$.input.*\` and \`$.steps.*\` selectors
3478
+
3479
+ ### Step 4: Write the workflow file
3480
+
3481
+ \`\`\`bash
3482
+ one --agent flow create <key> --definition '<json>'
3483
+ \`\`\`
3484
+
3485
+ Or write directly to \`.one/flows/<key>.flow.json\`.
3486
+
3487
+ ### Step 5: Validate
3488
+
3489
+ \`\`\`bash
3490
+ one --agent flow validate <key>
3491
+ \`\`\`
3492
+
3493
+ ### Step 6: Execute
3494
+
3495
+ \`\`\`bash
3496
+ one --agent flow execute <key> -i connectionKey=xxx -i param=value
3497
+ \`\`\`
3498
+
3499
+ ## 3. Workflow JSON Schema Reference
3500
+
3501
+ \`\`\`json
3502
+ {
3503
+ "key": "welcome-customer",
3504
+ "name": "Welcome New Customer",
3505
+ "description": "Look up a Stripe customer and send them a welcome email via Gmail",
3506
+ "version": "1",
3507
+ "inputs": {
3508
+ "stripeConnectionKey": {
3509
+ "type": "string",
3510
+ "required": true,
3511
+ "description": "Stripe connection key from one connection list",
3512
+ "connection": { "platform": "stripe" }
3513
+ },
3514
+ "gmailConnectionKey": {
3515
+ "type": "string",
3516
+ "required": true,
3517
+ "description": "Gmail connection key from one connection list",
3518
+ "connection": { "platform": "gmail" }
3519
+ },
3520
+ "customerEmail": {
3521
+ "type": "string",
3522
+ "required": true,
3523
+ "description": "Customer email to look up"
3524
+ }
3525
+ },
3526
+ "steps": [
3527
+ {
3528
+ "id": "stepId",
3529
+ "name": "Human-readable label",
3530
+ "type": "action",
3531
+ "action": {
3532
+ "platform": "stripe",
3533
+ "actionId": "the-action-id-from-search",
3534
+ "connectionKey": "$.input.stripeConnectionKey",
3535
+ "data": {},
3536
+ "pathVars": {},
3537
+ "queryParams": {},
3538
+ "headers": {}
3539
+ }
3540
+ }
3541
+ ]
3542
+ }
3543
+ \`\`\`
3544
+
3545
+ ### Input declarations
3546
+
3547
+ | Field | Type | Description |
3548
+ |---|---|---|
3549
+ | \`type\` | string | \`string\`, \`number\`, \`boolean\`, \`object\`, \`array\` |
3550
+ | \`required\` | boolean | Whether this input must be provided (default: true) |
3551
+ | \`default\` | any | Default value if not provided |
3552
+ | \`description\` | string | Human-readable description |
3553
+ | \`connection\` | object | Connection metadata: \`{ "platform": "gmail" }\` \u2014 enables auto-resolution |
3554
+
3555
+ **Connection inputs** have a \`connection\` field. If the user has exactly one connection for that platform, the workflow engine auto-resolves it.
3556
+
3557
+ ## 4. Selector Syntax Reference
3558
+
3559
+ | Pattern | Resolves To |
3560
+ |---|---|
3561
+ | \`$.input.gmailConnectionKey\` | Input value (including connection keys) |
3562
+ | \`$.input.customerEmail\` | Any input parameter |
3563
+ | \`$.steps.stepId.response\` | Full API response from a step |
3564
+ | \`$.steps.stepId.response.data[0].email\` | Nested field with array index |
3565
+ | \`$.steps.stepId.response.data[*].id\` | Wildcard \u2014 maps array to field |
3566
+ | \`$.env.MY_VAR\` | Environment variable |
3567
+ | \`$.loop.item\` | Current loop item |
3568
+ | \`$.loop.i\` | Current loop index |
3569
+ | \`"Hello {{$.steps.getUser.response.data.name}}"\` | String interpolation |
3570
+
3571
+ **Rules:**
3572
+ - A value that is purely \`$.xxx\` resolves to the raw type (object, array, number)
3573
+ - A string containing \`{{$.xxx}}\` does string interpolation (stringifies objects)
3574
+ - Selectors inside objects/arrays are resolved recursively
3575
+
3576
+ ## 5. Step Types Reference
3577
+
3578
+ ### \`action\` \u2014 Execute a One API action
3579
+
3580
+ \`\`\`json
3581
+ {
3582
+ "id": "findCustomer",
3583
+ "name": "Search Stripe customers",
3584
+ "type": "action",
3585
+ "action": {
3586
+ "platform": "stripe",
3587
+ "actionId": "conn_mod_def::xxx::yyy",
3588
+ "connectionKey": "$.input.stripeConnectionKey",
3589
+ "data": {
3590
+ "query": "email:'{{$.input.customerEmail}}'"
3591
+ }
3592
+ }
3593
+ }
3594
+ \`\`\`
3595
+
3596
+ ### \`transform\` \u2014 Transform data with a JS expression
3597
+
3598
+ \`\`\`json
3599
+ {
3600
+ "id": "extractNames",
3601
+ "name": "Extract customer names",
3602
+ "type": "transform",
3603
+ "transform": {
3604
+ "expression": "$.steps.findCustomer.response.data.map(c => c.name)"
3605
+ }
3606
+ }
3607
+ \`\`\`
3608
+
3609
+ The expression is evaluated with the full flow context as \`$\`.
3610
+
3611
+ ### \`code\` \u2014 Run multi-line JavaScript
3612
+
3613
+ 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\`.
3614
+
3615
+ \`\`\`json
3616
+ {
3617
+ "id": "processData",
3618
+ "name": "Process and enrich data",
3619
+ "type": "code",
3620
+ "code": {
3621
+ "source": "const customers = $.steps.listCustomers.response.data;\\nconst enriched = customers.map(c => ({\\n ...c,\\n tier: c.spend > 1000 ? 'gold' : 'silver'\\n}));\\nreturn enriched;"
3622
+ }
3623
+ }
3624
+ \`\`\`
3625
+
3626
+ 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.
3627
+
3628
+ ### \`condition\` \u2014 If/then/else branching
3629
+
3630
+ \`\`\`json
3631
+ {
3632
+ "id": "checkFound",
3633
+ "name": "Check if customer was found",
3634
+ "type": "condition",
3635
+ "condition": {
3636
+ "expression": "$.steps.findCustomer.response.data.length > 0",
3637
+ "then": [
3638
+ { "id": "sendEmail", "name": "Send welcome email", "type": "action", "action": { "..." : "..." } }
3639
+ ],
3640
+ "else": [
3641
+ { "id": "logNotFound", "name": "Log not found", "type": "transform", "transform": { "expression": "'Customer not found'" } }
3642
+ ]
3643
+ }
3644
+ }
3645
+ \`\`\`
3646
+
3647
+ ### \`loop\` \u2014 Iterate over an array
3648
+
3649
+ \`\`\`json
3650
+ {
3651
+ "id": "processOrders",
3652
+ "name": "Process each order",
3653
+ "type": "loop",
3654
+ "loop": {
3655
+ "over": "$.steps.listOrders.response.data",
3656
+ "as": "order",
3657
+ "indexAs": "i",
3658
+ "maxIterations": 1000,
3659
+ "maxConcurrency": 5,
3660
+ "steps": [
3661
+ {
3662
+ "id": "createInvoice",
3663
+ "name": "Create invoice for order",
3664
+ "type": "action",
3665
+ "action": {
3666
+ "platform": "quickbooks",
3667
+ "actionId": "...",
3668
+ "connectionKey": "$.input.qbConnectionKey",
3669
+ "data": { "amount": "$.loop.order.total" }
3670
+ }
3671
+ }
3672
+ ]
3673
+ }
3674
+ }
3675
+ \`\`\`
3676
+
3677
+ - \`maxConcurrency\` (optional): When set > 1, loop iterations run in parallel batches of that size. Default is sequential (1).
3678
+
3679
+ ### \`parallel\` \u2014 Run steps concurrently
3680
+
3681
+ \`\`\`json
3682
+ {
3683
+ "id": "parallelLookups",
3684
+ "name": "Look up in parallel",
3685
+ "type": "parallel",
3686
+ "parallel": {
3687
+ "maxConcurrency": 5,
3688
+ "steps": [
3689
+ { "id": "getStripe", "name": "Get Stripe data", "type": "action", "action": { "...": "..." } },
3690
+ { "id": "getHubspot", "name": "Get HubSpot data", "type": "action", "action": { "...": "..." } }
3691
+ ]
3692
+ }
3693
+ }
3694
+ \`\`\`
3695
+
3696
+ ### \`file-read\` \u2014 Read from filesystem
3697
+
3698
+ \`\`\`json
3699
+ {
3700
+ "id": "readConfig",
3701
+ "name": "Read config file",
3702
+ "type": "file-read",
3703
+ "fileRead": { "path": "./data/config.json", "parseJson": true }
3704
+ }
3705
+ \`\`\`
3706
+
3707
+ ### \`file-write\` \u2014 Write to filesystem
3708
+
3709
+ \`\`\`json
3710
+ {
3711
+ "id": "writeResults",
3712
+ "name": "Save results",
3713
+ "type": "file-write",
3714
+ "fileWrite": {
3715
+ "path": "./output/results.json",
3716
+ "content": "$.steps.transform.output",
3717
+ "append": false
3718
+ }
3719
+ }
3720
+ \`\`\`
3721
+
3722
+ ## 6. Error Handling
3723
+
3724
+ ### \`onError\` strategies
3725
+
3726
+ \`\`\`json
3727
+ {
3728
+ "id": "riskyStep",
3729
+ "name": "Might fail",
3730
+ "type": "action",
3731
+ "onError": {
3732
+ "strategy": "retry",
3733
+ "retries": 3,
3734
+ "retryDelayMs": 1000
3735
+ },
3736
+ "action": { "...": "..." }
3737
+ }
3738
+ \`\`\`
3739
+
3740
+ | Strategy | Behavior |
3741
+ |---|---|
3742
+ | \`fail\` | Stop the flow immediately (default) |
3743
+ | \`continue\` | Mark step as failed, continue to next step |
3744
+ | \`retry\` | Retry up to N times with delay |
3745
+ | \`fallback\` | On failure, execute a different step |
3746
+
3747
+ ### Conditional execution
3748
+
3749
+ Skip a step based on previous results:
3750
+
3751
+ \`\`\`json
3752
+ {
3753
+ "id": "sendEmail",
3754
+ "name": "Send email only if customer found",
3755
+ "type": "action",
3756
+ "if": "$.steps.findCustomer.response.data.length > 0",
3757
+ "action": { "...": "..." }
3758
+ }
3759
+ \`\`\`
3760
+
3761
+ ## 7. Updating Existing Workflows
3762
+
3763
+ To modify an existing workflow:
3764
+
3765
+ 1. Read the workflow JSON file at \`.one/flows/<key>.flow.json\`
3766
+ 2. Understand its current structure
3767
+ 3. Use \`one --agent actions knowledge <platform> <actionId>\` for any new actions
3768
+ 4. Modify the JSON (add/remove/update steps, change data mappings, add inputs)
3769
+ 5. Write back the updated workflow file
3770
+ 6. Validate: \`one --agent flow validate <key>\`
3771
+
3772
+ ## 8. Complete Examples
3773
+
3774
+ ### Example 1: Simple 2-step workflow \u2014 Search Stripe customer, send Gmail email
3775
+
3776
+ \`\`\`json
3777
+ {
3778
+ "key": "welcome-customer",
3779
+ "name": "Welcome New Customer",
3780
+ "description": "Look up a Stripe customer and send them a welcome email",
3781
+ "version": "1",
3782
+ "inputs": {
3783
+ "stripeConnectionKey": {
3784
+ "type": "string",
3785
+ "required": true,
3786
+ "description": "Stripe connection key",
3787
+ "connection": { "platform": "stripe" }
3788
+ },
3789
+ "gmailConnectionKey": {
3790
+ "type": "string",
3791
+ "required": true,
3792
+ "description": "Gmail connection key",
3793
+ "connection": { "platform": "gmail" }
3794
+ },
3795
+ "customerEmail": {
3796
+ "type": "string",
3797
+ "required": true,
3798
+ "description": "Customer email to look up"
3799
+ }
3800
+ },
3801
+ "steps": [
3802
+ {
3803
+ "id": "findCustomer",
3804
+ "name": "Search for customer in Stripe",
3805
+ "type": "action",
3806
+ "action": {
3807
+ "platform": "stripe",
3808
+ "actionId": "STRIPE_SEARCH_CUSTOMERS_ACTION_ID",
3809
+ "connectionKey": "$.input.stripeConnectionKey",
3810
+ "data": {
3811
+ "query": "email:'{{$.input.customerEmail}}'"
3812
+ }
3813
+ }
3814
+ },
3815
+ {
3816
+ "id": "sendWelcome",
3817
+ "name": "Send welcome email via Gmail",
3818
+ "type": "action",
3819
+ "if": "$.steps.findCustomer.response.data && $.steps.findCustomer.response.data.length > 0",
3820
+ "action": {
3821
+ "platform": "gmail",
3822
+ "actionId": "GMAIL_SEND_EMAIL_ACTION_ID",
3823
+ "connectionKey": "$.input.gmailConnectionKey",
3824
+ "data": {
3825
+ "to": "{{$.input.customerEmail}}",
3826
+ "subject": "Welcome, {{$.steps.findCustomer.response.data[0].name}}!",
3827
+ "body": "Thank you for being a customer. We're glad to have you!"
3828
+ }
3829
+ }
3830
+ }
3831
+ ]
3832
+ }
3833
+ \`\`\`
3834
+
3835
+ ### Example 2: Conditional \u2014 Check if HubSpot contact exists, create or update
3836
+
3837
+ \`\`\`json
3838
+ {
3839
+ "key": "sync-hubspot-contact",
3840
+ "name": "Sync Contact to HubSpot",
3841
+ "description": "Check if a contact exists in HubSpot, create if new or update if existing",
3842
+ "version": "1",
3843
+ "inputs": {
3844
+ "hubspotConnectionKey": {
3845
+ "type": "string",
3846
+ "required": true,
3847
+ "connection": { "platform": "hub-spot" }
3848
+ },
3849
+ "email": { "type": "string", "required": true },
3850
+ "firstName": { "type": "string", "required": true },
3851
+ "lastName": { "type": "string", "required": true }
3852
+ },
3853
+ "steps": [
3854
+ {
3855
+ "id": "searchContact",
3856
+ "name": "Search for existing contact",
3857
+ "type": "action",
3858
+ "action": {
3859
+ "platform": "hub-spot",
3860
+ "actionId": "HUBSPOT_SEARCH_CONTACTS_ACTION_ID",
3861
+ "connectionKey": "$.input.hubspotConnectionKey",
3862
+ "data": {
3863
+ "filterGroups": [{ "filters": [{ "propertyName": "email", "operator": "EQ", "value": "$.input.email" }] }]
3864
+ }
3865
+ }
3866
+ },
3867
+ {
3868
+ "id": "createOrUpdate",
3869
+ "name": "Create or update contact",
3870
+ "type": "condition",
3871
+ "condition": {
3872
+ "expression": "$.steps.searchContact.response.total > 0",
3873
+ "then": [
3874
+ {
3875
+ "id": "updateContact",
3876
+ "name": "Update existing contact",
3877
+ "type": "action",
3878
+ "action": {
3879
+ "platform": "hub-spot",
3880
+ "actionId": "HUBSPOT_UPDATE_CONTACT_ACTION_ID",
3881
+ "connectionKey": "$.input.hubspotConnectionKey",
3882
+ "pathVars": { "contactId": "$.steps.searchContact.response.results[0].id" },
3883
+ "data": {
3884
+ "properties": { "firstname": "$.input.firstName", "lastname": "$.input.lastName" }
3885
+ }
3886
+ }
3887
+ }
3888
+ ],
3889
+ "else": [
3890
+ {
3891
+ "id": "createContact",
3892
+ "name": "Create new contact",
3893
+ "type": "action",
3894
+ "action": {
3895
+ "platform": "hub-spot",
3896
+ "actionId": "HUBSPOT_CREATE_CONTACT_ACTION_ID",
3897
+ "connectionKey": "$.input.hubspotConnectionKey",
3898
+ "data": {
3899
+ "properties": { "email": "$.input.email", "firstname": "$.input.firstName", "lastname": "$.input.lastName" }
3900
+ }
3901
+ }
3902
+ }
3903
+ ]
3904
+ }
3905
+ }
3906
+ ]
3907
+ }
3908
+ \`\`\`
3909
+
3910
+ ### Example 3: Loop workflow \u2014 Iterate over Shopify orders, create invoices
3911
+
3912
+ \`\`\`json
3913
+ {
3914
+ "key": "shopify-to-invoices",
3915
+ "name": "Shopify Orders to Invoices",
3916
+ "description": "Fetch recent Shopify orders and create an invoice for each",
3917
+ "version": "1",
3918
+ "inputs": {
3919
+ "shopifyConnectionKey": {
3920
+ "type": "string",
3921
+ "required": true,
3922
+ "connection": { "platform": "shopify" }
3923
+ },
3924
+ "qbConnectionKey": {
3925
+ "type": "string",
3926
+ "required": true,
3927
+ "connection": { "platform": "quick-books" }
3928
+ }
3929
+ },
3930
+ "steps": [
3931
+ {
3932
+ "id": "listOrders",
3933
+ "name": "List recent Shopify orders",
3934
+ "type": "action",
3935
+ "action": {
3936
+ "platform": "shopify",
3937
+ "actionId": "SHOPIFY_LIST_ORDERS_ACTION_ID",
3938
+ "connectionKey": "$.input.shopifyConnectionKey",
3939
+ "queryParams": { "status": "any", "limit": "50" }
3940
+ }
3941
+ },
3942
+ {
3943
+ "id": "createInvoices",
3944
+ "name": "Create invoice for each order",
3945
+ "type": "loop",
3946
+ "loop": {
3947
+ "over": "$.steps.listOrders.response.orders",
3948
+ "as": "order",
3949
+ "indexAs": "i",
3950
+ "steps": [
3951
+ {
3952
+ "id": "createInvoice",
3953
+ "name": "Create QuickBooks invoice",
3954
+ "type": "action",
3955
+ "onError": { "strategy": "continue" },
3956
+ "action": {
3957
+ "platform": "quick-books",
3958
+ "actionId": "QB_CREATE_INVOICE_ACTION_ID",
3959
+ "connectionKey": "$.input.qbConnectionKey",
3960
+ "data": {
3961
+ "Line": [
3962
+ {
3963
+ "Amount": "$.loop.order.total_price",
3964
+ "Description": "Shopify Order #{{$.loop.order.order_number}}"
3965
+ }
3966
+ ]
3967
+ }
3968
+ }
3969
+ }
3970
+ ]
3971
+ }
3972
+ },
3973
+ {
3974
+ "id": "summary",
3975
+ "name": "Generate summary",
3976
+ "type": "transform",
3977
+ "transform": {
3978
+ "expression": "({ totalOrders: $.steps.listOrders.response.orders.length, processed: $.steps.createInvoices.output.length })"
3979
+ }
3980
+ }
3981
+ ]
3982
+ }
3983
+ \`\`\`
3984
+
3985
+ ## CLI Commands Reference
3986
+
3987
+ \`\`\`bash
3988
+ # Create a workflow
3989
+ one --agent flow create <key> --definition '<json>'
3990
+
3991
+ # List all workflows
3992
+ one --agent flow list
3993
+
3994
+ # Validate a workflow
3995
+ one --agent flow validate <key>
3996
+
3997
+ # Execute a workflow
3998
+ one --agent flow execute <key> -i connectionKey=value -i param=value
3999
+
4000
+ # Execute with dry run (validate only)
4001
+ one --agent flow execute <key> --dry-run -i connectionKey=value
4002
+
4003
+ # Execute with verbose output
4004
+ one --agent flow execute <key> -v -i connectionKey=value
4005
+
4006
+ # List workflow runs
4007
+ one --agent flow runs [flowKey]
4008
+
4009
+ # Resume a paused/failed run
4010
+ one --agent flow resume <runId>
4011
+ \`\`\`
4012
+
4013
+ ## Important Notes
4014
+
4015
+ - **Always use \`--agent\` flag** for structured JSON output
4016
+ - **Always call \`one actions knowledge\`** before adding an action step to a workflow
4017
+ - Platform names are **kebab-case** (e.g., \`hub-spot\`, not \`HubSpot\`)
4018
+ - Connection keys are **inputs**, not hardcoded \u2014 makes workflows portable and shareable
4019
+ - Use \`$.input.*\` for input values, \`$.steps.*\` for step results
4020
+ - Action IDs in examples (like \`STRIPE_SEARCH_CUSTOMERS_ACTION_ID\`) are placeholders \u2014 always use \`one actions search\` to find the real IDs
4021
+ `;
4022
+ var TOPICS = [
4023
+ { topic: "overview", description: "Setup, --agent flag, discovery workflow" },
4024
+ { topic: "actions", description: "Search, read docs, and execute platform actions" },
4025
+ { topic: "flows", description: "Build and execute multi-step workflows" },
4026
+ { topic: "all", description: "Complete guide (all topics combined)" }
4027
+ ];
4028
+ function getGuideContent(topic) {
4029
+ switch (topic) {
4030
+ case "overview":
4031
+ return { title: "One CLI \u2014 Agent Guide: Overview", content: GUIDE_OVERVIEW };
4032
+ case "actions":
4033
+ return { title: "One CLI \u2014 Agent Guide: Actions", content: GUIDE_ACTIONS };
4034
+ case "flows":
4035
+ return { title: "One CLI \u2014 Agent Guide: Workflows", content: GUIDE_FLOWS };
4036
+ case "all":
4037
+ return {
4038
+ title: "One CLI \u2014 Agent Guide: Complete",
4039
+ content: [GUIDE_OVERVIEW, GUIDE_ACTIONS, GUIDE_FLOWS].join("\n---\n\n")
4040
+ };
4041
+ }
4042
+ }
4043
+ function getAvailableTopics() {
4044
+ return TOPICS;
4045
+ }
4046
+
4047
+ // src/commands/guide.ts
4048
+ var VALID_TOPICS = ["overview", "actions", "flows", "all"];
4049
+ async function guideCommand(topic = "all") {
4050
+ if (!VALID_TOPICS.includes(topic)) {
4051
+ error(
4052
+ `Unknown topic "${topic}". Available topics: ${VALID_TOPICS.join(", ")}`
4053
+ );
4054
+ }
4055
+ const { title, content } = getGuideContent(topic);
4056
+ const availableTopics = getAvailableTopics();
4057
+ if (isAgentMode()) {
4058
+ json({ topic, title, content, availableTopics });
4059
+ return;
4060
+ }
4061
+ intro2(pc8.bgCyan(pc8.black(" One Guide ")));
4062
+ console.log();
4063
+ console.log(content);
4064
+ console.log(pc8.dim("\u2500".repeat(60)));
4065
+ console.log(
4066
+ pc8.dim("Available topics: ") + availableTopics.map((t) => pc8.cyan(t.topic)).join(", ")
4067
+ );
4068
+ console.log(pc8.dim(`Run ${pc8.cyan("one guide <topic>")} for a specific section.`));
4069
+ }
4070
+
4071
+ // src/index.ts
4072
+ var require2 = createRequire(import.meta.url);
4073
+ var { version } = require2("../package.json");
4074
+ var program = new Command();
4075
+ program.name("one").option("--agent", "Machine-readable JSON output (no colors, spinners, or prompts)").description(`One CLI \u2014 Connect AI agents to 200+ platforms through one interface.
4076
+
4077
+ Setup:
4078
+ one init Set up API key and install MCP server
4079
+ one add <platform> Connect a platform via OAuth (e.g. gmail, slack, shopify)
4080
+ one config Configure access control (permissions, scoping)
4081
+
4082
+ Workflow (use these in order):
4083
+ 1. one list List your connected platforms and connection keys
4084
+ 2. one actions search <platform> <q> Search for actions using natural language
4085
+ 3. one actions knowledge <plat> <id> Get full docs for an action (ALWAYS do this before execute)
4086
+ 4. one actions execute <p> <id> <key> Execute the action
4087
+
4088
+ Guide:
4089
+ one guide [topic] Full CLI guide (topics: overview, actions, workflows, all)
4090
+
4091
+ Workflows (multi-step):
4092
+ one flow list List saved workflows
4093
+ one flow create [key] Create a workflow from JSON
4094
+ one flow execute <key> Execute a workflow
4095
+ one flow validate <key> Validate a flow
4096
+
4097
+ Example \u2014 send an email through Gmail:
4098
+ $ one list
4099
+ # Find: gmail operational live::gmail::default::abc123
4100
+
4101
+ $ one actions search gmail "send email" -t execute
4102
+ # Find: POST Send Email conn_mod_def::xxx::yyy
4103
+
4104
+ $ one actions knowledge gmail conn_mod_def::xxx::yyy
4105
+ # Read the docs: required fields are to, subject, body, connectionKey
4106
+
4107
+ $ one actions execute gmail conn_mod_def::xxx::yyy live::gmail::default::abc123 \\
4108
+ -d '{"to":"j@example.com","subject":"Hello","body":"Hi!","connectionKey":"live::gmail::default::abc123"}'
4109
+
4110
+ Platform names are always kebab-case (e.g. hub-spot, ship-station, google-calendar).
4111
+ Run 'one platforms' to browse all 200+ available platforms.`).version(version);
4112
+ program.hook("preAction", (thisCommand) => {
4113
+ const opts = program.opts();
4114
+ if (opts.agent) {
4115
+ setAgentMode(true);
4116
+ }
4117
+ });
4118
+ program.command("init").description("Set up One and install MCP to your AI agents").option("-y, --yes", "Skip confirmations").option("-g, --global", "Install MCP globally (available in all projects)").option("-p, --project", "Install MCP for this project only (creates .mcp.json)").action(async (options) => {
4119
+ await initCommand(options);
4120
+ });
4121
+ program.command("config").description("Configure MCP access control (permissions, connections, actions)").action(async () => {
4122
+ await configCommand();
4123
+ });
4124
+ var connection = program.command("connection").description("Manage connections");
4125
+ connection.command("add [platform]").alias("a").description("Add a new connection").action(async (platform) => {
4126
+ await connectionAddCommand(platform);
4127
+ });
4128
+ connection.command("list").alias("ls").description("List your connections").action(async () => {
4129
+ await connectionListCommand();
4130
+ });
4131
+ program.command("platforms").alias("p").description("List available platforms").option("-c, --category <category>", "Filter by category").option("--json", "Output as JSON").action(async (options) => {
4132
+ await platformsCommand(options);
4133
+ });
4134
+ var actions = program.command("actions").alias("a").description("Search, explore, and execute platform actions (workflow: search \u2192 knowledge \u2192 execute)");
4135
+ actions.command("search <platform> <query>").description('Search for actions on a platform (e.g. one actions search gmail "send email")').option("-t, --type <type>", "execute (to run it) or knowledge (to learn about it). Default: knowledge").action(async (platform, query, options) => {
4136
+ await actionsSearchCommand(platform, query, options);
4137
+ });
4138
+ 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) => {
4139
+ await actionsKnowledgeCommand(platform, actionId);
4140
+ });
4141
+ actions.command("execute <platform> <actionId> <connectionKey>").alias("x").description('Execute an action \u2014 pass connectionKey from "one list", actionId from "actions search"').option("-d, --data <json>", "Request body as JSON").option("--path-vars <json>", "Path variables as JSON").option("--query-params <json>", "Query parameters as JSON").option("--headers <json>", "Additional headers as JSON").option("--form-data", "Send as multipart/form-data").option("--form-url-encoded", "Send as application/x-www-form-urlencoded").option("--dry-run", "Show request that would be sent without executing").action(async (platform, actionId, connectionKey, options) => {
4142
+ await actionsExecuteCommand(platform, actionId, connectionKey, {
4143
+ data: options.data,
4144
+ pathVars: options.pathVars,
4145
+ queryParams: options.queryParams,
4146
+ headers: options.headers,
4147
+ formData: options.formData,
4148
+ formUrlEncoded: options.formUrlEncoded,
4149
+ dryRun: options.dryRun
4150
+ });
4151
+ });
4152
+ var flow = program.command("flow").alias("f").description("Create, execute, and manage multi-step workflows");
4153
+ flow.command("create [key]").description("Create a new workflow from JSON definition").option("--definition <json>", "Workflow definition as JSON string").option("-o, --output <path>", "Custom output path (default .one/flows/<key>.flow.json)").action(async (key, options) => {
4154
+ await flowCreateCommand(key, options);
4155
+ });
4156
+ flow.command("execute <keyOrPath>").alias("x").description("Execute a workflow by key or file path").option("-i, --input <name=value>", "Input parameter (repeatable)", collect, []).option("--dry-run", "Validate and show execution plan without running").option("-v, --verbose", "Show full request/response for each step").action(async (keyOrPath, options) => {
4157
+ await flowExecuteCommand(keyOrPath, options);
4158
+ });
4159
+ flow.command("list").alias("ls").description("List all workflows in .one/flows/").action(async () => {
4160
+ await flowListCommand();
4161
+ });
4162
+ flow.command("validate <keyOrPath>").description("Validate a workflow JSON file").action(async (keyOrPath) => {
4163
+ await flowValidateCommand(keyOrPath);
4164
+ });
4165
+ flow.command("resume <runId>").description("Resume a paused or failed workflow run").action(async (runId) => {
4166
+ await flowResumeCommand(runId);
4167
+ });
4168
+ flow.command("runs [flowKey]").description("List workflow runs (optionally filtered by flow key)").action(async (flowKey) => {
4169
+ await flowRunsCommand(flowKey);
4170
+ });
4171
+ program.command("guide [topic]").description("Full CLI usage guide for agents (topics: overview, actions, flows, all)").action(async (topic) => {
4172
+ await guideCommand(topic);
4173
+ });
4174
+ program.command("add [platform]").description("Shortcut for: connection add").action(async (platform) => {
4175
+ await connectionAddCommand(platform);
4176
+ });
4177
+ program.command("list").alias("ls").description("Shortcut for: connection list").action(async () => {
4178
+ await connectionListCommand();
4179
+ });
4180
+ program.parse();