
























@@ -4,6 +4,11 @@ import type { MantisDesktopBrowserSmokeOptions } from "./desktop-browser-smoke.r
44import type { MantisDiscordSmokeOptions } from "./discord-smoke.runtime.js";
55import type { MantisBeforeAfterOptions } from "./run.runtime.js";
66import type { MantisSlackDesktopSmokeOptions } from "./slack-desktop-smoke.runtime.js";
7+import type {
8+MantisVisualDriverOptions,
9+MantisVisualTaskOptions,
10+MantisVisualTaskVisionMode,
11+} from "./visual-task.runtime.js";
712813type MantisCliRuntime = typeof import("./cli.runtime.js");
914@@ -31,6 +36,16 @@ async function runSlackDesktopSmoke(opts: MantisSlackDesktopSmokeOptions) {
3136await runtime.runMantisSlackDesktopSmokeCommand(opts);
3237}
333839+async function runVisualDriver(opts: MantisVisualDriverOptions) {
40+const runtime = await loadMantisCliRuntime();
41+await runtime.runMantisVisualDriverCommand(opts);
42+}
43+44+async function runVisualTask(opts: MantisVisualTaskOptions) {
45+const runtime = await loadMantisCliRuntime();
46+await runtime.runMantisVisualTaskCommand(opts);
47+}
48+3449type MantisDiscordSmokeCommanderOptions = {
3550channelId?: string;
3651guildId?: string;
@@ -96,10 +111,57 @@ type MantisSlackDesktopSmokeCommanderOptions = {
96111ttl?: string;
97112};
98113114+type MantisVisualTaskCommanderOptions = {
115+browserUrl?: string;
116+class?: string;
117+crabboxBin?: string;
118+duration?: string;
119+expectText?: string;
120+idleTimeout?: string;
121+keepLease?: boolean;
122+leaseId?: string;
123+machineClass?: string;
124+outputDir?: string;
125+provider?: string;
126+repoRoot?: string;
127+settleMs?: string;
128+ttl?: string;
129+visionMode?: MantisVisualTaskVisionMode;
130+visionModel?: string;
131+visionPrompt?: string;
132+visionTimeoutMs?: string;
133+};
134+135+type MantisVisualDriverCommanderOptions = {
136+browserUrl?: string;
137+crabboxBin?: string;
138+expectText?: string;
139+leaseId?: string;
140+outputDir?: string;
141+provider?: string;
142+repoRoot?: string;
143+settleMs?: string;
144+visionMode?: MantisVisualTaskVisionMode;
145+visionModel?: string;
146+visionPrompt?: string;
147+visionTimeoutMs?: string;
148+};
149+99150function collectString(value: string, previous: string[] = []) {
100151return [...previous, value];
101152}
102153154+function parseOptionalInteger(value: string | undefined, label: string) {
155+if (value === undefined) {
156+return undefined;
157+}
158+const parsed = Number.parseInt(value, 10);
159+if (!Number.isFinite(parsed) || String(parsed) !== value || parsed < 0) {
160+throw new Error(`${label} must be a non-negative integer`);
161+}
162+return parsed;
163+}
164+103165export function registerMantisCli(qa: Command) {
104166const mantis = qa
105167.command("mantis")
@@ -166,7 +228,7 @@ export function registerMantisCli(qa: Command) {
166228mantis
167229.command("desktop-browser-smoke")
168230.description(
169-"Lease or reuse a Crabbox desktop, open a visible browser, and capture a VNC desktop screenshot",
231+"Lease or reuse a Crabbox desktop, open a visible browser, and capture VNC desktop screenshot/video artifacts",
170232)
171233.option("--repo-root <path>", "Repository root to target when running from a neutral cwd")
172234.option("--output-dir <path>", "Mantis desktop browser artifact directory")
@@ -199,7 +261,7 @@ export function registerMantisCli(qa: Command) {
199261mantis
200262.command("slack-desktop-smoke")
201263.description(
202-"Lease or reuse a Crabbox VNC desktop, run Slack QA inside it, open Slack in the browser, and capture a screenshot",
264+"Lease or reuse a Crabbox VNC desktop, run Slack QA inside it, open Slack in the browser, and capture screenshot/video artifacts",
203265)
204266.option("--repo-root <path>", "Repository root to target when running from a neutral cwd")
205267.option("--output-dir <path>", "Mantis Slack desktop artifact directory")
@@ -249,4 +311,83 @@ export function registerMantisCli(qa: Command) {
249311ttl: opts.ttl,
250312});
251313});
314+315+mantis
316+.command("visual-task")
317+.description(
318+"Lease or reuse a Crabbox desktop, drive visible browser UI, record MP4, screenshot it, and optionally run image-understanding assertions",
319+)
320+.option("--repo-root <path>", "Repository root to target when running from a neutral cwd")
321+.option("--output-dir <path>", "Mantis visual-task artifact directory")
322+.option("--crabbox-bin <path>", "Crabbox binary path")
323+.option("--provider <provider>", "Crabbox provider")
324+.option("--machine-class <class>", "Crabbox machine class")
325+.option("--class <class>", "Alias for --machine-class")
326+.option("--lease-id <id>", "Reuse an existing Crabbox lease")
327+.option("--idle-timeout <duration>", "Crabbox idle timeout")
328+.option("--ttl <duration>", "Crabbox maximum lease lifetime")
329+.option("--keep-lease", "Keep a lease created by this run after a passing task")
330+.option("--browser-url <url>", "URL to open in the visible browser")
331+.option("--duration <duration>", "Desktop recording duration")
332+.option("--settle-ms <ms>", "Milliseconds to wait after launch before screenshot")
333+.option("--vision-mode <mode>", "Vision mode: image-describe or metadata")
334+.option("--vision-prompt <text>", "Prompt for image understanding")
335+.option("--vision-model <provider/model>", "Image-capable provider/model ref")
336+.option("--vision-timeout-ms <ms>", "Image understanding timeout in milliseconds")
337+.option("--expect-text <text>", "Case-insensitive text expected in the vision output")
338+.action(async (opts: MantisVisualTaskCommanderOptions) => {
339+await runVisualTask({
340+browserUrl: opts.browserUrl,
341+crabboxBin: opts.crabboxBin,
342+duration: opts.duration,
343+expectText: opts.expectText,
344+idleTimeout: opts.idleTimeout,
345+keepLease: opts.keepLease,
346+leaseId: opts.leaseId,
347+machineClass: opts.machineClass ?? opts.class,
348+outputDir: opts.outputDir,
349+provider: opts.provider,
350+repoRoot: opts.repoRoot,
351+settleMs: parseOptionalInteger(opts.settleMs, "--settle-ms"),
352+ttl: opts.ttl,
353+visionMode: opts.visionMode,
354+visionModel: opts.visionModel,
355+visionPrompt: opts.visionPrompt,
356+visionTimeoutMs: parseOptionalInteger(opts.visionTimeoutMs, "--vision-timeout-ms"),
357+});
358+});
359+360+mantis
361+.command("visual-driver")
362+.description(
363+"Driver half for Mantis visual-task; launched by Crabbox record --while, then opens browser, screenshots, and runs vision",
364+)
365+.option("--repo-root <path>", "Repository root to target when running from a neutral cwd")
366+.option("--output-dir <path>", "Mantis visual-task artifact directory")
367+.option("--crabbox-bin <path>", "Crabbox binary path")
368+.option("--provider <provider>", "Crabbox provider")
369+.option("--lease-id <id>", "Crabbox lease id")
370+.option("--browser-url <url>", "URL to open in the visible browser")
371+.option("--settle-ms <ms>", "Milliseconds to wait after launch before screenshot")
372+.option("--vision-mode <mode>", "Vision mode: image-describe or metadata")
373+.option("--vision-prompt <text>", "Prompt for image understanding")
374+.option("--vision-model <provider/model>", "Image-capable provider/model ref")
375+.option("--vision-timeout-ms <ms>", "Image understanding timeout in milliseconds")
376+.option("--expect-text <text>", "Case-insensitive text expected in the vision output")
377+.action(async (opts: MantisVisualDriverCommanderOptions) => {
378+await runVisualDriver({
379+browserUrl: opts.browserUrl,
380+crabboxBin: opts.crabboxBin,
381+expectText: opts.expectText,
382+leaseId: opts.leaseId,
383+outputDir: opts.outputDir,
384+provider: opts.provider,
385+repoRoot: opts.repoRoot,
386+settleMs: parseOptionalInteger(opts.settleMs, "--settle-ms"),
387+visionMode: opts.visionMode,
388+visionModel: opts.visionModel,
389+visionPrompt: opts.visionPrompt,
390+visionTimeoutMs: parseOptionalInteger(opts.visionTimeoutMs, "--vision-timeout-ms"),
391+});
392+});
252393}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。