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.
- package/.env.example +28 -0
- package/CHANGELOG.md +10 -0
- package/LEGAL_NOTICE.md +39 -0
- package/LICENSE +21 -0
- package/README.md +524 -0
- package/package.json +90 -0
- package/packages/cli/package.json +13 -0
- package/packages/cli/src/commands/analytics.ts +157 -0
- package/packages/cli/src/commands/attach.ts +299 -0
- package/packages/cli/src/commands/audit.ts +50 -0
- package/packages/cli/src/commands/auth.ts +261 -0
- package/packages/cli/src/commands/daemon.ts +162 -0
- package/packages/cli/src/commands/doctor.ts +303 -0
- package/packages/cli/src/commands/env-set.ts +162 -0
- package/packages/cli/src/commands/hook-forward.ts +268 -0
- package/packages/cli/src/commands/keys.ts +77 -0
- package/packages/cli/src/commands/kill.ts +19 -0
- package/packages/cli/src/commands/logs.ts +419 -0
- package/packages/cli/src/commands/model.ts +172 -0
- package/packages/cli/src/commands/policy-set.ts +185 -0
- package/packages/cli/src/commands/policy.ts +227 -0
- package/packages/cli/src/commands/providers.ts +114 -0
- package/packages/cli/src/commands/resize.ts +34 -0
- package/packages/cli/src/commands/send.ts +184 -0
- package/packages/cli/src/commands/sessions.ts +202 -0
- package/packages/cli/src/commands/slash.ts +92 -0
- package/packages/cli/src/commands/start.ts +243 -0
- package/packages/cli/src/commands/stop.ts +19 -0
- package/packages/cli/src/commands/tail.ts +137 -0
- package/packages/cli/src/commands/workflow.ts +786 -0
- package/packages/cli/src/commands/workspace.ts +127 -0
- package/packages/cli/src/lib/api.ts +78 -0
- package/packages/cli/src/lib/args.ts +72 -0
- package/packages/cli/src/lib/format.ts +93 -0
- package/packages/cli/src/main.ts +563 -0
- package/packages/core/package.json +7 -0
- package/packages/core/src/config.ts +30 -0
- package/packages/core/src/errors.ts +38 -0
- package/packages/core/src/eventBus.ts +56 -0
- package/packages/core/src/eventBusAdapter.ts +143 -0
- package/packages/core/src/index.ts +10 -0
- package/packages/core/src/sqliteEventBus.ts +336 -0
- package/packages/core/src/types.ts +63 -0
- package/packages/daemon/package.json +11 -0
- package/packages/daemon/src/api/analyticsRoutes.ts +343 -0
- package/packages/daemon/src/api/authRoutes.ts +344 -0
- package/packages/daemon/src/api/bodyLimit.ts +133 -0
- package/packages/daemon/src/api/envSetRoutes.ts +170 -0
- package/packages/daemon/src/api/gitRoutes.ts +409 -0
- package/packages/daemon/src/api/hooks.ts +588 -0
- package/packages/daemon/src/api/inputPolicy.ts +249 -0
- package/packages/daemon/src/api/notificationRoutes.ts +532 -0
- package/packages/daemon/src/api/policyRoutes.ts +234 -0
- package/packages/daemon/src/api/policySetRoutes.ts +445 -0
- package/packages/daemon/src/api/routeUtils.ts +28 -0
- package/packages/daemon/src/api/routes.ts +1004 -0
- package/packages/daemon/src/api/server.ts +1388 -0
- package/packages/daemon/src/api/settingsRoutes.ts +367 -0
- package/packages/daemon/src/api/sqliteErrors.ts +47 -0
- package/packages/daemon/src/api/stt.ts +143 -0
- package/packages/daemon/src/api/terminalRoutes.ts +200 -0
- package/packages/daemon/src/api/validation.ts +287 -0
- package/packages/daemon/src/api/validationRoutes.ts +174 -0
- package/packages/daemon/src/api/workflowRoutes.ts +567 -0
- package/packages/daemon/src/api/workspaceRoutes.ts +151 -0
- package/packages/daemon/src/api/ws.ts +1588 -0
- package/packages/daemon/src/auth/apiRateLimiter.ts +73 -0
- package/packages/daemon/src/auth/authMiddleware.ts +305 -0
- package/packages/daemon/src/auth/authService.ts +496 -0
- package/packages/daemon/src/auth/rateLimiter.ts +137 -0
- package/packages/daemon/src/config/pricing.ts +79 -0
- package/packages/daemon/src/crypto/encryption.ts +196 -0
- package/packages/daemon/src/db/db.ts +2745 -0
- package/packages/daemon/src/db/index.ts +16 -0
- package/packages/daemon/src/db/migrations.ts +182 -0
- package/packages/daemon/src/db/policyDb.ts +349 -0
- package/packages/daemon/src/db/schema.sql +408 -0
- package/packages/daemon/src/db/workflowDb.ts +464 -0
- package/packages/daemon/src/git/gitUtils.ts +544 -0
- package/packages/daemon/src/index.ts +6 -0
- package/packages/daemon/src/main.ts +525 -0
- package/packages/daemon/src/notifications/pushNotifier.ts +369 -0
- package/packages/daemon/src/providers/codexAppServerScaffold.ts +49 -0
- package/packages/daemon/src/providers/registry.ts +111 -0
- package/packages/daemon/src/providers/types.ts +82 -0
- package/packages/daemon/src/sessions/auditLogger.ts +103 -0
- package/packages/daemon/src/sessions/policyEngine.ts +165 -0
- package/packages/daemon/src/sessions/policyMatcher.ts +114 -0
- package/packages/daemon/src/sessions/policyTypes.ts +94 -0
- package/packages/daemon/src/sessions/ptyProcess.ts +141 -0
- package/packages/daemon/src/sessions/sessionManager.ts +1770 -0
- package/packages/daemon/src/sessions/tmuxSession.ts +1073 -0
- package/packages/daemon/src/sessions/transcriptManager.ts +110 -0
- package/packages/daemon/src/sessions/transcriptParser.ts +149 -0
- package/packages/daemon/src/sessions/transcriptTailer.ts +214 -0
- package/packages/daemon/src/tracking/tokenTracker.ts +168 -0
- package/packages/daemon/src/workflows/contextManager.ts +83 -0
- package/packages/daemon/src/workflows/index.ts +31 -0
- package/packages/daemon/src/workflows/resultExtractor.ts +118 -0
- package/packages/daemon/src/workflows/waitConditionPoller.ts +131 -0
- package/packages/daemon/src/workflows/workflowParser.ts +217 -0
- package/packages/daemon/src/workflows/workflowRunner.ts +969 -0
- package/packages/daemon/src/workflows/workflowTypes.ts +188 -0
- package/packages/daemon/src/workflows/workflowValidator.ts +533 -0
- package/packages/providers/claude-code/package.json +11 -0
- package/packages/providers/claude-code/src/index.ts +7 -0
- package/packages/providers/claude-code/src/overlaySettings.ts +198 -0
- package/packages/providers/claude-code/src/provider.ts +311 -0
- package/packages/web/dist/android-chrome-192x192.png +0 -0
- package/packages/web/dist/android-chrome-512x512.png +0 -0
- package/packages/web/dist/apple-touch-icon.png +0 -0
- package/packages/web/dist/assets/AnalyticsPage-BIopKWRf.js +17 -0
- package/packages/web/dist/assets/PoliciesPage-CjdLN3dl.js +11 -0
- package/packages/web/dist/assets/SessionDetailPage-BtSA0V0M.js +179 -0
- package/packages/web/dist/assets/SettingsPage-Dbbz4Ca5.js +37 -0
- package/packages/web/dist/assets/WorkflowsPage-Dv6f3GgU.js +1 -0
- package/packages/web/dist/assets/chart-vendor-DlOHLaCG.js +49 -0
- package/packages/web/dist/assets/codicon-ngg6Pgfi.ttf +0 -0
- package/packages/web/dist/assets/css.worker-BvV5MPou.js +93 -0
- package/packages/web/dist/assets/editor.worker-CKy7Pnvo.js +26 -0
- package/packages/web/dist/assets/html.worker-BLJhxQJQ.js +470 -0
- package/packages/web/dist/assets/index-BbdhRfr2.css +1 -0
- package/packages/web/dist/assets/index-hgphORiw.js +204 -0
- package/packages/web/dist/assets/json.worker-usMZ-FED.js +58 -0
- package/packages/web/dist/assets/monaco-core-B_19GPAS.css +1 -0
- package/packages/web/dist/assets/monaco-core-DQ5Mk8AK.js +1234 -0
- package/packages/web/dist/assets/monaco-react-DfZNWvtW.js +11 -0
- package/packages/web/dist/assets/monacoSetup-DvBj52bT.js +1 -0
- package/packages/web/dist/assets/pencil-Dbczxz59.js +11 -0
- package/packages/web/dist/assets/react-vendor-B5MgMUHH.js +136 -0
- package/packages/web/dist/assets/refresh-cw-B0MGsYPL.js +6 -0
- package/packages/web/dist/assets/tabs-C8LsWiR5.js +1 -0
- package/packages/web/dist/assets/terminal-vendor-Cs8KPbV3.js +9 -0
- package/packages/web/dist/assets/terminal-vendor-LcAfv9l9.css +32 -0
- package/packages/web/dist/assets/trash-2-Btlg0d4l.js +6 -0
- package/packages/web/dist/assets/ts.worker-DGHjMaqB.js +67731 -0
- package/packages/web/dist/favicon.ico +0 -0
- package/packages/web/dist/icon.svg +1 -0
- package/packages/web/dist/index.html +29 -0
- package/packages/web/dist/manifest.json +29 -0
- package/packages/web/dist/og-image.png +0 -0
- package/packages/web/dist/originals/android-chrome-192x192.png +0 -0
- package/packages/web/dist/originals/android-chrome-512x512.png +0 -0
- package/packages/web/dist/originals/apple-touch-icon.png +0 -0
- package/packages/web/dist/originals/favicon.ico +0 -0
- package/packages/web/dist/piper.svg +1 -0
- package/packages/web/dist/sounds/codepiper-soft-chime.wav +0 -0
- package/packages/web/dist/sw.js +257 -0
- 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
|
+
}
|