vibeview 0.39.0 → 0.40.0
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/{chunk-MYXUGCR5.js → chunk-GDTLLJFL.js} +14 -1
- package/dist/{chunk-EQN3X62U.js → chunk-MK3WDPVZ.js} +1 -1
- package/dist/cli.js +31 -9
- package/dist/{server-GEY4WZUH.js → server-56MR4KJW.js} +83 -25
- package/dist/{upload-app-UHJLFQKZ.js → upload-app-ZEQMGZIT.js} +1 -1
- package/package.json +1 -1
|
@@ -274,7 +274,7 @@ var ApiClient = class {
|
|
|
274
274
|
}
|
|
275
275
|
async getRunStatus(runId) {
|
|
276
276
|
const data = await this.request("GET", `/runs/${runId}`);
|
|
277
|
-
return { ...data, url: data
|
|
277
|
+
return { ...data, url: runResultUrl(data, `${this.baseUrl}/tests/runs/${runId}`) };
|
|
278
278
|
}
|
|
279
279
|
/** Request without /ci prefix — for non-CI endpoints like /apps, /tests */
|
|
280
280
|
async requestRaw(method, path, body) {
|
|
@@ -772,6 +772,13 @@ var ApiClient = class {
|
|
|
772
772
|
return this.requestRaw("POST", `/webhooks/${id}/test`);
|
|
773
773
|
}
|
|
774
774
|
};
|
|
775
|
+
function runResultUrl(data, fallback) {
|
|
776
|
+
const url = data.run_url ?? fallback;
|
|
777
|
+
if (!data.retry_run_id) return url;
|
|
778
|
+
const firstId = url.slice(url.lastIndexOf("/") + 1);
|
|
779
|
+
if (!firstId) return url;
|
|
780
|
+
return `${url.slice(0, url.length - firstId.length)}${encodeURIComponent(data.retry_run_id)}`;
|
|
781
|
+
}
|
|
775
782
|
function parseContentDispositionFilename(header) {
|
|
776
783
|
if (!header) return void 0;
|
|
777
784
|
const extMatch = header.match(/filename\*\s*=\s*[^']*''([^;]+)/i);
|
|
@@ -870,6 +877,10 @@ function createSpinner(text, opts = {}) {
|
|
|
870
877
|
var EXIT_PASS = 0;
|
|
871
878
|
var EXIT_TEST_FAIL = 1;
|
|
872
879
|
var EXIT_INFRA_ERROR = 2;
|
|
880
|
+
var RUN_COMMANDS = ["run-test", "run-suite", "run-suites"];
|
|
881
|
+
function runCommandParseExitCode(err) {
|
|
882
|
+
return err.exitCode === 0 ? EXIT_PASS : EXIT_INFRA_ERROR;
|
|
883
|
+
}
|
|
873
884
|
var UsageError = class extends Error {
|
|
874
885
|
constructor(message) {
|
|
875
886
|
super(message);
|
|
@@ -1015,6 +1026,8 @@ export {
|
|
|
1015
1026
|
EXIT_PASS,
|
|
1016
1027
|
EXIT_TEST_FAIL,
|
|
1017
1028
|
EXIT_INFRA_ERROR,
|
|
1029
|
+
RUN_COMMANDS,
|
|
1030
|
+
runCommandParseExitCode,
|
|
1018
1031
|
UsageError,
|
|
1019
1032
|
DEVICE_STATUS_FILTERS,
|
|
1020
1033
|
queueReasonCopy,
|
package/dist/cli.js
CHANGED
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
runAgentVerb,
|
|
31
31
|
runStepsNotChecked,
|
|
32
32
|
stepsNotCheckedLabel
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-MK3WDPVZ.js";
|
|
34
34
|
import {
|
|
35
35
|
ApiClient,
|
|
36
36
|
ApiStatusError,
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
EXIT_TEST_FAIL,
|
|
41
41
|
NetworkError,
|
|
42
42
|
NotFoundError,
|
|
43
|
+
RUN_COMMANDS,
|
|
43
44
|
UsageError,
|
|
44
45
|
createSpinner,
|
|
45
46
|
errorExitCode,
|
|
@@ -49,9 +50,10 @@ import {
|
|
|
49
50
|
queueReasonCopy,
|
|
50
51
|
reportCommandError,
|
|
51
52
|
resolveAuth,
|
|
53
|
+
runCommandParseExitCode,
|
|
52
54
|
saveConfig,
|
|
53
55
|
uploadApp
|
|
54
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-GDTLLJFL.js";
|
|
55
57
|
|
|
56
58
|
// src/cli.ts
|
|
57
59
|
import { createRequire } from "module";
|
|
@@ -125,9 +127,17 @@ function formatHuman(result, verbose) {
|
|
|
125
127
|
const notCheckedCases = cases.filter((t) => outcomeOf(t) === "not_checked").length;
|
|
126
128
|
const total = cases.length;
|
|
127
129
|
const notCheckedNote = notCheckedCases > 0 ? `, ${notCheckedCases} not checked` : notChecked > 0 ? `, ${stepsNotCheckedLabel(notChecked)}` : "";
|
|
128
|
-
|
|
130
|
+
const quarantined = cases.filter((t) => t.status === "failed" && t.quarantined === true).length;
|
|
131
|
+
const quarantinedNote = quarantined > 0 && (result.test_runs ?? []).length > 0 ? `, ${quarantined} failed (quarantined)` : "";
|
|
132
|
+
lines.push(
|
|
133
|
+
` ${chalk.dim(`${passed}/${total} cases passed${notCheckedNote}${quarantinedNote}`)}`
|
|
134
|
+
);
|
|
129
135
|
if (result.url) {
|
|
130
|
-
lines.push(` ${chalk.dim(result.url)}`);
|
|
136
|
+
lines.push(` ${chalk.dim(result.first_attempt ? `Retry: ${result.url}` : result.url)}`);
|
|
137
|
+
}
|
|
138
|
+
if (result.first_attempt) {
|
|
139
|
+
const first = result.first_attempt;
|
|
140
|
+
lines.push(` ${chalk.dim(`First attempt (${first.status ?? "unknown"}): ${first.url}`)}`);
|
|
131
141
|
}
|
|
132
142
|
const suiteCases = result.test_runs ?? [];
|
|
133
143
|
if (suiteCases.length > 0) {
|
|
@@ -1181,15 +1191,16 @@ Aggregate: ${aggregate}${aggregateNote}
|
|
|
1181
1191
|
for (const cr of sr.case_runs ?? []) {
|
|
1182
1192
|
const outcome = outcomeOf(cr);
|
|
1183
1193
|
const neutral = outcome === "not_checked" || outcome === "not_run";
|
|
1184
|
-
const caseMark = neutral ? chalk5.gray("\u25CB") : cr.status === "passed" ? chalk5.green("\u2713") : cr.status === "cancelled" || cr.status === "pending" || cr.status === "running" ? chalk5.yellow("-") : chalk5.red("\u2717");
|
|
1194
|
+
const caseMark = neutral ? chalk5.gray("\u25CB") : cr.status === "passed" ? chalk5.green("\u2713") : cr.status === "cancelled" || cr.status === "pending" || cr.status === "running" ? chalk5.yellow("-") : cr.status === "failed" && cr.quarantined === true ? chalk5.yellow("\u2717") : chalk5.red("\u2717");
|
|
1185
1195
|
const dur = cr.duration_ms ? chalk5.dim(` (${(cr.duration_ms / 1e3).toFixed(1)}s)`) : "";
|
|
1186
1196
|
const unchecked = notCheckedSuffix(outcome, cr.steps_not_checked);
|
|
1187
|
-
const
|
|
1197
|
+
const quarantined = cr.status === "failed" && cr.quarantined === true;
|
|
1198
|
+
const label = neutral ? outcome.replace("_", " ") : quarantined ? "failed (quarantined)" : cr.status ?? "?";
|
|
1188
1199
|
process.stdout.write(
|
|
1189
1200
|
` ${caseMark} ${cr.case_name ?? "Case"} ${label}${unchecked ? chalk5.yellow(unchecked) : ""}${dur} ${chalk5.dim(cr.url)}
|
|
1190
1201
|
`
|
|
1191
1202
|
);
|
|
1192
|
-
if (cr.error_message && neutral) {
|
|
1203
|
+
if (cr.error_message && (neutral || cr.status === "failed" && cr.quarantined === true)) {
|
|
1193
1204
|
process.stdout.write(` ${chalk5.dim(cr.error_message)}
|
|
1194
1205
|
`);
|
|
1195
1206
|
} else if (cr.error_message && cr.status !== "passed") {
|
|
@@ -1205,7 +1216,15 @@ Aggregate: ${aggregate}${aggregateNote}
|
|
|
1205
1216
|
}
|
|
1206
1217
|
function suiteCounts(sr) {
|
|
1207
1218
|
const nc = sr.not_checked_tests ?? 0;
|
|
1208
|
-
|
|
1219
|
+
const q = Math.min(
|
|
1220
|
+
sr.failed,
|
|
1221
|
+
sr.quarantined_failures ?? (sr.case_runs ?? []).filter((c) => c.status === "failed" && c.quarantined === true).length
|
|
1222
|
+
);
|
|
1223
|
+
const parts = [`${sr.passed} passed`];
|
|
1224
|
+
if (q === 0 || sr.failed > q) parts.push(`${sr.failed - q} failed`);
|
|
1225
|
+
if (q > 0) parts.push(`${q} failed (quarantined)`);
|
|
1226
|
+
if (nc > 0) parts.push(`${nc} not checked`);
|
|
1227
|
+
return parts.join(", ");
|
|
1209
1228
|
}
|
|
1210
1229
|
|
|
1211
1230
|
// src/commands/run-test.ts
|
|
@@ -4387,7 +4406,7 @@ program.command("dev-reload").description(
|
|
|
4387
4406
|
program.command("mcp").description(
|
|
4388
4407
|
"Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop/dev_reload as tools"
|
|
4389
4408
|
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
|
|
4390
|
-
const { runMcpServer } = await import("./server-
|
|
4409
|
+
const { runMcpServer } = await import("./server-56MR4KJW.js");
|
|
4391
4410
|
await runMcpServer({ ...opts, version: pkg.version });
|
|
4392
4411
|
});
|
|
4393
4412
|
program.command("agent-setup").description(
|
|
@@ -4411,6 +4430,9 @@ ${pressButtonsHelp()}`);
|
|
|
4411
4430
|
return runAgentVerb(cmd, positionals, opts);
|
|
4412
4431
|
});
|
|
4413
4432
|
}
|
|
4433
|
+
for (const name of RUN_COMMANDS) {
|
|
4434
|
+
program.commands.find((c) => c.name() === name)?.exitOverride((err) => process.exit(runCommandParseExitCode(err)));
|
|
4435
|
+
}
|
|
4414
4436
|
if (updateCheckAllowed(process.argv, process.env, process.stderr.isTTY)) {
|
|
4415
4437
|
void checkForUpdate(pkg.version, { print: (line) => process.stderr.write(`${line}
|
|
4416
4438
|
`) });
|
|
@@ -23,18 +23,19 @@ import {
|
|
|
23
23
|
outcomeOf,
|
|
24
24
|
parseBuildId,
|
|
25
25
|
parseIdleWarningSeconds,
|
|
26
|
+
platformLabel,
|
|
26
27
|
platformWithArticle,
|
|
27
28
|
readDevState,
|
|
28
29
|
recordDevUse,
|
|
29
30
|
renderEnvelope,
|
|
30
31
|
resolveMetro,
|
|
31
32
|
startDevSession
|
|
32
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-MK3WDPVZ.js";
|
|
33
34
|
import {
|
|
34
35
|
ApiClient,
|
|
35
36
|
NotFoundError,
|
|
36
37
|
resolveAuth
|
|
37
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-GDTLLJFL.js";
|
|
38
39
|
|
|
39
40
|
// src/mcp/server.ts
|
|
40
41
|
import { writeFileSync } from "fs";
|
|
@@ -59,7 +60,12 @@ var CREATE_TEST_DEF = {
|
|
|
59
60
|
app: { type: "string", description: "App id (from list_apps)." },
|
|
60
61
|
name: { type: "string", description: "Test name." },
|
|
61
62
|
steps: { type: "string", description: "The steps, one per line." },
|
|
62
|
-
group: { type: "string", description: "Group id to add the test to (optional)." }
|
|
63
|
+
group: { type: "string", description: "Group id to add the test to (optional)." },
|
|
64
|
+
if_exists: {
|
|
65
|
+
type: "string",
|
|
66
|
+
description: "When a test with this name already exists for the app: update replaces it, new creates another one beside it, fail refuses. Omitted: an identical test is reused, a different one is refused.",
|
|
67
|
+
enum: ["fail", "update", "new"]
|
|
68
|
+
}
|
|
63
69
|
},
|
|
64
70
|
required: ["app", "name", "steps"]
|
|
65
71
|
};
|
|
@@ -72,7 +78,7 @@ var RUN_TEST_DEF = {
|
|
|
72
78
|
session_id: { type: "string", description: "Run on this session instead of a new device." },
|
|
73
79
|
device_model: {
|
|
74
80
|
type: "string",
|
|
75
|
-
description: "Device model for a new device (from list_device_models); omitted: any free one."
|
|
81
|
+
description: "Device model for a new device (from list_device_models); omitted: any free one. A model of another platform (same form factor) runs the test's app for that platform: the app linked in its app family, else the app with the same bundle id / package. The reply names the platform and app the run went to."
|
|
76
82
|
},
|
|
77
83
|
ai_use: AI_USE_PROPERTY,
|
|
78
84
|
start_from_current_screen: {
|
|
@@ -94,7 +100,7 @@ var RUN_GROUP_DEF = {
|
|
|
94
100
|
session_id: { type: "string", description: "Run on this session instead of a new device." },
|
|
95
101
|
device_model: {
|
|
96
102
|
type: "string",
|
|
97
|
-
description: "Device model for a new device; omitted: any free one."
|
|
103
|
+
description: "Device model for a new device; omitted: any free one. A model of another platform (same form factor) runs the group's app for that platform: the app linked in its app family, else the app with the same bundle id / package. The reply names the platform and app the run went to."
|
|
98
104
|
},
|
|
99
105
|
ai_use: AI_USE_PROPERTY,
|
|
100
106
|
start_from_current_screen: {
|
|
@@ -347,6 +353,14 @@ async function explainInactive(sessionId, deps) {
|
|
|
347
353
|
function findCommand(toolName) {
|
|
348
354
|
return AGENT_COMMANDS.find((c) => toolNameFor(c.verb) === toolName);
|
|
349
355
|
}
|
|
356
|
+
function testToolErrorMessage(err) {
|
|
357
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
358
|
+
if (err instanceof NotFoundError && message.startsWith("Not found: ")) {
|
|
359
|
+
const detail = message.slice("Not found: ".length);
|
|
360
|
+
if (/not found/i.test(detail)) return detail;
|
|
361
|
+
}
|
|
362
|
+
return message;
|
|
363
|
+
}
|
|
350
364
|
async function handleCreateTest(args, deps) {
|
|
351
365
|
const steps = String(args.steps ?? "").split("\n").map((s) => s.trim()).filter(Boolean).map((text) => ({ text }));
|
|
352
366
|
if (!steps.length) return errorResult("create_test: `steps` needs at least one line");
|
|
@@ -355,7 +369,8 @@ async function handleCreateTest(args, deps) {
|
|
|
355
369
|
name: String(args.name),
|
|
356
370
|
app_id: String(args.app),
|
|
357
371
|
steps,
|
|
358
|
-
group_ids: typeof args.group === "string" && args.group ? [args.group] : void 0
|
|
372
|
+
group_ids: typeof args.group === "string" && args.group ? [args.group] : void 0,
|
|
373
|
+
if_exists: args.if_exists === "fail" || args.if_exists === "update" || args.if_exists === "new" ? args.if_exists : void 0
|
|
359
374
|
});
|
|
360
375
|
const verb = created.outcome === "unchanged" ? "Test already exists, unchanged" : created.outcome === "updated" ? "Updated existing test" : "Created test";
|
|
361
376
|
return textResult(
|
|
@@ -363,9 +378,17 @@ async function handleCreateTest(args, deps) {
|
|
|
363
378
|
created
|
|
364
379
|
);
|
|
365
380
|
} catch (err) {
|
|
366
|
-
return errorResult(`error: ${
|
|
381
|
+
return errorResult(`error: ${testToolErrorMessage(err)}`);
|
|
367
382
|
}
|
|
368
383
|
}
|
|
384
|
+
function runDestination(lanes) {
|
|
385
|
+
return lanes.map((l) => {
|
|
386
|
+
const label = platformLabel(l.platform);
|
|
387
|
+
const device = l.model ? ` (${l.model})` : "";
|
|
388
|
+
const app = l.app_name ? `: ${l.app_name}, the ${label} app` : "";
|
|
389
|
+
return ` Running on ${label}${device}${app}.`;
|
|
390
|
+
}).join("");
|
|
391
|
+
}
|
|
369
392
|
async function handleRun(kind, args, deps) {
|
|
370
393
|
const id = String(kind === "test" ? args.test : args.group);
|
|
371
394
|
const mode = typeof args.ai_use === "string" ? AI_USE_MODE[args.ai_use] : void 0;
|
|
@@ -387,12 +410,13 @@ async function handleRun(kind, args, deps) {
|
|
|
387
410
|
kind === "test" ? { target: "test", test_ids: [id], devices, mode } : { target: "group", group_id: id, devices, mode }
|
|
388
411
|
);
|
|
389
412
|
const queued = r.lanes.some((l) => l.status === "queued") ? " It is waiting for a free device." : "";
|
|
413
|
+
const where = runDestination(r.lanes);
|
|
390
414
|
return textResult(
|
|
391
|
-
`Started run ${r.batch_id}.${queued} Check it with get_run run_id=${r.batch_id}.`,
|
|
415
|
+
`Started run ${r.batch_id}.${where}${queued} Check it with get_run run_id=${r.batch_id}.`,
|
|
392
416
|
r
|
|
393
417
|
);
|
|
394
418
|
} catch (err) {
|
|
395
|
-
return errorResult(`error: ${
|
|
419
|
+
return errorResult(`error: ${testToolErrorMessage(err)}`);
|
|
396
420
|
}
|
|
397
421
|
}
|
|
398
422
|
function notCheckedNote(status, n) {
|
|
@@ -407,11 +431,25 @@ function agentStatus(r) {
|
|
|
407
431
|
function testsNotChecked(n) {
|
|
408
432
|
return typeof n === "number" && n > 0 ? `, ${n} not checked` : "";
|
|
409
433
|
}
|
|
434
|
+
function quarantinedFailure(t) {
|
|
435
|
+
return t?.status === "failed" && t?.quarantined === true;
|
|
436
|
+
}
|
|
437
|
+
function quarantinedCount(n) {
|
|
438
|
+
return typeof n === "number" && n > 0 ? `, ${n} failed (quarantined)` : "";
|
|
439
|
+
}
|
|
410
440
|
function formatRunForAgent(run) {
|
|
411
441
|
const lines = [];
|
|
412
442
|
if (run.lanes) {
|
|
443
|
+
const rowQuarantined = (r) => typeof r.tests_quarantined === "number" ? r.tests_quarantined : (r.tests ?? []).filter(quarantinedFailure).length;
|
|
444
|
+
let totalQuarantined = run.summary.tests_quarantined;
|
|
445
|
+
if (typeof totalQuarantined !== "number") {
|
|
446
|
+
totalQuarantined = 0;
|
|
447
|
+
for (const lane of run.lanes) {
|
|
448
|
+
for (const r of lane.runs ?? []) totalQuarantined += rowQuarantined(r);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
413
451
|
lines.push(
|
|
414
|
-
`${run.name}: ${agentStatus(run)} \u2014 ${run.summary.tests_passed} of ${run.summary.tests_total} tests passed${testsNotChecked(run.summary.tests_not_checked)}`
|
|
452
|
+
`${run.name}: ${agentStatus(run)} \u2014 ${run.summary.tests_passed} of ${run.summary.tests_total} tests passed${testsNotChecked(run.summary.tests_not_checked)}${quarantinedCount(totalQuarantined)}`
|
|
415
453
|
);
|
|
416
454
|
for (const lane of run.lanes) {
|
|
417
455
|
const detail = [lane.platform, lane.os_version].filter(Boolean).join(" ");
|
|
@@ -422,10 +460,19 @@ function formatRunForAgent(run) {
|
|
|
422
460
|
if (lane.error_message) head += ` \u2014 ${lane.error_message}`;
|
|
423
461
|
lines.push(head);
|
|
424
462
|
for (const r of lane.runs ?? []) {
|
|
425
|
-
const count = r.kind === "group" && typeof r.tests_total === "number" ? ` (${r.tests_passed ?? 0}/${r.tests_total} tests passed${testsNotChecked(r.tests_not_checked)})` : "";
|
|
463
|
+
const count = r.kind === "group" && typeof r.tests_total === "number" ? ` (${r.tests_passed ?? 0}/${r.tests_total} tests passed${testsNotChecked(r.tests_not_checked)}${quarantinedCount(rowQuarantined(r))})` : "";
|
|
464
|
+
const q = r.kind === "test" && quarantinedFailure(r.tests?.[0] ?? {}) ? " (quarantined)" : "";
|
|
426
465
|
lines.push(
|
|
427
|
-
` ${r.name}: ${agentStatus(r)}${count}${r.error_message ? ` \u2014 ${r.error_message}` : ""}`
|
|
466
|
+
` ${r.name}: ${agentStatus(r)}${q}${count}${r.error_message ? ` \u2014 ${r.error_message}` : ""}`
|
|
428
467
|
);
|
|
468
|
+
if (r.kind !== "group") continue;
|
|
469
|
+
for (const t of r.tests ?? []) {
|
|
470
|
+
if (t.status !== "failed" && t.status !== "error") continue;
|
|
471
|
+
const tq = quarantinedFailure(t) ? " (quarantined)" : "";
|
|
472
|
+
lines.push(
|
|
473
|
+
` ${t.name}: ${t.status}${tq}${t.error_message ? ` \u2014 ${t.error_message}` : ""}`
|
|
474
|
+
);
|
|
475
|
+
}
|
|
429
476
|
}
|
|
430
477
|
}
|
|
431
478
|
} else if (Array.isArray(run.test_runs)) {
|
|
@@ -463,7 +510,7 @@ async function handleGetRun(args, deps) {
|
|
|
463
510
|
const run = kind.kind === "batch" ? await client.getRunBatch(id) : await client.getRunStatus(id);
|
|
464
511
|
return textResult(formatRunForAgent(run), run);
|
|
465
512
|
} catch (err) {
|
|
466
|
-
return errorResult(`error: ${
|
|
513
|
+
return errorResult(`error: ${testToolErrorMessage(err)}`);
|
|
467
514
|
}
|
|
468
515
|
}
|
|
469
516
|
async function dispatchToolCall(toolName, args, deps, extra = {}) {
|
|
@@ -882,7 +929,7 @@ async function handleUploadApp(args, deps) {
|
|
|
882
929
|
}
|
|
883
930
|
const name = typeof args.name === "string" && args.name ? args.name : void 0;
|
|
884
931
|
const upload = deps.uploadApp ?? (async (f, n) => {
|
|
885
|
-
const { uploadAppBuild } = await import("./upload-app-
|
|
932
|
+
const { uploadAppBuild } = await import("./upload-app-ZEQMGZIT.js");
|
|
886
933
|
const { result: result2 } = await uploadAppBuild(f, { ...deps.auth, name: n });
|
|
887
934
|
return result2;
|
|
888
935
|
});
|
|
@@ -1278,6 +1325,23 @@ var MCP_INSTRUCTIONS = [
|
|
|
1278
1325
|
"A session stopped elsewhere is reported as ended; call dev_start again for a new one.",
|
|
1279
1326
|
'A session nobody uses ends after the idle timeout. A result that ends with "Session \u2026 ends in N s for inactivity" means act (tap, type, swipe, press) to keep it, or dev_stop if you are done; ui_tree, screenshot, logs, find and wait do not count as use.'
|
|
1280
1327
|
].join("\n");
|
|
1328
|
+
function toolInputSchema(def, z) {
|
|
1329
|
+
const shape = {};
|
|
1330
|
+
for (const [key, prop] of Object.entries(def.properties)) {
|
|
1331
|
+
const base = prop.enum ? z.enum(prop.enum) : z[prop.type]();
|
|
1332
|
+
const described = base.describe(prop.description);
|
|
1333
|
+
shape[key] = def.required.includes(key) ? described : described.optional();
|
|
1334
|
+
}
|
|
1335
|
+
const valid = Object.keys(def.properties);
|
|
1336
|
+
return z.strictObject(shape, {
|
|
1337
|
+
error: (issue) => {
|
|
1338
|
+
if (issue.code !== "unrecognized_keys") return void 0;
|
|
1339
|
+
const unknown = issue.keys.map((k) => `"${k}"`).join(", ");
|
|
1340
|
+
const takes = valid.length ? `${def.name} takes: ${valid.join(", ")}.` : `${def.name} takes no arguments.`;
|
|
1341
|
+
return `Unknown argument${issue.keys.length === 1 ? "" : "s"} ${unknown}. ${takes}`;
|
|
1342
|
+
}
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1281
1345
|
async function createMcpServer(opts) {
|
|
1282
1346
|
const [{ McpServer }, { z }] = await Promise.all([
|
|
1283
1347
|
import("@modelcontextprotocol/sdk/server/mcp.js"),
|
|
@@ -1296,21 +1360,12 @@ async function createMcpServer(opts) {
|
|
|
1296
1360
|
{ name: "vibeview", version: opts.version ?? "0.0.0" },
|
|
1297
1361
|
{ instructions: MCP_INSTRUCTIONS }
|
|
1298
1362
|
);
|
|
1299
|
-
function zodShape(def) {
|
|
1300
|
-
const shape = {};
|
|
1301
|
-
for (const [key, prop] of Object.entries(def.properties)) {
|
|
1302
|
-
const base = prop.enum ? z.enum(prop.enum) : z[prop.type]();
|
|
1303
|
-
const described = base.describe(prop.description);
|
|
1304
|
-
shape[key] = def.required.includes(key) ? described : described.optional();
|
|
1305
|
-
}
|
|
1306
|
-
return shape;
|
|
1307
|
-
}
|
|
1308
1363
|
for (const def of buildToolDefinitions()) {
|
|
1309
1364
|
server.registerTool(
|
|
1310
1365
|
def.name,
|
|
1311
1366
|
{
|
|
1312
1367
|
description: def.description,
|
|
1313
|
-
inputSchema:
|
|
1368
|
+
inputSchema: toolInputSchema(def, z),
|
|
1314
1369
|
annotations: { readOnlyHint: !def.mutating }
|
|
1315
1370
|
},
|
|
1316
1371
|
async (args, extra) => {
|
|
@@ -1397,7 +1452,10 @@ export {
|
|
|
1397
1452
|
idleNoticeText,
|
|
1398
1453
|
mcpText,
|
|
1399
1454
|
progressReporter,
|
|
1455
|
+
runDestination,
|
|
1400
1456
|
runMcpServer,
|
|
1401
1457
|
startProgress,
|
|
1402
|
-
startedDevice
|
|
1458
|
+
startedDevice,
|
|
1459
|
+
testToolErrorMessage,
|
|
1460
|
+
toolInputSchema
|
|
1403
1461
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibeview",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.40.0",
|
|
4
4
|
"description": "VibeView CLI -- run your app on cloud iOS simulators, Android emulators, Apple TV, Android TV and Roku devices: a React Native dev loop, device control for you or your coding agent (CLI or MCP), and tests from CI",
|
|
5
5
|
"mcpName": "io.github.vibeview/vibeview",
|
|
6
6
|
"type": "module",
|