@dunnewold-labs/mr-manager 0.4.3 → 0.4.7
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 +165 -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.7",
|
|
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 [
|
|
@@ -2018,6 +2052,8 @@ var watchCommand = new Command8("watch").description(
|
|
|
2018
2052
|
const agent = opts.agent === "codex" ? "codex" : opts.agent === "gemini" ? "gemini" : "claude";
|
|
2019
2053
|
const scanAt = opts.scanAt;
|
|
2020
2054
|
const taskStallTimeoutMs = getTaskStallTimeoutMs();
|
|
2055
|
+
const hungTaskTimeoutMinutes = Math.max(5, parseInt(process.env.MR_WATCH_HUNG_TASK_TIMEOUT_MINUTES ?? "60", 10) || 60);
|
|
2056
|
+
const hungTaskTimeoutMs = hungTaskTimeoutMinutes * 6e4;
|
|
2021
2057
|
const active = /* @__PURE__ */ new Map();
|
|
2022
2058
|
const failed = /* @__PURE__ */ new Map();
|
|
2023
2059
|
const queued = /* @__PURE__ */ new Set();
|
|
@@ -2061,7 +2097,9 @@ var watchCommand = new Command8("watch").description(
|
|
|
2061
2097
|
async function dispatchTask(task, repoDir) {
|
|
2062
2098
|
const sid = shortId(task.id);
|
|
2063
2099
|
const slug = slugify(task.title);
|
|
2064
|
-
const
|
|
2100
|
+
const owner = ownerPrefix(task);
|
|
2101
|
+
const branchName = `${owner}/${slug}`;
|
|
2102
|
+
const legacyBranchName = `mr/${sid}/${slug}`;
|
|
2065
2103
|
const prefix = taskTag(sid);
|
|
2066
2104
|
const vcs = detectVcs(repoDir)?.provider ?? "github";
|
|
2067
2105
|
logDispatch(prefix, `"${paint("bold", task.title)}" ${paint("gray", repoDir)} ${paint("dim", `[${vcs}]`)}`);
|
|
@@ -2110,7 +2148,8 @@ var watchCommand = new Command8("watch").description(
|
|
|
2110
2148
|
} catch {
|
|
2111
2149
|
}
|
|
2112
2150
|
const hasFeedback = feedbackUpdates.length > 0;
|
|
2113
|
-
const
|
|
2151
|
+
const wtName = `${owner}-${slug}`;
|
|
2152
|
+
const desiredWorktreePath = hasFeedback ? worktreePath(`${wtName}-fb`) : worktreePath(wtName);
|
|
2114
2153
|
let executionDir = repoDir;
|
|
2115
2154
|
let cleanupWorktreePath;
|
|
2116
2155
|
if (isGitRepo(repoDir)) {
|
|
@@ -2185,6 +2224,9 @@ var watchCommand = new Command8("watch").description(
|
|
|
2185
2224
|
let prUrl = null;
|
|
2186
2225
|
if (!noMrRequested) {
|
|
2187
2226
|
prUrl = await findPrUrlAcrossRepos(branchName, repoDir, vcs);
|
|
2227
|
+
if (!prUrl) {
|
|
2228
|
+
prUrl = await findPrUrlAcrossRepos(legacyBranchName, repoDir, vcs);
|
|
2229
|
+
}
|
|
2188
2230
|
if (!prUrl) {
|
|
2189
2231
|
prUrl = await extractPrUrlFromUpdates(task.id);
|
|
2190
2232
|
if (prUrl) {
|
|
@@ -2334,7 +2376,7 @@ var watchCommand = new Command8("watch").description(
|
|
|
2334
2376
|
} else {
|
|
2335
2377
|
prompt2 = buildPrototypePrompt(proto, repoDir);
|
|
2336
2378
|
}
|
|
2337
|
-
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);
|
|
2338
2380
|
active.set(`proto-${proto.id}`, { process: child, title: proto.title, repoDir, startedAt: Date.now() });
|
|
2339
2381
|
child.on("exit", async (code) => {
|
|
2340
2382
|
const key = `proto-${proto.id}`;
|
|
@@ -2391,7 +2433,7 @@ var watchCommand = new Command8("watch").description(
|
|
|
2391
2433
|
} catch {
|
|
2392
2434
|
}
|
|
2393
2435
|
const prompt2 = buildRepoCreationPrompt(project, workDir);
|
|
2394
|
-
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);
|
|
2395
2437
|
active.set(`repo-${project.id}`, { process: child, title: project.name, repoDir: workDir, startedAt: Date.now() });
|
|
2396
2438
|
child.on("exit", async (code) => {
|
|
2397
2439
|
const key = `repo-${project.id}`;
|
|
@@ -2424,7 +2466,7 @@ var watchCommand = new Command8("watch").description(
|
|
|
2424
2466
|
}
|
|
2425
2467
|
}
|
|
2426
2468
|
const prompt2 = buildIdeaPrompt(idea, repoDir);
|
|
2427
|
-
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);
|
|
2428
2470
|
active.set(`idea-${idea.id}`, { process: child, title: idea.title, repoDir, startedAt: Date.now() });
|
|
2429
2471
|
child.on("exit", async (code) => {
|
|
2430
2472
|
const key = `idea-${idea.id}`;
|
|
@@ -2975,6 +3017,55 @@ ${divider}`);
|
|
|
2975
3017
|
}
|
|
2976
3018
|
dispatchScan(scan, prefix, key);
|
|
2977
3019
|
}
|
|
3020
|
+
let reviewTasks = [];
|
|
3021
|
+
try {
|
|
3022
|
+
reviewTasks = await api.get("/api/tasks?status=review");
|
|
3023
|
+
} catch (err) {
|
|
3024
|
+
logError(watchTag(), `Failed to fetch review tasks: ${err.message}`);
|
|
3025
|
+
}
|
|
3026
|
+
for (const task of reviewTasks) {
|
|
3027
|
+
if (!task.link) continue;
|
|
3028
|
+
if (queued.has(task.id) || finishing.has(task.id) || active.has(task.id)) continue;
|
|
3029
|
+
const sid = shortId(task.id);
|
|
3030
|
+
const prefix = taskTag(sid);
|
|
3031
|
+
const prLabel = task.link.includes("gitlab") ? "MR" : "PR";
|
|
3032
|
+
const vcs = task.link.includes("gitlab") ? "gitlab" : "github";
|
|
3033
|
+
const repoDir = findDirectoryForProject(config, task.projectId, rootDir);
|
|
3034
|
+
if (!repoDir) continue;
|
|
3035
|
+
const status = await checkPrStatus(task.link, repoDir, vcs);
|
|
3036
|
+
if (!status) continue;
|
|
3037
|
+
if (status.merged) {
|
|
3038
|
+
logSuccess(prefix, `${prLabel} merged \u2014 auto-completing "${paint("bold", task.title)}"`);
|
|
3039
|
+
try {
|
|
3040
|
+
await api.patch(`/api/tasks/${task.id}`, {
|
|
3041
|
+
status: "completed",
|
|
3042
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3043
|
+
inProgressSince: null
|
|
3044
|
+
});
|
|
3045
|
+
await postTaskUpdate(task.id, `${prLabel} merged \u2014 task automatically completed`, "system");
|
|
3046
|
+
} catch (err) {
|
|
3047
|
+
logError(prefix, `Failed to auto-complete task: ${err.message}`);
|
|
3048
|
+
}
|
|
3049
|
+
continue;
|
|
3050
|
+
}
|
|
3051
|
+
if (status.hasConflicts) {
|
|
3052
|
+
logWarn(prefix, `${prLabel} has merge conflicts \u2014 re-dispatching agent for "${paint("bold", task.title)}"`);
|
|
3053
|
+
try {
|
|
3054
|
+
await api.patch(`/api/tasks/${task.id}`, { status: "queued" });
|
|
3055
|
+
await api.post(`/api/tasks/${task.id}/updates`, {
|
|
3056
|
+
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.`,
|
|
3057
|
+
source: "user"
|
|
3058
|
+
});
|
|
3059
|
+
await postTaskUpdate(
|
|
3060
|
+
task.id,
|
|
3061
|
+
`${prLabel} has merge conflicts \u2014 re-dispatching agent to resolve`,
|
|
3062
|
+
"system"
|
|
3063
|
+
);
|
|
3064
|
+
} catch (err) {
|
|
3065
|
+
logError(prefix, `Failed to re-queue task for conflict resolution: ${err.message}`);
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
2978
3069
|
} finally {
|
|
2979
3070
|
pollRunning = false;
|
|
2980
3071
|
}
|
|
@@ -4165,105 +4256,8 @@ var noMrCommand = new Command22("no-mr").description("Signal that a task does no
|
|
|
4165
4256
|
console.log(` Reason: ${description}`);
|
|
4166
4257
|
});
|
|
4167
4258
|
|
|
4168
|
-
// cli/commands/mobile.ts
|
|
4169
|
-
import { Command as Command23 } from "commander";
|
|
4170
|
-
function paint8(color, text) {
|
|
4171
|
-
const colors = {
|
|
4172
|
-
cyan: "\x1B[36m",
|
|
4173
|
-
green: "\x1B[32m",
|
|
4174
|
-
yellow: "\x1B[33m",
|
|
4175
|
-
red: "\x1B[31m",
|
|
4176
|
-
dim: "\x1B[2m",
|
|
4177
|
-
reset: "\x1B[0m"
|
|
4178
|
-
};
|
|
4179
|
-
return `${colors[color] ?? ""}${text}${colors.reset}`;
|
|
4180
|
-
}
|
|
4181
|
-
var mobileCommand = new Command23("mobile").description(
|
|
4182
|
-
"Start the Web-to-Mobile Conversion Wizard for the linked project"
|
|
4183
|
-
).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(
|
|
4184
|
-
async (projectIdArg, opts) => {
|
|
4185
|
-
const projectId = projectIdArg || getLinkedProjectId();
|
|
4186
|
-
if (!projectId) {
|
|
4187
|
-
console.error(
|
|
4188
|
-
'No project specified. Provide a project ID or run "mr link <project-id>" first.'
|
|
4189
|
-
);
|
|
4190
|
-
process.exit(1);
|
|
4191
|
-
}
|
|
4192
|
-
console.log(
|
|
4193
|
-
paint8("cyan", "mobile") + paint8("dim", " \u2014 starting conversion wizard")
|
|
4194
|
-
);
|
|
4195
|
-
const task = await api.post("/api/tasks", {
|
|
4196
|
-
title: "Convert to Mobile App",
|
|
4197
|
-
projectId,
|
|
4198
|
-
status: "in_progress"
|
|
4199
|
-
});
|
|
4200
|
-
console.log(` Created task: ${task.title} (${task.id})`);
|
|
4201
|
-
console.log(" Analyzing web app...");
|
|
4202
|
-
const analysis = await api.post("/api/wizard/mobile/analyze", {
|
|
4203
|
-
projectId,
|
|
4204
|
-
parentTaskId: task.id,
|
|
4205
|
-
webAppUrl: opts.url
|
|
4206
|
-
});
|
|
4207
|
-
console.log(
|
|
4208
|
-
` Found ${analysis.analysis.screens.length} screens (${analysis.analysis.framework})`
|
|
4209
|
-
);
|
|
4210
|
-
if (opts.framework) {
|
|
4211
|
-
console.log(
|
|
4212
|
-
` Generating architecture plan for ${opts.framework}...`
|
|
4213
|
-
);
|
|
4214
|
-
await api.post("/api/wizard/mobile/generate-plan", {
|
|
4215
|
-
projectId,
|
|
4216
|
-
parentTaskId: task.id,
|
|
4217
|
-
framework: opts.framework,
|
|
4218
|
-
analysisResourceId: analysis.resourceId
|
|
4219
|
-
});
|
|
4220
|
-
console.log(" Architecture plan created.");
|
|
4221
|
-
}
|
|
4222
|
-
console.log(
|
|
4223
|
-
`
|
|
4224
|
-
${paint8("green", "\u2713")} Wizard initialized. Open the web UI to continue:
|
|
4225
|
-
\u2192 Task ID: ${task.id}
|
|
4226
|
-
\u2192 Use the "Convert to Mobile" button on the project page`
|
|
4227
|
-
);
|
|
4228
|
-
}
|
|
4229
|
-
);
|
|
4230
|
-
var statusSubcommand = new Command23("status").description("Show mobile conversion status for a task").argument("<task-id>", "Parent conversion task ID").action(async (taskId) => {
|
|
4231
|
-
const resources = await api.get(
|
|
4232
|
-
`/api/tasks/${taskId}/resources`
|
|
4233
|
-
);
|
|
4234
|
-
const wizardState = resources.find(
|
|
4235
|
-
(r) => r.title === "Wizard State" && r.type === "plan"
|
|
4236
|
-
);
|
|
4237
|
-
if (!wizardState) {
|
|
4238
|
-
console.log("No wizard state found for this task.");
|
|
4239
|
-
return;
|
|
4240
|
-
}
|
|
4241
|
-
try {
|
|
4242
|
-
const state = JSON.parse(wizardState.content);
|
|
4243
|
-
console.log(paint8("cyan", "Mobile Conversion Status"));
|
|
4244
|
-
console.log(` Phase: ${state.phase}`);
|
|
4245
|
-
if (state.framework) {
|
|
4246
|
-
console.log(` Framework: ${state.framework}`);
|
|
4247
|
-
}
|
|
4248
|
-
if (state.screenDesigns?.length) {
|
|
4249
|
-
const completed = state.screenDesigns.filter(
|
|
4250
|
-
(d) => d.status === "complete"
|
|
4251
|
-
).length;
|
|
4252
|
-
console.log(
|
|
4253
|
-
` Screens: ${completed}/${state.screenDesigns.length} complete`
|
|
4254
|
-
);
|
|
4255
|
-
}
|
|
4256
|
-
if (state.mobileRepoUrl) {
|
|
4257
|
-
console.log(` Repo: ${state.mobileRepoUrl}`);
|
|
4258
|
-
}
|
|
4259
|
-
} catch {
|
|
4260
|
-
console.log("Could not parse wizard state.");
|
|
4261
|
-
}
|
|
4262
|
-
});
|
|
4263
|
-
mobileCommand.addCommand(statusSubcommand);
|
|
4264
|
-
|
|
4265
4259
|
// cli/commands/scan.ts
|
|
4266
|
-
import { Command as
|
|
4260
|
+
import { Command as Command23 } from "commander";
|
|
4267
4261
|
|
|
4268
4262
|
// lib/scanner/index.ts
|
|
4269
4263
|
import { spawn as spawn7 } from "child_process";
|
|
@@ -5012,25 +5006,25 @@ var c8 = {
|
|
|
5012
5006
|
magenta: "\x1B[35m",
|
|
5013
5007
|
gray: "\x1B[90m"
|
|
5014
5008
|
};
|
|
5015
|
-
function
|
|
5009
|
+
function paint8(color, text) {
|
|
5016
5010
|
return `${c8[color]}${text}${c8.reset}`;
|
|
5017
5011
|
}
|
|
5018
5012
|
function timestamp2() {
|
|
5019
|
-
return
|
|
5013
|
+
return paint8("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
|
|
5020
5014
|
}
|
|
5021
5015
|
function scanTag() {
|
|
5022
|
-
return
|
|
5016
|
+
return paint8("magenta", "[scan]");
|
|
5023
5017
|
}
|
|
5024
5018
|
function log(msg) {
|
|
5025
5019
|
console.log(`${timestamp2()} ${scanTag()} ${msg}`);
|
|
5026
5020
|
}
|
|
5027
5021
|
function logOk(msg) {
|
|
5028
|
-
console.log(`${timestamp2()} ${scanTag()} ${
|
|
5022
|
+
console.log(`${timestamp2()} ${scanTag()} ${paint8("green", "\u2713")} ${msg}`);
|
|
5029
5023
|
}
|
|
5030
5024
|
function logErr(msg) {
|
|
5031
|
-
console.error(`${timestamp2()} ${scanTag()} ${
|
|
5025
|
+
console.error(`${timestamp2()} ${scanTag()} ${paint8("red", "\u2717")} ${msg}`);
|
|
5032
5026
|
}
|
|
5033
|
-
var scanCommand = new
|
|
5027
|
+
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) => {
|
|
5034
5028
|
const config = loadConfig();
|
|
5035
5029
|
if (!config.apiKey) {
|
|
5036
5030
|
logErr('Not authenticated. Run "mr login" first.');
|
|
@@ -5038,11 +5032,11 @@ var scanCommand = new Command24("scan").description("Run a product scan on the c
|
|
|
5038
5032
|
}
|
|
5039
5033
|
const banner = [
|
|
5040
5034
|
``,
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5035
|
+
paint8("magenta", ` \u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2557\u2554`),
|
|
5036
|
+
paint8("magenta", ` \u255A\u2550\u2557\u2551 \u2560\u2550\u2563\u2551\u2551\u2551`),
|
|
5037
|
+
paint8("magenta", ` \u255A\u2550\u255D\u255A\u2550\u255D\u2569 \u2569\u255D\u255A\u255D`),
|
|
5038
|
+
paint8("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
|
|
5039
|
+
paint8("dim", ` autonomous product scanner`),
|
|
5046
5040
|
``
|
|
5047
5041
|
].join("\n");
|
|
5048
5042
|
console.log(banner);
|
|
@@ -5058,7 +5052,7 @@ var scanCommand = new Command24("scan").description("Run a product scan on the c
|
|
|
5058
5052
|
logErr(`Failed to fetch project ${projectId}`);
|
|
5059
5053
|
process.exit(1);
|
|
5060
5054
|
}
|
|
5061
|
-
log(`Scanning project: ${
|
|
5055
|
+
log(`Scanning project: ${paint8("cyan", project.name)}`);
|
|
5062
5056
|
let projectPath = project.localPath;
|
|
5063
5057
|
if (!projectPath) {
|
|
5064
5058
|
for (const [dir, pid] of Object.entries(config.directories)) {
|
|
@@ -5074,7 +5068,7 @@ var scanCommand = new Command24("scan").description("Run a product scan on the c
|
|
|
5074
5068
|
let reportId;
|
|
5075
5069
|
if (opts.report) {
|
|
5076
5070
|
reportId = opts.report;
|
|
5077
|
-
log(`Using existing scan report ${
|
|
5071
|
+
log(`Using existing scan report ${paint8("yellow", reportId.slice(0, 8))}`);
|
|
5078
5072
|
} else {
|
|
5079
5073
|
try {
|
|
5080
5074
|
const scans = await api.get(`/api/scans?projectId=${projectId}&status=processing`);
|
|
@@ -5090,7 +5084,7 @@ var scanCommand = new Command24("scan").description("Run a product scan on the c
|
|
|
5090
5084
|
status: "pending"
|
|
5091
5085
|
});
|
|
5092
5086
|
reportId = report.id;
|
|
5093
|
-
log(`Created scan report ${
|
|
5087
|
+
log(`Created scan report ${paint8("yellow", reportId.slice(0, 8))}`);
|
|
5094
5088
|
} catch (err) {
|
|
5095
5089
|
logErr(`Failed to create scan report: ${err.message}`);
|
|
5096
5090
|
process.exit(1);
|
|
@@ -5103,7 +5097,7 @@ var scanCommand = new Command24("scan").description("Run a product scan on the c
|
|
|
5103
5097
|
try {
|
|
5104
5098
|
const current = await api.get(`/api/scans/${reportId}`);
|
|
5105
5099
|
if (current.status === "cancelled") {
|
|
5106
|
-
log(
|
|
5100
|
+
log(paint8("yellow", "Scan was cancelled \u2014 aborting."));
|
|
5107
5101
|
process.exit(0);
|
|
5108
5102
|
}
|
|
5109
5103
|
} catch {
|
|
@@ -5119,7 +5113,7 @@ var scanCommand = new Command24("scan").description("Run a product scan on the c
|
|
|
5119
5113
|
runBrowse: runBrowseCommand2,
|
|
5120
5114
|
onLog: log,
|
|
5121
5115
|
onProgress: (phase, detail) => {
|
|
5122
|
-
log(`${
|
|
5116
|
+
log(`${paint8("dim", `[${phase}]`)} ${detail}`);
|
|
5123
5117
|
}
|
|
5124
5118
|
});
|
|
5125
5119
|
let wasCancelled = false;
|
|
@@ -5131,7 +5125,7 @@ var scanCommand = new Command24("scan").description("Run a product scan on the c
|
|
|
5131
5125
|
} catch {
|
|
5132
5126
|
}
|
|
5133
5127
|
if (wasCancelled) {
|
|
5134
|
-
log(
|
|
5128
|
+
log(paint8("yellow", "Scan was cancelled by user \u2014 discarding results."));
|
|
5135
5129
|
process.exit(0);
|
|
5136
5130
|
}
|
|
5137
5131
|
await api.patch(`/api/scans/${reportId}`, {
|
|
@@ -5142,32 +5136,32 @@ var scanCommand = new Command24("scan").description("Run a product scan on the c
|
|
|
5142
5136
|
scanDurationMs: result.scanDurationMs,
|
|
5143
5137
|
routesCrawled: result.routesCrawled
|
|
5144
5138
|
});
|
|
5145
|
-
logOk(`Scan complete \u2014 ${
|
|
5139
|
+
logOk(`Scan complete \u2014 ${paint8("cyan", String(result.findings.length))} findings`);
|
|
5146
5140
|
console.log("");
|
|
5147
|
-
console.log(` ${
|
|
5141
|
+
console.log(` ${paint8("bold", "Summary:")} ${result.summary}`);
|
|
5148
5142
|
console.log("");
|
|
5149
5143
|
const high = result.findings.filter((f) => f.priority === "high");
|
|
5150
5144
|
const medium = result.findings.filter((f) => f.priority === "medium");
|
|
5151
5145
|
const low = result.findings.filter((f) => f.priority === "low");
|
|
5152
5146
|
if (high.length > 0) {
|
|
5153
|
-
console.log(` ${
|
|
5147
|
+
console.log(` ${paint8("bold", paint8("red", `High Priority (${high.length})`))}`);
|
|
5154
5148
|
for (const f of high) {
|
|
5155
|
-
console.log(` ${
|
|
5156
|
-
console.log(` ${
|
|
5149
|
+
console.log(` ${paint8("red", "\u25CF")} [${f.type}] ${f.title}`);
|
|
5150
|
+
console.log(` ${paint8("dim", f.description.slice(0, 120))}`);
|
|
5157
5151
|
}
|
|
5158
5152
|
console.log("");
|
|
5159
5153
|
}
|
|
5160
5154
|
if (medium.length > 0) {
|
|
5161
|
-
console.log(` ${
|
|
5155
|
+
console.log(` ${paint8("bold", paint8("yellow", `Medium Priority (${medium.length})`))}`);
|
|
5162
5156
|
for (const f of medium) {
|
|
5163
|
-
console.log(` ${
|
|
5157
|
+
console.log(` ${paint8("yellow", "\u25CF")} [${f.type}] ${f.title}`);
|
|
5164
5158
|
}
|
|
5165
5159
|
console.log("");
|
|
5166
5160
|
}
|
|
5167
5161
|
if (low.length > 0) {
|
|
5168
|
-
console.log(` ${
|
|
5162
|
+
console.log(` ${paint8("dim", `Low Priority (${low.length})`)} `);
|
|
5169
5163
|
for (const f of low) {
|
|
5170
|
-
console.log(` ${
|
|
5164
|
+
console.log(` ${paint8("dim", `\u25CB [${f.type}] ${f.title}`)}`);
|
|
5171
5165
|
}
|
|
5172
5166
|
console.log("");
|
|
5173
5167
|
}
|
|
@@ -5185,7 +5179,7 @@ var scanCommand = new Command24("scan").description("Run a product scan on the c
|
|
|
5185
5179
|
});
|
|
5186
5180
|
|
|
5187
5181
|
// cli/commands/idea.ts
|
|
5188
|
-
import { Command as
|
|
5182
|
+
import { Command as Command24 } from "commander";
|
|
5189
5183
|
var c9 = {
|
|
5190
5184
|
reset: "\x1B[0m",
|
|
5191
5185
|
bold: "\x1B[1m",
|
|
@@ -5198,27 +5192,27 @@ var c9 = {
|
|
|
5198
5192
|
gray: "\x1B[90m",
|
|
5199
5193
|
magenta: "\x1B[35m"
|
|
5200
5194
|
};
|
|
5201
|
-
function
|
|
5195
|
+
function paint9(color, text) {
|
|
5202
5196
|
return `${c9[color]}${text}${c9.reset}`;
|
|
5203
5197
|
}
|
|
5204
5198
|
function statusBadge2(status) {
|
|
5205
5199
|
switch (status) {
|
|
5206
5200
|
case "draft":
|
|
5207
|
-
return
|
|
5201
|
+
return paint9("gray", "\u25CB draft");
|
|
5208
5202
|
case "generating":
|
|
5209
|
-
return
|
|
5203
|
+
return paint9("cyan", "\u27F3 generating");
|
|
5210
5204
|
case "generated":
|
|
5211
|
-
return
|
|
5205
|
+
return paint9("green", "\u2713 generated");
|
|
5212
5206
|
case "promoted":
|
|
5213
|
-
return
|
|
5207
|
+
return paint9("magenta", "\u2191 promoted");
|
|
5214
5208
|
case "archived":
|
|
5215
|
-
return
|
|
5209
|
+
return paint9("dim", "\u2298 archived");
|
|
5216
5210
|
default:
|
|
5217
|
-
return
|
|
5211
|
+
return paint9("gray", status);
|
|
5218
5212
|
}
|
|
5219
5213
|
}
|
|
5220
|
-
var ideaCommand = new
|
|
5221
|
-
new
|
|
5214
|
+
var ideaCommand = new Command24("idea").description("Manage ideas \u2014 brainstorm, generate prototypes & plans").addCommand(
|
|
5215
|
+
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) => {
|
|
5222
5216
|
const params = new URLSearchParams();
|
|
5223
5217
|
if (!opts.all) {
|
|
5224
5218
|
const projectId = getLinkedProjectId();
|
|
@@ -5229,23 +5223,23 @@ var ideaCommand = new Command25("idea").description("Manage ideas \u2014 brainst
|
|
|
5229
5223
|
if (opts.status) params.set("status", opts.status);
|
|
5230
5224
|
const ideas = await api.get(`/api/ideas?${params.toString()}`);
|
|
5231
5225
|
if (ideas.length === 0) {
|
|
5232
|
-
console.log(
|
|
5226
|
+
console.log(paint9("gray", "No ideas found."));
|
|
5233
5227
|
return;
|
|
5234
5228
|
}
|
|
5235
5229
|
console.log();
|
|
5236
5230
|
for (const idea of ideas) {
|
|
5237
5231
|
const date = new Date(idea.createdAt).toLocaleDateString();
|
|
5238
5232
|
console.log(
|
|
5239
|
-
` ${
|
|
5233
|
+
` ${paint9("bold", idea.title)} ${statusBadge2(idea.status)} ${paint9("gray", idea.id.slice(0, 8))} ${paint9("dim", date)}`
|
|
5240
5234
|
);
|
|
5241
5235
|
if (idea.description) {
|
|
5242
|
-
console.log(` ${
|
|
5236
|
+
console.log(` ${paint9("dim", idea.description.slice(0, 80) + (idea.description.length > 80 ? "\u2026" : ""))}`);
|
|
5243
5237
|
}
|
|
5244
5238
|
console.log();
|
|
5245
5239
|
}
|
|
5246
5240
|
})
|
|
5247
5241
|
).addCommand(
|
|
5248
|
-
new
|
|
5242
|
+
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) => {
|
|
5249
5243
|
const projectId = opts.project ?? getLinkedProjectId() ?? null;
|
|
5250
5244
|
const idea = await api.post("/api/ideas", {
|
|
5251
5245
|
title,
|
|
@@ -5253,59 +5247,59 @@ var ideaCommand = new Command25("idea").description("Manage ideas \u2014 brainst
|
|
|
5253
5247
|
projectId
|
|
5254
5248
|
});
|
|
5255
5249
|
console.log();
|
|
5256
|
-
console.log(` ${
|
|
5257
|
-
console.log(` ${
|
|
5250
|
+
console.log(` ${paint9("green", "\u2713")} Created idea: ${paint9("bold", idea.title)}`);
|
|
5251
|
+
console.log(` ${paint9("gray", "ID:")} ${idea.id}`);
|
|
5258
5252
|
if (opts.generate) {
|
|
5259
5253
|
await api.post(`/api/ideas/${idea.id}/generate`);
|
|
5260
|
-
console.log(` ${
|
|
5254
|
+
console.log(` ${paint9("cyan", "\u27F3")} Generation will begin automatically via the watch agent.`);
|
|
5261
5255
|
}
|
|
5262
5256
|
console.log();
|
|
5263
5257
|
})
|
|
5264
5258
|
).addCommand(
|
|
5265
|
-
new
|
|
5259
|
+
new Command24("generate").description("Start generating plan & prototype for an idea").argument("<id>", "Idea ID").action(async (id) => {
|
|
5266
5260
|
const idea = await api.post(`/api/ideas/${id}/generate`);
|
|
5267
5261
|
console.log();
|
|
5268
|
-
console.log(` ${
|
|
5269
|
-
console.log(` ${
|
|
5262
|
+
console.log(` ${paint9("cyan", "\u27F3")} Generating: ${paint9("bold", idea.title)}`);
|
|
5263
|
+
console.log(` ${paint9("gray", "The watch agent will pick this up shortly.")}`);
|
|
5270
5264
|
console.log();
|
|
5271
5265
|
})
|
|
5272
5266
|
).addCommand(
|
|
5273
|
-
new
|
|
5267
|
+
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) => {
|
|
5274
5268
|
const idea = await api.post(`/api/ideas/${id}/feedback`, { feedback });
|
|
5275
5269
|
console.log();
|
|
5276
|
-
console.log(` ${
|
|
5277
|
-
console.log(` ${
|
|
5270
|
+
console.log(` ${paint9("cyan", "\u27F3")} Feedback sent for: ${paint9("bold", idea.title)}`);
|
|
5271
|
+
console.log(` ${paint9("gray", "The watch agent will re-generate with your feedback.")}`);
|
|
5278
5272
|
console.log();
|
|
5279
5273
|
})
|
|
5280
5274
|
).addCommand(
|
|
5281
|
-
new
|
|
5275
|
+
new Command24("promote").description("Promote an idea to a task").argument("<id>", "Idea ID").action(async (id) => {
|
|
5282
5276
|
const result = await api.post(`/api/ideas/${id}/promote`);
|
|
5283
5277
|
console.log();
|
|
5284
|
-
console.log(` ${
|
|
5285
|
-
console.log(` ${
|
|
5278
|
+
console.log(` ${paint9("green", "\u2713")} Promoted idea to task: ${paint9("bold", result.task.title)}`);
|
|
5279
|
+
console.log(` ${paint9("gray", "Task ID:")} ${result.task.id}`);
|
|
5286
5280
|
console.log();
|
|
5287
5281
|
})
|
|
5288
5282
|
).addCommand(
|
|
5289
|
-
new
|
|
5283
|
+
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) => {
|
|
5290
5284
|
const body = {};
|
|
5291
5285
|
if (opts.name) body.name = opts.name;
|
|
5292
5286
|
const result = await api.post(`/api/ideas/${id}/spin-up`, body);
|
|
5293
5287
|
console.log();
|
|
5294
|
-
console.log(` ${
|
|
5295
|
-
console.log(` ${
|
|
5288
|
+
console.log(` ${paint9("green", "\u2713")} Spinning up project: ${paint9("bold", result.project.name)}`);
|
|
5289
|
+
console.log(` ${paint9("gray", "Project ID:")} ${result.project.id}`);
|
|
5296
5290
|
if (result.tasks && result.tasks.length > 0) {
|
|
5297
|
-
console.log(` ${
|
|
5291
|
+
console.log(` ${paint9("green", "\u2713")} Created ${result.tasks.length} follow-up task(s):`);
|
|
5298
5292
|
for (const task of result.tasks) {
|
|
5299
|
-
console.log(` ${
|
|
5293
|
+
console.log(` ${paint9("gray", "\u2022")} ${task.title}`);
|
|
5300
5294
|
}
|
|
5301
5295
|
}
|
|
5302
|
-
console.log(` ${
|
|
5296
|
+
console.log(` ${paint9("cyan", "\u27F3")} Repo creation is queued \u2014 the watch daemon will pick it up.`);
|
|
5303
5297
|
console.log();
|
|
5304
5298
|
})
|
|
5305
5299
|
);
|
|
5306
5300
|
|
|
5307
5301
|
// cli/commands/doctor.ts
|
|
5308
|
-
import { Command as
|
|
5302
|
+
import { Command as Command25 } from "commander";
|
|
5309
5303
|
import { existsSync as existsSync15 } from "fs";
|
|
5310
5304
|
import { homedir as homedir2 } from "os";
|
|
5311
5305
|
import { join as join11 } from "path";
|
|
@@ -5353,7 +5347,7 @@ async function checkProjectLink() {
|
|
|
5353
5347
|
optional: true
|
|
5354
5348
|
};
|
|
5355
5349
|
}
|
|
5356
|
-
var doctorCommand = new
|
|
5350
|
+
var doctorCommand = new Command25("doctor").description("Diagnose Mr. Manager CLI installation and environment").action(async () => {
|
|
5357
5351
|
const banner = [
|
|
5358
5352
|
``,
|
|
5359
5353
|
paint5("cyan", ` MR DOCTOR`),
|
|
@@ -5433,7 +5427,7 @@ if (isFirstRun && !shouldBypass) {
|
|
|
5433
5427
|
console.log("");
|
|
5434
5428
|
process.exit(0);
|
|
5435
5429
|
}
|
|
5436
|
-
var program = new
|
|
5430
|
+
var program = new Command26();
|
|
5437
5431
|
program.name("mr").description("Mr. Manager - Task and project management CLI").version(CLI_VERSION);
|
|
5438
5432
|
program.addCommand(initCommand);
|
|
5439
5433
|
program.addCommand(authCommand);
|
|
@@ -5460,7 +5454,6 @@ program.addCommand(setPathCommand);
|
|
|
5460
5454
|
program.addCommand(testCommand);
|
|
5461
5455
|
program.addCommand(featuresCommand);
|
|
5462
5456
|
program.addCommand(noMrCommand);
|
|
5463
|
-
program.addCommand(mobileCommand);
|
|
5464
5457
|
program.addCommand(scanCommand);
|
|
5465
5458
|
program.addCommand(ideaCommand);
|
|
5466
5459
|
program.addCommand(doctorCommand);
|