@matthugh1/conductor-cli 0.2.0 → 0.2.2

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,232 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ assembleAutonomousPrompt
4
+ } from "./chunk-6AA726KG.js";
5
+
6
+ // ../../src/core/agent-spawner.ts
7
+ import { spawn } from "child_process";
8
+ import { readFile } from "fs/promises";
9
+ import { join } from "path";
10
+ import { createInterface } from "readline";
11
+ async function readClaudeMd(projectRoot) {
12
+ try {
13
+ return await readFile(join(projectRoot, "CLAUDE.md"), "utf8");
14
+ } catch {
15
+ return "";
16
+ }
17
+ }
18
+ function parseStreamJsonLine(raw) {
19
+ let event;
20
+ try {
21
+ event = JSON.parse(raw);
22
+ } catch {
23
+ return raw;
24
+ }
25
+ const type = event.type;
26
+ if (type === "assistant") {
27
+ const msg = event.message;
28
+ const content = msg?.content;
29
+ if (!content) return null;
30
+ const parts = [];
31
+ for (const block of content) {
32
+ if (block.type === "text" && typeof block.text === "string") {
33
+ parts.push(block.text);
34
+ } else if (block.type === "tool_use") {
35
+ const name = block.name;
36
+ parts.push(`[tool] ${name}`);
37
+ }
38
+ }
39
+ return parts.length > 0 ? parts.join("\n") : null;
40
+ }
41
+ if (type === "tool_result") {
42
+ const name = event.tool_name ?? "tool";
43
+ const content = event.content;
44
+ if (content && content.length > 200) {
45
+ return `[${name}] ${content.slice(0, 200)}...`;
46
+ }
47
+ return content ? `[${name}] ${content}` : `[${name}] done`;
48
+ }
49
+ if (type === "result") {
50
+ const subtype = event.subtype;
51
+ const result = event.result;
52
+ if (subtype === "success" && result) {
53
+ return `[result] ${result.slice(0, 500)}`;
54
+ }
55
+ if (subtype === "error") {
56
+ return `[error] ${event.error ?? "unknown error"}`;
57
+ }
58
+ return null;
59
+ }
60
+ if (type === "system" || type === "rate_limit_event") {
61
+ return null;
62
+ }
63
+ return null;
64
+ }
65
+ function spawnAgent(opts) {
66
+ let child = null;
67
+ let cancelled = false;
68
+ let pid = null;
69
+ const cancel = () => {
70
+ cancelled = true;
71
+ if (child !== null && child.exitCode === null) {
72
+ child.kill("SIGTERM");
73
+ }
74
+ };
75
+ const done = runAgent(opts, (c) => {
76
+ child = c;
77
+ pid = c.pid ?? null;
78
+ }, () => cancelled);
79
+ return { done, cancel, get pid() {
80
+ return pid;
81
+ } };
82
+ }
83
+ async function runAgent(opts, onChild, isCancelled) {
84
+ const startTime = Date.now();
85
+ let lineCount = 0;
86
+ let prompt;
87
+ if (opts.assembledPrompt) {
88
+ prompt = opts.assembledPrompt;
89
+ } else {
90
+ const claudeMd = await readClaudeMd(opts.projectRoot);
91
+ prompt = assembleAutonomousPrompt({
92
+ claudeMd,
93
+ projectName: opts.projectRoot,
94
+ item: opts.item
95
+ });
96
+ }
97
+ const args = ["-p", prompt, "--output-format", "stream-json", "--verbose"];
98
+ if (opts.modelOverride) {
99
+ args.push("--model", opts.modelOverride);
100
+ }
101
+ if (opts.skipPermissions !== false) {
102
+ args.push("--dangerously-skip-permissions");
103
+ }
104
+ const child = spawn("claude", args, {
105
+ cwd: opts.projectRoot,
106
+ stdio: ["ignore", "pipe", "pipe"],
107
+ env: { ...process.env }
108
+ });
109
+ onChild(child);
110
+ if (child.pid === void 0) {
111
+ return {
112
+ outcome: "failed",
113
+ exitCode: null,
114
+ lineCount: 0,
115
+ durationMs: Date.now() - startTime,
116
+ errorMessage: "Failed to spawn claude process"
117
+ };
118
+ }
119
+ if (child.stdout !== null) {
120
+ const rl = createInterface({ input: child.stdout });
121
+ rl.on("line", (raw) => {
122
+ const text = parseStreamJsonLine(raw);
123
+ if (text === null) return;
124
+ lineCount++;
125
+ const currentLine = lineCount;
126
+ opts.onLine?.("stdout", text);
127
+ opts.client.appendOutputLine(opts.runId, {
128
+ lineNumber: currentLine,
129
+ stream: "stdout",
130
+ content: text
131
+ }).catch(() => {
132
+ });
133
+ });
134
+ }
135
+ if (child.stderr !== null) {
136
+ const rl = createInterface({ input: child.stderr });
137
+ rl.on("line", (line) => {
138
+ lineCount++;
139
+ const currentLine = lineCount;
140
+ opts.onLine?.("stderr", line);
141
+ opts.client.appendOutputLine(opts.runId, {
142
+ lineNumber: currentLine,
143
+ stream: "stderr",
144
+ content: line
145
+ }).catch(() => {
146
+ });
147
+ });
148
+ }
149
+ const exitCode = await waitWithTimeout(
150
+ child,
151
+ opts.timeoutMs,
152
+ opts.client,
153
+ opts.projectId,
154
+ opts.deliverableId,
155
+ isCancelled
156
+ );
157
+ const durationMs = Date.now() - startTime;
158
+ if (isCancelled()) {
159
+ return { outcome: "cancelled", exitCode, lineCount, durationMs };
160
+ }
161
+ if (exitCode === null) {
162
+ child.kill("SIGTERM");
163
+ await new Promise((r) => setTimeout(r, 2e3));
164
+ if (child.exitCode === null) {
165
+ child.kill("SIGKILL");
166
+ }
167
+ return {
168
+ outcome: "timeout",
169
+ exitCode: null,
170
+ lineCount,
171
+ durationMs,
172
+ errorMessage: `Task timed out after ${Math.round(opts.timeoutMs / 1e3)}s`
173
+ };
174
+ }
175
+ if (exitCode === 0) {
176
+ return { outcome: "completed", exitCode, lineCount, durationMs };
177
+ }
178
+ return {
179
+ outcome: "failed",
180
+ exitCode,
181
+ lineCount,
182
+ durationMs,
183
+ errorMessage: `Agent exited with code ${exitCode}`
184
+ };
185
+ }
186
+ async function waitWithTimeout(child, timeoutMs, client, projectId, deliverableId, isCancelled) {
187
+ return new Promise((resolve) => {
188
+ let resolved = false;
189
+ let elapsedMs = 0;
190
+ const TICK_MS = 3e3;
191
+ const finish = (code) => {
192
+ if (!resolved) {
193
+ resolved = true;
194
+ clearInterval(timer);
195
+ resolve(code);
196
+ }
197
+ };
198
+ child.on("close", (code) => {
199
+ finish(code ?? 1);
200
+ });
201
+ child.on("error", () => {
202
+ finish(1);
203
+ });
204
+ const timer = setInterval(async () => {
205
+ if (resolved || isCancelled()) {
206
+ clearInterval(timer);
207
+ if (isCancelled() && !resolved) {
208
+ finish(null);
209
+ }
210
+ return;
211
+ }
212
+ if (deliverableId) {
213
+ try {
214
+ const paused = await client.hasPendingDecisions(projectId, deliverableId);
215
+ if (!paused) {
216
+ elapsedMs += TICK_MS;
217
+ }
218
+ } catch {
219
+ elapsedMs += TICK_MS;
220
+ }
221
+ } else {
222
+ elapsedMs += TICK_MS;
223
+ }
224
+ if (elapsedMs >= timeoutMs) {
225
+ finish(null);
226
+ }
227
+ }, TICK_MS);
228
+ });
229
+ }
230
+ export {
231
+ spawnAgent
232
+ };
package/dist/agent.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  ensureDatabaseUrl,
4
4
  postToServer
