adb-ready 0.1.1 → 0.1.2

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/CHANGELOG.md CHANGED
@@ -9,6 +9,26 @@ breaking changes.
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.1.2] - 2026-09-10
13
+
14
+ ### Changed
15
+
16
+ - Start live log streams at the current buffer position by default, while
17
+ keeping explicit history available through `--tail`, `--since`, and `--dump`.
18
+ - Color live logcat and child output by parsed severity and conservative message
19
+ semantics without treating every `stderr` line as an error.
20
+ - Present a safely handled `Ctrl-C` as an interruption while preserving exit
21
+ code 130 and a failed machine result for automation.
22
+
23
+ ### Fixed
24
+
25
+ - Correlate duplicate ADB 37 mDNS service-name and stable endpoint transports
26
+ only when exact discovery and observed hardware identity prove they match.
27
+ - Prevent port-list results from being misclassified as saved-session results in
28
+ human and plain output.
29
+ - Emit saved session timelines as one event per NDJSON line followed by a compact
30
+ result summary instead of duplicating the complete event array.
31
+
12
32
  ## [0.1.1] - 2026-09-10
13
33
 
14
34
  ### Fixed
@@ -32,6 +52,7 @@ breaking changes.
32
52
  explainable configuration precedence.
33
53
  - Human, plain, JSON, and NDJSON output across Node, Bun, and Deno entrypoints.
34
54
 
35
- [Unreleased]: https://github.com/Adam014/adb-ready/compare/v0.1.1...HEAD
55
+ [Unreleased]: https://github.com/Adam014/adb-ready/compare/v0.1.2...HEAD
56
+ [0.1.2]: https://github.com/Adam014/adb-ready/compare/v0.1.1...v0.1.2
36
57
  [0.1.1]: https://github.com/Adam014/adb-ready/compare/v0.1.0...v0.1.1
37
58
  [0.1.0]: https://github.com/Adam014/adb-ready/compare/v0.0.1-alpha.0...v0.1.0
package/README.md CHANGED
@@ -195,7 +195,7 @@ Android transport backend.
195
195
  [Changelog](./CHANGELOG.md) · [Contributing](./CONTRIBUTING.md) ·
196
196
  [Security](./SECURITY.md) · [MIT License](./LICENSE)
197
197
 
198
- ADB Ready is Android-only. The `0.1.0` release focuses on target acquisition,
198
+ ADB Ready is Android-only. The `0.1.x` releases focus on target acquisition,
199
199
  ports, development-session recovery, logs, diagnostics, and automation. App
200
200
  lifecycle, files, screenshots, screen recording, and shell workflows come
201
201
  after this core is proven on real projects.
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ import process9 from "node:process";
9
9
  // package.json
