vibeview 0.34.0 → 0.36.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-4D2E2PE4.js → chunk-QRJAFBH3.js} +52 -10
- package/dist/{chunk-TPZG7NCU.js → chunk-VFCG4U7V.js} +393 -97
- package/dist/cli.js +7 -4
- package/dist/{server-RDGEVJD4.js → server-2CDIRTBV.js} +106 -8
- package/dist/{upload-app-AKD73742.js → upload-app-BPTGBDQB.js} +1 -1
- package/package.json +2 -2
- package/skills/vibeview-agent/SKILL.md +9 -1
package/dist/cli.js
CHANGED
|
@@ -23,8 +23,9 @@ import {
|
|
|
23
23
|
readDevState,
|
|
24
24
|
readDevStateFile,
|
|
25
25
|
readMetroState,
|
|
26
|
+
recordDevUse,
|
|
26
27
|
runAgentVerb
|
|
27
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-VFCG4U7V.js";
|
|
28
29
|
import {
|
|
29
30
|
ApiClient,
|
|
30
31
|
ApiStatusError,
|
|
@@ -45,7 +46,7 @@ import {
|
|
|
45
46
|
resolveAuth,
|
|
46
47
|
saveConfig,
|
|
47
48
|
uploadApp
|
|
48
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-QRJAFBH3.js";
|
|
49
50
|
|
|
50
51
|
// src/cli.ts
|
|
51
52
|
import { createRequire } from "module";
|
|
@@ -412,6 +413,7 @@ function printStepLines(stepNum, total, status, text, actions, error) {
|
|
|
412
413
|
process.stderr.write(` ${icon} Step ${stepNum}/${total}: ${text}
|
|
413
414
|
`);
|
|
414
415
|
for (const action of actions ?? []) {
|
|
416
|
+
if (action.tool === "fast_replay") continue;
|
|
415
417
|
process.stderr.write(` ${chalk2.dim(formatAction(action))}
|
|
416
418
|
`);
|
|
417
419
|
}
|
|
@@ -1786,6 +1788,7 @@ async function devReload(opts) {
|
|
|
1786
1788
|
} catch (err) {
|
|
1787
1789
|
fail(opts, "reload_failed", err instanceof Error ? err.message : String(err), 1);
|
|
1788
1790
|
}
|
|
1791
|
+
recordDevUse(state.session_id);
|
|
1789
1792
|
if (opts.json) {
|
|
1790
1793
|
console.log(
|
|
1791
1794
|
JSON.stringify({
|
|
@@ -3834,7 +3837,7 @@ function resolveMetadata(opts) {
|
|
|
3834
3837
|
}
|
|
3835
3838
|
var program = new Command();
|
|
3836
3839
|
program.name("vibeview").description(
|
|
3837
|
-
"VibeView CLI -- run your app on cloud iOS simulators, Android emulators, Apple TV, Android TV and Roku
|
|
3840
|
+
"VibeView CLI -- run your app on cloud iOS simulators, Android emulators, Apple TV, Android TV and Roku devices: a React Native dev loop (dev), device control for you or your coding agent (ui-tree, tap, mcp), and tests from CI (run-test, run-suites)"
|
|
3838
3841
|
).version(pkg.version);
|
|
3839
3842
|
program.command("run-suite <suite-id>").description("Run all test cases in a suite").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--output <format>", "Output format: human, json, junit", "human").option("--verbose", "Show per-case breakdown", false).option("--timeout <seconds>", "Max wait time in seconds (queue wait plus execution)", "1800").option("--commit-sha <sha>", "Git commit SHA for GitHub status checks").option("--model <model>", "AI model to use").option("--provider <provider>", "AI provider").option("--mode <mode>", "Execution mode: hybrid, ai, replay").option(
|
|
3840
3843
|
"--device-category <category>",
|
|
@@ -4144,7 +4147,7 @@ program.command("dev-reload").description(
|
|
|
4144
4147
|
program.command("mcp").description(
|
|
4145
4148
|
"Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop/dev_reload as tools"
|
|
4146
4149
|
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
|
|
4147
|
-
const { runMcpServer } = await import("./server-
|
|
4150
|
+
const { runMcpServer } = await import("./server-2CDIRTBV.js");
|
|
4148
4151
|
await runMcpServer({ ...opts, version: pkg.version });
|
|
4149
4152
|
});
|
|
4150
4153
|
program.command("agent-setup").description(
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
buildToolCall,
|
|
10
10
|
checkDevBuild,
|
|
11
11
|
checkMetro,
|
|
12
|
+
countsAsUse,
|
|
12
13
|
createRokuPush,
|
|
13
14
|
decodeScreenshotResult,
|
|
14
15
|
describeDevBuild,
|
|
@@ -20,17 +21,19 @@ import {
|
|
|
20
21
|
loadDevConfig,
|
|
21
22
|
loadLocalDevConfig,
|
|
22
23
|
parseBuildId,
|
|
24
|
+
parseIdleWarningSeconds,
|
|
23
25
|
platformWithArticle,
|
|
24
26
|
readDevState,
|
|
27
|
+
recordDevUse,
|
|
25
28
|
renderEnvelope,
|
|
26
29
|
resolveMetro,
|
|
27
30
|
startDevSession
|
|
28
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-VFCG4U7V.js";
|
|
29
32
|
import {
|
|
30
33
|
ApiClient,
|
|
31
34
|
NotFoundError,
|
|
32
35
|
resolveAuth
|
|
33
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-QRJAFBH3.js";
|
|
34
37
|
|
|
35
38
|
// src/mcp/server.ts
|
|
36
39
|
import { writeFileSync } from "fs";
|
|
@@ -241,9 +244,15 @@ function capitalize(text) {
|
|
|
241
244
|
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
242
245
|
}
|
|
243
246
|
function endedLine(ended) {
|
|
247
|
+
if (ended.status === "replaced") {
|
|
248
|
+
return `session ${ended.sessionId} was taken over by another VibeView dev connection and is no longer held here (it keeps running there)`;
|
|
249
|
+
}
|
|
244
250
|
const why = ended.reason ? `: ${ended.reason}` : "";
|
|
245
251
|
return `session ${ended.sessionId} has ended (${ended.status}${why})`;
|
|
246
252
|
}
|
|
253
|
+
function idleNoticeText(sessionId, seconds) {
|
|
254
|
+
return `Session ${sessionId} ends in ${seconds} s for inactivity: a device action (tap, type, swipe, press, \u2026) keeps it; ui_tree, screenshot, logs, find and wait do not.`;
|
|
255
|
+
}
|
|
247
256
|
async function explainInactive(sessionId, deps) {
|
|
248
257
|
let info;
|
|
249
258
|
try {
|
|
@@ -266,7 +275,7 @@ async function explainInactive(sessionId, deps) {
|
|
|
266
275
|
function findCommand(toolName) {
|
|
267
276
|
return AGENT_COMMANDS.find((c) => toolNameFor(c.verb) === toolName);
|
|
268
277
|
}
|
|
269
|
-
async function
|
|
278
|
+
async function dispatchToolCall(toolName, args, deps, extra = {}) {
|
|
270
279
|
if (toolName === "dev_start") {
|
|
271
280
|
return handleDevStart(args, deps, extra);
|
|
272
281
|
}
|
|
@@ -331,6 +340,55 @@ async function handleToolCall(toolName, args, deps, extra = {}) {
|
|
|
331
340
|
}
|
|
332
341
|
return textResult(renderEnvelope(envelope, toolNameFor(cmd.verb)), envelope, !envelope.ok);
|
|
333
342
|
}
|
|
343
|
+
async function handleToolCall(toolName, args, deps, extra = {}) {
|
|
344
|
+
const held = deps.devSession.current?.()?.sessionId;
|
|
345
|
+
const dispatched = typeof args.session_id === "string" ? args.session_id.trim() : deps.resolveSessionId();
|
|
346
|
+
const usesBefore = deps.devSession.useGeneration?.();
|
|
347
|
+
const result = await dispatchToolCall(toolName, args, deps, extra);
|
|
348
|
+
noteDeviceUse(toolName, result, deps, held, dispatched);
|
|
349
|
+
const usedSince = usesBefore !== void 0 && (deps.devSession.useGeneration?.() ?? usesBefore) > usesBefore;
|
|
350
|
+
return withIdleNotice(toolName, args, result, deps, held, dispatched, usedSince);
|
|
351
|
+
}
|
|
352
|
+
function noteDeviceUse(toolName, result, deps, held, dispatched) {
|
|
353
|
+
const cmd = findCommand(toolName);
|
|
354
|
+
if (!cmd || !dispatched || !result.structuredContent) return;
|
|
355
|
+
if (!countsAsUse(cmd.tool)) return;
|
|
356
|
+
if (dispatched === held) deps.devSession.noteUse?.(dispatched);
|
|
357
|
+
else recordDevUse(dispatched);
|
|
358
|
+
}
|
|
359
|
+
var IDLE_NOTICE_DISCARD = /* @__PURE__ */ new Set(["dev_start", "dev_stop", "dev_reload"]);
|
|
360
|
+
function withIdleNotice(toolName, args, result, deps, held, dispatched, usedSince = false) {
|
|
361
|
+
const dev = deps.devSession;
|
|
362
|
+
const stopsHeld = toolName === "stop_session" && held !== void 0 && (typeof args.session_id === "string" ? args.session_id.trim() : held) === held;
|
|
363
|
+
if ((IDLE_NOTICE_DISCARD.has(toolName) || stopsHeld) && !result.isError) {
|
|
364
|
+
const about = toolName === "dev_start" ? dev.current?.()?.sessionId : held;
|
|
365
|
+
if (about !== void 0) dev.takeIdleWarning?.(about);
|
|
366
|
+
return result;
|
|
367
|
+
}
|
|
368
|
+
if (findCommand(toolName) && result.structuredContent && typeof result.structuredContent === "object") {
|
|
369
|
+
const own = result.structuredContent.idle_warning;
|
|
370
|
+
if (own !== void 0) {
|
|
371
|
+
const target = dispatched;
|
|
372
|
+
if (target !== void 0 && target === held) dev.takeIdleWarning?.(target);
|
|
373
|
+
const seconds = parseIdleWarningSeconds(own?.seconds_remaining);
|
|
374
|
+
if (usedSince && target === held) return result;
|
|
375
|
+
if (target && seconds !== null) return appendNotice(result, idleNoticeText(target, seconds));
|
|
376
|
+
return result;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
const notice = dev.takeIdleWarning?.() ?? null;
|
|
380
|
+
return notice ? appendNotice(result, notice) : result;
|
|
381
|
+
}
|
|
382
|
+
function appendNotice(result, notice) {
|
|
383
|
+
const i = result.content.findIndex((b) => b.type === "text");
|
|
384
|
+
if (i === -1) return { ...result, content: [...result.content, { type: "text", text: notice }] };
|
|
385
|
+
const content = result.content.slice();
|
|
386
|
+
const block = content[i];
|
|
387
|
+
content[i] = { type: "text", text: `${block.text}
|
|
388
|
+
|
|
389
|
+
${notice}` };
|
|
390
|
+
return { ...result, content };
|
|
391
|
+
}
|
|
334
392
|
function screenshotToolResult(envelope, args) {
|
|
335
393
|
const { image, preview } = splitScreenshotResult(envelope.result);
|
|
336
394
|
let imageBytes;
|
|
@@ -613,7 +671,7 @@ async function handleDevStop(deps) {
|
|
|
613
671
|
}
|
|
614
672
|
if (!current) {
|
|
615
673
|
return textResult(
|
|
616
|
-
ended ? `no dev session was running \u2014 it had already ended. ${capitalize(endedLine(ended))}` : "no dev session was running",
|
|
674
|
+
ended ? ended.status === "replaced" ? `no dev session was running here. ${capitalize(endedLine(ended))}` : `no dev session was running \u2014 it had already ended. ${capitalize(endedLine(ended))}` : "no dev session was running",
|
|
617
675
|
{ session_id: null, stopped: false }
|
|
618
676
|
);
|
|
619
677
|
}
|
|
@@ -629,7 +687,7 @@ async function handleUploadApp(args, deps) {
|
|
|
629
687
|
}
|
|
630
688
|
const name = typeof args.name === "string" && args.name ? args.name : void 0;
|
|
631
689
|
const upload = deps.uploadApp ?? (async (f, n) => {
|
|
632
|
-
const { uploadAppBuild } = await import("./upload-app-
|
|
690
|
+
const { uploadAppBuild } = await import("./upload-app-BPTGBDQB.js");
|
|
633
691
|
const { result: result2 } = await uploadAppBuild(f, { ...deps.auth, name: n });
|
|
634
692
|
return result2;
|
|
635
693
|
});
|
|
@@ -725,6 +783,7 @@ function createDevSessionController(auth, webUrl) {
|
|
|
725
783
|
let ended = null;
|
|
726
784
|
let reloading = null;
|
|
727
785
|
let pending = null;
|
|
786
|
+
let idle = null;
|
|
728
787
|
const readRokuBuild = () => {
|
|
729
788
|
try {
|
|
730
789
|
return loadDevConfig(process.cwd())?.platforms?.roku?.build ?? null;
|
|
@@ -739,10 +798,25 @@ function createDevSessionController(auth, webUrl) {
|
|
|
739
798
|
const dead = handle;
|
|
740
799
|
handle = null;
|
|
741
800
|
platform = null;
|
|
801
|
+
idle = null;
|
|
742
802
|
ended = { ...e, reason: e.reason ?? dead.lapsedReason?.() ?? null };
|
|
743
803
|
void dead.stop().catch(() => {
|
|
744
804
|
});
|
|
745
805
|
};
|
|
806
|
+
const markReplaced = (sessionId) => {
|
|
807
|
+
if (!handle || handle.sessionId !== sessionId) return;
|
|
808
|
+
handle = null;
|
|
809
|
+
platform = null;
|
|
810
|
+
idle = null;
|
|
811
|
+
ended = { sessionId, status: "replaced", reason: null };
|
|
812
|
+
};
|
|
813
|
+
let uses = 0;
|
|
814
|
+
const noteHeldUse = (sessionId) => {
|
|
815
|
+
if (!handle || handle.sessionId !== sessionId) return;
|
|
816
|
+
uses++;
|
|
817
|
+
handle.noteUse?.();
|
|
818
|
+
if (idle?.sessionId === sessionId) idle = null;
|
|
819
|
+
};
|
|
746
820
|
const api = () => new ApiClient(auth.baseUrl, auth.token);
|
|
747
821
|
return {
|
|
748
822
|
async start(cfg) {
|
|
@@ -832,11 +906,18 @@ function createDevSessionController(auth, webUrl) {
|
|
|
832
906
|
onCreated: (created) => {
|
|
833
907
|
pending = created;
|
|
834
908
|
},
|
|
835
|
-
onDequeued: () => progress.dequeued()
|
|
909
|
+
onDequeued: () => progress.dequeued(),
|
|
910
|
+
onIdleWarning: (id, warning) => {
|
|
911
|
+
idle = warning ? { sessionId: id, endsAt: Date.now() + warning.seconds_remaining * 1e3 } : null;
|
|
912
|
+
}
|
|
836
913
|
},
|
|
837
914
|
(evt) => {
|
|
838
915
|
progress.event(evt);
|
|
839
916
|
if (evt.event === "warning") warnings.push(evt.message);
|
|
917
|
+
if (evt.event === "session_ended" && evt.replaced && started) {
|
|
918
|
+
markReplaced(evt.session_id);
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
840
921
|
if (evt.event === "session_ended" && evt.code !== 0 && started) {
|
|
841
922
|
const id = evt.session_id;
|
|
842
923
|
void api().getSession(id).then(
|
|
@@ -882,6 +963,7 @@ function createDevSessionController(auth, webUrl) {
|
|
|
882
963
|
await handle?.stop();
|
|
883
964
|
handle = null;
|
|
884
965
|
platform = null;
|
|
966
|
+
idle = null;
|
|
885
967
|
ended = null;
|
|
886
968
|
},
|
|
887
969
|
async shutdown() {
|
|
@@ -890,6 +972,7 @@ function createDevSessionController(auth, webUrl) {
|
|
|
890
972
|
const starting = pending;
|
|
891
973
|
handle = null;
|
|
892
974
|
platform = null;
|
|
975
|
+
idle = null;
|
|
893
976
|
pending = null;
|
|
894
977
|
await Promise.all([
|
|
895
978
|
held ? held.stop().then(
|
|
@@ -912,6 +995,17 @@ function createDevSessionController(auth, webUrl) {
|
|
|
912
995
|
lastEnded() {
|
|
913
996
|
return ended;
|
|
914
997
|
},
|
|
998
|
+
noteUse: noteHeldUse,
|
|
999
|
+
useGeneration: () => uses,
|
|
1000
|
+
takeIdleWarning(sessionId) {
|
|
1001
|
+
const w = idle;
|
|
1002
|
+
if (sessionId !== void 0 && w?.sessionId !== sessionId) return null;
|
|
1003
|
+
idle = null;
|
|
1004
|
+
if (!w || !handle || handle.sessionId !== w.sessionId) return null;
|
|
1005
|
+
const seconds = Math.ceil((w.endsAt - Date.now()) / 1e3);
|
|
1006
|
+
if (seconds <= 0) return null;
|
|
1007
|
+
return idleNoticeText(w.sessionId, seconds);
|
|
1008
|
+
},
|
|
915
1009
|
async reload(requested) {
|
|
916
1010
|
if (reloading) {
|
|
917
1011
|
throw new Error(
|
|
@@ -970,6 +1064,8 @@ function createDevSessionController(auth, webUrl) {
|
|
|
970
1064
|
log: (line) => console.error(line)
|
|
971
1065
|
});
|
|
972
1066
|
await push();
|
|
1067
|
+
if (handle?.sessionId === target.sessionId) noteHeldUse(target.sessionId);
|
|
1068
|
+
else recordDevUse(target.sessionId);
|
|
973
1069
|
return { sessionId: target.sessionId, seconds: (Date.now() - started) / 1e3 };
|
|
974
1070
|
}
|
|
975
1071
|
}
|
|
@@ -978,12 +1074,13 @@ function makeSessionResolver(devSession) {
|
|
|
978
1074
|
return () => devSession.current()?.sessionId ?? readDevState(process.cwd())?.session_id;
|
|
979
1075
|
}
|
|
980
1076
|
var MCP_INSTRUCTIONS = [
|
|
981
|
-
"VibeView runs your app on a cloud iOS simulator, Android emulator, Apple TV, Android TV or Roku
|
|
1077
|
+
"VibeView runs your app on a cloud iOS simulator, Android emulator, Apple TV, Android TV or Roku device you control with these tools.",
|
|
982
1078
|
"Loop: dev_start (platform, app from list_apps; add standalone: true for a release or CI build that needs no Metro) -> ui_tree -> act with refs (tap @e5, type, swipe, press) and read the change each action reports -> dev_stop when done.",
|
|
983
1079
|
"Foldables: pick one by model in dev_start (list_device_models marks them foldable, with free/busy counts), then set_posture and rotate.",
|
|
984
1080
|
"set_location sets the simulated GPS position (phones and tablets) for location-based flows.",
|
|
985
1081
|
"screenshot writes a PNG and returns its path; pass inline: true to see the image. logs reads the app output after a crash or blank screen.",
|
|
986
|
-
"A session stopped elsewhere is reported as ended; call dev_start again for a new one."
|
|
1082
|
+
"A session stopped elsewhere is reported as ended; call dev_start again for a new one.",
|
|
1083
|
+
'A session nobody uses ends after the idle timeout. A result that ends with "Session \u2026 ends in N s for inactivity" means act (tap, type, swipe, press) to keep it, or dev_stop if you are done; ui_tree, screenshot, logs, find and wait do not count as use.'
|
|
987
1084
|
].join("\n");
|
|
988
1085
|
async function createMcpServer(opts) {
|
|
989
1086
|
const [{ McpServer }, { z }] = await Promise.all([
|
|
@@ -1100,6 +1197,7 @@ export {
|
|
|
1100
1197
|
deviceLine,
|
|
1101
1198
|
forMcp,
|
|
1102
1199
|
handleToolCall,
|
|
1200
|
+
idleNoticeText,
|
|
1103
1201
|
mcpText,
|
|
1104
1202
|
progressReporter,
|
|
1105
1203
|
runMcpServer,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibeview",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "VibeView CLI -- run your app on cloud iOS simulators, Android emulators, Apple TV, Android TV and Roku
|
|
3
|
+
"version": "0.36.0",
|
|
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",
|
|
7
7
|
"bin": {
|
|
@@ -211,7 +211,7 @@ appear unexplained in their `git status`.
|
|
|
211
211
|
`ui-tree` afterwards — old refs are stale. On other phones and tablets
|
|
212
212
|
`rotate` toggles portrait/landscape (only 90 is accepted); TV devices don't
|
|
213
213
|
rotate.
|
|
214
|
-
- **Roku channels
|
|
214
|
+
- **Roku channels**: start the session with
|
|
215
215
|
`vibeview dev --platform roku --detach` (or `dev_start` with platform
|
|
216
216
|
`roku` over MCP); after an edit run `vibeview dev-reload` (MCP:
|
|
217
217
|
`dev_reload`) to re-package, upload and restart the channel in the same
|
|
@@ -254,6 +254,10 @@ vibeview dev-stop
|
|
|
254
254
|
If the `dev --detach` process is killed or the computer sleeps, its session
|
|
255
255
|
ends on its own within about 90 seconds (unless a browser tab is watching
|
|
256
256
|
it) — but don't rely on that; stop it.
|
|
257
|
+
A session nobody uses also ends on its own after the organization's idle
|
|
258
|
+
timeout (30 minutes by default). Only device actions (tap, type, swipe,
|
|
259
|
+
press, …) and app reloads count as use; `ui-tree`, `screenshot`, `logs`,
|
|
260
|
+
`find` and `wait` do not, and neither does keeping `dev --detach` running.
|
|
257
261
|
A session you did not start with `dev` (one created through the API, the
|
|
258
262
|
sandbox, or a collaboration link) is stopped by id:
|
|
259
263
|
```bash
|
|
@@ -332,6 +336,10 @@ steps the CLI does with plain shell commands:
|
|
|
332
336
|
If the held session ends elsewhere (dashboard, idle timeout, failure), the
|
|
333
337
|
next tool call answers "session … has ended (…) — call dev_start"; the
|
|
334
338
|
server has already let it go, so `dev_start` starts a fresh one.
|
|
339
|
+
A tool result that ends with `Session … ends in N s for inactivity` means
|
|
340
|
+
the held session is about to time out: act on the device to keep it, or call
|
|
341
|
+
`dev_stop` if you are done. A session another VibeView dev connection took
|
|
342
|
+
over is reported as taken over; it keeps running there, so don't stop it.
|
|
335
343
|
|
|
336
344
|
**`dev_start` is stateful — it changes the default session for every later
|
|
337
345
|
call.** Once it succeeds, any tool called without an explicit `session_id`
|