vibeview 0.21.0 → 0.23.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-VVKF7GGL.js → chunk-E2LZSLRF.js} +245 -58
- package/dist/{chunk-KON2FOEC.js → chunk-PSFXVUHG.js} +11 -0
- package/dist/cli.js +224 -141
- package/dist/{server-WWNWYD4Q.js → server-XMSBLIWY.js} +165 -12
- package/dist/{upload-app-W6NOSJPA.js → upload-app-ZAKAOJKE.js} +1 -1
- package/package.json +1 -1
- package/skills/vibeview-agent/SKILL.md +39 -3
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
AGENT_COMMANDS,
|
|
4
|
+
DEV_PLATFORMS,
|
|
4
5
|
PLATFORM_DEVICE,
|
|
5
6
|
PLATFORM_NEEDS_METRO,
|
|
7
|
+
ROKU_LIVENESS_INTERVAL_MS,
|
|
6
8
|
buildToolCall,
|
|
7
9
|
checkMetro,
|
|
10
|
+
createRokuPush,
|
|
8
11
|
decodeScreenshotResult,
|
|
12
|
+
filterDeviceModels,
|
|
13
|
+
formatDeviceModelsHuman,
|
|
9
14
|
isDevPlatform,
|
|
10
15
|
loadDevConfig,
|
|
11
16
|
readDevState,
|
|
12
17
|
renderEnvelope,
|
|
13
18
|
startDevSession
|
|
14
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-E2LZSLRF.js";
|
|
15
20
|
import {
|
|
16
21
|
ApiClient,
|
|
17
22
|
resolveAuth
|
|
18
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-PSFXVUHG.js";
|
|
19
24
|
|
|
20
25
|
// src/mcp/server.ts
|
|
21
26
|
import { writeFileSync } from "fs";
|
|
@@ -26,7 +31,7 @@ var SESSION_ID_PROPERTY = {
|
|
|
26
31
|
type: "string",
|
|
27
32
|
description: "Session ID to target. Optional: falls back to the MCP-server-held dev_start session, then this project's `vibeview dev --detach` state, if either is available."
|
|
28
33
|
};
|
|
29
|
-
var MCP_DEV_PLATFORMS =
|
|
34
|
+
var MCP_DEV_PLATFORMS = DEV_PLATFORMS;
|
|
30
35
|
function isMcpDevPlatform(p) {
|
|
31
36
|
return isDevPlatform(p) && MCP_DEV_PLATFORMS.includes(p);
|
|
32
37
|
}
|
|
@@ -75,10 +80,27 @@ var DEV_START_DEF = {
|
|
|
75
80
|
metro_port: {
|
|
76
81
|
type: "number",
|
|
77
82
|
description: "Local Metro port (default: 8081, or vibeview.json)."
|
|
83
|
+
},
|
|
84
|
+
model: {
|
|
85
|
+
type: "string",
|
|
86
|
+
description: 'Run on this exact device model, e.g. "iPhone Duo" for the foldable. Names come from list_device_models. Omit for a default phone (or TV on TV platforms).'
|
|
78
87
|
}
|
|
79
88
|
},
|
|
80
89
|
required: ["platform"]
|
|
81
90
|
};
|
|
91
|
+
var LIST_DEVICE_MODELS_DEF = {
|
|
92
|
+
name: "list_device_models",
|
|
93
|
+
description: 'List the device models you can start right now (model, OS version, platform, category, and form_factor "foldable" for a device that folds, like the iPhone Duo). Pass a model name to dev_start to run on it.',
|
|
94
|
+
mutating: false,
|
|
95
|
+
properties: {
|
|
96
|
+
device_type: {
|
|
97
|
+
type: "string",
|
|
98
|
+
description: "Only models of this device type.",
|
|
99
|
+
enum: ["ios", "android", "roku"]
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
required: []
|
|
103
|
+
};
|
|
82
104
|
var DEV_STOP_DEF = {
|
|
83
105
|
name: "dev_stop",
|
|
84
106
|
description: "Stop the dev-loop session started by dev_start.",
|
|
@@ -86,9 +108,16 @@ var DEV_STOP_DEF = {
|
|
|
86
108
|
properties: { session_id: SESSION_ID_PROPERTY },
|
|
87
109
|
required: []
|
|
88
110
|
};
|
|
111
|
+
var DEV_RELOAD_DEF = {
|
|
112
|
+
name: "dev_reload",
|
|
113
|
+
description: "Roku only: re-package the channel with the build command from vibeview.json (platforms.roku.build), upload it, and reload it into the session dev_start is holding (or, with no held session, this project's `vibeview dev --detach` session) \u2014 the MCP equivalent of pressing `r` in `vibeview dev`. Returns once the device restarts the channel (a few seconds); call ui_tree afterwards. Other platforms hot-reload through Metro and need no call.",
|
|
114
|
+
mutating: true,
|
|
115
|
+
properties: { session_id: SESSION_ID_PROPERTY },
|
|
116
|
+
required: []
|
|
117
|
+
};
|
|
89
118
|
var UPLOAD_APP_DEF = {
|
|
90
119
|
name: "upload_app",
|
|
91
|
-
description: "Upload an app build (zipped simulator .app for iOS, .apk for Android) and return its app_id for dev_start. The MCP equivalent of `vibeview upload-app`.",
|
|
120
|
+
description: "Upload an app build (zipped simulator .app for iOS/tvOS, .apk for Android/Android TV, or a Roku channel .zip) and return its app_id for dev_start. The MCP equivalent of `vibeview upload-app`.",
|
|
92
121
|
mutating: true,
|
|
93
122
|
properties: {
|
|
94
123
|
file: {
|
|
@@ -103,7 +132,14 @@ var UPLOAD_APP_DEF = {
|
|
|
103
132
|
required: ["file"]
|
|
104
133
|
};
|
|
105
134
|
function buildToolDefinitions() {
|
|
106
|
-
return [
|
|
135
|
+
return [
|
|
136
|
+
...AGENT_COMMANDS.map(toolDefFromCommand),
|
|
137
|
+
DEV_START_DEF,
|
|
138
|
+
DEV_STOP_DEF,
|
|
139
|
+
DEV_RELOAD_DEF,
|
|
140
|
+
UPLOAD_APP_DEF,
|
|
141
|
+
LIST_DEVICE_MODELS_DEF
|
|
142
|
+
];
|
|
107
143
|
}
|
|
108
144
|
function textResult(text, structuredContent, isError = false) {
|
|
109
145
|
return { content: [{ type: "text", text }], structuredContent, isError };
|
|
@@ -121,9 +157,15 @@ async function handleToolCall(toolName, args, deps) {
|
|
|
121
157
|
if (toolName === "dev_stop") {
|
|
122
158
|
return handleDevStop(deps);
|
|
123
159
|
}
|
|
160
|
+
if (toolName === "dev_reload") {
|
|
161
|
+
return handleDevReload(args, deps);
|
|
162
|
+
}
|
|
124
163
|
if (toolName === "upload_app") {
|
|
125
164
|
return handleUploadApp(args, deps);
|
|
126
165
|
}
|
|
166
|
+
if (toolName === "list_device_models") {
|
|
167
|
+
return handleListDeviceModels(args, deps);
|
|
168
|
+
}
|
|
127
169
|
const cmd = findCommand(toolName);
|
|
128
170
|
if (!cmd) {
|
|
129
171
|
return errorResult(`Unknown tool: ${toolName}`);
|
|
@@ -198,11 +240,6 @@ function optsFor(cmd, args) {
|
|
|
198
240
|
}
|
|
199
241
|
async function handleDevStart(args, deps) {
|
|
200
242
|
const platform = args.platform;
|
|
201
|
-
if (platform === "roku") {
|
|
202
|
-
return errorResult(
|
|
203
|
-
"dev_start does not support roku \u2014 the reload loop needs a foreground terminal; run `vibeview dev --platform roku` instead"
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
243
|
if (!isMcpDevPlatform(platform)) {
|
|
207
244
|
return errorResult(
|
|
208
245
|
`dev_start: platform must be one of ${MCP_DEV_PLATFORMS.join(", ")}, got "${String(platform)}"`
|
|
@@ -212,7 +249,8 @@ async function handleDevStart(args, deps) {
|
|
|
212
249
|
const result = await deps.devSession.start({
|
|
213
250
|
platform,
|
|
214
251
|
app: typeof args.app === "string" ? args.app : void 0,
|
|
215
|
-
metroPort: typeof args.metro_port === "number" ? args.metro_port : void 0
|
|
252
|
+
metroPort: typeof args.metro_port === "number" ? args.metro_port : void 0,
|
|
253
|
+
model: typeof args.model === "string" && args.model.trim() ? args.model : void 0
|
|
216
254
|
});
|
|
217
255
|
const payload = { session_id: result.sessionId, page_url: result.pageUrl };
|
|
218
256
|
return textResult(`dev session started: ${payload.session_id}
|
|
@@ -221,6 +259,30 @@ ${payload.page_url}`, payload);
|
|
|
221
259
|
return errorResult(`error: ${err instanceof Error ? err.message : String(err)}`);
|
|
222
260
|
}
|
|
223
261
|
}
|
|
262
|
+
async function handleListDeviceModels(args, deps) {
|
|
263
|
+
const list = deps.listDeviceModels ?? (() => new ApiClient(deps.auth.baseUrl, deps.auth.token).listDeviceModels());
|
|
264
|
+
let models;
|
|
265
|
+
try {
|
|
266
|
+
models = filterDeviceModels(await list(), {
|
|
267
|
+
type: typeof args.device_type === "string" ? args.device_type : void 0
|
|
268
|
+
});
|
|
269
|
+
} catch (err) {
|
|
270
|
+
return errorResult(`error: ${err instanceof Error ? err.message : String(err)}`);
|
|
271
|
+
}
|
|
272
|
+
return textResult(formatDeviceModelsHuman(models).trimEnd(), { models });
|
|
273
|
+
}
|
|
274
|
+
async function handleDevReload(args, deps) {
|
|
275
|
+
const requested = typeof args.session_id === "string" ? args.session_id : void 0;
|
|
276
|
+
try {
|
|
277
|
+
const { sessionId, seconds } = await deps.devSession.reload(requested);
|
|
278
|
+
return textResult(`channel reloaded in ${seconds.toFixed(1)}s`, {
|
|
279
|
+
session_id: sessionId,
|
|
280
|
+
seconds
|
|
281
|
+
});
|
|
282
|
+
} catch (err) {
|
|
283
|
+
return errorResult(`error: ${err instanceof Error ? err.message : String(err)}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
224
286
|
async function handleDevStop(deps) {
|
|
225
287
|
const current = deps.devSession.current();
|
|
226
288
|
try {
|
|
@@ -239,7 +301,7 @@ async function handleUploadApp(args, deps) {
|
|
|
239
301
|
}
|
|
240
302
|
const name = typeof args.name === "string" && args.name ? args.name : void 0;
|
|
241
303
|
const upload = deps.uploadApp ?? (async (f, n) => {
|
|
242
|
-
const { uploadAppBuild } = await import("./upload-app-
|
|
304
|
+
const { uploadAppBuild } = await import("./upload-app-ZAKAOJKE.js");
|
|
243
305
|
const { result: result2 } = await uploadAppBuild(f, { ...deps.auth, name: n });
|
|
244
306
|
return result2;
|
|
245
307
|
});
|
|
@@ -268,6 +330,22 @@ async function handleUploadApp(args, deps) {
|
|
|
268
330
|
}
|
|
269
331
|
function createDevSessionController(auth, webUrl) {
|
|
270
332
|
let handle = null;
|
|
333
|
+
let platform = null;
|
|
334
|
+
let presence = null;
|
|
335
|
+
let reloading = null;
|
|
336
|
+
const stopPresence = () => {
|
|
337
|
+
if (presence) clearInterval(presence);
|
|
338
|
+
presence = null;
|
|
339
|
+
};
|
|
340
|
+
const readRokuBuild = () => {
|
|
341
|
+
try {
|
|
342
|
+
return loadDevConfig(process.cwd())?.platforms?.roku?.build ?? null;
|
|
343
|
+
} catch (err) {
|
|
344
|
+
throw new Error(
|
|
345
|
+
`vibeview.json could not be read: ${err instanceof Error ? err.message : String(err)}`
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
};
|
|
271
349
|
return {
|
|
272
350
|
async start(cfg) {
|
|
273
351
|
if (handle) {
|
|
@@ -298,6 +376,7 @@ function createDevSessionController(auth, webUrl) {
|
|
|
298
376
|
}
|
|
299
377
|
}
|
|
300
378
|
const isTv = PLATFORM_DEVICE[cfg.platform].tv;
|
|
379
|
+
platform = cfg.platform;
|
|
301
380
|
handle = await startDevSession({
|
|
302
381
|
token: auth.token,
|
|
303
382
|
baseUrl: auth.baseUrl,
|
|
@@ -305,19 +384,92 @@ function createDevSessionController(auth, webUrl) {
|
|
|
305
384
|
platform: cfg.platform,
|
|
306
385
|
appId,
|
|
307
386
|
deviceCategory: isTv ? "tv" : entry?.deviceCategory ?? "phone",
|
|
387
|
+
// A named model decides the category itself (startDevSession).
|
|
388
|
+
model: cfg.model,
|
|
308
389
|
metroPort,
|
|
309
390
|
open: false
|
|
310
391
|
});
|
|
392
|
+
if (cfg.platform === "roku") {
|
|
393
|
+
const api = new ApiClient(auth.baseUrl, auth.token);
|
|
394
|
+
const sessionId = handle.sessionId;
|
|
395
|
+
presence = setInterval(() => {
|
|
396
|
+
api.touchSession(sessionId).catch(() => {
|
|
397
|
+
});
|
|
398
|
+
}, ROKU_LIVENESS_INTERVAL_MS);
|
|
399
|
+
presence.unref?.();
|
|
400
|
+
}
|
|
311
401
|
return handle;
|
|
312
402
|
},
|
|
313
403
|
async stop() {
|
|
404
|
+
stopPresence();
|
|
314
405
|
await handle?.stop();
|
|
315
406
|
handle = null;
|
|
407
|
+
platform = null;
|
|
316
408
|
},
|
|
317
409
|
current() {
|
|
318
410
|
return handle ? { sessionId: handle.sessionId, pageUrl: handle.pageUrl } : null;
|
|
411
|
+
},
|
|
412
|
+
async reload(requested) {
|
|
413
|
+
if (reloading) {
|
|
414
|
+
throw new Error(
|
|
415
|
+
"a reload is already in progress \u2014 wait for it to finish, then call dev_reload again"
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
reloading = reloadOnce(requested).finally(() => {
|
|
419
|
+
reloading = null;
|
|
420
|
+
});
|
|
421
|
+
return reloading;
|
|
319
422
|
}
|
|
320
423
|
};
|
|
424
|
+
async function reloadOnce(requested) {
|
|
425
|
+
{
|
|
426
|
+
let target;
|
|
427
|
+
if (handle) {
|
|
428
|
+
if (requested && requested !== handle.sessionId) {
|
|
429
|
+
throw new Error(
|
|
430
|
+
`dev_reload can only reload the session this server holds (${handle.sessionId}); got session_id "${requested}". Call dev_stop first, or omit session_id.`
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
target = {
|
|
434
|
+
sessionId: handle.sessionId,
|
|
435
|
+
platform: platform ?? "",
|
|
436
|
+
build: platform === "roku" ? readRokuBuild() : null
|
|
437
|
+
};
|
|
438
|
+
} else {
|
|
439
|
+
const state = readDevState(process.cwd());
|
|
440
|
+
if (!state || requested && requested !== state.session_id) {
|
|
441
|
+
throw new Error(
|
|
442
|
+
requested ? `session "${requested}" is neither held by dev_start nor this project's \`vibeview dev --detach\` session \u2014 dev_reload needs one of those two` : 'no dev session to reload \u2014 call dev_start with platform "roku" first'
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
target = { sessionId: state.session_id, platform: state.platform, build: readRokuBuild() };
|
|
446
|
+
}
|
|
447
|
+
if (target.platform !== "roku") {
|
|
448
|
+
throw new Error(
|
|
449
|
+
`dev_reload is for Roku channels; a ${target.platform} session hot-reloads through Metro on its own`
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
if (!target.build) {
|
|
453
|
+
throw new Error(
|
|
454
|
+
"no build config for roku \u2014 add platforms.roku.build ({ command, artifact }) to vibeview.json, or run `vibeview build --platform roku` once to set it up"
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
const started = Date.now();
|
|
458
|
+
const push = createRokuPush({
|
|
459
|
+
cwd: process.cwd(),
|
|
460
|
+
sessionId: target.sessionId,
|
|
461
|
+
build: target.build,
|
|
462
|
+
token: auth.token,
|
|
463
|
+
baseUrl: auth.baseUrl,
|
|
464
|
+
api: new ApiClient(auth.baseUrl, auth.token),
|
|
465
|
+
// Build output has no stdout to go to over MCP (stdout IS the
|
|
466
|
+
// protocol); stderr is the server's log.
|
|
467
|
+
log: (line) => console.error(line)
|
|
468
|
+
});
|
|
469
|
+
await push();
|
|
470
|
+
return { sessionId: target.sessionId, seconds: (Date.now() - started) / 1e3 };
|
|
471
|
+
}
|
|
472
|
+
}
|
|
321
473
|
}
|
|
322
474
|
function makeSessionResolver(devSession) {
|
|
323
475
|
return () => devSession.current()?.sessionId ?? readDevState(process.cwd())?.session_id;
|
|
@@ -373,6 +525,7 @@ async function runMcpServer(opts) {
|
|
|
373
525
|
}
|
|
374
526
|
export {
|
|
375
527
|
buildToolDefinitions,
|
|
528
|
+
createDevSessionController,
|
|
376
529
|
handleToolCall,
|
|
377
530
|
runMcpServer
|
|
378
531
|
};
|
package/package.json
CHANGED
|
@@ -156,6 +156,37 @@ it rather than letting it appear unexplained in their `git status`.
|
|
|
156
156
|
(`dpad_up`/`dpad_down`/`dpad_left`/`dpad_right`/`dpad_center`), or move
|
|
157
157
|
focus directly with `tap-focused` (moves focus AND activates) / `focus`
|
|
158
158
|
(moves focus only, no activation).
|
|
159
|
+
- **Foldable (iPhone Duo)**: start on it by model name —
|
|
160
|
+
`vibeview dev --detach --json --model "iPhone Duo"` (MCP: `dev_start` with
|
|
161
|
+
`model`). Exact names come from `vibeview list-devices --models` (MCP:
|
|
162
|
+
`list_device_models`); a foldable is marked `foldable`. It starts closed, on
|
|
163
|
+
the cover screen. Fold with a preset or an exact hinge angle, and rotate:
|
|
164
|
+
```bash
|
|
165
|
+
vibeview set-posture open --session <id> # closed | partial | open
|
|
166
|
+
vibeview set-posture --angle 75 --session <id> # 0 (shut) to 180 (flat)
|
|
167
|
+
vibeview rotate --session <id> # quarter turn clockwise
|
|
168
|
+
vibeview rotate --degrees 270 --session <id> # quarter turn back (180: half)
|
|
169
|
+
```
|
|
170
|
+
Each response says where the device ended up — posture, angle and which
|
|
171
|
+
screen is lit (`cover` or `inner`; the device decides — a small angle can
|
|
172
|
+
keep the inner screen lit on the way down), or the new orientation. Folding
|
|
173
|
+
switches screens of different sizes and rotating turns the picture, so run
|
|
174
|
+
`ui-tree` afterwards — old refs are stale. On other phones and tablets
|
|
175
|
+
`rotate` toggles portrait/landscape (only 90 is accepted); TV devices don't
|
|
176
|
+
rotate.
|
|
177
|
+
- **Roku channels (beta)**: start the session with
|
|
178
|
+
`vibeview dev --platform roku --detach` (or `dev_start` with platform
|
|
179
|
+
`roku` over MCP); after an edit run `vibeview dev-reload` (MCP:
|
|
180
|
+
`dev_reload`) to re-package, upload and restart the channel in the same
|
|
181
|
+
session — about five seconds. The remote's names are `press up/down/left/right/select/back`
|
|
182
|
+
(`dpad_*` also works); `home` is not forwarded. `type` lands in a focused
|
|
183
|
+
on-screen keyboard; the typed text shows up in the tree as the text box's
|
|
184
|
+
child label, which is what the response verifies against. `logs` returns
|
|
185
|
+
the channel's console output (its `print` lines, why it exited, and the
|
|
186
|
+
error plus backtrace after a runtime error); `open-url` is unavailable. A
|
|
187
|
+
Back on the channel's root
|
|
188
|
+
screen exits it; the session relaunches it within seconds, so a fresh
|
|
189
|
+
first screen after `press back` is not a crash.
|
|
159
190
|
|
|
160
191
|
## 4. When only a human can act — stop and hand off
|
|
161
192
|
|
|
@@ -207,6 +238,8 @@ human-readable text.
|
|
|
207
238
|
| `clear-text` | — | Clear the text in the currently focused input field. |
|
|
208
239
|
| `press` | `<button>` | Press a device button or perform a system gesture (home, back, d-pad, etc). |
|
|
209
240
|
| `open-url` | `<url>` | Open a deep link or URL in the app under test. |
|
|
241
|
+
| `set-posture` | `<closed\|partial\|open>` or `--angle <0-180>` | Fold or unfold a foldable device (iPhone Duo) to a preset or an exact hinge angle — exactly one. Reports posture, angle and lit screen. Errors on a device without a hinge. |
|
|
242
|
+
| `rotate` | `[--degrees <90\|180\|270>]` | Rotate the device and report the new orientation. Phones/tablets toggle portrait/landscape (90 only); a foldable turns a quarter clockwise by default. Not on TV. |
|
|
210
243
|
| `wait` | `[--ms <n>]` | Wait for a specified number of seconds before continuing (default: 2s). |
|
|
211
244
|
| `find` | `<text>` `[--below <text>] [--above <text>] [--near <text>]` | Find an element by text with optional spatial constraints. |
|
|
212
245
|
| `tap-focused` | `<ref>` | TV: move focus to an element and press SELECT in one step. |
|
|
@@ -216,9 +249,11 @@ Dev-loop lifecycle commands (not registry verbs, but needed for every run):
|
|
|
216
249
|
|
|
217
250
|
| Command | Purpose |
|
|
218
251
|
|---------|---------|
|
|
219
|
-
| `dev --detach --json` | Start a device + Metro-tunnel session in the background, emitting `session_ready`/`warning`/`error` JSON events on stdout. |
|
|
252
|
+
| `dev --detach --json` | Start a device + Metro-tunnel session in the background, emitting `session_ready`/`warning`/`error` JSON events on stdout. Add `--model "<name>"` to pick an exact device model. |
|
|
253
|
+
| `list-devices --models` | List the device models you can start (the names `--model` takes); foldables are marked `foldable`. |
|
|
220
254
|
| `dev-status` | Check on the current project's detached dev session. |
|
|
221
255
|
| `dev-stop` | End the current project's detached dev session. |
|
|
256
|
+
| `dev-reload` | Roku only: re-package, upload and restart the channel in the detached session (the `r` key of the foreground loop). |
|
|
222
257
|
|
|
223
258
|
## 7. If you're calling VibeView over MCP
|
|
224
259
|
|
|
@@ -230,13 +265,14 @@ hyphens replaced by underscores (`ui_tree`, `tap`, `scroll_to`, `long_press`,
|
|
|
230
265
|
`drag {from: "@e5", to: "300,400"}`, `alert {action: "get"}`) plus an
|
|
231
266
|
optional `session_id`.
|
|
232
267
|
|
|
233
|
-
|
|
268
|
+
These tools exist **only** over MCP, with no registry verb — they cover the
|
|
234
269
|
steps the CLI does with plain shell commands:
|
|
235
270
|
|
|
236
271
|
| MCP tool | Args | Purpose |
|
|
237
272
|
|----------|------|---------|
|
|
238
273
|
| `upload_app` | `file` (required path), `name` | Upload a debug build. The MCP equivalent of `vibeview upload-app`, so step 1 of the loop needs no shell. Returns the `app_id` to pass to `dev_start`. |
|
|
239
|
-
| `dev_start` | `platform` (required: `ios`/`android`/`tvos`/`androidtv`), `app`, `metro_port` | Start a dev-loop session held open by the MCP server. The MCP equivalent of `vibeview dev --detach
|
|
274
|
+
| `dev_start` | `platform` (required: `ios`/`android`/`tvos`/`androidtv`/`roku`), `app`, `metro_port`, `model` | Start a dev-loop session held open by the MCP server. The MCP equivalent of `vibeview dev --detach`; `model` picks an exact device model (e.g. `"iPhone Duo"`). Returns `page_url` — relay it to the developer immediately, same as step 5 of the loop. |
|
|
275
|
+
| `list_device_models` | `device_type` | List the device models you can start (model, OS, platform, category, `foldable` marker). The MCP equivalent of `vibeview list-devices --models`. |
|
|
240
276
|
| `dev_stop` | — | Stop the session `dev_start` started. The MCP equivalent of `vibeview dev-stop`. |
|
|
241
277
|
|
|
242
278
|
**`dev_start` is stateful — it changes the default session for every later
|