vibeview 0.9.0 → 0.10.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
@@ -95,6 +95,7 @@ Output formats: `human`, `json`, and `junit` for any CI system. The exit code re
95
95
  | `vibeview agent-setup` | Make VibeView discoverable to your coding agent (skill + MCP) |
96
96
  | `vibeview mcp` | Run the MCP server so an agent can drive the device |
97
97
  | `vibeview ui-tree` | The current screen's elements, each with a ref to act on |
98
+ | `vibeview logs` | Recent app logs from the device (console output, errors, crashes) |
98
99
  | `vibeview screenshot` | Capture the screen to a PNG |
99
100
  | `vibeview tap` / `type` / `scroll` / `press` | Drive the device |
100
101
  | `vibeview scroll-to` / `drag` / `alert` / `find` | Search, precise gestures, iOS system alerts |
@@ -495,6 +495,8 @@ function fail(opts, message, code) {
495
495
 
496
496
  // src/commands/dev.ts
497
497
  import { spawn as spawn3 } from "child_process";
498
+ import { createWriteStream, mkdirSync as mkdirSync3 } from "fs";
499
+ import { dirname as dirname4 } from "path";
498
500
  import { createInterface as createInterface3 } from "readline";
499
501
  import { select as select3 } from "@inquirer/prompts";
500
502
  import chalk2 from "chalk";
@@ -676,6 +678,9 @@ var STATE_FILE2 = "metro-state.json";
676
678
  function statePath2(cwd) {
677
679
  return join5(cwd, STATE_DIR2, STATE_FILE2);
678
680
  }
681
+ function metroLogPath(cwd) {
682
+ return join5(cwd, STATE_DIR2, "metro.log");
683
+ }
679
684
  function isPidAlive2(pid) {
680
685
  try {
681
686
  process.kill(pid, 0);
@@ -720,6 +725,11 @@ function killOwnedMetro(cwd) {
720
725
 
721
726
  // src/lib/metro-manager.ts
722
727
  var TAIL_LINES2 = 20;
728
+ var METRO_ECHO_RE = /error|failed|unable to|exception|bundled |bundling failed/i;
729
+ function shouldEchoMetroLine(line, debug) {
730
+ if (debug) return true;
731
+ return METRO_ECHO_RE.test(line);
732
+ }
723
733
  async function ensureMetro(cwd, metro, log, timeoutMs = 9e4) {
724
734
  const warnings = [];
725
735
  const status = await checkMetro(metro.port);
@@ -1151,6 +1161,18 @@ async function runDevInProcess(opts) {
1151
1161
  const metroPort = cfg.metroPort ?? 8081;
1152
1162
  let metroResult;
1153
1163
  try {
1164
+ let metroLogStream = null;
1165
+ const captureMetroLine = (line) => {
1166
+ try {
1167
+ if (!metroLogStream) {
1168
+ mkdirSync3(dirname4(metroLogPath(process.cwd())), { recursive: true });
1169
+ metroLogStream = createWriteStream(metroLogPath(process.cwd()), { flags: "w" });
1170
+ }
1171
+ metroLogStream.write(line.replace(/^\[metro\] /, "") + "\n");
1172
+ } catch {
1173
+ }
1174
+ if (shouldEchoMetroLine(line, opts.debug === true)) console.error(chalk2.dim(line));
1175
+ };
1154
1176
  metroResult = await ensureMetro(
1155
1177
  process.cwd(),
1156
1178
  {
@@ -1158,7 +1180,7 @@ async function runDevInProcess(opts) {
1158
1180
  port: metroPort,
1159
1181
  platformHasOverride: cfg.platformHasMetroOverride
1160
1182
  },
1161
- (line) => console.error(chalk2.dim(line))
1183
+ captureMetroLine
1162
1184
  );
1163
1185
  } catch (err) {
1164
1186
  const message = err.message;
@@ -1169,6 +1191,9 @@ async function runDevInProcess(opts) {
1169
1191
  for (const w of metroResult.warnings) console.error(chalk2.yellow(`\u26A0 ${w}`));
1170
1192
  if (metroResult.status === "spawned" || metroResult.status === "respawned") {
1171
1193
  console.error(chalk2.green(`\u2713 started Metro (${cfg.metroCommand})`));
1194
+ console.error(
1195
+ chalk2.dim(` full Metro output: ${metroLogPath(process.cwd())} (--debug echoes it here)`)
1196
+ );
1172
1197
  }
1173
1198
  const metroOwnedThisRun = metroResult.status === "spawned" || metroResult.status === "respawned";
1174
1199
  console.error(chalk2.dim(`Starting a ${cfg.platform} dev session for ${cfg.appId}...`));
@@ -1352,6 +1377,28 @@ var AGENT_COMMANDS = [
1352
1377
  mutating: false,
1353
1378
  params: []
1354
1379
  },
1380
+ {
1381
+ verb: "logs",
1382
+ tool: "logs",
1383
+ summary: "Read recent app logs from the device \u2014 JS console output, native errors, and crash messages from the app under test (not full system logs). Check after a crash, a frozen screen, or an unexpected blank tree, and to confirm an expected log line fired. Pass the previous response's cursor as --since to get only lines that arrived after it.",
1384
+ mutating: false,
1385
+ params: [
1386
+ {
1387
+ name: "tail",
1388
+ cli: "--tail",
1389
+ type: "number",
1390
+ required: false,
1391
+ description: "Maximum lines to return, newest kept (default 100, max 500)."
1392
+ },
1393
+ {
1394
+ name: "since",
1395
+ cli: "--since",
1396
+ type: "number",
1397
+ required: false,
1398
+ description: "Only lines newer than this cursor (use the previous response's `cursor` value)."
1399
+ }
1400
+ ]
1401
+ },
1355
1402
  {
1356
1403
  verb: "screenshot",
1357
1404
  tool: "screenshot",
@@ -1749,6 +1796,20 @@ function resultLines(result) {
1749
1796
  if (!result || typeof result !== "object") return [];
1750
1797
  const r = result;
1751
1798
  const lines = [];
1799
+ if (Array.isArray(r.entries) && typeof r.cursor === "number") {
1800
+ if (r.entries.length === 0) {
1801
+ lines.push(chalk3.dim("(no log lines)"));
1802
+ }
1803
+ for (const e of r.entries) {
1804
+ if (!e || typeof e !== "object") continue;
1805
+ const entry = e;
1806
+ const ts = typeof entry.timestamp === "number" ? new Date(entry.timestamp).toISOString().slice(11, 23) : "??:??:??.???";
1807
+ const level = typeof entry.level === "string" ? entry.level : "info";
1808
+ const levelTag = level === "error" ? chalk3.red(level) : level === "warn" ? chalk3.yellow(level) : chalk3.dim(level);
1809
+ lines.push(`${chalk3.dim(ts)} ${levelTag} ${String(entry.message ?? "")}`);
1810
+ }
1811
+ lines.push(chalk3.dim(`cursor: ${r.cursor}`));
1812
+ }
1752
1813
  if (r.found === true && typeof r.ref === "string" && r.ref) {
1753
1814
  let found = `found: @${r.ref}`;
1754
1815
  if (typeof r.text === "string" && r.text) found += ` ${JSON.stringify(r.text)}`;
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  readDevState,
9
9
  readMetroState,
10
10
  runAgentVerb
11
- } from "./chunk-WG2XR45H.js";
11
+ } from "./chunk-NA3JOV5Z.js";
12
12
  import {
13
13
  ApiClient,
14
14
  EXIT_INFRA_ERROR,
@@ -1448,7 +1448,7 @@ program.command("dev-stop").description("Stop a `vibeview dev --detach` session
1448
1448
  program.command("mcp").description(
1449
1449
  "Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop as tools"
1450
1450
  ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
1451
- const { runMcpServer } = await import("./server-S3D724K3.js");
1451
+ const { runMcpServer } = await import("./server-B2PDQJLR.js");
1452
1452
  await runMcpServer(opts);
1453
1453
  });
1454
1454
  program.command("agent-setup").description(
@@ -10,7 +10,7 @@ import {
10
10
  readDevState,
11
11
  renderEnvelope,
12
12
  startDevSession
13
- } from "./chunk-WG2XR45H.js";
13
+ } from "./chunk-NA3JOV5Z.js";
14
14
  import {
15
15
  ApiClient,
16
16
  resolveAuth
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeview",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "VibeView CLI -- run mobile tests from CI/CD pipelines",
5
5
  "type": "module",
6
6
  "bin": {
@@ -103,6 +103,17 @@ it rather than letting it appear unexplained in their `git status`.
103
103
  way you will ever see the screen. It costs a lot of context, so use it
104
104
  when appearance matters — not to confirm a tap landed, which the
105
105
  response's observation already tells you.
106
+ - **When something breaks — a crash, a frozen screen, an unexpectedly empty
107
+ tree — read the app logs before guessing.** `logs` returns the app's own
108
+ output from the device: JS console lines, native errors, and crash
109
+ messages (not full system logs, and not Metro's terminal — native crashes
110
+ never appear there). The response ends with a `cursor`; pass it back as
111
+ `--since` to see only lines that arrived after your last read, which is
112
+ the natural loop for "did my action log what I expected".
113
+ ```bash
114
+ vibeview logs --session <id> # recent lines (default 100)
115
+ vibeview logs --since 42 --session <id> # only lines after cursor 42
116
+ ```
106
117
  - **Off-screen elements**: use `scroll-to <text>` rather than repeating
107
118
  `scroll down`. It scrolls until an element whose label, value, or
108
119
  accessibility id contains that text is visible, and stops early when the
@@ -157,6 +168,7 @@ human-readable text.
157
168
  | Verb | Args | Purpose |
158
169
  |------|------|---------|
159
170
  | `ui-tree` | — | Fetch the ref-annotated UI tree for the current screen. |
171
+ | `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`. |
160
172
  | `screenshot` | `[--out <path>]` | Capture a screenshot of the current screen and save it to disk. |
161
173
  | `tap` | `<target>` | Tap an element by ref (e.g. `@e5`), or raw coordinates (e.g. `100,200`). |
162
174
  | `long-press` | `<ref>` `[--ms <n>]` | Long-press (touch and hold) an element by its ref. |