vibeview 0.26.0 → 0.27.1
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-JK3T3AMW.js → chunk-7IOGMM5W.js} +139 -38
- package/dist/{chunk-RFFCCPGZ.js → chunk-FTTWLCTE.js} +69 -5
- package/dist/cli.js +126 -68
- package/dist/{server-KYDTXPRZ.js → server-ZIUZ7U5T.js} +4 -4
- package/dist/{upload-app-E32EZXYQ.js → upload-app-MDKTSG4O.js} +1 -1
- package/package.json +1 -1
- package/skills/vibeview-agent/SKILL.md +7 -4
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ApiClient,
|
|
4
|
+
AuthError,
|
|
5
|
+
NotFoundError,
|
|
4
6
|
UsageError,
|
|
5
7
|
createSpinner,
|
|
8
|
+
errorExitCode,
|
|
9
|
+
formatUtcTimestamp,
|
|
6
10
|
loadConfig,
|
|
11
|
+
queueReasonCopy,
|
|
7
12
|
resolveAuth,
|
|
8
13
|
uploadAppBuild
|
|
9
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-FTTWLCTE.js";
|
|
10
15
|
|
|
11
16
|
// src/lib/dev-config.ts
|
|
12
17
|
import { readFileSync, writeFileSync } from "fs";
|
|
@@ -269,7 +274,7 @@ function formatBuildRowsHuman(app, rows) {
|
|
|
269
274
|
r.version ? `${r.version}${r.build_number ? ` (${r.build_number})` : ""}` : r.build_number ?? "",
|
|
270
275
|
r.platform,
|
|
271
276
|
r.kind,
|
|
272
|
-
r.uploaded_at
|
|
277
|
+
formatUtcTimestamp(r.uploaded_at),
|
|
273
278
|
(r.note ?? "").split("\n")[0]
|
|
274
279
|
]);
|
|
275
280
|
const widths = HEADERS.map((h, i) => Math.max(h.length, ...cells.map((c) => c[i].length)));
|
|
@@ -293,7 +298,7 @@ async function listBuilds(appId, opts, deps) {
|
|
|
293
298
|
const message = err instanceof Error ? err.message : String(err);
|
|
294
299
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
295
300
|
console.error(chalk.red(`error: ${message}`));
|
|
296
|
-
process.exitCode =
|
|
301
|
+
process.exitCode = errorExitCode(err);
|
|
297
302
|
}
|
|
298
303
|
}
|
|
299
304
|
|
|
@@ -1073,7 +1078,7 @@ async function buildCommand(opts, cloudDeps) {
|
|
|
1073
1078
|
if (!(err instanceof BuildFailError)) {
|
|
1074
1079
|
printFailure(opts, err instanceof Error ? err.message : String(err));
|
|
1075
1080
|
}
|
|
1076
|
-
process.exitCode =
|
|
1081
|
+
process.exitCode = errorExitCode(err);
|
|
1077
1082
|
}
|
|
1078
1083
|
}
|
|
1079
1084
|
function printFailure(opts, message, code) {
|
|
@@ -2182,6 +2187,11 @@ function parseWatchSeconds(flag) {
|
|
|
2182
2187
|
}
|
|
2183
2188
|
return seconds;
|
|
2184
2189
|
}
|
|
2190
|
+
function queuedLine(position, reasonMessage) {
|
|
2191
|
+
const where = position ? ` (position ${position})` : "";
|
|
2192
|
+
const why = reasonMessage ?? "Every device is busy.";
|
|
2193
|
+
return `\u23F3 ${why} Waiting in the queue${where}, Ctrl-C to give up.`;
|
|
2194
|
+
}
|
|
2185
2195
|
function emit(json, evt) {
|
|
2186
2196
|
if (json) console.log(JSON.stringify(evt));
|
|
2187
2197
|
}
|
|
@@ -2218,7 +2228,9 @@ async function startDevSession(cfg, onEvent = () => {
|
|
|
2218
2228
|
event: "queued",
|
|
2219
2229
|
session_id: sessionId,
|
|
2220
2230
|
position: started.position ?? null,
|
|
2221
|
-
queue_entry_id: queueEntryId
|
|
2231
|
+
queue_entry_id: queueEntryId,
|
|
2232
|
+
queue_reason: started.queue_reason ?? null,
|
|
2233
|
+
message: queueReasonCopy(started.queue_reason)
|
|
2222
2234
|
});
|
|
2223
2235
|
await pollWhileQueued(api, sessionId, QUEUE_WAIT_MS);
|
|
2224
2236
|
queueEntryId = null;
|
|
@@ -2309,6 +2321,10 @@ function findBlockingWarning(info) {
|
|
|
2309
2321
|
return (info.warnings ?? []).find((w) => BLOCKING_WARNING_PREFIXES.some((p) => w.includes(p)));
|
|
2310
2322
|
}
|
|
2311
2323
|
var QUEUE_WAIT_MS = 30 * 6e4;
|
|
2324
|
+
function sessionEndedMessage(info, when) {
|
|
2325
|
+
const reason = info.status === "failed" && info.failure_reason ? `: ${info.failure_reason}` : "";
|
|
2326
|
+
return `session ${info.status} ${when}${reason}`;
|
|
2327
|
+
}
|
|
2312
2328
|
async function pollWhileQueued(api, sessionId, timeoutMs, intervalMs = 3e3) {
|
|
2313
2329
|
const deadline = Date.now() + timeoutMs;
|
|
2314
2330
|
for (; ; ) {
|
|
@@ -2316,7 +2332,7 @@ async function pollWhileQueued(api, sessionId, timeoutMs, intervalMs = 3e3) {
|
|
|
2316
2332
|
const blocking = findBlockingWarning(info);
|
|
2317
2333
|
if (blocking) throw new Error(blocking);
|
|
2318
2334
|
if (info.status === "stopped" || info.status === "failed") {
|
|
2319
|
-
throw new Error(
|
|
2335
|
+
throw new Error(sessionEndedMessage(info, "while waiting for a device"));
|
|
2320
2336
|
}
|
|
2321
2337
|
if (info.status !== "pending" && info.status !== "queued") return info;
|
|
2322
2338
|
if (Date.now() > deadline) {
|
|
@@ -2335,7 +2351,7 @@ async function pollUntilTunnelReady(api, sessionId, timeoutMs, intervalMs = 2e3)
|
|
|
2335
2351
|
if (blocking) throw new Error(blocking);
|
|
2336
2352
|
if (info.tunnel_url) return info;
|
|
2337
2353
|
if (info.status === "stopped" || info.status === "failed") {
|
|
2338
|
-
throw new Error(
|
|
2354
|
+
throw new Error(sessionEndedMessage(info, "before the tunnel came up"));
|
|
2339
2355
|
}
|
|
2340
2356
|
if (Date.now() > deadline) throw new Error("timed out waiting for the device to start");
|
|
2341
2357
|
await new Promise((r) => setTimeout(r, intervalMs));
|
|
@@ -2354,7 +2370,7 @@ async function pollUntilActive(api, sessionId, timeoutMs, intervalMs = 2e3, onIn
|
|
|
2354
2370
|
}
|
|
2355
2371
|
if (info.status === "active" && !info.app_install_pending) return info;
|
|
2356
2372
|
if (info.status === "stopped" || info.status === "failed") {
|
|
2357
|
-
throw new Error(
|
|
2373
|
+
throw new Error(sessionEndedMessage(info, "before it became active"));
|
|
2358
2374
|
}
|
|
2359
2375
|
if (Date.now() > deadline) {
|
|
2360
2376
|
throw new Error("timed out waiting for the session to become active");
|
|
@@ -2404,12 +2420,29 @@ function validateAppChoice(requested, fromFlag, apps) {
|
|
|
2404
2420
|
if (apps.some((a) => a.public_id === requested)) return { appId: requested };
|
|
2405
2421
|
return fromFlag ? { staleFlag: requested } : { staleCached: requested };
|
|
2406
2422
|
}
|
|
2423
|
+
function staleAppFlagError(requested, platform, allApps) {
|
|
2424
|
+
const other = allApps.find((a) => a.public_id === requested);
|
|
2425
|
+
if (other) {
|
|
2426
|
+
return {
|
|
2427
|
+
message: `app "${requested}" is ${platformWithArticle(other.platform)} app, not ${platformWithArticle(platform)} app.`,
|
|
2428
|
+
hint: `Run \`vibeview dev --platform ${other.platform} --app ${requested}\`, or \`vibeview dev --platform ${platform}\` without --app to pick ${platformWithArticle(platform)} app.`
|
|
2429
|
+
};
|
|
2430
|
+
}
|
|
2431
|
+
return {
|
|
2432
|
+
message: `app "${requested}" not found \u2014 it may have been deleted.`,
|
|
2433
|
+
hint: "Run `vibeview dev` without --app to pick from the list."
|
|
2434
|
+
};
|
|
2435
|
+
}
|
|
2407
2436
|
function buildAppStrategy(apps, buildFlag, json, isTTY) {
|
|
2408
2437
|
if (buildFlag) return "build";
|
|
2409
2438
|
if (apps.length === 0) return "build";
|
|
2410
2439
|
if (json || !isTTY) return "pick";
|
|
2411
2440
|
return "ask";
|
|
2412
2441
|
}
|
|
2442
|
+
function noBuildConfigMessage(platform, interactive) {
|
|
2443
|
+
const addKey = `add platforms.${platform}.build {command, artifact} to vibeview.json`;
|
|
2444
|
+
return interactive ? `no build config for ${platform}: ${addKey}, or run \`vibeview build --platform ${platform}\` once to set it up` : `no build config for ${platform}: ${addKey}`;
|
|
2445
|
+
}
|
|
2413
2446
|
function requireNonInteractive(json, message) {
|
|
2414
2447
|
if (!json) return;
|
|
2415
2448
|
emit(json, { event: "error", code: "config_required", message });
|
|
@@ -2477,11 +2510,9 @@ async function ensureConfig(api, opts) {
|
|
|
2477
2510
|
const apps = appsForPlatform(allApps, platform);
|
|
2478
2511
|
const choice = validateAppChoice(opts.app || entry?.appId, Boolean(opts.app), apps);
|
|
2479
2512
|
if (choice.staleFlag) {
|
|
2480
|
-
const message =
|
|
2481
|
-
console.error(
|
|
2482
|
-
|
|
2483
|
-
Run \`vibeview dev\` without --app to pick from the list.`
|
|
2484
|
-
);
|
|
2513
|
+
const { message, hint } = staleAppFlagError(choice.staleFlag, platform, allApps);
|
|
2514
|
+
console.error(chalk4.red(`error: ${message}`) + `
|
|
2515
|
+
${hint}`);
|
|
2485
2516
|
emit(opts.json, { event: "error", message });
|
|
2486
2517
|
process.exit(2);
|
|
2487
2518
|
}
|
|
@@ -2574,15 +2605,17 @@ Run \`vibeview dev\` without --app to pick from the list.`
|
|
|
2574
2605
|
firstRunMetro,
|
|
2575
2606
|
firstRunBuild
|
|
2576
2607
|
});
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2608
|
+
const commitConfig = () => {
|
|
2609
|
+
saveDevConfig(process.cwd(), cfg);
|
|
2610
|
+
if (isFirstRun) {
|
|
2611
|
+
console.error(chalk4.dim(`Created vibeview.json (platform: ${platform}, app: ${appId}).`));
|
|
2612
|
+
const hint = fillLaterHint(
|
|
2613
|
+
{ build: !firstRunBuild, metro: PLATFORM_NEEDS_METRO[platform] && !firstRunMetro },
|
|
2614
|
+
platform
|
|
2615
|
+
);
|
|
2616
|
+
if (hint) console.error(chalk4.dim(hint));
|
|
2617
|
+
}
|
|
2618
|
+
};
|
|
2586
2619
|
const finalMetro = isFirstRun ? resolveMetro(cfg, platform, opts.metroPort ? parseInt(opts.metroPort, 10) : void 0) : metroResolved;
|
|
2587
2620
|
return {
|
|
2588
2621
|
platform,
|
|
@@ -2592,7 +2625,9 @@ Run \`vibeview dev\` without --app to pick from the list.`
|
|
|
2592
2625
|
metroPort: finalMetro.port,
|
|
2593
2626
|
metroCommand: finalMetro.command,
|
|
2594
2627
|
platformHasMetroOverride: finalMetro.platformHasOverride,
|
|
2595
|
-
builtThisRun
|
|
2628
|
+
builtThisRun,
|
|
2629
|
+
savedConfig: cfg,
|
|
2630
|
+
commitConfig
|
|
2596
2631
|
};
|
|
2597
2632
|
}
|
|
2598
2633
|
async function runDevInProcess(opts) {
|
|
@@ -2641,11 +2676,20 @@ async function runDevInProcess(opts) {
|
|
|
2641
2676
|
process.exit(1);
|
|
2642
2677
|
}
|
|
2643
2678
|
}
|
|
2679
|
+
const interactive = !opts.json && Boolean(process.stdin.isTTY);
|
|
2680
|
+
const plannedBuild = cfg.savedConfig.platforms?.[cfg.platform]?.build;
|
|
2681
|
+
if (!plannedBuild && (opts.build === true && !cfg.builtThisRun || !PLATFORM_NEEDS_METRO[cfg.platform])) {
|
|
2682
|
+
const message = noBuildConfigMessage(cfg.platform, interactive);
|
|
2683
|
+
console.error(chalk4.red(`error: ${message}`));
|
|
2684
|
+
emit(opts.json, { event: "error", code: "config_required", message });
|
|
2685
|
+
process.exit(1);
|
|
2686
|
+
}
|
|
2687
|
+
cfg.commitConfig();
|
|
2644
2688
|
if (opts.build === true && !cfg.builtThisRun) {
|
|
2645
2689
|
const reloaded = loadDevConfig(process.cwd());
|
|
2646
2690
|
const build = reloaded?.platforms?.[cfg.platform]?.build;
|
|
2647
2691
|
if (!build) {
|
|
2648
|
-
const message =
|
|
2692
|
+
const message = noBuildConfigMessage(cfg.platform, interactive);
|
|
2649
2693
|
console.error(chalk4.red(`error: ${message}`));
|
|
2650
2694
|
emit(opts.json, { event: "error", code: "config_required", message });
|
|
2651
2695
|
process.exit(1);
|
|
@@ -2718,7 +2762,7 @@ async function runDevInProcess(opts) {
|
|
|
2718
2762
|
rokuBuild = rokuEntry?.build;
|
|
2719
2763
|
watchIgnore = rokuEntry?.watchIgnore ?? [];
|
|
2720
2764
|
if (!rokuBuild) {
|
|
2721
|
-
const message =
|
|
2765
|
+
const message = noBuildConfigMessage(cfg.platform, interactive);
|
|
2722
2766
|
console.error(chalk4.red(`error: ${message}`));
|
|
2723
2767
|
emit(opts.json, { event: "error", code: "config_required", message });
|
|
2724
2768
|
process.exit(1);
|
|
@@ -2775,12 +2819,7 @@ async function runDevInProcess(opts) {
|
|
|
2775
2819
|
console.error(chalk4.yellow(`\u26A0 ${evt.message}`));
|
|
2776
2820
|
}
|
|
2777
2821
|
} else if (evt.event === "queued") {
|
|
2778
|
-
|
|
2779
|
-
console.error(
|
|
2780
|
-
chalk4.yellow(
|
|
2781
|
-
`\u23F3 every device is busy \u2014 waiting in the queue${where}, Ctrl-C to give up`
|
|
2782
|
-
)
|
|
2783
|
-
);
|
|
2822
|
+
console.error(chalk4.yellow(queuedLine(evt.position, evt.message)));
|
|
2784
2823
|
} else if (evt.event === "tunnel_connected") {
|
|
2785
2824
|
everConnected = true;
|
|
2786
2825
|
console.error(chalk4.green("\u2713 tunnel connected \u2014 edit away, Fast Refresh is live"));
|
|
@@ -3025,7 +3064,7 @@ function formatDevicesHuman(devices) {
|
|
|
3025
3064
|
const fmt = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd();
|
|
3026
3065
|
const lines = [chalk5.bold(fmt(HEADERS2))];
|
|
3027
3066
|
for (const row of rows.sort(rowSort)) {
|
|
3028
|
-
const statusColored = row[5] === "available" ? chalk5.green(row[5]) : row[5] === "busy" ? chalk5.yellow(row[5]) : chalk5.dim(row[5]);
|
|
3067
|
+
const statusColored = row[5] === "available" ? chalk5.green(row[5]) : row[5] === "busy" || row[5] === "updating" ? chalk5.yellow(row[5]) : chalk5.dim(row[5]);
|
|
3029
3068
|
lines.push(fmt([row[0], row[1], row[2], row[3], row[4], statusColored]));
|
|
3030
3069
|
}
|
|
3031
3070
|
return lines.join("\n") + "\n";
|
|
@@ -3056,6 +3095,66 @@ function formatDeviceModelsHuman(models) {
|
|
|
3056
3095
|
}
|
|
3057
3096
|
|
|
3058
3097
|
// src/agent/registry.ts
|
|
3098
|
+
var PRESS_BUTTONS = {
|
|
3099
|
+
ios: ["home", "back", "lock", "siri", "enter"],
|
|
3100
|
+
android: ["home", "back", "lock", "enter"],
|
|
3101
|
+
appleTv: [
|
|
3102
|
+
"dpad_up",
|
|
3103
|
+
"dpad_down",
|
|
3104
|
+
"dpad_left",
|
|
3105
|
+
"dpad_right",
|
|
3106
|
+
"dpad_center",
|
|
3107
|
+
"back",
|
|
3108
|
+
"home",
|
|
3109
|
+
"menu"
|
|
3110
|
+
],
|
|
3111
|
+
androidTv: [
|
|
3112
|
+
"dpad_up",
|
|
3113
|
+
"dpad_down",
|
|
3114
|
+
"dpad_left",
|
|
3115
|
+
"dpad_right",
|
|
3116
|
+
"dpad_center",
|
|
3117
|
+
"back",
|
|
3118
|
+
"home",
|
|
3119
|
+
"enter"
|
|
3120
|
+
],
|
|
3121
|
+
roku: [
|
|
3122
|
+
"dpad_up",
|
|
3123
|
+
"dpad_down",
|
|
3124
|
+
"dpad_left",
|
|
3125
|
+
"dpad_right",
|
|
3126
|
+
"dpad_center",
|
|
3127
|
+
"back",
|
|
3128
|
+
"enter",
|
|
3129
|
+
"up",
|
|
3130
|
+
"down",
|
|
3131
|
+
"left",
|
|
3132
|
+
"right",
|
|
3133
|
+
"select",
|
|
3134
|
+
"backspace",
|
|
3135
|
+
"play",
|
|
3136
|
+
"rewind",
|
|
3137
|
+
"forward",
|
|
3138
|
+
"replay",
|
|
3139
|
+
"info"
|
|
3140
|
+
]
|
|
3141
|
+
};
|
|
3142
|
+
var PRESS_PLATFORM_LABELS = [
|
|
3143
|
+
["ios", "iPhone / iPad"],
|
|
3144
|
+
["android", "Android phone / tablet"],
|
|
3145
|
+
["appleTv", "Apple TV"],
|
|
3146
|
+
["androidTv", "Android TV"],
|
|
3147
|
+
["roku", "Roku (beta)"]
|
|
3148
|
+
];
|
|
3149
|
+
var PRESS_BUTTONS_BY_PLATFORM = PRESS_PLATFORM_LABELS.map(([key, label]) => [label, PRESS_BUTTONS[key]]);
|
|
3150
|
+
function pressButtonLines(indent) {
|
|
3151
|
+
return PRESS_BUTTONS_BY_PLATFORM.map(
|
|
3152
|
+
([platform, buttons]) => `${indent}${platform}: ${buttons.join(", ")}`
|
|
3153
|
+
);
|
|
3154
|
+
}
|
|
3155
|
+
function pressButtonsHelp() {
|
|
3156
|
+
return ["Valid buttons:", ...pressButtonLines(" ")].join("\n");
|
|
3157
|
+
}
|
|
3059
3158
|
var AGENT_COMMANDS = [
|
|
3060
3159
|
{
|
|
3061
3160
|
verb: "ui-tree",
|
|
@@ -3089,7 +3188,7 @@ var AGENT_COMMANDS = [
|
|
|
3089
3188
|
{
|
|
3090
3189
|
verb: "screenshot",
|
|
3091
3190
|
tool: "screenshot",
|
|
3092
|
-
summary: "Capture a screenshot of the current screen. Use it to check how something LOOKS \u2014 the UI tree carries no colour, contrast, or stacking information, so a screen can pass every check the tree can express and still be visibly broken. Saved to disk; over MCP, pass inline: true to receive the image itself.",
|
|
3191
|
+
summary: "Capture a screenshot of the current screen. Use it to check how something LOOKS \u2014 the UI tree carries no colour, contrast, or stacking information, so a screen can pass every check the tree can express and still be visibly broken. Saved to disk; over MCP, pass inline: true to receive the image itself. On a phone or tablet its pixels are the coordinates `tap`/`drag` take, so a point read off it can be tapped as is.",
|
|
3093
3192
|
mutating: false,
|
|
3094
3193
|
params: [
|
|
3095
3194
|
{
|
|
@@ -3324,7 +3423,7 @@ var AGENT_COMMANDS = [
|
|
|
3324
3423
|
cli: "positional",
|
|
3325
3424
|
type: "string",
|
|
3326
3425
|
required: true,
|
|
3327
|
-
description: "Text to type into the focused field."
|
|
3426
|
+
description: "Text to type into the focused field. On a phone or tablet a line break presses Enter, which submits the field (bash: $'query\\n'); `press enter` does the same."
|
|
3328
3427
|
}
|
|
3329
3428
|
]
|
|
3330
3429
|
},
|
|
@@ -3350,7 +3449,7 @@ var AGENT_COMMANDS = [
|
|
|
3350
3449
|
cli: "positional",
|
|
3351
3450
|
type: "string",
|
|
3352
3451
|
required: true,
|
|
3353
|
-
description:
|
|
3452
|
+
description: `Button to press. ${pressButtonLines("").join(". ")}.`
|
|
3354
3453
|
}
|
|
3355
3454
|
]
|
|
3356
3455
|
},
|
|
@@ -3358,7 +3457,7 @@ var AGENT_COMMANDS = [
|
|
|
3358
3457
|
// tools.py open_url: { url: string }.
|
|
3359
3458
|
verb: "open-url",
|
|
3360
3459
|
tool: "open_url",
|
|
3361
|
-
summary: "Open a deep link or URL in the app under test.",
|
|
3460
|
+
summary: "Open a deep link or URL in the app under test. The link goes to that app only; one it has no screen for fails rather than opening a browser.",
|
|
3362
3461
|
mutating: true,
|
|
3363
3462
|
params: [
|
|
3364
3463
|
{
|
|
@@ -3787,13 +3886,13 @@ async function runAgentVerb(cmd, positionals, opts) {
|
|
|
3787
3886
|
} catch (err) {
|
|
3788
3887
|
const message = err instanceof Error ? err.message : String(err);
|
|
3789
3888
|
if (opts.json) {
|
|
3790
|
-
const code =
|
|
3889
|
+
const code = err instanceof AuthError ? "auth_failed" : err instanceof NotFoundError ? "not_found" : void 0;
|
|
3791
3890
|
console.log(JSON.stringify({ event: "error", code, message }));
|
|
3792
3891
|
} else {
|
|
3793
3892
|
process.stderr.write(`error: ${message}
|
|
3794
3893
|
`);
|
|
3795
3894
|
}
|
|
3796
|
-
process.exit(
|
|
3895
|
+
process.exit(errorExitCode(err));
|
|
3797
3896
|
}
|
|
3798
3897
|
if (cmd.verb === "screenshot" && envelope.ok && envelope.result) {
|
|
3799
3898
|
const outPath = opts.out || defaultScreenshotPath();
|
|
@@ -3819,6 +3918,7 @@ async function runAgentVerb(cmd, positionals, opts) {
|
|
|
3819
3918
|
console.log(JSON.stringify(envelope));
|
|
3820
3919
|
} else {
|
|
3821
3920
|
console.log(renderEnvelope(envelope));
|
|
3921
|
+
if (cmd.verb === "press" && !envelope.ok) process.stderr.write(pressButtonsHelp() + "\n");
|
|
3822
3922
|
}
|
|
3823
3923
|
process.exit(agentExitCode(envelope));
|
|
3824
3924
|
}
|
|
@@ -3854,6 +3954,7 @@ export {
|
|
|
3854
3954
|
formatDevicesHuman,
|
|
3855
3955
|
filterDeviceModels,
|
|
3856
3956
|
formatDeviceModelsHuman,
|
|
3957
|
+
pressButtonsHelp,
|
|
3857
3958
|
AGENT_COMMANDS,
|
|
3858
3959
|
renderEnvelope,
|
|
3859
3960
|
buildToolCall,
|
|
@@ -42,6 +42,51 @@ import { createWriteStream, mkdirSync as mkdirSync2, renameSync, rmSync } from "
|
|
|
42
42
|
import { basename, join as join2, resolve, sep } from "path";
|
|
43
43
|
import { pipeline } from "stream/promises";
|
|
44
44
|
import { Readable } from "stream";
|
|
45
|
+
|
|
46
|
+
// src/lib/formatters/time.ts
|
|
47
|
+
function formatUtcTimestamp(iso) {
|
|
48
|
+
if (!iso) return "";
|
|
49
|
+
const hasZone = /(?:Z|[+-]\d{2}:?\d{2})$/i.test(iso);
|
|
50
|
+
const d = new Date(hasZone ? iso : `${iso}Z`);
|
|
51
|
+
if (Number.isNaN(d.getTime())) return iso;
|
|
52
|
+
return `${d.toISOString().slice(0, 19).replace("T", " ")} UTC`;
|
|
53
|
+
}
|
|
54
|
+
var NAIVE_ISO_DATETIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?$/;
|
|
55
|
+
function withUtcTimestamps(value) {
|
|
56
|
+
if (typeof value === "string") {
|
|
57
|
+
return NAIVE_ISO_DATETIME.test(value) ? `${value}Z` : value;
|
|
58
|
+
}
|
|
59
|
+
if (Array.isArray(value)) return value.map((v) => withUtcTimestamps(v));
|
|
60
|
+
if (value !== null && typeof value === "object") {
|
|
61
|
+
const out = {};
|
|
62
|
+
for (const [k, v] of Object.entries(value)) {
|
|
63
|
+
out[k] = withUtcTimestamps(v);
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/lib/api-client.ts
|
|
71
|
+
var AuthError = class extends Error {
|
|
72
|
+
constructor(message = "Authentication failed. Check your API token.") {
|
|
73
|
+
super(message);
|
|
74
|
+
this.name = "AuthError";
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
function errorExitCode(err, otherwise = 1) {
|
|
78
|
+
return err instanceof AuthError ? 2 : otherwise;
|
|
79
|
+
}
|
|
80
|
+
function reportCommandError(json, err) {
|
|
81
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
82
|
+
if (json) printJsonError(err);
|
|
83
|
+
process.stderr.write(`error: ${message}
|
|
84
|
+
`);
|
|
85
|
+
}
|
|
86
|
+
function printJsonError(err) {
|
|
87
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
88
|
+
console.log(JSON.stringify({ event: "error", message }));
|
|
89
|
+
}
|
|
45
90
|
var NotFoundError = class extends Error {
|
|
46
91
|
constructor(message) {
|
|
47
92
|
super(message);
|
|
@@ -52,13 +97,15 @@ function extractDetail(text) {
|
|
|
52
97
|
try {
|
|
53
98
|
const parsed = JSON.parse(text);
|
|
54
99
|
if (parsed && typeof parsed.detail === "string") return parsed.detail;
|
|
100
|
+
if (parsed && parsed.detail && typeof parsed.detail.message === "string")
|
|
101
|
+
return parsed.detail.message;
|
|
55
102
|
} catch {
|
|
56
103
|
}
|
|
57
104
|
return void 0;
|
|
58
105
|
}
|
|
59
106
|
async function parseJsonBody(resp) {
|
|
60
107
|
if (resp.status === 204 || resp.status === 205) return void 0;
|
|
61
|
-
return resp.json();
|
|
108
|
+
return withUtcTimestamps(await resp.json());
|
|
62
109
|
}
|
|
63
110
|
var ApiClient = class {
|
|
64
111
|
constructor(baseUrl, token) {
|
|
@@ -96,7 +143,7 @@ var ApiClient = class {
|
|
|
96
143
|
});
|
|
97
144
|
if (!resp.ok) {
|
|
98
145
|
const text = await resp.text().catch(() => "");
|
|
99
|
-
if (resp.status === 401) throw new
|
|
146
|
+
if (resp.status === 401) throw new AuthError();
|
|
100
147
|
if (resp.status === 404) throw new NotFoundError(`Not found: ${extractDetail(text) ?? path}`);
|
|
101
148
|
throw new Error(extractDetail(text) ?? `API error ${resp.status}: ${text}`);
|
|
102
149
|
}
|
|
@@ -168,7 +215,7 @@ var ApiClient = class {
|
|
|
168
215
|
});
|
|
169
216
|
if (!resp.ok) {
|
|
170
217
|
const text = await resp.text().catch(() => "");
|
|
171
|
-
if (resp.status === 401) throw new
|
|
218
|
+
if (resp.status === 401) throw new AuthError();
|
|
172
219
|
if (resp.status === 404) throw new NotFoundError(`Not found: ${extractDetail(text) ?? path}`);
|
|
173
220
|
throw new Error(extractDetail(text) ?? `API error ${resp.status}: ${text}`);
|
|
174
221
|
}
|
|
@@ -188,7 +235,7 @@ var ApiClient = class {
|
|
|
188
235
|
});
|
|
189
236
|
if (!resp.ok) {
|
|
190
237
|
const text = await resp.text().catch(() => "");
|
|
191
|
-
if (resp.status === 401) throw new
|
|
238
|
+
if (resp.status === 401) throw new AuthError();
|
|
192
239
|
if (resp.status === 404) throw new NotFoundError(`Not found: ${extractDetail(text) ?? path}`);
|
|
193
240
|
throw new Error(extractDetail(text) ?? `API error ${resp.status}: ${text}`);
|
|
194
241
|
}
|
|
@@ -347,7 +394,7 @@ var ApiClient = class {
|
|
|
347
394
|
const resp = await this.fetchOrExplain(url, { method: "GET", headers });
|
|
348
395
|
if (!resp.ok) {
|
|
349
396
|
const text = await resp.text().catch(() => "");
|
|
350
|
-
if (resp.status === 401) throw new
|
|
397
|
+
if (resp.status === 401) throw new AuthError();
|
|
351
398
|
if (resp.status === 404)
|
|
352
399
|
throw new NotFoundError(`Not found: ${extractDetail(text) ?? `/builds/${id}/artifact`}`);
|
|
353
400
|
throw new Error(extractDetail(text) ?? `API error ${resp.status}: ${text}`);
|
|
@@ -646,6 +693,16 @@ var UsageError = class extends Error {
|
|
|
646
693
|
this.name = "UsageError";
|
|
647
694
|
}
|
|
648
695
|
};
|
|
696
|
+
var DEVICE_STATUS_FILTERS = ["available", "busy", "updating"];
|
|
697
|
+
var QUEUE_REASON_COPY = {
|
|
698
|
+
devices_busy: "All devices are in use right now.",
|
|
699
|
+
devices_updating: "Devices are being updated and will be back shortly.",
|
|
700
|
+
plan_limit: "The most sessions this app can run at once are already running.",
|
|
701
|
+
link_limit: "The most people who can play at once are already playing."
|
|
702
|
+
};
|
|
703
|
+
function queueReasonCopy(reason) {
|
|
704
|
+
return reason ? QUEUE_REASON_COPY[reason] ?? null : null;
|
|
705
|
+
}
|
|
649
706
|
|
|
650
707
|
// src/commands/upload-app.ts
|
|
651
708
|
async function uploadAppBuild(filePath, options) {
|
|
@@ -729,6 +786,11 @@ export {
|
|
|
729
786
|
loadConfig,
|
|
730
787
|
saveConfig,
|
|
731
788
|
resolveAuth,
|
|
789
|
+
formatUtcTimestamp,
|
|
790
|
+
AuthError,
|
|
791
|
+
errorExitCode,
|
|
792
|
+
reportCommandError,
|
|
793
|
+
printJsonError,
|
|
732
794
|
NotFoundError,
|
|
733
795
|
ApiClient,
|
|
734
796
|
createSpinner,
|
|
@@ -736,6 +798,8 @@ export {
|
|
|
736
798
|
EXIT_TEST_FAIL,
|
|
737
799
|
EXIT_INFRA_ERROR,
|
|
738
800
|
UsageError,
|
|
801
|
+
DEVICE_STATUS_FILTERS,
|
|
802
|
+
queueReasonCopy,
|
|
739
803
|
uploadAppBuild,
|
|
740
804
|
describeBuild,
|
|
741
805
|
uploadApp
|
package/dist/cli.js
CHANGED
|
@@ -16,29 +16,35 @@ import {
|
|
|
16
16
|
loadDevConfig,
|
|
17
17
|
newTransitionState,
|
|
18
18
|
platformWithArticle,
|
|
19
|
+
pressButtonsHelp,
|
|
19
20
|
printOutcomeLine,
|
|
20
21
|
printTransition,
|
|
21
22
|
readDevState,
|
|
22
23
|
readMetroState,
|
|
23
24
|
runAgentVerb
|
|
24
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-7IOGMM5W.js";
|
|
25
26
|
import {
|
|
26
27
|
ApiClient,
|
|
28
|
+
DEVICE_STATUS_FILTERS,
|
|
27
29
|
EXIT_INFRA_ERROR,
|
|
28
30
|
EXIT_PASS,
|
|
29
31
|
EXIT_TEST_FAIL,
|
|
30
32
|
NotFoundError,
|
|
31
33
|
UsageError,
|
|
32
34
|
createSpinner,
|
|
35
|
+
errorExitCode,
|
|
36
|
+
formatUtcTimestamp,
|
|
33
37
|
loadConfig,
|
|
38
|
+
printJsonError,
|
|
39
|
+
reportCommandError,
|
|
34
40
|
resolveAuth,
|
|
35
41
|
saveConfig,
|
|
36
42
|
uploadApp
|
|
37
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-FTTWLCTE.js";
|
|
38
44
|
|
|
39
45
|
// src/cli.ts
|
|
40
46
|
import { createRequire } from "module";
|
|
41
|
-
import { Command } from "commander";
|
|
47
|
+
import { Command, Option } from "commander";
|
|
42
48
|
|
|
43
49
|
// src/commands/run-suite.ts
|
|
44
50
|
import chalk3 from "chalk";
|
|
@@ -48,6 +54,26 @@ import chalk2 from "chalk";
|
|
|
48
54
|
|
|
49
55
|
// src/lib/formatters/human.ts
|
|
50
56
|
import chalk from "chalk";
|
|
57
|
+
|
|
58
|
+
// src/lib/formatters/cases.ts
|
|
59
|
+
function resultCases(result) {
|
|
60
|
+
if (Array.isArray(result.test_runs)) return result.test_runs;
|
|
61
|
+
const failedStep = result.steps?.find((s) => s.status !== "passed");
|
|
62
|
+
return [
|
|
63
|
+
{
|
|
64
|
+
case_name: result.case_name || result.run_id,
|
|
65
|
+
status: result.status,
|
|
66
|
+
duration_ms: result.duration_ms,
|
|
67
|
+
error_message: result.error_message ?? failedStep?.error ?? null,
|
|
68
|
+
error_kind: failedStep?.error_kind ?? null,
|
|
69
|
+
error_kind_description: failedStep?.error_kind_description ?? null,
|
|
70
|
+
screenshot_signed_url: failedStep?.screenshot_signed_url ?? null,
|
|
71
|
+
url: result.url
|
|
72
|
+
}
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/lib/formatters/human.ts
|
|
51
77
|
function failureLines(failure) {
|
|
52
78
|
const lines = [];
|
|
53
79
|
if (failure.error_kind) {
|
|
@@ -73,15 +99,17 @@ function formatHuman(result, verbose) {
|
|
|
73
99
|
} else {
|
|
74
100
|
lines.push(` ${icon} ${chalk.bold(result.run_id)} ${chalk.dim(duration)}`);
|
|
75
101
|
}
|
|
76
|
-
const
|
|
77
|
-
const
|
|
102
|
+
const cases = resultCases(result);
|
|
103
|
+
const passed = cases.filter((t) => t.status === "passed").length;
|
|
104
|
+
const total = cases.length;
|
|
78
105
|
lines.push(` ${chalk.dim(`${passed}/${total} cases passed`)}`);
|
|
79
106
|
if (result.url) {
|
|
80
107
|
lines.push(` ${chalk.dim(result.url)}`);
|
|
81
108
|
}
|
|
82
|
-
|
|
109
|
+
const suiteCases = result.test_runs ?? [];
|
|
110
|
+
if (suiteCases.length > 0) {
|
|
83
111
|
lines.push("");
|
|
84
|
-
for (const tr of
|
|
112
|
+
for (const tr of suiteCases) {
|
|
85
113
|
const caseIcon = tr.status === "passed" ? chalk.green(" PASS") : tr.status === "cancelled" || tr.status === "pending" || tr.status === "running" ? chalk.yellow(" SKIP") : chalk.red(" FAIL");
|
|
86
114
|
const caseDuration = tr.duration_ms ? `${(tr.duration_ms / 1e3).toFixed(1)}s` : "-";
|
|
87
115
|
lines.push(` ${caseIcon} ${tr.case_name} ${chalk.dim(caseDuration)}`);
|
|
@@ -130,10 +158,11 @@ function escapeXml(s) {
|
|
|
130
158
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
131
159
|
}
|
|
132
160
|
function formatJunit(result) {
|
|
133
|
-
const suiteName = escapeXml(result.suite_name || result.run_id);
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
const
|
|
161
|
+
const suiteName = escapeXml(result.suite_name || result.case_name || result.run_id);
|
|
162
|
+
const cases = resultCases(result);
|
|
163
|
+
const tests = cases.length;
|
|
164
|
+
const failures = cases.filter((t) => t.status === "failed").length;
|
|
165
|
+
const errors = cases.filter((t) => t.status === "error").length;
|
|
137
166
|
const totalTime = result.duration_ms ? (result.duration_ms / 1e3).toFixed(1) : "0";
|
|
138
167
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
139
168
|
const lines = [];
|
|
@@ -144,7 +173,7 @@ function formatJunit(result) {
|
|
|
144
173
|
lines.push(
|
|
145
174
|
` <testsuite name="${suiteName}" tests="${tests}" failures="${failures}" errors="${errors}" time="${totalTime}" timestamp="${timestamp}">`
|
|
146
175
|
);
|
|
147
|
-
for (const tr of
|
|
176
|
+
for (const tr of cases) {
|
|
148
177
|
const caseName = escapeXml(tr.case_name);
|
|
149
178
|
const caseTime = tr.duration_ms ? (tr.duration_ms / 1e3).toFixed(1) : "0";
|
|
150
179
|
if (tr.status === "passed") {
|
|
@@ -501,6 +530,7 @@ ${chalk3.dim("Run:")} ${response.run_id}
|
|
|
501
530
|
process.exit(mapExitCode(result.status));
|
|
502
531
|
} catch (err) {
|
|
503
532
|
spinner.fail(err instanceof Error ? err.message : "Unknown error");
|
|
533
|
+
if (options.output === "json") printJsonError(err);
|
|
504
534
|
if (lastQueueEntryId !== void 0) {
|
|
505
535
|
try {
|
|
506
536
|
await client.cancelQueueEntry(lastQueueEntryId);
|
|
@@ -589,10 +619,13 @@ ${signal} received.
|
|
|
589
619
|
const msg = err instanceof Error ? err.message : String(err);
|
|
590
620
|
if (msg.startsWith("Not found:") || msg.includes("404") || /No suites match tags/.test(msg)) {
|
|
591
621
|
const suffix = opts.platform ? ` on platform ${opts.platform}` : "";
|
|
592
|
-
|
|
622
|
+
const noMatch = `No suites match tags: ${opts.tag.join(", ")}${suffix}`;
|
|
623
|
+
if (opts.output === "json") printJsonError(noMatch);
|
|
624
|
+
process.stderr.write(chalk4.yellow(`${noMatch}
|
|
593
625
|
`));
|
|
594
626
|
process.exit(EXIT_INFRA_ERROR);
|
|
595
627
|
}
|
|
628
|
+
if (opts.output === "json") printJsonError(err);
|
|
596
629
|
process.stderr.write(chalk4.red(`Failed to start: ${msg}
|
|
597
630
|
`));
|
|
598
631
|
process.exit(EXIT_INFRA_ERROR);
|
|
@@ -676,6 +709,7 @@ ${chalk4.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u20
|
|
|
676
709
|
process.stdin.pause();
|
|
677
710
|
}
|
|
678
711
|
if (!last) {
|
|
712
|
+
if (opts.output === "json") printJsonError("Timed out before any status was returned.");
|
|
679
713
|
process.stderr.write(chalk4.red("Timed out before any status was returned.\n"));
|
|
680
714
|
process.exit(EXIT_INFRA_ERROR);
|
|
681
715
|
}
|
|
@@ -813,6 +847,7 @@ ${chalk5.dim("Run:")} ${response.run_id}
|
|
|
813
847
|
process.exit(mapExitCode(result.status));
|
|
814
848
|
} catch (err) {
|
|
815
849
|
spinner.fail(err instanceof Error ? err.message : "Unknown error");
|
|
850
|
+
if (options.output === "json") printJsonError(err);
|
|
816
851
|
if (lastQueueEntryId !== void 0) {
|
|
817
852
|
try {
|
|
818
853
|
await client.cancelQueueEntry(lastQueueEntryId);
|
|
@@ -834,10 +869,16 @@ import chalk6 from "chalk";
|
|
|
834
869
|
async function listSuites(options) {
|
|
835
870
|
const { token, baseUrl } = resolveAuth(options);
|
|
836
871
|
const client = new ApiClient(baseUrl, token);
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
872
|
+
let suites;
|
|
873
|
+
try {
|
|
874
|
+
suites = await client.listSuites({
|
|
875
|
+
tags: options.tag && options.tag.length > 0 ? options.tag : void 0,
|
|
876
|
+
match: options.match
|
|
877
|
+
});
|
|
878
|
+
} catch (err) {
|
|
879
|
+
reportCommandError(options.output === "json", err);
|
|
880
|
+
process.exit(EXIT_INFRA_ERROR);
|
|
881
|
+
}
|
|
841
882
|
if (options.output === "json") {
|
|
842
883
|
console.log(JSON.stringify(suites, null, 2));
|
|
843
884
|
return;
|
|
@@ -862,7 +903,13 @@ import chalk7 from "chalk";
|
|
|
862
903
|
async function listApps(options) {
|
|
863
904
|
const { token, baseUrl } = resolveAuth(options);
|
|
864
905
|
const client = new ApiClient(baseUrl, token);
|
|
865
|
-
|
|
906
|
+
let apps;
|
|
907
|
+
try {
|
|
908
|
+
apps = await client.listApps();
|
|
909
|
+
} catch (err) {
|
|
910
|
+
reportCommandError(options.output === "json", err);
|
|
911
|
+
process.exit(EXIT_INFRA_ERROR);
|
|
912
|
+
}
|
|
866
913
|
if (options.output === "json") {
|
|
867
914
|
console.log(JSON.stringify(apps, null, 2));
|
|
868
915
|
return;
|
|
@@ -896,7 +943,7 @@ async function stopCommand(sessionId, opts, deps) {
|
|
|
896
943
|
const message = err instanceof Error ? err.message : String(err);
|
|
897
944
|
if (opts.json) console.log(JSON.stringify({ stopped: false, session_id: id, message }));
|
|
898
945
|
console.error(chalk8.red(`error: ${message}`));
|
|
899
|
-
process.exitCode =
|
|
946
|
+
process.exitCode = errorExitCode(err);
|
|
900
947
|
return;
|
|
901
948
|
}
|
|
902
949
|
const state = readDevState(process.cwd());
|
|
@@ -1278,8 +1325,10 @@ import chalk13 from "chalk";
|
|
|
1278
1325
|
// src/lib/formatters/builds.ts
|
|
1279
1326
|
import chalk12 from "chalk";
|
|
1280
1327
|
var HEADERS = ["ID", "PLATFORM", "STATUS", "CREATED", "COST", "APP"];
|
|
1281
|
-
|
|
1282
|
-
|
|
1328
|
+
var UNBILLED_STATUSES = /* @__PURE__ */ new Set(["queued", "running"]);
|
|
1329
|
+
function formatCostCell(costMicro, status) {
|
|
1330
|
+
if (costMicro <= 0 && UNBILLED_STATUSES.has(status)) return "-";
|
|
1331
|
+
return `$${(Math.max(costMicro, 0) / 1e6).toFixed(2)}`;
|
|
1283
1332
|
}
|
|
1284
1333
|
var STATUS_COLOR = {
|
|
1285
1334
|
succeeded: chalk12.green,
|
|
@@ -1298,8 +1347,8 @@ function formatBuildsHuman(builds2) {
|
|
|
1298
1347
|
b.id,
|
|
1299
1348
|
b.platform,
|
|
1300
1349
|
b.status,
|
|
1301
|
-
b.created_at
|
|
1302
|
-
formatCostCell(b.cost_micro),
|
|
1350
|
+
formatUtcTimestamp(b.created_at),
|
|
1351
|
+
formatCostCell(b.cost_micro, b.status),
|
|
1303
1352
|
b.app_public_id ?? ""
|
|
1304
1353
|
]);
|
|
1305
1354
|
const widths = HEADERS.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
@@ -1350,7 +1399,7 @@ async function buildsList(opts, deps) {
|
|
|
1350
1399
|
process.exitCode = 0;
|
|
1351
1400
|
} catch (err) {
|
|
1352
1401
|
printError(opts, err);
|
|
1353
|
-
process.exitCode =
|
|
1402
|
+
process.exitCode = errorExitCode(err);
|
|
1354
1403
|
}
|
|
1355
1404
|
}
|
|
1356
1405
|
async function buildsLogs(id, opts, deps) {
|
|
@@ -1373,7 +1422,7 @@ async function buildsLogs(id, opts, deps) {
|
|
|
1373
1422
|
process.exitCode = final.status === "succeeded" ? 0 : 1;
|
|
1374
1423
|
} catch (err) {
|
|
1375
1424
|
printError(opts, err);
|
|
1376
|
-
process.exitCode =
|
|
1425
|
+
process.exitCode = errorExitCode(err);
|
|
1377
1426
|
}
|
|
1378
1427
|
}
|
|
1379
1428
|
var MAX_CONSECUTIVE_POLL_FAILURES = 5;
|
|
@@ -1466,7 +1515,7 @@ async function buildsDownload(id, opts, deps) {
|
|
|
1466
1515
|
process.exitCode = 0;
|
|
1467
1516
|
} catch (err) {
|
|
1468
1517
|
printError(opts, err);
|
|
1469
|
-
process.exitCode =
|
|
1518
|
+
process.exitCode = errorExitCode(err);
|
|
1470
1519
|
}
|
|
1471
1520
|
}
|
|
1472
1521
|
async function buildsCancel(id, opts, deps) {
|
|
@@ -1483,7 +1532,7 @@ async function buildsCancel(id, opts, deps) {
|
|
|
1483
1532
|
process.exitCode = 0;
|
|
1484
1533
|
} catch (err) {
|
|
1485
1534
|
printError(opts, err);
|
|
1486
|
-
process.exitCode =
|
|
1535
|
+
process.exitCode = errorExitCode(err);
|
|
1487
1536
|
}
|
|
1488
1537
|
}
|
|
1489
1538
|
async function buildsDeleteArtifact(id, opts, deps) {
|
|
@@ -1500,7 +1549,7 @@ async function buildsDeleteArtifact(id, opts, deps) {
|
|
|
1500
1549
|
process.exitCode = 0;
|
|
1501
1550
|
} catch (err) {
|
|
1502
1551
|
printError(opts, err);
|
|
1503
|
-
process.exitCode =
|
|
1552
|
+
process.exitCode = errorExitCode(err);
|
|
1504
1553
|
}
|
|
1505
1554
|
}
|
|
1506
1555
|
function printError(opts, err) {
|
|
@@ -1692,7 +1741,7 @@ async function submitCommand(opts, deps) {
|
|
|
1692
1741
|
process.exitCode = final.status === "succeeded" ? 0 : 1;
|
|
1693
1742
|
} catch (err) {
|
|
1694
1743
|
printError2(opts, err);
|
|
1695
|
-
process.exitCode =
|
|
1744
|
+
process.exitCode = errorExitCode(err);
|
|
1696
1745
|
}
|
|
1697
1746
|
}
|
|
1698
1747
|
async function checkStore(platform, androidFamily, ascKey2, opts, deps) {
|
|
@@ -1723,7 +1772,7 @@ async function checkStore(platform, androidFamily, ascKey2, opts, deps) {
|
|
|
1723
1772
|
process.exitCode = result.ready ? 0 : 1;
|
|
1724
1773
|
} catch (err) {
|
|
1725
1774
|
printError2(opts, err);
|
|
1726
|
-
process.exitCode =
|
|
1775
|
+
process.exitCode = errorExitCode(err);
|
|
1727
1776
|
}
|
|
1728
1777
|
}
|
|
1729
1778
|
async function resolveLatestBuild(platform, deps) {
|
|
@@ -1853,7 +1902,7 @@ async function versionsList(opts, deps) {
|
|
|
1853
1902
|
process.exitCode = 0;
|
|
1854
1903
|
} catch (err) {
|
|
1855
1904
|
printError3(opts, err);
|
|
1856
|
-
process.exitCode =
|
|
1905
|
+
process.exitCode = errorExitCode(err);
|
|
1857
1906
|
}
|
|
1858
1907
|
}
|
|
1859
1908
|
async function versionsSet(buildNumber, opts, deps) {
|
|
@@ -1886,7 +1935,7 @@ async function versionsSet(buildNumber, opts, deps) {
|
|
|
1886
1935
|
process.exitCode = 0;
|
|
1887
1936
|
} catch (err) {
|
|
1888
1937
|
printError3(opts, err);
|
|
1889
|
-
process.exitCode =
|
|
1938
|
+
process.exitCode = errorExitCode(err);
|
|
1890
1939
|
}
|
|
1891
1940
|
}
|
|
1892
1941
|
var HEADERS2 = ["PLATFORM", "BUILD NUMBER", "UPDATED"];
|
|
@@ -1962,7 +2011,7 @@ Create an admin API token in the dashboard and use ${chalk16.bold("--token")} or
|
|
|
1962
2011
|
console.log(JSON.stringify({ event: "error", message }));
|
|
1963
2012
|
}
|
|
1964
2013
|
console.error(chalk16.red(`error: ${message}`));
|
|
1965
|
-
process.exitCode =
|
|
2014
|
+
process.exitCode = errorExitCode(err);
|
|
1966
2015
|
}
|
|
1967
2016
|
}
|
|
1968
2017
|
|
|
@@ -2040,7 +2089,7 @@ async function envList(opts, deps) {
|
|
|
2040
2089
|
process.exitCode = 0;
|
|
2041
2090
|
} catch (err) {
|
|
2042
2091
|
printError4(opts, err);
|
|
2043
|
-
process.exitCode =
|
|
2092
|
+
process.exitCode = errorExitCode(err);
|
|
2044
2093
|
}
|
|
2045
2094
|
}
|
|
2046
2095
|
function formatEnvVarsHuman(variables) {
|
|
@@ -2089,7 +2138,7 @@ async function envSet(name, value, opts, deps) {
|
|
|
2089
2138
|
process.exitCode = 0;
|
|
2090
2139
|
} catch (err) {
|
|
2091
2140
|
printError4(opts, err);
|
|
2092
|
-
process.exitCode =
|
|
2141
|
+
process.exitCode = errorExitCode(err);
|
|
2093
2142
|
}
|
|
2094
2143
|
}
|
|
2095
2144
|
function resolveVisibility(opts) {
|
|
@@ -2122,7 +2171,7 @@ async function envRm(name, opts, deps) {
|
|
|
2122
2171
|
process.exitCode = 0;
|
|
2123
2172
|
} catch (err) {
|
|
2124
2173
|
printError4(opts, err);
|
|
2125
|
-
process.exitCode =
|
|
2174
|
+
process.exitCode = errorExitCode(err);
|
|
2126
2175
|
}
|
|
2127
2176
|
}
|
|
2128
2177
|
async function envPull(opts, deps) {
|
|
@@ -2154,7 +2203,7 @@ async function envPull(opts, deps) {
|
|
|
2154
2203
|
process.exitCode = 0;
|
|
2155
2204
|
} catch (err) {
|
|
2156
2205
|
printError4(opts, err);
|
|
2157
|
-
process.exitCode =
|
|
2206
|
+
process.exitCode = errorExitCode(err);
|
|
2158
2207
|
}
|
|
2159
2208
|
}
|
|
2160
2209
|
function isLossyEnvValue(value) {
|
|
@@ -2347,7 +2396,7 @@ async function credentialsAdd(opts, deps) {
|
|
|
2347
2396
|
process.exitCode = 0;
|
|
2348
2397
|
} catch (err) {
|
|
2349
2398
|
printError5(opts, err);
|
|
2350
|
-
process.exitCode =
|
|
2399
|
+
process.exitCode = errorExitCode(err);
|
|
2351
2400
|
}
|
|
2352
2401
|
}
|
|
2353
2402
|
async function credentialsGenerate(opts, deps) {
|
|
@@ -2421,7 +2470,7 @@ async function credentialsGenerate(opts, deps) {
|
|
|
2421
2470
|
process.exitCode = 0;
|
|
2422
2471
|
} catch (err) {
|
|
2423
2472
|
printError5(opts, err);
|
|
2424
|
-
process.exitCode =
|
|
2473
|
+
process.exitCode = errorExitCode(err);
|
|
2425
2474
|
}
|
|
2426
2475
|
}
|
|
2427
2476
|
async function generateAppleCredential(platform, opts, d) {
|
|
@@ -2457,7 +2506,7 @@ async function generateAppleCredential(platform, opts, d) {
|
|
|
2457
2506
|
process.exitCode = 0;
|
|
2458
2507
|
} catch (err) {
|
|
2459
2508
|
printError5(opts, err);
|
|
2460
|
-
process.exitCode =
|
|
2509
|
+
process.exitCode = errorExitCode(err);
|
|
2461
2510
|
}
|
|
2462
2511
|
}
|
|
2463
2512
|
async function credentialsDownload(name, opts, deps) {
|
|
@@ -2532,7 +2581,7 @@ async function credentialsDownload(name, opts, deps) {
|
|
|
2532
2581
|
process.exitCode = 0;
|
|
2533
2582
|
} catch (err) {
|
|
2534
2583
|
printError5(opts, err);
|
|
2535
|
-
process.exitCode =
|
|
2584
|
+
process.exitCode = errorExitCode(err);
|
|
2536
2585
|
}
|
|
2537
2586
|
}
|
|
2538
2587
|
async function ascKeyAdd(opts, deps) {
|
|
@@ -2572,7 +2621,7 @@ async function ascKeyAdd(opts, deps) {
|
|
|
2572
2621
|
process.exitCode = 0;
|
|
2573
2622
|
} catch (err) {
|
|
2574
2623
|
printError5(opts, err);
|
|
2575
|
-
process.exitCode =
|
|
2624
|
+
process.exitCode = errorExitCode(err);
|
|
2576
2625
|
}
|
|
2577
2626
|
}
|
|
2578
2627
|
async function ascKeyList(opts, deps) {
|
|
@@ -2587,7 +2636,7 @@ async function ascKeyList(opts, deps) {
|
|
|
2587
2636
|
process.exitCode = 0;
|
|
2588
2637
|
} catch (err) {
|
|
2589
2638
|
printError5(opts, err);
|
|
2590
|
-
process.exitCode =
|
|
2639
|
+
process.exitCode = errorExitCode(err);
|
|
2591
2640
|
}
|
|
2592
2641
|
}
|
|
2593
2642
|
function formatAscKeysHuman(keys) {
|
|
@@ -2595,7 +2644,7 @@ function formatAscKeysHuman(keys) {
|
|
|
2595
2644
|
return "No App Store Connect keys found.\n";
|
|
2596
2645
|
}
|
|
2597
2646
|
const HEADERS3 = ["NAME", "KEY ID", "ISSUER ID", "CREATED"];
|
|
2598
|
-
const rows = keys.map((k) => [k.name, k.key_id, k.issuer_id, k.created_at
|
|
2647
|
+
const rows = keys.map((k) => [k.name, k.key_id, k.issuer_id, formatUtcTimestamp(k.created_at)]);
|
|
2599
2648
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2600
2649
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2601
2650
|
const join3 = (cells) => cells.join(" ").trimEnd();
|
|
@@ -2615,7 +2664,7 @@ async function ascKeyRm(name, opts, deps) {
|
|
|
2615
2664
|
process.exitCode = 0;
|
|
2616
2665
|
} catch (err) {
|
|
2617
2666
|
printError5(opts, err);
|
|
2618
|
-
process.exitCode =
|
|
2667
|
+
process.exitCode = errorExitCode(err);
|
|
2619
2668
|
}
|
|
2620
2669
|
}
|
|
2621
2670
|
async function playKeyAdd(opts, deps) {
|
|
@@ -2648,7 +2697,7 @@ async function playKeyAdd(opts, deps) {
|
|
|
2648
2697
|
process.exitCode = 0;
|
|
2649
2698
|
} catch (err) {
|
|
2650
2699
|
printError5(opts, err);
|
|
2651
|
-
process.exitCode =
|
|
2700
|
+
process.exitCode = errorExitCode(err);
|
|
2652
2701
|
}
|
|
2653
2702
|
}
|
|
2654
2703
|
async function playKeyList(opts, deps) {
|
|
@@ -2663,7 +2712,7 @@ async function playKeyList(opts, deps) {
|
|
|
2663
2712
|
process.exitCode = 0;
|
|
2664
2713
|
} catch (err) {
|
|
2665
2714
|
printError5(opts, err);
|
|
2666
|
-
process.exitCode =
|
|
2715
|
+
process.exitCode = errorExitCode(err);
|
|
2667
2716
|
}
|
|
2668
2717
|
}
|
|
2669
2718
|
function formatPlayKeysHuman(keys) {
|
|
@@ -2671,7 +2720,7 @@ function formatPlayKeysHuman(keys) {
|
|
|
2671
2720
|
return "No Google Play keys found.\n";
|
|
2672
2721
|
}
|
|
2673
2722
|
const HEADERS3 = ["NAME", "CLIENT EMAIL", "CREATED"];
|
|
2674
|
-
const rows = keys.map((k) => [k.name, k.client_email, k.created_at
|
|
2723
|
+
const rows = keys.map((k) => [k.name, k.client_email, formatUtcTimestamp(k.created_at)]);
|
|
2675
2724
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2676
2725
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2677
2726
|
const join3 = (cells) => cells.join(" ").trimEnd();
|
|
@@ -2691,7 +2740,7 @@ async function playKeyRm(name, opts, deps) {
|
|
|
2691
2740
|
process.exitCode = 0;
|
|
2692
2741
|
} catch (err) {
|
|
2693
2742
|
printError5(opts, err);
|
|
2694
|
-
process.exitCode =
|
|
2743
|
+
process.exitCode = errorExitCode(err);
|
|
2695
2744
|
}
|
|
2696
2745
|
}
|
|
2697
2746
|
async function credentialsList(opts, deps) {
|
|
@@ -2712,7 +2761,7 @@ async function credentialsList(opts, deps) {
|
|
|
2712
2761
|
process.exitCode = 0;
|
|
2713
2762
|
} catch (err) {
|
|
2714
2763
|
printError5(opts, err);
|
|
2715
|
-
process.exitCode =
|
|
2764
|
+
process.exitCode = errorExitCode(err);
|
|
2716
2765
|
}
|
|
2717
2766
|
}
|
|
2718
2767
|
function formatIdentity(c) {
|
|
@@ -2731,10 +2780,10 @@ function earliestExpiry(c) {
|
|
|
2731
2780
|
function formatExpires(c) {
|
|
2732
2781
|
const parts = [];
|
|
2733
2782
|
if (c.profile_expires_at) {
|
|
2734
|
-
if (c.expires_at) parts.push(`cert ${c.expires_at}`);
|
|
2735
|
-
parts.push(`profile ${c.profile_expires_at}`);
|
|
2783
|
+
if (c.expires_at) parts.push(`cert ${formatUtcTimestamp(c.expires_at)}`);
|
|
2784
|
+
parts.push(`profile ${formatUtcTimestamp(c.profile_expires_at)}`);
|
|
2736
2785
|
} else if (c.expires_at) {
|
|
2737
|
-
parts.push(c.expires_at);
|
|
2786
|
+
parts.push(formatUtcTimestamp(c.expires_at));
|
|
2738
2787
|
}
|
|
2739
2788
|
if (parts.length === 0) return "";
|
|
2740
2789
|
const cell = parts.join(" \xB7 ");
|
|
@@ -2771,7 +2820,7 @@ async function credentialsRm(name, opts, deps) {
|
|
|
2771
2820
|
process.exitCode = 0;
|
|
2772
2821
|
} catch (err) {
|
|
2773
2822
|
printError5(opts, err);
|
|
2774
|
-
process.exitCode =
|
|
2823
|
+
process.exitCode = errorExitCode(err);
|
|
2775
2824
|
}
|
|
2776
2825
|
}
|
|
2777
2826
|
function printKeystoreWriteFailure(opts, err, result) {
|
|
@@ -2849,14 +2898,14 @@ async function webhooksAdd(opts, deps) {
|
|
|
2849
2898
|
process.exitCode = 0;
|
|
2850
2899
|
} catch (err) {
|
|
2851
2900
|
printError6(opts, err);
|
|
2852
|
-
process.exitCode =
|
|
2901
|
+
process.exitCode = errorExitCode(err);
|
|
2853
2902
|
}
|
|
2854
2903
|
}
|
|
2855
2904
|
function formatLastDelivery(ld) {
|
|
2856
2905
|
if (!ld) return "\u2014";
|
|
2857
2906
|
if (ld.status === "pending") return "pending";
|
|
2858
2907
|
const code = ld.response_code != null ? ` (${ld.response_code})` : "";
|
|
2859
|
-
const at = ld.at != null ? ` ${ld.at}` : "";
|
|
2908
|
+
const at = ld.at != null ? ` ${formatUtcTimestamp(ld.at)}` : "";
|
|
2860
2909
|
return `${ld.status}${code}${at}`;
|
|
2861
2910
|
}
|
|
2862
2911
|
function formatWebhooksHuman(webhooks2) {
|
|
@@ -2869,7 +2918,7 @@ function formatWebhooksHuman(webhooks2) {
|
|
|
2869
2918
|
w.url,
|
|
2870
2919
|
w.active ? "yes" : "no",
|
|
2871
2920
|
formatLastDelivery(w.last_delivery),
|
|
2872
|
-
w.created_at
|
|
2921
|
+
formatUtcTimestamp(w.created_at)
|
|
2873
2922
|
]);
|
|
2874
2923
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2875
2924
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
@@ -2890,7 +2939,7 @@ async function webhooksList(opts, deps) {
|
|
|
2890
2939
|
process.exitCode = 0;
|
|
2891
2940
|
} catch (err) {
|
|
2892
2941
|
printError6(opts, err);
|
|
2893
|
-
process.exitCode =
|
|
2942
|
+
process.exitCode = errorExitCode(err);
|
|
2894
2943
|
}
|
|
2895
2944
|
}
|
|
2896
2945
|
async function webhooksRm(id, opts, deps) {
|
|
@@ -2905,7 +2954,7 @@ async function webhooksRm(id, opts, deps) {
|
|
|
2905
2954
|
process.exitCode = 0;
|
|
2906
2955
|
} catch (err) {
|
|
2907
2956
|
printError6(opts, err);
|
|
2908
|
-
process.exitCode =
|
|
2957
|
+
process.exitCode = errorExitCode(err);
|
|
2909
2958
|
}
|
|
2910
2959
|
}
|
|
2911
2960
|
function parseUtcTimestamp(iso) {
|
|
@@ -2939,7 +2988,7 @@ async function webhooksTest(id, opts, deps) {
|
|
|
2939
2988
|
} else {
|
|
2940
2989
|
if (!opts.json) {
|
|
2941
2990
|
const code = ld.response_code != null ? `HTTP ${ld.response_code}` : "no response";
|
|
2942
|
-
console.error(chalk19.red(`\u2717 Failed (${code}) at ${ld.at}`));
|
|
2991
|
+
console.error(chalk19.red(`\u2717 Failed (${code}) at ${formatUtcTimestamp(ld.at)}`));
|
|
2943
2992
|
}
|
|
2944
2993
|
process.exitCode = 1;
|
|
2945
2994
|
}
|
|
@@ -2955,7 +3004,7 @@ async function webhooksTest(id, opts, deps) {
|
|
|
2955
3004
|
process.exitCode = 1;
|
|
2956
3005
|
} catch (err) {
|
|
2957
3006
|
printError6(opts, err);
|
|
2958
|
-
process.exitCode =
|
|
3007
|
+
process.exitCode = errorExitCode(err);
|
|
2959
3008
|
}
|
|
2960
3009
|
}
|
|
2961
3010
|
function printError6(opts, err) {
|
|
@@ -2978,8 +3027,7 @@ async function listDevicesCommand(options) {
|
|
|
2978
3027
|
}
|
|
2979
3028
|
process.exit(EXIT_PASS);
|
|
2980
3029
|
} catch (err) {
|
|
2981
|
-
|
|
2982
|
-
`);
|
|
3030
|
+
reportCommandError(options.output === "json", err);
|
|
2983
3031
|
process.exit(EXIT_INFRA_ERROR);
|
|
2984
3032
|
}
|
|
2985
3033
|
return;
|
|
@@ -2997,8 +3045,7 @@ async function listDevicesCommand(options) {
|
|
|
2997
3045
|
}
|
|
2998
3046
|
process.exit(EXIT_PASS);
|
|
2999
3047
|
} catch (err) {
|
|
3000
|
-
|
|
3001
|
-
`);
|
|
3048
|
+
reportCommandError(options.output === "json", err);
|
|
3002
3049
|
process.exit(EXIT_INFRA_ERROR);
|
|
3003
3050
|
}
|
|
3004
3051
|
}
|
|
@@ -3271,7 +3318,16 @@ program.command("list-apps").description("List apps in your organization").optio
|
|
|
3271
3318
|
program.command("list-builds <app>").description(
|
|
3272
3319
|
"List an app's uploaded builds, newest first: id, version, platform, debug/release, upload time and note (the ids `dev --build <id>` and `run-test --build` take)"
|
|
3273
3320
|
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the builds as a JSON array", false).action((app, opts) => listBuilds(app, opts));
|
|
3274
|
-
program.command("list-devices").description("List devices registered with the platform (filterable by type and category)").option("--token <token>", "API token").option("--output <format>", "Output format: human, json", "human").
|
|
3321
|
+
program.command("list-devices").description("List devices registered with the platform (filterable by type and category)").option("--token <token>", "API token").option("--output <format>", "Output format: human, json", "human").addOption(
|
|
3322
|
+
new Option("--type <type>", "Filter by device type").choices(["ios", "android", "roku"])
|
|
3323
|
+
).addOption(
|
|
3324
|
+
new Option("--category <category>", "Filter by category").choices(["phone", "tablet", "tv"])
|
|
3325
|
+
).addOption(
|
|
3326
|
+
new Option(
|
|
3327
|
+
"--status <status>",
|
|
3328
|
+
"Filter by status (updating = being updated, takes no new session yet)"
|
|
3329
|
+
).choices([...DEVICE_STATUS_FILTERS])
|
|
3330
|
+
).option(
|
|
3275
3331
|
"--models",
|
|
3276
3332
|
"List the device models you can start instead (the names `dev --model` takes)",
|
|
3277
3333
|
false
|
|
@@ -3445,8 +3501,8 @@ program.command("dev-reload").description(
|
|
|
3445
3501
|
program.command("mcp").description(
|
|
3446
3502
|
"Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop/dev_reload as tools"
|
|
3447
3503
|
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
|
|
3448
|
-
const { runMcpServer } = await import("./server-
|
|
3449
|
-
await runMcpServer(opts);
|
|
3504
|
+
const { runMcpServer } = await import("./server-ZIUZ7U5T.js");
|
|
3505
|
+
await runMcpServer({ ...opts, version: pkg.version });
|
|
3450
3506
|
});
|
|
3451
3507
|
program.command("agent-setup").description(
|
|
3452
3508
|
"Make VibeView discoverable to your coding agent: install the vibeview-agent skill (and optionally register the MCP server with Claude Code)"
|
|
@@ -3456,6 +3512,8 @@ for (const cmd of AGENT_COMMANDS) {
|
|
|
3456
3512
|
const flagParams = cmd.params.filter((p) => p.cli !== "positional");
|
|
3457
3513
|
const argSignature = positionalParams.map((p) => p.required ? `<${p.name}>` : `[${p.name}]`).join(" ");
|
|
3458
3514
|
let command = program.command(argSignature ? `${cmd.verb} ${argSignature}` : cmd.verb).description(cmd.summary);
|
|
3515
|
+
if (cmd.verb === "press") command.addHelpText("after", `
|
|
3516
|
+
${pressButtonsHelp()}`);
|
|
3459
3517
|
for (const param of flagParams) {
|
|
3460
3518
|
const flagArg = param.type === "number" ? `${param.cli} <n>` : `${param.cli} <value>`;
|
|
3461
3519
|
command = command.option(flagArg, param.description);
|
|
@@ -21,11 +21,11 @@ import {
|
|
|
21
21
|
readDevState,
|
|
22
22
|
renderEnvelope,
|
|
23
23
|
startDevSession
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-7IOGMM5W.js";
|
|
25
25
|
import {
|
|
26
26
|
ApiClient,
|
|
27
27
|
resolveAuth
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-FTTWLCTE.js";
|
|
29
29
|
|
|
30
30
|
// src/mcp/server.ts
|
|
31
31
|
import { writeFileSync } from "fs";
|
|
@@ -377,7 +377,7 @@ async function handleUploadApp(args, deps) {
|
|
|
377
377
|
}
|
|
378
378
|
const name = typeof args.name === "string" && args.name ? args.name : void 0;
|
|
379
379
|
const upload = deps.uploadApp ?? (async (f, n) => {
|
|
380
|
-
const { uploadAppBuild } = await import("./upload-app-
|
|
380
|
+
const { uploadAppBuild } = await import("./upload-app-MDKTSG4O.js");
|
|
381
381
|
const { result: result2 } = await uploadAppBuild(f, { ...deps.auth, name: n });
|
|
382
382
|
return result2;
|
|
383
383
|
});
|
|
@@ -578,7 +578,7 @@ async function createMcpServer(opts) {
|
|
|
578
578
|
agentTool: client.agentTool.bind(client),
|
|
579
579
|
devSession
|
|
580
580
|
};
|
|
581
|
-
const server = new McpServer({ name: "vibeview", version: "
|
|
581
|
+
const server = new McpServer({ name: "vibeview", version: opts.version ?? "0.0.0" });
|
|
582
582
|
function zodShape(def) {
|
|
583
583
|
const shape = {};
|
|
584
584
|
for (const [key, prop] of Object.entries(def.properties)) {
|
package/package.json
CHANGED
|
@@ -134,6 +134,9 @@ it rather than letting it appear unexplained in their `git status`.
|
|
|
134
134
|
```bash
|
|
135
135
|
vibeview scroll-to "Delete account" --session <id>
|
|
136
136
|
```
|
|
137
|
+
A `scroll-to` that never finds its text, or a `type` whose field does not
|
|
138
|
+
end up holding the text, prints `error` and exits 1 — check the reason
|
|
139
|
+
line and the screen before retrying.
|
|
137
140
|
- **Precise two-point gestures** (sliders, drag-and-drop reorder, map
|
|
138
141
|
panning) need `drag` — `swipe`/`scroll` are direction-only and can't
|
|
139
142
|
express them. Each end is independently a ref or `x,y`:
|
|
@@ -247,18 +250,18 @@ human-readable text.
|
|
|
247
250
|
|------|------|---------|
|
|
248
251
|
| `ui-tree` | — | Fetch the ref-annotated UI tree for the current screen. |
|
|
249
252
|
| `logs` | `[--tail <n>] [--since <cursor>]` | Read recent app logs from the device (JS console, native errors, crash messages). The response's `cursor` feeds the next call's `--since`. |
|
|
250
|
-
| `screenshot` | `[--out <path>]` | Capture a screenshot of the current screen and save it to disk. |
|
|
251
|
-
| `tap` | `<target>` | Tap an element by ref (e.g. `@e5`), or
|
|
253
|
+
| `screenshot` | `[--out <path>]` | Capture a screenshot of the current screen and save it to disk. On a phone or tablet its pixels are tap coordinates: a point read off it can be tapped as is — never rescale. |
|
|
254
|
+
| `tap` | `<target>` | Tap an element by ref (e.g. `@e5`), or coordinates (e.g. `100,200`) in the space of the tree's rects and the screenshot's pixels. |
|
|
252
255
|
| `long-press` | `<ref>` `[--ms <n>]` | Long-press (touch and hold) an element by its ref. |
|
|
253
256
|
| `swipe` | `<direction>` `[--distance <short\|medium\|long>]` | Perform a scrollbar-semantic swipe gesture in one of four directions. |
|
|
254
257
|
| `scroll` | `<direction>` | Scroll the current view up, down, left, or right using a gesture preset. |
|
|
255
258
|
| `scroll-to` | `<text>` `[--direction <up\|down\|left\|right>] [--max-scrolls <n>] [--element-type <type>]` | Scroll until an element matching the text is visible. Stops at end of list. |
|
|
256
259
|
| `drag` | `<from>` `<to>` `[--velocity <n>] [--press-ms <n>] [--hold-ms <n>]` | Drag between two points, each an `@ref` or `x,y`. The only precise two-point gesture. |
|
|
257
260
|
| `alert` | `<get\|accept\|dismiss>` `[--button <label>]` | Inspect or respond to a system alert on iOS or Apple TV. |
|
|
258
|
-
| `type` | `<text>` | Type text into the currently focused input field. |
|
|
261
|
+
| `type` | `<text>` | Type text into the currently focused input field. On a phone or tablet a line break presses Enter and submits the field (`$'query\n'`), like `press enter`. |
|
|
259
262
|
| `clear-text` | — | Clear the text in the currently focused input field. |
|
|
260
263
|
| `press` | `<button>` | Press a device button or perform a system gesture (home, back, d-pad, etc). |
|
|
261
|
-
| `open-url` | `<url>` | Open a deep link or URL in the app under test. |
|
|
264
|
+
| `open-url` | `<url>` | Open a deep link or URL in the app under test. The link goes to that app only; a link it has no screen for fails (no browser). |
|
|
262
265
|
| `set-posture` | `<closed\|partial\|open>` or `--angle <0-180>` | Fold or unfold a foldable device (form factor `foldable`: the iPhone Duo, Android foldables) to a preset or an exact hinge angle — exactly one. Reports posture, angle and lit screen. Errors on a device without a hinge. |
|
|
263
266
|
| `rotate` | `[--degrees <90\|180\|270>]` | Rotate the device and report the new orientation. Phones/tablets and Android foldables toggle portrait/landscape (90 only); the iPhone Duo turns a quarter clockwise by default. Not on TV. |
|
|
264
267
|
| `wait` | `[--ms <n>]` | Wait for a specified number of seconds before continuing (default: 2s). |
|