copilot-agent 0.12.0 → 1.0.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
@@ -639,7 +639,7 @@ async function runCopilot(args, options) {
639
639
  if (!options?.useWorktree) {
640
640
  await waitForAgentInDir(dir, "copilot");
641
641
  }
642
- return new Promise((resolve7) => {
642
+ return new Promise((resolve8) => {
643
643
  const child = spawn("copilot", args, {
644
644
  cwd: options?.cwd,
645
645
  stdio: "inherit",
@@ -649,10 +649,10 @@ async function runCopilot(args, options) {
649
649
  await sleep(3e3);
650
650
  const sid = getLatestSessionId();
651
651
  const premium = sid ? getSessionPremium(sid) : 0;
652
- resolve7({ exitCode: code ?? 1, sessionId: sid, premium });
652
+ resolve8({ exitCode: code ?? 1, sessionId: sid, premium });
653
653
  });
654
654
  child.on("error", () => {
655
- resolve7({ exitCode: 1, sessionId: null, premium: 0 });
655
+ resolve8({ exitCode: 1, sessionId: null, premium: 0 });
656
656
  });
657
657
  });
658
658
  }
@@ -685,7 +685,7 @@ async function runClaude(args, options) {
685
685
  if (!options?.useWorktree) {
686
686
  await waitForAgentInDir(dir, "claude");
687
687
  }
688
- return new Promise((resolve7) => {
688
+ return new Promise((resolve8) => {
689
689
  const child = spawn("claude", args, {
690
690
  cwd: options?.cwd,
691
691
  stdio: "inherit",
@@ -694,10 +694,10 @@ async function runClaude(args, options) {
694
694
  child.on("close", async (code) => {
695
695
  await sleep(2e3);
696
696
  const sid = getLatestClaudeSessionId(options?.cwd);
697
- resolve7({ exitCode: code ?? 1, sessionId: sid, premium: 0 });
697
+ resolve8({ exitCode: code ?? 1, sessionId: sid, premium: 0 });
698
698
  });
699
699
  child.on("error", () => {
700
- resolve7({ exitCode: 1, sessionId: null, premium: 0 });
700
+ resolve8({ exitCode: 1, sessionId: null, premium: 0 });
701
701
  });
702
702
  });
703
703
  }
@@ -1232,9 +1232,9 @@ async function runCommand(dir, opts) {
1232
1232
  ${"\u2550".repeat(60)}`);
1233
1233
  log(`${BOLD}${CYAN}Task: ${task.title}${RESET}`);
1234
1234
  log(`${"\u2550".repeat(60)}`);
1235
- const timestamp = Date.now().toString(36);
1235
+ const timestamp2 = Date.now().toString(36);
1236
1236
  const random = Math.random().toString(36).substring(2, 6);
1237
- const branchName = `agent/fix-${completed + 1}-${timestamp}-${random}`;
1237
+ const branchName = `agent/fix-${completed + 1}-${timestamp2}-${random}`;
1238
1238
  if (mainBranch && isGitRepo(dir)) {
1239
1239
  if (gitStatus(dir)) gitStash(dir);
1240
1240
  gitCheckout(dir, mainBranch);
@@ -1367,9 +1367,9 @@ ${"\u2550".repeat(60)}`);
1367
1367
  log(`${BOLD}${CYAN}[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Task ${taskIdx}: ${task.title}${RESET}`);
1368
1368
  log(`${DIM}Premium: ${totalPremium}/${opts.maxPremium}${RESET}`);
1369
1369
  log(`${"\u2550".repeat(60)}`);
1370
- const timestamp = Date.now().toString(36);
1370
+ const timestamp2 = Date.now().toString(36);
1371
1371
  const random = Math.random().toString(36).substring(2, 6);
1372
- const branchName = `agent/overnight-${taskIdx}-${timestamp}-${random}`;
1372
+ const branchName = `agent/overnight-${taskIdx}-${timestamp2}-${random}`;
1373
1373
  if (mainBranch && isGitRepo(dir)) {
1374
1374
  gitStash(dir);
1375
1375
  gitCheckout(dir, mainBranch);
@@ -1492,8 +1492,8 @@ async function researchCommand(dir, opts) {
1492
1492
  ok("RESEARCH-PROPOSALS.md generated.");
1493
1493
  const backupDir = join6(homedir4(), ".copilot", "research-reports");
1494
1494
  mkdirSync3(backupDir, { recursive: true });
1495
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1496
- const backupFile = join6(backupDir, `${projectName}-${timestamp}.md`);
1495
+ const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1496
+ const backupFile = join6(backupDir, `${projectName}-${timestamp2}.md`);
1497
1497
  copyFileSync(proposalsFile, backupFile);
1498
1498
  ok(`Backup saved: ${backupFile}`);
1499
1499
  } else {
@@ -1826,11 +1826,16 @@ function cacheDetail(cache, s) {
1826
1826
  }
1827
1827
  function runBlessedDashboard(refreshSec, limit) {
1828
1828
  const cache = createCache();
1829
+ const origTerm = process.env.TERM;
1830
+ if (origTerm?.includes("256color")) {
1831
+ process.env.TERM = "xterm";
1832
+ }
1829
1833
  const screen = blessed.screen({
1830
1834
  smartCSR: true,
1831
1835
  title: "copilot-agent dashboard",
1832
1836
  fullUnicode: true
1833
1837
  });
1838
+ if (origTerm) process.env.TERM = origTerm;
1834
1839
  const BG = "#0d1117";
1835
1840
  const FG = "#e6edf3";
1836
1841
  const BORDER_COLOR = "#30363d";
@@ -3611,9 +3616,681 @@ function fmtDur2(ms) {
3611
3616
  return `${Math.floor(ms / 36e5)}h ${Math.round(ms % 36e5 / 6e4)}m`;
3612
3617
  }
3613
3618
 
3619
+ // src/commands/multi.ts
3620
+ import { existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
3621
+ import { join as join13, resolve as resolve7, basename as basename3 } from "path";
3622
+ import { homedir as homedir11 } from "os";
3623
+ import chalk10 from "chalk";
3624
+ import { parse as parseYaml4, stringify as stringifyYaml3 } from "yaml";
3625
+ var CONFIG_DIR3 = join13(homedir11(), ".copilot-agent");
3626
+ var PROJECTS_FILE = join13(CONFIG_DIR3, "multi-projects.txt");
3627
+ var STATUS_FILE = join13(CONFIG_DIR3, "multi-status.yaml");
3628
+ var LOG_DIR = join13(CONFIG_DIR3, "multi-logs");
3629
+ var MAX_CONCURRENCY = 3;
3630
+ function registerMultiCommand(program2) {
3631
+ program2.command("multi <action> [args...]").description("Multi-project orchestration \u2014 add/remove/list/run/status/research").option("-a, --agent <type>", "Agent to use (copilot or claude)", "copilot").option("--parallel", "Run projects in parallel (max 3 concurrent)").option("--cooldown <n>", "Cooldown between projects in seconds", "30").option("-s, --steps <n>", "Max steps per task", "10").option("--max-premium <n>", "Premium budget per project", "50").option("--dry-run", "Preview without executing").action(async (action, args, opts) => {
3632
+ try {
3633
+ await multiCommand(action, args, {
3634
+ agent: resolveAgent(opts.agent),
3635
+ parallel: opts.parallel ?? false,
3636
+ cooldown: parseInt(opts.cooldown, 10),
3637
+ steps: parseInt(opts.steps, 10),
3638
+ maxPremium: parseInt(opts.maxPremium, 10),
3639
+ dryRun: opts.dryRun ?? false
3640
+ });
3641
+ } catch (err) {
3642
+ fail(`Multi error: ${err instanceof Error ? err.message : err}`);
3643
+ process.exit(1);
3644
+ }
3645
+ });
3646
+ }
3647
+ async function multiCommand(action, args, opts) {
3648
+ ensureFiles();
3649
+ switch (action) {
3650
+ case "add":
3651
+ return addProject(args[0]);
3652
+ case "remove":
3653
+ return removeProject(args[0]);
3654
+ case "list":
3655
+ return listProjects();
3656
+ case "status":
3657
+ return showStatus();
3658
+ case "run":
3659
+ case "health":
3660
+ return runAll("health", opts);
3661
+ case "research":
3662
+ return runAll("research", opts);
3663
+ default:
3664
+ fail(`Unknown action: ${action}. Use: add, remove, list, run, status, research`);
3665
+ process.exit(1);
3666
+ }
3667
+ }
3668
+ function ensureFiles() {
3669
+ mkdirSync8(LOG_DIR, { recursive: true });
3670
+ mkdirSync8(CONFIG_DIR3, { recursive: true });
3671
+ if (!existsSync11(PROJECTS_FILE)) writeFileSync7(PROJECTS_FILE, "");
3672
+ }
3673
+ function readProjects() {
3674
+ return readFileSync8(PROJECTS_FILE, "utf-8").split("\n").map((l) => l.trim()).filter(Boolean);
3675
+ }
3676
+ function writeProjects(projects) {
3677
+ writeFileSync7(PROJECTS_FILE, projects.join("\n") + "\n");
3678
+ }
3679
+ function readStatusFile() {
3680
+ if (!existsSync11(STATUS_FILE)) return [];
3681
+ try {
3682
+ const raw = readFileSync8(STATUS_FILE, "utf-8");
3683
+ const parsed = parseYaml4(raw);
3684
+ return Array.isArray(parsed) ? parsed : [];
3685
+ } catch {
3686
+ return [];
3687
+ }
3688
+ }
3689
+ function writeStatusFile(statuses) {
3690
+ writeFileSync7(STATUS_FILE, stringifyYaml3(statuses));
3691
+ }
3692
+ function upsertStatus(entry) {
3693
+ const statuses = readStatusFile();
3694
+ const idx = statuses.findIndex((s) => s.project === entry.project);
3695
+ if (idx >= 0) {
3696
+ statuses[idx] = entry;
3697
+ } else {
3698
+ statuses.push(entry);
3699
+ }
3700
+ writeStatusFile(statuses);
3701
+ }
3702
+ function formatDuration2(ms) {
3703
+ const totalSec = Math.round(ms / 1e3);
3704
+ const min = Math.floor(totalSec / 60);
3705
+ const sec = totalSec % 60;
3706
+ return min > 0 ? `${min}m ${sec}s` : `${sec}s`;
3707
+ }
3708
+ async function addProject(path) {
3709
+ if (!path) {
3710
+ fail("Usage: copilot-agent multi add <path>");
3711
+ process.exit(1);
3712
+ }
3713
+ const resolved = resolve7(path);
3714
+ if (!existsSync11(resolved)) {
3715
+ fail(`Not found: ${resolved}`);
3716
+ process.exit(1);
3717
+ }
3718
+ await withLock("projects-file", async () => {
3719
+ const projects = readProjects();
3720
+ if (projects.includes(resolved)) {
3721
+ warn(`Already registered: ${resolved}`);
3722
+ return;
3723
+ }
3724
+ projects.push(resolved);
3725
+ writeProjects(projects);
3726
+ ok(`Added: ${resolved}`);
3727
+ });
3728
+ }
3729
+ async function removeProject(path) {
3730
+ if (!path) {
3731
+ fail("Usage: copilot-agent multi remove <path>");
3732
+ process.exit(1);
3733
+ }
3734
+ const resolved = resolve7(path);
3735
+ await withLock("projects-file", async () => {
3736
+ const projects = readProjects();
3737
+ const filtered = projects.filter((p) => p !== resolved);
3738
+ if (filtered.length === projects.length) {
3739
+ warn(`Not registered: ${resolved}`);
3740
+ return;
3741
+ }
3742
+ writeProjects(filtered);
3743
+ ok(`Removed: ${resolved}`);
3744
+ });
3745
+ }
3746
+ function listProjects() {
3747
+ const projects = readProjects();
3748
+ if (projects.length === 0) {
3749
+ log("No projects registered. Add: copilot-agent multi add <path>");
3750
+ return;
3751
+ }
3752
+ const statuses = readStatusFile();
3753
+ console.log(chalk10.bold("\nRegistered projects:"));
3754
+ for (let i = 0; i < projects.length; i++) {
3755
+ const p = projects[i];
3756
+ const exists = existsSync11(p);
3757
+ const icon = exists ? chalk10.green("\u2705") : chalk10.red("\u274C");
3758
+ const type = exists ? detectProjectType(p) : "?";
3759
+ const st = statuses.find((s) => s.project === p);
3760
+ const statusTag = st ? chalk10.dim(` [${st.status} \u2014 ${st.agent} \u2014 ${st.duration}]`) : "";
3761
+ console.log(` ${i + 1}. ${icon} ${p} ${chalk10.dim(`(${type})`)}${statusTag}`);
3762
+ }
3763
+ console.log();
3764
+ }
3765
+ function showStatus() {
3766
+ const statuses = readStatusFile();
3767
+ if (statuses.length === 0) {
3768
+ log("No run history. Execute: copilot-agent multi run");
3769
+ return;
3770
+ }
3771
+ console.log(chalk10.bold("\nMulti-project status:"));
3772
+ for (const s of statuses) {
3773
+ const icon = s.status === "success" ? chalk10.green("\u2705") : s.status === "failed" ? chalk10.red("\u274C") : chalk10.yellow("\u{1F504}");
3774
+ console.log(
3775
+ ` ${icon} ${chalk10.bold(basename3(s.project))} \u2014 ${s.agent} \u2014 ${s.tasks} tasks \u2014 ${s.duration} \u2014 ${chalk10.dim(s.lastRun)}`
3776
+ );
3777
+ }
3778
+ console.log();
3779
+ }
3780
+ function createSemaphore(max) {
3781
+ let running = 0;
3782
+ const queue = [];
3783
+ async function acquire() {
3784
+ if (running < max) {
3785
+ running++;
3786
+ return;
3787
+ }
3788
+ return new Promise((resolve8) => {
3789
+ queue.push(() => {
3790
+ running++;
3791
+ resolve8();
3792
+ });
3793
+ });
3794
+ }
3795
+ function release() {
3796
+ running--;
3797
+ const next = queue.shift();
3798
+ if (next) next();
3799
+ }
3800
+ return { acquire, release };
3801
+ }
3802
+ async function runAll(mode, opts) {
3803
+ assertAgent(opts.agent);
3804
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").slice(0, 15);
3805
+ setLogFile(join13(LOG_DIR, `multi-${mode}-${ts}.log`));
3806
+ const projects = readProjects();
3807
+ if (projects.length === 0) {
3808
+ fail("No projects registered. Add: copilot-agent multi add <path>");
3809
+ process.exit(1);
3810
+ }
3811
+ log(`\u{1F3ED} Multi-project ${mode} \u2014 ${projects.length} projects \u2014 agent: ${opts.agent}${opts.parallel ? " (parallel)" : ""}`);
3812
+ if (opts.dryRun) {
3813
+ for (const p of projects) {
3814
+ const type = existsSync11(p) ? detectProjectType(p) : "unknown";
3815
+ const tasks = getTasksForProject(type);
3816
+ console.log(`
3817
+ ${chalk10.bold(basename3(p))} (${type})`);
3818
+ for (const t of tasks.slice(0, 3)) {
3819
+ console.log(` ${chalk10.dim("\u2022")} ${t.title}`);
3820
+ }
3821
+ }
3822
+ log(chalk10.dim("\n(dry-run \u2014 not executing)"));
3823
+ return;
3824
+ }
3825
+ const results = [];
3826
+ if (opts.parallel) {
3827
+ const sem = createSemaphore(MAX_CONCURRENCY);
3828
+ const promises = projects.map(async (project) => {
3829
+ await sem.acquire();
3830
+ try {
3831
+ const res = await runSingleProject(project, mode, opts);
3832
+ results.push(res);
3833
+ } finally {
3834
+ sem.release();
3835
+ }
3836
+ });
3837
+ await Promise.all(promises);
3838
+ } else {
3839
+ for (let i = 0; i < projects.length; i++) {
3840
+ const res = await runSingleProject(projects[i], mode, opts);
3841
+ results.push(res);
3842
+ if (i < projects.length - 1 && !res.skipped) {
3843
+ log(`Cooldown ${opts.cooldown}s\u2026`);
3844
+ await new Promise((r) => setTimeout(r, opts.cooldown * 1e3));
3845
+ }
3846
+ }
3847
+ }
3848
+ const success = results.filter((r) => r.success && !r.skipped).length;
3849
+ const failed = results.filter((r) => !r.success && !r.skipped).length;
3850
+ const skipped = results.filter((r) => r.skipped).length;
3851
+ const total = results.length;
3852
+ console.log(`
3853
+ ${"\u2550".repeat(50)}`);
3854
+ log(`\u{1F4CA} Summary: ${success}/${total} succeeded, ${failed} failed, ${skipped} skipped`);
3855
+ for (const r of results) {
3856
+ const icon = r.skipped ? "\u23ED " : r.success ? "\u2705" : "\u274C";
3857
+ console.log(` ${icon} ${r.name}`);
3858
+ }
3859
+ console.log();
3860
+ notify(`Multi-${mode}: ${success}/${total} succeeded`, "copilot-agent");
3861
+ }
3862
+ async function runSingleProject(project, mode, opts) {
3863
+ const name = existsSync11(project) ? detectProjectName(project) : basename3(project);
3864
+ if (!existsSync11(project)) {
3865
+ warn(`Skipping (not found): ${project}`);
3866
+ return { name, success: false, skipped: true };
3867
+ }
3868
+ const type = detectProjectType(project);
3869
+ log(`
3870
+ ${"\u2550".repeat(50)}`);
3871
+ log(`${chalk10.bold(name)} (${type}) \u2014 agent: ${opts.agent}`);
3872
+ log(`${"\u2550".repeat(50)}`);
3873
+ 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);
3874
+ const startTime = Date.now();
3875
+ let projectSuccess = true;
3876
+ upsertStatus({
3877
+ project,
3878
+ lastRun: (/* @__PURE__ */ new Date()).toISOString(),
3879
+ status: "running",
3880
+ agent: opts.agent,
3881
+ tasks: tasks.length,
3882
+ duration: "\u2014"
3883
+ });
3884
+ for (const task of tasks) {
3885
+ try {
3886
+ const result = await withLock(
3887
+ "agent-multi",
3888
+ () => runAgentTask(opts.agent, `Project: ${project}
3889
+
3890
+ ${task.prompt}`, opts.steps, project)
3891
+ );
3892
+ ok(`${task.title} \u2014 exit ${result.exitCode}, premium: ${result.premium}`);
3893
+ } catch (err) {
3894
+ fail(`${task.title} failed: ${err}`);
3895
+ projectSuccess = false;
3896
+ }
3897
+ }
3898
+ const duration = formatDuration2(Date.now() - startTime);
3899
+ upsertStatus({
3900
+ project,
3901
+ lastRun: (/* @__PURE__ */ new Date()).toISOString(),
3902
+ status: projectSuccess ? "success" : "failed",
3903
+ agent: opts.agent,
3904
+ tasks: tasks.length,
3905
+ duration
3906
+ });
3907
+ return { name, success: projectSuccess, skipped: false };
3908
+ }
3909
+
3910
+ // src/commands/review.ts
3911
+ import chalk11 from "chalk";
3912
+ import { execSync as execSync6 } from "child_process";
3913
+ var MAX_DIFF_LENGTH = 15e3;
3914
+ var MAX_BUFFER = 1024 * 1024;
3915
+ function getSessionDiff(sessionId) {
3916
+ const report = getAgentSessionReport(sessionId, "copilot") ?? getAgentSessionReport(sessionId, "claude");
3917
+ if (!report || report.gitCommits.length === 0) {
3918
+ return execSync6("git --no-pager diff HEAD~3", {
3919
+ encoding: "utf-8",
3920
+ maxBuffer: MAX_BUFFER
3921
+ });
3922
+ }
3923
+ return execSync6("git --no-pager diff HEAD~" + report.gitCommits.length, {
3924
+ encoding: "utf-8",
3925
+ cwd: report.cwd || process.cwd(),
3926
+ maxBuffer: MAX_BUFFER
3927
+ });
3928
+ }
3929
+ function getWorkingDiff() {
3930
+ const staged = execSync6("git --no-pager diff --cached", {
3931
+ encoding: "utf-8",
3932
+ maxBuffer: MAX_BUFFER
3933
+ });
3934
+ const unstaged = execSync6("git --no-pager diff", {
3935
+ encoding: "utf-8",
3936
+ maxBuffer: MAX_BUFFER
3937
+ });
3938
+ return staged + unstaged;
3939
+ }
3940
+ function getPrDiff(prNumber) {
3941
+ return execSync6(`gh pr diff ${prNumber} --color=never`, {
3942
+ encoding: "utf-8",
3943
+ maxBuffer: MAX_BUFFER
3944
+ });
3945
+ }
3946
+ var focusInstructions = {
3947
+ all: "Review for bugs, security issues, performance problems, and code quality.",
3948
+ security: "Focus exclusively on security vulnerabilities, injection risks, auth issues, and data exposure.",
3949
+ performance: "Focus exclusively on performance issues: N+1 queries, memory leaks, unnecessary allocations, slow algorithms.",
3950
+ bugs: "Focus exclusively on logic bugs, edge cases, null/undefined issues, race conditions.",
3951
+ style: "Focus exclusively on code style, naming, readability, and consistency."
3952
+ };
3953
+ function buildReviewPrompt(diff, focus) {
3954
+ const instruction = focusInstructions[focus] ?? focusInstructions.all;
3955
+ const truncatedDiff = diff.length > MAX_DIFF_LENGTH ? diff.slice(0, MAX_DIFF_LENGTH) + "\n... (truncated)" : diff;
3956
+ return `You are a senior code reviewer. ${instruction}
3957
+
3958
+ Review this diff and provide:
3959
+ 1. **Critical Issues** (bugs, security) \u2014 must fix
3960
+ 2. **Suggestions** \u2014 should fix for quality
3961
+ 3. **Positive Notes** \u2014 good patterns observed
3962
+ 4. **Summary** \u2014 one paragraph overall assessment
3963
+
3964
+ Be concise. Only flag real issues, not style nitpicks (unless focus is style).
3965
+
3966
+ \`\`\`diff
3967
+ ${truncatedDiff}
3968
+ \`\`\``;
3969
+ }
3970
+ async function runReview(label, diff, agent, focus, steps) {
3971
+ if (!diff.trim()) {
3972
+ fail("No changes found to review.");
3973
+ process.exit(1);
3974
+ }
3975
+ info(`Reviewing: ${chalk11.cyan(label)}`);
3976
+ log(`Focus: ${chalk11.yellow(focus)} | Agent: ${chalk11.yellow(agent)} | Steps: ${steps}`);
3977
+ const prompt = buildReviewPrompt(diff, focus);
3978
+ const result = await runAgentTask(agent, prompt, steps, process.cwd());
3979
+ if (result.exitCode === 0) {
3980
+ ok("Review complete.");
3981
+ } else {
3982
+ fail(`Agent exited with code ${result.exitCode}`);
3983
+ }
3984
+ }
3985
+ function registerReviewCommand(program2) {
3986
+ const cmd = program2.command("review [session-id]").description("AI-powered code review of agent changes or git diffs").option("-a, --agent <type>", "Agent to use: copilot or claude", "copilot").option(
3987
+ "-f, --focus <area>",
3988
+ "Focus area: all, security, performance, bugs, style",
3989
+ "all"
3990
+ ).option("-s, --steps <n>", "Max agent steps", "5").action(async (sessionId, opts) => {
3991
+ try {
3992
+ const agent = resolveAgent(opts.agent);
3993
+ const steps = parseInt(opts.steps, 10);
3994
+ let sid = sessionId;
3995
+ if (!sid) {
3996
+ const sessions = listAllSessions(1);
3997
+ if (sessions.length === 0) {
3998
+ fail('No sessions found. Use "review diff" to review working tree changes.');
3999
+ process.exit(1);
4000
+ }
4001
+ sid = sessions[0].id;
4002
+ log(`Using latest session: ${chalk11.dim(sid)}`);
4003
+ }
4004
+ const diff = getSessionDiff(sid);
4005
+ await runReview(`session ${sid.slice(0, 8)}\u2026`, diff, agent, opts.focus, steps);
4006
+ } catch (err) {
4007
+ fail(`Review error: ${err instanceof Error ? err.message : err}`);
4008
+ process.exit(1);
4009
+ }
4010
+ });
4011
+ cmd.command("diff").description("Review current working tree changes").option("-a, --agent <type>", "Agent to use: copilot or claude", "copilot").option(
4012
+ "-f, --focus <area>",
4013
+ "Focus area: all, security, performance, bugs, style",
4014
+ "all"
4015
+ ).option("-s, --steps <n>", "Max agent steps", "5").action(async (opts) => {
4016
+ try {
4017
+ const agent = resolveAgent(opts.agent);
4018
+ const steps = parseInt(opts.steps, 10);
4019
+ const diff = getWorkingDiff();
4020
+ await runReview("working tree changes", diff, agent, opts.focus, steps);
4021
+ } catch (err) {
4022
+ fail(`Review error: ${err instanceof Error ? err.message : err}`);
4023
+ process.exit(1);
4024
+ }
4025
+ });
4026
+ cmd.command("pr <number>").description("Review a GitHub Pull Request").option("-a, --agent <type>", "Agent to use: copilot or claude", "copilot").option(
4027
+ "-f, --focus <area>",
4028
+ "Focus area: all, security, performance, bugs, style",
4029
+ "all"
4030
+ ).option("-s, --steps <n>", "Max agent steps", "5").action(async (number, opts) => {
4031
+ try {
4032
+ const agent = resolveAgent(opts.agent);
4033
+ const steps = parseInt(opts.steps, 10);
4034
+ const diff = getPrDiff(number);
4035
+ await runReview(`PR #${number}`, diff, agent, opts.focus, steps);
4036
+ } catch (err) {
4037
+ fail(`Review error: ${err instanceof Error ? err.message : err}`);
4038
+ process.exit(1);
4039
+ }
4040
+ });
4041
+ }
4042
+
4043
+ // src/commands/schedule.ts
4044
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync8, existsSync as existsSync12, mkdirSync as mkdirSync9 } from "fs";
4045
+ import { join as join14 } from "path";
4046
+ import { homedir as homedir12 } from "os";
4047
+ import { parse as parseYaml5, stringify as stringifyYaml4 } from "yaml";
4048
+ var CONFIG_DIR4 = join14(homedir12(), ".copilot-agent");
4049
+ var SCHEDULES_FILE = join14(CONFIG_DIR4, "schedules.yaml");
4050
+ function ensureConfigDir2() {
4051
+ if (!existsSync12(CONFIG_DIR4)) mkdirSync9(CONFIG_DIR4, { recursive: true });
4052
+ }
4053
+ function loadSchedules() {
4054
+ if (!existsSync12(SCHEDULES_FILE)) return [];
4055
+ try {
4056
+ const raw = parseYaml5(readFileSync9(SCHEDULES_FILE, "utf-8"));
4057
+ return Array.isArray(raw) ? raw : [];
4058
+ } catch {
4059
+ return [];
4060
+ }
4061
+ }
4062
+ function saveSchedules(schedules) {
4063
+ ensureConfigDir2();
4064
+ writeFileSync8(SCHEDULES_FILE, stringifyYaml4(schedules), "utf-8");
4065
+ }
4066
+ function matchesCron(cron, date) {
4067
+ const parts = cron.trim().split(/\s+/);
4068
+ if (parts.length !== 5) return false;
4069
+ const [minPart, hourPart, , , dowPart] = parts;
4070
+ const minute = date.getMinutes();
4071
+ const hour = date.getHours();
4072
+ const dow = date.getDay();
4073
+ if (!matchField(minPart, minute)) return false;
4074
+ if (!matchField(hourPart, hour)) return false;
4075
+ if (!matchField(dowPart, dow)) return false;
4076
+ return true;
4077
+ }
4078
+ function matchField(pattern, value) {
4079
+ if (pattern === "*") return true;
4080
+ const stepMatch = pattern.match(/^\*\/(\d+)$/);
4081
+ if (stepMatch) {
4082
+ const step = parseInt(stepMatch[1], 10);
4083
+ return step > 0 && value % step === 0;
4084
+ }
4085
+ const num = parseInt(pattern, 10);
4086
+ if (!isNaN(num)) return value === num;
4087
+ return false;
4088
+ }
4089
+ function nextRunTime(cron) {
4090
+ const now = /* @__PURE__ */ new Date();
4091
+ const limit = 7 * 24 * 60;
4092
+ const candidate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), now.getMinutes());
4093
+ for (let i = 1; i <= limit; i++) {
4094
+ candidate.setMinutes(candidate.getMinutes() + 1);
4095
+ if (matchesCron(cron, candidate)) return new Date(candidate);
4096
+ }
4097
+ return null;
4098
+ }
4099
+ function formatTime2(date) {
4100
+ return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12: false });
4101
+ }
4102
+ function formatDateTime(date) {
4103
+ const day = date.toLocaleDateString([], { weekday: "short", month: "short", day: "numeric" });
4104
+ return `${day} ${formatTime2(date)}`;
4105
+ }
4106
+ function timestamp() {
4107
+ return `${DIM}[${formatTime2(/* @__PURE__ */ new Date())}]${RESET}`;
4108
+ }
4109
+ function listCommand() {
4110
+ const schedules = loadSchedules();
4111
+ if (schedules.length === 0) {
4112
+ info("No schedules configured.");
4113
+ info(`Add one with: ${CYAN}copilot-agent schedule add <name> --cron "..." --prompt "..."${RESET}`);
4114
+ return;
4115
+ }
4116
+ log(`
4117
+ ${BOLD}Schedules${RESET} ${DIM}(${SCHEDULES_FILE})${RESET}
4118
+ `);
4119
+ for (const s of schedules) {
4120
+ const status = s.enabled ? `${GREEN}\u25CF${RESET}` : `${DIM}\u25CB${RESET}`;
4121
+ const next = s.enabled ? nextRunTime(s.cron) : null;
4122
+ const nextStr = next ? `${DIM}next: ${formatDateTime(next)}${RESET}` : "";
4123
+ log(` ${status} ${BOLD}${s.name}${RESET} ${DIM}${s.cron}${RESET} ${nextStr}`);
4124
+ log(` ${DIM}prompt:${RESET} ${s.prompt.length > 60 ? s.prompt.slice(0, 57) + "..." : s.prompt}`);
4125
+ log(` ${DIM}project:${RESET} ${s.project} ${DIM}agent:${RESET} ${s.agent} ${DIM}steps:${RESET} ${s.maxSteps ?? 30}`);
4126
+ log("");
4127
+ }
4128
+ }
4129
+ function addCommand(name, opts) {
4130
+ const schedules = loadSchedules();
4131
+ if (schedules.some((s) => s.name === name)) {
4132
+ fail(`Schedule "${name}" already exists. Remove it first or use a different name.`);
4133
+ process.exit(1);
4134
+ }
4135
+ if (!opts.cron || opts.cron.trim().split(/\s+/).length !== 5) {
4136
+ fail("Invalid cron expression. Expected 5 fields: minute hour day month weekday");
4137
+ process.exit(1);
4138
+ }
4139
+ const schedule = {
4140
+ name,
4141
+ cron: opts.cron,
4142
+ prompt: opts.prompt,
4143
+ project: opts.project || process.cwd(),
4144
+ agent: resolveAgent(opts.agent),
4145
+ enabled: !opts.disabled,
4146
+ maxSteps: parseInt(opts.steps, 10) || 30
4147
+ };
4148
+ schedules.push(schedule);
4149
+ saveSchedules(schedules);
4150
+ ok(`Added schedule "${name}"`);
4151
+ const next = nextRunTime(schedule.cron);
4152
+ if (next) {
4153
+ info(`Next run: ${formatDateTime(next)}`);
4154
+ }
4155
+ }
4156
+ function removeCommand(name) {
4157
+ const schedules = loadSchedules();
4158
+ const idx = schedules.findIndex((s) => s.name === name);
4159
+ if (idx === -1) {
4160
+ fail(`Schedule "${name}" not found.`);
4161
+ process.exit(1);
4162
+ }
4163
+ schedules.splice(idx, 1);
4164
+ saveSchedules(schedules);
4165
+ ok(`Removed schedule "${name}"`);
4166
+ }
4167
+ function dryRunCommand() {
4168
+ const schedules = loadSchedules().filter((s) => s.enabled);
4169
+ if (schedules.length === 0) {
4170
+ info("No enabled schedules.");
4171
+ return;
4172
+ }
4173
+ log(`
4174
+ ${BOLD}Dry run \u2014 what would run next${RESET}
4175
+ `);
4176
+ for (const s of schedules) {
4177
+ const next = nextRunTime(s.cron);
4178
+ if (next) {
4179
+ log(` ${CYAN}${s.name}${RESET} \u2192 ${formatDateTime(next)}`);
4180
+ log(` ${DIM}${s.agent}${RESET} in ${s.project}`);
4181
+ log(` ${DIM}prompt:${RESET} ${s.prompt.length > 70 ? s.prompt.slice(0, 67) + "..." : s.prompt}`);
4182
+ log("");
4183
+ }
4184
+ }
4185
+ }
4186
+ async function runDaemon() {
4187
+ const schedules = loadSchedules();
4188
+ const enabled = schedules.filter((s) => s.enabled);
4189
+ if (enabled.length === 0) {
4190
+ fail("No enabled schedules. Add one first.");
4191
+ process.exit(1);
4192
+ }
4193
+ for (const s of enabled) {
4194
+ assertAgent(s.agent);
4195
+ }
4196
+ log(`
4197
+ ${BOLD}${CYAN}Scheduler daemon started${RESET}`);
4198
+ info(`${enabled.length} schedule(s) active. Press Ctrl+C to stop.
4199
+ `);
4200
+ const lastRun = /* @__PURE__ */ new Map();
4201
+ const showStatus2 = () => {
4202
+ const now = /* @__PURE__ */ new Date();
4203
+ let soonestName = "";
4204
+ let soonestTime = null;
4205
+ for (const s of enabled) {
4206
+ const next = nextRunTime(s.cron);
4207
+ if (next && (!soonestTime || next < soonestTime)) {
4208
+ soonestTime = next;
4209
+ soonestName = s.name;
4210
+ }
4211
+ }
4212
+ const nextStr = soonestTime ? `next: ${CYAN}${soonestName}${RESET} at ${formatTime2(soonestTime)}` : "no upcoming runs";
4213
+ log(`${timestamp()} ${DIM}Scheduler running \u2014 ${enabled.length} schedules, ${nextStr}${RESET}`);
4214
+ };
4215
+ showStatus2();
4216
+ const check = async () => {
4217
+ const now = /* @__PURE__ */ new Date();
4218
+ const minuteKey = `${now.getFullYear()}-${now.getMonth()}-${now.getDate()}-${now.getHours()}-${now.getMinutes()}`;
4219
+ for (const s of enabled) {
4220
+ if (!matchesCron(s.cron, now)) continue;
4221
+ const runKey = `${s.name}:${minuteKey}`;
4222
+ if (lastRun.has(runKey)) continue;
4223
+ lastRun.set(runKey, Date.now());
4224
+ const cutoff = Date.now() - 2 * 60 * 1e3;
4225
+ for (const [k, v] of lastRun) {
4226
+ if (v < cutoff) lastRun.delete(k);
4227
+ }
4228
+ log(`
4229
+ ${timestamp()} ${BOLD}${YELLOW}\u25B6 Running: ${s.name}${RESET}`);
4230
+ log(` ${DIM}cron:${RESET} ${s.cron} ${DIM}agent:${RESET} ${s.agent} ${DIM}project:${RESET} ${s.project}`);
4231
+ log(` ${DIM}prompt:${RESET} ${s.prompt}`);
4232
+ try {
4233
+ const result = await runAgentTask(
4234
+ s.agent,
4235
+ s.prompt,
4236
+ s.maxSteps ?? 30,
4237
+ s.project
4238
+ );
4239
+ if (result.exitCode === 0) {
4240
+ ok(`${timestamp()} \u2713 ${s.name} completed (session: ${result.sessionId?.slice(0, 8) ?? "n/a"})`);
4241
+ } else {
4242
+ warn(`${timestamp()} ${s.name} exited with code ${result.exitCode}`);
4243
+ }
4244
+ } catch (err) {
4245
+ fail(`${timestamp()} ${s.name} failed: ${err instanceof Error ? err.message : err}`);
4246
+ }
4247
+ showStatus2();
4248
+ }
4249
+ };
4250
+ await check();
4251
+ const interval = setInterval(() => {
4252
+ check().catch((err) => {
4253
+ fail(`Scheduler error: ${err instanceof Error ? err.message : err}`);
4254
+ });
4255
+ }, 6e4);
4256
+ const shutdown = () => {
4257
+ log(`
4258
+ ${timestamp()} ${DIM}Scheduler stopped.${RESET}`);
4259
+ clearInterval(interval);
4260
+ process.exit(0);
4261
+ };
4262
+ process.on("SIGINT", shutdown);
4263
+ process.on("SIGTERM", shutdown);
4264
+ await new Promise(() => {
4265
+ });
4266
+ }
4267
+ function registerScheduleCommand(program2) {
4268
+ const cmd = program2.command("schedule").description("Cron-like recurring task scheduler");
4269
+ cmd.command("list").description("Show all configured schedules").action(() => {
4270
+ listCommand();
4271
+ });
4272
+ cmd.command("add <name>").description("Add a recurring schedule").requiredOption("--cron <expression>", 'Cron expression (e.g. "0 */6 * * *")').requiredOption("--prompt <text>", "Agent prompt to execute").option("--project <path>", "Project directory", process.cwd()).option("-a, --agent <type>", "Agent: copilot or claude").option("-s, --steps <n>", "Max autopilot steps", "30").option("--disabled", "Add as disabled").action((name, opts) => {
4273
+ addCommand(name, opts);
4274
+ });
4275
+ cmd.command("remove <name>").description("Remove a schedule").action((name) => {
4276
+ removeCommand(name);
4277
+ });
4278
+ cmd.command("run").description("Start the scheduler daemon (foreground)").action(async () => {
4279
+ try {
4280
+ await runDaemon();
4281
+ } catch (err) {
4282
+ fail(`Scheduler error: ${err instanceof Error ? err.message : err}`);
4283
+ process.exit(1);
4284
+ }
4285
+ });
4286
+ cmd.command("dry-run").description("Show what would run next for each schedule").action(() => {
4287
+ dryRunCommand();
4288
+ });
4289
+ }
4290
+
3614
4291
  // src/index.ts
3615
4292
  var program = new Command();
3616
- program.name("copilot-agent").version("0.12.0").description("Autonomous AI agent manager \u2014 auto-resume, task discovery, overnight runs. Supports GitHub Copilot CLI + Claude Code.");
4293
+ program.name("copilot-agent").version("1.0.0").description("Autonomous AI agent manager \u2014 auto-resume, task discovery, overnight runs. Supports GitHub Copilot CLI + Claude Code.");
3617
4294
  registerStatusCommand(program);
3618
4295
  registerWatchCommand(program);
3619
4296
  registerRunCommand(program);
@@ -3631,5 +4308,8 @@ registerHooksCommand(program);
3631
4308
  registerPrCommand(program);
3632
4309
  registerLogCommand(program);
3633
4310
  registerTemplateCommand(program);
4311
+ registerMultiCommand(program);
4312
+ registerReviewCommand(program);
4313
+ registerScheduleCommand(program);
3634
4314
  program.parse();
3635
4315
  //# sourceMappingURL=index.js.map