@dunnewold-labs/mr-manager 0.4.46 → 0.4.50

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 +201 -48
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -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.46",
188
+ version: "0.4.50",
189
189
  description: "Mr. Manager - Task and project management CLI",
190
190
  bin: {
191
191
  mr: "./dist/index.mjs"
@@ -550,7 +550,7 @@ var contextCommand = new Command8("context").description("Output project context
550
550
  import { Command as Command9 } from "commander";
551
551
  import { spawn as spawn4, exec } from "child_process";
552
552
  import { randomUUID } from "crypto";
553
- import { resolve as resolve2 } from "path";
553
+ import { resolve as resolve2, extname } from "path";
554
554
  import { readFileSync as readFileSync5, readdirSync, unlinkSync, existsSync as existsSync7, statSync, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, rmdirSync } from "fs";
555
555
  import * as readline from "readline";
556
556
 
@@ -1133,7 +1133,7 @@ async function runTest(options) {
1133
1133
  }
1134
1134
 
1135
1135
  // lib/agent-fallback.ts
1136
- var AGENT_FALLBACK_ORDER = ["claude", "codex", "gemini"];
1136
+ var AGENT_FALLBACK_ORDER = ["claude", "codex", "antigravity"];
1137
1137
  function getAgentFallbackChain(agent) {
1138
1138
  const startIndex = AGENT_FALLBACK_ORDER.indexOf(agent);
1139
1139
  if (startIndex === -1) {
@@ -1194,6 +1194,36 @@ function isPrOrMrUrl(input) {
1194
1194
  }
1195
1195
  return false;
1196
1196
  }
1197
+ var MAX_SLUG_LENGTH = 30;
1198
+ function baseSlug(input) {
1199
+ return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, MAX_SLUG_LENGTH);
1200
+ }
1201
+ function extractTicketKey(input) {
1202
+ const match = input.match(/\b([A-Z][A-Z0-9]+-\d+)\b/);
1203
+ return match ? match[1].toLowerCase() : null;
1204
+ }
1205
+ function slugifyTitle(title) {
1206
+ const raw = (title ?? "").trim();
1207
+ if (!raw) return "";
1208
+ const ticketKey = extractTicketKey(raw);
1209
+ if (ticketKey) return baseSlug(ticketKey);
1210
+ const urlPathSlug = urlToPathSlug(raw);
1211
+ if (urlPathSlug) return urlPathSlug;
1212
+ return baseSlug(raw);
1213
+ }
1214
+ function urlToPathSlug(input) {
1215
+ let url;
1216
+ try {
1217
+ url = new URL(input);
1218
+ } catch {
1219
+ return null;
1220
+ }
1221
+ if (!/^https?:$/.test(url.protocol)) return null;
1222
+ const segments = url.pathname.split("/").filter(Boolean);
1223
+ if (segments.length === 0) return null;
1224
+ const slug = baseSlug(segments.slice(-2).join("-"));
1225
+ return slug || null;
1226
+ }
1197
1227
 
1198
1228
  // cli/browse-runner.ts
1199
1229
  import { execSync as execSync3, spawn as spawn3 } from "child_process";
@@ -1497,9 +1527,6 @@ function findDirectoryForProject(config, projectId, rootDir) {
1497
1527
  }
1498
1528
  return null;
1499
1529
  }
1500
- function slugify(title) {
1501
- return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 30);
1502
- }
1503
1530
  function shortId(id) {
1504
1531
  return id.slice(0, 8);
1505
1532
  }
@@ -1514,7 +1541,8 @@ function taskBranchName(task) {
1514
1541
  if (branch && !isPrOrMrUrl(branch)) {
1515
1542
  return branch;
1516
1543
  }
1517
- return `${ownerPrefix(task)}/${slugify(task.title)}`;
1544
+ const slug = slugifyTitle(task.title) || shortId(task.id);
1545
+ return `${ownerPrefix(task)}/${slug}`;
1518
1546
  }
1519
1547
  function resolveBranchFromPrUrl(prUrl, repoDir, vcs) {
1520
1548
  const cmd = vcs === "gitlab" ? `glab mr view "${prUrl}" --output json 2>/dev/null | jq -r '.source_branch // empty'` : `gh pr view "${prUrl}" --json headRefName -q .headRefName 2>/dev/null`;
@@ -1996,7 +2024,7 @@ function buildFeaturesSection(repoDir) {
1996
2024
  ].join("\n");
1997
2025
  }
1998
2026
  function buildExecutionPrompt(task, repoDir, subtasks, vcs = "github", protoRefs = [], feedbackUpdates = [], existingResources = [], skillRefs = [], executionDir, startWithoutWorktree = false, preparedBranchName) {
1999
- const slug = slugify(task.title);
2027
+ const slug = slugifyTitle(task.title) || shortId(task.id);
2000
2028
  const owner = ownerPrefix(task);
2001
2029
  const generatedBranchName = `${owner}/${slug}`;
2002
2030
  const branchName = taskBranchName(task);
@@ -2341,12 +2369,94 @@ function describeVariance(variance) {
2341
2369
  ]
2342
2370
  };
