tauri-agent-tools 0.6.0 → 0.7.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.
Files changed (76) hide show
  1. package/.agents/skills/tauri-agent-tools/SKILL.md +94 -5
  2. package/.agents/skills/tauri-bridge-setup/SKILL.md +45 -13
  3. package/.agents/skills/tauri-debug-quickstart/SKILL.md +80 -0
  4. package/README.md +71 -3
  5. package/dist/bridge/client.d.ts +17 -1
  6. package/dist/bridge/client.js +82 -1
  7. package/dist/bridge/client.js.map +1 -1
  8. package/dist/cli.js +25 -0
  9. package/dist/cli.js.map +1 -1
  10. package/dist/commands/appPaths.d.ts +2 -0
  11. package/dist/commands/appPaths.js +97 -0
  12. package/dist/commands/appPaths.js.map +1 -0
  13. package/dist/commands/capabilitiesAudit.d.ts +2 -0
  14. package/dist/commands/capabilitiesAudit.js +105 -0
  15. package/dist/commands/capabilitiesAudit.js.map +1 -0
  16. package/dist/commands/configInspect.d.ts +2 -0
  17. package/dist/commands/configInspect.js +223 -0
  18. package/dist/commands/configInspect.js.map +1 -0
  19. package/dist/commands/diagnose.d.ts +2 -0
  20. package/dist/commands/diagnose.js +311 -0
  21. package/dist/commands/diagnose.js.map +1 -0
  22. package/dist/commands/forensics.d.ts +2 -0
  23. package/dist/commands/forensics.js +331 -0
  24. package/dist/commands/forensics.js.map +1 -0
  25. package/dist/commands/health.d.ts +2 -0
  26. package/dist/commands/health.js +39 -0
  27. package/dist/commands/health.js.map +1 -0
  28. package/dist/commands/osLogs.d.ts +2 -0
  29. package/dist/commands/osLogs.js +130 -0
  30. package/dist/commands/osLogs.js.map +1 -0
  31. package/dist/commands/processTree.d.ts +2 -0
  32. package/dist/commands/processTree.js +45 -0
  33. package/dist/commands/processTree.js.map +1 -0
  34. package/dist/commands/sidecarReplay.d.ts +7 -0
  35. package/dist/commands/sidecarReplay.js +93 -0
  36. package/dist/commands/sidecarReplay.js.map +1 -0
  37. package/dist/commands/sidecarTap.d.ts +2 -0
  38. package/dist/commands/sidecarTap.js +118 -0
  39. package/dist/commands/sidecarTap.js.map +1 -0
  40. package/dist/commands/webviewAttach.d.ts +2 -0
  41. package/dist/commands/webviewAttach.js +64 -0
  42. package/dist/commands/webviewAttach.js.map +1 -0
  43. package/dist/platform/oslog/darwin.d.ts +21 -0
  44. package/dist/platform/oslog/darwin.js +72 -0
  45. package/dist/platform/oslog/darwin.js.map +1 -0
  46. package/dist/platform/oslog/linux.d.ts +16 -0
  47. package/dist/platform/oslog/linux.js +47 -0
  48. package/dist/platform/oslog/linux.js.map +1 -0
  49. package/dist/platform/oslog/windows.d.ts +15 -0
  50. package/dist/platform/oslog/windows.js +16 -0
  51. package/dist/platform/oslog/windows.js.map +1 -0
  52. package/dist/schemas/bridge.d.ts +222 -0
  53. package/dist/schemas/bridge.js +44 -0
  54. package/dist/schemas/bridge.js.map +1 -1
  55. package/dist/schemas/osLog.d.ts +34 -0
  56. package/dist/schemas/osLog.js +18 -0
  57. package/dist/schemas/osLog.js.map +1 -0
  58. package/dist/schemas/sidecar.d.ts +33 -0
  59. package/dist/schemas/sidecar.js +17 -0
  60. package/dist/schemas/sidecar.js.map +1 -0
  61. package/dist/schemas/tauriConfig.d.ts +825 -0
  62. package/dist/schemas/tauriConfig.js +102 -0
  63. package/dist/schemas/tauriConfig.js.map +1 -0
  64. package/dist/util/ndjson.d.ts +37 -0
  65. package/dist/util/ndjson.js +82 -0
  66. package/dist/util/ndjson.js.map +1 -0
  67. package/dist/util/tauriConfig.d.ts +63 -0
  68. package/dist/util/tauriConfig.js +235 -0
  69. package/dist/util/tauriConfig.js.map +1 -0
  70. package/examples/frontend-stub/index.html +1 -0
  71. package/examples/tauri-bridge/Cargo.toml +6 -0
  72. package/examples/tauri-bridge/build.rs +3 -0
  73. package/examples/tauri-bridge/icons/icon.png +0 -0
  74. package/examples/tauri-bridge/src/dev_bridge.rs +424 -11
  75. package/examples/tauri-bridge/tauri.conf.json +25 -0
  76. package/package.json +3 -1
