copilot-agent 0.2.0 → 0.3.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/dist/index.js CHANGED
@@ -1,11 +1,6 @@
1
- #!/usr/bin/env node
2
-
3
1
  // src/index.ts
4
2
  import { Command } from "commander";
5
3
 
6
- // src/commands/status.ts
7
- import chalk from "chalk";
8
-
9
4
  // src/lib/session.ts
10
5
  import {
11
6
  existsSync,
@@ -148,6 +143,9 @@ import { execSync } from "child_process";
148
143
  var RED = "\x1B[31m";
149
144
  var GREEN = "\x1B[32m";
150
145
  var YELLOW = "\x1B[33m";
146
+ var CYAN = "\x1B[36m";
147
+ var DIM = "\x1B[2m";
148
+ var BOLD = "\x1B[1m";
151
149
  var RESET = "\x1B[0m";
152
150
 
153
151
  // src/lib/logger.ts
@@ -186,6 +184,11 @@ function fail(msg) {
186
184
  console.error(out);
187
185
  writeToFile(`\u2716 ${msg}`);
188
186
  }
187
+ function info(msg) {
188
+ const out = `${CYAN}\u2139 ${msg}${RESET}`;
189
+ console.log(out);
190
+ writeToFile(`\u2139 ${msg}`);
191
+ }
189
192
  function notify(message, title = "copilot-agent") {
190
193
  try {
191
194
  if (process.platform === "darwin") {
@@ -226,16 +229,20 @@ function findCopilotProcesses() {
226
229
  try {
227
230
  const output = execSync2("ps -eo pid,command", { encoding: "utf-8" });
228
231
  const results = [];
232
+ const myPid = process.pid;
233
+ const parentPid = process.ppid;
229
234
  for (const line of output.split("\n")) {
230
235
  const trimmed = line.trim();
231
236
  if (!trimmed) continue;
232
237
  if ((trimmed.includes("copilot") || trimmed.includes("@githubnext/copilot")) && !trimmed.includes("ps -eo") && !trimmed.includes("copilot-agent") && !trimmed.includes("grep")) {
233
238
  const match = trimmed.match(/^(\d+)\s+(.+)$/);
234
239
  if (match) {
240
+ const pid = parseInt(match[1], 10);
241
+ if (pid === myPid || pid === parentPid) continue;
235
242
  const cmd = match[2];
236
243
  const sidMatch = cmd.match(/resume[= ]+([a-f0-9-]{36})/);
237
244
  results.push({
238
- pid: parseInt(match[1], 10),
245
+ pid,
239
246
  command: cmd,
240
247
  sessionId: sidMatch?.[1]
241
248
  });
@@ -252,6 +259,30 @@ function findPidForSession(sid) {
252
259
  const matching = procs.filter((p) => p.command.includes(sid)).sort((a, b) => b.pid - a.pid);
253
260
  return matching[0]?.pid ?? null;
254
261
  }
262
+ async function waitForAllCopilotToFinish(timeoutMs = 144e5, pollMs = 1e4) {
263
+ const start = Date.now();
264
+ let warned = false;
265
+ while (Date.now() - start < timeoutMs) {
266
+ const procs = findCopilotProcesses();
267
+ if (procs.length === 0) return;
268
+ if (!warned) {
269
+ warn(`Waiting for ${procs.length} copilot process(es) to finish before starting...`);
270
+ for (const p of procs) {
271
+ log(` PID ${p.pid}: ${p.command.slice(0, 80)}`);
272
+ }
273
+ warned = true;
274
+ }
275
+ await sleep(pollMs);
276
+ }
277
+ warn("Timeout waiting for copilot processes to finish");
278
+ }
279
+ function assertSessionNotRunning(sid) {
280
+ const pid = findPidForSession(sid);
281
+ if (pid) {
282
+ fail(`Session ${sid.slice(0, 8)}\u2026 already has copilot running (PID ${pid}). Cannot resume \u2014 would corrupt the session.`);
283
+ process.exit(1);
284
+ }
285
+ }
255
286
  async function waitForExit(pid, timeoutMs = 144e5) {
256
287
  const start = Date.now();
257
288
  while (Date.now() - start < timeoutMs) {
@@ -264,7 +295,8 @@ async function waitForExit(pid, timeoutMs = 144e5) {
264
295
  }
265
296
  return false;
266
297
  }
267
- function runCopilot(args, options) {
298
+ async function runCopilot(args, options) {
299
+ await waitForAllCopilotToFinish();
268
300
  return new Promise((resolve4) => {
269
301
  const child = spawn("copilot", args, {
270
302
  cwd: options?.cwd,
@@ -287,6 +319,7 @@ function runCopilot(args, options) {
287
319
  });
288
320
  }
289
321
  function runCopilotResume(sid, steps, message, cwd) {
322
+ assertSessionNotRunning(sid);
290
323
  const args = [
291
324
  `--resume=${sid}`,
292
325
  "--autopilot",
@@ -314,61 +347,77 @@ function sleep(ms) {
314
347
  }
315
348
 
316
349
  // src/commands/status.ts
317
- async function statusCommand(opts) {
318
- if (opts.active) {
319
- return showActive();
320
- }
321
- showRecent(opts.limit);
350
+ function registerStatusCommand(program2) {
351
+ program2.command("status").description("Show copilot session status").option("-l, --limit <n>", "Number of sessions to show", "10").option("-a, --active", "Show only active (running) processes").option("-i, --incomplete", "Only show incomplete sessions").action((opts) => {
352
+ if (opts.active) {
353
+ showActive();
354
+ } else {
355
+ showRecent(parseInt(opts.limit, 10), opts.incomplete ?? false);
356
+ }
357
+ });
322
358
  }
323
- async function showActive() {
324
- const procs = await findCopilotProcesses();
359
+ function showActive() {
360
+ const procs = findCopilotProcesses();
325
361
  if (procs.length === 0) {
326
- log("No active copilot processes.");
362
+ log(`${DIM}No active copilot processes.${RESET}`);
327
363
  return;
328
364
  }
329
- console.log(
330
- chalk.bold(
331
- `
332
- ${"PID".padEnd(8)} ${"Session".padEnd(40)} ${"Command".slice(0, 60)}`
333
- )
334
- );
335
- console.log("\u2500".repeat(108));
365
+ log(`
366
+ ${BOLD}${"PID".padEnd(8)} ${"Session".padEnd(40)} Command${RESET}`);
367
+ log("\u2500".repeat(108));
336
368
  for (const p of procs) {
337
- console.log(
338
- `${String(p.pid).padEnd(8)} ${(p.sessionId ?? "\u2014").padEnd(40)} ${(p.command ?? "").slice(0, 60)}`
369
+ log(
370
+ `${CYAN}${String(p.pid).padEnd(8)}${RESET} ${(p.sessionId ?? "\u2014").padEnd(40)} ${truncate(p.command, 58)}`
339
371
  );
340
372
  }
341
- console.log();
373
+ log("");
342
374
  }
343
- function showRecent(limit) {
344
- const sessions = listSessions(limit);
375
+ function showRecent(limit, incompleteOnly) {
376
+ let sessions = listSessions(limit);
377
+ if (incompleteOnly) {
378
+ sessions = sessions.filter((s) => !s.complete);
379
+ }
345
380
  if (sessions.length === 0) {
346
- log("No sessions found.");
381
+ log(`${DIM}No sessions found.${RESET}`);
347
382
  return;
348
383
  }
349
- console.log(
350
- chalk.bold(
351
- `
352
- ${"Status".padEnd(10)} ${"Premium".padEnd(10)} ${"Last Event".padEnd(25)} ${"Summary".padEnd(40)} ${"ID"}`
353
- )
384
+ log(
385
+ `
386
+ ${BOLD}${"Status".padEnd(10)} ${"Premium".padEnd(10)} ${"Last Event".padEnd(25)} ${"Summary".padEnd(40)} ID${RESET}`
354
387
  );
355
- console.log("\u2500".repeat(120));
388
+ log("\u2500".repeat(120));
356
389
  for (const s of sessions) {
357
- const done = hasTaskComplete(s.id);
358
- const status = done ? chalk.green("\u2705 done") : chalk.yellow("\u23F8\uFE0F stopped");
359
- const premium = String(getSessionPremium(s.id));
360
- const lastEvt = getLastEvent(s.id);
361
- const summary = (s.summary ?? "\u2014").slice(0, 38);
362
- console.log(
363
- `${status.padEnd(20)} ${premium.padEnd(10)} ${lastEvt.padEnd(25)} ${summary.padEnd(40)} ${chalk.dim(s.id)}`
390
+ const status = s.complete ? `${GREEN}\u2714 done${RESET}` : `${YELLOW}\u23F8 stop${RESET}`;
391
+ const premium = String(s.premiumRequests);
392
+ const summary = truncate(s.summary || "\u2014", 38);
393
+ log(
394
+ `${status.padEnd(10 + 9)} ${premium.padEnd(10)} ${s.lastEvent.padEnd(25)} ${summary.padEnd(40)} ${DIM}${s.id}${RESET}`
364
395
  );
365
396
  }
366
- console.log();
397
+ log(`
398
+ ${DIM}Total: ${sessions.length} session(s)${RESET}`);
399
+ }
400
+ function truncate(s, max) {
401
+ if (s.length <= max) return s;
402
+ return s.substring(0, max - 1) + "\u2026";
367
403
  }
368
404
 
369
405
  // src/commands/watch.ts
370
- import chalk2 from "chalk";
371
- import ora from "ora";
406
+ function registerWatchCommand(program2) {
407
+ program2.command("watch [session-id]").description("Watch a session and auto-resume when it stops").option("-s, --steps <n>", "Max autopilot continues per resume", "30").option("-r, --max-resumes <n>", "Max number of resumes", "10").option("-c, --cooldown <n>", "Seconds between resumes", "10").option("-m, --message <msg>", "Message to send on resume").action(async (sid, opts) => {
408
+ try {
409
+ await watchCommand(sid, {
410
+ steps: parseInt(opts.steps, 10),
411
+ maxResumes: parseInt(opts.maxResumes, 10),
412
+ cooldown: parseInt(opts.cooldown, 10),
413
+ message: opts.message
414
+ });
415
+ } catch (err) {
416
+ fail(`Watch error: ${err instanceof Error ? err.message : err}`);
417
+ process.exit(1);
418
+ }
419
+ });
420
+ }
372
421
  async function watchCommand(sid, opts) {
373
422
  assertCopilot();
374
423
  if (!sid) {
@@ -377,7 +426,7 @@ async function watchCommand(sid, opts) {
377
426
  fail("No incomplete session found.");
378
427
  process.exit(1);
379
428
  }
380
- log(`Auto-detected incomplete session: ${chalk2.cyan(sid)}`);
429
+ info(`Auto-detected incomplete session: ${CYAN}${sid}${RESET}`);
381
430
  }
382
431
  if (!validateSession(sid)) {
383
432
  fail(`Invalid session: ${sid}`);
@@ -389,13 +438,10 @@ async function watchCommand(sid, opts) {
389
438
  }
390
439
  let resumes = 0;
391
440
  while (resumes < opts.maxResumes) {
392
- const pid = await findPidForSession(sid);
441
+ const pid = findPidForSession(sid);
393
442
  if (pid) {
394
- const spinner = ora(
395
- `Watching PID ${pid} for session ${chalk2.cyan(sid.slice(0, 8))}\u2026`
396
- ).start();
443
+ info(`Watching PID ${pid} for session ${CYAN}${sid.slice(0, 8)}${RESET}\u2026`);
397
444
  const exited = await waitForExit(pid);
398
- spinner.stop();
399
445
  if (!exited) {
400
446
  warn("Timeout waiting for process exit.");
401
447
  break;
@@ -403,19 +449,25 @@ async function watchCommand(sid, opts) {
403
449
  }
404
450
  await sleep2(3e3);
405
451
  if (hasTaskComplete(sid)) {
406
- ok(
407
- `Task complete! Summary: ${getSessionSummary(sid) || "none"}`
408
- );
452
+ ok(`Task complete! Summary: ${getSessionSummary(sid) || "none"}`);
409
453
  notify("Task completed!", `Session ${sid.slice(0, 8)}`);
410
454
  return;
411
455
  }
412
456
  resumes++;
413
- log(
414
- `Session interrupted (${getLastEvent(sid)}). Resume ${resumes}/${opts.maxResumes}\u2026`
457
+ log(`Session interrupted (${getLastEvent(sid)}). Resume ${resumes}/${opts.maxResumes}\u2026`);
458
+ if (opts.cooldown > 0 && resumes > 1) {
459
+ info(`Cooldown ${opts.cooldown}s...`);
460
+ await sleep2(opts.cooldown * 1e3);
461
+ }
462
+ const cwd = getSessionCwd(sid) || void 0;
463
+ const result = await runCopilotResume(
464
+ sid,
465
+ opts.steps,
466
+ opts.message ?? "Continue remaining work. Pick up where you left off and complete the task.",
467
+ cwd
415
468
  );
416
- const result = await runCopilotResume(sid, opts.steps, opts.message);
417
469
  if (result.sessionId && result.sessionId !== sid) {
418
- log(`New session created: ${chalk2.cyan(result.sessionId)}`);
470
+ info(`New session created: ${CYAN}${result.sessionId}${RESET}`);
419
471
  sid = result.sessionId;
420
472
  }
421
473
  }
@@ -426,10 +478,6 @@ function sleep2(ms) {
426
478
  return new Promise((r) => setTimeout(r, ms));
427
479
  }
428
480
 
429
- // src/commands/run.ts
430
- import chalk3 from "chalk";
431
- import ora2 from "ora";
432
-
433
481
  // src/lib/detect.ts
434
482
  import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
435
483
  import { join as join2, basename, resolve as resolve2 } from "path";
@@ -479,6 +527,27 @@ function detectProjectName(dir) {
479
527
  }
480
528
  return basename(resolve2(dir));
481
529
  }
530
+ function detectMainBranch(dir) {
531
+ try {
532
+ const ref = execSync3("git symbolic-ref refs/remotes/origin/HEAD", {
533
+ cwd: dir,
534
+ encoding: "utf-8",
535
+ stdio: ["pipe", "pipe", "pipe"]
536
+ }).trim();
537
+ return ref.split("/").pop() ?? "main";
538
+ } catch {
539
+ }
540
+ try {
541
+ const branch = execSync3("git branch --show-current", {
542
+ cwd: dir,
543
+ encoding: "utf-8",
544
+ stdio: ["pipe", "pipe", "pipe"]
545
+ }).trim();
546
+ if (branch) return branch;
547
+ } catch {
548
+ }
549
+ return "main";
550
+ }
482
551
 
483
552
  // src/lib/tasks.ts
484
553
  var COMMON_TASKS = [
@@ -659,16 +728,41 @@ function gitStash(dir) {
659
728
  function gitCheckout(dir, branch) {
660
729
  return gitExec(dir, `git checkout ${branch} -q`) !== null;
661
730
  }
731
+ function gitCreateBranch(dir, branch) {
732
+ return gitExec(dir, `git checkout -b ${branch}`) !== null;
733
+ }
734
+ function gitCountCommits(dir, from, to) {
735
+ const result = gitExec(dir, `git log ${from}..${to} --oneline`);
736
+ if (!result) return 0;
737
+ return result.split("\n").filter((l) => l.trim()).length;
738
+ }
662
739
  function gitStatus(dir) {
663
740
  return gitExec(dir, "git status --porcelain") ?? "";
664
741
  }
665
742
 
666
743
  // src/commands/run.ts
744
+ function registerRunCommand(program2) {
745
+ program2.command("run [dir]").description("Discover and fix issues in a project").option("-s, --steps <n>", "Max autopilot continues per task", "30").option("-t, --max-tasks <n>", "Max number of tasks to run", "5").option("-p, --max-premium <n>", "Max total premium requests", "50").option("--dry-run", "Show tasks without executing").action(async (dir, opts) => {
746
+ try {
747
+ await runCommand(dir ?? process.cwd(), {
748
+ steps: parseInt(opts.steps, 10),
749
+ maxTasks: parseInt(opts.maxTasks, 10),
750
+ maxPremium: parseInt(opts.maxPremium, 10),
751
+ dryRun: opts.dryRun ?? false
752
+ });
753
+ } catch (err) {
754
+ fail(`Run error: ${err instanceof Error ? err.message : err}`);
755
+ process.exit(1);
756
+ }
757
+ });
758
+ }
667
759
  async function runCommand(dir, opts) {
668
760
  assertCopilot();
669
761
  const projectType = detectProjectType(dir);
670
762
  const name = detectProjectName(dir);
671
- log(`Project: ${chalk3.cyan(name)} (${projectType})`);
763
+ const mainBranch = isGitRepo(dir) ? detectMainBranch(dir) : null;
764
+ info(`Project: ${CYAN}${name}${RESET} (${projectType})`);
765
+ if (mainBranch) info(`Main branch: ${mainBranch}`);
672
766
  const tasks = getTasksForProject(projectType).slice(0, opts.maxTasks);
673
767
  if (tasks.length === 0) {
674
768
  warn("No tasks found for this project type.");
@@ -676,594 +770,244 @@ async function runCommand(dir, opts) {
676
770
  }
677
771
  log(`Found ${tasks.length} tasks:`);
678
772
  for (const t of tasks) {
679
- console.log(` ${chalk3.dim("\u2022")} ${t.title}`);
773
+ log(` ${DIM}\u2022${RESET} ${t.title}`);
680
774
  }
681
775
  if (opts.dryRun) {
682
- log(chalk3.dim("(dry-run \u2014 not executing)"));
776
+ log(`${DIM}(dry-run \u2014 not executing)${RESET}`);
683
777
  return;
684
778
  }
685
779
  const originalBranch = isGitRepo(dir) ? gitCurrentBranch(dir) : null;
686
780
  let completed = 0;
687
781
  let premiumTotal = 0;
688
782
  for (const task of tasks) {
783
+ if (premiumTotal >= opts.maxPremium) {
784
+ warn(`Premium request limit reached (${premiumTotal}/${opts.maxPremium}).`);
785
+ break;
786
+ }
689
787
  log(`
690
788
  ${"\u2550".repeat(60)}`);
691
- log(`Task: ${chalk3.bold(task.title)}`);
789
+ log(`${BOLD}${CYAN}Task: ${task.title}${RESET}`);
692
790
  log(`${"\u2550".repeat(60)}`);
693
- if (originalBranch && isGitRepo(dir)) {
694
- const branch = `copilot-agent/${task.title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
695
- try {
696
- if (gitStatus(dir)) gitStash(dir);
697
- gitCheckout(dir, branch);
698
- } catch (e) {
699
- warn(`Could not create branch ${branch}, continuing on current.`);
791
+ const timestamp = Date.now().toString(36);
792
+ const random = Math.random().toString(36).substring(2, 6);
793
+ const branchName = `agent/fix-${completed + 1}-${timestamp}-${random}`;
794
+ if (mainBranch && isGitRepo(dir)) {
795
+ if (gitStatus(dir)) gitStash(dir);
796
+ gitCheckout(dir, mainBranch);
797
+ if (!gitCreateBranch(dir, branchName)) {
798
+ warn(`Could not create branch ${branchName}, continuing on current.`);
700
799
  }
701
800
  }
702
- const spinner = ora2(`Running: ${task.title}\u2026`).start();
801
+ info(`Running: ${task.title}\u2026`);
703
802
  const result = await withLock(
704
803
  "copilot-run",
705
- () => runCopilotTask(task.prompt, opts.steps)
804
+ () => runCopilotTask(task.prompt, opts.steps, dir)
706
805
  );
707
- spinner.stop();
806
+ const commits = mainBranch ? gitCountCommits(dir, mainBranch, "HEAD") : 0;
708
807
  premiumTotal += result.premium;
709
808
  completed++;
710
- ok(`${task.title} \u2014 exit ${result.exitCode}, premium: ${result.premium}`);
809
+ ok(`${task.title} \u2014 ${commits} commit(s), ${result.premium} premium`);
711
810
  if (originalBranch && isGitRepo(dir)) {
712
- try {
713
- gitCheckout(dir, originalBranch);
714
- } catch {
715
- }
811
+ gitCheckout(dir, mainBranch ?? originalBranch);
716
812
  }
717
813
  }
718
814
  log(`
719
- Completed ${completed}/${tasks.length} tasks. Total premium: ${premiumTotal}`);
815
+ ${BOLD}\u2550\u2550\u2550 Run Summary \u2550\u2550\u2550${RESET}`);
816
+ log(`Completed ${completed}/${tasks.length} tasks. Total premium: ${premiumTotal}`);
720
817
  notify(`Completed ${completed} tasks`, name);
721
818
  }
722
819
 
723
820
  // src/commands/overnight.ts
724
- import chalk4 from "chalk";
725
- import ora3 from "ora";
726
821
  import { join as join5 } from "path";
727
822
  import { homedir as homedir3 } from "os";
823
+ function registerOvernightCommand(program2) {
824
+ program2.command("overnight [dir]").description("Run tasks continuously until a deadline").option("-u, --until <HH>", "Stop at this hour (24h format)", "07").option("-s, --steps <n>", "Max autopilot continues per task", "50").option("-c, --cooldown <n>", "Seconds between tasks", "15").option("-p, --max-premium <n>", "Max premium requests budget", "300").option("--dry-run", "Show plan without executing").action(async (dir, opts) => {
825
+ try {
826
+ await overnightCommand(dir ?? process.cwd(), {
827
+ until: parseInt(opts.until, 10),
828
+ steps: parseInt(opts.steps, 10),
829
+ cooldown: parseInt(opts.cooldown, 10),
830
+ maxPremium: parseInt(opts.maxPremium, 10),
831
+ dryRun: opts.dryRun ?? false
832
+ });
833
+ } catch (err) {
834
+ fail(`Overnight error: ${err instanceof Error ? err.message : err}`);
835
+ process.exit(1);
836
+ }
837
+ });
838
+ }
839
+ function isPastDeadline(untilHour) {
840
+ const hour = (/* @__PURE__ */ new Date()).getHours();
841
+ return hour >= untilHour && hour < 20;
842
+ }
728
843
  async function overnightCommand(dir, opts) {
729
844
  assertCopilot();
730
845
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").slice(0, 15);
731
- const logPath = join5(
732
- homedir3(),
733
- ".copilot",
734
- "auto-resume-logs",
735
- `overnight-${ts}.log`
736
- );
846
+ const logPath = join5(homedir3(), ".copilot", "auto-resume-logs", `overnight-${ts}.log`);
737
847
  setLogFile(logPath);
738
- const deadline = parseDeadline(opts.until);
739
848
  const name = detectProjectName(dir);
740
849
  const projectType = detectProjectType(dir);
741
- log(`Overnight runner for ${chalk4.cyan(name)} (${projectType})`);
742
- log(`Deadline: ${opts.until} (${msToHuman(deadline - Date.now())} from now)`);
743
- log(`Max premium: ${opts.maxPremium}, Steps: ${opts.steps}`);
744
- log(`Log: ${logPath}`);
850
+ const mainBranch = isGitRepo(dir) ? detectMainBranch(dir) : null;
851
+ info(`Overnight runner for ${CYAN}${name}${RESET} (${projectType})`);
852
+ info(`Deadline: ${String(opts.until).padStart(2, "0")}:00`);
853
+ info(`Max premium: ${opts.maxPremium}, Steps: ${opts.steps}`);
854
+ info(`Log: ${logPath}`);
855
+ const tasks = getTasksForProject(projectType);
745
856
  if (opts.dryRun) {
746
- const tasks2 = getTasksForProject(projectType);
747
857
  log(`
748
- Would run ${tasks2.length} tasks:`);
749
- for (const t of tasks2) console.log(` ${chalk4.dim("\u2022")} ${t.title}`);
858
+ Would run ${tasks.length} tasks:`);
859
+ for (const t of tasks) log(` ${DIM}\u2022${RESET} ${t.title}`);
750
860
  return;
751
861
  }
752
- const tasks = getTasksForProject(projectType);
862
+ const existingSession = findLatestIncomplete();
863
+ if (existingSession && validateSession(existingSession)) {
864
+ info(`Found incomplete session: ${existingSession}`);
865
+ const pid = findPidForSession(existingSession);
866
+ if (pid) {
867
+ info(`Waiting for running copilot (PID ${pid})...`);
868
+ await waitForExit(pid);
869
+ }
870
+ if (!hasTaskComplete(existingSession) && !isPastDeadline(opts.until)) {
871
+ info("Resuming incomplete session...");
872
+ const cwd = getSessionCwd(existingSession) || dir;
873
+ await runCopilotResume(
874
+ existingSession,
875
+ opts.steps,
876
+ "Continue remaining work. Complete the task.",
877
+ cwd
878
+ );
879
+ }
880
+ }
881
+ const originalBranch = isGitRepo(dir) ? gitCurrentBranch(dir) : null;
753
882
  let taskIdx = 0;
754
883
  let totalPremium = 0;
755
- let completedTasks = 0;
756
- let cycle = 0;
757
- while (Date.now() < deadline) {
884
+ let totalCommits = 0;
885
+ while (!isPastDeadline(opts.until) && taskIdx < tasks.length) {
758
886
  if (totalPremium >= opts.maxPremium) {
759
887
  warn(`Premium budget exhausted: ${totalPremium}/${opts.maxPremium}`);
760
888
  break;
761
889
  }
762
890
  const task = tasks[taskIdx % tasks.length];
763
- cycle++;
764
891
  taskIdx++;
765
892
  log(`
766
893
  ${"\u2550".repeat(60)}`);
767
- log(
768
- `Cycle ${cycle} | Task: ${chalk4.bold(task.title)} | Premium: ${totalPremium}/${opts.maxPremium}`
769
- );
770
- log(`Time remaining: ${msToHuman(deadline - Date.now())}`);
894
+ log(`${BOLD}${CYAN}[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Task ${taskIdx}: ${task.title}${RESET}`);
895
+ log(`${DIM}Premium: ${totalPremium}/${opts.maxPremium}${RESET}`);
771
896
  log(`${"\u2550".repeat(60)}`);
772
- const spinner = ora3(`Running: ${task.title}\u2026`).start();
897
+ const timestamp = Date.now().toString(36);
898
+ const random = Math.random().toString(36).substring(2, 6);
899
+ const branchName = `agent/overnight-${taskIdx}-${timestamp}-${random}`;
900
+ if (mainBranch && isGitRepo(dir)) {
901
+ gitStash(dir);
902
+ gitCheckout(dir, mainBranch);
903
+ gitCreateBranch(dir, branchName);
904
+ }
905
+ info(`Running: ${task.title}\u2026`);
773
906
  try {
774
907
  const result = await withLock(
775
908
  "copilot-overnight",
776
- () => runCopilotTask(task.prompt, opts.steps)
909
+ () => runCopilotTask(task.prompt, opts.steps, dir)
777
910
  );
778
- spinner.stop();
911
+ const commits = mainBranch ? gitCountCommits(dir, mainBranch, "HEAD") : 0;
779
912
  totalPremium += result.premium;
780
- completedTasks++;
781
- ok(
782
- `${task.title} \u2014 exit ${result.exitCode}, premium: ${result.premium}`
783
- );
913
+ totalCommits += commits;
914
+ if (commits > 0) {
915
+ ok(`${commits} commit(s) on ${branchName}`);
916
+ } else {
917
+ log(`${DIM}No commits on ${branchName}${RESET}`);
918
+ }
784
919
  } catch (err) {
785
- spinner.stop();
786
920
  fail(`Task failed: ${err}`);
787
921
  }
788
- if (Date.now() < deadline) {
789
- log("Cooldown 30s\u2026");
790
- await sleep3(3e4);
922
+ if (mainBranch && isGitRepo(dir)) {
923
+ gitCheckout(dir, mainBranch);
924
+ }
925
+ if (!isPastDeadline(opts.until)) {
926
+ info(`Cooldown ${opts.cooldown}s\u2026`);
927
+ await sleep3(opts.cooldown * 1e3);
791
928
  }
792
929
  }
793
- const summary = `Overnight done. ${completedTasks} tasks, ${totalPremium} premium.`;
794
- log(summary);
930
+ if (originalBranch && isGitRepo(dir)) {
931
+ gitCheckout(dir, originalBranch);
932
+ }
933
+ const summary = `Overnight done \u2014 ${taskIdx} tasks, ${totalCommits} commits, ${totalPremium} premium.`;
934
+ ok(summary);
795
935
  notify(summary, name);
796
936
  }
797
- function parseDeadline(hhmm) {
798
- const [h, m] = hhmm.split(":").map(Number);
799
- const now = /* @__PURE__ */ new Date();
800
- const target = new Date(now);
801
- target.setHours(h, m, 0, 0);
802
- if (target <= now) target.setDate(target.getDate() + 1);
803
- return target.getTime();
804
- }
805
- function msToHuman(ms) {
806
- const h = Math.floor(ms / 36e5);
807
- const m = Math.floor(ms % 36e5 / 6e4);
808
- return `${h}h ${m}m`;
809
- }
810
937
  function sleep3(ms) {
811
938
  return new Promise((r) => setTimeout(r, ms));
812
939
  }
813
940
 
814
941
  // src/commands/research.ts
815
- import chalk5 from "chalk";
816
- var RESEARCH_PROMPTS = [
817
- {
818
- title: "Dependency updates",
819
- prompt: "Research the latest versions of all dependencies. Check changelogs for breaking changes. Create a summary of what can be safely updated."
820
- },
821
- {
822
- title: "Performance review",
823
- prompt: "Profile the application for performance bottlenecks. Check startup time, memory usage, and hot paths. Suggest optimizations with benchmarks."
824
- },
825
- {
826
- title: "Architecture review",
827
- prompt: "Review the project architecture. Check for code smells, circular dependencies, and coupling issues. Suggest improvements following clean architecture."
828
- },
829
- {
830
- title: "Accessibility audit",
831
- prompt: "Audit the UI for accessibility issues. Check color contrast, screen reader support, and keyboard navigation. Create a report."
832
- },
833
- {
834
- title: "Best practices",
835
- prompt: "Compare the codebase against current best practices for this framework/language. Identify gaps and suggest improvements."
836
- }
837
- ];
838
- async function researchCommand(topic, opts) {
839
- assertCopilot();
840
- if (topic) {
841
- log(`Research topic: ${chalk5.cyan(topic)}`);
842
- const result = await withLock(
843
- "copilot-research",
844
- () => runCopilotTask(
845
- `Research the following topic and create a detailed report: ${topic}`,
846
- opts.steps
847
- )
848
- );
849
- ok(`Research complete \u2014 premium: ${result.premium}`);
850
- notify("Research complete", topic.slice(0, 30));
851
- return;
852
- }
853
- log("Running predefined research tasks\u2026");
854
- for (const r of RESEARCH_PROMPTS) {
855
- log(`
856
- ${chalk5.bold(r.title)}`);
857
- try {
858
- const result = await withLock(
859
- "copilot-research",
860
- () => runCopilotTask(r.prompt, opts.steps)
861
- );
862
- ok(`${r.title} \u2014 premium: ${result.premium}`);
863
- } catch (err) {
864
- fail(`${r.title} failed: ${err}`);
865
- }
866
- }
867
- notify("All research tasks complete");
868
- }
869
-
870
- // src/commands/daemon.ts
871
- import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync2, unlinkSync, statSync as statSync2 } from "fs";
872
- import { join as join6 } from "path";
942
+ import { existsSync as existsSync5, copyFileSync, mkdirSync as mkdirSync3 } from "fs";
943
+ import { join as join6, resolve as resolve3 } from "path";
873
944
  import { homedir as homedir4 } from "os";
874
- import { spawn as spawn2 } from "child_process";
875
- var PID_FILE = join6(homedir4(), ".copilot", "watchdog.pid");
876
- var LOG_FILE = join6(homedir4(), ".copilot", "auto-resume-logs", "watchdog.log");
877
- var SESSION_DIR2 = join6(homedir4(), ".copilot", "session-state");
878
- async function daemonCommand(action, opts) {
879
- switch (action) {
880
- case "start":
881
- return startDaemon(opts);
882
- case "stop":
883
- return stopDaemon();
884
- case "status":
885
- return statusDaemon();
886
- case "logs":
887
- return showLogs();
888
- default:
889
- fail(`Unknown action: ${action}. Use: start, stop, status, logs`);
890
- process.exit(1);
891
- }
892
- }
893
- function isDaemonRunning() {
894
- if (!existsSync5(PID_FILE)) return { running: false };
895
- try {
896
- const pid = parseInt(readFileSync4(PID_FILE, "utf-8").trim());
897
- process.kill(pid, 0);
898
- return { running: true, pid };
899
- } catch {
900
- return { running: false };
901
- }
902
- }
903
- async function startDaemon(opts) {
904
- assertCopilot();
905
- const { running, pid } = isDaemonRunning();
906
- if (running) {
907
- fail(`Watchdog already running (PID: ${pid}). Use 'copilot-agent daemon stop' first.`);
908
- process.exit(1);
909
- }
910
- if (existsSync5(PID_FILE)) {
911
- unlinkSync(PID_FILE);
912
- }
913
- log(`\u{1F415} Starting watchdog daemon (poll: ${opts.poll}s, auto-resume: ${opts.resume})`);
914
- log(` Log: ${LOG_FILE}`);
915
- const child = spawn2(
916
- process.execPath,
917
- [
918
- process.argv[1],
919
- "daemon",
920
- "_loop",
921
- "--poll",
922
- String(opts.poll),
923
- "--idle",
924
- String(opts.idle),
925
- "--steps",
926
- String(opts.steps),
927
- ...opts.resume ? ["--resume"] : []
928
- ],
929
- {
930
- detached: true,
931
- stdio: "ignore"
932
- }
933
- );
934
- child.unref();
935
- if (child.pid) {
936
- writeFileSync2(PID_FILE, String(child.pid));
937
- ok(`Watchdog started (PID: ${child.pid})`);
938
- } else {
939
- fail("Failed to start watchdog");
940
- }
941
- }
942
- async function stopDaemon() {
943
- const { running, pid } = isDaemonRunning();
944
- if (!running) {
945
- warn("Watchdog is not running.");
946
- return;
947
- }
948
- try {
949
- process.kill(pid, "SIGTERM");
950
- ok(`Watchdog stopped (PID: ${pid})`);
951
- } catch {
952
- fail(`Could not stop PID ${pid}`);
953
- }
954
- try {
955
- unlinkSync(PID_FILE);
956
- } catch {
957
- }
958
- }
959
- async function statusDaemon() {
960
- const { running, pid } = isDaemonRunning();
961
- if (running) {
962
- ok(`Watchdog running (PID: ${pid})`);
963
- if (existsSync5(LOG_FILE)) {
964
- const stat = statSync2(LOG_FILE);
965
- log(`Log: ${LOG_FILE} (${(stat.size / 1024).toFixed(1)} KB)`);
966
- }
967
- } else {
968
- log("Watchdog is not running.");
969
- }
970
- }
971
- async function showLogs() {
972
- if (!existsSync5(LOG_FILE)) {
973
- log("No log file found.");
974
- return;
975
- }
976
- const lines = readFileSync4(LOG_FILE, "utf-8").trimEnd().split("\n");
977
- const tail = lines.slice(-30);
978
- for (const line of tail) console.log(line);
979
- }
980
- async function daemonLoop(opts) {
981
- setLogFile(LOG_FILE);
982
- log(`\u{1F415} Watchdog daemon loop started (PID: ${process.pid})`);
983
- let lastAlertedSid = "";
984
- const shutdown = () => {
985
- log("Watchdog shutting down");
986
- try {
987
- unlinkSync(PID_FILE);
988
- } catch {
989
- }
990
- process.exit(0);
991
- };
992
- process.on("SIGTERM", shutdown);
993
- process.on("SIGINT", shutdown);
994
- while (true) {
995
- await sleep4(opts.poll * 1e3);
945
+ function registerResearchCommand(program2) {
946
+ program2.command("research [project]").description("Research improvements or a specific topic").option("-s, --steps <n>", "Max autopilot continues", "50").action(async (project, opts) => {
996
947
  try {
997
- const sid = getLatestSessionId();
998
- if (!sid || !validateSession(sid)) continue;
999
- if (sid === lastAlertedSid && hasTaskComplete(sid)) continue;
1000
- const pid = await findPidForSession(sid);
1001
- if (pid) continue;
1002
- if (hasTaskComplete(sid)) {
1003
- if (sid !== lastAlertedSid) {
1004
- log(`\u2705 Session ${sid.slice(0, 8)} completed: ${getSessionSummary(sid).slice(0, 60)}`);
1005
- notify("Task completed", `Session ${sid.slice(0, 8)}`);
1006
- lastAlertedSid = sid;
1007
- }
1008
- continue;
1009
- }
1010
- const eventsPath = join6(SESSION_DIR2, sid, "events.jsonl");
1011
- const mtime = statSync2(eventsPath).mtimeMs;
1012
- const idleMinutes = (Date.now() - mtime) / 6e4;
1013
- if (idleMinutes < opts.idle) continue;
1014
- log(`\u23F8\uFE0F Session ${sid.slice(0, 8)} idle ${idleMinutes.toFixed(0)}m (last: ${getLastEvent(sid)})`);
1015
- if (opts.resume) {
1016
- log(`\u{1F504} Auto-resuming session ${sid.slice(0, 8)}\u2026`);
1017
- lastAlertedSid = sid;
1018
- try {
1019
- await withLock("watchdog-resume", async () => {
1020
- const result = await runCopilotResume(sid, opts.steps);
1021
- ok(`Resume done \u2014 exit ${result.exitCode}, premium: ${result.premium}`);
1022
- });
1023
- } catch (err) {
1024
- fail(`Resume failed: ${err}`);
1025
- }
1026
- } else {
1027
- if (sid !== lastAlertedSid) {
1028
- warn(`Session ${sid.slice(0, 8)} needs attention (idle ${idleMinutes.toFixed(0)}m)`);
1029
- notify("Session interrupted", `${sid.slice(0, 8)} idle ${idleMinutes.toFixed(0)}m`);
1030
- lastAlertedSid = sid;
1031
- }
1032
- }
948
+ await researchCommand(project ?? process.cwd(), {
949
+ steps: parseInt(opts.steps, 10)
950
+ });
1033
951
  } catch (err) {
1034
- fail(`Watchdog error: ${err}`);
1035
- }
1036
- }
1037
- }
1038
- function sleep4(ms) {
1039
- return new Promise((r) => setTimeout(r, ms));
1040
- }
1041
-
1042
- // src/commands/multi.ts
1043
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
1044
- import { join as join7, resolve as resolve3, basename as basename2 } from "path";
1045
- import { homedir as homedir5 } from "os";
1046
- import chalk6 from "chalk";
1047
- var PROJECTS_FILE = join7(homedir5(), ".copilot", "autonomous-projects.txt");
1048
- var LOG_DIR = join7(homedir5(), ".copilot", "auto-resume-logs");
1049
- async function multiCommand(action, args, opts) {
1050
- ensureFiles();
1051
- switch (action) {
1052
- case "add":
1053
- return addProject(args[0]);
1054
- case "remove":
1055
- return removeProject(args[0]);
1056
- case "list":
1057
- return listProjects();
1058
- case "health":
1059
- case "research":
1060
- return runAll(action, opts);
1061
- default:
1062
- fail(`Unknown action: ${action}. Use: add, remove, list, health, research`);
952
+ fail(`Research error: ${err instanceof Error ? err.message : err}`);
1063
953
  process.exit(1);
1064
- }
1065
- }
1066
- function ensureFiles() {
1067
- mkdirSync3(LOG_DIR, { recursive: true });
1068
- if (!existsSync6(PROJECTS_FILE)) writeFileSync3(PROJECTS_FILE, "");
1069
- }
1070
- function readProjects() {
1071
- return readFileSync5(PROJECTS_FILE, "utf-8").split("\n").map((l) => l.trim()).filter(Boolean);
1072
- }
1073
- function writeProjects(projects) {
1074
- writeFileSync3(PROJECTS_FILE, projects.join("\n") + "\n");
1075
- }
1076
- async function addProject(path) {
1077
- if (!path) {
1078
- fail("Usage: copilot-agent multi add <path>");
1079
- process.exit(1);
1080
- }
1081
- const resolved = resolve3(path);
1082
- if (!existsSync6(resolved)) {
1083
- fail(`Not found: ${resolved}`);
1084
- process.exit(1);
1085
- }
1086
- await withLock("projects-file", async () => {
1087
- const projects = readProjects();
1088
- if (projects.includes(resolved)) {
1089
- warn(`Already registered: ${resolved}`);
1090
- return;
1091
954
  }
1092
- projects.push(resolved);
1093
- writeProjects(projects);
1094
- ok(`Added: ${resolved}`);
1095
955
  });
1096
956
  }
1097
- async function removeProject(path) {
1098
- if (!path) {
1099
- fail("Usage: copilot-agent multi remove <path>");
1100
- process.exit(1);
1101
- }
1102
- const resolved = resolve3(path);
1103
- await withLock("projects-file", async () => {
1104
- const projects = readProjects();
1105
- const filtered = projects.filter((p) => p !== resolved);
1106
- if (filtered.length === projects.length) {
1107
- warn(`Not registered: ${resolved}`);
1108
- return;
1109
- }
1110
- writeProjects(filtered);
1111
- ok(`Removed: ${resolved}`);
1112
- });
1113
- }
1114
- function listProjects() {
1115
- const projects = readProjects();
1116
- if (projects.length === 0) {
1117
- log("No projects registered. Add: copilot-agent multi add <path>");
1118
- return;
1119
- }
1120
- console.log(chalk6.bold("\nRegistered projects:"));
1121
- for (let i = 0; i < projects.length; i++) {
1122
- const exists = existsSync6(projects[i]);
1123
- const icon = exists ? chalk6.green("\u2705") : chalk6.red("\u274C");
1124
- const type = exists ? detectProjectType(projects[i]) : "?";
1125
- console.log(` ${i + 1}. ${icon} ${projects[i]} ${chalk6.dim(`(${type})`)}`);
1126
- }
1127
- console.log();
957
+ function buildResearchPrompt(projectType, projectName) {
958
+ return `You are a senior software architect. Analyze this ${projectType} project "${projectName}" thoroughly.
959
+
960
+ Research and produce a file called RESEARCH-PROPOSALS.md with:
961
+
962
+ 1. **Architecture Assessment** \u2014 Current architecture, patterns used, strengths and weaknesses
963
+ 2. **Code Quality Report** \u2014 Common issues, anti-patterns, technical debt areas
964
+ 3. **Security Audit** \u2014 Potential vulnerabilities, dependency risks, configuration issues
965
+ 4. **Performance Analysis** \u2014 Bottlenecks, optimization opportunities, resource usage
966
+ 5. **Testing Gap Analysis** \u2014 Untested areas, test quality, coverage recommendations
967
+ 6. **Improvement Proposals** \u2014 Prioritized list of actionable improvements with effort estimates
968
+
969
+ For each proposal, include:
970
+ - Priority (P0/P1/P2)
971
+ - Estimated effort (hours)
972
+ - Impact description
973
+ - Suggested implementation approach
974
+
975
+ Write RESEARCH-PROPOSALS.md in the project root.`;
1128
976
  }
1129
- async function runAll(mode, opts) {
977
+ async function researchCommand(dir, opts) {
1130
978
  assertCopilot();
1131
- const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").slice(0, 15);
1132
- setLogFile(join7(LOG_DIR, `multi-${mode}-${ts}.log`));
1133
- const projects = readProjects();
1134
- if (projects.length === 0) {
1135
- fail("No projects registered. Add: copilot-agent multi add <path>");
1136
- process.exit(1);
1137
- }
1138
- log(`\u{1F3ED} Multi-project ${mode} \u2014 ${projects.length} projects`);
1139
- if (opts.dryRun) {
1140
- for (const p of projects) {
1141
- const type = existsSync6(p) ? detectProjectType(p) : "unknown";
1142
- const tasks = getTasksForProject(type);
1143
- console.log(`
1144
- ${chalk6.bold(basename2(p))} (${type})`);
1145
- for (const t of tasks.slice(0, 3)) {
1146
- console.log(` ${chalk6.dim("\u2022")} ${t.title}`);
1147
- }
1148
- }
1149
- log(chalk6.dim("\n(dry-run \u2014 not executing)"));
1150
- return;
1151
- }
1152
- let total = 0;
1153
- let success = 0;
1154
- let failed = 0;
1155
- let skipped = 0;
1156
- const report = [];
1157
- for (const project of projects) {
1158
- total++;
1159
- if (!existsSync6(project)) {
1160
- warn(`Skipping (not found): ${project}`);
1161
- report.push(`\u23ED ${basename2(project)} (not found)`);
1162
- skipped++;
1163
- continue;
1164
- }
1165
- const name = detectProjectName(project);
1166
- const type = detectProjectType(project);
1167
- log(`
1168
- ${"\u2550".repeat(50)}`);
1169
- log(`${chalk6.bold(name)} (${type}) \u2014 ${total}/${projects.length}`);
1170
- log(`${"\u2550".repeat(50)}`);
1171
- const tasks = mode === "research" ? [{ title: "Research", prompt: "Research latest best practices, dependency updates, and architecture improvements. Create a report.", priority: 1 }] : getTasksForProject(type).slice(0, 3);
1172
- let projectSuccess = true;
1173
- for (const task of tasks) {
1174
- try {
1175
- const result = await withLock(
1176
- "copilot-multi",
1177
- () => runCopilotTask(
1178
- `Project: ${project}
1179
-
1180
- ${task.prompt}`,
1181
- opts.steps
1182
- )
1183
- );
1184
- ok(`${task.title} \u2014 exit ${result.exitCode}, premium: ${result.premium}`);
1185
- } catch (err) {
1186
- fail(`${task.title} failed: ${err}`);
1187
- projectSuccess = false;
1188
- }
1189
- }
1190
- if (projectSuccess) {
1191
- success++;
1192
- report.push(`\u2705 ${name}`);
1193
- } else {
1194
- failed++;
1195
- report.push(`\u274C ${name}`);
1196
- }
1197
- if (total < projects.length) {
1198
- log(`Cooldown ${opts.cooldown}s\u2026`);
1199
- await new Promise((r) => setTimeout(r, opts.cooldown * 1e3));
1200
- }
979
+ const projectDir = resolve3(dir);
980
+ const projectType = detectProjectType(projectDir);
981
+ const projectName = detectProjectName(projectDir);
982
+ info(`Researching: ${CYAN}${projectName}${RESET} (${projectType})`);
983
+ const prompt = buildResearchPrompt(projectType, projectName);
984
+ const result = await withLock(
985
+ "copilot-research",
986
+ () => runCopilotTask(prompt, opts.steps, projectDir)
987
+ );
988
+ log(`Copilot exited with code ${result.exitCode}`);
989
+ const proposalsFile = join6(projectDir, "RESEARCH-PROPOSALS.md");
990
+ if (existsSync5(proposalsFile)) {
991
+ ok("RESEARCH-PROPOSALS.md generated.");
992
+ const backupDir = join6(homedir4(), ".copilot", "research-reports");
993
+ mkdirSync3(backupDir, { recursive: true });
994
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
995
+ const backupFile = join6(backupDir, `${projectName}-${timestamp}.md`);
996
+ copyFileSync(proposalsFile, backupFile);
997
+ ok(`Backup saved: ${backupFile}`);
998
+ } else {
999
+ warn("RESEARCH-PROPOSALS.md was not generated. Check copilot output.");
1201
1000
  }
1202
- console.log(`
1203
- ${"\u2550".repeat(50)}`);
1204
- log(`\u{1F4CA} Summary: ${success}/${total} succeeded, ${failed} failed, ${skipped} skipped`);
1205
- for (const line of report) console.log(` ${line}`);
1206
- console.log();
1207
- notify(`Multi-${mode}: ${success}/${total} succeeded`, "copilot-agent");
1001
+ notify("Research complete", projectName);
1208
1002
  }
1209
1003
 
1210
1004
  // src/index.ts
1211
1005
  var program = new Command();
1212
- program.name("copilot-agent").description("Autonomous GitHub Copilot CLI agent \u2014 auto-resume, task discovery, overnight runner").version("0.2.0");
1213
- program.command("status").description("Show copilot session status").option("-l, --limit <n>", "Number of sessions to show", "10").option("-a, --active", "Show only active (running) sessions").action(async (opts) => {
1214
- await statusCommand({
1215
- limit: parseInt(opts.limit),
1216
- active: opts.active ?? false
1217
- });
1218
- });
1219
- program.command("watch [session-id]").description("Watch a session and auto-resume when it stops").option("-s, --steps <n>", "Max autopilot continues per resume", "50").option("-r, --max-resumes <n>", "Max number of resumes", "20").option("-m, --message <msg>", "Message to send on resume").action(async (sid, opts) => {
1220
- await watchCommand(sid, {
1221
- steps: parseInt(opts.steps),
1222
- maxResumes: parseInt(opts.maxResumes),
1223
- message: opts.message
1224
- });
1225
- });
1226
- program.command("run [dir]").description("Discover and fix issues in a project").option("-s, --steps <n>", "Max autopilot continues per task", "30").option("-t, --max-tasks <n>", "Max number of tasks to run", "5").option("--dry-run", "Show tasks without executing").action(async (dir, opts) => {
1227
- await runCommand(dir ?? process.cwd(), {
1228
- steps: parseInt(opts.steps),
1229
- maxTasks: parseInt(opts.maxTasks),
1230
- dryRun: opts.dryRun ?? false
1231
- });
1232
- });
1233
- program.command("overnight [dir]").description("Run tasks continuously until a deadline").option("-u, --until <HH:MM>", "Deadline time (24h format)", "07:00").option("-s, --steps <n>", "Max autopilot continues per task", "50").option("-p, --max-premium <n>", "Max premium requests budget", "300").option("--dry-run", "Show plan without executing").action(async (dir, opts) => {
1234
- await overnightCommand(dir ?? process.cwd(), {
1235
- until: opts.until,
1236
- steps: parseInt(opts.steps),
1237
- maxPremium: parseInt(opts.maxPremium),
1238
- dryRun: opts.dryRun ?? false
1239
- });
1240
- });
1241
- program.command("research [topic]").description("Research improvements or a specific topic").option("-s, --steps <n>", "Max autopilot continues", "30").action(async (topic, opts) => {
1242
- await researchCommand(topic, {
1243
- steps: parseInt(opts.steps)
1244
- });
1245
- });
1246
- var daemon = program.command("daemon <action>").description("Background watchdog daemon (start, stop, status, logs)").option("--poll <n>", "Poll interval in seconds", "20").option("--idle <n>", "Minutes before considering session idle", "5").option("--resume", "Auto-resume interrupted sessions").option("-s, --steps <n>", "Max autopilot continues per resume", "50").action(async (action, opts) => {
1247
- const parsed = {
1248
- poll: parseInt(opts.poll),
1249
- idle: parseInt(opts.idle),
1250
- resume: opts.resume ?? false,
1251
- steps: parseInt(opts.steps)
1252
- };
1253
- if (action === "_loop") {
1254
- await daemonLoop(parsed);
1255
- } else {
1256
- await daemonCommand(action, parsed);
1257
- }
1258
- });
1259
- var multi = program.command("multi <action> [args...]").description("Multi-project orchestrator (add, remove, list, health, research)").option("-s, --steps <n>", "Max autopilot continues per task", "30").option("-c, --cooldown <n>", "Seconds between projects", "60").option("-p, --max-premium <n>", "Max premium per project", "30").option("--dry-run", "Show plan without executing").action(async (action, args, opts) => {
1260
- await multiCommand(action, args, {
1261
- mode: action,
1262
- cooldown: parseInt(opts.cooldown),
1263
- steps: parseInt(opts.steps),
1264
- maxPremium: parseInt(opts.maxPremium),
1265
- dryRun: opts.dryRun ?? false
1266
- });
1267
- });
1006
+ program.name("copilot-agent").version("0.3.0").description("Autonomous GitHub Copilot CLI agent \u2014 auto-resume, task discovery, overnight runs");
1007
+ registerStatusCommand(program);
1008
+ registerWatchCommand(program);
1009
+ registerRunCommand(program);
1010
+ registerOvernightCommand(program);
1011
+ registerResearchCommand(program);
1268
1012
  program.parse();
1269
1013
  //# sourceMappingURL=index.js.map