vibeview 0.8.0 → 0.9.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/README.md CHANGED
@@ -46,6 +46,14 @@ vibeview tap @e5 # act — the response says what changed
46
46
  vibeview screenshot --out ./check.png
47
47
  ```
48
48
 
49
+ One command makes it discoverable to your agent — it installs the bundled
50
+ `vibeview-agent` skill where Claude Code finds it automatically and offers
51
+ to register the MCP server:
52
+
53
+ ```bash
54
+ vibeview agent-setup
55
+ ```
56
+
49
57
  Every command works over MCP too, for agents without shell access:
50
58
 
51
59
  ```bash
@@ -53,7 +61,7 @@ claude mcp add vibeview -- vibeview mcp
53
61
  ```
54
62
 
55
63
  Other MCP clients (Cursor, Claude Desktop, VS Code Copilot) run command
56
- `vibeview` with args `["mcp"]`. The package also ships a ready-made skill at
64
+ `vibeview` with args `["mcp"]`. The package also ships the skill at
57
65
  `skills/vibeview-agent/SKILL.md` describing the whole workflow.
58
66
 
59
67
  [Agent control docs →](https://vibeview.io/docs/agent-control)
@@ -84,6 +92,7 @@ Output formats: `human`, `json`, and `junit` for any CI system. The exit code re
84
92
  | `vibeview dev` | Live development on a cloud device |
85
93
  | `vibeview build` | Build your app and upload it |
86
94
  | `vibeview dev-status` / `dev-stop` | Check on, or stop, a backgrounded dev session |
95
+ | `vibeview agent-setup` | Make VibeView discoverable to your coding agent (skill + MCP) |
87
96
  | `vibeview mcp` | Run the MCP server so an agent can drive the device |
88
97
  | `vibeview ui-tree` | The current screen's elements, each with a ref to act on |
89
98
  | `vibeview screenshot` | Capture the screen to a PNG |
@@ -866,6 +866,11 @@ async function startDevSession(cfg, onEvent = () => {
866
866
  tunnel.start();
867
867
  if (cfg.open !== false) await openBrowser(pageUrl).catch(() => {
868
868
  });
869
+ const active = await pollUntilActive(api, sessionId, 18e4);
870
+ const seen = new Set(ready.warnings ?? []);
871
+ for (const w of active.warnings ?? []) {
872
+ if (!seen.has(w)) onEvent({ event: "warning", message: w });
873
+ }
869
874
  return { sessionId, pageUrl, stop: () => stopSession(0) };
870
875
  } catch (err) {
871
876
  await stopSession(1);
@@ -890,6 +895,22 @@ async function pollUntilTunnelReady(api, sessionId, timeoutMs, intervalMs = 2e3)
890
895
  await new Promise((r) => setTimeout(r, intervalMs));
891
896
  }
892
897
  }
898
+ async function pollUntilActive(api, sessionId, timeoutMs, intervalMs = 2e3) {
899
+ const deadline = Date.now() + timeoutMs;
900
+ for (; ; ) {
901
+ const info = await api.getSession(sessionId);
902
+ const blocking = findBlockingWarning(info);
903
+ if (blocking) throw new Error(blocking);
904
+ if (info.status === "active") return info;
905
+ if (info.status === "stopped" || info.status === "failed") {
906
+ throw new Error(`session ${info.status} before it became active`);
907
+ }
908
+ if (Date.now() > deadline) {
909
+ throw new Error("timed out waiting for the session to become active");
910
+ }
911
+ await new Promise((r) => setTimeout(r, intervalMs));
912
+ }
913
+ }
893
914
  var PLATFORM_DEVICE = {
894
915
  ios: { deviceType: "ios", tv: false },
895
916
  android: { deviceType: "android", tv: false },
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  readDevState,
9
9
  readMetroState,
10
10
  runAgentVerb
11
- } from "./chunk-NZ4G3JKV.js";
11
+ } from "./chunk-WG2XR45H.js";
12
12
  import {
13
13
  ApiClient,
14
14
  EXIT_INFRA_ERROR,
@@ -1205,6 +1205,104 @@ function readMetadataFile(path) {
1205
1205
  return parsed;
1206
1206
  }
1207
1207
 
1208
+ // src/commands/agent-setup.ts
1209
+ import { copyFileSync, existsSync as existsSync2, mkdirSync } from "fs";
1210
+ import { homedir } from "os";
1211
+ import { dirname, join } from "path";
1212
+ import { fileURLToPath } from "url";
1213
+ import { spawnSync } from "child_process";
1214
+ import { confirm } from "@inquirer/prompts";
1215
+ import chalk11 from "chalk";
1216
+ function resolveShippedSkillPath(fromDir) {
1217
+ for (const rel of ["..", join("..", "..")]) {
1218
+ const candidate = join(fromDir, rel, "skills", "vibeview-agent", "SKILL.md");
1219
+ if (existsSync2(candidate)) return candidate;
1220
+ }
1221
+ return null;
1222
+ }
1223
+ function skillInstallDir(project, cwd, home) {
1224
+ const base = project ? cwd : home;
1225
+ return join(base, ".claude", "skills", "vibeview-agent");
1226
+ }
1227
+ function installSkill(shippedPath, targetDir) {
1228
+ const target = join(targetDir, "SKILL.md");
1229
+ const existed = existsSync2(target);
1230
+ mkdirSync(targetDir, { recursive: true });
1231
+ copyFileSync(shippedPath, target);
1232
+ return existed ? "updated" : "installed";
1233
+ }
1234
+ var CLAUDE_MD_SUGGESTION = "After changing UI or app behavior, verify it on a live device with the `vibeview` CLI \u2014 the vibeview-agent skill covers the workflow.";
1235
+ function claudeCliAvailable() {
1236
+ return spawnSync("claude", ["--version"], { stdio: "ignore" }).error === void 0;
1237
+ }
1238
+ function registerMcp() {
1239
+ const res = spawnSync("claude", ["mcp", "add", "vibeview", "--", "vibeview", "mcp"], {
1240
+ encoding: "utf8"
1241
+ });
1242
+ if (res.status === 0) {
1243
+ console.log(chalk11.green("\u2713 MCP server registered with Claude Code (claude mcp add vibeview)"));
1244
+ return;
1245
+ }
1246
+ const out = `${res.stderr ?? ""}${res.stdout ?? ""}`;
1247
+ if (out.includes("already exists")) {
1248
+ console.log(chalk11.dim("MCP server already registered with Claude Code \u2014 nothing to do"));
1249
+ return;
1250
+ }
1251
+ console.error(chalk11.yellow(`\u26A0 could not register the MCP server: ${out.trim() || res.error}`));
1252
+ console.error(chalk11.yellow(" register it manually: claude mcp add vibeview -- vibeview mcp"));
1253
+ }
1254
+ async function agentSetup(opts) {
1255
+ const moduleDir = dirname(fileURLToPath(import.meta.url));
1256
+ const shipped = resolveShippedSkillPath(moduleDir);
1257
+ if (!shipped) {
1258
+ console.error(
1259
+ chalk11.red(
1260
+ "error: could not find the packaged skill (skills/vibeview-agent/SKILL.md) \u2014 is this a complete install of the vibeview package?"
1261
+ )
1262
+ );
1263
+ process.exitCode = 1;
1264
+ return;
1265
+ }
1266
+ const targetDir = skillInstallDir(opts.project ?? false, process.cwd(), homedir());
1267
+ const result = installSkill(shipped, targetDir);
1268
+ console.log(chalk11.green(`\u2713 vibeview-agent skill ${result}: ${join(targetDir, "SKILL.md")}`));
1269
+ console.log(
1270
+ chalk11.dim(
1271
+ opts.project ? " Claude Code discovers it automatically for anyone working in this project." : " Claude Code discovers it automatically in every project on this machine."
1272
+ )
1273
+ );
1274
+ if (opts.mcp !== false) {
1275
+ if (!claudeCliAvailable()) {
1276
+ if (opts.mcp === true) {
1277
+ console.error(
1278
+ chalk11.yellow(
1279
+ "\u26A0 claude CLI not found \u2014 install Claude Code, then run: claude mcp add vibeview -- vibeview mcp"
1280
+ )
1281
+ );
1282
+ }
1283
+ } else if (opts.mcp === true || process.stdin.isTTY && await confirm({
1284
+ message: "Register the MCP server with Claude Code? (claude mcp add vibeview)",
1285
+ default: true
1286
+ })) {
1287
+ registerMcp();
1288
+ }
1289
+ }
1290
+ console.log(
1291
+ `
1292
+ Suggested line for your project's ${chalk11.bold("CLAUDE.md")} (or ${chalk11.bold(
1293
+ "AGENTS.md"
1294
+ )} for other agents):
1295
+ `
1296
+ );
1297
+ console.log(` ${CLAUDE_MD_SUGGESTION}
1298
+ `);
1299
+ console.log(
1300
+ chalk11.dim(
1301
+ "Other agents (Cursor, Copilot, Codex, ...): point them at the skill file above, or\nadd the MCP server per client \u2014 see https://vibeview.io/docs/agent-control"
1302
+ )
1303
+ );
1304
+ }
1305
+
1208
1306
  // src/cli.ts
1209
1307
  var require2 = createRequire(import.meta.url);
1210
1308
  var pkg = require2("../package.json");
@@ -1350,9 +1448,12 @@ program.command("dev-stop").description("Stop a `vibeview dev --detach` session
1350
1448
  program.command("mcp").description(
1351
1449
  "Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop as tools"
1352
1450
  ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
1353
- const { runMcpServer } = await import("./server-JZAEEHZT.js");
1451
+ const { runMcpServer } = await import("./server-S3D724K3.js");
1354
1452
  await runMcpServer(opts);
1355
1453
  });
1454
+ program.command("agent-setup").description(
1455
+ "Make VibeView discoverable to your coding agent: install the vibeview-agent skill (and optionally register the MCP server with Claude Code)"
1456
+ ).option("--project", "Install into this project's .claude/skills/ instead of ~/.claude/skills/").option("--mcp", "Register the MCP server with Claude Code without asking").option("--no-mcp", "Skip MCP registration").action((opts) => agentSetup(opts));
1356
1457
  for (const cmd of AGENT_COMMANDS) {
1357
1458
  const positionalParams = cmd.params.filter((p) => p.cli === "positional");
1358
1459
  const flagParams = cmd.params.filter((p) => p.cli !== "positional");
@@ -10,7 +10,7 @@ import {
10
10
  readDevState,
11
11
  renderEnvelope,
12
12
  startDevSession
13
- } from "./chunk-NZ4G3JKV.js";
13
+ } from "./chunk-WG2XR45H.js";
14
14
  import {
15
15
  ApiClient,
16
16
  resolveAuth
@@ -55,7 +55,7 @@ function toolDefFromCommand(cmd) {
55
55
  }
56
56
  var DEV_START_DEF = {
57
57
  name: "dev_start",
58
- description: "Start a VibeView dev-loop session (device + tunnel) held in-process by this MCP server, and use it as the default session for every other tool call until dev_stop.",
58
+ description: "Start a VibeView dev-loop session (device + tunnel) held in-process by this MCP server, and use it as the default session for every other tool call until dev_stop. Returns once the session accepts commands (device up, app installed) \u2014 call ui_tree right away, no readiness polling needed.",
59
59
  mutating: true,
60
60
  properties: {
61
61
  session_id: SESSION_ID_PROPERTY,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeview",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "VibeView CLI -- run mobile tests from CI/CD pipelines",
5
5
  "type": "module",
6
6
  "bin": {
@@ -46,9 +46,10 @@ https://vibeview.io/docs/preparing-your-build.
46
46
  ```json
47
47
  {"event":"session_ready","session_id":"...","url":"...","pid":1234}
48
48
  ```
49
- Use `session_id` as `--session <id>` on every verb below. If the app
50
- ships no native changes since the last run, skip step 1 and just do
51
- steps 2–4.
49
+ Use `session_id` as `--session <id>` on every verb below. The event
50
+ fires once the session accepts commands (device up, app installed) —
51
+ no need to poll `dev-status` first. If the app ships no native changes
52
+ since the last run, skip step 1 and just do steps 2–4.
52
53
 
53
54
  Running `dev` writes two things into the project root: a `.vibeview/`
54
55
  directory (live session state — local machine state, never commit) and a
@@ -64,6 +65,10 @@ it rather than letting it appear unexplained in their `git status`.
64
65
  ```bash
65
66
  vibeview ui-tree --session <id>
66
67
  ```
68
+ An empty or near-empty first tree usually means the app is still booting
69
+ (splash screen, fetching remote config) — NOT a broken session. Take a
70
+ screenshot to confirm, `wait`, and fetch the tree again before concluding
71
+ anything is wrong.
67
72
  - Act on the screen using refs from the tree:
68
73
  ```bash
69
74
  vibeview tap @e5 --session <id>