codepiper 0.1.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 (149) hide show
  1. package/.env.example +28 -0
  2. package/CHANGELOG.md +10 -0
  3. package/LEGAL_NOTICE.md +39 -0
  4. package/LICENSE +21 -0
  5. package/README.md +524 -0
  6. package/package.json +90 -0
  7. package/packages/cli/package.json +13 -0
  8. package/packages/cli/src/commands/analytics.ts +157 -0
  9. package/packages/cli/src/commands/attach.ts +299 -0
  10. package/packages/cli/src/commands/audit.ts +50 -0
  11. package/packages/cli/src/commands/auth.ts +261 -0
  12. package/packages/cli/src/commands/daemon.ts +162 -0
  13. package/packages/cli/src/commands/doctor.ts +303 -0
  14. package/packages/cli/src/commands/env-set.ts +162 -0
  15. package/packages/cli/src/commands/hook-forward.ts +268 -0
  16. package/packages/cli/src/commands/keys.ts +77 -0
  17. package/packages/cli/src/commands/kill.ts +19 -0
  18. package/packages/cli/src/commands/logs.ts +419 -0
  19. package/packages/cli/src/commands/model.ts +172 -0
  20. package/packages/cli/src/commands/policy-set.ts +185 -0
  21. package/packages/cli/src/commands/policy.ts +227 -0
  22. package/packages/cli/src/commands/providers.ts +114 -0
  23. package/packages/cli/src/commands/resize.ts +34 -0
  24. package/packages/cli/src/commands/send.ts +184 -0
  25. package/packages/cli/src/commands/sessions.ts +202 -0
  26. package/packages/cli/src/commands/slash.ts +92 -0
  27. package/packages/cli/src/commands/start.ts +243 -0
  28. package/packages/cli/src/commands/stop.ts +19 -0
  29. package/packages/cli/src/commands/tail.ts +137 -0
  30. package/packages/cli/src/commands/workflow.ts +786 -0
  31. package/packages/cli/src/commands/workspace.ts +127 -0
  32. package/packages/cli/src/lib/api.ts +78 -0
  33. package/packages/cli/src/lib/args.ts +72 -0
  34. package/packages/cli/src/lib/format.ts +93 -0
  35. package/packages/cli/src/main.ts +563 -0
  36. package/packages/core/package.json +7 -0
  37. package/packages/core/src/config.ts +30 -0
  38. package/packages/core/src/errors.ts +38 -0
  39. package/packages/core/src/eventBus.ts +56 -0
  40. package/packages/core/src/eventBusAdapter.ts +143 -0
  41. package/packages/core/src/index.ts +10 -0
  42. package/packages/core/src/sqliteEventBus.ts +336 -0
  43. package/packages/core/src/types.ts +63 -0
  44. package/packages/daemon/package.json +11 -0
  45. package/packages/daemon/src/api/analyticsRoutes.ts +343 -0
  46. package/packages/daemon/src/api/authRoutes.ts +344 -0
  47. package/packages/daemon/src/api/bodyLimit.ts +133 -0
  48. package/packages/daemon/src/api/envSetRoutes.ts +170 -0
  49. package/packages/daemon/src/api/gitRoutes.ts +409 -0
  50. package/packages/daemon/src/api/hooks.ts +588 -0
  51. package/packages/daemon/src/api/inputPolicy.ts +249 -0
  52. package/packages/daemon/src/api/notificationRoutes.ts +532 -0
  53. package/packages/daemon/src/api/policyRoutes.ts +234 -0
  54. package/packages/daemon/src/api/policySetRoutes.ts +445 -0
  55. package/packages/daemon/src/api/routeUtils.ts +28 -0
  56. package/packages/daemon/src/api/routes.ts +1004 -0
  57. package/packages/daemon/src/api/server.ts +1388 -0
  58. package/packages/daemon/src/api/settingsRoutes.ts +367 -0
  59. package/packages/daemon/src/api/sqliteErrors.ts +47 -0
  60. package/packages/daemon/src/api/stt.ts +143 -0
  61. package/packages/daemon/src/api/terminalRoutes.ts +200 -0
  62. package/packages/daemon/src/api/validation.ts +287 -0
  63. package/packages/daemon/src/api/validationRoutes.ts +174 -0
  64. package/packages/daemon/src/api/workflowRoutes.ts +567 -0
  65. package/packages/daemon/src/api/workspaceRoutes.ts +151 -0
  66. package/packages/daemon/src/api/ws.ts +1588 -0
  67. package/packages/daemon/src/auth/apiRateLimiter.ts +73 -0
  68. package/packages/daemon/src/auth/authMiddleware.ts +305 -0
  69. package/packages/daemon/src/auth/authService.ts +496 -0
  70. package/packages/daemon/src/auth/rateLimiter.ts +137 -0
  71. package/packages/daemon/src/config/pricing.ts +79 -0
  72. package/packages/daemon/src/crypto/encryption.ts +196 -0
  73. package/packages/daemon/src/db/db.ts +2745 -0
  74. package/packages/daemon/src/db/index.ts +16 -0
  75. package/packages/daemon/src/db/migrations.ts +182 -0
  76. package/packages/daemon/src/db/policyDb.ts +349 -0
  77. package/packages/daemon/src/db/schema.sql +408 -0
  78. package/packages/daemon/src/db/workflowDb.ts +464 -0
  79. package/packages/daemon/src/git/gitUtils.ts +544 -0
  80. package/packages/daemon/src/index.ts +6 -0
  81. package/packages/daemon/src/main.ts +525 -0
  82. package/packages/daemon/src/notifications/pushNotifier.ts +369 -0
  83. package/packages/daemon/src/providers/codexAppServerScaffold.ts +49 -0
  84. package/packages/daemon/src/providers/registry.ts +111 -0
  85. package/packages/daemon/src/providers/types.ts +82 -0
  86. package/packages/daemon/src/sessions/auditLogger.ts +103 -0
  87. package/packages/daemon/src/sessions/policyEngine.ts +165 -0
  88. package/packages/daemon/src/sessions/policyMatcher.ts +114 -0
  89. package/packages/daemon/src/sessions/policyTypes.ts +94 -0
  90. package/packages/daemon/src/sessions/ptyProcess.ts +141 -0
  91. package/packages/daemon/src/sessions/sessionManager.ts +1770 -0
  92. package/packages/daemon/src/sessions/tmuxSession.ts +1073 -0
  93. package/packages/daemon/src/sessions/transcriptManager.ts +110 -0
  94. package/packages/daemon/src/sessions/transcriptParser.ts +149 -0
  95. package/packages/daemon/src/sessions/transcriptTailer.ts +214 -0
  96. package/packages/daemon/src/tracking/tokenTracker.ts +168 -0
  97. package/packages/daemon/src/workflows/contextManager.ts +83 -0
  98. package/packages/daemon/src/workflows/index.ts +31 -0
  99. package/packages/daemon/src/workflows/resultExtractor.ts +118 -0
  100. package/packages/daemon/src/workflows/waitConditionPoller.ts +131 -0
  101. package/packages/daemon/src/workflows/workflowParser.ts +217 -0
  102. package/packages/daemon/src/workflows/workflowRunner.ts +969 -0
  103. package/packages/daemon/src/workflows/workflowTypes.ts +188 -0
  104. package/packages/daemon/src/workflows/workflowValidator.ts +533 -0
  105. package/packages/providers/claude-code/package.json +11 -0
  106. package/packages/providers/claude-code/src/index.ts +7 -0
  107. package/packages/providers/claude-code/src/overlaySettings.ts +198 -0
  108. package/packages/providers/claude-code/src/provider.ts +311 -0
  109. package/packages/web/dist/android-chrome-192x192.png +0 -0
  110. package/packages/web/dist/android-chrome-512x512.png +0 -0
  111. package/packages/web/dist/apple-touch-icon.png +0 -0
  112. package/packages/web/dist/assets/AnalyticsPage-BIopKWRf.js +17 -0
  113. package/packages/web/dist/assets/PoliciesPage-CjdLN3dl.js +11 -0
  114. package/packages/web/dist/assets/SessionDetailPage-BtSA0V0M.js +179 -0
  115. package/packages/web/dist/assets/SettingsPage-Dbbz4Ca5.js +37 -0
  116. package/packages/web/dist/assets/WorkflowsPage-Dv6f3GgU.js +1 -0
  117. package/packages/web/dist/assets/chart-vendor-DlOHLaCG.js +49 -0
  118. package/packages/web/dist/assets/codicon-ngg6Pgfi.ttf +0 -0
  119. package/packages/web/dist/assets/css.worker-BvV5MPou.js +93 -0
  120. package/packages/web/dist/assets/editor.worker-CKy7Pnvo.js +26 -0
  121. package/packages/web/dist/assets/html.worker-BLJhxQJQ.js +470 -0
  122. package/packages/web/dist/assets/index-BbdhRfr2.css +1 -0
  123. package/packages/web/dist/assets/index-hgphORiw.js +204 -0
  124. package/packages/web/dist/assets/json.worker-usMZ-FED.js +58 -0
  125. package/packages/web/dist/assets/monaco-core-B_19GPAS.css +1 -0
  126. package/packages/web/dist/assets/monaco-core-DQ5Mk8AK.js +1234 -0
  127. package/packages/web/dist/assets/monaco-react-DfZNWvtW.js +11 -0
  128. package/packages/web/dist/assets/monacoSetup-DvBj52bT.js +1 -0
  129. package/packages/web/dist/assets/pencil-Dbczxz59.js +11 -0
  130. package/packages/web/dist/assets/react-vendor-B5MgMUHH.js +136 -0
  131. package/packages/web/dist/assets/refresh-cw-B0MGsYPL.js +6 -0
  132. package/packages/web/dist/assets/tabs-C8LsWiR5.js +1 -0
  133. package/packages/web/dist/assets/terminal-vendor-Cs8KPbV3.js +9 -0
  134. package/packages/web/dist/assets/terminal-vendor-LcAfv9l9.css +32 -0
  135. package/packages/web/dist/assets/trash-2-Btlg0d4l.js +6 -0
  136. package/packages/web/dist/assets/ts.worker-DGHjMaqB.js +67731 -0
  137. package/packages/web/dist/favicon.ico +0 -0
  138. package/packages/web/dist/icon.svg +1 -0
  139. package/packages/web/dist/index.html +29 -0
  140. package/packages/web/dist/manifest.json +29 -0
  141. package/packages/web/dist/og-image.png +0 -0
  142. package/packages/web/dist/originals/android-chrome-192x192.png +0 -0
  143. package/packages/web/dist/originals/android-chrome-512x512.png +0 -0
  144. package/packages/web/dist/originals/apple-touch-icon.png +0 -0
  145. package/packages/web/dist/originals/favicon.ico +0 -0
  146. package/packages/web/dist/piper.svg +1 -0
  147. package/packages/web/dist/sounds/codepiper-soft-chime.wav +0 -0
  148. package/packages/web/dist/sw.js +257 -0
  149. package/scripts/postinstall-link-workspaces.mjs +58 -0
