@dunnewold-labs/mr-manager 0.4.18 → 0.4.21
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 +270 -46
- 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 Command29 } from "commander";
|
|
5
5
|
import { existsSync as existsSync17 } 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.21",
|
|
189
189
|
description: "Mr. Manager - Task and project management CLI",
|
|
190
190
|
bin: {
|
|
191
191
|
mr: "./dist/index.mjs"
|
|
@@ -1341,6 +1341,19 @@ Rules:
|
|
|
1341
1341
|
- The "context" field should be a single sentence.
|
|
1342
1342
|
- Use sequential IDs: q1, q2, q3, etc.
|
|
1343
1343
|
- Place the JSON block at the very end of the "Open Questions" section.`;
|
|
1344
|
+
var SYSTEM_SECTION_MR_TESTS = `## MR Tests \u2014 Feature Testing Scenarios
|
|
1345
|
+
|
|
1346
|
+
Before writing your test plan, run:
|
|
1347
|
+
mr tests
|
|
1348
|
+
|
|
1349
|
+
This lists all human-authored test scenarios for this project. Each scenario describes how to navigate to a specific feature, what interactions to perform, and what to verify.
|
|
1350
|
+
|
|
1351
|
+
If a scenario name matches the feature you're testing (by name or keyword):
|
|
1352
|
+
1. Use its interaction steps as the basis for your test plan.
|
|
1353
|
+
2. Adapt specific assertions to the task's requirements.
|
|
1354
|
+
3. Note in your test plan summary which scenario you used.
|
|
1355
|
+
|
|
1356
|
+
If no matching scenario exists, infer interactions from the codebase as normal.`;
|
|
1344
1357
|
var SYSTEM_SECTIONS = {
|
|
1345
1358
|
"status-updates": SYSTEM_SECTION_STATUS_UPDATES,
|
|
1346
1359
|
"screenshots": SYSTEM_SECTION_SCREENSHOTS,
|
|
@@ -1348,12 +1361,13 @@ var SYSTEM_SECTIONS = {
|
|
|
1348
1361
|
"no-mr": SYSTEM_SECTION_NO_MR,
|
|
1349
1362
|
"features-workflow": SYSTEM_SECTION_FEATURES_WORKFLOW,
|
|
1350
1363
|
"prd-format": SYSTEM_SECTION_PRD_FORMAT,
|
|
1351
|
-
"prd-open-questions": SYSTEM_SECTION_PRD_OPEN_QUESTIONS
|
|
1364
|
+
"prd-open-questions": SYSTEM_SECTION_PRD_OPEN_QUESTIONS,
|
|
1365
|
+
"mr-tests": SYSTEM_SECTION_MR_TESTS
|
|
1352
1366
|
};
|
|
1353
1367
|
function composeSystemPrompt(sections) {
|
|
1354
1368
|
return sections.map((s) => SYSTEM_SECTIONS[s]).join("\n\n");
|
|
1355
1369
|
}
|
|
1356
|
-
var EXECUTION_SYSTEM_SECTIONS = ["status-updates", "screenshots", "test-plan", "no-mr", "features-workflow"];
|
|
1370
|
+
var EXECUTION_SYSTEM_SECTIONS = ["status-updates", "screenshots", "test-plan", "mr-tests", "no-mr", "features-workflow"];
|
|
1357
1371
|
var PRD_SYSTEM_SECTIONS = ["prd-format", "prd-open-questions"];
|
|
1358
1372
|
var c = {
|
|
1359
1373
|
reset: "\x1B[0m",
|
|
@@ -2166,6 +2180,7 @@ ${initPrompt}`] : [],
|
|
|
2166
2180
|
].join("\n");
|
|
2167
2181
|
}
|
|
2168
2182
|
function buildPrototypePrompt(proto, repoDir) {
|
|
2183
|
+
const prototypeType = proto.prototypeType ?? "web_app";
|
|
2169
2184
|
const variantSteps = [];
|
|
2170
2185
|
for (let i = 1; i <= proto.variantCount; i++) {
|
|
2171
2186
|
const filename = `prototype-${i}.html`;
|
|
@@ -2178,14 +2193,87 @@ function buildPrototypePrompt(proto, repoDir) {
|
|
|
2178
2193
|
);
|
|
2179
2194
|
}
|
|
2180
2195
|
const variantList = Array.from({ length: proto.variantCount }, (_, i) => `prototype-${i + 1}.html`);
|
|
2196
|
+
const typeConfig = {
|
|
2197
|
+
web_app: {
|
|
2198
|
+
role: "You are a UI designer and frontend engineer. Your job is to generate high-quality, visually distinct web app prototype variants based on the user's design request.",
|
|
2199
|
+
guidelines: [
|
|
2200
|
+
`- **Layout**: Grid, list, card, single-page, multi-section`,
|
|
2201
|
+
`- **Style**: Minimal, bold, playful, corporate, retro, futuristic`,
|
|
2202
|
+
`- **Color scheme**: Light, dark, colorful, monochrome`,
|
|
2203
|
+
`- **Interaction model**: Click-heavy, scroll-based, hover effects, animated`,
|
|
2204
|
+
`- **Information density**: Sparse, balanced, dense`,
|
|
2205
|
+
`- **Navigation**: Sidebar, top nav, bottom nav, hamburger, tabs`
|
|
2206
|
+
],
|
|
2207
|
+
rules: [
|
|
2208
|
+
`- Each file must be a complete, functional page \u2014 pure HTML/CSS/JS, no external libraries (Tailwind CDN is acceptable).`
|
|
2209
|
+
]
|
|
2210
|
+
},
|
|
2211
|
+
mobile_app: {
|
|
2212
|
+
role: "You are a mobile UI designer and frontend engineer. Your job is to generate high-quality, visually distinct mobile app prototype variants based on the user's design request.",
|
|
2213
|
+
guidelines: [
|
|
2214
|
+
`- **Viewport**: Use a mobile viewport (375px wide max, 812px tall). Center the phone frame on a neutral background.`,
|
|
2215
|
+
`- **Layout**: Bottom tabs, stack navigation, cards, lists, full-screen views`,
|
|
2216
|
+
`- **Style**: iOS-inspired, Material Design, custom/brand-driven, minimal`,
|
|
2217
|
+
`- **Color scheme**: Light mode, dark mode, brand-colored`,
|
|
2218
|
+
`- **Touch patterns**: Large tap targets (44px+), swipe gestures indicated, thumb-friendly navigation`,
|
|
2219
|
+
`- **Typography**: Mobile-scale fonts (14-18px body), clear hierarchy`,
|
|
2220
|
+
`- **Navigation**: Bottom tab bar, top navigation bar, hamburger drawer, floating action button`
|
|
2221
|
+
],
|
|
2222
|
+
rules: [
|
|
2223
|
+
`- Render the app as a phone-sized frame (375\xD7812px) centered on a light gray page background.`,
|
|
2224
|
+
`- Use a \`<meta name="viewport" content="width=375">\` tag.`,
|
|
2225
|
+
`- Each file must be a complete, functional HTML page \u2014 pure HTML/CSS/JS, no external libraries (Tailwind CDN is acceptable).`
|
|
2226
|
+
]
|
|
2227
|
+
},
|
|
2228
|
+
desktop_app: {
|
|
2229
|
+
role: "You are a desktop UI designer and frontend engineer. Your job is to generate high-quality, visually distinct desktop application prototype variants based on the user's design request.",
|
|
2230
|
+
guidelines: [
|
|
2231
|
+
`- **Viewport**: Full-width desktop layout (1280px+). Use the full browser window.`,
|
|
2232
|
+
`- **Layout**: Multi-panel, sidebar + main content, menubar, toolbar, status bar`,
|
|
2233
|
+
`- **Style**: Native OS-inspired (macOS, Windows), Electron-style, productivity tool, pro/creative app`,
|
|
2234
|
+
`- **Color scheme**: Light, dark, system-default`,
|
|
2235
|
+
`- **Interaction**: Dense information layouts, keyboard shortcuts shown, right-click context menus, drag handles`,
|
|
2236
|
+
`- **Navigation**: Left sidebar tree, top tabs, ribbon toolbar, split panes`
|
|
2237
|
+
],
|
|
2238
|
+
rules: [
|
|
2239
|
+
`- Design for 1280px+ wide layouts. Use the full browser viewport width.`,
|
|
2240
|
+
`- Each file must be a complete, functional HTML page \u2014 pure HTML/CSS/JS, no external libraries (Tailwind CDN is acceptable).`
|
|
2241
|
+
]
|
|
2242
|
+
},
|
|
2243
|
+
logo: {
|
|
2244
|
+
role: "You are a graphic designer specializing in logo and brand identity. Your job is to generate high-quality, visually distinct logo variants based on the user's brand brief.",
|
|
2245
|
+
guidelines: [
|
|
2246
|
+
`- **Style**: Wordmark, lettermark, icon + wordmark, abstract symbol, emblem/badge`,
|
|
2247
|
+
`- **Visual language**: Minimal/geometric, organic/hand-drawn, bold/impactful, elegant/luxury, playful/friendly`,
|
|
2248
|
+
`- **Color scheme**: Monochrome, duotone, full-color (max 3 colors), gradient`,
|
|
2249
|
+
`- **Typography**: Serif, sans-serif, display/decorative, script`,
|
|
2250
|
+
`- **Symbolism**: Use shapes, negative space, and iconography that reflect the brand concept`
|
|
2251
|
+
],
|
|
2252
|
+
rules: [
|
|
2253
|
+
`- Each file is an HTML page that displays the logo centered on a white background with a dark mode toggle.`,
|
|
2254
|
+
`- The logo itself MUST be created as inline SVG \u2014 no raster images, no external assets.`,
|
|
2255
|
+
`- Show the logo at three sizes: large (400px wide), medium (200px), small (80px) stacked vertically.`,
|
|
2256
|
+
`- Include a dark background preview section below the logo to test contrast.`,
|
|
2257
|
+
`- The SVG must be clean and production-ready \u2014 no lorem ipsum, placeholder paths, or broken shapes.`
|
|
2258
|
+
]
|
|
2259
|
+
}
|
|
2260
|
+
};
|
|
2261
|
+
const config = typeConfig[prototypeType] ?? typeConfig.web_app;
|
|
2262
|
+
const typeLabel = {
|
|
2263
|
+
web_app: "Web App",
|
|
2264
|
+
mobile_app: "Mobile App",
|
|
2265
|
+
desktop_app: "Desktop App",
|
|
2266
|
+
logo: "Logo"
|
|
2267
|
+
};
|
|
2181
2268
|
return [
|
|
2182
|
-
|
|
2269
|
+
`${config.role}`,
|
|
2183
2270
|
``,
|
|
2184
2271
|
`Working directory: ${repoDir}`,
|
|
2185
2272
|
``,
|
|
2186
2273
|
`## Prototype Request`,
|
|
2187
2274
|
`Title: ${proto.title}`,
|
|
2188
2275
|
`ID: ${proto.id}`,
|
|
2276
|
+
`Type: ${typeLabel[prototypeType] ?? prototypeType}`,
|
|
2189
2277
|
``,
|
|
2190
2278
|
`## Design Prompt`,
|
|
2191
2279
|
`${proto.prompt}`,
|
|
@@ -2196,17 +2284,12 @@ function buildPrototypePrompt(proto, repoDir) {
|
|
|
2196
2284
|
``,
|
|
2197
2285
|
`Each file must be completely self-contained (inline all CSS and JS \u2014 no external dependencies). Tailwind CDN is acceptable.`,
|
|
2198
2286
|
``,
|
|
2199
|
-
`##
|
|
2287
|
+
`## Variation Guidelines`,
|
|
2200
2288
|
``,
|
|
2201
|
-
`When generating multiple
|
|
2202
|
-
|
|
2203
|
-
`- **Style**: Minimal, bold, playful, corporate, retro, futuristic`,
|
|
2204
|
-
`- **Color scheme**: Light, dark, colorful, monochrome`,
|
|
2205
|
-
`- **Interaction model**: Click-heavy, scroll-based, hover effects, animated`,
|
|
2206
|
-
`- **Information density**: Sparse, balanced, dense`,
|
|
2207
|
-
`- **Navigation**: Sidebar, top nav, bottom nav, hamburger, tabs`,
|
|
2289
|
+
`When generating multiple variants, vary these aspects:`,
|
|
2290
|
+
...config.guidelines,
|
|
2208
2291
|
``,
|
|
2209
|
-
`
|
|
2292
|
+
`Variants should demonstrate different interpretations, not just color swaps. Vary from very similar to wildly different approaches.`,
|
|
2210
2293
|
``,
|
|
2211
2294
|
...variantSteps,
|
|
2212
2295
|
`### Final verification`,
|
|
@@ -2216,7 +2299,7 @@ function buildPrototypePrompt(proto, repoDir) {
|
|
|
2216
2299
|
`- You MUST produce exactly ${proto.variantCount} files: ${variantList.join(", ")}`,
|
|
2217
2300
|
`- Generate them ONE AT A TIME \u2014 design each variant, write the file, then move to the next.`,
|
|
2218
2301
|
`- Each variant must look visually DISTINCT from the others.`,
|
|
2219
|
-
|
|
2302
|
+
...config.rules,
|
|
2220
2303
|
`- Do NOT upload or POST the files anywhere. The watch handler will upload them automatically after you exit.`,
|
|
2221
2304
|
`- Do NOT exit until ALL ${proto.variantCount} files have been written and verified.`
|
|
2222
2305
|
].join("\n");
|
|
@@ -2251,14 +2334,28 @@ function buildRefinementPrompt(proto, parentFiles, repoDir) {
|
|
|
2251
2334
|
existingVariantLines.push(``);
|
|
2252
2335
|
}
|
|
2253
2336
|
const existingVariants = existingVariantLines.join("\n");
|
|
2337
|
+
const prototypeType = proto.prototypeType ?? "web_app";
|
|
2338
|
+
const typeRoleMap = {
|
|
2339
|
+
web_app: "You are a UI designer and frontend engineer. Your job is to REFINE an existing web app prototype based on user feedback.",
|
|
2340
|
+
mobile_app: "You are a mobile UI designer and frontend engineer. Your job is to REFINE an existing mobile app prototype based on user feedback.",
|
|
2341
|
+
desktop_app: "You are a desktop UI designer and frontend engineer. Your job is to REFINE an existing desktop app prototype based on user feedback.",
|
|
2342
|
+
logo: "You are a graphic designer specializing in logo and brand identity. Your job is to REFINE an existing logo prototype based on user feedback."
|
|
2343
|
+
};
|
|
2344
|
+
const typeLabel = {
|
|
2345
|
+
web_app: "Web App",
|
|
2346
|
+
mobile_app: "Mobile App",
|
|
2347
|
+
desktop_app: "Desktop App",
|
|
2348
|
+
logo: "Logo"
|
|
2349
|
+
};
|
|
2254
2350
|
return [
|
|
2255
|
-
|
|
2351
|
+
`${typeRoleMap[prototypeType] ?? typeRoleMap.web_app}`,
|
|
2256
2352
|
``,
|
|
2257
2353
|
`Working directory: ${repoDir}`,
|
|
2258
2354
|
``,
|
|
2259
2355
|
`## Prototype Request`,
|
|
2260
2356
|
`Title: ${proto.title}`,
|
|
2261
2357
|
`ID: ${proto.id}`,
|
|
2358
|
+
`Type: ${typeLabel[prototypeType] ?? prototypeType}`,
|
|
2262
2359
|
``,
|
|
2263
2360
|
`## Original Design Prompt`,
|
|
2264
2361
|
`${proto.prompt}`,
|
|
@@ -2400,7 +2497,10 @@ ${systemPrompt}` : prompt2;
|
|
|
2400
2497
|
if (mode === "plan") {
|
|
2401
2498
|
return { bin: "claude", args: [...sessionArgs, ...nameArgs, ...systemArgs, ...turnsArgs, "--permission-mode", "plan", "-p", prompt2] };
|
|
2402
2499
|
}
|
|
2403
|
-
|
|
2500
|
+
const cfg = loadConfig();
|
|
2501
|
+
const permissionMode = cfg.claudePermissionMode ?? "auto";
|
|
2502
|
+
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] };
|
|
2404
2504
|
}
|
|
2405
2505
|
function commandExists(cmd) {
|
|
2406
2506
|
return new Promise((resolve8) => {
|
|
@@ -2729,11 +2829,12 @@ var watchCommand = new Command8("watch").description(
|
|
|
2729
2829
|
return;
|
|
2730
2830
|
}
|
|
2731
2831
|
let attemptIndex = 0;
|
|
2832
|
+
let resumeAlreadyRetried = false;
|
|
2732
2833
|
const launchAttempt = async (attemptAgent) => {
|
|
2733
2834
|
let spawnFailureReason = null;
|
|
2734
2835
|
const pausedForNetwork = networkPaused.get(task.id);
|
|
2735
|
-
const shouldResumeClaudeSession = attemptAgent === "claude" && !!task.claudeSessionId && (hasFeedback || pausedForNetwork?.resumeSession === true);
|
|
2736
|
-
const sessionId = attemptAgent === "claude" ? task.claudeSessionId
|
|
2836
|
+
const shouldResumeClaudeSession = attemptAgent === "claude" && !!task.claudeSessionId && !resumeAlreadyRetried && (hasFeedback || pausedForNetwork?.resumeSession === true);
|
|
2837
|
+
const sessionId = attemptAgent === "claude" ? shouldResumeClaudeSession ? task.claudeSessionId : randomUUID() : void 0;
|
|
2737
2838
|
const executionSystemPrompt = composeSystemPrompt(EXECUTION_SYSTEM_SECTIONS);
|
|
2738
2839
|
const child = spawnAgent(
|
|
2739
2840
|
attemptAgent,
|
|
@@ -2777,6 +2878,17 @@ var watchCommand = new Command8("watch").description(
|
|
|
2777
2878
|
logWarn(prefix, `${attemptAgent} paused after network loss (${failureDetail})`);
|
|
2778
2879
|
return;
|
|
2779
2880
|
}
|
|
2881
|
+
if (shouldResumeClaudeSession && !resumeAlreadyRetried) {
|
|
2882
|
+
resumeAlreadyRetried = true;
|
|
2883
|
+
logWarn(prefix, `Claude session resume failed (${failureDetail}) \u2014 retrying with fresh session`);
|
|
2884
|
+
await postTaskUpdate(
|
|
2885
|
+
task.id,
|
|
2886
|
+
`Claude session resume failed \u2014 retrying with fresh session`,
|
|
2887
|
+
"system"
|
|
2888
|
+
);
|
|
2889
|
+
await launchAttempt("claude");
|
|
2890
|
+
return;
|
|
2891
|
+
}
|
|
2780
2892
|
const nextAgent = attemptOrder[attemptIndex + 1];
|
|
2781
2893
|
if (nextAgent) {
|
|
2782
2894
|
logWarn(prefix, `${attemptAgent} failed (${failureDetail}) \u2014 retrying with ${nextAgent}`);
|
|
@@ -4248,28 +4360,45 @@ var prototypeCommand = new Command13("prototype").description("Manage prototypes
|
|
|
4248
4360
|
return;
|
|
4249
4361
|
}
|
|
4250
4362
|
console.log();
|
|
4363
|
+
const typeLabels = {
|
|
4364
|
+
web_app: "web",
|
|
4365
|
+
mobile_app: "mobile",
|
|
4366
|
+
desktop_app: "desktop",
|
|
4367
|
+
logo: "logo"
|
|
4368
|
+
};
|
|
4251
4369
|
for (const p of prototypes) {
|
|
4252
4370
|
const date = new Date(p.createdAt).toLocaleDateString();
|
|
4371
|
+
const typeLabel = typeLabels[p.prototypeType] ?? p.prototypeType ?? "web";
|
|
4253
4372
|
console.log(
|
|
4254
|
-
` ${paint4("bold", p.title)} ${statusBadge(p.status)} ${paint4("gray", p.id.slice(0, 8))} ${paint4("dim", date)}`
|
|
4373
|
+
` ${paint4("bold", p.title)} ${statusBadge(p.status)} ${paint4("blue", `[${typeLabel}]`)} ${paint4("gray", p.id.slice(0, 8))} ${paint4("dim", date)}`
|
|
4255
4374
|
);
|
|
4256
4375
|
console.log(` ${paint4("dim", p.prompt.slice(0, 80) + (p.prompt.length > 80 ? "\u2026" : ""))}`);
|
|
4257
4376
|
console.log();
|
|
4258
4377
|
}
|
|
4259
4378
|
})
|
|
4260
4379
|
).addCommand(
|
|
4261
|
-
new Command13("create").description("Create a new prototype").argument("<title>", "Title of the prototype").requiredOption("--prompt <prompt>", "Design description / prompt").option("--project <projectId>", "Project ID (defaults to linked project, when available)").option("--variants <count>", "Number of variants to generate (1-50)", "5").action(async (title, opts) => {
|
|
4380
|
+
new Command13("create").description("Create a new prototype").argument("<title>", "Title of the prototype").requiredOption("--prompt <prompt>", "Design description / prompt").option("--project <projectId>", "Project ID (defaults to linked project, when available)").option("--variants <count>", "Number of variants to generate (1-50)", "5").option("--type <type>", "Prototype type: web_app, mobile_app, desktop_app, logo (default: web_app)", "web_app").action(async (title, opts) => {
|
|
4262
4381
|
const projectId = opts.project ?? getLinkedProjectId();
|
|
4263
4382
|
const variantCount = Math.max(1, Math.min(50, parseInt(opts.variants, 10) || 5));
|
|
4383
|
+
const validTypes = ["web_app", "mobile_app", "desktop_app", "logo"];
|
|
4384
|
+
const prototypeType = validTypes.includes(opts.type) ? opts.type : "web_app";
|
|
4264
4385
|
const prototype = await api.post("/api/prototypes", {
|
|
4265
4386
|
title,
|
|
4266
4387
|
prompt: opts.prompt,
|
|
4388
|
+
prototypeType,
|
|
4267
4389
|
variantCount,
|
|
4268
4390
|
projectId: projectId ?? null
|
|
4269
4391
|
});
|
|
4392
|
+
const typeLabels = {
|
|
4393
|
+
web_app: "Web App",
|
|
4394
|
+
mobile_app: "Mobile App",
|
|
4395
|
+
desktop_app: "Desktop App",
|
|
4396
|
+
logo: "Logo"
|
|
4397
|
+
};
|
|
4270
4398
|
console.log();
|
|
4271
4399
|
console.log(` ${paint4("green", "\u2713")} Created prototype: ${paint4("bold", prototype.title)}`);
|
|
4272
4400
|
console.log(` ${paint4("gray", "ID:")} ${prototype.id}`);
|
|
4401
|
+
console.log(` ${paint4("gray", "Type:")} ${typeLabels[prototype.prototypeType] ?? prototype.prototypeType}`);
|
|
4273
4402
|
if (!prototype.projectId) {
|
|
4274
4403
|
console.log(` ${paint4("gray", "Project:")} none (will generate in the active watch directory)`);
|
|
4275
4404
|
}
|
|
@@ -4557,7 +4686,7 @@ async function checkApiConnectivity() {
|
|
|
4557
4686
|
}
|
|
4558
4687
|
}
|
|
4559
4688
|
function printResults(checks) {
|
|
4560
|
-
const maxNameLen = Math.max(...checks.map((
|
|
4689
|
+
const maxNameLen = Math.max(...checks.map((c12) => c12.name.length));
|
|
4561
4690
|
let allOk = true;
|
|
4562
4691
|
for (const check of checks) {
|
|
4563
4692
|
const isOptional = check.optional ?? false;
|
|
@@ -4571,10 +4700,10 @@ function printResults(checks) {
|
|
|
4571
4700
|
}
|
|
4572
4701
|
async function autoFix(checks, agent) {
|
|
4573
4702
|
const { spawn: spawn8 } = await import("child_process");
|
|
4574
|
-
const ghInstalled = checks.find((
|
|
4575
|
-
const ghAuthed = checks.find((
|
|
4576
|
-
const mrAuthed = checks.find((
|
|
4577
|
-
const claudeCheck = checks.find((
|
|
4703
|
+
const ghInstalled = checks.find((c12) => c12.name === "GitHub CLI (gh)").ok;
|
|
4704
|
+
const ghAuthed = checks.find((c12) => c12.name === "GitHub CLI auth").ok;
|
|
4705
|
+
const mrAuthed = checks.find((c12) => c12.name === "Mr. Manager CLI auth").ok;
|
|
4706
|
+
const claudeCheck = checks.find((c12) => c12.name === "Claude Code (claude)");
|
|
4578
4707
|
if (claudeCheck && !claudeCheck.ok && agent === "claude") {
|
|
4579
4708
|
console.log(paint5("cyan", " Installing Claude Code..."));
|
|
4580
4709
|
console.log(paint5("dim", " Running: curl -fsSL https://claude.ai/install.sh | bash"));
|
|
@@ -4637,7 +4766,7 @@ var setupCommand = new Command14("setup").description("Check that all dependenci
|
|
|
4637
4766
|
console.log("");
|
|
4638
4767
|
return;
|
|
4639
4768
|
}
|
|
4640
|
-
const fixes = checks.filter((
|
|
4769
|
+
const fixes = checks.filter((c12) => !c12.ok && c12.fix && !c12.optional);
|
|
4641
4770
|
if (fixes.length > 0) {
|
|
4642
4771
|
console.log(paint5("yellow", " To fix:"));
|
|
4643
4772
|
for (const fix of fixes) {
|
|
@@ -4909,7 +5038,7 @@ async function ensureDevServer() {
|
|
|
4909
5038
|
}
|
|
4910
5039
|
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(
|
|
4911
5040
|
"--task-id <id>",
|
|
4912
|
-
"Attach
|
|
5041
|
+
"Attach output to a task update (for screenshot and recording-stop commands)"
|
|
4913
5042
|
).option("--dev", "Auto-start local dev server before browsing").allowUnknownOption(true).action(
|
|
4914
5043
|
async (command, args, opts) => {
|
|
4915
5044
|
if (!command) {
|
|
@@ -4948,6 +5077,62 @@ var browseCommand = new Command18("browse").description("Control a headless brow
|
|
|
4948
5077
|
if (exitCode !== 0) {
|
|
4949
5078
|
process.exit(exitCode);
|
|
4950
5079
|
}
|
|
5080
|
+
if (command === "recording-stop" && opts.taskId) {
|
|
5081
|
+
const recordingPath = stdout.match(/Recording saved: (.+)/)?.[1]?.trim();
|
|
5082
|
+
if (!recordingPath || !existsSync9(recordingPath)) {
|
|
5083
|
+
console.error("[browse] Could not find recording file");
|
|
5084
|
+
process.exit(1);
|
|
5085
|
+
}
|
|
5086
|
+
try {
|
|
5087
|
+
const config = loadConfig();
|
|
5088
|
+
const videoBuffer = readFileSync7(recordingPath);
|
|
5089
|
+
const fileName = recordingPath.split("/").pop() || "browse-recording.webm";
|
|
5090
|
+
const isMp4 = fileName.endsWith(".mp4");
|
|
5091
|
+
const formData = new FormData();
|
|
5092
|
+
const blob = new Blob([videoBuffer], {
|
|
5093
|
+
type: isMp4 ? "video/mp4" : "video/webm"
|
|
5094
|
+
});
|
|
5095
|
+
formData.append("file", blob, fileName);
|
|
5096
|
+
formData.append("prefix", "browse-recordings");
|
|
5097
|
+
const uploadRes = await fetch(`${config.apiUrl}/api/upload`, {
|
|
5098
|
+
method: "POST",
|
|
5099
|
+
headers: { Authorization: `Bearer ${config.apiKey}` },
|
|
5100
|
+
body: formData
|
|
5101
|
+
});
|
|
5102
|
+
if (!uploadRes.ok) {
|
|
5103
|
+
const errText = await uploadRes.text();
|
|
5104
|
+
console.error(
|
|
5105
|
+
`[browse] Upload failed: ${uploadRes.status} ${errText}`
|
|
5106
|
+
);
|
|
5107
|
+
process.exit(1);
|
|
5108
|
+
}
|
|
5109
|
+
const uploadData = await uploadRes.json();
|
|
5110
|
+
await api.post(`/api/tasks/${opts.taskId}/updates`, {
|
|
5111
|
+
message: "Browser recording",
|
|
5112
|
+
source: "agent",
|
|
5113
|
+
media: [
|
|
5114
|
+
{
|
|
5115
|
+
kind: "recording",
|
|
5116
|
+
url: uploadData.url,
|
|
5117
|
+
mimeType: isMp4 ? "video/mp4" : "video/webm",
|
|
5118
|
+
label: "Browse recording",
|
|
5119
|
+
captureContext: "browse-recording",
|
|
5120
|
+
uploadState: "uploaded",
|
|
5121
|
+
isCanonical: true
|
|
5122
|
+
}
|
|
5123
|
+
]
|
|
5124
|
+
});
|
|
5125
|
+
console.log(
|
|
5126
|
+
`[browse] Recording uploaded and attached to task ${opts.taskId}`
|
|
5127
|
+
);
|
|
5128
|
+
} catch (err) {
|
|
5129
|
+
console.error(
|
|
5130
|
+
`[browse] Failed to attach recording: ${err.message}`
|
|
5131
|
+
);
|
|
5132
|
+
process.exit(1);
|
|
5133
|
+
}
|
|
5134
|
+
return;
|
|
5135
|
+
}
|
|
4951
5136
|
if (command === "screenshot" && opts.taskId) {
|
|
4952
5137
|
const screenshotPath = stdout.match(/Screenshot saved: (.+)/)?.[1];
|
|
4953
5138
|
if (!screenshotPath || !existsSync9(screenshotPath)) {
|
|
@@ -5719,10 +5904,10 @@ ${codebaseAnalysis.routes.map((r) => `- ${r}`).join("\n")}
|
|
|
5719
5904
|
${codebaseAnalysis.prismaModels.map((m) => `- ${m}`).join("\n")}
|
|
5720
5905
|
|
|
5721
5906
|
**Components:**
|
|
5722
|
-
${codebaseAnalysis.components.slice(0, 15).map((
|
|
5907
|
+
${codebaseAnalysis.components.slice(0, 15).map((c12) => `- ${c12}`).join("\n")}
|
|
5723
5908
|
|
|
5724
5909
|
**Recent Git Commits:**
|
|
5725
|
-
${codebaseAnalysis.recentCommits.slice(0, 8).map((
|
|
5910
|
+
${codebaseAnalysis.recentCommits.slice(0, 8).map((c12) => `- ${c12}`).join("\n")}
|
|
5726
5911
|
|
|
5727
5912
|
**Completed Tasks:**
|
|
5728
5913
|
${context.completedTasks.slice(0, 10).map((t) => `- ${t.title}`).join("\n") || "None"}
|
|
@@ -6369,7 +6554,7 @@ var doctorCommand = new Command25("doctor").description("Diagnose Mr. Manager CL
|
|
|
6369
6554
|
console.log("");
|
|
6370
6555
|
return;
|
|
6371
6556
|
}
|
|
6372
|
-
const fixes = checks.filter((
|
|
6557
|
+
const fixes = checks.filter((c12) => !c12.ok && c12.fix && !c12.optional);
|
|
6373
6558
|
if (fixes.length > 0) {
|
|
6374
6559
|
console.log(paint5("yellow", " To fix:"));
|
|
6375
6560
|
for (const fix of fixes) {
|
|
@@ -6700,6 +6885,44 @@ skillCommand.command("generate").alias("gen").description("Generate a new skill
|
|
|
6700
6885
|
}
|
|
6701
6886
|
});
|
|
6702
6887
|
|
|
6888
|
+
// cli/commands/tests.ts
|
|
6889
|
+
import { Command as Command28 } from "commander";
|
|
6890
|
+
var c11 = {
|
|
6891
|
+
reset: "\x1B[0m",
|
|
6892
|
+
dim: "\x1B[2m",
|
|
6893
|
+
yellow: "\x1B[33m"
|
|
6894
|
+
};
|
|
6895
|
+
var testsCommand = new Command28("tests").description("List MR Test scenarios for the linked project").action(async () => {
|
|
6896
|
+
const projectId = getLinkedProjectId();
|
|
6897
|
+
if (!projectId) {
|
|
6898
|
+
console.error(
|
|
6899
|
+
`${c11.yellow}No project linked to this directory.${c11.reset} Run "mr link <project-id>" first.`
|
|
6900
|
+
);
|
|
6901
|
+
process.exit(1);
|
|
6902
|
+
}
|
|
6903
|
+
const params = new URLSearchParams({ category: "test", projectId });
|
|
6904
|
+
let scenarios;
|
|
6905
|
+
try {
|
|
6906
|
+
scenarios = await api.get(`/api/skills?${params}`);
|
|
6907
|
+
} catch (err) {
|
|
6908
|
+
console.error(`Failed to fetch test scenarios: ${err.message}`);
|
|
6909
|
+
process.exit(1);
|
|
6910
|
+
}
|
|
6911
|
+
if (scenarios.length === 0) {
|
|
6912
|
+
console.log(`${c11.dim}No test scenarios found for this project.${c11.reset}`);
|
|
6913
|
+
return;
|
|
6914
|
+
}
|
|
6915
|
+
for (const scenario of scenarios) {
|
|
6916
|
+
console.log(`### ${scenario.name}`);
|
|
6917
|
+
if (scenario.description) {
|
|
6918
|
+
console.log(`${c11.dim}${scenario.description}${c11.reset}`);
|
|
6919
|
+
console.log();
|
|
6920
|
+
}
|
|
6921
|
+
console.log(scenario.content);
|
|
6922
|
+
console.log();
|
|
6923
|
+
}
|
|
6924
|
+
});
|
|
6925
|
+
|
|
6703
6926
|
// cli/index.ts
|
|
6704
6927
|
var configPath = join12(homedir3(), ".mr-manager", "config.json");
|
|
6705
6928
|
var isFirstRun = !existsSync17(configPath);
|
|
@@ -6707,7 +6930,7 @@ var userArgs = process.argv.slice(2);
|
|
|
6707
6930
|
var bypassCommands = /* @__PURE__ */ new Set(["login", "init", "auth", "help", "--help", "-h", "--version", "-V", "doctor", "setup"]);
|
|
6708
6931
|
var shouldBypass = userArgs.length > 0 && bypassCommands.has(userArgs[0]);
|
|
6709
6932
|
if (isFirstRun && !shouldBypass) {
|
|
6710
|
-
const
|
|
6933
|
+
const c12 = {
|
|
6711
6934
|
reset: "\x1B[0m",
|
|
6712
6935
|
bold: "\x1B[1m",
|
|
6713
6936
|
dim: "\x1B[2m",
|
|
@@ -6717,28 +6940,28 @@ if (isFirstRun && !shouldBypass) {
|
|
|
6717
6940
|
magenta: "\x1B[35m"
|
|
6718
6941
|
};
|
|
6719
6942
|
console.log("");
|
|
6720
|
-
console.log(`${
|
|
6721
|
-
console.log(`${
|
|
6722
|
-
console.log(`${
|
|
6723
|
-
console.log(`${
|
|
6943
|
+
console.log(`${c12.cyan} \u2554\u2566\u2557\u2566\u2550\u2557 \u2554\u2566\u2557\u2554\u2550\u2557\u2554\u2557\u2554\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2566\u2550\u2557${c12.reset}`);
|
|
6944
|
+
console.log(`${c12.magenta} \u2551\u2551\u2551\u2560\u2566\u255D \u2551\u2551\u2551\u2560\u2550\u2563\u2551\u2551\u2551\u2560\u2550\u2563\u2551 \u2566\u2551\u2563 \u2560\u2566\u255D${c12.reset}`);
|
|
6945
|
+
console.log(`${c12.cyan} \u2569 \u2569\u2569\u255A\u2550 \u2569 \u2569\u2569 \u2569\u255D\u255A\u255D\u2569 \u2569\u255A\u2550\u255D\u255A\u2550\u255D\u2569\u255A\u2550${c12.reset}`);
|
|
6946
|
+
console.log(`${c12.dim} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c12.reset}`);
|
|
6724
6947
|
console.log("");
|
|
6725
|
-
console.log(`${
|
|
6726
|
-
console.log(`${
|
|
6948
|
+
console.log(`${c12.bold} Welcome to Mr. Manager!${c12.reset}`);
|
|
6949
|
+
console.log(`${c12.dim} Let's get you set up in a few quick steps.${c12.reset}`);
|
|
6727
6950
|
console.log("");
|
|
6728
|
-
console.log(` ${
|
|
6729
|
-
console.log(` ${
|
|
6951
|
+
console.log(` ${c12.yellow}Step 1:${c12.reset} Authenticate via Google OAuth`);
|
|
6952
|
+
console.log(` ${c12.dim}Run:${c12.reset} ${c12.cyan}mr login${c12.reset}`);
|
|
6730
6953
|
console.log("");
|
|
6731
|
-
console.log(` ${
|
|
6732
|
-
console.log(` ${
|
|
6954
|
+
console.log(` ${c12.yellow}Step 2:${c12.reset} Verify your environment`);
|
|
6955
|
+
console.log(` ${c12.dim}Run:${c12.reset} ${c12.cyan}mr setup${c12.reset}`);
|
|
6733
6956
|
console.log("");
|
|
6734
|
-
console.log(` ${
|
|
6735
|
-
console.log(` ${
|
|
6957
|
+
console.log(` ${c12.yellow}Step 3:${c12.reset} Link a repo and start watching`);
|
|
6958
|
+
console.log(` ${c12.dim}Run:${c12.reset} ${c12.cyan}mr link${c12.reset} ${c12.dim}&&${c12.reset} ${c12.cyan}mr watch${c12.reset}`);
|
|
6736
6959
|
console.log("");
|
|
6737
|
-
console.log(`${
|
|
6960
|
+
console.log(`${c12.dim} Or run ${c12.reset}${c12.cyan}mr login${c12.reset}${c12.dim} to get started now.${c12.reset}`);
|
|
6738
6961
|
console.log("");
|
|
6739
6962
|
process.exit(0);
|
|
6740
6963
|
}
|
|
6741
|
-
var program = new
|
|
6964
|
+
var program = new Command29();
|
|
6742
6965
|
program.name("mr").description("Mr. Manager - Task and project management CLI").version(CLI_VERSION);
|
|
6743
6966
|
program.addCommand(initCommand);
|
|
6744
6967
|
program.addCommand(authCommand);
|
|
@@ -6770,4 +6993,5 @@ program.addCommand(ideaCommand);
|
|
|
6770
6993
|
program.addCommand(doctorCommand);
|
|
6771
6994
|
program.addCommand(promptAuditCommand);
|
|
6772
6995
|
program.addCommand(skillCommand);
|
|
6996
|
+
program.addCommand(testsCommand);
|
|
6773
6997
|
program.parse();
|