@@ -0,0 +1,311 @@
1
+ import { Command } from 'commander';
2
+ import { spawn } from 'node:child_process';
3
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
4
+ import { existsSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ import { addBridgeOptions } from './shared.js';
7
+ import { BridgeClient } from '../bridge/client.js';
8
+ import { discoverBridge, discoverBridgesByPid } from '../bridge/tokenDiscovery.js';
9
+ import { resolveTauriProject } from '../util/tauriConfig.js';
10
+ export function registerDiagnose(program) {
11
+ const cmd = new Command('diagnose')
12
+ .description("Full diagnostic bundle: composes `forensics` with live bridge data when available. Best-effort — degrades cleanly when the bridge isn't reachable.")
13
+ .option('--config <path>', 'Path to tauri.conf.json (or its directory). Auto-detected if omitted.')
14
+ .option('--identifier <id>', 'Bundle identifier override')
15
+ .option('-o, --out <dir>', 'Output directory (default: ./diagnose-<timestamp>)')
16
+ .option('--since <duration>', 'How far back to pull from app log files (forensics phase, default: 10m)')
17
+ .option('--logs-duration <ms>', 'Time budget for live OS-log tail in milliseconds (default: 3000)')
18
+ .option('--no-bridge', 'Skip bridge-enrichment phase even if a bridge is reachable')
19
+ .option('--json', 'Print the master summary as JSON in addition to writing to disk');
20
+ addBridgeOptions(cmd);
21
+ cmd.action(async (opts) => {
22
+ const outDir = opts.out ?? `./diagnose-${timestampSlug()}`;
23
+ await mkdir(outDir, { recursive: true });
24
+ const forensicsDir = join(outDir, 'forensics');
25
+ await mkdir(forensicsDir, { recursive: true });
26
+ const outcomes = [];
27
+ // ── Phase A: Resolve project (for the summary header) ──────────────────
28
+ let identifier = '<unknown>';
29
+ let productName = '<unknown>';
30
+ try {
31
+ const resolved = await resolveTauriProject({
32
+ configPath: opts.config,
33
+ identifierOverride: opts.identifier,
34
+ });
35
+ identifier = resolved.identifier;
36
+ productName = resolved.productName;
37
+ outcomes.push({
38
+ phase: 'resolve-config',
39
+ ok: true,
40
+ detail: `identifier=${identifier} productName=${productName}`,
41
+ });
42
+ }
43
+ catch (err) {
44
+ outcomes.push({
45
+ phase: 'resolve-config',
46
+ ok: false,
47
+ detail: err instanceof Error ? err.message : String(err),
48
+ });
49
+ }
50
+ // ── Phase B: Forensics subprocess (Tier 1 bundle) ──────────────────────
51
+ const forensicsArgs = ['forensics', '-o', forensicsDir, '--logs-duration', String(opts.logsDuration ?? 3000)];
52
+ if (opts.config)
53
+ forensicsArgs.push('--config', opts.config);
54
+ if (opts.identifier)
55
+ forensicsArgs.push('--identifier', opts.identifier);
56
+ if (opts.since)
57
+ forensicsArgs.push('--since', opts.since);
58
+ try {
59
+ await runSelf(forensicsArgs);
60
+ outcomes.push({ phase: 'forensics', ok: true, detail: `→ ${forensicsDir}` });
61
+ }
62
+ catch (err) {
63
+ outcomes.push({
64
+ phase: 'forensics',
65
+ ok: false,
66
+ detail: err instanceof Error ? err.message : String(err),
67
+ });
68
+ }
69
+ // ── Phase C: Bridge enrichment (Tier 2) — optional, best-effort ────────
70
+ const bridgeData = await collectBridgeData(opts, outcomes);
71
+ await writeFile(join(outDir, 'bridge.json'), JSON.stringify(bridgeData, null, 2));
72
+ // ── Phase D: Master summary ────────────────────────────────────────────
73
+ const forensicsSummary = await tryReadJson(join(forensicsDir, 'summary.json'));
74
+ const master = {
75
+ identifier,
76
+ productName,
77
+ outDir,
78
+ forensicsDir,
79
+ phases: outcomes,
80
+ bridgeReachable: bridgeData.reachable,
81
+ bridgeVersion: bridgeData.version,
82
+ forensicsSummary: forensicsSummary ?? null,
83
+ };
84
+ await writeFile(join(outDir, 'summary.json'), JSON.stringify(master, null, 2));
85
+ await writeFile(join(outDir, 'summary.md'), renderMaster(master, bridgeData, outcomes));
86
+ if (opts.json) {
87
+ console.log(JSON.stringify(master, null, 2));
88
+ }
89
+ else {
90
+ const ok = outcomes.filter((o) => o.ok).length;
91
+ console.log(`✓ Diagnostic bundle written to ${outDir}`);
92
+ console.log(` ${ok}/${outcomes.length} phases succeeded`);
93
+ console.log(` Bridge: ${bridgeData.reachable ? `reachable (v${bridgeData.version})` : 'unreachable — bundle is forensics-only'}`);
94
+ }
95
+ });
96
+ program.addCommand(cmd);
97
+ }
98
+ function timestampSlug() {
99
+ return new Date().toISOString().replace(/[:.]/g, '-');
100
+ }
101
+ /** Spawn the same CLI binary that's currently running, for one-level composition. */
102
+ function runSelf(args) {
103
+ return new Promise((resolve, reject) => {
104
+ const cliPath = process.argv[1];
105
+ if (!cliPath) {
106
+ reject(new Error('Could not locate the current CLI binary (process.argv[1] is empty)'));
107
+ return;
108
+ }
109
+ const child = spawn(process.execPath, [cliPath, ...args], {
110
+ stdio: ['ignore', 'pipe', 'pipe'],
111
+ });
112
+ let stderr = '';
113
+ child.stderr.setEncoding('utf-8');
114
+ child.stderr.on('data', (chunk) => (stderr += chunk));
115
+ child.on('error', reject);
116
+ child.on('close', (code) => {
117
+ if (code === 0)
118
+ resolve();
119
+ else
120
+ reject(new Error(`subprocess exited ${code}${stderr ? ': ' + stderr.trim() : ''}`));
121
+ });
122
+ });
123
+ }
124
+ async function tryReadJson(path) {
125
+ if (!existsSync(path))
126
+ return null;
127
+ try {
128
+ return JSON.parse(await readFile(path, 'utf-8'));
129
+ }
130
+ catch {
131
+ return null;
132
+ }
133
+ }
134
+ async function collectBridgeData(opts, outcomes) {
135
+ const data = {
136
+ reachable: false,
137
+ version: null,
138
+ process: null,
139
+ capabilities: null,
140
+ devtools: null,
141
+ health: null,
142
+ errors: {},
143
+ };
144
+ if (opts.noBridge) {
145
+ outcomes.push({ phase: 'bridge', ok: true, detail: 'skipped (--no-bridge)' });
146
+ return data;
147
+ }
148
+ // Resolve a bridge config without throwing — diagnose tolerates absence.
149
+ const cfg = await tryResolveBridgeConfig(opts);
150
+ if (!cfg) {
151
+ outcomes.push({ phase: 'bridge', ok: false, detail: 'no live bridge discovered' });
152
+ return data;
153
+ }
154
+ const client = new BridgeClient(cfg, opts.windowLabel);
155
+ const version = await client.version();
156
+ if (!version) {
157
+ outcomes.push({ phase: 'bridge', ok: false, detail: `bridge at port ${cfg.port} did not respond to /version` });
158
+ return data;
159
+ }
160
+ data.reachable = true;
161
+ data.version = version.version;
162
+ // Each endpoint is best-effort; record per-endpoint failures rather than aborting.
163
+ for (const [name, fn] of [
164
+ ['process', () => client.process()],
165
+ ['capabilities', () => client.capabilities()],
166
+ ['devtools', () => client.devtools()],
167
+ ['health', () => client.health()],
168
+ ]) {
169
+ try {
170
+ const result = await fn();
171
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
172
+ data[name] = result;
173
+ outcomes.push({ phase: `bridge:${name}`, ok: true, detail: 'ok' });
174
+ }
175
+ catch (err) {
176
+ const msg = err instanceof Error ? err.message : String(err);
177
+ data.errors[name] = msg;
178
+ outcomes.push({ phase: `bridge:${name}`, ok: false, detail: msg });
179
+ }
180
+ }
181
+ return data;
182
+ }
183
+ async function tryResolveBridgeConfig(opts) {
184
+ try {
185
+ if (opts.port && opts.token) {
186
+ return { port: opts.port, token: opts.token };
187
+ }
188
+ if (opts.pid !== undefined) {
189
+ const bridges = await discoverBridgesByPid();
190
+ const match = bridges.get(opts.pid);
191
+ if (!match)
192
+ return null;
193
+ return {
194
+ port: opts.port ?? match.port,
195
+ token: opts.token ?? match.token,
196
+ };
197
+ }
198
+ const discovered = await discoverBridge();
199
+ if (!discovered)
200
+ return null;
201
+ return {
202
+ port: opts.port ?? discovered.port,
203
+ token: opts.token ?? discovered.token,
204
+ };
205
+ }
206
+ catch {
207
+ return null;
208
+ }
209
+ }
210
+ function renderMaster(master, bridge, outcomes) {
211
+ const phaseRows = outcomes
212
+ .map((o) => `- ${o.ok ? '✓' : '✗'} **${o.phase}** — ${o.detail}`)
213
+ .join('\n');
214
+ const bridgeSection = bridge.reachable ? renderBridgeSection(bridge) : NO_BRIDGE_HINT;
215
+ return `# Diagnose: ${master.productName}
216
+
217
+ - **Identifier:** \`${master.identifier}\`
218
+ - **Output:** \`${master.outDir}\`
219
+ - **Forensics bundle:** [\`${master.forensicsDir}/summary.md\`](./forensics/summary.md)
220
+
221
+ ## Phases
222
+
223
+ ${phaseRows}
224
+
225
+ ## Bridge enrichment
226
+
227
+ ${bridgeSection}
228
+
229
+ ## Next steps
230
+
231
+ ${suggestNext(master, bridge, outcomes)}
232
+ `;
233
+ }
234
+ const NO_BRIDGE_HINT = `_No live dev bridge was reachable._ The bundle in \`forensics/\` is still useful for post-mortem analysis (OS log tail, panic markers, app data file listing). To get richer data next time:
235
+
236
+ - Confirm the app is running in dev mode (\`cfg!(debug_assertions)\` must be true)
237
+ - Check for token files: \`ls /tmp/tauri-dev-bridge-*.token\`
238
+ - If you have a PID, pass \`--pid <n>\` explicitly
239
+ `;
240
+ function renderBridgeSection(bridge) {
241
+ const parts = [`_Bridge v${bridge.version} responded._`, ''];
242
+ if (bridge.health) {
243
+ parts.push('### Health');
244
+ parts.push('');
245
+ parts.push(`- Uptime: ${Math.floor(bridge.health.uptime_ms / 1000)}s`);
246
+ parts.push(`- Webview ready: ${bridge.health.webview_ready ? 'yes' : '**NO**'}`);
247
+ parts.push(`- Sidecars alive: ${bridge.health.sidecars_alive ? 'yes' : '**NO**'}`);
248
+ parts.push('');
249
+ }
250
+ if (bridge.process) {
251
+ parts.push('### Process tree');
252
+ parts.push('');
253
+ parts.push(`- Tauri pid \`${bridge.process.tauri.pid}\` (uptime ${Math.floor(bridge.process.tauri.uptime_ms / 1000)}s)`);
254
+ if (bridge.process.sidecars.length === 0) {
255
+ parts.push('- No sidecars registered');
256
+ }
257
+ else {
258
+ for (const s of bridge.process.sidecars) {
259
+ const alive = s.alive === null || s.alive === undefined ? '?' : s.alive ? 'alive' : '**DEAD**';
260
+ parts.push(`- Sidecar \`${s.name}\` pid \`${s.pid}\` — ${alive}`);
261
+ }
262
+ }
263
+ parts.push('');
264
+ }
265
+ if (bridge.devtools) {
266
+ parts.push('### Webview devtools');
267
+ parts.push('');
268
+ parts.push(`- Platform: ${bridge.devtools.platform}`);
269
+ parts.push(`- Inspectable: ${bridge.devtools.inspectable ? 'yes' : 'no'}`);
270
+ if (bridge.devtools.url)
271
+ parts.push(`- URL: ${bridge.devtools.url}`);
272
+ parts.push(`- Hint: ${bridge.devtools.hint}`);
273
+ parts.push('');
274
+ }
275
+ if (bridge.capabilities) {
276
+ parts.push('### Capabilities');
277
+ parts.push('');
278
+ parts.push(`- Windows: ${bridge.capabilities.windows.join(', ') || '(none)'}`);
279
+ parts.push(`- Declared: ${bridge.capabilities.declared.length}`);
280
+ parts.push('');
281
+ }
282
+ if (Object.keys(bridge.errors).length > 0) {
283
+ parts.push('### Bridge endpoint errors');
284
+ parts.push('');
285
+ for (const [endpoint, err] of Object.entries(bridge.errors)) {
286
+ parts.push(`- \`${endpoint}\`: ${err}`);
287
+ }
288
+ parts.push('');
289
+ }
290
+ return parts.join('\n');
291
+ }
292
+ function suggestNext(master, bridge, outcomes) {
293
+ const tips = [];
294
+ if (!bridge.reachable) {
295
+ tips.push(`- Bridge was not reachable; the bundle in \`forensics/\` is your best evidence. Inspect \`forensics/summary.md\` first.`);
296
+ }
297
+ if (bridge.health && !bridge.health.webview_ready) {
298
+ tips.push(`- The bridge reports the webview is **not ready**. Likely a pre-webview-boot failure — check \`forensics/app-log-tail.txt\` for the last Rust messages before the webview was supposed to come up.`);
299
+ }
300
+ if (bridge.health && !bridge.health.sidecars_alive) {
301
+ tips.push(`- One or more sidecars are dead. Run \`tauri-agent-tools sidecar tap --schema <path> -- <cmd>\` against the failing sidecar to see exactly what envelope it emits before exiting.`);
302
+ }
303
+ if (outcomes.some((o) => o.phase === 'forensics' && !o.ok)) {
304
+ tips.push(`- The forensics subprocess failed. Run \`tauri-agent-tools forensics --config ${master.identifier === '<unknown>' ? '<path>' : 'auto'} -o /tmp/forensics\` directly to surface the error.`);
305
+ }
306
+ if (tips.length === 0) {
307
+ tips.push(`- No specific anomalies detected. Browse \`${'forensics/summary.md'}\` and the per-endpoint JSON in \`bridge.json\` for context.`);
308
+ }
309
+ return tips.join('\n');
310
+ }
311
+ //# sourceMappingURL=diagnose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnose.js","sourceRoot":"","sources":["../../src/commands/diagnose.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerForensics(program: Command): void;
@@ -0,0 +1,331 @@
1
+ import { Command } from 'commander';
2
+ import { spawn } from 'node:child_process';
3
+ import { mkdir, readdir, stat, writeFile, readFile } from 'node:fs/promises';
4
+ import { existsSync } from 'node:fs';
5
+ import { homedir } from 'node:os';
6
+ import { join, basename } from 'node:path';
7
+ import { LineFramer } from '../util/ndjson.js';
8
+ import { resolveTauriProject } from '../util/tauriConfig.js';
9
+ import { buildArgs as buildDarwinArgs, parseLine as parseDarwinLine } from '../platform/oslog/darwin.js';
10
+ import { buildArgs as buildLinuxArgs, parseLine as parseLinuxLine } from '../platform/oslog/linux.js';
11
+ const PANIC_MARKERS = [
12
+ 'panicked at',
13
+ 'panic occurred',
14
+ 'SIGSEGV',
15
+ 'SIGABRT',
16
+ 'fatal runtime error',
17
+ 'thread \'main\' panicked',
18
+ 'Uncaught',
19
+ 'unhandledRejection',
20
+ ];
21
+ export function registerForensics(program) {
22
+ const cmd = new Command('forensics')
23
+ .description("Bundle a forensic snapshot of a Tauri app (paths, logs, crash reports). Works on dead apps.")
24
+ .option('--config <path>', 'Path to tauri.conf.json (or its directory). Auto-detected if omitted.')
25
+ .option('--identifier <id>', 'Bundle identifier override')
26
+ .option('-o, --out <dir>', 'Output directory (default: ./forensics-<timestamp>)')
27
+ .option('--since <duration>', 'How far back to pull from app log files (default: 10m)')
28
+ .option('--logs-duration <ms>', 'Time budget for the live OS-log tail in milliseconds (default: 3000)', parsePositiveInt)
29
+ .option('--json', 'Print the summary as JSON to stdout in addition to writing to disk')
30
+ .action(async (opts) => {
31
+ const outDir = opts.out ?? `./forensics-${timestampSlug()}`;
32
+ await mkdir(outDir, { recursive: true });
33
+ const outcomes = [];
34
+ const summary = { phases: outcomes };
35
+ // ── Phase 1: Resolve project + paths ─────────────────────────────────
36
+ const resolved = await safe(async () => resolveTauriProject({ configPath: opts.config, identifierOverride: opts.identifier }));
37
+ if (!resolved.ok) {
38
+ outcomes.push({ phase: 'resolve-config', ok: false, detail: resolved.error });
39
+ await writeSummary(outDir, summary, opts.json);
40
+ throw new Error(`Could not resolve Tauri config: ${resolved.error}`);
41
+ }
42
+ const project = resolved.value;
43
+ outcomes.push({
44
+ phase: 'resolve-config',
45
+ ok: true,
46
+ detail: `identifier=${project.identifier} productName=${project.productName}`,
47
+ });
48
+ summary['identifier'] = project.identifier;
49
+ summary['productName'] = project.productName;
50
+ summary['configPath'] = project.configPath;
51
+ summary['platform'] = project.platform;
52
+ summary['paths'] = project.paths[project.platform];
53
+ // ── Phase 2: List files in app-data + app-log directories ────────────
54
+ const dataFiles = await safeListDir(project.paths[project.platform].appDataDir);
55
+ const logFiles = await safeListDir(project.paths[project.platform].appLogDir);
56
+ summary['appDataFiles'] = dataFiles.files;
57
+ summary['appLogFiles'] = logFiles.files;
58
+ outcomes.push({
59
+ phase: 'list-app-data',
60
+ ok: dataFiles.ok,
61
+ detail: dataFiles.ok ? `${dataFiles.files.length} files` : dataFiles.detail,
62
+ });
63
+ outcomes.push({
64
+ phase: 'list-app-log',
65
+ ok: logFiles.ok,
66
+ detail: logFiles.ok ? `${logFiles.files.length} files` : logFiles.detail,
67
+ });
68
+ // ── Phase 3: Tail the most-recently-modified app log file ────────────
69
+ const newestLog = pickNewest(logFiles.files);
70
+ let logTail = [];
71
+ let panicLines = [];
72
+ if (newestLog) {
73
+ const tailResult = await safe(async () => tailFile(newestLog.path, 200));
74
+ if (tailResult.ok) {
75
+ logTail = tailResult.value;
76
+ panicLines = logTail.filter((l) => PANIC_MARKERS.some((m) => l.includes(m)));
77
+ outcomes.push({
78
+ phase: 'tail-app-log',
79
+ ok: true,
80
+ detail: `${logTail.length} lines, ${panicLines.length} panic marker(s)`,
81
+ });
82
+ await writeFile(join(outDir, 'app-log-tail.txt'), logTail.join('\n'));
83
+ }
84
+ else {
85
+ outcomes.push({ phase: 'tail-app-log', ok: false, detail: tailResult.error });
86
+ }
87
+ }
88
+ else {
89
+ outcomes.push({ phase: 'tail-app-log', ok: false, detail: 'no app log files found' });
90
+ }
91
+ summary['logTailLineCount'] = logTail.length;
92
+ summary['panicLines'] = panicLines;
93
+ // ── Phase 4: macOS DiagnosticReports (skip on other platforms) ───────
94
+ let diagnosticReports = [];
95
+ if (project.platform === 'darwin') {
96
+ const drDir = `${homedir()}/Library/Logs/DiagnosticReports`;
97
+ if (existsSync(drDir)) {
98
+ const all = await safeListDir(drDir);
99
+ // Filter to entries whose name starts with productName (macOS naming convention).
100
+ diagnosticReports = all.files
101
+ .filter((f) => basename(f.path).startsWith(project.productName))
102
+ .sort((a, b) => (a.mtime < b.mtime ? 1 : -1))
103
+ .slice(0, 5);
104
+ outcomes.push({
105
+ phase: 'macos-diagnostic-reports',
106
+ ok: true,
107
+ detail: `${diagnosticReports.length} report(s) matching productName`,
108
+ });
109
+ }
110
+ else {
111
+ outcomes.push({
112
+ phase: 'macos-diagnostic-reports',
113
+ ok: false,
114
+ detail: `${drDir} not accessible`,
115
+ });
116
+ }
117
+ }
118
+ summary['diagnosticReports'] = diagnosticReports;
119
+ // ── Phase 5: Live OS-log tail (best-effort) ──────────────────────────
120
+ const liveLogs = [];
121
+ const budgetMs = opts.logsDuration ?? 3000;
122
+ if (project.platform === 'darwin' || project.platform === 'linux') {
123
+ const livRes = await safe(async () => collectLiveLogs(project.identifier, project.productName, budgetMs, project.platform));
124
+ if (livRes.ok) {
125
+ liveLogs.push(...livRes.value);
126
+ outcomes.push({
127
+ phase: 'live-os-log-tail',
128
+ ok: true,
129
+ detail: `${liveLogs.length} entries in ${budgetMs}ms`,
130
+ });
131
+ await writeFile(join(outDir, 'live-os-log.ndjson'), liveLogs.map((e) => JSON.stringify(e)).join('\n'));
132
+ }
133
+ else {
134
+ outcomes.push({ phase: 'live-os-log-tail', ok: false, detail: livRes.error });
135
+ }
136
+ }
137
+ else {
138
+ outcomes.push({
139
+ phase: 'live-os-log-tail',
140
+ ok: false,
141
+ detail: `not implemented on ${project.platform}`,
142
+ });
143
+ }
144
+ summary['liveLogEntries'] = liveLogs.length;
145
+ // ── Phase 6: Write artifacts to outDir ───────────────────────────────
146
+ await writeFile(join(outDir, 'project.json'), JSON.stringify(project, null, 2));
147
+ await writeFile(join(outDir, 'summary.json'), JSON.stringify(summary, null, 2));
148
+ await writeFile(join(outDir, 'summary.md'), renderMarkdown(summary, outcomes, project, panicLines, logTail));
149
+ summary['outDir'] = outDir;
150
+ if (opts.json) {
151
+ console.log(JSON.stringify(summary, null, 2));
152
+ }
153
+ else {
154
+ console.log(`✓ Forensic bundle written to ${outDir}`);
155
+ console.log(` ${outcomes.filter((o) => o.ok).length}/${outcomes.length} phases succeeded`);
156
+ if (panicLines.length > 0) {
157
+ console.log(` ⚠ ${panicLines.length} panic marker(s) in the most-recent log`);
158
+ }
159
+ }
160
+ });
161
+ program.addCommand(cmd);
162
+ }
163
+ function timestampSlug() {
164
+ return new Date().toISOString().replace(/[:.]/g, '-');
165
+ }
166
+ async function safe(fn) {
167
+ try {
168
+ return { ok: true, value: await fn() };
169
+ }
170
+ catch (err) {
171
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
172
+ }
173
+ }
174
+ async function safeListDir(dir) {
175
+ if (!existsSync(dir)) {
176
+ return { ok: false, files: [], detail: `${dir} does not exist` };
177
+ }
178
+ try {
179
+ const entries = await readdir(dir);
180
+ const files = [];
181
+ for (const name of entries) {
182
+ const path = join(dir, name);
183
+ try {
184
+ const st = await stat(path);
185
+ if (!st.isFile())
186
+ continue;
187
+ files.push({ path, size: st.size, mtime: st.mtime.toISOString() });
188
+ }
189
+ catch {
190
+ // Skip unreadable entries (permission denied, etc.)
191
+ }
192
+ }
193
+ return { ok: true, files, detail: `${files.length} files` };
194
+ }
195
+ catch (err) {
196
+ return {
197
+ ok: false,
198
+ files: [],
199
+ detail: err instanceof Error ? err.message : String(err),
200
+ };
201
+ }
202
+ }
203
+ function pickNewest(files) {
204
+ if (files.length === 0)
205
+ return null;
206
+ return [...files].sort((a, b) => (a.mtime < b.mtime ? 1 : -1))[0] ?? null;
207
+ }
208
+ async function tailFile(path, lines) {
209
+ // Naive impl: read whole file, slice last N. Good enough for "tail the most
210
+ // recent app log" since these files are typically rotated/small.
211
+ const text = await readFile(path, 'utf-8');
212
+ return text.split(/\r?\n/).slice(-lines).filter((l) => l.length > 0);
213
+ }
214
+ async function collectLiveLogs(identifier, productName, budgetMs, platform) {
215
+ if (platform === 'win32')
216
+ return [];
217
+ const args = platform === 'darwin'
218
+ ? buildDarwinArgs({ identifier, productName })
219
+ : buildLinuxArgs({ identifier, productName, since: `${Math.ceil(budgetMs / 1000)}s ago` });
220
+ const cmdBin = platform === 'darwin' ? 'log' : 'journalctl';
221
+ const parser = platform === 'darwin' ? parseDarwinLine : parseLinuxLine;
222
+ const child = spawn(cmdBin, args, { stdio: ['ignore', 'pipe', 'pipe'] });
223
+ const framer = new LineFramer();
224
+ const entries = [];
225
+ child.stdout.setEncoding('utf-8');
226
+ child.stdout.on('data', (chunk) => {
227
+ for (const line of framer.push(chunk)) {
228
+ const e = parser(line);
229
+ if (e)
230
+ entries.push(e);
231
+ }
232
+ });
233
+ const timer = setTimeout(() => child.kill('SIGTERM'), budgetMs);
234
+ await new Promise((resolve) => {
235
+ child.on('close', () => {
236
+ clearTimeout(timer);
237
+ resolve();
238
+ });
239
+ child.on('error', () => {
240
+ clearTimeout(timer);
241
+ resolve(); // swallow — forensics is best-effort
242
+ });
243
+ });
244
+ return entries;
245
+ }
246
+ async function writeSummary(outDir, summary, alsoStdout) {
247
+ await writeFile(join(outDir, 'summary.json'), JSON.stringify(summary, null, 2));
248
+ if (alsoStdout)
249
+ console.log(JSON.stringify(summary, null, 2));
250
+ }
251
+ function renderMarkdown(summary, outcomes, project, panicLines, logTail) {
252
+ const phases = outcomes
253
+ .map((o) => `- ${o.ok ? '✓' : '✗'} **${o.phase}** — ${o.detail}`)
254
+ .join('\n');
255
+ const dataFiles = summary['appDataFiles'] ?? [];
256
+ const logFiles = summary['appLogFiles'] ?? [];
257
+ const dataList = dataFiles.length === 0
258
+ ? '_no files_'
259
+ : dataFiles.map((f) => `- \`${f.path}\` (${f.size} bytes, ${f.mtime})`).join('\n');
260
+ const logList = logFiles.length === 0
261
+ ? '_no files_'
262
+ : logFiles.map((f) => `- \`${f.path}\` (${f.size} bytes, ${f.mtime})`).join('\n');
263
+ const panicBlock = panicLines.length === 0
264
+ ? '_none_'
265
+ : panicLines.map((l) => ' ' + l).join('\n');
266
+ const tailBlock = logTail.length === 0
267
+ ? '_no log file to tail_'
268
+ : '```\n' + logTail.slice(-20).join('\n') + '\n```';
269
+ return `# Forensics: ${project.productName}
270
+
271
+ - **Identifier:** \`${project.identifier}\`
272
+ - **Platform:** ${project.platform}
273
+ - **Config:** \`${project.configPath}\`
274
+
275
+ ## Phases
276
+
277
+ ${phases}
278
+
279
+ ## Resolved paths (current platform)
280
+
281
+ \`\`\`json
282
+ ${JSON.stringify(project.paths[project.platform], null, 2)}
283
+ \`\`\`
284
+
285
+ ## App data files
286
+
287
+ ${dataList}
288
+
289
+ ## App log files
290
+
291
+ ${logList}
292
+
293
+ ## Panic markers in most-recent log
294
+
295
+ \`\`\`
296
+ ${panicBlock}
297
+ \`\`\`
298
+
299
+ ## Tail of most-recent log (last 20 lines)
300
+
301
+ ${tailBlock}
302
+
303
+ ## Suggested next steps
304
+
305
+ ${suggestNextSteps(project, panicLines, logTail, outcomes)}
306
+ `;
307
+ }
308
+ function suggestNextSteps(project, panicLines, logTail, outcomes) {
309
+ const tips = [];
310
+ if (panicLines.length > 0) {
311
+ tips.push(`- A panic was detected. Run \`tauri-agent-tools os-logs --identifier ${project.identifier} --level error --since 30m --json\` to fetch surrounding error-level entries.`);
312
+ }
313
+ if (logTail.length === 0) {
314
+ tips.push(`- No app log file found. Verify the app actually writes to \`appLogDir\` (default for Tauri is via \`tauri-plugin-log\`).`);
315
+ }
316
+ if (outcomes.some((o) => o.phase === 'live-os-log-tail' && !o.ok)) {
317
+ tips.push(`- Live OS-log tail failed. Check that \`${project.platform === 'darwin' ? 'log' : 'journalctl'}\` is on PATH.`);
318
+ }
319
+ if (tips.length === 0) {
320
+ tips.push(`- No specific anomalies detected. Inspect the artifacts in this folder for context.`);
321
+ }
322
+ return tips.join('\n');
323
+ }
324
+ function parsePositiveInt(value) {
325
+ const n = parseInt(value, 10);
326
+ if (!Number.isFinite(n) || n <= 0) {
327
+ throw new Error(`Expected a positive integer, got: ${value}`);
328
+ }
329
+ return n;
330
+ }
331
+ //# sourceMappingURL=forensics.js.map