@matthugh1/conductor-cli 0.2.0 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,435 @@
1
+ #!/usr/bin/env node
2
+
3
+ // ../../src/core/daemon-client-types.ts
4
+ var DaemonClientError = class extends Error {
5
+ constructor(message, status) {
6
+ super(message);
7
+ this.status = status;
8
+ this.name = "DaemonClientError";
9
+ }
10
+ status;
11
+ };
12
+ var RETRY_STATUSES = /* @__PURE__ */ new Set([502, 503, 504]);
13
+ var MAX_RETRIES = 2;
14
+ async function fetchWithRetry(url, init) {
15
+ let lastError;
16
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
17
+ try {
18
+ const res = await fetch(url, init);
19
+ if (attempt < MAX_RETRIES && RETRY_STATUSES.has(res.status)) {
20
+ await sleep(backoffMs(attempt));
21
+ continue;
22
+ }
23
+ return res;
24
+ } catch (err) {
25
+ lastError = err;
26
+ if (attempt < MAX_RETRIES && isTransient(err)) {
27
+ await sleep(backoffMs(attempt));
28
+ continue;
29
+ }
30
+ throw err;
31
+ }
32
+ }
33
+ throw lastError;
34
+ }
35
+ function isTransient(err) {
36
+ if (err instanceof TypeError) return true;
37
+ if (err instanceof Error) {
38
+ const msg = err.message.toLowerCase();
39
+ return msg.includes("econnrefused") || msg.includes("econnreset");
40
+ }
41
+ return false;
42
+ }
43
+ function backoffMs(attempt) {
44
+ return (attempt + 1) * 500;
45
+ }
46
+ function sleep(ms) {
47
+ return new Promise((r) => setTimeout(r, ms));
48
+ }
49
+ async function assertOk(res, context) {
50
+ if (res.ok) return;
51
+ let message = `${context}: HTTP ${res.status}`;
52
+ try {
53
+ const body = await res.json();
54
+ if (body.error) message = `${context}: ${body.error}`;
55
+ } catch {
56
+ }
57
+ throw new DaemonClientError(message, res.status);
58
+ }
59
+ async function jsonBody(res, context) {
60
+ await assertOk(res, context);
61
+ return await res.json();
62
+ }
63
+
64
+ // ../../src/core/daemon-client.ts
65
+ function createDaemonClient(baseUrl = "https://conductor-297703646986.europe-west2.run.app", apiKey) {
66
+ const base = baseUrl.replace(/\/+$/, "");
67
+ let defaultProjectId;
68
+ let resolvedKey = apiKey ?? process.env.CONDUCTOR_API_KEY;
69
+ let cliConfigLoaded = false;
70
+ async function ensureConfig() {
71
+ if (cliConfigLoaded) return;
72
+ cliConfigLoaded = true;
73
+ try {
74
+ const { readApiKey, readProjectId } = await import("./cli-config-LEERSU5N.js");
75
+ if (!resolvedKey) resolvedKey = readApiKey();
76
+ if (!defaultProjectId) defaultProjectId = readProjectId();
77
+ } catch {
78
+ }
79
+ }
80
+ function authHeaders() {
81
+ const headers = {};
82
+ if (resolvedKey) {
83
+ headers.Authorization = `Bearer ${resolvedKey}`;
84
+ }
85
+ return headers;
86
+ }
87
+ function url(path, params) {
88
+ const u = new URL(path, base);
89
+ if (defaultProjectId) {
90
+ u.searchParams.set("projectId", defaultProjectId);
91
+ }
92
+ if (params) {
93
+ for (const [k, v] of Object.entries(params)) {
94
+ u.searchParams.set(k, v);
95
+ }
96
+ }
97
+ return u.toString();
98
+ }
99
+ async function post(path, body, params) {
100
+ await ensureConfig();
101
+ return fetchWithRetry(url(path, params), {
102
+ method: "POST",
103
+ headers: { ...authHeaders(), "Content-Type": "application/json" },
104
+ body: JSON.stringify(body)
105
+ });
106
+ }
107
+ async function patch(path, body, params) {
108
+ await ensureConfig();
109
+ return fetchWithRetry(url(path, params), {
110
+ method: "PATCH",
111
+ headers: { ...authHeaders(), "Content-Type": "application/json" },
112
+ body: JSON.stringify(body)
113
+ });
114
+ }
115
+ async function get(path, params) {
116
+ await ensureConfig();
117
+ return fetchWithRetry(url(path, params), {
118
+ method: "GET",
119
+ headers: authHeaders()
120
+ });
121
+ }
122
+ async function del(path, params) {
123
+ await ensureConfig();
124
+ return fetchWithRetry(url(path, params), {
125
+ method: "DELETE",
126
+ headers: authHeaders()
127
+ });
128
+ }
129
+ return {
130
+ async resolveProject(opts) {
131
+ const res = await get("/api/projects");
132
+ const { projects } = await jsonBody(
133
+ res,
134
+ "resolveProject"
135
+ );
136
+ const match = projects.find((p) => {
137
+ if (opts.name && p.name === opts.name) return true;
138
+ if (opts.path && p.path === opts.path) return true;
139
+ return false;
140
+ });
141
+ if (!match) {
142
+ throw new DaemonClientError(
143
+ `Project not found (name=${opts.name ?? ""}, path=${opts.path ?? ""})`,
144
+ 404
145
+ );
146
+ }
147
+ if (!match.path) {
148
+ throw new DaemonClientError(
149
+ `Project "${match.name}" has no local path registered.`,
150
+ 400
151
+ );
152
+ }
153
+ defaultProjectId = match.id;
154
+ return { id: match.id, path: match.path };
155
+ },
156
+ async upsertHeartbeat(opts) {
157
+ const res = await post("/api/daemon/heartbeat", opts);
158
+ await assertOk(res, "upsertHeartbeat");
159
+ },
160
+ async clearHeartbeat(projectId) {
161
+ const res = await del("/api/daemon/heartbeat", { projectId });
162
+ await assertOk(res, "clearHeartbeat");
163
+ },
164
+ async createRun(opts) {
165
+ const res = await post("/api/daemon/runs", opts);
166
+ const { run } = await jsonBody(res, "createRun");
167
+ return run;
168
+ },
169
+ async completeRun(runId, opts) {
170
+ const res = await patch(`/api/daemon/runs/${runId}`, opts);
171
+ await assertOk(res, "completeRun");
172
+ },
173
+ async updateRunPid(runId, pid) {
174
+ const res = await patch(`/api/daemon/runs/${runId}`, { pid });
175
+ await assertOk(res, "updateRunPid");
176
+ },
177
+ async getQuota(projectId) {
178
+ const res = await get("/api/daemon/quota", { projectId });
179
+ const { count } = await jsonBody(res, "getQuota");
180
+ return count;
181
+ },
182
+ async purgeOldRuns(daysOld) {
183
+ const body = daysOld !== void 0 ? { daysOld } : {};
184
+ const res = await post("/api/daemon/purge", body);
185
+ const { purged } = await jsonBody(
186
+ res,
187
+ "purgeOldRuns"
188
+ );
189
+ return purged;
190
+ },
191
+ async pollWorkQueue(projectId, limit, opts) {
192
+ const params = { projectId };
193
+ if (limit !== void 0) params.limit = String(limit);
194
+ if (opts?.autonomous) params.autonomous = "true";
195
+ const res = await get("/api/work-queue", params);
196
+ return jsonBody(res, "pollWorkQueue");
197
+ },
198
+ async updateDeliverableStatus(deliverableId, status) {
199
+ const res = await patch(`/api/backlog/${deliverableId}`, { status });
200
+ await assertOk(res, "updateDeliverableStatus");
201
+ },
202
+ async getDeliverableDetail(deliverableId) {
203
+ const res = await get(`/api/backlog/${deliverableId}`);
204
+ if (res.status === 404) return null;
205
+ const data = await jsonBody(res, "getDeliverableDetail");
206
+ return {
207
+ prompt: data.deliverable?.prompt ?? null,
208
+ type: data.deliverable?.type ?? null
209
+ };
210
+ },
211
+ async getInitiativeForDeliverable(deliverableId) {
212
+ const res = await get(
213
+ `/api/pipeline/deliverables/${deliverableId}`
214
+ );
215
+ if (res.status === 404) return null;
216
+ const data = await jsonBody(res, "getInitiativeForDeliverable");
217
+ const d = data.parent;
218
+ if (!d?.initiativeId || !d.initiativeTitle) return null;
219
+ return {
220
+ initiativeId: d.initiativeId,
221
+ initiativeTitle: d.initiativeTitle
222
+ };
223
+ },
224
+ async appendOutputLine(runId, opts) {
225
+ const res = await post(`/api/daemon/runs/${runId}/output`, opts);
226
+ await assertOk(res, "appendOutputLine");
227
+ },
228
+ async hasPendingDecisions(projectId, deliverableId) {
229
+ const res = await get("/api/decisions", {
230
+ status: "pending",
231
+ scope: "deliverable",
232
+ deliverableId,
233
+ projectId
234
+ });
235
+ const { pending } = await jsonBody(res, "hasPendingDecisions");
236
+ return pending.length > 0;
237
+ },
238
+ async ingestAutonomousDeliverables(projectId, projectRoot) {
239
+ const res = await post("/api/daemon/ingest", {
240
+ projectId,
241
+ projectRoot
242
+ });
243
+ const { created } = await jsonBody(
244
+ res,
245
+ "ingestAutonomousDeliverables"
246
+ );
247
+ return created;
248
+ },
249
+ async routeUnassignedTasks(projectId) {
250
+ const res = await post(
251
+ "/api/daemon/tasks/route-unassigned",
252
+ { projectId }
253
+ );
254
+ const { assignments } = await jsonBody(res, "routeUnassignedTasks");
255
+ return assignments;
256
+ },
257
+ async routePendingTasks(projectId, projectRoot) {
258
+ const res = await post("/api/daemon/route", {
259
+ projectId,
260
+ projectRoot
261
+ });
262
+ return jsonBody(
263
+ res,
264
+ "routePendingTasks"
265
+ );
266
+ },
267
+ // ── CLI operation methods ─────────────────────────────────────────
268
+ async resolveProjectByPath(path) {
269
+ const res = await get("/api/projects/resolve", { path });
270
+ if (res.status === 404) return null;
271
+ return jsonBody(
272
+ res,
273
+ "resolveProjectByPath"
274
+ );
275
+ },
276
+ async saveHealthSnapshot(projectId, report) {
277
+ const res = await post("/api/environment/health", {
278
+ projectId,
279
+ report
280
+ });
281
+ await assertOk(res, "saveHealthSnapshot");
282
+ },
283
+ async saveGitStatusSnapshot(projectId, snapshot) {
284
+ const res = await post("/api/environment/health/git-snapshot", {
285
+ projectId,
286
+ snapshot
287
+ });
288
+ await assertOk(res, "saveGitStatusSnapshot");
289
+ },
290
+ async saveBranchOverviewSnapshot(projectId, overview) {
291
+ const res = await post("/api/environment/health/branch-overview", {
292
+ projectId,
293
+ overview
294
+ });
295
+ await assertOk(res, "saveBranchOverviewSnapshot");
296
+ },
297
+ async claimNextCliTask(projectId) {
298
+ const res = await post("/api/cli-tasks/claim", { projectId });
299
+ const { task } = await jsonBody(
300
+ res,
301
+ "claimNextCliTask"
302
+ );
303
+ return task;
304
+ },
305
+ async completeCliTask(taskId, result) {
306
+ const body = { status: "done" };
307
+ if (result !== void 0) body.result = result;
308
+ const res = await patch(`/api/cli-tasks/${taskId}`, body);
309
+ await assertOk(res, "completeCliTask");
310
+ },
311
+ async failCliTask(taskId, error) {
312
+ const body = { status: "failed" };
313
+ if (error !== void 0) body.error = error;
314
+ const res = await patch(`/api/cli-tasks/${taskId}`, body);
315
+ await assertOk(res, "failCliTask");
316
+ },
317
+ async failStaleCliTasks(projectId) {
318
+ const res = await post("/api/cli-tasks/fail-stale", { projectId });
319
+ const { count } = await jsonBody(
320
+ res,
321
+ "failStaleCliTasks"
322
+ );
323
+ return count;
324
+ },
325
+ async getWorktreeForInitiative(projectId, initiativeId) {
326
+ const res = await get("/api/git/worktrees", {
327
+ projectId,
328
+ initiativeId,
329
+ status: "active"
330
+ });
331
+ const { worktrees } = await jsonBody(res, "getWorktreeForInitiative");
332
+ return worktrees.length > 0 ? worktrees[0] : null;
333
+ },
334
+ async createWorktreeViaApi(projectId, initiativeId, initiativeTitle) {
335
+ const res = await post("/api/git/worktrees", {
336
+ projectId,
337
+ action: "create",
338
+ initiativeId,
339
+ initiativeTitle
340
+ });
341
+ const { worktree } = await jsonBody(res, "createWorktreeViaApi");
342
+ return worktree;
343
+ },
344
+ async registerWorktree(opts) {
345
+ const res = await post("/api/git/worktrees/register", {
346
+ projectId: opts.projectId,
347
+ initiativeId: opts.initiativeId,
348
+ branchName: opts.branchName,
349
+ worktreePath: opts.worktreePath
350
+ });
351
+ return jsonBody(res, "registerWorktree");
352
+ },
353
+ async scanWorktreesViaApi(projectId) {
354
+ const res = await post("/api/git/worktrees", {
355
+ projectId,
356
+ action: "scan"
357
+ });
358
+ return jsonBody(
359
+ res,
360
+ "scanWorktreesViaApi"
361
+ );
362
+ },
363
+ async listActiveWorktrees(projectId) {
364
+ const res = await get("/api/git/worktrees", {
365
+ projectId,
366
+ status: "active"
367
+ });
368
+ const { worktrees } = await jsonBody(res, "listActiveWorktrees");
369
+ return worktrees;
370
+ },
371
+ async removeWorktreeViaApi(projectId, worktreeId) {
372
+ const res = await post("/api/git/worktrees", {
373
+ projectId,
374
+ action: "remove",
375
+ worktreeId
376
+ });
377
+ await assertOk(res, "removeWorktreeViaApi");
378
+ },
379
+ async logGitActivityViaApi(opts) {
380
+ const res = await post("/api/git/activity/hook", {
381
+ hookName: "cli",
382
+ eventType: opts.eventType,
383
+ branch: opts.branch,
384
+ projectRoot: opts.projectRoot,
385
+ summary: opts.summary,
386
+ detail: opts.detail ?? {}
387
+ });
388
+ await assertOk(res, "logGitActivityViaApi");
389
+ },
390
+ // ── Agent backlog methods ───────────────────────────────────────
391
+ async listEnabledAgents(projectId) {
392
+ const res = await get("/api/daemon/agents", {
393
+ projectId,
394
+ enabledOnly: "true"
395
+ });
396
+ const { agents } = await jsonBody(
397
+ res,
398
+ "listEnabledAgents"
399
+ );
400
+ return agents;
401
+ },
402
+ async getNextTasksForAgent(agentId, limit) {
403
+ const params = { agentId };
404
+ if (limit !== void 0) params.limit = String(limit);
405
+ const res = await get("/api/daemon/tasks/next", params);
406
+ const { tasks } = await jsonBody(
407
+ res,
408
+ "getNextTasksForAgent"
409
+ );
410
+ return tasks;
411
+ },
412
+ async claimTask(taskId) {
413
+ const res = await post(`/api/daemon/tasks/${taskId}/claim`, {});
414
+ if (res.status === 409) return null;
415
+ const { task } = await jsonBody(
416
+ res,
417
+ "claimTask"
418
+ );
419
+ return task;
420
+ },
421
+ async updateTask(taskId, updates) {
422
+ const res = await patch(`/api/daemon/tasks/${taskId}`, updates);
423
+ if (res.status === 404) return null;
424
+ const { task } = await jsonBody(
425
+ res,
426
+ "updateTask"
427
+ );
428
+ return task;
429
+ }
430
+ };
431
+ }
432
+ export {
433
+ DaemonClientError,
434
+ createDaemonClient
435
+ };
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runGit
4
- } from "./chunk-FAZ7FCZQ.js";
5
- import "./chunk-4YEHSYVN.js";
4
+ } from "./chunk-N2KKNG4C.js";
6
5
 
