@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.
Files changed (2) hide show
  1. package/dist/index.mjs +270 -46
  2. 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 Command28 } from "commander";
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.18",
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
- `You are a UI designer and frontend engineer. Your job is to generate ${proto.variantCount} high-quality, visually distinct HTML prototype variants based on the user's design request.`,
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
- `## Prototype Variation Guidelines`,
2287
+ `## Variation Guidelines`,
2200
2288
  ``,
2201
- `When generating multiple prototypes, vary these aspects:`,
2202
- `- **Layout**: Grid, list, card, single-page, multi-section`,
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
- `Prototypes should demonstrate different interpretations, not just color swaps. Vary from very similar to wildly different approaches.`,
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
- `- Each file must be a complete, functional page \u2014 pure HTML/CSS/JS, no external libraries (Tailwind CDN is acceptable).`,
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
- `You are a UI designer and frontend engineer. Your job is to REFINE an existing prototype based on user feedback.`,
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
- return { bin: "claude", args: [...sessionArgs, ...nameArgs, ...systemArgs, ...turnsArgs, "-p", "--dangerously-skip-permissions", prompt2] };
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 ?? randomUUID() : void 0;
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((c11) => c11.name.length));
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((c11) => c11.name === "GitHub CLI (gh)").ok;
4575
- const ghAuthed = checks.find((c11) => c11.name === "GitHub CLI auth").ok;
4576
- const mrAuthed = checks.find((c11) => c11.name === "Mr. Manager CLI auth").ok;
4577
- const claudeCheck = checks.find((c11) => c11.name === "Claude Code (claude)");
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((c11) => !c11.ok && c11.fix && !c11.optional);
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 screenshot to a task update (only for screenshot command)"
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((c11) => `- ${c11}`).join("\n")}
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((c11) => `- ${c11}`).join("\n")}
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((c11) => !c11.ok && c11.fix && !c11.optional);
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 c11 = {
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(`${c11.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${c11.reset}`);
6721
- console.log(`${c11.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${c11.reset}`);
6722
- console.log(`${c11.cyan} \u2569 \u2569\u2569\u255A\u2550 \u2569 \u2569\u2569 \u2569\u255D\u255A\u255D\u2569 \u2569\u255A\u2550\u255D\u255A\u2550\u255D\u2569\u255A\u2550${c11.reset}`);
6723
- console.log(`${c11.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${c11.reset}`);
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(`${c11.bold} Welcome to Mr. Manager!${c11.reset}`);
6726
- console.log(`${c11.dim} Let's get you set up in a few quick steps.${c11.reset}`);
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(` ${c11.yellow}Step 1:${c11.reset} Authenticate via Google OAuth`);
6729
- console.log(` ${c11.dim}Run:${c11.reset} ${c11.cyan}mr login${c11.reset}`);
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(` ${c11.yellow}Step 2:${c11.reset} Verify your environment`);
6732
- console.log(` ${c11.dim}Run:${c11.reset} ${c11.cyan}mr setup${c11.reset}`);
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(` ${c11.yellow}Step 3:${c11.reset} Link a repo and start watching`);
6735
- console.log(` ${c11.dim}Run:${c11.reset} ${c11.cyan}mr link${c11.reset} ${c11.dim}&&${c11.reset} ${c11.cyan}mr watch${c11.reset}`);
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(`${c11.dim} Or run ${c11.reset}${c11.cyan}mr login${c11.reset}${c11.dim} to get started now.${c11.reset}`);
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 Command28();
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dunnewold-labs/mr-manager",
3
- "version": "0.4.18",
3
+ "version": "0.4.21",
4
4
  "description": "Mr. Manager - Task and project management CLI",
5
5
  "bin": {
6
6
  "mr": "./dist/index.mjs"