@dunnewold-labs/mr-manager 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +1167 -0
  2. package/dist/index.mjs +5545 -0
  3. package/package.json +24 -0
package/dist/index.js ADDED
@@ -0,0 +1,1167 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
6
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
7
+ var __spreadValues = (a, b) => {
8
+ for (var prop in b || (b = {}))
9
+ if (__hasOwnProp.call(b, prop))
10
+ __defNormalProp(a, prop, b[prop]);
11
+ if (__getOwnPropSymbols)
12
+ for (var prop of __getOwnPropSymbols(b)) {
13
+ if (__propIsEnum.call(b, prop))
14
+ __defNormalProp(a, prop, b[prop]);
15
+ }
16
+ return a;
17
+ };
18
+ var __async = (__this, __arguments, generator) => {
19
+ return new Promise((resolve3, reject) => {
20
+ var fulfilled = (value) => {
21
+ try {
22
+ step(generator.next(value));
23
+ } catch (e) {
24
+ reject(e);
25
+ }
26
+ };
27
+ var rejected = (value) => {
28
+ try {
29
+ step(generator.throw(value));
30
+ } catch (e) {
31
+ reject(e);
32
+ }
33
+ };
34
+ var step = (x) => x.done ? resolve3(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
35
+ step((generator = generator.apply(__this, __arguments)).next());
36
+ });
37
+ };
38
+
39
+ // index.ts
40
+ import { Command as Command15 } from "commander";
41
+
42
+ // commands/init.ts
43
+ import { Command } from "commander";
44
+
45
+ // config.ts
46
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
47
+ import { homedir } from "os";
48
+ import { join } from "path";
49
+ var CONFIG_DIR = join(homedir(), ".mr-manager");
50
+ var CONFIG_FILE = join(CONFIG_DIR, "config.json");
51
+ var DEFAULT_CONFIG = {
52
+ apiUrl: "https://mr-manager-gold.vercel.app",
53
+ apiKey: "",
54
+ directories: {}
55
+ };
56
+ function loadConfig() {
57
+ if (!existsSync(CONFIG_FILE)) {
58
+ return __spreadValues({}, DEFAULT_CONFIG);
59
+ }
60
+ const raw = readFileSync(CONFIG_FILE, "utf-8");
61
+ return __spreadValues(__spreadValues({}, DEFAULT_CONFIG), JSON.parse(raw));
62
+ }
63
+ function saveConfig(config) {
64
+ if (!existsSync(CONFIG_DIR)) {
65
+ mkdirSync(CONFIG_DIR, { recursive: true });
66
+ }
67
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n");
68
+ }
69
+ function getLinkedProjectId() {
70
+ var _a;
71
+ const config = loadConfig();
72
+ const cwd = process.cwd();
73
+ return (_a = config.directories[cwd]) != null ? _a : null;
74
+ }
75
+
76
+ // commands/init.ts
77
+ var initCommand = new Command("init").description("Set up Mr. Manager CLI authentication").option("--url <url>", "API URL", "https://mr-manager-gold.vercel.app").action((opts) => __async(null, null, function* () {
78
+ const config = loadConfig();
79
+ config.apiUrl = opts.url;
80
+ const url = `${config.apiUrl}/api/auth/key`;
81
+ const res = yield fetch(url, {
82
+ method: "POST",
83
+ headers: { "Content-Type": "application/json" },
84
+ body: JSON.stringify({ name: "cli" })
85
+ });
86
+ if (!res.ok) {
87
+ const text = yield res.text();
88
+ console.error(`Failed to create API key: ${res.status} ${text}`);
89
+ process.exit(1);
90
+ }
91
+ const data = yield res.json();
92
+ config.apiKey = data.key;
93
+ saveConfig(config);
94
+ console.log("Authenticated successfully. Config saved to ~/.mr-manager/config.json");
95
+ }));
96
+
97
+ // commands/auth.ts
98
+ import { Command as Command2 } from "commander";
99
+ var authCommand = new Command2("auth").description("Manually set an API key").argument("<key>", "API key to save").option("--url <url>", "API URL", "https://mr-manager-gold.vercel.app").action((key, opts) => __async(null, null, function* () {
100
+ const config = loadConfig();
101
+ config.apiKey = key;
102
+ if (opts.url) config.apiUrl = opts.url;
103
+ saveConfig(config);
104
+ console.log("API key saved to ~/.mr-manager/config.json");
105
+ }));
106
+
107
+ // commands/login.ts
108
+ import { Command as Command3 } from "commander";
109
+ import { createServer } from "http";
110
+ import { spawn } from "child_process";
111
+ function getRandomPort() {
112
+ return Math.floor(Math.random() * (65535 - 49152 + 1)) + 49152;
113
+ }
114
+ function openBrowser(url) {
115
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
116
+ spawn(cmd, [url], { detached: true, stdio: "ignore" }).unref();
117
+ }
118
+ var loginCommand = new Command3("login").description("Authenticate the CLI via browser (Google OAuth)").option("--url <url>", "API URL", "https://mr-manager-gold.vercel.app").action((opts) => __async(null, null, function* () {
119
+ var _a, _b;
120
+ const config = loadConfig();
121
+ const apiUrl = (_b = (_a = opts.url) != null ? _a : config.apiUrl) != null ? _b : "https://mr-manager-gold.vercel.app";
122
+ const port = getRandomPort();
123
+ const key = yield new Promise((resolve3, reject) => {
124
+ const server = createServer((req, res) => {
125
+ var _a2;
126
+ const url = new URL((_a2 = req.url) != null ? _a2 : "/", `http://localhost:${port}`);
127
+ const key2 = url.searchParams.get("key");
128
+ res.writeHead(200, { "Content-Type": "text/html" });
129
+ res.end(`
130
+ <html><body style="font-family:sans-serif;text-align:center;padding:60px">
131
+ <h2>${key2 ? "\u2713 Authenticated!" : "Something went wrong."}</h2>
132
+ <p>${key2 ? "You can close this tab and return to the terminal." : "No key received. Please try again."}</p>
133
+ </body></html>
134
+ `);
135
+ server.close();
136
+ if (key2) resolve3(key2);
137
+ else reject(new Error("No key received from server"));
138
+ });
139
+ server.listen(port, () => {
140
+ const loginUrl = `${apiUrl}/login?cliPort=${port}`;
141
+ console.log(`
142
+ Opening browser for authentication\u2026`);
143
+ console.log(`If the browser doesn't open, visit:
144
+ ${loginUrl}
145
+ `);
146
+ openBrowser(loginUrl);
147
+ });
148
+ server.on("error", reject);
149
+ setTimeout(() => {
150
+ server.close();
151
+ reject(new Error("Login timed out after 5 minutes"));
152
+ }, 5 * 60 * 1e3);
153
+ });
154
+ config.apiKey = key;
155
+ config.apiUrl = apiUrl;
156
+ saveConfig(config);
157
+ console.log("Authenticated successfully. Config saved to ~/.mr-manager/config.json");
158
+ }));
159
+
160
+ // commands/projects.ts
161
+ import { Command as Command4 } from "commander";
162
+
163
+ // api.ts
164
+ var ApiError = class extends Error {
165
+ constructor(status, statusText, body) {
166
+ super(`API error ${status} ${statusText}: ${body}`);
167
+ this.status = status;
168
+ this.statusText = statusText;
169
+ this.body = body;
170
+ this.name = "ApiError";
171
+ }
172
+ };
173
+ function request(method, path, body) {
174
+ return __async(this, null, function* () {
175
+ const config = loadConfig();
176
+ if (!config.apiKey) {
177
+ throw new Error(
178
+ 'Not authenticated. Run "mr init" to set up your API key.'
179
+ );
180
+ }
181
+ const url = `${config.apiUrl}${path}`;
182
+ const headers = {
183
+ Authorization: `Bearer ${config.apiKey}`,
184
+ "Content-Type": "application/json"
185
+ };
186
+ const res = yield fetch(url, {
187
+ method,
188
+ headers,
189
+ body: body ? JSON.stringify(body) : void 0
190
+ });
191
+ const text = yield res.text();
192
+ if (!res.ok) {
193
+ throw new ApiError(res.status, res.statusText, text);
194
+ }
195
+ if (!text) return null;
196
+ try {
197
+ return JSON.parse(text);
198
+ } catch (e) {
199
+ return text;
200
+ }
201
+ });
202
+ }
203
+ var api = {
204
+ get: (path) => request("GET", path),
205
+ post: (path, body) => request("POST", path, body),
206
+ patch: (path, body) => request("PATCH", path, body),
207
+ del: (path) => request("DELETE", path)
208
+ };
209
+
210
+ // commands/projects.ts
211
+ var projectsCommand = new Command4("projects").description("List all projects").action(() => __async(null, null, function* () {
212
+ const projects = yield api.get("/api/projects");
213
+ console.log(JSON.stringify(projects, null, 2));
214
+ }));
215
+
216
+ // commands/tasks.ts
217
+ import { Command as Command5 } from "commander";
218
+ var tasksCommand = new Command5("tasks").description("List tasks for the linked project").option("--in-progress", "Show only in-progress tasks").option("--completed", "Show only completed tasks").option("--all", "Show all tasks").action((opts) => __async(null, null, function* () {
219
+ const projectId = getLinkedProjectId();
220
+ if (!projectId) {
221
+ console.error(
222
+ 'No project linked to this directory. Run "mr link <project-id>" first.'
223
+ );
224
+ process.exit(1);
225
+ }
226
+ let path = `/api/tasks`;
227
+ const params = new URLSearchParams();
228
+ params.set("projectId", projectId);
229
+ if (opts.inProgress) {
230
+ params.set("inProgress", "true");
231
+ } else if (opts.completed) {
232
+ params.set("completed", "true");
233
+ } else if (!opts.all) {
234
+ params.set("completed", "false");
235
+ }
236
+ path += `?${params.toString()}`;
237
+ const tasks = yield api.get(path);
238
+ console.log(JSON.stringify(tasks, null, 2));
239
+ }));
240
+
241
+ // commands/link.ts
242
+ import { Command as Command6 } from "commander";
243
+ var linkCommand = new Command6("link").description("Associate current directory with a project").argument("[project-id]", "Project ID to link").action((projectId) => __async(null, null, function* () {
244
+ if (!projectId) {
245
+ const current = getLinkedProjectId();
246
+ if (current) {
247
+ console.log(`Linked to project: ${current}`);
248
+ } else {
249
+ console.log("No project linked to this directory.");
250
+ }
251
+ return;
252
+ }
253
+ const config = loadConfig();
254
+ config.directories[process.cwd()] = projectId;
255
+ saveConfig(config);
256
+ console.log(`Linked ${process.cwd()} to project ${projectId}`);
257
+ }));
258
+ var unlinkCommand = new Command6("unlink").description("Remove current directory's project association").action(() => __async(null, null, function* () {
259
+ const config = loadConfig();
260
+ const cwd = process.cwd();
261
+ if (!config.directories[cwd]) {
262
+ console.log("No project linked to this directory.");
263
+ return;
264
+ }
265
+ delete config.directories[cwd];
266
+ saveConfig(config);
267
+ console.log("Unlinked current directory.");
268
+ }));
269
+
270
+ // commands/context.ts
271
+ import { Command as Command7 } from "commander";
272
+ import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
273
+ import { join as join2 } from "path";
274
+ var contextCommand = new Command7("context").description("Output project context JSON for Claude Code").option("--install", "Install Claude Code command to .claude/commands/").action((opts) => __async(null, null, function* () {
275
+ var _a;
276
+ if (opts.install) {
277
+ const dir = join2(process.cwd(), ".claude", "commands");
278
+ if (!existsSync2(dir)) {
279
+ mkdirSync2(dir, { recursive: true });
280
+ }
281
+ const filePath = join2(dir, "mr-tasks.md");
282
+ writeFileSync2(
283
+ filePath,
284
+ "Run the following command and use the output as context for this project:\n\n`mr context`\n"
285
+ );
286
+ console.log(`Installed Claude Code command to ${filePath}`);
287
+ return;
288
+ }
289
+ const projectId = getLinkedProjectId();
290
+ if (!projectId) {
291
+ console.error(
292
+ 'No project linked to this directory. Run "mr link <project-id>" first.'
293
+ );
294
+ process.exit(1);
295
+ }
296
+ const project = yield api.get(`/api/projects/${projectId}`);
297
+ const allTasks = yield api.get(
298
+ `/api/projects/${projectId}/tasks`
299
+ );
300
+ const inProgress = allTasks.filter((t) => t.inProgress && !t.completed);
301
+ const todo = allTasks.filter((t) => !t.completed && !t.inProgress);
302
+ const recentlyCompleted = allTasks.filter((t) => t.completed);
303
+ const summaryLines = [
304
+ `Project: ${project.name} (${project.status})`,
305
+ ...inProgress.map((t) => `In Progress: ${t.title}`),
306
+ `Todo: ${todo.length} tasks`,
307
+ `Recently Completed: ${recentlyCompleted.length} tasks`
308
+ ];
309
+ const output = {
310
+ project: {
311
+ name: project.name,
312
+ status: project.status,
313
+ description: (_a = project.description) != null ? _a : ""
314
+ },
315
+ tasks: {
316
+ inProgress,
317
+ todo,
318
+ recentlyCompleted
319
+ },
320
+ summary: summaryLines.join("\n")
321
+ };
322
+ console.log(JSON.stringify(output, null, 2));
323
+ }));
324
+
325
+ // commands/watch.ts
326
+ import { Command as Command8 } from "commander";
327
+ import { spawn as spawn2, exec } from "child_process";
328
+ import { resolve } from "path";
329
+ import * as readline from "readline";
330
+ var c = {
331
+ reset: "\x1B[0m",
332
+ bold: "\x1B[1m",
333
+ dim: "\x1B[2m",
334
+ cyan: "\x1B[36m",
335
+ green: "\x1B[32m",
336
+ yellow: "\x1B[33m",
337
+ red: "\x1B[31m",
338
+ magenta: "\x1B[35m",
339
+ blue: "\x1B[34m",
340
+ white: "\x1B[37m",
341
+ gray: "\x1B[90m"
342
+ };
343
+ function paint(color, text) {
344
+ return `${c[color]}${text}${c.reset}`;
345
+ }
346
+ function timestamp() {
347
+ return paint("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
348
+ }
349
+ function taskTag(sid) {
350
+ return paint("yellow", `[${sid}]`);
351
+ }
352
+ function watchTag() {
353
+ return paint("magenta", "[watch]");
354
+ }
355
+ function logInfo(prefix, msg) {
356
+ console.log(`${timestamp()} ${prefix} ${msg}`);
357
+ }
358
+ function logSuccess(prefix, msg) {
359
+ console.log(`${timestamp()} ${prefix} ${paint("green", "\u2713")} ${msg}`);
360
+ }
361
+ function logError(prefix, msg) {
362
+ console.error(`${timestamp()} ${prefix} ${paint("red", "\u2717")} ${msg}`);
363
+ }
364
+ function logWarn(prefix, msg) {
365
+ console.log(`${timestamp()} ${prefix} ${paint("yellow", "!")} ${msg}`);
366
+ }
367
+ function logDispatch(prefix, msg) {
368
+ console.log(`${timestamp()} ${prefix} ${paint("cyan", "\u2192")} ${msg}`);
369
+ }
370
+ function logSpinner(prefix, msg) {
371
+ console.log(`${timestamp()} ${prefix} ${paint("blue", "\u27F3")} ${msg}`);
372
+ }
373
+ function findDirectoryForProject(config, projectId, rootDir) {
374
+ for (const [dir, pid] of Object.entries(config.directories)) {
375
+ if (pid === projectId && resolve(dir).startsWith(resolve(rootDir))) {
376
+ return dir;
377
+ }
378
+ }
379
+ return null;
380
+ }
381
+ function slugify(title) {
382
+ return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 30);
383
+ }
384
+ function shortId(id) {
385
+ return id.slice(0, 8);
386
+ }
387
+ function worktreePath(sid) {
388
+ return `.mr-worktrees/mr-${sid}`;
389
+ }
390
+ function pullLatestMain(repoDir, prefix) {
391
+ return new Promise((resolve3) => {
392
+ exec(
393
+ "git pull origin main --ff-only",
394
+ { cwd: repoDir },
395
+ (err, stdout, stderr) => {
396
+ if (err) {
397
+ logWarn(prefix, `git pull failed (proceeding anyway): ${stderr.trim() || err.message}`);
398
+ } else {
399
+ const msg = stdout.trim();
400
+ if (msg && msg !== "Already up to date.") {
401
+ logInfo(prefix, `pulled latest main: ${paint("gray", msg.split("\n")[0])}`);
402
+ }
403
+ }
404
+ resolve3();
405
+ }
406
+ );
407
+ });
408
+ }
409
+ function buildPlanningPrompt(task, repoDir) {
410
+ const notes = task.notes ? `
411
+
412
+ Task notes:
413
+ ${task.notes}` : "";
414
+ return [
415
+ `You are reviewing a task from MR Manager. Your job is to produce a concise implementation plan \u2014 do NOT make any changes yet.`,
416
+ ``,
417
+ `Working directory: ${repoDir}`,
418
+ ``,
419
+ `## Task`,
420
+ `Title: ${task.title}`,
421
+ `ID: ${task.id}${notes}`,
422
+ ``,
423
+ `## Instructions`,
424
+ `1. Run \`mr context\` to understand the project state.`,
425
+ `2. Explore the relevant files and repos under ${repoDir}.`,
426
+ `3. Output a short plan: which files/repos you would change, what the approach is, and any risks or open questions.`,
427
+ ``,
428
+ `Do not write or edit any files. Output the plan only.`
429
+ ].join("\n");
430
+ }
431
+ function findPrUrl(branchName, repoDir) {
432
+ return new Promise((resolve3) => {
433
+ exec(
434
+ `gh pr view "${branchName}" --json url -q .url`,
435
+ { cwd: repoDir },
436
+ (err, stdout) => {
437
+ if (err) resolve3(null);
438
+ else resolve3(stdout.trim() || null);
439
+ }
440
+ );
441
+ });
442
+ }
443
+ function buildExecutionPrompt(task, repoDir, subtasks) {
444
+ const sid = shortId(task.id);
445
+ const slug = slugify(task.title);
446
+ const branchName = `mr/${sid}/${slug}`;
447
+ const wtPath = worktreePath(sid);
448
+ const notes = task.notes ? `
449
+
450
+ Task notes:
451
+ ${task.notes}` : "";
452
+ const pendingSubtasks = subtasks.filter((s) => !s.completed);
453
+ const subtaskSection = pendingSubtasks.length > 0 ? [
454
+ ``,
455
+ `## Subtasks`,
456
+ ``,
457
+ `The following subtasks need to be completed. As you finish each one, mark it done by running:`,
458
+ `\`mr subtask-complete ${task.id} <subtask-id>\``,
459
+ ``,
460
+ ...pendingSubtasks.map((s) => `- [ ] ${s.title} (id: ${s.id})`),
461
+ ``
462
+ ].join("\n") : "";
463
+ return [
464
+ `You are an autonomous agent working on a task from MR Manager.`,
465
+ `Working directory: ${repoDir}`,
466
+ ``,
467
+ `## Task`,
468
+ `Title: ${task.title}`,
469
+ `ID: ${task.id}${notes}`,
470
+ subtaskSection,
471
+ `## Instructions`,
472
+ ``,
473
+ `1. Run \`mr context\` first to understand the full project state.`,
474
+ ``,
475
+ `2. Set up an isolated working environment:`,
476
+ ` - If ${repoDir} is a git repo, create a worktree: \`git worktree add -b ${branchName} ${wtPath}\``,
477
+ ` - If ${repoDir} is not itself a git repo, it is likely a parent/wrapper folder \u2014 look for git repos inside it and create worktrees in the appropriate one(s).`,
478
+ ` - If the task spans multiple repos, set up worktrees in each one as needed.`,
479
+ ``,
480
+ `3. Implement the task. You may read, write, and run code across any relevant repos under ${repoDir}.`,
481
+ ...pendingSubtasks.length > 0 ? [
482
+ ` - Work through each subtask in order. After completing each subtask, immediately run \`mr subtask-complete ${task.id} <subtask-id>\` to mark it done before moving on.`
483
+ ] : [],
484
+ ``,
485
+ `4. Once implementation is complete, for each repo that has changes:`,
486
+ ` a. Commit all changes with a clear, descriptive message.`,
487
+ ` b. Push the branch: \`git push -u origin HEAD\``,
488
+ ` c. Open a pull request: \`gh pr create --title "${task.title}" --body "Resolves MR Manager task ${task.id}"\``,
489
+ ``,
490
+ `5. Clean up any worktrees you created: \`git worktree remove --force <path>\``,
491
+ ``,
492
+ `Complete all steps autonomously without asking for confirmation. Exit with code 0 when done.`
493
+ ].join("\n");
494
+ }
495
+ function runPlanningPhase(task, repoDir) {
496
+ return new Promise((resolve3, reject) => {
497
+ var _a, _b;
498
+ const child = spawn2(
499
+ "claude",
500
+ ["--permission-mode", "plan", "-p", buildPlanningPrompt(task, repoDir)],
501
+ { cwd: repoDir, stdio: ["ignore", "pipe", "pipe"] }
502
+ );
503
+ let output = "";
504
+ (_a = child.stdout) == null ? void 0 : _a.on("data", (d) => {
505
+ output += d.toString();
506
+ });
507
+ (_b = child.stderr) == null ? void 0 : _b.on("data", (d) => {
508
+ output += d.toString();
509
+ });
510
+ child.on("exit", (code) => {
511
+ if (code === 0) resolve3(output.trim());
512
+ else reject(new Error(`Planning exited with code ${code}
513
+ ${output.trim()}`));
514
+ });
515
+ });
516
+ }
517
+ function askYesNo(question) {
518
+ return new Promise((resolve3) => {
519
+ const rl = readline.createInterface({
520
+ input: process.stdin,
521
+ output: process.stdout
522
+ });
523
+ rl.question(question, (answer) => {
524
+ rl.close();
525
+ resolve3(answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes");
526
+ });
527
+ });
528
+ }
529
+ function spawnClaude(repoDir, prompt, prefix) {
530
+ var _a, _b;
531
+ const child = spawn2(
532
+ "claude",
533
+ ["-p", "--dangerously-skip-permissions", prompt],
534
+ { cwd: repoDir, stdio: ["ignore", "pipe", "pipe"] }
535
+ );
536
+ (_a = child.stdout) == null ? void 0 : _a.on("data", (data) => {
537
+ for (const line of data.toString().split("\n")) {
538
+ if (line) console.log(`${timestamp()} ${prefix} ${paint("dim", line)}`);
539
+ }
540
+ });
541
+ (_b = child.stderr) == null ? void 0 : _b.on("data", (data) => {
542
+ for (const line of data.toString().split("\n")) {
543
+ if (line) logError(prefix, paint("dim", line));
544
+ }
545
+ });
546
+ return child;
547
+ }
548
+ var watchCommand = new Command8("watch").description(
549
+ "Watch for in-progress tasks and autonomously dispatch Claude Code to work on them"
550
+ ).option("--interval <seconds>", "Polling interval in seconds", "15").option("--dry-run", "Show what would be dispatched without spawning Claude", false).option("--plan-approval", "Show Claude's plan and ask for approval before executing", false).option("--root <dir>", "Root directory filter for linked repos (default: cwd)").action((opts) => __async(null, null, function* () {
551
+ const intervalMs = parseInt(opts.interval, 10) * 1e3;
552
+ const dryRun = opts.dryRun;
553
+ const planApproval = opts.planApproval;
554
+ const rootDir = opts.root ? resolve(opts.root) : process.cwd();
555
+ const active = /* @__PURE__ */ new Map();
556
+ const failed = /* @__PURE__ */ new Map();
557
+ const queued = /* @__PURE__ */ new Set();
558
+ const approvalQueue = [];
559
+ let approvalRunning = false;
560
+ const flags = [
561
+ `interval=${paint("cyan", opts.interval + "s")}`,
562
+ `root=${paint("cyan", rootDir)}`,
563
+ ...planApproval ? [paint("yellow", "plan-approval")] : [],
564
+ ...dryRun ? [paint("yellow", "dry-run")] : []
565
+ ].join(" ");
566
+ const banner = [
567
+ ``,
568
+ paint("magenta", ` \u2554\u2566\u2557\u2566\u2550\u2557 \u2566 \u2566\u2554\u2550\u2557\u2554\u2566\u2557\u2554\u2550\u2557\u2566 \u2566`),
569
+ paint("magenta", ` \u2551\u2551\u2551\u2560\u2566\u255D \u2551\u2551\u2551\u2560\u2550\u2563 \u2551 \u2551 \u2560\u2550\u2563`),
570
+ paint("magenta", ` \u2569 \u2569\u2569\u255A\u2550 \u255A\u2569\u255D\u2569 \u2569 \u2569 \u255A\u2550\u255D\u2569 \u2569`),
571
+ paint("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`),
572
+ paint("dim", ` autonomous task runner \xB7 powered by claude`),
573
+ ``
574
+ ].join("\n");
575
+ console.log(banner);
576
+ console.log(` ${flags}
577
+ `);
578
+ function dispatchTask(task, repoDir) {
579
+ return __async(this, null, function* () {
580
+ const sid = shortId(task.id);
581
+ const slug = slugify(task.title);
582
+ const branchName = `mr/${sid}/${slug}`;
583
+ const prefix = taskTag(sid);
584
+ logDispatch(prefix, `"${paint("bold", task.title)}" ${paint("gray", repoDir)}`);
585
+ yield pullLatestMain(repoDir, prefix);
586
+ let subtasks = [];
587
+ try {
588
+ subtasks = yield api.get(`/api/tasks/${task.id}/subtasks`);
589
+ const pending = subtasks.filter((s) => !s.completed);
590
+ if (pending.length > 0) {
591
+ logInfo(prefix, `${paint("cyan", String(pending.length))} subtask(s) to complete`);
592
+ }
593
+ } catch (e) {
594
+ }
595
+ const prompt = buildExecutionPrompt(task, repoDir, subtasks);
596
+ const child = spawnClaude(repoDir, prompt, prefix);
597
+ active.set(task.id, { process: child, title: task.title, repoDir });
598
+ child.on("exit", (code) => __async(null, null, function* () {
599
+ active.delete(task.id);
600
+ queued.delete(task.id);
601
+ if (code === 0) {
602
+ try {
603
+ const prUrl = yield findPrUrl(branchName, repoDir);
604
+ if (prUrl) {
605
+ logSuccess(prefix, `PR ready: ${paint("cyan", prUrl)}`);
606
+ } else {
607
+ logWarn(prefix, `No PR found for branch ${paint("cyan", branchName)}`);
608
+ }
609
+ yield api.patch(`/api/tasks/${task.id}`, __spreadValues({
610
+ inProgress: false,
611
+ readyForReview: true
612
+ }, prUrl ? { link: prUrl } : {}));
613
+ logSuccess(prefix, `"${paint("bold", task.title)}" marked ready for review`);
614
+ } catch (err) {
615
+ logError(prefix, `Failed to update task: ${err.message}`);
616
+ }
617
+ } else {
618
+ logError(prefix, `"${paint("bold", task.title)}" failed (exit ${code}), leaving status unchanged`);
619
+ }
620
+ }));
621
+ });
622
+ }
623
+ function processApprovalQueue() {
624
+ return __async(this, null, function* () {
625
+ if (approvalRunning || approvalQueue.length === 0) return;
626
+ approvalRunning = true;
627
+ const { task, repoDir } = approvalQueue.shift();
628
+ const sid = shortId(task.id);
629
+ const prefix = taskTag(sid);
630
+ try {
631
+ logSpinner(prefix, `Generating plan for "${paint("bold", task.title)}"\u2026`);
632
+ const plan = yield runPlanningPhase(task, repoDir);
633
+ const width = 64;
634
+ const divider = paint("gray", "\u2500".repeat(width));
635
+ const header = `${paint("bold", "Plan")} "${paint("bold", task.title)}" ${taskTag(sid)}`;
636
+ console.log(`
637
+ ${divider}`);
638
+ console.log(header);
639
+ console.log(divider);
640
+ console.log(plan);
641
+ console.log(`${divider}
642
+ `);
643
+ const approved = yield askYesNo(
644
+ `${paint("cyan", "?")} Approve task "${paint("bold", task.title)}"? [y/N] `
645
+ );
646
+ if (approved) {
647
+ dispatchTask(task, repoDir);
648
+ } else {
649
+ logWarn(prefix, `"${paint("bold", task.title)}" denied \u2014 will not retry`);
650
+ failed.set(task.id, "denied by user");
651
+ queued.delete(task.id);
652
+ }
653
+ } catch (err) {
654
+ logError(prefix, `Planning failed: ${err.message} \u2014 will not retry`);
655
+ failed.set(task.id, `planning failed: ${err.message}`);
656
+ queued.delete(task.id);
657
+ } finally {
658
+ approvalRunning = false;
659
+ processApprovalQueue();
660
+ }
661
+ });
662
+ }
663
+ function poll() {
664
+ return __async(this, null, function* () {
665
+ let tasks;
666
+ try {
667
+ tasks = yield api.get("/api/tasks?inProgress=true&delegated=true");
668
+ } catch (err) {
669
+ logError(watchTag(), `Failed to fetch tasks: ${err.message}`);
670
+ return;
671
+ }
672
+ const config = loadConfig();
673
+ const inProgressIds = new Set(tasks.map((t) => t.id));
674
+ for (const [taskId, entry] of active) {
675
+ if (!inProgressIds.has(taskId)) {
676
+ logWarn(watchTag(), `Task ${paint("yellow", taskId.slice(0, 8))} no longer in-progress, terminating\u2026`);
677
+ entry.process.kill("SIGTERM");
678
+ active.delete(taskId);
679
+ queued.delete(taskId);
680
+ }
681
+ }
682
+ for (const taskId of failed.keys()) {
683
+ if (!inProgressIds.has(taskId)) failed.delete(taskId);
684
+ }
685
+ for (const taskId of queued) {
686
+ if (!inProgressIds.has(taskId)) queued.delete(taskId);
687
+ }
688
+ for (const task of tasks) {
689
+ if (queued.has(task.id)) continue;
690
+ if (failed.has(task.id)) continue;
691
+ const sid = shortId(task.id);
692
+ const prefix = taskTag(sid);
693
+ const repoDir = findDirectoryForProject(config, task.projectId, rootDir);
694
+ if (!repoDir) {
695
+ const reason = `no linked directory found under ${rootDir}`;
696
+ logError(prefix, `"${task.title}": ${reason} \u2014 will not retry`);
697
+ failed.set(task.id, reason);
698
+ continue;
699
+ }
700
+ queued.add(task.id);
701
+ if (dryRun) {
702
+ logInfo(
703
+ watchTag(),
704
+ `${paint("yellow", "[dry-run]")} would dispatch "${paint("bold", task.title)}" (${paint("gray", task.id)}) in ${paint("cyan", repoDir)}`
705
+ );
706
+ continue;
707
+ }
708
+ if (planApproval) {
709
+ approvalQueue.push({ task, repoDir });
710
+ processApprovalQueue();
711
+ } else {
712
+ dispatchTask(task, repoDir);
713
+ }
714
+ }
715
+ });
716
+ }
717
+ function shutdown() {
718
+ return __async(this, null, function* () {
719
+ console.log(`
720
+ ${timestamp()} ${watchTag()} Shutting down\u2026`);
721
+ for (const [taskId, entry] of active) {
722
+ logWarn(watchTag(), `Terminating Claude for task ${paint("yellow", taskId.slice(0, 8))}`);
723
+ entry.process.kill("SIGTERM");
724
+ }
725
+ active.clear();
726
+ process.exit(0);
727
+ });
728
+ }
729
+ process.on("SIGINT", shutdown);
730
+ process.on("SIGTERM", shutdown);
731
+ yield poll();
732
+ setInterval(poll, intervalMs);
733
+ }));
734
+
735
+ // commands/start.ts
736
+ import { Command as Command9 } from "commander";
737
+ var c2 = {
738
+ reset: "\x1B[0m",
739
+ bold: "\x1B[1m",
740
+ dim: "\x1B[2m",
741
+ cyan: "\x1B[36m",
742
+ green: "\x1B[32m",
743
+ yellow: "\x1B[33m",
744
+ magenta: "\x1B[35m",
745
+ gray: "\x1B[90m"
746
+ };
747
+ function paint2(color, text) {
748
+ return `${c2[color]}${text}${c2.reset}`;
749
+ }
750
+ function printTaskBanner(action, title, id) {
751
+ const sid = id.slice(0, 8);
752
+ const divider = paint2("dim", "\u2500".repeat(44));
753
+ console.log([
754
+ ``,
755
+ paint2("magenta", ` \u2554\u2566\u2557\u2566\u2550\u2557 `) + paint2("bold", action),
756
+ paint2("magenta", ` \u2551\u2551\u2551\u2560\u2566\u255D `) + divider,
757
+ paint2("magenta", ` \u2569 \u2569\u2569\u255A\u2550 `) + title,
758
+ ` ` + paint2("gray", sid),
759
+ ``
760
+ ].join("\n"));
761
+ }
762
+ var startCommand = new Command9("start").description("Mark a task as in-progress (you are working on it)").argument("<task-id>", "Task ID to start").action((taskId) => __async(null, null, function* () {
763
+ const task = yield api.patch(`/api/tasks/${taskId}`, {
764
+ inProgress: true,
765
+ delegated: false
766
+ });
767
+ printTaskBanner(paint2("green", "start"), task.title, task.id);
768
+ }));
769
+ var delegateCommand = new Command9("delegate").description("Mark a task as in-progress and delegate it to the watch agent").argument("<task-id>", "Task ID to delegate").action((taskId) => __async(null, null, function* () {
770
+ const task = yield api.patch(`/api/tasks/${taskId}`, {
771
+ inProgress: true,
772
+ delegated: true
773
+ });
774
+ printTaskBanner(paint2("cyan", "delegate"), task.title, task.id);
775
+ }));
776
+
777
+ // commands/create.ts
778
+ import { Command as Command10 } from "commander";
779
+ var createCommand = new Command10("create").description("Create a new task in the linked project").argument("<title>", "Task title").option("--notes <notes>", "Task notes").option("--in-progress", "Mark the task as in-progress immediately").action((title, opts) => __async(null, null, function* () {
780
+ var _a;
781
+ const projectId = getLinkedProjectId();
782
+ if (!projectId) {
783
+ console.error(
784
+ 'No project linked to this directory. Run "mr link <project-id>" first.'
785
+ );
786
+ process.exit(1);
787
+ }
788
+ const task = yield api.post("/api/tasks", {
789
+ title,
790
+ projectId,
791
+ notes: opts.notes,
792
+ inProgress: (_a = opts.inProgress) != null ? _a : false
793
+ });
794
+ const t = task;
795
+ console.log(`Created: ${t.title} (${t.id})`);
796
+ }));
797
+
798
+ // commands/complete.ts
799
+ import { Command as Command11 } from "commander";
800
+ var c3 = {
801
+ reset: "\x1B[0m",
802
+ bold: "\x1B[1m",
803
+ dim: "\x1B[2m",
804
+ cyan: "\x1B[36m",
805
+ green: "\x1B[32m",
806
+ yellow: "\x1B[33m",
807
+ magenta: "\x1B[35m",
808
+ gray: "\x1B[90m"
809
+ };
810
+ function paint3(color, text) {
811
+ return `${c3[color]}${text}${c3.reset}`;
812
+ }
813
+ function printTaskBanner2(action, title, id) {
814
+ const sid = id.slice(0, 8);
815
+ const divider = paint3("dim", "\u2500".repeat(44));
816
+ console.log([
817
+ ``,
818
+ paint3("magenta", ` \u2554\u2566\u2557\u2566\u2550\u2557 `) + paint3("bold", action),
819
+ paint3("magenta", ` \u2551\u2551\u2551\u2560\u2566\u255D `) + divider,
820
+ paint3("magenta", ` \u2569 \u2569\u2569\u255A\u2550 `) + title,
821
+ ` ` + paint3("gray", sid),
822
+ ``
823
+ ].join("\n"));
824
+ }
825
+ var completeCommand = new Command11("complete").description("Mark a task as completed").argument("<task-id>", "Task ID to complete").action((taskId) => __async(null, null, function* () {
826
+ const task = yield api.patch(`/api/tasks/${taskId}`, {
827
+ completed: true,
828
+ inProgress: false
829
+ });
830
+ printTaskBanner2(paint3("green", "complete \u2713"), task.title, task.id);
831
+ }));
832
+
833
+ // commands/subtask-complete.ts
834
+ import { Command as Command12 } from "commander";
835
+ var subtaskCompleteCommand = new Command12("subtask-complete").description("Mark a subtask as completed").argument("<task-id>", "Parent task ID").argument("<subtask-id>", "Subtask ID to complete").action((taskId, subtaskId) => __async(null, null, function* () {
836
+ const subtask = yield api.patch(
837
+ `/api/tasks/${taskId}/subtasks/${subtaskId}`,
838
+ { completed: true }
839
+ );
840
+ console.log(`\u2713 Subtask completed: ${subtask.title}`);
841
+ }));
842
+
843
+ // commands/digest.ts
844
+ import { Command as Command13 } from "commander";
845
+ import { spawn as spawn3 } from "child_process";
846
+ var c4 = {
847
+ reset: "\x1B[0m",
848
+ bold: "\x1B[1m",
849
+ dim: "\x1B[2m",
850
+ cyan: "\x1B[36m",
851
+ green: "\x1B[32m",
852
+ yellow: "\x1B[33m",
853
+ red: "\x1B[31m",
854
+ magenta: "\x1B[35m",
855
+ gray: "\x1B[90m"
856
+ };
857
+ function paint4(color, text) {
858
+ return `${c4[color]}${text}${c4.reset}`;
859
+ }
860
+ function timestamp2() {
861
+ return paint4("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
862
+ }
863
+ function digestTag() {
864
+ return paint4("magenta", "[digest]");
865
+ }
866
+ function shortId2(id) {
867
+ return id.slice(0, 8);
868
+ }
869
+ function log(msg) {
870
+ console.log(`${timestamp2()} ${digestTag()} ${msg}`);
871
+ }
872
+ function logOk(msg) {
873
+ console.log(`${timestamp2()} ${digestTag()} ${paint4("green", "\u2713")} ${msg}`);
874
+ }
875
+ function logErr(msg) {
876
+ console.error(`${timestamp2()} ${digestTag()} ${paint4("red", "\u2717")} ${msg}`);
877
+ }
878
+ function buildPrompt(messages) {
879
+ const emailsText = messages.map(
880
+ (msg, i) => `Email ${i + 1}:
881
+ ID: ${msg.id}
882
+ Subject: ${msg.subject}
883
+ From: ${msg.sender} <${msg.senderEmail}>
884
+ Date: ${msg.date}
885
+ Snippet: ${msg.snippet}
886
+ Body preview: ${msg.body.slice(0, 500)}`
887
+ ).join("\n\n---\n\n");
888
+ return `You are an email triage assistant. Analyze the following unread emails and produce a structured digest.
889
+
890
+ For each email, determine:
891
+ 1. Is it relevant/actionable (requires attention, is personal, work-related, or important)?
892
+ 2. Or is it not relevant (newsletters, marketing, automated notifications, promotions)?
893
+
894
+ Also write a 2-3 sentence executive summary of the overall inbox state.
895
+
896
+ Here are the unread emails:
897
+
898
+ ${emailsText}
899
+
900
+ Respond with a JSON object in this exact format:
901
+ {
902
+ "summary": "2-3 sentence overview of the inbox",
903
+ "items": [
904
+ {
905
+ "emailId": "the email ID",
906
+ "isRelevant": true or false,
907
+ "relevanceReason": "brief explanation of why relevant or not"
908
+ }
909
+ ]
910
+ }
911
+
912
+ 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.`;
913
+ }
914
+ function runClaude(prompt) {
915
+ return new Promise((resolve3, reject) => {
916
+ var _a, _b;
917
+ const child = spawn3("claude", ["-p", "--dangerously-skip-permissions", prompt], {
918
+ stdio: ["ignore", "pipe", "pipe"]
919
+ });
920
+ let output = "";
921
+ let errOutput = "";
922
+ (_a = child.stdout) == null ? void 0 : _a.on("data", (d) => {
923
+ output += d.toString();
924
+ });
925
+ (_b = child.stderr) == null ? void 0 : _b.on("data", (d) => {
926
+ errOutput += d.toString();
927
+ });
928
+ child.on("exit", (code) => {
929
+ if (code === 0) resolve3(output.trim());
930
+ else reject(new Error(`claude exited with code ${code}
931
+ ${errOutput.trim()}`));
932
+ });
933
+ });
934
+ }
935
+ function processDigest(digest) {
936
+ return __async(this, null, function* () {
937
+ const sid = shortId2(digest.id);
938
+ if (digest.rawMessages.length === 0) {
939
+ yield api.post(`/api/email-digest/${digest.id}/complete`, {
940
+ summary: "No unread emails found in your inbox.",
941
+ items: []
942
+ });
943
+ logOk(`${paint4("yellow", sid)} completed (no emails)`);
944
+ return;
945
+ }
946
+ log(`${paint4("yellow", sid)} analyzing ${paint4("cyan", String(digest.rawMessages.length))} emails\u2026`);
947
+ const prompt = buildPrompt(digest.rawMessages);
948
+ const output = yield runClaude(prompt);
949
+ const jsonMatch = output.match(/\{[\s\S]*\}/);
950
+ if (!jsonMatch) {
951
+ throw new Error("Could not extract JSON from Claude output");
952
+ }
953
+ const parsed = JSON.parse(jsonMatch[0]);
954
+ const msgMap = new Map(digest.rawMessages.map((m) => [m.id, m]));
955
+ const items = parsed.items.map((item) => {
956
+ const msg = msgMap.get(item.emailId);
957
+ if (!msg) return null;
958
+ return {
959
+ emailId: msg.id,
960
+ threadId: msg.threadId,
961
+ subject: msg.subject,
962
+ sender: msg.sender,
963
+ senderEmail: msg.senderEmail,
964
+ date: msg.date,
965
+ snippet: msg.snippet,
966
+ isRelevant: item.isRelevant,
967
+ relevanceReason: item.relevanceReason,
968
+ archived: false
969
+ };
970
+ }).filter((item) => item !== null);
971
+ yield api.post(`/api/email-digest/${digest.id}/complete`, {
972
+ summary: parsed.summary,
973
+ items
974
+ });
975
+ logOk(
976
+ `${paint4("yellow", sid)} done \u2014 ${paint4("green", String(items.filter((i) => i.isRelevant).length))} relevant, ${paint4("gray", String(items.filter((i) => !i.isRelevant).length))} not relevant`
977
+ );
978
+ });
979
+ }
980
+ function generateDigest() {
981
+ return __async(this, null, function* () {
982
+ let result;
983
+ try {
984
+ result = yield api.post("/api/email-digest/generate");
985
+ } catch (err) {
986
+ logErr(`Auto-generate failed: ${err.message}`);
987
+ return;
988
+ }
989
+ if (result.needsReauth) {
990
+ logErr("Auto-generate skipped: Gmail re-authentication required");
991
+ return;
992
+ }
993
+ if (result.status === "completed") {
994
+ log(`Auto-generated digest ${paint4("yellow", shortId2(result.id))} \u2014 no new emails`);
995
+ } else {
996
+ log(`Auto-generated digest ${paint4("yellow", shortId2(result.id))} \u2014 queued for processing`);
997
+ }
998
+ });
999
+ }
1000
+ var digestCommand = new Command13("digest").description("Process pending email digests using Claude Code (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).action((opts) => __async(null, null, function* () {
1001
+ const intervalMs = parseInt(opts.interval, 10) * 1e3;
1002
+ const generateIntervalMs = parseInt(opts.generateInterval, 10) * 1e3;
1003
+ const once = opts.once;
1004
+ const processing = /* @__PURE__ */ new Set();
1005
+ const banner = [
1006
+ ``,
1007
+ paint4("magenta", ` \u2554\u2566\u2557\u2566\u2550\u2557 \u2554\u2566\u2557\u2566\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2566\u2557`),
1008
+ paint4("magenta", ` \u2551\u2551\u2551\u2560\u2566\u255D \u2551\u2551\u2551\u2551 \u2566\u2551\u2563 \u255A\u2550\u2557 \u2551`),
1009
+ paint4("magenta", ` \u2569 \u2569\u2569\u255A\u2550 \u2550\u2569\u255D\u2569\u255A\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u255D \u2569`),
1010
+ 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`),
1011
+ paint4("dim", ` email digest processor \xB7 powered by claude`),
1012
+ ``
1013
+ ].join("\n");
1014
+ console.log(banner);
1015
+ console.log(
1016
+ ` interval=${paint4("cyan", opts.interval + "s")} generate-interval=${paint4("cyan", opts.generateInterval + "s")}${once ? ` ${paint4("yellow", "once")}` : ""}
1017
+ `
1018
+ );
1019
+ function poll() {
1020
+ return __async(this, null, function* () {
1021
+ let pending;
1022
+ try {
1023
+ pending = yield api.get("/api/email-digest/pending");
1024
+ } catch (err) {
1025
+ logErr(`Failed to fetch pending digests: ${err.message}`);
1026
+ return;
1027
+ }
1028
+ if (pending.length === 0) return;
1029
+ log(`Found ${paint4("cyan", String(pending.length))} pending digest(s)`);
1030
+ for (const digest of pending) {
1031
+ if (processing.has(digest.id)) continue;
1032
+ processing.add(digest.id);
1033
+ processDigest(digest).catch((err) => logErr(`${shortId2(digest.id)} failed: ${err.message}`)).finally(() => processing.delete(digest.id));
1034
+ }
1035
+ });
1036
+ }
1037
+ process.on("SIGINT", () => {
1038
+ console.log(`
1039
+ ${timestamp2()} ${digestTag()} Shutting down\u2026`);
1040
+ process.exit(0);
1041
+ });
1042
+ yield generateDigest();
1043
+ yield poll();
1044
+ if (once) {
1045
+ const wait = () => new Promise((resolve3) => {
1046
+ if (processing.size === 0) return resolve3();
1047
+ const check = setInterval(() => {
1048
+ if (processing.size === 0) {
1049
+ clearInterval(check);
1050
+ resolve3();
1051
+ }
1052
+ }, 500);
1053
+ });
1054
+ yield wait();
1055
+ } else {
1056
+ setInterval(poll, intervalMs);
1057
+ setInterval(generateDigest, generateIntervalMs);
1058
+ }
1059
+ }));
1060
+
1061
+ // commands/up.ts
1062
+ import { Command as Command14 } from "commander";
1063
+ import { spawn as spawn4 } from "child_process";
1064
+ import { resolve as resolve2 } from "path";
1065
+ import { fileURLToPath } from "url";
1066
+ var c5 = {
1067
+ reset: "\x1B[0m",
1068
+ bold: "\x1B[1m",
1069
+ dim: "\x1B[2m",
1070
+ cyan: "\x1B[36m",
1071
+ green: "\x1B[32m",
1072
+ yellow: "\x1B[33m",
1073
+ red: "\x1B[31m",
1074
+ magenta: "\x1B[35m",
1075
+ gray: "\x1B[90m"
1076
+ };
1077
+ function paint5(color, text) {
1078
+ return `${c5[color]}${text}${c5.reset}`;
1079
+ }
1080
+ function timestamp3() {
1081
+ return paint5("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
1082
+ }
1083
+ function upTag() {
1084
+ return paint5("cyan", "[up]");
1085
+ }
1086
+ function log2(msg) {
1087
+ console.log(`${timestamp3()} ${upTag()} ${msg}`);
1088
+ }
1089
+ function logErr2(msg) {
1090
+ console.error(`${timestamp3()} ${upTag()} ${paint5("red", "\u2717")} ${msg}`);
1091
+ }
1092
+ function spawnMr(args) {
1093
+ const entry = process.argv[1];
1094
+ const child = spawn4(process.execPath, [entry, ...args], {
1095
+ stdio: ["ignore", "pipe", "pipe"]
1096
+ });
1097
+ return child;
1098
+ }
1099
+ 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)").action((opts) => {
1100
+ var _a, _b, _c, _d;
1101
+ const watchInterval = (_a = opts.watchInterval) != null ? _a : opts.interval;
1102
+ const digestInterval = (_b = opts.digestInterval) != null ? _b : opts.interval;
1103
+ const watchArgs = ["watch", "--interval", watchInterval];
1104
+ if (opts.dryRun) watchArgs.push("--dry-run");
1105
+ if (opts.planApproval) watchArgs.push("--plan-approval");
1106
+ if (opts.root) watchArgs.push("--root", opts.root);
1107
+ const digestArgs = ["digest", "--interval", digestInterval];
1108
+ const banner = [
1109
+ ``,
1110
+ paint5("cyan", ` \u2554\u2566\u2557\u2566\u2550\u2557 \u2566 \u2566\u2554\u2550\u2557`),
1111
+ paint5("magenta", ` \u2551\u2551\u2551\u2560\u2566\u255D \u2551 \u2551\u2560\u2550\u255D`),
1112
+ paint5("cyan", ` \u2569 \u2569\u2569\u255A\u2550 \u255A\u2550\u255D\u2569 `),
1113
+ 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`),
1114
+ paint5("dim", ` mr watch + mr digest \xB7 powered by claude`),
1115
+ ``
1116
+ ].join("\n");
1117
+ console.log(banner);
1118
+ const flags = [
1119
+ `watch-interval=${paint5("cyan", watchInterval + "s")}`,
1120
+ `digest-interval=${paint5("cyan", digestInterval + "s")}`,
1121
+ ...opts.dryRun ? [paint5("yellow", "dry-run")] : [],
1122
+ ...opts.planApproval ? [paint5("yellow", "plan-approval")] : [],
1123
+ ...opts.root ? [`root=${paint5("cyan", resolve2(opts.root))}`] : []
1124
+ ].join(" ");
1125
+ console.log(` ${flags}
1126
+ `);
1127
+ const watchProc = spawnMr(watchArgs);
1128
+ const digestProc = spawnMr(digestArgs);
1129
+ for (const [proc, label] of [[watchProc, "watch"], [digestProc, "digest"]]) {
1130
+ (_c = proc.stdout) == null ? void 0 : _c.on("data", (d) => process.stdout.write(d));
1131
+ (_d = proc.stderr) == null ? void 0 : _d.on("data", (d) => process.stderr.write(d));
1132
+ proc.on("exit", (code, signal) => {
1133
+ if (signal !== "SIGTERM" && signal !== "SIGINT") {
1134
+ logErr2(`mr ${label} exited unexpectedly (code=${code != null ? code : "?"}, signal=${signal != null ? signal : "none"})`);
1135
+ }
1136
+ });
1137
+ }
1138
+ function shutdown() {
1139
+ log2("Shutting down\u2026");
1140
+ watchProc.kill("SIGTERM");
1141
+ digestProc.kill("SIGTERM");
1142
+ setTimeout(() => process.exit(0), 500);
1143
+ }
1144
+ process.on("SIGINT", shutdown);
1145
+ process.on("SIGTERM", shutdown);
1146
+ });
1147
+
1148
+ // index.ts
1149
+ var program = new Command15();
1150
+ program.name("mr").description("Mr. Manager - Task and project management CLI").version("0.1.0");
1151
+ program.addCommand(initCommand);
1152
+ program.addCommand(authCommand);
1153
+ program.addCommand(loginCommand);
1154
+ program.addCommand(projectsCommand);
1155
+ program.addCommand(tasksCommand);
1156
+ program.addCommand(linkCommand);
1157
+ program.addCommand(unlinkCommand);
1158
+ program.addCommand(contextCommand);
1159
+ program.addCommand(watchCommand);
1160
+ program.addCommand(startCommand);
1161
+ program.addCommand(delegateCommand);
1162
+ program.addCommand(createCommand);
1163
+ program.addCommand(completeCommand);
1164
+ program.addCommand(subtaskCompleteCommand);
1165
+ program.addCommand(digestCommand);
1166
+ program.addCommand(upCommand);
1167
+ program.parse();