vibeview 0.42.0 → 0.44.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.
|
@@ -982,6 +982,16 @@ function nativeAndroidDefaults(cwd, production) {
|
|
|
982
982
|
artifact: `${m}/build/outputs/apk/debug/*.apk`
|
|
983
983
|
};
|
|
984
984
|
}
|
|
985
|
+
var FVM_CONFIGS = [".fvmrc", ".fvm/fvm_config.json"];
|
|
986
|
+
function flutterCommand(cwd) {
|
|
987
|
+
let ig = null;
|
|
988
|
+
try {
|
|
989
|
+
ig = ignore().add(readFileSync5(join5(cwd, ".gitignore"), "utf-8"));
|
|
990
|
+
} catch {
|
|
991
|
+
}
|
|
992
|
+
const pinned = FVM_CONFIGS.some((f) => isFile(join5(cwd, f)) && !ig?.ignores(f));
|
|
993
|
+
return pinned ? "fvm flutter" : "flutter";
|
|
994
|
+
}
|
|
985
995
|
var FLUTTER_ANDROID_ARTIFACT = "build/app/outputs/flutter-apk/app-debug.apk";
|
|
986
996
|
var FLUTTER_IOS_ARTIFACT = "build/ios/iphonesimulator/Runner.app";
|
|
987
997
|
var FLUTTER_ANDROID_PRODUCTION_ARTIFACT = "build/app/outputs/bundle/release/app-release.aab";
|
|
@@ -1117,13 +1127,17 @@ function defaultsFor(cwd, platform, evaluatedIosName) {
|
|
|
1117
1127
|
function otherFrameworkDefaults(cwd, flavor, platform, production) {
|
|
1118
1128
|
if (flavor === "flutter") {
|
|
1119
1129
|
if (!existsSync3(join5(cwd, platform))) return {};
|
|
1130
|
+
const flutter = flutterCommand(cwd);
|
|
1120
1131
|
if (platform === "android") {
|
|
1121
1132
|
return production ? {
|
|
1122
|
-
buildCommand:
|
|
1133
|
+
buildCommand: `${flutter} build appbundle --release`,
|
|
1123
1134
|
artifact: FLUTTER_ANDROID_PRODUCTION_ARTIFACT
|
|
1124
|
-
} : { buildCommand:
|
|
1135
|
+
} : { buildCommand: `${flutter} build apk --debug`, artifact: FLUTTER_ANDROID_ARTIFACT };
|
|
1125
1136
|
}
|
|
1126
|
-
return production ? {} : {
|
|
1137
|
+
return production ? {} : {
|
|
1138
|
+
buildCommand: `${flutter} build ios --simulator --debug`,
|
|
1139
|
+
artifact: FLUTTER_IOS_ARTIFACT
|
|
1140
|
+
};
|
|
1127
1141
|
}
|
|
1128
1142
|
if (flavor === "capacitor") return capacitorDefaults(cwd, platform, production);
|
|
1129
1143
|
if (platform === "android") return nativeAndroidDefaults(cwd, production);
|
|
@@ -2730,9 +2744,10 @@ your build command must produce a zip \u2014 use the package/zip step, not deplo
|
|
|
2730
2744
|
|
|
2731
2745
|
// src/commands/dev.ts
|
|
2732
2746
|
import { spawn as spawn4 } from "child_process";
|
|
2733
|
-
import { createWriteStream, mkdirSync as mkdirSync5 } from "fs";
|
|
2734
|
-
import { dirname as dirname5 } from "path";
|
|
2747
|
+
import { closeSync, createWriteStream, mkdirSync as mkdirSync5, openSync, readFileSync as readFileSync8 } from "fs";
|
|
2748
|
+
import { dirname as dirname5, join as join9 } from "path";
|
|
2735
2749
|
import { createInterface as createInterface3 } from "readline";
|
|
2750
|
+
import { stripVTControlCharacters } from "util";
|
|
2736
2751
|
import { select as select3 } from "@inquirer/prompts";
|
|
2737
2752
|
import chalk4 from "chalk";
|
|
2738
2753
|
import openBrowser from "open";
|
|
@@ -3306,7 +3321,7 @@ async function startDevSession(cfg, onEvent = () => {
|
|
|
3306
3321
|
}, onTunnelState) {
|
|
3307
3322
|
const api = new ApiClient(cfg.baseUrl, cfg.token);
|
|
3308
3323
|
const usesTunnel = PLATFORM_NEEDS_METRO[cfg.platform] && !cfg.standalone;
|
|
3309
|
-
const pool = cfg.model ? resolveDeviceModel(await api.listDeviceModels(), cfg.platform, cfg.model) : null;
|
|
3324
|
+
const pool = cfg.model ? resolveDeviceModel(await api.listDeviceModels(), cfg.platform, cfg.model, cfg.modelCategory) : null;
|
|
3310
3325
|
let app = null;
|
|
3311
3326
|
let project = null;
|
|
3312
3327
|
let pick = {
|
|
@@ -3530,14 +3545,21 @@ function devSessionCreateBody(cfg) {
|
|
|
3530
3545
|
...cfg.buildId ? { build_id: cfg.buildId } : {}
|
|
3531
3546
|
};
|
|
3532
3547
|
}
|
|
3533
|
-
function resolveDeviceModel(pools, platform, model) {
|
|
3548
|
+
function resolveDeviceModel(pools, platform, model, category) {
|
|
3534
3549
|
const { deviceType, tv } = PLATFORM_DEVICE[platform];
|
|
3535
3550
|
const candidates = pools.filter(
|
|
3536
3551
|
(p) => p.device_type === deviceType && p.category === "tv" === tv
|
|
3537
3552
|
);
|
|
3538
3553
|
const wanted = model.trim();
|
|
3539
3554
|
const match = candidates.find((p) => p.model === wanted) ?? candidates.find((p) => p.model.toLowerCase() === wanted.toLowerCase());
|
|
3540
|
-
if (match)
|
|
3555
|
+
if (match) {
|
|
3556
|
+
if (category && match.category !== category) {
|
|
3557
|
+
throw new Error(
|
|
3558
|
+
`"${match.model}" is a ${match.category}, not a ${category} \u2014 drop --device-category, the model already decides it`
|
|
3559
|
+
);
|
|
3560
|
+
}
|
|
3561
|
+
return match;
|
|
3562
|
+
}
|
|
3541
3563
|
const names = [...new Set(candidates.map((p) => p.model))].sort();
|
|
3542
3564
|
const guess = closestModelName(wanted, names);
|
|
3543
3565
|
throw new Error(
|
|
@@ -3731,10 +3753,6 @@ async function ensureConfig(api, opts) {
|
|
|
3731
3753
|
console.error(chalk4.red(`error: use --platform tvos or --platform androidtv for TV devices`));
|
|
3732
3754
|
process.exit(2);
|
|
3733
3755
|
}
|
|
3734
|
-
if (opts.model !== void 0 && opts.deviceCategory) {
|
|
3735
|
-
console.error(chalk4.red("error: pass --model or --device-category, not both"));
|
|
3736
|
-
process.exit(2);
|
|
3737
|
-
}
|
|
3738
3756
|
if (opts.model !== void 0 && !opts.model.trim()) {
|
|
3739
3757
|
console.error(
|
|
3740
3758
|
chalk4.red("error: --model needs a model name (see `vibeview list-devices --models`)")
|
|
@@ -3845,7 +3863,8 @@ ${hint}`);
|
|
|
3845
3863
|
choices: apps.map((a) => ({ value: a.public_id, name: a.name }))
|
|
3846
3864
|
});
|
|
3847
3865
|
}
|
|
3848
|
-
const
|
|
3866
|
+
const categoryFlag = opts.model === void 0 ? opts.deviceCategory : void 0;
|
|
3867
|
+
const deviceCategory = isTv ? "tv" : categoryFlag || entry?.deviceCategory || "phone";
|
|
3849
3868
|
const metroPortFlag = opts.metroPort ? parseInt(opts.metroPort, 10) : void 0;
|
|
3850
3869
|
const metroResolved = resolveMetro(effective, platform, metroPortFlag);
|
|
3851
3870
|
const committedEntry = existing?.platforms?.[platform];
|
|
@@ -3925,6 +3944,7 @@ ${hint}`);
|
|
|
3925
3944
|
appId,
|
|
3926
3945
|
deviceCategory,
|
|
3927
3946
|
model: opts.model,
|
|
3947
|
+
...opts.model !== void 0 && opts.deviceCategory ? { modelCategory: opts.deviceCategory } : {},
|
|
3928
3948
|
metroPort: finalMetro.port,
|
|
3929
3949
|
metroCommand: finalMetro.command,
|
|
3930
3950
|
platformHasMetroOverride: finalMetro.platformHasOverride,
|
|
@@ -4157,6 +4177,7 @@ async function runDevInProcess(opts) {
|
|
|
4157
4177
|
appId: cfg.appId,
|
|
4158
4178
|
deviceCategory: cfg.deviceCategory,
|
|
4159
4179
|
model: cfg.model,
|
|
4180
|
+
...cfg.modelCategory ? { modelCategory: cfg.modelCategory } : {},
|
|
4160
4181
|
buildId: pinnedBuildId,
|
|
4161
4182
|
projectDir: process.cwd(),
|
|
4162
4183
|
...standalone ? { standalone } : {},
|
|
@@ -4375,10 +4396,13 @@ async function dev(opts) {
|
|
|
4375
4396
|
return runDevInProcess(opts);
|
|
4376
4397
|
}
|
|
4377
4398
|
const rawArgs = process.argv.slice(2).filter((a) => a !== "--detach");
|
|
4399
|
+
const stderrLog = detachLogPath(process.cwd());
|
|
4400
|
+
const stderrFd = openDetachLog(stderrLog);
|
|
4378
4401
|
const child = spawn4(process.execPath, [process.argv[1], ...rawArgs, "--json", "--no-open"], {
|
|
4379
4402
|
detached: true,
|
|
4380
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
4403
|
+
stdio: ["ignore", "pipe", stderrFd ?? "ignore"]
|
|
4381
4404
|
});
|
|
4405
|
+
if (stderrFd !== null) closeSync(stderrFd);
|
|
4382
4406
|
const rl = createInterface3({ input: child.stdout });
|
|
4383
4407
|
let settled = false;
|
|
4384
4408
|
await new Promise((resolve, reject) => {
|
|
@@ -4425,7 +4449,8 @@ async function dev(opts) {
|
|
|
4425
4449
|
child.on("exit", (code) => {
|
|
4426
4450
|
if (settled) return;
|
|
4427
4451
|
rl.close();
|
|
4428
|
-
const
|
|
4452
|
+
const reason = lastErrorLine(stderrLog);
|
|
4453
|
+
const message = `dev session exited before starting (code ${code ?? "unknown"})` + (reason ? `: ${reason}` : "");
|
|
4429
4454
|
emit(opts.json, { event: "error", message });
|
|
4430
4455
|
if (!opts.json) console.error(chalk4.red(`error: ${message}`));
|
|
4431
4456
|
reject(new Error(message));
|
|
@@ -4443,6 +4468,30 @@ async function dev(opts) {
|
|
|
4443
4468
|
});
|
|
4444
4469
|
if (process.exitCode === 1) return;
|
|
4445
4470
|
}
|
|
4471
|
+
function detachLogPath(cwd) {
|
|
4472
|
+
return join9(cwd, ".vibeview", "dev-detach.log");
|
|
4473
|
+
}
|
|
4474
|
+
function openDetachLog(path) {
|
|
4475
|
+
try {
|
|
4476
|
+
mkdirSync5(dirname5(path), { recursive: true });
|
|
4477
|
+
ensureGitignored(dirname5(dirname5(path)));
|
|
4478
|
+
return openSync(path, "w");
|
|
4479
|
+
} catch {
|
|
4480
|
+
return null;
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
function lastErrorLine(path) {
|
|
4484
|
+
let text;
|
|
4485
|
+
try {
|
|
4486
|
+
text = readFileSync8(path, "utf-8");
|
|
4487
|
+
} catch {
|
|
4488
|
+
return null;
|
|
4489
|
+
}
|
|
4490
|
+
const lines = stripVTControlCharacters(text).split("\n").map((l) => l.trim()).filter(Boolean);
|
|
4491
|
+
const error = [...lines].reverse().find((l) => /^error\b|error:/i.test(l));
|
|
4492
|
+
const line = error ?? lines[lines.length - 1];
|
|
4493
|
+
return line ? line.replace(/^error:\s*/i, "").slice(0, 300) : null;
|
|
4494
|
+
}
|
|
4446
4495
|
|
|
4447
4496
|
// src/lib/formatters/devices.ts
|
|
4448
4497
|
import chalk5 from "chalk";
|
|
@@ -4965,6 +5014,24 @@ var AGENT_COMMANDS = [
|
|
|
4965
5014
|
}
|
|
4966
5015
|
]
|
|
4967
5016
|
},
|
|
5017
|
+
{
|
|
5018
|
+
// set_status_bar: { preset: 'clean' | 'default' } — the dashboard's
|
|
5019
|
+
// Device tab Clean status bar (simctl status_bar override). iOS phones and
|
|
5020
|
+
// tablets only; tool_dispatch refuses it elsewhere.
|
|
5021
|
+
verb: "set-status-bar",
|
|
5022
|
+
tool: "set_status_bar",
|
|
5023
|
+
summary: "Show a clean status bar for screenshots (9:41, full battery, full Wi-Fi and cellular signal) or put the device's own status bar back. It stays until you change it or the session ends. iOS phones and tablets only.",
|
|
5024
|
+
mutating: true,
|
|
5025
|
+
params: [
|
|
5026
|
+
{
|
|
5027
|
+
name: "preset",
|
|
5028
|
+
cli: "positional",
|
|
5029
|
+
type: "string",
|
|
5030
|
+
required: true,
|
|
5031
|
+
description: "'clean' for the screenshot status bar, 'default' for the device's own."
|
|
5032
|
+
}
|
|
5033
|
+
]
|
|
5034
|
+
},
|
|
4968
5035
|
{
|
|
4969
5036
|
// tools.py wait: { seconds?: number } (default: 2). Note the wire arg is
|
|
4970
5037
|
// "seconds", NOT "ms" — tool_dispatch.py reads args.get("seconds", 2)
|
|
@@ -5117,6 +5184,11 @@ function resultLines(result) {
|
|
|
5117
5184
|
if (r.action === "location_set" && typeof r.latitude === "number" && typeof r.longitude === "number") {
|
|
5118
5185
|
lines.push(`location: ${r.latitude}, ${r.longitude}`);
|
|
5119
5186
|
}
|
|
5187
|
+
if (r.action === "status_bar_set" && typeof r.preset === "string") {
|
|
5188
|
+
lines.push(
|
|
5189
|
+
`status bar: ${r.preset === "clean" ? "clean (9:41, full battery and signal)" : "the device\u2019s own"}`
|
|
5190
|
+
);
|
|
5191
|
+
}
|
|
5120
5192
|
if (typeof r.note === "string" && r.note) lines.push(chalk6.dim(r.note));
|
|
5121
5193
|
if (typeof r.message === "string" && r.message) lines.push(r.message);
|
|
5122
5194
|
if (typeof r.warning === "string" && r.warning) lines.push(r.warning);
|
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-JUNGGSS3.js";
|
|
34
34
|
import {
|
|
35
35
|
ApiClient,
|
|
36
36
|
ApiStatusError,
|
|
@@ -4461,7 +4461,7 @@ program.command("dev-reload").description(
|
|
|
4461
4461
|
program.command("mcp").description(
|
|
4462
4462
|
"Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop/dev_reload as tools"
|
|
4463
4463
|
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
|
|
4464
|
-
const { runMcpServer } = await import("./server-
|
|
4464
|
+
const { runMcpServer } = await import("./server-MLDOTK2G.js");
|
|
4465
4465
|
await runMcpServer({ ...opts, version: pkg.version });
|
|
4466
4466
|
});
|
|
4467
4467
|
program.command("agent-setup").description(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibeview",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.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",
|
|
@@ -173,6 +173,10 @@ appear unexplained in their `git status`.
|
|
|
173
173
|
- Use `set-location --lat 51.5074 --lon -0.1278` to place the device before
|
|
174
174
|
a location-based flow (maps, store finders, region-gated screens); the app
|
|
175
175
|
reads it like a GPS fix. Not on TV.
|
|
176
|
+
- Use `set-status-bar clean` before App Store screenshots: 9:41, a full
|
|
177
|
+
battery and full Wi-Fi and cellular signal, kept for the rest of the session
|
|
178
|
+
(`set-status-bar default` puts the real one back). iOS phones and tablets
|
|
179
|
+
only.
|
|
176
180
|
- Use `open-url` to jump straight to a deep link instead of navigating by
|
|
177
181
|
hand:
|
|
178
182
|
```bash
|
|
@@ -290,6 +294,7 @@ human-readable text.
|
|
|
290
294
|
| `relaunch-app` | — | Close the app and start it again (cold start, data kept) — for "does it persist after a restart" checks. The app log keeps streaming. Not on embed sessions. |
|
|
291
295
|
| `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. |
|
|
292
296
|
| `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. |
|
|
297
|
+
| `set-status-bar` | `<clean\|default>` | Show the clean screenshot status bar (9:41, full battery and signal) or the device's own. iOS phones and tablets only. |
|
|
293
298
|
| `set-location` | `--lat <n> --lon <n>` | Set the device's simulated GPS position (decimal degrees; west and south are negative) for location-based flows. Phones, tablets and foldables; not on TV. |
|
|
294
299
|
| `wait` | `[--ms <n>]` | Wait for a specified number of seconds before continuing (default: 2s). |
|
|
295
300
|
| `find` | `<text>` `[--below <text>] [--above <text>] [--near <text>]` | Find an element by text with optional spatial constraints. |
|