@dunnewold-labs/mr-manager 0.4.4 → 0.4.8
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 +172 -172
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// cli/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command26 } from "commander";
|
|
5
5
|
import { existsSync as existsSync16 } from "fs";
|
|
6
6
|
import { homedir as homedir3 } from "os";
|
|
7
7
|
import { join as join12 } from "path";
|
|
@@ -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.8",
|
|
189
189
|
description: "Mr. Manager - Task and project management CLI",
|
|
190
190
|
bin: {
|
|
191
191
|
mr: "./dist/index.mjs"
|
|
@@ -1026,6 +1026,12 @@ function slugify(title) {
|
|
|
1026
1026
|
function shortId(id) {
|
|
1027
1027
|
return id.slice(0, 8);
|
|
1028
1028
|
}
|
|
1029
|
+
function ownerPrefix(task) {
|
|
1030
|
+
const name = task.user?.name;
|
|
1031
|
+
if (!name) return "mr";
|
|
1032
|
+
const first = name.split(/\s+/)[0].toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1033
|
+
return first || "mr";
|
|
1034
|
+
}
|
|
1029
1035
|
function formatElapsed(ms) {
|
|
1030
1036
|
const totalMinutes = Math.max(1, Math.floor(ms / 6e4));
|
|
1031
1037
|
const hours = Math.floor(totalMinutes / 60);
|
|
@@ -1035,8 +1041,8 @@ function formatElapsed(ms) {
|
|
|
1035
1041
|
}
|
|
1036
1042
|
return `${totalMinutes}m`;
|
|
1037
1043
|
}
|
|
1038
|
-
function worktreePath(
|
|
1039
|
-
return `.mr-worktrees
|
|
1044
|
+
function worktreePath(name) {
|
|
1045
|
+
return `.mr-worktrees/${name}`;
|
|
1040
1046
|
}
|
|
1041
1047
|
function worktreeNameFromPath(wtPath) {
|
|
1042
1048
|
return wtPath.replace(/^\.mr-worktrees\//, "");
|
|
@@ -1227,6 +1233,33 @@ async function extractPrUrlFromUpdates(taskId) {
|
|
|
1227
1233
|
}
|
|
1228
1234
|
return null;
|
|
1229
1235
|
}
|
|
1236
|
+
function checkPrStatus(prUrl, repoDir, vcs = "github") {
|
|
1237
|
+
const cmd = vcs === "gitlab" ? `glab mr view "${prUrl}" --output json 2>/dev/null` : `gh pr view "${prUrl}" --json merged,mergeable 2>/dev/null`;
|
|
1238
|
+
return new Promise((resolve7) => {
|
|
1239
|
+
exec(cmd, { cwd: repoDir }, (err, stdout) => {
|
|
1240
|
+
if (err || !stdout.trim()) {
|
|
1241
|
+
resolve7(null);
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
try {
|
|
1245
|
+
const data = JSON.parse(stdout.trim());
|
|
1246
|
+
if (vcs === "gitlab") {
|
|
1247
|
+
resolve7({
|
|
1248
|
+
merged: data.state === "merged",
|
|
1249
|
+
hasConflicts: data.has_conflicts === true
|
|
1250
|
+
});
|
|
1251
|
+
} else {
|
|
1252
|
+
resolve7({
|
|
1253
|
+
merged: data.merged === true,
|
|
1254
|
+
hasConflicts: data.mergeable === "CONFLICTING"
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
} catch {
|
|
1258
|
+
resolve7(null);
|
|
1259
|
+
}
|
|
1260
|
+
});
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1230
1263
|
function buildPrototypeSection(protoRefs) {
|
|
1231
1264
|
if (protoRefs.length === 0) return "";
|
|
1232
1265
|
const sections = [
|
|
@@ -1357,8 +1390,9 @@ function buildFeaturesSection(repoDir) {
|
|
|
1357
1390
|
function buildExecutionPrompt(task, repoDir, subtasks, vcs = "github", protoRefs = [], feedbackUpdates = [], existingResources = [], skillRefs = [], executionDir) {
|
|
1358
1391
|
const sid = shortId(task.id);
|
|
1359
1392
|
const slug = slugify(task.title);
|
|
1360
|
-
const
|
|
1361
|
-
const
|
|
1393
|
+
const owner = ownerPrefix(task);
|
|
1394
|
+
const branchName = `${owner}/${slug}`;
|
|
1395
|
+
const wtPath = worktreePath(`${owner}-${slug}`);
|
|
1362
1396
|
const workingDir = executionDir ?? repoDir;
|
|
1363
1397
|
const prBodyPath = "/tmp/mr-pr-body.md";
|
|
1364
1398
|
const notes = task.prdContent ? `
|
|
@@ -1381,7 +1415,7 @@ ${task.notes}` : "";
|
|
|
1381
1415
|
``
|
|
1382
1416
|
].join("\n") : "";
|
|
1383
1417
|
const hasFeedback = feedbackUpdates.length > 0;
|
|
1384
|
-
const feedbackWtPath = hasFeedback ? worktreePath(`${
|
|
1418
|
+
const feedbackWtPath = hasFeedback ? worktreePath(`${owner}-${slug}-fb`) : wtPath;
|
|
1385
1419
|
const prBodyTemplate = buildPrBodyTemplate(task, pendingSubtasks, protoRefs, feedbackUpdates, existingResources, skillRefs);
|
|
1386
1420
|
const prCreateCmd = vcs === "gitlab" ? `glab mr create --title "${task.title}" --description-file ${prBodyPath} --yes` : `gh pr create --title "${task.title}" --body-file ${prBodyPath}`;
|
|
1387
1421
|
return [
|
|
@@ -2063,7 +2097,9 @@ var watchCommand = new Command8("watch").description(
|
|
|
2063
2097
|
async function dispatchTask(task, repoDir) {
|
|
2064
2098
|
const sid = shortId(task.id);
|
|
2065
2099
|
const slug = slugify(task.title);
|
|
2066
|
-
const
|
|
2100
|
+
const owner = ownerPrefix(task);
|
|
2101
|
+
const branchName = `${owner}/${slug}`;
|
|
2102
|
+
const legacyBranchName = `mr/${sid}/${slug}`;
|
|
2067
2103
|
const prefix = taskTag(sid);
|
|
2068
2104
|
const vcs = detectVcs(repoDir)?.provider ?? "github";
|
|
2069
2105
|
logDispatch(prefix, `"${paint("bold", task.title)}" ${paint("gray", repoDir)} ${paint("dim", `[${vcs}]`)}`);
|
|
@@ -2112,7 +2148,8 @@ var watchCommand = new Command8("watch").description(
|
|
|
2112
2148
|
} catch {
|
|
2113
2149
|
}
|
|
2114
2150
|
const hasFeedback = feedbackUpdates.length > 0;
|
|
2115
|
-
const
|
|
2151
|
+
const wtName = `${owner}-${slug}`;
|
|
2152
|
+
const desiredWorktreePath = hasFeedback ? worktreePath(`${wtName}-fb`) : worktreePath(wtName);
|
|
2116
2153
|
let executionDir = repoDir;
|
|
2117
2154
|
let cleanupWorktreePath;
|
|
2118
2155
|
if (isGitRepo(repoDir)) {
|
|
@@ -2187,6 +2224,9 @@ var watchCommand = new Command8("watch").description(
|
|
|
2187
2224
|
let prUrl = null;
|
|
2188
2225
|
if (!noMrRequested) {
|
|
2189
2226
|
prUrl = await findPrUrlAcrossRepos(branchName, repoDir, vcs);
|
|
2227
|
+
if (!prUrl) {
|
|
2228
|
+
prUrl = await findPrUrlAcrossRepos(legacyBranchName, repoDir, vcs);
|
|
2229
|
+
}
|
|
2190
2230
|
if (!prUrl) {
|
|
2191
2231
|
prUrl = await extractPrUrlFromUpdates(task.id);
|
|
2192
2232
|
if (prUrl) {
|
|
@@ -2336,7 +2376,7 @@ var watchCommand = new Command8("watch").description(
|
|
|
2336
2376
|
} else {
|
|
2337
2377
|
prompt2 = buildPrototypePrompt(proto, repoDir);
|
|
2338
2378
|
}
|
|
2339
|
-
const child = spawnAgent(agent, repoDir, prompt2, prefix, void 0, proto.title);
|
|
2379
|
+
const child = spawnAgent(agent, repoDir, prompt2, prefix, void 0, void 0, proto.title);
|
|
2340
2380
|
active.set(`proto-${proto.id}`, { process: child, title: proto.title, repoDir, startedAt: Date.now() });
|
|
2341
2381
|
child.on("exit", async (code) => {
|
|
2342
2382
|
const key = `proto-${proto.id}`;
|
|
@@ -2393,7 +2433,7 @@ var watchCommand = new Command8("watch").description(
|
|
|
2393
2433
|
} catch {
|
|
2394
2434
|
}
|
|
2395
2435
|
const prompt2 = buildRepoCreationPrompt(project, workDir);
|
|
2396
|
-
const child = spawnAgent(agent, workDir, prompt2, prefix, void 0, project.name);
|
|
2436
|
+
const child = spawnAgent(agent, workDir, prompt2, prefix, void 0, void 0, project.name);
|
|
2397
2437
|
active.set(`repo-${project.id}`, { process: child, title: project.name, repoDir: workDir, startedAt: Date.now() });
|
|
2398
2438
|
child.on("exit", async (code) => {
|
|
2399
2439
|
const key = `repo-${project.id}`;
|
|
@@ -2426,7 +2466,7 @@ var watchCommand = new Command8("watch").description(
|
|
|
2426
2466
|
}
|
|
2427
2467
|
}
|
|
2428
2468
|
const prompt2 = buildIdeaPrompt(idea, repoDir);
|
|
2429
|
-
const child = spawnAgent(agent, repoDir, prompt2, prefix, void 0, idea.title);
|
|
2469
|
+
const child = spawnAgent(agent, repoDir, prompt2, prefix, void 0, void 0, idea.title);
|
|
2430
2470
|
active.set(`idea-${idea.id}`, { process: child, title: idea.title, repoDir, startedAt: Date.now() });
|
|
2431
2471
|
child.on("exit", async (code) => {
|
|
2432
2472
|
const key = `idea-${idea.id}`;
|
|
@@ -2779,16 +2819,25 @@ ${divider}`);
|
|
|
2779
2819
|
}
|
|
2780
2820
|
dispatchPrototypeJob(proto, repoDir);
|
|
2781
2821
|
}
|
|
2822
|
+
const MAX_CONCURRENT_TESTS = 2;
|
|
2782
2823
|
const testTasks = queuedTasks.filter((t) => t.mode === "testing");
|
|
2783
2824
|
for (const task of testTasks) {
|
|
2784
2825
|
const key = `test-${task.id}`;
|
|
2785
2826
|
if (queued.has(key)) continue;
|
|
2786
2827
|
if (finishing.has(key)) continue;
|
|
2787
2828
|
if (failed.has(key)) continue;
|
|
2829
|
+
const activeTests = [...queued].filter((k) => k.startsWith("test-")).length;
|
|
2830
|
+
if (activeTests >= MAX_CONCURRENT_TESTS) break;
|
|
2788
2831
|
const sid = shortId(task.id);
|
|
2789
2832
|
const prefix = testTag(sid);
|
|
2790
2833
|
if (!task.link) {
|
|
2791
2834
|
logWarn(prefix, `"${task.title}": no MR/PR link \u2014 skipping`);
|
|
2835
|
+
failed.set(key, "no MR/PR link");
|
|
2836
|
+
try {
|
|
2837
|
+
await api.patch(`/api/tasks/${task.id}`, { status: "error" });
|
|
2838
|
+
await postTaskUpdate(task.id, "Test skipped: task has no MR/PR link", "system");
|
|
2839
|
+
} catch {
|
|
2840
|
+
}
|
|
2792
2841
|
continue;
|
|
2793
2842
|
}
|
|
2794
2843
|
queued.add(key);
|
|
@@ -2977,6 +3026,55 @@ ${divider}`);
|
|
|
2977
3026
|
}
|
|
2978
3027
|
dispatchScan(scan, prefix, key);
|
|
2979
3028
|
}
|
|
3029
|
+
let reviewTasks = [];
|
|
3030
|
+
try {
|
|
3031
|
+
reviewTasks = await api.get("/api/tasks?status=review");
|
|
3032
|
+
} catch (err) {
|
|
3033
|
+
logError(watchTag(), `Failed to fetch review tasks: ${err.message}`);
|
|
3034
|
+
}
|
|
3035
|
+
for (const task of reviewTasks) {
|
|
3036
|
+
if (!task.link) continue;
|
|
3037
|
+
if (queued.has(task.id) || finishing.has(task.id) || active.has(task.id)) continue;
|
|
3038
|
+
const sid = shortId(task.id);
|
|
3039
|
+
const prefix = taskTag(sid);
|
|
3040
|
+
const prLabel = task.link.includes("gitlab") ? "MR" : "PR";
|
|
3041
|
+
const vcs = task.link.includes("gitlab") ? "gitlab" : "github";
|
|
3042
|
+
const repoDir = findDirectoryForProject(config, task.projectId, rootDir);
|
|
3043
|
+
if (!repoDir) continue;
|
|
3044
|
+
const status = await checkPrStatus(task.link, repoDir, vcs);
|
|
3045
|
+
if (!status) continue;
|
|
3046
|
+
if (status.merged) {
|
|
3047
|
+
logSuccess(prefix, `${prLabel} merged \u2014 auto-completing "${paint("bold", task.title)}"`);
|
|
3048
|
+
try {
|
|
3049
|
+
await api.patch(`/api/tasks/${task.id}`, {
|
|
3050
|
+
status: "completed",
|
|
3051
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3052
|
+
inProgressSince: null
|
|
3053
|
+
});
|
|
3054
|
+
await postTaskUpdate(task.id, `${prLabel} merged \u2014 task automatically completed`, "system");
|
|
3055
|
+
} catch (err) {
|
|
3056
|
+
logError(prefix, `Failed to auto-complete task: ${err.message}`);
|
|
3057
|
+
}
|
|
3058
|
+
continue;
|
|
3059
|
+
}
|
|
3060
|
+
if (status.hasConflicts) {
|
|
3061
|
+
logWarn(prefix, `${prLabel} has merge conflicts \u2014 re-dispatching agent for "${paint("bold", task.title)}"`);
|
|
3062
|
+
try {
|
|
3063
|
+
await api.patch(`/api/tasks/${task.id}`, { status: "queued" });
|
|
3064
|
+
await api.post(`/api/tasks/${task.id}/updates`, {
|
|
3065
|
+
message: `Your ${prLabel} has merge conflicts with the base branch. Please resolve the conflicts by rebasing or merging main into your branch, then push the updated branch.`,
|
|
3066
|
+
source: "user"
|
|
3067
|
+
});
|
|
3068
|
+
await postTaskUpdate(
|
|
3069
|
+
task.id,
|
|
3070
|
+
`${prLabel} has merge conflicts \u2014 re-dispatching agent to resolve`,
|
|
3071
|
+
"system"
|
|
3072
|
+
);
|
|
3073
|
+
} catch (err) {
|
|
3074
|
+
logError(prefix, `Failed to re-queue task for conflict resolution: ${err.message}`);
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
2980
3078
|
} finally {
|
|
2981
3079
|
pollRunning = false;
|
|
2982
3080
|
}
|
|
@@ -4167,105 +4265,8 @@ var noMrCommand = new Command22("no-mr").description("Signal that a task does no
|
|
|
4167
4265
|
console.log(` Reason: ${description}`);
|
|
4168
4266
|
});
|
|
4169
4267
|
|
|
4170
|
-
// cli/commands/mobile.ts
|
|
4171
|
-
import { Command as Command23 } from "commander";
|
|
4172
|
-
function paint8(color, text) {
|
|
4173
|
-
const colors = {
|
|
4174
|
-
cyan: "\x1B[36m",
|
|
4175
|
-
green: "\x1B[32m",
|
|
4176
|
-
yellow: "\x1B[33m",
|
|
4177
|
-
red: "\x1B[31m",
|
|
4178
|
-
dim: "\x1B[2m",
|
|
4179
|
-
reset: "\x1B[0m"
|
|
4180
|
-
};
|
|
4181
|
-
return `${colors[color] ?? ""}${text}${colors.reset}`;
|
|
4182
|
-
}
|
|
4183
|
-
var mobileCommand = new Command23("mobile").description(
|
|
4184
|
-
"Start the Web-to-Mobile Conversion Wizard for the linked project"
|
|
4185
|
-
).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(
|
|
4186
|
-
async (projectIdArg, opts) => {
|
|
4187
|
-
const projectId = projectIdArg || getLinkedProjectId();
|
|
4188
|
-
if (!projectId) {
|
|
4189
|
-
console.error(
|
|
4190
|
-
'No project specified. Provide a project ID or run "mr link <project-id>" first.'
|
|
4191
|
-
);
|
|
4192
|
-
process.exit(1);
|
|
4193
|
-
}
|
|
4194
|
-
console.log(
|
|
4195
|
-
paint8("cyan", "mobile") + paint8("dim", " \u2014 starting conversion wizard")
|
|
4196
|
-
);
|
|
4197
|
-
const task = await api.post("/api/tasks", {
|
|
4198
|
-
title: "Convert to Mobile App",
|
|
4199
|
-
projectId,
|
|
4200
|
-
status: "in_progress"
|
|
4201
|
-
});
|
|
4202
|
-
console.log(` Created task: ${task.title} (${task.id})`);
|
|
4203
|
-
console.log(" Analyzing web app...");
|
|
4204
|
-
const analysis = await api.post("/api/wizard/mobile/analyze", {
|
|
4205
|
-
projectId,
|
|
4206
|
-
parentTaskId: task.id,
|
|
4207
|
-
webAppUrl: opts.url
|
|
4208
|
-
});
|
|
4209
|
-
console.log(
|
|
4210
|
-
` Found ${analysis.analysis.screens.length} screens (${analysis.analysis.framework})`
|
|
4211
|
-
);
|
|
4212
|
-
if (opts.framework) {
|
|
4213
|
-
console.log(
|
|
4214
|
-
` Generating architecture plan for ${opts.framework}...`
|
|
4215
|
-
);
|
|
4216
|
-
await api.post("/api/wizard/mobile/generate-plan", {
|
|
4217
|
-
projectId,
|
|
4218
|
-
parentTaskId: task.id,
|
|
4219
|
-
framework: opts.framework,
|
|
4220
|
-
analysisResourceId: analysis.resourceId
|
|
4221
|
-
});
|
|
4222
|
-
console.log(" Architecture plan created.");
|
|
4223
|
-
}
|
|
4224
|
-
console.log(
|
|
4225
|
-
`
|
|
4226
|
-
${paint8("green", "\u2713")} Wizard initialized. Open the web UI to continue:
|
|
4227
|
-
\u2192 Task ID: ${task.id}
|
|
4228
|
-
\u2192 Use the "Convert to Mobile" button on the project page`
|
|
4229
|
-
);
|
|
4230
|
-
}
|
|
4231
|
-
);
|
|
4232
|
-
var statusSubcommand = new Command23("status").description("Show mobile conversion status for a task").argument("<task-id>", "Parent conversion task ID").action(async (taskId) => {
|
|
4233
|
-
const resources = await api.get(
|
|
4234
|
-
`/api/tasks/${taskId}/resources`
|
|
4235
|
-
);
|
|
4236
|
-
const wizardState = resources.find(
|
|
4237
|
-
(r) => r.title === "Wizard State" && r.type === "plan"
|
|
4238
|
-
);
|
|
4239
|
-
if (!wizardState) {
|
|
4240
|
-
console.log("No wizard state found for this task.");
|
|
4241
|
-
return;
|
|
4242
|
-
}
|
|
4243
|
-
try {
|
|
4244
|
-
const state = JSON.parse(wizardState.content);
|
|
4245
|
-
console.log(paint8("cyan", "Mobile Conversion Status"));
|
|
4246
|
-
console.log(` Phase: ${state.phase}`);
|
|
4247
|
-
if (state.framework) {
|
|
4248
|
-
console.log(` Framework: ${state.framework}`);
|
|
4249
|
-
}
|
|
4250
|
-
if (state.screenDesigns?.length) {
|
|
4251
|
-
const completed = state.screenDesigns.filter(
|
|
4252
|
-
(d) => d.status === "complete"
|
|
4253
|
-
).length;
|
|
4254
|
-
console.log(
|
|
4255
|
-
` Screens: ${completed}/${state.screenDesigns.length} complete`
|
|
4256
|
-
);
|
|
4257
|
-
}
|
|
4258
|
-
if (state.mobileRepoUrl) {
|
|
4259
|
-
console.log(` Repo: ${state.mobileRepoUrl}`);
|
|
4260
|
-
}
|
|
4261
|
-
} catch {
|
|
4262
|
-
console.log("Could not parse wizard state.");
|
|
4263
|
-
}
|
|
4264
|
-
});
|
|
4265
|
-
mobileCommand.addCommand(statusSubcommand);
|
|
4266
|
-
|
|
4267
4268
|
// cli/commands/scan.ts
|
|
4268
|
-
import { Command as
|
|
4269
|
+
import { Command as Command23 } from "commander";
|
|
4269
4270
|
|
|
4270
4271
|
// lib/scanner/index.ts
|
|
4271
4272
|
import { spawn as spawn7 } from "child_process";
|
|
@@ -5014,25 +5015,25 @@ var c8 = {
|
|
|
5014
5015
|
magenta: "\x1B[35m",
|
|
5015
5016
|
gray: "\x1B[90m"
|
|
5016
5017
|
};
|
|
5017
|
-
function
|
|
5018
|
+
function paint8(color, text) {
|
|
5018
5019
|
return `${c8[color]}${text}${c8.reset}`;
|
|
5019
5020
|
}
|
|
5020
5021
|
function timestamp2() {
|
|
5021
|
-
return
|
|
5022
|
+
return paint8("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
|
|
5022
5023
|
}
|
|
5023
5024
|
function scanTag() {
|
|
5024
|
-
return
|
|
5025
|
+
return paint8("magenta", "[scan]");
|
|
5025
5026
|
}
|
|
5026
5027
|
function log(msg) {
|
|
5027
5028
|
console.log(`${timestamp2()} ${scanTag()} ${msg}`);
|
|
5028
5029
|
}
|
|
5029
5030
|
function logOk(msg) {
|
|
5030
|
-
console.log(`${timestamp2()} ${scanTag()} ${
|
|
5031
|
+
console.log(`${timestamp2()} ${scanTag()} ${paint8("green", "\u2713")} ${msg}`);
|
|
5031
5032
|
}
|
|
5032
5033
|
function logErr(msg) {
|
|
5033
|
-
console.error(`${timestamp2()} ${scanTag()} ${
|
|
5034
|
+
console.error(`${timestamp2()} ${scanTag()} ${paint8("red", "\u2717")} ${msg}`);
|
|
5034
5035
|
}
|
|
5035
|
-
var scanCommand = new
|
|
5036
|
+
var scanCommand = new Command23("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) => {
|
|
5036
5037
|
const config = loadConfig();
|
|
5037
5038
|
if (!config.apiKey) {
|
|
5038
5039
|
logErr('Not authenticated. Run "mr login" first.');
|
|
@@ -5040,11 +5041,11 @@ var scanCommand = new Command24("scan").description("Run a product scan on the c
|
|
|
5040
5041
|
}
|
|
5041
5042
|
const banner = [
|
|
5042
5043
|
``,
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5044
|
+
paint8("magenta", ` \u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2557\u2554`),
|
|
5045
|
+
paint8("magenta", ` \u255A\u2550\u2557\u2551 \u2560\u2550\u2563\u2551\u2551\u2551`),
|
|
5046
|
+
paint8("magenta", ` \u255A\u2550\u255D\u255A\u2550\u255D\u2569 \u2569\u255D\u255A\u255D`),
|
|
5047
|
+
paint8("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
|
|
5048
|
+
paint8("dim", ` autonomous product scanner`),
|
|
5048
5049
|
``
|
|
5049
5050
|
].join("\n");
|
|
5050
5051
|
console.log(banner);
|
|
@@ -5060,7 +5061,7 @@ var scanCommand = new Command24("scan").description("Run a product scan on the c
|
|
|
5060
5061
|
logErr(`Failed to fetch project ${projectId}`);
|
|
5061
5062
|
process.exit(1);
|
|
5062
5063
|
}
|
|
5063
|
-
log(`Scanning project: ${
|
|
5064
|
+
log(`Scanning project: ${paint8("cyan", project.name)}`);
|
|
5064
5065
|
let projectPath = project.localPath;
|
|
5065
5066
|
if (!projectPath) {
|
|
5066
5067
|
for (const [dir, pid] of Object.entries(config.directories)) {
|
|
@@ -5076,7 +5077,7 @@ var scanCommand = new Command24("scan").description("Run a product scan on the c
|
|
|
5076
5077
|
let reportId;
|
|
5077
5078
|
if (opts.report) {
|
|
5078
5079
|
reportId = opts.report;
|
|
5079
|
-
log(`Using existing scan report ${
|
|
5080
|
+
log(`Using existing scan report ${paint8("yellow", reportId.slice(0, 8))}`);
|
|
5080
5081
|
} else {
|
|
5081
5082
|
try {
|
|
5082
5083
|
const scans = await api.get(`/api/scans?projectId=${projectId}&status=processing`);
|
|
@@ -5092,7 +5093,7 @@ var scanCommand = new Command24("scan").description("Run a product scan on the c
|
|
|
5092
5093
|
status: "pending"
|
|
5093
5094
|
});
|
|
5094
5095
|
reportId = report.id;
|
|
5095
|
-
log(`Created scan report ${
|
|
5096
|
+
log(`Created scan report ${paint8("yellow", reportId.slice(0, 8))}`);
|
|
5096
5097
|
} catch (err) {
|
|
5097
5098
|
logErr(`Failed to create scan report: ${err.message}`);
|
|
5098
5099
|
process.exit(1);
|
|
@@ -5105,7 +5106,7 @@ var scanCommand = new Command24("scan").description("Run a product scan on the c
|
|
|
5105
5106
|
try {
|
|
5106
5107
|
const current = await api.get(`/api/scans/${reportId}`);
|
|
5107
5108
|
if (current.status === "cancelled") {
|
|
5108
|
-
log(
|
|
5109
|
+
log(paint8("yellow", "Scan was cancelled \u2014 aborting."));
|
|
5109
5110
|
process.exit(0);
|
|
5110
5111
|
}
|
|
5111
5112
|
} catch {
|
|
@@ -5121,7 +5122,7 @@ var scanCommand = new Command24("scan").description("Run a product scan on the c
|
|
|
5121
5122
|
runBrowse: runBrowseCommand2,
|
|
5122
5123
|
onLog: log,
|
|
5123
5124
|
onProgress: (phase, detail) => {
|
|
5124
|
-
log(`${
|
|
5125
|
+
log(`${paint8("dim", `[${phase}]`)} ${detail}`);
|
|
5125
5126
|
}
|
|
5126
5127
|
});
|
|
5127
5128
|
let wasCancelled = false;
|
|
@@ -5133,7 +5134,7 @@ var scanCommand = new Command24("scan").description("Run a product scan on the c
|
|
|
5133
5134
|
} catch {
|
|
5134
5135
|
}
|
|
5135
5136
|
if (wasCancelled) {
|
|
5136
|
-
log(
|
|
5137
|
+
log(paint8("yellow", "Scan was cancelled by user \u2014 discarding results."));
|
|
5137
5138
|
process.exit(0);
|
|
5138
5139
|
}
|
|
5139
5140
|
await api.patch(`/api/scans/${reportId}`, {
|
|
@@ -5144,32 +5145,32 @@ var scanCommand = new Command24("scan").description("Run a product scan on the c
|
|
|
5144
5145
|
scanDurationMs: result.scanDurationMs,
|
|
5145
5146
|
routesCrawled: result.routesCrawled
|
|
5146
5147
|
});
|
|
5147
|
-
logOk(`Scan complete \u2014 ${
|
|
5148
|
+
logOk(`Scan complete \u2014 ${paint8("cyan", String(result.findings.length))} findings`);
|
|
5148
5149
|
console.log("");
|
|
5149
|
-
console.log(` ${
|
|
5150
|
+
console.log(` ${paint8("bold", "Summary:")} ${result.summary}`);
|
|
5150
5151
|
console.log("");
|
|
5151
5152
|
const high = result.findings.filter((f) => f.priority === "high");
|
|
5152
5153
|
const medium = result.findings.filter((f) => f.priority === "medium");
|
|
5153
5154
|
const low = result.findings.filter((f) => f.priority === "low");
|
|
5154
5155
|
if (high.length > 0) {
|
|
5155
|
-
console.log(` ${
|
|
5156
|
+
console.log(` ${paint8("bold", paint8("red", `High Priority (${high.length})`))}`);
|
|
5156
5157
|
for (const f of high) {
|
|
5157
|
-
console.log(` ${
|
|
5158
|
-
console.log(` ${
|
|
5158
|
+
console.log(` ${paint8("red", "\u25CF")} [${f.type}] ${f.title}`);
|
|
5159
|
+
console.log(` ${paint8("dim", f.description.slice(0, 120))}`);
|
|
5159
5160
|
}
|
|
5160
5161
|
console.log("");
|
|
5161
5162
|
}
|
|
5162
5163
|
if (medium.length > 0) {
|
|
5163
|
-
console.log(` ${
|
|
5164
|
+
console.log(` ${paint8("bold", paint8("yellow", `Medium Priority (${medium.length})`))}`);
|
|
5164
5165
|
for (const f of medium) {
|
|
5165
|
-
console.log(` ${
|
|
5166
|
+
console.log(` ${paint8("yellow", "\u25CF")} [${f.type}] ${f.title}`);
|
|
5166
5167
|
}
|
|
5167
5168
|
console.log("");
|
|
5168
5169
|
}
|
|
5169
5170
|
if (low.length > 0) {
|
|
5170
|
-
console.log(` ${
|
|
5171
|
+
console.log(` ${paint8("dim", `Low Priority (${low.length})`)} `);
|
|
5171
5172
|
for (const f of low) {
|
|
5172
|
-
console.log(` ${
|
|
5173
|
+
console.log(` ${paint8("dim", `\u25CB [${f.type}] ${f.title}`)}`);
|
|
5173
5174
|
}
|
|
5174
5175
|
console.log("");
|
|
5175
5176
|
}
|
|
@@ -5187,7 +5188,7 @@ var scanCommand = new Command24("scan").description("Run a product scan on the c
|
|
|
5187
5188
|
});
|
|
5188
5189
|
|
|
5189
5190
|
// cli/commands/idea.ts
|
|
5190
|
-
import { Command as
|
|
5191
|
+
import { Command as Command24 } from "commander";
|
|
5191
5192
|
var c9 = {
|
|
5192
5193
|
reset: "\x1B[0m",
|
|
5193
5194
|
bold: "\x1B[1m",
|
|
@@ -5200,27 +5201,27 @@ var c9 = {
|
|
|
5200
5201
|
gray: "\x1B[90m",
|
|
5201
5202
|
magenta: "\x1B[35m"
|
|
5202
5203
|
};
|
|
5203
|
-
function
|
|
5204
|
+
function paint9(color, text) {
|
|
5204
5205
|
return `${c9[color]}${text}${c9.reset}`;
|
|
5205
5206
|
}
|
|
5206
5207
|
function statusBadge2(status) {
|
|
5207
5208
|
switch (status) {
|
|
5208
5209
|
case "draft":
|
|
5209
|
-
return
|
|
5210
|
+
return paint9("gray", "\u25CB draft");
|
|
5210
5211
|
case "generating":
|
|
5211
|
-
return
|
|
5212
|
+
return paint9("cyan", "\u27F3 generating");
|
|
5212
5213
|
case "generated":
|
|
5213
|
-
return
|
|
5214
|
+
return paint9("green", "\u2713 generated");
|
|
5214
5215
|
case "promoted":
|
|
5215
|
-
return
|
|
5216
|
+
return paint9("magenta", "\u2191 promoted");
|
|
5216
5217
|
case "archived":
|
|
5217
|
-
return
|
|
5218
|
+
return paint9("dim", "\u2298 archived");
|
|
5218
5219
|
default:
|
|
5219
|
-
return
|
|
5220
|
+
return paint9("gray", status);
|
|
5220
5221
|
}
|
|
5221
5222
|
}
|
|
5222
|
-
var ideaCommand = new
|
|
5223
|
-
new
|
|
5223
|
+
var ideaCommand = new Command24("idea").description("Manage ideas \u2014 brainstorm, generate prototypes & plans").addCommand(
|
|
5224
|
+
new Command24("list").description("List ideas for the linked project").option("--all", "Show ideas for all projects").option("--status <status>", "Filter by status").action(async (opts) => {
|
|
5224
5225
|
const params = new URLSearchParams();
|
|
5225
5226
|
if (!opts.all) {
|
|
5226
5227
|
const projectId = getLinkedProjectId();
|
|
@@ -5231,23 +5232,23 @@ var ideaCommand = new Command25("idea").description("Manage ideas \u2014 brainst
|
|
|
5231
5232
|
if (opts.status) params.set("status", opts.status);
|
|
5232
5233
|
const ideas = await api.get(`/api/ideas?${params.toString()}`);
|
|
5233
5234
|
if (ideas.length === 0) {
|
|
5234
|
-
console.log(
|
|
5235
|
+
console.log(paint9("gray", "No ideas found."));
|
|
5235
5236
|
return;
|
|
5236
5237
|
}
|
|
5237
5238
|
console.log();
|
|
5238
5239
|
for (const idea of ideas) {
|
|
5239
5240
|
const date = new Date(idea.createdAt).toLocaleDateString();
|
|
5240
5241
|
console.log(
|
|
5241
|
-
` ${
|
|
5242
|
+
` ${paint9("bold", idea.title)} ${statusBadge2(idea.status)} ${paint9("gray", idea.id.slice(0, 8))} ${paint9("dim", date)}`
|
|
5242
5243
|
);
|
|
5243
5244
|
if (idea.description) {
|
|
5244
|
-
console.log(` ${
|
|
5245
|
+
console.log(` ${paint9("dim", idea.description.slice(0, 80) + (idea.description.length > 80 ? "\u2026" : ""))}`);
|
|
5245
5246
|
}
|
|
5246
5247
|
console.log();
|
|
5247
5248
|
}
|
|
5248
5249
|
})
|
|
5249
5250
|
).addCommand(
|
|
5250
|
-
new
|
|
5251
|
+
new Command24("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) => {
|
|
5251
5252
|
const projectId = opts.project ?? getLinkedProjectId() ?? null;
|
|
5252
5253
|
const idea = await api.post("/api/ideas", {
|
|
5253
5254
|
title,
|
|
@@ -5255,59 +5256,59 @@ var ideaCommand = new Command25("idea").description("Manage ideas \u2014 brainst
|
|
|
5255
5256
|
projectId
|
|
5256
5257
|
});
|
|
5257
5258
|
console.log();
|
|
5258
|
-
console.log(` ${
|
|
5259
|
-
console.log(` ${
|
|
5259
|
+
console.log(` ${paint9("green", "\u2713")} Created idea: ${paint9("bold", idea.title)}`);
|
|
5260
|
+
console.log(` ${paint9("gray", "ID:")} ${idea.id}`);
|
|
5260
5261
|
if (opts.generate) {
|
|
5261
5262
|
await api.post(`/api/ideas/${idea.id}/generate`);
|
|
5262
|
-
console.log(` ${
|
|
5263
|
+
console.log(` ${paint9("cyan", "\u27F3")} Generation will begin automatically via the watch agent.`);
|
|
5263
5264
|
}
|
|
5264
5265
|
console.log();
|
|
5265
5266
|
})
|
|
5266
5267
|
).addCommand(
|
|
5267
|
-
new
|
|
5268
|
+
new Command24("generate").description("Start generating plan & prototype for an idea").argument("<id>", "Idea ID").action(async (id) => {
|
|
5268
5269
|
const idea = await api.post(`/api/ideas/${id}/generate`);
|
|
5269
5270
|
console.log();
|
|
5270
|
-
console.log(` ${
|
|
5271
|
-
console.log(` ${
|
|
5271
|
+
console.log(` ${paint9("cyan", "\u27F3")} Generating: ${paint9("bold", idea.title)}`);
|
|
5272
|
+
console.log(` ${paint9("gray", "The watch agent will pick this up shortly.")}`);
|
|
5272
5273
|
console.log();
|
|
5273
5274
|
})
|
|
5274
5275
|
).addCommand(
|
|
5275
|
-
new
|
|
5276
|
+
new Command24("feedback").description("Send feedback to iterate on an idea's generated content").argument("<id>", "Idea ID").argument("<feedback>", "Feedback text").action(async (id, feedback) => {
|
|
5276
5277
|
const idea = await api.post(`/api/ideas/${id}/feedback`, { feedback });
|
|
5277
5278
|
console.log();
|
|
5278
|
-
console.log(` ${
|
|
5279
|
-
console.log(` ${
|
|
5279
|
+
console.log(` ${paint9("cyan", "\u27F3")} Feedback sent for: ${paint9("bold", idea.title)}`);
|
|
5280
|
+
console.log(` ${paint9("gray", "The watch agent will re-generate with your feedback.")}`);
|
|
5280
5281
|
console.log();
|
|
5281
5282
|
})
|
|
5282
5283
|
).addCommand(
|
|
5283
|
-
new
|
|
5284
|
+
new Command24("promote").description("Promote an idea to a task").argument("<id>", "Idea ID").action(async (id) => {
|
|
5284
5285
|
const result = await api.post(`/api/ideas/${id}/promote`);
|
|
5285
5286
|
console.log();
|
|
5286
|
-
console.log(` ${
|
|
5287
|
-
console.log(` ${
|
|
5287
|
+
console.log(` ${paint9("green", "\u2713")} Promoted idea to task: ${paint9("bold", result.task.title)}`);
|
|
5288
|
+
console.log(` ${paint9("gray", "Task ID:")} ${result.task.id}`);
|
|
5288
5289
|
console.log();
|
|
5289
5290
|
})
|
|
5290
5291
|
).addCommand(
|
|
5291
|
-
new
|
|
5292
|
+
new Command24("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) => {
|
|
5292
5293
|
const body = {};
|
|
5293
5294
|
if (opts.name) body.name = opts.name;
|
|
5294
5295
|
const result = await api.post(`/api/ideas/${id}/spin-up`, body);
|
|
5295
5296
|
console.log();
|
|
5296
|
-
console.log(` ${
|
|
5297
|
-
console.log(` ${
|
|
5297
|
+
console.log(` ${paint9("green", "\u2713")} Spinning up project: ${paint9("bold", result.project.name)}`);
|
|
5298
|
+
console.log(` ${paint9("gray", "Project ID:")} ${result.project.id}`);
|
|
5298
5299
|
if (result.tasks && result.tasks.length > 0) {
|
|
5299
|
-
console.log(` ${
|
|
5300
|
+
console.log(` ${paint9("green", "\u2713")} Created ${result.tasks.length} follow-up task(s):`);
|
|
5300
5301
|
for (const task of result.tasks) {
|
|
5301
|
-
console.log(` ${
|
|
5302
|
+
console.log(` ${paint9("gray", "\u2022")} ${task.title}`);
|
|
5302
5303
|
}
|
|
5303
5304
|
}
|
|
5304
|
-
console.log(` ${
|
|
5305
|
+
console.log(` ${paint9("cyan", "\u27F3")} Repo creation is queued \u2014 the watch daemon will pick it up.`);
|
|
5305
5306
|
console.log();
|
|
5306
5307
|
})
|
|
5307
5308
|
);
|
|
5308
5309
|
|
|
5309
5310
|
// cli/commands/doctor.ts
|
|
5310
|
-
import { Command as
|
|
5311
|
+
import { Command as Command25 } from "commander";
|
|
5311
5312
|
import { existsSync as existsSync15 } from "fs";
|
|
5312
5313
|
import { homedir as homedir2 } from "os";
|
|
5313
5314
|
import { join as join11 } from "path";
|
|
@@ -5355,7 +5356,7 @@ async function checkProjectLink() {
|
|
|
5355
5356
|
optional: true
|
|
5356
5357
|
};
|
|
5357
5358
|
}
|
|
5358
|
-
var doctorCommand = new
|
|
5359
|
+
var doctorCommand = new Command25("doctor").description("Diagnose Mr. Manager CLI installation and environment").action(async () => {
|
|
5359
5360
|
const banner = [
|
|
5360
5361
|
``,
|
|
5361
5362
|
paint5("cyan", ` MR DOCTOR`),
|
|
@@ -5435,7 +5436,7 @@ if (isFirstRun && !shouldBypass) {
|
|
|
5435
5436
|
console.log("");
|
|
5436
5437
|
process.exit(0);
|
|
5437
5438
|
}
|
|
5438
|
-
var program = new
|
|
5439
|
+
var program = new Command26();
|
|
5439
5440
|
program.name("mr").description("Mr. Manager - Task and project management CLI").version(CLI_VERSION);
|
|
5440
5441
|
program.addCommand(initCommand);
|
|
5441
5442
|
program.addCommand(authCommand);
|
|
@@ -5462,7 +5463,6 @@ program.addCommand(setPathCommand);
|
|
|
5462
5463
|
program.addCommand(testCommand);
|
|
5463
5464
|
program.addCommand(featuresCommand);
|
|
5464
5465
|
program.addCommand(noMrCommand);
|
|
5465
|
-
program.addCommand(mobileCommand);
|
|
5466
5466
|
program.addCommand(scanCommand);
|
|
5467
5467
|
program.addCommand(ideaCommand);
|
|
5468
5468
|
program.addCommand(doctorCommand);
|