@@ -0,0 +1,786 @@
1
+ /**
2
+ * Workflow CLI commands
3
+ */
4
+
5
+ import { readFileSync } from "node:fs";
6
+ import { load as parseYAML } from "js-yaml";
7
+ import { readErrorJson, readJson, responseErrorMessage } from "../lib/api";
8
+ import { getRequiredValue } from "../lib/args";
9
+
10
+ /**
11
+ * Workflow create command options
12
+ */
13
+ export interface WorkflowCreateOptions {
14
+ file: string;
15
+ id?: string;
16
+ socket: string;
17
+ }
18
+
19
+ /**
20
+ * Workflow list command options
21
+ */
22
+ export interface WorkflowListOptions {
23
+ socket: string;
24
+ }
25
+
26
+ /**
27
+ * Workflow show command options
28
+ */
29
+ export interface WorkflowShowOptions {
30
+ workflowId: string;
31
+ socket: string;
32
+ }
33
+
34
+ /**
35
+ * Workflow run command options
36
+ */
37
+ export interface WorkflowRunOptions {
38
+ workflowId: string;
39
+ variables: Record<string, string>;
40
+ socket: string;
41
+ }
42
+
43
+ /**
44
+ * Workflow status command options
45
+ */
46
+ export interface WorkflowStatusOptions {
47
+ executionId: string;
48
+ socket: string;
49
+ }
50
+
51
+ /**
52
+ * Workflow cancel command options
53
+ */
54
+ export interface WorkflowCancelOptions {
55
+ executionId: string;
56
+ socket: string;
57
+ }
58
+
59
+ /**
60
+ * Workflow logs command options
61
+ */
62
+ export interface WorkflowLogsOptions {
63
+ executionId: string;
64
+ follow: boolean;
65
+ socket: string;
66
+ }
67
+
68
+ interface WorkflowExecutionView {
69
+ execution: {
70
+ id: string;
71
+ status: string;
72
+ };
73
+ steps?: Array<{
74
+ stepName: string;
75
+ status: string;
76
+ startedAt?: string;
77
+ completedAt?: string;
78
+ result?: unknown;
79
+ errorMessage?: string;
80
+ }>;
81
+ }
82
+
83
+ interface WorkflowSummary {
84
+ id: string;
85
+ name: string;
86
+ description?: string;
87
+ createdAt: string;
88
+ updatedAt: string;
89
+ definition: {
90
+ steps?: unknown[];
91
+ [key: string]: unknown;
92
+ };
93
+ }
94
+
95
+ interface WorkflowListResponse {
96
+ workflows: WorkflowSummary[];
97
+ }
98
+
99
+ interface WorkflowShowResponse {
100
+ workflow: WorkflowSummary;
101
+ }
102
+
103
+ interface WorkflowCreateResponse {
104
+ workflow: {
105
+ id: string;
106
+ name: string;
107
+ description?: string;
108
+ };
109
+ }
110
+
111
+ interface WorkflowRunResponse {
112
+ executionId: string;
113
+ status: string;
114
+ }
115
+
116
+ interface WorkflowStatusResponse {
117
+ execution: {
118
+ id: string;
119
+ workflowId: string;
120
+ status: string;
121
+ startedAt: string;
122
+ completedAt?: string;
123
+ errorMessage?: string;
124
+ };
125
+ steps?: Array<{
126
+ stepName: string;
127
+ status: string;
128
+ sessionId?: string;
129
+ errorMessage?: string;
130
+ }>;
131
+ }
132
+
133
+ interface WorkflowValidationError {
134
+ message?: string;
135
+ path?: string;
136
+ }
137
+
138
+ /**
139
+ * Parse workflow create command options
140
+ */
141
+ export function parseWorkflowCreateOptions(args: string[]): WorkflowCreateOptions {
142
+ let file: string | undefined;
143
+ let id: string | undefined;
144
+ let socket = "/tmp/codepiper.sock";
145
+
146
+ for (let i = 0; i < args.length; i++) {
147
+ const arg = args[i];
148
+ if (arg === undefined) {
149
+ continue;
150
+ }
151
+
152
+ if (arg === "--socket" || arg === "-s") {
153
+ socket = getRequiredValue(args, i, arg);
154
+ i++;
155
+ } else if (arg === "--id") {
156
+ id = getRequiredValue(args, i, arg);
157
+ i++;
158
+ } else if (!arg.startsWith("-")) {
159
+ if (!file) {
160
+ file = arg;
161
+ }
162
+ }
163
+ }
164
+
165
+ if (!file) {
166
+ throw new Error("file path is required");
167
+ }
168
+
169
+ const options: WorkflowCreateOptions = {
170
+ file,
171
+ socket,
172
+ };
173
+ if (id !== undefined) {
174
+ options.id = id;
175
+ }
176
+
177
+ return options;
178
+ }
179
+
180
+ /**
181
+ * Parse workflow list command options
182
+ */
183
+ export function parseWorkflowListOptions(args: string[]): WorkflowListOptions {
184
+ let socket = "/tmp/codepiper.sock";
185
+
186
+ for (let i = 0; i < args.length; i++) {
187
+ const arg = args[i];
188
+ if (arg === undefined) {
189
+ continue;
190
+ }
191
+
192
+ if (arg === "--socket" || arg === "-s") {
193
+ socket = getRequiredValue(args, i, arg);
194
+ i++;
195
+ }
196
+ }
197
+
198
+ return { socket };
199
+ }
200
+
201
+ /**
202
+ * Parse workflow show command options
203
+ */
204
+ export function parseWorkflowShowOptions(args: string[]): WorkflowShowOptions {
205
+ let workflowId: string | undefined;
206
+ let socket = "/tmp/codepiper.sock";
207
+
208
+ for (let i = 0; i < args.length; i++) {
209
+ const arg = args[i];
210
+ if (arg === undefined) {
211
+ continue;
212
+ }
213
+
214
+ if (arg === "--socket" || arg === "-s") {
215
+ socket = getRequiredValue(args, i, arg);
216
+ i++;
217
+ } else if (!arg.startsWith("-")) {
218
+ if (!workflowId) {
219
+ workflowId = arg;
220
+ }
221
+ }
222
+ }
223
+
224
+ if (!workflowId) {
225
+ throw new Error("workflow ID is required");
226
+ }
227
+
228
+ return { workflowId, socket };
229
+ }
230
+
231
+ /**
232
+ * Parse workflow run command options
233
+ */
234
+ export function parseWorkflowRunOptions(args: string[]): WorkflowRunOptions {
235
+ let workflowId: string | undefined;
236
+ let socket = "/tmp/codepiper.sock";
237
+ const variables: Record<string, string> = {};
238
+
239
+ for (let i = 0; i < args.length; i++) {
240
+ const arg = args[i];
241
+ if (arg === undefined) {
242
+ continue;
243
+ }
244
+
245
+ if (arg === "--socket" || arg === "-s") {
246
+ socket = getRequiredValue(args, i, arg);
247
+ i++;
248
+ } else if (arg === "--var" || arg === "-v") {
249
+ const varArg = getRequiredValue(args, i, arg);
250
+ i++;
251
+ const match = varArg.match(/^([^=]+)=(.*)$/);
252
+ if (!match) {
253
+ throw new Error(`Invalid variable format: ${varArg}. Expected KEY=VALUE`);
254
+ }
255
+ const key = match[1];
256
+ const value = match[2];
257
+ if (key === undefined || value === undefined) {
258
+ throw new Error(`Invalid variable format: ${varArg}. Expected KEY=VALUE`);
259
+ }
260
+ variables[key] = value;
261
+ } else if (!arg.startsWith("-")) {
262
+ if (!workflowId) {
263
+ workflowId = arg;
264
+ }
265
+ }
266
+ }
267
+
268
+ if (!workflowId) {
269
+ throw new Error("workflow ID is required");
270
+ }
271
+
272
+ return { workflowId, variables, socket };
273
+ }
274
+
275
+ /**
276
+ * Parse workflow status command options
277
+ */
278
+ export function parseWorkflowStatusOptions(args: string[]): WorkflowStatusOptions {
279
+ let executionId: string | undefined;
280
+ let socket = "/tmp/codepiper.sock";
281
+
282
+ for (let i = 0; i < args.length; i++) {
283
+ const arg = args[i];
284
+ if (arg === undefined) {
285
+ continue;
286
+ }
287
+
288
+ if (arg === "--socket" || arg === "-s") {
289
+ socket = getRequiredValue(args, i, arg);
290
+ i++;
291
+ } else if (!arg.startsWith("-")) {
292
+ if (!executionId) {
293
+ executionId = arg;
294
+ }
295
+ }
296
+ }
297
+
298
+ if (!executionId) {
299
+ throw new Error("execution ID is required");
300
+ }
301
+
302
+ return { executionId, socket };
303
+ }
304
+
305
+ /**
306
+ * Parse workflow cancel command options
307
+ */
308
+ export function parseWorkflowCancelOptions(args: string[]): WorkflowCancelOptions {
309
+ let executionId: string | undefined;
310
+ let socket = "/tmp/codepiper.sock";
311
+
312
+ for (let i = 0; i < args.length; i++) {
313
+ const arg = args[i];
314
+ if (arg === undefined) {
315
+ continue;
316
+ }
317
+
318
+ if (arg === "--socket" || arg === "-s") {
319
+ socket = getRequiredValue(args, i, arg);
320
+ i++;
321
+ } else if (!arg.startsWith("-")) {
322
+ if (!executionId) {
323
+ executionId = arg;
324
+ }
325
+ }
326
+ }
327
+
328
+ if (!executionId) {
329
+ throw new Error("execution ID is required");
330
+ }
331
+
332
+ return { executionId, socket };
333
+ }
334
+
335
+ /**
336
+ * Parse workflow logs command options
337
+ */
338
+ export function parseWorkflowLogsOptions(args: string[]): WorkflowLogsOptions {
339
+ let executionId: string | undefined;
340
+ let socket = "/tmp/codepiper.sock";
341
+ let follow = false;
342
+
343
+ for (let i = 0; i < args.length; i++) {
344
+ const arg = args[i];
345
+ if (arg === undefined) {
346
+ continue;
347
+ }
348
+
349
+ if (arg === "--socket" || arg === "-s") {
350
+ socket = getRequiredValue(args, i, arg);
351
+ i++;
352
+ } else if (arg === "--follow" || arg === "-f") {
353
+ follow = true;
354
+ } else if (!arg.startsWith("-")) {
355
+ if (!executionId) {
356
+ executionId = arg;
357
+ }
358
+ }
359
+ }
360
+
361
+ if (!executionId) {
362
+ throw new Error("execution ID is required");
363
+ }
364
+
365
+ return { executionId, follow, socket };
366
+ }
367
+
368
+ /**
369
+ * Create workflow from file
370
+ */
371
+ export async function createWorkflow(options: WorkflowCreateOptions): Promise<void> {
372
+ // Read and parse file
373
+ const content = readFileSync(options.file, "utf-8");
374
+ let definition: any;
375
+
376
+ if (options.file.endsWith(".yaml") || options.file.endsWith(".yml")) {
377
+ definition = parseYAML(content);
378
+ } else if (options.file.endsWith(".json")) {
379
+ definition = JSON.parse(content);
380
+ } else {
381
+ throw new Error("File must be .yaml, .yml, or .json");
382
+ }
383
+
384
+ // Generate ID if not provided
385
+ const id = options.id ?? crypto.randomUUID();
386
+
387
+ // Extract name and description from definition
388
+ const name = definition.name ?? "Unnamed Workflow";
389
+ const description = definition.description;
390
+
391
+ // Create workflow via API
392
+ try {
393
+ const response = await fetch("http://localhost/workflows", {
394
+ unix: options.socket,
395
+ method: "POST",
396
+ headers: {
397
+ "Content-Type": "application/json",
398
+ },
399
+ body: JSON.stringify({
400
+ id,
401
+ name,
402
+ description,
403
+ definition,
404
+ }),
405
+ });
406
+
407
+ if (!response.ok) {
408
+ const errorData = await readErrorJson(response);
409
+ const validationErrors = Array.isArray(errorData.validationErrors)
410
+ ? (errorData.validationErrors as WorkflowValidationError[])
411
+ : [];
412
+
413
+ if (response.status === 422 && validationErrors.length > 0) {
414
+ const details = validationErrors
415
+ .map((error) => {
416
+ const prefix = error.path ? `${error.path}: ` : "";
417
+ return `- ${prefix}${error.message ?? "Unknown validation error"}`;
418
+ })
419
+ .join("\n");
420
+ throw new Error(`Workflow validation failed:\n${details}`);
421
+ }
422
+
423
+ throw new Error(responseErrorMessage(response, errorData));
424
+ }
425
+
426
+ const data = await readJson<WorkflowCreateResponse>(response);
427
+ console.log(`Workflow created: ${data.workflow.id}`);
428
+ console.log(`Name: ${data.workflow.name}`);
429
+ if (data.workflow.description) {
430
+ console.log(`Description: ${data.workflow.description}`);
431
+ }
432
+ } catch (error: any) {
433
+ if (error.code === "ENOENT" || error.message?.includes("ENOENT")) {
434
+ throw new Error(`Failed to connect to daemon at ${options.socket}. Is the daemon running?`);
435
+ }
436
+ throw error;
437
+ }
438
+ }
439
+
440
+ /**
441
+ * List all workflows
442
+ */
443
+ export async function listWorkflows(options: WorkflowListOptions): Promise<void> {
444
+ try {
445
+ const response = await fetch("http://localhost/workflows", {
446
+ unix: options.socket,
447
+ method: "GET",
448
+ });
449
+
450
+ if (!response.ok) {
451
+ const errorData = await readErrorJson(response);
452
+ throw new Error(responseErrorMessage(response, errorData));
453
+ }
454
+
455
+ const data = await readJson<WorkflowListResponse>(response);
456
+
457
+ if (data.workflows.length === 0) {
458
+ console.log("No workflows found");
459
+ return;
460
+ }
461
+
462
+ console.log(`Found ${data.workflows.length} workflow(s):\n`);
463
+
464
+ for (const workflow of data.workflows) {
465
+ console.log(`ID: ${workflow.id}`);
466
+ console.log(`Name: ${workflow.name}`);
467
+ if (workflow.description) {
468
+ console.log(`Description: ${workflow.description}`);
469
+ }
470
+ console.log(`Created: ${new Date(workflow.createdAt).toLocaleString()}`);
471
+ console.log(`Steps: ${workflow.definition.steps?.length ?? 0}`);
472
+ console.log("");
473
+ }
474
+ } catch (error: any) {
475
+ if (error.code === "ENOENT" || error.message?.includes("ENOENT")) {
476
+ throw new Error(`Failed to connect to daemon at ${options.socket}. Is the daemon running?`);
477
+ }
478
+ throw error;
479
+ }
480
+ }
481
+
482
+ /**
483
+ * Show workflow definition
484
+ */
485
+ export async function showWorkflow(options: WorkflowShowOptions): Promise<void> {
486
+ try {
487
+ const response = await fetch(`http://localhost/workflows/${options.workflowId}`, {
488
+ unix: options.socket,
489
+ method: "GET",
490
+ });
491
+
492
+ if (!response.ok) {
493
+ const errorData = await readErrorJson(response);
494
+ throw new Error(responseErrorMessage(response, errorData));
495
+ }
496
+
497
+ const data = await readJson<WorkflowShowResponse>(response);
498
+
499
+ console.log(`ID: ${data.workflow.id}`);
500
+ console.log(`Name: ${data.workflow.name}`);
501
+ if (data.workflow.description) {
502
+ console.log(`Description: ${data.workflow.description}`);
503
+ }
504
+ console.log(`Created: ${new Date(data.workflow.createdAt).toLocaleString()}`);
505
+ console.log(`Updated: ${new Date(data.workflow.updatedAt).toLocaleString()}`);
506
+ console.log("\nDefinition:");
507
+ console.log(JSON.stringify(data.workflow.definition, null, 2));
508
+ } catch (error: any) {
509
+ if (error.code === "ENOENT" || error.message?.includes("ENOENT")) {
510
+ throw new Error(`Failed to connect to daemon at ${options.socket}. Is the daemon running?`);
511
+ }
512
+ throw error;
513
+ }
514
+ }
515
+
516
+ /**
517
+ * Run workflow
518
+ */
519
+ export async function runWorkflow(options: WorkflowRunOptions): Promise<void> {
520
+ try {
521
+ const response = await fetch(`http://localhost/workflows/${options.workflowId}/execute`, {
522
+ unix: options.socket,
523
+ method: "POST",
524
+ headers: {
525
+ "Content-Type": "application/json",
526
+ },
527
+ body: JSON.stringify({
528
+ variables: options.variables,
529
+ }),
530
+ });
531
+
532
+ if (!response.ok) {
533
+ const errorData = await readErrorJson(response);
534
+ throw new Error(responseErrorMessage(response, errorData));
535
+ }
536
+
537
+ const data = await readJson<WorkflowRunResponse>(response);
538
+
539
+ console.log(`Workflow execution started: ${data.executionId}`);
540
+ console.log(`Status: ${data.status}`);
541
+ console.log("\nUse 'codepiper workflow status <executionId>' to check progress");
542
+ } catch (error: any) {
543
+ if (error.code === "ENOENT" || error.message?.includes("ENOENT")) {
544
+ throw new Error(`Failed to connect to daemon at ${options.socket}. Is the daemon running?`);
545
+ }
546
+ throw error;
547
+ }
548
+ }
549
+
550
+ /**
551
+ * Get workflow execution status
552
+ */
553
+ export async function getWorkflowStatus(options: WorkflowStatusOptions): Promise<void> {
554
+ try {
555
+ const response = await fetch(`http://localhost/workflows/executions/${options.executionId}`, {
556
+ unix: options.socket,
557
+ method: "GET",
558
+ });
559
+
560
+ if (!response.ok) {
561
+ const errorData = await readErrorJson(response);
562
+ throw new Error(responseErrorMessage(response, errorData));
563
+ }
564
+
565
+ const data = await readJson<WorkflowStatusResponse>(response);
566
+
567
+ console.log(`Execution ID: ${data.execution.id}`);
568
+ console.log(`Workflow ID: ${data.execution.workflowId}`);
569
+ console.log(`Status: ${data.execution.status}`);
570
+ console.log(`Started: ${new Date(data.execution.startedAt).toLocaleString()}`);
571
+
572
+ if (data.execution.completedAt) {
573
+ console.log(`Completed: ${new Date(data.execution.completedAt).toLocaleString()}`);
574
+ }
575
+
576
+ if (data.execution.errorMessage) {
577
+ console.log(`Error: ${data.execution.errorMessage}`);
578
+ }
579
+
580
+ if (data.steps && data.steps.length > 0) {
581
+ console.log("\nSteps:");
582
+ for (const step of data.steps) {
583
+ console.log(` - ${step.stepName}: ${step.status}`);
584
+ if (step.sessionId) {
585
+ console.log(` Session: ${step.sessionId}`);
586
+ }
587
+ if (step.errorMessage) {
588
+ console.log(` Error: ${step.errorMessage}`);
589
+ }
590
+ }
591
+ }
592
+ } catch (error: any) {
593
+ if (error.code === "ENOENT" || error.message?.includes("ENOENT")) {
594
+ throw new Error(`Failed to connect to daemon at ${options.socket}. Is the daemon running?`);
595
+ }
596
+ throw error;
597
+ }
598
+ }
599
+
600
+ /**
601
+ * Cancel workflow execution
602
+ */
603
+ export async function cancelWorkflow(options: WorkflowCancelOptions): Promise<void> {
604
+ try {
605
+ const response = await fetch(
606
+ `http://localhost/workflows/executions/${options.executionId}/cancel`,
607
+ {
608
+ unix: options.socket,
609
+ method: "POST",
610
+ }
611
+ );
612
+
613
+ if (!response.ok) {
614
+ const errorData = await readErrorJson(response);
615
+ throw new Error(responseErrorMessage(response, errorData));
616
+ }
617
+
618
+ console.log(`Workflow execution cancelled: ${options.executionId}`);
619
+ } catch (error: any) {
620
+ if (error.code === "ENOENT" || error.message?.includes("ENOENT")) {
621
+ throw new Error(`Failed to connect to daemon at ${options.socket}. Is the daemon running?`);
622
+ }
623
+ throw error;
624
+ }
625
+ }
626
+
627
+ /**
628
+ * Show workflow execution logs
629
+ */
630
+ export async function showWorkflowLogs(options: WorkflowLogsOptions): Promise<void> {
631
+ try {
632
+ const data = await fetchWorkflowExecution(options.executionId, options.socket);
633
+ printWorkflowExecution(data);
634
+
635
+ if (options.follow && !isTerminalExecutionStatus(data.execution.status)) {
636
+ console.log("Following execution updates... Press Ctrl+C to stop.\n");
637
+ await followWorkflowLogs(options, data);
638
+ }
639
+ } catch (error: any) {
640
+ if (error.code === "ENOENT" || error.message?.includes("ENOENT")) {
641
+ throw new Error(`Failed to connect to daemon at ${options.socket}. Is the daemon running?`);
642
+ }
643
+ throw error;
644
+ }
645
+ }
646
+
647
+ async function fetchWorkflowExecution(
648
+ executionId: string,
649
+ socket: string
650
+ ): Promise<WorkflowExecutionView> {
651
+ const response = await fetch(`http://localhost/workflows/executions/${executionId}`, {
652
+ unix: socket,
653
+ method: "GET",
654
+ });
655
+
656
+ if (!response.ok) {
657
+ const errorData = await readErrorJson(response);
658
+ throw new Error(responseErrorMessage(response, errorData));
659
+ }
660
+
661
+ return await readJson<WorkflowExecutionView>(response);
662
+ }
663
+
664
+ function printWorkflowExecution(data: WorkflowExecutionView): void {
665
+ console.log(`Execution: ${data.execution.id}`);
666
+ console.log(`Status: ${data.execution.status}`);
667
+ console.log("");
668
+
669
+ if (data.steps && data.steps.length > 0) {
670
+ for (const step of data.steps) {
671
+ console.log(`[${step.stepName}] ${step.status}`);
672
+ if (step.startedAt) {
673
+ console.log(` Started: ${new Date(step.startedAt).toLocaleString()}`);
674
+ }
675
+ if (step.completedAt) {
676
+ console.log(` Completed: ${new Date(step.completedAt).toLocaleString()}`);
677
+ }
678
+ if (step.result !== undefined) {
679
+ console.log(` Result: ${JSON.stringify(step.result)}`);
680
+ }
681
+ if (step.errorMessage) {
682
+ console.log(` Error: ${step.errorMessage}`);
683
+ }
684
+ console.log("");
685
+ }
686
+ } else {
687
+ console.log("No steps executed yet");
688
+ }
689
+ }
690
+
691
+ function isTerminalExecutionStatus(status: string): boolean {
692
+ return status === "completed" || status === "failed" || status === "cancelled";
693
+ }
694
+
695
+ function serializeExecutionView(data: WorkflowExecutionView): string {
696
+ return JSON.stringify({
697
+ status: data.execution.status,
698
+ steps: (data.steps || []).map((step) => ({
699
+ stepName: step.stepName,
700
+ status: step.status,
701
+ startedAt: step.startedAt,
702
+ completedAt: step.completedAt,
703
+ result: step.result,
704
+ errorMessage: step.errorMessage,
705
+ })),
706
+ });
707
+ }
708
+
709
+ function getWorkflowFollowPollMs(): number {
710
+ const raw = process.env.CODEPIPER_WORKFLOW_LOG_POLL_MS;
711
+ if (!raw) return 1000;
712
+ const parsed = Number.parseInt(raw, 10);
713
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 1000;
714
+ }
715
+
716
+ async function followWorkflowLogs(
717
+ options: WorkflowLogsOptions,
718
+ initialData: WorkflowExecutionView
719
+ ): Promise<void> {
720
+ let stopping = false;
721
+ let lastSnapshot = serializeExecutionView(initialData);
722
+ const pollMs = getWorkflowFollowPollMs();
723
+
724
+ const sigintHandler = () => {
725
+ if (!stopping) {
726
+ stopping = true;
727
+ console.log("\nStopping...");
728
+ }
729
+ };
730
+
731
+ process.on("SIGINT", sigintHandler);
732
+
733
+ try {
734
+ while (!stopping) {
735
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
736
+ if (stopping) {
737
+ break;
738
+ }
739
+
740
+ const current = await fetchWorkflowExecution(options.executionId, options.socket);
741
+ const snapshot = serializeExecutionView(current);
742
+ if (snapshot !== lastSnapshot) {
743
+ console.log("");
744
+ printWorkflowExecution(current);
745
+ lastSnapshot = snapshot;
746
+ }
747
+
748
+ if (isTerminalExecutionStatus(current.execution.status)) {
749
+ break;
750
+ }
751
+ }
752
+ } finally {
753
+ process.off("SIGINT", sigintHandler);
754
+ }
755
+ }
756
+
757
+ /**
758
+ * Main workflow command dispatcher
759
+ */
760
+ export async function runWorkflowCommand(args: string[]): Promise<void> {
761
+ if (args.length === 0) {
762
+ throw new Error("workflow subcommand required (create, list, show, run, status, cancel, logs)");
763
+ }
764
+
765
+ const subcommand = args[0];
766
+ const subArgs = args.slice(1);
767
+
768
+ switch (subcommand) {
769
+ case "create":
770
+ return createWorkflow(parseWorkflowCreateOptions(subArgs));
771
+ case "list":
772
+ return listWorkflows(parseWorkflowListOptions(subArgs));
773
+ case "show":
774
+ return showWorkflow(parseWorkflowShowOptions(subArgs));
775
+ case "run":
776
+ return runWorkflow(parseWorkflowRunOptions(subArgs));
777
+ case "status":
778
+ return getWorkflowStatus(parseWorkflowStatusOptions(subArgs));
779
+ case "cancel":
780
+ return cancelWorkflow(parseWorkflowCancelOptions(subArgs));
781
+ case "logs":
782
+ return showWorkflowLogs(parseWorkflowLogsOptions(subArgs));
783
+ default:
784
+ throw new Error(`Unknown workflow subcommand: ${subcommand}`);
785
+ }
786
+ }