2343
2371
  }
2372
+ var EXEMPLAR_POOLS = [
2373
+ ["Linear", "Vercel", "Arc", "Things 3"],
2374
+ ["Stripe", "Notion", "Figma", "Cron"],
2375
+ ["Raycast", "Superhuman", "Height", "Campsite"],
2376
+ ["Apple", "Readymag", "Family", "Stripe Press"],
2377
+ [
2378
+ "Awwwards Site of the Day winners",
2379
+ "indie SaaS",
2380
+ "editorial / magazine sites",
2381
+ "design-studio portfolios"
2382
+ ]
2383
+ ];
2384
+ function pickExemplars(seed) {
2385
+ let hash = 0;
2386
+ for (let i = 0; i < seed.length; i++) {
2387
+ hash = hash * 31 + seed.charCodeAt(i) | 0;
2388
+ }
2389
+ return EXEMPLAR_POOLS[Math.abs(hash) % EXEMPLAR_POOLS.length].join(", ");
2390
+ }
2391
+ function buildReferenceSection(referenceImagePaths) {
2392
+ if (referenceImagePaths.length === 0) return [];
2393
+ return [
2394
+ `## Reference Designs (CRITICAL \u2014 these define the visual direction)`,
2395
+ ``,
2396
+ `The user attached ${referenceImagePaths.length} reference image(s), saved on disk at:`,
2397
+ ...referenceImagePaths.map((p) => `- \`${p}\``),
2398
+ ``,
2399
+ `Before designing anything, open and study each reference image (read the files at the paths above with your image-reading capability).`,
2400
+ `The references define the intended look and feel: layout structure, color palette, typography, spacing, component styling, density, and overall mood. Match them closely.`,
2401
+ `Where the references conflict with any generic style guidance below, THE REFERENCES WIN \u2014 do not override the user's attached direction with a default "design-led SaaS" look.`,
2402
+ `Treat multiple references as one combined moodboard: extract the consistent visual language across them.`,
2403
+ `You may still vary layout and composition across variants per the Design Variance section \u2014 but every variant must clearly belong to the reference designs' visual world.`,
2404
+ ``
2405
+ ];
2406
+ }
2407
+ async function downloadReferenceImages(proto, baseDir, prefix) {
2408
+ const refs = proto.referenceImages;
2409
+ if (!Array.isArray(refs) || refs.length === 0) return { paths: [], dir: null };
2410
+ const dir = resolve2(baseDir, `.mr-proto-refs-${shortId(proto.id)}`);
2411
+ try {
2412
+ mkdirSync3(dir, { recursive: true });
2413
+ } catch {
2414
+ return { paths: [], dir: null };
2415
+ }
2416
+ const paths = [];
2417
+ for (let i = 0; i < refs.length; i++) {
2418
+ const ref = refs[i];
2419
+ if (!ref || typeof ref.url !== "string") continue;
2420
+ try {
2421
+ const res = await fetch(ref.url);
2422
+ if (!res.ok) {
2423
+ logWarn(prefix, `Reference image ${i + 1} \u2192 HTTP ${res.status}, skipping`);
2424
+ continue;
2425
+ }
2426
+ const buf = Buffer.from(await res.arrayBuffer());
2427
+ let ext = extname(new URL(ref.url).pathname).toLowerCase();
2428
+ if (!ext || ext.length > 5) ext = ".png";
2429
+ const dest = resolve2(dir, `reference-${i + 1}${ext}`);
2430
+ writeFileSync3(dest, buf);
2431
+ paths.push(dest);
2432
+ } catch (err) {
2433
+ logWarn(prefix, `Failed to download reference image ${i + 1}: ${err.message}`);
2434
+ }
2435
+ }
2436
+ return { paths, dir: paths.length > 0 ? dir : null };
2437
+ }
2438
+ function cleanupRefDir(dir) {
2439
+ if (!dir) return;
2440
+ try {
2441
+ for (const f of readdirSync(dir)) {
2442
+ try {
2443
+ unlinkSync(resolve2(dir, f));
2444
+ } catch {
2445
+ }
2446
+ }
2447
+ rmdirSync(dir);
2448
+ } catch {
2449
+ }
2450
+ }
2344
2451
  function buildPrototypePrompt(proto, repoDir, options = {}) {
2345
2452
  const prototypeType = proto.prototypeType ?? "web_app";
2346
2453
  const variantsToProduce = options.variantsToProduce ?? proto.variantCount;
2347
2454
  const startIndex = options.variantStartIndex ?? 1;
2348
2455
  const variance = typeof proto.designVariance === "number" ? proto.designVariance : 70;
2349
2456
  const varianceInfo = describeVariance(variance);
2457
+ const referenceImagePaths = options.referenceImagePaths ?? [];
2458
+ const hasReferences = referenceImagePaths.length > 0;
2459
+ const exemplars = pickExemplars(proto.id);
2350
2460
  const variantSteps = [];
2351
2461
  for (let i = 0; i < variantsToProduce; i++) {
2352
2462
  const idx = startIndex + i;
@@ -2354,7 +2464,7 @@ function buildPrototypePrompt(proto, repoDir, options = {}) {
2354
2464
  variantSteps.push(
2355
2465
  `### Variant ${idx}: ${filename}`,
2356
2466
  `1. ${varianceInfo.perVariantDirective}`,
2357
- `2. Spend a moment composing a real, opinionated design before you start typing HTML. Imagine the screen on Dribbble or in a Linear / Stripe / Vercel / Arc / Things-style portfolio.`,
2467
+ `2. Spend a moment composing a real, opinionated design before you start typing HTML. ${hasReferences ? "Imagine the screen sitting alongside the reference designs the user attached \u2014 it should look like it belongs in the same set." : `Imagine the screen on Dribbble, holding its own next to ${exemplars}.`}`,
2358
2468
  `3. Write the complete self-contained HTML to \`${repoDir}/${filename}\` using the Write tool.`,
2359
2469
  `4. Verify the file was created by reading the first few lines of \`${repoDir}/${filename}\`.`,
2360
2470
  ``
@@ -2367,7 +2477,7 @@ function buildPrototypePrompt(proto, repoDir, options = {}) {
2367
2477
  const sharedQualityBar = [
2368
2478
  `## Craft & Polish Bar (this is the most important section \u2014 read carefully)`,
2369
2479
  ``,
2370
- `These are PORTFOLIO-QUALITY prototypes, not throwaway sketches. The reviewer will judge them at a glance against work from Linear, Vercel, Stripe, Arc, Things, Things 3, Notion, Figma, Apple, Cron, Raycast, Superhuman, and indie SaaS that wins design awards. Your output must look like it belongs in that company.`,
2480
+ hasReferences ? `These are PORTFOLIO-QUALITY prototypes, not throwaway sketches. They must match the visual direction of the reference designs the user attached (see the Reference Designs section above) while hitting a high craft bar. Study the references first \u2014 everything below is secondary to them.` : `These are PORTFOLIO-QUALITY prototypes, not throwaway sketches. The reviewer will judge them at a glance against the craft level of ${exemplars}, and indie SaaS that wins design awards. Your output must look like it belongs in that company \u2014 but interpret the brief on its own terms, don't imitate any one of them.`,
2371
2481
  ``,
2372
2482
  `**Composition & layout**`,
2373
2483
  `- Use a real grid with intentional spacing (multiples of 4 or 8). No cramped or arbitrary padding.`,
@@ -2376,12 +2486,12 @@ function buildPrototypePrompt(proto, repoDir, options = {}) {
2376
2486
  `- Consider "uncommon" navigation: side-scrolling, vertical headers, command-palette-first interfaces, or minimalist "zen" modes.`,
2377
2487
  ``,
2378
2488
  `**Typography**`,
2379
- `- Pair a real display face with a clean text face. Pull from Google Fonts via \`<link>\` (Inter, Geist, Space Grotesk, Instrument Serif, Fraunces, S\xF6hne-style fallbacks, JetBrains Mono, IBM Plex, Manrope, DM Sans, Playfair Display, Caladea, Bricolage Grotesque). Tailwind CDN does NOT ship custom fonts \u2014 link them yourself.`,
2489
+ hasReferences ? `- Match the typography of the reference designs \u2014 identify the display and text faces they use (or the closest Google Fonts equivalents) and pull them in via \`<link>\`. Tailwind CDN does NOT ship custom fonts \u2014 link them yourself.` : `- Pair a real display face with a clean text face, pulled from Google Fonts via \`<link>\`. Good starting points: Inter, Geist, Space Grotesk, Instrument Serif, Fraunces, JetBrains Mono, IBM Plex, Manrope, DM Sans, Playfair Display, Bricolage Grotesque \u2014 but don't feel limited to this list; pick faces that fit the brief and vary them across variants. Tailwind CDN does NOT ship custom fonts \u2014 link them yourself.`,
2380
2490
  `- Use real type scales (e.g. 12 / 14 / 16 / 20 / 28 / 40 / 64). Tighten tracking on display sizes (-0.02em to -0.05em). Use weight contrast (300 vs 600), not just size.`,
2381
2491
  `- Pay attention to line-height (1.5-1.6 for body, 1.1-1.2 for headers).`,
2382
2492
  ``,
2383
2493
  `**Color & material**`,
2384
- `- Pick a deliberate palette per variant. Avoid default Tailwind blue-500 / gray-900 unless that IS the design intent. Mix off-whites (#F9F9F9), warm neutrals, deep ink colors (#0A0A0B), accent pops, gradients, noise textures.`,
2494
+ hasReferences ? `- Derive the palette from the reference designs \u2014 sample their actual colors (backgrounds, ink, accents) rather than defaulting to a generic scheme. Avoid default Tailwind blue-500 / gray-900 unless the references use them.` : `- Pick a deliberate palette per variant. Avoid default Tailwind blue-500 / gray-900. Off-whites, warm neutrals, deep inks, accent pops, gradients, and noise textures are all fair game \u2014 specific values like #F9F9F9 or #0A0A0B are illustrations, not a required palette. Commit to a distinct color story per variant.`,
2385
2495
  `- Use depth: soft shadows (e.g. \`0 10px 30px rgba(0,0,0,0.05)\`), subtle borders (1px @ low opacity), inner glows, glassmorphism (backdrop-blur), grain overlays, or mesh gradients. No flat-default look unless that's the chosen aesthetic.`,
2386
2496
  `- Use "subtle motion" even in static colors: very soft gradients that feel like lighting, not just a color ramp.`,
2387
2497
  ``,
@@ -2405,7 +2515,7 @@ function buildPrototypePrompt(proto, repoDir, options = {}) {
2405
2515
  ];
2406
2516
  const typeConfig = {
2407
2517
  web_app: {
2408
- role: "You are a senior product designer at a top-tier design-led startup, comfortable in Figma and shipping production HTML/CSS. You design at the level of Linear, Vercel, Stripe, and Arc.",
2518
+ role: "You are a senior product designer at a top-tier design-led startup, comfortable in Figma and shipping production HTML/CSS.",
2409
2519
  guidelines: [
2410
2520
  `- **Layout DNA**: bento grid, editorial split, command-bar driven, three-pane mail-style, dashboard with sidebar + canvas, marketing landing with pinned hero, terminal/CLI-inspired, doc-style long-form.`,
2411
2521
  `- **Aesthetic**: Swiss minimal, brutalist editorial, glassmorphism, neumorphic, retro 90s web revival, hand-drawn / sketchy, dark IDE, pastel friendly, monochrome high-contrast, neo-skeuomorphic, "soft-ui" with deep shadows.`,
@@ -2434,7 +2544,7 @@ function buildPrototypePrompt(proto, repoDir, options = {}) {
2434
2544
  ]
2435
2545
  },
2436
2546
  desktop_app: {
2437
- role: "You are a senior desktop product designer who has shipped work on Linear, Things 3, Cron, Raycast, Notion, and Arc. You design at the level of those apps and don't settle for generic.",
2547
+ role: "You are a senior desktop product designer who ships polished, best-in-class, native-feeling apps and doesn't settle for generic.",
2438
2548
  guidelines: [
2439
2549
  `- **Window chrome**: include realistic traffic-light controls or Windows controls, an integrated title bar, sidebar/main split, optional inspector pane.`,
2440
2550
  `- **Layout DNA**: three-pane mail-style, sidebar + canvas + inspector, command-bar driven (kbar / Raycast), tabbed workspace, ribbon toolbar, split-pane editor, agenda + detail, "spatial" layout with floating windows.`,
@@ -2473,6 +2583,7 @@ function buildPrototypePrompt(proto, repoDir, options = {}) {
2473
2583
  logo: "Logo"
2474
2584
  };
2475
2585
  const agentNote = options.agentLabel ? `You are one of several models collaborating on this prototype set. You are the "${options.agentLabel}" track, responsible for variants ${startIndex} through ${startIndex + variantsToProduce - 1}. Bring YOUR creative perspective \u2014 don't try to be a generic averaged designer.` : `You are the sole designer on this prototype set \u2014 bring a strong, opinionated point of view.`;
2586
+ const creativeAnchor = hasReferences ? `The user attached reference designs for this prototype \u2014 they are the single most important input. Treat them as the definitive brief for look and feel (details in the Reference Designs section below).` : `Aim for the craft level of ${exemplars} \u2014 but interpret this brief on its own terms, don't imitate any one product.`;
2476
2587
  const creativeDirection = [
2477
2588
  `## Creative Direction & Polish`,
2478
2589
  `- **Be Opinionated**: Do not design generic "safe" UIs. Imagine you are designing for a product that wants to win "Site of the Day" on Awwwards or be featured in a "Best of Linear-style design" gallery.`,
@@ -2486,6 +2597,8 @@ function buildPrototypePrompt(proto, repoDir, options = {}) {
2486
2597
  ``,
2487
2598
  agentNote,
2488
2599
  ``,
2600
+ creativeAnchor,
2601
+ ``,
2489
2602
  `Working directory: ${repoDir}`,
2490
2603
  ``,
2491
2604
  `## Prototype Request`,
@@ -2496,6 +2609,7 @@ function buildPrototypePrompt(proto, repoDir, options = {}) {
2496
2609
  `## Design Prompt`,
2497
2610
  `${proto.prompt}`,
2498
2611
  ``,
2612
+ ...buildReferenceSection(referenceImagePaths),
2499
2613
  `## Instructions`,
2500
2614
  ``,
2501
2615
  `Generate exactly ${variantsToProduce} HTML file(s) covering variants ${startIndex} through ${startIndex + variantsToProduce - 1}: ${variantList.join(", ")}. Follow the steps below IN ORDER. Do NOT collapse or skip variants. Each file must be completely self-contained (inline all CSS and JS, plus any Google Fonts \`<link>\` you reference). Tailwind CDN is acceptable.`,
@@ -2536,6 +2650,7 @@ function buildPrototypePrompt(proto, repoDir, options = {}) {
2536
2650
  ].join("\n");
2537
2651
  }
2538
2652
  function buildRefinementPrompt(proto, parentFiles, repoDir, options = {}) {
2653
+ const referenceImagePaths = options.referenceImagePaths ?? [];
2539
2654
  const variantsToProduce = options.variantsToProduce ?? proto.variantCount;
2540
2655
  const startIndex = options.variantStartIndex ?? 1;
2541
2656
  const variance = typeof proto.designVariance === "number" ? proto.designVariance : 70;
@@ -2600,6 +2715,7 @@ function buildRefinementPrompt(proto, parentFiles, repoDir, options = {}) {
2600
2715
  `## Original Design Prompt`,
2601
2716
  `${proto.prompt}`,
2602
2717
  ``,
2718
+ ...buildReferenceSection(referenceImagePaths),
2603
2719
  `## User Feedback (IMPORTANT \u2014 this is what you must address)`,
2604
2720
  `${proto.refinementFeedback}`,
2605
2721
  ``,
@@ -2652,15 +2768,15 @@ ${systemPrompt}` : prompt2;
2652
2768
  args.push(fullPrompt);
2653
2769
  return { bin: "codex", args };
2654
2770
  }
2655
- if (agent === "gemini") {
2771
+ if (agent === "antigravity") {
2656
2772
  const fullPrompt = systemPrompt ? `${prompt2}
2657
2773
 
2658
2774
  ${systemPrompt}` : prompt2;
2659
2775
  const args = ["-p", fullPrompt];
2660
2776
  if (mode === "execute") {
2661
- args.push("--yolo");
2777
+ args.push("--dangerously-skip-permissions");
2662
2778
  }
2663
- return { bin: "gemini", args };
2779
+ return { bin: AGENT_BINARIES.antigravity, args };
2664
2780
  }
2665
2781
  const sessionArgs = sessionId ? resumeSession ? ["--resume", sessionId] : ["--session-id", sessionId] : [];
2666
2782
  const nameArgs = name ? ["--name", name] : [];
@@ -2673,6 +2789,11 @@ ${systemPrompt}` : prompt2;
2673
2789
  const permissionArgs = ["--dangerously-skip-permissions"];
2674
2790
  return { bin: "claude", args: [...sessionArgs, ...nameArgs, ...systemArgs, ...turnsArgs, ...modelArgs, ...permissionArgs, "-p", prompt2] };
2675
2791
  }
2792
+ var AGENT_BINARIES = {
2793
+ claude: "claude",
2794
+ codex: "codex",
2795
+ antigravity: "agy"
2796
+ };
2676
2797
  function commandExists(cmd) {
2677
2798
  return new Promise((resolve9) => {
2678
2799
  exec(`command -v ${cmd}`, (err) => resolve9(!err));
@@ -2689,8 +2810,11 @@ function resolveTaskAgentAndModel(delegatedModel, watchAgent) {
2689
2810
  return { agent: "claude", claudeModel: "claude-haiku-4-5-20251001" };
2690
2811
  case "codex":
2691
2812
  return { agent: "codex" };
2813
+ case "antigravity":
2814
+ return { agent: "antigravity" };
2815
+ // Legacy alias: tasks delegated before the Antigravity migration.
2692
2816
  case "gemini":
2693
- return { agent: "gemini" };
2817
+ return { agent: "antigravity" };
2694
2818
  default:
2695
2819
  return { agent: watchAgent };
2696
2820
  }
@@ -2778,12 +2902,12 @@ function spawnAgent(agent, repoDir, prompt2, prefix, onActivity, sessionId, name
2778
2902
  }
2779
2903
  var watchCommand = new Command9("watch").description(
2780
2904
  "Watch for in-progress tasks and autonomously dispatch an AI coding agent to work on them"
2781
- ).option("--interval <seconds>", "Polling interval in seconds", "15").option("--dry-run", "Show what would be dispatched without spawning the agent", false).option("--plan-approval", "Show the agent's plan and ask for approval before executing", false).option("--root <dir>", "Root directory filter for linked repos (default: cwd)").option("--agent <agent>", "AI agent to use: claude, codex, or gemini", "claude").option("--scan-at <HH:MM>", "Run a product scan daily at this time (e.g., 02:00)").action(async (opts) => {
2905
+ ).option("--interval <seconds>", "Polling interval in seconds", "15").option("--dry-run", "Show what would be dispatched without spawning the agent", false).option("--plan-approval", "Show the agent's plan and ask for approval before executing", false).option("--root <dir>", "Root directory filter for linked repos (default: cwd)").option("--agent <agent>", "AI agent to use: claude, codex, or antigravity", "claude").option("--scan-at <HH:MM>", "Run a product scan daily at this time (e.g., 02:00)").action(async (opts) => {
2782
2906
  const intervalMs = parseInt(opts.interval, 10) * 1e3;
2783
2907
  const dryRun = opts.dryRun;
2784
2908
  const planApproval = opts.planApproval;
2785
2909
  const rootDir = opts.root ? resolve2(opts.root) : process.cwd();
2786
- const agent = opts.agent === "codex" ? "codex" : opts.agent === "gemini" ? "gemini" : "claude";
2910
+ const agent = opts.agent === "codex" ? "codex" : opts.agent === "antigravity" ? "antigravity" : "claude";
2787
2911
  const scanAt = opts.scanAt;
2788
2912
  const taskStallTimeoutMs = getTaskStallTimeoutMs();
2789
2913
  const hungTaskTimeoutMinutes = Math.max(5, parseInt(process.env.MR_WATCH_HUNG_TASK_TIMEOUT_MINUTES ?? "60", 10) || 60);
@@ -2825,13 +2949,13 @@ var watchCommand = new Command9("watch").description(
2825
2949
  if (cached !== void 0) {
2826
2950
  return cached;
2827
2951
  }
2828
- const available = await commandExists(candidate);
2952
+ const available = await commandExists(AGENT_BINARIES[candidate]);
2829
2953
  agentAvailability.set(candidate, available);
2830
2954
  return available;
2831
2955
  }
2832
2956
  async function resolveAgentChain(preferred) {
2833
2957
  const availability = {};
2834
- for (const candidate of ["claude", "codex", "gemini"]) {
2958
+ for (const candidate of ["claude", "codex", "antigravity"]) {
2835
2959
  availability[candidate] = await isAgentAvailable(candidate);
2836
2960
  }
2837
2961
  return getAvailableAgentFallbackChain(preferred, availability);
@@ -2923,7 +3047,7 @@ var watchCommand = new Command9("watch").description(
2923
3047
  }
2924
3048
  async function dispatchTask(task, repoDir) {
2925
3049
  const sid = shortId(task.id);
2926
- const slug = slugify(task.title);
3050
+ const slug = slugifyTitle(task.title) || sid;
2927
3051
  const owner = ownerPrefix(task);
2928
3052
  let branchName = taskBranchName(task);
2929
3053
  const legacyBranchName = `mr/${sid}/${slug}`;
@@ -3408,7 +3532,15 @@ var watchCommand = new Command9("watch").description(
3408
3532
  } catch {
3409
3533
  }
3410
3534
  }
3411
- const validAgents = ["claude", "codex", "gemini"];
3535
+ const { paths: refImagePaths, dir: refImageDir } = await downloadReferenceImages(
3536
+ proto,
3537
+ repoDir,
3538
+ prefix
3539
+ );
3540
+ if (refImagePaths.length > 0) {
3541
+ logDispatch(prefix, `${refImagePaths.length} reference image(s) attached`);
3542
+ }
3543
+ const validAgents = ["claude", "codex", "antigravity"];
3412
3544
  const requested = Array.isArray(proto.selectedAgents) ? proto.selectedAgents.filter(
3413
3545
  (a) => validAgents.includes(a)
3414
3546
  ) : [];
@@ -3428,13 +3560,15 @@ var watchCommand = new Command9("watch").description(
3428
3560
  return buildRefinementPrompt(proto, parentFiles, sliceDir, {
3429
3561
  variantStartIndex: startIndex,
3430
3562
  variantsToProduce,
3431
- agentLabel
3563
+ agentLabel,
3564
+ referenceImagePaths: refImagePaths
3432
3565
  });
3433
3566
  }
3434
3567
  return buildPrototypePrompt(proto, sliceDir, {
3435
3568
  variantStartIndex: startIndex,
3436
3569
  variantsToProduce,
3437
- agentLabel
3570
+ agentLabel,
3571
+ referenceImagePaths: refImagePaths
3438
3572
  });
3439
3573
  };
3440
3574
  if (dedupedRequested.length <= 1) {
@@ -3444,6 +3578,7 @@ var watchCommand = new Command9("watch").description(
3444
3578
  logError(prefix, `No available agents found for ${preferred}`);
3445
3579
  await api.patch(`/api/prototypes/${proto.id}`, { status: "failed" }).catch(() => {
3446
3580
  });
3581
+ cleanupRefDir(refImageDir);
3447
3582
  queued.delete(key);
3448
3583
  return;
3449
3584
  }
@@ -3497,14 +3632,16 @@ var watchCommand = new Command9("watch").description(
3497
3632
  const found = readdirSync(repoDir).filter((f) => protoPattern.test(f)).sort();
3498
3633
  const files = found.map((f) => ({
3499
3634
  name: f,
3500
- content: readFileSync5(resolve2(repoDir, f), "utf-8")
3635
+ content: readFileSync5(resolve2(repoDir, f), "utf-8"),
3636
+ agent: attemptAgent
3501
3637
  }));
3502
3638
  if (files.length === 0) {
3503
3639
  logError(prefix, `No prototype HTML files found in ${repoDir}`);
3504
3640
  await api.patch(`/api/prototypes/${proto.id}`, { status: "failed" });
3505
3641
  return;
3506
3642
  }
3507
- await api.patch(`/api/prototypes/${proto.id}`, { status: "completed", files });
3643
+ const variantModels = files.map((f) => f.agent ?? null);
3644
+ await api.patch(`/api/prototypes/${proto.id}`, { status: "completed", files, variantModels });
3508
3645
  logSuccess(prefix, `"${paint("bold", proto.title)}" uploaded ${files.length} file(s)`);
3509
3646
  for (const file of files) {
3510
3647
  try {
@@ -3528,6 +3665,7 @@ var watchCommand = new Command9("watch").description(
3528
3665
  }
3529
3666
  }
3530
3667
  } finally {
3668
+ cleanupRefDir(refImageDir);
3531
3669
  queued.delete(key);
3532
3670
  finishing.delete(key);
3533
3671
  }
@@ -3563,6 +3701,7 @@ var watchCommand = new Command9("watch").description(
3563
3701
  logError(prefix, `Could not assign any variants to selected agents`);
3564
3702
  await api.patch(`/api/prototypes/${proto.id}`, { status: "failed" }).catch(() => {
3565
3703
  });
3704
+ cleanupRefDir(refImageDir);
3566
3705
  queued.delete(key);
3567
3706
  return;
3568
3707
  }
@@ -3626,7 +3765,7 @@ var watchCommand = new Command9("watch").description(
3626
3765
  for (const f of found) {
3627
3766
  try {
3628
3767
  const content = readFileSync5(resolve2(slice.dir, f), "utf-8");
3629
- collected.push({ name: f, content });
3768
+ collected.push({ name: f, content, agent: slice.agentLabel });
3630
3769
  } catch (err) {
3631
3770
  logError(prefix, `Failed reading ${f} from ${slice.dir}: ${err.message}`);
3632
3771
  }
@@ -3647,7 +3786,7 @@ var watchCommand = new Command9("watch").description(
3647
3786
  name = `prototype-${renumberCursor}.html`;
3648
3787
  }
3649
3788
  seen.add(name);
3650
- finalFiles.push({ name, content: f.content });
3789
+ finalFiles.push({ name, content: f.content, agent: f.agent });
3651
3790
  }
3652
3791
  const allOk = sliceResults.every((r) => r.ok);
3653
3792
  if (finalFiles.length === 0) {
@@ -3656,7 +3795,8 @@ var watchCommand = new Command9("watch").description(
3656
3795
  } else {
3657
3796
  await api.patch(`/api/prototypes/${proto.id}`, {
3658
3797
  status: "completed",
3659
- files: finalFiles
3798
+ files: finalFiles,
3799
+ variantModels: finalFiles.map((f) => f.agent ?? null)
3660
3800
  });
3661
3801
  if (allOk) {
3662
3802
  logSuccess(prefix, `"${paint("bold", proto.title)}" uploaded ${finalFiles.length} file(s) from ${slices.length} agent(s)`);
@@ -3684,6 +3824,7 @@ var watchCommand = new Command9("watch").description(
3684
3824
  } catch {
3685
3825
  }
3686
3826
  } finally {
3827
+ cleanupRefDir(refImageDir);
3687
3828
  queued.delete(key);
3688
3829
  finishing.delete(key);
3689
3830
  }
@@ -4456,6 +4597,17 @@ ${summaryBlock}${lines.join("\n")}`;
4456
4597
  }
4457
4598
  const vcs = review.branchUrl.includes("gitlab") ? "gitlab" : "github";
4458
4599
  const prLabel = vcs === "gitlab" ? "MR" : "PR";
4600
+ const preMergeStatus = await checkPrStatus(review.branchUrl, repoDir, vcs);
4601
+ if (preMergeStatus?.merged) {
4602
+ logSuccess(prefix, `${prLabel} already merged \u2014 recording auto-merge as complete`);
4603
+ try {
4604
+ await api.patch(`/api/reviews/${review.id}`, { autoMergeStatus: "merged" });
4605
+ await postTaskUpdate(taskId, `Auto-merge complete \u2014 ${prLabel} was already merged`, "system");
4606
+ } catch (err) {
4607
+ logError(prefix, `Failed to record auto-merge success: ${err.message}`);
4608
+ }
4609
+ continue;
4610
+ }
4459
4611
  logDispatch(prefix, `Sanity review clean \u2014 merging ${prLabel} ${paint("cyan", review.branchUrl)}`);
4460
4612
  const mergeResult = await mergePrViaCli(review.branchUrl, repoDir, vcs);
4461
4613
  if (!mergeResult.ok) {
@@ -4469,8 +4621,9 @@ ${summaryBlock}${lines.join("\n")}`;
4469
4621
  }
4470
4622
  try {
4471
4623
  await api.patch(`/api/reviews/${review.id}`, { autoMergeStatus: "merged" });
4472
- await postTaskUpdate(taskId, `Auto-merge complete \u2014 sanity review passed and ${prLabel} merged`, "system");
4473
- logSuccess(prefix, `Auto-merge complete for ${prLabel}`);
4624
+ await api.patch(`/api/tasks/${taskId}`, { status: "completed" });
4625
+ await postTaskUpdate(taskId, `Auto-merge complete \u2014 sanity review passed, ${prLabel} merged, and task moved to Done`, "system");
4626
+ logSuccess(prefix, `Auto-merge complete for ${prLabel} \u2014 task moved to Done`);
4474
4627
  } catch (err) {
4475
4628
  logError(prefix, `Failed to record auto-merge success: ${err.message}`);
4476
4629
  }
@@ -4932,30 +5085,30 @@ async function checkCodexAuth() {
4932
5085
  fix: hasKey ? void 0 : "Set OPENAI_API_KEY or CODEX_API_KEY environment variable"
4933
5086
  };
4934
5087
  }
4935
- async function checkGeminiInstalled() {
4936
- const exists = await commandExists2("gemini");
5088
+ async function checkAntigravityInstalled() {
5089
+ const exists = await commandExists2("agy");
4937
5090
  return {
4938
- name: "Gemini CLI (gemini)",
5091
+ name: "Antigravity CLI (agy)",
4939
5092
  ok: exists,
4940
5093
  message: exists ? "installed" : "not found",
4941
- fix: exists ? void 0 : "Install: npm install -g @google/gemini-cli"
5094
+ fix: exists ? void 0 : "Install: curl -fsSL https://antigravity.google/cli/install.sh | bash"
4942
5095
  };
4943
5096
  }
4944
- async function checkGeminiAuth() {
4945
- const exists = await commandExists2("gemini");
5097
+ async function checkAntigravityAuth() {
5098
+ const exists = await commandExists2("agy");
4946
5099
  if (!exists) {
4947
5100
  return {
4948
- name: "Gemini CLI auth",
5101
+ name: "Antigravity CLI auth",
4949
5102
  ok: false,
4950
- message: "skipped (gemini not installed)"
5103
+ message: "skipped (agy not installed)"
4951
5104
  };
4952
5105
  }
4953
- const hasKey = !!process.env.GEMINI_API_KEY || !!process.env.GOOGLE_API_KEY;
5106
+ const hasKey = !!process.env.ANTIGRAVITY_API_KEY;
4954
5107
  return {
4955
- name: "Gemini CLI auth",
5108
+ name: "Antigravity CLI auth",
4956
5109
  ok: hasKey,
4957
5110
  message: hasKey ? "API key configured" : "no API key found",
4958
- fix: hasKey ? void 0 : "Set GEMINI_API_KEY or GOOGLE_API_KEY environment variable, or run: gcloud auth login"
5111
+ fix: hasKey ? void 0 : "Set ANTIGRAVITY_API_KEY environment variable, or run `agy` once to sign in with Google"
4959
5112
  };
4960
5113
  }
4961
5114
  async function checkGlabInstalled() {
@@ -5087,8 +5240,8 @@ async function autoFix(checks, agent) {
5087
5240
  console.log("");
5088
5241
  }
5089
5242
  }
5090
- var setupCommand = new Command16("setup").description("Check that all dependencies for mr watch are installed and configured").option("--fix", "Attempt to auto-fix issues where possible", false).option("--agent <agent>", "AI agent to check: claude, codex, or gemini (default: claude)", "claude").action(async (opts) => {
5091
- const agent = opts.agent === "codex" ? "codex" : opts.agent === "gemini" ? "gemini" : "claude";
5243
+ var setupCommand = new Command16("setup").description("Check that all dependencies for mr watch are installed and configured").option("--fix", "Attempt to auto-fix issues where possible", false).option("--agent <agent>", "AI agent to check: claude, codex, or antigravity (default: claude)", "claude").action(async (opts) => {
5244
+ const agent = opts.agent === "codex" ? "codex" : opts.agent === "antigravity" ? "antigravity" : "claude";
5092
5245
  const banner = [
5093
5246
  ``,
5094
5247
  paint5("cyan", ` \u2554\u2566\u2557\u2551\u2550\u2557 \u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2566\u2557\u2551 \u2551\u2554\u2550\u2557`),
@@ -5099,7 +5252,7 @@ var setupCommand = new Command16("setup").description("Check that all dependenci
5099
5252
  ``
5100
5253
  ].join("\n");
5101
5254
  console.log(banner);
5102
- const agentChecks = agent === "codex" ? [checkCodexInstalled(), checkCodexAuth()] : agent === "gemini" ? [checkGeminiInstalled(), checkGeminiAuth()] : [checkClaudeInstalled(), checkClaudeAuth()];
5255
+ const agentChecks = agent === "codex" ? [checkCodexInstalled(), checkCodexAuth()] : agent === "antigravity" ? [checkAntigravityInstalled(), checkAntigravityAuth()] : [checkClaudeInstalled(), checkClaudeAuth()];
5103
5256
  const checks = await Promise.all([
5104
5257
  checkGitInstalled(),
5105
5258
  checkNodeVersion(),
@@ -5117,7 +5270,7 @@ var setupCommand = new Command16("setup").description("Check that all dependenci
5117
5270
  if (allOk) {
5118
5271
  console.log(paint5("green", " All checks passed! You're ready to run mr watch."));
5119
5272
  if (agent === "claude") {
5120
- console.log(paint5("dim", " Tip: check other agents with --agent codex or --agent gemini"));
5273
+ console.log(paint5("dim", " Tip: check other agents with --agent codex or --agent antigravity"));
5121
5274
  }
5122
5275
  console.log("");
5123
5276
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dunnewold-labs/mr-manager",
3
- "version": "0.4.46",
3
+ "version": "0.4.50",
4
4
  "description": "Mr. Manager - Task and project management CLI",
5
5
  "bin": {
6
6
  "mr": "./dist/index.mjs"