7
6
  // ../../src/core/git-hooks.ts
8
7
  import { chmod, mkdir, readFile, writeFile } from "fs/promises";
@@ -11,8 +11,7 @@ import {
11
11
  getRecentCommits,
12
12
  getStatusSummary,
13
13
  runGit
14
- } from "./chunk-FAZ7FCZQ.js";
15
- import "./chunk-4YEHSYVN.js";
14
+ } from "./chunk-N2KKNG4C.js";
16
15
  export {
17
16
  classifyGitCommand,
18
17
  createSafetySnapshot,
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ assembleAutonomousPrompt,
4
+ assembleTaskPrompt
5
+ } from "./chunk-6AA726KG.js";
6
+ export {
7
+ assembleAutonomousPrompt,
8
+ assembleTaskPrompt
9
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matthugh1/conductor-cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.3",
4
4
  "description": "Conductor CLI — session management, health checks, and autonomous runner for the Conductor pipeline",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,18 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- assertCanQueueMerge,
4
- getBranchOverview,
5
- getConflictBranchesForWorkQueue,
6
- getMergeStats,
7
- isBranchFullyOnMain
8
- } from "./chunk-IHARLSA6.js";
9
- import "./chunk-FAZ7FCZQ.js";
10
- import "./chunk-PANC6BTV.js";
11
- import "./chunk-4YEHSYVN.js";
12
- export {
13
- assertCanQueueMerge,
14
- getBranchOverview,
15
- getConflictBranchesForWorkQueue,
16
- getMergeStats,
17
- isBranchFullyOnMain
18
- };
@@ -1,17 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // ../../src/core/errors.ts
4
- var ConductorError = class extends Error {
5
- code;
6
- retryable;
7
- constructor(code, message, retryable = false) {
8
- super(message);
9
- this.name = "ConductorError";
10
- this.code = code;
11
- this.retryable = retryable;
12
- }
13
- };
14
-
15
- export {
16
- ConductorError
17
- };