@dunnewold-labs/mr-manager 0.4.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.
- package/dist/index.mjs +415 -522
- 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
|
|
5
|
-
import { existsSync as
|
|
6
|
-
import { homedir as
|
|
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/
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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
|
|
442
|
-
import { readFileSync as readFileSync5, readdirSync, unlinkSync, existsSync as
|
|
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
|
|
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
|
|
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 (
|
|
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 (
|
|
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 (
|
|
580
|
+
if (existsSync4(join4(wtPath, "bun.lock")) || existsSync4(join4(wtPath, "bun.lockb"))) {
|
|
529
581
|
execSync2("bun install", { cwd: wtPath, stdio: "pipe" });
|
|
530
|
-
} else if (
|
|
582
|
+
} else if (existsSync4(join4(wtPath, "pnpm-lock.yaml"))) {
|
|
531
583
|
execSync2("pnpm install --frozen-lockfile", { cwd: wtPath, stdio: "pipe" });
|
|
532
|
-
} else if (
|
|
584
|
+
} else if (existsSync4(join4(wtPath, "package-lock.json"))) {
|
|
533
585
|
execSync2("npm ci", { cwd: wtPath, stdio: "pipe" });
|
|
534
|
-
} else if (
|
|
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 (
|
|
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 (
|
|
604
|
+
if (existsSync5(join5(wtPath, "bun.lock")) || existsSync5(join5(wtPath, "bun.lockb"))) {
|
|
553
605
|
return { cmd: "bun", args: ["dev"] };
|
|
554
606
|
}
|
|
555
|
-
if (
|
|
607
|
+
if (existsSync5(join5(wtPath, "pnpm-lock.yaml"))) {
|
|
556
608
|
return { cmd: "pnpm", args: ["dev"] };
|
|
557
609
|
}
|
|
558
|
-
if (
|
|
610
|
+
if (existsSync5(join5(wtPath, "package-lock.json"))) {
|
|
559
611
|
return { cmd: "npm", args: ["run", "dev"] };
|
|
560
612
|
}
|
|
561
|
-
if (
|
|
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
|
|
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
|
-
|
|
764
|
+
log2("Test timed out after 5 minutes");
|
|
713
765
|
if (devProc) devProc.kill("SIGTERM");
|
|
714
766
|
}, 5 * 60 * 1e3);
|
|
715
767
|
try {
|
|
716
|
-
|
|
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
|
-
|
|
722
|
-
|
|
773
|
+
log2(`Branch: ${branch}`);
|
|
774
|
+
log2("Creating git worktree...");
|
|
723
775
|
wtPath = createWorktree(localPath, branch, worktreeName);
|
|
724
|
-
|
|
725
|
-
|
|
776
|
+
log2(`Worktree created at ${wtPath}`);
|
|
777
|
+
log2("Installing dependencies...");
|
|
726
778
|
try {
|
|
727
779
|
installDependencies(wtPath);
|
|
728
780
|
} catch (err) {
|
|
729
|
-
|
|
781
|
+
log2(`Warning: dependency install failed: ${err.message}`);
|
|
730
782
|
}
|
|
731
|
-
|
|
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
|
-
|
|
787
|
+
log2(`Dev server running on ${baseUrl}`);
|
|
736
788
|
await browseRunner(["goto", baseUrl]);
|
|
737
789
|
const plan = customPlan || buildDefaultTestPlan(baseUrl);
|
|
738
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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 (
|
|
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 =
|
|
892
|
-
if (!
|
|
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 &&
|
|
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
|
|
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) =>
|
|
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("
|
|
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 =
|
|
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,9 +1886,10 @@ 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 ?
|
|
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();
|
|
@@ -1914,6 +1901,7 @@ var watchCommand = new Command8("watch").description(
|
|
|
1914
1901
|
`interval=${paint("cyan", opts.interval + "s")}`,
|
|
1915
1902
|
`root=${paint("cyan", rootDir)}`,
|
|
1916
1903
|
`agent=${paint("cyan", agent)}`,
|
|
1904
|
+
`stall-timeout=${paint("cyan", formatTimeoutMinutes(taskStallTimeoutMs))}`,
|
|
1917
1905
|
...planApproval ? [paint("yellow", "plan-approval")] : [],
|
|
1918
1906
|
...dryRun ? [paint("yellow", "dry-run")] : [],
|
|
1919
1907
|
...scanAt ? [`scan-at=${paint("cyan", scanAt)}`] : []
|
|
@@ -1983,21 +1971,32 @@ var watchCommand = new Command8("watch").description(
|
|
|
1983
1971
|
}
|
|
1984
1972
|
const prompt2 = buildExecutionPrompt(task, repoDir, subtasks, vcs, protoRefs, feedbackUpdates, existingResources, skillRefs);
|
|
1985
1973
|
const sessionId = agent === "claude" ? randomUUID() : void 0;
|
|
1986
|
-
const
|
|
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;
|
|
1987
1986
|
if (sessionId) {
|
|
1988
1987
|
api.patch(`/api/tasks/${task.id}`, { claudeSessionId: sessionId }).catch(() => {
|
|
1989
1988
|
});
|
|
1990
1989
|
logInfo(prefix, `Claude session: ${paint("dim", sessionId)}`);
|
|
1991
1990
|
}
|
|
1992
|
-
active.set(task.id,
|
|
1991
|
+
active.set(task.id, activeEntry);
|
|
1993
1992
|
child.on("exit", async (code) => {
|
|
1994
1993
|
active.delete(task.id);
|
|
1995
1994
|
finishing.add(task.id);
|
|
1996
1995
|
try {
|
|
1997
1996
|
if (code === 0) {
|
|
1998
1997
|
try {
|
|
1999
|
-
const researchPath =
|
|
2000
|
-
if (
|
|
1998
|
+
const researchPath = resolve2(repoDir, "research.md");
|
|
1999
|
+
if (existsSync7(researchPath)) {
|
|
2001
2000
|
try {
|
|
2002
2001
|
const researchContent = readFileSync5(researchPath, "utf-8");
|
|
2003
2002
|
const existingResearch = existingResources.find((r) => r.type === "research");
|
|
@@ -2019,32 +2018,8 @@ var watchCommand = new Command8("watch").description(
|
|
|
2019
2018
|
logWarn(prefix, `Failed to upload research resource: ${err.message}`);
|
|
2020
2019
|
}
|
|
2021
2020
|
}
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
const sessionEntries = readAndParseSessionLog(sessionId, repoDir);
|
|
2025
|
-
if (sessionEntries && sessionEntries.length > 0) {
|
|
2026
|
-
const sessionContent = JSON.stringify(sessionEntries);
|
|
2027
|
-
const existingSessionLog = existingResources.find((r) => r.type === "session-log");
|
|
2028
|
-
if (existingSessionLog) {
|
|
2029
|
-
await api.patch(`/api/tasks/${task.id}/resources/${existingSessionLog.id}`, {
|
|
2030
|
-
content: sessionContent
|
|
2031
|
-
});
|
|
2032
|
-
logSuccess(prefix, `Updated session log resource (${sessionEntries.length} messages)`);
|
|
2033
|
-
} else {
|
|
2034
|
-
await api.post(`/api/tasks/${task.id}/resources`, {
|
|
2035
|
-
type: "session-log",
|
|
2036
|
-
title: `Session Log \u2014 ${task.title}`,
|
|
2037
|
-
content: sessionContent
|
|
2038
|
-
});
|
|
2039
|
-
logSuccess(prefix, `Uploaded session log (${sessionEntries.length} messages)`);
|
|
2040
|
-
}
|
|
2041
|
-
}
|
|
2042
|
-
} catch (err) {
|
|
2043
|
-
logWarn(prefix, `Failed to upload session log: ${err.message}`);
|
|
2044
|
-
}
|
|
2045
|
-
}
|
|
2046
|
-
const noMrPath = resolve(repoDir, ".mr-no-mr");
|
|
2047
|
-
const noMrRequested = existsSync6(noMrPath);
|
|
2021
|
+
const noMrPath = resolve2(repoDir, ".mr-no-mr");
|
|
2022
|
+
const noMrRequested = existsSync7(noMrPath);
|
|
2048
2023
|
let noMrDescription;
|
|
2049
2024
|
if (noMrRequested) {
|
|
2050
2025
|
noMrDescription = readFileSync5(noMrPath, "utf-8").trim();
|
|
@@ -2077,31 +2052,9 @@ var watchCommand = new Command8("watch").description(
|
|
|
2077
2052
|
} catch (err) {
|
|
2078
2053
|
logError(prefix, `Failed to update task: ${err.message}`);
|
|
2079
2054
|
}
|
|
2080
|
-
} else {
|
|
2055
|
+
} else if (!activeEntry.terminatedForError) {
|
|
2081
2056
|
logError(prefix, `"${paint("bold", task.title)}" failed (exit ${code}), leaving status unchanged`);
|
|
2082
2057
|
await postTaskUpdate(task.id, `Agent failed with exit code ${code}`, "system");
|
|
2083
|
-
if (sessionId) {
|
|
2084
|
-
try {
|
|
2085
|
-
const sessionEntries = readAndParseSessionLog(sessionId, repoDir);
|
|
2086
|
-
if (sessionEntries && sessionEntries.length > 0) {
|
|
2087
|
-
const sessionContent = JSON.stringify(sessionEntries);
|
|
2088
|
-
const existingSessionLog = existingResources.find((r) => r.type === "session-log");
|
|
2089
|
-
if (existingSessionLog) {
|
|
2090
|
-
await api.patch(`/api/tasks/${task.id}/resources/${existingSessionLog.id}`, {
|
|
2091
|
-
content: sessionContent
|
|
2092
|
-
});
|
|
2093
|
-
} else {
|
|
2094
|
-
await api.post(`/api/tasks/${task.id}/resources`, {
|
|
2095
|
-
type: "session-log",
|
|
2096
|
-
title: `Session Log \u2014 ${task.title}`,
|
|
2097
|
-
content: sessionContent
|
|
2098
|
-
});
|
|
2099
|
-
}
|
|
2100
|
-
logInfo(prefix, `Uploaded session log for failed task (${sessionEntries.length} messages)`);
|
|
2101
|
-
}
|
|
2102
|
-
} catch {
|
|
2103
|
-
}
|
|
2104
|
-
}
|
|
2105
2058
|
}
|
|
2106
2059
|
} finally {
|
|
2107
2060
|
queued.delete(task.id);
|
|
@@ -2146,7 +2099,7 @@ var watchCommand = new Command8("watch").description(
|
|
|
2146
2099
|
try {
|
|
2147
2100
|
if (code === 0) {
|
|
2148
2101
|
try {
|
|
2149
|
-
const prdPath =
|
|
2102
|
+
const prdPath = resolve2(repoDir, "prd.md");
|
|
2150
2103
|
let prdContent;
|
|
2151
2104
|
try {
|
|
2152
2105
|
prdContent = readFileSync5(prdPath, "utf-8");
|
|
@@ -2200,7 +2153,7 @@ var watchCommand = new Command8("watch").description(
|
|
|
2200
2153
|
const stalePattern = /^prototype-\d+\.html$/;
|
|
2201
2154
|
for (const f of readdirSync(repoDir).filter((f2) => stalePattern.test(f2))) {
|
|
2202
2155
|
try {
|
|
2203
|
-
unlinkSync(
|
|
2156
|
+
unlinkSync(resolve2(repoDir, f));
|
|
2204
2157
|
} catch {
|
|
2205
2158
|
}
|
|
2206
2159
|
}
|
|
@@ -2230,7 +2183,7 @@ var watchCommand = new Command8("watch").description(
|
|
|
2230
2183
|
const found = readdirSync(repoDir).filter((f) => protoPattern.test(f)).sort();
|
|
2231
2184
|
const files = found.map((f) => ({
|
|
2232
2185
|
name: f,
|
|
2233
|
-
content: readFileSync5(
|
|
2186
|
+
content: readFileSync5(resolve2(repoDir, f), "utf-8")
|
|
2234
2187
|
}));
|
|
2235
2188
|
if (files.length === 0) {
|
|
2236
2189
|
logError(prefix, `No prototype HTML files found in ${repoDir}`);
|
|
@@ -2241,7 +2194,7 @@ var watchCommand = new Command8("watch").description(
|
|
|
2241
2194
|
logSuccess(prefix, `"${paint("bold", proto.title)}" uploaded ${files.length} file(s)`);
|
|
2242
2195
|
for (const file of files) {
|
|
2243
2196
|
try {
|
|
2244
|
-
unlinkSync(
|
|
2197
|
+
unlinkSync(resolve2(repoDir, file.name));
|
|
2245
2198
|
} catch {
|
|
2246
2199
|
}
|
|
2247
2200
|
}
|
|
@@ -2302,7 +2255,7 @@ var watchCommand = new Command8("watch").description(
|
|
|
2302
2255
|
logDispatch(prefix, `"${paint("bold", idea.title)}"${idea.feedback ? paint("cyan", " (iteration)") : ""} ${paint("gray", repoDir)}`);
|
|
2303
2256
|
for (const f of ["idea-plan.md", "idea-tasks.json", "idea-prototype.html"]) {
|
|
2304
2257
|
try {
|
|
2305
|
-
unlinkSync(
|
|
2258
|
+
unlinkSync(resolve2(repoDir, f));
|
|
2306
2259
|
} catch {
|
|
2307
2260
|
}
|
|
2308
2261
|
}
|
|
@@ -2319,9 +2272,9 @@ var watchCommand = new Command8("watch").description(
|
|
|
2319
2272
|
let plan;
|
|
2320
2273
|
let protoHtml;
|
|
2321
2274
|
let followUpTasks;
|
|
2322
|
-
const planPath =
|
|
2323
|
-
const tasksPath =
|
|
2324
|
-
const protoPath =
|
|
2275
|
+
const planPath = resolve2(repoDir, "idea-plan.md");
|
|
2276
|
+
const tasksPath = resolve2(repoDir, "idea-tasks.json");
|
|
2277
|
+
const protoPath = resolve2(repoDir, "idea-prototype.html");
|
|
2325
2278
|
try {
|
|
2326
2279
|
plan = readFileSync5(planPath, "utf-8");
|
|
2327
2280
|
} catch {
|
|
@@ -2490,6 +2443,35 @@ ${divider}`);
|
|
|
2490
2443
|
const tasks = [...nonTestQueued, ...nonTestDelegated];
|
|
2491
2444
|
const config = loadConfig();
|
|
2492
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
|
+
}
|
|
2462
|
+
try {
|
|
2463
|
+
await api.patch(`/api/tasks/${task.id}`, { status: "error" });
|
|
2464
|
+
await postTaskUpdate(task.id, `Task moved to error \u2014 ${reason}`, "system");
|
|
2465
|
+
} catch (err) {
|
|
2466
|
+
logError(prefix, `Failed to mark task as error: ${err.message}`);
|
|
2467
|
+
continue;
|
|
2468
|
+
}
|
|
2469
|
+
failed.set(task.id, reason);
|
|
2470
|
+
queued.delete(task.id);
|
|
2471
|
+
if (activeEntry) {
|
|
2472
|
+
activeEntry.process.kill("SIGTERM");
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2493
2475
|
for (const [taskId, entry] of active) {
|
|
2494
2476
|
if (taskId.startsWith("proto-") || taskId.startsWith("repo-") || taskId.startsWith("scan-")) continue;
|
|
2495
2477
|
if (!activeTaskIds.has(taskId)) {
|
|
@@ -2522,7 +2504,7 @@ ${divider}`);
|
|
|
2522
2504
|
failed.set(task.id, reason);
|
|
2523
2505
|
continue;
|
|
2524
2506
|
}
|
|
2525
|
-
if (!
|
|
2507
|
+
if (!existsSync7(repoDir)) {
|
|
2526
2508
|
const reason = `linked directory "${repoDir}" does not exist`;
|
|
2527
2509
|
logError(prefix, `"${task.title}": ${reason} \u2014 will not retry`);
|
|
2528
2510
|
failed.set(task.id, reason);
|
|
@@ -2593,7 +2575,7 @@ ${divider}`);
|
|
|
2593
2575
|
logWarn(prefix, `"${proto.title}": no linked directory found \u2014 skipping`);
|
|
2594
2576
|
continue;
|
|
2595
2577
|
}
|
|
2596
|
-
if (!
|
|
2578
|
+
if (!existsSync7(repoDir)) {
|
|
2597
2579
|
logError(prefix, `"${proto.title}": linked directory "${repoDir}" does not exist \u2014 skipping`);
|
|
2598
2580
|
failed.set(key, `directory does not exist: ${repoDir}`);
|
|
2599
2581
|
continue;
|
|
@@ -2987,11 +2969,8 @@ var subtaskCompleteCommand = new Command12("subtask-complete").description("Mark
|
|
|
2987
2969
|
console.log(`\u2713 Subtask completed: ${subtask.title}`);
|
|
2988
2970
|
});
|
|
2989
2971
|
|
|
2990
|
-
// cli/commands/
|
|
2972
|
+
// cli/commands/prototype.ts
|
|
2991
2973
|
import { Command as Command13 } from "commander";
|
|
2992
|
-
import { spawn as spawn5 } from "child_process";
|
|
2993
|
-
import { resolve as resolve2 } from "path";
|
|
2994
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2995
2974
|
var c4 = {
|
|
2996
2975
|
reset: "\x1B[0m",
|
|
2997
2976
|
bold: "\x1B[1m",
|
|
@@ -3000,113 +2979,28 @@ var c4 = {
|
|
|
3000
2979
|
green: "\x1B[32m",
|
|
3001
2980
|
yellow: "\x1B[33m",
|
|
3002
2981
|
red: "\x1B[31m",
|
|
3003
|
-
|
|
2982
|
+
blue: "\x1B[34m",
|
|
3004
2983
|
gray: "\x1B[90m"
|
|
3005
2984
|
};
|
|
3006
2985
|
function paint4(color, text) {
|
|
3007
2986
|
return `${c4[color]}${text}${c4.reset}`;
|
|
3008
2987
|
}
|
|
3009
|
-
function timestamp2() {
|
|
3010
|
-
return paint4("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
|
|
3011
|
-
}
|
|
3012
|
-
function upTag() {
|
|
3013
|
-
return paint4("cyan", "[up]");
|
|
3014
|
-
}
|
|
3015
|
-
function log(msg) {
|
|
3016
|
-
console.log(`${timestamp2()} ${upTag()} ${msg}`);
|
|
3017
|
-
}
|
|
3018
|
-
function logErr(msg) {
|
|
3019
|
-
console.error(`${timestamp2()} ${upTag()} ${paint4("red", "\u2717")} ${msg}`);
|
|
3020
|
-
}
|
|
3021
|
-
function spawnMr(args) {
|
|
3022
|
-
const entry = process.argv[1];
|
|
3023
|
-
const child = spawn5(process.execPath, [entry, ...args], {
|
|
3024
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
3025
|
-
});
|
|
3026
|
-
return child;
|
|
3027
|
-
}
|
|
3028
|
-
var upCommand = new Command13("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) => {
|
|
3029
|
-
const watchInterval = opts.watchInterval ?? opts.interval;
|
|
3030
|
-
const digestInterval = opts.digestInterval ?? opts.interval;
|
|
3031
|
-
const agent = opts.agent === "codex" ? "codex" : "claude";
|
|
3032
|
-
const watchArgs = ["watch", "--interval", watchInterval, "--agent", agent];
|
|
3033
|
-
if (opts.dryRun) watchArgs.push("--dry-run");
|
|
3034
|
-
if (opts.planApproval) watchArgs.push("--plan-approval");
|
|
3035
|
-
if (opts.root) watchArgs.push("--root", opts.root);
|
|
3036
|
-
const digestArgs = ["digest", "--interval", digestInterval, "--agent", agent];
|
|
3037
|
-
const banner = [
|
|
3038
|
-
``,
|
|
3039
|
-
paint4("cyan", ` \u2554\u2566\u2557\u2566\u2550\u2557 \u2566 \u2566\u2554\u2550\u2557`),
|
|
3040
|
-
paint4("magenta", ` \u2551\u2551\u2551\u2560\u2566\u255D \u2551 \u2551\u2560\u2550\u255D`),
|
|
3041
|
-
paint4("cyan", ` \u2569 \u2569\u2569\u255A\u2550 \u255A\u2550\u255D\u2569 `),
|
|
3042
|
-
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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
|
|
3043
|
-
paint4("dim", ` mr watch + mr digest \xB7 powered by ${agent}`),
|
|
3044
|
-
``
|
|
3045
|
-
].join("\n");
|
|
3046
|
-
console.log(banner);
|
|
3047
|
-
const flags = [
|
|
3048
|
-
`watch-interval=${paint4("cyan", watchInterval + "s")}`,
|
|
3049
|
-
`digest-interval=${paint4("cyan", digestInterval + "s")}`,
|
|
3050
|
-
`agent=${paint4("cyan", agent)}`,
|
|
3051
|
-
...opts.dryRun ? [paint4("yellow", "dry-run")] : [],
|
|
3052
|
-
...opts.planApproval ? [paint4("yellow", "plan-approval")] : [],
|
|
3053
|
-
...opts.root ? [`root=${paint4("cyan", resolve2(opts.root))}`] : []
|
|
3054
|
-
].join(" ");
|
|
3055
|
-
console.log(` ${flags}
|
|
3056
|
-
`);
|
|
3057
|
-
const watchProc = spawnMr(watchArgs);
|
|
3058
|
-
const digestProc = spawnMr(digestArgs);
|
|
3059
|
-
for (const [proc, label] of [[watchProc, "watch"], [digestProc, "digest"]]) {
|
|
3060
|
-
proc.stdout?.on("data", (d) => process.stdout.write(d));
|
|
3061
|
-
proc.stderr?.on("data", (d) => process.stderr.write(d));
|
|
3062
|
-
proc.on("exit", (code, signal) => {
|
|
3063
|
-
if (signal !== "SIGTERM" && signal !== "SIGINT") {
|
|
3064
|
-
logErr(`mr ${label} exited unexpectedly (code=${code ?? "?"}, signal=${signal ?? "none"})`);
|
|
3065
|
-
}
|
|
3066
|
-
});
|
|
3067
|
-
}
|
|
3068
|
-
function shutdown() {
|
|
3069
|
-
log("Shutting down\u2026");
|
|
3070
|
-
watchProc.kill("SIGTERM");
|
|
3071
|
-
digestProc.kill("SIGTERM");
|
|
3072
|
-
setTimeout(() => process.exit(0), 500);
|
|
3073
|
-
}
|
|
3074
|
-
process.on("SIGINT", shutdown);
|
|
3075
|
-
process.on("SIGTERM", shutdown);
|
|
3076
|
-
});
|
|
3077
|
-
|
|
3078
|
-
// cli/commands/prototype.ts
|
|
3079
|
-
import { Command as Command14 } from "commander";
|
|
3080
|
-
var c5 = {
|
|
3081
|
-
reset: "\x1B[0m",
|
|
3082
|
-
bold: "\x1B[1m",
|
|
3083
|
-
dim: "\x1B[2m",
|
|
3084
|
-
cyan: "\x1B[36m",
|
|
3085
|
-
green: "\x1B[32m",
|
|
3086
|
-
yellow: "\x1B[33m",
|
|
3087
|
-
red: "\x1B[31m",
|
|
3088
|
-
blue: "\x1B[34m",
|
|
3089
|
-
gray: "\x1B[90m"
|
|
3090
|
-
};
|
|
3091
|
-
function paint5(color, text) {
|
|
3092
|
-
return `${c5[color]}${text}${c5.reset}`;
|
|
3093
|
-
}
|
|
3094
2988
|
function statusBadge(status) {
|
|
3095
2989
|
switch (status) {
|
|
3096
2990
|
case "pending":
|
|
3097
|
-
return
|
|
2991
|
+
return paint4("gray", "\u25CB pending");
|
|
3098
2992
|
case "in_progress":
|
|
3099
|
-
return
|
|
2993
|
+
return paint4("cyan", "\u27F3 in_progress");
|
|
3100
2994
|
case "completed":
|
|
3101
|
-
return
|
|
2995
|
+
return paint4("green", "\u2713 completed");
|
|
3102
2996
|
case "failed":
|
|
3103
|
-
return
|
|
2997
|
+
return paint4("red", "\u2717 failed");
|
|
3104
2998
|
default:
|
|
3105
|
-
return
|
|
2999
|
+
return paint4("gray", status);
|
|
3106
3000
|
}
|
|
3107
3001
|
}
|
|
3108
|
-
var prototypeCommand = new
|
|
3109
|
-
new
|
|
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) => {
|
|
3110
3004
|
const params = new URLSearchParams();
|
|
3111
3005
|
if (!opts.all) {
|
|
3112
3006
|
const projectId = getLinkedProjectId();
|
|
@@ -3118,21 +3012,21 @@ var prototypeCommand = new Command14("prototype").description("Manage prototypes
|
|
|
3118
3012
|
}
|
|
3119
3013
|
const prototypes = await api.get(`/api/prototypes?${params.toString()}`);
|
|
3120
3014
|
if (prototypes.length === 0) {
|
|
3121
|
-
console.log(
|
|
3015
|
+
console.log(paint4("gray", "No prototypes found."));
|
|
3122
3016
|
return;
|
|
3123
3017
|
}
|
|
3124
3018
|
console.log();
|
|
3125
3019
|
for (const p of prototypes) {
|
|
3126
3020
|
const date = new Date(p.createdAt).toLocaleDateString();
|
|
3127
3021
|
console.log(
|
|
3128
|
-
` ${
|
|
3022
|
+
` ${paint4("bold", p.title)} ${statusBadge(p.status)} ${paint4("gray", p.id.slice(0, 8))} ${paint4("dim", date)}`
|
|
3129
3023
|
);
|
|
3130
|
-
console.log(` ${
|
|
3024
|
+
console.log(` ${paint4("dim", p.prompt.slice(0, 80) + (p.prompt.length > 80 ? "\u2026" : ""))}`);
|
|
3131
3025
|
console.log();
|
|
3132
3026
|
}
|
|
3133
3027
|
})
|
|
3134
3028
|
).addCommand(
|
|
3135
|
-
new
|
|
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) => {
|
|
3136
3030
|
const projectId = opts.project ?? getLinkedProjectId();
|
|
3137
3031
|
if (!projectId) {
|
|
3138
3032
|
console.error('No project linked. Run "mr link <project-id>" or use --project.');
|
|
@@ -3146,37 +3040,37 @@ var prototypeCommand = new Command14("prototype").description("Manage prototypes
|
|
|
3146
3040
|
projectId
|
|
3147
3041
|
});
|
|
3148
3042
|
console.log();
|
|
3149
|
-
console.log(` ${
|
|
3150
|
-
console.log(` ${
|
|
3151
|
-
console.log(` ${
|
|
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.`);
|
|
3152
3046
|
console.log();
|
|
3153
3047
|
})
|
|
3154
3048
|
).addCommand(
|
|
3155
|
-
new
|
|
3049
|
+
new Command13("start").description("Start prototype generation (sets status to in_progress)").argument("<id>", "Prototype ID").action(async (id) => {
|
|
3156
3050
|
const prototype = await api.patch(`/api/prototypes/${id}`, {
|
|
3157
3051
|
status: "in_progress"
|
|
3158
3052
|
});
|
|
3159
3053
|
console.log();
|
|
3160
|
-
console.log(` ${
|
|
3161
|
-
console.log(` ${
|
|
3054
|
+
console.log(` ${paint4("cyan", "\u27F3")} Started: ${paint4("bold", prototype.title)}`);
|
|
3055
|
+
console.log(` ${paint4("gray", "The watch agent will pick this up shortly.")}`);
|
|
3162
3056
|
console.log();
|
|
3163
3057
|
})
|
|
3164
3058
|
).addCommand(
|
|
3165
|
-
new
|
|
3059
|
+
new Command13("retry").description("Retry a failed prototype").argument("<id>", "Prototype ID").action(async (id) => {
|
|
3166
3060
|
const prototype = await api.patch(`/api/prototypes/${id}`, {
|
|
3167
3061
|
status: "in_progress",
|
|
3168
3062
|
files: null
|
|
3169
3063
|
});
|
|
3170
3064
|
console.log();
|
|
3171
|
-
console.log(` ${
|
|
3065
|
+
console.log(` ${paint4("cyan", "\u27F3")} Retrying: ${paint4("bold", prototype.title)}`);
|
|
3172
3066
|
console.log();
|
|
3173
3067
|
})
|
|
3174
3068
|
);
|
|
3175
3069
|
|
|
3176
3070
|
// cli/commands/setup.ts
|
|
3177
|
-
import { Command as
|
|
3071
|
+
import { Command as Command14 } from "commander";
|
|
3178
3072
|
import { exec as exec2 } from "child_process";
|
|
3179
|
-
var
|
|
3073
|
+
var c5 = {
|
|
3180
3074
|
reset: "\x1B[0m",
|
|
3181
3075
|
bold: "\x1B[1m",
|
|
3182
3076
|
dim: "\x1B[2m",
|
|
@@ -3187,8 +3081,8 @@ var c6 = {
|
|
|
3187
3081
|
magenta: "\x1B[35m",
|
|
3188
3082
|
gray: "\x1B[90m"
|
|
3189
3083
|
};
|
|
3190
|
-
function
|
|
3191
|
-
return `${
|
|
3084
|
+
function paint5(color, text) {
|
|
3085
|
+
return `${c5[color]}${text}${c5.reset}`;
|
|
3192
3086
|
}
|
|
3193
3087
|
function commandExists(cmd) {
|
|
3194
3088
|
return new Promise((resolve7) => {
|
|
@@ -3432,60 +3326,60 @@ async function checkApiConnectivity() {
|
|
|
3432
3326
|
}
|
|
3433
3327
|
}
|
|
3434
3328
|
function printResults(checks) {
|
|
3435
|
-
const maxNameLen = Math.max(...checks.map((
|
|
3329
|
+
const maxNameLen = Math.max(...checks.map((c10) => c10.name.length));
|
|
3436
3330
|
let allOk = true;
|
|
3437
3331
|
for (const check of checks) {
|
|
3438
3332
|
const isOptional = check.optional ?? false;
|
|
3439
|
-
const icon = check.ok ?
|
|
3333
|
+
const icon = check.ok ? paint5("green", "\u2713") : isOptional ? paint5("yellow", "\u25CB") : paint5("red", "\u2717");
|
|
3440
3334
|
const name = check.name.padEnd(maxNameLen);
|
|
3441
|
-
const msg = check.ok ?
|
|
3335
|
+
const msg = check.ok ? paint5("green", check.message) : isOptional ? paint5("yellow", check.message) : paint5("red", check.message);
|
|
3442
3336
|
console.log(` ${icon} ${name} ${msg}`);
|
|
3443
3337
|
if (!check.ok && !isOptional) allOk = false;
|
|
3444
3338
|
}
|
|
3445
3339
|
return allOk;
|
|
3446
3340
|
}
|
|
3447
3341
|
async function autoFix(checks, agent) {
|
|
3448
|
-
const { spawn:
|
|
3449
|
-
const ghInstalled = checks.find((
|
|
3450
|
-
const ghAuthed = checks.find((
|
|
3451
|
-
const mrAuthed = checks.find((
|
|
3452
|
-
const claudeCheck = checks.find((
|
|
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)");
|
|
3453
3347
|
if (claudeCheck && !claudeCheck.ok && agent === "claude") {
|
|
3454
|
-
console.log(
|
|
3455
|
-
console.log(
|
|
3348
|
+
console.log(paint5("cyan", " Installing Claude Code..."));
|
|
3349
|
+
console.log(paint5("dim", " Running: curl -fsSL https://claude.ai/install.sh | bash"));
|
|
3456
3350
|
await new Promise((resolve7) => {
|
|
3457
|
-
const child =
|
|
3351
|
+
const child = spawn8("bash", ["-c", "curl -fsSL https://claude.ai/install.sh | bash"], { stdio: "inherit" });
|
|
3458
3352
|
child.on("exit", () => resolve7());
|
|
3459
3353
|
});
|
|
3460
3354
|
console.log("");
|
|
3461
3355
|
}
|
|
3462
3356
|
if (ghInstalled && !ghAuthed) {
|
|
3463
|
-
console.log(
|
|
3357
|
+
console.log(paint5("cyan", " Running gh auth login..."));
|
|
3464
3358
|
await new Promise((resolve7) => {
|
|
3465
|
-
const child =
|
|
3359
|
+
const child = spawn8("gh", ["auth", "login"], { stdio: "inherit" });
|
|
3466
3360
|
child.on("exit", () => resolve7());
|
|
3467
3361
|
});
|
|
3468
3362
|
console.log("");
|
|
3469
3363
|
}
|
|
3470
3364
|
if (!mrAuthed) {
|
|
3471
|
-
console.log(
|
|
3365
|
+
console.log(paint5("cyan", " Running mr login..."));
|
|
3472
3366
|
const entry = process.argv[1];
|
|
3473
3367
|
await new Promise((resolve7) => {
|
|
3474
|
-
const child =
|
|
3368
|
+
const child = spawn8(process.execPath, [entry, "login"], { stdio: "inherit" });
|
|
3475
3369
|
child.on("exit", () => resolve7());
|
|
3476
3370
|
});
|
|
3477
3371
|
console.log("");
|
|
3478
3372
|
}
|
|
3479
3373
|
}
|
|
3480
|
-
var setupCommand = new
|
|
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) => {
|
|
3481
3375
|
const agent = opts.agent === "codex" ? "codex" : opts.agent === "gemini" ? "gemini" : "claude";
|
|
3482
3376
|
const banner = [
|
|
3483
3377
|
``,
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
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})`),
|
|
3489
3383
|
``
|
|
3490
3384
|
].join("\n");
|
|
3491
3385
|
console.log(banner);
|
|
@@ -3505,18 +3399,18 @@ var setupCommand = new Command15("setup").description("Check that all dependenci
|
|
|
3505
3399
|
const allOk = printResults(checks);
|
|
3506
3400
|
console.log("");
|
|
3507
3401
|
if (allOk) {
|
|
3508
|
-
console.log(
|
|
3402
|
+
console.log(paint5("green", " All checks passed! You're ready to run mr watch."));
|
|
3509
3403
|
if (agent === "claude") {
|
|
3510
|
-
console.log(
|
|
3404
|
+
console.log(paint5("dim", " Tip: check other agents with --agent codex or --agent gemini"));
|
|
3511
3405
|
}
|
|
3512
3406
|
console.log("");
|
|
3513
3407
|
return;
|
|
3514
3408
|
}
|
|
3515
|
-
const fixes = checks.filter((
|
|
3409
|
+
const fixes = checks.filter((c10) => !c10.ok && c10.fix && !c10.optional);
|
|
3516
3410
|
if (fixes.length > 0) {
|
|
3517
|
-
console.log(
|
|
3411
|
+
console.log(paint5("yellow", " To fix:"));
|
|
3518
3412
|
for (const fix of fixes) {
|
|
3519
|
-
console.log(` ${
|
|
3413
|
+
console.log(` ${paint5("dim", "\u2192")} ${paint5("bold", fix.name)}: ${fix.fix}`);
|
|
3520
3414
|
}
|
|
3521
3415
|
console.log("");
|
|
3522
3416
|
}
|
|
@@ -3527,8 +3421,8 @@ var setupCommand = new Command15("setup").description("Check that all dependenci
|
|
|
3527
3421
|
});
|
|
3528
3422
|
|
|
3529
3423
|
// cli/commands/update.ts
|
|
3530
|
-
import { Command as
|
|
3531
|
-
var updateCommand = new
|
|
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) => {
|
|
3532
3426
|
await api.post(`/api/tasks/${taskId}/updates`, {
|
|
3533
3427
|
message,
|
|
3534
3428
|
source: opts.source
|
|
@@ -3537,11 +3431,11 @@ var updateCommand = new Command16("update").description("Post a status update to
|
|
|
3537
3431
|
});
|
|
3538
3432
|
|
|
3539
3433
|
// cli/commands/screenshot.ts
|
|
3540
|
-
import { Command as
|
|
3541
|
-
import { readFileSync as readFileSync6, existsSync as
|
|
3434
|
+
import { Command as Command16 } from "commander";
|
|
3435
|
+
import { readFileSync as readFileSync6, existsSync as existsSync8, unlinkSync as unlinkSync2 } from "fs";
|
|
3542
3436
|
import { join as join8 } from "path";
|
|
3543
3437
|
import { tmpdir } from "os";
|
|
3544
|
-
var screenshotCommand = new
|
|
3438
|
+
var screenshotCommand = new Command16("screenshot").description(
|
|
3545
3439
|
"Take or attach a screenshot to a task update (agents use this to show their work)"
|
|
3546
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) => {
|
|
3547
3441
|
let filePath = file;
|
|
@@ -3578,13 +3472,13 @@ var screenshotCommand = new Command17("screenshot").description(
|
|
|
3578
3472
|
console.error("[screenshot] Make sure the browse daemon is set up: mr browse setup");
|
|
3579
3473
|
process.exit(1);
|
|
3580
3474
|
}
|
|
3581
|
-
if (!
|
|
3475
|
+
if (!existsSync8(tempFile)) {
|
|
3582
3476
|
console.error("[screenshot] Screenshot file was not created");
|
|
3583
3477
|
process.exit(1);
|
|
3584
3478
|
}
|
|
3585
3479
|
filePath = tempFile;
|
|
3586
3480
|
}
|
|
3587
|
-
if (!
|
|
3481
|
+
if (!existsSync8(filePath)) {
|
|
3588
3482
|
console.error(`File not found: ${filePath}`);
|
|
3589
3483
|
process.exit(1);
|
|
3590
3484
|
}
|
|
@@ -3626,16 +3520,16 @@ var screenshotCommand = new Command17("screenshot").description(
|
|
|
3626
3520
|
source: "agent"
|
|
3627
3521
|
});
|
|
3628
3522
|
console.log(`\u2713 Screenshot uploaded and attached to task`);
|
|
3629
|
-
if (tempFile &&
|
|
3523
|
+
if (tempFile && existsSync8(tempFile)) {
|
|
3630
3524
|
unlinkSync2(tempFile);
|
|
3631
3525
|
}
|
|
3632
3526
|
});
|
|
3633
3527
|
|
|
3634
3528
|
// cli/commands/resume.ts
|
|
3635
|
-
import { Command as
|
|
3636
|
-
import { spawn as
|
|
3529
|
+
import { Command as Command17 } from "commander";
|
|
3530
|
+
import { spawn as spawn5 } from "child_process";
|
|
3637
3531
|
import { resolve as resolve3 } from "path";
|
|
3638
|
-
var
|
|
3532
|
+
var c6 = {
|
|
3639
3533
|
reset: "\x1B[0m",
|
|
3640
3534
|
bold: "\x1B[1m",
|
|
3641
3535
|
dim: "\x1B[2m",
|
|
@@ -3646,15 +3540,15 @@ var c7 = {
|
|
|
3646
3540
|
magenta: "\x1B[35m",
|
|
3647
3541
|
gray: "\x1B[90m"
|
|
3648
3542
|
};
|
|
3649
|
-
function
|
|
3650
|
-
return `${
|
|
3543
|
+
function paint6(color, text) {
|
|
3544
|
+
return `${c6[color]}${text}${c6.reset}`;
|
|
3651
3545
|
}
|
|
3652
|
-
var resumeCommand = new
|
|
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) => {
|
|
3653
3547
|
const task = await api.get(`/api/tasks/${taskId}`);
|
|
3654
3548
|
if (!task.claudeSessionId) {
|
|
3655
3549
|
console.error(
|
|
3656
3550
|
`
|
|
3657
|
-
${
|
|
3551
|
+
${paint6("red", "\u2717")} No Claude session found for task ${paint6("dim", taskId.slice(0, 8))}`
|
|
3658
3552
|
);
|
|
3659
3553
|
console.error(
|
|
3660
3554
|
` The task may not have been dispatched yet.
|
|
@@ -3665,16 +3559,16 @@ var resumeCommand = new Command18("resume").description("Resume an interactive C
|
|
|
3665
3559
|
if (task.status === "completed" || task.status === "todo") {
|
|
3666
3560
|
console.error(
|
|
3667
3561
|
`
|
|
3668
|
-
${
|
|
3562
|
+
${paint6("yellow", "\u26A0")} Task ${paint6("dim", taskId.slice(0, 8))} has already completed.`
|
|
3669
3563
|
);
|
|
3670
3564
|
console.error(
|
|
3671
|
-
` Session ID: ${
|
|
3565
|
+
` Session ID: ${paint6("cyan", task.claudeSessionId)}`
|
|
3672
3566
|
);
|
|
3673
3567
|
console.error(
|
|
3674
|
-
`
|
|
3568
|
+
` To resume the session, run:`
|
|
3675
3569
|
);
|
|
3676
3570
|
console.error(
|
|
3677
|
-
` ${
|
|
3571
|
+
` ${paint6("dim", `claude --resume ${task.claudeSessionId}`)}
|
|
3678
3572
|
`
|
|
3679
3573
|
);
|
|
3680
3574
|
process.exit(0);
|
|
@@ -3693,16 +3587,16 @@ var resumeCommand = new Command18("resume").description("Resume an interactive C
|
|
|
3693
3587
|
const sid = taskId.slice(0, 8);
|
|
3694
3588
|
console.log([
|
|
3695
3589
|
``,
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
` ${
|
|
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)}`,
|
|
3700
3594
|
``,
|
|
3701
|
-
` ${
|
|
3702
|
-
` ${
|
|
3595
|
+
` ${paint6("dim", "session")} ${paint6("cyan", task.claudeSessionId)}`,
|
|
3596
|
+
` ${paint6("dim", "cwd")} ${paint6("cyan", cwd)}`,
|
|
3703
3597
|
``
|
|
3704
3598
|
].join("\n"));
|
|
3705
|
-
const child =
|
|
3599
|
+
const child = spawn5("claude", ["--resume", task.claudeSessionId], {
|
|
3706
3600
|
cwd,
|
|
3707
3601
|
stdio: "inherit"
|
|
3708
3602
|
});
|
|
@@ -3711,16 +3605,16 @@ var resumeCommand = new Command18("resume").description("Resume an interactive C
|
|
|
3711
3605
|
});
|
|
3712
3606
|
child.on("error", (err) => {
|
|
3713
3607
|
console.error(`
|
|
3714
|
-
${
|
|
3608
|
+
${paint6("red", "\u2717")} Failed to launch Claude: ${err.message}
|
|
3715
3609
|
`);
|
|
3716
3610
|
process.exit(1);
|
|
3717
3611
|
});
|
|
3718
3612
|
});
|
|
3719
3613
|
|
|
3720
3614
|
// cli/commands/browse.ts
|
|
3721
|
-
import { Command as
|
|
3722
|
-
import { execSync as execSync5, spawn as
|
|
3723
|
-
import { existsSync as
|
|
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";
|
|
3724
3618
|
import { join as join9 } from "path";
|
|
3725
3619
|
var BROWSE_DIR2 = join9(import.meta.dirname, "..", "..", "browse");
|
|
3726
3620
|
function isProcessAlive(pid) {
|
|
@@ -3760,7 +3654,7 @@ async function ensureDevServer() {
|
|
|
3760
3654
|
}
|
|
3761
3655
|
const port = await findAvailablePort2(3e3);
|
|
3762
3656
|
console.log(`[browse] Starting dev server on port ${port}...`);
|
|
3763
|
-
const devProc =
|
|
3657
|
+
const devProc = spawn6("npm", ["run", "dev", "--", "--port", String(port)], {
|
|
3764
3658
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3765
3659
|
detached: true,
|
|
3766
3660
|
cwd: join9(import.meta.dirname, "..", ".."),
|
|
@@ -3782,7 +3676,7 @@ async function ensureDevServer() {
|
|
|
3782
3676
|
}
|
|
3783
3677
|
throw new Error("Dev server failed to start within 30s");
|
|
3784
3678
|
}
|
|
3785
|
-
var browseCommand = new
|
|
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(
|
|
3786
3680
|
"--task-id <id>",
|
|
3787
3681
|
"Attach screenshot to a task update (only for screenshot command)"
|
|
3788
3682
|
).option("--dev", "Auto-start local dev server before browsing").allowUnknownOption(true).action(
|
|
@@ -3825,7 +3719,7 @@ var browseCommand = new Command19("browse").description("Control a headless brow
|
|
|
3825
3719
|
}
|
|
3826
3720
|
if (command === "screenshot" && opts.taskId) {
|
|
3827
3721
|
const screenshotPath = stdout.match(/Screenshot saved: (.+)/)?.[1];
|
|
3828
|
-
if (!screenshotPath || !
|
|
3722
|
+
if (!screenshotPath || !existsSync9(screenshotPath)) {
|
|
3829
3723
|
console.error("[browse] Could not find screenshot file");
|
|
3830
3724
|
process.exit(1);
|
|
3831
3725
|
}
|
|
@@ -3865,12 +3759,12 @@ var browseCommand = new Command19("browse").description("Control a headless brow
|
|
|
3865
3759
|
);
|
|
3866
3760
|
|
|
3867
3761
|
// cli/commands/set-path.ts
|
|
3868
|
-
import { Command as
|
|
3762
|
+
import { Command as Command19 } from "commander";
|
|
3869
3763
|
import { resolve as resolve4 } from "path";
|
|
3870
|
-
import { existsSync as
|
|
3871
|
-
var setPathCommand = new
|
|
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) => {
|
|
3872
3766
|
const absolutePath = resolve4(pathArg);
|
|
3873
|
-
if (!
|
|
3767
|
+
if (!existsSync10(absolutePath)) {
|
|
3874
3768
|
console.error(`Error: Path does not exist: ${absolutePath}`);
|
|
3875
3769
|
process.exit(1);
|
|
3876
3770
|
}
|
|
@@ -3886,9 +3780,9 @@ var setPathCommand = new Command20("set-path").description("Set or update the lo
|
|
|
3886
3780
|
});
|
|
3887
3781
|
|
|
3888
3782
|
// cli/commands/test.ts
|
|
3889
|
-
import { Command as
|
|
3890
|
-
import { readFileSync as readFileSync8, existsSync as
|
|
3891
|
-
var testCommand = new
|
|
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) => {
|
|
3892
3786
|
const config = loadConfig();
|
|
3893
3787
|
console.log("[test] Fetching task...");
|
|
3894
3788
|
let task;
|
|
@@ -3909,7 +3803,7 @@ var testCommand = new Command21("test").description("Run automated browser test
|
|
|
3909
3803
|
);
|
|
3910
3804
|
process.exit(1);
|
|
3911
3805
|
}
|
|
3912
|
-
if (!
|
|
3806
|
+
if (!existsSync11(localPath)) {
|
|
3913
3807
|
console.error(
|
|
3914
3808
|
`[test] Local repo path not found at ${localPath}. Run \`mr set-path\` to update.`
|
|
3915
3809
|
);
|
|
@@ -4008,11 +3902,11 @@ var testCommand = new Command21("test").description("Run automated browser test
|
|
|
4008
3902
|
});
|
|
4009
3903
|
|
|
4010
3904
|
// cli/commands/features.ts
|
|
4011
|
-
import { Command as
|
|
4012
|
-
import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, existsSync as
|
|
3905
|
+
import { Command as Command21 } from "commander";
|
|
3906
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, existsSync as existsSync12 } from "fs";
|
|
4013
3907
|
import { resolve as resolve5, sep as sep2 } from "path";
|
|
4014
3908
|
var FEATURES_FILE3 = ".mr-features.md";
|
|
4015
|
-
var
|
|
3909
|
+
var c7 = {
|
|
4016
3910
|
reset: "\x1B[0m",
|
|
4017
3911
|
bold: "\x1B[1m",
|
|
4018
3912
|
dim: "\x1B[2m",
|
|
@@ -4022,8 +3916,8 @@ var c8 = {
|
|
|
4022
3916
|
magenta: "\x1B[35m",
|
|
4023
3917
|
gray: "\x1B[90m"
|
|
4024
3918
|
};
|
|
4025
|
-
function
|
|
4026
|
-
return `${
|
|
3919
|
+
function paint7(color, text) {
|
|
3920
|
+
return `${c7[color]}${text}${c7.reset}`;
|
|
4027
3921
|
}
|
|
4028
3922
|
function resolveProjectRoot2() {
|
|
4029
3923
|
const cwd = process.cwd();
|
|
@@ -4039,10 +3933,10 @@ function getFeaturesPath() {
|
|
|
4039
3933
|
}
|
|
4040
3934
|
function readFeatures2() {
|
|
4041
3935
|
const path = getFeaturesPath();
|
|
4042
|
-
if (!
|
|
3936
|
+
if (!existsSync12(path)) return null;
|
|
4043
3937
|
return readFileSync9(path, "utf-8");
|
|
4044
3938
|
}
|
|
4045
|
-
var featuresCommand = new
|
|
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) => {
|
|
4046
3940
|
if (opts.path) {
|
|
4047
3941
|
console.log(getFeaturesPath());
|
|
4048
3942
|
return;
|
|
@@ -4051,30 +3945,30 @@ var featuresCommand = new Command22("features").description("View or update the
|
|
|
4051
3945
|
const content2 = readFileSync9(resolve5(opts.file), "utf-8");
|
|
4052
3946
|
const featuresPath = getFeaturesPath();
|
|
4053
3947
|
writeFileSync5(featuresPath, content2);
|
|
4054
|
-
console.log(`${
|
|
3948
|
+
console.log(`${paint7("green", "\u2713")} Updated ${paint7("cyan", featuresPath)} from ${paint7("cyan", opts.file)}`);
|
|
4055
3949
|
return;
|
|
4056
3950
|
}
|
|
4057
3951
|
if (opts.update) {
|
|
4058
3952
|
const featuresPath = getFeaturesPath();
|
|
4059
3953
|
writeFileSync5(featuresPath, opts.update);
|
|
4060
|
-
console.log(`${
|
|
3954
|
+
console.log(`${paint7("green", "\u2713")} Updated ${paint7("cyan", featuresPath)}`);
|
|
4061
3955
|
return;
|
|
4062
3956
|
}
|
|
4063
3957
|
const content = readFeatures2();
|
|
4064
3958
|
if (!content) {
|
|
4065
|
-
console.log(
|
|
4066
|
-
console.log(
|
|
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()}`));
|
|
4067
3961
|
return;
|
|
4068
3962
|
}
|
|
4069
3963
|
console.log(content);
|
|
4070
3964
|
});
|
|
4071
3965
|
|
|
4072
3966
|
// cli/commands/no-mr.ts
|
|
4073
|
-
import { Command as
|
|
3967
|
+
import { Command as Command22 } from "commander";
|
|
4074
3968
|
import { writeFileSync as writeFileSync6 } from "fs";
|
|
4075
3969
|
import { resolve as resolve6 } from "path";
|
|
4076
3970
|
var NO_MR_FILE = ".mr-no-mr";
|
|
4077
|
-
var noMrCommand = new
|
|
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) => {
|
|
4078
3972
|
const filePath = resolve6(process.cwd(), NO_MR_FILE);
|
|
4079
3973
|
writeFileSync6(filePath, description, "utf-8");
|
|
4080
3974
|
await api.post(`/api/tasks/${taskId}/updates`, {
|
|
@@ -4086,8 +3980,8 @@ var noMrCommand = new Command23("no-mr").description("Signal that a task does no
|
|
|
4086
3980
|
});
|
|
4087
3981
|
|
|
4088
3982
|
// cli/commands/mobile.ts
|
|
4089
|
-
import { Command as
|
|
4090
|
-
function
|
|
3983
|
+
import { Command as Command23 } from "commander";
|
|
3984
|
+
function paint8(color, text) {
|
|
4091
3985
|
const colors = {
|
|
4092
3986
|
cyan: "\x1B[36m",
|
|
4093
3987
|
green: "\x1B[32m",
|
|
@@ -4098,7 +3992,7 @@ function paint9(color, text) {
|
|
|
4098
3992
|
};
|
|
4099
3993
|
return `${colors[color] ?? ""}${text}${colors.reset}`;
|
|
4100
3994
|
}
|
|
4101
|
-
var mobileCommand = new
|
|
3995
|
+
var mobileCommand = new Command23("mobile").description(
|
|
4102
3996
|
"Start the Web-to-Mobile Conversion Wizard for the linked project"
|
|
4103
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(
|
|
4104
3998
|
async (projectIdArg, opts) => {
|
|
@@ -4110,7 +4004,7 @@ var mobileCommand = new Command24("mobile").description(
|
|
|
4110
4004
|
process.exit(1);
|
|
4111
4005
|
}
|
|
4112
4006
|
console.log(
|
|
4113
|
-
|
|
4007
|
+
paint8("cyan", "mobile") + paint8("dim", " \u2014 starting conversion wizard")
|
|
4114
4008
|
);
|
|
4115
4009
|
const task = await api.post("/api/tasks", {
|
|
4116
4010
|
title: "Convert to Mobile App",
|
|
@@ -4141,13 +4035,13 @@ var mobileCommand = new Command24("mobile").description(
|
|
|
4141
4035
|
}
|
|
4142
4036
|
console.log(
|
|
4143
4037
|
`
|
|
4144
|
-
${
|
|
4038
|
+
${paint8("green", "\u2713")} Wizard initialized. Open the web UI to continue:
|
|
4145
4039
|
\u2192 Task ID: ${task.id}
|
|
4146
4040
|
\u2192 Use the "Convert to Mobile" button on the project page`
|
|
4147
4041
|
);
|
|
4148
4042
|
}
|
|
4149
4043
|
);
|
|
4150
|
-
var statusSubcommand = new
|
|
4044
|
+
var statusSubcommand = new Command23("status").description("Show mobile conversion status for a task").argument("<task-id>", "Parent conversion task ID").action(async (taskId) => {
|
|
4151
4045
|
const resources = await api.get(
|
|
4152
4046
|
`/api/tasks/${taskId}/resources`
|
|
4153
4047
|
);
|
|
@@ -4160,7 +4054,7 @@ var statusSubcommand = new Command24("status").description("Show mobile conversi
|
|
|
4160
4054
|
}
|
|
4161
4055
|
try {
|
|
4162
4056
|
const state = JSON.parse(wizardState.content);
|
|
4163
|
-
console.log(
|
|
4057
|
+
console.log(paint8("cyan", "Mobile Conversion Status"));
|
|
4164
4058
|
console.log(` Phase: ${state.phase}`);
|
|
4165
4059
|
if (state.framework) {
|
|
4166
4060
|
console.log(` Framework: ${state.framework}`);
|
|
@@ -4183,13 +4077,13 @@ var statusSubcommand = new Command24("status").description("Show mobile conversi
|
|
|
4183
4077
|
mobileCommand.addCommand(statusSubcommand);
|
|
4184
4078
|
|
|
4185
4079
|
// cli/commands/scan.ts
|
|
4186
|
-
import { Command as
|
|
4080
|
+
import { Command as Command24 } from "commander";
|
|
4187
4081
|
|
|
4188
4082
|
// lib/scanner/index.ts
|
|
4189
|
-
import { spawn as
|
|
4083
|
+
import { spawn as spawn7 } from "child_process";
|
|
4190
4084
|
|
|
4191
4085
|
// lib/scanner/config.ts
|
|
4192
|
-
import { readFileSync as readFileSync10, existsSync as
|
|
4086
|
+
import { readFileSync as readFileSync10, existsSync as existsSync13 } from "fs";
|
|
4193
4087
|
import { join as join10 } from "path";
|
|
4194
4088
|
var ALL_FINDING_TYPES = [
|
|
4195
4089
|
"idea",
|
|
@@ -4207,7 +4101,7 @@ var DEFAULTS = {
|
|
|
4207
4101
|
};
|
|
4208
4102
|
function loadScanConfig(projectPath) {
|
|
4209
4103
|
const configPath2 = join10(projectPath, ".mr-scan.json");
|
|
4210
|
-
if (!
|
|
4104
|
+
if (!existsSync13(configPath2)) {
|
|
4211
4105
|
return { ...DEFAULTS };
|
|
4212
4106
|
}
|
|
4213
4107
|
try {
|
|
@@ -4253,13 +4147,13 @@ async function authenticateBrowseSession(magicUrl, runBrowse) {
|
|
|
4253
4147
|
}
|
|
4254
4148
|
|
|
4255
4149
|
// lib/scanner/codebase-analysis.ts
|
|
4256
|
-
import { readdirSync as readdirSync2, readFileSync as readFileSync11, existsSync as
|
|
4150
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync11, existsSync as existsSync14 } from "fs";
|
|
4257
4151
|
import { join as join11, relative } from "path";
|
|
4258
4152
|
import { execSync as execSync6 } from "child_process";
|
|
4259
4153
|
function resolveDir(projectPath, candidates) {
|
|
4260
4154
|
for (const candidate of candidates) {
|
|
4261
4155
|
const dir = join11(projectPath, candidate);
|
|
4262
|
-
if (
|
|
4156
|
+
if (existsSync14(dir)) return dir;
|
|
4263
4157
|
}
|
|
4264
4158
|
return null;
|
|
4265
4159
|
}
|
|
@@ -4293,7 +4187,7 @@ function discoverRoutes(projectPath) {
|
|
|
4293
4187
|
}
|
|
4294
4188
|
function extractModels(projectPath) {
|
|
4295
4189
|
const schemaPath = join11(projectPath, "prisma", "schema.prisma");
|
|
4296
|
-
if (
|
|
4190
|
+
if (existsSync14(schemaPath)) {
|
|
4297
4191
|
const content = readFileSync11(schemaPath, "utf-8");
|
|
4298
4192
|
const models2 = [];
|
|
4299
4193
|
const modelRegex = /^model\s+(\w+)\s*\{/gm;
|
|
@@ -4307,7 +4201,7 @@ function extractModels(projectPath) {
|
|
|
4307
4201
|
const drizzleDirs = ["src/db", "src/schema", "db", "drizzle"];
|
|
4308
4202
|
for (const dir of drizzleDirs) {
|
|
4309
4203
|
const fullDir = join11(projectPath, dir);
|
|
4310
|
-
if (!
|
|
4204
|
+
if (!existsSync14(fullDir)) continue;
|
|
4311
4205
|
try {
|
|
4312
4206
|
const entries = readdirSync2(fullDir, { withFileTypes: true });
|
|
4313
4207
|
for (const entry of entries) {
|
|
@@ -4348,7 +4242,7 @@ function discoverComponents(projectPath) {
|
|
|
4348
4242
|
function extractInternalLinks(projectPath) {
|
|
4349
4243
|
const links = /* @__PURE__ */ new Set();
|
|
4350
4244
|
function searchDir(dir) {
|
|
4351
|
-
if (!
|
|
4245
|
+
if (!existsSync14(dir)) return;
|
|
4352
4246
|
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
4353
4247
|
for (const entry of entries) {
|
|
4354
4248
|
if (entry.isDirectory()) {
|
|
@@ -4652,10 +4546,10 @@ ${codebaseAnalysis.routes.map((r) => `- ${r}`).join("\n")}
|
|
|
4652
4546
|
${codebaseAnalysis.prismaModels.map((m) => `- ${m}`).join("\n")}
|
|
4653
4547
|
|
|
4654
4548
|
**Components:**
|
|
4655
|
-
${codebaseAnalysis.components.slice(0, 30).map((
|
|
4549
|
+
${codebaseAnalysis.components.slice(0, 30).map((c10) => `- ${c10}`).join("\n")}
|
|
4656
4550
|
|
|
4657
4551
|
**Recent Git Commits:**
|
|
4658
|
-
${codebaseAnalysis.recentCommits.slice(0, 15).map((
|
|
4552
|
+
${codebaseAnalysis.recentCommits.slice(0, 15).map((c10) => `- ${c10}`).join("\n")}
|
|
4659
4553
|
|
|
4660
4554
|
**Completed Tasks:**
|
|
4661
4555
|
${context.completedTasks.slice(0, 20).map((t) => `- ${t.title}`).join("\n") || "None"}
|
|
@@ -4865,7 +4759,7 @@ async function fetchScanContext(opts) {
|
|
|
4865
4759
|
}
|
|
4866
4760
|
function runClaude(prompt2) {
|
|
4867
4761
|
return new Promise((resolve7, reject) => {
|
|
4868
|
-
const child =
|
|
4762
|
+
const child = spawn7("claude", ["-p", "--dangerously-skip-permissions", prompt2], {
|
|
4869
4763
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4870
4764
|
});
|
|
4871
4765
|
let output = "";
|
|
@@ -4921,7 +4815,7 @@ function parseSynthesisOutput(output) {
|
|
|
4921
4815
|
}
|
|
4922
4816
|
|
|
4923
4817
|
// cli/commands/scan.ts
|
|
4924
|
-
var
|
|
4818
|
+
var c8 = {
|
|
4925
4819
|
reset: "\x1B[0m",
|
|
4926
4820
|
bold: "\x1B[1m",
|
|
4927
4821
|
dim: "\x1B[2m",
|
|
@@ -4932,53 +4826,53 @@ var c9 = {
|
|
|
4932
4826
|
magenta: "\x1B[35m",
|
|
4933
4827
|
gray: "\x1B[90m"
|
|
4934
4828
|
};
|
|
4935
|
-
function
|
|
4936
|
-
return `${
|
|
4829
|
+
function paint9(color, text) {
|
|
4830
|
+
return `${c8[color]}${text}${c8.reset}`;
|
|
4937
4831
|
}
|
|
4938
|
-
function
|
|
4939
|
-
return
|
|
4832
|
+
function timestamp2() {
|
|
4833
|
+
return paint9("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
|
|
4940
4834
|
}
|
|
4941
4835
|
function scanTag() {
|
|
4942
|
-
return
|
|
4836
|
+
return paint9("magenta", "[scan]");
|
|
4943
4837
|
}
|
|
4944
|
-
function
|
|
4945
|
-
console.log(`${
|
|
4838
|
+
function log(msg) {
|
|
4839
|
+
console.log(`${timestamp2()} ${scanTag()} ${msg}`);
|
|
4946
4840
|
}
|
|
4947
4841
|
function logOk(msg) {
|
|
4948
|
-
console.log(`${
|
|
4842
|
+
console.log(`${timestamp2()} ${scanTag()} ${paint9("green", "\u2713")} ${msg}`);
|
|
4949
4843
|
}
|
|
4950
|
-
function
|
|
4951
|
-
console.error(`${
|
|
4844
|
+
function logErr(msg) {
|
|
4845
|
+
console.error(`${timestamp2()} ${scanTag()} ${paint9("red", "\u2717")} ${msg}`);
|
|
4952
4846
|
}
|
|
4953
|
-
var scanCommand = new
|
|
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) => {
|
|
4954
4848
|
const config = loadConfig();
|
|
4955
4849
|
if (!config.apiKey) {
|
|
4956
|
-
|
|
4850
|
+
logErr('Not authenticated. Run "mr login" first.');
|
|
4957
4851
|
process.exit(1);
|
|
4958
4852
|
}
|
|
4959
4853
|
const banner = [
|
|
4960
4854
|
``,
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
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`),
|
|
4966
4860
|
``
|
|
4967
4861
|
].join("\n");
|
|
4968
4862
|
console.log(banner);
|
|
4969
4863
|
const projectId = opts.project || getLinkedProjectId();
|
|
4970
4864
|
if (!projectId) {
|
|
4971
|
-
|
|
4865
|
+
logErr('No project linked. Run "mr link" or pass --project <id>.');
|
|
4972
4866
|
process.exit(1);
|
|
4973
4867
|
}
|
|
4974
4868
|
let project;
|
|
4975
4869
|
try {
|
|
4976
4870
|
project = await api.get(`/api/projects/${projectId}`);
|
|
4977
4871
|
} catch {
|
|
4978
|
-
|
|
4872
|
+
logErr(`Failed to fetch project ${projectId}`);
|
|
4979
4873
|
process.exit(1);
|
|
4980
4874
|
}
|
|
4981
|
-
|
|
4875
|
+
log(`Scanning project: ${paint9("cyan", project.name)}`);
|
|
4982
4876
|
let projectPath = project.localPath;
|
|
4983
4877
|
if (!projectPath) {
|
|
4984
4878
|
for (const [dir, pid] of Object.entries(config.directories)) {
|
|
@@ -4994,12 +4888,12 @@ var scanCommand = new Command25("scan").description("Run a product scan on the c
|
|
|
4994
4888
|
let reportId;
|
|
4995
4889
|
if (opts.report) {
|
|
4996
4890
|
reportId = opts.report;
|
|
4997
|
-
|
|
4891
|
+
log(`Using existing scan report ${paint9("yellow", reportId.slice(0, 8))}`);
|
|
4998
4892
|
} else {
|
|
4999
4893
|
try {
|
|
5000
4894
|
const scans = await api.get(`/api/scans?projectId=${projectId}&status=processing`);
|
|
5001
4895
|
if (scans.length > 0) {
|
|
5002
|
-
|
|
4896
|
+
logErr("A scan is already in progress for this project. Wait for it to complete.");
|
|
5003
4897
|
process.exit(1);
|
|
5004
4898
|
}
|
|
5005
4899
|
} catch {
|
|
@@ -5010,9 +4904,9 @@ var scanCommand = new Command25("scan").description("Run a product scan on the c
|
|
|
5010
4904
|
status: "pending"
|
|
5011
4905
|
});
|
|
5012
4906
|
reportId = report.id;
|
|
5013
|
-
|
|
4907
|
+
log(`Created scan report ${paint9("yellow", reportId.slice(0, 8))}`);
|
|
5014
4908
|
} catch (err) {
|
|
5015
|
-
|
|
4909
|
+
logErr(`Failed to create scan report: ${err.message}`);
|
|
5016
4910
|
process.exit(1);
|
|
5017
4911
|
}
|
|
5018
4912
|
}
|
|
@@ -5023,7 +4917,7 @@ var scanCommand = new Command25("scan").description("Run a product scan on the c
|
|
|
5023
4917
|
try {
|
|
5024
4918
|
const current = await api.get(`/api/scans/${reportId}`);
|
|
5025
4919
|
if (current.status === "cancelled") {
|
|
5026
|
-
|
|
4920
|
+
log(paint9("yellow", "Scan was cancelled \u2014 aborting."));
|
|
5027
4921
|
process.exit(0);
|
|
5028
4922
|
}
|
|
5029
4923
|
} catch {
|
|
@@ -5037,9 +4931,9 @@ var scanCommand = new Command25("scan").description("Run a product scan on the c
|
|
|
5037
4931
|
apiUrl: config.apiUrl,
|
|
5038
4932
|
apiKey: config.apiKey,
|
|
5039
4933
|
runBrowse: runBrowseCommand2,
|
|
5040
|
-
onLog:
|
|
4934
|
+
onLog: log,
|
|
5041
4935
|
onProgress: (phase, detail) => {
|
|
5042
|
-
|
|
4936
|
+
log(`${paint9("dim", `[${phase}]`)} ${detail}`);
|
|
5043
4937
|
}
|
|
5044
4938
|
});
|
|
5045
4939
|
let wasCancelled = false;
|
|
@@ -5051,7 +4945,7 @@ var scanCommand = new Command25("scan").description("Run a product scan on the c
|
|
|
5051
4945
|
} catch {
|
|
5052
4946
|
}
|
|
5053
4947
|
if (wasCancelled) {
|
|
5054
|
-
|
|
4948
|
+
log(paint9("yellow", "Scan was cancelled by user \u2014 discarding results."));
|
|
5055
4949
|
process.exit(0);
|
|
5056
4950
|
}
|
|
5057
4951
|
await api.patch(`/api/scans/${reportId}`, {
|
|
@@ -5062,37 +4956,37 @@ var scanCommand = new Command25("scan").description("Run a product scan on the c
|
|
|
5062
4956
|
scanDurationMs: result.scanDurationMs,
|
|
5063
4957
|
routesCrawled: result.routesCrawled
|
|
5064
4958
|
});
|
|
5065
|
-
logOk(`Scan complete \u2014 ${
|
|
4959
|
+
logOk(`Scan complete \u2014 ${paint9("cyan", String(result.findings.length))} findings`);
|
|
5066
4960
|
console.log("");
|
|
5067
|
-
console.log(` ${
|
|
4961
|
+
console.log(` ${paint9("bold", "Summary:")} ${result.summary}`);
|
|
5068
4962
|
console.log("");
|
|
5069
4963
|
const high = result.findings.filter((f) => f.priority === "high");
|
|
5070
4964
|
const medium = result.findings.filter((f) => f.priority === "medium");
|
|
5071
4965
|
const low = result.findings.filter((f) => f.priority === "low");
|
|
5072
4966
|
if (high.length > 0) {
|
|
5073
|
-
console.log(` ${
|
|
4967
|
+
console.log(` ${paint9("bold", paint9("red", `High Priority (${high.length})`))}`);
|
|
5074
4968
|
for (const f of high) {
|
|
5075
|
-
console.log(` ${
|
|
5076
|
-
console.log(` ${
|
|
4969
|
+
console.log(` ${paint9("red", "\u25CF")} [${f.type}] ${f.title}`);
|
|
4970
|
+
console.log(` ${paint9("dim", f.description.slice(0, 120))}`);
|
|
5077
4971
|
}
|
|
5078
4972
|
console.log("");
|
|
5079
4973
|
}
|
|
5080
4974
|
if (medium.length > 0) {
|
|
5081
|
-
console.log(` ${
|
|
4975
|
+
console.log(` ${paint9("bold", paint9("yellow", `Medium Priority (${medium.length})`))}`);
|
|
5082
4976
|
for (const f of medium) {
|
|
5083
|
-
console.log(` ${
|
|
4977
|
+
console.log(` ${paint9("yellow", "\u25CF")} [${f.type}] ${f.title}`);
|
|
5084
4978
|
}
|
|
5085
4979
|
console.log("");
|
|
5086
4980
|
}
|
|
5087
4981
|
if (low.length > 0) {
|
|
5088
|
-
console.log(` ${
|
|
4982
|
+
console.log(` ${paint9("dim", `Low Priority (${low.length})`)} `);
|
|
5089
4983
|
for (const f of low) {
|
|
5090
|
-
console.log(` ${
|
|
4984
|
+
console.log(` ${paint9("dim", `\u25CB [${f.type}] ${f.title}`)}`);
|
|
5091
4985
|
}
|
|
5092
4986
|
console.log("");
|
|
5093
4987
|
}
|
|
5094
4988
|
} catch (err) {
|
|
5095
|
-
|
|
4989
|
+
logErr(`Scan failed: ${err.message}`);
|
|
5096
4990
|
try {
|
|
5097
4991
|
await api.patch(`/api/scans/${reportId}`, {
|
|
5098
4992
|
status: "failed",
|
|
@@ -5105,8 +4999,8 @@ var scanCommand = new Command25("scan").description("Run a product scan on the c
|
|
|
5105
4999
|
});
|
|
5106
5000
|
|
|
5107
5001
|
// cli/commands/idea.ts
|
|
5108
|
-
import { Command as
|
|
5109
|
-
var
|
|
5002
|
+
import { Command as Command25 } from "commander";
|
|
5003
|
+
var c9 = {
|
|
5110
5004
|
reset: "\x1B[0m",
|
|
5111
5005
|
bold: "\x1B[1m",
|
|
5112
5006
|
dim: "\x1B[2m",
|
|
@@ -5118,27 +5012,27 @@ var c10 = {
|
|
|
5118
5012
|
gray: "\x1B[90m",
|
|
5119
5013
|
magenta: "\x1B[35m"
|
|
5120
5014
|
};
|
|
5121
|
-
function
|
|
5122
|
-
return `${
|
|
5015
|
+
function paint10(color, text) {
|
|
5016
|
+
return `${c9[color]}${text}${c9.reset}`;
|
|
5123
5017
|
}
|
|
5124
5018
|
function statusBadge2(status) {
|
|
5125
5019
|
switch (status) {
|
|
5126
5020
|
case "draft":
|
|
5127
|
-
return
|
|
5021
|
+
return paint10("gray", "\u25CB draft");
|
|
5128
5022
|
case "generating":
|
|
5129
|
-
return
|
|
5023
|
+
return paint10("cyan", "\u27F3 generating");
|
|
5130
5024
|
case "generated":
|
|
5131
|
-
return
|
|
5025
|
+
return paint10("green", "\u2713 generated");
|
|
5132
5026
|
case "promoted":
|
|
5133
|
-
return
|
|
5027
|
+
return paint10("magenta", "\u2191 promoted");
|
|
5134
5028
|
case "archived":
|
|
5135
|
-
return
|
|
5029
|
+
return paint10("dim", "\u2298 archived");
|
|
5136
5030
|
default:
|
|
5137
|
-
return
|
|
5031
|
+
return paint10("gray", status);
|
|
5138
5032
|
}
|
|
5139
5033
|
}
|
|
5140
|
-
var ideaCommand = new
|
|
5141
|
-
new
|
|
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) => {
|
|
5142
5036
|
const params = new URLSearchParams();
|
|
5143
5037
|
if (!opts.all) {
|
|
5144
5038
|
const projectId = getLinkedProjectId();
|
|
@@ -5149,23 +5043,23 @@ var ideaCommand = new Command26("idea").description("Manage ideas \u2014 brainst
|
|
|
5149
5043
|
if (opts.status) params.set("status", opts.status);
|
|
5150
5044
|
const ideas = await api.get(`/api/ideas?${params.toString()}`);
|
|
5151
5045
|
if (ideas.length === 0) {
|
|
5152
|
-
console.log(
|
|
5046
|
+
console.log(paint10("gray", "No ideas found."));
|
|
5153
5047
|
return;
|
|
5154
5048
|
}
|
|
5155
5049
|
console.log();
|
|
5156
5050
|
for (const idea of ideas) {
|
|
5157
5051
|
const date = new Date(idea.createdAt).toLocaleDateString();
|
|
5158
5052
|
console.log(
|
|
5159
|
-
` ${
|
|
5053
|
+
` ${paint10("bold", idea.title)} ${statusBadge2(idea.status)} ${paint10("gray", idea.id.slice(0, 8))} ${paint10("dim", date)}`
|
|
5160
5054
|
);
|
|
5161
5055
|
if (idea.description) {
|
|
5162
|
-
console.log(` ${
|
|
5056
|
+
console.log(` ${paint10("dim", idea.description.slice(0, 80) + (idea.description.length > 80 ? "\u2026" : ""))}`);
|
|
5163
5057
|
}
|
|
5164
5058
|
console.log();
|
|
5165
5059
|
}
|
|
5166
5060
|
})
|
|
5167
5061
|
).addCommand(
|
|
5168
|
-
new
|
|
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) => {
|
|
5169
5063
|
const projectId = opts.project ?? getLinkedProjectId() ?? null;
|
|
5170
5064
|
const idea = await api.post("/api/ideas", {
|
|
5171
5065
|
title,
|
|
@@ -5173,65 +5067,65 @@ var ideaCommand = new Command26("idea").description("Manage ideas \u2014 brainst
|
|
|
5173
5067
|
projectId
|
|
5174
5068
|
});
|
|
5175
5069
|
console.log();
|
|
5176
|
-
console.log(` ${
|
|
5177
|
-
console.log(` ${
|
|
5070
|
+
console.log(` ${paint10("green", "\u2713")} Created idea: ${paint10("bold", idea.title)}`);
|
|
5071
|
+
console.log(` ${paint10("gray", "ID:")} ${idea.id}`);
|
|
5178
5072
|
if (opts.generate) {
|
|
5179
5073
|
await api.post(`/api/ideas/${idea.id}/generate`);
|
|
5180
|
-
console.log(` ${
|
|
5074
|
+
console.log(` ${paint10("cyan", "\u27F3")} Generation will begin automatically via the watch agent.`);
|
|
5181
5075
|
}
|
|
5182
5076
|
console.log();
|
|
5183
5077
|
})
|
|
5184
5078
|
).addCommand(
|
|
5185
|
-
new
|
|
5079
|
+
new Command25("generate").description("Start generating plan & prototype for an idea").argument("<id>", "Idea ID").action(async (id) => {
|
|
5186
5080
|
const idea = await api.post(`/api/ideas/${id}/generate`);
|
|
5187
5081
|
console.log();
|
|
5188
|
-
console.log(` ${
|
|
5189
|
-
console.log(` ${
|
|
5082
|
+
console.log(` ${paint10("cyan", "\u27F3")} Generating: ${paint10("bold", idea.title)}`);
|
|
5083
|
+
console.log(` ${paint10("gray", "The watch agent will pick this up shortly.")}`);
|
|
5190
5084
|
console.log();
|
|
5191
5085
|
})
|
|
5192
5086
|
).addCommand(
|
|
5193
|
-
new
|
|
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) => {
|
|
5194
5088
|
const idea = await api.post(`/api/ideas/${id}/feedback`, { feedback });
|
|
5195
5089
|
console.log();
|
|
5196
|
-
console.log(` ${
|
|
5197
|
-
console.log(` ${
|
|
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.")}`);
|
|
5198
5092
|
console.log();
|
|
5199
5093
|
})
|
|
5200
5094
|
).addCommand(
|
|
5201
|
-
new
|
|
5095
|
+
new Command25("promote").description("Promote an idea to a task").argument("<id>", "Idea ID").action(async (id) => {
|
|
5202
5096
|
const result = await api.post(`/api/ideas/${id}/promote`);
|
|
5203
5097
|
console.log();
|
|
5204
|
-
console.log(` ${
|
|
5205
|
-
console.log(` ${
|
|
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}`);
|
|
5206
5100
|
console.log();
|
|
5207
5101
|
})
|
|
5208
5102
|
).addCommand(
|
|
5209
|
-
new
|
|
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) => {
|
|
5210
5104
|
const body = {};
|
|
5211
5105
|
if (opts.name) body.name = opts.name;
|
|
5212
5106
|
const result = await api.post(`/api/ideas/${id}/spin-up`, body);
|
|
5213
5107
|
console.log();
|
|
5214
|
-
console.log(` ${
|
|
5215
|
-
console.log(` ${
|
|
5108
|
+
console.log(` ${paint10("green", "\u2713")} Spinning up project: ${paint10("bold", result.project.name)}`);
|
|
5109
|
+
console.log(` ${paint10("gray", "Project ID:")} ${result.project.id}`);
|
|
5216
5110
|
if (result.tasks && result.tasks.length > 0) {
|
|
5217
|
-
console.log(` ${
|
|
5111
|
+
console.log(` ${paint10("green", "\u2713")} Created ${result.tasks.length} follow-up task(s):`);
|
|
5218
5112
|
for (const task of result.tasks) {
|
|
5219
|
-
console.log(` ${
|
|
5113
|
+
console.log(` ${paint10("gray", "\u2022")} ${task.title}`);
|
|
5220
5114
|
}
|
|
5221
5115
|
}
|
|
5222
|
-
console.log(` ${
|
|
5116
|
+
console.log(` ${paint10("cyan", "\u27F3")} Repo creation is queued \u2014 the watch daemon will pick it up.`);
|
|
5223
5117
|
console.log();
|
|
5224
5118
|
})
|
|
5225
5119
|
);
|
|
5226
5120
|
|
|
5227
5121
|
// cli/commands/doctor.ts
|
|
5228
|
-
import { Command as
|
|
5229
|
-
import { existsSync as
|
|
5230
|
-
import { homedir as
|
|
5122
|
+
import { Command as Command26 } from "commander";
|
|
5123
|
+
import { existsSync as existsSync15 } from "fs";
|
|
5124
|
+
import { homedir as homedir2 } from "os";
|
|
5231
5125
|
import { join as join12 } from "path";
|
|
5232
5126
|
async function checkConfigExists() {
|
|
5233
|
-
const configPath2 = join12(
|
|
5234
|
-
const exists =
|
|
5127
|
+
const configPath2 = join12(homedir2(), ".mr-manager", "config.json");
|
|
5128
|
+
const exists = existsSync15(configPath2);
|
|
5235
5129
|
if (!exists) {
|
|
5236
5130
|
return {
|
|
5237
5131
|
name: "Config file",
|
|
@@ -5273,12 +5167,12 @@ async function checkProjectLink() {
|
|
|
5273
5167
|
optional: true
|
|
5274
5168
|
};
|
|
5275
5169
|
}
|
|
5276
|
-
var doctorCommand = new
|
|
5170
|
+
var doctorCommand = new Command26("doctor").description("Diagnose Mr. Manager CLI installation and environment").action(async () => {
|
|
5277
5171
|
const banner = [
|
|
5278
5172
|
``,
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
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`),
|
|
5282
5176
|
``
|
|
5283
5177
|
].join("\n");
|
|
5284
5178
|
console.log(banner);
|
|
@@ -5300,15 +5194,15 @@ var doctorCommand = new Command27("doctor").description("Diagnose Mr. Manager CL
|
|
|
5300
5194
|
const allOk = printResults(checks);
|
|
5301
5195
|
console.log("");
|
|
5302
5196
|
if (allOk) {
|
|
5303
|
-
console.log(
|
|
5197
|
+
console.log(paint5("green", " Everything looks good!"));
|
|
5304
5198
|
console.log("");
|
|
5305
5199
|
return;
|
|
5306
5200
|
}
|
|
5307
|
-
const fixes = checks.filter((
|
|
5201
|
+
const fixes = checks.filter((c10) => !c10.ok && c10.fix && !c10.optional);
|
|
5308
5202
|
if (fixes.length > 0) {
|
|
5309
|
-
console.log(
|
|
5203
|
+
console.log(paint5("yellow", " To fix:"));
|
|
5310
5204
|
for (const fix of fixes) {
|
|
5311
|
-
console.log(` ${
|
|
5205
|
+
console.log(` ${paint5("dim", "\u2192")} ${paint5("bold", fix.name)}: ${fix.fix}`);
|
|
5312
5206
|
}
|
|
5313
5207
|
console.log("");
|
|
5314
5208
|
}
|
|
@@ -5316,13 +5210,13 @@ var doctorCommand = new Command27("doctor").description("Diagnose Mr. Manager CL
|
|
|
5316
5210
|
});
|
|
5317
5211
|
|
|
5318
5212
|
// cli/index.ts
|
|
5319
|
-
var configPath = join13(
|
|
5320
|
-
var isFirstRun = !
|
|
5213
|
+
var configPath = join13(homedir3(), ".mr-manager", "config.json");
|
|
5214
|
+
var isFirstRun = !existsSync16(configPath);
|
|
5321
5215
|
var userArgs = process.argv.slice(2);
|
|
5322
5216
|
var bypassCommands = /* @__PURE__ */ new Set(["login", "init", "auth", "help", "--help", "-h", "--version", "-V", "doctor", "setup"]);
|
|
5323
5217
|
var shouldBypass = userArgs.length > 0 && bypassCommands.has(userArgs[0]);
|
|
5324
5218
|
if (isFirstRun && !shouldBypass) {
|
|
5325
|
-
const
|
|
5219
|
+
const c10 = {
|
|
5326
5220
|
reset: "\x1B[0m",
|
|
5327
5221
|
bold: "\x1B[1m",
|
|
5328
5222
|
dim: "\x1B[2m",
|
|
@@ -5332,29 +5226,29 @@ if (isFirstRun && !shouldBypass) {
|
|
|
5332
5226
|
magenta: "\x1B[35m"
|
|
5333
5227
|
};
|
|
5334
5228
|
console.log("");
|
|
5335
|
-
console.log(`${
|
|
5336
|
-
console.log(`${
|
|
5337
|
-
console.log(`${
|
|
5338
|
-
console.log(`${
|
|
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}`);
|
|
5339
5233
|
console.log("");
|
|
5340
|
-
console.log(`${
|
|
5341
|
-
console.log(`${
|
|
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}`);
|
|
5342
5236
|
console.log("");
|
|
5343
|
-
console.log(` ${
|
|
5344
|
-
console.log(` ${
|
|
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}`);
|
|
5345
5239
|
console.log("");
|
|
5346
|
-
console.log(` ${
|
|
5347
|
-
console.log(` ${
|
|
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}`);
|
|
5348
5242
|
console.log("");
|
|
5349
|
-
console.log(` ${
|
|
5350
|
-
console.log(` ${
|
|
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}`);
|
|
5351
5245
|
console.log("");
|
|
5352
|
-
console.log(`${
|
|
5246
|
+
console.log(`${c10.dim} Or run ${c10.reset}${c10.cyan}mr login${c10.reset}${c10.dim} to get started now.${c10.reset}`);
|
|
5353
5247
|
console.log("");
|
|
5354
5248
|
process.exit(0);
|
|
5355
5249
|
}
|
|
5356
|
-
var program = new
|
|
5357
|
-
program.name("mr").description("Mr. Manager - Task and project management CLI").version(
|
|
5250
|
+
var program = new Command27();
|
|
5251
|
+
program.name("mr").description("Mr. Manager - Task and project management CLI").version(CLI_VERSION);
|
|
5358
5252
|
program.addCommand(initCommand);
|
|
5359
5253
|
program.addCommand(authCommand);
|
|
5360
5254
|
program.addCommand(loginCommand);
|
|
@@ -5370,7 +5264,6 @@ program.addCommand(undelegateCommand);
|
|
|
5370
5264
|
program.addCommand(createCommand);
|
|
5371
5265
|
program.addCommand(completeCommand);
|
|
5372
5266
|
program.addCommand(subtaskCompleteCommand);
|
|
5373
|
-
program.addCommand(upCommand);
|
|
5374
5267
|
program.addCommand(prototypeCommand);
|
|
5375
5268
|
program.addCommand(setupCommand);
|
|
5376
5269
|
program.addCommand(updateCommand);
|