10
10
  var package_default = {
11
11
  name: "adb-ready",
12
- version: "0.1.1",
12
+ version: "0.1.2",
13
13
  description: "Make an Android target ready, then keep the development session working.",
14
14
  private: false,
15
15
  type: "module",
@@ -1555,9 +1555,10 @@ function targetInventoryProblems(targets, correlation) {
1555
1555
  const problems = [];
1556
1556
  for (const target of targets) {
1557
1557
  const serialCounts = new Map;
1558
+ const hasStableTransport = target.transports.some(({ stable }) => stable);
1558
1559
  for (const transport of target.transports) {
1559
1560
  serialCounts.set(transport.serial, (serialCounts.get(transport.serial) ?? 0) + 1);
1560
- if (!transport.stable) {
1561
+ if (!transport.stable && !hasStableTransport) {
1561
1562
  problems.push({
1562
1563
  code: ProblemCode.UnstableTargetSerial,
1563
1564
  category: "target.identity",
@@ -2711,6 +2712,44 @@ function isStableAdbSerial(serial) {
2711
2712
  }
2712
2713
  return true;
2713
2714
  }
2715
+ function normalizedServiceSerial(value) {
2716
+ return value.trim().replace(/\.+$/u, "");
2717
+ }
2718
+ function serviceSerial(service) {
2719
+ return normalizedServiceSerial(`${service.instance}.${service.rawServiceType}`);
2720
+ }
2721
+ function correlateMdnsTransportIdentities(observations, services) {
2722
+ const identityByEndpoint = new Map;
2723
+ for (const observation of observations) {
2724
+ const hardwareSerial = observation.hardwareSerial?.trim();
2725
+ const endpoint = parseAdbNetworkEndpoint(observation.device.serial);
2726
+ if (hardwareSerial === undefined || hardwareSerial === "" || endpoint === undefined)
2727
+ continue;
2728
+ const identities = identityByEndpoint.get(endpoint.serial) ?? new Set;
2729
+ identities.add(hardwareSerial);
2730
+ identityByEndpoint.set(endpoint.serial, identities);
2731
+ }
2732
+ return observations.map((observation) => {
2733
+ if (observation.hardwareSerial !== undefined || !isMdnsServiceSerial(observation.device.serial)) {
2734
+ return observation;
2735
+ }
2736
+ const matchingServices = services.filter((service) => serviceSerial(service) === normalizedServiceSerial(observation.device.serial));
2737
+ const identities = new Set;
2738
+ for (const service of matchingServices) {
2739
+ const advertisedIdentity = service.hardwareSerial?.trim();
2740
+ if (advertisedIdentity !== undefined && advertisedIdentity !== "") {
2741
+ identities.add(advertisedIdentity);
2742
+ }
2743
+ for (const endpoint of [service.endpoint, ...service.alternateEndpoints ?? []]) {
2744
+ for (const identity of identityByEndpoint.get(endpoint.serial) ?? []) {
2745
+ identities.add(identity);
2746
+ }
2747
+ }
2748
+ }
2749
+ const [hardwareSerial] = identities;
2750
+ return identities.size === 1 && hardwareSerial !== undefined ? { ...observation, hardwareSerial } : observation;
2751
+ });
2752
+ }
2714
2753
  function transportKind(device, services) {
2715
2754
  if (/^emulator-\d+$/u.test(device.serial)) {
2716
2755
  return "emulator";
@@ -2968,6 +3007,7 @@ function finish(context, data, problems) {
2968
3007
  const finished = context.clock();
2969
3008
  const exitCode = exitCodeForProblems(problems);
2970
3009
  const ok = exitCode === 0 /* Success */;
3010
+ const interrupted = exitCode === 130 /* Interrupted */;
2971
3011
  const result = {
2972
3012
  schemaVersion: SCHEMA_VERSION,
2973
3013
  command: context.command,
@@ -2980,10 +3020,10 @@ function finish(context, data, problems) {
2980
3020
  problems
2981
3021
  };
2982
3022
  context.bus.emit({
2983
- type: ok ? "command.completed" : "command.failed",
3023
+ type: ok ? "command.completed" : interrupted ? "command.interrupted" : "command.failed",
2984
3024
  source: `command.${context.command}`,
2985
- severity: ok ? "info" : "error",
2986
- message: ok ? `${context.command} completed` : `${context.command} failed`,
3025
+ severity: ok ? "info" : interrupted ? "warning" : "error",
3026
+ message: ok ? `${context.command} completed` : interrupted ? `${context.command} interrupted` : `${context.command} failed`,
2987
3027
  correlation: { commandId: context.commandId },
2988
3028
  data: { exitCode, problemCount: problems.length }
2989
3029
  });
@@ -3162,13 +3202,14 @@ async function inspectTargets(client, devices, commandId, signal, hostFeatures)
3162
3202
  }
3163
3203
  });
3164
3204
  const services = mdnsAvailable ? mdns.value : [];
3165
- const inventory = buildTargetInventory(devices.map((device) => {
3205
+ const observations = devices.map((device) => {
3166
3206
  const hardwareSerial = identityBySerial.get(device.serial);
3167
3207
  return {
3168
3208
  device,
3169
3209
  ...hardwareSerial === undefined ? {} : { hardwareSerial }
3170
3210
  };
3171
- }), services);
3211
+ });
3212
+ const inventory = buildTargetInventory(correlateMdnsTransportIdentities(observations, services), services);
3172
3213
  return {
3173
3214
  targets: inventory.targets,
3174
3215
  discovery: {
@@ -3916,7 +3957,7 @@ async function runLogs(options, config = {}, dependencies = {}, signal) {
3916
3957
  "-v",
3917
3958
  "threadtime",
3918
3959
  ...buffers.flatMap((buffer) => ["-b", buffer]),
3919
- ...options.tail === undefined && options.since === undefined ? options.dump ? ["-d"] : [] : [options.dump ? "-t" : "-T", String(options.tail ?? options.since)],
3960
+ ...options.tail === undefined && options.since === undefined ? options.dump ? ["-d"] : ["-T", "1"] : [options.dump ? "-t" : "-T", String(options.tail ?? options.since)],
3920
3961
  ...resolvedUid === undefined ? [] : [`--uid=${String(resolvedUid)}`],
3921
3962
  ...resolvedPid === undefined ? [] : [`--pid=${String(resolvedPid)}`],
3922
3963
  ...filters
@@ -4068,16 +4109,18 @@ async function runDev(options, config = {}, dependencies = {}, signal) {
4068
4109
  }
4069
4110
  }
4070
4111
  const complete = async (data) => {
4071
- if (problems.some(({ severity }) => severity === "error")) {
4072
- transition("failed", problems.find(({ severity }) => severity === "error")?.summary ?? "failed");
4112
+ const interrupted = problems.some(({ code }) => code === ProblemCode.OperationInterrupted);
4113
+ const failed = problems.some(({ code, severity }) => severity === "error" && code !== ProblemCode.OperationInterrupted);
4114
+ if (failed) {
4115
+ transition("failed", problems.find(({ code, severity }) => severity === "error" && code !== ProblemCode.OperationInterrupted)?.summary ?? "failed");
4073
4116
  }
4074
4117
  transition("stopping", "finalizing owned session resources");
4075
4118
  transition("ended", "session command finalized");
4076
4119
  bus.emit({
4077
4120
  type: "session.ended",
4078
4121
  source: "session",
4079
- severity: problems.some(({ severity }) => severity === "error") ? "error" : "info",
4080
- message: "Development session ended",
4122
+ severity: failed ? "error" : interrupted ? "warning" : "info",
4123
+ message: interrupted ? "Development session ended safely after interruption" : "Development session ended",
4081
4124
  correlation: { commandId: context.commandId, sessionId }
4082
4125
  });
4083
4126
  const execution2 = finish(context, data, problems);
@@ -4506,6 +4549,8 @@ async function runDev(options, config = {}, dependencies = {}, signal) {
4506
4549
  "logcat",
4507
4550
  "-v",
4508
4551
  "threadtime",
4552
+ "-T",
4553
+ "1",
4509
4554
  "ReactNativeJS:V",
4510
4555
  "ReactNative:V",
4511
4556
  "AndroidRuntime:E",
@@ -4874,7 +4919,7 @@ async function runDev(options, config = {}, dependencies = {}, signal) {
4874
4919
  await runHookPhase("finally", {}, false);
4875
4920
  const execution = await complete({
4876
4921
  ...baseData(),
4877
- status: problems.some(({ severity }) => severity === "error") ? "failed" : "completed",
4922
+ status: problems.some(({ code, severity }) => severity === "error" && code !== ProblemCode.OperationInterrupted) ? "failed" : problems.some(({ code }) => code === ProblemCode.OperationInterrupted) ? "interrupted" : "completed",
4878
4923
  ports: {
4879
4924
  requested: normalizedPorts.mappings,
4880
4925
  created,
@@ -7060,7 +7105,13 @@ function isLogsData(value) {
7060
7105
  return isRecord(value) && isRecord(value.selected) && Array.isArray(value.filters) && Array.isArray(value.records) && typeof value.dropped === "number";
7061
7106
  }
7062
7107
  function isSessionCommandData(value) {
7063
- return isRecord(value) && (value.action === "list" || value.action === "show" || value.action === "events");
7108
+ if (!isRecord(value))
7109
+ return false;
7110
+ if (value.action === "list")
7111
+ return Array.isArray(value.sessions);
7112
+ if (value.action === "show")
7113
+ return isRecord(value.session);
7114
+ return value.action === "events" && isRecord(value.session) && Array.isArray(value.events);
7064
7115
  }
7065
7116
  function isProblemsCommandData(value) {
7066
7117
  return isRecord(value) && typeof value.sessionId === "string" && typeof value.status === "string" && Array.isArray(value.problems);
@@ -7093,7 +7144,7 @@ function wirelessServiceLabel(service) {
7093
7144
  }
7094
7145
  function problemLines(problem, capabilities, verbose) {
7095
7146
  const glyphs = symbols(capabilities);
7096
- const marker = problem.severity === "error" ? style.failure(glyphs.failure, capabilities) : style.warning(glyphs.warning, capabilities);
7147
+ const marker = problem.severity === "error" && problem.code !== ProblemCode.OperationInterrupted ? style.failure(glyphs.failure, capabilities) : style.warning(glyphs.warning, capabilities);
7097
7148
  const lines = [
7098
7149
  `${marker} ${style.strong(clean(problem.summary), capabilities)}`,
7099
7150
  ` ${clean(problem.detail)}`
@@ -7129,11 +7180,13 @@ function renderHuman(result, options) {
7129
7180
  }
7130
7181
  if (result.command === "dev" && isDevData(result.data)) {
7131
7182
  const data = result.data;
7183
+ const sessionMarker = data.status === "interrupted" ? style.warning(glyphs.warning, capabilities) : data.status === "failed" ? style.failure(glyphs.failure, capabilities) : style.success(glyphs.success, capabilities);
7132
7184
  lines.push(`${style.success(glyphs.success, capabilities)} Target ${clean(data.selected.target.name)} · ${clean(data.selected.transport.serial)}`, `${style.success(glyphs.success, capabilities)} Project ${clean(data.project.name ?? data.project.root)} · ${clean(data.preset)}`, `${style.success(glyphs.success, capabilities)} Ports ${String(data.ports.requested.length)} ready · ${String(data.ports.created.length)} created · ${String(data.ports.reused.length)} reused`, `${style.success(glyphs.success, capabilities)} Command ${clean(data.command.executable)} ${data.command.args.map(clean).join(" ")}`);
7133
7185
  if (data.child !== undefined) {
7134
- lines.push(`${data.child.exitCode === 0 ? style.success(glyphs.success, capabilities) : style.failure(glyphs.failure, capabilities)} Child ${data.child.exitCode === null ? clean(data.child.signal) : `exit ${String(data.child.exitCode)}`}`);
7186
+ const childMarker = data.status === "interrupted" ? style.warning(glyphs.warning, capabilities) : data.child.exitCode === 0 ? style.success(glyphs.success, capabilities) : style.failure(glyphs.failure, capabilities);
7187
+ lines.push(`${childMarker} Child ${data.child.exitCode === null ? clean(data.child.signal) : `exit ${String(data.child.exitCode)}`}`);
7135
7188
  }
7136
- lines.push(`${style.success(glyphs.success, capabilities)} Journal ${String(data.journal.events.length)} events${data.journal.dropped === 0 ? "" : ` · ${String(data.journal.dropped)} dropped`}`, `${data.recovery.failed ? style.failure(glyphs.failure, capabilities) : style.success(glyphs.success, capabilities)} Recovery ${data.recovery.failed ? "failed" : data.recovery.recoveries === 0 ? "healthy" : `${String(data.recovery.recoveries)} verified repair(s)`}`, `${style.success(glyphs.success, capabilities)} Session ${clean(data.sessionId)} · ${clean(data.status)}`);
7189
+ lines.push(`${style.success(glyphs.success, capabilities)} Journal ${String(data.journal.events.length)} events${data.journal.dropped === 0 ? "" : ` · ${String(data.journal.dropped)} dropped`}`, `${data.recovery.failed ? style.failure(glyphs.failure, capabilities) : style.success(glyphs.success, capabilities)} Recovery ${data.recovery.failed ? "failed" : data.recovery.recoveries === 0 ? "healthy" : `${String(data.recovery.recoveries)} verified repair(s)`}`, `${sessionMarker} Session ${clean(data.sessionId)} · ${clean(data.status)}`);
7137
7190
  }
7138
7191
  if (result.command === "logs" && isLogsData(result.data)) {
7139
7192
  const data = result.data;
@@ -7261,7 +7314,8 @@ function renderHuman(result, options) {
7261
7314
  lines.push(...problemLines(problem, capabilities, options.verbose ?? false));
7262
7315
  }
7263
7316
  }
7264
- const status = result.ok ? `${style.success(glyphs.success, capabilities)} Completed in ${String(result.durationMs)}ms` : `${style.failure(glyphs.failure, capabilities)} Failed in ${String(result.durationMs)}ms`;
7317
+ const interrupted = result.problems.some(({ code }) => code === ProblemCode.OperationInterrupted) && !result.problems.some(({ code, severity }) => severity === "error" && code !== ProblemCode.OperationInterrupted);
7318
+ const status = result.ok ? `${style.success(glyphs.success, capabilities)} Completed in ${String(result.durationMs)}ms` : interrupted ? `${style.warning(glyphs.warning, capabilities)} Interrupted safely in ${String(result.durationMs)}ms` : `${style.failure(glyphs.failure, capabilities)} Failed in ${String(result.durationMs)}ms`;
7265
7319
  lines.push("", status);
7266
7320
  sink.write(`${lines.join(`
7267
7321
  `)}
@@ -7500,8 +7554,25 @@ function renderResult(result, options) {
7500
7554
  options.sink.write(`${JSON.stringify(result)}
7501
7555
  `);
7502
7556
  } else if (options.format === "ndjson") {
7503
- options.sink.write(`${JSON.stringify({ kind: "result", ...result })}
7557
+ if (isSessionCommandData(result.data) && result.data.action === "events") {
7558
+ for (const event of result.data.events) {
7559
+ options.sink.write(`${JSON.stringify({ kind: "event", ...event })}
7504
7560
  `);
7561
+ }
7562
+ options.sink.write(`${JSON.stringify({
7563
+ kind: "result",
7564
+ ...result,
7565
+ data: {
7566
+ action: result.data.action,
7567
+ session: result.data.session,
7568
+ eventCount: result.data.events.length
7569
+ }
7570
+ })}
7571
+ `);
7572
+ } else {
7573
+ options.sink.write(`${JSON.stringify({ kind: "result", ...result })}
7574
+ `);
7575
+ }
7505
7576
  } else if (options.format === "plain") {
7506
7577
  renderPlain(result, options.sink);
7507
7578
  } else {
@@ -7523,6 +7594,37 @@ class NdjsonEventRenderer {
7523
7594
  }
7524
7595
  }
7525
7596
 
7597
+ // src/ui/stream-renderer.ts
7598
+ var ERROR_LINE = /(?:^|\s)(?:err!|error|fatal|exception|failed|failure|crash)(?::|\b)/iu;
7599
+ var WARNING_LINE = /(?:^|\s)(?:warn|warning|deprecated|deprecation)(?::|\b)/iu;
7600
+ var SUCCESS_LINE = /(?:^|\s)(?:ready|started|bundled|done|success|completed)(?::|\b)/iu;
7601
+ var PROGRESS_LINE = /(?:^|\s)(?:starting|building|bundling|opening|waiting)(?::|\b)/iu;
7602
+ function renderLogStreamLine(line, capabilities) {
7603
+ const safe = sanitizeTerminalText(line);
7604
+ const parsed = parseLogcatThreadtimeLine(safe);
7605
+ if (parsed?.priority === "E" || parsed?.priority === "F" || parsed?.priority === "A") {
7606
+ return style.failure(safe, capabilities);
7607
+ }
7608
+ if (parsed?.priority === "W")
7609
+ return style.warning(safe, capabilities);
7610
+ if (parsed?.priority === "D" || parsed?.priority === "V") {
7611
+ return style.dim(safe, capabilities);
7612
+ }
7613
+ if (parsed?.priority === "I")
7614
+ return style.accent(safe, capabilities);
7615
+ if (ERROR_LINE.test(safe))
7616
+ return style.failure(safe, capabilities);
7617
+ if (WARNING_LINE.test(safe))
7618
+ return style.warning(safe, capabilities);
7619
+ return safe;
7620
+ }
7621
+ function renderChildStreamLine(stream, line, capabilities) {
7622
+ const safe = sanitizeTerminalText(line);
7623
+ const prefix = style.dim(stream === "stderr" ? "│" : " ", capabilities);
7624
+ const rendered = ERROR_LINE.test(safe) ? style.failure(safe, capabilities) : WARNING_LINE.test(safe) ? style.warning(safe, capabilities) : SUCCESS_LINE.test(safe) ? style.success(safe, capabilities) : PROGRESS_LINE.test(safe) ? style.accent(safe, capabilities) : safe;
7625
+ return `${prefix} ${rendered}`;
7626
+ }
7627
+
7526
7628
  // src/ui/terminal.ts
7527
7629
  function environmentFlag(value) {
7528
7630
  if (value === undefined) {
@@ -8271,7 +8373,8 @@ Lists every target visible to ADB. Add --select to open the keyboard picker.
8271
8373
  `,
8272
8374
  logs: `Usage: adb-ready logs [options]
8273
8375
 
8274
- Streams parsed, redacted logcat records from one deterministic target.
8376
+ Streams parsed, redacted logcat records from one deterministic target. Live
8377
+ streams follow from now by default instead of replaying the device buffer.
8275
8378
 
8276
8379
  Log options:
8277
8380
  --package NAME Resolve and filter the currently running app process
@@ -8847,8 +8950,10 @@ async function runCliInternal(argv, io, dependencies = {}, signal) {
8847
8950
  ...options.logTail === undefined ? {} : { tail: options.logTail },
8848
8951
  ...options.logDump === undefined ? {} : { dump: options.logDump },
8849
8952
  ...options.logMaxRecords === undefined ? {} : { maxRecords: options.logMaxRecords },
8850
- ...options.format === "human" && !options.quiet ? { onLine: (line) => io.error.write(`${line}
8851
- `) } : {}
8953
+ ...options.format === "human" && !options.quiet ? {
8954
+ onLine: (line) => io.error.write(`${renderLogStreamLine(line, errorCapabilities)}
8955
+ `)
8956
+ } : {}
8852
8957
  }, config, commandDependencies, signal);
8853
8958
  } else if (options.command === "dev") {
8854
8959
  execution2 = await runDev({
@@ -8890,7 +8995,7 @@ async function runCliInternal(argv, io, dependencies = {}, signal) {
8890
8995
  },
8891
8996
  ...options.format === "human" && !options.quiet ? {
8892
8997
  onChildLine: (stream, line) => {
8893
- io.error.write(`${stream === "stderr" ? "│" : " "} ${line}
8998
+ io.error.write(`${renderChildStreamLine(stream, line, errorCapabilities)}
8894
8999
  `);
8895
9000
  }
8896
9001
  } : {}
@@ -9060,4 +9165,4 @@ try {
9060
9165
  process10.removeListener("SIGTERM", abort);
9061
9166
  }
9062
9167
 
9063
- //# debugId=9D393E2FCFBCF69364756E2164756E21
9168
+ //# debugId=D3161FCA6036C77564756E2164756E21