@misha_misha/agentwatch 0.0.1

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/dist/index.js ADDED
@@ -0,0 +1,1210 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ detectAgents
4
+ } from "./chunk-LF76VUCL.js";
5
+ import {
6
+ claudeProjectsDir,
7
+ detectWorkspaceRoot
8
+ } from "./chunk-EMMMIDXY.js";
9
+
10
+ // src/index.tsx
11
+ import { render } from "ink";
12
+
13
+ // src/ui/App.tsx
14
+ import { useEffect, useReducer, useState } from "react";
15
+ import { Box as Box5, Text as Text5, useApp, useInput } from "ink";
16
+
17
+ // src/ui/Timeline.tsx
18
+ import { Box, Text } from "ink";
19
+ import { jsx, jsxs } from "react/jsx-runtime";
20
+ function Timeline({ events }) {
21
+ const header = /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { bold: true, dimColor: true, children: [
22
+ "TIME ",
23
+ pad("AGENT", 10),
24
+ " ",
25
+ pad("TYPE", 13),
26
+ " ",
27
+ "EVENT"
28
+ ] }) });
29
+ if (events.length === 0) {
30
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
31
+ header,
32
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "waiting for activity\u2026 use Claude Code or edit a file in your workspace" }) })
33
+ ] });
34
+ }
35
+ const visible = events.slice(0, 40);
36
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
37
+ header,
38
+ visible.map((e) => /* @__PURE__ */ jsx(EventRow, { event: e }, e.id))
39
+ ] });
40
+ }
41
+ function EventRow({ event }) {
42
+ const time = event.ts.slice(11, 19);
43
+ const line = event.summary ?? event.path ?? event.cmd ?? event.tool ?? event.type;
44
+ return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { wrap: "truncate", children: [
45
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
46
+ time,
47
+ " "
48
+ ] }),
49
+ /* @__PURE__ */ jsxs(Text, { color: agentColor(event.agent), children: [
50
+ pad(event.agent, 10),
51
+ " "
52
+ ] }),
53
+ /* @__PURE__ */ jsxs(Text, { color: riskColor(event.riskScore), children: [
54
+ pad(event.type, 13),
55
+ " "
56
+ ] }),
57
+ /* @__PURE__ */ jsx(Text, { children: line })
58
+ ] }) });
59
+ }
60
+ function agentColor(a) {
61
+ switch (a) {
62
+ case "claude-code":
63
+ return "cyan";
64
+ case "codex":
65
+ return "green";
66
+ case "cursor":
67
+ return "magenta";
68
+ case "gemini":
69
+ return "blue";
70
+ case "openclaw":
71
+ return "yellow";
72
+ default:
73
+ return "gray";
74
+ }
75
+ }
76
+ function riskColor(r) {
77
+ if (r >= 8) return "red";
78
+ if (r >= 5) return "yellow";
79
+ if (r >= 3) return "white";
80
+ return "gray";
81
+ }
82
+ function pad(s, n) {
83
+ if (s.length >= n) return s.slice(0, n);
84
+ return s + " ".repeat(n - s.length);
85
+ }
86
+
87
+ // src/ui/AgentPanel.tsx
88
+ import { Box as Box2, Text as Text2 } from "ink";
89
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
90
+ function AgentPanel({ agents, events }) {
91
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", borderStyle: "single", paddingX: 1, children: [
92
+ /* @__PURE__ */ jsx2(Text2, { bold: true, children: "Agents" }),
93
+ agents.map((a) => {
94
+ const count = events.filter((e) => e.agent === a.name).length;
95
+ const last = events.find((e) => e.agent === a.name);
96
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: 1, children: [
97
+ /* @__PURE__ */ jsxs2(Text2, { color: a.present ? "green" : "gray", children: [
98
+ a.present ? "\u25CF" : "\u25CB",
99
+ " ",
100
+ a.label
101
+ ] }),
102
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
103
+ " ",
104
+ a.present ? "installed" : "not detected"
105
+ ] }),
106
+ a.present && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
107
+ " ",
108
+ "events: ",
109
+ count,
110
+ last ? `, last ${last.ts.slice(11, 19)}` : ""
111
+ ] })
112
+ ] }, a.name);
113
+ })
114
+ ] });
115
+ }
116
+
117
+ // src/ui/Header.tsx
118
+ import { Box as Box3, Text as Text3 } from "ink";
119
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
120
+ function Header({ workspace, eventCount, filter, paused }) {
121
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "row", justifyContent: "space-between", marginBottom: 1, children: [
122
+ /* @__PURE__ */ jsxs3(Text3, { children: [
123
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: "agentwatch " }),
124
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "v0.0.1" })
125
+ ] }),
126
+ /* @__PURE__ */ jsxs3(Text3, { children: [
127
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "workspace: " }),
128
+ /* @__PURE__ */ jsx3(Text3, { children: workspace }),
129
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " events: " }),
130
+ /* @__PURE__ */ jsx3(Text3, { children: eventCount }),
131
+ filter && /* @__PURE__ */ jsxs3(Fragment, { children: [
132
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " filter: " }),
133
+ /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: filter })
134
+ ] }),
135
+ paused && /* @__PURE__ */ jsxs3(Fragment, { children: [
136
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " " }),
137
+ /* @__PURE__ */ jsx3(Text3, { color: "red", children: "[PAUSED]" })
138
+ ] })
139
+ ] })
140
+ ] });
141
+ }
142
+
143
+ // src/ui/PermissionView.tsx
144
+ import { Box as Box4, Text as Text4 } from "ink";
145
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
146
+ function PermissionView({ permissions }) {
147
+ if (permissions.length === 0) {
148
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", borderStyle: "double", paddingX: 1, children: [
149
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "Permissions \u2014 Claude Code" }),
150
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "No settings.json found at ~/.claude/ or project .claude/" }),
151
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Press p to close." })
152
+ ] });
153
+ }
154
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", borderStyle: "double", paddingX: 1, children: [
155
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "Permissions \u2014 Claude Code" }),
156
+ permissions.map((p) => /* @__PURE__ */ jsx4(Block, { perms: p }, p.source)),
157
+ /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Press p to close. These are the permissions Claude Code uses on your machine." }) })
158
+ ] });
159
+ }
160
+ function Block({ perms }) {
161
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: 1, children: [
162
+ /* @__PURE__ */ jsxs4(Text4, { children: [
163
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "source: " }),
164
+ /* @__PURE__ */ jsx4(Text4, { children: perms.source })
165
+ ] }),
166
+ /* @__PURE__ */ jsxs4(Text4, { children: [
167
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "defaultMode: " }),
168
+ /* @__PURE__ */ jsx4(Text4, { color: modeColor(perms.defaultMode), children: perms.defaultMode })
169
+ ] }),
170
+ /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
171
+ /* @__PURE__ */ jsxs4(Text4, { bold: true, color: "green", children: [
172
+ "CAN (",
173
+ perms.allow.length,
174
+ ")"
175
+ ] }),
176
+ perms.allow.length === 0 ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " (none \u2014 defaultMode applies)" }) : perms.allow.map((a, i) => /* @__PURE__ */ jsxs4(Text4, { children: [
177
+ " ",
178
+ /* @__PURE__ */ jsx4(Text4, { color: "green", children: "\u2713" }),
179
+ " ",
180
+ a
181
+ ] }, i))
182
+ ] }),
183
+ /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
184
+ /* @__PURE__ */ jsxs4(Text4, { bold: true, color: "red", children: [
185
+ "CANNOT (",
186
+ perms.deny.length,
187
+ ")"
188
+ ] }),
189
+ perms.deny.length === 0 ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " (none \u2014 no explicit denies)" }) : perms.deny.map((d, i) => /* @__PURE__ */ jsxs4(Text4, { children: [
190
+ " ",
191
+ /* @__PURE__ */ jsx4(Text4, { color: "red", children: "\u2717" }),
192
+ " ",
193
+ d
194
+ ] }, i))
195
+ ] }),
196
+ perms.additionalDirectories.length > 0 && /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
197
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: "Additional writable directories" }),
198
+ perms.additionalDirectories.map((d, i) => /* @__PURE__ */ jsxs4(Text4, { children: [
199
+ " \u2022 ",
200
+ d
201
+ ] }, i))
202
+ ] }),
203
+ perms.flags.length > 0 && /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
204
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: "\u26A0 Flags" }),
205
+ perms.flags.map((f, i) => /* @__PURE__ */ jsxs4(Text4, { color: f.level === "risk" ? "red" : "yellow", children: [
206
+ " ",
207
+ f.level === "risk" ? "\u2717" : "!",
208
+ " ",
209
+ f.message
210
+ ] }, i))
211
+ ] })
212
+ ] });
213
+ }
214
+ function modeColor(mode) {
215
+ if (mode === "auto" || mode === "bypassPermissions") return "red";
216
+ if (mode === "ask") return "green";
217
+ return "yellow";
218
+ }
219
+
220
+ // src/adapters/claude-code.ts
221
+ import chokidar from "chokidar";
222
+ import { createReadStream, existsSync, statSync } from "fs";
223
+ import { createInterface } from "readline";
224
+ import { basename, sep } from "path";
225
+
226
+ // src/schema.ts
227
+ function riskOf(type, path, cmd) {
228
+ if (type === "shell_exec") {
229
+ if (cmd && /\b(rm|sudo|curl|wget|chmod|chown)\b/.test(cmd)) return 9;
230
+ return 6;
231
+ }
232
+ if (type === "file_write" || type === "file_change") {
233
+ if (path && /\.(env|key|pem|credentials)/.test(path)) return 9;
234
+ if (path && /(^|\/)(\.ssh|\.aws|\.gnupg)\//.test(path)) return 10;
235
+ return 4;
236
+ }
237
+ if (type === "file_read") {
238
+ if (path && /\.(env|key|pem|credentials)/.test(path)) return 7;
239
+ return 2;
240
+ }
241
+ if (type === "tool_call") return 3;
242
+ return 1;
243
+ }
244
+
245
+ // src/util/ids.ts
246
+ var counter = 0;
247
+ function nextId() {
248
+ counter += 1;
249
+ return `${Date.now().toString(36)}-${counter.toString(36)}`;
250
+ }
251
+
252
+ // src/adapters/claude-code.ts
253
+ var BACKFILL_BYTES = 64 * 1024;
254
+ function startClaudeAdapter(emit) {
255
+ const dir = claudeProjectsDir();
256
+ if (!existsSync(dir)) {
257
+ return () => {
258
+ };
259
+ }
260
+ const cursors = /* @__PURE__ */ new Map();
261
+ const sessionRe = /[\\/]projects[\\/][^\\/]+[\\/][^\\/]+\.jsonl$/;
262
+ const watcher = chokidar.watch(dir, {
263
+ persistent: true,
264
+ ignoreInitial: false,
265
+ depth: 3
266
+ });
267
+ const process2 = (file, isInitialAdd) => {
268
+ if (!sessionRe.test(file)) return;
269
+ const size = safeSize(file);
270
+ let cursor = cursors.get(file);
271
+ if (!cursor) {
272
+ const start2 = isInitialAdd ? Math.max(0, size - BACKFILL_BYTES) : size;
273
+ cursor = { offset: start2 };
274
+ cursors.set(file, cursor);
275
+ }
276
+ if (size <= cursor.offset) return;
277
+ const start = cursor.offset;
278
+ const stream = createReadStream(file, {
279
+ start,
280
+ end: size - 1,
281
+ encoding: "utf8"
282
+ });
283
+ const sessionId = basename(file, ".jsonl");
284
+ const project = extractProject(file);
285
+ let consumed = 0;
286
+ let skippedFirst = false;
287
+ const rl = createInterface({ input: stream, crlfDelay: Infinity });
288
+ rl.on("line", (line) => {
289
+ consumed += Buffer.byteLength(line, "utf8") + 1;
290
+ if (isInitialAdd && start > 0 && !skippedFirst) {
291
+ skippedFirst = true;
292
+ return;
293
+ }
294
+ if (!line.trim()) return;
295
+ try {
296
+ const obj = JSON.parse(line);
297
+ const event = translateClaudeLine(obj, sessionId, project);
298
+ if (event) emit(event);
299
+ } catch {
300
+ }
301
+ });
302
+ rl.on("close", () => {
303
+ cursor.offset = start + consumed;
304
+ });
305
+ };
306
+ watcher.on("add", (f) => process2(f, true));
307
+ watcher.on("change", (f) => process2(f, false));
308
+ watcher.on("error", (err) => {
309
+ if (typeof err === "object" && err !== null) {
310
+ const code = err.code;
311
+ if (code === "EMFILE" || code === "ENOSPC" || code === "EACCES") return;
312
+ }
313
+ console.error("[agentwatch/claude]", String(err));
314
+ });
315
+ return () => {
316
+ void watcher.close();
317
+ };
318
+ }
319
+ function extractProject(file) {
320
+ const parts = file.split(sep);
321
+ const projIdx = parts.lastIndexOf("projects");
322
+ if (projIdx >= 0 && parts[projIdx + 1]) {
323
+ const dir = parts[projIdx + 1];
324
+ const segs = dir.split("-").filter(Boolean);
325
+ return segs[segs.length - 1] ?? dir;
326
+ }
327
+ return "";
328
+ }
329
+ function safeSize(file) {
330
+ try {
331
+ return statSync(file).size;
332
+ } catch {
333
+ return 0;
334
+ }
335
+ }
336
+ function translateClaudeLine(obj, sessionId, project = "") {
337
+ if (!obj || typeof obj !== "object") return null;
338
+ const o = obj;
339
+ const ts = typeof o.timestamp === "string" && o.timestamp || (/* @__PURE__ */ new Date()).toISOString();
340
+ const prefix = project ? `[${project}] ` : "";
341
+ const role = o.role ?? o.message?.role;
342
+ const type = o.type;
343
+ const content = o.message?.content;
344
+ if (type === "tool_result" || type === "summary") return null;
345
+ if (type === "worktree-state" || type === "compact") return null;
346
+ if (type === "assistant" || role === "assistant") {
347
+ const toolUse = findToolUse(content);
348
+ if (toolUse) {
349
+ const evType = inferToolType(toolUse.name);
350
+ const summary = buildToolSummary(toolUse);
351
+ return {
352
+ id: nextId(),
353
+ ts,
354
+ agent: "claude-code",
355
+ type: evType,
356
+ path: toolUse.path,
357
+ cmd: toolUse.cmd,
358
+ tool: toolUse.name,
359
+ summary: prefix + summary,
360
+ sessionId,
361
+ riskScore: riskOf(evType, toolUse.path, toolUse.cmd)
362
+ };
363
+ }
364
+ const text = extractText(content);
365
+ if (!text) return null;
366
+ return {
367
+ id: nextId(),
368
+ ts,
369
+ agent: "claude-code",
370
+ type: "response",
371
+ summary: prefix + truncate(text),
372
+ sessionId,
373
+ riskScore: riskOf("response")
374
+ };
375
+ }
376
+ if (type === "user" || role === "user") {
377
+ const text = extractUserText(content);
378
+ if (!text) return null;
379
+ return {
380
+ id: nextId(),
381
+ ts,
382
+ agent: "claude-code",
383
+ type: "prompt",
384
+ summary: prefix + truncate(text),
385
+ sessionId,
386
+ riskScore: riskOf("prompt")
387
+ };
388
+ }
389
+ return null;
390
+ }
391
+ function findToolUse(content) {
392
+ if (!Array.isArray(content)) return null;
393
+ for (const c of content) {
394
+ if (typeof c !== "object" || c === null) continue;
395
+ const rec = c;
396
+ if (rec.type !== "tool_use") continue;
397
+ const name = typeof rec.name === "string" ? rec.name : "unknown";
398
+ const input = rec.input ?? {};
399
+ const path = typeof input.file_path === "string" ? input.file_path : typeof input.path === "string" ? input.path : void 0;
400
+ const cmd = typeof input.command === "string" ? input.command : void 0;
401
+ return { name, path, cmd, input };
402
+ }
403
+ return null;
404
+ }
405
+ function buildToolSummary(t) {
406
+ if (/^Bash/i.test(t.name) && t.cmd) return `Bash: ${truncate(t.cmd, 100)}`;
407
+ if (/^(Write|Edit|MultiEdit|Read)/i.test(t.name) && t.path) {
408
+ return `${t.name}: ${t.path}`;
409
+ }
410
+ if (/^(Grep|Glob)/i.test(t.name)) {
411
+ const pat = typeof t.input.pattern === "string" ? t.input.pattern : typeof t.input.glob === "string" ? t.input.glob : "";
412
+ return `${t.name}: ${truncate(pat, 100)}`;
413
+ }
414
+ if (/^Task/i.test(t.name)) {
415
+ const desc = typeof t.input.description === "string" ? t.input.description : "";
416
+ return `Task: ${truncate(desc, 100)}`;
417
+ }
418
+ if (/^WebFetch/i.test(t.name)) {
419
+ const url = typeof t.input.url === "string" ? t.input.url : "";
420
+ return `WebFetch: ${url}`;
421
+ }
422
+ const firstVal = Object.values(t.input).find(
423
+ (v) => typeof v === "string"
424
+ );
425
+ return firstVal ? `${t.name}: ${truncate(firstVal, 100)}` : t.name;
426
+ }
427
+ function extractText(content) {
428
+ if (typeof content === "string") return content;
429
+ if (!Array.isArray(content)) return "";
430
+ const parts = [];
431
+ for (const c of content) {
432
+ if (typeof c !== "object" || c === null) continue;
433
+ const rec = c;
434
+ if (rec.type === "text" && typeof rec.text === "string") {
435
+ parts.push(rec.text);
436
+ } else if (rec.type === "thinking" && typeof rec.thinking === "string") {
437
+ parts.push(rec.thinking);
438
+ }
439
+ }
440
+ return parts.join(" ").trim();
441
+ }
442
+ function extractUserText(content) {
443
+ if (typeof content === "string") return content.trim();
444
+ if (!Array.isArray(content)) return "";
445
+ const parts = [];
446
+ for (const c of content) {
447
+ if (typeof c !== "object" || c === null) continue;
448
+ const rec = c;
449
+ if (rec.type === "text" && typeof rec.text === "string") {
450
+ parts.push(rec.text);
451
+ }
452
+ }
453
+ return parts.join(" ").trim();
454
+ }
455
+ function inferToolType(name) {
456
+ if (/^Bash/i.test(name)) return "shell_exec";
457
+ if (/^(Read|Grep|Glob)/i.test(name)) return "file_read";
458
+ if (/^(Write|Edit|MultiEdit)/i.test(name)) return "file_write";
459
+ return "tool_call";
460
+ }
461
+ function truncate(s, max = 140) {
462
+ const clean = s.replace(/\s+/g, " ").trim();
463
+ if (!clean) return "";
464
+ return clean.length <= max ? clean : clean.slice(0, max - 1) + "\u2026";
465
+ }
466
+
467
+ // src/adapters/openclaw.ts
468
+ import chokidar2 from "chokidar";
469
+ import { createReadStream as createReadStream2, existsSync as existsSync2, statSync as statSync2 } from "fs";
470
+ import { createInterface as createInterface2 } from "readline";
471
+ import { basename as basename2, join, sep as sep2 } from "path";
472
+ import { homedir } from "os";
473
+ var sessionCwd = /* @__PURE__ */ new Map();
474
+ function startOpenClawAdapter(emit) {
475
+ const root = join(homedir(), ".openclaw");
476
+ if (!existsSync2(root)) return () => {
477
+ };
478
+ const cursors = /* @__PURE__ */ new Map();
479
+ const stoppers = [];
480
+ const agentsDir = join(root, "agents");
481
+ const sessionRe = /[\\/]agents[\\/][^\\/]+[\\/]sessions[\\/][^\\/]+\.jsonl$/;
482
+ const sessionsWatcher = chokidar2.watch(agentsDir, {
483
+ persistent: true,
484
+ ignoreInitial: false,
485
+ depth: 4,
486
+ ignored: (p) => /\.reset\./.test(p)
487
+ });
488
+ const handleSession = (f, initial) => {
489
+ if (!sessionRe.test(f)) return;
490
+ processSession(f, initial, cursors, emit);
491
+ };
492
+ sessionsWatcher.on("add", (f) => handleSession(f, true));
493
+ sessionsWatcher.on("change", (f) => handleSession(f, false));
494
+ sessionsWatcher.on("error", swallow);
495
+ stoppers.push(() => {
496
+ void sessionsWatcher.close();
497
+ });
498
+ const auditPath = join(root, "logs", "config-audit.jsonl");
499
+ const auditWatcher = chokidar2.watch(auditPath, {
500
+ persistent: true,
501
+ ignoreInitial: false
502
+ });
503
+ auditWatcher.on("add", (f) => processAudit(f, true, cursors, emit));
504
+ auditWatcher.on("change", (f) => processAudit(f, false, cursors, emit));
505
+ auditWatcher.on("error", swallow);
506
+ stoppers.push(() => {
507
+ void auditWatcher.close();
508
+ });
509
+ return () => {
510
+ for (const s of stoppers) s();
511
+ };
512
+ }
513
+ function processSession(file, startFromEnd, cursors, emit) {
514
+ const subAgent = extractSubAgent(file);
515
+ const sessionId = basename2(file, ".jsonl");
516
+ streamLines(file, startFromEnd, cursors, (line) => {
517
+ let obj;
518
+ try {
519
+ obj = JSON.parse(line);
520
+ } catch {
521
+ return;
522
+ }
523
+ const event = translateSession(obj, subAgent, sessionId);
524
+ if (event) emit(event);
525
+ });
526
+ }
527
+ function processAudit(file, startFromEnd, cursors, emit) {
528
+ streamLines(file, startFromEnd, cursors, (line) => {
529
+ let obj;
530
+ try {
531
+ obj = JSON.parse(line);
532
+ } catch {
533
+ return;
534
+ }
535
+ const event = translateAudit(obj);
536
+ if (event) emit(event);
537
+ });
538
+ }
539
+ var BACKFILL_BYTES2 = 64 * 1024;
540
+ function streamLines(file, isInitialAdd, cursors, onLine) {
541
+ const size = safeSize2(file);
542
+ let cursor = cursors.get(file);
543
+ if (!cursor) {
544
+ const backfillStart = Math.max(0, size - BACKFILL_BYTES2);
545
+ cursor = { offset: isInitialAdd ? backfillStart : size };
546
+ cursors.set(file, cursor);
547
+ }
548
+ if (size <= cursor.offset) return;
549
+ const start = cursor.offset;
550
+ const stream = createReadStream2(file, {
551
+ start,
552
+ end: size - 1,
553
+ encoding: "utf8"
554
+ });
555
+ let consumed = 0;
556
+ let skippedFirst = false;
557
+ const rl = createInterface2({ input: stream, crlfDelay: Infinity });
558
+ rl.on("line", (line) => {
559
+ consumed += Buffer.byteLength(line, "utf8") + 1;
560
+ if (isInitialAdd && start > 0 && !skippedFirst) {
561
+ skippedFirst = true;
562
+ return;
563
+ }
564
+ if (line.trim()) onLine(line);
565
+ });
566
+ rl.on("close", () => {
567
+ cursor.offset = start + consumed;
568
+ });
569
+ }
570
+ function swallow(err) {
571
+ if (typeof err !== "object" || err === null) return;
572
+ const code = err.code;
573
+ if (code === "EMFILE" || code === "ENOSPC" || code === "EACCES") return;
574
+ console.error("[agentwatch/openclaw]", String(err));
575
+ }
576
+ function safeSize2(file) {
577
+ try {
578
+ return statSync2(file).size;
579
+ } catch {
580
+ return 0;
581
+ }
582
+ }
583
+ function extractSubAgent(file) {
584
+ const parts = file.split(sep2);
585
+ const agentsIdx = parts.lastIndexOf("agents");
586
+ if (agentsIdx >= 0 && parts[agentsIdx + 1]) return parts[agentsIdx + 1];
587
+ return "unknown";
588
+ }
589
+ function translateSession(obj, subAgent, sessionId) {
590
+ if (!obj || typeof obj !== "object") return null;
591
+ const o = obj;
592
+ const ts = typeof o.timestamp === "string" && o.timestamp || (/* @__PURE__ */ new Date()).toISOString();
593
+ const t = o.type;
594
+ const projectLabel = () => {
595
+ const cwd = sessionCwd.get(sessionId);
596
+ if (!cwd) return "";
597
+ const b = cwd.split("/").filter(Boolean).pop();
598
+ return b ? `[${b}] ` : "";
599
+ };
600
+ const base = (type, fields = {}) => {
601
+ const prefix = projectLabel();
602
+ const rawSummary = fields.summary ?? "";
603
+ return {
604
+ id: nextId(),
605
+ ts,
606
+ agent: "openclaw",
607
+ type,
608
+ tool: `openclaw:${subAgent}`,
609
+ sessionId,
610
+ riskScore: riskOf(type, fields.path, fields.cmd),
611
+ ...fields,
612
+ summary: rawSummary ? prefix + rawSummary : prefix + type
613
+ };
614
+ };
615
+ if (t === "session") {
616
+ const cwd = typeof o.cwd === "string" ? o.cwd : void 0;
617
+ if (cwd) sessionCwd.set(sessionId, cwd);
618
+ return base("session_start", {
619
+ path: cwd,
620
+ summary: `openclaw/${subAgent} session started${cwd ? ` in ${cwd}` : ""}`
621
+ });
622
+ }
623
+ if (t === "model_change") {
624
+ const model = typeof o.modelId === "string" ? o.modelId : "";
625
+ const provider = typeof o.provider === "string" ? o.provider : "";
626
+ return base("tool_call", {
627
+ summary: `model \u2192 ${provider}/${model}`,
628
+ tool: `openclaw:${subAgent}:model`
629
+ });
630
+ }
631
+ if (t === "message") {
632
+ const msg = o.message;
633
+ const role = msg?.role;
634
+ const content = msg?.content;
635
+ const text = extractText2(content);
636
+ if (role === "user") {
637
+ return base("prompt", { summary: truncate2(text) });
638
+ }
639
+ if (role === "assistant") {
640
+ const toolUse = extractToolUse(content);
641
+ if (toolUse) {
642
+ const type = inferToolType2(toolUse.name);
643
+ return base(type, {
644
+ tool: `openclaw:${subAgent}:${toolUse.name}`,
645
+ path: toolUse.path,
646
+ cmd: toolUse.cmd,
647
+ summary: truncate2(toolUse.summary)
648
+ });
649
+ }
650
+ if (!text) return null;
651
+ return base("response", { summary: truncate2(text) });
652
+ }
653
+ }
654
+ return null;
655
+ }
656
+ function translateAudit(obj) {
657
+ if (!obj || typeof obj !== "object") return null;
658
+ const o = obj;
659
+ const ts = typeof o.ts === "string" && o.ts || (/* @__PURE__ */ new Date()).toISOString();
660
+ const event = typeof o.event === "string" ? o.event : "config.event";
661
+ const configPath = typeof o.configPath === "string" ? o.configPath : void 0;
662
+ const cwd = typeof o.cwd === "string" ? o.cwd : void 0;
663
+ const argv = Array.isArray(o.argv) ? o.argv.join(" ") : "";
664
+ const suspicious = Array.isArray(o.suspicious) && o.suspicious.length > 0;
665
+ return {
666
+ id: nextId(),
667
+ ts,
668
+ agent: "openclaw",
669
+ type: "file_write",
670
+ tool: `openclaw:audit:${event}`,
671
+ path: configPath,
672
+ cmd: argv,
673
+ summary: `${event}${configPath ? ` ${basename2(configPath)}` : ""}${cwd ? ` (cwd: ${cwd})` : ""}`,
674
+ // audit writes are inherently sensitive; suspicious flag bumps to max
675
+ riskScore: suspicious ? 10 : Math.max(5, riskOf("file_write", configPath))
676
+ };
677
+ }
678
+ function extractText2(content) {
679
+ if (typeof content === "string") return content;
680
+ if (!Array.isArray(content)) return "";
681
+ return content.filter(
682
+ (c) => typeof c === "object" && c !== null && c.type === "text"
683
+ ).map((c) => c.text).join(" ");
684
+ }
685
+ function extractToolUse(content) {
686
+ if (!Array.isArray(content)) return null;
687
+ for (const c of content) {
688
+ if (typeof c === "object" && c !== null && c.type === "tool_use") {
689
+ const r = c;
690
+ const name = typeof r.name === "string" ? r.name : "unknown";
691
+ const input = r.input ?? {};
692
+ const path = typeof input.file_path === "string" ? input.file_path : typeof input.path === "string" ? input.path : void 0;
693
+ const cmd = typeof input.command === "string" ? input.command : void 0;
694
+ const summary = cmd ?? path ?? name;
695
+ return { name, path, cmd, summary };
696
+ }
697
+ }
698
+ return null;
699
+ }
700
+ function inferToolType2(name) {
701
+ if (/^Bash|^Shell|^Exec/i.test(name)) return "shell_exec";
702
+ if (/^Read|^View|^Open/i.test(name)) return "file_read";
703
+ if (/^(Write|Edit|MultiEdit|Create)/i.test(name)) return "file_write";
704
+ return "tool_call";
705
+ }
706
+ function truncate2(s, max = 140) {
707
+ const clean = s.replace(/\s+/g, " ").trim();
708
+ if (!clean) return "";
709
+ return clean.length <= max ? clean : clean.slice(0, max - 1) + "\u2026";
710
+ }
711
+
712
+ // src/adapters/cursor.ts
713
+ import chokidar3 from "chokidar";
714
+ import {
715
+ readFileSync,
716
+ existsSync as existsSync3,
717
+ readdirSync,
718
+ statSync as statSync3
719
+ } from "fs";
720
+ import { homedir as homedir2 } from "os";
721
+ import { join as join2 } from "path";
722
+ function startCursorAdapter(workspace, emit) {
723
+ const cursorDir = join2(homedir2(), ".cursor");
724
+ const installed = existsSync3(cursorDir);
725
+ const status = {
726
+ installed,
727
+ mcpServers: [],
728
+ cursorRulesFiles: []
729
+ };
730
+ if (!installed) return { stop: () => {
731
+ }, status };
732
+ const stoppers = [];
733
+ const lastRecentFiles = /* @__PURE__ */ new Set();
734
+ const emitEvent = (type, summary, opts = {}) => {
735
+ emit({
736
+ id: nextId(),
737
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
738
+ agent: "cursor",
739
+ type,
740
+ tool: "cursor",
741
+ summary,
742
+ riskScore: riskOf(type, opts.path, opts.cmd),
743
+ ...opts
744
+ });
745
+ };
746
+ const mcpPath = join2(cursorDir, "mcp.json");
747
+ if (existsSync3(mcpPath)) {
748
+ status.mcpServers = readMcpServers(mcpPath);
749
+ const w = chokidar3.watch(mcpPath, {
750
+ persistent: true,
751
+ ignoreInitial: true
752
+ });
753
+ w.on("change", () => {
754
+ status.mcpServers = readMcpServers(mcpPath);
755
+ emitEvent(
756
+ "file_write",
757
+ `Cursor MCP changed: ${status.mcpServers.length} server(s) (${status.mcpServers.join(", ") || "none"})`,
758
+ { path: mcpPath, tool: "cursor:mcp" }
759
+ );
760
+ });
761
+ w.on("error", swallow2);
762
+ stoppers.push(() => {
763
+ void w.close();
764
+ });
765
+ }
766
+ const permPath = join2(cursorDir, "cli-config.json");
767
+ if (existsSync3(permPath)) {
768
+ status.permissions = readPermissions(permPath);
769
+ const w = chokidar3.watch(permPath, {
770
+ persistent: true,
771
+ ignoreInitial: true
772
+ });
773
+ w.on("change", () => {
774
+ status.permissions = readPermissions(permPath);
775
+ const p = status.permissions;
776
+ emitEvent(
777
+ "file_write",
778
+ `Cursor permissions changed: mode=${p?.approvalMode}, sandbox=${p?.sandboxMode}, allow=${p?.allowCount}, deny=${p?.denyCount}`,
779
+ { path: permPath, tool: "cursor:permissions" }
780
+ );
781
+ });
782
+ w.on("error", swallow2);
783
+ stoppers.push(() => {
784
+ void w.close();
785
+ });
786
+ }
787
+ const stateFile = join2(cursorDir, "ide_state.json");
788
+ if (existsSync3(stateFile)) {
789
+ for (const p of readRecentFiles(stateFile)) lastRecentFiles.add(p);
790
+ const w = chokidar3.watch(stateFile, {
791
+ persistent: true,
792
+ ignoreInitial: true
793
+ });
794
+ w.on("change", () => {
795
+ const recent = readRecentFiles(stateFile);
796
+ for (const path of recent) {
797
+ if (lastRecentFiles.has(path)) continue;
798
+ lastRecentFiles.add(path);
799
+ const project = extractProject2(path);
800
+ emitEvent("file_read", project ? `[${project}] ${path}` : path, {
801
+ tool: "cursor:ide_state",
802
+ path
803
+ });
804
+ }
805
+ });
806
+ w.on("error", swallow2);
807
+ stoppers.push(() => {
808
+ void w.close();
809
+ });
810
+ }
811
+ const rulesFiles = discoverCursorrules(workspace);
812
+ status.cursorRulesFiles = rulesFiles;
813
+ for (const path of rulesFiles) {
814
+ const w = chokidar3.watch(path, {
815
+ persistent: true,
816
+ ignoreInitial: true
817
+ });
818
+ w.on("change", () => {
819
+ const project = extractProject2(path);
820
+ emitEvent(
821
+ "file_write",
822
+ `${project ? `[${project}] ` : ""}.cursorrules edited`,
823
+ { tool: "cursor:rules", path }
824
+ );
825
+ });
826
+ w.on("error", swallow2);
827
+ stoppers.push(() => {
828
+ void w.close();
829
+ });
830
+ }
831
+ return {
832
+ stop: () => {
833
+ for (const s of stoppers) s();
834
+ },
835
+ status
836
+ };
837
+ }
838
+ function swallow2(err) {
839
+ if (typeof err !== "object" || err === null) return;
840
+ const code = err.code;
841
+ if (code === "EMFILE" || code === "ENOSPC" || code === "EACCES") return;
842
+ console.error("[agentwatch/cursor]", String(err));
843
+ }
844
+ function readMcpServers(path) {
845
+ try {
846
+ const obj = JSON.parse(readFileSync(path, "utf8"));
847
+ return Object.keys(obj.mcpServers ?? {});
848
+ } catch {
849
+ return [];
850
+ }
851
+ }
852
+ function readPermissions(path) {
853
+ try {
854
+ const obj = JSON.parse(readFileSync(path, "utf8"));
855
+ const perms = obj.permissions ?? {};
856
+ const sandbox = obj.sandbox?.mode;
857
+ return {
858
+ approvalMode: String(obj.approvalMode ?? "unknown"),
859
+ sandboxMode: String(sandbox ?? "unknown"),
860
+ allowCount: Array.isArray(perms.allow) ? perms.allow.length : 0,
861
+ denyCount: Array.isArray(perms.deny) ? perms.deny.length : 0
862
+ };
863
+ } catch {
864
+ return void 0;
865
+ }
866
+ }
867
+ function readRecentFiles(stateFile) {
868
+ try {
869
+ const obj = JSON.parse(readFileSync(stateFile, "utf8"));
870
+ const list = obj.recentlyViewedFiles;
871
+ if (!Array.isArray(list)) return [];
872
+ return list.map((r) => {
873
+ if (typeof r !== "object" || r === null) return void 0;
874
+ const rec = r;
875
+ if (typeof rec.absolutePath === "string") return rec.absolutePath;
876
+ if (typeof rec.relativePath === "string") return rec.relativePath;
877
+ return void 0;
878
+ }).filter((x) => typeof x === "string").slice(0, 20);
879
+ } catch {
880
+ return [];
881
+ }
882
+ }
883
+ function discoverCursorrules(workspace) {
884
+ const hits = [];
885
+ if (!existsSync3(workspace)) return hits;
886
+ const rootRules = join2(workspace, ".cursorrules");
887
+ if (existsSync3(rootRules)) hits.push(rootRules);
888
+ let entries = [];
889
+ try {
890
+ entries = readdirSync(workspace);
891
+ } catch {
892
+ return hits;
893
+ }
894
+ for (const name of entries) {
895
+ if (name.startsWith(".")) continue;
896
+ if (name === "node_modules") continue;
897
+ const dir = join2(workspace, name);
898
+ try {
899
+ if (!statSync3(dir).isDirectory()) continue;
900
+ } catch {
901
+ continue;
902
+ }
903
+ const candidate = join2(dir, ".cursorrules");
904
+ if (existsSync3(candidate)) hits.push(candidate);
905
+ }
906
+ return hits;
907
+ }
908
+ function extractProject2(path) {
909
+ const segs = path.split("/").filter(Boolean);
910
+ const ideaIdx = segs.indexOf("IdeaProjects");
911
+ if (ideaIdx >= 0 && segs[ideaIdx + 1]) return segs[ideaIdx + 1];
912
+ return segs[segs.length - 2] ?? "";
913
+ }
914
+
915
+ // src/adapters/fs-watcher.ts
916
+ import chokidar4 from "chokidar";
917
+ var DEFAULT_IGNORES = [
918
+ /(^|[/\\])node_modules([/\\]|$)/,
919
+ /(^|[/\\])\.git([/\\]|$)/,
920
+ /(^|[/\\])dist([/\\]|$)/,
921
+ /(^|[/\\])build([/\\]|$)/,
922
+ /(^|[/\\])\.next([/\\]|$)/,
923
+ /(^|[/\\])\.cache([/\\]|$)/,
924
+ /(^|[/\\])\.turbo([/\\]|$)/,
925
+ /(^|[/\\])target([/\\]|$)/,
926
+ /(^|[/\\])coverage([/\\]|$)/,
927
+ /(^|[/\\])\.venv([/\\]|$)/,
928
+ /(^|[/\\])venv([/\\]|$)/,
929
+ /(^|[/\\])__pycache__([/\\]|$)/,
930
+ /(^|[/\\])\.pytest_cache([/\\]|$)/,
931
+ /(^|[/\\])\.idea([/\\]|$)/,
932
+ /(^|[/\\])\.vscode([/\\]|$)/,
933
+ /(^|[/\\])\.DS_Store$/,
934
+ /\.log$/,
935
+ /\.lock$/,
936
+ /package-lock\.json$/,
937
+ /pnpm-lock\.yaml$/,
938
+ /yarn\.lock$/,
939
+ /bun\.lockb$/
940
+ ];
941
+ function startFsAdapter(root, emit) {
942
+ const watcher = chokidar4.watch(root, {
943
+ persistent: true,
944
+ ignoreInitial: true,
945
+ ignored: (p) => DEFAULT_IGNORES.some((r) => r.test(p)),
946
+ depth: 3
947
+ });
948
+ watcher.on("error", (err) => {
949
+ if (isSuppressible(err)) return;
950
+ console.error("[agentwatch/fs]", String(err));
951
+ });
952
+ const emitFs = (path) => {
953
+ const event = {
954
+ id: nextId(),
955
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
956
+ agent: "unknown",
957
+ type: "file_change",
958
+ path,
959
+ summary: path,
960
+ riskScore: riskOf("file_change", path)
961
+ };
962
+ emit(event);
963
+ };
964
+ watcher.on("add", emitFs);
965
+ watcher.on("change", emitFs);
966
+ return () => {
967
+ void watcher.close();
968
+ };
969
+ }
970
+ function isSuppressible(err) {
971
+ if (typeof err !== "object" || err === null) return false;
972
+ const code = err.code;
973
+ return code === "EMFILE" || code === "ENOSPC" || code === "EACCES";
974
+ }
975
+
976
+ // src/util/claude-permissions.ts
977
+ import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
978
+ import { homedir as homedir3 } from "os";
979
+ import { join as join3 } from "path";
980
+ function readClaudePermissions(workspace) {
981
+ const sources = [join3(homedir3(), ".claude", "settings.json")];
982
+ if (workspace) {
983
+ sources.push(join3(workspace, ".claude", "settings.json"));
984
+ sources.push(join3(workspace, ".claude", "settings.local.json"));
985
+ }
986
+ const out = [];
987
+ for (const path of sources) {
988
+ const parsed = readOne(path);
989
+ if (parsed) out.push(parsed);
990
+ }
991
+ return out;
992
+ }
993
+ function readOne(path) {
994
+ if (!existsSync4(path)) return null;
995
+ try {
996
+ const raw = readFileSync2(path, "utf8");
997
+ const obj = JSON.parse(raw);
998
+ const perms = obj.permissions ?? {};
999
+ const allow = toStringArray(perms.allow);
1000
+ const deny = toStringArray(perms.deny);
1001
+ const defaultMode = typeof perms.defaultMode === "string" ? perms.defaultMode : "default";
1002
+ const additionalDirectories = toStringArray(perms.additionalDirectories);
1003
+ return {
1004
+ source: path,
1005
+ allow,
1006
+ deny,
1007
+ defaultMode,
1008
+ additionalDirectories,
1009
+ flags: assessRisk({ allow, deny, defaultMode })
1010
+ };
1011
+ } catch {
1012
+ return null;
1013
+ }
1014
+ }
1015
+ function toStringArray(v) {
1016
+ if (!Array.isArray(v)) return [];
1017
+ return v.filter((x) => typeof x === "string");
1018
+ }
1019
+ function assessRisk({
1020
+ allow,
1021
+ deny,
1022
+ defaultMode
1023
+ }) {
1024
+ const flags = [];
1025
+ if (allow.some((a) => /^Bash\(\*\)$/i.test(a))) {
1026
+ flags.push({
1027
+ level: "risk",
1028
+ message: "Bash(*) allows arbitrary shell \u2014 any command not explicitly denied will run"
1029
+ });
1030
+ }
1031
+ if (allow.some((a) => /^Write$/i.test(a) || /^Edit$/i.test(a))) {
1032
+ if (!deny.some((d) => /(^|[\s(])~\/\.ssh|\.aws|\.gnupg/.test(d))) {
1033
+ flags.push({
1034
+ level: "warn",
1035
+ message: "Write/Edit allowed with no deny rule for ~/.ssh, ~/.aws, ~/.gnupg"
1036
+ });
1037
+ }
1038
+ }
1039
+ if (deny.length === 0) {
1040
+ flags.push({
1041
+ level: "warn",
1042
+ message: "deny list is empty \u2014 no guardrails against destructive commands"
1043
+ });
1044
+ }
1045
+ if (defaultMode === "auto" || defaultMode === "bypassPermissions") {
1046
+ flags.push({
1047
+ level: "warn",
1048
+ message: `defaultMode=${defaultMode} \u2014 anything not in allow/deny runs without prompting`
1049
+ });
1050
+ }
1051
+ return flags;
1052
+ }
1053
+
1054
+ // src/ui/App.tsx
1055
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1056
+ var MAX_EVENTS = 500;
1057
+ function findInsertIdx(events, ts) {
1058
+ let lo = 0;
1059
+ let hi = events.length;
1060
+ while (lo < hi) {
1061
+ const mid = lo + hi >>> 1;
1062
+ if (events[mid].ts > ts) lo = mid + 1;
1063
+ else hi = mid;
1064
+ }
1065
+ return lo;
1066
+ }
1067
+ function reducer(state, action) {
1068
+ switch (action.type) {
1069
+ case "event": {
1070
+ if (state.paused) return state;
1071
+ const next = state.events.slice();
1072
+ const idx = findInsertIdx(next, action.event.ts);
1073
+ next.splice(idx, 0, action.event);
1074
+ if (next.length > MAX_EVENTS) next.length = MAX_EVENTS;
1075
+ return { ...state, events: next };
1076
+ }
1077
+ case "toggle-agents":
1078
+ return { ...state, showAgents: !state.showAgents };
1079
+ case "toggle-permissions":
1080
+ return { ...state, showPermissions: !state.showPermissions };
1081
+ case "cycle-filter": {
1082
+ const idx = state.filterAgent ? action.agents.indexOf(state.filterAgent) : -1;
1083
+ const next = idx + 1 >= action.agents.length ? null : action.agents[idx + 1];
1084
+ return { ...state, filterAgent: next ?? null };
1085
+ }
1086
+ case "toggle-pause":
1087
+ return { ...state, paused: !state.paused };
1088
+ case "clear":
1089
+ return { ...state, events: [] };
1090
+ }
1091
+ }
1092
+ function App() {
1093
+ const { exit } = useApp();
1094
+ const [workspace] = useState(detectWorkspaceRoot());
1095
+ const [agents] = useState(detectAgents());
1096
+ const [permissions] = useState(() => readClaudePermissions(workspace));
1097
+ const [state, dispatch] = useReducer(reducer, {
1098
+ events: [],
1099
+ filterAgent: null,
1100
+ showAgents: true,
1101
+ showPermissions: false,
1102
+ paused: false
1103
+ });
1104
+ useEffect(() => {
1105
+ const onEvent = (e) => dispatch({ type: "event", event: e });
1106
+ const stopClaude = startClaudeAdapter(onEvent);
1107
+ const stopOpenClaw = startOpenClawAdapter(onEvent);
1108
+ const cursor = startCursorAdapter(workspace, onEvent);
1109
+ const stopFs = startFsAdapter(workspace, onEvent);
1110
+ return () => {
1111
+ stopClaude();
1112
+ stopOpenClaw();
1113
+ cursor.stop();
1114
+ stopFs();
1115
+ };
1116
+ }, [workspace]);
1117
+ useInput((input, key) => {
1118
+ if (input === "q" || key.ctrl && input === "c") {
1119
+ exit();
1120
+ setImmediate(() => process.exit(0));
1121
+ return;
1122
+ }
1123
+ if (input === "a") dispatch({ type: "toggle-agents" });
1124
+ if (input === "f") {
1125
+ const presentAgents = agents.filter((a) => a.present).map((a) => a.name);
1126
+ const pool = presentAgents.length ? presentAgents : ["claude-code", "unknown"];
1127
+ dispatch({ type: "cycle-filter", agents: pool });
1128
+ }
1129
+ if (input === " ") dispatch({ type: "toggle-pause" });
1130
+ if (input === "p") dispatch({ type: "toggle-permissions" });
1131
+ if (input === "c") dispatch({ type: "clear" });
1132
+ });
1133
+ const filtered = state.filterAgent ? state.events.filter((e) => e.agent === state.filterAgent) : state.events;
1134
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
1135
+ /* @__PURE__ */ jsx5(
1136
+ Header,
1137
+ {
1138
+ workspace,
1139
+ eventCount: state.events.length,
1140
+ filter: state.filterAgent,
1141
+ paused: state.paused
1142
+ }
1143
+ ),
1144
+ state.showPermissions ? /* @__PURE__ */ jsx5(PermissionView, { permissions }) : /* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", children: [
1145
+ /* @__PURE__ */ jsx5(Box5, { flexGrow: 1, flexDirection: "column", children: /* @__PURE__ */ jsx5(Timeline, { events: filtered }) }),
1146
+ state.showAgents && /* @__PURE__ */ jsx5(Box5, { width: 32, marginLeft: 1, children: /* @__PURE__ */ jsx5(AgentPanel, { agents, events: state.events }) })
1147
+ ] }),
1148
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
1149
+ "[q] quit [a] agents [f] filter [p] permissions [space] ",
1150
+ state.paused ? "resume" : "pause",
1151
+ " [c] clear"
1152
+ ] }) })
1153
+ ] });
1154
+ }
1155
+
1156
+ // src/index.tsx
1157
+ import { jsx as jsx6 } from "react/jsx-runtime";
1158
+ var arg = process.argv[2];
1159
+ var ENTER_ALT_SCREEN = "\x1B[?1049h\x1B[2J\x1B[H";
1160
+ var LEAVE_ALT_SCREEN = "\x1B[?1049l";
1161
+ function enterAltScreen() {
1162
+ if (process.stdout.isTTY) process.stdout.write(ENTER_ALT_SCREEN);
1163
+ }
1164
+ function leaveAltScreen() {
1165
+ if (process.stdout.isTTY) process.stdout.write(LEAVE_ALT_SCREEN);
1166
+ }
1167
+ if (arg === "--help" || arg === "-h") {
1168
+ console.log(`agentwatch \u2014 local observability for AI coding agents
1169
+
1170
+ Usage:
1171
+ agentwatch launch the TUI
1172
+ agentwatch doctor detect installed agents and print readiness
1173
+ agentwatch --help show this help
1174
+
1175
+ Hotkeys inside the TUI:
1176
+ q quit
1177
+ a toggle agent panel
1178
+ f cycle agent filter
1179
+ p pause / resume event stream
1180
+ c clear events
1181
+
1182
+ Environment:
1183
+ WORKSPACE_ROOT override the detected workspace root
1184
+ `);
1185
+ process.exit(0);
1186
+ }
1187
+ if (arg === "doctor") {
1188
+ const { detectAgents: detectAgents2 } = await import("./detect-57ZQXQNN.js");
1189
+ const { detectWorkspaceRoot: detectWorkspaceRoot2 } = await import("./workspace-N6FQVHKD.js");
1190
+ const agents = detectAgents2();
1191
+ console.log(`workspace: ${detectWorkspaceRoot2()}
1192
+ `);
1193
+ console.log("agents:");
1194
+ for (const a of agents) {
1195
+ const mark = a.present ? "\u25CF" : "\u25CB";
1196
+ const status = a.present ? "installed" : "not detected";
1197
+ console.log(` ${mark} ${a.label.padEnd(14)} ${status}`);
1198
+ if (a.configPath) console.log(` config: ${a.configPath}`);
1199
+ }
1200
+ process.exit(0);
1201
+ }
1202
+ enterAltScreen();
1203
+ for (const sig of ["exit", "SIGINT", "SIGTERM", "SIGHUP"]) {
1204
+ process.on(sig, () => {
1205
+ leaveAltScreen();
1206
+ if (sig !== "exit") process.exit(0);
1207
+ });
1208
+ }
1209
+ var { waitUntilExit } = render(/* @__PURE__ */ jsx6(App, {}));
1210
+ waitUntilExit().finally(() => leaveAltScreen());