@pixelml/agenticflow-cli 0.1.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1100 @@
1
+ /**
2
+ * Main CLI program definition with Commander.js.
3
+ * Resource commands (workflow, agent, node-types, connections, uploads)
4
+ * use the SDK resource classes. Generic commands (call, ops, catalog,
5
+ * doctor, auth, policy, playbook) remain spec-based.
6
+ */
7
+ import { Command } from "commander";
8
+ import { readFileSync, existsSync, mkdirSync, writeFileSync, unlinkSync } from "node:fs";
9
+ import { resolve, dirname } from "node:path";
10
+ import { homedir } from "node:os";
11
+ import { createInterface } from "node:readline";
12
+ import { createClient, DEFAULT_BASE_URL, AGENTICFLOW_API_KEY, } from "@pixelml/agenticflow-sdk";
13
+ import { OperationRegistry, defaultSpecPath, loadOpenapiSpec, isPublic, } from "./spec.js";
14
+ import { listPlaybooks, getPlaybook } from "./playbooks.js";
15
+ import { loadPolicy, writeDefaultPolicy, policyFilePath, } from "./policy.js";
16
+ import { parseKeyValuePairs, loadJsonPayload, buildRequestSpec } from "./client.js";
17
+ // --- Constants ---
18
+ const AUTH_ENV_API_KEY = "AGENTICFLOW_PUBLIC_API_KEY";
19
+ const DOCTOR_SCHEMA_VERSION = "agenticflow.doctor.v1";
20
+ const CATALOG_EXPORT_SCHEMA_VERSION = "agenticflow.catalog.export.v1";
21
+ const CATALOG_RANK_SCHEMA_VERSION = "agenticflow.catalog.rank.v1";
22
+ // ═══════════════════════════════════════════════════════════════════
23
+ // Helpers
24
+ // ═══════════════════════════════════════════════════════════════════
25
+ function printJson(data) {
26
+ console.log(JSON.stringify(data, null, 2));
27
+ }
28
+ /** Print an SDK result in CLI-friendly format. */
29
+ function printResult(data) {
30
+ printJson(data);
31
+ }
32
+ /** Load the active auth profile from ~/.agenticflow/auth.json */
33
+ function loadActiveProfile() {
34
+ try {
35
+ const config = loadAuthFile(defaultAuthConfigPath());
36
+ const profileName = config["default_profile"] ?? "default";
37
+ const profiles = config["profiles"];
38
+ return profiles?.[profileName] ?? {};
39
+ }
40
+ catch {
41
+ return {};
42
+ }
43
+ }
44
+ /**
45
+ * Resolve a value with priority: flag → env var → auth.json profile → fallback.
46
+ */
47
+ function resolveToken(options) {
48
+ if (options.apiKey)
49
+ return options.apiKey;
50
+ const fromEnv = process.env[AGENTICFLOW_API_KEY] ?? process.env[AUTH_ENV_API_KEY];
51
+ if (fromEnv)
52
+ return fromEnv;
53
+ return loadActiveProfile()["api_key"] ?? null;
54
+ }
55
+ function resolveWorkspaceId(explicit) {
56
+ if (explicit)
57
+ return explicit;
58
+ const fromEnv = process.env["AGENTICFLOW_WORKSPACE_ID"];
59
+ if (fromEnv)
60
+ return fromEnv;
61
+ return loadActiveProfile()["workspace_id"] ?? undefined;
62
+ }
63
+ function resolveProjectId(explicit) {
64
+ if (explicit)
65
+ return explicit;
66
+ const fromEnv = process.env["AGENTICFLOW_PROJECT_ID"];
67
+ if (fromEnv)
68
+ return fromEnv;
69
+ return loadActiveProfile()["project_id"] ?? undefined;
70
+ }
71
+ /** Build an SDK client from global CLI options. */
72
+ function buildClient(parentOpts) {
73
+ return createClient({
74
+ apiKey: resolveToken(parentOpts),
75
+ workspaceId: resolveWorkspaceId(parentOpts.workspaceId),
76
+ projectId: resolveProjectId(parentOpts.projectId),
77
+ });
78
+ }
79
+ /** Wrap an async SDK call with error handling. */
80
+ async function run(fn) {
81
+ try {
82
+ const result = await fn();
83
+ printResult(result);
84
+ }
85
+ catch (err) {
86
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
87
+ process.exit(1);
88
+ }
89
+ }
90
+ // ═══════════════════════════════════════════════════════════════════
91
+ // Spec-based helpers (for generic commands: call, ops, catalog, doctor)
92
+ // ═══════════════════════════════════════════════════════════════════
93
+ function loadRegistry(specFile) {
94
+ try {
95
+ const spec = loadOpenapiSpec(specFile);
96
+ return OperationRegistry.fromSpec(spec);
97
+ }
98
+ catch (err) {
99
+ console.error(`Warning: Unable to load OpenAPI spec from ${specFile}: ${err}`);
100
+ return null;
101
+ }
102
+ }
103
+ function catalogOperationItem(op) {
104
+ return {
105
+ operation_id: op.operationId,
106
+ method: op.method,
107
+ path: op.path,
108
+ summary: op.summary ?? "",
109
+ tags: op.tags,
110
+ public: isPublic(op),
111
+ };
112
+ }
113
+ // --- Auth helpers ---
114
+ function defaultAuthConfigPath() {
115
+ const envDir = process.env["AGENTICFLOW_CLI_DIR"];
116
+ const dir = envDir ?? resolve(homedir(), ".agenticflow");
117
+ return resolve(dir, "auth.json");
118
+ }
119
+ function loadAuthFile(path) {
120
+ if (!existsSync(path))
121
+ return {};
122
+ try {
123
+ return JSON.parse(readFileSync(path, "utf-8"));
124
+ }
125
+ catch {
126
+ return {};
127
+ }
128
+ }
129
+ function parseKeyValueEnv(line) {
130
+ const trimmed = line.trim();
131
+ if (!trimmed || trimmed.startsWith("#"))
132
+ return null;
133
+ const idx = trimmed.indexOf("=");
134
+ if (idx === -1)
135
+ return null;
136
+ const key = trimmed.slice(0, idx).trim();
137
+ let value = trimmed.slice(idx + 1).trim();
138
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
139
+ value = value.slice(1, -1);
140
+ }
141
+ return [key, value];
142
+ }
143
+ // ═══════════════════════════════════════════════════════════════════
144
+ // Main program
145
+ // ═══════════════════════════════════════════════════════════════════
146
+ export function createProgram() {
147
+ const program = new Command();
148
+ program
149
+ .name("agenticflow")
150
+ .description("AgenticFlow CLI for agent-native API operations.")
151
+ .version("1.0.0")
152
+ .option("--api-key <key>", "API key for authentication")
153
+ .option("--workspace-id <id>", "Default workspace ID")
154
+ .option("--project-id <id>", "Default project ID")
155
+ .option("--spec-file <path>", "Path to OpenAPI spec JSON file")
156
+ .option("--json", "Force JSON output");
157
+ // ═════════════════════════════════════════════════════════════════
158
+ // doctor
159
+ // ═════════════════════════════════════════════════════════════════
160
+ program
161
+ .command("doctor")
162
+ .description("Preflight checks for CLI configuration and connectivity.")
163
+ .option("--json", "JSON output")
164
+ .action(async (opts) => {
165
+ const parentOpts = program.opts();
166
+ const baseUrl = DEFAULT_BASE_URL;
167
+ const token = resolveToken(parentOpts);
168
+ const wsId = resolveWorkspaceId(parentOpts.workspaceId);
169
+ const projId = resolveProjectId(parentOpts.projectId);
170
+ const specFile = parentOpts.specFile ?? defaultSpecPath();
171
+ const registry = loadRegistry(specFile);
172
+ const configPath = defaultAuthConfigPath();
173
+ const configExists = existsSync(configPath);
174
+ const tokenSource = parentOpts.apiKey ? "flag" : (process.env[AUTH_ENV_API_KEY] ? "env" : (configExists ? "config" : "none"));
175
+ // Health check
176
+ let healthOk = false;
177
+ let healthStatus = 0;
178
+ let healthError = "";
179
+ try {
180
+ const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/v1/health`);
181
+ healthOk = response.ok;
182
+ healthStatus = response.status;
183
+ }
184
+ catch (err) {
185
+ healthError = err instanceof Error ? err.message : String(err);
186
+ }
187
+ const checks = {
188
+ config: configExists,
189
+ token: !!token,
190
+ tokenSource,
191
+ workspaceId: wsId ?? null,
192
+ projectId: projId ?? null,
193
+ baseUrl,
194
+ health: healthOk,
195
+ healthStatus,
196
+ healthError,
197
+ specFile,
198
+ operationsLoaded: registry?.listOperations().length ?? 0,
199
+ };
200
+ if (opts.json || parentOpts.json) {
201
+ printJson({ schema: DOCTOR_SCHEMA_VERSION, ...checks });
202
+ }
203
+ else {
204
+ const ok = (v) => v ? "✓" : "✗";
205
+ const dim = (s) => `\x1b[2m${s}\x1b[0m`;
206
+ console.log("");
207
+ console.log(" Environment");
208
+ console.log(` └ Version: ${program.version()}`);
209
+ console.log(` └ Node.js: ${process.version}`);
210
+ console.log(` └ Platform: ${process.platform} ${process.arch}`);
211
+ console.log("");
212
+ console.log(" Authentication");
213
+ console.log(` └ API Key: ${token ? `${ok(true)} present ${dim(`(source: ${tokenSource})`)}` : `${ok(false)} not set`}`);
214
+ console.log(` └ Workspace ID: ${wsId ?? "not set"}`);
215
+ console.log(` └ Project ID: ${projId ?? "not set"}`);
216
+ console.log(` └ Config: ${configExists ? configPath : `${ok(false)} not found`}`);
217
+ console.log("");
218
+ console.log(" API Connectivity");
219
+ console.log(` └ Base URL: ${baseUrl}`);
220
+ console.log(` └ Health: ${healthOk ? `${ok(true)} reachable ${dim(`(HTTP ${healthStatus})`)}` : `${ok(false)} ${healthError || `HTTP ${healthStatus}`}`}`);
221
+ console.log("");
222
+ console.log(" OpenAPI Spec");
223
+ console.log(` └ Spec file: ${registry ? ok(true) : ok(false)} ${specFile}`);
224
+ console.log(` └ Operations: ${checks.operationsLoaded} loaded`);
225
+ console.log("");
226
+ }
227
+ });
228
+ // ═════════════════════════════════════════════════════════════════
229
+ // ops
230
+ // ═════════════════════════════════════════════════════════════════
231
+ const opsCmd = program
232
+ .command("ops")
233
+ .description("OpenAPI operation discovery.");
234
+ opsCmd
235
+ .command("list")
236
+ .description("List available operations.")
237
+ .option("--public-only", "Show only public operations")
238
+ .option("--tag <tag>", "Filter by tag")
239
+ .action((opts) => {
240
+ const parentOpts = program.opts();
241
+ const specFile = parentOpts.specFile ?? defaultSpecPath();
242
+ const registry = loadRegistry(specFile);
243
+ if (!registry) {
244
+ console.error("Failed to load OpenAPI spec.");
245
+ process.exit(1);
246
+ }
247
+ const operations = registry.listOperations({ publicOnly: opts.publicOnly, tag: opts.tag });
248
+ console.log(`${operations.length} operations found:\n`);
249
+ for (const op of operations) {
250
+ console.log(` ${op.method.padEnd(7)} ${op.path}`);
251
+ console.log(` ${op.operationId}`);
252
+ }
253
+ });
254
+ opsCmd
255
+ .command("show <operationId>")
256
+ .description("Show details for a specific operation.")
257
+ .action((operationId) => {
258
+ const parentOpts = program.opts();
259
+ const specFile = parentOpts.specFile ?? defaultSpecPath();
260
+ const registry = loadRegistry(specFile);
261
+ if (!registry) {
262
+ console.error("Failed to load OpenAPI spec.");
263
+ process.exit(1);
264
+ }
265
+ const operation = registry.getOperationById(operationId);
266
+ if (!operation) {
267
+ console.error(`Operation not found: ${operationId}`);
268
+ process.exit(1);
269
+ }
270
+ printJson(catalogOperationItem(operation));
271
+ });
272
+ // ═════════════════════════════════════════════════════════════════
273
+ // catalog
274
+ // ═════════════════════════════════════════════════════════════════
275
+ const catalogCmd = program
276
+ .command("catalog")
277
+ .description("Operation catalog tools.");
278
+ catalogCmd
279
+ .command("export")
280
+ .description("Export operation catalog.")
281
+ .option("--public-only", "Export only public operations")
282
+ .option("--json", "JSON output")
283
+ .action((opts) => {
284
+ const parentOpts = program.opts();
285
+ const specFile = parentOpts.specFile ?? defaultSpecPath();
286
+ const registry = loadRegistry(specFile);
287
+ if (!registry) {
288
+ console.error("Failed to load OpenAPI spec.");
289
+ process.exit(1);
290
+ }
291
+ const operations = registry.listOperations({ publicOnly: opts.publicOnly });
292
+ const items = operations.map(catalogOperationItem);
293
+ if (opts.json || parentOpts.json) {
294
+ printJson({ schema: CATALOG_EXPORT_SCHEMA_VERSION, count: items.length, operations: items });
295
+ }
296
+ else {
297
+ for (const item of items) {
298
+ console.log(`${item["method"].padEnd(7)} ${item["path"]} ${item["operation_id"]}`);
299
+ }
300
+ console.log(`\n${items.length} operations.`);
301
+ }
302
+ });
303
+ catalogCmd
304
+ .command("rank")
305
+ .description("Rank operations for a task.")
306
+ .requiredOption("--task <task>", "Task description")
307
+ .option("--public-only", "Only public operations")
308
+ .option("--json", "JSON output")
309
+ .option("--top <n>", "Top N results", "10")
310
+ .action((opts) => {
311
+ const parentOpts = program.opts();
312
+ const specFile = parentOpts.specFile ?? defaultSpecPath();
313
+ const registry = loadRegistry(specFile);
314
+ if (!registry) {
315
+ console.error("Failed to load OpenAPI spec.");
316
+ process.exit(1);
317
+ }
318
+ const operations = registry.listOperations({ publicOnly: opts.publicOnly });
319
+ const task = opts.task.toLowerCase();
320
+ const taskTerms = [...new Set(task.split(/\s+/))];
321
+ const scored = operations.map((op) => {
322
+ const text = [op.operationId, op.summary ?? "", op.description ?? "", ...op.tags, op.method, op.path].join(" ").toLowerCase();
323
+ let score = 0;
324
+ for (const term of taskTerms) {
325
+ if (text.includes(term))
326
+ score += 1;
327
+ }
328
+ return { op, score };
329
+ });
330
+ scored.sort((a, b) => b.score - a.score);
331
+ const top = scored.slice(0, parseInt(opts.top, 10));
332
+ if (opts.json || parentOpts.json) {
333
+ printJson({
334
+ schema: CATALOG_RANK_SCHEMA_VERSION,
335
+ task: opts.task,
336
+ results: top.map((r) => ({ ...catalogOperationItem(r.op), score: r.score })),
337
+ });
338
+ }
339
+ else {
340
+ for (const r of top) {
341
+ console.log(`[${r.score}] ${r.op.method.padEnd(7)} ${r.op.path} ${r.op.operationId}`);
342
+ }
343
+ }
344
+ });
345
+ // ═════════════════════════════════════════════════════════════════
346
+ // playbook
347
+ // ═════════════════════════════════════════════════════════════════
348
+ program
349
+ .command("playbook [topic]")
350
+ .description("View built-in playbooks for AgenticFlow workflows.")
351
+ .option("--list", "List available playbooks")
352
+ .action((topic, opts) => {
353
+ if (opts.list || !topic) {
354
+ const playbooks = listPlaybooks();
355
+ for (const pb of playbooks) {
356
+ console.log(` ${pb.topic.padEnd(20)} ${pb.title} — ${pb.summary}`);
357
+ }
358
+ return;
359
+ }
360
+ const pb = getPlaybook(topic);
361
+ if (!pb) {
362
+ console.error(`Playbook not found: ${topic}`);
363
+ process.exit(1);
364
+ }
365
+ console.log(`# ${pb.title}\n`);
366
+ console.log(pb.content);
367
+ });
368
+ // ═════════════════════════════════════════════════════════════════
369
+ // login (top-level)
370
+ // ═════════════════════════════════════════════════════════════════
371
+ program
372
+ .command("login")
373
+ .description("Interactively configure your credentials.")
374
+ .option("--profile <profile>", "Profile name", "default")
375
+ .action(async (opts) => {
376
+ const parentOpts = program.opts();
377
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
378
+ const ask = (q) => new Promise((res) => rl.question(q, (a) => res(a.trim())));
379
+ console.log("\n🔑 AgenticFlow Login\n");
380
+ const apiKey = parentOpts.apiKey || await ask(" API Key: ");
381
+ if (!apiKey) {
382
+ console.error("\n✗ API key is required.");
383
+ rl.close();
384
+ process.exit(1);
385
+ }
386
+ if (parentOpts.apiKey)
387
+ console.log(" API Key: ••••••••");
388
+ const workspaceId = parentOpts.workspaceId || await ask(" Workspace ID: ");
389
+ if (parentOpts.workspaceId)
390
+ console.log(` Workspace ID: ${parentOpts.workspaceId}`);
391
+ const projectId = parentOpts.projectId || await ask(" Project ID: ");
392
+ if (parentOpts.projectId)
393
+ console.log(` Project ID: ${parentOpts.projectId}`);
394
+ rl.close();
395
+ // Validate the API key by calling the health endpoint
396
+ console.log("\n Verifying credentials...");
397
+ try {
398
+ const client = createClient({ apiKey });
399
+ await client.sdk.get("/health");
400
+ console.log(" ✓ API key is valid.\n");
401
+ }
402
+ catch {
403
+ console.error(" ✗ Could not verify API key. Saving anyway.\n");
404
+ }
405
+ const configPath = defaultAuthConfigPath();
406
+ const config = loadAuthFile(configPath);
407
+ const profiles = config["profiles"] ?? {};
408
+ const profile = { api_key: apiKey };
409
+ if (workspaceId)
410
+ profile["workspace_id"] = workspaceId;
411
+ if (projectId)
412
+ profile["project_id"] = projectId;
413
+ profiles[opts.profile] = profile;
414
+ if (!config["default_profile"])
415
+ config["default_profile"] = opts.profile;
416
+ config["profiles"] = profiles;
417
+ mkdirSync(dirname(configPath), { recursive: true });
418
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
419
+ console.log(`Saved to profile '${opts.profile}' at ${configPath}`);
420
+ });
421
+ // ═════════════════════════════════════════════════════════════════
422
+ // logout (top-level)
423
+ // ═════════════════════════════════════════════════════════════════
424
+ program
425
+ .command("logout")
426
+ .description("Remove saved credentials.")
427
+ .option("--profile <profile>", "Profile to remove (default: all)")
428
+ .option("-y, --yes", "Skip confirmation")
429
+ .action(async (opts) => {
430
+ const configPath = defaultAuthConfigPath();
431
+ if (!existsSync(configPath)) {
432
+ console.log("No credentials found. Already logged out.");
433
+ return;
434
+ }
435
+ if (!opts.yes) {
436
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
437
+ const answer = await new Promise((res) => rl.question(opts.profile
438
+ ? `Remove profile '${opts.profile}'? (y/N) `
439
+ : "Remove all saved credentials? (y/N) ", (a) => { res(a.trim().toLowerCase()); rl.close(); }));
440
+ if (answer !== "y" && answer !== "yes") {
441
+ console.log("Cancelled.");
442
+ return;
443
+ }
444
+ }
445
+ if (opts.profile) {
446
+ // Remove a single profile
447
+ const config = loadAuthFile(configPath);
448
+ const profiles = config["profiles"];
449
+ if (profiles && opts.profile in profiles) {
450
+ delete profiles[opts.profile];
451
+ if (config["default_profile"] === opts.profile) {
452
+ const remaining = Object.keys(profiles);
453
+ config["default_profile"] = remaining[0] ?? "default";
454
+ }
455
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
456
+ console.log(`✓ Removed profile '${opts.profile}'.`);
457
+ }
458
+ else {
459
+ console.log(`Profile '${opts.profile}' not found.`);
460
+ }
461
+ }
462
+ else {
463
+ // Remove the entire auth file
464
+ unlinkSync(configPath);
465
+ console.log(`✓ Removed ${configPath}`);
466
+ }
467
+ });
468
+ // ═════════════════════════════════════════════════════════════════
469
+ // whoami (top-level)
470
+ // ═════════════════════════════════════════════════════════════════
471
+ program
472
+ .command("whoami")
473
+ .description("Show current authentication state.")
474
+ .option("--json", "JSON output")
475
+ .action((opts) => {
476
+ const parentOpts = program.opts();
477
+ const token = resolveToken(parentOpts);
478
+ const wsId = resolveWorkspaceId(parentOpts.workspaceId);
479
+ const projId = resolveProjectId(parentOpts.projectId);
480
+ const configPath = defaultAuthConfigPath();
481
+ const config = loadAuthFile(configPath);
482
+ const profileName = config["default_profile"] ?? "default";
483
+ const result = {
484
+ profile: profileName,
485
+ api_key_present: !!token,
486
+ workspace_id: wsId ?? "not set",
487
+ project_id: projId ?? "not set",
488
+ config_path: configPath,
489
+ };
490
+ if (opts.json || parentOpts.json) {
491
+ printJson(result);
492
+ }
493
+ else {
494
+ console.log(`Profile: ${result.profile}`);
495
+ console.log(`API Key: ${result.api_key_present ? "present" : "not set"}`);
496
+ console.log(`Workspace ID: ${result.workspace_id}`);
497
+ console.log(`Project ID: ${result.project_id}`);
498
+ console.log(`Config: ${result.config_path}`);
499
+ }
500
+ });
501
+ // ═════════════════════════════════════════════════════════════════
502
+ // auth (import-env stays here)
503
+ // ═════════════════════════════════════════════════════════════════
504
+ const authCmd = program
505
+ .command("auth")
506
+ .description("Authentication management.");
507
+ authCmd
508
+ .command("import-env")
509
+ .description("Import credentials from an env file.")
510
+ .requiredOption("--file <path>", "Path to .env file")
511
+ .option("--profile <profile>", "Profile name", "default")
512
+ .action((opts) => {
513
+ const envPath = resolve(opts.file);
514
+ if (!existsSync(envPath)) {
515
+ console.error(`File not found: ${envPath}`);
516
+ process.exit(1);
517
+ }
518
+ const content = readFileSync(envPath, "utf-8");
519
+ const env = {};
520
+ for (const line of content.split("\n")) {
521
+ const parsed = parseKeyValueEnv(line);
522
+ if (parsed)
523
+ env[parsed[0]] = parsed[1];
524
+ }
525
+ const apiKey = env["AGENTICFLOW_API_KEY"] ?? env["AGENTICFLOW_PUBLIC_API_KEY"];
526
+ const workspaceId = env["AGENTICFLOW_WORKSPACE_ID"];
527
+ const projectId = env["AGENTICFLOW_PROJECT_ID"];
528
+ if (!apiKey) {
529
+ console.error("No AGENTICFLOW_API_KEY found in env file.");
530
+ process.exit(1);
531
+ }
532
+ const configPath = defaultAuthConfigPath();
533
+ const config = loadAuthFile(configPath);
534
+ const profiles = config["profiles"] ?? {};
535
+ const profile = { api_key: apiKey };
536
+ if (workspaceId)
537
+ profile["workspace_id"] = workspaceId;
538
+ if (projectId)
539
+ profile["project_id"] = projectId;
540
+ profiles[opts.profile] = profile;
541
+ if (!config["default_profile"])
542
+ config["default_profile"] = opts.profile;
543
+ config["profiles"] = profiles;
544
+ mkdirSync(dirname(configPath), { recursive: true });
545
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
546
+ console.log(`Imported credentials to profile '${opts.profile}' at ${configPath}`);
547
+ });
548
+ // ═════════════════════════════════════════════════════════════════
549
+ // policy
550
+ // ═════════════════════════════════════════════════════════════════
551
+ const policyCmd = program
552
+ .command("policy")
553
+ .description("Local policy guardrails management.");
554
+ policyCmd
555
+ .command("show")
556
+ .description("Show current policy configuration.")
557
+ .option("--json", "JSON output")
558
+ .action((opts) => {
559
+ const parentOpts = program.opts();
560
+ try {
561
+ const policy = loadPolicy();
562
+ const filePath = policyFilePath();
563
+ if (opts.json || parentOpts.json) {
564
+ printJson({ file: filePath, ...policy });
565
+ }
566
+ else {
567
+ console.log(`Policy file: ${filePath}`);
568
+ console.log(`Spend ceiling: ${policy.spendCeiling ?? "none"}`);
569
+ console.log(`Allowlist: ${policy.allowlist.length > 0 ? policy.allowlist.join(", ") : "none"}`);
570
+ console.log(`Blocklist: ${policy.blocklist.length > 0 ? policy.blocklist.join(", ") : "none"}`);
571
+ }
572
+ }
573
+ catch (err) {
574
+ console.error(`Policy error: ${err instanceof Error ? err.message : err}`);
575
+ process.exit(1);
576
+ }
577
+ });
578
+ policyCmd
579
+ .command("init")
580
+ .description("Initialize default policy file.")
581
+ .option("--force", "Overwrite existing policy file")
582
+ .option("--spend-ceiling <amount>", "Set spend ceiling")
583
+ .action((opts) => {
584
+ try {
585
+ const filePath = writeDefaultPolicy({
586
+ force: opts.force,
587
+ spendCeiling: opts.spendCeiling ? parseFloat(opts.spendCeiling) : undefined,
588
+ });
589
+ console.log(`Policy file created: ${filePath}`);
590
+ }
591
+ catch (err) {
592
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
593
+ process.exit(1);
594
+ }
595
+ });
596
+ // ═════════════════════════════════════════════════════════════════
597
+ // call (generic, spec-based)
598
+ // ═════════════════════════════════════════════════════════════════
599
+ program
600
+ .command("call")
601
+ .description("Low-level OpenAPI transport — execute an operation directly.")
602
+ .option("--operation-id <id>", "Operation ID to invoke")
603
+ .option("--method <method>", "HTTP method")
604
+ .option("--path <path>", "API path")
605
+ .option("-P, --path-param <params...>", "Path parameters (key=value)")
606
+ .option("-Q, --query-param <params...>", "Query parameters (key=value)")
607
+ .option("-H, --header <headers...>", "Extra headers (key=value)")
608
+ .option("--body <body>", "JSON body (inline or @file)")
609
+ .option("--dry-run", "Show request without executing")
610
+ .action(async (opts) => {
611
+ const parentOpts = program.opts();
612
+ const baseUrl = DEFAULT_BASE_URL;
613
+ const token = resolveToken(parentOpts);
614
+ const specFile = parentOpts.specFile ?? defaultSpecPath();
615
+ const registry = loadRegistry(specFile);
616
+ if (!registry) {
617
+ console.error("Failed to load OpenAPI spec.");
618
+ process.exit(1);
619
+ }
620
+ // Resolve operation
621
+ let operation = null;
622
+ if (opts.operationId) {
623
+ operation = registry.getOperationById(opts.operationId);
624
+ }
625
+ else if (opts.method && opts.path) {
626
+ operation = registry.getOperationByMethodPath(opts.method, opts.path);
627
+ }
628
+ if (!operation && opts.method && opts.path) {
629
+ operation = {
630
+ operationId: `${opts.method.toLowerCase()}_${opts.path.replace(/^\//, "").replace(/\//g, "_")}`,
631
+ method: opts.method.toUpperCase(),
632
+ path: opts.path,
633
+ tags: [], security: [], parameters: [],
634
+ requestBody: null, summary: null, description: null, raw: {},
635
+ };
636
+ }
637
+ if (!operation) {
638
+ console.error("Unable to resolve operation.");
639
+ process.exit(1);
640
+ }
641
+ const pathParams = opts.pathParam ? parseKeyValuePairs(opts.pathParam) : {};
642
+ const queryParams = opts.queryParam ? parseKeyValuePairs(opts.queryParam) : {};
643
+ const headers = opts.header ? parseKeyValuePairs(opts.header) : {};
644
+ const body = opts.body ? loadJsonPayload(opts.body) : undefined;
645
+ const requestSpec = buildRequestSpec(operation, baseUrl, pathParams, queryParams, headers, token, body);
646
+ if (opts.dryRun) {
647
+ printJson({
648
+ dry_run: true,
649
+ operation_id: operation.operationId,
650
+ method: requestSpec.method,
651
+ url: requestSpec.url,
652
+ params: requestSpec.params,
653
+ headers: Object.fromEntries(Object.entries(requestSpec.headers).map(([k, v]) => k.toLowerCase() === "authorization" ? [k, "Bearer ***"] : [k, v])),
654
+ body: requestSpec.body ?? null,
655
+ });
656
+ return;
657
+ }
658
+ // Execute request
659
+ try {
660
+ const response = await fetch(requestSpec.url + (Object.keys(requestSpec.params).length > 0
661
+ ? "?" + new URLSearchParams(requestSpec.params).toString()
662
+ : ""), {
663
+ method: requestSpec.method,
664
+ headers: requestSpec.headers,
665
+ body: requestSpec.body != null ? JSON.stringify(requestSpec.body) : undefined,
666
+ });
667
+ const text = await response.text();
668
+ let data;
669
+ try {
670
+ data = JSON.parse(text);
671
+ }
672
+ catch {
673
+ data = text;
674
+ }
675
+ printJson({ status: response.status, body: data });
676
+ if (!response.ok)
677
+ process.exitCode = 1;
678
+ }
679
+ catch (err) {
680
+ console.error(`Request failed: ${err instanceof Error ? err.message : err}`);
681
+ process.exit(1);
682
+ }
683
+ });
684
+ // ═════════════════════════════════════════════════════════════════
685
+ // workflow (SDK-based)
686
+ // ═════════════════════════════════════════════════════════════════
687
+ const workflowCmd = program
688
+ .command("workflow")
689
+ .description("Workflow management commands.");
690
+ workflowCmd
691
+ .command("list")
692
+ .description("List workflows.")
693
+ .option("--workspace-id <id>", "Workspace ID (overrides global)")
694
+ .option("--project-id <id>", "Project ID")
695
+ .option("--search <query>", "Search query")
696
+ .option("--limit <n>", "Limit results")
697
+ .option("--offset <n>", "Offset")
698
+ .action(async (opts) => {
699
+ const client = buildClient(program.opts());
700
+ await run(() => client.workflows.list({
701
+ workspaceId: opts.workspaceId,
702
+ projectId: opts.projectId,
703
+ searchQuery: opts.search,
704
+ limit: opts.limit ? parseInt(opts.limit) : undefined,
705
+ offset: opts.offset ? parseInt(opts.offset) : undefined,
706
+ }));
707
+ });
708
+ workflowCmd
709
+ .command("get")
710
+ .description("Get a workflow by ID.")
711
+ .requiredOption("--workflow-id <id>", "Workflow ID")
712
+ .action(async (opts) => {
713
+ const client = buildClient(program.opts());
714
+ const token = resolveToken(program.opts());
715
+ if (token) {
716
+ await run(() => client.workflows.get(opts.workflowId));
717
+ }
718
+ else {
719
+ await run(() => client.workflows.getAnonymous(opts.workflowId));
720
+ }
721
+ });
722
+ workflowCmd
723
+ .command("create")
724
+ .description("Create a new workflow.")
725
+ .option("--workspace-id <id>", "Workspace ID")
726
+ .requiredOption("--body <body>", "JSON body (inline or @file)")
727
+ .action(async (opts) => {
728
+ const client = buildClient(program.opts());
729
+ const body = loadJsonPayload(opts.body);
730
+ await run(() => client.workflows.create(body, opts.workspaceId));
731
+ });
732
+ workflowCmd
733
+ .command("update")
734
+ .description("Update a workflow.")
735
+ .option("--workspace-id <id>", "Workspace ID")
736
+ .requiredOption("--workflow-id <id>", "Workflow ID")
737
+ .requiredOption("--body <body>", "JSON body (inline or @file)")
738
+ .action(async (opts) => {
739
+ const client = buildClient(program.opts());
740
+ const body = loadJsonPayload(opts.body);
741
+ await run(() => client.workflows.update(opts.workflowId, body, opts.workspaceId));
742
+ });
743
+ workflowCmd
744
+ .command("delete")
745
+ .description("Delete a workflow.")
746
+ .option("--workspace-id <id>", "Workspace ID")
747
+ .requiredOption("--workflow-id <id>", "Workflow ID")
748
+ .action(async (opts) => {
749
+ const client = buildClient(program.opts());
750
+ await run(() => client.workflows.delete(opts.workflowId, opts.workspaceId));
751
+ });
752
+ workflowCmd
753
+ .command("run")
754
+ .description("Run a workflow.")
755
+ .requiredOption("--workflow-id <id>", "Workflow ID")
756
+ .option("--input <input>", "JSON input (inline or @file)")
757
+ .action(async (opts) => {
758
+ const client = buildClient(program.opts());
759
+ const token = resolveToken(program.opts());
760
+ const body = { workflow_id: opts.workflowId };
761
+ if (opts.input)
762
+ body["input"] = loadJsonPayload(opts.input);
763
+ if (token) {
764
+ await run(() => client.workflows.run(body));
765
+ }
766
+ else {
767
+ await run(() => client.workflows.runAnonymous(body));
768
+ }
769
+ });
770
+ workflowCmd
771
+ .command("run-status")
772
+ .description("Get workflow run status.")
773
+ .requiredOption("--workflow-run-id <id>", "Workflow run ID")
774
+ .action(async (opts) => {
775
+ const client = buildClient(program.opts());
776
+ const token = resolveToken(program.opts());
777
+ if (token) {
778
+ await run(() => client.workflows.getRun(opts.workflowRunId));
779
+ }
780
+ else {
781
+ await run(() => client.workflows.getRunAnonymous(opts.workflowRunId));
782
+ }
783
+ });
784
+ workflowCmd
785
+ .command("list-runs")
786
+ .description("List runs for a workflow.")
787
+ .requiredOption("--workflow-id <id>", "Workflow ID")
788
+ .option("--workspace-id <id>", "Workspace ID")
789
+ .option("--limit <n>", "Limit")
790
+ .option("--offset <n>", "Offset")
791
+ .option("--sort-order <order>", "Sort order (asc|desc)")
792
+ .action(async (opts) => {
793
+ const client = buildClient(program.opts());
794
+ await run(() => client.workflows.listRuns(opts.workflowId, {
795
+ workspaceId: opts.workspaceId,
796
+ limit: opts.limit ? parseInt(opts.limit) : undefined,
797
+ offset: opts.offset ? parseInt(opts.offset) : undefined,
798
+ sortOrder: opts.sortOrder,
799
+ }));
800
+ });
801
+ workflowCmd
802
+ .command("validate")
803
+ .description("Validate a workflow payload.")
804
+ .requiredOption("--body <body>", "JSON body (inline or @file)")
805
+ .action(async (opts) => {
806
+ const client = buildClient(program.opts());
807
+ const body = loadJsonPayload(opts.body);
808
+ await run(() => client.workflows.validate(body));
809
+ });
810
+ workflowCmd
811
+ .command("run-history")
812
+ .description("Get run history for a workflow.")
813
+ .requiredOption("--workflow-id <id>", "Workflow ID")
814
+ .option("--limit <n>", "Limit")
815
+ .option("--offset <n>", "Offset")
816
+ .action(async (opts) => {
817
+ const client = buildClient(program.opts());
818
+ await run(() => client.workflows.runHistory(opts.workflowId, {
819
+ limit: opts.limit ? parseInt(opts.limit) : undefined,
820
+ offset: opts.offset ? parseInt(opts.offset) : undefined,
821
+ }));
822
+ });
823
+ workflowCmd
824
+ .command("like")
825
+ .description("Like a workflow.")
826
+ .requiredOption("--workflow-id <id>", "Workflow ID")
827
+ .action(async (opts) => {
828
+ const client = buildClient(program.opts());
829
+ await run(() => client.workflows.like(opts.workflowId));
830
+ });
831
+ workflowCmd
832
+ .command("unlike")
833
+ .description("Unlike a workflow.")
834
+ .requiredOption("--workflow-id <id>", "Workflow ID")
835
+ .action(async (opts) => {
836
+ const client = buildClient(program.opts());
837
+ await run(() => client.workflows.unlike(opts.workflowId));
838
+ });
839
+ workflowCmd
840
+ .command("like-status")
841
+ .description("Get like status for a workflow.")
842
+ .requiredOption("--workflow-id <id>", "Workflow ID")
843
+ .action(async (opts) => {
844
+ const client = buildClient(program.opts());
845
+ await run(() => client.workflows.getLikeStatus(opts.workflowId));
846
+ });
847
+ workflowCmd
848
+ .command("reference-impact")
849
+ .description("Get reference impact analysis for a workflow.")
850
+ .requiredOption("--workflow-id <id>", "Workflow ID")
851
+ .action(async (opts) => {
852
+ const client = buildClient(program.opts());
853
+ await run(() => client.workflows.getReferenceImpact(opts.workflowId));
854
+ });
855
+ // ═════════════════════════════════════════════════════════════════
856
+ // agent (SDK-based)
857
+ // ═════════════════════════════════════════════════════════════════
858
+ const agentCmd = program
859
+ .command("agent")
860
+ .description("Agent management commands.");
861
+ agentCmd
862
+ .command("list")
863
+ .description("List agents.")
864
+ .option("--project-id <id>", "Project ID")
865
+ .option("--search <query>", "Search query")
866
+ .option("--limit <n>", "Limit results")
867
+ .option("--offset <n>", "Offset")
868
+ .action(async (opts) => {
869
+ const client = buildClient(program.opts());
870
+ await run(() => client.agents.list({
871
+ projectId: opts.projectId,
872
+ searchQuery: opts.search,
873
+ limit: opts.limit ? parseInt(opts.limit) : undefined,
874
+ offset: opts.offset ? parseInt(opts.offset) : undefined,
875
+ }));
876
+ });
877
+ agentCmd
878
+ .command("get")
879
+ .description("Get an agent by ID.")
880
+ .requiredOption("--agent-id <id>", "Agent ID")
881
+ .action(async (opts) => {
882
+ const client = buildClient(program.opts());
883
+ const token = resolveToken(program.opts());
884
+ if (token) {
885
+ await run(() => client.agents.get(opts.agentId));
886
+ }
887
+ else {
888
+ await run(() => client.agents.getAnonymous(opts.agentId));
889
+ }
890
+ });
891
+ agentCmd
892
+ .command("create")
893
+ .description("Create an agent.")
894
+ .requiredOption("--body <body>", "JSON body (inline or @file)")
895
+ .action(async (opts) => {
896
+ const client = buildClient(program.opts());
897
+ const body = loadJsonPayload(opts.body);
898
+ await run(() => client.agents.create(body));
899
+ });
900
+ agentCmd
901
+ .command("update")
902
+ .description("Update an agent.")
903
+ .requiredOption("--agent-id <id>", "Agent ID")
904
+ .requiredOption("--body <body>", "JSON body (inline or @file)")
905
+ .action(async (opts) => {
906
+ const client = buildClient(program.opts());
907
+ const body = loadJsonPayload(opts.body);
908
+ await run(() => client.agents.update(opts.agentId, body));
909
+ });
910
+ agentCmd
911
+ .command("delete")
912
+ .description("Delete an agent.")
913
+ .requiredOption("--agent-id <id>", "Agent ID")
914
+ .action(async (opts) => {
915
+ const client = buildClient(program.opts());
916
+ await run(() => client.agents.delete(opts.agentId));
917
+ });
918
+ agentCmd
919
+ .command("stream")
920
+ .description("Stream interaction with an agent.")
921
+ .requiredOption("--agent-id <id>", "Agent ID")
922
+ .requiredOption("--body <body>", "JSON body (inline or @file)")
923
+ .action(async (opts) => {
924
+ const client = buildClient(program.opts());
925
+ const token = resolveToken(program.opts());
926
+ const body = loadJsonPayload(opts.body);
927
+ if (token) {
928
+ await run(() => client.agents.stream(opts.agentId, body));
929
+ }
930
+ else {
931
+ await run(() => client.agents.streamAnonymous(opts.agentId, body));
932
+ }
933
+ });
934
+ agentCmd
935
+ .command("reference-impact")
936
+ .description("Get reference impact analysis for an agent.")
937
+ .requiredOption("--agent-id <id>", "Agent ID")
938
+ .action(async (opts) => {
939
+ const client = buildClient(program.opts());
940
+ await run(() => client.agents.getReferenceImpact(opts.agentId));
941
+ });
942
+ // ═════════════════════════════════════════════════════════════════
943
+ // node-types (SDK-based)
944
+ // ═════════════════════════════════════════════════════════════════
945
+ const nodeTypesCmd = program
946
+ .command("node-types")
947
+ .description("Node type discovery commands.");
948
+ nodeTypesCmd
949
+ .command("list")
950
+ .description("List available node types.")
951
+ .action(async () => {
952
+ const client = buildClient(program.opts());
953
+ await run(() => client.nodeTypes.list());
954
+ });
955
+ nodeTypesCmd
956
+ .command("get")
957
+ .description("Get a specific node type.")
958
+ .requiredOption("--name <name>", "Node type name")
959
+ .action(async (opts) => {
960
+ const client = buildClient(program.opts());
961
+ await run(() => client.nodeTypes.get(opts.name));
962
+ });
963
+ nodeTypesCmd
964
+ .command("search")
965
+ .description("Search node types.")
966
+ .requiredOption("--query <query>", "Search query")
967
+ .action(async (opts) => {
968
+ const client = buildClient(program.opts());
969
+ await run(() => client.nodeTypes.search(opts.query));
970
+ });
971
+ nodeTypesCmd
972
+ .command("dynamic-options")
973
+ .description("Get dynamic options for a node type field.")
974
+ .requiredOption("--name <name>", "Node type name")
975
+ .requiredOption("--field-name <field>", "Field name")
976
+ .option("--project-id <id>", "Project ID")
977
+ .option("--input-config <json>", "Input config JSON")
978
+ .option("--connection <name>", "Connection name")
979
+ .option("--search-term <term>", "Search term")
980
+ .action(async (opts) => {
981
+ const client = buildClient(program.opts());
982
+ await run(() => client.nodeTypes.dynamicOptions({
983
+ name: opts.name,
984
+ fieldName: opts.fieldName,
985
+ projectId: opts.projectId,
986
+ inputConfig: opts.inputConfig ? JSON.parse(opts.inputConfig) : undefined,
987
+ connection: opts.connection,
988
+ searchTerm: opts.searchTerm,
989
+ }));
990
+ });
991
+ // ═════════════════════════════════════════════════════════════════
992
+ // connections (SDK-based)
993
+ // ═════════════════════════════════════════════════════════════════
994
+ const connectionsCmd = program
995
+ .command("connections")
996
+ .description("App connection management.");
997
+ connectionsCmd
998
+ .command("list")
999
+ .description("List connections.")
1000
+ .option("--workspace-id <id>", "Workspace ID")
1001
+ .option("--project-id <id>", "Project ID")
1002
+ .option("--limit <n>", "Limit")
1003
+ .option("--offset <n>", "Offset")
1004
+ .action(async (opts) => {
1005
+ const client = buildClient(program.opts());
1006
+ await run(() => client.connections.list({
1007
+ workspaceId: opts.workspaceId,
1008
+ projectId: opts.projectId,
1009
+ limit: opts.limit ? parseInt(opts.limit) : undefined,
1010
+ offset: opts.offset ? parseInt(opts.offset) : undefined,
1011
+ }));
1012
+ });
1013
+ connectionsCmd
1014
+ .command("create")
1015
+ .description("Create a connection.")
1016
+ .option("--workspace-id <id>", "Workspace ID")
1017
+ .requiredOption("--body <body>", "JSON body (inline or @file)")
1018
+ .action(async (opts) => {
1019
+ const client = buildClient(program.opts());
1020
+ const body = loadJsonPayload(opts.body);
1021
+ await run(() => client.connections.create(body, opts.workspaceId));
1022
+ });
1023
+ connectionsCmd
1024
+ .command("get-default")
1025
+ .description("Get default connection for a category.")
1026
+ .requiredOption("--category <name>", "Category name")
1027
+ .option("--workspace-id <id>", "Workspace ID")
1028
+ .option("--project-id <id>", "Project ID")
1029
+ .action(async (opts) => {
1030
+ const client = buildClient(program.opts());
1031
+ await run(() => client.connections.getDefault({
1032
+ categoryName: opts.category,
1033
+ workspaceId: opts.workspaceId,
1034
+ projectId: opts.projectId,
1035
+ }));
1036
+ });
1037
+ connectionsCmd
1038
+ .command("update")
1039
+ .description("Update a connection.")
1040
+ .requiredOption("--connection-id <id>", "Connection ID")
1041
+ .requiredOption("--body <body>", "JSON body (inline or @file)")
1042
+ .option("--workspace-id <id>", "Workspace ID")
1043
+ .action(async (opts) => {
1044
+ const client = buildClient(program.opts());
1045
+ const body = loadJsonPayload(opts.body);
1046
+ await run(() => client.connections.update(opts.connectionId, body, opts.workspaceId));
1047
+ });
1048
+ connectionsCmd
1049
+ .command("delete")
1050
+ .description("Delete a connection.")
1051
+ .requiredOption("--connection-id <id>", "Connection ID")
1052
+ .option("--workspace-id <id>", "Workspace ID")
1053
+ .action(async (opts) => {
1054
+ const client = buildClient(program.opts());
1055
+ await run(() => client.connections.delete(opts.connectionId, opts.workspaceId));
1056
+ });
1057
+ connectionsCmd
1058
+ .command("categories")
1059
+ .description("List connection categories.")
1060
+ .option("--workspace-id <id>", "Workspace ID")
1061
+ .option("--limit <n>", "Limit")
1062
+ .option("--offset <n>", "Offset")
1063
+ .action(async (opts) => {
1064
+ const client = buildClient(program.opts());
1065
+ await run(() => client.connections.categories({
1066
+ workspaceId: opts.workspaceId,
1067
+ limit: opts.limit ? parseInt(opts.limit) : undefined,
1068
+ offset: opts.offset ? parseInt(opts.offset) : undefined,
1069
+ }));
1070
+ });
1071
+ // ═════════════════════════════════════════════════════════════════
1072
+ // uploads (SDK-based)
1073
+ // ═════════════════════════════════════════════════════════════════
1074
+ const uploadsCmd = program
1075
+ .command("uploads")
1076
+ .description("Upload session management.");
1077
+ uploadsCmd
1078
+ .command("create")
1079
+ .description("Create an upload session.")
1080
+ .requiredOption("--body <body>", "JSON body (inline or @file)")
1081
+ .action(async (opts) => {
1082
+ const client = buildClient(program.opts());
1083
+ const body = loadJsonPayload(opts.body);
1084
+ await run(() => client.uploads.inputCreate(body));
1085
+ });
1086
+ uploadsCmd
1087
+ .command("status")
1088
+ .description("Get upload session status.")
1089
+ .requiredOption("--session-id <id>", "Session ID")
1090
+ .action(async (opts) => {
1091
+ const client = buildClient(program.opts());
1092
+ await run(() => client.uploads.inputStatus(opts.sessionId));
1093
+ });
1094
+ return program;
1095
+ }
1096
+ export async function runCli(argv) {
1097
+ const program = createProgram();
1098
+ await program.parseAsync(argv ?? process.argv);
1099
+ }
1100
+ //# sourceMappingURL=main.js.map