adb-ready 0.1.0 → 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,33 @@ 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
+
32
+ ## [0.1.1] - 2026-09-10
33
+
34
+ ### Fixed
35
+
36
+ - Keep successful background target and port health probes in diagnostic
37
+ events without repeatedly printing them over development-server output.
38
+
12
39
  ## [0.1.0] - 2026-09-10
13
40
 
14
41
  ### Added
@@ -25,5 +52,7 @@ breaking changes.
25
52
  explainable configuration precedence.
26
53
  - Human, plain, JSON, and NDJSON output across Node, Bun, and Deno entrypoints.
27
54
 
28
- [Unreleased]: https://github.com/Adam014/adb-ready/compare/v0.1.0...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
57
+ [0.1.1]: https://github.com/Adam014/adb-ready/compare/v0.1.0...v0.1.1
29
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.0",
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",
@@ -837,6 +837,7 @@ class AdbClient {
837
837
  async#observe(operation, message, args, parse, signal, options = {}) {
838
838
  const operationId = this.#idFactory();
839
839
  const correlation = { ...this.#options.correlation, operationId };
840
+ const background = this.#options.presentation === "background";
840
841
  const finalArgs = [
841
842
  ...options.useServerArguments === false ? [] : this.#serverArguments(),
842
843
  ...args
@@ -844,12 +845,13 @@ class AdbClient {
844
845
  this.#options.bus.emit({
845
846
  type: "operation.started",
846
847
  source: `adb.${operation}`,
847
- severity: "info",
848
+ severity: background ? "debug" : "info",
848
849
  message,
849
850
  correlation,
850
851
  data: {
851
852
  executable: redactText(this.#options.executable).value,
852
- args: finalArgs.map((argument) => redactText(argument).value)
853
+ args: finalArgs.map((argument) => redactText(argument).value),
854
+ ...background ? { presentation: "background" } : {}
853
855
  }
854
856
  });
855
857
  const request = {
@@ -865,10 +867,13 @@ class AdbClient {
865
867
  this.#options.bus.emit({
866
868
  type: succeeded ? "operation.completed" : "operation.failed",
867
869
  source: `adb.${operation}`,
868
- severity: succeeded ? "info" : "error",
870
+ severity: succeeded ? background ? "debug" : "info" : "error",
869
871
  message: succeeded ? `${message} completed` : `${message} failed`,
870
872
  correlation,
871
- data: processMetadata(result)
873
+ data: {
874
+ ...processMetadata(result),
875
+ ...background ? { presentation: "background" } : {}
876
+ }
872
877
  });
873
878
  return {
874
879
  operationId,
@@ -1550,9 +1555,10 @@ function targetInventoryProblems(targets, correlation) {
1550
1555
  const problems = [];
1551
1556
  for (const target of targets) {
1552
1557
  const serialCounts = new Map;
1558
+ const hasStableTransport = target.transports.some(({ stable }) => stable);
1553
1559
  for (const transport of target.transports) {
1554
1560
  serialCounts.set(transport.serial, (serialCounts.get(transport.serial) ?? 0) + 1);
1555
- if (!transport.stable) {
1561
+ if (!transport.stable && !hasStableTransport) {
1556
1562
  problems.push({
1557
1563
  code: ProblemCode.UnstableTargetSerial,
1558
1564
  category: "target.identity",
@@ -2706,6 +2712,44 @@ function isStableAdbSerial(serial) {
2706
2712
  }
2707
2713
  return true;
2708
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
+ }
2709
2753
  function transportKind(device, services) {
2710
2754
  if (/^emulator-\d+$/u.test(device.serial)) {
2711
2755
  return "emulator";
@@ -2963,6 +3007,7 @@ function finish(context, data, problems) {
2963
3007
  const finished = context.clock();
2964
3008
  const exitCode = exitCodeForProblems(problems);
2965
3009
  const ok = exitCode === 0 /* Success */;
3010
+ const interrupted = exitCode === 130 /* Interrupted */;
2966
3011
  const result = {
2967
3012
  schemaVersion: SCHEMA_VERSION,
2968
3013
  command: context.command,
@@ -2975,10 +3020,10 @@ function finish(context, data, problems) {
2975
3020
  problems
2976
3021
  };
2977
3022
  context.bus.emit({
2978
- type: ok ? "command.completed" : "command.failed",
3023
+ type: ok ? "command.completed" : interrupted ? "command.interrupted" : "command.failed",
2979
3024
  source: `command.${context.command}`,
2980
- severity: ok ? "info" : "error",
2981
- 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`,
2982
3027
  correlation: { commandId: context.commandId },
2983
3028
  data: { exitCode, problemCount: problems.length }
2984
3029
  });
@@ -3157,13 +3202,14 @@ async function inspectTargets(client, devices, commandId, signal, hostFeatures)
3157
3202
  }
3158
3203
  });
3159
3204
  const services = mdnsAvailable ? mdns.value : [];
3160
- const inventory = buildTargetInventory(devices.map((device) => {
3205
+ const observations = devices.map((device) => {
3161
3206
  const hardwareSerial = identityBySerial.get(device.serial);
3162
3207
  return {
3163
3208
  device,
3164
3209
  ...hardwareSerial === undefined ? {} : { hardwareSerial }
3165
3210
  };
3166
- }), services);
3211
+ });
3212
+ const inventory = buildTargetInventory(correlateMdnsTransportIdentities(observations, services), services);
3167
3213
  return {
3168
3214
  targets: inventory.targets,
3169
3215
  discovery: {
@@ -3911,7 +3957,7 @@ async function runLogs(options, config = {}, dependencies = {}, signal) {
3911
3957
  "-v",
3912
3958
  "threadtime",
3913
3959
  ...buffers.flatMap((buffer) => ["-b", buffer]),
3914
- ...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)],
3915
3961
  ...resolvedUid === undefined ? [] : [`--uid=${String(resolvedUid)}`],
3916
3962
  ...resolvedPid === undefined ? [] : [`--pid=${String(resolvedPid)}`],
3917
3963
  ...filters
@@ -4063,16 +4109,18 @@ async function runDev(options, config = {}, dependencies = {}, signal) {
4063
4109
  }
4064
4110
  }
4065
4111
  const complete = async (data) => {
4066
- if (problems.some(({ severity }) => severity === "error")) {
4067
- 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");
4068
4116
  }
4069
4117
  transition("stopping", "finalizing owned session resources");
4070
4118
  transition("ended", "session command finalized");
4071
4119
  bus.emit({
4072
4120
  type: "session.ended",
4073
4121
  source: "session",
4074
- severity: problems.some(({ severity }) => severity === "error") ? "error" : "info",
4075
- message: "Development session ended",
4122
+ severity: failed ? "error" : interrupted ? "warning" : "info",
4123
+ message: interrupted ? "Development session ended safely after interruption" : "Development session ended",
4076
4124
  correlation: { commandId: context.commandId, sessionId }
4077
4125
  });
4078
4126
  const execution2 = finish(context, data, problems);
@@ -4185,7 +4233,7 @@ async function runDev(options, config = {}, dependencies = {}, signal) {
4185
4233
  if (normalizedPorts.problems.length > 0)
4186
4234
  return await complete(null);
4187
4235
  const targetCorrelation = { ...correlation, targetId: selected.target.id };
4188
- const client = new AdbClient({
4236
+ const clientOptions = {
4189
4237
  executable,
4190
4238
  bus,
4191
4239
  correlation: targetCorrelation,
@@ -4194,7 +4242,9 @@ async function runDev(options, config = {}, dependencies = {}, signal) {
4194
4242
  ...config.timeoutMs === undefined ? {} : { timeoutMs: config.timeoutMs },
4195
4243
  ...dependencies.runner === undefined ? {} : { runner: dependencies.runner },
4196
4244
  idFactory
4197
- });
4245
+ };
4246
+ const client = new AdbClient(clientOptions);
4247
+ const healthClient = new AdbClient({ ...clientOptions, presentation: "background" });
4198
4248
  const listed = await client.listPortMappings(target, "reverse", signal);
4199
4249
  if (!processSucceeded(listed.process)) {
4200
4250
  problems.push(operationProblem("reverse-list", listed, context.commandId));
@@ -4499,6 +4549,8 @@ async function runDev(options, config = {}, dependencies = {}, signal) {
4499
4549
  "logcat",
4500
4550
  "-v",
4501
4551
  "threadtime",
4552
+ "-T",
4553
+ "1",
4502
4554
  "ReactNativeJS:V",
4503
4555
  "ReactNative:V",
4504
4556
  "AndroidRuntime:E",
@@ -4593,7 +4645,7 @@ async function runDev(options, config = {}, dependencies = {}, signal) {
4593
4645
  const abortWatcher = () => watchController.abort();
4594
4646
  signal?.addEventListener("abort", abortWatcher, { once: true });
4595
4647
  const observeSession = async (watchSignal) => {
4596
- const state = await client.getState(target.serial, watchSignal);
4648
+ const state = await healthClient.getState(target.serial, watchSignal);
4597
4649
  if (!processSucceeded(state.process) || state.value !== "device") {
4598
4650
  return {
4599
4651
  targetReady: false,
@@ -4604,7 +4656,7 @@ async function runDev(options, config = {}, dependencies = {}, signal) {
4604
4656
  detail: `Target ${target.serial} is not ready.`
4605
4657
  };
4606
4658
  }
4607
- const mappings = await client.listPortMappings(target, "reverse", watchSignal);
4659
+ const mappings = await healthClient.listPortMappings(target, "reverse", watchSignal);
4608
4660
  if (!processSucceeded(mappings.process)) {
4609
4661
  return {
4610
4662
  targetReady: true,
@@ -4867,7 +4919,7 @@ async function runDev(options, config = {}, dependencies = {}, signal) {
4867
4919
  await runHookPhase("finally", {}, false);
4868
4920
  const execution = await complete({
4869
4921
  ...baseData(),
4870
- 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",
4871
4923
  ports: {
4872
4924
  requested: normalizedPorts.mappings,
4873
4925
  created,
@@ -6978,6 +7030,8 @@ class ProgressRenderer {
6978
7030
  this.#spinner.dispose();
6979
7031
  }
6980
7032
  onEvent(event) {
7033
+ if (event.data?.presentation === "background")
7034
+ return;
6981
7035
  if (event.type === "operation.started") {
6982
7036
  this.#spinner.start(event.message);
6983
7037
  } else if (event.type === "operation.completed") {
@@ -7051,7 +7105,13 @@ function isLogsData(value) {
7051
7105
  return isRecord(value) && isRecord(value.selected) && Array.isArray(value.filters) && Array.isArray(value.records) && typeof value.dropped === "number";
7052
7106
  }
7053
7107
  function isSessionCommandData(value) {
7054
- 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);
7055
7115
  }
7056
7116
  function isProblemsCommandData(value) {
7057
7117
  return isRecord(value) && typeof value.sessionId === "string" && typeof value.status === "string" && Array.isArray(value.problems);
@@ -7084,7 +7144,7 @@ function wirelessServiceLabel(service) {
7084
7144
  }
7085
7145
  function problemLines(problem, capabilities, verbose) {
7086
7146
  const glyphs = symbols(capabilities);
7087
- 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);
7088
7148
  const lines = [
7089
7149
  `${marker} ${style.strong(clean(problem.summary), capabilities)}`,
7090
7150
  ` ${clean(problem.detail)}`
@@ -7120,11 +7180,13 @@ function renderHuman(result, options) {
7120
7180
  }
7121
7181
  if (result.command === "dev" && isDevData(result.data)) {
7122
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);
7123
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(" ")}`);
7124
7185
  if (data.child !== undefined) {
7125
- 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)}`}`);
7126
7188
  }
7127
- 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)}`);
7128
7190
  }
7129
7191
  if (result.command === "logs" && isLogsData(result.data)) {
7130
7192
  const data = result.data;
@@ -7252,7 +7314,8 @@ function renderHuman(result, options) {
7252
7314
  lines.push(...problemLines(problem, capabilities, options.verbose ?? false));
7253
7315
  }
7254
7316
  }
7255
- 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`;
7256
7319
  lines.push("", status);
7257
7320
  sink.write(`${lines.join(`
7258
7321
  `)}
@@ -7491,8 +7554,25 @@ function renderResult(result, options) {
7491
7554
  options.sink.write(`${JSON.stringify(result)}
7492
7555
  `);
7493
7556
  } else if (options.format === "ndjson") {
7494
- 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 })}
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
+ })}
7495
7571
  `);
7572
+ } else {
7573
+ options.sink.write(`${JSON.stringify({ kind: "result", ...result })}
7574
+ `);
7575
+ }
7496
7576
  } else if (options.format === "plain") {
7497
7577
  renderPlain(result, options.sink);
7498
7578
  } else {
@@ -7514,6 +7594,37 @@ class NdjsonEventRenderer {
7514
7594
  }
7515
7595
  }
7516
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
+
7517
7628
  // src/ui/terminal.ts
7518
7629
  function environmentFlag(value) {
7519
7630
  if (value === undefined) {
@@ -8262,7 +8373,8 @@ Lists every target visible to ADB. Add --select to open the keyboard picker.
8262
8373
  `,
8263
8374
  logs: `Usage: adb-ready logs [options]
8264
8375
 
8265
- 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.
8266
8378
 
8267
8379
  Log options:
8268
8380
  --package NAME Resolve and filter the currently running app process
@@ -8838,8 +8950,10 @@ async function runCliInternal(argv, io, dependencies = {}, signal) {
8838
8950
  ...options.logTail === undefined ? {} : { tail: options.logTail },
8839
8951
  ...options.logDump === undefined ? {} : { dump: options.logDump },
8840
8952
  ...options.logMaxRecords === undefined ? {} : { maxRecords: options.logMaxRecords },
8841
- ...options.format === "human" && !options.quiet ? { onLine: (line) => io.error.write(`${line}
8842
- `) } : {}
8953
+ ...options.format === "human" && !options.quiet ? {
8954
+ onLine: (line) => io.error.write(`${renderLogStreamLine(line, errorCapabilities)}
8955
+ `)
8956
+ } : {}
8843
8957
  }, config, commandDependencies, signal);
8844
8958
  } else if (options.command === "dev") {
8845
8959
  execution2 = await runDev({
@@ -8881,7 +8995,7 @@ async function runCliInternal(argv, io, dependencies = {}, signal) {
8881
8995
  },
8882
8996
  ...options.format === "human" && !options.quiet ? {
8883
8997
  onChildLine: (stream, line) => {
8884
- io.error.write(`${stream === "stderr" ? "│" : " "} ${line}
8998
+ io.error.write(`${renderChildStreamLine(stream, line, errorCapabilities)}
8885
8999
  `);
8886
9000
  }
8887
9001
  } : {}
@@ -9051,4 +9165,4 @@ try {
9051
9165
  process10.removeListener("SIGTERM", abort);
9052
9166
  }
9053
9167
 
9054
- //# debugId=62DDFFDF33E82E9264756E2164756E21
9168
+ //# debugId=D3161FCA6036C77564756E2164756E21