@syntropic137/cli 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +264 -0
  2. package/dist/syn.mjs +4823 -0
  3. package/package.json +37 -0
package/dist/syn.mjs ADDED
@@ -0,0 +1,4823 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/config.ts
4
+ var CLI_NAME = "syn";
5
+ var CLI_VERSION = "0.18.0";
6
+ var CLI_DESCRIPTION = "Syntropic137 - Event-sourced workflow engine for AI agents";
7
+ var DEFAULT_TIMEOUT_MS = 3e4;
8
+ var SSE_CONNECT_TIMEOUT_MS = 5e3;
9
+ function getApiUrl() {
10
+ return process.env["SYN_API_URL"] ?? "http://localhost:8137";
11
+ }
12
+ function getAuthHeaders() {
13
+ const token = process.env["SYN_API_TOKEN"];
14
+ if (token) return { Authorization: `Bearer ${token}` };
15
+ const user = process.env["SYN_API_USER"];
16
+ const password = process.env["SYN_API_PASSWORD"];
17
+ if (user && password) {
18
+ const encoded = Buffer.from(`${user}:${password}`).toString("base64");
19
+ return { Authorization: `Basic ${encoded}` };
20
+ }
21
+ return {};
22
+ }
23
+
24
+ // src/framework/errors.ts
25
+ var CLIError = class extends Error {
26
+ exitCode;
27
+ constructor(message, exitCode = 1) {
28
+ super(message);
29
+ this.name = "CLIError";
30
+ this.exitCode = exitCode;
31
+ }
32
+ };
33
+
34
+ // src/output/ansi.ts
35
+ var RESET = "\x1B[0m";
36
+ var BOLD = "\x1B[1m";
37
+ var DIM = "\x1B[2m";
38
+ var RED = "\x1B[31m";
39
+ var GREEN = "\x1B[32m";
40
+ var YELLOW = "\x1B[33m";
41
+ var BLUE = "\x1B[34m";
42
+ var CYAN = "\x1B[36m";
43
+ var ANSI_REGEX = /\x1b\[[0-9;]*m/g;
44
+ function isColorEnabled() {
45
+ if (process.env["NO_COLOR"] !== void 0) return false;
46
+ return process.stdout.isTTY === true;
47
+ }
48
+ function style(text, ...codes) {
49
+ if (!isColorEnabled()) return text;
50
+ return codes.join("") + text + RESET;
51
+ }
52
+ function stripAnsi(text) {
53
+ return text.replace(ANSI_REGEX, "");
54
+ }
55
+
56
+ // src/output/console.ts
57
+ function printError(message) {
58
+ process.stderr.write(style("Error:", BOLD, RED) + " " + message + "\n");
59
+ }
60
+ function printSuccess(message) {
61
+ process.stdout.write(style(message, GREEN) + "\n");
62
+ }
63
+ function printDim(message) {
64
+ process.stdout.write(style(message, DIM) + "\n");
65
+ }
66
+ function print(message) {
67
+ process.stdout.write(message + "\n");
68
+ }
69
+
70
+ // src/client/http.ts
71
+ var SynClient = class {
72
+ baseUrl;
73
+ timeoutMs;
74
+ authHeaders;
75
+ constructor(options) {
76
+ this.baseUrl = options?.baseUrl ?? getApiUrl();
77
+ this.timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
78
+ this.authHeaders = getAuthHeaders();
79
+ }
80
+ async get(path8, params) {
81
+ const url = this.buildUrl(path8, params);
82
+ return this.request(url, { method: "GET" });
83
+ }
84
+ async post(path8, body, params) {
85
+ const url = this.buildUrl(path8, params);
86
+ return this.request(url, {
87
+ method: "POST",
88
+ headers: { "Content-Type": "application/json" },
89
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
90
+ });
91
+ }
92
+ async put(path8, body) {
93
+ const url = this.buildUrl(path8);
94
+ return this.request(url, {
95
+ method: "PUT",
96
+ headers: { "Content-Type": "application/json" },
97
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
98
+ });
99
+ }
100
+ async patch(path8, body) {
101
+ const url = this.buildUrl(path8);
102
+ return this.request(url, {
103
+ method: "PATCH",
104
+ headers: { "Content-Type": "application/json" },
105
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
106
+ });
107
+ }
108
+ async delete(path8) {
109
+ const url = this.buildUrl(path8);
110
+ return this.request(url, { method: "DELETE" });
111
+ }
112
+ async stream(path8) {
113
+ const url = this.buildUrl(path8);
114
+ const controller = new AbortController();
115
+ const timer = setTimeout(() => controller.abort(), SSE_CONNECT_TIMEOUT_MS);
116
+ const response = await fetch(url.toString(), {
117
+ method: "GET",
118
+ headers: { ...this.authHeaders },
119
+ signal: controller.signal
120
+ });
121
+ clearTimeout(timer);
122
+ if (!response.body) {
123
+ throw new Error("Response body is null");
124
+ }
125
+ return response.body;
126
+ }
127
+ buildUrl(path8, params) {
128
+ const base = new URL(this.baseUrl);
129
+ const basePath = base.pathname.replace(/\/+$/, "");
130
+ const reqPath = path8.startsWith("/") ? path8 : `/${path8}`;
131
+ const url = new URL(`${basePath}${reqPath}`, base);
132
+ if (params) {
133
+ for (const [key, value] of Object.entries(params)) {
134
+ if (value !== void 0) {
135
+ url.searchParams.set(key, String(value));
136
+ }
137
+ }
138
+ }
139
+ return url;
140
+ }
141
+ async request(url, init) {
142
+ const controller = new AbortController();
143
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
144
+ try {
145
+ const existingHeaders = init.headers;
146
+ const response = await fetch(url.toString(), {
147
+ ...init,
148
+ headers: { ...this.authHeaders, ...existingHeaders },
149
+ signal: controller.signal
150
+ });
151
+ const data = response.status === 204 || response.status === 205 ? void 0 : await response.json();
152
+ return { status: response.status, data };
153
+ } finally {
154
+ clearTimeout(timer);
155
+ }
156
+ }
157
+ };
158
+
159
+ // src/client/api.ts
160
+ function handleConnectError() {
161
+ printError(`Could not connect to API at ${getApiUrl()}`);
162
+ printDim("Make sure the API server is running.");
163
+ throw new CLIError("Connection failed", 1);
164
+ }
165
+ function checkResponse(status, data, expected) {
166
+ if (expected.includes(status)) return;
167
+ let detail = `HTTP ${status}`;
168
+ if (typeof data === "object" && data !== null && "detail" in data && typeof data.detail === "string") {
169
+ detail = data.detail;
170
+ }
171
+ throw new CLIError(detail);
172
+ }
173
+ async function safeRequest(fn) {
174
+ try {
175
+ return await fn();
176
+ } catch (err) {
177
+ if (err instanceof CLIError) throw err;
178
+ handleConnectError();
179
+ }
180
+ }
181
+ async function apiGet(path8, options) {
182
+ const client = new SynClient();
183
+ const { status, data } = await safeRequest(
184
+ () => client.get(path8, options?.params)
185
+ );
186
+ checkResponse(status, data, options?.expected ?? [200]);
187
+ return data;
188
+ }
189
+ async function apiGetList(path8, options) {
190
+ const client = new SynClient();
191
+ const { status, data } = await safeRequest(
192
+ () => client.get(path8, options?.params)
193
+ );
194
+ checkResponse(status, data, options?.expected ?? [200]);
195
+ return data;
196
+ }
197
+ async function apiPost(path8, options) {
198
+ const client = new SynClient(
199
+ options?.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : void 0
200
+ );
201
+ const { status, data } = await safeRequest(
202
+ () => client.post(path8, options?.body, options?.params)
203
+ );
204
+ checkResponse(status, data, options?.expected ?? [200]);
205
+ return data;
206
+ }
207
+ async function apiPut(path8, options) {
208
+ const client = new SynClient();
209
+ const { status, data } = await safeRequest(
210
+ () => client.put(path8, options?.body)
211
+ );
212
+ checkResponse(status, data, options?.expected ?? [200]);
213
+ return data;
214
+ }
215
+ async function apiDelete(path8, options) {
216
+ const client = new SynClient();
217
+ const { status, data } = await safeRequest(() => client.delete(path8));
218
+ checkResponse(status, data, options?.expected ?? [200]);
219
+ return data;
220
+ }
221
+ function buildParams(params) {
222
+ const result = {};
223
+ for (const [key, value] of Object.entries(params)) {
224
+ if (value !== null && value !== void 0) {
225
+ result[key] = String(value);
226
+ }
227
+ }
228
+ return result;
229
+ }
230
+
231
+ // src/commands/health.ts
232
+ var healthCommand = {
233
+ name: "health",
234
+ description: "Check API server health status",
235
+ handler: async () => {
236
+ const data = await apiGet("/health");
237
+ const { status, mode } = data;
238
+ if (status === "healthy" && mode === "full") {
239
+ print(style("Healthy", BOLD, GREEN) + " \u2014 all systems operational");
240
+ } else if (status === "healthy") {
241
+ print(style("Degraded", BOLD, YELLOW) + ` \u2014 mode: ${mode}`);
242
+ if (data.degraded_reasons) {
243
+ for (const reason of data.degraded_reasons) {
244
+ print(style(` \u2022 ${reason}`, YELLOW));
245
+ }
246
+ }
247
+ } else {
248
+ print(style("Unhealthy", BOLD, RED) + ` \u2014 status: ${status}`);
249
+ throw new CLIError("API is unhealthy");
250
+ }
251
+ if (data.subscription) {
252
+ print(style(" Event store: connected", DIM));
253
+ print(style(` Subscription: ${data.subscription.status}`, DIM));
254
+ }
255
+ }
256
+ };
257
+
258
+ // src/commands/version.ts
259
+ var versionCommand = {
260
+ name: "version",
261
+ description: "Show version information",
262
+ handler: () => {
263
+ print(`${style("Syntropic137", BOLD)} v${CLI_VERSION}`);
264
+ }
265
+ };
266
+
267
+ // src/framework/command.ts
268
+ var CommandGroup = class {
269
+ name;
270
+ description;
271
+ _commands = /* @__PURE__ */ new Map();
272
+ constructor(name, description) {
273
+ this.name = name;
274
+ this.description = description;
275
+ }
276
+ get commands() {
277
+ return this._commands;
278
+ }
279
+ command(def) {
280
+ this._commands.set(def.name, def);
281
+ return this;
282
+ }
283
+ getCommand(name) {
284
+ return this._commands.get(name);
285
+ }
286
+ };
287
+
288
+ // src/output/table.ts
289
+ var Table = class {
290
+ columns = [];
291
+ rows = [];
292
+ title;
293
+ constructor(options) {
294
+ this.title = options?.title;
295
+ }
296
+ addColumn(name, options) {
297
+ this.columns.push({ name, ...options });
298
+ return this;
299
+ }
300
+ addRow(...values) {
301
+ this.rows.push(values);
302
+ return this;
303
+ }
304
+ render() {
305
+ const widths = this.columns.map((col, i) => {
306
+ const headerLen = stripAnsi(col.name).length;
307
+ const cellLens = this.rows.map(
308
+ (row) => stripAnsi(row[i] ?? "").length
309
+ );
310
+ const maxCell = cellLens.length > 0 ? Math.max(...cellLens) : 0;
311
+ const natural = Math.max(headerLen, maxCell);
312
+ return col.maxWidth !== void 0 ? Math.min(natural, col.maxWidth) : natural;
313
+ });
314
+ const lines = [];
315
+ if (this.title) {
316
+ lines.push(style(this.title, BOLD));
317
+ }
318
+ const header = this.columns.map((col, i) => pad(style(col.name, BOLD), widths[i], col.align)).join(" ");
319
+ lines.push(header);
320
+ const sep = widths.map((w) => "\u2500".repeat(w)).join(" ");
321
+ lines.push(style(sep, DIM));
322
+ for (const row of this.rows) {
323
+ const cells = this.columns.map((col, i) => {
324
+ const raw = row[i] ?? "";
325
+ const truncated = truncate(raw, widths[i]);
326
+ const styled = col.style ? style(truncated, col.style) : truncated;
327
+ return pad(styled, widths[i], col.align);
328
+ });
329
+ lines.push(cells.join(" "));
330
+ }
331
+ return lines.join("\n");
332
+ }
333
+ print() {
334
+ process.stdout.write(this.render() + "\n");
335
+ }
336
+ };
337
+ function truncate(text, maxWidth) {
338
+ const visible = stripAnsi(text);
339
+ if (visible.length <= maxWidth) return text;
340
+ return visible.slice(0, maxWidth - 1) + "\u2026";
341
+ }
342
+ function pad(text, width, align) {
343
+ const visible = stripAnsi(text).length;
344
+ const diff = width - visible;
345
+ if (diff <= 0) return text;
346
+ const spaces = " ".repeat(diff);
347
+ return align === "right" ? spaces + text : text + spaces;
348
+ }
349
+
350
+ // src/commands/workflow/resolver.ts
351
+ async function resolveWorkflow(partialId, opts) {
352
+ const params = {};
353
+ if (opts?.includeArchived) {
354
+ params["include_archived"] = "true";
355
+ }
356
+ const data = await apiGet("/workflows", { params });
357
+ const workflows = data.workflows ?? [];
358
+ const matching = workflows.filter((w) => w.id.startsWith(partialId));
359
+ if (matching.length === 0) {
360
+ printError(`No workflow found matching: ${partialId}`);
361
+ throw new CLIError("Workflow not found", 1);
362
+ }
363
+ if (matching.length > 1) {
364
+ print(style(`Multiple workflows match '${partialId}':`, YELLOW));
365
+ for (const w of matching.slice(0, 5)) {
366
+ print(` ${style(w.id.slice(0, 12) + "...", DIM)} - ${w.name}`);
367
+ }
368
+ printDim("Please provide a more specific ID");
369
+ throw new CLIError("Ambiguous workflow ID", 1);
370
+ }
371
+ const m = matching[0];
372
+ return {
373
+ id: m.id,
374
+ name: m.name,
375
+ workflow_type: m.workflow_type,
376
+ phase_count: m.phase_count ?? 0
377
+ };
378
+ }
379
+
380
+ // src/packages/resolver.ts
381
+ import fs3 from "fs";
382
+ import path3 from "path";
383
+
384
+ // src/persistence/store.ts
385
+ import fs from "fs";
386
+ import path from "path";
387
+ import os from "os";
388
+ var SYN_DIR = path.join(os.homedir(), ".syntropic137");
389
+ function synPath(...segments) {
390
+ return path.join(SYN_DIR, ...segments);
391
+ }
392
+ function readJsonFile(filePath, schema, fallback) {
393
+ if (!fs.existsSync(filePath)) return fallback;
394
+ try {
395
+ const content = fs.readFileSync(filePath, "utf-8");
396
+ return schema.parse(JSON.parse(content));
397
+ } catch {
398
+ return fallback;
399
+ }
400
+ }
401
+ function writeJsonFile(filePath, data) {
402
+ const dir = path.dirname(filePath);
403
+ fs.mkdirSync(dir, { recursive: true });
404
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
405
+ }
406
+
407
+ // src/packages/models.ts
408
+ import { z } from "zod";
409
+ var PluginManifestSchema = z.object({
410
+ manifest_version: z.number().default(1),
411
+ name: z.string().min(1),
412
+ version: z.string().default("0.1.0"),
413
+ description: z.string().nullish(),
414
+ author: z.string().nullish(),
415
+ license: z.string().nullish(),
416
+ repository: z.string().nullish()
417
+ }).passthrough();
418
+ var InstalledWorkflowRefSchema = z.object({
419
+ id: z.string(),
420
+ name: z.string()
421
+ }).strict();
422
+ var InstallationRecordSchema = z.object({
423
+ package_name: z.string(),
424
+ package_version: z.string(),
425
+ source: z.string(),
426
+ source_ref: z.string(),
427
+ installed_at: z.string(),
428
+ format: z.string(),
429
+ workflows: z.array(InstalledWorkflowRefSchema).default([]),
430
+ marketplace_source: z.string().nullish(),
431
+ git_sha: z.string().nullish()
432
+ }).strict();
433
+ var InstalledRegistrySchema = z.object({
434
+ version: z.number().default(1),
435
+ installations: z.array(InstallationRecordSchema).default([])
436
+ }).strict();
437
+
438
+ // src/packages/git.ts
439
+ import { execFile } from "child_process";
440
+ import fs2 from "fs";
441
+ import os2 from "os";
442
+ import path2 from "path";
443
+ function gitClone(url, ref, destDir) {
444
+ return new Promise((resolve, reject) => {
445
+ const child = execFile(
446
+ "git",
447
+ ["clone", "--depth=1", "--branch", ref, url, destDir],
448
+ { timeout: 12e4 },
449
+ (error, _stdout, stderr) => {
450
+ if (error) {
451
+ reject(new Error(`git clone failed: ${stderr.trim()}`));
452
+ } else {
453
+ resolve();
454
+ }
455
+ }
456
+ );
457
+ child.unref?.();
458
+ });
459
+ }
460
+ function gitLsRemote(repo, ref) {
461
+ return new Promise((resolve) => {
462
+ execFile(
463
+ "git",
464
+ ["ls-remote", `https://github.com/${repo}.git`, ref],
465
+ { timeout: 3e4 },
466
+ (error, stdout) => {
467
+ if (error) {
468
+ resolve(null);
469
+ return;
470
+ }
471
+ const line = stdout.trim().split("\n")[0] ?? "";
472
+ if (line.includes(" ")) {
473
+ resolve(line.split(" ")[0]);
474
+ } else {
475
+ resolve(null);
476
+ }
477
+ }
478
+ );
479
+ });
480
+ }
481
+ function makeTempDir(prefix) {
482
+ return fs2.mkdtempSync(path2.join(os2.tmpdir(), prefix));
483
+ }
484
+ function removeTempDir(dir) {
485
+ try {
486
+ fs2.rmSync(dir, { recursive: true, force: true });
487
+ } catch {
488
+ }
489
+ }
490
+
491
+ // src/packages/yaml.ts
492
+ function parseYaml(input) {
493
+ const lines = input.split("\n");
494
+ const { value } = parseNode(lines, 0, -1);
495
+ return value;
496
+ }
497
+ function skipBlanksAndComments(lines, start) {
498
+ let i = start;
499
+ while (i < lines.length) {
500
+ const trimmed = lines[i].trim();
501
+ if (trimmed !== "" && !trimmed.startsWith("#")) break;
502
+ i++;
503
+ }
504
+ return i;
505
+ }
506
+ function parseNode(lines, startLine, _parentIndent) {
507
+ const i = skipBlanksAndComments(lines, startLine);
508
+ if (i >= lines.length) {
509
+ return { value: null, nextLine: i };
510
+ }
511
+ const line = lines[i];
512
+ const indent = getIndent(line);
513
+ const trimmed = line.trim();
514
+ if (trimmed.startsWith("- ") || trimmed === "-") {
515
+ return parseList(lines, i, indent);
516
+ }
517
+ if (trimmed.includes(":")) {
518
+ return parseMap(lines, i, indent);
519
+ }
520
+ return { value: parseScalar(trimmed), nextLine: i + 1 };
521
+ }
522
+ function parseMapEntry(lines, i, afterColon, mapIndent) {
523
+ if (afterColon === "" || afterColon.startsWith("#")) {
524
+ return parseNode(lines, i + 1, mapIndent);
525
+ }
526
+ if (afterColon === "|" || afterColon === ">") {
527
+ return parseMultilineString(lines, i + 1, afterColon);
528
+ }
529
+ return { value: parseInlineValue(afterColon), nextLine: i + 1 };
530
+ }
531
+ function parseMap(lines, startLine, mapIndent) {
532
+ const result = {};
533
+ let i = startLine;
534
+ while (i < lines.length) {
535
+ const trimmed = lines[i].trim();
536
+ if (trimmed === "" || trimmed.startsWith("#")) {
537
+ i++;
538
+ continue;
539
+ }
540
+ const indent = getIndent(lines[i]);
541
+ if (indent !== mapIndent) break;
542
+ const colonIdx = findUnquotedColon(trimmed);
543
+ if (colonIdx === -1) break;
544
+ const key = trimmed.slice(0, colonIdx).trim();
545
+ const afterColon = trimmed.slice(colonIdx + 1).trim();
546
+ const { value, nextLine } = parseMapEntry(lines, i, afterColon, mapIndent);
547
+ result[key] = value;
548
+ i = nextLine;
549
+ }
550
+ return { value: result, nextLine: i };
551
+ }
552
+ function parseListItem(lines, i, trimmed, indent) {
553
+ const afterDash = trimmed.slice(2).trim();
554
+ if (afterDash === "" || trimmed === "-") {
555
+ const { value, nextLine } = parseNode(lines, i + 1, indent);
556
+ return { value, nextLine };
557
+ }
558
+ if (afterDash.includes(":") && !isQuoted(afterDash)) {
559
+ return parseInlineMapItem(lines, i, afterDash, indent);
560
+ }
561
+ return { value: parseInlineValue(afterDash), nextLine: i + 1 };
562
+ }
563
+ function parseInlineMapItem(lines, i, afterDash, indent) {
564
+ const itemIndent = indent + 2;
565
+ const originalLine = lines[i];
566
+ lines[i] = " ".repeat(itemIndent) + afterDash;
567
+ const { value, nextLine } = parseMap(lines, i, itemIndent);
568
+ lines[i] = originalLine;
569
+ return { value, nextLine };
570
+ }
571
+ function parseList(lines, startLine, listIndent) {
572
+ const result = [];
573
+ let i = startLine;
574
+ while (i < lines.length) {
575
+ const trimmed = lines[i].trim();
576
+ if (trimmed === "" || trimmed.startsWith("#")) {
577
+ i++;
578
+ continue;
579
+ }
580
+ const indent = getIndent(lines[i]);
581
+ if (indent !== listIndent) break;
582
+ if (!trimmed.startsWith("- ") && trimmed !== "-") break;
583
+ const { value, nextLine } = parseListItem(lines, i, trimmed, indent);
584
+ result.push(value);
585
+ i = nextLine;
586
+ }
587
+ return { value: result, nextLine: i };
588
+ }
589
+ function collectMultilineContent(lines, startLine) {
590
+ const contentLines = [];
591
+ let i = startLine;
592
+ let blockIndent = -1;
593
+ while (i < lines.length) {
594
+ const line = lines[i];
595
+ if (line.trim() === "") {
596
+ contentLines.push("");
597
+ i++;
598
+ continue;
599
+ }
600
+ const indent = getIndent(line);
601
+ if (blockIndent === -1) blockIndent = indent;
602
+ if (indent < blockIndent) break;
603
+ contentLines.push(line.slice(blockIndent));
604
+ i++;
605
+ }
606
+ while (contentLines.length > 0 && contentLines[contentLines.length - 1] === "") {
607
+ contentLines.pop();
608
+ }
609
+ return { contentLines, nextLine: i };
610
+ }
611
+ function parseMultilineString(lines, startLine, blockStyle) {
612
+ const { contentLines, nextLine } = collectMultilineContent(lines, startLine);
613
+ const value = blockStyle === "|" ? contentLines.join("\n") : contentLines.join(" ").replace(/\s+/g, " ").trim();
614
+ return { value, nextLine };
615
+ }
616
+ function parseInlineValue(raw) {
617
+ const value = stripInlineComment(raw);
618
+ if (value.startsWith("[") && value.endsWith("]")) {
619
+ const inner = value.slice(1, -1).trim();
620
+ if (inner === "") return [];
621
+ return splitFlow(inner).map((item) => parseScalar(item.trim()));
622
+ }
623
+ return parseScalar(value);
624
+ }
625
+ var TRUE_VALUES = /* @__PURE__ */ new Set(["true", "True", "TRUE"]);
626
+ var FALSE_VALUES = /* @__PURE__ */ new Set(["false", "False", "FALSE"]);
627
+ var NULL_VALUES = /* @__PURE__ */ new Set(["null", "~", ""]);
628
+ function parseQuoted(raw) {
629
+ if (raw.startsWith('"') && raw.endsWith('"') || raw.startsWith("'") && raw.endsWith("'")) {
630
+ return raw.slice(1, -1);
631
+ }
632
+ return null;
633
+ }
634
+ function parseNumber(raw) {
635
+ if (/^-?\d+$/.test(raw)) return parseInt(raw, 10);
636
+ if (/^-?\d+\.\d+$/.test(raw)) return parseFloat(raw);
637
+ return null;
638
+ }
639
+ function parseScalar(raw) {
640
+ if (NULL_VALUES.has(raw)) return null;
641
+ if (TRUE_VALUES.has(raw)) return true;
642
+ if (FALSE_VALUES.has(raw)) return false;
643
+ const quoted = parseQuoted(raw);
644
+ if (quoted !== null) return quoted;
645
+ const num = parseNumber(raw);
646
+ if (num !== null) return num;
647
+ return raw;
648
+ }
649
+ function getIndent(line) {
650
+ let count = 0;
651
+ for (const ch of line) {
652
+ if (ch === " ") count++;
653
+ else break;
654
+ }
655
+ return count;
656
+ }
657
+ var QUOTE_CHARS = /* @__PURE__ */ new Set(["'", '"']);
658
+ function toggleQuote(current, ch) {
659
+ if (current === "") return QUOTE_CHARS.has(ch) ? ch : "";
660
+ return ch === current ? "" : current;
661
+ }
662
+ function buildQuoteMask(text) {
663
+ const mask = new Array(text.length);
664
+ let quote = "";
665
+ for (let i = 0; i < text.length; i++) {
666
+ quote = toggleQuote(quote, text[i]);
667
+ mask[i] = quote !== "";
668
+ }
669
+ return mask;
670
+ }
671
+ function findUnquotedColon(text) {
672
+ const mask = buildQuoteMask(text);
673
+ for (let i = 0; i < text.length; i++) {
674
+ if (text[i] !== ":" || mask[i]) continue;
675
+ if (i + 1 >= text.length || text[i + 1] === " ") return i;
676
+ }
677
+ return -1;
678
+ }
679
+ function isQuoted(text) {
680
+ return text.startsWith('"') && text.endsWith('"') || text.startsWith("'") && text.endsWith("'");
681
+ }
682
+ function stripInlineComment(text) {
683
+ const mask = buildQuoteMask(text);
684
+ for (let i = 0; i < text.length; i++) {
685
+ if (text[i] !== " " || mask[i]) continue;
686
+ if (text[i + 1] === "#") return text.slice(0, i).trim();
687
+ }
688
+ return text;
689
+ }
690
+ var DEPTH_CHANGE = { "[": 1, "]": -1 };
691
+ function buildDepthMap(text, mask) {
692
+ const depths = new Int8Array(text.length);
693
+ let depth = 0;
694
+ for (let i = 0; i < text.length; i++) {
695
+ if (!mask[i]) depth += DEPTH_CHANGE[text[i]] ?? 0;
696
+ depths[i] = depth;
697
+ }
698
+ return depths;
699
+ }
700
+ function splitFlow(text) {
701
+ const mask = buildQuoteMask(text);
702
+ const depths = buildDepthMap(text, mask);
703
+ const items = [];
704
+ let start = 0;
705
+ for (let i = 0; i < text.length; i++) {
706
+ if (text[i] === "," && !mask[i] && depths[i] === 0) {
707
+ items.push(text.slice(start, i));
708
+ start = i + 1;
709
+ }
710
+ }
711
+ const last = text.slice(start);
712
+ if (last.trim()) items.push(last);
713
+ return items;
714
+ }
715
+
716
+ // src/packages/resolver.ts
717
+ var INSTALLED_PATH = synPath("workflows", "installed.json");
718
+ function loadInstalled() {
719
+ const fallback = { version: 1, installations: [] };
720
+ return readJsonFile(INSTALLED_PATH, InstalledRegistrySchema, fallback);
721
+ }
722
+ function saveInstalled(registry) {
723
+ writeJsonFile(INSTALLED_PATH, registry);
724
+ }
725
+ function recordInstallation(opts) {
726
+ const registry = loadInstalled();
727
+ const record = {
728
+ package_name: opts.packageName,
729
+ package_version: opts.packageVersion,
730
+ source: opts.source,
731
+ source_ref: opts.sourceRef,
732
+ installed_at: (/* @__PURE__ */ new Date()).toISOString(),
733
+ format: opts.format,
734
+ workflows: opts.workflows,
735
+ marketplace_source: opts.marketplaceSource ?? null,
736
+ git_sha: opts.gitSha ?? null
737
+ };
738
+ saveInstalled({
739
+ version: registry.version,
740
+ installations: [...registry.installations, record]
741
+ });
742
+ }
743
+ var REMOTE_PREFIXES = ["https://", "http://", "git@", "ssh://"];
744
+ function isRemoteUrl(source) {
745
+ return REMOTE_PREFIXES.some((prefix) => source.startsWith(prefix));
746
+ }
747
+ function isLocalPath(source) {
748
+ return fs3.existsSync(source) || source.startsWith(".") || source.startsWith("/");
749
+ }
750
+ function isGitHubShorthand(source) {
751
+ return source.includes("/") && !source.includes("@") && !source.startsWith(".");
752
+ }
753
+ function parseSource(source) {
754
+ if (isRemoteUrl(source)) {
755
+ return { resolved: source, isRemote: true };
756
+ }
757
+ if (isLocalPath(source)) {
758
+ return { resolved: source, isRemote: false };
759
+ }
760
+ if (isGitHubShorthand(source)) {
761
+ return { resolved: `https://github.com/${source}.git`, isRemote: true };
762
+ }
763
+ return { resolved: source, isRemote: false };
764
+ }
765
+ function detectFormat(pkgPath) {
766
+ if (!fs3.existsSync(pkgPath)) {
767
+ throw new Error(`Package path does not exist: ${pkgPath}`);
768
+ }
769
+ if (!fs3.statSync(pkgPath).isDirectory()) {
770
+ throw new Error(`Package path is not a directory: ${pkgPath}`);
771
+ }
772
+ if (hasMultiWorkflowLayout(pkgPath)) return "multi";
773
+ if (fs3.existsSync(path3.join(pkgPath, "workflow.yaml"))) return "single";
774
+ if (hasYamlFiles(pkgPath)) return "standalone";
775
+ throw new Error(
776
+ `No workflow files found in ${pkgPath}
777
+ Expected: workflow.yaml, workflows/*/workflow.yaml, or *.yaml files`
778
+ );
779
+ }
780
+ function hasMultiWorkflowLayout(pkgPath) {
781
+ const workflowsDir = path3.join(pkgPath, "workflows");
782
+ if (!fs3.existsSync(workflowsDir) || !fs3.statSync(workflowsDir).isDirectory()) {
783
+ return false;
784
+ }
785
+ return fs3.readdirSync(workflowsDir).some((d) => {
786
+ const subPath = path3.join(workflowsDir, d);
787
+ return fs3.statSync(subPath).isDirectory() && fs3.existsSync(path3.join(subPath, "workflow.yaml"));
788
+ });
789
+ }
790
+ function hasYamlFiles(pkgPath) {
791
+ return fs3.readdirSync(pkgPath).some(
792
+ (f) => f.endsWith(".yaml") || f.endsWith(".yml")
793
+ );
794
+ }
795
+ function loadManifest(pkgPath) {
796
+ const jsonPath = path3.join(pkgPath, "syntropic137-plugin.json");
797
+ if (fs3.existsSync(jsonPath)) {
798
+ return parseManifestFile(jsonPath, "json");
799
+ }
800
+ const yamlPath = path3.join(pkgPath, "syntropic137.yaml");
801
+ if (fs3.existsSync(yamlPath)) {
802
+ return parseManifestFile(yamlPath, "yaml");
803
+ }
804
+ return null;
805
+ }
806
+ function parseManifestFile(filePath, format) {
807
+ const content = fs3.readFileSync(filePath, "utf-8");
808
+ const data = format === "json" ? JSON.parse(content) : parseYaml(content);
809
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
810
+ throw new Error(`${path3.basename(filePath)} must be a ${format === "json" ? "JSON object" : "YAML mapping"}`);
811
+ }
812
+ return PluginManifestSchema.parse(data);
813
+ }
814
+ function loadWorkflowYaml(workflowDir, sourcePath) {
815
+ const yamlPath = path3.join(workflowDir, "workflow.yaml");
816
+ if (!fs3.existsSync(yamlPath)) {
817
+ throw new Error(`workflow.yaml not found in ${workflowDir}`);
818
+ }
819
+ const content = fs3.readFileSync(yamlPath, "utf-8");
820
+ const data = parseYaml(content);
821
+ const phases = Array.isArray(data["phases"]) ? data["phases"] : [];
822
+ const resolvedPhases = phases.map(
823
+ (phase) => resolvePhase(phase, workflowDir)
824
+ );
825
+ const repository = data["repository"];
826
+ return {
827
+ id: String(data["id"] ?? ""),
828
+ name: String(data["name"] ?? ""),
829
+ workflow_type: String(data["type"] ?? data["workflow_type"] ?? "custom"),
830
+ classification: String(data["classification"] ?? "standard"),
831
+ repository_url: repository ? String(repository["url"] ?? "https://github.com/placeholder/not-configured") : "https://github.com/placeholder/not-configured",
832
+ repository_ref: repository ? String(repository["ref"] ?? "main") : "main",
833
+ description: data["description"] ? String(data["description"]) : null,
834
+ project_name: data["project_name"] ? String(data["project_name"]) : null,
835
+ phases: resolvedPhases,
836
+ input_declarations: parseInputDeclarations(data),
837
+ source_path: sourcePath
838
+ };
839
+ }
840
+ function resolvePhase(phase, workflowDir) {
841
+ if (typeof phase["prompt_file"] !== "string" || phase["prompt_template"]) {
842
+ return phase;
843
+ }
844
+ const promptPath = path3.join(workflowDir, phase["prompt_file"]);
845
+ if (!fs3.existsSync(promptPath)) return phase;
846
+ const promptContent = fs3.readFileSync(promptPath, "utf-8");
847
+ const { frontmatter, body } = parseFrontmatter(promptContent);
848
+ const resolved = { ...phase, prompt_template: body };
849
+ delete resolved["prompt_file"];
850
+ if (frontmatter) {
851
+ mergeFrontmatter(resolved, frontmatter);
852
+ }
853
+ return resolved;
854
+ }
855
+ function mergeFrontmatter(phase, fm) {
856
+ const mappings = [
857
+ ["argument-hint", "argument_hint"],
858
+ ["allowed-tools", "allowed_tools", (v) => String(v).split(",").map((t) => t.trim())],
859
+ ["max-tokens", "max_tokens", Number],
860
+ ["timeout-seconds", "timeout_seconds", Number],
861
+ ["model", "model"]
862
+ ];
863
+ for (const [fmKey, phaseKey, transform] of mappings) {
864
+ if (fm[fmKey] && !phase[phaseKey]) {
865
+ phase[phaseKey] = transform ? transform(fm[fmKey]) : fm[fmKey];
866
+ }
867
+ }
868
+ }
869
+ function parseInputDeclarations(data) {
870
+ const inputs = Array.isArray(data["inputs"]) ? data["inputs"] : [];
871
+ return inputs.map((i) => {
872
+ const inp = i;
873
+ return {
874
+ name: inp["name"] ?? "",
875
+ description: inp["description"] ?? "",
876
+ required: inp["required"] ?? true,
877
+ default: inp["default"] ?? null
878
+ };
879
+ });
880
+ }
881
+ function parseFrontmatter(content) {
882
+ if (!content.startsWith("---")) {
883
+ return { frontmatter: null, body: content };
884
+ }
885
+ const endIdx = content.indexOf("---", 3);
886
+ if (endIdx === -1) {
887
+ return { frontmatter: null, body: content };
888
+ }
889
+ const fmContent = content.slice(3, endIdx).trim();
890
+ const body = content.slice(endIdx + 3).trim();
891
+ const fm = parseYaml(fmContent);
892
+ return {
893
+ frontmatter: typeof fm === "object" && fm !== null && !Array.isArray(fm) ? fm : null,
894
+ body
895
+ };
896
+ }
897
+ function resolveMultiWorkflow(pkgPath, source) {
898
+ const workflowsDir = path3.join(pkgPath, "workflows");
899
+ const subdirs = fs3.readdirSync(workflowsDir).sort().filter((d) => {
900
+ const subPath = path3.join(workflowsDir, d);
901
+ return fs3.statSync(subPath).isDirectory() && fs3.existsSync(path3.join(subPath, "workflow.yaml"));
902
+ });
903
+ return subdirs.map(
904
+ (d) => loadWorkflowYaml(path3.join(workflowsDir, d), source)
905
+ );
906
+ }
907
+ function resolveStandaloneYaml(pkgPath, source) {
908
+ const files = fs3.readdirSync(pkgPath).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml")).sort();
909
+ return files.map((f) => {
910
+ const filePath = path3.join(pkgPath, f);
911
+ const content = fs3.readFileSync(filePath, "utf-8");
912
+ const data = parseYaml(content);
913
+ const baseName = path3.basename(f, path3.extname(f));
914
+ return {
915
+ id: String(data["id"] ?? baseName),
916
+ name: String(data["name"] ?? baseName),
917
+ workflow_type: String(data["type"] ?? data["workflow_type"] ?? "custom"),
918
+ classification: String(data["classification"] ?? "standard"),
919
+ repository_url: "https://github.com/placeholder/not-configured",
920
+ repository_ref: "main",
921
+ description: data["description"] ? String(data["description"]) : null,
922
+ project_name: data["project_name"] ? String(data["project_name"]) : null,
923
+ phases: Array.isArray(data["phases"]) ? data["phases"] : [],
924
+ input_declarations: [],
925
+ source_path: source
926
+ };
927
+ });
928
+ }
929
+ function resolvePackage(pkgPath) {
930
+ const format = detectFormat(pkgPath);
931
+ const manifest = loadManifest(pkgPath);
932
+ const source = pkgPath;
933
+ if (format === "single") {
934
+ const workflow = loadWorkflowYaml(pkgPath, source);
935
+ return { manifest, workflows: [workflow] };
936
+ }
937
+ if (format === "multi") {
938
+ return { manifest, workflows: resolveMultiWorkflow(pkgPath, source) };
939
+ }
940
+ return { manifest, workflows: resolveStandaloneYaml(pkgPath, source) };
941
+ }
942
+ async function resolveFromGit(url, ref) {
943
+ const tmpdir = makeTempDir("syn-pkg-");
944
+ try {
945
+ await gitClone(url, ref, tmpdir);
946
+ } catch (err) {
947
+ removeTempDir(tmpdir);
948
+ throw err;
949
+ }
950
+ const { manifest, workflows } = resolvePackage(tmpdir);
951
+ return { tmpdir, manifest, workflows };
952
+ }
953
+ var PHASE_MD_TEMPLATE = (phaseNum, phaseName) => `---
954
+ model: sonnet
955
+ argument-hint: "[topic]"
956
+ allowed-tools: Read,Glob,Grep,Bash
957
+ max-tokens: 4096
958
+ timeout-seconds: 300
959
+ ---
960
+
961
+ You are an AI assistant working on phase ${phaseNum}: ${phaseName}.
962
+
963
+ Your task: $ARGUMENTS
964
+
965
+ Work thoroughly and report your findings.
966
+ `;
967
+ function generatePhaseNames(workflowType, count) {
968
+ const presets = {
969
+ research: ["Discovery", "Deep Dive", "Synthesis"],
970
+ implementation: ["Research", "Plan", "Execute", "Review", "Ship"],
971
+ review: ["Analyze", "Evaluate", "Report"],
972
+ planning: ["Gather Context", "Design", "Validate"],
973
+ deployment: ["Prepare", "Deploy", "Verify"]
974
+ };
975
+ const names = [...presets[workflowType] ?? []];
976
+ while (names.length < count) {
977
+ names.push(`Phase ${names.length + 1}`);
978
+ }
979
+ return names.slice(0, count);
980
+ }
981
+ function scaffoldSinglePackage(directory, opts) {
982
+ const workflowType = opts.workflowType ?? "research";
983
+ const numPhases = opts.numPhases ?? 3;
984
+ const phasesDir = path3.join(directory, "phases");
985
+ fs3.mkdirSync(phasesDir, { recursive: true });
986
+ const workflowId = opts.name.toLowerCase().replace(/ /g, "-") + "-v1";
987
+ const phaseNames = generatePhaseNames(workflowType, numPhases);
988
+ const phasesYamlLines = [];
989
+ for (let i = 0; i < phaseNames.length; i++) {
990
+ const phaseName = phaseNames[i];
991
+ const phaseId = phaseName.toLowerCase().replace(/ /g, "-");
992
+ fs3.writeFileSync(
993
+ path3.join(phasesDir, `${phaseId}.md`),
994
+ PHASE_MD_TEMPLATE(i + 1, phaseName),
995
+ "utf-8"
996
+ );
997
+ phasesYamlLines.push(
998
+ ` - id: ${phaseId}
999
+ name: ${phaseName}
1000
+ order: ${i + 1}
1001
+ execution_type: sequential
1002
+ prompt_file: phases/${phaseId}.md
1003
+ output_artifacts: [${phaseId}_output]`
1004
+ );
1005
+ }
1006
+ const workflowYaml = `id: ${workflowId}
1007
+ name: ${opts.name}
1008
+ description: "${opts.name} workflow"
1009
+ type: ${workflowType}
1010
+ classification: standard
1011
+
1012
+ inputs:
1013
+ - name: task
1014
+ description: "The primary task to accomplish"
1015
+ required: true
1016
+
1017
+ phases:
1018
+ ` + phasesYamlLines.join("\n");
1019
+ fs3.writeFileSync(path3.join(directory, "workflow.yaml"), workflowYaml, "utf-8");
1020
+ const phaseList = phaseNames.map((pn, i) => `- **Phase ${i + 1}:** ${pn}`).join("\n");
1021
+ const readme = `# ${opts.name}
1022
+
1023
+ ${opts.name} workflow
1024
+
1025
+ ## Usage
1026
+
1027
+ \`\`\`bash
1028
+ syn workflow install ./${path3.basename(directory)}/
1029
+ syn workflow run ${workflowId} --task "Your task here"
1030
+ \`\`\`
1031
+
1032
+ ## Phases
1033
+
1034
+ ${phaseList}
1035
+ `;
1036
+ fs3.writeFileSync(path3.join(directory, "README.md"), readme, "utf-8");
1037
+ }
1038
+ function scaffoldMultiPackage(directory, opts) {
1039
+ fs3.mkdirSync(directory, { recursive: true });
1040
+ const manifest = {
1041
+ manifest_version: 1,
1042
+ name: opts.name.toLowerCase().replace(/ /g, "-"),
1043
+ version: "0.1.0",
1044
+ description: `${opts.name} plugin`
1045
+ };
1046
+ fs3.writeFileSync(
1047
+ path3.join(directory, "syntropic137-plugin.json"),
1048
+ JSON.stringify(manifest, null, 2) + "\n",
1049
+ "utf-8"
1050
+ );
1051
+ const libDir = path3.join(directory, "phase-library");
1052
+ fs3.mkdirSync(libDir, { recursive: true });
1053
+ fs3.writeFileSync(
1054
+ path3.join(libDir, "summarize.md"),
1055
+ PHASE_MD_TEMPLATE("N", "Summarize"),
1056
+ "utf-8"
1057
+ );
1058
+ const wfName = opts.name.toLowerCase().replace(/ /g, "-");
1059
+ const wfDir = path3.join(directory, "workflows", wfName);
1060
+ scaffoldSinglePackage(wfDir, opts);
1061
+ const readme = `# ${opts.name} Plugin
1062
+
1063
+ Plugin containing ${opts.name} workflows and shared phases
1064
+
1065
+ ## Usage
1066
+
1067
+ \`\`\`bash
1068
+ syn workflow install ./${path3.basename(directory)}/
1069
+ syn workflow run ${wfName}-v1 --task "Your task here"
1070
+ \`\`\`
1071
+
1072
+ ## Phases
1073
+
1074
+ - **${opts.name}** \u2014 ${opts.numPhases ?? 3} phases
1075
+ `;
1076
+ fs3.writeFileSync(path3.join(directory, "README.md"), readme, "utf-8");
1077
+ }
1078
+
1079
+ // src/commands/workflow/crud.ts
1080
+ var createCommand = {
1081
+ name: "create",
1082
+ description: "Create a new workflow",
1083
+ args: [{ name: "name", description: "Name of the workflow", required: true }],
1084
+ options: {
1085
+ type: { type: "string", short: "t", description: "Workflow type (research, planning, implementation, review, deployment, custom)", default: "custom" },
1086
+ repo: { type: "string", short: "r", description: "Repository URL", default: "https://github.com/example/repo" },
1087
+ ref: { type: "string", description: "Repository ref/branch", default: "main" },
1088
+ description: { type: "string", short: "d", description: "Workflow description" }
1089
+ },
1090
+ handler: async (parsed) => {
1091
+ const name = parsed.positionals[0];
1092
+ if (!name) {
1093
+ printError("Missing required argument: name");
1094
+ throw new CLIError("Missing argument", 1);
1095
+ }
1096
+ const workflowType = parsed.values["type"] ?? "custom";
1097
+ const repoUrl = parsed.values["repo"] ?? "https://github.com/example/repo";
1098
+ const repoRef = parsed.values["ref"] ?? "main";
1099
+ const description = parsed.values["description"];
1100
+ const data = await apiPost("/workflows", {
1101
+ body: {
1102
+ name,
1103
+ workflow_type: workflowType,
1104
+ repository_url: repoUrl,
1105
+ repository_ref: repoRef,
1106
+ description: description ?? null
1107
+ }
1108
+ });
1109
+ const workflowId = String(data["id"] ?? data["workflow_id"] ?? "unknown");
1110
+ printSuccess(`Created workflow: ${style(name, CYAN)}`);
1111
+ print(` ID: ${style(workflowId, DIM)}`);
1112
+ print(` Type: ${style(workflowType, DIM)}`);
1113
+ }
1114
+ };
1115
+ var listCommand = {
1116
+ name: "list",
1117
+ description: "List all workflows",
1118
+ options: {
1119
+ "include-archived": { type: "boolean", description: "Include archived workflows", default: false }
1120
+ },
1121
+ handler: async (parsed) => {
1122
+ const includeArchived = parsed.values["include-archived"] === true;
1123
+ const params = {};
1124
+ if (includeArchived) params["include_archived"] = "true";
1125
+ const data = await apiGet("/workflows", { params });
1126
+ const workflows = data.workflows ?? [];
1127
+ if (workflows.length === 0) {
1128
+ printDim("No workflows found. Create one with:");
1129
+ print(` ${style('syn workflow create "My Workflow"', CYAN)}`);
1130
+ return;
1131
+ }
1132
+ const table = new Table({ title: "Workflows" });
1133
+ table.addColumn("ID", { style: DIM });
1134
+ table.addColumn("Name", { style: CYAN });
1135
+ table.addColumn("Type", { style: GREEN });
1136
+ table.addColumn("Phases", { align: "right" });
1137
+ for (const w of workflows) {
1138
+ table.addRow(
1139
+ String(w["id"] ?? "").slice(0, 12) + "...",
1140
+ String(w["name"] ?? ""),
1141
+ String(w["workflow_type"] ?? ""),
1142
+ String(w["phase_count"] ?? 0)
1143
+ );
1144
+ }
1145
+ table.print();
1146
+ }
1147
+ };
1148
+ function renderWorkflowDetail(detail) {
1149
+ print("");
1150
+ print(style("Workflow Details", BOLD));
1151
+ print(` ${style("ID:", DIM)} ${detail.id}`);
1152
+ print(` ${style("Name:", DIM)} ${style(detail.name, CYAN)}`);
1153
+ print(` ${style("Type:", DIM)} ${detail.workflow_type}`);
1154
+ print(` ${style("Classification:", DIM)} ${detail.classification}`);
1155
+ if (detail.phases.length > 0) {
1156
+ print(`
1157
+ ${style(`Phases (${detail.phases.length}):`, BOLD)}`);
1158
+ for (const phase of detail.phases) {
1159
+ print(` - ${String(phase["name"] ?? "unnamed")}`);
1160
+ }
1161
+ } else {
1162
+ printDim(" No phases defined");
1163
+ }
1164
+ }
1165
+ var showCommand = {
1166
+ name: "show",
1167
+ description: "Show details of a workflow",
1168
+ args: [{ name: "workflow-id", description: "Workflow ID (partial match supported)", required: true }],
1169
+ handler: async (parsed) => {
1170
+ const partialId = parsed.positionals[0];
1171
+ if (!partialId) {
1172
+ printError("Missing required argument: workflow-id");
1173
+ throw new CLIError("Missing argument", 1);
1174
+ }
1175
+ const wf = await resolveWorkflow(partialId);
1176
+ const data = await apiGet(`/workflows/${wf.id}`);
1177
+ renderWorkflowDetail(data);
1178
+ }
1179
+ };
1180
+ var validateCommand = {
1181
+ name: "validate",
1182
+ description: "Validate a workflow YAML file or package directory",
1183
+ args: [{ name: "file", description: "YAML file or package directory", required: true }],
1184
+ handler: async (parsed) => {
1185
+ const file = parsed.positionals[0];
1186
+ if (!file) {
1187
+ printError("Missing required argument: file");
1188
+ throw new CLIError("Missing argument", 1);
1189
+ }
1190
+ const fs8 = await import("fs");
1191
+ const stat = fs8.statSync(file);
1192
+ if (stat.isDirectory()) {
1193
+ validatePackageDir(file);
1194
+ return;
1195
+ }
1196
+ const data = await apiPost("/workflows/validate", {
1197
+ body: { file }
1198
+ });
1199
+ if (data["valid"]) {
1200
+ printSuccess("Valid workflow definition\n");
1201
+ print(` ${style("Name:", DIM)} ${String(data["name"] ?? "")}`);
1202
+ print(` ${style("Type:", DIM)} ${String(data["workflow_type"] ?? "")}`);
1203
+ print(` ${style("Phases:", DIM)} ${String(data["phase_count"] ?? 0)}`);
1204
+ } else {
1205
+ printError("Invalid workflow definition");
1206
+ const errors = data["errors"];
1207
+ if (errors) {
1208
+ for (const error of errors) {
1209
+ print(` ${error}`);
1210
+ }
1211
+ }
1212
+ throw new CLIError("Validation failed", 1);
1213
+ }
1214
+ }
1215
+ };
1216
+ function validatePackageDir(pkgPath) {
1217
+ try {
1218
+ const fmt = detectFormat(pkgPath);
1219
+ const { workflows } = resolvePackage(pkgPath);
1220
+ printSuccess(`Valid ${fmt} package
1221
+ `);
1222
+ print(` ${style("Directory:", DIM)} ${pkgPath}`);
1223
+ print(` ${style("Workflows:", DIM)} ${workflows.length}`);
1224
+ const totalPhases = workflows.reduce((sum, wf) => sum + wf.phases.length, 0);
1225
+ print(` ${style("Total phases:", DIM)} ${totalPhases}`);
1226
+ for (const wf of workflows) {
1227
+ printDim(` \u2022 ${wf.name} (${wf.phases.length} phases)`);
1228
+ }
1229
+ } catch (err) {
1230
+ printError(err instanceof Error ? err.message : String(err));
1231
+ throw new CLIError("Validation failed", 1);
1232
+ }
1233
+ }
1234
+ var deleteCommand = {
1235
+ name: "delete",
1236
+ description: "Archive (soft-delete) a workflow",
1237
+ args: [{ name: "workflow-id", description: "Workflow ID (partial match supported)", required: true }],
1238
+ options: {
1239
+ force: { type: "boolean", short: "f", description: "Skip confirmation prompt", default: false }
1240
+ },
1241
+ handler: async (parsed) => {
1242
+ const partialId = parsed.positionals[0];
1243
+ if (!partialId) {
1244
+ printError("Missing required argument: workflow-id");
1245
+ throw new CLIError("Missing argument", 1);
1246
+ }
1247
+ const force = parsed.values["force"] === true;
1248
+ const wf = await resolveWorkflow(partialId, { includeArchived: true });
1249
+ if (!force) {
1250
+ printError(`Use --force to confirm archiving '${wf.name}' (${wf.id})`);
1251
+ throw new CLIError("Confirmation required", 1);
1252
+ }
1253
+ await apiDelete(`/workflows/${wf.id}`);
1254
+ printSuccess(`Archived workflow: ${style(wf.name, CYAN)}`);
1255
+ print(` ID: ${style(wf.id, DIM)}`);
1256
+ }
1257
+ };
1258
+
1259
+ // src/output/format.ts
1260
+ function formatCost(cost) {
1261
+ const n = typeof cost === "string" ? Number(cost) : cost;
1262
+ if (n < 0.01) return `$${n.toFixed(4)}`;
1263
+ return `$${n.toFixed(2)}`;
1264
+ }
1265
+ function formatTokens(tokens) {
1266
+ if (tokens >= 1e6) return `${(tokens / 1e6).toFixed(1)}M`;
1267
+ if (tokens >= 1e3) return `${(tokens / 1e3).toFixed(1)}K`;
1268
+ return String(tokens);
1269
+ }
1270
+ function formatDuration(ms) {
1271
+ if (ms < 1e3) return `${Math.round(ms)}ms`;
1272
+ const seconds = ms / 1e3;
1273
+ if (seconds < 60) return `${seconds.toFixed(1)}s`;
1274
+ const minutes = Math.floor(seconds / 60);
1275
+ const secs = Math.floor(seconds % 60);
1276
+ if (minutes < 60) return secs ? `${minutes}m ${secs}s` : `${minutes}m`;
1277
+ const hours = Math.floor(minutes / 60);
1278
+ const mins = minutes % 60;
1279
+ return mins ? `${hours}h ${mins}m` : `${hours}h`;
1280
+ }
1281
+ function formatTimestamp(iso) {
1282
+ if (!iso) return "-";
1283
+ try {
1284
+ const date = new Date(iso);
1285
+ if (isNaN(date.getTime())) return iso;
1286
+ return date.toLocaleDateString("en-US", {
1287
+ month: "short",
1288
+ day: "2-digit",
1289
+ hour: "2-digit",
1290
+ minute: "2-digit",
1291
+ hour12: false
1292
+ });
1293
+ } catch {
1294
+ return iso;
1295
+ }
1296
+ }
1297
+ var STATUS_COLORS = {
1298
+ active: GREEN,
1299
+ completed: GREEN,
1300
+ paused: YELLOW,
1301
+ deleted: RED,
1302
+ failed: RED,
1303
+ running: BLUE,
1304
+ pending: DIM
1305
+ };
1306
+ function statusStyle(status) {
1307
+ return STATUS_COLORS[status] ?? "";
1308
+ }
1309
+ function formatStatus(status) {
1310
+ const color = statusStyle(status);
1311
+ return color ? style(status, color) : status;
1312
+ }
1313
+ function formatBreakdown(breakdown, title, valueFn) {
1314
+ const lines = [style(title, BOLD)];
1315
+ for (const [key, value] of Object.entries(breakdown)) {
1316
+ const formatted = valueFn ? valueFn(value) : value;
1317
+ lines.push(` ${key}: ${formatted}`);
1318
+ }
1319
+ return lines.join("\n");
1320
+ }
1321
+
1322
+ // src/commands/workflow/models.ts
1323
+ function isQuoted2(value) {
1324
+ return value.length >= 2 && value[0] === value[value.length - 1] && (value[0] === '"' || value[0] === "'");
1325
+ }
1326
+ function coerceValue(value) {
1327
+ if (/^-?\d+$/.test(value)) return parseInt(value, 10);
1328
+ if (/^-?\d+\.\d+$/.test(value)) return parseFloat(value);
1329
+ return value;
1330
+ }
1331
+ function parseSingleValue(value) {
1332
+ if (isQuoted2(value)) return value.slice(1, -1);
1333
+ const lower = value.toLowerCase();
1334
+ if (lower === "true") return true;
1335
+ if (lower === "false") return false;
1336
+ return coerceValue(value);
1337
+ }
1338
+ function parseInputs(inputs) {
1339
+ if (!inputs || inputs.length === 0) return {};
1340
+ const result = {};
1341
+ for (const item of inputs) {
1342
+ const eqIdx = item.indexOf("=");
1343
+ if (eqIdx === -1) {
1344
+ process.stderr.write(`Warning: Ignoring invalid input '${item}' (expected key=value)
1345
+ `);
1346
+ continue;
1347
+ }
1348
+ const key = item.slice(0, eqIdx).trim();
1349
+ const value = item.slice(eqIdx + 1);
1350
+ result[key] = parseSingleValue(value);
1351
+ }
1352
+ return result;
1353
+ }
1354
+
1355
+ // src/commands/workflow/run.ts
1356
+ function displayRunPreview(workflowName, fullId, phaseCount, task, parsedInputs) {
1357
+ print("");
1358
+ print(style("Workflow Execution", CYAN));
1359
+ print(` ${style(workflowName, BOLD)}`);
1360
+ print(` ${style(`ID: ${fullId}`, DIM)}`);
1361
+ print(` ${style(`Phases: ${phaseCount}`, DIM)}`);
1362
+ if (task) {
1363
+ print(`
1364
+ ${style("Task:", BOLD)} ${style(task, GREEN)}`);
1365
+ }
1366
+ const inputEntries = Object.entries(parsedInputs);
1367
+ if (inputEntries.length > 0) {
1368
+ print(`
1369
+ ${style("Inputs:", BOLD)}`);
1370
+ for (const [key, value] of inputEntries) {
1371
+ print(` ${key}: ${style(String(value), GREEN)}`);
1372
+ }
1373
+ }
1374
+ }
1375
+ var runCommand = {
1376
+ name: "run",
1377
+ description: "Execute a workflow",
1378
+ args: [{ name: "workflow-id", description: "Workflow ID (partial match supported)", required: true }],
1379
+ options: {
1380
+ input: { type: "string", short: "i", description: "Input variables as key=value", multiple: true },
1381
+ task: { type: "string", short: "t", description: "Primary task description ($ARGUMENTS)" },
1382
+ "dry-run": { type: "boolean", short: "n", description: "Validate without executing", default: false },
1383
+ quiet: { type: "boolean", short: "q", description: "Minimal output", default: false }
1384
+ },
1385
+ handler: async (parsed) => {
1386
+ const partialId = parsed.positionals[0];
1387
+ if (!partialId) {
1388
+ printError("Missing required argument: workflow-id");
1389
+ throw new CLIError("Missing argument", 1);
1390
+ }
1391
+ const inputValues = parsed.values["input"];
1392
+ const inputs = Array.isArray(inputValues) ? inputValues : void 0;
1393
+ const parsedInputs = parseInputs(inputs);
1394
+ const task = parsed.values["task"];
1395
+ const dryRun = parsed.values["dry-run"] === true;
1396
+ const quiet = parsed.values["quiet"] === true;
1397
+ const wf = await resolveWorkflow(partialId);
1398
+ if (!quiet) {
1399
+ displayRunPreview(wf.name, wf.id, wf.phase_count, task, parsedInputs);
1400
+ }
1401
+ if (dryRun) {
1402
+ print(`
1403
+ ${style("DRY RUN", YELLOW)} - Workflow is valid and ready to execute`);
1404
+ printDim("Remove --dry-run to execute");
1405
+ return;
1406
+ }
1407
+ const body = {
1408
+ inputs: Object.fromEntries(
1409
+ Object.entries(parsedInputs).map(([k, v]) => [k, String(v)])
1410
+ )
1411
+ };
1412
+ if (task) body["task"] = task;
1413
+ const result = await apiPost(
1414
+ `/workflows/${wf.id}/execute`,
1415
+ { body, timeoutMs: 3e5 }
1416
+ );
1417
+ if (result.status === "started") {
1418
+ printSuccess("\nWorkflow execution started");
1419
+ print(` Execution ID: ${result.execution_id}`);
1420
+ } else {
1421
+ print(`
1422
+ ${style(`Status: ${result.status}`, YELLOW)}`);
1423
+ }
1424
+ }
1425
+ };
1426
+ var statusCommand = {
1427
+ name: "status",
1428
+ description: "Show execution history for a workflow",
1429
+ args: [{ name: "workflow-id", description: "Workflow ID (partial match supported)", required: true }],
1430
+ handler: async (parsed) => {
1431
+ const partialId = parsed.positionals[0];
1432
+ if (!partialId) {
1433
+ printError("Missing required argument: workflow-id");
1434
+ throw new CLIError("Missing argument", 1);
1435
+ }
1436
+ const wf = await resolveWorkflow(partialId);
1437
+ print("");
1438
+ print(style("Workflow Status", CYAN));
1439
+ print(` ${style(wf.name, BOLD)}`);
1440
+ print(` ${style(`ID: ${wf.id}`, DIM)}`);
1441
+ const data = await apiGet(`/workflows/${wf.id}/runs`);
1442
+ const runs = data.runs ?? [];
1443
+ if (runs.length === 0) {
1444
+ printDim("\nNo executions found.");
1445
+ printDim(`Run with: syn workflow run ${partialId}`);
1446
+ return;
1447
+ }
1448
+ const table = new Table({ title: "Executions" });
1449
+ table.addColumn("ID", { style: DIM });
1450
+ table.addColumn("Status");
1451
+ table.addColumn("Phases", { align: "right" });
1452
+ table.addColumn("Tokens", { align: "right" });
1453
+ table.addColumn("Cost", { align: "right" });
1454
+ for (const run of runs) {
1455
+ table.addRow(
1456
+ String(run.workflow_execution_id ?? "").slice(0, 12) + "...",
1457
+ String(run.status ?? ""),
1458
+ `${run.completed_phases ?? 0}/${run.total_phases ?? 0}`,
1459
+ formatTokens(Number(run.total_tokens ?? 0)),
1460
+ formatCost(String(run.total_cost_usd ?? "0"))
1461
+ );
1462
+ }
1463
+ table.print();
1464
+ }
1465
+ };
1466
+
1467
+ // src/commands/workflow/install.ts
1468
+ import fs5 from "fs";
1469
+ import path5 from "path";
1470
+
1471
+ // src/marketplace/client.ts
1472
+ import fs4 from "fs";
1473
+ import path4 from "path";
1474
+
1475
+ // src/marketplace/models.ts
1476
+ import { z as z2 } from "zod";
1477
+ var SyntropicMarkerSchema = z2.object({
1478
+ type: z2.string(),
1479
+ min_platform_version: z2.string().default("0.0.0")
1480
+ }).passthrough();
1481
+ var MarketplacePluginEntrySchema = z2.object({
1482
+ name: z2.string().min(1),
1483
+ source: z2.string(),
1484
+ version: z2.string().default("0.1.0"),
1485
+ description: z2.string().default(""),
1486
+ category: z2.string().default(""),
1487
+ tags: z2.array(z2.string()).default([])
1488
+ }).passthrough();
1489
+ var MarketplaceIndexSchema = z2.object({
1490
+ name: z2.string().min(1),
1491
+ syntropic137: SyntropicMarkerSchema,
1492
+ plugins: z2.array(MarketplacePluginEntrySchema).default([])
1493
+ }).passthrough();
1494
+ var RegistryEntrySchema = z2.object({
1495
+ repo: z2.string(),
1496
+ ref: z2.string().default("main"),
1497
+ added_at: z2.string()
1498
+ }).strict();
1499
+ var RegistryConfigSchema = z2.object({
1500
+ version: z2.number().default(1),
1501
+ registries: z2.record(z2.string(), RegistryEntrySchema).default({})
1502
+ }).strict();
1503
+ var CachedMarketplaceSchema = z2.object({
1504
+ fetched_at: z2.string(),
1505
+ index: MarketplaceIndexSchema
1506
+ }).strict();
1507
+
1508
+ // src/marketplace/client.ts
1509
+ var REGISTRIES_PATH = synPath("registries.json");
1510
+ var CACHE_DIR = synPath("marketplace", "cache");
1511
+ var CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
1512
+ var SAFE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
1513
+ function validateRegistryName(name) {
1514
+ if (!SAFE_NAME_RE.test(name) || name.includes("..")) {
1515
+ throw new Error(
1516
+ `Invalid registry name '${name}': must start with alphanumeric character and contain only letters, digits, hyphens, underscores, and dots`
1517
+ );
1518
+ }
1519
+ return name;
1520
+ }
1521
+ function loadRegistries() {
1522
+ const fallback = { version: 1, registries: {} };
1523
+ return readJsonFile(REGISTRIES_PATH, RegistryConfigSchema, fallback);
1524
+ }
1525
+ function saveRegistries(config) {
1526
+ writeJsonFile(REGISTRIES_PATH, config);
1527
+ }
1528
+ function loadCachedIndex(registryName) {
1529
+ validateRegistryName(registryName);
1530
+ const cachePath = path4.join(CACHE_DIR, `${registryName}.json`);
1531
+ if (!fs4.existsSync(cachePath)) return null;
1532
+ try {
1533
+ const content = fs4.readFileSync(cachePath, "utf-8");
1534
+ return CachedMarketplaceSchema.parse(JSON.parse(content));
1535
+ } catch {
1536
+ return null;
1537
+ }
1538
+ }
1539
+ function saveCachedIndex(registryName, cached) {
1540
+ validateRegistryName(registryName);
1541
+ fs4.mkdirSync(CACHE_DIR, { recursive: true });
1542
+ const cachePath = path4.join(CACHE_DIR, `${registryName}.json`);
1543
+ writeJsonFile(cachePath, cached);
1544
+ }
1545
+ function isCacheStale(cached) {
1546
+ try {
1547
+ const fetched = new Date(cached.fetched_at).getTime();
1548
+ if (isNaN(fetched)) return true;
1549
+ return Date.now() - fetched > CACHE_TTL_MS;
1550
+ } catch {
1551
+ return true;
1552
+ }
1553
+ }
1554
+ async function fetchMarketplaceJson(repo, ref = "main") {
1555
+ const url = `https://github.com/${repo}.git`;
1556
+ const tmpdir = makeTempDir("syn-mkt-");
1557
+ try {
1558
+ await gitClone(url, ref, tmpdir);
1559
+ const marketplacePath = path4.join(tmpdir, "marketplace.json");
1560
+ if (!fs4.existsSync(marketplacePath)) {
1561
+ throw new Error(`No marketplace.json found in ${repo}`);
1562
+ }
1563
+ const content = fs4.readFileSync(marketplacePath, "utf-8");
1564
+ const data = JSON.parse(content);
1565
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
1566
+ throw new Error("marketplace.json must be a JSON object");
1567
+ }
1568
+ const index = MarketplaceIndexSchema.parse(data);
1569
+ if (index.syntropic137.type !== "workflow-marketplace") {
1570
+ throw new Error(
1571
+ `Expected syntropic137.type='workflow-marketplace', got '${index.syntropic137.type}'`
1572
+ );
1573
+ }
1574
+ return index;
1575
+ } finally {
1576
+ removeTempDir(tmpdir);
1577
+ }
1578
+ }
1579
+ async function refreshIndex(registryName, entry, force = false) {
1580
+ if (!force) {
1581
+ const cached = loadCachedIndex(registryName);
1582
+ if (cached !== null && !isCacheStale(cached)) {
1583
+ return cached.index;
1584
+ }
1585
+ }
1586
+ const index = await fetchMarketplaceJson(entry.repo, entry.ref);
1587
+ saveCachedIndex(registryName, {
1588
+ fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
1589
+ index
1590
+ });
1591
+ return index;
1592
+ }
1593
+ function matchesQuery(plugin, query) {
1594
+ if (!query) return true;
1595
+ const q = query.toLowerCase();
1596
+ return plugin.name.toLowerCase().includes(q) || plugin.description.toLowerCase().includes(q) || plugin.category.toLowerCase().includes(q) || plugin.tags.some((t) => t.toLowerCase().includes(q));
1597
+ }
1598
+ function matchesFilters(plugin, query, category, tag) {
1599
+ if (!matchesQuery(plugin, query)) return false;
1600
+ if (category && plugin.category.toLowerCase() !== category.toLowerCase())
1601
+ return false;
1602
+ if (tag && !plugin.tags.some((t) => t.toLowerCase() === tag.toLowerCase()))
1603
+ return false;
1604
+ return true;
1605
+ }
1606
+ async function getRegistryIndex(name, entry) {
1607
+ try {
1608
+ return await refreshIndex(name, entry);
1609
+ } catch {
1610
+ return null;
1611
+ }
1612
+ }
1613
+ async function searchAllRegistries(query = "", opts) {
1614
+ const config = loadRegistries();
1615
+ const results = [];
1616
+ const category = opts?.category ?? null;
1617
+ const tag = opts?.tag ?? null;
1618
+ for (const [name, entry] of Object.entries(config.registries)) {
1619
+ const index = await getRegistryIndex(name, entry);
1620
+ if (index === null) continue;
1621
+ for (const plugin of index.plugins) {
1622
+ if (matchesFilters(plugin, query, category, tag)) {
1623
+ results.push([name, plugin]);
1624
+ }
1625
+ }
1626
+ }
1627
+ return results;
1628
+ }
1629
+ function getSearchTargets(config, registry) {
1630
+ if (!registry) return Object.entries(config.registries);
1631
+ const entry = config.registries[registry];
1632
+ return entry ? [[registry, entry]] : [];
1633
+ }
1634
+ async function findPluginInRegistry(regName, entry, pluginName) {
1635
+ const index = await getRegistryIndex(regName, entry);
1636
+ if (index === null) return null;
1637
+ const plugin = index.plugins.find((p) => p.name === pluginName);
1638
+ return plugin ? [regName, entry, plugin] : null;
1639
+ }
1640
+ async function resolvePluginByName(name, registry) {
1641
+ const targets = getSearchTargets(loadRegistries(), registry);
1642
+ for (const [regName, entry] of targets) {
1643
+ const result = await findPluginInRegistry(regName, entry, name);
1644
+ if (result) return result;
1645
+ }
1646
+ return null;
1647
+ }
1648
+
1649
+ // src/commands/workflow/install.ts
1650
+ function isBarePluginName(source) {
1651
+ return !source.includes("/") && !source.startsWith(".") && !source.startsWith("http") && !source.startsWith("git@") && !source.startsWith("ssh://") && !fs5.existsSync(source);
1652
+ }
1653
+ async function tryMarketplaceResolution(source, ref) {
1654
+ const result = await resolvePluginByName(source);
1655
+ if (result === null) return null;
1656
+ const [regName, entry, plugin] = result;
1657
+ const effectiveRef = ref !== "main" ? ref : entry.ref;
1658
+ const url = `https://github.com/${entry.repo}.git`;
1659
+ if (plugin.source.startsWith("/") || plugin.source.includes("..")) {
1660
+ throw new Error(`Unsafe plugin source path in marketplace: ${plugin.source}`);
1661
+ }
1662
+ print(`Found ${style(plugin.name, BOLD)} in marketplace ${style(regName, CYAN)}`);
1663
+ print(`Cloning ${style(entry.repo, CYAN)}@${effectiveRef}...`);
1664
+ const tmpdir = makeTempDir("syn-pkg-");
1665
+ try {
1666
+ await gitClone(url, effectiveRef, tmpdir);
1667
+ } catch (err) {
1668
+ removeTempDir(tmpdir);
1669
+ throw err;
1670
+ }
1671
+ const subdir = path5.resolve(tmpdir, plugin.source.replace(/^\.\//, ""));
1672
+ if (!subdir.startsWith(tmpdir)) {
1673
+ removeTempDir(tmpdir);
1674
+ throw new Error(`Plugin source path escapes repository: ${plugin.source}`);
1675
+ }
1676
+ const { manifest, workflows } = resolvePackage(subdir);
1677
+ const gitSha = await gitLsRemote(entry.repo, effectiveRef);
1678
+ return { packagePath: subdir, manifest, workflows, tmpdir, marketplaceSource: regName, gitSha, effectiveRef };
1679
+ }
1680
+ async function resolveSource(source, ref) {
1681
+ const { resolved, isRemote } = parseSource(source);
1682
+ if (isRemote) {
1683
+ print(`Cloning ${style(resolved, CYAN)}@${ref}...`);
1684
+ const { tmpdir, manifest: manifest2, workflows: workflows2 } = await resolveFromGit(resolved, ref);
1685
+ return { packagePath: tmpdir, manifest: manifest2, workflows: workflows2, tmpdir };
1686
+ }
1687
+ const packagePath = path5.resolve(resolved);
1688
+ const { manifest, workflows } = resolvePackage(packagePath);
1689
+ return { packagePath, manifest, workflows, tmpdir: null };
1690
+ }
1691
+ async function installWorkflowsViaApi(workflows) {
1692
+ const installed = [];
1693
+ for (let i = 0; i < workflows.length; i++) {
1694
+ const wf = workflows[i];
1695
+ process.stdout.write(` [${i + 1}/${workflows.length}] Creating ${style(wf.name, BOLD)}... `);
1696
+ try {
1697
+ const data = await apiPost("/workflows", {
1698
+ body: {
1699
+ name: wf.name,
1700
+ workflow_type: wf.workflow_type,
1701
+ classification: wf.classification,
1702
+ repository_url: wf.repository_url,
1703
+ repository_ref: wf.repository_ref,
1704
+ description: wf.description,
1705
+ project_name: wf.project_name,
1706
+ phases: wf.phases,
1707
+ input_declarations: wf.input_declarations
1708
+ },
1709
+ expected: [201]
1710
+ });
1711
+ const wfId = String(data["id"] ?? "unknown");
1712
+ print(`${style("done", GREEN)} (id: ${wfId})`);
1713
+ installed.push({ id: wfId, name: wf.name });
1714
+ } catch (err) {
1715
+ print(style("failed", "\x1B[31m"));
1716
+ if (err instanceof Error) printError(err.message);
1717
+ }
1718
+ }
1719
+ return installed;
1720
+ }
1721
+ var installCommand = {
1722
+ name: "install",
1723
+ description: "Install workflow(s) from a package, git repository, or marketplace",
1724
+ args: [{ name: "source", description: "Plugin name, local path, GitHub URL, or org/repo shorthand", required: true }],
1725
+ options: {
1726
+ ref: { type: "string", description: "Git branch/tag to clone", default: "main" },
1727
+ "dry-run": { type: "boolean", short: "n", description: "Validate without installing", default: false }
1728
+ },
1729
+ handler: async (parsed) => {
1730
+ const source = parsed.positionals[0];
1731
+ if (!source) {
1732
+ printError("Missing required argument: source");
1733
+ throw new CLIError("Missing argument", 1);
1734
+ }
1735
+ const ref = parsed.values["ref"] ?? "main";
1736
+ const dryRun = parsed.values["dry-run"] === true;
1737
+ let packagePath;
1738
+ let manifest;
1739
+ let workflows;
1740
+ let tmpdir = null;
1741
+ let marketplaceSource = null;
1742
+ let gitSha = null;
1743
+ let effectiveRef = ref;
1744
+ try {
1745
+ if (isBarePluginName(source)) {
1746
+ const mktResult = await tryMarketplaceResolution(source, ref);
1747
+ if (mktResult !== null) {
1748
+ ({ packagePath, manifest, workflows, tmpdir, marketplaceSource, gitSha, effectiveRef } = mktResult);
1749
+ } else {
1750
+ ({ packagePath, manifest, workflows, tmpdir } = await resolveSource(source, ref));
1751
+ }
1752
+ } else {
1753
+ ({ packagePath, manifest, workflows, tmpdir } = await resolveSource(source, ref));
1754
+ }
1755
+ } catch (err) {
1756
+ printError(err instanceof Error ? err.message : String(err));
1757
+ throw new CLIError("Resolution failed", 1);
1758
+ }
1759
+ try {
1760
+ if (workflows.length === 0) {
1761
+ printError("No workflows found in package");
1762
+ throw new CLIError("No workflows", 1);
1763
+ }
1764
+ const fmt = detectFormat(packagePath);
1765
+ const pkgName = manifest?.name ?? path5.basename(packagePath);
1766
+ const pkgVersion = manifest?.version ?? "0.0.0";
1767
+ printPackagePreview(pkgName, pkgVersion, source, fmt, workflows);
1768
+ if (dryRun) {
1769
+ printSuccess("Dry run \u2014 package is valid, no workflows installed");
1770
+ printWorkflowSummary(workflows);
1771
+ return;
1772
+ }
1773
+ const installedRefs = await installWorkflowsViaApi(workflows);
1774
+ if (installedRefs.length === 0) {
1775
+ printError("No workflows were installed");
1776
+ throw new CLIError("Install failed", 1);
1777
+ }
1778
+ recordInstallation({
1779
+ packageName: pkgName,
1780
+ packageVersion: pkgVersion,
1781
+ source,
1782
+ sourceRef: effectiveRef,
1783
+ format: fmt,
1784
+ workflows: installedRefs,
1785
+ marketplaceSource,
1786
+ gitSha
1787
+ });
1788
+ printSuccess(`
1789
+ Installed ${installedRefs.length} workflow(s) from ${source}`);
1790
+ } finally {
1791
+ if (tmpdir !== null) {
1792
+ removeTempDir(tmpdir);
1793
+ }
1794
+ }
1795
+ }
1796
+ };
1797
+ function printPackagePreview(name, version, source, fmt, workflows) {
1798
+ const totalPhases = workflows.reduce((sum, wf) => sum + wf.phases.length, 0);
1799
+ print("");
1800
+ print(style("Package Preview", CYAN));
1801
+ print(` ${style(`${name} v${version}`, BOLD)}`);
1802
+ print(` Source: ${source}`);
1803
+ print(` Format: ${fmt}`);
1804
+ print(` Workflows: ${workflows.length}`);
1805
+ print(` Total phases: ${totalPhases}`);
1806
+ }
1807
+ function printWorkflowSummary(workflows) {
1808
+ const table = new Table({ title: "Resolved Workflows" });
1809
+ table.addColumn("Name", { style: CYAN });
1810
+ table.addColumn("ID", { style: DIM });
1811
+ table.addColumn("Type");
1812
+ table.addColumn("Phases", { align: "right" });
1813
+ for (const wf of workflows) {
1814
+ table.addRow(wf.name, wf.id, wf.workflow_type, String(wf.phases.length));
1815
+ }
1816
+ table.print();
1817
+ }
1818
+ var installedCommand = {
1819
+ name: "installed",
1820
+ description: "List installed workflow packages",
1821
+ handler: async () => {
1822
+ const registry = loadInstalled();
1823
+ if (registry.installations.length === 0) {
1824
+ printDim("No packages installed yet.");
1825
+ print(`Install one with: ${style("syn workflow install <source>", CYAN)}`);
1826
+ return;
1827
+ }
1828
+ const table = new Table({ title: "Installed Packages" });
1829
+ table.addColumn("Package", { style: CYAN });
1830
+ table.addColumn("Version");
1831
+ table.addColumn("Source", { style: DIM });
1832
+ table.addColumn("Workflows", { align: "right" });
1833
+ table.addColumn("Installed", { style: DIM });
1834
+ for (const record of registry.installations) {
1835
+ table.addRow(
1836
+ record.package_name,
1837
+ record.package_version,
1838
+ record.source,
1839
+ String(record.workflows.length),
1840
+ formatTimestamp(record.installed_at)
1841
+ );
1842
+ }
1843
+ table.print();
1844
+ }
1845
+ };
1846
+ var initCommand = {
1847
+ name: "init",
1848
+ description: "Scaffold a new workflow package from a template",
1849
+ args: [{ name: "directory", description: "Directory to scaffold (defaults to current dir)" }],
1850
+ options: {
1851
+ name: { type: "string", short: "n", description: "Workflow name" },
1852
+ type: { type: "string", short: "t", description: "Workflow type", default: "research" },
1853
+ phases: { type: "string", description: "Number of phases", default: "3" },
1854
+ multi: { type: "boolean", description: "Scaffold multi-workflow plugin", default: false }
1855
+ },
1856
+ handler: async (parsed) => {
1857
+ const directory = parsed.positionals[0] ?? ".";
1858
+ const resolvedDir = path5.resolve(directory);
1859
+ const workflowType = parsed.values["type"] ?? "research";
1860
+ const numPhases = parseInt(parsed.values["phases"] ?? "3", 10);
1861
+ const multi = parsed.values["multi"] === true;
1862
+ const wfName = parsed.values["name"] ?? path5.basename(resolvedDir).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1863
+ if (fs5.existsSync(resolvedDir)) {
1864
+ const entries = fs5.readdirSync(resolvedDir);
1865
+ if (entries.length > 0) {
1866
+ printError(`Directory is not empty: ${resolvedDir}`);
1867
+ throw new CLIError("Directory not empty", 1);
1868
+ }
1869
+ }
1870
+ if (multi) {
1871
+ scaffoldMultiPackage(resolvedDir, { name: wfName, workflowType, numPhases });
1872
+ } else {
1873
+ scaffoldSinglePackage(resolvedDir, { name: wfName, workflowType, numPhases });
1874
+ }
1875
+ const fmtLabel = multi ? "multi-workflow plugin" : "single workflow package";
1876
+ printSuccess(`Scaffolded ${fmtLabel} at ${resolvedDir}`);
1877
+ print("\nNext steps:");
1878
+ print(` 1. Edit the prompts in ${style(`${resolvedDir}/phases/`, CYAN)}`);
1879
+ print(` 2. Validate: ${style(`syn workflow validate ${resolvedDir}`, CYAN)}`);
1880
+ print(` 3. Install: ${style(`syn workflow install ${resolvedDir}`, CYAN)}`);
1881
+ }
1882
+ };
1883
+
1884
+ // src/commands/workflow/export.ts
1885
+ import fs6 from "fs";
1886
+ import path6 from "path";
1887
+ var exportCommand = {
1888
+ name: "export",
1889
+ description: "Export a workflow as a distributable package or Claude Code plugin",
1890
+ args: [{ name: "workflow-id", description: "Workflow ID to export", required: true }],
1891
+ options: {
1892
+ format: { type: "string", short: "f", description: "Export format: 'package' (default) or 'plugin'", default: "package" },
1893
+ output: { type: "string", short: "o", description: "Output directory (created if absent)", default: "." }
1894
+ },
1895
+ handler: async (parsed) => {
1896
+ const workflowId = parsed.positionals[0];
1897
+ if (!workflowId) {
1898
+ printError("Missing required argument: workflow-id");
1899
+ throw new CLIError("Missing argument", 1);
1900
+ }
1901
+ const fmt = parsed.values["format"] ?? "package";
1902
+ if (fmt !== "package" && fmt !== "plugin") {
1903
+ printError(`Invalid format '${fmt}'. Must be 'package' or 'plugin'.`);
1904
+ throw new CLIError("Invalid format", 1);
1905
+ }
1906
+ const outputDir = parsed.values["output"] ?? ".";
1907
+ const data = await apiGet(
1908
+ `/workflows/${workflowId}/export`,
1909
+ { params: { format: fmt } }
1910
+ );
1911
+ const files = data.files ?? {};
1912
+ if (Object.keys(files).length === 0) {
1913
+ printError("Export returned no files");
1914
+ throw new CLIError("Export empty", 1);
1915
+ }
1916
+ const outDir = path6.resolve(outputDir);
1917
+ if (fs6.existsSync(outDir)) {
1918
+ const entries = fs6.readdirSync(outDir);
1919
+ if (entries.length > 0) {
1920
+ printError(`Output directory is not empty: ${outDir}`);
1921
+ throw new CLIError("Directory not empty", 1);
1922
+ }
1923
+ }
1924
+ for (const [relPath, content] of Object.entries(files).sort(([a], [b]) => a.localeCompare(b))) {
1925
+ if (relPath.startsWith("/") || relPath.includes("..")) {
1926
+ printError(`Unsafe file path in export manifest: ${relPath}`);
1927
+ throw new CLIError("Path traversal", 1);
1928
+ }
1929
+ const filePath = path6.resolve(outDir, relPath);
1930
+ if (!filePath.startsWith(outDir)) {
1931
+ printError(`Path escapes output directory: ${relPath}`);
1932
+ throw new CLIError("Path traversal", 1);
1933
+ }
1934
+ fs6.mkdirSync(path6.dirname(filePath), { recursive: true });
1935
+ fs6.writeFileSync(filePath, content, "utf-8");
1936
+ }
1937
+ const workflowName = data.workflow_name ?? workflowId;
1938
+ print("");
1939
+ print(style("Export Complete", GREEN));
1940
+ print(` ${style(workflowName, BOLD)}`);
1941
+ print(` Format: ${fmt}`);
1942
+ print(` Output: ${outDir}`);
1943
+ print(` Files: ${Object.keys(files).length}`);
1944
+ print("");
1945
+ print(style(`${path6.basename(outDir)}/`, CYAN));
1946
+ const sortedPaths = Object.keys(files).sort();
1947
+ for (const relPath of sortedPaths) {
1948
+ const parts = relPath.split("/");
1949
+ const indent = " ".repeat(parts.length);
1950
+ print(`${indent}${style(parts[parts.length - 1], DIM)}`);
1951
+ }
1952
+ printSuccess(`
1953
+ Exported to ${outDir}`);
1954
+ print(`
1955
+ To install: ${style(`syn workflow install ${outDir}`, CYAN)}`);
1956
+ if (fmt === "plugin") {
1957
+ print(`Plugin command: ${style(`/syn-${workflowName.toLowerCase().replace(/ /g, "-")}`, CYAN)}`);
1958
+ }
1959
+ }
1960
+ };
1961
+
1962
+ // src/commands/workflow/search.ts
1963
+ function truncate2(text, maxLen) {
1964
+ if (text.length <= maxLen) return text;
1965
+ return text.slice(0, maxLen - 3) + "...";
1966
+ }
1967
+ var searchCommand = {
1968
+ name: "search",
1969
+ description: "Search for workflows across registered marketplaces",
1970
+ args: [{ name: "query", description: "Search term (matches name, description, tags)" }],
1971
+ options: {
1972
+ category: { type: "string", short: "c", description: "Filter by category" },
1973
+ tag: { type: "string", short: "t", description: "Filter by tag" },
1974
+ registry: { type: "string", short: "r", description: "Search specific marketplace only" }
1975
+ },
1976
+ handler: async (parsed) => {
1977
+ const query = parsed.positionals[0] ?? "";
1978
+ const category = parsed.values["category"] ?? null;
1979
+ const tag = parsed.values["tag"] ?? null;
1980
+ const registryFilter = parsed.values["registry"];
1981
+ let results = await searchAllRegistries(query, { category, tag });
1982
+ if (registryFilter) {
1983
+ results = results.filter(([rn]) => rn === registryFilter);
1984
+ }
1985
+ if (results.length === 0) {
1986
+ printDim("No workflows found.");
1987
+ if (!query && !category && !tag) {
1988
+ printDim("Add a marketplace first: syn marketplace add syntropic137/workflow-library");
1989
+ }
1990
+ return;
1991
+ }
1992
+ const table = new Table({ title: "Available Workflows" });
1993
+ table.addColumn("Name", { style: BOLD });
1994
+ table.addColumn("Version");
1995
+ table.addColumn("Category");
1996
+ table.addColumn("Description");
1997
+ table.addColumn("Registry", { style: DIM });
1998
+ for (const [regName, plugin] of results) {
1999
+ table.addRow(
2000
+ plugin.name,
2001
+ plugin.version,
2002
+ plugin.category || "-",
2003
+ truncate2(plugin.description, 50),
2004
+ regName
2005
+ );
2006
+ }
2007
+ table.print();
2008
+ const count = results.length;
2009
+ printDim(`
2010
+ ${count} result${count !== 1 ? "s" : ""}. Install with: syn workflow install <name>`);
2011
+ }
2012
+ };
2013
+ var infoCommand = {
2014
+ name: "info",
2015
+ description: "Show details of a marketplace workflow plugin",
2016
+ args: [{ name: "name", description: "Plugin name from marketplace", required: true }],
2017
+ handler: async (parsed) => {
2018
+ const name = parsed.positionals[0];
2019
+ if (!name) {
2020
+ printError("Missing required argument: name");
2021
+ throw new CLIError("Missing argument", 1);
2022
+ }
2023
+ const result = await resolvePluginByName(name);
2024
+ if (result === null) {
2025
+ printError(`Plugin '${name}' not found in any registered marketplace`);
2026
+ printDim("Try: syn workflow search");
2027
+ throw new CLIError("Not found", 1);
2028
+ }
2029
+ const [regName, entry, plugin] = result;
2030
+ const tagsStr = plugin.tags.length > 0 ? plugin.tags.join(", ") : "-";
2031
+ print("");
2032
+ print(style(plugin.name, BOLD));
2033
+ print(` ${style("Version:", BOLD)} ${plugin.version}`);
2034
+ print(` ${style("Description:", BOLD)} ${plugin.description || "-"}`);
2035
+ print(` ${style("Category:", BOLD)} ${plugin.category || "-"}`);
2036
+ print(` ${style("Tags:", BOLD)} ${tagsStr}`);
2037
+ print(` ${style("Source:", BOLD)} ${entry.repo} (${plugin.source})`);
2038
+ print(` ${style("Registry:", BOLD)} ${regName}`);
2039
+ print("");
2040
+ printDim(`Install: syn workflow install ${plugin.name}`);
2041
+ }
2042
+ };
2043
+
2044
+ // src/commands/workflow/update.ts
2045
+ function findInstallation(name) {
2046
+ const registry = loadInstalled();
2047
+ for (const record of registry.installations) {
2048
+ if (record.package_name === name) return record;
2049
+ }
2050
+ return null;
2051
+ }
2052
+ function removeInstallation(name) {
2053
+ const registry = loadInstalled();
2054
+ const remaining = registry.installations.filter((r) => r.package_name !== name);
2055
+ saveInstalled({ version: registry.version, installations: remaining });
2056
+ }
2057
+ async function deleteWorkflowsViaApi(record) {
2058
+ let deleted = 0;
2059
+ for (const wfRef of record.workflows) {
2060
+ process.stdout.write(` Removing ${style(wfRef.name, BOLD)}... `);
2061
+ try {
2062
+ await apiDelete(`/workflows/${wfRef.id}`, { expected: [200, 204, 404] });
2063
+ print(style("done", GREEN));
2064
+ deleted++;
2065
+ } catch {
2066
+ print(style("failed", RED));
2067
+ }
2068
+ }
2069
+ return deleted;
2070
+ }
2071
+ async function isAlreadyUpToDate(record, effectiveRef) {
2072
+ if (!record.marketplace_source || !record.git_sha) return false;
2073
+ const result = await resolvePluginByName(record.package_name, record.marketplace_source);
2074
+ if (result === null) return false;
2075
+ const [_regName, entry, _plugin] = result;
2076
+ const currentSha = await gitLsRemote(entry.repo, effectiveRef);
2077
+ return currentSha !== null && currentSha === record.git_sha;
2078
+ }
2079
+ var updateCommand = {
2080
+ name: "update",
2081
+ description: "Update an installed workflow package to the latest version",
2082
+ args: [{ name: "name", description: "Package name to update", required: true }],
2083
+ options: {
2084
+ ref: { type: "string", description: "Override git ref" },
2085
+ "dry-run": { type: "boolean", short: "n", description: "Check for updates without applying", default: false }
2086
+ },
2087
+ handler: async (parsed) => {
2088
+ const name = parsed.positionals[0];
2089
+ if (!name) {
2090
+ printError("Missing required argument: name");
2091
+ throw new CLIError("Missing argument", 1);
2092
+ }
2093
+ const record = findInstallation(name);
2094
+ if (record === null) {
2095
+ printError(`Package '${name}' is not installed`);
2096
+ printDim("See installed packages: syn workflow installed");
2097
+ throw new CLIError("Not installed", 1);
2098
+ }
2099
+ const source = record.source;
2100
+ const effectiveRef = parsed.values["ref"] ?? record.source_ref;
2101
+ const dryRun = parsed.values["dry-run"] === true;
2102
+ if (await isAlreadyUpToDate(record, effectiveRef)) {
2103
+ printDim(`Package '${name}' is already up to date`);
2104
+ return;
2105
+ }
2106
+ if (dryRun) {
2107
+ print(`${style("Update available", CYAN)} for ${style(name, BOLD)}`);
2108
+ print(` Source: ${source}`);
2109
+ print(` Ref: ${effectiveRef}`);
2110
+ printDim("Run without --dry-run to apply");
2111
+ return;
2112
+ }
2113
+ let packagePath;
2114
+ let manifest;
2115
+ let workflows;
2116
+ let tmpdir = null;
2117
+ let marketplaceSource = null;
2118
+ let gitSha = null;
2119
+ let resolvedRef = effectiveRef;
2120
+ try {
2121
+ if (isBarePluginName(source) && record.marketplace_source) {
2122
+ const mktResult = await tryMarketplaceResolution(source, effectiveRef);
2123
+ if (mktResult !== null) {
2124
+ ({ packagePath, manifest, workflows, tmpdir, marketplaceSource, gitSha, effectiveRef: resolvedRef } = mktResult);
2125
+ } else {
2126
+ printError(`Plugin '${name}' no longer found in marketplace`);
2127
+ throw new CLIError("Not found", 1);
2128
+ }
2129
+ } else {
2130
+ ({ packagePath, manifest, workflows, tmpdir } = await resolveSource(source, effectiveRef));
2131
+ }
2132
+ } catch (err) {
2133
+ if (err instanceof CLIError) throw err;
2134
+ printError(err instanceof Error ? err.message : String(err));
2135
+ throw new CLIError("Resolution failed", 1);
2136
+ }
2137
+ try {
2138
+ if (workflows.length === 0) {
2139
+ printError("No workflows found in updated package");
2140
+ throw new CLIError("No workflows", 1);
2141
+ }
2142
+ const fmt = detectFormat(packagePath);
2143
+ const pkgName = manifest?.name ?? name;
2144
+ const pkgVersion = manifest?.version ?? "0.0.0";
2145
+ print("");
2146
+ print(style("Package Preview", CYAN));
2147
+ print(` ${style(`${pkgName} v${pkgVersion}`, BOLD)}`);
2148
+ print(` Source: ${source}`);
2149
+ print(` Format: ${fmt}`);
2150
+ print(` Workflows: ${workflows.length}`);
2151
+ print(`
2152
+ ${style("Removing old workflows...", BOLD)}`);
2153
+ await deleteWorkflowsViaApi(record);
2154
+ removeInstallation(name);
2155
+ print(`
2156
+ ${style("Installing updated workflows...", BOLD)}`);
2157
+ const installedRefs = await installWorkflowsViaApi(workflows);
2158
+ if (installedRefs.length === 0) {
2159
+ printError("No workflows were installed during update");
2160
+ throw new CLIError("Update failed", 1);
2161
+ }
2162
+ recordInstallation({
2163
+ packageName: pkgName,
2164
+ packageVersion: pkgVersion,
2165
+ source,
2166
+ sourceRef: resolvedRef,
2167
+ format: fmt,
2168
+ workflows: installedRefs,
2169
+ marketplaceSource: marketplaceSource ?? record.marketplace_source ?? null,
2170
+ gitSha: gitSha ?? record.git_sha ?? null
2171
+ });
2172
+ printSuccess(`
2173
+ Updated ${pkgName} (${installedRefs.length} workflow(s))`);
2174
+ } finally {
2175
+ if (tmpdir !== null) {
2176
+ removeTempDir(tmpdir);
2177
+ }
2178
+ }
2179
+ }
2180
+ };
2181
+ var uninstallCommand = {
2182
+ name: "uninstall",
2183
+ description: "Uninstall a workflow package",
2184
+ args: [{ name: "name", description: "Package name to uninstall", required: true }],
2185
+ options: {
2186
+ "keep-workflows": { type: "boolean", description: "Remove from registry but keep workflows in the platform", default: false }
2187
+ },
2188
+ handler: async (parsed) => {
2189
+ const name = parsed.positionals[0];
2190
+ if (!name) {
2191
+ printError("Missing required argument: name");
2192
+ throw new CLIError("Missing argument", 1);
2193
+ }
2194
+ const record = findInstallation(name);
2195
+ if (record === null) {
2196
+ printError(`Package '${name}' is not installed`);
2197
+ printDim("See installed packages: syn workflow installed");
2198
+ throw new CLIError("Not installed", 1);
2199
+ }
2200
+ if (parsed.values["keep-workflows"] !== true) {
2201
+ print(`Removing workflows from ${style(name, BOLD)}...`);
2202
+ const deleted = await deleteWorkflowsViaApi(record);
2203
+ print(` Removed ${deleted} workflow(s)`);
2204
+ }
2205
+ removeInstallation(name);
2206
+ printSuccess(`Uninstalled ${style(name, BOLD)}`);
2207
+ }
2208
+ };
2209
+
2210
+ // src/commands/workflow/index.ts
2211
+ var workflowGroup = new CommandGroup(
2212
+ "workflow",
2213
+ "Manage workflows - create, list, run, and inspect"
2214
+ );
2215
+ workflowGroup.command(createCommand).command(listCommand).command(showCommand).command(validateCommand).command(deleteCommand).command(runCommand).command(statusCommand).command(installCommand).command(installedCommand).command(initCommand).command(exportCommand).command(searchCommand).command(infoCommand).command(updateCommand).command(uninstallCommand);
2216
+
2217
+ // src/commands/marketplace/registry.ts
2218
+ import fs7 from "fs";
2219
+ import path7 from "path";
2220
+ var addCommand = {
2221
+ name: "add",
2222
+ description: "Register a GitHub repo as a workflow marketplace",
2223
+ args: [{ name: "repo", description: "GitHub repo (org/repo shorthand)", required: true }],
2224
+ options: {
2225
+ ref: { type: "string", short: "r", description: "Git branch or tag", default: "main" },
2226
+ name: { type: "string", short: "n", description: "Override registry name" }
2227
+ },
2228
+ handler: async (parsed) => {
2229
+ const repo = parsed.positionals[0];
2230
+ if (!repo) {
2231
+ printError("Missing required argument: repo");
2232
+ throw new CLIError("Missing argument", 1);
2233
+ }
2234
+ const ref = parsed.values["ref"] ?? "main";
2235
+ const nameOverride = parsed.values["name"];
2236
+ print(`Fetching marketplace.json from ${style(repo, CYAN)}@${ref}...`);
2237
+ let index;
2238
+ try {
2239
+ index = await fetchMarketplaceJson(repo, ref);
2240
+ } catch (err) {
2241
+ printError(err instanceof Error ? err.message : String(err));
2242
+ throw new CLIError("Fetch failed", 1);
2243
+ }
2244
+ const registryName = nameOverride ?? index.name;
2245
+ try {
2246
+ validateRegistryName(registryName);
2247
+ } catch (err) {
2248
+ printError(err instanceof Error ? err.message : String(err));
2249
+ throw new CLIError("Invalid name", 1);
2250
+ }
2251
+ const config = loadRegistries();
2252
+ if (config.registries[registryName]) {
2253
+ printError(`Marketplace '${registryName}' is already registered`);
2254
+ printDim("Use 'syn marketplace remove' first to re-register.");
2255
+ throw new CLIError("Already registered", 1);
2256
+ }
2257
+ const entry = {
2258
+ repo,
2259
+ ref,
2260
+ added_at: (/* @__PURE__ */ new Date()).toISOString()
2261
+ };
2262
+ saveRegistries({
2263
+ version: config.version,
2264
+ registries: { ...config.registries, [registryName]: entry }
2265
+ });
2266
+ saveCachedIndex(registryName, {
2267
+ fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
2268
+ index
2269
+ });
2270
+ const pluginCount = index.plugins.length;
2271
+ printSuccess(
2272
+ `Added marketplace ${style(registryName, BOLD)} (${pluginCount} plugin${pluginCount !== 1 ? "s" : ""})`
2273
+ );
2274
+ }
2275
+ };
2276
+ var listMarketplaceCommand = {
2277
+ name: "list",
2278
+ description: "List registered marketplace registries",
2279
+ handler: async () => {
2280
+ const config = loadRegistries();
2281
+ if (Object.keys(config.registries).length === 0) {
2282
+ printDim("No marketplaces registered.");
2283
+ printDim("Add one with: syn marketplace add syntropic137/workflow-library");
2284
+ return;
2285
+ }
2286
+ const table = new Table({ title: "Registered Marketplaces" });
2287
+ table.addColumn("Name", { style: BOLD });
2288
+ table.addColumn("Repo");
2289
+ table.addColumn("Ref");
2290
+ table.addColumn("Added");
2291
+ for (const [name, entry] of Object.entries(config.registries)) {
2292
+ table.addRow(
2293
+ name,
2294
+ entry.repo,
2295
+ entry.ref,
2296
+ formatTimestamp(entry.added_at)
2297
+ );
2298
+ }
2299
+ table.print();
2300
+ }
2301
+ };
2302
+ var removeCommand = {
2303
+ name: "remove",
2304
+ description: "Remove a registered marketplace",
2305
+ args: [{ name: "name", description: "Registry name to remove", required: true }],
2306
+ handler: async (parsed) => {
2307
+ const name = parsed.positionals[0];
2308
+ if (!name) {
2309
+ printError("Missing required argument: name");
2310
+ throw new CLIError("Missing argument", 1);
2311
+ }
2312
+ const config = loadRegistries();
2313
+ if (!config.registries[name]) {
2314
+ printError(`Marketplace '${name}' is not registered`);
2315
+ throw new CLIError("Not found", 1);
2316
+ }
2317
+ const remaining = {};
2318
+ for (const [k, v] of Object.entries(config.registries)) {
2319
+ if (k !== name) remaining[k] = v;
2320
+ }
2321
+ saveRegistries({ version: config.version, registries: remaining });
2322
+ try {
2323
+ validateRegistryName(name);
2324
+ const cachePath = path7.join(synPath("marketplace", "cache"), `${name}.json`);
2325
+ if (fs7.existsSync(cachePath)) fs7.unlinkSync(cachePath);
2326
+ } catch {
2327
+ }
2328
+ printSuccess(`Removed marketplace ${style(name, BOLD)}`);
2329
+ }
2330
+ };
2331
+ var refreshCommand = {
2332
+ name: "refresh",
2333
+ description: "Force-refresh cached marketplace indexes",
2334
+ args: [{ name: "name", description: "Registry name (refreshes all if omitted)" }],
2335
+ handler: async (parsed) => {
2336
+ const nameFilter = parsed.positionals[0];
2337
+ const config = loadRegistries();
2338
+ if (Object.keys(config.registries).length === 0) {
2339
+ printDim("No marketplaces registered.");
2340
+ return;
2341
+ }
2342
+ let targets;
2343
+ if (nameFilter) {
2344
+ const entry = config.registries[nameFilter];
2345
+ if (!entry) {
2346
+ printError(`Marketplace '${nameFilter}' is not registered`);
2347
+ throw new CLIError("Not found", 1);
2348
+ }
2349
+ targets = [[nameFilter, entry]];
2350
+ } else {
2351
+ targets = Object.entries(config.registries);
2352
+ }
2353
+ for (const [regName, entry] of targets) {
2354
+ process.stdout.write(`Refreshing ${style(regName, CYAN)}... `);
2355
+ try {
2356
+ const index = await refreshIndex(regName, entry, true);
2357
+ const pluginCount = index.plugins.length;
2358
+ print(`${style("done", GREEN)} (${pluginCount} plugin${pluginCount !== 1 ? "s" : ""})`);
2359
+ } catch (err) {
2360
+ print(`${style("failed", RED)} (${err instanceof Error ? err.message : String(err)})`);
2361
+ }
2362
+ }
2363
+ }
2364
+ };
2365
+
2366
+ // src/commands/marketplace/index.ts
2367
+ var marketplaceGroup = new CommandGroup(
2368
+ "marketplace",
2369
+ "Manage workflow marketplace registries"
2370
+ );
2371
+ marketplaceGroup.command(addCommand).command(listMarketplaceCommand).command(removeCommand).command(refreshCommand);
2372
+
2373
+ // src/commands/agent.ts
2374
+ var listCommand2 = {
2375
+ name: "list",
2376
+ description: "List available agent providers",
2377
+ handler: async () => {
2378
+ const providers = await apiGetList("/agents/providers");
2379
+ const table = new Table({ title: "Agent Providers" });
2380
+ table.addColumn("Provider", { style: CYAN });
2381
+ table.addColumn("Display Name");
2382
+ table.addColumn("Available");
2383
+ table.addColumn("Default Model");
2384
+ for (const p of providers) {
2385
+ const available = p["available"] ? style("Yes", GREEN) : style("No", "\x1B[31m");
2386
+ table.addRow(
2387
+ String(p["provider"] ?? ""),
2388
+ String(p["display_name"] ?? ""),
2389
+ available,
2390
+ String(p["default_model"] ?? "")
2391
+ );
2392
+ }
2393
+ table.print();
2394
+ }
2395
+ };
2396
+ var testCommand = {
2397
+ name: "test",
2398
+ description: "Test an agent provider with a simple prompt",
2399
+ options: {
2400
+ provider: { type: "string", short: "p", description: "Agent provider (claude, mock)", default: "claude" },
2401
+ prompt: { type: "string", description: "Test prompt", default: "Say hello in one sentence." },
2402
+ model: { type: "string", short: "m", description: "Model override" }
2403
+ },
2404
+ handler: async (parsed) => {
2405
+ const provider = parsed.values["provider"] ?? "claude";
2406
+ const prompt = parsed.values["prompt"] ?? "Say hello in one sentence.";
2407
+ const model = parsed.values["model"] ?? null;
2408
+ const result = await apiPost("/agents/test", {
2409
+ body: { provider, prompt, model },
2410
+ timeoutMs: 6e4
2411
+ });
2412
+ print(`${style(`Response from ${String(result["provider"] ?? provider)}:`, GREEN)}`);
2413
+ print(` Model: ${String(result["model"] ?? "unknown")}`);
2414
+ print(` Response: ${String(result["response_text"] ?? "")}`);
2415
+ print(` Tokens: ${String(result["input_tokens"] ?? 0)} in / ${String(result["output_tokens"] ?? 0)} out`);
2416
+ }
2417
+ };
2418
+ var agentGroup = new CommandGroup("agent", "AI agent management and testing");
2419
+ agentGroup.command(listCommand2).command(testCommand);
2420
+
2421
+ // src/commands/artifacts.ts
2422
+ var listCommand3 = {
2423
+ name: "list",
2424
+ description: "List artifacts",
2425
+ options: {
2426
+ workflow: { type: "string", short: "w", description: "Filter by workflow ID" },
2427
+ phase: { type: "string", short: "p", description: "Filter by phase ID" },
2428
+ type: { type: "string", short: "t", description: "Filter by artifact type" },
2429
+ limit: { type: "string", description: "Max results (max 200)", default: "50" }
2430
+ },
2431
+ handler: async (parsed) => {
2432
+ const params = buildParams({
2433
+ workflow_id: parsed.values["workflow"] ?? null,
2434
+ phase_id: parsed.values["phase"] ?? null,
2435
+ artifact_type: parsed.values["type"] ?? null,
2436
+ limit: parsed.values["limit"] ?? "50"
2437
+ });
2438
+ const items = await apiGetList("/artifacts", { params });
2439
+ if (items.length === 0) {
2440
+ printDim("No artifacts found.");
2441
+ return;
2442
+ }
2443
+ const table = new Table({ title: "Artifacts" });
2444
+ table.addColumn("ID", { style: CYAN });
2445
+ table.addColumn("Type");
2446
+ table.addColumn("Title");
2447
+ table.addColumn("Size", { align: "right" });
2448
+ table.addColumn("Created");
2449
+ for (const a of items) {
2450
+ const size = a.size_bytes;
2451
+ const sizeStr = size < 1024 ? `${size}B` : `${Math.floor(size / 1024)}KB`;
2452
+ table.addRow(
2453
+ a.id.slice(0, 12),
2454
+ a.artifact_type,
2455
+ a.title ?? "\u2014",
2456
+ sizeStr,
2457
+ formatTimestamp(a.created_at)
2458
+ );
2459
+ }
2460
+ table.print();
2461
+ }
2462
+ };
2463
+ var showCommand2 = {
2464
+ name: "show",
2465
+ description: "Show artifact metadata and content",
2466
+ args: [{ name: "artifact-id", description: "Artifact ID", required: true }],
2467
+ options: {
2468
+ "no-content": { type: "boolean", description: "Skip content; show metadata only", default: false }
2469
+ },
2470
+ handler: async (parsed) => {
2471
+ const id = parsed.positionals[0];
2472
+ if (!id) {
2473
+ printError("Missing artifact-id");
2474
+ throw new CLIError("Missing argument", 1);
2475
+ }
2476
+ const noContent = parsed.values["no-content"] === true;
2477
+ const a = await apiGet(`/artifacts/${id}`, {
2478
+ params: { include_content: String(!noContent) }
2479
+ });
2480
+ print(`${style("Artifact:", BOLD)} ${a.id}`);
2481
+ print(` Type: ${a.artifact_type}`);
2482
+ if (a.title) print(` Title: ${a.title}`);
2483
+ if (a.workflow_id) print(` Workflow: ${a.workflow_id}`);
2484
+ if (a.phase_id) print(` Phase: ${a.phase_id}`);
2485
+ print(` Created: ${formatTimestamp(a.created_at)}`);
2486
+ if (a.size_bytes) print(` Size: ${a.size_bytes.toLocaleString()} bytes`);
2487
+ if (!noContent && a.content) {
2488
+ print("");
2489
+ print(a.content);
2490
+ }
2491
+ }
2492
+ };
2493
+ var contentCommand = {
2494
+ name: "content",
2495
+ description: "Print the raw content of an artifact",
2496
+ args: [{ name: "artifact-id", description: "Artifact ID", required: true }],
2497
+ handler: async (parsed) => {
2498
+ const id = parsed.positionals[0];
2499
+ if (!id) {
2500
+ printError("Missing artifact-id");
2501
+ throw new CLIError("Missing argument", 1);
2502
+ }
2503
+ const data = await apiGet(`/artifacts/${id}/content`);
2504
+ if (!data.content) {
2505
+ printDim("(no content)");
2506
+ return;
2507
+ }
2508
+ print(data.content);
2509
+ }
2510
+ };
2511
+ var createCommand2 = {
2512
+ name: "create",
2513
+ description: "Create a new artifact",
2514
+ options: {
2515
+ workflow: { type: "string", short: "w", description: "Workflow ID" },
2516
+ type: { type: "string", short: "t", description: "Artifact type (code, document, research_summary)" },
2517
+ title: { type: "string", description: "Artifact title" },
2518
+ content: { type: "string", short: "c", description: "Artifact content" },
2519
+ phase: { type: "string", short: "p", description: "Phase ID" }
2520
+ },
2521
+ handler: async (parsed) => {
2522
+ const data = await apiPost("/artifacts", {
2523
+ body: {
2524
+ workflow_id: parsed.values["workflow"] ?? null,
2525
+ artifact_type: parsed.values["type"] ?? null,
2526
+ title: parsed.values["title"] ?? null,
2527
+ content: parsed.values["content"] ?? null,
2528
+ phase_id: parsed.values["phase"] ?? null
2529
+ }
2530
+ });
2531
+ print(`${style("Created artifact:", GREEN)} ${style(data.title, CYAN)}`);
2532
+ print(` ID: ${style(data.id, DIM)}`);
2533
+ print(` Type: ${style(data.artifact_type, DIM)}`);
2534
+ }
2535
+ };
2536
+ var artifactsGroup = new CommandGroup("artifacts", "Browse and retrieve workflow artifacts");
2537
+ artifactsGroup.command(listCommand3).command(showCommand2).command(contentCommand).command(createCommand2);
2538
+
2539
+ // src/commands/config.ts
2540
+ var showCommand3 = {
2541
+ name: "show",
2542
+ description: "Display current configuration",
2543
+ options: {
2544
+ "show-secrets": { type: "boolean", description: "Show secret values", default: false }
2545
+ },
2546
+ handler: async (parsed) => {
2547
+ const showSecrets = parsed.values["show-secrets"] === true;
2548
+ const snapshot = await apiGet("/config", {
2549
+ params: { show_secrets: String(showSecrets) }
2550
+ });
2551
+ for (const section of ["app", "database", "agents", "storage"]) {
2552
+ const data = snapshot[section];
2553
+ if (!data) continue;
2554
+ print(`
2555
+ ${style(section.charAt(0).toUpperCase() + section.slice(1), BOLD)}`);
2556
+ for (const [k, v] of Object.entries(data)) {
2557
+ print(` ${k}: ${String(v)}`);
2558
+ }
2559
+ }
2560
+ }
2561
+ };
2562
+ var validateCommand2 = {
2563
+ name: "validate",
2564
+ description: "Validate configuration and show issues",
2565
+ handler: async () => {
2566
+ print("Validating configuration...\n");
2567
+ const data = await apiGet("/config/validate");
2568
+ const issues = data["issues"] ?? [];
2569
+ if (issues.length === 0) {
2570
+ print(style("No issues found.", GREEN));
2571
+ return;
2572
+ }
2573
+ const table = new Table({ title: "Configuration Issues" });
2574
+ table.addColumn("Level");
2575
+ table.addColumn("Category");
2576
+ table.addColumn("Message");
2577
+ const levelStyles = { error: RED, warning: YELLOW, info: BLUE };
2578
+ let hasErrors = false;
2579
+ for (const issue of issues) {
2580
+ const level = issue["level"] ?? "info";
2581
+ const levelColor = levelStyles[level];
2582
+ table.addRow(
2583
+ levelColor ? style(level, levelColor) : level,
2584
+ issue["category"] ?? "",
2585
+ issue["message"] ?? ""
2586
+ );
2587
+ if (level === "error") hasErrors = true;
2588
+ }
2589
+ table.print();
2590
+ if (hasErrors) {
2591
+ print(`
2592
+ ${style("Configuration has errors.", RED)}`);
2593
+ throw new CLIError("Config errors", 1);
2594
+ }
2595
+ }
2596
+ };
2597
+ var envCommand = {
2598
+ name: "env",
2599
+ description: "Show environment variable template",
2600
+ handler: async () => {
2601
+ const data = await apiGet("/config/env");
2602
+ const template = typeof data === "string" ? data : String(data["template"] ?? JSON.stringify(data));
2603
+ print(template);
2604
+ }
2605
+ };
2606
+ var configGroup = new CommandGroup("config", "Configuration management");
2607
+ configGroup.command(showCommand3).command(validateCommand2).command(envCommand);
2608
+
2609
+ // src/commands/control.ts
2610
+ function reqId(parsed) {
2611
+ const id = parsed.positionals[0];
2612
+ if (!id) {
2613
+ printError("Missing execution-id");
2614
+ throw new CLIError("Missing argument", 1);
2615
+ }
2616
+ return id;
2617
+ }
2618
+ var pauseCommand = {
2619
+ name: "pause",
2620
+ description: "Pause a running execution at the next yield point",
2621
+ args: [{ name: "execution-id", description: "Execution ID to pause", required: true }],
2622
+ options: { reason: { type: "string", short: "r", description: "Reason for pausing" } },
2623
+ handler: async (parsed) => {
2624
+ const id = reqId(parsed);
2625
+ const reason = parsed.values["reason"];
2626
+ const data = await apiPost(`/executions/${id}/pause`, {
2627
+ ...reason ? { body: { reason } } : {}
2628
+ });
2629
+ print(style(`Pause signal sent for execution ${id}`, GREEN));
2630
+ print(` State: ${data.state}`);
2631
+ if (data.message) print(` Message: ${data.message}`);
2632
+ }
2633
+ };
2634
+ var resumeCommand = {
2635
+ name: "resume",
2636
+ description: "Resume a paused execution",
2637
+ args: [{ name: "execution-id", description: "Execution ID to resume", required: true }],
2638
+ handler: async (parsed) => {
2639
+ const id = reqId(parsed);
2640
+ const data = await apiPost(`/executions/${id}/resume`);
2641
+ print(style(`Resume signal sent for execution ${id}`, GREEN));
2642
+ print(` State: ${data.state}`);
2643
+ }
2644
+ };
2645
+ var cancelCommand = {
2646
+ name: "cancel",
2647
+ description: "Cancel a running or paused execution",
2648
+ args: [{ name: "execution-id", description: "Execution ID to cancel", required: true }],
2649
+ options: {
2650
+ reason: { type: "string", short: "r", description: "Reason for cancelling" },
2651
+ force: { type: "boolean", short: "f", description: "Skip confirmation prompt", default: false }
2652
+ },
2653
+ handler: async (parsed) => {
2654
+ const id = reqId(parsed);
2655
+ if (parsed.values["force"] !== true) {
2656
+ printError(`Use --force to confirm cancelling execution ${id}`);
2657
+ throw new CLIError("Confirmation required", 1);
2658
+ }
2659
+ const reason = parsed.values["reason"];
2660
+ const data = await apiPost(`/executions/${id}/cancel`, {
2661
+ ...reason ? { body: { reason } } : {}
2662
+ });
2663
+ print(style(`Cancel signal sent for execution ${id}`, GREEN));
2664
+ print(` State: ${data.state}`);
2665
+ }
2666
+ };
2667
+ var statusCommand2 = {
2668
+ name: "status",
2669
+ description: "Get current execution control state",
2670
+ args: [{ name: "execution-id", description: "Execution ID to check", required: true }],
2671
+ handler: async (parsed) => {
2672
+ const id = reqId(parsed);
2673
+ const data = await apiGet(`/executions/${id}/state`);
2674
+ print(`Execution: ${id}`);
2675
+ print(`State: ${formatStatus(data.state)}`);
2676
+ }
2677
+ };
2678
+ var injectCommand = {
2679
+ name: "inject",
2680
+ description: "Inject a message into a running execution",
2681
+ args: [{ name: "execution-id", description: "Execution ID", required: true }],
2682
+ options: {
2683
+ message: { type: "string", short: "m", description: "Message to inject" }
2684
+ },
2685
+ handler: async (parsed) => {
2686
+ const id = reqId(parsed);
2687
+ const message = parsed.values["message"];
2688
+ if (!message) {
2689
+ printError("Missing --message");
2690
+ throw new CLIError("Missing option", 1);
2691
+ }
2692
+ await apiPost(`/executions/${id}/inject`, { body: { message } });
2693
+ print(style(`Message injected into execution ${id}`, GREEN));
2694
+ }
2695
+ };
2696
+ var stopCommand = {
2697
+ name: "stop",
2698
+ description: "Forcefully stop a running execution via SIGINT",
2699
+ args: [{ name: "execution-id", description: "Execution ID to stop", required: true }],
2700
+ options: {
2701
+ reason: { type: "string", short: "r", description: "Reason for stopping" },
2702
+ force: { type: "boolean", short: "f", description: "Skip confirmation prompt", default: false }
2703
+ },
2704
+ handler: async (parsed) => {
2705
+ const id = reqId(parsed);
2706
+ if (parsed.values["force"] !== true) {
2707
+ printError(`Use --force to confirm stopping execution ${id}`);
2708
+ throw new CLIError("Confirmation required", 1);
2709
+ }
2710
+ const reason = parsed.values["reason"] ?? "Stopped by user via syn stop";
2711
+ const data = await apiPost(`/executions/${id}/cancel`, {
2712
+ body: { reason }
2713
+ });
2714
+ print(style(`Stop signal sent for execution ${id}`, YELLOW));
2715
+ print(` State: ${data.state}`);
2716
+ if (data.message) print(` Message: ${data.message}`);
2717
+ }
2718
+ };
2719
+ var controlGroup = new CommandGroup("control", "Control running executions");
2720
+ controlGroup.command(pauseCommand).command(resumeCommand).command(cancelCommand).command(statusCommand2).command(injectCommand).command(stopCommand);
2721
+
2722
+ // src/commands/conversations.ts
2723
+ var showCommand4 = {
2724
+ name: "show",
2725
+ description: "Show parsed conversation log lines for a session",
2726
+ args: [{ name: "session-id", description: "Session ID", required: true }],
2727
+ options: {
2728
+ offset: { type: "string", description: "Line offset for pagination", default: "0" },
2729
+ limit: { type: "string", description: "Max lines to show (max 500)", default: "100" }
2730
+ },
2731
+ handler: async (parsed) => {
2732
+ const sessionId = parsed.positionals[0];
2733
+ if (!sessionId) {
2734
+ printError("Missing session-id");
2735
+ throw new CLIError("Missing argument", 1);
2736
+ }
2737
+ const offset = parseInt(parsed.values["offset"] ?? "0", 10);
2738
+ const limit = parseInt(parsed.values["limit"] ?? "100", 10);
2739
+ const data = await apiGet(`/conversations/${sessionId}`, {
2740
+ params: { offset, limit }
2741
+ });
2742
+ if (data.lines.length === 0) {
2743
+ printDim("No conversation lines found.");
2744
+ return;
2745
+ }
2746
+ print(`${style("Conversation:", BOLD)} ${sessionId.slice(0, 16)} ${style(`(lines ${offset + 1}-${offset + data.lines.length} of ${data.total_lines})`, DIM)}`);
2747
+ print("");
2748
+ const table = new Table();
2749
+ table.addColumn("#", { align: "right", style: DIM });
2750
+ table.addColumn("Type", { style: CYAN });
2751
+ table.addColumn("Tool", { style: DIM });
2752
+ table.addColumn("Preview");
2753
+ for (const line of data.lines) {
2754
+ table.addRow(
2755
+ String(line.line_number),
2756
+ line.event_type ?? "\u2014",
2757
+ line.tool_name ?? "\u2014",
2758
+ line.content_preview ?? "\u2014"
2759
+ );
2760
+ }
2761
+ table.print();
2762
+ if (data.total_lines > offset + data.lines.length) {
2763
+ printDim(`More lines available. Use --offset ${offset + limit}.`);
2764
+ }
2765
+ }
2766
+ };
2767
+ var metadataCommand = {
2768
+ name: "metadata",
2769
+ description: "Show metadata summary for a session's conversation",
2770
+ args: [{ name: "session-id", description: "Session ID", required: true }],
2771
+ handler: async (parsed) => {
2772
+ const sessionId = parsed.positionals[0];
2773
+ if (!sessionId) {
2774
+ printError("Missing session-id");
2775
+ throw new CLIError("Missing argument", 1);
2776
+ }
2777
+ const m = await apiGet(`/conversations/${sessionId}/metadata`);
2778
+ print(`${style("Metadata:", BOLD)} ${m.session_id}`);
2779
+ if (m.model != null) print(` Model: ${m.model}`);
2780
+ if (m.event_count != null) print(` Events: ${m.event_count.toLocaleString()}`);
2781
+ if (m.total_input_tokens != null) print(` Input tokens: ${m.total_input_tokens.toLocaleString()}`);
2782
+ if (m.total_output_tokens != null) print(` Output tokens: ${m.total_output_tokens.toLocaleString()}`);
2783
+ if (m.started_at != null) print(` Started: ${formatTimestamp(m.started_at)}`);
2784
+ if (m.completed_at != null) print(` Completed: ${formatTimestamp(m.completed_at)}`);
2785
+ if (m.size_bytes != null) print(` Log size: ${m.size_bytes.toLocaleString()} bytes`);
2786
+ if (m.tool_counts) {
2787
+ print(" Tool counts:");
2788
+ const sorted = Object.entries(m.tool_counts).sort(([, a], [, b]) => b - a);
2789
+ for (const [tool, count] of sorted) {
2790
+ print(` ${tool}: ${count}`);
2791
+ }
2792
+ }
2793
+ }
2794
+ };
2795
+ var conversationsGroup = new CommandGroup("conversations", "Inspect agent conversation logs");
2796
+ conversationsGroup.command(showCommand4).command(metadataCommand);
2797
+
2798
+ // src/commands/costs.ts
2799
+ function safeCost(v) {
2800
+ if (v.startsWith("$")) return v;
2801
+ try {
2802
+ return formatCost(v);
2803
+ } catch {
2804
+ return v;
2805
+ }
2806
+ }
2807
+ var summaryCommand = {
2808
+ name: "summary",
2809
+ description: "Show aggregated cost summary",
2810
+ handler: async () => {
2811
+ const d = await apiGet("/costs/summary");
2812
+ print(style("Cost Summary", CYAN));
2813
+ print(` ${style("Total Cost:", BOLD)} ${formatCost(d.total_cost_usd)}`);
2814
+ print(` ${style("Sessions:", BOLD)} ${d.total_sessions}`);
2815
+ print(` ${style("Executions:", BOLD)} ${d.total_executions}`);
2816
+ print(` ${style("Tokens:", BOLD)} ${formatTokens(d.total_tokens)}`);
2817
+ print(` ${style("Tool Calls:", BOLD)} ${d.total_tool_calls}`);
2818
+ const topModels = d.top_models ?? [];
2819
+ if (topModels.length > 0) {
2820
+ const table = new Table({ title: "Top Models" });
2821
+ table.addColumn("Model", { style: CYAN });
2822
+ table.addColumn("Cost", { align: "right" });
2823
+ for (const e of topModels) table.addRow(e["model"] ?? "unknown", e["cost"] ?? "$0");
2824
+ table.print();
2825
+ }
2826
+ const topSessions = d.top_sessions ?? [];
2827
+ if (topSessions.length > 0) {
2828
+ const table = new Table({ title: "Top Sessions" });
2829
+ table.addColumn("Session", { style: DIM });
2830
+ table.addColumn("Cost", { align: "right" });
2831
+ for (const e of topSessions) {
2832
+ const sid = e["session_id"] ?? "unknown";
2833
+ table.addRow(sid.length > 12 ? sid.slice(0, 12) + "..." : sid, e["cost"] ?? "$0");
2834
+ }
2835
+ table.print();
2836
+ }
2837
+ }
2838
+ };
2839
+ var sessionsCommand = {
2840
+ name: "sessions",
2841
+ description: "List cost data for sessions",
2842
+ options: {
2843
+ execution: { type: "string", short: "e", description: "Filter by execution ID" },
2844
+ limit: { type: "string", short: "n", description: "Max results", default: "50" }
2845
+ },
2846
+ handler: async (parsed) => {
2847
+ const params = buildParams({
2848
+ execution_id: parsed.values["execution"] ?? null,
2849
+ limit: parsed.values["limit"] ?? "50"
2850
+ });
2851
+ const items = await apiGetList("/costs/sessions", { params });
2852
+ if (items.length === 0) {
2853
+ printDim("No session cost data found.");
2854
+ return;
2855
+ }
2856
+ const table = new Table({ title: "Session Costs" });
2857
+ table.addColumn("Session ID", { style: DIM });
2858
+ table.addColumn("Cost", { align: "right" });
2859
+ table.addColumn("Tokens", { align: "right" });
2860
+ table.addColumn("Duration", { align: "right" });
2861
+ table.addColumn("Tools", { align: "right" });
2862
+ for (const s of items) {
2863
+ const sid = s.session_id;
2864
+ table.addRow(
2865
+ sid.length > 12 ? sid.slice(0, 12) + "..." : sid,
2866
+ formatCost(s.total_cost_usd),
2867
+ formatTokens(s.total_tokens),
2868
+ formatDuration(s.duration_ms),
2869
+ String(s.tool_calls)
2870
+ );
2871
+ }
2872
+ table.print();
2873
+ }
2874
+ };
2875
+ var sessionDetailCommand = {
2876
+ name: "session",
2877
+ description: "Show detailed cost breakdown for a session",
2878
+ args: [{ name: "session-id", description: "Session ID", required: true }],
2879
+ handler: async (parsed) => {
2880
+ const id = parsed.positionals[0];
2881
+ if (!id) {
2882
+ printError("Missing session-id");
2883
+ throw new CLIError("Missing argument", 1);
2884
+ }
2885
+ const s = await apiGet(`/costs/sessions/${id}`);
2886
+ print(style("Session Cost Detail", CYAN));
2887
+ print(` ${style("Session:", BOLD)} ${s.session_id}`);
2888
+ print(` ${style("Cost:", BOLD)} ${formatCost(s.total_cost_usd)}`);
2889
+ print(` ${style("Tokens:", BOLD)} ${formatTokens(s.total_tokens)} (in: ${formatTokens(s.input_tokens)}, out: ${formatTokens(s.output_tokens)})`);
2890
+ print(` ${style("Tool Calls:", BOLD)} ${s.tool_calls} ${style("Turns:", BOLD)} ${s.turns}`);
2891
+ print(` ${style("Duration:", BOLD)} ${formatDuration(s.duration_ms)}`);
2892
+ print(` ${style("Started:", BOLD)} ${formatTimestamp(s.started_at)}`);
2893
+ if (s.cost_by_model && Object.keys(s.cost_by_model).length > 0) print(formatBreakdown(s.cost_by_model, "Cost by Model", safeCost));
2894
+ if (s.cost_by_tool && Object.keys(s.cost_by_tool).length > 0) print(formatBreakdown(s.cost_by_tool, "Cost by Tool", safeCost));
2895
+ }
2896
+ };
2897
+ var executionsCommand = {
2898
+ name: "executions",
2899
+ description: "List cost data for workflow executions",
2900
+ options: { limit: { type: "string", short: "n", description: "Max results", default: "50" } },
2901
+ handler: async (parsed) => {
2902
+ const limit = parsed.values["limit"] ?? "50";
2903
+ const items = await apiGetList("/costs/executions", { params: { limit } });
2904
+ if (items.length === 0) {
2905
+ printDim("No execution cost data found.");
2906
+ return;
2907
+ }
2908
+ const table = new Table({ title: "Execution Costs" });
2909
+ table.addColumn("Execution ID", { style: DIM });
2910
+ table.addColumn("Cost", { align: "right" });
2911
+ table.addColumn("Sessions", { align: "right" });
2912
+ table.addColumn("Tokens", { align: "right" });
2913
+ for (const e of items) {
2914
+ const eid = e.execution_id;
2915
+ table.addRow(
2916
+ eid.length > 12 ? eid.slice(0, 12) + "..." : eid,
2917
+ formatCost(e.total_cost_usd),
2918
+ String(e.session_count),
2919
+ formatTokens(e.total_tokens)
2920
+ );
2921
+ }
2922
+ table.print();
2923
+ }
2924
+ };
2925
+ var executionDetailCommand = {
2926
+ name: "execution",
2927
+ description: "Show detailed cost breakdown for an execution",
2928
+ args: [{ name: "execution-id", description: "Execution ID", required: true }],
2929
+ handler: async (parsed) => {
2930
+ const id = parsed.positionals[0];
2931
+ if (!id) {
2932
+ printError("Missing execution-id");
2933
+ throw new CLIError("Missing argument", 1);
2934
+ }
2935
+ const e = await apiGet(`/costs/executions/${id}`);
2936
+ print(style("Execution Cost Detail", CYAN));
2937
+ print(` ${style("Execution:", BOLD)} ${e.execution_id}`);
2938
+ print(` ${style("Cost:", BOLD)} ${formatCost(e.total_cost_usd)}`);
2939
+ print(` ${style("Sessions:", BOLD)} ${e.session_count}`);
2940
+ print(` ${style("Tokens:", BOLD)} ${formatTokens(e.total_tokens)} (in: ${formatTokens(e.input_tokens)}, out: ${formatTokens(e.output_tokens)})`);
2941
+ print(` ${style("Duration:", BOLD)} ${formatDuration(e.duration_ms)}`);
2942
+ print(` ${style("Started:", BOLD)} ${formatTimestamp(e.started_at)}`);
2943
+ if (e.cost_by_phase && Object.keys(e.cost_by_phase).length > 0) print(formatBreakdown(e.cost_by_phase, "Cost by Phase", safeCost));
2944
+ if (e.cost_by_model && Object.keys(e.cost_by_model).length > 0) print(formatBreakdown(e.cost_by_model, "Cost by Model", safeCost));
2945
+ if (e.cost_by_tool && Object.keys(e.cost_by_tool).length > 0) print(formatBreakdown(e.cost_by_tool, "Cost by Tool", safeCost));
2946
+ }
2947
+ };
2948
+ var costsGroup = new CommandGroup("costs", "View cost tracking data for sessions and executions");
2949
+ costsGroup.command(summaryCommand).command(sessionsCommand).command(sessionDetailCommand).command(executionsCommand).command(executionDetailCommand);
2950
+
2951
+ // src/commands/events.ts
2952
+ function reqSessionId(parsed) {
2953
+ const id = parsed.positionals[0];
2954
+ if (!id) {
2955
+ printError("Missing session-id");
2956
+ throw new CLIError("Missing argument", 1);
2957
+ }
2958
+ return id;
2959
+ }
2960
+ var recentCommand = {
2961
+ name: "recent",
2962
+ description: "Show recent domain events across all sessions",
2963
+ options: { limit: { type: "string", description: "Max events (max 200)", default: "50" } },
2964
+ handler: async (parsed) => {
2965
+ const limit = parsed.values["limit"] ?? "50";
2966
+ const data = await apiGet("/events/recent", { params: { limit } });
2967
+ if (data.events.length === 0) {
2968
+ printDim("No recent events.");
2969
+ return;
2970
+ }
2971
+ const table = new Table({ title: "Recent Events" });
2972
+ table.addColumn("Time");
2973
+ table.addColumn("Type", { style: CYAN });
2974
+ table.addColumn("Session", { style: DIM });
2975
+ table.addColumn("Execution", { style: DIM });
2976
+ for (const ev of data.events) {
2977
+ table.addRow(
2978
+ formatTimestamp(String(ev.time ?? "")),
2979
+ ev.event_type,
2980
+ (ev.session_id ?? "").slice(0, 12),
2981
+ (ev.execution_id ?? "").slice(0, 12)
2982
+ );
2983
+ }
2984
+ table.print();
2985
+ if (data.has_more) printDim("More events available. Use --limit to fetch more.");
2986
+ }
2987
+ };
2988
+ var sessionEventsCommand = {
2989
+ name: "session",
2990
+ description: "List events for a specific session",
2991
+ args: [{ name: "session-id", description: "Session ID", required: true }],
2992
+ options: {
2993
+ type: { type: "string", short: "t", description: "Filter by event type" },
2994
+ limit: { type: "string", description: "Max events (max 1000)", default: "100" },
2995
+ offset: { type: "string", description: "Pagination offset", default: "0" }
2996
+ },
2997
+ handler: async (parsed) => {
2998
+ const sid = reqSessionId(parsed);
2999
+ const params = buildParams({
3000
+ limit: parsed.values["limit"] ?? "100",
3001
+ offset: parsed.values["offset"] ?? "0",
3002
+ event_type: parsed.values["type"] ?? null
3003
+ });
3004
+ const data = await apiGet(`/events/sessions/${sid}`, { params });
3005
+ if (data.events.length === 0) {
3006
+ printDim(`No events for session ${sid}.`);
3007
+ return;
3008
+ }
3009
+ const table = new Table({ title: `Events: ${sid.slice(0, 12)}` });
3010
+ table.addColumn("Time");
3011
+ table.addColumn("Type", { style: CYAN });
3012
+ table.addColumn("Phase", { style: DIM });
3013
+ for (const ev of data.events) {
3014
+ table.addRow(
3015
+ formatTimestamp(String(ev.time ?? "")),
3016
+ ev.event_type,
3017
+ (ev.phase_id ?? "").slice(0, 12)
3018
+ );
3019
+ }
3020
+ table.print();
3021
+ const offset = parseInt(parsed.values["offset"] ?? "0", 10);
3022
+ const limit = parseInt(parsed.values["limit"] ?? "100", 10);
3023
+ if (data.has_more) printDim(`More events available. Use --offset ${offset + limit}.`);
3024
+ }
3025
+ };
3026
+ var timelineCommand = {
3027
+ name: "timeline",
3028
+ description: "Show a chronological tool-call timeline for a session",
3029
+ args: [{ name: "session-id", description: "Session ID", required: true }],
3030
+ options: { limit: { type: "string", description: "Max entries (max 500)", default: "100" } },
3031
+ handler: async (parsed) => {
3032
+ const sid = reqSessionId(parsed);
3033
+ const limit = parsed.values["limit"] ?? "100";
3034
+ const entries = await apiGetList(`/events/sessions/${sid}/timeline`, { params: { limit } });
3035
+ if (entries.length === 0) {
3036
+ printDim("No timeline entries.");
3037
+ return;
3038
+ }
3039
+ const table = new Table({ title: `Timeline: ${sid.slice(0, 12)}` });
3040
+ table.addColumn("Time");
3041
+ table.addColumn("Event", { style: CYAN });
3042
+ table.addColumn("Tool");
3043
+ table.addColumn("Duration", { align: "right" });
3044
+ table.addColumn("\u2713");
3045
+ for (const e of entries) {
3046
+ table.addRow(
3047
+ formatTimestamp(String(e.time ?? "")),
3048
+ e.event_type,
3049
+ e.tool_name ?? "\u2014",
3050
+ e.duration_ms != null ? formatDuration(e.duration_ms) : "\u2014",
3051
+ e.success === true ? style("\u2713", GREEN) : e.success === false ? style("\u2717", RED) : "\u2014"
3052
+ );
3053
+ }
3054
+ table.print();
3055
+ }
3056
+ };
3057
+ var costsCommand = {
3058
+ name: "costs",
3059
+ description: "Show token usage and cost breakdown for a session",
3060
+ args: [{ name: "session-id", description: "Session ID", required: true }],
3061
+ handler: async (parsed) => {
3062
+ const sid = reqSessionId(parsed);
3063
+ const d = await apiGet(`/events/sessions/${sid}/costs`);
3064
+ print(`${style("Session costs:", BOLD)} ${d.session_id}`);
3065
+ print(` Input tokens: ${d.input_tokens.toLocaleString()}`);
3066
+ print(` Output tokens: ${d.output_tokens.toLocaleString()}`);
3067
+ print(` Total tokens: ${d.total_tokens.toLocaleString()}`);
3068
+ if (d.cache_creation_tokens) print(` Cache creation: ${d.cache_creation_tokens.toLocaleString()}`);
3069
+ if (d.cache_read_tokens) print(` Cache read: ${d.cache_read_tokens.toLocaleString()}`);
3070
+ if (d.estimated_cost_usd != null)
3071
+ print(` Estimated cost: ${formatCost(String(d.estimated_cost_usd))}`);
3072
+ }
3073
+ };
3074
+ var toolsCommand = {
3075
+ name: "tools",
3076
+ description: "Show tool usage summary for a session",
3077
+ args: [{ name: "session-id", description: "Session ID", required: true }],
3078
+ handler: async (parsed) => {
3079
+ const sid = reqSessionId(parsed);
3080
+ const tools = await apiGetList(`/events/sessions/${sid}/tools`);
3081
+ if (tools.length === 0) {
3082
+ printDim("No tool usage recorded.");
3083
+ return;
3084
+ }
3085
+ const table = new Table({ title: `Tool Usage: ${sid.slice(0, 12)}` });
3086
+ table.addColumn("Tool", { style: CYAN });
3087
+ table.addColumn("Calls", { align: "right" });
3088
+ table.addColumn("Success", { align: "right" });
3089
+ table.addColumn("Errors", { align: "right" });
3090
+ table.addColumn("Avg Duration", { align: "right" });
3091
+ const sorted = [...tools].sort((a, b) => b.call_count - a.call_count);
3092
+ for (const t of sorted) {
3093
+ table.addRow(
3094
+ t.tool_name,
3095
+ String(t.call_count),
3096
+ String(t.success_count),
3097
+ String(t.error_count),
3098
+ t.avg_duration_ms ? formatDuration(t.avg_duration_ms) : "\u2014"
3099
+ );
3100
+ }
3101
+ table.print();
3102
+ }
3103
+ };
3104
+ var eventsGroup = new CommandGroup("events", "Query domain events and session observability");
3105
+ eventsGroup.command(recentCommand).command(sessionEventsCommand).command(timelineCommand).command(costsCommand).command(toolsCommand);
3106
+
3107
+ // src/commands/execution.ts
3108
+ var listCommand4 = {
3109
+ name: "list",
3110
+ description: "List all workflow executions",
3111
+ options: {
3112
+ status: { type: "string", short: "s", description: "Filter by status" },
3113
+ page: { type: "string", description: "Page number", default: "1" },
3114
+ "page-size": { type: "string", description: "Items per page (max 100)", default: "50" }
3115
+ },
3116
+ handler: async (parsed) => {
3117
+ const params = buildParams({
3118
+ status: parsed.values["status"] ?? null,
3119
+ page: parsed.values["page"] ?? "1",
3120
+ page_size: parsed.values["page-size"] ?? "50"
3121
+ });
3122
+ const data = await apiGet("/executions", { params });
3123
+ const { executions, total } = data;
3124
+ const page = parseInt(parsed.values["page"] ?? "1", 10);
3125
+ const pageSize = parseInt(parsed.values["page-size"] ?? "50", 10);
3126
+ if (executions.length === 0) {
3127
+ printDim("No executions found.");
3128
+ return;
3129
+ }
3130
+ const table = new Table({ title: `Executions (page ${page}, ${total} total)` });
3131
+ table.addColumn("ID", { style: CYAN });
3132
+ table.addColumn("Workflow");
3133
+ table.addColumn("Status");
3134
+ table.addColumn("Started");
3135
+ table.addColumn("Phases", { align: "right" });
3136
+ table.addColumn("Tokens", { align: "right" });
3137
+ table.addColumn("Cost", { align: "right" });
3138
+ for (const ex of executions) {
3139
+ table.addRow(
3140
+ ex.workflow_execution_id,
3141
+ ex.workflow_name,
3142
+ formatStatus(ex.status),
3143
+ formatTimestamp(ex.started_at),
3144
+ `${ex.completed_phases}/${ex.total_phases}`,
3145
+ formatTokens(ex.total_tokens),
3146
+ formatCost(ex.total_cost_usd)
3147
+ );
3148
+ }
3149
+ table.print();
3150
+ if (total > page * pageSize) printDim(`Showing page ${page}. Use --page ${page + 1} for more.`);
3151
+ }
3152
+ };
3153
+ var showCommand5 = {
3154
+ name: "show",
3155
+ description: "Show detailed information about a single execution",
3156
+ args: [{ name: "execution-id", description: "Execution ID", required: true }],
3157
+ handler: async (parsed) => {
3158
+ const id = parsed.positionals[0];
3159
+ if (!id) {
3160
+ printError("Missing execution-id");
3161
+ throw new CLIError("Missing argument", 1);
3162
+ }
3163
+ const ex = await apiGet(`/executions/${id}`);
3164
+ print(`${style("Execution:", BOLD)} ${ex.workflow_execution_id}`);
3165
+ print(` Workflow: ${ex.workflow_name}`);
3166
+ print(` Status: ${formatStatus(ex.status)}`);
3167
+ print(` Started: ${formatTimestamp(ex.started_at)}`);
3168
+ if (ex.completed_at) print(` Completed: ${formatTimestamp(ex.completed_at)}`);
3169
+ print(` Tokens: ${formatTokens(ex.total_tokens)}`);
3170
+ print(` Cost: ${formatCost(ex.total_cost_usd)}`);
3171
+ if (ex.error_message) print(` ${style("Error:", RED)} ${ex.error_message}`);
3172
+ const phases = ex.phases ?? [];
3173
+ if (phases.length > 0) {
3174
+ print("");
3175
+ const table = new Table({ title: "Phases" });
3176
+ table.addColumn("#", { align: "right", style: DIM });
3177
+ table.addColumn("Name");
3178
+ table.addColumn("Status");
3179
+ table.addColumn("Started");
3180
+ table.addColumn("Tokens", { align: "right" });
3181
+ table.addColumn("Cost", { align: "right" });
3182
+ for (let i = 0; i < phases.length; i++) {
3183
+ const ph = phases[i];
3184
+ table.addRow(
3185
+ String(i + 1),
3186
+ ph.name,
3187
+ formatStatus(ph.status),
3188
+ formatTimestamp(ph.started_at),
3189
+ formatTokens(ph.total_tokens),
3190
+ formatCost(ph.cost_usd)
3191
+ );
3192
+ }
3193
+ table.print();
3194
+ }
3195
+ }
3196
+ };
3197
+ var executionGroup = new CommandGroup("execution", "List and inspect workflow executions");
3198
+ executionGroup.command(listCommand4).command(showCommand5);
3199
+
3200
+ // src/commands/insights.ts
3201
+ var SPARKLINE_CHARS = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
3202
+ function renderSparkline(values) {
3203
+ if (values.length === 0) return "";
3204
+ const max = Math.max(...values);
3205
+ if (max === 0) return SPARKLINE_CHARS[0].repeat(values.length);
3206
+ return values.map((v) => {
3207
+ const idx = Math.min(Math.round(v / max * 8), 8);
3208
+ return SPARKLINE_CHARS[idx] ?? " ";
3209
+ }).join("");
3210
+ }
3211
+ var overviewCommand = {
3212
+ name: "overview",
3213
+ description: "Show global overview of systems and health",
3214
+ handler: async () => {
3215
+ const d = await apiGet("/insights/overview");
3216
+ print(style("System Overview", CYAN));
3217
+ print(` ${style("Total systems:", BOLD)} ${d["total_systems"] ?? 0}`);
3218
+ print(` ${style("Total repos:", BOLD)} ${d["total_repos"] ?? 0}`);
3219
+ print(` ${style("Active sessions:", BOLD)} ${d["active_sessions"] ?? 0}`);
3220
+ print(` ${style("Total executions:", BOLD)} ${d["total_executions"] ?? 0}`);
3221
+ const health = d["health"];
3222
+ if (health) {
3223
+ const h = String(health["status"] ?? "unknown");
3224
+ const color = h === "healthy" ? GREEN : h === "degraded" ? YELLOW : RED;
3225
+ print(` ${style("Health:", BOLD)} ${style(h, color)}`);
3226
+ }
3227
+ const systems = d["systems"] ?? [];
3228
+ if (systems.length > 0) {
3229
+ const table = new Table({ title: "Systems" });
3230
+ table.addColumn("Name", { style: CYAN });
3231
+ table.addColumn("Repos", { align: "right" });
3232
+ table.addColumn("Health");
3233
+ table.addColumn("Executions", { align: "right" });
3234
+ for (const sys of systems) {
3235
+ const sh = String(sys["health"] ?? "unknown");
3236
+ const sc = sh === "healthy" ? GREEN : sh === "degraded" ? YELLOW : RED;
3237
+ table.addRow(
3238
+ String(sys["name"] ?? ""),
3239
+ String(sys["repo_count"] ?? 0),
3240
+ style(sh, sc),
3241
+ String(sys["execution_count"] ?? 0)
3242
+ );
3243
+ }
3244
+ table.print();
3245
+ }
3246
+ }
3247
+ };
3248
+ var costCommand = {
3249
+ name: "cost",
3250
+ description: "Show global cost breakdown",
3251
+ options: {
3252
+ days: { type: "string", short: "d", description: "Number of days to look back", default: "30" }
3253
+ },
3254
+ handler: async (parsed) => {
3255
+ const days = parsed.values["days"] ?? "30";
3256
+ const d = await apiGet("/insights/costs", { params: { days } });
3257
+ print(style(`Cost Overview (last ${days} days)`, CYAN));
3258
+ print(` ${style("Total cost:", BOLD)} ${formatCost(String(d["total_cost_usd"] ?? "0"))}`);
3259
+ print(` ${style("Total tokens:", BOLD)} ${formatTokens(Number(d["total_tokens"] ?? 0))}`);
3260
+ const byRepo = d["by_repo"] ?? [];
3261
+ if (byRepo.length > 0) {
3262
+ const table = new Table({ title: "Cost by Repository" });
3263
+ table.addColumn("Repository", { style: CYAN });
3264
+ table.addColumn("Cost", { align: "right" });
3265
+ table.addColumn("Tokens", { align: "right" });
3266
+ for (const r of byRepo) {
3267
+ table.addRow(
3268
+ String(r["repo_name"] ?? r["repo_id"] ?? ""),
3269
+ formatCost(String(r["cost_usd"] ?? "0")),
3270
+ formatTokens(Number(r["tokens"] ?? 0))
3271
+ );
3272
+ }
3273
+ table.print();
3274
+ }
3275
+ const byModel = d["by_model"] ?? [];
3276
+ if (byModel.length > 0) {
3277
+ const table = new Table({ title: "Cost by Model" });
3278
+ table.addColumn("Model", { style: CYAN });
3279
+ table.addColumn("Cost", { align: "right" });
3280
+ for (const m of byModel) {
3281
+ table.addRow(
3282
+ String(m["model"] ?? ""),
3283
+ formatCost(String(m["cost_usd"] ?? "0"))
3284
+ );
3285
+ }
3286
+ table.print();
3287
+ }
3288
+ }
3289
+ };
3290
+ var heatmapCommand = {
3291
+ name: "heatmap",
3292
+ description: "Show activity heatmap over time",
3293
+ options: {
3294
+ days: { type: "string", short: "d", description: "Number of days to show", default: "14" }
3295
+ },
3296
+ handler: async (parsed) => {
3297
+ const days = parsed.values["days"] ?? "14";
3298
+ const d = await apiGet("/insights/heatmap", { params: { days } });
3299
+ const buckets = d["buckets"] ?? [];
3300
+ if (buckets.length === 0) {
3301
+ printDim("No activity data.");
3302
+ return;
3303
+ }
3304
+ const values = buckets.map((b) => Number(b["count"] ?? 0));
3305
+ print(style(`Activity Heatmap (last ${days} days)`, CYAN));
3306
+ print(` ${renderSparkline(values)}`);
3307
+ print(style(` ${values.reduce((a, b) => a + b, 0)} total events`, DIM));
3308
+ const topRepos = d["top_repos"] ?? [];
3309
+ if (topRepos.length > 0) {
3310
+ const table = new Table({ title: "Top Repositories" });
3311
+ table.addColumn("Repository", { style: CYAN });
3312
+ table.addColumn("Events", { align: "right" });
3313
+ for (const r of topRepos.slice(0, 5)) {
3314
+ table.addRow(
3315
+ String(r["repo_name"] ?? r["repo_id"] ?? ""),
3316
+ String(r["count"] ?? 0)
3317
+ );
3318
+ }
3319
+ table.print();
3320
+ }
3321
+ }
3322
+ };
3323
+ var insightsGroup = new CommandGroup("insights", "Global system insights and cost analysis");
3324
+ insightsGroup.command(overviewCommand).command(costCommand).command(heatmapCommand);
3325
+
3326
+ // src/commands/metrics.ts
3327
+ var showCommand6 = {
3328
+ name: "show",
3329
+ description: "Show aggregated workflow and session metrics",
3330
+ options: {
3331
+ workflow: { type: "string", short: "w", description: "Filter by workflow ID" }
3332
+ },
3333
+ handler: async (parsed) => {
3334
+ const workflow = parsed.values["workflow"] ?? null;
3335
+ const params = {};
3336
+ if (workflow) params["workflow_id"] = workflow;
3337
+ const d = await apiGet("/metrics", { params });
3338
+ print(style("Aggregated Metrics", CYAN));
3339
+ print(` ${style("Workflows:", BOLD)} ${d.total_workflows}`);
3340
+ print(` ${style("Sessions:", BOLD)} ${d.total_sessions}`);
3341
+ print(` ${style("Input tokens:", BOLD)} ${formatTokens(d.total_input_tokens)}`);
3342
+ print(` ${style("Output tokens:", BOLD)} ${formatTokens(d.total_output_tokens)}`);
3343
+ print(` ${style("Total cost:", BOLD)} ${formatCost(d.total_cost_usd)}`);
3344
+ print(` ${style("Artifacts:", BOLD)} ${d.total_artifacts}`);
3345
+ const phases = d.phases ?? [];
3346
+ if (phases.length > 0) {
3347
+ const table = new Table({ title: "Phase Metrics" });
3348
+ table.addColumn("Phase");
3349
+ table.addColumn("Status");
3350
+ table.addColumn("Tokens", { align: "right" });
3351
+ table.addColumn("Cost", { align: "right" });
3352
+ table.addColumn("Duration", { align: "right" });
3353
+ table.addColumn("Artifacts", { align: "right" });
3354
+ for (const ph of phases) {
3355
+ table.addRow(
3356
+ ph.phase_name,
3357
+ formatStatus(ph.status),
3358
+ formatTokens(ph.total_tokens),
3359
+ formatCost(ph.cost_usd),
3360
+ formatDuration(ph.duration_seconds * 1e3),
3361
+ String(ph.artifact_count)
3362
+ );
3363
+ }
3364
+ table.print();
3365
+ }
3366
+ if (phases.length === 0 && !d.total_workflows) {
3367
+ printDim("No metrics data available.");
3368
+ }
3369
+ }
3370
+ };
3371
+ var metricsGroup = new CommandGroup("metrics", "View aggregated workflow and session metrics");
3372
+ metricsGroup.command(showCommand6);
3373
+
3374
+ // src/commands/observe.ts
3375
+ function reqSessionId2(parsed) {
3376
+ const id = parsed.positionals[0];
3377
+ if (!id) {
3378
+ printError("Missing session-id");
3379
+ throw new CLIError("Missing argument", 1);
3380
+ }
3381
+ return id;
3382
+ }
3383
+ var toolTimelineCommand = {
3384
+ name: "tools",
3385
+ description: "Show tool execution timeline for a session",
3386
+ args: [{ name: "session-id", description: "Session ID", required: true }],
3387
+ options: {
3388
+ limit: { type: "string", description: "Max entries", default: "100" }
3389
+ },
3390
+ handler: async (parsed) => {
3391
+ const sid = reqSessionId2(parsed);
3392
+ const limit = parsed.values["limit"] ?? "100";
3393
+ const entries = await apiGetList(`/observe/sessions/${sid}/tools`, { params: { limit } });
3394
+ if (entries.length === 0) {
3395
+ printDim("No tool timeline entries.");
3396
+ return;
3397
+ }
3398
+ const table = new Table({ title: `Tool Timeline: ${sid.slice(0, 12)}` });
3399
+ table.addColumn("Time");
3400
+ table.addColumn("Tool", { style: CYAN });
3401
+ table.addColumn("Duration", { align: "right" });
3402
+ table.addColumn("Status");
3403
+ for (const e of entries) {
3404
+ const dur = e["duration_ms"];
3405
+ const success = e["success"];
3406
+ table.addRow(
3407
+ formatTimestamp(String(e["time"] ?? "")),
3408
+ String(e["tool_name"] ?? ""),
3409
+ dur !== null && dur !== void 0 ? formatDuration(Number(dur)) : "\u2014",
3410
+ success === true ? style("ok", GREEN) : success === false ? style("error", RED) : style("\u2014", DIM)
3411
+ );
3412
+ }
3413
+ table.print();
3414
+ }
3415
+ };
3416
+ var tokenMetricsCommand = {
3417
+ name: "tokens",
3418
+ description: "Show token breakdown for a session",
3419
+ args: [{ name: "session-id", description: "Session ID", required: true }],
3420
+ handler: async (parsed) => {
3421
+ const sid = reqSessionId2(parsed);
3422
+ const d = await apiGet(`/observe/sessions/${sid}/tokens`);
3423
+ print(`${style("Token Metrics:", BOLD)} ${d["session_id"] ?? sid}`);
3424
+ print(` Input tokens: ${Number(d["input_tokens"] ?? 0).toLocaleString()}`);
3425
+ print(` Output tokens: ${Number(d["output_tokens"] ?? 0).toLocaleString()}`);
3426
+ print(` Total tokens: ${Number(d["total_tokens"] ?? 0).toLocaleString()}`);
3427
+ if (d["cache_creation_tokens"]) print(` Cache creation: ${Number(d["cache_creation_tokens"]).toLocaleString()}`);
3428
+ if (d["cache_read_tokens"]) print(` Cache read: ${Number(d["cache_read_tokens"]).toLocaleString()}`);
3429
+ if (d["estimated_cost_usd"] !== null && d["estimated_cost_usd"] !== void 0)
3430
+ print(` Estimated cost: ${formatCost(String(d["estimated_cost_usd"]))}`);
3431
+ }
3432
+ };
3433
+ var observeGroup = new CommandGroup("observe", "Session observability \u2014 tool timelines and token metrics");
3434
+ observeGroup.command(toolTimelineCommand).command(tokenMetricsCommand);
3435
+
3436
+ // src/commands/org.ts
3437
+ function reqId2(parsed) {
3438
+ const id = parsed.positionals[0];
3439
+ if (!id) {
3440
+ printError("Missing org-id");
3441
+ throw new CLIError("Missing argument", 1);
3442
+ }
3443
+ return id;
3444
+ }
3445
+ var createCommand3 = {
3446
+ name: "create",
3447
+ description: "Create a new organization",
3448
+ options: {
3449
+ name: { type: "string", short: "n", description: "Organization name" },
3450
+ slug: { type: "string", short: "s", description: "URL-safe slug" }
3451
+ },
3452
+ handler: async (parsed) => {
3453
+ const name = parsed.values["name"];
3454
+ const slug = parsed.values["slug"];
3455
+ if (!name) {
3456
+ printError("Missing --name");
3457
+ throw new CLIError("Missing option", 1);
3458
+ }
3459
+ const body = { name };
3460
+ if (slug) body["slug"] = slug;
3461
+ const d = await apiPost("/organizations", { body, expected: [200, 201] });
3462
+ printSuccess(`Organization created: ${d["organization_id"] ?? ""}`);
3463
+ print(` Name: ${d["name"] ?? name}`);
3464
+ if (d["slug"]) print(` Slug: ${String(d["slug"])}`);
3465
+ }
3466
+ };
3467
+ var listCommand5 = {
3468
+ name: "list",
3469
+ description: "List all organizations",
3470
+ handler: async () => {
3471
+ const items = await apiGetList("/organizations");
3472
+ if (items.length === 0) {
3473
+ printDim("No organizations found.");
3474
+ return;
3475
+ }
3476
+ const table = new Table({ title: "Organizations" });
3477
+ table.addColumn("ID", { style: CYAN });
3478
+ table.addColumn("Name");
3479
+ table.addColumn("Slug", { style: DIM });
3480
+ table.addColumn("Systems", { align: "right" });
3481
+ table.addColumn("Repos", { align: "right" });
3482
+ for (const o of items) {
3483
+ table.addRow(
3484
+ String(o["organization_id"] ?? ""),
3485
+ String(o["name"] ?? ""),
3486
+ String(o["slug"] ?? ""),
3487
+ String(o["system_count"] ?? 0),
3488
+ String(o["repo_count"] ?? 0)
3489
+ );
3490
+ }
3491
+ table.print();
3492
+ }
3493
+ };
3494
+ var showCommand7 = {
3495
+ name: "show",
3496
+ description: "Show organization details",
3497
+ args: [{ name: "org-id", description: "Organization ID", required: true }],
3498
+ handler: async (parsed) => {
3499
+ const id = reqId2(parsed);
3500
+ const d = await apiGet(`/organizations/${id}`);
3501
+ print(`${style("Organization:", BOLD)} ${d["name"] ?? id}`);
3502
+ print(` ID: ${d["organization_id"] ?? id}`);
3503
+ if (d["slug"]) print(` Slug: ${String(d["slug"])}`);
3504
+ print(` Systems: ${d["system_count"] ?? 0}`);
3505
+ print(` Repos: ${d["repo_count"] ?? 0}`);
3506
+ }
3507
+ };
3508
+ var updateCommand2 = {
3509
+ name: "update",
3510
+ description: "Update an organization",
3511
+ args: [{ name: "org-id", description: "Organization ID", required: true }],
3512
+ options: {
3513
+ name: { type: "string", short: "n", description: "New name" },
3514
+ slug: { type: "string", short: "s", description: "New slug" }
3515
+ },
3516
+ handler: async (parsed) => {
3517
+ const id = reqId2(parsed);
3518
+ const body = {};
3519
+ const name = parsed.values["name"];
3520
+ const slug = parsed.values["slug"];
3521
+ if (name) body["name"] = name;
3522
+ if (slug) body["slug"] = slug;
3523
+ if (Object.keys(body).length === 0) {
3524
+ printError("Nothing to update. Use --name or --slug.");
3525
+ throw new CLIError("No updates", 1);
3526
+ }
3527
+ await apiPut(`/organizations/${id}`, { body });
3528
+ printSuccess(`Organization ${id} updated.`);
3529
+ }
3530
+ };
3531
+ var deleteCommand2 = {
3532
+ name: "delete",
3533
+ description: "Delete an organization",
3534
+ args: [{ name: "org-id", description: "Organization ID", required: true }],
3535
+ options: {
3536
+ force: { type: "boolean", short: "f", description: "Skip confirmation", default: false }
3537
+ },
3538
+ handler: async (parsed) => {
3539
+ const id = reqId2(parsed);
3540
+ if (parsed.values["force"] !== true) {
3541
+ printError(`Use --force to confirm deleting organization ${id}`);
3542
+ throw new CLIError("Confirmation required", 1);
3543
+ }
3544
+ await apiDelete(`/organizations/${id}`);
3545
+ printSuccess(`Organization ${id} deleted.`);
3546
+ }
3547
+ };
3548
+ var orgGroup = new CommandGroup("org", "Manage organizations");
3549
+ orgGroup.command(createCommand3).command(listCommand5).command(showCommand7).command(updateCommand2).command(deleteCommand2);
3550
+
3551
+ // src/commands/repo.ts
3552
+ function reqRepoId(parsed) {
3553
+ const id = parsed.positionals[0];
3554
+ if (!id) {
3555
+ printError("Missing repo-id");
3556
+ throw new CLIError("Missing argument", 1);
3557
+ }
3558
+ return id;
3559
+ }
3560
+ var registerCommand = {
3561
+ name: "register",
3562
+ description: "Register a repository",
3563
+ options: {
3564
+ url: { type: "string", short: "u", description: "Repository URL (owner/repo or full URL)" },
3565
+ system: { type: "string", short: "s", description: "Assign to system ID" },
3566
+ org: { type: "string", short: "o", description: "Organization ID" }
3567
+ },
3568
+ handler: async (parsed) => {
3569
+ const url = parsed.values["url"];
3570
+ if (!url) {
3571
+ printError("Missing --url");
3572
+ throw new CLIError("Missing option", 1);
3573
+ }
3574
+ const body = { repo_url: url };
3575
+ const system = parsed.values["system"];
3576
+ const org = parsed.values["org"];
3577
+ if (system) body["system_id"] = system;
3578
+ if (org) body["organization_id"] = org;
3579
+ const d = await apiPost("/repos", { body, expected: [200, 201] });
3580
+ printSuccess(`Repository registered: ${d["repo_id"] ?? ""}`);
3581
+ print(` URL: ${d["repo_url"] ?? url}`);
3582
+ if (d["system_id"]) print(` System: ${String(d["system_id"])}`);
3583
+ }
3584
+ };
3585
+ var listCommand6 = {
3586
+ name: "list",
3587
+ description: "List repositories",
3588
+ options: {
3589
+ org: { type: "string", short: "o", description: "Filter by organization" },
3590
+ system: { type: "string", short: "s", description: "Filter by system" }
3591
+ },
3592
+ handler: async (parsed) => {
3593
+ const params = buildParams({
3594
+ organization_id: parsed.values["org"] ?? null,
3595
+ system_id: parsed.values["system"] ?? null
3596
+ });
3597
+ const items = await apiGetList("/repos", { params });
3598
+ if (items.length === 0) {
3599
+ printDim("No repositories found.");
3600
+ return;
3601
+ }
3602
+ const table = new Table({ title: "Repositories" });
3603
+ table.addColumn("ID", { style: CYAN });
3604
+ table.addColumn("URL");
3605
+ table.addColumn("System", { style: DIM });
3606
+ table.addColumn("Status");
3607
+ for (const r of items) {
3608
+ table.addRow(
3609
+ String(r["repo_id"] ?? ""),
3610
+ String(r["repo_url"] ?? ""),
3611
+ String(r["system_id"] ?? "\u2014"),
3612
+ formatStatus(String(r["status"] ?? ""))
3613
+ );
3614
+ }
3615
+ table.print();
3616
+ }
3617
+ };
3618
+ var showCommand8 = {
3619
+ name: "show",
3620
+ description: "Show repository details",
3621
+ args: [{ name: "repo-id", description: "Repository ID", required: true }],
3622
+ handler: async (parsed) => {
3623
+ const id = reqRepoId(parsed);
3624
+ const d = await apiGet(`/repos/${id}`);
3625
+ print(`${style("Repository:", BOLD)} ${d["repo_url"] ?? id}`);
3626
+ print(` ID: ${d["repo_id"] ?? id}`);
3627
+ if (d["system_id"]) print(` System: ${String(d["system_id"])}`);
3628
+ if (d["organization_id"]) print(` Org: ${String(d["organization_id"])}`);
3629
+ print(` Status: ${formatStatus(String(d["status"] ?? ""))}`);
3630
+ }
3631
+ };
3632
+ var assignCommand = {
3633
+ name: "assign",
3634
+ description: "Assign a repository to a system",
3635
+ args: [{ name: "repo-id", description: "Repository ID", required: true }],
3636
+ options: {
3637
+ system: { type: "string", short: "s", description: "System ID to assign to" }
3638
+ },
3639
+ handler: async (parsed) => {
3640
+ const id = reqRepoId(parsed);
3641
+ const system = parsed.values["system"];
3642
+ if (!system) {
3643
+ printError("Missing --system");
3644
+ throw new CLIError("Missing option", 1);
3645
+ }
3646
+ await apiPut(`/repos/${id}/assign`, { body: { system_id: system } });
3647
+ printSuccess(`Repository ${id} assigned to system ${system}.`);
3648
+ }
3649
+ };
3650
+ var unassignCommand = {
3651
+ name: "unassign",
3652
+ description: "Remove repository from its system",
3653
+ args: [{ name: "repo-id", description: "Repository ID", required: true }],
3654
+ handler: async (parsed) => {
3655
+ const id = reqRepoId(parsed);
3656
+ await apiPost(`/repos/${id}/unassign`);
3657
+ printSuccess(`Repository ${id} unassigned from system.`);
3658
+ }
3659
+ };
3660
+ var healthCommand2 = {
3661
+ name: "health",
3662
+ description: "Show health metrics for a repository",
3663
+ args: [{ name: "repo-id", description: "Repository ID", required: true }],
3664
+ handler: async (parsed) => {
3665
+ const id = reqRepoId(parsed);
3666
+ const d = await apiGet(`/repos/${id}/health`);
3667
+ const h = String(d["health_status"] ?? "unknown");
3668
+ const color = h === "healthy" ? GREEN : h === "degraded" ? YELLOW : RED;
3669
+ print(`${style("Repository Health:", BOLD)} ${id}`);
3670
+ print(` Status: ${style(h, color)}`);
3671
+ print(` Success rate: ${d["success_rate"] != null ? `${(Number(d["success_rate"]) * 100).toFixed(1)}%` : "\u2014"}`);
3672
+ print(` Avg duration: ${d["avg_duration_ms"] != null ? formatDuration(Number(d["avg_duration_ms"])) : "\u2014"}`);
3673
+ print(` Total runs: ${d["total_executions"] ?? 0}`);
3674
+ const trends = d["trends"] ?? [];
3675
+ if (trends.length > 0) {
3676
+ const table = new Table({ title: "Trends" });
3677
+ table.addColumn("Period");
3678
+ table.addColumn("Runs", { align: "right" });
3679
+ table.addColumn("Success", { align: "right" });
3680
+ table.addColumn("Cost", { align: "right" });
3681
+ for (const t of trends) {
3682
+ table.addRow(
3683
+ String(t["period"] ?? ""),
3684
+ String(t["count"] ?? 0),
3685
+ String(t["success_count"] ?? 0),
3686
+ formatCost(String(t["cost_usd"] ?? "0"))
3687
+ );
3688
+ }
3689
+ table.print();
3690
+ }
3691
+ }
3692
+ };
3693
+ var costCommand2 = {
3694
+ name: "cost",
3695
+ description: "Show cost breakdown for a repository",
3696
+ args: [{ name: "repo-id", description: "Repository ID", required: true }],
3697
+ handler: async (parsed) => {
3698
+ const id = reqRepoId(parsed);
3699
+ const d = await apiGet(`/repos/${id}/costs`);
3700
+ print(`${style("Repository Costs:", BOLD)} ${id}`);
3701
+ print(` Total cost: ${formatCost(String(d["total_cost_usd"] ?? "0"))}`);
3702
+ print(` Tokens: ${formatTokens(Number(d["total_tokens"] ?? 0))}`);
3703
+ const byModel = d["by_model"] ?? [];
3704
+ if (byModel.length > 0) {
3705
+ const table = new Table({ title: "Cost by Model" });
3706
+ table.addColumn("Model", { style: CYAN });
3707
+ table.addColumn("Cost", { align: "right" });
3708
+ table.addColumn("Tokens", { align: "right" });
3709
+ for (const m of byModel) {
3710
+ table.addRow(
3711
+ String(m["model"] ?? ""),
3712
+ formatCost(String(m["cost_usd"] ?? "0")),
3713
+ formatTokens(Number(m["tokens"] ?? 0))
3714
+ );
3715
+ }
3716
+ table.print();
3717
+ }
3718
+ }
3719
+ };
3720
+ var activityCommand = {
3721
+ name: "activity",
3722
+ description: "Show recent execution activity for a repository",
3723
+ args: [{ name: "repo-id", description: "Repository ID", required: true }],
3724
+ options: {
3725
+ limit: { type: "string", short: "n", description: "Max results", default: "20" }
3726
+ },
3727
+ handler: async (parsed) => {
3728
+ const id = reqRepoId(parsed);
3729
+ const limit = parsed.values["limit"] ?? "20";
3730
+ const items = await apiGetList(`/repos/${id}/activity`, { params: { limit } });
3731
+ if (items.length === 0) {
3732
+ printDim("No recent activity.");
3733
+ return;
3734
+ }
3735
+ const table = new Table({ title: `Activity: ${id}` });
3736
+ table.addColumn("Execution", { style: DIM });
3737
+ table.addColumn("Workflow");
3738
+ table.addColumn("Status");
3739
+ table.addColumn("Started");
3740
+ table.addColumn("Duration", { align: "right" });
3741
+ table.addColumn("Cost", { align: "right" });
3742
+ for (const e of items) {
3743
+ table.addRow(
3744
+ String(e["execution_id"] ?? "").slice(0, 12),
3745
+ String(e["workflow_name"] ?? ""),
3746
+ formatStatus(String(e["status"] ?? "")),
3747
+ formatTimestamp(e["started_at"]),
3748
+ e["duration_ms"] != null ? formatDuration(Number(e["duration_ms"])) : "\u2014",
3749
+ formatCost(String(e["cost_usd"] ?? "0"))
3750
+ );
3751
+ }
3752
+ table.print();
3753
+ }
3754
+ };
3755
+ var failuresCommand = {
3756
+ name: "failures",
3757
+ description: "Show recent execution failures for a repository",
3758
+ args: [{ name: "repo-id", description: "Repository ID", required: true }],
3759
+ options: {
3760
+ limit: { type: "string", short: "n", description: "Max results", default: "10" }
3761
+ },
3762
+ handler: async (parsed) => {
3763
+ const id = reqRepoId(parsed);
3764
+ const limit = parsed.values["limit"] ?? "10";
3765
+ const items = await apiGetList(`/repos/${id}/failures`, { params: { limit } });
3766
+ if (items.length === 0) {
3767
+ printDim("No recent failures.");
3768
+ return;
3769
+ }
3770
+ const table = new Table({ title: `Failures: ${id}` });
3771
+ table.addColumn("Execution", { style: DIM });
3772
+ table.addColumn("Workflow");
3773
+ table.addColumn("Error");
3774
+ table.addColumn("Time");
3775
+ for (const e of items) {
3776
+ table.addRow(
3777
+ String(e["execution_id"] ?? "").slice(0, 12),
3778
+ String(e["workflow_name"] ?? ""),
3779
+ String(e["error_message"] ?? "\u2014").slice(0, 60),
3780
+ formatTimestamp(e["failed_at"])
3781
+ );
3782
+ }
3783
+ table.print();
3784
+ }
3785
+ };
3786
+ var sessionsCommand2 = {
3787
+ name: "sessions",
3788
+ description: "Show agent sessions for a repository",
3789
+ args: [{ name: "repo-id", description: "Repository ID", required: true }],
3790
+ options: {
3791
+ limit: { type: "string", short: "n", description: "Max results", default: "20" }
3792
+ },
3793
+ handler: async (parsed) => {
3794
+ const id = reqRepoId(parsed);
3795
+ const limit = parsed.values["limit"] ?? "20";
3796
+ const items = await apiGetList(`/repos/${id}/sessions`, { params: { limit } });
3797
+ if (items.length === 0) {
3798
+ printDim("No sessions found.");
3799
+ return;
3800
+ }
3801
+ const table = new Table({ title: `Sessions: ${id}` });
3802
+ table.addColumn("Session", { style: DIM });
3803
+ table.addColumn("Status");
3804
+ table.addColumn("Started");
3805
+ table.addColumn("Tokens", { align: "right" });
3806
+ table.addColumn("Cost", { align: "right" });
3807
+ for (const s of items) {
3808
+ table.addRow(
3809
+ String(s["session_id"] ?? "").slice(0, 12),
3810
+ formatStatus(String(s["status"] ?? "")),
3811
+ formatTimestamp(s["started_at"]),
3812
+ formatTokens(Number(s["total_tokens"] ?? 0)),
3813
+ formatCost(String(s["cost_usd"] ?? "0"))
3814
+ );
3815
+ }
3816
+ table.print();
3817
+ }
3818
+ };
3819
+ var repoGroup = new CommandGroup("repo", "Manage repositories and view observability data");
3820
+ repoGroup.command(registerCommand).command(listCommand6).command(showCommand8).command(assignCommand).command(unassignCommand).command(healthCommand2).command(costCommand2).command(activityCommand).command(failuresCommand).command(sessionsCommand2);
3821
+
3822
+ // src/commands/sessions.ts
3823
+ var listCommand7 = {
3824
+ name: "list",
3825
+ description: "List agent sessions",
3826
+ options: {
3827
+ workflow: { type: "string", short: "w", description: "Filter by workflow ID" },
3828
+ status: { type: "string", short: "s", description: "Filter by status" },
3829
+ limit: { type: "string", short: "n", description: "Max results", default: "50" }
3830
+ },
3831
+ handler: async (parsed) => {
3832
+ const params = buildParams({
3833
+ workflow_id: parsed.values["workflow"] ?? null,
3834
+ status: parsed.values["status"] ?? null,
3835
+ limit: parsed.values["limit"] ?? "50"
3836
+ });
3837
+ const items = await apiGetList("/sessions", { params });
3838
+ if (items.length === 0) {
3839
+ printDim("No sessions found.");
3840
+ return;
3841
+ }
3842
+ const table = new Table({ title: "Sessions" });
3843
+ table.addColumn("Session ID", { style: CYAN });
3844
+ table.addColumn("Status");
3845
+ table.addColumn("Provider");
3846
+ table.addColumn("Started");
3847
+ table.addColumn("Tokens", { align: "right" });
3848
+ table.addColumn("Cost", { align: "right" });
3849
+ for (const s of items) {
3850
+ table.addRow(
3851
+ s.id.slice(0, 16),
3852
+ formatStatus(s.status),
3853
+ s.agent_provider ?? "\u2014",
3854
+ formatTimestamp(s.started_at),
3855
+ formatTokens(s.total_tokens),
3856
+ formatCost(s.total_cost_usd)
3857
+ );
3858
+ }
3859
+ table.print();
3860
+ }
3861
+ };
3862
+ var showCommand9 = {
3863
+ name: "show",
3864
+ description: "Show detailed session information",
3865
+ args: [{ name: "session-id", description: "Session ID", required: true }],
3866
+ handler: async (parsed) => {
3867
+ const id = parsed.positionals[0];
3868
+ if (!id) {
3869
+ printError("Missing session-id");
3870
+ throw new CLIError("Missing argument", 1);
3871
+ }
3872
+ const d = await apiGet(`/sessions/${id}`);
3873
+ print(`${style("Session:", BOLD)} ${d.id}`);
3874
+ print(` Workflow: ${d.workflow_name ?? d.workflow_id ?? "\u2014"}`);
3875
+ print(` Status: ${formatStatus(d.status)}`);
3876
+ print(` Provider: ${d.agent_provider ?? "\u2014"}`);
3877
+ print(` Model: ${d.agent_model ?? "\u2014"}`);
3878
+ print(` Started: ${formatTimestamp(d.started_at)}`);
3879
+ if (d.completed_at) print(` Completed: ${formatTimestamp(d.completed_at)}`);
3880
+ if (d.duration_seconds != null) print(` Duration: ${formatDuration(d.duration_seconds * 1e3)}`);
3881
+ print(` Tokens: ${formatTokens(d.total_tokens)} (in: ${formatTokens(d.input_tokens)}, out: ${formatTokens(d.output_tokens)})`);
3882
+ print(` Cost: ${formatCost(d.total_cost_usd)}`);
3883
+ if (d.error_message) print(` Error: ${d.error_message}`);
3884
+ const operations = d.operations ?? [];
3885
+ if (operations.length > 0) {
3886
+ print("");
3887
+ const table = new Table({ title: "Operations" });
3888
+ table.addColumn("#", { align: "right", style: DIM });
3889
+ table.addColumn("Type", { style: CYAN });
3890
+ table.addColumn("Tool");
3891
+ table.addColumn("Success");
3892
+ for (let i = 0; i < operations.length; i++) {
3893
+ const op = operations[i];
3894
+ table.addRow(
3895
+ String(i + 1),
3896
+ op.operation_type,
3897
+ op.tool_name ?? "\u2014",
3898
+ op.success ? "yes" : "no"
3899
+ );
3900
+ }
3901
+ table.print();
3902
+ }
3903
+ }
3904
+ };
3905
+ var sessionsGroup = new CommandGroup("sessions", "List and inspect agent sessions");
3906
+ sessionsGroup.command(listCommand7).command(showCommand9);
3907
+
3908
+ // src/commands/system.ts
3909
+ function reqId3(parsed) {
3910
+ const id = parsed.positionals[0];
3911
+ if (!id) {
3912
+ printError("Missing system-id");
3913
+ throw new CLIError("Missing argument", 1);
3914
+ }
3915
+ return id;
3916
+ }
3917
+ var createCommand4 = {
3918
+ name: "create",
3919
+ description: "Create a new system",
3920
+ options: {
3921
+ name: { type: "string", short: "n", description: "System name" },
3922
+ description: { type: "string", short: "d", description: "System description" },
3923
+ org: { type: "string", short: "o", description: "Organization ID" }
3924
+ },
3925
+ handler: async (parsed) => {
3926
+ const name = parsed.values["name"];
3927
+ if (!name) {
3928
+ printError("Missing --name");
3929
+ throw new CLIError("Missing option", 1);
3930
+ }
3931
+ const body = { name };
3932
+ const desc = parsed.values["description"];
3933
+ const org = parsed.values["org"];
3934
+ if (desc) body["description"] = desc;
3935
+ if (org) body["organization_id"] = org;
3936
+ const d = await apiPost("/systems", { body, expected: [200, 201] });
3937
+ printSuccess(`System created: ${d["system_id"] ?? ""}`);
3938
+ print(` Name: ${d["name"] ?? name}`);
3939
+ }
3940
+ };
3941
+ var listCommand8 = {
3942
+ name: "list",
3943
+ description: "List all systems",
3944
+ options: {
3945
+ org: { type: "string", short: "o", description: "Filter by organization" }
3946
+ },
3947
+ handler: async (parsed) => {
3948
+ const params = buildParams({
3949
+ organization_id: parsed.values["org"] ?? null
3950
+ });
3951
+ const items = await apiGetList("/systems", { params });
3952
+ if (items.length === 0) {
3953
+ printDim("No systems found.");
3954
+ return;
3955
+ }
3956
+ const table = new Table({ title: "Systems" });
3957
+ table.addColumn("ID", { style: CYAN });
3958
+ table.addColumn("Name");
3959
+ table.addColumn("Org", { style: DIM });
3960
+ table.addColumn("Repos", { align: "right" });
3961
+ table.addColumn("Status");
3962
+ for (const s of items) {
3963
+ table.addRow(
3964
+ String(s["system_id"] ?? ""),
3965
+ String(s["name"] ?? ""),
3966
+ String(s["organization_id"] ?? "\u2014"),
3967
+ String(s["repo_count"] ?? 0),
3968
+ formatStatus(String(s["status"] ?? ""))
3969
+ );
3970
+ }
3971
+ table.print();
3972
+ }
3973
+ };
3974
+ var showCommand10 = {
3975
+ name: "show",
3976
+ description: "Show system details",
3977
+ args: [{ name: "system-id", description: "System ID", required: true }],
3978
+ handler: async (parsed) => {
3979
+ const id = reqId3(parsed);
3980
+ const d = await apiGet(`/systems/${id}`);
3981
+ print(`${style("System:", BOLD)} ${d["name"] ?? id}`);
3982
+ print(` ID: ${d["system_id"] ?? id}`);
3983
+ if (d["description"]) print(` Description: ${String(d["description"])}`);
3984
+ if (d["organization_id"]) print(` Org: ${String(d["organization_id"])}`);
3985
+ print(` Repos: ${d["repo_count"] ?? 0}`);
3986
+ print(` Status: ${formatStatus(String(d["status"] ?? ""))}`);
3987
+ }
3988
+ };
3989
+ var updateCommand3 = {
3990
+ name: "update",
3991
+ description: "Update a system",
3992
+ args: [{ name: "system-id", description: "System ID", required: true }],
3993
+ options: {
3994
+ name: { type: "string", short: "n", description: "New name" },
3995
+ description: { type: "string", short: "d", description: "New description" }
3996
+ },
3997
+ handler: async (parsed) => {
3998
+ const id = reqId3(parsed);
3999
+ const body = {};
4000
+ const name = parsed.values["name"];
4001
+ const desc = parsed.values["description"];
4002
+ if (name) body["name"] = name;
4003
+ if (desc) body["description"] = desc;
4004
+ if (Object.keys(body).length === 0) {
4005
+ printError("Nothing to update. Use --name or --description.");
4006
+ throw new CLIError("No updates", 1);
4007
+ }
4008
+ await apiPut(`/systems/${id}`, { body });
4009
+ printSuccess(`System ${id} updated.`);
4010
+ }
4011
+ };
4012
+ var deleteCommand3 = {
4013
+ name: "delete",
4014
+ description: "Delete a system",
4015
+ args: [{ name: "system-id", description: "System ID", required: true }],
4016
+ options: {
4017
+ force: { type: "boolean", short: "f", description: "Skip confirmation", default: false }
4018
+ },
4019
+ handler: async (parsed) => {
4020
+ const id = reqId3(parsed);
4021
+ if (parsed.values["force"] !== true) {
4022
+ printError(`Use --force to confirm deleting system ${id}`);
4023
+ throw new CLIError("Confirmation required", 1);
4024
+ }
4025
+ await apiDelete(`/systems/${id}`);
4026
+ printSuccess(`System ${id} deleted.`);
4027
+ }
4028
+ };
4029
+ var statusCommand3 = {
4030
+ name: "status",
4031
+ description: "Show system health status",
4032
+ args: [{ name: "system-id", description: "System ID", required: true }],
4033
+ handler: async (parsed) => {
4034
+ const id = reqId3(parsed);
4035
+ const d = await apiGet(`/systems/${id}/status`);
4036
+ const h = String(d["health_status"] ?? "unknown");
4037
+ const color = h === "healthy" ? GREEN : h === "degraded" ? YELLOW : RED;
4038
+ print(`${style("System Status:", BOLD)} ${d["name"] ?? id}`);
4039
+ print(` Health: ${style(h, color)}`);
4040
+ print(` Repos: ${d["repo_count"] ?? 0}`);
4041
+ print(` Active: ${d["active_executions"] ?? 0}`);
4042
+ const repos = d["repos"] ?? [];
4043
+ if (repos.length > 0) {
4044
+ const table = new Table({ title: "Repository Health" });
4045
+ table.addColumn("Repo", { style: CYAN });
4046
+ table.addColumn("Health");
4047
+ table.addColumn("Last Run");
4048
+ table.addColumn("Success Rate", { align: "right" });
4049
+ for (const r of repos) {
4050
+ const rh = String(r["health"] ?? "unknown");
4051
+ const rc = rh === "healthy" ? GREEN : rh === "degraded" ? YELLOW : RED;
4052
+ table.addRow(
4053
+ String(r["repo_url"] ?? r["repo_id"] ?? ""),
4054
+ style(rh, rc),
4055
+ formatTimestamp(r["last_run_at"]),
4056
+ r["success_rate"] != null ? `${(Number(r["success_rate"]) * 100).toFixed(0)}%` : "\u2014"
4057
+ );
4058
+ }
4059
+ table.print();
4060
+ }
4061
+ }
4062
+ };
4063
+ var costCommand3 = {
4064
+ name: "cost",
4065
+ description: "Show cost breakdown for a system",
4066
+ args: [{ name: "system-id", description: "System ID", required: true }],
4067
+ handler: async (parsed) => {
4068
+ const id = reqId3(parsed);
4069
+ const d = await apiGet(`/systems/${id}/costs`);
4070
+ print(`${style("System Costs:", BOLD)} ${id}`);
4071
+ print(` Total cost: ${formatCost(String(d["total_cost_usd"] ?? "0"))}`);
4072
+ print(` Tokens: ${formatTokens(Number(d["total_tokens"] ?? 0))}`);
4073
+ const byRepo = d["by_repo"] ?? [];
4074
+ if (byRepo.length > 0) {
4075
+ const table = new Table({ title: "Cost by Repository" });
4076
+ table.addColumn("Repo", { style: CYAN });
4077
+ table.addColumn("Cost", { align: "right" });
4078
+ table.addColumn("Tokens", { align: "right" });
4079
+ for (const r of byRepo) {
4080
+ table.addRow(
4081
+ String(r["repo_url"] ?? r["repo_id"] ?? ""),
4082
+ formatCost(String(r["cost_usd"] ?? "0")),
4083
+ formatTokens(Number(r["tokens"] ?? 0))
4084
+ );
4085
+ }
4086
+ table.print();
4087
+ }
4088
+ }
4089
+ };
4090
+ var activityCommand2 = {
4091
+ name: "activity",
4092
+ description: "Show recent execution activity for a system",
4093
+ args: [{ name: "system-id", description: "System ID", required: true }],
4094
+ options: {
4095
+ limit: { type: "string", short: "n", description: "Max results", default: "20" }
4096
+ },
4097
+ handler: async (parsed) => {
4098
+ const id = reqId3(parsed);
4099
+ const limit = parsed.values["limit"] ?? "20";
4100
+ const items = await apiGetList(`/systems/${id}/activity`, { params: { limit } });
4101
+ if (items.length === 0) {
4102
+ printDim("No recent activity.");
4103
+ return;
4104
+ }
4105
+ const table = new Table({ title: `Activity: ${id}` });
4106
+ table.addColumn("Execution", { style: DIM });
4107
+ table.addColumn("Repo");
4108
+ table.addColumn("Workflow");
4109
+ table.addColumn("Status");
4110
+ table.addColumn("Started");
4111
+ table.addColumn("Cost", { align: "right" });
4112
+ for (const e of items) {
4113
+ table.addRow(
4114
+ String(e["execution_id"] ?? "").slice(0, 12),
4115
+ String(e["repo_url"] ?? ""),
4116
+ String(e["workflow_name"] ?? ""),
4117
+ formatStatus(String(e["status"] ?? "")),
4118
+ formatTimestamp(e["started_at"]),
4119
+ formatCost(String(e["cost_usd"] ?? "0"))
4120
+ );
4121
+ }
4122
+ table.print();
4123
+ }
4124
+ };
4125
+ var patternsCommand = {
4126
+ name: "patterns",
4127
+ description: "Show failure patterns and cost outliers for a system",
4128
+ args: [{ name: "system-id", description: "System ID", required: true }],
4129
+ handler: async (parsed) => {
4130
+ const id = reqId3(parsed);
4131
+ const d = await apiGet(`/systems/${id}/patterns`);
4132
+ const failures = d["failure_patterns"] ?? [];
4133
+ if (failures.length > 0) {
4134
+ const table = new Table({ title: "Failure Patterns" });
4135
+ table.addColumn("Pattern");
4136
+ table.addColumn("Count", { align: "right" });
4137
+ table.addColumn("Last Seen");
4138
+ for (const f of failures) {
4139
+ table.addRow(
4140
+ String(f["pattern"] ?? "").slice(0, 60),
4141
+ String(f["count"] ?? 0),
4142
+ formatTimestamp(f["last_seen"])
4143
+ );
4144
+ }
4145
+ table.print();
4146
+ } else {
4147
+ printDim("No failure patterns detected.");
4148
+ }
4149
+ const outliers = d["cost_outliers"] ?? [];
4150
+ if (outliers.length > 0) {
4151
+ const table = new Table({ title: "Cost Outliers" });
4152
+ table.addColumn("Execution", { style: DIM });
4153
+ table.addColumn("Cost", { align: "right" });
4154
+ table.addColumn("Avg Cost", { align: "right" });
4155
+ table.addColumn("Ratio", { align: "right" });
4156
+ for (const o of outliers) {
4157
+ table.addRow(
4158
+ String(o["execution_id"] ?? "").slice(0, 12),
4159
+ formatCost(String(o["cost_usd"] ?? "0")),
4160
+ formatCost(String(o["avg_cost_usd"] ?? "0")),
4161
+ `${Number(o["ratio"] ?? 0).toFixed(1)}x`
4162
+ );
4163
+ }
4164
+ table.print();
4165
+ }
4166
+ }
4167
+ };
4168
+ var historyCommand = {
4169
+ name: "history",
4170
+ description: "Show full execution history for a system",
4171
+ args: [{ name: "system-id", description: "System ID", required: true }],
4172
+ options: {
4173
+ limit: { type: "string", short: "n", description: "Max results", default: "50" },
4174
+ status: { type: "string", short: "s", description: "Filter by status" }
4175
+ },
4176
+ handler: async (parsed) => {
4177
+ const id = reqId3(parsed);
4178
+ const params = buildParams({
4179
+ limit: parsed.values["limit"] ?? "50",
4180
+ status: parsed.values["status"] ?? null
4181
+ });
4182
+ const items = await apiGetList(`/systems/${id}/history`, { params });
4183
+ if (items.length === 0) {
4184
+ printDim("No execution history.");
4185
+ return;
4186
+ }
4187
+ const table = new Table({ title: `History: ${id}` });
4188
+ table.addColumn("Execution", { style: DIM });
4189
+ table.addColumn("Workflow");
4190
+ table.addColumn("Status");
4191
+ table.addColumn("Started");
4192
+ table.addColumn("Duration", { align: "right" });
4193
+ table.addColumn("Cost", { align: "right" });
4194
+ for (const e of items) {
4195
+ table.addRow(
4196
+ String(e["execution_id"] ?? "").slice(0, 12),
4197
+ String(e["workflow_name"] ?? ""),
4198
+ formatStatus(String(e["status"] ?? "")),
4199
+ formatTimestamp(e["started_at"]),
4200
+ e["duration_ms"] != null ? formatDuration(Number(e["duration_ms"])) : "\u2014",
4201
+ formatCost(String(e["cost_usd"] ?? "0"))
4202
+ );
4203
+ }
4204
+ table.print();
4205
+ }
4206
+ };
4207
+ var systemGroup = new CommandGroup("system", "Manage systems and view system observability");
4208
+ systemGroup.command(createCommand4).command(listCommand8).command(showCommand10).command(updateCommand3).command(deleteCommand3).command(statusCommand3).command(costCommand3).command(activityCommand2).command(patternsCommand).command(historyCommand);
4209
+
4210
+ // src/commands/triggers.ts
4211
+ function reqId4(parsed) {
4212
+ const id = parsed.positionals[0];
4213
+ if (!id) {
4214
+ printError("Missing trigger-id");
4215
+ throw new CLIError("Missing argument", 1);
4216
+ }
4217
+ return id;
4218
+ }
4219
+ function parseConditions(condStrs) {
4220
+ return condStrs.map((c) => {
4221
+ const eqIdx = c.indexOf("=");
4222
+ if (eqIdx < 1) throw new CLIError(`Invalid condition format: ${c} (expected key=value)`, 1);
4223
+ return { key: c.slice(0, eqIdx), value: c.slice(eqIdx + 1) };
4224
+ });
4225
+ }
4226
+ var registerCommand2 = {
4227
+ name: "register",
4228
+ description: "Register a new trigger rule",
4229
+ options: {
4230
+ repo: { type: "string", short: "r", description: "Repository ID" },
4231
+ workflow: { type: "string", short: "w", description: "Workflow ID to execute" },
4232
+ event: { type: "string", short: "e", description: "GitHub event type (e.g. check_run.completed)" },
4233
+ condition: { type: "string", short: "c", multiple: true, description: "Condition as key=value (repeatable)" },
4234
+ "max-fires": { type: "string", description: "Maximum fires per period", default: "5" },
4235
+ cooldown: { type: "string", description: "Cooldown in seconds", default: "300" },
4236
+ budget: { type: "string", description: "Budget limit in USD" }
4237
+ },
4238
+ handler: async (parsed) => {
4239
+ const repo = parsed.values["repo"];
4240
+ const workflow = parsed.values["workflow"];
4241
+ const event = parsed.values["event"];
4242
+ if (!repo) {
4243
+ printError("Missing --repo");
4244
+ throw new CLIError("Missing option", 1);
4245
+ }
4246
+ if (!workflow) {
4247
+ printError("Missing --workflow");
4248
+ throw new CLIError("Missing option", 1);
4249
+ }
4250
+ if (!event) {
4251
+ printError("Missing --event");
4252
+ throw new CLIError("Missing option", 1);
4253
+ }
4254
+ const conditionStrs = parsed.values["condition"] ?? [];
4255
+ const conditions = conditionStrs.length > 0 ? parseConditions(conditionStrs) : [];
4256
+ const body = {
4257
+ repo_id: repo,
4258
+ workflow_id: workflow,
4259
+ event_type: event,
4260
+ conditions,
4261
+ max_fires_per_period: parseInt(parsed.values["max-fires"] ?? "5", 10),
4262
+ cooldown_seconds: parseInt(parsed.values["cooldown"] ?? "300", 10)
4263
+ };
4264
+ const budget = parsed.values["budget"];
4265
+ if (budget) body["budget_usd"] = parseFloat(budget);
4266
+ const d = await apiPost("/triggers", { body, expected: [200, 201] });
4267
+ printSuccess(`Trigger registered: ${d["trigger_id"] ?? ""}`);
4268
+ }
4269
+ };
4270
+ var enablePresetCommand = {
4271
+ name: "enable",
4272
+ description: "Enable a built-in trigger preset",
4273
+ args: [{ name: "preset", description: "Preset name (self-healing, review-fix)", required: true }],
4274
+ options: {
4275
+ repo: { type: "string", short: "r", description: "Repository ID" },
4276
+ workflow: { type: "string", short: "w", description: "Workflow ID" }
4277
+ },
4278
+ handler: async (parsed) => {
4279
+ const preset = parsed.positionals[0];
4280
+ if (!preset) {
4281
+ printError("Missing preset name");
4282
+ throw new CLIError("Missing argument", 1);
4283
+ }
4284
+ const repo = parsed.values["repo"];
4285
+ if (!repo) {
4286
+ printError("Missing --repo");
4287
+ throw new CLIError("Missing option", 1);
4288
+ }
4289
+ const body = { preset, repo_id: repo };
4290
+ const workflow = parsed.values["workflow"];
4291
+ if (workflow) body["workflow_id"] = workflow;
4292
+ const d = await apiPost("/triggers/presets", { body, expected: [200, 201] });
4293
+ printSuccess(`Preset "${preset}" enabled: ${d["trigger_id"] ?? ""}`);
4294
+ }
4295
+ };
4296
+ var listCommand9 = {
4297
+ name: "list",
4298
+ description: "List trigger rules",
4299
+ options: {
4300
+ repo: { type: "string", short: "r", description: "Filter by repository" },
4301
+ status: { type: "string", short: "s", description: "Filter by status" }
4302
+ },
4303
+ handler: async (parsed) => {
4304
+ const params = buildParams({
4305
+ repo_id: parsed.values["repo"] ?? null,
4306
+ status: parsed.values["status"] ?? null
4307
+ });
4308
+ const items = await apiGetList("/triggers", { params });
4309
+ if (items.length === 0) {
4310
+ printDim("No triggers found.");
4311
+ return;
4312
+ }
4313
+ const table = new Table({ title: "Triggers" });
4314
+ table.addColumn("ID", { style: CYAN });
4315
+ table.addColumn("Event");
4316
+ table.addColumn("Repo", { style: DIM });
4317
+ table.addColumn("Workflow", { style: DIM });
4318
+ table.addColumn("Status");
4319
+ table.addColumn("Fires", { align: "right" });
4320
+ for (const t of items) {
4321
+ table.addRow(
4322
+ String(t["trigger_id"] ?? "").slice(0, 12),
4323
+ String(t["event_type"] ?? ""),
4324
+ String(t["repo_id"] ?? "").slice(0, 12),
4325
+ String(t["workflow_id"] ?? "").slice(0, 12),
4326
+ formatStatus(String(t["status"] ?? "")),
4327
+ String(t["fire_count"] ?? 0)
4328
+ );
4329
+ }
4330
+ table.print();
4331
+ }
4332
+ };
4333
+ var showCommand11 = {
4334
+ name: "show",
4335
+ description: "Show trigger details",
4336
+ args: [{ name: "trigger-id", description: "Trigger ID", required: true }],
4337
+ handler: async (parsed) => {
4338
+ const id = reqId4(parsed);
4339
+ const d = await apiGet(`/triggers/${id}`);
4340
+ print(`${style("Trigger:", BOLD)} ${d["trigger_id"] ?? id}`);
4341
+ print(` Event: ${d["event_type"] ?? ""}`);
4342
+ print(` Repo: ${d["repo_id"] ?? ""}`);
4343
+ print(` Workflow: ${d["workflow_id"] ?? ""}`);
4344
+ print(` Status: ${formatStatus(String(d["status"] ?? ""))}`);
4345
+ print(` Fires: ${d["fire_count"] ?? 0} / max ${d["max_fires_per_period"] ?? "\u2014"}`);
4346
+ print(` Cooldown: ${d["cooldown_seconds"] ?? 0}s`);
4347
+ if (d["budget_usd"] != null) print(` Budget: ${formatCost(String(d["budget_usd"]))}`);
4348
+ if (d["last_fired_at"]) print(` Last fired: ${formatTimestamp(d["last_fired_at"])}`);
4349
+ const conditions = d["conditions"] ?? [];
4350
+ if (conditions.length > 0) {
4351
+ print(style(" Conditions:", BOLD));
4352
+ for (const c of conditions) {
4353
+ print(` ${c["key"] ?? ""} = ${c["value"] ?? ""}`);
4354
+ }
4355
+ }
4356
+ }
4357
+ };
4358
+ var historyCommand2 = {
4359
+ name: "history",
4360
+ description: "Show trigger execution history",
4361
+ args: [{ name: "trigger-id", description: "Trigger ID", required: true }],
4362
+ options: {
4363
+ limit: { type: "string", short: "n", description: "Max results", default: "20" }
4364
+ },
4365
+ handler: async (parsed) => {
4366
+ const id = reqId4(parsed);
4367
+ const limit = parsed.values["limit"] ?? "20";
4368
+ const items = await apiGetList(`/triggers/${id}/history`, { params: { limit } });
4369
+ if (items.length === 0) {
4370
+ printDim("No trigger history.");
4371
+ return;
4372
+ }
4373
+ const table = new Table({ title: `Trigger History: ${id.slice(0, 12)}` });
4374
+ table.addColumn("Time");
4375
+ table.addColumn("Execution", { style: DIM });
4376
+ table.addColumn("Status");
4377
+ table.addColumn("Cost", { align: "right" });
4378
+ for (const h of items) {
4379
+ table.addRow(
4380
+ formatTimestamp(h["fired_at"]),
4381
+ String(h["execution_id"] ?? "").slice(0, 12),
4382
+ formatStatus(String(h["status"] ?? "")),
4383
+ formatCost(String(h["cost_usd"] ?? "0"))
4384
+ );
4385
+ }
4386
+ table.print();
4387
+ }
4388
+ };
4389
+ var pauseCommand2 = {
4390
+ name: "pause",
4391
+ description: "Pause a trigger rule",
4392
+ args: [{ name: "trigger-id", description: "Trigger ID", required: true }],
4393
+ handler: async (parsed) => {
4394
+ const id = reqId4(parsed);
4395
+ await apiPost(`/triggers/${id}/pause`);
4396
+ printSuccess(`Trigger ${id} paused.`);
4397
+ }
4398
+ };
4399
+ var resumeCommand2 = {
4400
+ name: "resume",
4401
+ description: "Resume a paused trigger rule",
4402
+ args: [{ name: "trigger-id", description: "Trigger ID", required: true }],
4403
+ handler: async (parsed) => {
4404
+ const id = reqId4(parsed);
4405
+ await apiPost(`/triggers/${id}/resume`);
4406
+ printSuccess(`Trigger ${id} resumed.`);
4407
+ }
4408
+ };
4409
+ var deleteCommand4 = {
4410
+ name: "delete",
4411
+ description: "Delete a trigger rule",
4412
+ args: [{ name: "trigger-id", description: "Trigger ID", required: true }],
4413
+ options: {
4414
+ force: { type: "boolean", short: "f", description: "Skip confirmation", default: false }
4415
+ },
4416
+ handler: async (parsed) => {
4417
+ const id = reqId4(parsed);
4418
+ if (parsed.values["force"] !== true) {
4419
+ printError(`Use --force to confirm deleting trigger ${id}`);
4420
+ throw new CLIError("Confirmation required", 1);
4421
+ }
4422
+ await apiDelete(`/triggers/${id}`);
4423
+ printSuccess(`Trigger ${id} deleted.`);
4424
+ }
4425
+ };
4426
+ var disableAllCommand = {
4427
+ name: "disable-all",
4428
+ description: "Disable all triggers for a repository",
4429
+ options: {
4430
+ repo: { type: "string", short: "r", description: "Repository ID" },
4431
+ force: { type: "boolean", short: "f", description: "Skip confirmation", default: false }
4432
+ },
4433
+ handler: async (parsed) => {
4434
+ const repo = parsed.values["repo"];
4435
+ if (!repo) {
4436
+ printError("Missing --repo");
4437
+ throw new CLIError("Missing option", 1);
4438
+ }
4439
+ if (parsed.values["force"] !== true) {
4440
+ printError(`Use --force to confirm disabling all triggers for repo ${repo}`);
4441
+ throw new CLIError("Confirmation required", 1);
4442
+ }
4443
+ await apiPost(`/triggers/disable-all`, { body: { repo_id: repo } });
4444
+ printSuccess(`All triggers disabled for repository ${repo}.`);
4445
+ }
4446
+ };
4447
+ var triggersGroup = new CommandGroup("triggers", "Manage self-healing trigger rules");
4448
+ triggersGroup.command(registerCommand2).command(enablePresetCommand).command(listCommand9).command(showCommand11).command(historyCommand2).command(pauseCommand2).command(resumeCommand2).command(deleteCommand4).command(disableAllCommand);
4449
+
4450
+ // src/client/sse.ts
4451
+ function parseSseLine(line) {
4452
+ if (!line.startsWith("data: ")) return null;
4453
+ const raw = line.slice(6).trim();
4454
+ if (!raw) return null;
4455
+ try {
4456
+ return JSON.parse(raw);
4457
+ } catch {
4458
+ return null;
4459
+ }
4460
+ }
4461
+ async function* streamSSE(path8) {
4462
+ const client = new SynClient();
4463
+ const body = await client.stream(path8);
4464
+ const reader = body.pipeThrough(new TextDecoderStream()).getReader();
4465
+ let buffer = "";
4466
+ try {
4467
+ for (; ; ) {
4468
+ const { done, value } = await reader.read();
4469
+ if (done) break;
4470
+ buffer += value;
4471
+ const lines = buffer.split("\n");
4472
+ buffer = lines.pop() ?? "";
4473
+ for (const line of lines) {
4474
+ const trimmed = line.trim();
4475
+ if (!trimmed) continue;
4476
+ const event = parseSseLine(trimmed);
4477
+ if (event) yield event;
4478
+ }
4479
+ }
4480
+ if (buffer.trim()) {
4481
+ const event = parseSseLine(buffer.trim());
4482
+ if (event) yield event;
4483
+ }
4484
+ } finally {
4485
+ reader.releaseLock();
4486
+ }
4487
+ }
4488
+
4489
+ // src/commands/watch.ts
4490
+ var EVENT_STYLES = {
4491
+ WorkflowExecutionStarted: GREEN,
4492
+ WorkflowCompleted: GREEN,
4493
+ WorkflowFailed: RED,
4494
+ PhaseStarted: CYAN,
4495
+ PhaseCompleted: GREEN,
4496
+ PhaseFailed: RED,
4497
+ SessionTokensRecorded: DIM,
4498
+ ExecutionPaused: YELLOW,
4499
+ ExecutionResumed: GREEN,
4500
+ ExecutionCancelled: RED
4501
+ };
4502
+ function extractEventId(data) {
4503
+ return data["workflow_execution_id"] ?? data["execution_id"] ?? data["session_id"] ?? "";
4504
+ }
4505
+ function renderEventData(parts, data) {
4506
+ const id = extractEventId(data);
4507
+ if (id) parts.push(` ${style(id.slice(0, 12), DIM)}`);
4508
+ if (data["workflow_name"]) parts.push(` ${String(data["workflow_name"])}`);
4509
+ if (data["phase_name"]) parts.push(` phase:${String(data["phase_name"])}`);
4510
+ if (data["total_tokens"]) parts.push(` tokens:${formatTokens(Number(data["total_tokens"]))}`);
4511
+ if (data["cost_usd"]) parts.push(` cost:${formatCost(String(data["cost_usd"]))}`);
4512
+ if (data["error_message"]) parts.push(` ${style(String(data["error_message"]).slice(0, 80), RED)}`);
4513
+ }
4514
+ function renderEvent(event) {
4515
+ const ts = event.timestamp ? formatTimestamp(event.timestamp) : style("now", DIM);
4516
+ const eventType = event.event_type ?? event.type ?? "unknown";
4517
+ const eventStyle = EVENT_STYLES[eventType] ?? "";
4518
+ const parts = [style(ts, DIM), " ", eventStyle ? style(eventType, eventStyle) : eventType];
4519
+ if (event.data) {
4520
+ renderEventData(parts, event.data);
4521
+ }
4522
+ print(parts.join(""));
4523
+ }
4524
+ var executionCommand = {
4525
+ name: "execution",
4526
+ description: "Stream live events for a specific execution",
4527
+ args: [{ name: "execution-id", description: "Execution ID to watch", required: true }],
4528
+ handler: async (parsed) => {
4529
+ const id = parsed.positionals[0];
4530
+ if (!id) {
4531
+ printError("Missing execution-id");
4532
+ throw new CLIError("Missing argument", 1);
4533
+ }
4534
+ print(style(`Watching execution ${id}...`, BOLD));
4535
+ printDim("Press Ctrl+C to stop.\n");
4536
+ try {
4537
+ for await (const event of streamSSE(`/watch/executions/${id}`)) {
4538
+ renderEvent(event);
4539
+ }
4540
+ print(style("\nStream ended.", DIM));
4541
+ } catch (err) {
4542
+ if (err instanceof CLIError) throw err;
4543
+ if (err instanceof Error && err.name === "AbortError") return;
4544
+ printError(`Stream error: ${err instanceof Error ? err.message : String(err)}`);
4545
+ throw new CLIError("Stream failed", 1);
4546
+ }
4547
+ }
4548
+ };
4549
+ var activityCommand3 = {
4550
+ name: "activity",
4551
+ description: "Stream live global activity across all executions",
4552
+ handler: async () => {
4553
+ print(style("Watching global activity...", BOLD));
4554
+ printDim("Press Ctrl+C to stop.\n");
4555
+ try {
4556
+ for await (const event of streamSSE("/watch/activity")) {
4557
+ renderEvent(event);
4558
+ }
4559
+ print(style("\nStream ended.", DIM));
4560
+ } catch (err) {
4561
+ if (err instanceof CLIError) throw err;
4562
+ if (err instanceof Error && err.name === "AbortError") return;
4563
+ printError(`Stream error: ${err instanceof Error ? err.message : String(err)}`);
4564
+ throw new CLIError("Stream failed", 1);
4565
+ }
4566
+ }
4567
+ };
4568
+ var watchGroup = new CommandGroup("watch", "Stream live execution events via SSE");
4569
+ watchGroup.command(executionCommand).command(activityCommand3);
4570
+
4571
+ // src/framework/cli.ts
4572
+ import { parseArgs } from "util";
4573
+
4574
+ // src/framework/help.ts
4575
+ function renderTopLevelHelp(name, description, groups, rootCommands) {
4576
+ const lines = [];
4577
+ lines.push(`${style(name, BOLD)} \u2014 ${description}`);
4578
+ lines.push("");
4579
+ lines.push(style("Usage:", BOLD) + ` ${name} <command> [options]`);
4580
+ if (rootCommands.size > 0) {
4581
+ lines.push("");
4582
+ lines.push(style("Commands:", BOLD));
4583
+ const maxLen = maxNameLength(rootCommands);
4584
+ for (const [cmdName, cmd] of rootCommands) {
4585
+ lines.push(
4586
+ ` ${style(cmdName.padEnd(maxLen), CYAN)} ${style(cmd.description, DIM)}`
4587
+ );
4588
+ }
4589
+ }
4590
+ if (groups.size > 0) {
4591
+ lines.push("");
4592
+ lines.push(style("Command Groups:", BOLD));
4593
+ const maxLen = Math.max(...[...groups.values()].map((g) => g.name.length));
4594
+ for (const group of groups.values()) {
4595
+ lines.push(
4596
+ ` ${style(group.name.padEnd(maxLen), CYAN)} ${style(group.description, DIM)}`
4597
+ );
4598
+ }
4599
+ }
4600
+ lines.push("");
4601
+ lines.push(
4602
+ style(`Run '${name} <command> --help' for more information on a command.`, DIM)
4603
+ );
4604
+ return lines.join("\n");
4605
+ }
4606
+ function renderGroupHelp(name, group) {
4607
+ const lines = [];
4608
+ lines.push(`${style(name + " " + group.name, BOLD)} \u2014 ${group.description}`);
4609
+ lines.push("");
4610
+ lines.push(
4611
+ style("Usage:", BOLD) + ` ${name} ${group.name} <command> [options]`
4612
+ );
4613
+ lines.push("");
4614
+ lines.push(style("Commands:", BOLD));
4615
+ const maxLen = maxNameLength(group.commands);
4616
+ for (const [cmdName, cmd] of group.commands) {
4617
+ lines.push(
4618
+ ` ${style(cmdName.padEnd(maxLen), CYAN)} ${style(cmd.description, DIM)}`
4619
+ );
4620
+ }
4621
+ return lines.join("\n");
4622
+ }
4623
+ function renderCommandHelp(command, cliName, groupName) {
4624
+ const lines = [];
4625
+ const prefix = groupName ? `${cliName} ${groupName} ${command.name}` : `${cliName} ${command.name}`;
4626
+ lines.push(`${style(prefix, BOLD)} \u2014 ${command.description}`);
4627
+ lines.push("");
4628
+ lines.push(style("Usage:", BOLD) + renderUsageLine(prefix, command));
4629
+ if (command.args && command.args.length > 0) {
4630
+ renderArguments(lines, command.args);
4631
+ }
4632
+ if (command.options) {
4633
+ renderOptions(lines, command.options);
4634
+ }
4635
+ return lines.join("\n");
4636
+ }
4637
+ function renderUsageLine(prefix, command) {
4638
+ let usage = ` ${prefix}`;
4639
+ if (command.args) {
4640
+ for (const arg of command.args) {
4641
+ usage += arg.required !== false ? ` <${arg.name}>` : ` [${arg.name}]`;
4642
+ }
4643
+ }
4644
+ if (command.options && Object.keys(command.options).length > 0) {
4645
+ usage += " [options]";
4646
+ }
4647
+ return usage;
4648
+ }
4649
+ function renderArguments(lines, args) {
4650
+ lines.push("");
4651
+ lines.push(style("Arguments:", BOLD));
4652
+ const maxLen = Math.max(...args.map((a) => a.name.length));
4653
+ for (const arg of args) {
4654
+ const req = arg.required !== false ? " (required)" : "";
4655
+ lines.push(
4656
+ ` ${style(arg.name.padEnd(maxLen), CYAN)} ${arg.description}${style(req, DIM)}`
4657
+ );
4658
+ }
4659
+ }
4660
+ function renderOptions(lines, options) {
4661
+ const entries = Object.entries(options);
4662
+ if (entries.length === 0) return;
4663
+ lines.push("");
4664
+ lines.push(style("Options:", BOLD));
4665
+ const formatted = entries.map(([name, opt]) => {
4666
+ const flag = opt.short ? `-${opt.short}, --${name}` : ` --${name}`;
4667
+ return { flag, opt };
4668
+ });
4669
+ const maxLen = Math.max(...formatted.map((f) => f.flag.length));
4670
+ for (const { flag, opt } of formatted) {
4671
+ const def = opt.default !== void 0 ? ` ${style(`(default: ${String(opt.default)})`, DIM)}` : "";
4672
+ lines.push(` ${style(flag.padEnd(maxLen), CYAN)} ${opt.description}${def}`);
4673
+ }
4674
+ }
4675
+ function maxNameLength(map) {
4676
+ return Math.max(...[...map.values()].map((v) => v.name.length));
4677
+ }
4678
+
4679
+ // src/framework/cli.ts
4680
+ var CLI = class {
4681
+ name;
4682
+ description;
4683
+ version;
4684
+ groups = /* @__PURE__ */ new Map();
4685
+ rootCommands = /* @__PURE__ */ new Map();
4686
+ constructor(options) {
4687
+ this.name = options.name;
4688
+ this.description = options.description;
4689
+ this.version = options.version;
4690
+ }
4691
+ addGroup(group) {
4692
+ this.groups.set(group.name, group);
4693
+ return this;
4694
+ }
4695
+ addCommand(def) {
4696
+ this.rootCommands.set(def.name, def);
4697
+ return this;
4698
+ }
4699
+ async run(argv) {
4700
+ const args = argv ?? process.argv.slice(2);
4701
+ try {
4702
+ await this.dispatch(args);
4703
+ } catch (err) {
4704
+ if (err instanceof CLIError) {
4705
+ printError(err.message);
4706
+ process.exit(err.exitCode);
4707
+ }
4708
+ printError(`Unexpected error: ${String(err)}`);
4709
+ process.exit(2);
4710
+ }
4711
+ }
4712
+ async dispatch(args) {
4713
+ const first = args[0];
4714
+ if (!first || first === "--help" || first === "-h") {
4715
+ print(renderTopLevelHelp(this.name, this.description, this.groups, this.rootCommands));
4716
+ process.exit(0);
4717
+ }
4718
+ if (first === "--version") {
4719
+ print(`${this.name} v${this.version}`);
4720
+ process.exit(0);
4721
+ }
4722
+ const rootCmd = this.rootCommands.get(first);
4723
+ if (rootCmd) {
4724
+ await this.executeCommand(rootCmd, args.slice(1));
4725
+ return;
4726
+ }
4727
+ const group = this.groups.get(first);
4728
+ if (group) {
4729
+ await this.dispatchGroup(first, group, args.slice(1));
4730
+ return;
4731
+ }
4732
+ throw new CLIError(`Unknown command: ${first}. Run '${this.name} --help' for usage.`);
4733
+ }
4734
+ async dispatchGroup(groupName, group, args) {
4735
+ const second = args[0];
4736
+ if (!second || second === "--help" || second === "-h") {
4737
+ print(renderGroupHelp(this.name, group));
4738
+ process.exit(0);
4739
+ }
4740
+ const cmd = group.getCommand(second);
4741
+ if (!cmd) {
4742
+ throw new CLIError(
4743
+ `Unknown command: ${groupName} ${second}. Run '${this.name} ${groupName} --help' for usage.`
4744
+ );
4745
+ }
4746
+ await this.executeCommand(cmd, args.slice(1), group.name);
4747
+ }
4748
+ async executeCommand(cmd, argv, groupName) {
4749
+ const parsed = this.parseCommandArgs(cmd, argv);
4750
+ if (parsed.values["help"]) {
4751
+ print(renderCommandHelp(cmd, this.name, groupName));
4752
+ process.exit(0);
4753
+ }
4754
+ await cmd.handler(parsed);
4755
+ }
4756
+ parseCommandArgs(cmd, argv) {
4757
+ const optionsConfig = buildOptionsConfig(cmd);
4758
+ try {
4759
+ const { values, positionals } = parseArgs({
4760
+ args: argv,
4761
+ options: optionsConfig,
4762
+ allowPositionals: true,
4763
+ strict: true
4764
+ });
4765
+ return {
4766
+ positionals,
4767
+ values
4768
+ };
4769
+ } catch (err) {
4770
+ const msg = err instanceof Error ? err.message : String(err);
4771
+ throw new CLIError(msg);
4772
+ }
4773
+ }
4774
+ };
4775
+ function buildOptionsConfig(cmd) {
4776
+ const config = {
4777
+ help: { type: "boolean", short: "h", default: false }
4778
+ };
4779
+ if (!cmd.options) return config;
4780
+ for (const [name, def] of Object.entries(cmd.options)) {
4781
+ const entry = {
4782
+ type: def.type
4783
+ };
4784
+ if (def.short) entry.short = def.short;
4785
+ if (def.multiple) entry.multiple = def.multiple;
4786
+ if (def.default !== void 0) entry.default = def.default;
4787
+ config[name] = entry;
4788
+ }
4789
+ return config;
4790
+ }
4791
+
4792
+ // src/index.ts
4793
+ var cli = new CLI({
4794
+ name: CLI_NAME,
4795
+ description: CLI_DESCRIPTION,
4796
+ version: CLI_VERSION
4797
+ });
4798
+ cli.addCommand(healthCommand);
4799
+ cli.addCommand(versionCommand);
4800
+ cli.addCommand({
4801
+ ...runCommand,
4802
+ description: "Execute a workflow (shortcut for 'syn workflow run')"
4803
+ });
4804
+ cli.addGroup(workflowGroup);
4805
+ cli.addGroup(marketplaceGroup);
4806
+ cli.addGroup(agentGroup);
4807
+ cli.addGroup(artifactsGroup);
4808
+ cli.addGroup(configGroup);
4809
+ cli.addGroup(controlGroup);
4810
+ cli.addGroup(conversationsGroup);
4811
+ cli.addGroup(costsGroup);
4812
+ cli.addGroup(eventsGroup);
4813
+ cli.addGroup(executionGroup);
4814
+ cli.addGroup(insightsGroup);
4815
+ cli.addGroup(metricsGroup);
4816
+ cli.addGroup(observeGroup);
4817
+ cli.addGroup(orgGroup);
4818
+ cli.addGroup(repoGroup);
4819
+ cli.addGroup(sessionsGroup);
4820
+ cli.addGroup(systemGroup);
4821
+ cli.addGroup(triggersGroup);
4822
+ cli.addGroup(watchGroup);
4823
+ cli.run();