@dunnewold-labs/mr-manager 0.4.22 → 0.4.24
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 +149 -67
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -132,7 +132,7 @@ Remote login \u2014 visit this URL in any browser:
|
|
|
132
132
|
}
|
|
133
133
|
async function loginWithLocalServer(apiUrl) {
|
|
134
134
|
const port = getRandomPort();
|
|
135
|
-
return new Promise((
|
|
135
|
+
return new Promise((resolve9, reject) => {
|
|
136
136
|
const server = createServer((req, res) => {
|
|
137
137
|
const url = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
138
138
|
const key = url.searchParams.get("key");
|
|
@@ -144,7 +144,7 @@ async function loginWithLocalServer(apiUrl) {
|
|
|
144
144
|
</body></html>
|
|
145
145
|
`);
|
|
146
146
|
server.close();
|
|
147
|
-
if (key)
|
|
147
|
+
if (key) resolve9(key);
|
|
148
148
|
else reject(new Error("No key received from server"));
|
|
149
149
|
});
|
|
150
150
|
server.listen(port, () => {
|
|
@@ -185,7 +185,7 @@ import { fileURLToPath } from "url";
|
|
|
185
185
|
// cli/package.json
|
|
186
186
|
var package_default = {
|
|
187
187
|
name: "@dunnewold-labs/mr-manager",
|
|
188
|
-
version: "0.4.
|
|
188
|
+
version: "0.4.24",
|
|
189
189
|
description: "Mr. Manager - Task and project management CLI",
|
|
190
190
|
bin: {
|
|
191
191
|
mr: "./dist/index.mjs"
|
|
@@ -395,10 +395,10 @@ function detectVcs(cwd) {
|
|
|
395
395
|
// cli/commands/link.ts
|
|
396
396
|
function prompt(question) {
|
|
397
397
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
398
|
-
return new Promise((
|
|
398
|
+
return new Promise((resolve9) => {
|
|
399
399
|
rl.question(question, (answer) => {
|
|
400
400
|
rl.close();
|
|
401
|
-
|
|
401
|
+
resolve9(answer.trim());
|
|
402
402
|
});
|
|
403
403
|
});
|
|
404
404
|
}
|
|
@@ -1188,7 +1188,7 @@ Run the setup script: cd browse && ./setup`
|
|
|
1188
1188
|
async function runBrowseCommand2(browseArgs) {
|
|
1189
1189
|
const runner = getBrowseRunner();
|
|
1190
1190
|
const fullArgs = [...runner.args, ...browseArgs];
|
|
1191
|
-
return new Promise((
|
|
1191
|
+
return new Promise((resolve9) => {
|
|
1192
1192
|
const proc = spawn3(runner.cmd, fullArgs, {
|
|
1193
1193
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1194
1194
|
env: { ...process.env }
|
|
@@ -1205,10 +1205,10 @@ async function runBrowseCommand2(browseArgs) {
|
|
|
1205
1205
|
if (stderr && code !== 0) {
|
|
1206
1206
|
process.stderr.write(stderr);
|
|
1207
1207
|
}
|
|
1208
|
-
|
|
1208
|
+
resolve9({ stdout: stdout.trim(), exitCode: code || 0 });
|
|
1209
1209
|
});
|
|
1210
1210
|
proc.on("error", () => {
|
|
1211
|
-
|
|
1211
|
+
resolve9({ stdout: "", exitCode: 1 });
|
|
1212
1212
|
});
|
|
1213
1213
|
});
|
|
1214
1214
|
}
|
|
@@ -1595,13 +1595,13 @@ ${task.notes}` : "";
|
|
|
1595
1595
|
}
|
|
1596
1596
|
function findPrUrl(branchName, repoDir, vcs = "github") {
|
|
1597
1597
|
const cmd = vcs === "gitlab" ? `glab mr view "${branchName}" --output json 2>/dev/null | jq -r '.web_url // empty'` : `gh pr view "${branchName}" --json url -q .url`;
|
|
1598
|
-
return new Promise((
|
|
1598
|
+
return new Promise((resolve9) => {
|
|
1599
1599
|
exec(
|
|
1600
1600
|
cmd,
|
|
1601
1601
|
{ cwd: repoDir },
|
|
1602
1602
|
(err, stdout) => {
|
|
1603
|
-
if (err)
|
|
1604
|
-
else
|
|
1603
|
+
if (err) resolve9(null);
|
|
1604
|
+
else resolve9(stdout.trim() || null);
|
|
1605
1605
|
}
|
|
1606
1606
|
);
|
|
1607
1607
|
});
|
|
@@ -1640,8 +1640,8 @@ function extractPrUrlFromText(value) {
|
|
|
1640
1640
|
return match ? match[0] : null;
|
|
1641
1641
|
}
|
|
1642
1642
|
function commandSucceeds(command, cwd) {
|
|
1643
|
-
return new Promise((
|
|
1644
|
-
exec(command, { cwd }, (err) =>
|
|
1643
|
+
return new Promise((resolve9) => {
|
|
1644
|
+
exec(command, { cwd }, (err) => resolve9(!err));
|
|
1645
1645
|
});
|
|
1646
1646
|
}
|
|
1647
1647
|
async function createPrInRepo(task, branchName, repoDir, vcs, subtasks, protoRefs = [], feedbackUpdates = [], existingResources = [], skillRefs = []) {
|
|
@@ -1656,13 +1656,13 @@ async function createPrInRepo(task, branchName, repoDir, vcs, subtasks, protoRef
|
|
|
1656
1656
|
`, "utf-8");
|
|
1657
1657
|
const createCommand2 = vcs === "gitlab" ? `glab mr create --source-branch ${JSON.stringify(branchName)} --title ${JSON.stringify(task.title)} --description-file ${JSON.stringify(bodyPath)} --yes` : `gh pr create --head ${JSON.stringify(branchName)} --title ${JSON.stringify(task.title)} --body-file ${JSON.stringify(bodyPath)}`;
|
|
1658
1658
|
try {
|
|
1659
|
-
const output = await new Promise((
|
|
1659
|
+
const output = await new Promise((resolve9, reject) => {
|
|
1660
1660
|
exec(createCommand2, { cwd: repoDir }, (err, stdout, stderr) => {
|
|
1661
1661
|
if (err) {
|
|
1662
1662
|
reject(new Error(stderr.trim() || stdout.trim() || err.message));
|
|
1663
1663
|
return;
|
|
1664
1664
|
}
|
|
1665
|
-
|
|
1665
|
+
resolve9(`${stdout}
|
|
1666
1666
|
${stderr}`.trim());
|
|
1667
1667
|
});
|
|
1668
1668
|
});
|
|
@@ -1745,27 +1745,27 @@ async function extractPrUrlFromUpdates(taskId) {
|
|
|
1745
1745
|
}
|
|
1746
1746
|
function checkPrStatus(prUrl, repoDir, vcs = "github") {
|
|
1747
1747
|
const cmd = vcs === "gitlab" ? `glab mr view "${prUrl}" --output json 2>/dev/null` : `gh pr view "${prUrl}" --json merged,mergeable 2>/dev/null`;
|
|
1748
|
-
return new Promise((
|
|
1748
|
+
return new Promise((resolve9) => {
|
|
1749
1749
|
exec(cmd, { cwd: repoDir }, (err, stdout) => {
|
|
1750
1750
|
if (err || !stdout.trim()) {
|
|
1751
|
-
|
|
1751
|
+
resolve9(null);
|
|
1752
1752
|
return;
|
|
1753
1753
|
}
|
|
1754
1754
|
try {
|
|
1755
1755
|
const data = JSON.parse(stdout.trim());
|
|
1756
1756
|
if (vcs === "gitlab") {
|
|
1757
|
-
|
|
1757
|
+
resolve9({
|
|
1758
1758
|
merged: data.state === "merged",
|
|
1759
1759
|
hasConflicts: data.has_conflicts === true
|
|
1760
1760
|
});
|
|
1761
1761
|
} else {
|
|
1762
|
-
|
|
1762
|
+
resolve9({
|
|
1763
1763
|
merged: data.merged === true,
|
|
1764
1764
|
hasConflicts: data.mergeable === "CONFLICTING"
|
|
1765
1765
|
});
|
|
1766
1766
|
}
|
|
1767
1767
|
} catch {
|
|
1768
|
-
|
|
1768
|
+
resolve9(null);
|
|
1769
1769
|
}
|
|
1770
1770
|
});
|
|
1771
1771
|
});
|
|
@@ -2464,7 +2464,7 @@ function buildIdeaPrompt(idea, repoDir) {
|
|
|
2464
2464
|
`- Do NOT exit until both files have been written and verified`
|
|
2465
2465
|
].join("\n");
|
|
2466
2466
|
}
|
|
2467
|
-
function buildAgentArgs(agent, prompt2, mode, sessionId, name, resumeSession = false, systemPrompt, maxTurns) {
|
|
2467
|
+
function buildAgentArgs(agent, prompt2, mode, sessionId, name, resumeSession = false, systemPrompt, maxTurns, claudeModel) {
|
|
2468
2468
|
if (agent === "codex") {
|
|
2469
2469
|
const args = [];
|
|
2470
2470
|
if (mode === "execute") {
|
|
@@ -2494,19 +2494,37 @@ ${systemPrompt}` : prompt2;
|
|
|
2494
2494
|
const nameArgs = name ? ["--name", name] : [];
|
|
2495
2495
|
const systemArgs = systemPrompt ? ["--append-system-prompt", systemPrompt] : [];
|
|
2496
2496
|
const turnsArgs = maxTurns ? ["--max-turns", String(maxTurns)] : [];
|
|
2497
|
+
const modelArgs = claudeModel ? ["--model", claudeModel] : [];
|
|
2497
2498
|
if (mode === "plan") {
|
|
2498
|
-
return { bin: "claude", args: [...sessionArgs, ...nameArgs, ...systemArgs, ...turnsArgs, "--permission-mode", "plan", "-p", prompt2] };
|
|
2499
|
+
return { bin: "claude", args: [...sessionArgs, ...nameArgs, ...systemArgs, ...turnsArgs, ...modelArgs, "--permission-mode", "plan", "-p", prompt2] };
|
|
2499
2500
|
}
|
|
2500
2501
|
const cfg = loadConfig();
|
|
2501
|
-
const permissionMode = cfg.claudePermissionMode ?? "
|
|
2502
|
+
const permissionMode = cfg.claudePermissionMode ?? "auto";
|
|
2502
2503
|
const permissionArgs = permissionMode === "dangerously-skip-permissions" ? ["--dangerously-skip-permissions"] : ["--permission-mode", "auto", "--enable-auto-mode"];
|
|
2503
|
-
return { bin: "claude", args: [...sessionArgs, ...nameArgs, ...systemArgs, ...turnsArgs, ...permissionArgs, "-p", prompt2] };
|
|
2504
|
+
return { bin: "claude", args: [...sessionArgs, ...nameArgs, ...systemArgs, ...turnsArgs, ...modelArgs, ...permissionArgs, "-p", prompt2] };
|
|
2504
2505
|
}
|
|
2505
2506
|
function commandExists(cmd) {
|
|
2506
|
-
return new Promise((
|
|
2507
|
-
exec(`command -v ${cmd}`, (err) =>
|
|
2507
|
+
return new Promise((resolve9) => {
|
|
2508
|
+
exec(`command -v ${cmd}`, (err) => resolve9(!err));
|
|
2508
2509
|
});
|
|
2509
2510
|
}
|
|
2511
|
+
function resolveTaskAgentAndModel(delegatedModel, watchAgent) {
|
|
2512
|
+
if (!delegatedModel) return { agent: watchAgent };
|
|
2513
|
+
switch (delegatedModel) {
|
|
2514
|
+
case "claude-opus":
|
|
2515
|
+
return { agent: "claude", claudeModel: "claude-opus-4-6" };
|
|
2516
|
+
case "claude-sonnet":
|
|
2517
|
+
return { agent: "claude", claudeModel: "claude-sonnet-4-6" };
|
|
2518
|
+
case "claude-haiku":
|
|
2519
|
+
return { agent: "claude", claudeModel: "claude-haiku-4-5-20251001" };
|
|
2520
|
+
case "codex":
|
|
2521
|
+
return { agent: "codex" };
|
|
2522
|
+
case "gemini":
|
|
2523
|
+
return { agent: "gemini" };
|
|
2524
|
+
default:
|
|
2525
|
+
return { agent: watchAgent };
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2510
2528
|
function runPlanningPhase(task, repoDir, agent) {
|
|
2511
2529
|
return new Promise((res, reject) => {
|
|
2512
2530
|
const planPrompt = buildPlanningPrompt(task, repoDir);
|
|
@@ -2544,21 +2562,21 @@ ${output.trim()}`));
|
|
|
2544
2562
|
});
|
|
2545
2563
|
}
|
|
2546
2564
|
function askYesNo(question) {
|
|
2547
|
-
return new Promise((
|
|
2565
|
+
return new Promise((resolve9) => {
|
|
2548
2566
|
const rl = readline.createInterface({
|
|
2549
2567
|
input: process.stdin,
|
|
2550
2568
|
output: process.stdout
|
|
2551
2569
|
});
|
|
2552
2570
|
rl.question(question, (answer) => {
|
|
2553
2571
|
rl.close();
|
|
2554
|
-
|
|
2572
|
+
resolve9(answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes");
|
|
2555
2573
|
});
|
|
2556
2574
|
});
|
|
2557
2575
|
}
|
|
2558
|
-
function spawnAgent(agent, repoDir, prompt2, prefix, onActivity, sessionId, name, resumeSession = false, onSpawnError, systemPrompt, maxTurns) {
|
|
2576
|
+
function spawnAgent(agent, repoDir, prompt2, prefix, onActivity, sessionId, name, resumeSession = false, onSpawnError, systemPrompt, maxTurns, claudeModel) {
|
|
2559
2577
|
const jobLabel = name ?? "unknown";
|
|
2560
2578
|
console.log(`${timestamp()} ${prefix} ${paint("dim", tokenLogLine("agent", jobLabel, prompt2, systemPrompt))}`);
|
|
2561
|
-
const { bin, args } = buildAgentArgs(agent, prompt2, "execute", sessionId, name, resumeSession, systemPrompt, maxTurns);
|
|
2579
|
+
const { bin, args } = buildAgentArgs(agent, prompt2, "execute", sessionId, name, resumeSession, systemPrompt, maxTurns, claudeModel);
|
|
2562
2580
|
const child = spawn4(bin, args, { cwd: repoDir, stdio: ["ignore", "pipe", "pipe"] });
|
|
2563
2581
|
child.on("error", (err) => {
|
|
2564
2582
|
logError(prefix, `Failed to spawn ${agent}: ${err.message}`);
|
|
@@ -2818,10 +2836,16 @@ var watchCommand = new Command8("watch").description(
|
|
|
2818
2836
|
const touchActivity = () => {
|
|
2819
2837
|
activeEntry.lastActivityAt = Date.now();
|
|
2820
2838
|
};
|
|
2821
|
-
const
|
|
2839
|
+
const { agent: taskAgent, claudeModel: taskClaudeModel } = resolveTaskAgentAndModel(task.delegatedModel, agent);
|
|
2840
|
+
if (taskClaudeModel) {
|
|
2841
|
+
logInfo(prefix, `Using model: ${paint("cyan", taskClaudeModel)}`);
|
|
2842
|
+
} else if (taskAgent !== agent) {
|
|
2843
|
+
logInfo(prefix, `Using agent override: ${paint("cyan", taskAgent)}`);
|
|
2844
|
+
}
|
|
2845
|
+
const attemptOrder = await resolveAgentChain(taskAgent);
|
|
2822
2846
|
if (attemptOrder.length === 0) {
|
|
2823
|
-
logError(prefix, `No available agents found for fallback chain starting at ${
|
|
2824
|
-
await moveTaskToError(task, prefix, `No available agent found for fallback chain starting at ${
|
|
2847
|
+
logError(prefix, `No available agents found for fallback chain starting at ${taskAgent}`);
|
|
2848
|
+
await moveTaskToError(task, prefix, `No available agent found for fallback chain starting at ${taskAgent}`);
|
|
2825
2849
|
if (activeEntry.cleanupRepoDir && activeEntry.cleanupWorktreePath) {
|
|
2826
2850
|
removeWorktree(activeEntry.cleanupRepoDir, activeEntry.cleanupWorktreePath);
|
|
2827
2851
|
}
|
|
@@ -2835,6 +2859,7 @@ var watchCommand = new Command8("watch").description(
|
|
|
2835
2859
|
const pausedForNetwork = networkPaused.get(task.id);
|
|
2836
2860
|
const shouldResumeClaudeSession = attemptAgent === "claude" && !!task.claudeSessionId && !resumeAlreadyRetried && (hasFeedback || pausedForNetwork?.resumeSession === true);
|
|
2837
2861
|
const sessionId = attemptAgent === "claude" ? shouldResumeClaudeSession ? task.claudeSessionId : randomUUID() : void 0;
|
|
2862
|
+
const effectiveClaudeModel = attemptAgent === "claude" ? taskClaudeModel : void 0;
|
|
2838
2863
|
const executionSystemPrompt = composeSystemPrompt(EXECUTION_SYSTEM_SECTIONS);
|
|
2839
2864
|
const child = spawnAgent(
|
|
2840
2865
|
attemptAgent,
|
|
@@ -2848,7 +2873,9 @@ var watchCommand = new Command8("watch").description(
|
|
|
2848
2873
|
(err) => {
|
|
2849
2874
|
spawnFailureReason = err.message;
|
|
2850
2875
|
},
|
|
2851
|
-
executionSystemPrompt
|
|
2876
|
+
executionSystemPrompt,
|
|
2877
|
+
void 0,
|
|
2878
|
+
effectiveClaudeModel
|
|
2852
2879
|
);
|
|
2853
2880
|
activeEntry.process = child;
|
|
2854
2881
|
activeEntry.currentAgent = attemptAgent;
|
|
@@ -4445,14 +4472,14 @@ function paint5(color, text) {
|
|
|
4445
4472
|
return `${c5[color]}${text}${c5.reset}`;
|
|
4446
4473
|
}
|
|
4447
4474
|
function commandExists2(cmd) {
|
|
4448
|
-
return new Promise((
|
|
4449
|
-
exec2(`which ${cmd}`, (err) =>
|
|
4475
|
+
return new Promise((resolve9) => {
|
|
4476
|
+
exec2(`which ${cmd}`, (err) => resolve9(!err));
|
|
4450
4477
|
});
|
|
4451
4478
|
}
|
|
4452
4479
|
function execQuiet(cmd) {
|
|
4453
|
-
return new Promise((
|
|
4480
|
+
return new Promise((resolve9) => {
|
|
4454
4481
|
exec2(cmd, (err, stdout, stderr) => {
|
|
4455
|
-
|
|
4482
|
+
resolve9({ ok: !err, stdout: stdout.trim(), stderr: stderr.trim() });
|
|
4456
4483
|
});
|
|
4457
4484
|
});
|
|
4458
4485
|
}
|
|
@@ -4707,26 +4734,26 @@ async function autoFix(checks, agent) {
|
|
|
4707
4734
|
if (claudeCheck && !claudeCheck.ok && agent === "claude") {
|
|
4708
4735
|
console.log(paint5("cyan", " Installing Claude Code..."));
|
|
4709
4736
|
console.log(paint5("dim", " Running: curl -fsSL https://claude.ai/install.sh | bash"));
|
|
4710
|
-
await new Promise((
|
|
4737
|
+
await new Promise((resolve9) => {
|
|
4711
4738
|
const child = spawn8("bash", ["-c", "curl -fsSL https://claude.ai/install.sh | bash"], { stdio: "inherit" });
|
|
4712
|
-
child.on("exit", () =>
|
|
4739
|
+
child.on("exit", () => resolve9());
|
|
4713
4740
|
});
|
|
4714
4741
|
console.log("");
|
|
4715
4742
|
}
|
|
4716
4743
|
if (ghInstalled && !ghAuthed) {
|
|
4717
4744
|
console.log(paint5("cyan", " Running gh auth login..."));
|
|
4718
|
-
await new Promise((
|
|
4745
|
+
await new Promise((resolve9) => {
|
|
4719
4746
|
const child = spawn8("gh", ["auth", "login"], { stdio: "inherit" });
|
|
4720
|
-
child.on("exit", () =>
|
|
4747
|
+
child.on("exit", () => resolve9());
|
|
4721
4748
|
});
|
|
4722
4749
|
console.log("");
|
|
4723
4750
|
}
|
|
4724
4751
|
if (!mrAuthed) {
|
|
4725
4752
|
console.log(paint5("cyan", " Running mr login..."));
|
|
4726
4753
|
const entry = process.argv[1];
|
|
4727
|
-
await new Promise((
|
|
4754
|
+
await new Promise((resolve9) => {
|
|
4728
4755
|
const child = spawn8(process.execPath, [entry, "login"], { stdio: "inherit" });
|
|
4729
|
-
child.on("exit", () =>
|
|
4756
|
+
child.on("exit", () => resolve9());
|
|
4730
4757
|
});
|
|
4731
4758
|
console.log("");
|
|
4732
4759
|
}
|
|
@@ -4975,7 +5002,8 @@ var resumeCommand = new Command17("resume").description("Resume an interactive C
|
|
|
4975
5002
|
import { Command as Command18 } from "commander";
|
|
4976
5003
|
import { execSync as execSync4, spawn as spawn6 } from "child_process";
|
|
4977
5004
|
import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
4978
|
-
import {
|
|
5005
|
+
import { createHash } from "crypto";
|
|
5006
|
+
import { join as join8, resolve as resolve4 } from "path";
|
|
4979
5007
|
var BROWSE_DIR2 = join8(import.meta.dirname, "..", "..", "browse");
|
|
4980
5008
|
function isProcessAlive(pid) {
|
|
4981
5009
|
try {
|
|
@@ -5003,43 +5031,93 @@ async function findAvailablePort2(startPort) {
|
|
|
5003
5031
|
}
|
|
5004
5032
|
throw new Error(`No available port found in range ${startPort}-${startPort + 99}`);
|
|
5005
5033
|
}
|
|
5006
|
-
|
|
5007
|
-
|
|
5034
|
+
function detectDevCommand2(cwd) {
|
|
5035
|
+
try {
|
|
5036
|
+
const pkg = JSON.parse(readFileSync7(join8(cwd, "package.json"), "utf-8"));
|
|
5037
|
+
const scripts = pkg.scripts || {};
|
|
5038
|
+
if (scripts.dev) return "npm run dev";
|
|
5039
|
+
if (scripts.start) return "npm start";
|
|
5040
|
+
if (scripts.serve) return "npm run serve";
|
|
5041
|
+
} catch {
|
|
5042
|
+
}
|
|
5043
|
+
return "npm run dev";
|
|
5044
|
+
}
|
|
5045
|
+
function parseCommand(cmd) {
|
|
5046
|
+
const args = [];
|
|
5047
|
+
let current = "";
|
|
5048
|
+
let inSingle = false;
|
|
5049
|
+
let inDouble = false;
|
|
5050
|
+
for (let i = 0; i < cmd.length; i++) {
|
|
5051
|
+
const ch = cmd[i];
|
|
5052
|
+
if (ch === "'" && !inDouble) {
|
|
5053
|
+
inSingle = !inSingle;
|
|
5054
|
+
} else if (ch === '"' && !inSingle) {
|
|
5055
|
+
inDouble = !inDouble;
|
|
5056
|
+
} else if (ch === " " && !inSingle && !inDouble) {
|
|
5057
|
+
if (current) {
|
|
5058
|
+
args.push(current);
|
|
5059
|
+
current = "";
|
|
5060
|
+
}
|
|
5061
|
+
} else {
|
|
5062
|
+
current += ch;
|
|
5063
|
+
}
|
|
5064
|
+
}
|
|
5065
|
+
if (current) args.push(current);
|
|
5066
|
+
return args;
|
|
5067
|
+
}
|
|
5068
|
+
async function ensureDevServer(options = {}) {
|
|
5069
|
+
const projectCwd = options.cwd ? resolve4(options.cwd) : join8(import.meta.dirname, "..", "..");
|
|
5070
|
+
const devCmd = options.cmd || detectDevCommand2(projectCwd);
|
|
5071
|
+
const cwdHash = createHash("md5").update(projectCwd).digest("hex").slice(0, 8);
|
|
5072
|
+
const devStateFile = `/tmp/mr-dev-server-${cwdHash}.json`;
|
|
5008
5073
|
try {
|
|
5009
5074
|
const state = JSON.parse(readFileSync7(devStateFile, "utf-8"));
|
|
5010
5075
|
if (isProcessAlive(state.pid) && await isPortResponding2(state.port)) {
|
|
5076
|
+
console.log(`[browse] Reusing dev server on port ${state.port} (${projectCwd})`);
|
|
5011
5077
|
return state.port;
|
|
5012
5078
|
}
|
|
5013
5079
|
} catch {
|
|
5014
5080
|
}
|
|
5015
5081
|
const port = await findAvailablePort2(3e3);
|
|
5016
|
-
|
|
5017
|
-
const
|
|
5082
|
+
const argv = parseCommand(devCmd);
|
|
5083
|
+
const portFlag = options.portFlag;
|
|
5084
|
+
let spawnArgs;
|
|
5085
|
+
let spawnEnv = { ...process.env };
|
|
5086
|
+
if (portFlag) {
|
|
5087
|
+
spawnArgs = [...argv.slice(1), portFlag, String(port)];
|
|
5088
|
+
} else if (devCmd.includes("next") || devCmd.includes("vite") || devCmd.includes("webpack")) {
|
|
5089
|
+
spawnArgs = [...argv.slice(1), "--", "--port", String(port)];
|
|
5090
|
+
} else {
|
|
5091
|
+
spawnEnv.PORT = String(port);
|
|
5092
|
+
spawnArgs = argv.slice(1);
|
|
5093
|
+
}
|
|
5094
|
+
console.log(`[browse] Starting dev server: ${devCmd} (port ${port}) in ${projectCwd}`);
|
|
5095
|
+
const devProc = spawn6(argv[0], spawnArgs, {
|
|
5018
5096
|
stdio: ["ignore", "pipe", "pipe"],
|
|
5019
5097
|
detached: true,
|
|
5020
|
-
cwd:
|
|
5021
|
-
env:
|
|
5098
|
+
cwd: projectCwd,
|
|
5099
|
+
env: spawnEnv
|
|
5022
5100
|
});
|
|
5023
5101
|
devProc.unref();
|
|
5024
5102
|
writeFileSync4(
|
|
5025
5103
|
devStateFile,
|
|
5026
|
-
JSON.stringify({ pid: devProc.pid, port, startedAt: (/* @__PURE__ */ new Date()).toISOString() }),
|
|
5104
|
+
JSON.stringify({ pid: devProc.pid, port, cwd: projectCwd, cmd: devCmd, startedAt: (/* @__PURE__ */ new Date()).toISOString() }),
|
|
5027
5105
|
{ mode: 384 }
|
|
5028
5106
|
);
|
|
5029
5107
|
const start = Date.now();
|
|
5030
|
-
while (Date.now() - start <
|
|
5108
|
+
while (Date.now() - start < 6e4) {
|
|
5031
5109
|
if (await isPortResponding2(port)) {
|
|
5032
5110
|
console.log(`[browse] Dev server ready on port ${port}`);
|
|
5033
5111
|
return port;
|
|
5034
5112
|
}
|
|
5035
5113
|
await new Promise((r) => setTimeout(r, 500));
|
|
5036
5114
|
}
|
|
5037
|
-
throw new Error(
|
|
5115
|
+
throw new Error(`Dev server failed to start within 60s. Command: ${devCmd} in ${projectCwd}`);
|
|
5038
5116
|
}
|
|
5039
5117
|
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(
|
|
5040
5118
|
"--task-id <id>",
|
|
5041
5119
|
"Attach output to a task update (for screenshot and recording-stop commands)"
|
|
5042
|
-
).option("--dev", "Auto-start local dev server before browsing").allowUnknownOption(true).action(
|
|
5120
|
+
).option("--dev", "Auto-start local dev server before browsing").option("--dev-cwd <path>", "Working directory for the dev server (defaults to mr-manager root)").option("--dev-cmd <command>", "Dev server command to run (auto-detected from package.json if omitted)").option("--dev-port-flag <flag>", "CLI flag name used to set port (e.g. --port). Omit to use PORT env var.").allowUnknownOption(true).action(
|
|
5043
5121
|
async (command, args, opts) => {
|
|
5044
5122
|
if (!command) {
|
|
5045
5123
|
const { stdout: stdout2 } = await runBrowseCommand2(["--help"]);
|
|
@@ -5058,7 +5136,11 @@ var browseCommand = new Command18("browse").description("Control a headless brow
|
|
|
5058
5136
|
}
|
|
5059
5137
|
if (opts.dev) {
|
|
5060
5138
|
try {
|
|
5061
|
-
const port = await ensureDevServer(
|
|
5139
|
+
const port = await ensureDevServer({
|
|
5140
|
+
cwd: opts.devCwd,
|
|
5141
|
+
cmd: opts.devCmd,
|
|
5142
|
+
portFlag: opts.devPortFlag
|
|
5143
|
+
});
|
|
5062
5144
|
if (command === "goto" && args[0]) {
|
|
5063
5145
|
const url = args[0];
|
|
5064
5146
|
if (url.startsWith("/")) {
|
|
@@ -5176,10 +5258,10 @@ var browseCommand = new Command18("browse").description("Control a headless brow
|
|
|
5176
5258
|
|
|
5177
5259
|
// cli/commands/set-path.ts
|
|
5178
5260
|
import { Command as Command19 } from "commander";
|
|
5179
|
-
import { resolve as
|
|
5261
|
+
import { resolve as resolve5 } from "path";
|
|
5180
5262
|
import { existsSync as existsSync10 } from "fs";
|
|
5181
5263
|
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) => {
|
|
5182
|
-
const absolutePath =
|
|
5264
|
+
const absolutePath = resolve5(pathArg);
|
|
5183
5265
|
if (!existsSync10(absolutePath)) {
|
|
5184
5266
|
console.error(`Error: Path does not exist: ${absolutePath}`);
|
|
5185
5267
|
process.exit(1);
|
|
@@ -5362,7 +5444,7 @@ var testCommand = new Command20("test").description("Run automated browser test
|
|
|
5362
5444
|
// cli/commands/features.ts
|
|
5363
5445
|
import { Command as Command21 } from "commander";
|
|
5364
5446
|
import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, existsSync as existsSync12 } from "fs";
|
|
5365
|
-
import { resolve as
|
|
5447
|
+
import { resolve as resolve6, sep as sep2 } from "path";
|
|
5366
5448
|
var FEATURES_FILE3 = ".mr-features.md";
|
|
5367
5449
|
var c7 = {
|
|
5368
5450
|
reset: "\x1B[0m",
|
|
@@ -5387,7 +5469,7 @@ function resolveProjectRoot2() {
|
|
|
5387
5469
|
return cwd;
|
|
5388
5470
|
}
|
|
5389
5471
|
function getFeaturesPath() {
|
|
5390
|
-
return
|
|
5472
|
+
return resolve6(resolveProjectRoot2(), FEATURES_FILE3);
|
|
5391
5473
|
}
|
|
5392
5474
|
function readFeatures2() {
|
|
5393
5475
|
const path = getFeaturesPath();
|
|
@@ -5400,7 +5482,7 @@ var featuresCommand = new Command21("features").description("View or update the
|
|
|
5400
5482
|
return;
|
|
5401
5483
|
}
|
|
5402
5484
|
if (opts.file) {
|
|
5403
|
-
const content2 = readFileSync9(
|
|
5485
|
+
const content2 = readFileSync9(resolve6(opts.file), "utf-8");
|
|
5404
5486
|
const featuresPath = getFeaturesPath();
|
|
5405
5487
|
writeFileSync5(featuresPath, content2);
|
|
5406
5488
|
console.log(`${paint7("green", "\u2713")} Updated ${paint7("cyan", featuresPath)} from ${paint7("cyan", opts.file)}`);
|
|
@@ -5424,10 +5506,10 @@ var featuresCommand = new Command21("features").description("View or update the
|
|
|
5424
5506
|
// cli/commands/no-mr.ts
|
|
5425
5507
|
import { Command as Command22 } from "commander";
|
|
5426
5508
|
import { writeFileSync as writeFileSync6 } from "fs";
|
|
5427
|
-
import { resolve as
|
|
5509
|
+
import { resolve as resolve7 } from "path";
|
|
5428
5510
|
var NO_MR_FILE = ".mr-no-mr";
|
|
5429
5511
|
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) => {
|
|
5430
|
-
const filePath =
|
|
5512
|
+
const filePath = resolve7(process.cwd(), NO_MR_FILE);
|
|
5431
5513
|
writeFileSync6(filePath, description, "utf-8");
|
|
5432
5514
|
await api.post(`/api/tasks/${taskId}/updates`, {
|
|
5433
5515
|
message: `No MR/PR needed \u2014 ${description}`,
|
|
@@ -6114,7 +6196,7 @@ async function fetchScanContext(opts) {
|
|
|
6114
6196
|
};
|
|
6115
6197
|
}
|
|
6116
6198
|
function runClaude(prompt2) {
|
|
6117
|
-
return new Promise((
|
|
6199
|
+
return new Promise((resolve9, reject) => {
|
|
6118
6200
|
const child = spawn7("claude", ["-p", "--dangerously-skip-permissions", prompt2], {
|
|
6119
6201
|
stdio: ["ignore", "pipe", "pipe"]
|
|
6120
6202
|
});
|
|
@@ -6127,7 +6209,7 @@ function runClaude(prompt2) {
|
|
|
6127
6209
|
errOutput += d.toString();
|
|
6128
6210
|
});
|
|
6129
6211
|
child.on("exit", (code) => {
|
|
6130
|
-
if (code === 0)
|
|
6212
|
+
if (code === 0) resolve9(output.trim());
|
|
6131
6213
|
else reject(new Error(`claude exited with code ${code}
|
|
6132
6214
|
${errOutput.trim()}`));
|
|
6133
6215
|
});
|
|
@@ -6567,7 +6649,7 @@ var doctorCommand = new Command25("doctor").description("Diagnose Mr. Manager CL
|
|
|
6567
6649
|
|
|
6568
6650
|
// cli/commands/prompt-audit.ts
|
|
6569
6651
|
import { Command as Command26 } from "commander";
|
|
6570
|
-
import { resolve as
|
|
6652
|
+
import { resolve as resolve8 } from "path";
|
|
6571
6653
|
import { existsSync as existsSync16, readFileSync as readFileSync12 } from "fs";
|
|
6572
6654
|
function auditLine(label, tokens) {
|
|
6573
6655
|
const bar = "\u2588".repeat(Math.min(60, Math.round(tokens / 200)));
|
|
@@ -6622,7 +6704,7 @@ ${task.notes}` : "";
|
|
|
6622
6704
|
const config = loadConfig();
|
|
6623
6705
|
const repoDir = Object.entries(config.directories).find(([, pid]) => pid === task.projectId)?.[0];
|
|
6624
6706
|
if (repoDir) {
|
|
6625
|
-
const featuresPath =
|
|
6707
|
+
const featuresPath = resolve8(repoDir, ".mr-features.md");
|
|
6626
6708
|
if (existsSync16(featuresPath)) {
|
|
6627
6709
|
const featuresContent = readFileSync12(featuresPath, "utf-8");
|
|
6628
6710
|
sections.push({ name: "features-doc", tokens: estimateTokens(featuresContent) });
|