@dunnewold-labs/mr-manager 0.3.0 → 0.4.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.
Files changed (2) hide show
  1. package/dist/index.mjs +932 -1242
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // cli/index.ts
4
- import { Command as Command29 } from "commander";
5
- import { existsSync as existsSync15 } from "fs";
6
- import { homedir as homedir4 } from "os";
4
+ import { Command as Command27 } from "commander";
5
+ import { existsSync as existsSync16 } from "fs";
6
+ import { homedir as homedir3 } from "os";
7
7
  import { join as join13 } from "path";
8
8
 
9
9
  // cli/commands/init.ts
@@ -177,19 +177,70 @@ var loginCommand = new Command3("login").description("Authenticate the CLI via b
177
177
  // cli/commands/projects.ts
178
178
  import { Command as Command4 } from "commander";
179
179
 
180
- // cli/api.ts
181
- import { readFileSync as readFileSync2 } from "fs";
180
+ // cli/package.ts
181
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
182
+ import { dirname, join as join2, resolve } from "path";
182
183
  import { fileURLToPath } from "url";
183
- import { dirname, join as join2 } from "path";
184
- var cliVersion = "unknown";
185
- try {
186
- const __filename = fileURLToPath(import.meta.url);
187
- const __dirname = dirname(__filename);
188
- const pkg = JSON.parse(readFileSync2(join2(__dirname, "..", "package.json"), "utf-8"));
189
- cliVersion = pkg.version;
190
- } catch {
191
- cliVersion = "0.2.0";
184
+
185
+ // cli/package.json
186
+ var package_default = {
187
+ name: "@dunnewold-labs/mr-manager",
188
+ version: "0.4.2",
189
+ description: "Mr. Manager - Task and project management CLI",
190
+ bin: {
191
+ mr: "./dist/index.mjs"
192
+ },
193
+ files: [
194
+ "dist"
195
+ ],
196
+ type: "module",
197
+ engines: {
198
+ node: ">=20"
199
+ },
200
+ publishConfig: {
201
+ access: "public"
202
+ },
203
+ scripts: {
204
+ postinstall: `echo '\\n Run "mr login" to authenticate with Mr. Manager.\\n'`
205
+ },
206
+ dependencies: {
207
+ commander: "^13.1.0"
208
+ }
209
+ };
210
+
211
+ // cli/package.ts
212
+ var FALLBACK_CLI_VERSION = "0.2.0";
213
+ var CLI_PACKAGE_NAME = "@dunnewold-labs/mr-manager";
214
+ var __filename = fileURLToPath(import.meta.url);
215
+ var __dirname = dirname(__filename);
216
+ function isCliPackageDir(dir) {
217
+ const pkgPath = join2(dir, "package.json");
218
+ if (!existsSync2(pkgPath)) {
219
+ return false;
220
+ }
221
+ try {
222
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
223
+ return pkg.name === CLI_PACKAGE_NAME;
224
+ } catch {
225
+ return false;
226
+ }
227
+ }
228
+ function resolveCliDir() {
229
+ const candidates = [resolve(__dirname), resolve(__dirname, "..")];
230
+ for (const candidate of candidates) {
231
+ if (isCliPackageDir(candidate)) {
232
+ return candidate;
233
+ }
234
+ }
235
+ return resolve(__dirname, "..");
192
236
  }
237
+ var CLI_DIR = resolveCliDir();
238
+ var REPO_ROOT = resolve(CLI_DIR, "..");
239
+ var CLI_PACKAGE_PATH = join2(CLI_DIR, "package.json");
240
+ var CLI_VERSION = package_default.version ?? FALLBACK_CLI_VERSION;
241
+
242
+ // cli/api.ts
243
+ var cliVersion = CLI_VERSION;
193
244
  var ApiError = class extends Error {
194
245
  constructor(status, statusText, body) {
195
246
  super(`API error ${status} ${statusText}: ${body}`);
@@ -365,7 +416,7 @@ var unlinkCommand = new Command6("unlink").description("Remove current directory
365
416
 
366
417
  // cli/commands/context.ts
367
418
  import { Command as Command7 } from "commander";
368
- import { writeFileSync as writeFileSync2, readFileSync as readFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
419
+ import { writeFileSync as writeFileSync2, readFileSync as readFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
369
420
  import { join as join3, sep } from "path";
370
421
  var FEATURES_FILE = ".mr-features.md";
371
422
  function resolveProjectRoot() {
@@ -379,13 +430,13 @@ function resolveProjectRoot() {
379
430
  }
380
431
  function readFeatures() {
381
432
  const path = join3(resolveProjectRoot(), FEATURES_FILE);
382
- if (!existsSync2(path)) return null;
433
+ if (!existsSync3(path)) return null;
383
434
  return readFileSync3(path, "utf-8");
384
435
  }
385
436
  var contextCommand = new Command7("context").description("Output project context JSON for Claude Code").option("--install", "Install Claude Code command to .claude/commands/").action(async (opts) => {
386
437
  if (opts.install) {
387
438
  const dir = join3(process.cwd(), ".claude", "commands");
388
- if (!existsSync2(dir)) {
439
+ if (!existsSync3(dir)) {
389
440
  mkdirSync2(dir, { recursive: true });
390
441
  }
391
442
  const filePath = join3(dir, "mr-tasks.md");
@@ -408,11 +459,13 @@ var contextCommand = new Command7("context").description("Output project context
408
459
  `/api/projects/${projectId}/tasks`
409
460
  );
410
461
  const inProgress = allTasks.filter((t) => t.status === "in_progress" || t.status === "queued" || t.status === "delegated" || t.status === "review");
462
+ const needsAttention = allTasks.filter((t) => t.status === "error");
411
463
  const todo = allTasks.filter((t) => t.status === "todo");
412
464
  const recentlyCompleted = allTasks.filter((t) => t.status === "completed").slice(0, 10);
413
465
  const summaryLines = [
414
466
  `Project: ${project.name} (${project.status})`,
415
467
  ...inProgress.map((t) => `In Progress: ${t.title}`),
468
+ ...needsAttention.map((t) => `Needs Attention: ${t.title}`),
416
469
  `Todo: ${todo.length} tasks`,
417
470
  `Recently Completed: ${recentlyCompleted.length} tasks`
418
471
  ];
@@ -438,23 +491,22 @@ var contextCommand = new Command7("context").description("Output project context
438
491
  import { Command as Command8 } from "commander";
439
492
  import { spawn as spawn4, exec } from "child_process";
440
493
  import { randomUUID } from "crypto";
441
- import { resolve, join as join7 } from "path";
442
- import { readFileSync as readFileSync5, readdirSync, unlinkSync, existsSync as existsSync6, statSync } from "fs";
443
- import { homedir as homedir2 } from "os";
494
+ import { resolve as resolve2 } from "path";
495
+ import { readFileSync as readFileSync5, readdirSync, unlinkSync, existsSync as existsSync7, statSync } from "fs";
444
496
  import * as readline from "readline";
445
497
 
446
498
  // lib/test-runner.ts
447
499
  import { spawn as spawn2 } from "child_process";
448
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
500
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
449
501
  import { join as join5 } from "path";
450
502
 
451
503
  // lib/git-worktree.ts
452
504
  import { execSync as execSync2 } from "child_process";
453
- import { copyFileSync, existsSync as existsSync3 } from "fs";
505
+ import { copyFileSync, existsSync as existsSync4 } from "fs";
454
506
  import { join as join4 } from "path";
455
507
  function createWorktree(repoDir, branch, worktreeName) {
456
508
  const wtPath = join4(repoDir, ".mr-worktrees", worktreeName);
457
- if (existsSync3(wtPath)) {
509
+ if (existsSync4(wtPath)) {
458
510
  execSync2(`git checkout ${branch}`, { cwd: wtPath, stdio: "pipe" });
459
511
  return wtPath;
460
512
  }
@@ -482,7 +534,7 @@ function createWorktree(repoDir, branch, worktreeName) {
482
534
  }
483
535
  for (const envFile of [".env", ".env.local"]) {
484
536
  const src = join4(repoDir, envFile);
485
- if (existsSync3(src)) {
537
+ if (existsSync4(src)) {
486
538
  copyFileSync(src, join4(wtPath, envFile));
487
539
  }
488
540
  }
@@ -525,13 +577,13 @@ function extractBranchFromLink(link, cwd) {
525
577
  return null;
526
578
  }
527
579
  function installDependencies(wtPath) {
528
- if (existsSync3(join4(wtPath, "bun.lock")) || existsSync3(join4(wtPath, "bun.lockb"))) {
580
+ if (existsSync4(join4(wtPath, "bun.lock")) || existsSync4(join4(wtPath, "bun.lockb"))) {
529
581
  execSync2("bun install", { cwd: wtPath, stdio: "pipe" });
530
- } else if (existsSync3(join4(wtPath, "pnpm-lock.yaml"))) {
582
+ } else if (existsSync4(join4(wtPath, "pnpm-lock.yaml"))) {
531
583
  execSync2("pnpm install --frozen-lockfile", { cwd: wtPath, stdio: "pipe" });
532
- } else if (existsSync3(join4(wtPath, "package-lock.json"))) {
584
+ } else if (existsSync4(join4(wtPath, "package-lock.json"))) {
533
585
  execSync2("npm ci", { cwd: wtPath, stdio: "pipe" });
534
- } else if (existsSync3(join4(wtPath, "yarn.lock"))) {
586
+ } else if (existsSync4(join4(wtPath, "yarn.lock"))) {
535
587
  execSync2("yarn install --frozen-lockfile", { cwd: wtPath, stdio: "pipe" });
536
588
  }
537
589
  }
@@ -539,7 +591,7 @@ function installDependencies(wtPath) {
539
591
  // lib/test-runner.ts
540
592
  function detectDevCommand(wtPath) {
541
593
  const configPath2 = join5(wtPath, ".mr-test.json");
542
- if (existsSync4(configPath2)) {
594
+ if (existsSync5(configPath2)) {
543
595
  try {
544
596
  const config = JSON.parse(readFileSync4(configPath2, "utf-8"));
545
597
  if (config.devCommand) {
@@ -549,16 +601,16 @@ function detectDevCommand(wtPath) {
549
601
  } catch {
550
602
  }
551
603
  }
552
- if (existsSync4(join5(wtPath, "bun.lock")) || existsSync4(join5(wtPath, "bun.lockb"))) {
604
+ if (existsSync5(join5(wtPath, "bun.lock")) || existsSync5(join5(wtPath, "bun.lockb"))) {
553
605
  return { cmd: "bun", args: ["dev"] };
554
606
  }
555
- if (existsSync4(join5(wtPath, "pnpm-lock.yaml"))) {
607
+ if (existsSync5(join5(wtPath, "pnpm-lock.yaml"))) {
556
608
  return { cmd: "pnpm", args: ["dev"] };
557
609
  }
558
- if (existsSync4(join5(wtPath, "package-lock.json"))) {
610
+ if (existsSync5(join5(wtPath, "package-lock.json"))) {
559
611
  return { cmd: "npm", args: ["run", "dev"] };
560
612
  }
561
- if (existsSync4(join5(wtPath, "yarn.lock"))) {
613
+ if (existsSync5(join5(wtPath, "yarn.lock"))) {
562
614
  return { cmd: "yarn", args: ["dev"] };
563
615
  }
564
616
  return { cmd: "npm", args: ["run", "dev"] };
@@ -696,7 +748,7 @@ async function runTest(options) {
696
748
  postUpdate,
697
749
  onProgress
698
750
  } = options;
699
- const log4 = onProgress || (() => {
751
+ const log2 = onProgress || (() => {
700
752
  });
701
753
  const result = {
702
754
  status: "passed",
@@ -709,37 +761,37 @@ async function runTest(options) {
709
761
  let wtPath = null;
710
762
  const worktreeName = `mr-test-${taskId.slice(0, 8)}`;
711
763
  const timeoutHandle = setTimeout(() => {
712
- log4("Test timed out after 5 minutes");
764
+ log2("Test timed out after 5 minutes");
713
765
  if (devProc) devProc.kill("SIGTERM");
714
766
  }, 5 * 60 * 1e3);
715
767
  try {
716
- log4("Extracting branch from MR/PR link...");
768
+ log2("Extracting branch from MR/PR link...");
717
769
  const branch = extractBranchFromLink(taskLink, localPath);
718
770
  if (!branch) {
719
771
  throw new Error(`Could not extract branch from link: ${taskLink}`);
720
772
  }
721
- log4(`Branch: ${branch}`);
722
- log4("Creating git worktree...");
773
+ log2(`Branch: ${branch}`);
774
+ log2("Creating git worktree...");
723
775
  wtPath = createWorktree(localPath, branch, worktreeName);
724
- log4(`Worktree created at ${wtPath}`);
725
- log4("Installing dependencies...");
776
+ log2(`Worktree created at ${wtPath}`);
777
+ log2("Installing dependencies...");
726
778
  try {
727
779
  installDependencies(wtPath);
728
780
  } catch (err) {
729
- log4(`Warning: dependency install failed: ${err.message}`);
781
+ log2(`Warning: dependency install failed: ${err.message}`);
730
782
  }
731
- log4("Starting dev server...");
783
+ log2("Starting dev server...");
732
784
  const port = await findAvailablePort(4e3);
733
785
  devProc = await startDevServer(wtPath, port);
734
786
  const baseUrl = `http://127.0.0.1:${port}`;
735
- log4(`Dev server running on ${baseUrl}`);
787
+ log2(`Dev server running on ${baseUrl}`);
736
788
  await browseRunner(["goto", baseUrl]);
737
789
  const plan = customPlan || buildDefaultTestPlan(baseUrl);
738
- log4(`Executing ${plan.length}-step test plan...`);
790
+ log2(`Executing ${plan.length}-step test plan...`);
739
791
  for (let i = 0; i < plan.length; i++) {
740
792
  const step = plan[i];
741
793
  const stepDesc = step.description || `${step.command} ${(step.args || []).join(" ")}`;
742
- log4(`Step ${i + 1}/${plan.length}: ${stepDesc}`);
794
+ log2(`Step ${i + 1}/${plan.length}: ${stepDesc}`);
743
795
  try {
744
796
  if (step.command.startsWith("assert")) {
745
797
  const assertResult = await evaluateAssertion(step, i, browseRunner);
@@ -789,7 +841,7 @@ async function runTest(options) {
789
841
  }
790
842
  } catch (err) {
791
843
  result.errors.push(`Step ${i + 1} (${step.command}): ${err.message}`);
792
- log4(`Step ${i + 1} error: ${err.message}`);
844
+ log2(`Step ${i + 1} error: ${err.message}`);
793
845
  }
794
846
  }
795
847
  const totalAssertions = result.assertions.length;
@@ -835,18 +887,18 @@ async function runTest(options) {
835
887
 
836
888
  // cli/browse-runner.ts
837
889
  import { execSync as execSync4, spawn as spawn3 } from "child_process";
838
- import { existsSync as existsSync5 } from "fs";
890
+ import { existsSync as existsSync6 } from "fs";
839
891
  import { join as join6 } from "path";
840
892
  var BROWSE_DIR = join6(import.meta.dirname, "..", "..", "browse");
841
893
  var BROWSE_BINARY = join6(BROWSE_DIR, "dist", "browse");
842
894
  var BROWSE_DEV_CMD = join6(BROWSE_DIR, "src", "cli.ts");
843
895
  function getBrowseRunner() {
844
- if (existsSync5(BROWSE_BINARY)) {
896
+ if (existsSync6(BROWSE_BINARY)) {
845
897
  return { cmd: BROWSE_BINARY, args: [] };
846
898
  }
847
899
  try {
848
900
  execSync4("which bun", { stdio: "pipe" });
849
- if (existsSync5(BROWSE_DEV_CMD)) {
901
+ if (existsSync6(BROWSE_DEV_CMD)) {
850
902
  return { cmd: "bun", args: ["run", BROWSE_DEV_CMD] };
851
903
  }
852
904
  } catch {
@@ -886,95 +938,11 @@ async function runBrowseCommand2(browseArgs) {
886
938
 
887
939
  // cli/commands/watch.ts
888
940
  var FEATURES_FILE2 = ".mr-features.md";
889
- var CLAUDE_PROJECTS_DIR = join7(homedir2(), ".claude", "projects");
890
941
  function readFeaturesDoc(repoDir) {
891
- const path = resolve(repoDir, FEATURES_FILE2);
892
- if (!existsSync6(path)) return null;
942
+ const path = resolve2(repoDir, FEATURES_FILE2);
943
+ if (!existsSync7(path)) return null;
893
944
  return readFileSync5(path, "utf-8");
894
945
  }
895
- function toClaudePath(dir) {
896
- return dir.replace(/\//g, "-");
897
- }
898
- function findSessionFile(sessionId, repoDir) {
899
- const mainPath = join7(CLAUDE_PROJECTS_DIR, toClaudePath(repoDir));
900
- const mainFile = join7(mainPath, `${sessionId}.jsonl`);
901
- if (existsSync6(mainFile)) return mainFile;
902
- const worktreePrefix = toClaudePath(repoDir) + "-.mr-worktrees-";
903
- try {
904
- const entries = readdirSync(CLAUDE_PROJECTS_DIR);
905
- for (const entry of entries) {
906
- if (entry.startsWith(worktreePrefix)) {
907
- const worktreeFile = join7(CLAUDE_PROJECTS_DIR, entry, `${sessionId}.jsonl`);
908
- if (existsSync6(worktreeFile)) return worktreeFile;
909
- }
910
- }
911
- } catch {
912
- }
913
- return null;
914
- }
915
- function parseSessionJsonl(raw) {
916
- const lines = raw.split("\n").filter((l) => l.trim());
917
- const entries = [];
918
- for (const line of lines) {
919
- try {
920
- const parsed = JSON.parse(line);
921
- if (parsed.type !== "user" && parsed.type !== "assistant") continue;
922
- if (!parsed.message) continue;
923
- const content = [];
924
- const msgContent = parsed.message.content;
925
- if (typeof msgContent === "string") {
926
- content.push({ type: "text", text: msgContent });
927
- } else if (Array.isArray(msgContent)) {
928
- for (const block of msgContent) {
929
- if (block.type === "thinking" && block.thinking) {
930
- content.push({ type: "thinking", thinking: block.thinking });
931
- } else if (block.type === "text" && block.text) {
932
- content.push({ type: "text", text: block.text });
933
- } else if (block.type === "tool_use") {
934
- content.push({
935
- type: "tool_use",
936
- id: block.id,
937
- name: block.name,
938
- input: block.input ?? {}
939
- });
940
- } else if (block.type === "tool_result") {
941
- let resultContent = block.content;
942
- if (typeof resultContent === "string") {
943
- } else if (Array.isArray(resultContent)) {
944
- resultContent = resultContent.filter((r) => r.type === "text").map((r) => r.text).join("\n");
945
- } else {
946
- resultContent = "";
947
- }
948
- content.push({
949
- type: "tool_result",
950
- tool_use_id: block.tool_use_id,
951
- content: resultContent
952
- });
953
- }
954
- }
955
- }
956
- if (content.length === 0) continue;
957
- entries.push({
958
- type: parsed.type,
959
- timestamp: parsed.timestamp,
960
- uuid: parsed.uuid,
961
- content
962
- });
963
- } catch {
964
- }
965
- }
966
- return entries;
967
- }
968
- function readAndParseSessionLog(sessionId, repoDir) {
969
- const filePath = findSessionFile(sessionId, repoDir);
970
- if (!filePath) return null;
971
- try {
972
- const raw = readFileSync5(filePath, "utf-8");
973
- return parseSessionJsonl(raw);
974
- } catch {
975
- return null;
976
- }
977
- }
978
946
  var c = {
979
947
  reset: "\x1B[0m",
980
948
  bold: "\x1B[1m",
@@ -1028,9 +996,20 @@ async function postTaskUpdate(taskId, message, source = "system") {
1028
996
  logError(taskTag(shortId(taskId)), `Failed to post update: ${err.message}`);
1029
997
  }
1030
998
  }
999
+ var DEFAULT_TASK_STALL_TIMEOUT_MS = 45 * 60 * 1e3;
1000
+ function getTaskStallTimeoutMs() {
1001
+ const raw = process.env.MR_TASK_STALL_TIMEOUT_MS;
1002
+ if (!raw) return DEFAULT_TASK_STALL_TIMEOUT_MS;
1003
+ const parsed = Number(raw);
1004
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_TASK_STALL_TIMEOUT_MS;
1005
+ }
1006
+ function formatTimeoutMinutes(ms) {
1007
+ const minutes = Math.round(ms / 6e4);
1008
+ return `${minutes} minute${minutes === 1 ? "" : "s"}`;
1009
+ }
1031
1010
  function findDirectoryForProject(config, projectId, rootDir) {
1032
1011
  for (const [dir, pid] of Object.entries(config.directories)) {
1033
- if (pid === projectId && resolve(dir).startsWith(resolve(rootDir))) {
1012
+ if (pid === projectId && resolve2(dir).startsWith(resolve2(rootDir))) {
1034
1013
  return dir;
1035
1014
  }
1036
1015
  }
@@ -1102,14 +1081,14 @@ function findPrUrl(branchName, repoDir, vcs = "github") {
1102
1081
  }
1103
1082
  function isGitRepo(dir) {
1104
1083
  try {
1105
- return existsSync6(resolve(dir, ".git"));
1084
+ return existsSync7(resolve2(dir, ".git"));
1106
1085
  } catch {
1107
1086
  return false;
1108
1087
  }
1109
1088
  }
1110
1089
  function findChildGitRepos(parentDir) {
1111
1090
  try {
1112
- return readdirSync(parentDir).filter((name) => !name.startsWith(".")).map((name) => resolve(parentDir, name)).filter((path) => {
1091
+ return readdirSync(parentDir).filter((name) => !name.startsWith(".")).map((name) => resolve2(parentDir, name)).filter((path) => {
1113
1092
  try {
1114
1093
  return statSync(path).isDirectory() && isGitRepo(path);
1115
1094
  } catch {
@@ -1808,7 +1787,7 @@ function buildAgentArgs(agent, prompt2, mode, sessionId, name) {
1808
1787
  if (agent === "codex") {
1809
1788
  const args = ["exec"];
1810
1789
  if (mode === "execute") {
1811
- args.push("--full-auto");
1790
+ args.push("-a", "never", "-s", "danger-full-access");
1812
1791
  }
1813
1792
  args.push(prompt2);
1814
1793
  return { bin: "codex", args };
@@ -1847,7 +1826,7 @@ function runPlanningPhase(task, repoDir, agent) {
1847
1826
  ${output.trim()}`));
1848
1827
  return;
1849
1828
  }
1850
- const planPath = resolve(repoDir, "plan.md");
1829
+ const planPath = resolve2(repoDir, "plan.md");
1851
1830
  try {
1852
1831
  const planContent = readFileSync5(planPath, "utf-8");
1853
1832
  try {
@@ -1873,7 +1852,7 @@ function askYesNo(question) {
1873
1852
  });
1874
1853
  });
1875
1854
  }
1876
- function spawnAgent(agent, repoDir, prompt2, prefix, sessionId, name) {
1855
+ function spawnAgent(agent, repoDir, prompt2, prefix, onActivity, sessionId, name) {
1877
1856
  const { bin, args } = buildAgentArgs(agent, prompt2, "execute", sessionId, name);
1878
1857
  const child = spawn4(bin, args, { cwd: repoDir, stdio: ["ignore", "pipe", "pipe"] });
1879
1858
  child.on("error", (err) => {
@@ -1882,12 +1861,19 @@ function spawnAgent(agent, repoDir, prompt2, prefix, sessionId, name) {
1882
1861
  logError(prefix, `Check that "${bin}" is on PATH and "${repoDir}" exists`);
1883
1862
  }
1884
1863
  });
1864
+ if (agent === "codex") {
1865
+ child.stdout?.on("data", () => onActivity?.());
1866
+ child.stderr?.on("data", () => onActivity?.());
1867
+ return child;
1868
+ }
1885
1869
  child.stdout?.on("data", (data) => {
1870
+ onActivity?.();
1886
1871
  for (const line of data.toString().split("\n")) {
1887
1872
  if (line) console.log(`${timestamp()} ${prefix} ${paint("dim", line)}`);
1888
1873
  }
1889
1874
  });
1890
1875
  child.stderr?.on("data", (data) => {
1876
+ onActivity?.();
1891
1877
  for (const line of data.toString().split("\n")) {
1892
1878
  if (line) logError(prefix, paint("dim", line));
1893
1879
  }
@@ -1900,18 +1886,22 @@ var watchCommand = new Command8("watch").description(
1900
1886
  const intervalMs = parseInt(opts.interval, 10) * 1e3;
1901
1887
  const dryRun = opts.dryRun;
1902
1888
  const planApproval = opts.planApproval;
1903
- const rootDir = opts.root ? resolve(opts.root) : process.cwd();
1889
+ const rootDir = opts.root ? resolve2(opts.root) : process.cwd();
1904
1890
  const agent = opts.agent === "codex" ? "codex" : opts.agent === "gemini" ? "gemini" : "claude";
1905
1891
  const scanAt = opts.scanAt;
1892
+ const taskStallTimeoutMs = getTaskStallTimeoutMs();
1906
1893
  const active = /* @__PURE__ */ new Map();
1907
1894
  const failed = /* @__PURE__ */ new Map();
1908
1895
  const queued = /* @__PURE__ */ new Set();
1896
+ const finishing = /* @__PURE__ */ new Set();
1897
+ let pollRunning = false;
1909
1898
  const approvalQueue = [];
1910
1899
  let approvalRunning = false;
1911
1900
  const flags = [
1912
1901
  `interval=${paint("cyan", opts.interval + "s")}`,
1913
1902
  `root=${paint("cyan", rootDir)}`,
1914
1903
  `agent=${paint("cyan", agent)}`,
1904
+ `stall-timeout=${paint("cyan", formatTimeoutMinutes(taskStallTimeoutMs))}`,
1915
1905
  ...planApproval ? [paint("yellow", "plan-approval")] : [],
1916
1906
  ...dryRun ? [paint("yellow", "dry-run")] : [],
1917
1907
  ...scanAt ? [`scan-at=${paint("cyan", scanAt)}`] : []
@@ -1981,124 +1971,94 @@ var watchCommand = new Command8("watch").description(
1981
1971
  }
1982
1972
  const prompt2 = buildExecutionPrompt(task, repoDir, subtasks, vcs, protoRefs, feedbackUpdates, existingResources, skillRefs);
1983
1973
  const sessionId = agent === "claude" ? randomUUID() : void 0;
1984
- const child = spawnAgent(agent, repoDir, prompt2, prefix, sessionId, task.title);
1974
+ const activeEntry = {
1975
+ process: void 0,
1976
+ title: task.title,
1977
+ repoDir,
1978
+ startedAt: Date.now(),
1979
+ lastActivityAt: Date.now()
1980
+ };
1981
+ const touchActivity = () => {
1982
+ activeEntry.lastActivityAt = Date.now();
1983
+ };
1984
+ const child = spawnAgent(agent, repoDir, prompt2, prefix, touchActivity, sessionId, task.title);
1985
+ activeEntry.process = child;
1985
1986
  if (sessionId) {
1986
1987
  api.patch(`/api/tasks/${task.id}`, { claudeSessionId: sessionId }).catch(() => {
1987
1988
  });
1988
1989
  logInfo(prefix, `Claude session: ${paint("dim", sessionId)}`);
1989
1990
  }
1990
- active.set(task.id, { process: child, title: task.title, repoDir });
1991
+ active.set(task.id, activeEntry);
1991
1992
  child.on("exit", async (code) => {
1992
1993
  active.delete(task.id);
1993
- queued.delete(task.id);
1994
- if (code === 0) {
1995
- try {
1996
- const researchPath = resolve(repoDir, "research.md");
1997
- if (existsSync6(researchPath)) {
1998
- try {
1999
- const researchContent = readFileSync5(researchPath, "utf-8");
2000
- const existingResearch = existingResources.find((r) => r.type === "research");
2001
- if (existingResearch) {
2002
- await api.patch(`/api/tasks/${task.id}/resources/${existingResearch.id}`, {
2003
- content: researchContent
2004
- });
2005
- logSuccess(prefix, `Updated existing research resource`);
2006
- } else {
2007
- await api.post(`/api/tasks/${task.id}/resources`, {
2008
- type: "research",
2009
- title: `Research \u2014 ${task.title}`,
2010
- content: researchContent
2011
- });
2012
- logSuccess(prefix, `Uploaded research.md as task resource`);
2013
- }
2014
- unlinkSync(researchPath);
2015
- } catch (err) {
2016
- logWarn(prefix, `Failed to upload research resource: ${err.message}`);
2017
- }
2018
- }
2019
- if (sessionId) {
2020
- try {
2021
- const sessionEntries = readAndParseSessionLog(sessionId, repoDir);
2022
- if (sessionEntries && sessionEntries.length > 0) {
2023
- const sessionContent = JSON.stringify(sessionEntries);
2024
- const existingSessionLog = existingResources.find((r) => r.type === "session-log");
2025
- if (existingSessionLog) {
2026
- await api.patch(`/api/tasks/${task.id}/resources/${existingSessionLog.id}`, {
2027
- content: sessionContent
1994
+ finishing.add(task.id);
1995
+ try {
1996
+ if (code === 0) {
1997
+ try {
1998
+ const researchPath = resolve2(repoDir, "research.md");
1999
+ if (existsSync7(researchPath)) {
2000
+ try {
2001
+ const researchContent = readFileSync5(researchPath, "utf-8");
2002
+ const existingResearch = existingResources.find((r) => r.type === "research");
2003
+ if (existingResearch) {
2004
+ await api.patch(`/api/tasks/${task.id}/resources/${existingResearch.id}`, {
2005
+ content: researchContent
2028
2006
  });
2029
- logSuccess(prefix, `Updated session log resource (${sessionEntries.length} messages)`);
2007
+ logSuccess(prefix, `Updated existing research resource`);
2030
2008
  } else {
2031
2009
  await api.post(`/api/tasks/${task.id}/resources`, {
2032
- type: "session-log",
2033
- title: `Session Log \u2014 ${task.title}`,
2034
- content: sessionContent
2010
+ type: "research",
2011
+ title: `Research \u2014 ${task.title}`,
2012
+ content: researchContent
2035
2013
  });
2036
- logSuccess(prefix, `Uploaded session log (${sessionEntries.length} messages)`);
2014
+ logSuccess(prefix, `Uploaded research.md as task resource`);
2037
2015
  }
2038
- }
2039
- } catch (err) {
2040
- logWarn(prefix, `Failed to upload session log: ${err.message}`);
2041
- }
2042
- }
2043
- const noMrPath = resolve(repoDir, ".mr-no-mr");
2044
- const noMrRequested = existsSync6(noMrPath);
2045
- let noMrDescription;
2046
- if (noMrRequested) {
2047
- noMrDescription = readFileSync5(noMrPath, "utf-8").trim();
2048
- unlinkSync(noMrPath);
2049
- logSuccess(prefix, `No ${vcs === "gitlab" ? "MR" : "PR"} needed \u2014 ${noMrDescription}`);
2050
- }
2051
- const prLabel = vcs === "gitlab" ? "MR" : "PR";
2052
- let prUrl = null;
2053
- if (!noMrRequested) {
2054
- prUrl = await findPrUrlAcrossRepos(branchName, repoDir, vcs);
2055
- if (!prUrl) {
2056
- prUrl = await extractPrUrlFromUpdates(task.id);
2057
- if (prUrl) {
2058
- logInfo(prefix, `Found ${prLabel} URL from agent updates: ${paint("cyan", prUrl)}`);
2016
+ unlinkSync(researchPath);
2017
+ } catch (err) {
2018
+ logWarn(prefix, `Failed to upload research resource: ${err.message}`);
2059
2019
  }
2060
2020
  }
2061
- if (prUrl) {
2062
- logSuccess(prefix, `${prLabel} ready: ${paint("cyan", prUrl)}`);
2063
- } else {
2064
- logWarn(prefix, `No ${prLabel} found for branch ${paint("cyan", branchName)}`);
2065
- await postTaskUpdate(task.id, `Agent finished \u2014 no ${prLabel} found for branch ${branchName}`, "system");
2021
+ const noMrPath = resolve2(repoDir, ".mr-no-mr");
2022
+ const noMrRequested = existsSync7(noMrPath);
2023
+ let noMrDescription;
2024
+ if (noMrRequested) {
2025
+ noMrDescription = readFileSync5(noMrPath, "utf-8").trim();
2026
+ unlinkSync(noMrPath);
2027
+ logSuccess(prefix, `No ${vcs === "gitlab" ? "MR" : "PR"} needed \u2014 ${noMrDescription}`);
2066
2028
  }
2067
- }
2068
- await api.patch(`/api/tasks/${task.id}`, {
2069
- status: "review",
2070
- ...prUrl ? { link: prUrl } : {}
2071
- });
2072
- logSuccess(prefix, `"${paint("bold", task.title)}" marked ready for review`);
2073
- await postTaskUpdate(task.id, noMrRequested ? `Task marked ready for review \u2014 no ${prLabel} needed: ${noMrDescription}` : "Task marked ready for review", "system");
2074
- } catch (err) {
2075
- logError(prefix, `Failed to update task: ${err.message}`);
2076
- }
2077
- } else {
2078
- logError(prefix, `"${paint("bold", task.title)}" failed (exit ${code}), leaving status unchanged`);
2079
- await postTaskUpdate(task.id, `Agent failed with exit code ${code}`, "system");
2080
- if (sessionId) {
2081
- try {
2082
- const sessionEntries = readAndParseSessionLog(sessionId, repoDir);
2083
- if (sessionEntries && sessionEntries.length > 0) {
2084
- const sessionContent = JSON.stringify(sessionEntries);
2085
- const existingSessionLog = existingResources.find((r) => r.type === "session-log");
2086
- if (existingSessionLog) {
2087
- await api.patch(`/api/tasks/${task.id}/resources/${existingSessionLog.id}`, {
2088
- content: sessionContent
2089
- });
2029
+ const prLabel = vcs === "gitlab" ? "MR" : "PR";
2030
+ let prUrl = null;
2031
+ if (!noMrRequested) {
2032
+ prUrl = await findPrUrlAcrossRepos(branchName, repoDir, vcs);
2033
+ if (!prUrl) {
2034
+ prUrl = await extractPrUrlFromUpdates(task.id);
2035
+ if (prUrl) {
2036
+ logInfo(prefix, `Found ${prLabel} URL from agent updates: ${paint("cyan", prUrl)}`);
2037
+ }
2038
+ }
2039
+ if (prUrl) {
2040
+ logSuccess(prefix, `${prLabel} ready: ${paint("cyan", prUrl)}`);
2090
2041
  } else {
2091
- await api.post(`/api/tasks/${task.id}/resources`, {
2092
- type: "session-log",
2093
- title: `Session Log \u2014 ${task.title}`,
2094
- content: sessionContent
2095
- });
2042
+ logWarn(prefix, `No ${prLabel} found for branch ${paint("cyan", branchName)}`);
2043
+ await postTaskUpdate(task.id, `Agent finished \u2014 no ${prLabel} found for branch ${branchName}`, "system");
2096
2044
  }
2097
- logInfo(prefix, `Uploaded session log for failed task (${sessionEntries.length} messages)`);
2098
2045
  }
2099
- } catch {
2046
+ await api.patch(`/api/tasks/${task.id}`, {
2047
+ status: "review",
2048
+ ...prUrl ? { link: prUrl } : {}
2049
+ });
2050
+ logSuccess(prefix, `"${paint("bold", task.title)}" marked ready for review`);
2051
+ await postTaskUpdate(task.id, noMrRequested ? `Task marked ready for review \u2014 no ${prLabel} needed: ${noMrDescription}` : "Task marked ready for review", "system");
2052
+ } catch (err) {
2053
+ logError(prefix, `Failed to update task: ${err.message}`);
2100
2054
  }
2055
+ } else if (!activeEntry.terminatedForError) {
2056
+ logError(prefix, `"${paint("bold", task.title)}" failed (exit ${code}), leaving status unchanged`);
2057
+ await postTaskUpdate(task.id, `Agent failed with exit code ${code}`, "system");
2101
2058
  }
2059
+ } finally {
2060
+ queued.delete(task.id);
2061
+ finishing.delete(task.id);
2102
2062
  }
2103
2063
  });
2104
2064
  }
@@ -2135,49 +2095,54 @@ var watchCommand = new Command8("watch").description(
2135
2095
  active.set(task.id, { process: child, title: task.title, repoDir });
2136
2096
  child.on("exit", async (code) => {
2137
2097
  active.delete(task.id);
2138
- queued.delete(task.id);
2139
- if (code === 0) {
2140
- try {
2141
- const prdPath = resolve(repoDir, "prd.md");
2142
- let prdContent;
2098
+ finishing.add(task.id);
2099
+ try {
2100
+ if (code === 0) {
2143
2101
  try {
2144
- prdContent = readFileSync5(prdPath, "utf-8");
2145
- logInfo(prefix, `Read PRD from ${paint("cyan", "prd.md")} (${prdContent.length} chars)`);
2146
- unlinkSync(prdPath);
2147
- } catch {
2148
- logWarn(prefix, `No prd.md file found in ${repoDir} \u2014 PRD may have been posted inline`);
2149
- }
2150
- if (prdContent) {
2102
+ const prdPath = resolve2(repoDir, "prd.md");
2103
+ let prdContent;
2151
2104
  try {
2152
- if (existingPlanResource) {
2153
- await api.patch(`/api/tasks/${task.id}/resources/${existingPlanResource.id}`, {
2154
- content: prdContent
2155
- });
2156
- logSuccess(prefix, `Updated existing PRD resource`);
2157
- } else {
2158
- await api.post(`/api/tasks/${task.id}/resources`, {
2159
- type: "plan",
2160
- title: `PRD \u2014 ${task.title}`,
2161
- content: prdContent
2162
- });
2163
- logSuccess(prefix, `Uploaded PRD as task resource`);
2105
+ prdContent = readFileSync5(prdPath, "utf-8");
2106
+ logInfo(prefix, `Read PRD from ${paint("cyan", "prd.md")} (${prdContent.length} chars)`);
2107
+ unlinkSync(prdPath);
2108
+ } catch {
2109
+ logWarn(prefix, `No prd.md file found in ${repoDir} \u2014 PRD may have been posted inline`);
2110
+ }
2111
+ if (prdContent) {
2112
+ try {
2113
+ if (existingPlanResource) {
2114
+ await api.patch(`/api/tasks/${task.id}/resources/${existingPlanResource.id}`, {
2115
+ content: prdContent
2116
+ });
2117
+ logSuccess(prefix, `Updated existing PRD resource`);
2118
+ } else {
2119
+ await api.post(`/api/tasks/${task.id}/resources`, {
2120
+ type: "plan",
2121
+ title: `PRD \u2014 ${task.title}`,
2122
+ content: prdContent
2123
+ });
2124
+ logSuccess(prefix, `Uploaded PRD as task resource`);
2125
+ }
2126
+ } catch (err) {
2127
+ logWarn(prefix, `Failed to upload PRD resource: ${err.message}`);
2164
2128
  }
2165
- } catch (err) {
2166
- logWarn(prefix, `Failed to upload PRD resource: ${err.message}`);
2167
2129
  }
2130
+ await api.patch(`/api/tasks/${task.id}`, {
2131
+ status: "review",
2132
+ ...prdContent ? { prdContent } : {}
2133
+ });
2134
+ logSuccess(prefix, `"${paint("bold", task.title)}" PRD generated and marked ready for review`);
2135
+ await postTaskUpdate(task.id, "PRD generated \u2014 ready for review", "system");
2136
+ } catch (err) {
2137
+ logError(prefix, `Failed to update task: ${err.message}`);
2168
2138
  }
2169
- await api.patch(`/api/tasks/${task.id}`, {
2170
- status: "review",
2171
- ...prdContent ? { prdContent } : {}
2172
- });
2173
- logSuccess(prefix, `"${paint("bold", task.title)}" PRD generated and marked ready for review`);
2174
- await postTaskUpdate(task.id, "PRD generated \u2014 ready for review", "system");
2175
- } catch (err) {
2176
- logError(prefix, `Failed to update task: ${err.message}`);
2139
+ } else {
2140
+ logError(prefix, `"${paint("bold", task.title)}" PRD generation failed (exit ${code}), leaving status unchanged`);
2141
+ await postTaskUpdate(task.id, `PRD generation failed with exit code ${code}`, "system");
2177
2142
  }
2178
- } else {
2179
- logError(prefix, `"${paint("bold", task.title)}" PRD generation failed (exit ${code}), leaving status unchanged`);
2180
- await postTaskUpdate(task.id, `PRD generation failed with exit code ${code}`, "system");
2143
+ } finally {
2144
+ queued.delete(task.id);
2145
+ finishing.delete(task.id);
2181
2146
  }
2182
2147
  });
2183
2148
  }
@@ -2188,7 +2153,7 @@ var watchCommand = new Command8("watch").description(
2188
2153
  const stalePattern = /^prototype-\d+\.html$/;
2189
2154
  for (const f of readdirSync(repoDir).filter((f2) => stalePattern.test(f2))) {
2190
2155
  try {
2191
- unlinkSync(resolve(repoDir, f));
2156
+ unlinkSync(resolve2(repoDir, f));
2192
2157
  } catch {
2193
2158
  }
2194
2159
  }
@@ -2208,42 +2173,48 @@ var watchCommand = new Command8("watch").description(
2208
2173
  const child = spawnAgent(agent, repoDir, prompt2, prefix, void 0, proto.title);
2209
2174
  active.set(`proto-${proto.id}`, { process: child, title: proto.title, repoDir });
2210
2175
  child.on("exit", async (code) => {
2211
- active.delete(`proto-${proto.id}`);
2212
- queued.delete(`proto-${proto.id}`);
2213
- if (code === 0) {
2214
- try {
2215
- const protoPattern = /^prototype-\d+\.html$/;
2216
- const found = readdirSync(repoDir).filter((f) => protoPattern.test(f)).sort();
2217
- const files = found.map((f) => ({
2218
- name: f,
2219
- content: readFileSync5(resolve(repoDir, f), "utf-8")
2220
- }));
2221
- if (files.length === 0) {
2222
- logError(prefix, `No prototype HTML files found in ${repoDir}`);
2223
- await api.patch(`/api/prototypes/${proto.id}`, { status: "failed" });
2224
- return;
2225
- }
2226
- await api.patch(`/api/prototypes/${proto.id}`, { status: "completed", files });
2227
- logSuccess(prefix, `"${paint("bold", proto.title)}" uploaded ${files.length} file(s)`);
2228
- for (const file of files) {
2176
+ const key = `proto-${proto.id}`;
2177
+ active.delete(key);
2178
+ finishing.add(key);
2179
+ try {
2180
+ if (code === 0) {
2181
+ try {
2182
+ const protoPattern = /^prototype-\d+\.html$/;
2183
+ const found = readdirSync(repoDir).filter((f) => protoPattern.test(f)).sort();
2184
+ const files = found.map((f) => ({
2185
+ name: f,
2186
+ content: readFileSync5(resolve2(repoDir, f), "utf-8")
2187
+ }));
2188
+ if (files.length === 0) {
2189
+ logError(prefix, `No prototype HTML files found in ${repoDir}`);
2190
+ await api.patch(`/api/prototypes/${proto.id}`, { status: "failed" });
2191
+ return;
2192
+ }
2193
+ await api.patch(`/api/prototypes/${proto.id}`, { status: "completed", files });
2194
+ logSuccess(prefix, `"${paint("bold", proto.title)}" uploaded ${files.length} file(s)`);
2195
+ for (const file of files) {
2196
+ try {
2197
+ unlinkSync(resolve2(repoDir, file.name));
2198
+ } catch {
2199
+ }
2200
+ }
2201
+ } catch (err) {
2202
+ logError(prefix, `Failed to upload prototype: ${err.message}`);
2229
2203
  try {
2230
- unlinkSync(resolve(repoDir, file.name));
2204
+ await api.patch(`/api/prototypes/${proto.id}`, { status: "failed" });
2231
2205
  } catch {
2232
2206
  }
2233
2207
  }
2234
- } catch (err) {
2235
- logError(prefix, `Failed to upload prototype: ${err.message}`);
2208
+ } else {
2209
+ logError(prefix, `"${paint("bold", proto.title)}" prototype failed (exit ${code})`);
2236
2210
  try {
2237
2211
  await api.patch(`/api/prototypes/${proto.id}`, { status: "failed" });
2238
2212
  } catch {
2239
2213
  }
2240
2214
  }
2241
- } else {
2242
- logError(prefix, `"${paint("bold", proto.title)}" prototype failed (exit ${code})`);
2243
- try {
2244
- await api.patch(`/api/prototypes/${proto.id}`, { status: "failed" });
2245
- } catch {
2246
- }
2215
+ } finally {
2216
+ queued.delete(key);
2217
+ finishing.delete(key);
2247
2218
  }
2248
2219
  });
2249
2220
  }
@@ -2259,16 +2230,22 @@ var watchCommand = new Command8("watch").description(
2259
2230
  const child = spawnAgent(agent, workDir, prompt2, prefix, void 0, project.name);
2260
2231
  active.set(`repo-${project.id}`, { process: child, title: project.name, repoDir: workDir });
2261
2232
  child.on("exit", async (code) => {
2262
- active.delete(`repo-${project.id}`);
2263
- queued.delete(`repo-${project.id}`);
2264
- if (code === 0) {
2265
- logSuccess(prefix, `"${paint("bold", project.name)}" repo creation complete`);
2266
- } else {
2267
- logError(prefix, `"${paint("bold", project.name)}" repo creation failed (exit ${code})`);
2268
- try {
2269
- await api.patch(`/api/projects/${project.id}`, { repoCreationStatus: "failed" });
2270
- } catch {
2233
+ const key = `repo-${project.id}`;
2234
+ active.delete(key);
2235
+ finishing.add(key);
2236
+ try {
2237
+ if (code === 0) {
2238
+ logSuccess(prefix, `"${paint("bold", project.name)}" repo creation complete`);
2239
+ } else {
2240
+ logError(prefix, `"${paint("bold", project.name)}" repo creation failed (exit ${code})`);
2241
+ try {
2242
+ await api.patch(`/api/projects/${project.id}`, { repoCreationStatus: "failed" });
2243
+ } catch {
2244
+ }
2271
2245
  }
2246
+ } finally {
2247
+ queued.delete(key);
2248
+ finishing.delete(key);
2272
2249
  }
2273
2250
  });
2274
2251
  }
@@ -2278,7 +2255,7 @@ var watchCommand = new Command8("watch").description(
2278
2255
  logDispatch(prefix, `"${paint("bold", idea.title)}"${idea.feedback ? paint("cyan", " (iteration)") : ""} ${paint("gray", repoDir)}`);
2279
2256
  for (const f of ["idea-plan.md", "idea-tasks.json", "idea-prototype.html"]) {
2280
2257
  try {
2281
- unlinkSync(resolve(repoDir, f));
2258
+ unlinkSync(resolve2(repoDir, f));
2282
2259
  } catch {
2283
2260
  }
2284
2261
  }
@@ -2286,85 +2263,91 @@ var watchCommand = new Command8("watch").description(
2286
2263
  const child = spawnAgent(agent, repoDir, prompt2, prefix, void 0, idea.title);
2287
2264
  active.set(`idea-${idea.id}`, { process: child, title: idea.title, repoDir });
2288
2265
  child.on("exit", async (code) => {
2289
- active.delete(`idea-${idea.id}`);
2290
- queued.delete(`idea-${idea.id}`);
2291
- if (code === 0) {
2292
- try {
2293
- let plan;
2294
- let protoHtml;
2295
- let followUpTasks;
2296
- const planPath = resolve(repoDir, "idea-plan.md");
2297
- const tasksPath = resolve(repoDir, "idea-tasks.json");
2298
- const protoPath = resolve(repoDir, "idea-prototype.html");
2299
- try {
2300
- plan = readFileSync5(planPath, "utf-8");
2301
- } catch {
2302
- }
2303
- try {
2304
- protoHtml = readFileSync5(protoPath, "utf-8");
2305
- } catch {
2306
- }
2266
+ const key = `idea-${idea.id}`;
2267
+ active.delete(key);
2268
+ finishing.add(key);
2269
+ try {
2270
+ if (code === 0) {
2307
2271
  try {
2308
- const tasksRaw = readFileSync5(tasksPath, "utf-8");
2309
- const parsed = JSON.parse(tasksRaw);
2310
- if (Array.isArray(parsed)) {
2311
- followUpTasks = parsed.filter((t) => t && typeof t === "object" && "title" in t);
2272
+ let plan;
2273
+ let protoHtml;
2274
+ let followUpTasks;
2275
+ const planPath = resolve2(repoDir, "idea-plan.md");
2276
+ const tasksPath = resolve2(repoDir, "idea-tasks.json");
2277
+ const protoPath = resolve2(repoDir, "idea-prototype.html");
2278
+ try {
2279
+ plan = readFileSync5(planPath, "utf-8");
2280
+ } catch {
2312
2281
  }
2313
- } catch {
2314
- }
2315
- if (!plan && !protoHtml) {
2316
- logError(prefix, `No output files found in ${repoDir}`);
2317
- await api.patch(`/api/ideas/${idea.id}`, { status: "draft" });
2318
- return;
2319
- }
2320
- const updateData = { status: "generated" };
2321
- if (plan) updateData.plan = plan;
2322
- if (followUpTasks) updateData.followUpTasks = followUpTasks;
2323
- if (protoHtml) {
2324
2282
  try {
2325
- const proto = await api.post("/api/prototypes", {
2326
- title: `${idea.title} \u2014 Idea Prototype`,
2327
- prompt: idea.description || idea.title,
2328
- variantCount: 1,
2329
- projectId: idea.projectId ?? null
2330
- });
2331
- await api.patch(`/api/prototypes/${proto.id}`, {
2332
- status: "completed",
2333
- files: [{ name: "idea-prototype.html", content: protoHtml }]
2334
- });
2335
- updateData.generatedPrototypeId = proto.id;
2336
- logSuccess(prefix, `Prototype created: ${paint("gray", proto.id.slice(0, 8))}`);
2337
- } catch (err) {
2338
- logError(prefix, `Failed to create prototype: ${err.message}`);
2283
+ protoHtml = readFileSync5(protoPath, "utf-8");
2284
+ } catch {
2285
+ }
2286
+ try {
2287
+ const tasksRaw = readFileSync5(tasksPath, "utf-8");
2288
+ const parsed = JSON.parse(tasksRaw);
2289
+ if (Array.isArray(parsed)) {
2290
+ followUpTasks = parsed.filter((t) => t && typeof t === "object" && "title" in t);
2291
+ }
2292
+ } catch {
2293
+ }
2294
+ if (!plan && !protoHtml) {
2295
+ logError(prefix, `No output files found in ${repoDir}`);
2296
+ await api.patch(`/api/ideas/${idea.id}`, { status: "draft" });
2297
+ return;
2298
+ }
2299
+ const updateData = { status: "generated" };
2300
+ if (plan) updateData.plan = plan;
2301
+ if (followUpTasks) updateData.followUpTasks = followUpTasks;
2302
+ if (protoHtml) {
2303
+ try {
2304
+ const proto = await api.post("/api/prototypes", {
2305
+ title: `${idea.title} \u2014 Idea Prototype`,
2306
+ prompt: idea.description || idea.title,
2307
+ variantCount: 1,
2308
+ projectId: idea.projectId ?? null
2309
+ });
2310
+ await api.patch(`/api/prototypes/${proto.id}`, {
2311
+ status: "completed",
2312
+ files: [{ name: "idea-prototype.html", content: protoHtml }]
2313
+ });
2314
+ updateData.generatedPrototypeId = proto.id;
2315
+ logSuccess(prefix, `Prototype created: ${paint("gray", proto.id.slice(0, 8))}`);
2316
+ } catch (err) {
2317
+ logError(prefix, `Failed to create prototype: ${err.message}`);
2318
+ }
2319
+ }
2320
+ await api.patch(`/api/ideas/${idea.id}`, updateData);
2321
+ logSuccess(prefix, `"${paint("bold", idea.title)}" generation complete`);
2322
+ try {
2323
+ unlinkSync(planPath);
2324
+ } catch {
2325
+ }
2326
+ try {
2327
+ unlinkSync(tasksPath);
2328
+ } catch {
2329
+ }
2330
+ try {
2331
+ unlinkSync(protoPath);
2332
+ } catch {
2333
+ }
2334
+ } catch (err) {
2335
+ logError(prefix, `Failed to upload idea output: ${err.message}`);
2336
+ try {
2337
+ await api.patch(`/api/ideas/${idea.id}`, { status: "draft" });
2338
+ } catch {
2339
2339
  }
2340
2340
  }
2341
- await api.patch(`/api/ideas/${idea.id}`, updateData);
2342
- logSuccess(prefix, `"${paint("bold", idea.title)}" generation complete`);
2343
- try {
2344
- unlinkSync(planPath);
2345
- } catch {
2346
- }
2347
- try {
2348
- unlinkSync(tasksPath);
2349
- } catch {
2350
- }
2351
- try {
2352
- unlinkSync(protoPath);
2353
- } catch {
2354
- }
2355
- } catch (err) {
2356
- logError(prefix, `Failed to upload idea output: ${err.message}`);
2341
+ } else {
2342
+ logError(prefix, `"${paint("bold", idea.title)}" generation failed (exit ${code})`);
2357
2343
  try {
2358
2344
  await api.patch(`/api/ideas/${idea.id}`, { status: "draft" });
2359
2345
  } catch {
2360
2346
  }
2361
2347
  }
2362
- } else {
2363
- logError(prefix, `"${paint("bold", idea.title)}" generation failed (exit ${code})`);
2364
- try {
2365
- await api.patch(`/api/ideas/${idea.id}`, { status: "draft" });
2366
- } catch {
2367
- }
2348
+ } finally {
2349
+ queued.delete(key);
2350
+ finishing.delete(key);
2368
2351
  }
2369
2352
  });
2370
2353
  }
@@ -2443,329 +2426,371 @@ ${divider}`);
2443
2426
  }
2444
2427
  }
2445
2428
  async function poll() {
2446
- let queuedTasks;
2447
- let delegatedTasks;
2429
+ if (pollRunning) return;
2430
+ pollRunning = true;
2448
2431
  try {
2449
- queuedTasks = await api.get("/api/tasks?status=queued");
2450
- delegatedTasks = await api.get("/api/tasks?status=delegated");
2451
- } catch (err) {
2452
- logError(watchTag(), `Failed to fetch tasks: ${err.message}`);
2453
- return;
2454
- }
2455
- const nonTestQueued = queuedTasks.filter((t) => t.mode !== "testing");
2456
- const nonTestDelegated = delegatedTasks.filter((t) => t.mode !== "testing");
2457
- const tasks = [...nonTestQueued, ...nonTestDelegated];
2458
- const config = loadConfig();
2459
- const activeTaskIds = new Set(tasks.map((t) => t.id));
2460
- for (const [taskId, entry] of active) {
2461
- if (taskId.startsWith("proto-") || taskId.startsWith("repo-") || taskId.startsWith("scan-")) continue;
2462
- if (!activeTaskIds.has(taskId)) {
2463
- logWarn(watchTag(), `Task ${paint("yellow", taskId.slice(0, 8))} no longer active, terminating\u2026`);
2464
- entry.process.kill("SIGTERM");
2465
- active.delete(taskId);
2466
- queued.delete(taskId);
2467
- }
2468
- }
2469
- const nonTaskPrefixes = ["proto-", "repo-", "scan-", "idea-", "test-"];
2470
- for (const taskId of failed.keys()) {
2471
- if (nonTaskPrefixes.some((p) => taskId.startsWith(p))) continue;
2472
- if (!activeTaskIds.has(taskId)) failed.delete(taskId);
2473
- }
2474
- for (const taskId of queued) {
2475
- if (nonTaskPrefixes.some((p) => taskId.startsWith(p))) continue;
2476
- if (!activeTaskIds.has(taskId)) queued.delete(taskId);
2477
- }
2478
- for (const task of tasks) {
2479
- if (queued.has(task.id)) continue;
2480
- if (failed.has(task.id)) continue;
2481
- const sid = shortId(task.id);
2482
- const prefix = taskTag(sid);
2483
- const repoDir = findDirectoryForProject(config, task.projectId, rootDir);
2484
- if (!repoDir) {
2485
- const reason = `no linked directory found under ${rootDir}`;
2486
- logError(prefix, `"${task.title}": ${reason} \u2014 will not retry`);
2487
- failed.set(task.id, reason);
2488
- continue;
2489
- }
2490
- if (!existsSync6(repoDir)) {
2491
- const reason = `linked directory "${repoDir}" does not exist`;
2492
- logError(prefix, `"${task.title}": ${reason} \u2014 will not retry`);
2493
- failed.set(task.id, reason);
2494
- continue;
2432
+ let queuedTasks;
2433
+ let delegatedTasks;
2434
+ try {
2435
+ queuedTasks = await api.get("/api/tasks?status=queued");
2436
+ delegatedTasks = await api.get("/api/tasks?status=delegated");
2437
+ } catch (err) {
2438
+ logError(watchTag(), `Failed to fetch tasks: ${err.message}`);
2439
+ return;
2495
2440
  }
2496
- if (task.status === "queued") {
2441
+ const nonTestQueued = queuedTasks.filter((t) => t.mode !== "testing");
2442
+ const nonTestDelegated = delegatedTasks.filter((t) => t.mode !== "testing");
2443
+ const tasks = [...nonTestQueued, ...nonTestDelegated];
2444
+ const config = loadConfig();
2445
+ const activeTaskIds = new Set(tasks.map((t) => t.id));
2446
+ for (const task of tasks) {
2447
+ if (task.status !== "delegated") continue;
2448
+ const sid = shortId(task.id);
2449
+ const prefix = taskTag(sid);
2450
+ const activeEntry = active.get(task.id);
2451
+ const idleMs = activeEntry ? Date.now() - activeEntry.lastActivityAt : null;
2452
+ const delegatedAtMs = task.inProgressSince ? Date.now() - new Date(task.inProgressSince).getTime() : null;
2453
+ const exceededIdleTimeout = idleMs !== null && idleMs >= taskStallTimeoutMs;
2454
+ const exceededOrphanTimeout = !activeEntry && delegatedAtMs !== null && delegatedAtMs >= taskStallTimeoutMs;
2455
+ if (!exceededIdleTimeout && !exceededOrphanTimeout) continue;
2456
+ const timeoutLabel = formatTimeoutMinutes(taskStallTimeoutMs);
2457
+ const reason = activeEntry ? `Agent process became idle for more than ${timeoutLabel}` : `Task remained delegated without an active watch process for more than ${timeoutLabel}`;
2458
+ logError(prefix, `"${task.title}" marked as error: ${reason}`);
2459
+ if (activeEntry) {
2460
+ activeEntry.terminatedForError = true;
2461
+ }
2497
2462
  try {
2498
- await api.patch(`/api/tasks/${task.id}`, { status: "delegated" });
2463
+ await api.patch(`/api/tasks/${task.id}`, { status: "error" });
2464
+ await postTaskUpdate(task.id, `Task moved to error \u2014 ${reason}`, "system");
2499
2465
  } catch (err) {
2500
- logError(prefix, `Failed to mark task as delegated: ${err.message}`);
2466
+ logError(prefix, `Failed to mark task as error: ${err.message}`);
2501
2467
  continue;
2502
2468
  }
2469
+ failed.set(task.id, reason);
2470
+ queued.delete(task.id);
2471
+ if (activeEntry) {
2472
+ activeEntry.process.kill("SIGTERM");
2473
+ }
2503
2474
  }
2504
- queued.add(task.id);
2505
- if (dryRun) {
2506
- logInfo(
2507
- watchTag(),
2508
- `${paint("yellow", "[dry-run]")} would dispatch "${paint("bold", task.title)}" (${paint("gray", task.id)}) in ${paint("cyan", repoDir)}`
2509
- );
2510
- continue;
2475
+ for (const [taskId, entry] of active) {
2476
+ if (taskId.startsWith("proto-") || taskId.startsWith("repo-") || taskId.startsWith("scan-")) continue;
2477
+ if (!activeTaskIds.has(taskId)) {
2478
+ logWarn(watchTag(), `Task ${paint("yellow", taskId.slice(0, 8))} no longer active, terminating\u2026`);
2479
+ entry.process.kill("SIGTERM");
2480
+ active.delete(taskId);
2481
+ queued.delete(taskId);
2482
+ }
2511
2483
  }
2512
- if (task.mode === "planning") {
2513
- dispatchPlanModeTask(task, repoDir);
2514
- } else if (planApproval) {
2515
- approvalQueue.push({ task, repoDir });
2516
- processApprovalQueue();
2517
- } else {
2518
- dispatchTask(task, repoDir);
2484
+ const nonTaskPrefixes = ["proto-", "repo-", "scan-", "idea-", "test-"];
2485
+ for (const taskId of failed.keys()) {
2486
+ if (nonTaskPrefixes.some((p) => taskId.startsWith(p))) continue;
2487
+ if (!activeTaskIds.has(taskId)) failed.delete(taskId);
2519
2488
  }
2520
- }
2521
- let prototypes = [];
2522
- try {
2523
- prototypes = await api.get("/api/prototypes?status=in_progress");
2524
- } catch (err) {
2525
- logError(watchTag(), `Failed to fetch prototypes: ${err.message}`);
2526
- }
2527
- const inProgressProtoKeys = new Set(prototypes.map((p) => `proto-${p.id}`));
2528
- for (const [key, entry] of active) {
2529
- if (key.startsWith("proto-") && !inProgressProtoKeys.has(key)) {
2530
- logWarn(watchTag(), `Prototype ${paint("yellow", key)} no longer in_progress, terminating\u2026`);
2531
- entry.process.kill("SIGTERM");
2532
- active.delete(key);
2533
- queued.delete(key);
2489
+ for (const taskId of queued) {
2490
+ if (nonTaskPrefixes.some((p) => taskId.startsWith(p))) continue;
2491
+ if (finishing.has(taskId)) continue;
2492
+ if (!activeTaskIds.has(taskId)) queued.delete(taskId);
2534
2493
  }
2535
- }
2536
- for (const key of failed.keys()) {
2537
- if (key.startsWith("proto-") && !inProgressProtoKeys.has(key)) failed.delete(key);
2538
- }
2539
- for (const key of queued) {
2540
- if (key.startsWith("proto-") && !inProgressProtoKeys.has(key)) queued.delete(key);
2541
- }
2542
- const activeProtoCount = [...active.keys()].filter((k) => k.startsWith("proto-")).length;
2543
- let protoSlots = Math.max(0, 2 - activeProtoCount);
2544
- for (const proto of prototypes) {
2545
- if (protoSlots <= 0) break;
2546
- const key = `proto-${proto.id}`;
2547
- if (queued.has(key)) continue;
2548
- if (failed.has(key)) continue;
2549
- const sid = shortId(proto.id);
2550
- const prefix = protoTag(sid);
2551
- if (!proto.projectId) {
2552
- logWarn(prefix, `"${proto.title}": no projectId \u2014 skipping`);
2553
- continue;
2494
+ for (const task of tasks) {
2495
+ if (queued.has(task.id)) continue;
2496
+ if (finishing.has(task.id)) continue;
2497
+ if (failed.has(task.id)) continue;
2498
+ const sid = shortId(task.id);
2499
+ const prefix = taskTag(sid);
2500
+ const repoDir = findDirectoryForProject(config, task.projectId, rootDir);
2501
+ if (!repoDir) {
2502
+ const reason = `no linked directory found under ${rootDir}`;
2503
+ logError(prefix, `"${task.title}": ${reason} \u2014 will not retry`);
2504
+ failed.set(task.id, reason);
2505
+ continue;
2506
+ }
2507
+ if (!existsSync7(repoDir)) {
2508
+ const reason = `linked directory "${repoDir}" does not exist`;
2509
+ logError(prefix, `"${task.title}": ${reason} \u2014 will not retry`);
2510
+ failed.set(task.id, reason);
2511
+ continue;
2512
+ }
2513
+ if (task.status === "queued") {
2514
+ try {
2515
+ await api.patch(`/api/tasks/${task.id}`, { status: "delegated" });
2516
+ } catch (err) {
2517
+ logError(prefix, `Failed to mark task as delegated: ${err.message}`);
2518
+ continue;
2519
+ }
2520
+ }
2521
+ queued.add(task.id);
2522
+ if (dryRun) {
2523
+ logInfo(
2524
+ watchTag(),
2525
+ `${paint("yellow", "[dry-run]")} would dispatch "${paint("bold", task.title)}" (${paint("gray", task.id)}) in ${paint("cyan", repoDir)}`
2526
+ );
2527
+ continue;
2528
+ }
2529
+ if (task.mode === "planning") {
2530
+ dispatchPlanModeTask(task, repoDir);
2531
+ } else if (planApproval) {
2532
+ approvalQueue.push({ task, repoDir });
2533
+ processApprovalQueue();
2534
+ } else {
2535
+ dispatchTask(task, repoDir);
2536
+ }
2554
2537
  }
2555
- const repoDir = findDirectoryForProject(config, proto.projectId, rootDir);
2556
- if (!repoDir) {
2557
- logWarn(prefix, `"${proto.title}": no linked directory found \u2014 skipping`);
2558
- continue;
2538
+ let prototypes = [];
2539
+ try {
2540
+ prototypes = await api.get("/api/prototypes?status=in_progress");
2541
+ } catch (err) {
2542
+ logError(watchTag(), `Failed to fetch prototypes: ${err.message}`);
2559
2543
  }
2560
- if (!existsSync6(repoDir)) {
2561
- logError(prefix, `"${proto.title}": linked directory "${repoDir}" does not exist \u2014 skipping`);
2562
- failed.set(key, `directory does not exist: ${repoDir}`);
2563
- continue;
2544
+ const inProgressProtoKeys = new Set(prototypes.map((p) => `proto-${p.id}`));
2545
+ for (const [key, entry] of active) {
2546
+ if (key.startsWith("proto-") && !inProgressProtoKeys.has(key)) {
2547
+ logWarn(watchTag(), `Prototype ${paint("yellow", key)} no longer in_progress, terminating\u2026`);
2548
+ entry.process.kill("SIGTERM");
2549
+ active.delete(key);
2550
+ queued.delete(key);
2551
+ }
2564
2552
  }
2565
- queued.add(key);
2566
- protoSlots--;
2567
- if (dryRun) {
2568
- logInfo(
2569
- watchTag(),
2570
- `${paint("yellow", "[dry-run]")} would dispatch prototype "${paint("bold", proto.title)}" in ${paint("cyan", repoDir)}`
2571
- );
2572
- continue;
2553
+ for (const key of failed.keys()) {
2554
+ if (key.startsWith("proto-") && !inProgressProtoKeys.has(key)) failed.delete(key);
2573
2555
  }
2574
- dispatchPrototypeJob(proto, repoDir);
2575
- }
2576
- const testTasks = queuedTasks.filter((t) => t.mode === "testing");
2577
- for (const task of testTasks) {
2578
- const key = `test-${task.id}`;
2579
- if (queued.has(key)) continue;
2580
- if (failed.has(key)) continue;
2581
- const sid = shortId(task.id);
2582
- const prefix = testTag(sid);
2583
- if (!task.link) {
2584
- logWarn(prefix, `"${task.title}": no MR/PR link \u2014 skipping`);
2585
- continue;
2556
+ for (const key of queued) {
2557
+ if (key.startsWith("proto-") && !inProgressProtoKeys.has(key) && !finishing.has(key)) queued.delete(key);
2586
2558
  }
2587
- queued.add(key);
2588
- if (dryRun) {
2589
- logInfo(
2590
- watchTag(),
2591
- `${paint("yellow", "[dry-run]")} would run test for "${paint("bold", task.title)}"`
2592
- );
2593
- continue;
2559
+ const activeProtoCount = [...active.keys()].filter((k) => k.startsWith("proto-")).length;
2560
+ let protoSlots = Math.max(0, 2 - activeProtoCount);
2561
+ for (const proto of prototypes) {
2562
+ if (protoSlots <= 0) break;
2563
+ const key = `proto-${proto.id}`;
2564
+ if (queued.has(key)) continue;
2565
+ if (finishing.has(key)) continue;
2566
+ if (failed.has(key)) continue;
2567
+ const sid = shortId(proto.id);
2568
+ const prefix = protoTag(sid);
2569
+ if (!proto.projectId) {
2570
+ logWarn(prefix, `"${proto.title}": no projectId \u2014 skipping`);
2571
+ continue;
2572
+ }
2573
+ const repoDir = findDirectoryForProject(config, proto.projectId, rootDir);
2574
+ if (!repoDir) {
2575
+ logWarn(prefix, `"${proto.title}": no linked directory found \u2014 skipping`);
2576
+ continue;
2577
+ }
2578
+ if (!existsSync7(repoDir)) {
2579
+ logError(prefix, `"${proto.title}": linked directory "${repoDir}" does not exist \u2014 skipping`);
2580
+ failed.set(key, `directory does not exist: ${repoDir}`);
2581
+ continue;
2582
+ }
2583
+ queued.add(key);
2584
+ protoSlots--;
2585
+ if (dryRun) {
2586
+ logInfo(
2587
+ watchTag(),
2588
+ `${paint("yellow", "[dry-run]")} would dispatch prototype "${paint("bold", proto.title)}" in ${paint("cyan", repoDir)}`
2589
+ );
2590
+ continue;
2591
+ }
2592
+ dispatchPrototypeJob(proto, repoDir);
2594
2593
  }
2595
- (async () => {
2596
- logDispatch(prefix, `Running test for "${paint("bold", task.title)}"\u2026`);
2597
- try {
2598
- await api.patch(`/api/tasks/${task.id}`, { status: "delegated" });
2599
- await postTaskUpdate(task.id, "Test started \u2014 setting up environment\u2026");
2600
- let localPath;
2601
- try {
2602
- const project = await api.get(`/api/projects/${task.projectId}`);
2603
- localPath = project.localPath ?? void 0;
2604
- } catch {
2605
- }
2606
- if (!localPath) {
2607
- const cfg = loadConfig();
2608
- localPath = findDirectoryForProject(cfg, task.projectId, rootDir) ?? void 0;
2609
- }
2610
- if (!localPath) {
2611
- throw new Error("Local repo path not configured. Run `mr link` in the project directory.");
2612
- }
2613
- let testPlan;
2594
+ const testTasks = queuedTasks.filter((t) => t.mode === "testing");
2595
+ for (const task of testTasks) {
2596
+ const key = `test-${task.id}`;
2597
+ if (queued.has(key)) continue;
2598
+ if (finishing.has(key)) continue;
2599
+ if (failed.has(key)) continue;
2600
+ const sid = shortId(task.id);
2601
+ const prefix = testTag(sid);
2602
+ if (!task.link) {
2603
+ logWarn(prefix, `"${task.title}": no MR/PR link \u2014 skipping`);
2604
+ continue;
2605
+ }
2606
+ queued.add(key);
2607
+ if (dryRun) {
2608
+ logInfo(
2609
+ watchTag(),
2610
+ `${paint("yellow", "[dry-run]")} would run test for "${paint("bold", task.title)}"`
2611
+ );
2612
+ continue;
2613
+ }
2614
+ (async () => {
2615
+ logDispatch(prefix, `Running test for "${paint("bold", task.title)}"\u2026`);
2614
2616
  try {
2615
- const resources = await api.get(`/api/tasks/${task.id}/resources`);
2616
- const planResource = resources.find((r) => r.type === "test-plan");
2617
- if (planResource) {
2618
- testPlan = JSON.parse(planResource.content);
2619
- logInfo(prefix, `Using agent-authored test plan (${testPlan.length} steps)`);
2617
+ await api.patch(`/api/tasks/${task.id}`, { status: "delegated" });
2618
+ await postTaskUpdate(task.id, "Test started \u2014 setting up environment\u2026");
2619
+ let localPath;
2620
+ try {
2621
+ const project = await api.get(`/api/projects/${task.projectId}`);
2622
+ localPath = project.localPath ?? void 0;
2623
+ } catch {
2620
2624
  }
2621
- } catch {
2622
- }
2623
- const result = await runTest({
2624
- taskId: task.id,
2625
- taskLink: task.link,
2626
- localPath,
2627
- testPlan,
2628
- browseRunner: runBrowseCommand2,
2629
- uploadScreenshot: async (screenshotPath, message) => {
2630
- try {
2631
- const { readFileSync: readFileSync12 } = await import("fs");
2632
- const cfg = loadConfig();
2633
- const imageBuffer = readFileSync12(screenshotPath);
2634
- const fileName = screenshotPath.split("/").pop() || "test-screenshot.png";
2635
- const formData = new FormData();
2636
- const blob = new Blob([imageBuffer], { type: "image/png" });
2637
- formData.append("file", blob, fileName);
2638
- formData.append("prefix", "test-screenshots");
2639
- const uploadRes = await fetch(`${cfg.apiUrl}/api/upload`, {
2640
- method: "POST",
2641
- headers: { Authorization: `Bearer ${cfg.apiKey}` },
2642
- body: formData
2643
- });
2644
- if (!uploadRes.ok) return null;
2645
- const uploadData = await uploadRes.json();
2646
- await api.post(`/api/tasks/${task.id}/updates`, {
2647
- message,
2648
- imageUrl: uploadData.url,
2649
- source: "system"
2650
- });
2651
- return uploadData.url;
2652
- } catch {
2653
- return null;
2625
+ if (!localPath) {
2626
+ const cfg = loadConfig();
2627
+ localPath = findDirectoryForProject(cfg, task.projectId, rootDir) ?? void 0;
2628
+ }
2629
+ if (!localPath) {
2630
+ throw new Error("Local repo path not configured. Run `mr link` in the project directory.");
2631
+ }
2632
+ let testPlan;
2633
+ try {
2634
+ const resources = await api.get(`/api/tasks/${task.id}/resources`);
2635
+ const planResource = resources.find((r) => r.type === "test-plan");
2636
+ if (planResource) {
2637
+ testPlan = JSON.parse(planResource.content);
2638
+ logInfo(prefix, `Using agent-authored test plan (${testPlan.length} steps)`);
2654
2639
  }
2655
- },
2656
- postUpdate: async (message, imageUrl) => {
2657
- await postTaskUpdate(task.id, message);
2658
- },
2659
- onProgress: (msg) => logInfo(prefix, msg)
2660
- });
2661
- await api.patch(`/api/tasks/${task.id}`, { status: "completed", testResult: result.status });
2662
- logSuccess(prefix, result.summary);
2663
- } catch (err) {
2664
- logError(prefix, `Test failed: ${err.message}`);
2665
- try {
2666
- await api.patch(`/api/tasks/${task.id}`, { status: "completed", testResult: "failed" });
2667
- await postTaskUpdate(task.id, `Test failed: ${err.message}`);
2668
- } catch {
2640
+ } catch {
2641
+ }
2642
+ const result = await runTest({
2643
+ taskId: task.id,
2644
+ taskLink: task.link,
2645
+ localPath,
2646
+ testPlan,
2647
+ browseRunner: runBrowseCommand2,
2648
+ uploadScreenshot: async (screenshotPath, message) => {
2649
+ try {
2650
+ const { readFileSync: readFileSync12 } = await import("fs");
2651
+ const cfg = loadConfig();
2652
+ const imageBuffer = readFileSync12(screenshotPath);
2653
+ const fileName = screenshotPath.split("/").pop() || "test-screenshot.png";
2654
+ const formData = new FormData();
2655
+ const blob = new Blob([imageBuffer], { type: "image/png" });
2656
+ formData.append("file", blob, fileName);
2657
+ formData.append("prefix", "test-screenshots");
2658
+ const uploadRes = await fetch(`${cfg.apiUrl}/api/upload`, {
2659
+ method: "POST",
2660
+ headers: { Authorization: `Bearer ${cfg.apiKey}` },
2661
+ body: formData
2662
+ });
2663
+ if (!uploadRes.ok) return null;
2664
+ const uploadData = await uploadRes.json();
2665
+ await api.post(`/api/tasks/${task.id}/updates`, {
2666
+ message,
2667
+ imageUrl: uploadData.url,
2668
+ source: "system"
2669
+ });
2670
+ return uploadData.url;
2671
+ } catch {
2672
+ return null;
2673
+ }
2674
+ },
2675
+ postUpdate: async (message, imageUrl) => {
2676
+ await postTaskUpdate(task.id, message);
2677
+ },
2678
+ onProgress: (msg) => logInfo(prefix, msg)
2679
+ });
2680
+ await api.patch(`/api/tasks/${task.id}`, { status: "completed", testResult: result.status });
2681
+ logSuccess(prefix, result.summary);
2682
+ } catch (err) {
2683
+ logError(prefix, `Test failed: ${err.message}`);
2684
+ try {
2685
+ await api.patch(`/api/tasks/${task.id}`, { status: "completed", testResult: "failed" });
2686
+ await postTaskUpdate(task.id, `Test failed: ${err.message}`);
2687
+ } catch {
2688
+ }
2689
+ failed.set(key, err.message);
2690
+ } finally {
2691
+ queued.delete(key);
2669
2692
  }
2670
- failed.set(key, err.message);
2671
- } finally {
2693
+ })();
2694
+ }
2695
+ let pendingProjects = [];
2696
+ try {
2697
+ pendingProjects = await api.get("/api/projects?repoCreationStatus=pending");
2698
+ } catch (err) {
2699
+ logError(watchTag(), `Failed to fetch pending projects: ${err.message}`);
2700
+ }
2701
+ for (const project of pendingProjects) {
2702
+ const key = `repo-${project.id}`;
2703
+ if (queued.has(key)) continue;
2704
+ if (finishing.has(key)) continue;
2705
+ if (failed.has(key)) continue;
2706
+ queued.add(key);
2707
+ if (dryRun) {
2708
+ logInfo(
2709
+ watchTag(),
2710
+ `${paint("yellow", "[dry-run]")} would create repo for "${paint("bold", project.name)}" in ${paint("cyan", rootDir)}`
2711
+ );
2712
+ continue;
2713
+ }
2714
+ dispatchRepoCreation(project, rootDir);
2715
+ }
2716
+ let generatingIdeas = [];
2717
+ try {
2718
+ generatingIdeas = await api.get("/api/ideas?status=generating");
2719
+ } catch (err) {
2720
+ logError(watchTag(), `Failed to fetch generating ideas: ${err.message}`);
2721
+ }
2722
+ const inProgressIdeaKeys = new Set(generatingIdeas.map((i) => `idea-${i.id}`));
2723
+ for (const [key, entry] of active) {
2724
+ if (key.startsWith("idea-") && !inProgressIdeaKeys.has(key)) {
2725
+ logWarn(watchTag(), `Idea ${paint("yellow", key)} no longer generating, terminating\u2026`);
2726
+ entry.process.kill("SIGTERM");
2727
+ active.delete(key);
2672
2728
  queued.delete(key);
2673
2729
  }
2674
- })();
2675
- }
2676
- let pendingProjects = [];
2677
- try {
2678
- pendingProjects = await api.get("/api/projects?repoCreationStatus=pending");
2679
- } catch (err) {
2680
- logError(watchTag(), `Failed to fetch pending projects: ${err.message}`);
2681
- }
2682
- for (const project of pendingProjects) {
2683
- const key = `repo-${project.id}`;
2684
- if (queued.has(key)) continue;
2685
- if (failed.has(key)) continue;
2686
- queued.add(key);
2687
- if (dryRun) {
2688
- logInfo(
2689
- watchTag(),
2690
- `${paint("yellow", "[dry-run]")} would create repo for "${paint("bold", project.name)}" in ${paint("cyan", rootDir)}`
2691
- );
2692
- continue;
2693
2730
  }
2694
- dispatchRepoCreation(project, rootDir);
2695
- }
2696
- let generatingIdeas = [];
2697
- try {
2698
- generatingIdeas = await api.get("/api/ideas?status=generating");
2699
- } catch (err) {
2700
- logError(watchTag(), `Failed to fetch generating ideas: ${err.message}`);
2701
- }
2702
- const inProgressIdeaKeys = new Set(generatingIdeas.map((i) => `idea-${i.id}`));
2703
- for (const [key, entry] of active) {
2704
- if (key.startsWith("idea-") && !inProgressIdeaKeys.has(key)) {
2705
- logWarn(watchTag(), `Idea ${paint("yellow", key)} no longer generating, terminating\u2026`);
2706
- entry.process.kill("SIGTERM");
2707
- active.delete(key);
2708
- queued.delete(key);
2731
+ for (const key of failed.keys()) {
2732
+ if (key.startsWith("idea-") && !inProgressIdeaKeys.has(key)) failed.delete(key);
2709
2733
  }
2710
- }
2711
- for (const key of failed.keys()) {
2712
- if (key.startsWith("idea-") && !inProgressIdeaKeys.has(key)) failed.delete(key);
2713
- }
2714
- for (const key of queued) {
2715
- if (key.startsWith("idea-") && !inProgressIdeaKeys.has(key)) queued.delete(key);
2716
- }
2717
- const activeIdeaCount = [...active.keys()].filter((k) => k.startsWith("idea-")).length;
2718
- let ideaSlots = Math.max(0, 1 - activeIdeaCount);
2719
- for (const idea of generatingIdeas) {
2720
- if (ideaSlots <= 0) break;
2721
- const key = `idea-${idea.id}`;
2722
- if (queued.has(key)) continue;
2723
- if (failed.has(key)) continue;
2724
- const sid = shortId(idea.id);
2725
- const prefix = ideaTag(sid);
2726
- let repoDir;
2727
- if (idea.projectId) {
2728
- const dir = findDirectoryForProject(config, idea.projectId, rootDir);
2729
- if (!dir) {
2730
- logWarn(prefix, `"${idea.title}": no linked directory found \u2014 skipping`);
2734
+ for (const key of queued) {
2735
+ if (key.startsWith("idea-") && !inProgressIdeaKeys.has(key) && !finishing.has(key)) queued.delete(key);
2736
+ }
2737
+ const activeIdeaCount = [...active.keys()].filter((k) => k.startsWith("idea-")).length;
2738
+ let ideaSlots = Math.max(0, 1 - activeIdeaCount);
2739
+ for (const idea of generatingIdeas) {
2740
+ if (ideaSlots <= 0) break;
2741
+ const key = `idea-${idea.id}`;
2742
+ if (queued.has(key)) continue;
2743
+ if (finishing.has(key)) continue;
2744
+ if (failed.has(key)) continue;
2745
+ const sid = shortId(idea.id);
2746
+ const prefix = ideaTag(sid);
2747
+ let repoDir;
2748
+ if (idea.projectId) {
2749
+ const dir = findDirectoryForProject(config, idea.projectId, rootDir);
2750
+ if (!dir) {
2751
+ logWarn(prefix, `"${idea.title}": no linked directory found \u2014 skipping`);
2752
+ continue;
2753
+ }
2754
+ repoDir = dir;
2755
+ } else {
2756
+ repoDir = rootDir;
2757
+ }
2758
+ queued.add(key);
2759
+ ideaSlots--;
2760
+ if (dryRun) {
2761
+ logInfo(
2762
+ watchTag(),
2763
+ `${paint("yellow", "[dry-run]")} would dispatch idea "${paint("bold", idea.title)}" in ${paint("cyan", repoDir)}`
2764
+ );
2731
2765
  continue;
2732
2766
  }
2733
- repoDir = dir;
2734
- } else {
2735
- repoDir = rootDir;
2767
+ dispatchIdeaJob(idea, repoDir);
2736
2768
  }
2737
- queued.add(key);
2738
- ideaSlots--;
2739
- if (dryRun) {
2740
- logInfo(
2741
- watchTag(),
2742
- `${paint("yellow", "[dry-run]")} would dispatch idea "${paint("bold", idea.title)}" in ${paint("cyan", repoDir)}`
2743
- );
2744
- continue;
2769
+ let pendingScans = [];
2770
+ try {
2771
+ pendingScans = await api.get("/api/scans?status=pending&limit=5");
2772
+ } catch (err) {
2773
+ logError(watchTag(), `Failed to fetch pending scans: ${err.message}`);
2745
2774
  }
2746
- dispatchIdeaJob(idea, repoDir);
2747
- }
2748
- let pendingScans = [];
2749
- try {
2750
- pendingScans = await api.get("/api/scans?status=pending&limit=5");
2751
- } catch (err) {
2752
- logError(watchTag(), `Failed to fetch pending scans: ${err.message}`);
2753
- }
2754
- for (const scan of pendingScans) {
2755
- const key = `scan-${scan.id}`;
2756
- if (queued.has(key)) continue;
2757
- if (failed.has(key)) continue;
2758
- const sid = shortId(scan.id);
2759
- const prefix = `${paint("magenta", `[scan:${sid}]`)}`;
2760
- queued.add(key);
2761
- if (dryRun) {
2762
- logInfo(
2763
- watchTag(),
2764
- `${paint("yellow", "[dry-run]")} would run scan ${paint("yellow", sid)} for project ${paint("cyan", scan.projectId)}`
2765
- );
2766
- continue;
2775
+ for (const scan of pendingScans) {
2776
+ const key = `scan-${scan.id}`;
2777
+ if (queued.has(key)) continue;
2778
+ if (finishing.has(key)) continue;
2779
+ if (failed.has(key)) continue;
2780
+ const sid = shortId(scan.id);
2781
+ const prefix = `${paint("magenta", `[scan:${sid}]`)}`;
2782
+ queued.add(key);
2783
+ if (dryRun) {
2784
+ logInfo(
2785
+ watchTag(),
2786
+ `${paint("yellow", "[dry-run]")} would run scan ${paint("yellow", sid)} for project ${paint("cyan", scan.projectId)}`
2787
+ );
2788
+ continue;
2789
+ }
2790
+ dispatchScan(scan, prefix, key);
2767
2791
  }
2768
- dispatchScan(scan, prefix, key);
2792
+ } finally {
2793
+ pollRunning = false;
2769
2794
  }
2770
2795
  }
2771
2796
  async function shutdown() {
@@ -2944,9 +2969,8 @@ var subtaskCompleteCommand = new Command12("subtask-complete").description("Mark
2944
2969
  console.log(`\u2713 Subtask completed: ${subtask.title}`);
2945
2970
  });
2946
2971
 
2947
- // cli/commands/digest.ts
2972
+ // cli/commands/prototype.ts
2948
2973
  import { Command as Command13 } from "commander";
2949
- import { spawn as spawn5 } from "child_process";
2950
2974
  var c4 = {
2951
2975
  reset: "\x1B[0m",
2952
2976
  bold: "\x1B[1m",
@@ -2955,360 +2979,28 @@ var c4 = {
2955
2979
  green: "\x1B[32m",
2956
2980
  yellow: "\x1B[33m",
2957
2981
  red: "\x1B[31m",
2958
- magenta: "\x1B[35m",
2982
+ blue: "\x1B[34m",
2959
2983
  gray: "\x1B[90m"
2960
2984
  };
2961
2985
  function paint4(color, text) {
2962
2986
  return `${c4[color]}${text}${c4.reset}`;
2963
2987
  }
2964
- function timestamp2() {
2965
- return paint4("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
2966
- }
2967
- function digestTag() {
2968
- return paint4("magenta", "[digest]");
2969
- }
2970
- function shortId2(id) {
2971
- return id.slice(0, 8);
2972
- }
2973
- function log(msg) {
2974
- console.log(`${timestamp2()} ${digestTag()} ${msg}`);
2975
- }
2976
- function logOk(msg) {
2977
- console.log(`${timestamp2()} ${digestTag()} ${paint4("green", "\u2713")} ${msg}`);
2978
- }
2979
- function logErr(msg) {
2980
- console.error(`${timestamp2()} ${digestTag()} ${paint4("red", "\u2717")} ${msg}`);
2981
- }
2982
- function buildPrompt(messages) {
2983
- const emailsText = messages.map(
2984
- (msg, i) => `Email ${i + 1}:
2985
- ID: ${msg.id}
2986
- Subject: ${msg.subject}
2987
- From: ${msg.sender} <${msg.senderEmail}>
2988
- Date: ${msg.date}
2989
- Snippet: ${msg.snippet}
2990
- Body preview: ${msg.body.slice(0, 500)}`
2991
- ).join("\n\n---\n\n");
2992
- return `You are an email triage assistant. Analyze the following unread emails and produce a structured digest.
2993
-
2994
- For each email, determine:
2995
- 1. Is it relevant/actionable (requires attention, is personal, work-related, or important)?
2996
- 2. Or is it not relevant (newsletters, marketing, automated notifications, promotions)?
2997
- 3. Assign a category that groups it with similar emails (e.g. "Finance & Trading", "Real Estate", "Work", "Personal", "Promotions & Newsletters", "Travel", "Health", etc.).
2998
- 4. Write a 2-3 sentence summary of the email's content and what action (if any) is needed.
2999
-
3000
- Also write a 2-3 sentence executive summary of the overall inbox state.
3001
-
3002
- Here are the unread emails:
3003
-
3004
- ${emailsText}
3005
-
3006
- Respond with a JSON object in this exact format:
3007
- {
3008
- "summary": "2-3 sentence overview of the inbox",
3009
- "items": [
3010
- {
3011
- "emailId": "the email ID",
3012
- "summary": "2-3 sentence summary of this email's content and any action needed",
3013
- "isRelevant": true or false,
3014
- "relevanceReason": "brief explanation of why relevant or not",
3015
- "category": "short category label (2-4 words)"
3016
- }
3017
- ]
3018
- }
3019
-
3020
- Be conservative with "isRelevant: false" \u2014 only mark as not relevant if clearly promotional, newsletter, or automated. Personal emails, work emails, and anything requiring a response should be relevant. Group similar emails under the same category label.`;
3021
- }
3022
- function runAgent(prompt2, agent) {
3023
- return new Promise((resolve7, reject) => {
3024
- let bin;
3025
- let args;
3026
- if (agent === "codex") {
3027
- bin = "codex";
3028
- args = ["exec", "--full-auto", prompt2];
3029
- } else {
3030
- bin = "claude";
3031
- args = ["-p", "--dangerously-skip-permissions", prompt2];
3032
- }
3033
- const child = spawn5(bin, args, {
3034
- stdio: ["ignore", "pipe", "pipe"]
3035
- });
3036
- let output = "";
3037
- let errOutput = "";
3038
- child.stdout?.on("data", (d) => {
3039
- output += d.toString();
3040
- });
3041
- child.stderr?.on("data", (d) => {
3042
- errOutput += d.toString();
3043
- });
3044
- child.on("exit", (code) => {
3045
- if (code === 0) resolve7(output.trim());
3046
- else reject(new Error(`${bin} exited with code ${code}
3047
- ${errOutput.trim()}`));
3048
- });
3049
- });
3050
- }
3051
- async function processDigest(digest, agent) {
3052
- const sid = shortId2(digest.id);
3053
- if (digest.rawMessages.length === 0) {
3054
- await api.post(`/api/email-digest/${digest.id}/complete`, {
3055
- summary: "No unread emails found in your inbox.",
3056
- items: []
3057
- });
3058
- logOk(`${paint4("yellow", sid)} completed (no emails)`);
3059
- return;
3060
- }
3061
- log(`${paint4("yellow", sid)} analyzing ${paint4("cyan", String(digest.rawMessages.length))} emails\u2026`);
3062
- const prompt2 = buildPrompt(digest.rawMessages);
3063
- const output = await runAgent(prompt2, agent);
3064
- const jsonMatch = output.match(/\{[\s\S]*\}/);
3065
- if (!jsonMatch) {
3066
- throw new Error("Could not extract JSON from Claude output");
3067
- }
3068
- const parsed = JSON.parse(jsonMatch[0]);
3069
- const msgMap = new Map(digest.rawMessages.map((m) => [m.id, m]));
3070
- const items = parsed.items.map((item) => {
3071
- const msg = msgMap.get(item.emailId);
3072
- if (!msg) return null;
3073
- return {
3074
- emailId: msg.id,
3075
- threadId: msg.threadId,
3076
- subject: msg.subject,
3077
- sender: msg.sender,
3078
- senderEmail: msg.senderEmail,
3079
- date: msg.date,
3080
- snippet: msg.snippet,
3081
- summary: item.summary || msg.snippet,
3082
- isRelevant: item.isRelevant,
3083
- relevanceReason: item.relevanceReason,
3084
- category: item.category || (item.isRelevant ? "General" : "Promotions & Newsletters"),
3085
- archived: false
3086
- };
3087
- }).filter((item) => item !== null);
3088
- await api.post(`/api/email-digest/${digest.id}/complete`, {
3089
- summary: parsed.summary,
3090
- items
3091
- });
3092
- const relevantItems = items.filter((i) => i.isRelevant);
3093
- const noiseItems = items.filter((i) => !i.isRelevant);
3094
- logOk(
3095
- `${paint4("yellow", sid)} done \u2014 ${paint4("green", String(relevantItems.length))} relevant, ${paint4("gray", String(noiseItems.length))} not relevant`
3096
- );
3097
- console.log("");
3098
- console.log(` ${paint4("bold", "Summary:")} ${parsed.summary}`);
3099
- console.log("");
3100
- if (relevantItems.length > 0) {
3101
- console.log(` ${paint4("bold", paint4("green", "Important"))}`);
3102
- for (const item of relevantItems) {
3103
- console.log(` ${paint4("cyan", "\u2022")} ${item.subject} ${paint4("dim", `\u2014 ${item.sender}`)}`);
3104
- }
3105
- console.log("");
3106
- }
3107
- if (noiseItems.length > 0) {
3108
- console.log(` ${paint4("dim", "Not important")}`);
3109
- for (const item of noiseItems) {
3110
- console.log(` ${paint4("dim", `\u2022 ${item.subject} \u2014 ${item.sender}`)}`);
3111
- }
3112
- console.log("");
3113
- }
3114
- }
3115
- async function generateDigest() {
3116
- let result;
3117
- try {
3118
- result = await api.post("/api/email-digest/generate");
3119
- } catch (err) {
3120
- logErr(`Auto-generate failed: ${err.message}`);
3121
- return;
3122
- }
3123
- if (result.needsReauth) {
3124
- logErr("Auto-generate skipped: Gmail re-authentication required");
3125
- return;
3126
- }
3127
- if (result.status === "completed") {
3128
- log(`Auto-generated digest ${paint4("yellow", shortId2(result.id))} \u2014 no new emails`);
3129
- } else {
3130
- log(`Auto-generated digest ${paint4("yellow", shortId2(result.id))} \u2014 queued for processing`);
3131
- }
3132
- }
3133
- var digestCommand = new Command13("digest").description("Process pending email digests using an AI coding agent (run alongside mr watch)").option("--interval <seconds>", "Polling interval in seconds", "15").option("--generate-interval <seconds>", "How often to auto-generate new digests", "3600").option("--once", "Process pending digests once and exit", false).option("--agent <agent>", "AI agent to use: claude or codex", "claude").action(async (opts) => {
3134
- const intervalMs = parseInt(opts.interval, 10) * 1e3;
3135
- const generateIntervalMs = parseInt(opts.generateInterval, 10) * 1e3;
3136
- const once = opts.once;
3137
- const agent = opts.agent === "codex" ? "codex" : "claude";
3138
- const processing = /* @__PURE__ */ new Set();
3139
- const banner = [
3140
- ``,
3141
- paint4("magenta", ` \u2554\u2566\u2557\u2566\u2550\u2557 \u2554\u2566\u2557\u2566\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2566\u2557`),
3142
- paint4("magenta", ` \u2551\u2551\u2551\u2560\u2566\u255D \u2551\u2551\u2551\u2551 \u2566\u2551\u2563 \u255A\u2550\u2557 \u2551`),
3143
- paint4("magenta", ` \u2569 \u2569\u2569\u255A\u2550 \u2550\u2569\u255D\u2569\u255A\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u255D \u2569`),
3144
- paint4("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
3145
- paint4("dim", ` email digest processor \xB7 powered by ${agent}`),
3146
- ``
3147
- ].join("\n");
3148
- console.log(banner);
3149
- console.log(
3150
- ` interval=${paint4("cyan", opts.interval + "s")} generate-interval=${paint4("cyan", opts.generateInterval + "s")}${once ? ` ${paint4("yellow", "once")}` : ""}
3151
- `
3152
- );
3153
- async function poll() {
3154
- let pending;
3155
- try {
3156
- pending = await api.get("/api/email-digest/pending");
3157
- } catch (err) {
3158
- logErr(`Failed to fetch pending digests: ${err.message}`);
3159
- return;
3160
- }
3161
- if (pending.length === 0) return;
3162
- const digest = pending[pending.length - 1];
3163
- if (processing.has(digest.id)) return;
3164
- log(`Processing digest ${paint4("yellow", shortId2(digest.id))} (${paint4("cyan", String(digest.rawMessages.length))} emails)`);
3165
- processing.add(digest.id);
3166
- processDigest(digest, agent).catch((err) => logErr(`${shortId2(digest.id)} failed: ${err.message}`)).finally(() => processing.delete(digest.id));
3167
- }
3168
- process.on("SIGINT", () => {
3169
- console.log(`
3170
- ${timestamp2()} ${digestTag()} Shutting down\u2026`);
3171
- process.exit(0);
3172
- });
3173
- await generateDigest();
3174
- await poll();
3175
- if (once) {
3176
- const wait = () => new Promise((resolve7) => {
3177
- if (processing.size === 0) return resolve7();
3178
- const check = setInterval(() => {
3179
- if (processing.size === 0) {
3180
- clearInterval(check);
3181
- resolve7();
3182
- }
3183
- }, 500);
3184
- });
3185
- await wait();
3186
- } else {
3187
- setInterval(poll, intervalMs);
3188
- setInterval(generateDigest, generateIntervalMs);
3189
- }
3190
- });
3191
-
3192
- // cli/commands/up.ts
3193
- import { Command as Command14 } from "commander";
3194
- import { spawn as spawn6 } from "child_process";
3195
- import { resolve as resolve2 } from "path";
3196
- import { fileURLToPath as fileURLToPath2 } from "url";
3197
- var c5 = {
3198
- reset: "\x1B[0m",
3199
- bold: "\x1B[1m",
3200
- dim: "\x1B[2m",
3201
- cyan: "\x1B[36m",
3202
- green: "\x1B[32m",
3203
- yellow: "\x1B[33m",
3204
- red: "\x1B[31m",
3205
- magenta: "\x1B[35m",
3206
- gray: "\x1B[90m"
3207
- };
3208
- function paint5(color, text) {
3209
- return `${c5[color]}${text}${c5.reset}`;
3210
- }
3211
- function timestamp3() {
3212
- return paint5("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
3213
- }
3214
- function upTag() {
3215
- return paint5("cyan", "[up]");
3216
- }
3217
- function log2(msg) {
3218
- console.log(`${timestamp3()} ${upTag()} ${msg}`);
3219
- }
3220
- function logErr2(msg) {
3221
- console.error(`${timestamp3()} ${upTag()} ${paint5("red", "\u2717")} ${msg}`);
3222
- }
3223
- function spawnMr(args) {
3224
- const entry = process.argv[1];
3225
- const child = spawn6(process.execPath, [entry, ...args], {
3226
- stdio: ["ignore", "pipe", "pipe"]
3227
- });
3228
- return child;
3229
- }
3230
- var upCommand = new Command14("up").description("Run mr watch and mr digest together with a single command").option("--interval <seconds>", "Polling interval for both watch and digest", "15").option("--watch-interval <seconds>", "Override polling interval for mr watch").option("--digest-interval <seconds>", "Override polling interval for mr digest").option("--dry-run", "Pass --dry-run to mr watch", false).option("--plan-approval", "Pass --plan-approval to mr watch", false).option("--root <dir>", "Pass --root to mr watch (default: cwd)").option("--agent <agent>", "AI agent to use: claude or codex", "claude").action((opts) => {
3231
- const watchInterval = opts.watchInterval ?? opts.interval;
3232
- const digestInterval = opts.digestInterval ?? opts.interval;
3233
- const agent = opts.agent === "codex" ? "codex" : "claude";
3234
- const watchArgs = ["watch", "--interval", watchInterval, "--agent", agent];
3235
- if (opts.dryRun) watchArgs.push("--dry-run");
3236
- if (opts.planApproval) watchArgs.push("--plan-approval");
3237
- if (opts.root) watchArgs.push("--root", opts.root);
3238
- const digestArgs = ["digest", "--interval", digestInterval, "--agent", agent];
3239
- const banner = [
3240
- ``,
3241
- paint5("cyan", ` \u2554\u2566\u2557\u2566\u2550\u2557 \u2566 \u2566\u2554\u2550\u2557`),
3242
- paint5("magenta", ` \u2551\u2551\u2551\u2560\u2566\u255D \u2551 \u2551\u2560\u2550\u255D`),
3243
- paint5("cyan", ` \u2569 \u2569\u2569\u255A\u2550 \u255A\u2550\u255D\u2569 `),
3244
- paint5("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
3245
- paint5("dim", ` mr watch + mr digest \xB7 powered by ${agent}`),
3246
- ``
3247
- ].join("\n");
3248
- console.log(banner);
3249
- const flags = [
3250
- `watch-interval=${paint5("cyan", watchInterval + "s")}`,
3251
- `digest-interval=${paint5("cyan", digestInterval + "s")}`,
3252
- `agent=${paint5("cyan", agent)}`,
3253
- ...opts.dryRun ? [paint5("yellow", "dry-run")] : [],
3254
- ...opts.planApproval ? [paint5("yellow", "plan-approval")] : [],
3255
- ...opts.root ? [`root=${paint5("cyan", resolve2(opts.root))}`] : []
3256
- ].join(" ");
3257
- console.log(` ${flags}
3258
- `);
3259
- const watchProc = spawnMr(watchArgs);
3260
- const digestProc = spawnMr(digestArgs);
3261
- for (const [proc, label] of [[watchProc, "watch"], [digestProc, "digest"]]) {
3262
- proc.stdout?.on("data", (d) => process.stdout.write(d));
3263
- proc.stderr?.on("data", (d) => process.stderr.write(d));
3264
- proc.on("exit", (code, signal) => {
3265
- if (signal !== "SIGTERM" && signal !== "SIGINT") {
3266
- logErr2(`mr ${label} exited unexpectedly (code=${code ?? "?"}, signal=${signal ?? "none"})`);
3267
- }
3268
- });
3269
- }
3270
- function shutdown() {
3271
- log2("Shutting down\u2026");
3272
- watchProc.kill("SIGTERM");
3273
- digestProc.kill("SIGTERM");
3274
- setTimeout(() => process.exit(0), 500);
3275
- }
3276
- process.on("SIGINT", shutdown);
3277
- process.on("SIGTERM", shutdown);
3278
- });
3279
-
3280
- // cli/commands/prototype.ts
3281
- import { Command as Command15 } from "commander";
3282
- var c6 = {
3283
- reset: "\x1B[0m",
3284
- bold: "\x1B[1m",
3285
- dim: "\x1B[2m",
3286
- cyan: "\x1B[36m",
3287
- green: "\x1B[32m",
3288
- yellow: "\x1B[33m",
3289
- red: "\x1B[31m",
3290
- blue: "\x1B[34m",
3291
- gray: "\x1B[90m"
3292
- };
3293
- function paint6(color, text) {
3294
- return `${c6[color]}${text}${c6.reset}`;
3295
- }
3296
2988
  function statusBadge(status) {
3297
2989
  switch (status) {
3298
2990
  case "pending":
3299
- return paint6("gray", "\u25CB pending");
2991
+ return paint4("gray", "\u25CB pending");
3300
2992
  case "in_progress":
3301
- return paint6("cyan", "\u27F3 in_progress");
2993
+ return paint4("cyan", "\u27F3 in_progress");
3302
2994
  case "completed":
3303
- return paint6("green", "\u2713 completed");
2995
+ return paint4("green", "\u2713 completed");
3304
2996
  case "failed":
3305
- return paint6("red", "\u2717 failed");
2997
+ return paint4("red", "\u2717 failed");
3306
2998
  default:
3307
- return paint6("gray", status);
2999
+ return paint4("gray", status);
3308
3000
  }
3309
3001
  }
3310
- var prototypeCommand = new Command15("prototype").description("Manage prototypes").addCommand(
3311
- new Command15("list").description("List prototypes for the linked project").option("--all", "Show prototypes for all projects").action(async (opts) => {
3002
+ var prototypeCommand = new Command13("prototype").description("Manage prototypes").addCommand(
3003
+ new Command13("list").description("List prototypes for the linked project").option("--all", "Show prototypes for all projects").action(async (opts) => {
3312
3004
  const params = new URLSearchParams();
3313
3005
  if (!opts.all) {
3314
3006
  const projectId = getLinkedProjectId();
@@ -3320,21 +3012,21 @@ var prototypeCommand = new Command15("prototype").description("Manage prototypes
3320
3012
  }
3321
3013
  const prototypes = await api.get(`/api/prototypes?${params.toString()}`);
3322
3014
  if (prototypes.length === 0) {
3323
- console.log(paint6("gray", "No prototypes found."));
3015
+ console.log(paint4("gray", "No prototypes found."));
3324
3016
  return;
3325
3017
  }
3326
3018
  console.log();
3327
3019
  for (const p of prototypes) {
3328
3020
  const date = new Date(p.createdAt).toLocaleDateString();
3329
3021
  console.log(
3330
- ` ${paint6("bold", p.title)} ${statusBadge(p.status)} ${paint6("gray", p.id.slice(0, 8))} ${paint6("dim", date)}`
3022
+ ` ${paint4("bold", p.title)} ${statusBadge(p.status)} ${paint4("gray", p.id.slice(0, 8))} ${paint4("dim", date)}`
3331
3023
  );
3332
- console.log(` ${paint6("dim", p.prompt.slice(0, 80) + (p.prompt.length > 80 ? "\u2026" : ""))}`);
3024
+ console.log(` ${paint4("dim", p.prompt.slice(0, 80) + (p.prompt.length > 80 ? "\u2026" : ""))}`);
3333
3025
  console.log();
3334
3026
  }
3335
3027
  })
3336
3028
  ).addCommand(
3337
- new Command15("create").description("Create a new prototype").argument("<title>", "Title of the prototype").requiredOption("--prompt <prompt>", "Design description / prompt").option("--project <projectId>", "Project ID (defaults to linked project)").option("--variants <count>", "Number of variants to generate (1-50)", "5").action(async (title, opts) => {
3029
+ new Command13("create").description("Create a new prototype").argument("<title>", "Title of the prototype").requiredOption("--prompt <prompt>", "Design description / prompt").option("--project <projectId>", "Project ID (defaults to linked project)").option("--variants <count>", "Number of variants to generate (1-50)", "5").action(async (title, opts) => {
3338
3030
  const projectId = opts.project ?? getLinkedProjectId();
3339
3031
  if (!projectId) {
3340
3032
  console.error('No project linked. Run "mr link <project-id>" or use --project.');
@@ -3348,37 +3040,37 @@ var prototypeCommand = new Command15("prototype").description("Manage prototypes
3348
3040
  projectId
3349
3041
  });
3350
3042
  console.log();
3351
- console.log(` ${paint6("green", "\u2713")} Created prototype: ${paint6("bold", prototype.title)}`);
3352
- console.log(` ${paint6("gray", "ID:")} ${prototype.id}`);
3353
- console.log(` ${paint6("cyan", "\u27F3")} Generation will begin automatically via the watch agent.`);
3043
+ console.log(` ${paint4("green", "\u2713")} Created prototype: ${paint4("bold", prototype.title)}`);
3044
+ console.log(` ${paint4("gray", "ID:")} ${prototype.id}`);
3045
+ console.log(` ${paint4("cyan", "\u27F3")} Generation will begin automatically via the watch agent.`);
3354
3046
  console.log();
3355
3047
  })
3356
3048
  ).addCommand(
3357
- new Command15("start").description("Start prototype generation (sets status to in_progress)").argument("<id>", "Prototype ID").action(async (id) => {
3049
+ new Command13("start").description("Start prototype generation (sets status to in_progress)").argument("<id>", "Prototype ID").action(async (id) => {
3358
3050
  const prototype = await api.patch(`/api/prototypes/${id}`, {
3359
3051
  status: "in_progress"
3360
3052
  });
3361
3053
  console.log();
3362
- console.log(` ${paint6("cyan", "\u27F3")} Started: ${paint6("bold", prototype.title)}`);
3363
- console.log(` ${paint6("gray", "The watch agent will pick this up shortly.")}`);
3054
+ console.log(` ${paint4("cyan", "\u27F3")} Started: ${paint4("bold", prototype.title)}`);
3055
+ console.log(` ${paint4("gray", "The watch agent will pick this up shortly.")}`);
3364
3056
  console.log();
3365
3057
  })
3366
3058
  ).addCommand(
3367
- new Command15("retry").description("Retry a failed prototype").argument("<id>", "Prototype ID").action(async (id) => {
3059
+ new Command13("retry").description("Retry a failed prototype").argument("<id>", "Prototype ID").action(async (id) => {
3368
3060
  const prototype = await api.patch(`/api/prototypes/${id}`, {
3369
3061
  status: "in_progress",
3370
3062
  files: null
3371
3063
  });
3372
3064
  console.log();
3373
- console.log(` ${paint6("cyan", "\u27F3")} Retrying: ${paint6("bold", prototype.title)}`);
3065
+ console.log(` ${paint4("cyan", "\u27F3")} Retrying: ${paint4("bold", prototype.title)}`);
3374
3066
  console.log();
3375
3067
  })
3376
3068
  );
3377
3069
 
3378
3070
  // cli/commands/setup.ts
3379
- import { Command as Command16 } from "commander";
3071
+ import { Command as Command14 } from "commander";
3380
3072
  import { exec as exec2 } from "child_process";
3381
- var c7 = {
3073
+ var c5 = {
3382
3074
  reset: "\x1B[0m",
3383
3075
  bold: "\x1B[1m",
3384
3076
  dim: "\x1B[2m",
@@ -3389,8 +3081,8 @@ var c7 = {
3389
3081
  magenta: "\x1B[35m",
3390
3082
  gray: "\x1B[90m"
3391
3083
  };
3392
- function paint7(color, text) {
3393
- return `${c7[color]}${text}${c7.reset}`;
3084
+ function paint5(color, text) {
3085
+ return `${c5[color]}${text}${c5.reset}`;
3394
3086
  }
3395
3087
  function commandExists(cmd) {
3396
3088
  return new Promise((resolve7) => {
@@ -3634,60 +3326,60 @@ async function checkApiConnectivity() {
3634
3326
  }
3635
3327
  }
3636
3328
  function printResults(checks) {
3637
- const maxNameLen = Math.max(...checks.map((c12) => c12.name.length));
3329
+ const maxNameLen = Math.max(...checks.map((c10) => c10.name.length));
3638
3330
  let allOk = true;
3639
3331
  for (const check of checks) {
3640
3332
  const isOptional = check.optional ?? false;
3641
- const icon = check.ok ? paint7("green", "\u2713") : isOptional ? paint7("yellow", "\u25CB") : paint7("red", "\u2717");
3333
+ const icon = check.ok ? paint5("green", "\u2713") : isOptional ? paint5("yellow", "\u25CB") : paint5("red", "\u2717");
3642
3334
  const name = check.name.padEnd(maxNameLen);
3643
- const msg = check.ok ? paint7("green", check.message) : isOptional ? paint7("yellow", check.message) : paint7("red", check.message);
3335
+ const msg = check.ok ? paint5("green", check.message) : isOptional ? paint5("yellow", check.message) : paint5("red", check.message);
3644
3336
  console.log(` ${icon} ${name} ${msg}`);
3645
3337
  if (!check.ok && !isOptional) allOk = false;
3646
3338
  }
3647
3339
  return allOk;
3648
3340
  }
3649
3341
  async function autoFix(checks, agent) {
3650
- const { spawn: spawn10 } = await import("child_process");
3651
- const ghInstalled = checks.find((c12) => c12.name === "GitHub CLI (gh)").ok;
3652
- const ghAuthed = checks.find((c12) => c12.name === "GitHub CLI auth").ok;
3653
- const mrAuthed = checks.find((c12) => c12.name === "Mr. Manager CLI auth").ok;
3654
- const claudeCheck = checks.find((c12) => c12.name === "Claude Code (claude)");
3342
+ const { spawn: spawn8 } = await import("child_process");
3343
+ const ghInstalled = checks.find((c10) => c10.name === "GitHub CLI (gh)").ok;
3344
+ const ghAuthed = checks.find((c10) => c10.name === "GitHub CLI auth").ok;
3345
+ const mrAuthed = checks.find((c10) => c10.name === "Mr. Manager CLI auth").ok;
3346
+ const claudeCheck = checks.find((c10) => c10.name === "Claude Code (claude)");
3655
3347
  if (claudeCheck && !claudeCheck.ok && agent === "claude") {
3656
- console.log(paint7("cyan", " Installing Claude Code..."));
3657
- console.log(paint7("dim", " Running: curl -fsSL https://claude.ai/install.sh | bash"));
3348
+ console.log(paint5("cyan", " Installing Claude Code..."));
3349
+ console.log(paint5("dim", " Running: curl -fsSL https://claude.ai/install.sh | bash"));
3658
3350
  await new Promise((resolve7) => {
3659
- const child = spawn10("bash", ["-c", "curl -fsSL https://claude.ai/install.sh | bash"], { stdio: "inherit" });
3351
+ const child = spawn8("bash", ["-c", "curl -fsSL https://claude.ai/install.sh | bash"], { stdio: "inherit" });
3660
3352
  child.on("exit", () => resolve7());
3661
3353
  });
3662
3354
  console.log("");
3663
3355
  }
3664
3356
  if (ghInstalled && !ghAuthed) {
3665
- console.log(paint7("cyan", " Running gh auth login..."));
3357
+ console.log(paint5("cyan", " Running gh auth login..."));
3666
3358
  await new Promise((resolve7) => {
3667
- const child = spawn10("gh", ["auth", "login"], { stdio: "inherit" });
3359
+ const child = spawn8("gh", ["auth", "login"], { stdio: "inherit" });
3668
3360
  child.on("exit", () => resolve7());
3669
3361
  });
3670
3362
  console.log("");
3671
3363
  }
3672
3364
  if (!mrAuthed) {
3673
- console.log(paint7("cyan", " Running mr login..."));
3365
+ console.log(paint5("cyan", " Running mr login..."));
3674
3366
  const entry = process.argv[1];
3675
3367
  await new Promise((resolve7) => {
3676
- const child = spawn10(process.execPath, [entry, "login"], { stdio: "inherit" });
3368
+ const child = spawn8(process.execPath, [entry, "login"], { stdio: "inherit" });
3677
3369
  child.on("exit", () => resolve7());
3678
3370
  });
3679
3371
  console.log("");
3680
3372
  }
3681
3373
  }
3682
- var setupCommand = new Command16("setup").description("Check that all dependencies for mr watch are installed and configured").option("--fix", "Attempt to auto-fix issues where possible", false).option("--agent <agent>", "AI agent to check: claude, codex, or gemini (default: claude)", "claude").action(async (opts) => {
3374
+ var setupCommand = new Command14("setup").description("Check that all dependencies for mr watch are installed and configured").option("--fix", "Attempt to auto-fix issues where possible", false).option("--agent <agent>", "AI agent to check: claude, codex, or gemini (default: claude)", "claude").action(async (opts) => {
3683
3375
  const agent = opts.agent === "codex" ? "codex" : opts.agent === "gemini" ? "gemini" : "claude";
3684
3376
  const banner = [
3685
3377
  ``,
3686
- paint7("cyan", ` \u2554\u2566\u2557\u2551\u2550\u2557 \u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2566\u2557\u2551 \u2551\u2554\u2550\u2557`),
3687
- paint7("magenta", ` \u2551\u2551\u2551\u2560\u2566\u2563 \u255A\u2550\u2557\u2551\u2563 \u2551 \u2551 \u2551\u2560\u2550\u2563`),
3688
- paint7("cyan", ` \u2569 \u2569\u2569\u255A\u2550 \u255A\u2550\u255D\u255A\u2550\u255D \u2569 \u255A\u2550\u255D\u2569 `),
3689
- paint7("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
3690
- paint7("dim", ` environment check for mr watch (agent: ${agent})`),
3378
+ paint5("cyan", ` \u2554\u2566\u2557\u2551\u2550\u2557 \u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2566\u2557\u2551 \u2551\u2554\u2550\u2557`),
3379
+ paint5("magenta", ` \u2551\u2551\u2551\u2560\u2566\u2563 \u255A\u2550\u2557\u2551\u2563 \u2551 \u2551 \u2551\u2560\u2550\u2563`),
3380
+ paint5("cyan", ` \u2569 \u2569\u2569\u255A\u2550 \u255A\u2550\u255D\u255A\u2550\u255D \u2569 \u255A\u2550\u255D\u2569 `),
3381
+ paint5("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
3382
+ paint5("dim", ` environment check for mr watch (agent: ${agent})`),
3691
3383
  ``
3692
3384
  ].join("\n");
3693
3385
  console.log(banner);
@@ -3707,18 +3399,18 @@ var setupCommand = new Command16("setup").description("Check that all dependenci
3707
3399
  const allOk = printResults(checks);
3708
3400
  console.log("");
3709
3401
  if (allOk) {
3710
- console.log(paint7("green", " All checks passed! You're ready to run mr watch."));
3402
+ console.log(paint5("green", " All checks passed! You're ready to run mr watch."));
3711
3403
  if (agent === "claude") {
3712
- console.log(paint7("dim", " Tip: check other agents with --agent codex or --agent gemini"));
3404
+ console.log(paint5("dim", " Tip: check other agents with --agent codex or --agent gemini"));
3713
3405
  }
3714
3406
  console.log("");
3715
3407
  return;
3716
3408
  }
3717
- const fixes = checks.filter((c12) => !c12.ok && c12.fix && !c12.optional);
3409
+ const fixes = checks.filter((c10) => !c10.ok && c10.fix && !c10.optional);
3718
3410
  if (fixes.length > 0) {
3719
- console.log(paint7("yellow", " To fix:"));
3411
+ console.log(paint5("yellow", " To fix:"));
3720
3412
  for (const fix of fixes) {
3721
- console.log(` ${paint7("dim", "\u2192")} ${paint7("bold", fix.name)}: ${fix.fix}`);
3413
+ console.log(` ${paint5("dim", "\u2192")} ${paint5("bold", fix.name)}: ${fix.fix}`);
3722
3414
  }
3723
3415
  console.log("");
3724
3416
  }
@@ -3729,8 +3421,8 @@ var setupCommand = new Command16("setup").description("Check that all dependenci
3729
3421
  });
3730
3422
 
3731
3423
  // cli/commands/update.ts
3732
- import { Command as Command17 } from "commander";
3733
- var updateCommand = new Command17("update").description("Post a status update to a task (used by agents to report progress)").argument("<task-id>", "Task ID").argument("<message>", "Status update message").option("--source <source>", "Update source: agent, system, or user", "agent").action(async (taskId, message, opts) => {
3424
+ import { Command as Command15 } from "commander";
3425
+ var updateCommand = new Command15("update").description("Post a status update to a task (used by agents to report progress)").argument("<task-id>", "Task ID").argument("<message>", "Status update message").option("--source <source>", "Update source: agent, system, or user", "agent").action(async (taskId, message, opts) => {
3734
3426
  await api.post(`/api/tasks/${taskId}/updates`, {
3735
3427
  message,
3736
3428
  source: opts.source
@@ -3739,11 +3431,11 @@ var updateCommand = new Command17("update").description("Post a status update to
3739
3431
  });
3740
3432
 
3741
3433
  // cli/commands/screenshot.ts
3742
- import { Command as Command18 } from "commander";
3743
- import { readFileSync as readFileSync6, existsSync as existsSync7, unlinkSync as unlinkSync2 } from "fs";
3434
+ import { Command as Command16 } from "commander";
3435
+ import { readFileSync as readFileSync6, existsSync as existsSync8, unlinkSync as unlinkSync2 } from "fs";
3744
3436
  import { join as join8 } from "path";
3745
3437
  import { tmpdir } from "os";
3746
- var screenshotCommand = new Command18("screenshot").description(
3438
+ var screenshotCommand = new Command16("screenshot").description(
3747
3439
  "Take or attach a screenshot to a task update (agents use this to show their work)"
3748
3440
  ).argument("<task-id>", "Task ID").argument("[file]", "Path to an image file (if omitted, uses headless browser to screenshot the app)").option("-m, --message <message>", "Optional message to include with the screenshot").option("-u, --url <url>", "Custom URL to screenshot (defaults to the task's project page)").action(async (taskId, file, opts) => {
3749
3441
  let filePath = file;
@@ -3780,13 +3472,13 @@ var screenshotCommand = new Command18("screenshot").description(
3780
3472
  console.error("[screenshot] Make sure the browse daemon is set up: mr browse setup");
3781
3473
  process.exit(1);
3782
3474
  }
3783
- if (!existsSync7(tempFile)) {
3475
+ if (!existsSync8(tempFile)) {
3784
3476
  console.error("[screenshot] Screenshot file was not created");
3785
3477
  process.exit(1);
3786
3478
  }
3787
3479
  filePath = tempFile;
3788
3480
  }
3789
- if (!existsSync7(filePath)) {
3481
+ if (!existsSync8(filePath)) {
3790
3482
  console.error(`File not found: ${filePath}`);
3791
3483
  process.exit(1);
3792
3484
  }
@@ -3828,16 +3520,16 @@ var screenshotCommand = new Command18("screenshot").description(
3828
3520
  source: "agent"
3829
3521
  });
3830
3522
  console.log(`\u2713 Screenshot uploaded and attached to task`);
3831
- if (tempFile && existsSync7(tempFile)) {
3523
+ if (tempFile && existsSync8(tempFile)) {
3832
3524
  unlinkSync2(tempFile);
3833
3525
  }
3834
3526
  });
3835
3527
 
3836
3528
  // cli/commands/resume.ts
3837
- import { Command as Command19 } from "commander";
3838
- import { spawn as spawn7 } from "child_process";
3529
+ import { Command as Command17 } from "commander";
3530
+ import { spawn as spawn5 } from "child_process";
3839
3531
  import { resolve as resolve3 } from "path";
3840
- var c8 = {
3532
+ var c6 = {
3841
3533
  reset: "\x1B[0m",
3842
3534
  bold: "\x1B[1m",
3843
3535
  dim: "\x1B[2m",
@@ -3848,15 +3540,15 @@ var c8 = {
3848
3540
  magenta: "\x1B[35m",
3849
3541
  gray: "\x1B[90m"
3850
3542
  };
3851
- function paint8(color, text) {
3852
- return `${c8[color]}${text}${c8.reset}`;
3543
+ function paint6(color, text) {
3544
+ return `${c6[color]}${text}${c6.reset}`;
3853
3545
  }
3854
- var resumeCommand = new Command19("resume").description("Resume an interactive Claude session for a task (non-headless)").argument("<task-id>", "Task ID whose Claude session to resume").option("--dir <directory>", "Override the working directory for the session").action(async (taskId, opts) => {
3546
+ var resumeCommand = new Command17("resume").description("Resume an interactive Claude session for a task (non-headless)").argument("<task-id>", "Task ID whose Claude session to resume").option("--dir <directory>", "Override the working directory for the session").action(async (taskId, opts) => {
3855
3547
  const task = await api.get(`/api/tasks/${taskId}`);
3856
3548
  if (!task.claudeSessionId) {
3857
3549
  console.error(
3858
3550
  `
3859
- ${paint8("red", "\u2717")} No Claude session found for task ${paint8("dim", taskId.slice(0, 8))}`
3551
+ ${paint6("red", "\u2717")} No Claude session found for task ${paint6("dim", taskId.slice(0, 8))}`
3860
3552
  );
3861
3553
  console.error(
3862
3554
  ` The task may not have been dispatched yet.
@@ -3867,16 +3559,16 @@ var resumeCommand = new Command19("resume").description("Resume an interactive C
3867
3559
  if (task.status === "completed" || task.status === "todo") {
3868
3560
  console.error(
3869
3561
  `
3870
- ${paint8("yellow", "\u26A0")} Task ${paint8("dim", taskId.slice(0, 8))} has already completed.`
3562
+ ${paint6("yellow", "\u26A0")} Task ${paint6("dim", taskId.slice(0, 8))} has already completed.`
3871
3563
  );
3872
3564
  console.error(
3873
- ` Session ID: ${paint8("cyan", task.claudeSessionId)}`
3565
+ ` Session ID: ${paint6("cyan", task.claudeSessionId)}`
3874
3566
  );
3875
3567
  console.error(
3876
- ` View the session log in the Mr. Manager web UI, or run:`
3568
+ ` To resume the session, run:`
3877
3569
  );
3878
3570
  console.error(
3879
- ` ${paint8("dim", `claude --resume ${task.claudeSessionId}`)}
3571
+ ` ${paint6("dim", `claude --resume ${task.claudeSessionId}`)}
3880
3572
  `
3881
3573
  );
3882
3574
  process.exit(0);
@@ -3895,16 +3587,16 @@ var resumeCommand = new Command19("resume").description("Resume an interactive C
3895
3587
  const sid = taskId.slice(0, 8);
3896
3588
  console.log([
3897
3589
  ``,
3898
- paint8("magenta", ` \u2554\u2566\u2557\u2566\u2550\u2557 `) + paint8("bold", "resume"),
3899
- paint8("magenta", ` \u2551\u2551\u2551\u2560\u2566\u255D `) + paint8("dim", "\u2500".repeat(44)),
3900
- paint8("magenta", ` \u2569 \u2569\u2569\u255A\u2550 `) + task.title,
3901
- ` ${paint8("gray", sid)}`,
3590
+ paint6("magenta", ` \u2554\u2566\u2557\u2566\u2550\u2557 `) + paint6("bold", "resume"),
3591
+ paint6("magenta", ` \u2551\u2551\u2551\u2560\u2566\u255D `) + paint6("dim", "\u2500".repeat(44)),
3592
+ paint6("magenta", ` \u2569 \u2569\u2569\u255A\u2550 `) + task.title,
3593
+ ` ${paint6("gray", sid)}`,
3902
3594
  ``,
3903
- ` ${paint8("dim", "session")} ${paint8("cyan", task.claudeSessionId)}`,
3904
- ` ${paint8("dim", "cwd")} ${paint8("cyan", cwd)}`,
3595
+ ` ${paint6("dim", "session")} ${paint6("cyan", task.claudeSessionId)}`,
3596
+ ` ${paint6("dim", "cwd")} ${paint6("cyan", cwd)}`,
3905
3597
  ``
3906
3598
  ].join("\n"));
3907
- const child = spawn7("claude", ["--resume", task.claudeSessionId], {
3599
+ const child = spawn5("claude", ["--resume", task.claudeSessionId], {
3908
3600
  cwd,
3909
3601
  stdio: "inherit"
3910
3602
  });
@@ -3913,16 +3605,16 @@ var resumeCommand = new Command19("resume").description("Resume an interactive C
3913
3605
  });
3914
3606
  child.on("error", (err) => {
3915
3607
  console.error(`
3916
- ${paint8("red", "\u2717")} Failed to launch Claude: ${err.message}
3608
+ ${paint6("red", "\u2717")} Failed to launch Claude: ${err.message}
3917
3609
  `);
3918
3610
  process.exit(1);
3919
3611
  });
3920
3612
  });
3921
3613
 
3922
3614
  // cli/commands/browse.ts
3923
- import { Command as Command20 } from "commander";
3924
- import { execSync as execSync5, spawn as spawn8 } from "child_process";
3925
- import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
3615
+ import { Command as Command18 } from "commander";
3616
+ import { execSync as execSync5, spawn as spawn6 } from "child_process";
3617
+ import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
3926
3618
  import { join as join9 } from "path";
3927
3619
  var BROWSE_DIR2 = join9(import.meta.dirname, "..", "..", "browse");
3928
3620
  function isProcessAlive(pid) {
@@ -3962,7 +3654,7 @@ async function ensureDevServer() {
3962
3654
  }
3963
3655
  const port = await findAvailablePort2(3e3);
3964
3656
  console.log(`[browse] Starting dev server on port ${port}...`);
3965
- const devProc = spawn8("npm", ["run", "dev", "--", "--port", String(port)], {
3657
+ const devProc = spawn6("npm", ["run", "dev", "--", "--port", String(port)], {
3966
3658
  stdio: ["ignore", "pipe", "pipe"],
3967
3659
  detached: true,
3968
3660
  cwd: join9(import.meta.dirname, "..", ".."),
@@ -3984,7 +3676,7 @@ async function ensureDevServer() {
3984
3676
  }
3985
3677
  throw new Error("Dev server failed to start within 30s");
3986
3678
  }
3987
- var browseCommand = new Command20("browse").description("Control a headless browser for QA and testing").argument("[command]", "Browse command (goto, click, fill, screenshot, etc.)").argument("[args...]", "Command arguments").option(
3679
+ var browseCommand = new Command18("browse").description("Control a headless browser for QA and testing").argument("[command]", "Browse command (goto, click, fill, screenshot, etc.)").argument("[args...]", "Command arguments").option(
3988
3680
  "--task-id <id>",
3989
3681
  "Attach screenshot to a task update (only for screenshot command)"
3990
3682
  ).option("--dev", "Auto-start local dev server before browsing").allowUnknownOption(true).action(
@@ -4027,7 +3719,7 @@ var browseCommand = new Command20("browse").description("Control a headless brow
4027
3719
  }
4028
3720
  if (command === "screenshot" && opts.taskId) {
4029
3721
  const screenshotPath = stdout.match(/Screenshot saved: (.+)/)?.[1];
4030
- if (!screenshotPath || !existsSync8(screenshotPath)) {
3722
+ if (!screenshotPath || !existsSync9(screenshotPath)) {
4031
3723
  console.error("[browse] Could not find screenshot file");
4032
3724
  process.exit(1);
4033
3725
  }
@@ -4067,12 +3759,12 @@ var browseCommand = new Command20("browse").description("Control a headless brow
4067
3759
  );
4068
3760
 
4069
3761
  // cli/commands/set-path.ts
4070
- import { Command as Command21 } from "commander";
3762
+ import { Command as Command19 } from "commander";
4071
3763
  import { resolve as resolve4 } from "path";
4072
- import { existsSync as existsSync9 } from "fs";
4073
- var setPathCommand = new Command21("set-path").description("Set or update the local repo path for a project").argument("<project-id>", "Project ID").argument("<path>", "Absolute or relative path to the local repo").action(async (projectId, pathArg) => {
3764
+ import { existsSync as existsSync10 } from "fs";
3765
+ var setPathCommand = new Command19("set-path").description("Set or update the local repo path for a project").argument("<project-id>", "Project ID").argument("<path>", "Absolute or relative path to the local repo").action(async (projectId, pathArg) => {
4074
3766
  const absolutePath = resolve4(pathArg);
4075
- if (!existsSync9(absolutePath)) {
3767
+ if (!existsSync10(absolutePath)) {
4076
3768
  console.error(`Error: Path does not exist: ${absolutePath}`);
4077
3769
  process.exit(1);
4078
3770
  }
@@ -4088,9 +3780,9 @@ var setPathCommand = new Command21("set-path").description("Set or update the lo
4088
3780
  });
4089
3781
 
4090
3782
  // cli/commands/test.ts
4091
- import { Command as Command22 } from "commander";
4092
- import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
4093
- var testCommand = new Command22("test").description("Run automated browser test for a task's MR/PR").argument("<task-id>", "Task ID to test").option("--plan <file>", "Path to a custom test plan JSON file").action(async (taskId, opts) => {
3783
+ import { Command as Command20 } from "commander";
3784
+ import { readFileSync as readFileSync8, existsSync as existsSync11 } from "fs";
3785
+ var testCommand = new Command20("test").description("Run automated browser test for a task's MR/PR").argument("<task-id>", "Task ID to test").option("--plan <file>", "Path to a custom test plan JSON file").action(async (taskId, opts) => {
4094
3786
  const config = loadConfig();
4095
3787
  console.log("[test] Fetching task...");
4096
3788
  let task;
@@ -4111,7 +3803,7 @@ var testCommand = new Command22("test").description("Run automated browser test
4111
3803
  );
4112
3804
  process.exit(1);
4113
3805
  }
4114
- if (!existsSync10(localPath)) {
3806
+ if (!existsSync11(localPath)) {
4115
3807
  console.error(
4116
3808
  `[test] Local repo path not found at ${localPath}. Run \`mr set-path\` to update.`
4117
3809
  );
@@ -4210,11 +3902,11 @@ var testCommand = new Command22("test").description("Run automated browser test
4210
3902
  });
4211
3903
 
4212
3904
  // cli/commands/features.ts
4213
- import { Command as Command23 } from "commander";
4214
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, existsSync as existsSync11 } from "fs";
3905
+ import { Command as Command21 } from "commander";
3906
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, existsSync as existsSync12 } from "fs";
4215
3907
  import { resolve as resolve5, sep as sep2 } from "path";
4216
3908
  var FEATURES_FILE3 = ".mr-features.md";
4217
- var c9 = {
3909
+ var c7 = {
4218
3910
  reset: "\x1B[0m",
4219
3911
  bold: "\x1B[1m",
4220
3912
  dim: "\x1B[2m",
@@ -4224,8 +3916,8 @@ var c9 = {
4224
3916
  magenta: "\x1B[35m",
4225
3917
  gray: "\x1B[90m"
4226
3918
  };
4227
- function paint9(color, text) {
4228
- return `${c9[color]}${text}${c9.reset}`;
3919
+ function paint7(color, text) {
3920
+ return `${c7[color]}${text}${c7.reset}`;
4229
3921
  }
4230
3922
  function resolveProjectRoot2() {
4231
3923
  const cwd = process.cwd();
@@ -4241,10 +3933,10 @@ function getFeaturesPath() {
4241
3933
  }
4242
3934
  function readFeatures2() {
4243
3935
  const path = getFeaturesPath();
4244
- if (!existsSync11(path)) return null;
3936
+ if (!existsSync12(path)) return null;
4245
3937
  return readFileSync9(path, "utf-8");
4246
3938
  }
4247
- var featuresCommand = new Command23("features").description("View or update the project features & goals document (.mr-features.md)").option("--update <content>", "Replace the features document with the given content").option("--file <path>", "Read content from a file and use it to update the features document").option("--path", "Print the path to the features file").action(async (opts) => {
3939
+ var featuresCommand = new Command21("features").description("View or update the project features & goals document (.mr-features.md)").option("--update <content>", "Replace the features document with the given content").option("--file <path>", "Read content from a file and use it to update the features document").option("--path", "Print the path to the features file").action(async (opts) => {
4248
3940
  if (opts.path) {
4249
3941
  console.log(getFeaturesPath());
4250
3942
  return;
@@ -4253,30 +3945,30 @@ var featuresCommand = new Command23("features").description("View or update the
4253
3945
  const content2 = readFileSync9(resolve5(opts.file), "utf-8");
4254
3946
  const featuresPath = getFeaturesPath();
4255
3947
  writeFileSync5(featuresPath, content2);
4256
- console.log(`${paint9("green", "\u2713")} Updated ${paint9("cyan", featuresPath)} from ${paint9("cyan", opts.file)}`);
3948
+ console.log(`${paint7("green", "\u2713")} Updated ${paint7("cyan", featuresPath)} from ${paint7("cyan", opts.file)}`);
4257
3949
  return;
4258
3950
  }
4259
3951
  if (opts.update) {
4260
3952
  const featuresPath = getFeaturesPath();
4261
3953
  writeFileSync5(featuresPath, opts.update);
4262
- console.log(`${paint9("green", "\u2713")} Updated ${paint9("cyan", featuresPath)}`);
3954
+ console.log(`${paint7("green", "\u2713")} Updated ${paint7("cyan", featuresPath)}`);
4263
3955
  return;
4264
3956
  }
4265
3957
  const content = readFeatures2();
4266
3958
  if (!content) {
4267
- console.log(paint9("dim", `No features document found. One will be created when an agent completes a task.`));
4268
- console.log(paint9("dim", `Path: ${getFeaturesPath()}`));
3959
+ console.log(paint7("dim", `No features document found. One will be created when an agent completes a task.`));
3960
+ console.log(paint7("dim", `Path: ${getFeaturesPath()}`));
4269
3961
  return;
4270
3962
  }
4271
3963
  console.log(content);
4272
3964
  });
4273
3965
 
4274
3966
  // cli/commands/no-mr.ts
4275
- import { Command as Command24 } from "commander";
3967
+ import { Command as Command22 } from "commander";
4276
3968
  import { writeFileSync as writeFileSync6 } from "fs";
4277
3969
  import { resolve as resolve6 } from "path";
4278
3970
  var NO_MR_FILE = ".mr-no-mr";
4279
- var noMrCommand = new Command24("no-mr").description("Signal that a task does not require a merge/pull request and describe what was done instead").argument("<task-id>", "Task ID").argument("<description>", "Description of what was done instead of creating an MR/PR").action(async (taskId, description) => {
3971
+ var noMrCommand = new Command22("no-mr").description("Signal that a task does not require a merge/pull request and describe what was done instead").argument("<task-id>", "Task ID").argument("<description>", "Description of what was done instead of creating an MR/PR").action(async (taskId, description) => {
4280
3972
  const filePath = resolve6(process.cwd(), NO_MR_FILE);
4281
3973
  writeFileSync6(filePath, description, "utf-8");
4282
3974
  await api.post(`/api/tasks/${taskId}/updates`, {
@@ -4288,8 +3980,8 @@ var noMrCommand = new Command24("no-mr").description("Signal that a task does no
4288
3980
  });
4289
3981
 
4290
3982
  // cli/commands/mobile.ts
4291
- import { Command as Command25 } from "commander";
4292
- function paint10(color, text) {
3983
+ import { Command as Command23 } from "commander";
3984
+ function paint8(color, text) {
4293
3985
  const colors = {
4294
3986
  cyan: "\x1B[36m",
4295
3987
  green: "\x1B[32m",
@@ -4300,7 +3992,7 @@ function paint10(color, text) {
4300
3992
  };
4301
3993
  return `${colors[color] ?? ""}${text}${colors.reset}`;
4302
3994
  }
4303
- var mobileCommand = new Command25("mobile").description(
3995
+ var mobileCommand = new Command23("mobile").description(
4304
3996
  "Start the Web-to-Mobile Conversion Wizard for the linked project"
4305
3997
  ).argument("[project-id]", "Project ID (defaults to linked project)").option("--framework <framework>", "Framework: react-native or native").option("--url <url>", "Web app URL for analysis").action(
4306
3998
  async (projectIdArg, opts) => {
@@ -4312,7 +4004,7 @@ var mobileCommand = new Command25("mobile").description(
4312
4004
  process.exit(1);
4313
4005
  }
4314
4006
  console.log(
4315
- paint10("cyan", "mobile") + paint10("dim", " \u2014 starting conversion wizard")
4007
+ paint8("cyan", "mobile") + paint8("dim", " \u2014 starting conversion wizard")
4316
4008
  );
4317
4009
  const task = await api.post("/api/tasks", {
4318
4010
  title: "Convert to Mobile App",
@@ -4343,13 +4035,13 @@ var mobileCommand = new Command25("mobile").description(
4343
4035
  }
4344
4036
  console.log(
4345
4037
  `
4346
- ${paint10("green", "\u2713")} Wizard initialized. Open the web UI to continue:
4038
+ ${paint8("green", "\u2713")} Wizard initialized. Open the web UI to continue:
4347
4039
  \u2192 Task ID: ${task.id}
4348
4040
  \u2192 Use the "Convert to Mobile" button on the project page`
4349
4041
  );
4350
4042
  }
4351
4043
  );
4352
- var statusSubcommand = new Command25("status").description("Show mobile conversion status for a task").argument("<task-id>", "Parent conversion task ID").action(async (taskId) => {
4044
+ var statusSubcommand = new Command23("status").description("Show mobile conversion status for a task").argument("<task-id>", "Parent conversion task ID").action(async (taskId) => {
4353
4045
  const resources = await api.get(
4354
4046
  `/api/tasks/${taskId}/resources`
4355
4047
  );
@@ -4362,7 +4054,7 @@ var statusSubcommand = new Command25("status").description("Show mobile conversi
4362
4054
  }
4363
4055
  try {
4364
4056
  const state = JSON.parse(wizardState.content);
4365
- console.log(paint10("cyan", "Mobile Conversion Status"));
4057
+ console.log(paint8("cyan", "Mobile Conversion Status"));
4366
4058
  console.log(` Phase: ${state.phase}`);
4367
4059
  if (state.framework) {
4368
4060
  console.log(` Framework: ${state.framework}`);
@@ -4385,13 +4077,13 @@ var statusSubcommand = new Command25("status").description("Show mobile conversi
4385
4077
  mobileCommand.addCommand(statusSubcommand);
4386
4078
 
4387
4079
  // cli/commands/scan.ts
4388
- import { Command as Command26 } from "commander";
4080
+ import { Command as Command24 } from "commander";
4389
4081
 
4390
4082
  // lib/scanner/index.ts
4391
- import { spawn as spawn9 } from "child_process";
4083
+ import { spawn as spawn7 } from "child_process";
4392
4084
 
4393
4085
  // lib/scanner/config.ts
4394
- import { readFileSync as readFileSync10, existsSync as existsSync12 } from "fs";
4086
+ import { readFileSync as readFileSync10, existsSync as existsSync13 } from "fs";
4395
4087
  import { join as join10 } from "path";
4396
4088
  var ALL_FINDING_TYPES = [
4397
4089
  "idea",
@@ -4409,7 +4101,7 @@ var DEFAULTS = {
4409
4101
  };
4410
4102
  function loadScanConfig(projectPath) {
4411
4103
  const configPath2 = join10(projectPath, ".mr-scan.json");
4412
- if (!existsSync12(configPath2)) {
4104
+ if (!existsSync13(configPath2)) {
4413
4105
  return { ...DEFAULTS };
4414
4106
  }
4415
4107
  try {
@@ -4455,13 +4147,13 @@ async function authenticateBrowseSession(magicUrl, runBrowse) {
4455
4147
  }
4456
4148
 
4457
4149
  // lib/scanner/codebase-analysis.ts
4458
- import { readdirSync as readdirSync2, readFileSync as readFileSync11, existsSync as existsSync13 } from "fs";
4150
+ import { readdirSync as readdirSync2, readFileSync as readFileSync11, existsSync as existsSync14 } from "fs";
4459
4151
  import { join as join11, relative } from "path";
4460
4152
  import { execSync as execSync6 } from "child_process";
4461
4153
  function resolveDir(projectPath, candidates) {
4462
4154
  for (const candidate of candidates) {
4463
4155
  const dir = join11(projectPath, candidate);
4464
- if (existsSync13(dir)) return dir;
4156
+ if (existsSync14(dir)) return dir;
4465
4157
  }
4466
4158
  return null;
4467
4159
  }
@@ -4495,7 +4187,7 @@ function discoverRoutes(projectPath) {
4495
4187
  }
4496
4188
  function extractModels(projectPath) {
4497
4189
  const schemaPath = join11(projectPath, "prisma", "schema.prisma");
4498
- if (existsSync13(schemaPath)) {
4190
+ if (existsSync14(schemaPath)) {
4499
4191
  const content = readFileSync11(schemaPath, "utf-8");
4500
4192
  const models2 = [];
4501
4193
  const modelRegex = /^model\s+(\w+)\s*\{/gm;
@@ -4509,7 +4201,7 @@ function extractModels(projectPath) {
4509
4201
  const drizzleDirs = ["src/db", "src/schema", "db", "drizzle"];
4510
4202
  for (const dir of drizzleDirs) {
4511
4203
  const fullDir = join11(projectPath, dir);
4512
- if (!existsSync13(fullDir)) continue;
4204
+ if (!existsSync14(fullDir)) continue;
4513
4205
  try {
4514
4206
  const entries = readdirSync2(fullDir, { withFileTypes: true });
4515
4207
  for (const entry of entries) {
@@ -4550,7 +4242,7 @@ function discoverComponents(projectPath) {
4550
4242
  function extractInternalLinks(projectPath) {
4551
4243
  const links = /* @__PURE__ */ new Set();
4552
4244
  function searchDir(dir) {
4553
- if (!existsSync13(dir)) return;
4245
+ if (!existsSync14(dir)) return;
4554
4246
  const entries = readdirSync2(dir, { withFileTypes: true });
4555
4247
  for (const entry of entries) {
4556
4248
  if (entry.isDirectory()) {
@@ -4854,10 +4546,10 @@ ${codebaseAnalysis.routes.map((r) => `- ${r}`).join("\n")}
4854
4546
  ${codebaseAnalysis.prismaModels.map((m) => `- ${m}`).join("\n")}
4855
4547
 
4856
4548
  **Components:**
4857
- ${codebaseAnalysis.components.slice(0, 30).map((c12) => `- ${c12}`).join("\n")}
4549
+ ${codebaseAnalysis.components.slice(0, 30).map((c10) => `- ${c10}`).join("\n")}
4858
4550
 
4859
4551
  **Recent Git Commits:**
4860
- ${codebaseAnalysis.recentCommits.slice(0, 15).map((c12) => `- ${c12}`).join("\n")}
4552
+ ${codebaseAnalysis.recentCommits.slice(0, 15).map((c10) => `- ${c10}`).join("\n")}
4861
4553
 
4862
4554
  **Completed Tasks:**
4863
4555
  ${context.completedTasks.slice(0, 20).map((t) => `- ${t.title}`).join("\n") || "None"}
@@ -5067,7 +4759,7 @@ async function fetchScanContext(opts) {
5067
4759
  }
5068
4760
  function runClaude(prompt2) {
5069
4761
  return new Promise((resolve7, reject) => {
5070
- const child = spawn9("claude", ["-p", "--dangerously-skip-permissions", prompt2], {
4762
+ const child = spawn7("claude", ["-p", "--dangerously-skip-permissions", prompt2], {
5071
4763
  stdio: ["ignore", "pipe", "pipe"]
5072
4764
  });
5073
4765
  let output = "";
@@ -5123,7 +4815,7 @@ function parseSynthesisOutput(output) {
5123
4815
  }
5124
4816
 
5125
4817
  // cli/commands/scan.ts
5126
- var c10 = {
4818
+ var c8 = {
5127
4819
  reset: "\x1B[0m",
5128
4820
  bold: "\x1B[1m",
5129
4821
  dim: "\x1B[2m",
@@ -5134,53 +4826,53 @@ var c10 = {
5134
4826
  magenta: "\x1B[35m",
5135
4827
  gray: "\x1B[90m"
5136
4828
  };
5137
- function paint11(color, text) {
5138
- return `${c10[color]}${text}${c10.reset}`;
4829
+ function paint9(color, text) {
4830
+ return `${c8[color]}${text}${c8.reset}`;
5139
4831
  }
5140
- function timestamp4() {
5141
- return paint11("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
4832
+ function timestamp2() {
4833
+ return paint9("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
5142
4834
  }
5143
4835
  function scanTag() {
5144
- return paint11("magenta", "[scan]");
4836
+ return paint9("magenta", "[scan]");
5145
4837
  }
5146
- function log3(msg) {
5147
- console.log(`${timestamp4()} ${scanTag()} ${msg}`);
4838
+ function log(msg) {
4839
+ console.log(`${timestamp2()} ${scanTag()} ${msg}`);
5148
4840
  }
5149
- function logOk2(msg) {
5150
- console.log(`${timestamp4()} ${scanTag()} ${paint11("green", "\u2713")} ${msg}`);
4841
+ function logOk(msg) {
4842
+ console.log(`${timestamp2()} ${scanTag()} ${paint9("green", "\u2713")} ${msg}`);
5151
4843
  }
5152
- function logErr3(msg) {
5153
- console.error(`${timestamp4()} ${scanTag()} ${paint11("red", "\u2717")} ${msg}`);
4844
+ function logErr(msg) {
4845
+ console.error(`${timestamp2()} ${scanTag()} ${paint9("red", "\u2717")} ${msg}`);
5154
4846
  }
5155
- var scanCommand = new Command26("scan").description("Run a product scan on the current project \u2014 analyzes codebase, crawls the app, and surfaces findings").option("--project <id>", "Project ID (defaults to linked project)").option("--report <id>", "Use an existing scan report ID (created by UI trigger)").option("--no-crawl", "Skip live crawl (codebase analysis only)").action(async (opts) => {
4847
+ var scanCommand = new Command24("scan").description("Run a product scan on the current project \u2014 analyzes codebase, crawls the app, and surfaces findings").option("--project <id>", "Project ID (defaults to linked project)").option("--report <id>", "Use an existing scan report ID (created by UI trigger)").option("--no-crawl", "Skip live crawl (codebase analysis only)").action(async (opts) => {
5156
4848
  const config = loadConfig();
5157
4849
  if (!config.apiKey) {
5158
- logErr3('Not authenticated. Run "mr login" first.');
4850
+ logErr('Not authenticated. Run "mr login" first.');
5159
4851
  process.exit(1);
5160
4852
  }
5161
4853
  const banner = [
5162
4854
  ``,
5163
- paint11("magenta", ` \u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2557\u2554`),
5164
- paint11("magenta", ` \u255A\u2550\u2557\u2551 \u2560\u2550\u2563\u2551\u2551\u2551`),
5165
- paint11("magenta", ` \u255A\u2550\u255D\u255A\u2550\u255D\u2569 \u2569\u255D\u255A\u255D`),
5166
- paint11("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
5167
- paint11("dim", ` autonomous product scanner`),
4855
+ paint9("magenta", ` \u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2557\u2554`),
4856
+ paint9("magenta", ` \u255A\u2550\u2557\u2551 \u2560\u2550\u2563\u2551\u2551\u2551`),
4857
+ paint9("magenta", ` \u255A\u2550\u255D\u255A\u2550\u255D\u2569 \u2569\u255D\u255A\u255D`),
4858
+ paint9("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
4859
+ paint9("dim", ` autonomous product scanner`),
5168
4860
  ``
5169
4861
  ].join("\n");
5170
4862
  console.log(banner);
5171
4863
  const projectId = opts.project || getLinkedProjectId();
5172
4864
  if (!projectId) {
5173
- logErr3('No project linked. Run "mr link" or pass --project <id>.');
4865
+ logErr('No project linked. Run "mr link" or pass --project <id>.');
5174
4866
  process.exit(1);
5175
4867
  }
5176
4868
  let project;
5177
4869
  try {
5178
4870
  project = await api.get(`/api/projects/${projectId}`);
5179
4871
  } catch {
5180
- logErr3(`Failed to fetch project ${projectId}`);
4872
+ logErr(`Failed to fetch project ${projectId}`);
5181
4873
  process.exit(1);
5182
4874
  }
5183
- log3(`Scanning project: ${paint11("cyan", project.name)}`);
4875
+ log(`Scanning project: ${paint9("cyan", project.name)}`);
5184
4876
  let projectPath = project.localPath;
5185
4877
  if (!projectPath) {
5186
4878
  for (const [dir, pid] of Object.entries(config.directories)) {
@@ -5196,12 +4888,12 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
5196
4888
  let reportId;
5197
4889
  if (opts.report) {
5198
4890
  reportId = opts.report;
5199
- log3(`Using existing scan report ${paint11("yellow", reportId.slice(0, 8))}`);
4891
+ log(`Using existing scan report ${paint9("yellow", reportId.slice(0, 8))}`);
5200
4892
  } else {
5201
4893
  try {
5202
4894
  const scans = await api.get(`/api/scans?projectId=${projectId}&status=processing`);
5203
4895
  if (scans.length > 0) {
5204
- logErr3("A scan is already in progress for this project. Wait for it to complete.");
4896
+ logErr("A scan is already in progress for this project. Wait for it to complete.");
5205
4897
  process.exit(1);
5206
4898
  }
5207
4899
  } catch {
@@ -5212,9 +4904,9 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
5212
4904
  status: "pending"
5213
4905
  });
5214
4906
  reportId = report.id;
5215
- log3(`Created scan report ${paint11("yellow", reportId.slice(0, 8))}`);
4907
+ log(`Created scan report ${paint9("yellow", reportId.slice(0, 8))}`);
5216
4908
  } catch (err) {
5217
- logErr3(`Failed to create scan report: ${err.message}`);
4909
+ logErr(`Failed to create scan report: ${err.message}`);
5218
4910
  process.exit(1);
5219
4911
  }
5220
4912
  }
@@ -5225,7 +4917,7 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
5225
4917
  try {
5226
4918
  const current = await api.get(`/api/scans/${reportId}`);
5227
4919
  if (current.status === "cancelled") {
5228
- log3(paint11("yellow", "Scan was cancelled \u2014 aborting."));
4920
+ log(paint9("yellow", "Scan was cancelled \u2014 aborting."));
5229
4921
  process.exit(0);
5230
4922
  }
5231
4923
  } catch {
@@ -5239,9 +4931,9 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
5239
4931
  apiUrl: config.apiUrl,
5240
4932
  apiKey: config.apiKey,
5241
4933
  runBrowse: runBrowseCommand2,
5242
- onLog: log3,
4934
+ onLog: log,
5243
4935
  onProgress: (phase, detail) => {
5244
- log3(`${paint11("dim", `[${phase}]`)} ${detail}`);
4936
+ log(`${paint9("dim", `[${phase}]`)} ${detail}`);
5245
4937
  }
5246
4938
  });
5247
4939
  let wasCancelled = false;
@@ -5253,7 +4945,7 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
5253
4945
  } catch {
5254
4946
  }
5255
4947
  if (wasCancelled) {
5256
- log3(paint11("yellow", "Scan was cancelled by user \u2014 discarding results."));
4948
+ log(paint9("yellow", "Scan was cancelled by user \u2014 discarding results."));
5257
4949
  process.exit(0);
5258
4950
  }
5259
4951
  await api.patch(`/api/scans/${reportId}`, {
@@ -5264,37 +4956,37 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
5264
4956
  scanDurationMs: result.scanDurationMs,
5265
4957
  routesCrawled: result.routesCrawled
5266
4958
  });
5267
- logOk2(`Scan complete \u2014 ${paint11("cyan", String(result.findings.length))} findings`);
4959
+ logOk(`Scan complete \u2014 ${paint9("cyan", String(result.findings.length))} findings`);
5268
4960
  console.log("");
5269
- console.log(` ${paint11("bold", "Summary:")} ${result.summary}`);
4961
+ console.log(` ${paint9("bold", "Summary:")} ${result.summary}`);
5270
4962
  console.log("");
5271
4963
  const high = result.findings.filter((f) => f.priority === "high");
5272
4964
  const medium = result.findings.filter((f) => f.priority === "medium");
5273
4965
  const low = result.findings.filter((f) => f.priority === "low");
5274
4966
  if (high.length > 0) {
5275
- console.log(` ${paint11("bold", paint11("red", `High Priority (${high.length})`))}`);
4967
+ console.log(` ${paint9("bold", paint9("red", `High Priority (${high.length})`))}`);
5276
4968
  for (const f of high) {
5277
- console.log(` ${paint11("red", "\u25CF")} [${f.type}] ${f.title}`);
5278
- console.log(` ${paint11("dim", f.description.slice(0, 120))}`);
4969
+ console.log(` ${paint9("red", "\u25CF")} [${f.type}] ${f.title}`);
4970
+ console.log(` ${paint9("dim", f.description.slice(0, 120))}`);
5279
4971
  }
5280
4972
  console.log("");
5281
4973
  }
5282
4974
  if (medium.length > 0) {
5283
- console.log(` ${paint11("bold", paint11("yellow", `Medium Priority (${medium.length})`))}`);
4975
+ console.log(` ${paint9("bold", paint9("yellow", `Medium Priority (${medium.length})`))}`);
5284
4976
  for (const f of medium) {
5285
- console.log(` ${paint11("yellow", "\u25CF")} [${f.type}] ${f.title}`);
4977
+ console.log(` ${paint9("yellow", "\u25CF")} [${f.type}] ${f.title}`);
5286
4978
  }
5287
4979
  console.log("");
5288
4980
  }
5289
4981
  if (low.length > 0) {
5290
- console.log(` ${paint11("dim", `Low Priority (${low.length})`)} `);
4982
+ console.log(` ${paint9("dim", `Low Priority (${low.length})`)} `);
5291
4983
  for (const f of low) {
5292
- console.log(` ${paint11("dim", `\u25CB [${f.type}] ${f.title}`)}`);
4984
+ console.log(` ${paint9("dim", `\u25CB [${f.type}] ${f.title}`)}`);
5293
4985
  }
5294
4986
  console.log("");
5295
4987
  }
5296
4988
  } catch (err) {
5297
- logErr3(`Scan failed: ${err.message}`);
4989
+ logErr(`Scan failed: ${err.message}`);
5298
4990
  try {
5299
4991
  await api.patch(`/api/scans/${reportId}`, {
5300
4992
  status: "failed",
@@ -5307,8 +4999,8 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
5307
4999
  });
5308
5000
 
5309
5001
  // cli/commands/idea.ts
5310
- import { Command as Command27 } from "commander";
5311
- var c11 = {
5002
+ import { Command as Command25 } from "commander";
5003
+ var c9 = {
5312
5004
  reset: "\x1B[0m",
5313
5005
  bold: "\x1B[1m",
5314
5006
  dim: "\x1B[2m",
@@ -5320,27 +5012,27 @@ var c11 = {
5320
5012
  gray: "\x1B[90m",
5321
5013
  magenta: "\x1B[35m"
5322
5014
  };
5323
- function paint12(color, text) {
5324
- return `${c11[color]}${text}${c11.reset}`;
5015
+ function paint10(color, text) {
5016
+ return `${c9[color]}${text}${c9.reset}`;
5325
5017
  }
5326
5018
  function statusBadge2(status) {
5327
5019
  switch (status) {
5328
5020
  case "draft":
5329
- return paint12("gray", "\u25CB draft");
5021
+ return paint10("gray", "\u25CB draft");
5330
5022
  case "generating":
5331
- return paint12("cyan", "\u27F3 generating");
5023
+ return paint10("cyan", "\u27F3 generating");
5332
5024
  case "generated":
5333
- return paint12("green", "\u2713 generated");
5025
+ return paint10("green", "\u2713 generated");
5334
5026
  case "promoted":
5335
- return paint12("magenta", "\u2191 promoted");
5027
+ return paint10("magenta", "\u2191 promoted");
5336
5028
  case "archived":
5337
- return paint12("dim", "\u2298 archived");
5029
+ return paint10("dim", "\u2298 archived");
5338
5030
  default:
5339
- return paint12("gray", status);
5031
+ return paint10("gray", status);
5340
5032
  }
5341
5033
  }
5342
- var ideaCommand = new Command27("idea").description("Manage ideas \u2014 brainstorm, generate prototypes & plans").addCommand(
5343
- new Command27("list").description("List ideas for the linked project").option("--all", "Show ideas for all projects").option("--status <status>", "Filter by status").action(async (opts) => {
5034
+ var ideaCommand = new Command25("idea").description("Manage ideas \u2014 brainstorm, generate prototypes & plans").addCommand(
5035
+ new Command25("list").description("List ideas for the linked project").option("--all", "Show ideas for all projects").option("--status <status>", "Filter by status").action(async (opts) => {
5344
5036
  const params = new URLSearchParams();
5345
5037
  if (!opts.all) {
5346
5038
  const projectId = getLinkedProjectId();
@@ -5351,23 +5043,23 @@ var ideaCommand = new Command27("idea").description("Manage ideas \u2014 brainst
5351
5043
  if (opts.status) params.set("status", opts.status);
5352
5044
  const ideas = await api.get(`/api/ideas?${params.toString()}`);
5353
5045
  if (ideas.length === 0) {
5354
- console.log(paint12("gray", "No ideas found."));
5046
+ console.log(paint10("gray", "No ideas found."));
5355
5047
  return;
5356
5048
  }
5357
5049
  console.log();
5358
5050
  for (const idea of ideas) {
5359
5051
  const date = new Date(idea.createdAt).toLocaleDateString();
5360
5052
  console.log(
5361
- ` ${paint12("bold", idea.title)} ${statusBadge2(idea.status)} ${paint12("gray", idea.id.slice(0, 8))} ${paint12("dim", date)}`
5053
+ ` ${paint10("bold", idea.title)} ${statusBadge2(idea.status)} ${paint10("gray", idea.id.slice(0, 8))} ${paint10("dim", date)}`
5362
5054
  );
5363
5055
  if (idea.description) {
5364
- console.log(` ${paint12("dim", idea.description.slice(0, 80) + (idea.description.length > 80 ? "\u2026" : ""))}`);
5056
+ console.log(` ${paint10("dim", idea.description.slice(0, 80) + (idea.description.length > 80 ? "\u2026" : ""))}`);
5365
5057
  }
5366
5058
  console.log();
5367
5059
  }
5368
5060
  })
5369
5061
  ).addCommand(
5370
- new Command27("create").description("Create a new idea").argument("<title>", "Title of the idea").option("--description <desc>", "Description of the idea").option("--project <projectId>", "Project ID (defaults to linked project)").option("--generate", "Immediately start generating plan & prototype").action(async (title, opts) => {
5062
+ new Command25("create").description("Create a new idea").argument("<title>", "Title of the idea").option("--description <desc>", "Description of the idea").option("--project <projectId>", "Project ID (defaults to linked project)").option("--generate", "Immediately start generating plan & prototype").action(async (title, opts) => {
5371
5063
  const projectId = opts.project ?? getLinkedProjectId() ?? null;
5372
5064
  const idea = await api.post("/api/ideas", {
5373
5065
  title,
@@ -5375,65 +5067,65 @@ var ideaCommand = new Command27("idea").description("Manage ideas \u2014 brainst
5375
5067
  projectId
5376
5068
  });
5377
5069
  console.log();
5378
- console.log(` ${paint12("green", "\u2713")} Created idea: ${paint12("bold", idea.title)}`);
5379
- console.log(` ${paint12("gray", "ID:")} ${idea.id}`);
5070
+ console.log(` ${paint10("green", "\u2713")} Created idea: ${paint10("bold", idea.title)}`);
5071
+ console.log(` ${paint10("gray", "ID:")} ${idea.id}`);
5380
5072
  if (opts.generate) {
5381
5073
  await api.post(`/api/ideas/${idea.id}/generate`);
5382
- console.log(` ${paint12("cyan", "\u27F3")} Generation will begin automatically via the watch agent.`);
5074
+ console.log(` ${paint10("cyan", "\u27F3")} Generation will begin automatically via the watch agent.`);
5383
5075
  }
5384
5076
  console.log();
5385
5077
  })
5386
5078
  ).addCommand(
5387
- new Command27("generate").description("Start generating plan & prototype for an idea").argument("<id>", "Idea ID").action(async (id) => {
5079
+ new Command25("generate").description("Start generating plan & prototype for an idea").argument("<id>", "Idea ID").action(async (id) => {
5388
5080
  const idea = await api.post(`/api/ideas/${id}/generate`);
5389
5081
  console.log();
5390
- console.log(` ${paint12("cyan", "\u27F3")} Generating: ${paint12("bold", idea.title)}`);
5391
- console.log(` ${paint12("gray", "The watch agent will pick this up shortly.")}`);
5082
+ console.log(` ${paint10("cyan", "\u27F3")} Generating: ${paint10("bold", idea.title)}`);
5083
+ console.log(` ${paint10("gray", "The watch agent will pick this up shortly.")}`);
5392
5084
  console.log();
5393
5085
  })
5394
5086
  ).addCommand(
5395
- new Command27("feedback").description("Send feedback to iterate on an idea's generated content").argument("<id>", "Idea ID").argument("<feedback>", "Feedback text").action(async (id, feedback) => {
5087
+ new Command25("feedback").description("Send feedback to iterate on an idea's generated content").argument("<id>", "Idea ID").argument("<feedback>", "Feedback text").action(async (id, feedback) => {
5396
5088
  const idea = await api.post(`/api/ideas/${id}/feedback`, { feedback });
5397
5089
  console.log();
5398
- console.log(` ${paint12("cyan", "\u27F3")} Feedback sent for: ${paint12("bold", idea.title)}`);
5399
- console.log(` ${paint12("gray", "The watch agent will re-generate with your feedback.")}`);
5090
+ console.log(` ${paint10("cyan", "\u27F3")} Feedback sent for: ${paint10("bold", idea.title)}`);
5091
+ console.log(` ${paint10("gray", "The watch agent will re-generate with your feedback.")}`);
5400
5092
  console.log();
5401
5093
  })
5402
5094
  ).addCommand(
5403
- new Command27("promote").description("Promote an idea to a task").argument("<id>", "Idea ID").action(async (id) => {
5095
+ new Command25("promote").description("Promote an idea to a task").argument("<id>", "Idea ID").action(async (id) => {
5404
5096
  const result = await api.post(`/api/ideas/${id}/promote`);
5405
5097
  console.log();
5406
- console.log(` ${paint12("green", "\u2713")} Promoted idea to task: ${paint12("bold", result.task.title)}`);
5407
- console.log(` ${paint12("gray", "Task ID:")} ${result.task.id}`);
5098
+ console.log(` ${paint10("green", "\u2713")} Promoted idea to task: ${paint10("bold", result.task.title)}`);
5099
+ console.log(` ${paint10("gray", "Task ID:")} ${result.task.id}`);
5408
5100
  console.log();
5409
5101
  })
5410
5102
  ).addCommand(
5411
- new Command27("spin-up").description("Spin up a new project with a GitHub repo from an idea").argument("<id>", "Idea ID").option("--name <name>", "Custom project name (defaults to idea title)").action(async (id, opts) => {
5103
+ new Command25("spin-up").description("Spin up a new project with a GitHub repo from an idea").argument("<id>", "Idea ID").option("--name <name>", "Custom project name (defaults to idea title)").action(async (id, opts) => {
5412
5104
  const body = {};
5413
5105
  if (opts.name) body.name = opts.name;
5414
5106
  const result = await api.post(`/api/ideas/${id}/spin-up`, body);
5415
5107
  console.log();
5416
- console.log(` ${paint12("green", "\u2713")} Spinning up project: ${paint12("bold", result.project.name)}`);
5417
- console.log(` ${paint12("gray", "Project ID:")} ${result.project.id}`);
5108
+ console.log(` ${paint10("green", "\u2713")} Spinning up project: ${paint10("bold", result.project.name)}`);
5109
+ console.log(` ${paint10("gray", "Project ID:")} ${result.project.id}`);
5418
5110
  if (result.tasks && result.tasks.length > 0) {
5419
- console.log(` ${paint12("green", "\u2713")} Created ${result.tasks.length} follow-up task(s):`);
5111
+ console.log(` ${paint10("green", "\u2713")} Created ${result.tasks.length} follow-up task(s):`);
5420
5112
  for (const task of result.tasks) {
5421
- console.log(` ${paint12("gray", "\u2022")} ${task.title}`);
5113
+ console.log(` ${paint10("gray", "\u2022")} ${task.title}`);
5422
5114
  }
5423
5115
  }
5424
- console.log(` ${paint12("cyan", "\u27F3")} Repo creation is queued \u2014 the watch daemon will pick it up.`);
5116
+ console.log(` ${paint10("cyan", "\u27F3")} Repo creation is queued \u2014 the watch daemon will pick it up.`);
5425
5117
  console.log();
5426
5118
  })
5427
5119
  );
5428
5120
 
5429
5121
  // cli/commands/doctor.ts
5430
- import { Command as Command28 } from "commander";
5431
- import { existsSync as existsSync14 } from "fs";
5432
- import { homedir as homedir3 } from "os";
5122
+ import { Command as Command26 } from "commander";
5123
+ import { existsSync as existsSync15 } from "fs";
5124
+ import { homedir as homedir2 } from "os";
5433
5125
  import { join as join12 } from "path";
5434
5126
  async function checkConfigExists() {
5435
- const configPath2 = join12(homedir3(), ".mr-manager", "config.json");
5436
- const exists = existsSync14(configPath2);
5127
+ const configPath2 = join12(homedir2(), ".mr-manager", "config.json");
5128
+ const exists = existsSync15(configPath2);
5437
5129
  if (!exists) {
5438
5130
  return {
5439
5131
  name: "Config file",
@@ -5475,12 +5167,12 @@ async function checkProjectLink() {
5475
5167
  optional: true
5476
5168
  };
5477
5169
  }
5478
- var doctorCommand = new Command28("doctor").description("Diagnose Mr. Manager CLI installation and environment").action(async () => {
5170
+ var doctorCommand = new Command26("doctor").description("Diagnose Mr. Manager CLI installation and environment").action(async () => {
5479
5171
  const banner = [
5480
5172
  ``,
5481
- paint7("cyan", ` MR DOCTOR`),
5482
- paint7("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
5483
- paint7("dim", ` diagnosing your mr environment`),
5173
+ paint5("cyan", ` MR DOCTOR`),
5174
+ paint5("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
5175
+ paint5("dim", ` diagnosing your mr environment`),
5484
5176
  ``
5485
5177
  ].join("\n");
5486
5178
  console.log(banner);
@@ -5502,15 +5194,15 @@ var doctorCommand = new Command28("doctor").description("Diagnose Mr. Manager CL
5502
5194
  const allOk = printResults(checks);
5503
5195
  console.log("");
5504
5196
  if (allOk) {
5505
- console.log(paint7("green", " Everything looks good!"));
5197
+ console.log(paint5("green", " Everything looks good!"));
5506
5198
  console.log("");
5507
5199
  return;
5508
5200
  }
5509
- const fixes = checks.filter((c12) => !c12.ok && c12.fix && !c12.optional);
5201
+ const fixes = checks.filter((c10) => !c10.ok && c10.fix && !c10.optional);
5510
5202
  if (fixes.length > 0) {
5511
- console.log(paint7("yellow", " To fix:"));
5203
+ console.log(paint5("yellow", " To fix:"));
5512
5204
  for (const fix of fixes) {
5513
- console.log(` ${paint7("dim", "\u2192")} ${paint7("bold", fix.name)}: ${fix.fix}`);
5205
+ console.log(` ${paint5("dim", "\u2192")} ${paint5("bold", fix.name)}: ${fix.fix}`);
5514
5206
  }
5515
5207
  console.log("");
5516
5208
  }
@@ -5518,13 +5210,13 @@ var doctorCommand = new Command28("doctor").description("Diagnose Mr. Manager CL
5518
5210
  });
5519
5211
 
5520
5212
  // cli/index.ts
5521
- var configPath = join13(homedir4(), ".mr-manager", "config.json");
5522
- var isFirstRun = !existsSync15(configPath);
5213
+ var configPath = join13(homedir3(), ".mr-manager", "config.json");
5214
+ var isFirstRun = !existsSync16(configPath);
5523
5215
  var userArgs = process.argv.slice(2);
5524
5216
  var bypassCommands = /* @__PURE__ */ new Set(["login", "init", "auth", "help", "--help", "-h", "--version", "-V", "doctor", "setup"]);
5525
5217
  var shouldBypass = userArgs.length > 0 && bypassCommands.has(userArgs[0]);
5526
5218
  if (isFirstRun && !shouldBypass) {
5527
- const c12 = {
5219
+ const c10 = {
5528
5220
  reset: "\x1B[0m",
5529
5221
  bold: "\x1B[1m",
5530
5222
  dim: "\x1B[2m",
@@ -5534,29 +5226,29 @@ if (isFirstRun && !shouldBypass) {
5534
5226
  magenta: "\x1B[35m"
5535
5227
  };
5536
5228
  console.log("");
5537
- console.log(`${c12.cyan} \u2554\u2566\u2557\u2566\u2550\u2557 \u2554\u2566\u2557\u2554\u2550\u2557\u2554\u2557\u2554\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2566\u2550\u2557${c12.reset}`);
5538
- console.log(`${c12.magenta} \u2551\u2551\u2551\u2560\u2566\u255D \u2551\u2551\u2551\u2560\u2550\u2563\u2551\u2551\u2551\u2560\u2550\u2563\u2551 \u2566\u2551\u2563 \u2560\u2566\u255D${c12.reset}`);
5539
- console.log(`${c12.cyan} \u2569 \u2569\u2569\u255A\u2550 \u2569 \u2569\u2569 \u2569\u255D\u255A\u255D\u2569 \u2569\u255A\u2550\u255D\u255A\u2550\u255D\u2569\u255A\u2550${c12.reset}`);
5540
- console.log(`${c12.dim} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c12.reset}`);
5229
+ console.log(`${c10.cyan} \u2554\u2566\u2557\u2566\u2550\u2557 \u2554\u2566\u2557\u2554\u2550\u2557\u2554\u2557\u2554\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2566\u2550\u2557${c10.reset}`);
5230
+ console.log(`${c10.magenta} \u2551\u2551\u2551\u2560\u2566\u255D \u2551\u2551\u2551\u2560\u2550\u2563\u2551\u2551\u2551\u2560\u2550\u2563\u2551 \u2566\u2551\u2563 \u2560\u2566\u255D${c10.reset}`);
5231
+ console.log(`${c10.cyan} \u2569 \u2569\u2569\u255A\u2550 \u2569 \u2569\u2569 \u2569\u255D\u255A\u255D\u2569 \u2569\u255A\u2550\u255D\u255A\u2550\u255D\u2569\u255A\u2550${c10.reset}`);
5232
+ console.log(`${c10.dim} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c10.reset}`);
5541
5233
  console.log("");
5542
- console.log(`${c12.bold} Welcome to Mr. Manager!${c12.reset}`);
5543
- console.log(`${c12.dim} Let's get you set up in a few quick steps.${c12.reset}`);
5234
+ console.log(`${c10.bold} Welcome to Mr. Manager!${c10.reset}`);
5235
+ console.log(`${c10.dim} Let's get you set up in a few quick steps.${c10.reset}`);
5544
5236
  console.log("");
5545
- console.log(` ${c12.yellow}Step 1:${c12.reset} Authenticate via Google OAuth`);
5546
- console.log(` ${c12.dim}Run:${c12.reset} ${c12.cyan}mr login${c12.reset}`);
5237
+ console.log(` ${c10.yellow}Step 1:${c10.reset} Authenticate via Google OAuth`);
5238
+ console.log(` ${c10.dim}Run:${c10.reset} ${c10.cyan}mr login${c10.reset}`);
5547
5239
  console.log("");
5548
- console.log(` ${c12.yellow}Step 2:${c12.reset} Verify your environment`);
5549
- console.log(` ${c12.dim}Run:${c12.reset} ${c12.cyan}mr setup${c12.reset}`);
5240
+ console.log(` ${c10.yellow}Step 2:${c10.reset} Verify your environment`);
5241
+ console.log(` ${c10.dim}Run:${c10.reset} ${c10.cyan}mr setup${c10.reset}`);
5550
5242
  console.log("");
5551
- console.log(` ${c12.yellow}Step 3:${c12.reset} Link a repo and start watching`);
5552
- console.log(` ${c12.dim}Run:${c12.reset} ${c12.cyan}mr link${c12.reset} ${c12.dim}&&${c12.reset} ${c12.cyan}mr watch${c12.reset}`);
5243
+ console.log(` ${c10.yellow}Step 3:${c10.reset} Link a repo and start watching`);
5244
+ console.log(` ${c10.dim}Run:${c10.reset} ${c10.cyan}mr link${c10.reset} ${c10.dim}&&${c10.reset} ${c10.cyan}mr watch${c10.reset}`);
5553
5245
  console.log("");
5554
- console.log(`${c12.dim} Or run ${c12.reset}${c12.cyan}mr login${c12.reset}${c12.dim} to get started now.${c12.reset}`);
5246
+ console.log(`${c10.dim} Or run ${c10.reset}${c10.cyan}mr login${c10.reset}${c10.dim} to get started now.${c10.reset}`);
5555
5247
  console.log("");
5556
5248
  process.exit(0);
5557
5249
  }
5558
- var program = new Command29();
5559
- program.name("mr").description("Mr. Manager - Task and project management CLI").version("0.2.0");
5250
+ var program = new Command27();
5251
+ program.name("mr").description("Mr. Manager - Task and project management CLI").version(CLI_VERSION);
5560
5252
  program.addCommand(initCommand);
5561
5253
  program.addCommand(authCommand);
5562
5254
  program.addCommand(loginCommand);
@@ -5572,8 +5264,6 @@ program.addCommand(undelegateCommand);
5572
5264
  program.addCommand(createCommand);
5573
5265
  program.addCommand(completeCommand);
5574
5266
  program.addCommand(subtaskCompleteCommand);
5575
- program.addCommand(digestCommand);
5576
- program.addCommand(upCommand);
5577
5267
  program.addCommand(prototypeCommand);
5578
5268
  program.addCommand(setupCommand);
5579
5269
  program.addCommand(updateCommand);