@slack/radar-mcp 1.6.0 → 1.8.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 +4 -3
- package/dist/mcp/api-paths.d.ts +1 -0
- package/dist/mcp/api-paths.js +108 -0
- package/dist/mcp/index.js +52 -83
- package/dist/mcp/logs-result.d.ts +9 -0
- package/dist/mcp/logs-result.js +15 -0
- package/dist/mcp/radar-response.d.ts +21 -0
- package/dist/mcp/radar-response.js +38 -0
- package/dist/mcp/snapshot.d.ts +4 -2
- package/dist/mcp/snapshot.js +25 -12
- package/dist/mcp/tools.js +34 -10
- package/dist/shared/android.d.ts +2 -2
- package/dist/shared/android.js +16 -11
- package/dist/shared/db.d.ts +13 -0
- package/dist/shared/db.js +30 -3
- package/dist/shared/screen.d.ts +10 -0
- package/dist/shared/screen.js +13 -1
- package/dist/shared/stream.d.ts +1 -1
- package/dist/shared/transport.d.ts +12 -4
- package/dist/web/public/index.html +242 -1126
- package/dist/web/public/next/app.js +210 -0
- package/dist/web/public/next/db.js +286 -0
- package/dist/web/public/next/esc.js +13 -0
- package/dist/web/public/next/feed.js +608 -0
- package/dist/web/public/next/filters.js +120 -0
- package/dist/web/public/next/hooks.js +57 -0
- package/dist/web/public/next/html.js +10 -0
- package/dist/web/public/next/inspector.js +258 -0
- package/dist/web/public/next/registry.js +65 -0
- package/dist/web/public/next/screen.js +180 -0
- package/dist/web/public/next/spec-types.js +1 -0
- package/dist/web/public/next/store.js +766 -0
- package/dist/web/public/next/widgets.js +194 -0
- package/dist/web/server.js +7 -1
- package/package.json +3 -2
package/dist/mcp/tools.js
CHANGED
|
@@ -20,9 +20,10 @@ export const SERVER_INSTRUCTIONS = [
|
|
|
20
20
|
'- "why isn\'t this loading" / "this is broken" -> screenshot + get_recent_network_calls(status_code=400 or 500)',
|
|
21
21
|
'- "watch what happens when I..." -> clear_buffers then wait_for_events (DO NOT filter unless the user specifies a type)',
|
|
22
22
|
'- "did that clog fire" / "check analytics" -> get_recent_clogs(name="...")',
|
|
23
|
+
'- "why is this slow" / "how long did X take" / "show me the traces" / "check performance" -> get_recent_traces(name="...") then get_trace_detail',
|
|
23
24
|
'- "what API call was that" -> get_recent_network_calls then get_network_call_detail',
|
|
24
25
|
'- "did I get X" / "did X happen" -> search(query="X") across ALL buffers, not just one',
|
|
25
|
-
'- "check the debug logs" -> get_recent_logs(tag="
|
|
26
|
+
'- "check the debug logs" -> get_recent_logs (tag is OPTIONAL: pass tag="FKDebug" to filter, or omit it for all recent lines)',
|
|
26
27
|
'- "save this" / "file this as a bug" / "share this" / "bundle this" / "attach to ticket" / "give me a repro" / "snapshot this" / "preserve this" / "hold this state" / "save what you just saw" -> capture_snapshot',
|
|
27
28
|
"",
|
|
28
29
|
"Key behaviors:",
|
|
@@ -101,10 +102,10 @@ export const SERVER_INSTRUCTIONS = [
|
|
|
101
102
|
"When capture_snapshot returns, tell the user:",
|
|
102
103
|
" 1. The result.zip_path if non-null (the single shareable file). Fall back to",
|
|
103
104
|
" result.path (a directory) if zip_path is null.",
|
|
104
|
-
" 2. A one-line summary of counts (network/rtm/clogs/log_lines/screenshot).",
|
|
105
|
+
" 2. A one-line summary of counts (network/rtm/clogs/traces/log_lines/screenshot).",
|
|
105
106
|
" 3. Print this EXACT sentence as the privacy line, verbatim — no paraphrase, no",
|
|
106
107
|
" compression, no skipping even under time pressure:",
|
|
107
|
-
' "Privacy: bundle.json contains raw API request/response bodies (including auth headers), RTM message content, user IDs, and
|
|
108
|
+
' "Privacy: bundle.json contains raw API request/response bodies (including auth headers), RTM message content, user IDs, clog payloads, and performance trace spans. Review before sharing externally."',
|
|
108
109
|
" 4. If result.log_lines_truncated is true, say: 'you got fewer log lines than",
|
|
109
110
|
" requested because the 1 MB read cap was the limiting factor.'",
|
|
110
111
|
" 5. If the user's original ask was 'file this as a bug' / 'attach to ticket' /",
|
|
@@ -203,6 +204,30 @@ export const TOOL_DEFINITIONS = [
|
|
|
203
204
|
},
|
|
204
205
|
annotations: { readOnlyHint: true },
|
|
205
206
|
},
|
|
207
|
+
{
|
|
208
|
+
name: "get_recent_traces",
|
|
209
|
+
description: "Get recent performance trace summaries (one row per completed span from the app's telemetry pipeline). Returns trace name, span name, and duration. Use get_trace_detail for the full span, including its tags and the parent/trace ids.",
|
|
210
|
+
inputSchema: {
|
|
211
|
+
type: "object",
|
|
212
|
+
properties: {
|
|
213
|
+
limit: { type: "number", description: "Max number of traces to return (default: 50)" },
|
|
214
|
+
name: { type: "string", description: "Filter by trace name or span name substring" },
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
annotations: { readOnlyHint: true },
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: "get_trace_detail",
|
|
221
|
+
description: "Get full details of a specific performance trace by ID: trace and span names, the trace and parent ids, start/end/duration, and the span tags.",
|
|
222
|
+
inputSchema: {
|
|
223
|
+
type: "object",
|
|
224
|
+
properties: {
|
|
225
|
+
id: { type: "number", description: "The trace ID from get_recent_traces" },
|
|
226
|
+
},
|
|
227
|
+
required: ["id"],
|
|
228
|
+
},
|
|
229
|
+
annotations: { readOnlyHint: true },
|
|
230
|
+
},
|
|
206
231
|
{
|
|
207
232
|
name: "wait_for_events",
|
|
208
233
|
description: "Wait for matching events to arrive in real-time via the SSE stream.",
|
|
@@ -211,7 +236,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
211
236
|
properties: {
|
|
212
237
|
event_type: { type: "string", description: "RTM event type to wait for" },
|
|
213
238
|
channel: { type: "string", description: "Filter by channel ID" },
|
|
214
|
-
source: { type: "string", description: "
|
|
239
|
+
source: { type: "string", description: "Limit to one stream: network, rtm, clog, log, or trace (default: all)" },
|
|
215
240
|
timeout_ms: { type: "number", description: "Max time to wait in ms (default: 30000)" },
|
|
216
241
|
idle_timeout_ms: { type: "number", description: "Resolve after this many ms of silence (default: 3000)" },
|
|
217
242
|
},
|
|
@@ -220,7 +245,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
220
245
|
},
|
|
221
246
|
{
|
|
222
247
|
name: "search",
|
|
223
|
-
description: "Search across all captured network calls and
|
|
248
|
+
description: "Search across all captured network calls, RTM events, clogs, and performance traces.",
|
|
224
249
|
inputSchema: {
|
|
225
250
|
type: "object",
|
|
226
251
|
properties: {
|
|
@@ -239,15 +264,14 @@ export const TOOL_DEFINITIONS = [
|
|
|
239
264
|
},
|
|
240
265
|
{
|
|
241
266
|
name: "get_recent_logs",
|
|
242
|
-
description: "Get recent device logs for the Slack debug build
|
|
267
|
+
description: "Get recent device logs for the Slack debug build. Reads the app's own log buffer first (the DEBUG-and-above Timber lines the in-app Log Viewer shows, this app only), and falls back to an adb logcat snapshot when that buffer cannot be read. The result includes a `source` field (app_buffer / logcat_capture / logcat_ring) so you can tell a real empty result from a fallback. Leave `tag` out to get all recent lines. For native crashes, VERBOSE lines, or non-Slack processes, use adb logcat directly.",
|
|
243
268
|
inputSchema: {
|
|
244
269
|
type: "object",
|
|
245
270
|
properties: {
|
|
246
|
-
tag: { type: "string", description: "
|
|
247
|
-
limit: { type: "number", description: "Max number of lines to return (default: 50)" },
|
|
271
|
+
tag: { type: "string", description: "Optional substring to filter log lines by (matched anywhere in the line, not an exact tag-column match). Omit to return all recent lines." },
|
|
272
|
+
limit: { type: "number", description: "Max number of lines to return (default: 50, capped at 1000)" },
|
|
248
273
|
grep: { type: "string", description: "Optional text to grep within the filtered output" },
|
|
249
274
|
},
|
|
250
|
-
required: ["tag"],
|
|
251
275
|
},
|
|
252
276
|
annotations: { readOnlyHint: true },
|
|
253
277
|
},
|
|
@@ -278,7 +302,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
278
302
|
},
|
|
279
303
|
{
|
|
280
304
|
name: "capture_snapshot",
|
|
281
|
-
description: "Bundle the current Radar state into a single on-disk artifact for sharing. Writes four files to ~/.slack-radar/snapshots/snapshot-<ts>/: bundle.json (network + RTM + clog buffers, up to 1000 items each — a bulk export, much larger than point queries like get_network_call_detail), logcat.tail.log (last N lines of the active capture), screenshot.png (current screen), and README.txt (receiver-facing explainer). Use when the user says 'save this', 'file this as a bug', 'share this', 'bundle this up', 'attach to a ticket', 'make me a repro', or otherwise wants a shareable reproducible handoff. PRIVACY: bundle.json contains raw API request/response bodies including auth headers, RTM message content, user/team/channel IDs,
|
|
305
|
+
description: "Bundle the current Radar state into a single on-disk artifact for sharing. Writes four files to ~/.slack-radar/snapshots/snapshot-<ts>/: bundle.json (network + RTM + clog + trace buffers, up to 1000 items each — a bulk export, much larger than point queries like get_network_call_detail), logcat.tail.log (last N lines of the active capture), screenshot.png (current screen), and README.txt (receiver-facing explainer). Use when the user says 'save this', 'file this as a bug', 'share this', 'bundle this up', 'attach to a ticket', 'make me a repro', or otherwise wants a shareable reproducible handoff. PRIVACY: bundle.json contains raw API request/response bodies including auth headers, RTM message content, user/team/channel IDs, clog payloads, and performance trace spans — review before sharing externally.",
|
|
282
306
|
inputSchema: {
|
|
283
307
|
type: "object",
|
|
284
308
|
properties: {
|
package/dist/shared/android.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DeviceTransport } from "./transport.js";
|
|
1
|
+
import type { DeviceTransport, LogResult } from "./transport.js";
|
|
2
2
|
/**
|
|
3
3
|
* Resolve the adb binary. MCP servers spawned from GUI launchers (Spotlight,
|
|
4
4
|
* Raycast, Dock) do not inherit the user's shell PATH, so a bare `adb` call
|
|
@@ -44,7 +44,7 @@ export declare class AndroidTransport implements DeviceTransport {
|
|
|
44
44
|
screenshot(fullRes: boolean): string;
|
|
45
45
|
recordScreen(durationS: number): string;
|
|
46
46
|
getAppState(): string;
|
|
47
|
-
getRecentLogs(tag: string, limit: number, grep?: string):
|
|
47
|
+
getRecentLogs(tag: string, limit: number, grep?: string): LogResult;
|
|
48
48
|
listProfiles(): {
|
|
49
49
|
userId: string;
|
|
50
50
|
label: string;
|
package/dist/shared/android.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execSync } from "child_process";
|
|
1
|
+
import { execSync, execFileSync } from "child_process";
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import os from "os";
|
|
4
4
|
import path from "path";
|
|
@@ -209,7 +209,7 @@ export class AndroidTransport {
|
|
|
209
209
|
// sandbox -- matching how the DB browser threads its user.
|
|
210
210
|
const appLog = readDebugLogBuffer(ADB, this.resolveActiveUser());
|
|
211
211
|
if (appLog !== null) {
|
|
212
|
-
return filterLogLines(appLog, tag, limit, grep);
|
|
212
|
+
return { lines: filterLogLines(appLog, tag, limit, grep), source: "app_buffer" };
|
|
213
213
|
}
|
|
214
214
|
// Fallback: read from the session capture file so lines cannot have been
|
|
215
215
|
// evicted from the device ring buffer between enable and now.
|
|
@@ -220,20 +220,25 @@ export class AndroidTransport {
|
|
|
220
220
|
rotateCaptureIfNeeded(ADB);
|
|
221
221
|
const lines = readRecentLogs(tag, limit, grep);
|
|
222
222
|
if (lines !== null)
|
|
223
|
-
return lines;
|
|
223
|
+
return { lines, source: "logcat_capture" };
|
|
224
224
|
}
|
|
225
225
|
// Fallback: device ring buffer snapshot. Used when capture is not running
|
|
226
|
-
// (activate() was skipped, child died, or the file was deleted).
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
226
|
+
// (activate() was skipped, child died, or the file was deleted). No shell:
|
|
227
|
+
// dump the ring with execFileSync (argv, so a tag/grep with a backtick,
|
|
228
|
+
// $(...), or trailing backslash cannot break out of a command string) and do
|
|
229
|
+
// the tag/grep/limit filtering in pure JS via the same filterLogLines the
|
|
230
|
+
// other two sources use. An empty tag means "no filter" (includes("") is
|
|
231
|
+
// true), so the whole tail is returned.
|
|
232
|
+
const output = execFileSync(ADB, ["logcat", "-d"], {
|
|
232
233
|
timeout: 10000,
|
|
233
|
-
shell: "/bin/zsh",
|
|
234
234
|
encoding: "utf-8",
|
|
235
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
235
236
|
});
|
|
236
|
-
|
|
237
|
+
const lines = output.split("\n").filter(Boolean);
|
|
238
|
+
return {
|
|
239
|
+
lines: filterLogLines(lines, tag, limit, grep),
|
|
240
|
+
source: "logcat_ring",
|
|
241
|
+
};
|
|
237
242
|
}
|
|
238
243
|
listProfiles() {
|
|
239
244
|
if (!ADB)
|
package/dist/shared/db.d.ts
CHANGED
|
@@ -77,6 +77,19 @@ export declare function pullDatabase(name: string, user?: string | null, force?:
|
|
|
77
77
|
* (1) and (2), not from the open mode.
|
|
78
78
|
*/
|
|
79
79
|
export declare function assertReadOnlyQuery(sql: string): void;
|
|
80
|
+
/**
|
|
81
|
+
* True for EXPLAIN / EXPLAIN QUERY PLAN, which sqlite3 prints as a text table (ignoring -json).
|
|
82
|
+
* Leading-token match; assertReadOnlyQuery gates the same leading keyword, so both reject on
|
|
83
|
+
* comment-prefixed queries before this routing matters.
|
|
84
|
+
*/
|
|
85
|
+
export declare function isExplainQuery(sql: string): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* sqlite3 ignores -json for EXPLAIN (prints text table instead of JSON).
|
|
88
|
+
* Shape it into row objects so the web grid renders the plan readably.
|
|
89
|
+
*/
|
|
90
|
+
export declare function parseExplainOutput(out: string): {
|
|
91
|
+
plan: string;
|
|
92
|
+
}[];
|
|
80
93
|
/**
|
|
81
94
|
* Run a read-only query against a (lazily pulled) local copy. The query is gated by
|
|
82
95
|
* assertReadOnlyQuery (see there for the full read-only/exfil rationale). Surfaces sqlite's
|
package/dist/shared/db.js
CHANGED
|
@@ -353,6 +353,24 @@ export function assertReadOnlyQuery(sql) {
|
|
|
353
353
|
throw new Error("that function/statement is not allowed (read-only, no file access)");
|
|
354
354
|
}
|
|
355
355
|
}
|
|
356
|
+
/**
|
|
357
|
+
* True for EXPLAIN / EXPLAIN QUERY PLAN, which sqlite3 prints as a text table (ignoring -json).
|
|
358
|
+
* Leading-token match; assertReadOnlyQuery gates the same leading keyword, so both reject on
|
|
359
|
+
* comment-prefixed queries before this routing matters.
|
|
360
|
+
*/
|
|
361
|
+
export function isExplainQuery(sql) {
|
|
362
|
+
return /^\s*explain\b/i.test(sql);
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* sqlite3 ignores -json for EXPLAIN (prints text table instead of JSON).
|
|
366
|
+
* Shape it into row objects so the web grid renders the plan readably.
|
|
367
|
+
*/
|
|
368
|
+
export function parseExplainOutput(out) {
|
|
369
|
+
return out
|
|
370
|
+
.split("\n")
|
|
371
|
+
.filter((line) => line.trim().length > 0)
|
|
372
|
+
.map((line) => ({ plan: line }));
|
|
373
|
+
}
|
|
356
374
|
/**
|
|
357
375
|
* Run a read-only query against a (lazily pulled) local copy. The query is gated by
|
|
358
376
|
* assertReadOnlyQuery (see there for the full read-only/exfil rationale). Surfaces sqlite's
|
|
@@ -365,6 +383,12 @@ export async function queryDatabase(name, sql, user) {
|
|
|
365
383
|
const local = localPath(name, u);
|
|
366
384
|
if (!fs.existsSync(local))
|
|
367
385
|
await pullDatabase(name, u);
|
|
386
|
+
// EXPLAIN / EXPLAIN QUERY PLAN are read-query-shaped (they pass the gate) but sqlite3 IGNORES
|
|
387
|
+
// -json for them: it prints a fixed-width opcode/plan TEXT table, not JSON, so JSON.parse below
|
|
388
|
+
// would throw "Unexpected token" and the user sees a confusing parse error for a query that
|
|
389
|
+
// actually ran. Handle them as text: one result row per output line under a single column, so
|
|
390
|
+
// the existing grid renders the plan readably instead of choking.
|
|
391
|
+
const explain = isExplainQuery(sql);
|
|
368
392
|
let out;
|
|
369
393
|
try {
|
|
370
394
|
// Open the pulled copy NORMALLY (not `immutable`, not `-readonly`): pullDatabase brought
|
|
@@ -375,14 +399,15 @@ export async function queryDatabase(name, sql, user) {
|
|
|
375
399
|
// is touched. `-safe` is the load-bearing read-only/exfil control (blocks ATTACH/readfile/
|
|
376
400
|
// writefile/load_extension at the engine level) and holds in normal mode too. The path is
|
|
377
401
|
// URI-encoded so spaces/specials are safe. Async exec so a large query does not block the
|
|
378
|
-
// event loop.
|
|
402
|
+
// event loop. -json for normal queries; plain text for EXPLAIN (sqlite ignores -json there).
|
|
379
403
|
const uri = `file:${encodeURI(local)}`;
|
|
380
|
-
const
|
|
404
|
+
const args = explain ? ["-safe", uri, sql] : ["-safe", "-json", uri, sql];
|
|
405
|
+
const r = await execFileP(sqliteBin(), args, {
|
|
381
406
|
timeout: 15000,
|
|
382
407
|
maxBuffer: SQLITE_MAX_BUFFER,
|
|
383
408
|
encoding: "utf8",
|
|
384
409
|
});
|
|
385
|
-
out = String(r.stdout) || "[]";
|
|
410
|
+
out = String(r.stdout) || (explain ? "" : "[]");
|
|
386
411
|
}
|
|
387
412
|
catch (e) {
|
|
388
413
|
const err = e;
|
|
@@ -390,6 +415,8 @@ export async function queryDatabase(name, sql, user) {
|
|
|
390
415
|
const m = stderr.match(/(no such table:.*|no such column:.*|near ".*|syntax error.*|.*error.*)/i);
|
|
391
416
|
throw new Error(m ? m[0].trim() : stderr.split("\n")[0] || "query failed");
|
|
392
417
|
}
|
|
418
|
+
if (explain)
|
|
419
|
+
return parseExplainOutput(out);
|
|
393
420
|
return JSON.parse(out);
|
|
394
421
|
}
|
|
395
422
|
/** Size in bytes of a pulled local copy for the given user, or 0 if not pulled. */
|
package/dist/shared/screen.d.ts
CHANGED
|
@@ -130,6 +130,16 @@ export declare function startLive(fps: number): LiveCast | null;
|
|
|
130
130
|
/** Attach an HTTP response as a viewer of the live cast (after the multipart headers are written). */
|
|
131
131
|
export declare function addViewer(L: LiveCast, res: NodeJS.WritableStream): void;
|
|
132
132
|
export declare function removeViewer(res: NodeJS.WritableStream): void;
|
|
133
|
+
/**
|
|
134
|
+
* Tear down the live cast when nothing is keeping it alive: no viewers AND no active
|
|
135
|
+
* recording. Either a viewer leaving or a recording stopping can be the last thing
|
|
136
|
+
* holding the cast, so both paths call this. The close-while-recording case needs it:
|
|
137
|
+
* the browser aborts the <img> (dropping the last viewer) BEFORE its record/stop POST
|
|
138
|
+
* resolves, so the viewer-leave finds recording still set and cannot reap; the cast
|
|
139
|
+
* would then run forever (screenrecord + ffmpeg) once the recording ends. Reaping again
|
|
140
|
+
* after stopRecording closes that gap.
|
|
141
|
+
*/
|
|
142
|
+
export declare function reapLiveIfIdle(): void;
|
|
133
143
|
export declare function getLive(): LiveCast | null;
|
|
134
144
|
export declare function lastFrame(): Buffer | null;
|
|
135
145
|
export declare function primeIfNeeded(): Promise<void>;
|
package/dist/shared/screen.js
CHANGED
|
@@ -461,7 +461,19 @@ export function removeViewer(res) {
|
|
|
461
461
|
if (!scLive)
|
|
462
462
|
return;
|
|
463
463
|
scLive.viewers.delete(res);
|
|
464
|
-
|
|
464
|
+
reapLiveIfIdle();
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Tear down the live cast when nothing is keeping it alive: no viewers AND no active
|
|
468
|
+
* recording. Either a viewer leaving or a recording stopping can be the last thing
|
|
469
|
+
* holding the cast, so both paths call this. The close-while-recording case needs it:
|
|
470
|
+
* the browser aborts the <img> (dropping the last viewer) BEFORE its record/stop POST
|
|
471
|
+
* resolves, so the viewer-leave finds recording still set and cannot reap; the cast
|
|
472
|
+
* would then run forever (screenrecord + ffmpeg) once the recording ends. Reaping again
|
|
473
|
+
* after stopRecording closes that gap.
|
|
474
|
+
*/
|
|
475
|
+
export function reapLiveIfIdle() {
|
|
476
|
+
if (scLive && !scLive.viewers.size && !scLive.recording) {
|
|
465
477
|
cleanupLive(scLive);
|
|
466
478
|
scLive = null;
|
|
467
479
|
}
|
package/dist/shared/stream.d.ts
CHANGED
|
@@ -6,6 +6,13 @@
|
|
|
6
6
|
* device-specific operations. The MCP server and tool handlers
|
|
7
7
|
* interact only through this interface.
|
|
8
8
|
*/
|
|
9
|
+
/** Which underlying source served a getRecentLogs result. */
|
|
10
|
+
export type LogSource = "app_buffer" | "logcat_capture" | "logcat_ring";
|
|
11
|
+
/** Lines plus the source that produced them. */
|
|
12
|
+
export interface LogResult {
|
|
13
|
+
lines: string[];
|
|
14
|
+
source: LogSource;
|
|
15
|
+
}
|
|
9
16
|
export interface DeviceTransport {
|
|
10
17
|
/** Human-readable platform name (e.g. "android", "ios"). */
|
|
11
18
|
readonly platform: string;
|
|
@@ -47,14 +54,15 @@ export interface DeviceTransport {
|
|
|
47
54
|
*/
|
|
48
55
|
getAppState(): string;
|
|
49
56
|
/**
|
|
50
|
-
* Get recent log entries filtered by tag.
|
|
57
|
+
* Get recent log entries, optionally filtered by tag.
|
|
51
58
|
*
|
|
52
|
-
* @param tag Log tag to filter by
|
|
59
|
+
* @param tag Log tag to filter by. Empty string returns every line (no tag filter).
|
|
53
60
|
* @param limit Max number of lines
|
|
54
61
|
* @param grep Optional text to further filter
|
|
55
|
-
* @returns
|
|
62
|
+
* @returns The matching lines plus which source served them, so the caller can tell a
|
|
63
|
+
* true-empty result from a silent fallback to an inferior source.
|
|
56
64
|
*/
|
|
57
|
-
getRecentLogs(tag: string, limit: number, grep?: string):
|
|
65
|
+
getRecentLogs(tag: string, limit: number, grep?: string): LogResult;
|
|
58
66
|
/**
|
|
59
67
|
* Returns the currently active user ID, or null if none is set. On Android
|
|
60
68
|
* this is the profile (e.g. "0" for personal, "10" for work); on iOS it
|