5
- } from "./chunk-MJKFQIYA.js";
5
+ } from "./chunk-B2WDTKD7.js";
6
6
 
7
7
  // ../../src/cli/agent.ts
8
8
  import { resolve } from "path";
@@ -18,6 +18,12 @@ function parseArgs(argv) {
18
18
  let consecutiveFailures = 3;
19
19
  let skipPermissions = false;
20
20
  let dryRun = false;
21
+ let checkInterval = 3e4;
22
+ let maxPerDay = 50;
23
+ let maxConcurrent = 1;
24
+ let noWorktree = false;
25
+ let apiUrl;
26
+ let apiKey;
21
27
  let command;
22
28
  let subcommand;
23
29
  for (let i = 0; i < args.length; i++) {
@@ -36,6 +42,18 @@ function parseArgs(argv) {
36
42
  skipPermissions = true;
37
43
  } else if (arg === "--dry-run") {
38
44
  dryRun = true;
45
+ } else if (arg === "--check-interval" && i + 1 < args.length) {
46
+ checkInterval = Number.parseInt(args[++i], 10);
47
+ } else if (arg === "--max-per-day" && i + 1 < args.length) {
48
+ maxPerDay = Number.parseInt(args[++i], 10);
49
+ } else if (arg === "--max-concurrent" && i + 1 < args.length) {
50
+ maxConcurrent = Number.parseInt(args[++i], 10);
51
+ } else if (arg === "--no-worktree") {
52
+ noWorktree = true;
53
+ } else if (arg === "--api-url" && i + 1 < args.length) {
54
+ apiUrl = args[++i];
55
+ } else if (arg === "--api-key" && i + 1 < args.length) {
56
+ apiKey = args[++i];
39
57
  } else if (arg === "--json") {
40
58
  json = true;
41
59
  } else if (arg === "--help" || arg === "-h") {
@@ -48,7 +66,7 @@ function parseArgs(argv) {
48
66
  subcommand = arg;
49
67
  }
50
68
  }
51
- return { command, subcommand, projectRoot, project, json, help, once, maxDeliverables, timeout, consecutiveFailures, skipPermissions, dryRun };
69
+ return { command, subcommand, projectRoot, project, json, help, once, maxDeliverables, timeout, consecutiveFailures, skipPermissions, dryRun, checkInterval, maxPerDay, maxConcurrent, noWorktree, apiUrl, apiKey };
52
70
  }
53
71
  var HELP_TEXT = `
54
72
  Conductor \u2014 local agent CLI
@@ -62,10 +80,12 @@ Commands:
62
80
  hooks Check hook status or install hooks
63
81
  watch Poll for cleanup tasks and execute them locally
64
82
  run Autonomous runner \u2014 work through deliverable queue
83
+ daemon Long-running daemon \u2014 polls for autonomous work
84
+ daemon cancel Stop a running daemon
65
85
 
66
86
  Options:
67
87
  --project-root <path> Project directory (default: cwd)
68
- --project <name> Project name (for run command)
88
+ --project <name> Project name (for run/daemon commands)
69
89
  --json Output as JSON instead of plain English
70
90
  --once Process one task and exit (watch only)
71
91
  --help Show this help text
@@ -77,6 +97,15 @@ Run options:
77
97
  --skip-permissions Allow Claude to write files without prompting
78
98
  --dry-run Show queue without spawning agents
79
99
 
100
+ Daemon options:
101
+ --check-interval <ms> Poll interval in milliseconds (default: 30000)
102
+ --max-per-day <n> Max tasks per day (default: 50)
103
+ --max-concurrent <n> Max concurrent agent tasks (default: 1)
104
+ --timeout <minutes> Max minutes per deliverable (default: 120)
105
+ --consecutive-failures <n> Stop after N failures in a row (default: 3)
106
+ --no-worktree Skip git worktree isolation
107
+ --skip-permissions Allow Claude to write files without prompting
108
+
80
109
  Examples:
81
110
  conductor check
82
111
  conductor init --project-root /path/to/project
@@ -85,6 +114,8 @@ Examples:
85
114
  conductor watch
86
115
  conductor run --project Conductor --skip-permissions
87
116
  conductor run --project Conductor --dry-run
117
+ conductor daemon --project Conductor
118
+ conductor daemon cancel --project Conductor
88
119
  `.trim();
89
120
  function writeOut(text) {
90
121
  process.stdout.write(text + "\n");
@@ -113,7 +144,7 @@ async function cmdCheck(projectRoot, jsonOutput) {
113
144
  }
114
145
  return 1;
115
146
  }
116
- const { getEnvironmentHealthReport } = await import("./health-CTND2ANA.js");
147
+ const { getEnvironmentHealthReport } = await import("./health-UFK7YCKQ.js");
117
148
  const report = await getEnvironmentHealthReport(projects[0].id);
118
149
  if (jsonOutput) {
119
150
  writeOut(JSON.stringify(report, null, 2));
@@ -188,7 +219,7 @@ async function cmdCheck(projectRoot, jsonOutput) {
188
219
  }
189
220
  try {
190
221
  const { getGitBranchInfo } = await import("./git-wrapper-DVJ46TMA.js");
191
- const { getBranchOverview } = await import("./branch-overview-XVHTGFCJ.js");
222
+ const { getBranchOverview } = await import("./branch-overview-DSSCUE5F.js");
192
223
  const { saveBranchOverviewSnapshot } = await import("./git-snapshots-N3FBS7T3.js");
193
224
  const branchInfo = await getGitBranchInfo(projectRoot);
194
225
  let currentBranch = branchInfo.branchName;
@@ -214,7 +245,7 @@ async function cmdCheck(projectRoot, jsonOutput) {
214
245
  return 0;
215
246
  }
216
247
  async function cmdScan(projectRoot, jsonOutput) {
217
- const { scanWorktrees } = await import("./worktree-manager-QKRBTPVC.js");
248
+ const { scanWorktrees } = await import("./worktree-manager-2ZUJEL3L.js");
218
249
  const { query: dbQuery } = await import("./db-U6Y3QJDD.js");
219
250
  const projects = await dbQuery(
220
251
  "SELECT id FROM projects WHERE path = $1 LIMIT 1",
@@ -262,7 +293,7 @@ async function cmdScan(projectRoot, jsonOutput) {
262
293
  async function cmdInit(projectRoot, jsonOutput) {
263
294
  const { resolve: resolvePath, basename, join } = await import("path");
264
295
  const { realpath, mkdir, readFile, writeFile, access } = await import("fs/promises");
265
- const { getServerBaseUrl } = await import("./cli-config-TDSTAXIA.js");
296
+ const { getServerBaseUrl } = await import("./cli-config-2ZDXUUQN.js");
266
297
  let root = resolvePath(projectRoot.trim());
267
298
  try {
268
299
  root = await realpath(root);
@@ -379,7 +410,7 @@ async function cmdHooks(projectRoot, subcommand, jsonOutput) {
379
410
  return 2;
380
411
  }
381
412
  async function cmdWatch(projectRoot, once, jsonOutput) {
382
- const { claimNextTask, executeTask, failStaleTasks } = await import("./cli-tasks-NW3BONXC.js");
413
+ const { claimNextTask, executeTask, failStaleTasks } = await import("./cli-tasks-NM5D5PIZ.js");
383
414
  const { query: dbQuery } = await import("./db-U6Y3QJDD.js");
384
415
  const projects = await dbQuery(
385
416
  "SELECT id FROM projects WHERE path = $1 LIMIT 1",
@@ -464,8 +495,8 @@ async function cmdWatch(projectRoot, once, jsonOutput) {
464
495
  async function cmdRun(opts) {
465
496
  const { spawn } = await import("child_process");
466
497
  const { readFileSync, existsSync } = await import("fs");
467
- const { assembleAutonomousPrompt } = await import("./runner-prompt-2B6EXGN6.js");
468
- const { computeWorkQueue } = await import("./work-queue-YE5P4S7R.js");
498
+ const { assembleAutonomousPrompt } = await import("./runner-prompt-MOOPKA5P.js");
499
+ const { computeWorkQueue } = await import("./work-queue-U3JYHLX2.js");
469
500
  const { query: dbQuery } = await import("./db-U6Y3QJDD.js");
470
501
  let project = opts.projectName ?? opts.projectRoot;
471
502
  if (opts.projectName) {
@@ -647,12 +678,15 @@ async function main() {
647
678
  writeOut(HELP_TEXT);
648
679
  process.exit(parsed.help ? 0 : 1);
649
680
  }
650
- const hasDb = await ensureDatabaseUrl();
651
- if (!hasDb) {
652
- writeErr(
653
- "Could not find a database connection. Make sure the MCP server is running (npm run mcp) and try again."
654
- );
655
- process.exit(1);
681
+ const needsDb = parsed.command !== "daemon";
682
+ if (needsDb) {
683
+ const hasDb = await ensureDatabaseUrl();
684
+ if (!hasDb) {
685
+ writeErr(
686
+ "Could not find a database connection. Make sure the MCP server is running (npm run mcp) and try again."
687
+ );
688
+ process.exit(1);
689
+ }
656
690
  }
657
691
  try {
658
692
  let exitCode;
@@ -692,6 +726,34 @@ async function main() {
692
726
  jsonOutput: parsed.json
693
727
  });
694
728
  break;
729
+ case "daemon": {
730
+ const { cmdDaemon, cmdDaemonCancel } = await import("./daemon-GGOJDZDB.js");
731
+ if (parsed.subcommand === "cancel") {
732
+ exitCode = await cmdDaemonCancel(
733
+ parsed.projectRoot,
734
+ parsed.project,
735
+ parsed.json,
736
+ parsed.apiUrl,
737
+ parsed.apiKey
738
+ );
739
+ } else {
740
+ exitCode = await cmdDaemon({
741
+ projectRoot: parsed.projectRoot,
742
+ projectName: parsed.project,
743
+ apiUrl: parsed.apiUrl,
744
+ apiKey: parsed.apiKey,
745
+ checkInterval: parsed.checkInterval,
746
+ maxPerDay: parsed.maxPerDay,
747
+ maxConsecutiveFailures: parsed.consecutiveFailures,
748
+ timeout: parsed.timeout,
749
+ maxConcurrent: parsed.maxConcurrent,
750
+ noWorktree: parsed.noWorktree,
751
+ skipPermissions: parsed.skipPermissions,
752
+ jsonOutput: parsed.json
753
+ });
754
+ }
755
+ break;
756
+ }
695
757
  default:
696
758
  writeErr(`Unknown command: ${parsed.command}`);
697
759
  writeErr("Run conductor --help for usage.");
@@ -5,7 +5,7 @@ import {
5
5
  getConflictBranchesForWorkQueue,
6
6
  getMergeStats,
7
7
  isBranchFullyOnMain
8
- } from "./chunk-IHARLSA6.js";
8
+ } from "./chunk-7S5HKGS5.js";
9
9
  import "./chunk-FAZ7FCZQ.js";
10
10
  import "./chunk-PANC6BTV.js";
11
11
  import "./chunk-4YEHSYVN.js";
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ query
4
+ } from "./chunk-PANC6BTV.js";
5
+
6
+ // ../../src/core/notifications.ts
7
+ var VALID_EVENT_TYPES = /* @__PURE__ */ new Set([
8
+ "gate_rejection",
9
+ "gate_bypassed",
10
+ "review_needed",
11
+ "handoff_needed",
12
+ "decision_pending",
13
+ "stale_session",
14
+ "abandoned_session",
15
+ "missing_checkin",
16
+ "orphan_work",
17
+ "stage_transition",
18
+ "watchdog_flag",
19
+ "stale_worktree",
20
+ "autonomous_flag_changed"
21
+ ]);
22
+ var VALID_PRIORITIES = /* @__PURE__ */ new Set([
23
+ "info",
24
+ "warning",
25
+ "action_needed"
26
+ ]);
27
+ var VALID_LINK_TYPES = /* @__PURE__ */ new Set([
28
+ "deliverable",
29
+ "decision",
30
+ "initiative",
31
+ "session"
32
+ ]);
33
+ async function createNotification(params) {
34
+ if (!params.projectId || params.projectId.trim().length === 0) {
35
+ throw new Error("A project id is required to create a notification.");
36
+ }
37
+ if (!VALID_EVENT_TYPES.has(params.eventType)) {
38
+ throw new Error(
39
+ `Unknown event type "${params.eventType}". Expected one of: ${[...VALID_EVENT_TYPES].join(", ")}.`
40
+ );
41
+ }
42
+ if (!params.message || params.message.trim().length === 0) {
43
+ throw new Error("A notification needs a message.");
44
+ }
45
+ if (!VALID_PRIORITIES.has(params.priority)) {
46
+ throw new Error(
47
+ `Unknown priority "${params.priority}". Expected info, warning, or action_needed.`
48
+ );
49
+ }
50
+ if (params.linkType !== void 0 && !VALID_LINK_TYPES.has(params.linkType)) {
51
+ throw new Error(
52
+ `Unknown link type "${params.linkType}". Expected deliverable, decision, initiative, or session.`
53
+ );
54
+ }
55
+ const rows = await query(
56
+ `INSERT INTO notifications (project_id, event_type, message, priority, link_type, link_id, agent_type, agent_name)
57
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
58
+ RETURNING id`,
59
+ [
60
+ params.projectId,
61
+ params.eventType,
62
+ params.message.trim(),
63
+ params.priority,
64
+ params.linkType ?? null,
65
+ params.linkId ?? null,
66
+ params.agentType ?? null,
67
+ params.agentName ?? null
68
+ ]
69
+ );
70
+ return rows[0].id;
71
+ }
72
+
73
+ export {
74
+ createNotification
75
+ };