@dunnewold-labs/mr-manager 0.4.22 → 0.4.23
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 +188 -76
- 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.23",
|
|
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
|
});
|
|
@@ -1744,32 +1744,43 @@ async function extractPrUrlFromUpdates(taskId) {
|
|
|
1744
1744
|
return null;
|
|
1745
1745
|
}
|
|
1746
1746
|
function checkPrStatus(prUrl, repoDir, vcs = "github") {
|
|
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((
|
|
1747
|
+
const cmd = vcs === "gitlab" ? `glab mr view "${prUrl}" --output json 2>/dev/null` : `gh pr view "${prUrl}" --json merged,mergeable,isDraft,statusCheckRollup,reviewDecision 2>/dev/null`;
|
|
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
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
});
|
|
1757
|
+
const merged = data.state === "merged";
|
|
1758
|
+
const hasConflicts = data.has_conflicts === true;
|
|
1759
|
+
const isMergeable = !merged && data.state === "opened" && !hasConflicts && !data.draft && (data.merge_status === "can_be_merged" || data.merge_status === "can_be_merged_rebase");
|
|
1760
|
+
resolve9({ merged, hasConflicts, isMergeable });
|
|
1761
1761
|
} else {
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1762
|
+
const merged = data.merged === true;
|
|
1763
|
+
const hasConflicts = data.mergeable === "CONFLICTING";
|
|
1764
|
+
const checks = data.statusCheckRollup ?? [];
|
|
1765
|
+
const allChecksPassed = checks.length === 0 || checks.every((c12) => c12.state === "SUCCESS" || c12.state === "NEUTRAL" || c12.state === "SKIPPED");
|
|
1766
|
+
const noChangesRequested = data.reviewDecision !== "CHANGES_REQUESTED";
|
|
1767
|
+
const isMergeable = !merged && !hasConflicts && !data.isDraft && data.mergeable === "MERGEABLE" && allChecksPassed && noChangesRequested;
|
|
1768
|
+
resolve9({ merged, hasConflicts, isMergeable });
|
|
1766
1769
|
}
|
|
1767
1770
|
} catch {
|
|
1768
|
-
|
|
1771
|
+
resolve9(null);
|
|
1769
1772
|
}
|
|
1770
1773
|
});
|
|
1771
1774
|
});
|
|
1772
1775
|
}
|
|
1776
|
+
function mergePrViaCli(prUrl, repoDir, vcs = "github") {
|
|
1777
|
+
const cmd = vcs === "gitlab" ? `glab mr merge "${prUrl}" --squash --remove-source-branch 2>&1` : `gh pr merge "${prUrl}" --squash --delete-branch 2>&1`;
|
|
1778
|
+
return new Promise((resolve9) => {
|
|
1779
|
+
exec(cmd, { cwd: repoDir }, (err) => {
|
|
1780
|
+
resolve9(!err);
|
|
1781
|
+
});
|
|
1782
|
+
});
|
|
1783
|
+
}
|
|
1773
1784
|
function buildPrototypeSection(protoRefs, workingDir) {
|
|
1774
1785
|
if (protoRefs.length === 0) return "";
|
|
1775
1786
|
const sections = [
|
|
@@ -2464,7 +2475,7 @@ function buildIdeaPrompt(idea, repoDir) {
|
|
|
2464
2475
|
`- Do NOT exit until both files have been written and verified`
|
|
2465
2476
|
].join("\n");
|
|
2466
2477
|
}
|
|
2467
|
-
function buildAgentArgs(agent, prompt2, mode, sessionId, name, resumeSession = false, systemPrompt, maxTurns) {
|
|
2478
|
+
function buildAgentArgs(agent, prompt2, mode, sessionId, name, resumeSession = false, systemPrompt, maxTurns, claudeModel) {
|
|
2468
2479
|
if (agent === "codex") {
|
|
2469
2480
|
const args = [];
|
|
2470
2481
|
if (mode === "execute") {
|
|
@@ -2494,19 +2505,37 @@ ${systemPrompt}` : prompt2;
|
|
|
2494
2505
|
const nameArgs = name ? ["--name", name] : [];
|
|
2495
2506
|
const systemArgs = systemPrompt ? ["--append-system-prompt", systemPrompt] : [];
|
|
2496
2507
|
const turnsArgs = maxTurns ? ["--max-turns", String(maxTurns)] : [];
|
|
2508
|
+
const modelArgs = claudeModel ? ["--model", claudeModel] : [];
|
|
2497
2509
|
if (mode === "plan") {
|
|
2498
|
-
return { bin: "claude", args: [...sessionArgs, ...nameArgs, ...systemArgs, ...turnsArgs, "--permission-mode", "plan", "-p", prompt2] };
|
|
2510
|
+
return { bin: "claude", args: [...sessionArgs, ...nameArgs, ...systemArgs, ...turnsArgs, ...modelArgs, "--permission-mode", "plan", "-p", prompt2] };
|
|
2499
2511
|
}
|
|
2500
2512
|
const cfg = loadConfig();
|
|
2501
|
-
const permissionMode = cfg.claudePermissionMode ?? "
|
|
2513
|
+
const permissionMode = cfg.claudePermissionMode ?? "auto";
|
|
2502
2514
|
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] };
|
|
2515
|
+
return { bin: "claude", args: [...sessionArgs, ...nameArgs, ...systemArgs, ...turnsArgs, ...modelArgs, ...permissionArgs, "-p", prompt2] };
|
|
2504
2516
|
}
|
|
2505
2517
|
function commandExists(cmd) {
|
|
2506
|
-
return new Promise((
|
|
2507
|
-
exec(`command -v ${cmd}`, (err) =>
|
|
2518
|
+
return new Promise((resolve9) => {
|
|
2519
|
+
exec(`command -v ${cmd}`, (err) => resolve9(!err));
|
|
2508
2520
|
});
|
|
2509
2521
|
}
|
|
2522
|
+
function resolveTaskAgentAndModel(delegatedModel, watchAgent) {
|
|
2523
|
+
if (!delegatedModel) return { agent: watchAgent };
|
|
2524
|
+
switch (delegatedModel) {
|
|
2525
|
+
case "claude-opus":
|
|
2526
|
+
return { agent: "claude", claudeModel: "claude-opus-4-6" };
|
|
2527
|
+
case "claude-sonnet":
|
|
2528
|
+
return { agent: "claude", claudeModel: "claude-sonnet-4-6" };
|
|
2529
|
+
case "claude-haiku":
|
|
2530
|
+
return { agent: "claude", claudeModel: "claude-haiku-4-5-20251001" };
|
|
2531
|
+
case "codex":
|
|
2532
|
+
return { agent: "codex" };
|
|
2533
|
+
case "gemini":
|
|
2534
|
+
return { agent: "gemini" };
|
|
2535
|
+
default:
|
|
2536
|
+
return { agent: watchAgent };
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2510
2539
|
function runPlanningPhase(task, repoDir, agent) {
|
|
2511
2540
|
return new Promise((res, reject) => {
|
|
2512
2541
|
const planPrompt = buildPlanningPrompt(task, repoDir);
|
|
@@ -2544,21 +2573,21 @@ ${output.trim()}`));
|
|
|
2544
2573
|
});
|
|
2545
2574
|
}
|
|
2546
2575
|
function askYesNo(question) {
|
|
2547
|
-
return new Promise((
|
|
2576
|
+
return new Promise((resolve9) => {
|
|
2548
2577
|
const rl = readline.createInterface({
|
|
2549
2578
|
input: process.stdin,
|
|
2550
2579
|
output: process.stdout
|
|
2551
2580
|
});
|
|
2552
2581
|
rl.question(question, (answer) => {
|
|
2553
2582
|
rl.close();
|
|
2554
|
-
|
|
2583
|
+
resolve9(answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes");
|
|
2555
2584
|
});
|
|
2556
2585
|
});
|
|
2557
2586
|
}
|
|
2558
|
-
function spawnAgent(agent, repoDir, prompt2, prefix, onActivity, sessionId, name, resumeSession = false, onSpawnError, systemPrompt, maxTurns) {
|
|
2587
|
+
function spawnAgent(agent, repoDir, prompt2, prefix, onActivity, sessionId, name, resumeSession = false, onSpawnError, systemPrompt, maxTurns, claudeModel) {
|
|
2559
2588
|
const jobLabel = name ?? "unknown";
|
|
2560
2589
|
console.log(`${timestamp()} ${prefix} ${paint("dim", tokenLogLine("agent", jobLabel, prompt2, systemPrompt))}`);
|
|
2561
|
-
const { bin, args } = buildAgentArgs(agent, prompt2, "execute", sessionId, name, resumeSession, systemPrompt, maxTurns);
|
|
2590
|
+
const { bin, args } = buildAgentArgs(agent, prompt2, "execute", sessionId, name, resumeSession, systemPrompt, maxTurns, claudeModel);
|
|
2562
2591
|
const child = spawn4(bin, args, { cwd: repoDir, stdio: ["ignore", "pipe", "pipe"] });
|
|
2563
2592
|
child.on("error", (err) => {
|
|
2564
2593
|
logError(prefix, `Failed to spawn ${agent}: ${err.message}`);
|
|
@@ -2818,10 +2847,16 @@ var watchCommand = new Command8("watch").description(
|
|
|
2818
2847
|
const touchActivity = () => {
|
|
2819
2848
|
activeEntry.lastActivityAt = Date.now();
|
|
2820
2849
|
};
|
|
2821
|
-
const
|
|
2850
|
+
const { agent: taskAgent, claudeModel: taskClaudeModel } = resolveTaskAgentAndModel(task.delegatedModel, agent);
|
|
2851
|
+
if (taskClaudeModel) {
|
|
2852
|
+
logInfo(prefix, `Using model: ${paint("cyan", taskClaudeModel)}`);
|
|
2853
|
+
} else if (taskAgent !== agent) {
|
|
2854
|
+
logInfo(prefix, `Using agent override: ${paint("cyan", taskAgent)}`);
|
|
2855
|
+
}
|
|
2856
|
+
const attemptOrder = await resolveAgentChain(taskAgent);
|
|
2822
2857
|
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 ${
|
|
2858
|
+
logError(prefix, `No available agents found for fallback chain starting at ${taskAgent}`);
|
|
2859
|
+
await moveTaskToError(task, prefix, `No available agent found for fallback chain starting at ${taskAgent}`);
|
|
2825
2860
|
if (activeEntry.cleanupRepoDir && activeEntry.cleanupWorktreePath) {
|
|
2826
2861
|
removeWorktree(activeEntry.cleanupRepoDir, activeEntry.cleanupWorktreePath);
|
|
2827
2862
|
}
|
|
@@ -2835,6 +2870,7 @@ var watchCommand = new Command8("watch").description(
|
|
|
2835
2870
|
const pausedForNetwork = networkPaused.get(task.id);
|
|
2836
2871
|
const shouldResumeClaudeSession = attemptAgent === "claude" && !!task.claudeSessionId && !resumeAlreadyRetried && (hasFeedback || pausedForNetwork?.resumeSession === true);
|
|
2837
2872
|
const sessionId = attemptAgent === "claude" ? shouldResumeClaudeSession ? task.claudeSessionId : randomUUID() : void 0;
|
|
2873
|
+
const effectiveClaudeModel = attemptAgent === "claude" ? taskClaudeModel : void 0;
|
|
2838
2874
|
const executionSystemPrompt = composeSystemPrompt(EXECUTION_SYSTEM_SECTIONS);
|
|
2839
2875
|
const child = spawnAgent(
|
|
2840
2876
|
attemptAgent,
|
|
@@ -2848,7 +2884,9 @@ var watchCommand = new Command8("watch").description(
|
|
|
2848
2884
|
(err) => {
|
|
2849
2885
|
spawnFailureReason = err.message;
|
|
2850
2886
|
},
|
|
2851
|
-
executionSystemPrompt
|
|
2887
|
+
executionSystemPrompt,
|
|
2888
|
+
void 0,
|
|
2889
|
+
effectiveClaudeModel
|
|
2852
2890
|
);
|
|
2853
2891
|
activeEntry.process = child;
|
|
2854
2892
|
activeEntry.currentAgent = attemptAgent;
|
|
@@ -4097,8 +4135,7 @@ ${divider}`);
|
|
|
4097
4135
|
const prefix = taskTag(sid);
|
|
4098
4136
|
const prLabel = task.link.includes("gitlab") ? "MR" : "PR";
|
|
4099
4137
|
const vcs = task.link.includes("gitlab") ? "gitlab" : "github";
|
|
4100
|
-
const repoDir = findDirectoryForProject(config, task.projectId, rootDir);
|
|
4101
|
-
if (!repoDir) continue;
|
|
4138
|
+
const repoDir = findDirectoryForProject(config, task.projectId, rootDir) ?? rootDir;
|
|
4102
4139
|
const status = await checkPrStatus(task.link, repoDir, vcs);
|
|
4103
4140
|
if (!status) continue;
|
|
4104
4141
|
if (status.merged) {
|
|
@@ -4115,6 +4152,26 @@ ${divider}`);
|
|
|
4115
4152
|
}
|
|
4116
4153
|
continue;
|
|
4117
4154
|
}
|
|
4155
|
+
if (task.autoMerge && status.isMergeable) {
|
|
4156
|
+
logInfo(prefix, `Auto-merging ${prLabel} for "${paint("bold", task.title)}"`);
|
|
4157
|
+
const mergeOk = await mergePrViaCli(task.link, repoDir, vcs);
|
|
4158
|
+
if (mergeOk) {
|
|
4159
|
+
logSuccess(prefix, `${prLabel} auto-merged \u2014 completing "${paint("bold", task.title)}"`);
|
|
4160
|
+
try {
|
|
4161
|
+
await api.patch(`/api/tasks/${task.id}`, {
|
|
4162
|
+
status: "completed",
|
|
4163
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4164
|
+
inProgressSince: null
|
|
4165
|
+
});
|
|
4166
|
+
await postTaskUpdate(task.id, `${prLabel} auto-merged and task completed`, "system");
|
|
4167
|
+
} catch (err) {
|
|
4168
|
+
logError(prefix, `Failed to complete task after auto-merge: ${err.message}`);
|
|
4169
|
+
}
|
|
4170
|
+
} else {
|
|
4171
|
+
logWarn(prefix, `Auto-merge of ${prLabel} failed for "${paint("bold", task.title)}" \u2014 will retry next poll`);
|
|
4172
|
+
}
|
|
4173
|
+
continue;
|
|
4174
|
+
}
|
|
4118
4175
|
if (status.hasConflicts) {
|
|
4119
4176
|
logWarn(prefix, `${prLabel} has merge conflicts \u2014 re-dispatching agent for "${paint("bold", task.title)}"`);
|
|
4120
4177
|
try {
|
|
@@ -4445,14 +4502,14 @@ function paint5(color, text) {
|
|
|
4445
4502
|
return `${c5[color]}${text}${c5.reset}`;
|
|
4446
4503
|
}
|
|
4447
4504
|
function commandExists2(cmd) {
|
|
4448
|
-
return new Promise((
|
|
4449
|
-
exec2(`which ${cmd}`, (err) =>
|
|
4505
|
+
return new Promise((resolve9) => {
|
|
4506
|
+
exec2(`which ${cmd}`, (err) => resolve9(!err));
|
|
4450
4507
|
});
|
|
4451
4508
|
}
|
|
4452
4509
|
function execQuiet(cmd) {
|
|
4453
|
-
return new Promise((
|
|
4510
|
+
return new Promise((resolve9) => {
|
|
4454
4511
|
exec2(cmd, (err, stdout, stderr) => {
|
|
4455
|
-
|
|
4512
|
+
resolve9({ ok: !err, stdout: stdout.trim(), stderr: stderr.trim() });
|
|
4456
4513
|
});
|
|
4457
4514
|
});
|
|
4458
4515
|
}
|
|
@@ -4707,26 +4764,26 @@ async function autoFix(checks, agent) {
|
|
|
4707
4764
|
if (claudeCheck && !claudeCheck.ok && agent === "claude") {
|
|
4708
4765
|
console.log(paint5("cyan", " Installing Claude Code..."));
|
|
4709
4766
|
console.log(paint5("dim", " Running: curl -fsSL https://claude.ai/install.sh | bash"));
|
|
4710
|
-
await new Promise((
|
|
4767
|
+
await new Promise((resolve9) => {
|
|
4711
4768
|
const child = spawn8("bash", ["-c", "curl -fsSL https://claude.ai/install.sh | bash"], { stdio: "inherit" });
|
|
4712
|
-
child.on("exit", () =>
|
|
4769
|
+
child.on("exit", () => resolve9());
|
|
4713
4770
|
});
|
|
4714
4771
|
console.log("");
|
|
4715
4772
|
}
|
|
4716
4773
|
if (ghInstalled && !ghAuthed) {
|
|
4717
4774
|
console.log(paint5("cyan", " Running gh auth login..."));
|
|
4718
|
-
await new Promise((
|
|
4775
|
+
await new Promise((resolve9) => {
|
|
4719
4776
|
const child = spawn8("gh", ["auth", "login"], { stdio: "inherit" });
|
|
4720
|
-
child.on("exit", () =>
|
|
4777
|
+
child.on("exit", () => resolve9());
|
|
4721
4778
|
});
|
|
4722
4779
|
console.log("");
|
|
4723
4780
|
}
|
|
4724
4781
|
if (!mrAuthed) {
|
|
4725
4782
|
console.log(paint5("cyan", " Running mr login..."));
|
|
4726
4783
|
const entry = process.argv[1];
|
|
4727
|
-
await new Promise((
|
|
4784
|
+
await new Promise((resolve9) => {
|
|
4728
4785
|
const child = spawn8(process.execPath, [entry, "login"], { stdio: "inherit" });
|
|
4729
|
-
child.on("exit", () =>
|
|
4786
|
+
child.on("exit", () => resolve9());
|
|
4730
4787
|
});
|
|
4731
4788
|
console.log("");
|
|
4732
4789
|
}
|
|
@@ -4975,7 +5032,8 @@ var resumeCommand = new Command17("resume").description("Resume an interactive C
|
|
|
4975
5032
|
import { Command as Command18 } from "commander";
|
|
4976
5033
|
import { execSync as execSync4, spawn as spawn6 } from "child_process";
|
|
4977
5034
|
import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
4978
|
-
import {
|
|
5035
|
+
import { createHash } from "crypto";
|
|
5036
|
+
import { join as join8, resolve as resolve4 } from "path";
|
|
4979
5037
|
var BROWSE_DIR2 = join8(import.meta.dirname, "..", "..", "browse");
|
|
4980
5038
|
function isProcessAlive(pid) {
|
|
4981
5039
|
try {
|
|
@@ -5003,43 +5061,93 @@ async function findAvailablePort2(startPort) {
|
|
|
5003
5061
|
}
|
|
5004
5062
|
throw new Error(`No available port found in range ${startPort}-${startPort + 99}`);
|
|
5005
5063
|
}
|
|
5006
|
-
|
|
5007
|
-
|
|
5064
|
+
function detectDevCommand2(cwd) {
|
|
5065
|
+
try {
|
|
5066
|
+
const pkg = JSON.parse(readFileSync7(join8(cwd, "package.json"), "utf-8"));
|
|
5067
|
+
const scripts = pkg.scripts || {};
|
|
5068
|
+
if (scripts.dev) return "npm run dev";
|
|
5069
|
+
if (scripts.start) return "npm start";
|
|
5070
|
+
if (scripts.serve) return "npm run serve";
|
|
5071
|
+
} catch {
|
|
5072
|
+
}
|
|
5073
|
+
return "npm run dev";
|
|
5074
|
+
}
|
|
5075
|
+
function parseCommand(cmd) {
|
|
5076
|
+
const args = [];
|
|
5077
|
+
let current = "";
|
|
5078
|
+
let inSingle = false;
|
|
5079
|
+
let inDouble = false;
|
|
5080
|
+
for (let i = 0; i < cmd.length; i++) {
|
|
5081
|
+
const ch = cmd[i];
|
|
5082
|
+
if (ch === "'" && !inDouble) {
|
|
5083
|
+
inSingle = !inSingle;
|
|
5084
|
+
} else if (ch === '"' && !inSingle) {
|
|
5085
|
+
inDouble = !inDouble;
|
|
5086
|
+
} else if (ch === " " && !inSingle && !inDouble) {
|
|
5087
|
+
if (current) {
|
|
5088
|
+
args.push(current);
|
|
5089
|
+
current = "";
|
|
5090
|
+
}
|
|
5091
|
+
} else {
|
|
5092
|
+
current += ch;
|
|
5093
|
+
}
|
|
5094
|
+
}
|
|
5095
|
+
if (current) args.push(current);
|
|
5096
|
+
return args;
|
|
5097
|
+
}
|
|
5098
|
+
async function ensureDevServer(options = {}) {
|
|
5099
|
+
const projectCwd = options.cwd ? resolve4(options.cwd) : join8(import.meta.dirname, "..", "..");
|
|
5100
|
+
const devCmd = options.cmd || detectDevCommand2(projectCwd);
|
|
5101
|
+
const cwdHash = createHash("md5").update(projectCwd).digest("hex").slice(0, 8);
|
|
5102
|
+
const devStateFile = `/tmp/mr-dev-server-${cwdHash}.json`;
|
|
5008
5103
|
try {
|
|
5009
5104
|
const state = JSON.parse(readFileSync7(devStateFile, "utf-8"));
|
|
5010
5105
|
if (isProcessAlive(state.pid) && await isPortResponding2(state.port)) {
|
|
5106
|
+
console.log(`[browse] Reusing dev server on port ${state.port} (${projectCwd})`);
|
|
5011
5107
|
return state.port;
|
|
5012
5108
|
}
|
|
5013
5109
|
} catch {
|
|
5014
5110
|
}
|
|
5015
5111
|
const port = await findAvailablePort2(3e3);
|
|
5016
|
-
|
|
5017
|
-
const
|
|
5112
|
+
const argv = parseCommand(devCmd);
|
|
5113
|
+
const portFlag = options.portFlag;
|
|
5114
|
+
let spawnArgs;
|
|
5115
|
+
let spawnEnv = { ...process.env };
|
|
5116
|
+
if (portFlag) {
|
|
5117
|
+
spawnArgs = [...argv.slice(1), portFlag, String(port)];
|
|
5118
|
+
} else if (devCmd.includes("next") || devCmd.includes("vite") || devCmd.includes("webpack")) {
|
|
5119
|
+
spawnArgs = [...argv.slice(1), "--", "--port", String(port)];
|
|
5120
|
+
} else {
|
|
5121
|
+
spawnEnv.PORT = String(port);
|
|
5122
|
+
spawnArgs = argv.slice(1);
|
|
5123
|
+
}
|
|
5124
|
+
console.log(`[browse] Starting dev server: ${devCmd} (port ${port}) in ${projectCwd}`);
|
|
5125
|
+
const devProc = spawn6(argv[0], spawnArgs, {
|
|
5018
5126
|
stdio: ["ignore", "pipe", "pipe"],
|
|
5019
5127
|
detached: true,
|
|
5020
|
-
cwd:
|
|
5021
|
-
env:
|
|
5128
|
+
cwd: projectCwd,
|
|
5129
|
+
env: spawnEnv
|
|
5022
5130
|
});
|
|
5023
5131
|
devProc.unref();
|
|
5024
5132
|
writeFileSync4(
|
|
5025
5133
|
devStateFile,
|
|
5026
|
-
JSON.stringify({ pid: devProc.pid, port, startedAt: (/* @__PURE__ */ new Date()).toISOString() }),
|
|
5134
|
+
JSON.stringify({ pid: devProc.pid, port, cwd: projectCwd, cmd: devCmd, startedAt: (/* @__PURE__ */ new Date()).toISOString() }),
|
|
5027
5135
|
{ mode: 384 }
|
|
5028
5136
|
);
|
|
5029
5137
|
const start = Date.now();
|
|
5030
|
-
while (Date.now() - start <
|
|
5138
|
+
while (Date.now() - start < 6e4) {
|
|
5031
5139
|
if (await isPortResponding2(port)) {
|
|
5032
5140
|
console.log(`[browse] Dev server ready on port ${port}`);
|
|
5033
5141
|
return port;
|
|
5034
5142
|
}
|
|
5035
5143
|
await new Promise((r) => setTimeout(r, 500));
|
|
5036
5144
|
}
|
|
5037
|
-
throw new Error(
|
|
5145
|
+
throw new Error(`Dev server failed to start within 60s. Command: ${devCmd} in ${projectCwd}`);
|
|
5038
5146
|
}
|
|
5039
5147
|
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
5148
|
"--task-id <id>",
|
|
5041
5149
|
"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(
|
|
5150
|
+
).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
5151
|
async (command, args, opts) => {
|
|
5044
5152
|
if (!command) {
|
|
5045
5153
|
const { stdout: stdout2 } = await runBrowseCommand2(["--help"]);
|
|
@@ -5058,7 +5166,11 @@ var browseCommand = new Command18("browse").description("Control a headless brow
|
|
|
5058
5166
|
}
|
|
5059
5167
|
if (opts.dev) {
|
|
5060
5168
|
try {
|
|
5061
|
-
const port = await ensureDevServer(
|
|
5169
|
+
const port = await ensureDevServer({
|
|
5170
|
+
cwd: opts.devCwd,
|
|
5171
|
+
cmd: opts.devCmd,
|
|
5172
|
+
portFlag: opts.devPortFlag
|
|
5173
|
+
});
|
|
5062
5174
|
if (command === "goto" && args[0]) {
|
|
5063
5175
|
const url = args[0];
|
|
5064
5176
|
if (url.startsWith("/")) {
|
|
@@ -5176,10 +5288,10 @@ var browseCommand = new Command18("browse").description("Control a headless brow
|
|
|
5176
5288
|
|
|
5177
5289
|
// cli/commands/set-path.ts
|
|
5178
5290
|
import { Command as Command19 } from "commander";
|
|
5179
|
-
import { resolve as
|
|
5291
|
+
import { resolve as resolve5 } from "path";
|
|
5180
5292
|
import { existsSync as existsSync10 } from "fs";
|
|
5181
5293
|
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 =
|
|
5294
|
+
const absolutePath = resolve5(pathArg);
|
|
5183
5295
|
if (!existsSync10(absolutePath)) {
|
|
5184
5296
|
console.error(`Error: Path does not exist: ${absolutePath}`);
|
|
5185
5297
|
process.exit(1);
|
|
@@ -5362,7 +5474,7 @@ var testCommand = new Command20("test").description("Run automated browser test
|
|
|
5362
5474
|
// cli/commands/features.ts
|
|
5363
5475
|
import { Command as Command21 } from "commander";
|
|
5364
5476
|
import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, existsSync as existsSync12 } from "fs";
|
|
5365
|
-
import { resolve as
|
|
5477
|
+
import { resolve as resolve6, sep as sep2 } from "path";
|
|
5366
5478
|
var FEATURES_FILE3 = ".mr-features.md";
|
|
5367
5479
|
var c7 = {
|
|
5368
5480
|
reset: "\x1B[0m",
|
|
@@ -5387,7 +5499,7 @@ function resolveProjectRoot2() {
|
|
|
5387
5499
|
return cwd;
|
|
5388
5500
|
}
|
|
5389
5501
|
function getFeaturesPath() {
|
|
5390
|
-
return
|
|
5502
|
+
return resolve6(resolveProjectRoot2(), FEATURES_FILE3);
|
|
5391
5503
|
}
|
|
5392
5504
|
function readFeatures2() {
|
|
5393
5505
|
const path = getFeaturesPath();
|
|
@@ -5400,7 +5512,7 @@ var featuresCommand = new Command21("features").description("View or update the
|
|
|
5400
5512
|
return;
|
|
5401
5513
|
}
|
|
5402
5514
|
if (opts.file) {
|
|
5403
|
-
const content2 = readFileSync9(
|
|
5515
|
+
const content2 = readFileSync9(resolve6(opts.file), "utf-8");
|
|
5404
5516
|
const featuresPath = getFeaturesPath();
|
|
5405
5517
|
writeFileSync5(featuresPath, content2);
|
|
5406
5518
|
console.log(`${paint7("green", "\u2713")} Updated ${paint7("cyan", featuresPath)} from ${paint7("cyan", opts.file)}`);
|
|
@@ -5424,10 +5536,10 @@ var featuresCommand = new Command21("features").description("View or update the
|
|
|
5424
5536
|
// cli/commands/no-mr.ts
|
|
5425
5537
|
import { Command as Command22 } from "commander";
|
|
5426
5538
|
import { writeFileSync as writeFileSync6 } from "fs";
|
|
5427
|
-
import { resolve as
|
|
5539
|
+
import { resolve as resolve7 } from "path";
|
|
5428
5540
|
var NO_MR_FILE = ".mr-no-mr";
|
|
5429
5541
|
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 =
|
|
5542
|
+
const filePath = resolve7(process.cwd(), NO_MR_FILE);
|
|
5431
5543
|
writeFileSync6(filePath, description, "utf-8");
|
|
5432
5544
|
await api.post(`/api/tasks/${taskId}/updates`, {
|
|
5433
5545
|
message: `No MR/PR needed \u2014 ${description}`,
|
|
@@ -6114,7 +6226,7 @@ async function fetchScanContext(opts) {
|
|
|
6114
6226
|
};
|
|
6115
6227
|
}
|
|
6116
6228
|
function runClaude(prompt2) {
|
|
6117
|
-
return new Promise((
|
|
6229
|
+
return new Promise((resolve9, reject) => {
|
|
6118
6230
|
const child = spawn7("claude", ["-p", "--dangerously-skip-permissions", prompt2], {
|
|
6119
6231
|
stdio: ["ignore", "pipe", "pipe"]
|
|
6120
6232
|
});
|
|
@@ -6127,7 +6239,7 @@ function runClaude(prompt2) {
|
|
|
6127
6239
|
errOutput += d.toString();
|
|
6128
6240
|
});
|
|
6129
6241
|
child.on("exit", (code) => {
|
|
6130
|
-
if (code === 0)
|
|
6242
|
+
if (code === 0) resolve9(output.trim());
|
|
6131
6243
|
else reject(new Error(`claude exited with code ${code}
|
|
6132
6244
|
${errOutput.trim()}`));
|
|
6133
6245
|
});
|
|
@@ -6567,7 +6679,7 @@ var doctorCommand = new Command25("doctor").description("Diagnose Mr. Manager CL
|
|
|
6567
6679
|
|
|
6568
6680
|
// cli/commands/prompt-audit.ts
|
|
6569
6681
|
import { Command as Command26 } from "commander";
|
|
6570
|
-
import { resolve as
|
|
6682
|
+
import { resolve as resolve8 } from "path";
|
|
6571
6683
|
import { existsSync as existsSync16, readFileSync as readFileSync12 } from "fs";
|
|
6572
6684
|
function auditLine(label, tokens) {
|
|
6573
6685
|
const bar = "\u2588".repeat(Math.min(60, Math.round(tokens / 200)));
|
|
@@ -6622,7 +6734,7 @@ ${task.notes}` : "";
|
|
|
6622
6734
|
const config = loadConfig();
|
|
6623
6735
|
const repoDir = Object.entries(config.directories).find(([, pid]) => pid === task.projectId)?.[0];
|
|
6624
6736
|
if (repoDir) {
|
|
6625
|
-
const featuresPath =
|
|
6737
|
+
const featuresPath = resolve8(repoDir, ".mr-features.md");
|
|
6626
6738
|
if (existsSync16(featuresPath)) {
|
|
6627
6739
|
const featuresContent = readFileSync12(featuresPath, "utf-8");
|
|
6628
6740
|
sections.push({ name: "features-doc", tokens: estimateTokens(featuresContent) });
|