@misha_misha/agentwatch 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +508 -46
- package/dist/chunk-5QNDC2VP.js +98 -0
- package/dist/{detect-57ZQXQNN.js → detect-JH6COHZ5.js} +1 -1
- package/dist/index.js +1979 -152
- package/package.json +1 -1
- package/dist/chunk-LF76VUCL.js +0 -45
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
detectAgents
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-5QNDC2VP.js";
|
|
5
5
|
import {
|
|
6
6
|
claudeProjectsDir,
|
|
7
7
|
detectWorkspaceRoot
|
|
@@ -12,12 +12,16 @@ import { render } from "ink";
|
|
|
12
12
|
|
|
13
13
|
// src/ui/App.tsx
|
|
14
14
|
import { useEffect, useReducer, useState } from "react";
|
|
15
|
-
import { Box as
|
|
15
|
+
import { Box as Box10, Text as Text10, useApp, useInput, useStdout } from "ink";
|
|
16
16
|
|
|
17
17
|
// src/ui/Timeline.tsx
|
|
18
18
|
import { Box, Text } from "ink";
|
|
19
19
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
20
|
-
function Timeline({
|
|
20
|
+
function Timeline({
|
|
21
|
+
events,
|
|
22
|
+
selectedIdx,
|
|
23
|
+
childCountByAgentId
|
|
24
|
+
}) {
|
|
21
25
|
const header = /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { bold: true, dimColor: true, children: [
|
|
22
26
|
"TIME ",
|
|
23
27
|
pad("AGENT", 10),
|
|
@@ -32,16 +36,32 @@ function Timeline({ events }) {
|
|
|
32
36
|
/* @__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
37
|
] });
|
|
34
38
|
}
|
|
35
|
-
const
|
|
39
|
+
const windowStart = selectedIdx != null && selectedIdx > 30 ? Math.max(0, selectedIdx - 15) : 0;
|
|
40
|
+
const visible = events.slice(windowStart, windowStart + 40);
|
|
36
41
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
37
42
|
header,
|
|
38
|
-
visible.map((e) => /* @__PURE__ */ jsx(
|
|
43
|
+
visible.map((e, i) => /* @__PURE__ */ jsx(
|
|
44
|
+
EventRow,
|
|
45
|
+
{
|
|
46
|
+
event: e,
|
|
47
|
+
selected: windowStart + i === selectedIdx,
|
|
48
|
+
childCount: e.details?.subAgentId ? childCountByAgentId?.get(e.details.subAgentId) ?? 0 : 0
|
|
49
|
+
},
|
|
50
|
+
e.id
|
|
51
|
+
))
|
|
39
52
|
] });
|
|
40
53
|
}
|
|
41
|
-
function EventRow({
|
|
54
|
+
function EventRow({
|
|
55
|
+
event,
|
|
56
|
+
selected,
|
|
57
|
+
childCount
|
|
58
|
+
}) {
|
|
42
59
|
const time = event.ts.slice(11, 19);
|
|
43
|
-
const
|
|
44
|
-
|
|
60
|
+
const baseLine = event.summary ?? event.path ?? event.cmd ?? event.tool ?? event.type;
|
|
61
|
+
const duration = event.details?.durationMs != null ? ` \xB7 ${formatMs(event.details.durationMs)}` : "";
|
|
62
|
+
const err = event.details?.toolError ? " \xB7 ERR" : "";
|
|
63
|
+
const marker = childCount > 0 ? ` \u25B8 ${childCount} child events` : "";
|
|
64
|
+
return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { wrap: "truncate", inverse: selected, children: [
|
|
45
65
|
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
46
66
|
time,
|
|
47
67
|
" "
|
|
@@ -54,9 +74,17 @@ function EventRow({ event }) {
|
|
|
54
74
|
pad(event.type, 13),
|
|
55
75
|
" "
|
|
56
76
|
] }),
|
|
57
|
-
/* @__PURE__ */ jsx(Text, { children:
|
|
77
|
+
/* @__PURE__ */ jsx(Text, { children: baseLine }),
|
|
78
|
+
duration && /* @__PURE__ */ jsx(Text, { dimColor: true, children: duration }),
|
|
79
|
+
err && /* @__PURE__ */ jsx(Text, { color: "red", children: err }),
|
|
80
|
+
childCount > 0 && /* @__PURE__ */ jsx(Text, { color: "yellow", children: marker })
|
|
58
81
|
] }) });
|
|
59
82
|
}
|
|
83
|
+
function formatMs(ms) {
|
|
84
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
85
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
86
|
+
return `${Math.floor(ms / 6e4)}m${Math.floor(ms % 6e4 / 1e3)}s`;
|
|
87
|
+
}
|
|
60
88
|
function agentColor(a) {
|
|
61
89
|
switch (a) {
|
|
62
90
|
case "claude-code":
|
|
@@ -91,19 +119,22 @@ function AgentPanel({ agents, events }) {
|
|
|
91
119
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", borderStyle: "single", paddingX: 1, children: [
|
|
92
120
|
/* @__PURE__ */ jsx2(Text2, { bold: true, children: "Agents" }),
|
|
93
121
|
agents.map((a) => {
|
|
94
|
-
const
|
|
95
|
-
const
|
|
122
|
+
const forAgent = events.filter((e) => e.agent === a.name);
|
|
123
|
+
const count = forAgent.length;
|
|
124
|
+
const last = forAgent[0];
|
|
125
|
+
const dotColor = !a.present ? "gray" : a.instrumented ? "green" : "yellow";
|
|
126
|
+
const statusLabel = !a.present ? "not detected" : a.instrumented ? "installed" : "detected (events TBD)";
|
|
96
127
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: 1, children: [
|
|
97
|
-
/* @__PURE__ */ jsxs2(Text2, { color:
|
|
128
|
+
/* @__PURE__ */ jsxs2(Text2, { color: dotColor, children: [
|
|
98
129
|
a.present ? "\u25CF" : "\u25CB",
|
|
99
130
|
" ",
|
|
100
131
|
a.label
|
|
101
132
|
] }),
|
|
102
133
|
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
103
134
|
" ",
|
|
104
|
-
|
|
135
|
+
statusLabel
|
|
105
136
|
] }),
|
|
106
|
-
a.present && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
137
|
+
a.present && a.instrumented && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
107
138
|
" ",
|
|
108
139
|
"events: ",
|
|
109
140
|
count,
|
|
@@ -143,78 +174,978 @@ function Header({ workspace, eventCount, filter, paused }) {
|
|
|
143
174
|
// src/ui/PermissionView.tsx
|
|
144
175
|
import { Box as Box4, Text as Text4 } from "ink";
|
|
145
176
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
146
|
-
function PermissionView({
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
177
|
+
function PermissionView({
|
|
178
|
+
claude,
|
|
179
|
+
cursor,
|
|
180
|
+
openclaw,
|
|
181
|
+
viewportRows,
|
|
182
|
+
scrollOffset
|
|
183
|
+
}) {
|
|
184
|
+
const rows = buildRows(claude, cursor, openclaw);
|
|
185
|
+
const height = Math.max(3, viewportRows);
|
|
186
|
+
const maxScroll = Math.max(0, rows.length - height);
|
|
187
|
+
const offset = Math.min(scrollOffset, maxScroll);
|
|
188
|
+
const visible = rows.slice(offset, offset + height);
|
|
154
189
|
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", borderStyle: "double", paddingX: 1, children: [
|
|
155
|
-
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "Permissions
|
|
156
|
-
|
|
157
|
-
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */
|
|
190
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "Permissions / Configuration across installed agents" }),
|
|
191
|
+
/* @__PURE__ */ jsx4(Box4, { flexDirection: "column", marginTop: 1, children: visible.map((row, i) => /* @__PURE__ */ jsx4(RowView, { row }, i)) }),
|
|
192
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
193
|
+
rows.length > height ? `${offset + 1}\u2013${offset + visible.length} of ${rows.length} [\u2191\u2193] scroll ` : "",
|
|
194
|
+
"[p] close [q] quit"
|
|
195
|
+
] }) })
|
|
158
196
|
] });
|
|
159
197
|
}
|
|
160
|
-
function
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
/* @__PURE__ */
|
|
164
|
-
|
|
198
|
+
function RowView({ row }) {
|
|
199
|
+
switch (row.kind) {
|
|
200
|
+
case "h1":
|
|
201
|
+
return /* @__PURE__ */ jsxs4(Text4, { bold: true, color: row.color ?? "cyan", children: [
|
|
202
|
+
"\u2501 ",
|
|
203
|
+
row.text,
|
|
204
|
+
" \u2501"
|
|
205
|
+
] });
|
|
206
|
+
case "h2":
|
|
207
|
+
return /* @__PURE__ */ jsx4(Text4, { bold: true, color: row.color ?? "white", children: row.text });
|
|
208
|
+
case "kv":
|
|
209
|
+
return /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
210
|
+
/* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
211
|
+
row.label,
|
|
212
|
+
": "
|
|
213
|
+
] }),
|
|
214
|
+
/* @__PURE__ */ jsx4(Text4, { color: row.valueColor, children: row.value })
|
|
215
|
+
] });
|
|
216
|
+
case "item":
|
|
217
|
+
return /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
218
|
+
" ",
|
|
219
|
+
/* @__PURE__ */ jsx4(Text4, { color: row.markColor, children: row.mark }),
|
|
220
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
221
|
+
" ",
|
|
222
|
+
row.text
|
|
223
|
+
] })
|
|
224
|
+
] });
|
|
225
|
+
case "text":
|
|
226
|
+
return /* @__PURE__ */ jsx4(Text4, { color: row.color, dimColor: row.dim, children: row.text || " " });
|
|
227
|
+
case "blank":
|
|
228
|
+
return /* @__PURE__ */ jsx4(Text4, { children: " " });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function buildRows(claude, cursor, openclaw) {
|
|
232
|
+
const rows = [];
|
|
233
|
+
rows.push({ kind: "h1", text: "Claude Code", color: "cyan" });
|
|
234
|
+
if (claude.length === 0) {
|
|
235
|
+
rows.push({ kind: "text", text: " No settings.json found.", dim: true });
|
|
236
|
+
} else {
|
|
237
|
+
for (const perms of claude) {
|
|
238
|
+
rows.push({ kind: "blank" });
|
|
239
|
+
rows.push({ kind: "kv", label: "source", value: perms.source });
|
|
240
|
+
rows.push({
|
|
241
|
+
kind: "kv",
|
|
242
|
+
label: "defaultMode",
|
|
243
|
+
value: perms.defaultMode,
|
|
244
|
+
valueColor: modeColor(perms.defaultMode)
|
|
245
|
+
});
|
|
246
|
+
rows.push({ kind: "blank" });
|
|
247
|
+
rows.push({
|
|
248
|
+
kind: "h2",
|
|
249
|
+
text: `CAN (${perms.allow.length})`,
|
|
250
|
+
color: "green"
|
|
251
|
+
});
|
|
252
|
+
if (perms.allow.length === 0) {
|
|
253
|
+
rows.push({
|
|
254
|
+
kind: "text",
|
|
255
|
+
text: " (none \u2014 defaultMode applies)",
|
|
256
|
+
dim: true
|
|
257
|
+
});
|
|
258
|
+
} else {
|
|
259
|
+
for (const a of perms.allow)
|
|
260
|
+
rows.push({ kind: "item", mark: "\u2713", markColor: "green", text: a });
|
|
261
|
+
}
|
|
262
|
+
rows.push({ kind: "blank" });
|
|
263
|
+
rows.push({
|
|
264
|
+
kind: "h2",
|
|
265
|
+
text: `CANNOT (${perms.deny.length})`,
|
|
266
|
+
color: "red"
|
|
267
|
+
});
|
|
268
|
+
if (perms.deny.length === 0) {
|
|
269
|
+
rows.push({
|
|
270
|
+
kind: "text",
|
|
271
|
+
text: " (none \u2014 no explicit denies)",
|
|
272
|
+
dim: true
|
|
273
|
+
});
|
|
274
|
+
} else {
|
|
275
|
+
for (const d of perms.deny)
|
|
276
|
+
rows.push({ kind: "item", mark: "\u2717", markColor: "red", text: d });
|
|
277
|
+
}
|
|
278
|
+
if (perms.flags.length > 0) {
|
|
279
|
+
rows.push({ kind: "blank" });
|
|
280
|
+
rows.push({ kind: "h2", text: "\u26A0 Flags", color: "yellow" });
|
|
281
|
+
for (const f of perms.flags) {
|
|
282
|
+
rows.push({
|
|
283
|
+
kind: "item",
|
|
284
|
+
mark: f.level === "risk" ? "\u2717" : "!",
|
|
285
|
+
markColor: f.level === "risk" ? "red" : "yellow",
|
|
286
|
+
text: f.message
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
rows.push({ kind: "blank" });
|
|
293
|
+
rows.push({ kind: "h1", text: "Cursor", color: "magenta" });
|
|
294
|
+
if (!cursor?.installed) {
|
|
295
|
+
rows.push({ kind: "text", text: " not detected", dim: true });
|
|
296
|
+
} else {
|
|
297
|
+
rows.push({ kind: "blank" });
|
|
298
|
+
if (cursor.permissions) {
|
|
299
|
+
rows.push({
|
|
300
|
+
kind: "kv",
|
|
301
|
+
label: "approvalMode",
|
|
302
|
+
value: cursor.permissions.approvalMode,
|
|
303
|
+
valueColor: modeColor(cursor.permissions.approvalMode)
|
|
304
|
+
});
|
|
305
|
+
rows.push({
|
|
306
|
+
kind: "kv",
|
|
307
|
+
label: "sandbox",
|
|
308
|
+
value: cursor.permissions.sandboxMode,
|
|
309
|
+
valueColor: cursor.permissions.sandboxMode === "disabled" ? "red" : "green"
|
|
310
|
+
});
|
|
311
|
+
rows.push({
|
|
312
|
+
kind: "text",
|
|
313
|
+
text: ` allow: ${cursor.permissions.allowCount} deny: ${cursor.permissions.denyCount}`
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
rows.push({
|
|
317
|
+
kind: "kv",
|
|
318
|
+
label: "MCP servers",
|
|
319
|
+
value: cursor.mcpServers.length === 0 ? "none" : `${cursor.mcpServers.length} (${cursor.mcpServers.join(", ")})`
|
|
320
|
+
});
|
|
321
|
+
rows.push({
|
|
322
|
+
kind: "kv",
|
|
323
|
+
label: ".cursorrules discovered",
|
|
324
|
+
value: String(cursor.cursorRulesFiles.length)
|
|
325
|
+
});
|
|
326
|
+
for (const f of cursor.cursorRulesFiles.slice(0, 10))
|
|
327
|
+
rows.push({ kind: "text", text: ` \u2022 ${f}`, dim: true });
|
|
328
|
+
}
|
|
329
|
+
rows.push({ kind: "blank" });
|
|
330
|
+
rows.push({ kind: "h1", text: "OpenClaw", color: "yellow" });
|
|
331
|
+
if (!openclaw) {
|
|
332
|
+
rows.push({ kind: "text", text: " not detected", dim: true });
|
|
333
|
+
} else {
|
|
334
|
+
rows.push({ kind: "blank" });
|
|
335
|
+
rows.push({ kind: "kv", label: "source", value: openclaw.source });
|
|
336
|
+
if (openclaw.defaultWorkspace) {
|
|
337
|
+
rows.push({
|
|
338
|
+
kind: "kv",
|
|
339
|
+
label: "default workspace",
|
|
340
|
+
value: openclaw.defaultWorkspace
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
rows.push({
|
|
344
|
+
kind: "text",
|
|
345
|
+
text: " OpenClaw runs with broad shell + file access per agent. No allow/deny list \u2014 scope is the workspace path.",
|
|
346
|
+
dim: true
|
|
347
|
+
});
|
|
348
|
+
rows.push({ kind: "blank" });
|
|
349
|
+
rows.push({
|
|
350
|
+
kind: "h2",
|
|
351
|
+
text: `Sub-agents (${openclaw.agents.length})`
|
|
352
|
+
});
|
|
353
|
+
if (openclaw.agents.length === 0) {
|
|
354
|
+
rows.push({ kind: "text", text: " (none configured)", dim: true });
|
|
355
|
+
} else {
|
|
356
|
+
for (const a of openclaw.agents) {
|
|
357
|
+
rows.push({ kind: "blank" });
|
|
358
|
+
rows.push({
|
|
359
|
+
kind: "text",
|
|
360
|
+
text: `${a.emoji ?? "\u2022"} ${a.name ?? a.id} (id: ${a.id}${a.default ? ", default" : ""})`
|
|
361
|
+
});
|
|
362
|
+
if (a.model) rows.push({ kind: "text", text: ` model: ${a.model}`, dim: true });
|
|
363
|
+
if (a.workspace)
|
|
364
|
+
rows.push({ kind: "text", text: ` workspace: ${a.workspace}`, dim: true });
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
rows.push({ kind: "blank" });
|
|
369
|
+
rows.push({
|
|
370
|
+
kind: "text",
|
|
371
|
+
text: "Gemini CLI exposes no permission model beyond auth, so it is omitted.",
|
|
372
|
+
dim: true
|
|
373
|
+
});
|
|
374
|
+
return rows;
|
|
375
|
+
}
|
|
376
|
+
function modeColor(mode) {
|
|
377
|
+
if (mode === "auto" || mode === "bypassPermissions") return "red";
|
|
378
|
+
if (mode === "ask" || mode === "allowlist") return "green";
|
|
379
|
+
return "yellow";
|
|
380
|
+
}
|
|
381
|
+
function permissionRowCount(claude, cursor, openclaw) {
|
|
382
|
+
return buildRows(claude, cursor, openclaw).length;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// src/ui/EventDetail.tsx
|
|
386
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
387
|
+
|
|
388
|
+
// src/util/cost.ts
|
|
389
|
+
var RATES = {
|
|
390
|
+
"claude-opus-4-6": {
|
|
391
|
+
input: 15,
|
|
392
|
+
cacheCreate: 18.75,
|
|
393
|
+
cacheRead: 1.5,
|
|
394
|
+
output: 75
|
|
395
|
+
},
|
|
396
|
+
"claude-sonnet-4-6": {
|
|
397
|
+
input: 3,
|
|
398
|
+
cacheCreate: 3.75,
|
|
399
|
+
cacheRead: 0.3,
|
|
400
|
+
output: 15
|
|
401
|
+
},
|
|
402
|
+
"claude-haiku-4-5": {
|
|
403
|
+
input: 1,
|
|
404
|
+
cacheCreate: 1.25,
|
|
405
|
+
cacheRead: 0.1,
|
|
406
|
+
output: 5
|
|
407
|
+
},
|
|
408
|
+
// Fallback for unknown / synthetic models
|
|
409
|
+
default: {
|
|
410
|
+
input: 3,
|
|
411
|
+
cacheCreate: 3.75,
|
|
412
|
+
cacheRead: 0.3,
|
|
413
|
+
output: 15
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
function costOf(model, u) {
|
|
417
|
+
const rate = RATES[normalizeModel(model)] ?? RATES.default;
|
|
418
|
+
return (u.input * rate.input + u.cacheCreate * rate.cacheCreate + u.cacheRead * rate.cacheRead + u.output * rate.output) / 1e6;
|
|
419
|
+
}
|
|
420
|
+
function formatUSD(n) {
|
|
421
|
+
if (n === 0) return "$0";
|
|
422
|
+
if (n < 0.01) return `$${n.toFixed(4)}`;
|
|
423
|
+
if (n < 1) return `$${n.toFixed(3)}`;
|
|
424
|
+
return `$${n.toFixed(2)}`;
|
|
425
|
+
}
|
|
426
|
+
function normalizeModel(model) {
|
|
427
|
+
return model.replace(/\[.*?\]$/, "").toLowerCase();
|
|
428
|
+
}
|
|
429
|
+
function parseUsage(obj) {
|
|
430
|
+
if (!obj || typeof obj !== "object") return null;
|
|
431
|
+
const o = obj;
|
|
432
|
+
const input = typeof o.input_tokens === "number" ? o.input_tokens : 0;
|
|
433
|
+
const cacheCreate = typeof o.cache_creation_input_tokens === "number" ? o.cache_creation_input_tokens : 0;
|
|
434
|
+
const cacheRead = typeof o.cache_read_input_tokens === "number" ? o.cache_read_input_tokens : 0;
|
|
435
|
+
const output = typeof o.output_tokens === "number" ? o.output_tokens : 0;
|
|
436
|
+
if (input + cacheCreate + cacheRead + output === 0) return null;
|
|
437
|
+
return { input, cacheCreate, cacheRead, output };
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// src/ui/EventDetail.tsx
|
|
441
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
442
|
+
function EventDetail({ event, width, height, scrollOffset }) {
|
|
443
|
+
const rows = buildRows2(event, width);
|
|
444
|
+
const visible = rows.slice(scrollOffset, scrollOffset + height - 4);
|
|
445
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", borderStyle: "double", paddingX: 1, children: [
|
|
446
|
+
/* @__PURE__ */ jsxs5(Text5, { bold: true, color: colorFor(event), children: [
|
|
447
|
+
event.ts.slice(11, 19),
|
|
448
|
+
" \u2014 ",
|
|
449
|
+
event.agent,
|
|
450
|
+
" \u2014 ",
|
|
451
|
+
event.type,
|
|
452
|
+
event.tool ? ` (${event.tool})` : ""
|
|
453
|
+
] }),
|
|
454
|
+
event.path && /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
455
|
+
"path: ",
|
|
456
|
+
event.path
|
|
165
457
|
] }),
|
|
166
|
-
/* @__PURE__ */
|
|
167
|
-
|
|
168
|
-
|
|
458
|
+
event.cmd && /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
459
|
+
"cmd: ",
|
|
460
|
+
truncateLine(event.cmd, width - 6)
|
|
461
|
+
] }),
|
|
462
|
+
/* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", children: visible.length === 0 ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "(no additional content captured for this event)" }) : visible.map((r, i) => /* @__PURE__ */ jsx5(Row, { row: r }, i)) }),
|
|
463
|
+
/* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
464
|
+
rows.length > height - 4 ? `${scrollOffset + 1}\u2013${Math.min(scrollOffset + height - 4, rows.length)} of ${rows.length} \u2191\u2193 scroll ` : "",
|
|
465
|
+
"[esc] close"
|
|
466
|
+
] }) })
|
|
467
|
+
] });
|
|
468
|
+
}
|
|
469
|
+
function buildRows2(event, width) {
|
|
470
|
+
const d = event.details;
|
|
471
|
+
const rows = [];
|
|
472
|
+
const max = Math.max(40, width - 4);
|
|
473
|
+
if (d?.usage || d?.cost != null || d?.durationMs != null) {
|
|
474
|
+
rows.push({ kind: "heading", text: "tokens / cost / duration" });
|
|
475
|
+
const u = d?.usage;
|
|
476
|
+
if (u) {
|
|
477
|
+
rows.push({
|
|
478
|
+
kind: "text",
|
|
479
|
+
text: `in=${u.input} cache_create=${u.cacheCreate} cache_read=${u.cacheRead} out=${u.output}`,
|
|
480
|
+
dim: true
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
if (d?.cost != null) {
|
|
484
|
+
rows.push({
|
|
485
|
+
kind: "text",
|
|
486
|
+
text: `cost: ${formatUSD(d.cost)}${d.model ? ` (${d.model})` : ""}`,
|
|
487
|
+
dim: true
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
if (d?.durationMs != null) {
|
|
491
|
+
rows.push({
|
|
492
|
+
kind: "text",
|
|
493
|
+
text: `duration: ${formatDuration(d.durationMs)}${d.toolError ? " \u2014 ERROR" : ""}`,
|
|
494
|
+
dim: true
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (d?.toolResult) {
|
|
499
|
+
rows.push({
|
|
500
|
+
kind: "heading",
|
|
501
|
+
text: d.toolError ? "tool result (error)" : "tool result"
|
|
502
|
+
});
|
|
503
|
+
for (const l of wrap(d.toolResult, max)) rows.push({ kind: "text", text: l });
|
|
504
|
+
}
|
|
505
|
+
if (d?.fullText) {
|
|
506
|
+
rows.push({ kind: "heading", text: "text" });
|
|
507
|
+
for (const l of wrap(d.fullText, max)) rows.push({ kind: "text", text: l });
|
|
508
|
+
}
|
|
509
|
+
if (d?.thinking) {
|
|
510
|
+
rows.push({ kind: "heading", text: "extended thinking" });
|
|
511
|
+
for (const l of wrap(d.thinking, max))
|
|
512
|
+
rows.push({ kind: "text", text: l, dim: true });
|
|
513
|
+
}
|
|
514
|
+
if (d?.toolInput) {
|
|
515
|
+
rows.push({ kind: "heading", text: "tool input" });
|
|
516
|
+
const pretty = JSON.stringify(d.toolInput, null, 2);
|
|
517
|
+
for (const l of pretty.split("\n"))
|
|
518
|
+
for (const w of wrap(l, max))
|
|
519
|
+
rows.push({ kind: "text", text: w });
|
|
520
|
+
}
|
|
521
|
+
if (d?.toolUseId) {
|
|
522
|
+
rows.push({ kind: "text", text: "", dim: true });
|
|
523
|
+
rows.push({ kind: "text", text: `tool_use_id: ${d.toolUseId}`, dim: true });
|
|
524
|
+
}
|
|
525
|
+
return rows;
|
|
526
|
+
}
|
|
527
|
+
function Row({ row }) {
|
|
528
|
+
if (row.kind === "heading") {
|
|
529
|
+
return /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text5, { bold: true, color: "cyan", children: [
|
|
530
|
+
"\u2014 ",
|
|
531
|
+
row.text,
|
|
532
|
+
" \u2014"
|
|
533
|
+
] }) });
|
|
534
|
+
}
|
|
535
|
+
return /* @__PURE__ */ jsx5(Text5, { dimColor: row.dim, children: row.text || " " });
|
|
536
|
+
}
|
|
537
|
+
function wrap(text, width) {
|
|
538
|
+
const out = [];
|
|
539
|
+
for (const line of text.split("\n")) {
|
|
540
|
+
if (line.length <= width) {
|
|
541
|
+
out.push(line);
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
let rest = line;
|
|
545
|
+
while (rest.length > width) {
|
|
546
|
+
out.push(rest.slice(0, width));
|
|
547
|
+
rest = rest.slice(width);
|
|
548
|
+
}
|
|
549
|
+
if (rest) out.push(rest);
|
|
550
|
+
}
|
|
551
|
+
return out;
|
|
552
|
+
}
|
|
553
|
+
function truncateLine(s, n) {
|
|
554
|
+
return s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
|
|
555
|
+
}
|
|
556
|
+
function formatDuration(ms) {
|
|
557
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
558
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
559
|
+
return `${Math.floor(ms / 6e4)}m${Math.floor(ms % 6e4 / 1e3)}s`;
|
|
560
|
+
}
|
|
561
|
+
function colorFor(e) {
|
|
562
|
+
switch (e.agent) {
|
|
563
|
+
case "claude-code":
|
|
564
|
+
return "cyan";
|
|
565
|
+
case "openclaw":
|
|
566
|
+
return "yellow";
|
|
567
|
+
case "cursor":
|
|
568
|
+
return "magenta";
|
|
569
|
+
case "codex":
|
|
570
|
+
return "green";
|
|
571
|
+
case "gemini":
|
|
572
|
+
return "blue";
|
|
573
|
+
default:
|
|
574
|
+
return "white";
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
function totalDetailRows(event, width) {
|
|
578
|
+
return buildRows2(event, width).length;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// src/ui/ProjectsView.tsx
|
|
582
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
583
|
+
|
|
584
|
+
// src/util/project-index.ts
|
|
585
|
+
function buildProjectIndex(events) {
|
|
586
|
+
const byName = /* @__PURE__ */ new Map();
|
|
587
|
+
for (const e of events) {
|
|
588
|
+
const name = extractProjectName(e);
|
|
589
|
+
if (!name) continue;
|
|
590
|
+
let row = byName.get(name);
|
|
591
|
+
if (!row) {
|
|
592
|
+
row = {
|
|
593
|
+
name,
|
|
594
|
+
events: 0,
|
|
595
|
+
byAgent: /* @__PURE__ */ new Map(),
|
|
596
|
+
sessions: /* @__PURE__ */ new Set(),
|
|
597
|
+
cost: 0,
|
|
598
|
+
lastTs: e.ts
|
|
599
|
+
};
|
|
600
|
+
byName.set(name, row);
|
|
601
|
+
}
|
|
602
|
+
row.events += 1;
|
|
603
|
+
row.byAgent.set(e.agent, (row.byAgent.get(e.agent) ?? 0) + 1);
|
|
604
|
+
if (e.sessionId) row.sessions.add(e.sessionId);
|
|
605
|
+
if (e.details?.cost) row.cost += e.details.cost;
|
|
606
|
+
if (e.ts > row.lastTs) row.lastTs = e.ts;
|
|
607
|
+
}
|
|
608
|
+
const rows = Array.from(byName.values());
|
|
609
|
+
rows.sort((a, b) => a.lastTs < b.lastTs ? 1 : -1);
|
|
610
|
+
return rows;
|
|
611
|
+
}
|
|
612
|
+
function extractProjectName(e) {
|
|
613
|
+
const s = e.summary ?? "";
|
|
614
|
+
const m = s.match(/^\[([^\]/ ]+)/);
|
|
615
|
+
if (m) return m[1] ?? null;
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
function agoFromNow(iso) {
|
|
619
|
+
const then = new Date(iso).getTime();
|
|
620
|
+
const diff = Date.now() - then;
|
|
621
|
+
if (diff < 6e4) return "just now";
|
|
622
|
+
if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
|
|
623
|
+
if (diff < 864e5) return `${Math.floor(diff / 36e5)}h ago`;
|
|
624
|
+
return `${Math.floor(diff / 864e5)}d ago`;
|
|
625
|
+
}
|
|
626
|
+
function buildSessionRows(events, project) {
|
|
627
|
+
const byId = /* @__PURE__ */ new Map();
|
|
628
|
+
for (const e of events) {
|
|
629
|
+
const p = (e.summary ?? "").match(/^\[([^\]/ ]+)/)?.[1];
|
|
630
|
+
if (p !== project) continue;
|
|
631
|
+
const sid = e.sessionId;
|
|
632
|
+
if (!sid) continue;
|
|
633
|
+
let row = byId.get(sid);
|
|
634
|
+
if (!row) {
|
|
635
|
+
row = {
|
|
636
|
+
sessionId: sid,
|
|
637
|
+
agent: e.agent,
|
|
638
|
+
subAgent: extractSubAgent(e),
|
|
639
|
+
project,
|
|
640
|
+
firstPrompt: "",
|
|
641
|
+
events: 0,
|
|
642
|
+
firstTs: e.ts,
|
|
643
|
+
lastTs: e.ts,
|
|
644
|
+
cost: 0,
|
|
645
|
+
hasError: false
|
|
646
|
+
};
|
|
647
|
+
byId.set(sid, row);
|
|
648
|
+
}
|
|
649
|
+
row.events += 1;
|
|
650
|
+
if (e.ts < row.firstTs) row.firstTs = e.ts;
|
|
651
|
+
if (e.ts > row.lastTs) row.lastTs = e.ts;
|
|
652
|
+
if (e.details?.cost) row.cost += e.details.cost;
|
|
653
|
+
if (e.details?.toolError) row.hasError = true;
|
|
654
|
+
if (!row.firstPrompt && e.type === "prompt" && e.details?.fullText) {
|
|
655
|
+
row.firstPrompt = e.details.fullText.trim().slice(0, 200);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
const rows = Array.from(byId.values());
|
|
659
|
+
rows.sort((a, b) => a.lastTs < b.lastTs ? 1 : -1);
|
|
660
|
+
return rows;
|
|
661
|
+
}
|
|
662
|
+
function dateBucket(iso) {
|
|
663
|
+
const then = new Date(iso);
|
|
664
|
+
const now = /* @__PURE__ */ new Date();
|
|
665
|
+
const diffMs = now.getTime() - then.getTime();
|
|
666
|
+
const sameDay = then.getFullYear() === now.getFullYear() && then.getMonth() === now.getMonth() && then.getDate() === now.getDate();
|
|
667
|
+
if (sameDay) return "today";
|
|
668
|
+
if (diffMs < 48 * 36e5) return "yesterday";
|
|
669
|
+
if (diffMs < 7 * 864e5) return "7d";
|
|
670
|
+
return "older";
|
|
671
|
+
}
|
|
672
|
+
function extractSubAgent(e) {
|
|
673
|
+
const tool = e.tool ?? "";
|
|
674
|
+
const m = tool.match(/^openclaw:([^:]+)/);
|
|
675
|
+
if (m) return m[1];
|
|
676
|
+
return void 0;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// src/ui/ProjectsView.tsx
|
|
680
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
681
|
+
function ProjectsView({ projects, selectedIdx, searchQuery }) {
|
|
682
|
+
const filtered = searchQuery ? projects.filter(
|
|
683
|
+
(p) => p.name.toLowerCase().includes(searchQuery.toLowerCase())
|
|
684
|
+
) : projects;
|
|
685
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", borderStyle: "double", paddingX: 1, children: [
|
|
686
|
+
/* @__PURE__ */ jsxs6(Text6, { bold: true, color: "cyan", children: [
|
|
687
|
+
"Projects \u2014 ",
|
|
688
|
+
filtered.length,
|
|
689
|
+
" workspace",
|
|
690
|
+
filtered.length === 1 ? "" : "s"
|
|
169
691
|
] }),
|
|
170
|
-
/* @__PURE__ */
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
692
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "sorted by last activity \xB7 enter to filter timeline \xB7 esc to close" }),
|
|
693
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, flexDirection: "column", children: filtered.length === 0 ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "No projects yet. Use Claude Code / OpenClaw / Cursor and they'll show up here as events stream in." }) : filtered.map((p, i) => /* @__PURE__ */ jsx6(
|
|
694
|
+
ProjectRowView,
|
|
695
|
+
{
|
|
696
|
+
row: p,
|
|
697
|
+
selected: i === selectedIdx
|
|
698
|
+
},
|
|
699
|
+
p.name
|
|
700
|
+
)) })
|
|
701
|
+
] });
|
|
702
|
+
}
|
|
703
|
+
function ProjectRowView({
|
|
704
|
+
row,
|
|
705
|
+
selected
|
|
706
|
+
}) {
|
|
707
|
+
const agentCounts = Array.from(row.byAgent.entries()).sort(([, a], [, b]) => b - a).map(([name, n]) => `${shortName(name)}:${n}`).join(" ");
|
|
708
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginTop: 1, children: [
|
|
709
|
+
/* @__PURE__ */ jsxs6(Text6, { inverse: selected, children: [
|
|
710
|
+
/* @__PURE__ */ jsx6(Text6, { color: "yellow", bold: true, children: selected ? "\u25B6 " : " " }),
|
|
711
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: row.name.padEnd(26) }),
|
|
712
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
713
|
+
" ",
|
|
714
|
+
agoFromNow(row.lastTs).padStart(10)
|
|
175
715
|
] }),
|
|
176
|
-
|
|
716
|
+
row.cost > 0 && /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
177
717
|
" ",
|
|
178
|
-
/* @__PURE__ */
|
|
179
|
-
|
|
180
|
-
a
|
|
181
|
-
] }, i))
|
|
718
|
+
/* @__PURE__ */ jsx6(Text6, { color: "yellow", children: formatUSD(row.cost) })
|
|
719
|
+
] })
|
|
182
720
|
] }),
|
|
183
|
-
/* @__PURE__ */
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
721
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
722
|
+
" ",
|
|
723
|
+
row.events,
|
|
724
|
+
" events \xB7 ",
|
|
725
|
+
row.sessions.size,
|
|
726
|
+
" session",
|
|
727
|
+
row.sessions.size === 1 ? "" : "s",
|
|
728
|
+
" \xB7 ",
|
|
729
|
+
agentCounts
|
|
730
|
+
] })
|
|
731
|
+
] });
|
|
732
|
+
}
|
|
733
|
+
function shortName(a) {
|
|
734
|
+
switch (a) {
|
|
735
|
+
case "claude-code":
|
|
736
|
+
return "claude";
|
|
737
|
+
case "openclaw":
|
|
738
|
+
return "openclaw";
|
|
739
|
+
case "cursor":
|
|
740
|
+
return "cursor";
|
|
741
|
+
case "codex":
|
|
742
|
+
return "codex";
|
|
743
|
+
case "gemini":
|
|
744
|
+
return "gemini";
|
|
745
|
+
default:
|
|
746
|
+
return a;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// src/ui/SessionsView.tsx
|
|
751
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
752
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
753
|
+
function SessionsView({
|
|
754
|
+
project,
|
|
755
|
+
sessions,
|
|
756
|
+
selectedIdx,
|
|
757
|
+
viewportRows,
|
|
758
|
+
scrollOffset
|
|
759
|
+
}) {
|
|
760
|
+
const lines = buildLines(sessions);
|
|
761
|
+
const height = Math.max(3, viewportRows);
|
|
762
|
+
const maxScroll = Math.max(0, lines.length - height);
|
|
763
|
+
const offset = Math.min(scrollOffset, maxScroll);
|
|
764
|
+
const visible = lines.slice(offset, offset + height);
|
|
765
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", borderStyle: "double", paddingX: 1, children: [
|
|
766
|
+
/* @__PURE__ */ jsxs7(Text7, { children: [
|
|
767
|
+
/* @__PURE__ */ jsxs7(Text7, { bold: true, color: "cyan", children: [
|
|
768
|
+
"Sessions \u2014",
|
|
769
|
+
" "
|
|
188
770
|
] }),
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
771
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, children: project }),
|
|
772
|
+
/* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
773
|
+
" ",
|
|
774
|
+
sessions.length,
|
|
775
|
+
" session",
|
|
776
|
+
sessions.length === 1 ? "" : "s"
|
|
777
|
+
] })
|
|
195
778
|
] }),
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
779
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "[\u2191\u2193] select [enter] open session [esc] back to projects [q] quit" }),
|
|
780
|
+
/* @__PURE__ */ jsx7(Box7, { flexDirection: "column", marginTop: 1, children: visible.length === 0 ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "(no sessions found for this project)" }) : visible.map((l, i) => /* @__PURE__ */ jsx7(LineView, { line: l, selectedIdx }, i)) }),
|
|
781
|
+
lines.length > height && /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
782
|
+
offset + 1,
|
|
783
|
+
"\u2013",
|
|
784
|
+
offset + visible.length,
|
|
785
|
+
" of ",
|
|
786
|
+
lines.length
|
|
787
|
+
] }) })
|
|
788
|
+
] });
|
|
789
|
+
}
|
|
790
|
+
function LineView({ line, selectedIdx }) {
|
|
791
|
+
if (line.kind === "bucket") {
|
|
792
|
+
return /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { bold: true, dimColor: true, children: bucketLabel(line.label) }) });
|
|
793
|
+
}
|
|
794
|
+
const r = line.row;
|
|
795
|
+
const selected = line.absIdx === selectedIdx;
|
|
796
|
+
const agentTag = tagFor(r.agent, r.subAgent);
|
|
797
|
+
return /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", inverse: selected, children: [
|
|
798
|
+
/* @__PURE__ */ jsx7(Text7, { color: "yellow", children: selected ? "\u25B6 " : " " }),
|
|
799
|
+
/* @__PURE__ */ jsx7(Text7, { color: colorForAgent(r.agent), children: pad2(agentTag, 22) }),
|
|
800
|
+
/* @__PURE__ */ jsxs7(Text7, { children: [
|
|
801
|
+
" ",
|
|
802
|
+
truncate(r.firstPrompt || "(no user prompt yet)", 56)
|
|
202
803
|
] }),
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
804
|
+
/* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
805
|
+
" \xB7 ",
|
|
806
|
+
r.events,
|
|
807
|
+
"ev \xB7 ",
|
|
808
|
+
agoFromNow(r.lastTs)
|
|
809
|
+
] }),
|
|
810
|
+
r.cost > 0 && /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
811
|
+
" \xB7 ",
|
|
812
|
+
/* @__PURE__ */ jsx7(Text7, { color: "yellow", children: formatUSD(r.cost) })
|
|
813
|
+
] }),
|
|
814
|
+
r.hasError && /* @__PURE__ */ jsx7(Text7, { color: "red", children: " \xB7 ERR" })
|
|
815
|
+
] }) });
|
|
816
|
+
}
|
|
817
|
+
function buildLines(sessions) {
|
|
818
|
+
const lines = [];
|
|
819
|
+
let currentBucket = "";
|
|
820
|
+
let idx = 0;
|
|
821
|
+
for (const row of sessions) {
|
|
822
|
+
const bucket = dateBucket(row.lastTs);
|
|
823
|
+
if (bucket !== currentBucket) {
|
|
824
|
+
currentBucket = bucket;
|
|
825
|
+
lines.push({ kind: "bucket", label: bucket });
|
|
826
|
+
}
|
|
827
|
+
lines.push({ kind: "session", row, absIdx: idx++ });
|
|
828
|
+
}
|
|
829
|
+
return lines;
|
|
830
|
+
}
|
|
831
|
+
function bucketLabel(b) {
|
|
832
|
+
if (b === "today") return "TODAY";
|
|
833
|
+
if (b === "yesterday") return "YESTERDAY";
|
|
834
|
+
if (b === "7d") return "LAST 7 DAYS";
|
|
835
|
+
return "OLDER";
|
|
836
|
+
}
|
|
837
|
+
function tagFor(agent, sub) {
|
|
838
|
+
if (sub) return `[${agent}:${sub}]`;
|
|
839
|
+
return `[${agent}]`;
|
|
840
|
+
}
|
|
841
|
+
function colorForAgent(a) {
|
|
842
|
+
switch (a) {
|
|
843
|
+
case "claude-code":
|
|
844
|
+
return "cyan";
|
|
845
|
+
case "openclaw":
|
|
846
|
+
return "yellow";
|
|
847
|
+
case "cursor":
|
|
848
|
+
return "magenta";
|
|
849
|
+
case "codex":
|
|
850
|
+
return "green";
|
|
851
|
+
case "gemini":
|
|
852
|
+
return "blue";
|
|
853
|
+
default:
|
|
854
|
+
return "white";
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
function pad2(s, n) {
|
|
858
|
+
return s.length >= n ? s.slice(0, n) : s + " ".repeat(n - s.length);
|
|
859
|
+
}
|
|
860
|
+
function truncate(s, n) {
|
|
861
|
+
return s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
|
|
862
|
+
}
|
|
863
|
+
function sessionLineCount(sessions) {
|
|
864
|
+
return buildLines(sessions).length;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// src/util/clipboard.ts
|
|
868
|
+
import { spawnSync } from "child_process";
|
|
869
|
+
import { platform } from "os";
|
|
870
|
+
function copyToClipboard(text) {
|
|
871
|
+
const os = platform();
|
|
872
|
+
try {
|
|
873
|
+
if (os === "darwin") {
|
|
874
|
+
return run("pbcopy", [], text);
|
|
875
|
+
}
|
|
876
|
+
if (os === "linux") {
|
|
877
|
+
if (commandExists("wl-copy")) return run("wl-copy", [], text);
|
|
878
|
+
if (commandExists("xclip")) return run("xclip", ["-selection", "clipboard"], text);
|
|
879
|
+
if (commandExists("xsel")) return run("xsel", ["--clipboard", "--input"], text);
|
|
880
|
+
return {
|
|
881
|
+
ok: false,
|
|
882
|
+
reason: "install wl-copy / xclip / xsel for clipboard support"
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
if (os === "win32") {
|
|
886
|
+
return run("clip", [], text);
|
|
887
|
+
}
|
|
888
|
+
return { ok: false, reason: `clipboard not supported on ${os}` };
|
|
889
|
+
} catch (err) {
|
|
890
|
+
return { ok: false, reason: String(err) };
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
function run(cmd, args, input) {
|
|
894
|
+
const res = spawnSync(cmd, args, {
|
|
895
|
+
input,
|
|
896
|
+
stdio: ["pipe", "ignore", "ignore"]
|
|
897
|
+
});
|
|
898
|
+
if (res.error) return { ok: false, reason: String(res.error) };
|
|
899
|
+
if (res.status !== 0)
|
|
900
|
+
return { ok: false, reason: `${cmd} exited ${res.status}` };
|
|
901
|
+
return { ok: true };
|
|
902
|
+
}
|
|
903
|
+
function commandExists(cmd) {
|
|
904
|
+
const res = spawnSync("sh", ["-c", `command -v ${cmd}`], {
|
|
905
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
906
|
+
});
|
|
907
|
+
return res.status === 0;
|
|
908
|
+
}
|
|
909
|
+
function eventToYankText(summary, path, cmd, toolResult, fullText) {
|
|
910
|
+
if (toolResult && toolResult.trim()) return toolResult;
|
|
911
|
+
if (fullText && fullText.trim()) return fullText;
|
|
912
|
+
if (cmd) return cmd;
|
|
913
|
+
if (path) return path;
|
|
914
|
+
return summary ?? "";
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// src/util/notifier.ts
|
|
918
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
919
|
+
import { platform as platform2 } from "os";
|
|
920
|
+
var RATE_MS = 6e4;
|
|
921
|
+
var recent = /* @__PURE__ */ new Map();
|
|
922
|
+
var notifierDisabled = false;
|
|
923
|
+
function shouldNotify(event) {
|
|
924
|
+
if ((event.type === "file_read" || event.type === "file_write") && event.path && /(^|\/)\.env($|\.)/.test(event.path)) {
|
|
925
|
+
return gate(`env:${event.path}`, {
|
|
926
|
+
title: "\u26A0 agentwatch \u2014 .env access",
|
|
927
|
+
body: `${event.agent} ${event.type} ${event.path}`
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
if (event.path && /(^|\/)(\.ssh|\.aws|\.gnupg)($|\/)/.test(event.path)) {
|
|
931
|
+
return gate(`creds:${event.path}`, {
|
|
932
|
+
title: "\u26A0 agentwatch \u2014 credential path touched",
|
|
933
|
+
body: `${event.agent} ${event.type} ${event.path}`
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
if (event.type === "shell_exec" && event.cmd) {
|
|
937
|
+
const cmd = event.cmd;
|
|
938
|
+
if (/\brm\s+-rf\b/.test(cmd)) {
|
|
939
|
+
return gate(`rm-rf:${cmd.slice(0, 40)}`, {
|
|
940
|
+
title: "\u26A0 agentwatch \u2014 rm -rf",
|
|
941
|
+
body: `${event.agent}: ${cmd.slice(0, 160)}`
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
if (/\bsudo\b/.test(cmd)) {
|
|
945
|
+
return gate(`sudo:${cmd.slice(0, 40)}`, {
|
|
946
|
+
title: "\u26A0 agentwatch \u2014 sudo",
|
|
947
|
+
body: `${event.agent}: ${cmd.slice(0, 160)}`
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
if (/curl[^|]*\|\s*(sh|bash)/.test(cmd)) {
|
|
951
|
+
return gate(`curl-sh:${cmd.slice(0, 40)}`, {
|
|
952
|
+
title: "\u26A0 agentwatch \u2014 curl | sh",
|
|
953
|
+
body: `${event.agent}: ${cmd.slice(0, 160)}`
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
if (event.details?.toolError) {
|
|
958
|
+
const tool = event.tool ?? "tool";
|
|
959
|
+
return gate(`err:${tool}:${event.sessionId ?? ""}`, {
|
|
960
|
+
title: `\u26A0 agentwatch \u2014 ${tool} failed`,
|
|
961
|
+
body: `${event.agent} in ${projectOf(event) ?? "?"}: ${event.summary ?? ""}`.slice(0, 200)
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
return null;
|
|
965
|
+
}
|
|
966
|
+
function gate(key, payload) {
|
|
967
|
+
const now = Date.now();
|
|
968
|
+
const last = recent.get(key);
|
|
969
|
+
if (last && now - last < RATE_MS) return null;
|
|
970
|
+
recent.set(key, now);
|
|
971
|
+
return payload;
|
|
972
|
+
}
|
|
973
|
+
function projectOf(event) {
|
|
974
|
+
const m = (event.summary ?? "").match(/^\[([^\]/ ]+)/);
|
|
975
|
+
return m?.[1];
|
|
976
|
+
}
|
|
977
|
+
function notify(title, body) {
|
|
978
|
+
if (notifierDisabled) return;
|
|
979
|
+
const os = platform2();
|
|
980
|
+
const silentStdio = {
|
|
981
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
982
|
+
};
|
|
983
|
+
try {
|
|
984
|
+
if (os === "darwin") {
|
|
985
|
+
const escTitle = title.replace(/"/g, '\\"');
|
|
986
|
+
const escBody = body.replace(/"/g, '\\"');
|
|
987
|
+
spawnSync2(
|
|
988
|
+
"osascript",
|
|
989
|
+
["-e", `display notification "${escBody}" with title "${escTitle}"`],
|
|
990
|
+
silentStdio
|
|
991
|
+
);
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
if (os === "linux") {
|
|
995
|
+
spawnSync2("notify-send", [title, body], silentStdio);
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
if (os === "win32") {
|
|
999
|
+
const msg = `[System.Windows.Forms.MessageBox]::Show('${body.replace(/'/g, "''")}', '${title.replace(/'/g, "''")}')`;
|
|
1000
|
+
spawnSync2("powershell", ["-Command", msg], silentStdio);
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
} catch {
|
|
1004
|
+
notifierDisabled = true;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// src/ui/HelpView.tsx
|
|
1009
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
1010
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1011
|
+
var GROUPS = [
|
|
1012
|
+
{
|
|
1013
|
+
title: "Navigate",
|
|
1014
|
+
rows: [
|
|
1015
|
+
["\u2191 \u2193 / j k", "move selection in timeline"],
|
|
1016
|
+
["enter", "open event detail pane"],
|
|
1017
|
+
["esc", "close current view / clear selection"],
|
|
1018
|
+
["P", "projects grid \u2014 every workspace on this machine"],
|
|
1019
|
+
["enter on project", "sessions list for that project (by date)"],
|
|
1020
|
+
["enter on session", "scoped timeline for that session"],
|
|
1021
|
+
["q / ctrl-c", "quit agentwatch"]
|
|
1022
|
+
]
|
|
1023
|
+
},
|
|
1024
|
+
{
|
|
1025
|
+
title: "Filter & scope",
|
|
1026
|
+
rows: [
|
|
1027
|
+
["/", "full-text search (summary, path, cmd, tool, text)"],
|
|
1028
|
+
["f", "cycle agent filter (claude / openclaw / cursor / \u2026)"],
|
|
1029
|
+
["a", "toggle agent side panel"],
|
|
1030
|
+
["x", "drill into selected Agent event's subagent run"],
|
|
1031
|
+
["X", "unscope subagent"],
|
|
1032
|
+
["A", "clear project filter"]
|
|
1033
|
+
]
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
title: "Actions",
|
|
1037
|
+
rows: [
|
|
1038
|
+
["y", "yank selected event content to clipboard"],
|
|
1039
|
+
["space", "pause / resume live event stream"],
|
|
1040
|
+
["c", "clear event buffer"]
|
|
1041
|
+
]
|
|
1042
|
+
},
|
|
1043
|
+
{
|
|
1044
|
+
title: "Info views",
|
|
1045
|
+
rows: [
|
|
1046
|
+
["p", "permissions view (Claude + Cursor + OpenClaw)"],
|
|
1047
|
+
["\u2191\u2193 / j k inside permissions", "scroll"]
|
|
1048
|
+
]
|
|
1049
|
+
},
|
|
1050
|
+
{
|
|
1051
|
+
title: "Detail pane (open with enter)",
|
|
1052
|
+
rows: [
|
|
1053
|
+
["\u2191 \u2193 / j k", "scroll detail content"],
|
|
1054
|
+
["esc", "close detail"]
|
|
1055
|
+
]
|
|
1056
|
+
},
|
|
1057
|
+
{
|
|
1058
|
+
title: "Help",
|
|
1059
|
+
rows: [
|
|
1060
|
+
["?", "toggle this help"],
|
|
1061
|
+
["esc", "close this help"]
|
|
1062
|
+
]
|
|
1063
|
+
}
|
|
1064
|
+
];
|
|
1065
|
+
function HelpView() {
|
|
1066
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", borderStyle: "double", paddingX: 1, children: [
|
|
1067
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: "cyan", children: "agentwatch \u2014 keybindings" }),
|
|
1068
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Press ? or esc to close." }),
|
|
1069
|
+
GROUPS.map((g) => /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
|
|
1070
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: "yellow", children: g.title }),
|
|
1071
|
+
g.rows.map(([k, d]) => /* @__PURE__ */ jsxs8(Text8, { children: [
|
|
1072
|
+
/* @__PURE__ */ jsx8(Text8, { color: "green", children: pad3(k, 34) }),
|
|
1073
|
+
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
1074
|
+
" ",
|
|
1075
|
+
d
|
|
1076
|
+
] })
|
|
1077
|
+
] }, k))
|
|
1078
|
+
] }, g.title)),
|
|
1079
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "docs: github.com/mishanefedov/agentwatch \xB7 issues + feature requests welcome" }) })
|
|
212
1080
|
] });
|
|
213
1081
|
}
|
|
214
|
-
function
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
1082
|
+
function pad3(s, n) {
|
|
1083
|
+
return s.length >= n ? s : s + " ".repeat(n - s.length);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// src/ui/Breadcrumb.tsx
|
|
1087
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
1088
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1089
|
+
function Breadcrumb(props) {
|
|
1090
|
+
const parts = [
|
|
1091
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: true, children: "agentwatch" }, "root")
|
|
1092
|
+
];
|
|
1093
|
+
const push = (el) => {
|
|
1094
|
+
parts.push(
|
|
1095
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " \xB7 " }, `sep-${parts.length}`)
|
|
1096
|
+
);
|
|
1097
|
+
parts.push(el);
|
|
1098
|
+
};
|
|
1099
|
+
if (props.view === "help") push(/* @__PURE__ */ jsx9(Text9, { children: "help" }, "v"));
|
|
1100
|
+
if (props.view === "projects") push(/* @__PURE__ */ jsx9(Text9, { children: "projects" }, "v"));
|
|
1101
|
+
if (props.view === "permissions") push(/* @__PURE__ */ jsx9(Text9, { children: "permissions" }, "v"));
|
|
1102
|
+
if (props.sessionsForProject)
|
|
1103
|
+
push(
|
|
1104
|
+
/* @__PURE__ */ jsxs9(Text9, { children: [
|
|
1105
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: props.sessionsForProject }),
|
|
1106
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " (sessions)" })
|
|
1107
|
+
] }, "proj-list")
|
|
1108
|
+
);
|
|
1109
|
+
if (props.projectFilter && !props.sessionsForProject)
|
|
1110
|
+
push(
|
|
1111
|
+
/* @__PURE__ */ jsxs9(Text9, { children: [
|
|
1112
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "project " }),
|
|
1113
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: "yellow", children: props.projectFilter })
|
|
1114
|
+
] }, "proj")
|
|
1115
|
+
);
|
|
1116
|
+
if (props.sessionFilter)
|
|
1117
|
+
push(
|
|
1118
|
+
/* @__PURE__ */ jsxs9(Text9, { children: [
|
|
1119
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "session " }),
|
|
1120
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: "yellow", children: props.sessionFilter.slice(0, 16) })
|
|
1121
|
+
] }, "sess")
|
|
1122
|
+
);
|
|
1123
|
+
if (props.subAgentScope)
|
|
1124
|
+
push(
|
|
1125
|
+
/* @__PURE__ */ jsxs9(Text9, { children: [
|
|
1126
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "sub " }),
|
|
1127
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: "yellow", children: props.subAgentScope.slice(0, 12) })
|
|
1128
|
+
] }, "sub")
|
|
1129
|
+
);
|
|
1130
|
+
if (props.agentFilter)
|
|
1131
|
+
push(
|
|
1132
|
+
/* @__PURE__ */ jsxs9(Text9, { children: [
|
|
1133
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "agent " }),
|
|
1134
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: "yellow", children: props.agentFilter })
|
|
1135
|
+
] }, "agent")
|
|
1136
|
+
);
|
|
1137
|
+
if (props.searchQuery)
|
|
1138
|
+
push(
|
|
1139
|
+
/* @__PURE__ */ jsxs9(Text9, { children: [
|
|
1140
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "search " }),
|
|
1141
|
+
/* @__PURE__ */ jsxs9(Text9, { bold: true, color: "yellow", children: [
|
|
1142
|
+
'"',
|
|
1143
|
+
props.searchQuery,
|
|
1144
|
+
'"'
|
|
1145
|
+
] })
|
|
1146
|
+
] }, "q")
|
|
1147
|
+
);
|
|
1148
|
+
return /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text9, { children: parts }) });
|
|
218
1149
|
}
|
|
219
1150
|
|
|
220
1151
|
// src/adapters/claude-code.ts
|
|
@@ -224,6 +1155,13 @@ import { createInterface } from "readline";
|
|
|
224
1155
|
import { basename, sep } from "path";
|
|
225
1156
|
|
|
226
1157
|
// src/schema.ts
|
|
1158
|
+
function clampTs(ts) {
|
|
1159
|
+
const t = new Date(ts).getTime();
|
|
1160
|
+
if (!Number.isFinite(t)) return (/* @__PURE__ */ new Date()).toISOString();
|
|
1161
|
+
const now = Date.now();
|
|
1162
|
+
if (t > now + 6e4) return new Date(now).toISOString();
|
|
1163
|
+
return ts;
|
|
1164
|
+
}
|
|
227
1165
|
function riskOf(type, path, cmd) {
|
|
228
1166
|
if (type === "shell_exec") {
|
|
229
1167
|
if (cmd && /\b(rm|sudo|curl|wget|chmod|chown)\b/.test(cmd)) return 9;
|
|
@@ -249,23 +1187,61 @@ function nextId() {
|
|
|
249
1187
|
return `${Date.now().toString(36)}-${counter.toString(36)}`;
|
|
250
1188
|
}
|
|
251
1189
|
|
|
1190
|
+
// src/util/recent-writes.ts
|
|
1191
|
+
var DEDUPE_WINDOW_MS = 5e3;
|
|
1192
|
+
var EXPIRY_MS = 3e4;
|
|
1193
|
+
var recent2 = /* @__PURE__ */ new Map();
|
|
1194
|
+
var lastSweep = 0;
|
|
1195
|
+
function markAgentWrite(path, ts = Date.now()) {
|
|
1196
|
+
const t = typeof ts === "string" ? new Date(ts).getTime() : ts;
|
|
1197
|
+
if (!path || Number.isNaN(t)) return;
|
|
1198
|
+
recent2.set(path, t);
|
|
1199
|
+
sweepIfDue();
|
|
1200
|
+
}
|
|
1201
|
+
function wasRecentlyWrittenByAgent(path) {
|
|
1202
|
+
const t = recent2.get(path);
|
|
1203
|
+
if (t == null) return false;
|
|
1204
|
+
return Date.now() - t <= DEDUPE_WINDOW_MS;
|
|
1205
|
+
}
|
|
1206
|
+
function sweepIfDue() {
|
|
1207
|
+
const now = Date.now();
|
|
1208
|
+
if (now - lastSweep < EXPIRY_MS) return;
|
|
1209
|
+
lastSweep = now;
|
|
1210
|
+
for (const [p, t] of recent2) {
|
|
1211
|
+
if (now - t > EXPIRY_MS) recent2.delete(p);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
|
|
252
1215
|
// src/adapters/claude-code.ts
|
|
1216
|
+
var MAX_PENDING_TOOL_USES = 5e3;
|
|
1217
|
+
var pendingToolUses = /* @__PURE__ */ new Map();
|
|
1218
|
+
var orphanResults = /* @__PURE__ */ new Map();
|
|
1219
|
+
function capMap(m, max) {
|
|
1220
|
+
while (m.size > max) {
|
|
1221
|
+
const first = m.keys().next().value;
|
|
1222
|
+
if (first === void 0) break;
|
|
1223
|
+
m.delete(first);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
253
1226
|
var BACKFILL_BYTES = 64 * 1024;
|
|
254
|
-
function startClaudeAdapter(
|
|
1227
|
+
function startClaudeAdapter(sink) {
|
|
1228
|
+
const { emit, enrich } = normalizeSink(sink);
|
|
255
1229
|
const dir = claudeProjectsDir();
|
|
256
1230
|
if (!existsSync(dir)) {
|
|
257
1231
|
return () => {
|
|
258
1232
|
};
|
|
259
1233
|
}
|
|
260
1234
|
const cursors = /* @__PURE__ */ new Map();
|
|
261
|
-
const
|
|
1235
|
+
const mainRe = /[\\/]projects[\\/][^\\/]+[\\/][^\\/]+\.jsonl$/;
|
|
1236
|
+
const subRe = /[\\/]projects[\\/][^\\/]+[\\/][^\\/]+[\\/]subagents[\\/][^\\/]+\.jsonl$/;
|
|
262
1237
|
const watcher = chokidar.watch(dir, {
|
|
263
1238
|
persistent: true,
|
|
264
1239
|
ignoreInitial: false,
|
|
265
|
-
depth:
|
|
1240
|
+
depth: 5
|
|
266
1241
|
});
|
|
267
1242
|
const process2 = (file, isInitialAdd) => {
|
|
268
|
-
|
|
1243
|
+
const isSub = subRe.test(file);
|
|
1244
|
+
if (!isSub && !mainRe.test(file)) return;
|
|
269
1245
|
const size = safeSize(file);
|
|
270
1246
|
let cursor = cursors.get(file);
|
|
271
1247
|
if (!cursor) {
|
|
@@ -282,6 +1258,7 @@ function startClaudeAdapter(emit) {
|
|
|
282
1258
|
});
|
|
283
1259
|
const sessionId = basename(file, ".jsonl");
|
|
284
1260
|
const project = extractProject(file);
|
|
1261
|
+
const subAgentId = isSub ? extractSubAgentId(file) : void 0;
|
|
285
1262
|
let consumed = 0;
|
|
286
1263
|
let skippedFirst = false;
|
|
287
1264
|
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
@@ -294,8 +1271,33 @@ function startClaudeAdapter(emit) {
|
|
|
294
1271
|
if (!line.trim()) return;
|
|
295
1272
|
try {
|
|
296
1273
|
const obj = JSON.parse(line);
|
|
297
|
-
|
|
298
|
-
|
|
1274
|
+
handleToolResults(obj, enrich);
|
|
1275
|
+
const event = translateClaudeLine(obj, sessionId, project, subAgentId);
|
|
1276
|
+
if (event) {
|
|
1277
|
+
emit(event);
|
|
1278
|
+
if (event.path && (event.type === "file_write" || event.type === "file_read")) {
|
|
1279
|
+
markAgentWrite(event.path, event.ts);
|
|
1280
|
+
}
|
|
1281
|
+
const toolUseId = event.details?.toolUseId;
|
|
1282
|
+
if (toolUseId && orphanResults.has(toolUseId)) {
|
|
1283
|
+
const orphan = orphanResults.get(toolUseId);
|
|
1284
|
+
orphanResults.delete(toolUseId);
|
|
1285
|
+
enrich(event.id, {
|
|
1286
|
+
toolResult: orphan.content,
|
|
1287
|
+
toolError: orphan.isError,
|
|
1288
|
+
durationMs: Math.max(
|
|
1289
|
+
0,
|
|
1290
|
+
new Date(orphan.ts).getTime() - new Date(event.ts).getTime()
|
|
1291
|
+
)
|
|
1292
|
+
});
|
|
1293
|
+
} else if (toolUseId) {
|
|
1294
|
+
pendingToolUses.set(toolUseId, {
|
|
1295
|
+
eventId: event.id,
|
|
1296
|
+
ts: event.ts
|
|
1297
|
+
});
|
|
1298
|
+
capMap(pendingToolUses, MAX_PENDING_TOOL_USES);
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
299
1301
|
} catch {
|
|
300
1302
|
}
|
|
301
1303
|
});
|
|
@@ -326,6 +1328,81 @@ function extractProject(file) {
|
|
|
326
1328
|
}
|
|
327
1329
|
return "";
|
|
328
1330
|
}
|
|
1331
|
+
function extractSubAgentId(file) {
|
|
1332
|
+
const base = basename(file, ".jsonl");
|
|
1333
|
+
return base.replace(/^agent-/, "");
|
|
1334
|
+
}
|
|
1335
|
+
function normalizeSink(sink) {
|
|
1336
|
+
if (typeof sink === "function") {
|
|
1337
|
+
return { emit: sink, enrich: () => {
|
|
1338
|
+
} };
|
|
1339
|
+
}
|
|
1340
|
+
return sink;
|
|
1341
|
+
}
|
|
1342
|
+
function handleToolResults(obj, enrich) {
|
|
1343
|
+
if (!obj || typeof obj !== "object") return;
|
|
1344
|
+
const o = obj;
|
|
1345
|
+
const role = o.role ?? o.message?.role;
|
|
1346
|
+
if (role !== "user") return;
|
|
1347
|
+
const content = o.message?.content;
|
|
1348
|
+
if (!Array.isArray(content)) return;
|
|
1349
|
+
const ts = typeof o.timestamp === "string" && o.timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
1350
|
+
for (const block of content) {
|
|
1351
|
+
if (typeof block !== "object" || block === null) continue;
|
|
1352
|
+
const b = block;
|
|
1353
|
+
if (b.type !== "tool_result") continue;
|
|
1354
|
+
const id = typeof b.tool_use_id === "string" ? b.tool_use_id : void 0;
|
|
1355
|
+
if (!id) continue;
|
|
1356
|
+
const isError = b.is_error === true;
|
|
1357
|
+
const resultText = flattenResultContent(b.content);
|
|
1358
|
+
const subAgentId = extractSubAgentIdFromResult(resultText);
|
|
1359
|
+
const pending = pendingToolUses.get(id);
|
|
1360
|
+
if (pending) {
|
|
1361
|
+
pendingToolUses.delete(id);
|
|
1362
|
+
enrich(pending.eventId, {
|
|
1363
|
+
toolResult: resultText,
|
|
1364
|
+
toolError: isError,
|
|
1365
|
+
durationMs: Math.max(
|
|
1366
|
+
0,
|
|
1367
|
+
new Date(ts).getTime() - new Date(pending.ts).getTime()
|
|
1368
|
+
),
|
|
1369
|
+
...subAgentId ? { subAgentId } : {}
|
|
1370
|
+
});
|
|
1371
|
+
} else {
|
|
1372
|
+
orphanResults.set(id, { ts, content: resultText, isError });
|
|
1373
|
+
if (orphanResults.size > 1e3) {
|
|
1374
|
+
const first = orphanResults.keys().next().value;
|
|
1375
|
+
if (first) orphanResults.delete(first);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
function extractSubAgentIdFromResult(text) {
|
|
1381
|
+
const m = text.match(/agentId[":\s]+([a-f0-9]{16,})/) || text.match(/agent-([a-f0-9]{16,})/);
|
|
1382
|
+
return m?.[1];
|
|
1383
|
+
}
|
|
1384
|
+
var MAX_TOOL_RESULT_BYTES = 256 * 1024;
|
|
1385
|
+
function flattenResultContent(content) {
|
|
1386
|
+
if (typeof content === "string") return capBytes(content);
|
|
1387
|
+
if (!Array.isArray(content)) return "";
|
|
1388
|
+
const parts = [];
|
|
1389
|
+
for (const c of content) {
|
|
1390
|
+
if (typeof c === "string") {
|
|
1391
|
+
parts.push(c);
|
|
1392
|
+
} else if (typeof c === "object" && c !== null) {
|
|
1393
|
+
const rec = c;
|
|
1394
|
+
if (typeof rec.text === "string") parts.push(rec.text);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
return capBytes(parts.join("\n"));
|
|
1398
|
+
}
|
|
1399
|
+
function capBytes(s, max = MAX_TOOL_RESULT_BYTES) {
|
|
1400
|
+
if (s.length <= max) return s;
|
|
1401
|
+
const truncated = s.length - max;
|
|
1402
|
+
return s.slice(0, max) + `
|
|
1403
|
+
|
|
1404
|
+
\u2026 [${truncated.toLocaleString()} bytes truncated]`;
|
|
1405
|
+
}
|
|
329
1406
|
function safeSize(file) {
|
|
330
1407
|
try {
|
|
331
1408
|
return statSync(file).size;
|
|
@@ -333,17 +1410,26 @@ function safeSize(file) {
|
|
|
333
1410
|
return 0;
|
|
334
1411
|
}
|
|
335
1412
|
}
|
|
336
|
-
function translateClaudeLine(obj, sessionId, project = "") {
|
|
1413
|
+
function translateClaudeLine(obj, sessionId, project = "", subAgentId) {
|
|
337
1414
|
if (!obj || typeof obj !== "object") return null;
|
|
338
1415
|
const o = obj;
|
|
339
|
-
const ts =
|
|
340
|
-
|
|
1416
|
+
const ts = clampTs(
|
|
1417
|
+
typeof o.timestamp === "string" && o.timestamp || (/* @__PURE__ */ new Date()).toISOString()
|
|
1418
|
+
);
|
|
1419
|
+
const tagParts = [];
|
|
1420
|
+
if (project) tagParts.push(project);
|
|
1421
|
+
if (subAgentId) tagParts.push(`sub:${subAgentId.slice(0, 8)}`);
|
|
1422
|
+
const prefix = tagParts.length > 0 ? `[${tagParts.join(" / ")}] ` : "";
|
|
341
1423
|
const role = o.role ?? o.message?.role;
|
|
342
1424
|
const type = o.type;
|
|
343
1425
|
const content = o.message?.content;
|
|
344
1426
|
if (type === "tool_result" || type === "summary") return null;
|
|
345
1427
|
if (type === "worktree-state" || type === "compact") return null;
|
|
346
1428
|
if (type === "assistant" || role === "assistant") {
|
|
1429
|
+
const msg = o.message;
|
|
1430
|
+
const model = typeof msg?.model === "string" ? msg.model : "default";
|
|
1431
|
+
const usage = parseUsage(msg?.usage) ?? void 0;
|
|
1432
|
+
const cost = usage ? costOf(model, usage) : void 0;
|
|
347
1433
|
const toolUse = findToolUse(content);
|
|
348
1434
|
if (toolUse) {
|
|
349
1435
|
const evType = inferToolType(toolUse.name);
|
|
@@ -358,19 +1444,35 @@ function translateClaudeLine(obj, sessionId, project = "") {
|
|
|
358
1444
|
tool: toolUse.name,
|
|
359
1445
|
summary: prefix + summary,
|
|
360
1446
|
sessionId,
|
|
361
|
-
riskScore: riskOf(evType, toolUse.path, toolUse.cmd)
|
|
1447
|
+
riskScore: riskOf(evType, toolUse.path, toolUse.cmd),
|
|
1448
|
+
details: {
|
|
1449
|
+
toolInput: toolUse.input,
|
|
1450
|
+
toolUseId: toolUse.id,
|
|
1451
|
+
thinking: extractThinking(content),
|
|
1452
|
+
usage,
|
|
1453
|
+
cost,
|
|
1454
|
+
model
|
|
1455
|
+
}
|
|
362
1456
|
};
|
|
363
1457
|
}
|
|
364
1458
|
const text = extractText(content);
|
|
365
|
-
|
|
1459
|
+
const thinking = extractThinking(content);
|
|
1460
|
+
if (!text && !thinking) return null;
|
|
366
1461
|
return {
|
|
367
1462
|
id: nextId(),
|
|
368
1463
|
ts,
|
|
369
1464
|
agent: "claude-code",
|
|
370
1465
|
type: "response",
|
|
371
|
-
summary: prefix +
|
|
1466
|
+
summary: prefix + truncate2(text || thinking || ""),
|
|
372
1467
|
sessionId,
|
|
373
|
-
riskScore: riskOf("response")
|
|
1468
|
+
riskScore: riskOf("response"),
|
|
1469
|
+
details: {
|
|
1470
|
+
fullText: text || void 0,
|
|
1471
|
+
thinking: thinking || void 0,
|
|
1472
|
+
usage,
|
|
1473
|
+
cost,
|
|
1474
|
+
model
|
|
1475
|
+
}
|
|
374
1476
|
};
|
|
375
1477
|
}
|
|
376
1478
|
if (type === "user" || role === "user") {
|
|
@@ -381,9 +1483,10 @@ function translateClaudeLine(obj, sessionId, project = "") {
|
|
|
381
1483
|
ts,
|
|
382
1484
|
agent: "claude-code",
|
|
383
1485
|
type: "prompt",
|
|
384
|
-
summary: prefix +
|
|
1486
|
+
summary: prefix + truncate2(text),
|
|
385
1487
|
sessionId,
|
|
386
|
-
riskScore: riskOf("prompt")
|
|
1488
|
+
riskScore: riskOf("prompt"),
|
|
1489
|
+
details: { fullText: text }
|
|
387
1490
|
};
|
|
388
1491
|
}
|
|
389
1492
|
return null;
|
|
@@ -395,25 +1498,38 @@ function findToolUse(content) {
|
|
|
395
1498
|
const rec = c;
|
|
396
1499
|
if (rec.type !== "tool_use") continue;
|
|
397
1500
|
const name = typeof rec.name === "string" ? rec.name : "unknown";
|
|
1501
|
+
const id = typeof rec.id === "string" ? rec.id : void 0;
|
|
398
1502
|
const input = rec.input ?? {};
|
|
399
1503
|
const path = typeof input.file_path === "string" ? input.file_path : typeof input.path === "string" ? input.path : void 0;
|
|
400
1504
|
const cmd = typeof input.command === "string" ? input.command : void 0;
|
|
401
|
-
return { name, path, cmd, input };
|
|
1505
|
+
return { name, path, cmd, input, id };
|
|
402
1506
|
}
|
|
403
1507
|
return null;
|
|
404
1508
|
}
|
|
1509
|
+
function extractThinking(content) {
|
|
1510
|
+
if (!Array.isArray(content)) return "";
|
|
1511
|
+
const parts = [];
|
|
1512
|
+
for (const c of content) {
|
|
1513
|
+
if (typeof c !== "object" || c === null) continue;
|
|
1514
|
+
const rec = c;
|
|
1515
|
+
if (rec.type === "thinking" && typeof rec.thinking === "string") {
|
|
1516
|
+
parts.push(rec.thinking);
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
return parts.join("\n").trim();
|
|
1520
|
+
}
|
|
405
1521
|
function buildToolSummary(t) {
|
|
406
|
-
if (/^Bash/i.test(t.name) && t.cmd) return `Bash: ${
|
|
1522
|
+
if (/^Bash/i.test(t.name) && t.cmd) return `Bash: ${truncate2(t.cmd, 100)}`;
|
|
407
1523
|
if (/^(Write|Edit|MultiEdit|Read)/i.test(t.name) && t.path) {
|
|
408
1524
|
return `${t.name}: ${t.path}`;
|
|
409
1525
|
}
|
|
410
1526
|
if (/^(Grep|Glob)/i.test(t.name)) {
|
|
411
1527
|
const pat = typeof t.input.pattern === "string" ? t.input.pattern : typeof t.input.glob === "string" ? t.input.glob : "";
|
|
412
|
-
return `${t.name}: ${
|
|
1528
|
+
return `${t.name}: ${truncate2(pat, 100)}`;
|
|
413
1529
|
}
|
|
414
1530
|
if (/^Task/i.test(t.name)) {
|
|
415
1531
|
const desc = typeof t.input.description === "string" ? t.input.description : "";
|
|
416
|
-
return `Task: ${
|
|
1532
|
+
return `Task: ${truncate2(desc, 100)}`;
|
|
417
1533
|
}
|
|
418
1534
|
if (/^WebFetch/i.test(t.name)) {
|
|
419
1535
|
const url = typeof t.input.url === "string" ? t.input.url : "";
|
|
@@ -422,7 +1538,7 @@ function buildToolSummary(t) {
|
|
|
422
1538
|
const firstVal = Object.values(t.input).find(
|
|
423
1539
|
(v) => typeof v === "string"
|
|
424
1540
|
);
|
|
425
|
-
return firstVal ? `${t.name}: ${
|
|
1541
|
+
return firstVal ? `${t.name}: ${truncate2(firstVal, 100)}` : t.name;
|
|
426
1542
|
}
|
|
427
1543
|
function extractText(content) {
|
|
428
1544
|
if (typeof content === "string") return content;
|
|
@@ -458,7 +1574,7 @@ function inferToolType(name) {
|
|
|
458
1574
|
if (/^(Write|Edit|MultiEdit)/i.test(name)) return "file_write";
|
|
459
1575
|
return "tool_call";
|
|
460
1576
|
}
|
|
461
|
-
function
|
|
1577
|
+
function truncate2(s, max = 140) {
|
|
462
1578
|
const clean = s.replace(/\s+/g, " ").trim();
|
|
463
1579
|
if (!clean) return "";
|
|
464
1580
|
return clean.length <= max ? clean : clean.slice(0, max - 1) + "\u2026";
|
|
@@ -471,7 +1587,8 @@ import { createInterface as createInterface2 } from "readline";
|
|
|
471
1587
|
import { basename as basename2, join, sep as sep2 } from "path";
|
|
472
1588
|
import { homedir } from "os";
|
|
473
1589
|
var sessionCwd = /* @__PURE__ */ new Map();
|
|
474
|
-
function startOpenClawAdapter(
|
|
1590
|
+
function startOpenClawAdapter(sink) {
|
|
1591
|
+
const emit = typeof sink === "function" ? sink : sink.emit;
|
|
475
1592
|
const root = join(homedir(), ".openclaw");
|
|
476
1593
|
if (!existsSync2(root)) return () => {
|
|
477
1594
|
};
|
|
@@ -511,7 +1628,7 @@ function startOpenClawAdapter(emit) {
|
|
|
511
1628
|
};
|
|
512
1629
|
}
|
|
513
1630
|
function processSession(file, startFromEnd, cursors, emit) {
|
|
514
|
-
const subAgent =
|
|
1631
|
+
const subAgent = extractSubAgent2(file);
|
|
515
1632
|
const sessionId = basename2(file, ".jsonl");
|
|
516
1633
|
streamLines(file, startFromEnd, cursors, (line) => {
|
|
517
1634
|
let obj;
|
|
@@ -580,7 +1697,7 @@ function safeSize2(file) {
|
|
|
580
1697
|
return 0;
|
|
581
1698
|
}
|
|
582
1699
|
}
|
|
583
|
-
function
|
|
1700
|
+
function extractSubAgent2(file) {
|
|
584
1701
|
const parts = file.split(sep2);
|
|
585
1702
|
const agentsIdx = parts.lastIndexOf("agents");
|
|
586
1703
|
if (agentsIdx >= 0 && parts[agentsIdx + 1]) return parts[agentsIdx + 1];
|
|
@@ -589,7 +1706,9 @@ function extractSubAgent(file) {
|
|
|
589
1706
|
function translateSession(obj, subAgent, sessionId) {
|
|
590
1707
|
if (!obj || typeof obj !== "object") return null;
|
|
591
1708
|
const o = obj;
|
|
592
|
-
const ts =
|
|
1709
|
+
const ts = clampTs(
|
|
1710
|
+
typeof o.timestamp === "string" && o.timestamp || (/* @__PURE__ */ new Date()).toISOString()
|
|
1711
|
+
);
|
|
593
1712
|
const t = o.type;
|
|
594
1713
|
const projectLabel = () => {
|
|
595
1714
|
const cwd = sessionCwd.get(sessionId);
|
|
@@ -634,7 +1753,10 @@ function translateSession(obj, subAgent, sessionId) {
|
|
|
634
1753
|
const content = msg?.content;
|
|
635
1754
|
const text = extractText2(content);
|
|
636
1755
|
if (role === "user") {
|
|
637
|
-
return base("prompt", {
|
|
1756
|
+
return base("prompt", {
|
|
1757
|
+
summary: truncate3(text),
|
|
1758
|
+
details: { fullText: text }
|
|
1759
|
+
});
|
|
638
1760
|
}
|
|
639
1761
|
if (role === "assistant") {
|
|
640
1762
|
const toolUse = extractToolUse(content);
|
|
@@ -644,11 +1766,15 @@ function translateSession(obj, subAgent, sessionId) {
|
|
|
644
1766
|
tool: `openclaw:${subAgent}:${toolUse.name}`,
|
|
645
1767
|
path: toolUse.path,
|
|
646
1768
|
cmd: toolUse.cmd,
|
|
647
|
-
summary:
|
|
1769
|
+
summary: truncate3(toolUse.summary),
|
|
1770
|
+
details: { toolInput: toolUse.input }
|
|
648
1771
|
});
|
|
649
1772
|
}
|
|
650
1773
|
if (!text) return null;
|
|
651
|
-
return base("response", {
|
|
1774
|
+
return base("response", {
|
|
1775
|
+
summary: truncate3(text),
|
|
1776
|
+
details: { fullText: text }
|
|
1777
|
+
});
|
|
652
1778
|
}
|
|
653
1779
|
}
|
|
654
1780
|
return null;
|
|
@@ -656,7 +1782,9 @@ function translateSession(obj, subAgent, sessionId) {
|
|
|
656
1782
|
function translateAudit(obj) {
|
|
657
1783
|
if (!obj || typeof obj !== "object") return null;
|
|
658
1784
|
const o = obj;
|
|
659
|
-
const ts =
|
|
1785
|
+
const ts = clampTs(
|
|
1786
|
+
typeof o.ts === "string" && o.ts || (/* @__PURE__ */ new Date()).toISOString()
|
|
1787
|
+
);
|
|
660
1788
|
const event = typeof o.event === "string" ? o.event : "config.event";
|
|
661
1789
|
const configPath = typeof o.configPath === "string" ? o.configPath : void 0;
|
|
662
1790
|
const cwd = typeof o.cwd === "string" ? o.cwd : void 0;
|
|
@@ -692,7 +1820,7 @@ function extractToolUse(content) {
|
|
|
692
1820
|
const path = typeof input.file_path === "string" ? input.file_path : typeof input.path === "string" ? input.path : void 0;
|
|
693
1821
|
const cmd = typeof input.command === "string" ? input.command : void 0;
|
|
694
1822
|
const summary = cmd ?? path ?? name;
|
|
695
|
-
return { name, path, cmd, summary };
|
|
1823
|
+
return { name, path, cmd, summary, input };
|
|
696
1824
|
}
|
|
697
1825
|
}
|
|
698
1826
|
return null;
|
|
@@ -703,7 +1831,7 @@ function inferToolType2(name) {
|
|
|
703
1831
|
if (/^(Write|Edit|MultiEdit|Create)/i.test(name)) return "file_write";
|
|
704
1832
|
return "tool_call";
|
|
705
1833
|
}
|
|
706
|
-
function
|
|
1834
|
+
function truncate3(s, max = 140) {
|
|
707
1835
|
const clean = s.replace(/\s+/g, " ").trim();
|
|
708
1836
|
if (!clean) return "";
|
|
709
1837
|
return clean.length <= max ? clean : clean.slice(0, max - 1) + "\u2026";
|
|
@@ -719,7 +1847,8 @@ import {
|
|
|
719
1847
|
} from "fs";
|
|
720
1848
|
import { homedir as homedir2 } from "os";
|
|
721
1849
|
import { join as join2 } from "path";
|
|
722
|
-
function startCursorAdapter(workspace,
|
|
1850
|
+
function startCursorAdapter(workspace, sink) {
|
|
1851
|
+
const emit = typeof sink === "function" ? sink : sink.emit;
|
|
723
1852
|
const cursorDir = join2(homedir2(), ".cursor");
|
|
724
1853
|
const installed = existsSync3(cursorDir);
|
|
725
1854
|
const status = {
|
|
@@ -792,8 +1921,8 @@ function startCursorAdapter(workspace, emit) {
|
|
|
792
1921
|
ignoreInitial: true
|
|
793
1922
|
});
|
|
794
1923
|
w.on("change", () => {
|
|
795
|
-
const
|
|
796
|
-
for (const path of
|
|
1924
|
+
const recent3 = readRecentFiles(stateFile);
|
|
1925
|
+
for (const path of recent3) {
|
|
797
1926
|
if (lastRecentFiles.has(path)) continue;
|
|
798
1927
|
lastRecentFiles.add(path);
|
|
799
1928
|
const project = extractProject2(path);
|
|
@@ -912,8 +2041,139 @@ function extractProject2(path) {
|
|
|
912
2041
|
return segs[segs.length - 2] ?? "";
|
|
913
2042
|
}
|
|
914
2043
|
|
|
915
|
-
// src/adapters/
|
|
2044
|
+
// src/adapters/gemini.ts
|
|
916
2045
|
import chokidar4 from "chokidar";
|
|
2046
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
|
|
2047
|
+
import { homedir as homedir3 } from "os";
|
|
2048
|
+
import { basename as basename3, join as join3, sep as sep3 } from "path";
|
|
2049
|
+
function startGeminiAdapter(sink) {
|
|
2050
|
+
const { emit } = normalizeSink2(sink);
|
|
2051
|
+
const root = join3(homedir3(), ".gemini", "tmp");
|
|
2052
|
+
if (!existsSync4(root)) return () => {
|
|
2053
|
+
};
|
|
2054
|
+
const emittedIds = /* @__PURE__ */ new Map();
|
|
2055
|
+
const watcher = chokidar4.watch(root, {
|
|
2056
|
+
persistent: true,
|
|
2057
|
+
ignoreInitial: false,
|
|
2058
|
+
depth: 4
|
|
2059
|
+
});
|
|
2060
|
+
const sessionRe = /[\\/]chats[\\/]session-[^\\/]+\.json$/;
|
|
2061
|
+
const process2 = (file, _isInitial) => {
|
|
2062
|
+
if (!sessionRe.test(file)) return;
|
|
2063
|
+
let doc;
|
|
2064
|
+
try {
|
|
2065
|
+
doc = JSON.parse(readFileSync2(file, "utf8"));
|
|
2066
|
+
} catch {
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
2069
|
+
if (!doc || typeof doc !== "object") return;
|
|
2070
|
+
const d = doc;
|
|
2071
|
+
const sessionId = typeof d.sessionId === "string" && d.sessionId || basename3(file, ".json");
|
|
2072
|
+
const kind = typeof d.kind === "string" ? d.kind : "main";
|
|
2073
|
+
const project = extractProject3(file);
|
|
2074
|
+
const messages = Array.isArray(d.messages) ? d.messages : [];
|
|
2075
|
+
let seen = emittedIds.get(file);
|
|
2076
|
+
if (!seen) {
|
|
2077
|
+
seen = /* @__PURE__ */ new Set();
|
|
2078
|
+
emittedIds.set(file, seen);
|
|
2079
|
+
}
|
|
2080
|
+
for (const m of messages) {
|
|
2081
|
+
if (!m || typeof m !== "object") continue;
|
|
2082
|
+
const msg = m;
|
|
2083
|
+
const id = typeof msg.id === "string" ? msg.id : void 0;
|
|
2084
|
+
if (!id || seen.has(id)) continue;
|
|
2085
|
+
seen.add(id);
|
|
2086
|
+
const ev = translate(msg, sessionId, kind, project);
|
|
2087
|
+
if (ev) emit(ev);
|
|
2088
|
+
}
|
|
2089
|
+
};
|
|
2090
|
+
watcher.on("add", (f) => process2(f, true));
|
|
2091
|
+
watcher.on("change", (f) => process2(f, false));
|
|
2092
|
+
watcher.on("error", swallow3);
|
|
2093
|
+
return () => {
|
|
2094
|
+
void watcher.close();
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
function translate(msg, sessionId, kind, project) {
|
|
2098
|
+
const ts = clampTs(
|
|
2099
|
+
typeof msg.timestamp === "string" && msg.timestamp || (/* @__PURE__ */ new Date()).toISOString()
|
|
2100
|
+
);
|
|
2101
|
+
const type = typeof msg.type === "string" ? msg.type : "";
|
|
2102
|
+
const text = extractText3(msg.content);
|
|
2103
|
+
const subAgentSuffix = kind === "subagent" ? " / sub:gemini" : "";
|
|
2104
|
+
const prefix = project ? `[${project}${subAgentSuffix}] ` : "";
|
|
2105
|
+
let eventType;
|
|
2106
|
+
if (type === "user") {
|
|
2107
|
+
if (!text) return null;
|
|
2108
|
+
eventType = "prompt";
|
|
2109
|
+
} else if (type === "gemini") {
|
|
2110
|
+
if (!text) return null;
|
|
2111
|
+
eventType = "response";
|
|
2112
|
+
} else if (type === "error") {
|
|
2113
|
+
if (!text) return null;
|
|
2114
|
+
eventType = "response";
|
|
2115
|
+
} else {
|
|
2116
|
+
return null;
|
|
2117
|
+
}
|
|
2118
|
+
return {
|
|
2119
|
+
id: nextId(),
|
|
2120
|
+
ts,
|
|
2121
|
+
agent: "gemini",
|
|
2122
|
+
type: eventType,
|
|
2123
|
+
sessionId,
|
|
2124
|
+
summary: prefix + truncate4(text),
|
|
2125
|
+
riskScore: type === "error" ? 6 : riskOf(eventType),
|
|
2126
|
+
tool: kind === "subagent" ? "gemini:subagent" : "gemini",
|
|
2127
|
+
details: { fullText: text }
|
|
2128
|
+
};
|
|
2129
|
+
}
|
|
2130
|
+
function extractText3(content) {
|
|
2131
|
+
if (typeof content === "string") return content.trim();
|
|
2132
|
+
if (!Array.isArray(content)) return "";
|
|
2133
|
+
const parts = [];
|
|
2134
|
+
for (const item of content) {
|
|
2135
|
+
if (typeof item === "string") {
|
|
2136
|
+
parts.push(item);
|
|
2137
|
+
} else if (item && typeof item === "object") {
|
|
2138
|
+
const rec = item;
|
|
2139
|
+
if (typeof rec.text === "string") parts.push(rec.text);
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
return parts.join("\n").trim();
|
|
2143
|
+
}
|
|
2144
|
+
function extractProject3(file) {
|
|
2145
|
+
const parts = file.split(sep3);
|
|
2146
|
+
const tmpIdx = parts.lastIndexOf("tmp");
|
|
2147
|
+
if (tmpIdx >= 0) {
|
|
2148
|
+
const candidate = parts[tmpIdx + 1];
|
|
2149
|
+
if (candidate && candidate !== "chats") return candidate;
|
|
2150
|
+
}
|
|
2151
|
+
const chatsIdx = parts.lastIndexOf("chats");
|
|
2152
|
+
if (chatsIdx > 0) {
|
|
2153
|
+
const cand = parts[chatsIdx - 1];
|
|
2154
|
+
if (cand && cand !== "tmp") return cand;
|
|
2155
|
+
}
|
|
2156
|
+
return "";
|
|
2157
|
+
}
|
|
2158
|
+
function truncate4(s, max = 140) {
|
|
2159
|
+
const clean = s.replace(/\s+/g, " ").trim();
|
|
2160
|
+
if (!clean) return "";
|
|
2161
|
+
return clean.length <= max ? clean : clean.slice(0, max - 1) + "\u2026";
|
|
2162
|
+
}
|
|
2163
|
+
function normalizeSink2(sink) {
|
|
2164
|
+
if (typeof sink === "function") return { emit: sink, enrich: () => {
|
|
2165
|
+
} };
|
|
2166
|
+
return sink;
|
|
2167
|
+
}
|
|
2168
|
+
function swallow3(err) {
|
|
2169
|
+
if (typeof err !== "object" || err === null) return;
|
|
2170
|
+
const code = err.code;
|
|
2171
|
+
if (code === "EMFILE" || code === "ENOSPC" || code === "EACCES") return;
|
|
2172
|
+
console.error("[agentwatch/gemini]", String(err));
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
// src/adapters/fs-watcher.ts
|
|
2176
|
+
import chokidar5 from "chokidar";
|
|
917
2177
|
var DEFAULT_IGNORES = [
|
|
918
2178
|
/(^|[/\\])node_modules([/\\]|$)/,
|
|
919
2179
|
/(^|[/\\])\.git([/\\]|$)/,
|
|
@@ -938,8 +2198,9 @@ var DEFAULT_IGNORES = [
|
|
|
938
2198
|
/yarn\.lock$/,
|
|
939
2199
|
/bun\.lockb$/
|
|
940
2200
|
];
|
|
941
|
-
function startFsAdapter(root,
|
|
942
|
-
const
|
|
2201
|
+
function startFsAdapter(root, sink) {
|
|
2202
|
+
const emit = typeof sink === "function" ? sink : sink.emit;
|
|
2203
|
+
const watcher = chokidar5.watch(root, {
|
|
943
2204
|
persistent: true,
|
|
944
2205
|
ignoreInitial: true,
|
|
945
2206
|
ignored: (p) => DEFAULT_IGNORES.some((r) => r.test(p)),
|
|
@@ -950,6 +2211,7 @@ function startFsAdapter(root, emit) {
|
|
|
950
2211
|
console.error("[agentwatch/fs]", String(err));
|
|
951
2212
|
});
|
|
952
2213
|
const emitFs = (path) => {
|
|
2214
|
+
if (wasRecentlyWrittenByAgent(path)) return;
|
|
953
2215
|
const event = {
|
|
954
2216
|
id: nextId(),
|
|
955
2217
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -973,15 +2235,71 @@ function isSuppressible(err) {
|
|
|
973
2235
|
return code === "EMFILE" || code === "ENOSPC" || code === "EACCES";
|
|
974
2236
|
}
|
|
975
2237
|
|
|
2238
|
+
// src/adapters/registry.ts
|
|
2239
|
+
function startAllAdapters(sink, workspace) {
|
|
2240
|
+
const started = [];
|
|
2241
|
+
started.push({
|
|
2242
|
+
name: "claude-code",
|
|
2243
|
+
stop: wrap2(() => startClaudeAdapter(sink), "claude-code")
|
|
2244
|
+
});
|
|
2245
|
+
started.push({
|
|
2246
|
+
name: "openclaw",
|
|
2247
|
+
stop: wrap2(() => startOpenClawAdapter(sink), "openclaw")
|
|
2248
|
+
});
|
|
2249
|
+
const cursor = safeStart(() => startCursorAdapter(workspace, sink), "cursor");
|
|
2250
|
+
if (cursor) {
|
|
2251
|
+
started.push({
|
|
2252
|
+
name: "cursor",
|
|
2253
|
+
stop: cursor.stop,
|
|
2254
|
+
status: cursor.status
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2257
|
+
started.push({
|
|
2258
|
+
name: "gemini",
|
|
2259
|
+
stop: wrap2(() => startGeminiAdapter(sink), "gemini")
|
|
2260
|
+
});
|
|
2261
|
+
started.push({
|
|
2262
|
+
name: "fs-watcher",
|
|
2263
|
+
stop: wrap2(() => startFsAdapter(workspace, sink), "fs-watcher")
|
|
2264
|
+
});
|
|
2265
|
+
return started;
|
|
2266
|
+
}
|
|
2267
|
+
function stopAllAdapters(adapters) {
|
|
2268
|
+
for (const a of adapters) {
|
|
2269
|
+
try {
|
|
2270
|
+
a.stop();
|
|
2271
|
+
} catch (err) {
|
|
2272
|
+
console.error(`[agentwatch] adapter ${a.name} stop failed:`, err);
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
function wrap2(start, name) {
|
|
2277
|
+
try {
|
|
2278
|
+
return start();
|
|
2279
|
+
} catch (err) {
|
|
2280
|
+
console.error(`[agentwatch] adapter ${name} failed to start:`, err);
|
|
2281
|
+
return () => {
|
|
2282
|
+
};
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
function safeStart(start, name) {
|
|
2286
|
+
try {
|
|
2287
|
+
return start();
|
|
2288
|
+
} catch (err) {
|
|
2289
|
+
console.error(`[agentwatch] adapter ${name} failed to start:`, err);
|
|
2290
|
+
return null;
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
|
|
976
2294
|
// src/util/claude-permissions.ts
|
|
977
|
-
import { existsSync as
|
|
978
|
-
import { homedir as
|
|
979
|
-
import { join as
|
|
2295
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
|
|
2296
|
+
import { homedir as homedir4 } from "os";
|
|
2297
|
+
import { join as join4 } from "path";
|
|
980
2298
|
function readClaudePermissions(workspace) {
|
|
981
|
-
const sources = [
|
|
2299
|
+
const sources = [join4(homedir4(), ".claude", "settings.json")];
|
|
982
2300
|
if (workspace) {
|
|
983
|
-
sources.push(
|
|
984
|
-
sources.push(
|
|
2301
|
+
sources.push(join4(workspace, ".claude", "settings.json"));
|
|
2302
|
+
sources.push(join4(workspace, ".claude", "settings.local.json"));
|
|
985
2303
|
}
|
|
986
2304
|
const out = [];
|
|
987
2305
|
for (const path of sources) {
|
|
@@ -991,9 +2309,9 @@ function readClaudePermissions(workspace) {
|
|
|
991
2309
|
return out;
|
|
992
2310
|
}
|
|
993
2311
|
function readOne(path) {
|
|
994
|
-
if (!
|
|
2312
|
+
if (!existsSync5(path)) return null;
|
|
995
2313
|
try {
|
|
996
|
-
const raw =
|
|
2314
|
+
const raw = readFileSync3(path, "utf8");
|
|
997
2315
|
const obj = JSON.parse(raw);
|
|
998
2316
|
const perms = obj.permissions ?? {};
|
|
999
2317
|
const allow = toStringArray(perms.allow);
|
|
@@ -1051,9 +2369,58 @@ function assessRisk({
|
|
|
1051
2369
|
return flags;
|
|
1052
2370
|
}
|
|
1053
2371
|
|
|
2372
|
+
// src/util/openclaw-config.ts
|
|
2373
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
2374
|
+
import { homedir as homedir5 } from "os";
|
|
2375
|
+
import { join as join5 } from "path";
|
|
2376
|
+
function readOpenClawConfig() {
|
|
2377
|
+
const path = join5(homedir5(), ".openclaw", "openclaw.json");
|
|
2378
|
+
if (!existsSync6(path)) return null;
|
|
2379
|
+
try {
|
|
2380
|
+
const raw = readFileSync4(path, "utf8");
|
|
2381
|
+
const obj = JSON.parse(raw);
|
|
2382
|
+
const agentsObj = obj.agents ?? {};
|
|
2383
|
+
const defaults = agentsObj.defaults ?? {};
|
|
2384
|
+
const list = Array.isArray(agentsObj.list) ? agentsObj.list : [];
|
|
2385
|
+
return {
|
|
2386
|
+
source: path,
|
|
2387
|
+
defaultWorkspace: typeof defaults.workspace === "string" ? defaults.workspace : void 0,
|
|
2388
|
+
agents: list.filter(
|
|
2389
|
+
(a) => typeof a === "object" && a !== null
|
|
2390
|
+
).map((a) => {
|
|
2391
|
+
const identity = a.identity ?? {};
|
|
2392
|
+
return {
|
|
2393
|
+
id: typeof a.id === "string" ? a.id : "unknown",
|
|
2394
|
+
default: a.default === true,
|
|
2395
|
+
workspace: typeof a.workspace === "string" ? a.workspace : void 0,
|
|
2396
|
+
model: typeof a.model === "string" ? a.model : void 0,
|
|
2397
|
+
name: typeof identity.name === "string" ? identity.name : void 0,
|
|
2398
|
+
emoji: typeof identity.emoji === "string" ? identity.emoji : void 0
|
|
2399
|
+
};
|
|
2400
|
+
})
|
|
2401
|
+
};
|
|
2402
|
+
} catch {
|
|
2403
|
+
return null;
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
|
|
1054
2407
|
// src/ui/App.tsx
|
|
1055
|
-
import { jsx as
|
|
2408
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1056
2409
|
var MAX_EVENTS = 500;
|
|
2410
|
+
function matchesQuery(e, q) {
|
|
2411
|
+
const needle = q.toLowerCase();
|
|
2412
|
+
if ((e.summary ?? "").toLowerCase().includes(needle)) return true;
|
|
2413
|
+
if ((e.path ?? "").toLowerCase().includes(needle)) return true;
|
|
2414
|
+
if ((e.cmd ?? "").toLowerCase().includes(needle)) return true;
|
|
2415
|
+
if ((e.tool ?? "").toLowerCase().includes(needle)) return true;
|
|
2416
|
+
if ((e.agent ?? "").toLowerCase().includes(needle)) return true;
|
|
2417
|
+
const d = e.details;
|
|
2418
|
+
if (d) {
|
|
2419
|
+
if ((d.fullText ?? "").toLowerCase().includes(needle)) return true;
|
|
2420
|
+
if ((d.thinking ?? "").toLowerCase().includes(needle)) return true;
|
|
2421
|
+
}
|
|
2422
|
+
return false;
|
|
2423
|
+
}
|
|
1057
2424
|
function findInsertIdx(events, ts) {
|
|
1058
2425
|
let lo = 0;
|
|
1059
2426
|
let hi = events.length;
|
|
@@ -1072,54 +2439,420 @@ function reducer(state, action) {
|
|
|
1072
2439
|
const idx = findInsertIdx(next, action.event.ts);
|
|
1073
2440
|
next.splice(idx, 0, action.event);
|
|
1074
2441
|
if (next.length > MAX_EVENTS) next.length = MAX_EVENTS;
|
|
1075
|
-
|
|
2442
|
+
let sel = state.selectedIdx;
|
|
2443
|
+
if (sel !== null && idx <= sel) sel = sel + 1;
|
|
2444
|
+
return { ...state, events: next, selectedIdx: sel };
|
|
2445
|
+
}
|
|
2446
|
+
case "enrich": {
|
|
2447
|
+
const next = state.events.slice();
|
|
2448
|
+
for (let i = 0; i < next.length; i++) {
|
|
2449
|
+
if (next[i].id !== action.eventId) continue;
|
|
2450
|
+
const e = next[i];
|
|
2451
|
+
next[i] = {
|
|
2452
|
+
...e,
|
|
2453
|
+
details: { ...e.details ?? {}, ...action.patch }
|
|
2454
|
+
};
|
|
2455
|
+
return { ...state, events: next };
|
|
2456
|
+
}
|
|
2457
|
+
return state;
|
|
1076
2458
|
}
|
|
1077
2459
|
case "toggle-agents":
|
|
1078
2460
|
return { ...state, showAgents: !state.showAgents };
|
|
1079
2461
|
case "toggle-permissions":
|
|
1080
|
-
return {
|
|
2462
|
+
return {
|
|
2463
|
+
...state,
|
|
2464
|
+
showPermissions: !state.showPermissions,
|
|
2465
|
+
permissionsScroll: 0
|
|
2466
|
+
};
|
|
1081
2467
|
case "cycle-filter": {
|
|
1082
2468
|
const idx = state.filterAgent ? action.agents.indexOf(state.filterAgent) : -1;
|
|
1083
2469
|
const next = idx + 1 >= action.agents.length ? null : action.agents[idx + 1];
|
|
1084
|
-
return { ...state, filterAgent: next ?? null };
|
|
2470
|
+
return { ...state, filterAgent: next ?? null, selectedIdx: null };
|
|
1085
2471
|
}
|
|
1086
2472
|
case "toggle-pause":
|
|
1087
2473
|
return { ...state, paused: !state.paused };
|
|
1088
2474
|
case "clear":
|
|
1089
|
-
return { ...state, events: [] };
|
|
2475
|
+
return { ...state, events: [], selectedIdx: null };
|
|
2476
|
+
case "move": {
|
|
2477
|
+
if (action.max <= 0) return state;
|
|
2478
|
+
const cur = state.selectedIdx ?? -1;
|
|
2479
|
+
const next = Math.max(0, Math.min(action.max - 1, cur + action.delta));
|
|
2480
|
+
return { ...state, selectedIdx: next };
|
|
2481
|
+
}
|
|
2482
|
+
case "open-detail":
|
|
2483
|
+
if (state.selectedIdx === null) return state;
|
|
2484
|
+
return { ...state, detailOpen: true, detailScroll: 0 };
|
|
2485
|
+
case "close-detail":
|
|
2486
|
+
return { ...state, detailOpen: false, detailScroll: 0 };
|
|
2487
|
+
case "scroll-detail": {
|
|
2488
|
+
const next = Math.max(0, Math.min(action.max, state.detailScroll + action.delta));
|
|
2489
|
+
return { ...state, detailScroll: next };
|
|
2490
|
+
}
|
|
2491
|
+
case "open-search":
|
|
2492
|
+
return { ...state, searchOpen: true, selectedIdx: null };
|
|
2493
|
+
case "close-search":
|
|
2494
|
+
return { ...state, searchOpen: false, searchQuery: "" };
|
|
2495
|
+
case "confirm-search":
|
|
2496
|
+
return { ...state, searchOpen: false };
|
|
2497
|
+
case "search-input":
|
|
2498
|
+
return {
|
|
2499
|
+
...state,
|
|
2500
|
+
searchQuery: state.searchQuery + action.char,
|
|
2501
|
+
selectedIdx: null
|
|
2502
|
+
};
|
|
2503
|
+
case "search-backspace":
|
|
2504
|
+
return {
|
|
2505
|
+
...state,
|
|
2506
|
+
searchQuery: state.searchQuery.slice(0, -1),
|
|
2507
|
+
selectedIdx: null
|
|
2508
|
+
};
|
|
2509
|
+
case "scope-subagent":
|
|
2510
|
+
return {
|
|
2511
|
+
...state,
|
|
2512
|
+
subAgentScope: action.subAgentId,
|
|
2513
|
+
selectedIdx: null,
|
|
2514
|
+
detailOpen: false
|
|
2515
|
+
};
|
|
2516
|
+
case "unscope-subagent":
|
|
2517
|
+
return { ...state, subAgentScope: null, selectedIdx: null };
|
|
2518
|
+
case "toggle-projects":
|
|
2519
|
+
return {
|
|
2520
|
+
...state,
|
|
2521
|
+
projectsOpen: !state.projectsOpen,
|
|
2522
|
+
projectsSelectedIdx: 0,
|
|
2523
|
+
detailOpen: false,
|
|
2524
|
+
showPermissions: false
|
|
2525
|
+
};
|
|
2526
|
+
case "projects-move": {
|
|
2527
|
+
if (action.max <= 0) return state;
|
|
2528
|
+
const next = Math.max(
|
|
2529
|
+
0,
|
|
2530
|
+
Math.min(action.max - 1, state.projectsSelectedIdx + action.delta)
|
|
2531
|
+
);
|
|
2532
|
+
return { ...state, projectsSelectedIdx: next };
|
|
2533
|
+
}
|
|
2534
|
+
case "projects-select":
|
|
2535
|
+
return {
|
|
2536
|
+
...state,
|
|
2537
|
+
sessionsForProject: action.name,
|
|
2538
|
+
sessionsSelectedIdx: 0,
|
|
2539
|
+
sessionsScroll: 0,
|
|
2540
|
+
projectsOpen: false
|
|
2541
|
+
};
|
|
2542
|
+
case "set-project-filter":
|
|
2543
|
+
return { ...state, projectFilter: action.project, selectedIdx: null };
|
|
2544
|
+
case "scroll-permissions": {
|
|
2545
|
+
const next = Math.max(0, Math.min(action.max, state.permissionsScroll + action.delta));
|
|
2546
|
+
return { ...state, permissionsScroll: next };
|
|
2547
|
+
}
|
|
2548
|
+
case "open-sessions":
|
|
2549
|
+
return {
|
|
2550
|
+
...state,
|
|
2551
|
+
sessionsForProject: action.project,
|
|
2552
|
+
sessionsSelectedIdx: 0,
|
|
2553
|
+
sessionsScroll: 0
|
|
2554
|
+
};
|
|
2555
|
+
case "close-sessions":
|
|
2556
|
+
return { ...state, sessionsForProject: null };
|
|
2557
|
+
case "sessions-move": {
|
|
2558
|
+
if (action.max <= 0) return state;
|
|
2559
|
+
const next = Math.max(
|
|
2560
|
+
0,
|
|
2561
|
+
Math.min(action.max - 1, state.sessionsSelectedIdx + action.delta)
|
|
2562
|
+
);
|
|
2563
|
+
return { ...state, sessionsSelectedIdx: next };
|
|
2564
|
+
}
|
|
2565
|
+
case "sessions-scroll": {
|
|
2566
|
+
const next = Math.max(0, Math.min(action.max, state.sessionsScroll + action.delta));
|
|
2567
|
+
return { ...state, sessionsScroll: next };
|
|
2568
|
+
}
|
|
2569
|
+
case "sessions-open-selected":
|
|
2570
|
+
return {
|
|
2571
|
+
...state,
|
|
2572
|
+
sessionFilter: action.sessionId,
|
|
2573
|
+
sessionsForProject: null,
|
|
2574
|
+
selectedIdx: null
|
|
2575
|
+
};
|
|
2576
|
+
case "flash":
|
|
2577
|
+
return { ...state, flashMessage: action.text };
|
|
2578
|
+
case "flash-clear":
|
|
2579
|
+
return { ...state, flashMessage: null };
|
|
2580
|
+
case "toggle-help":
|
|
2581
|
+
return { ...state, showHelp: !state.showHelp };
|
|
2582
|
+
case "home":
|
|
2583
|
+
return {
|
|
2584
|
+
...state,
|
|
2585
|
+
showHelp: false,
|
|
2586
|
+
showPermissions: false,
|
|
2587
|
+
detailOpen: false,
|
|
2588
|
+
projectsOpen: false,
|
|
2589
|
+
sessionsForProject: null,
|
|
2590
|
+
projectFilter: null,
|
|
2591
|
+
sessionFilter: null,
|
|
2592
|
+
subAgentScope: null,
|
|
2593
|
+
filterAgent: null,
|
|
2594
|
+
searchQuery: "",
|
|
2595
|
+
searchOpen: false,
|
|
2596
|
+
selectedIdx: null,
|
|
2597
|
+
detailScroll: 0,
|
|
2598
|
+
permissionsScroll: 0,
|
|
2599
|
+
sessionsScroll: 0
|
|
2600
|
+
};
|
|
2601
|
+
case "clear-filters":
|
|
2602
|
+
return {
|
|
2603
|
+
...state,
|
|
2604
|
+
projectFilter: null,
|
|
2605
|
+
sessionFilter: null,
|
|
2606
|
+
subAgentScope: null,
|
|
2607
|
+
filterAgent: null,
|
|
2608
|
+
searchQuery: "",
|
|
2609
|
+
selectedIdx: null
|
|
2610
|
+
};
|
|
2611
|
+
case "back": {
|
|
2612
|
+
if (state.showHelp) return { ...state, showHelp: false };
|
|
2613
|
+
if (state.detailOpen) return { ...state, detailOpen: false, detailScroll: 0 };
|
|
2614
|
+
if (state.showPermissions)
|
|
2615
|
+
return { ...state, showPermissions: false, permissionsScroll: 0 };
|
|
2616
|
+
if (state.sessionsForProject)
|
|
2617
|
+
return { ...state, sessionsForProject: null, projectsOpen: true };
|
|
2618
|
+
if (state.projectsOpen) return { ...state, projectsOpen: false };
|
|
2619
|
+
if (state.subAgentScope)
|
|
2620
|
+
return { ...state, subAgentScope: null, selectedIdx: null };
|
|
2621
|
+
if (state.sessionFilter)
|
|
2622
|
+
return { ...state, sessionFilter: null, selectedIdx: null };
|
|
2623
|
+
if (state.projectFilter)
|
|
2624
|
+
return { ...state, projectFilter: null, selectedIdx: null };
|
|
2625
|
+
if (state.filterAgent)
|
|
2626
|
+
return { ...state, filterAgent: null, selectedIdx: null };
|
|
2627
|
+
if (state.searchQuery)
|
|
2628
|
+
return { ...state, searchQuery: "", selectedIdx: null };
|
|
2629
|
+
if (state.selectedIdx !== null) return { ...state, selectedIdx: null };
|
|
2630
|
+
return state;
|
|
2631
|
+
}
|
|
1090
2632
|
}
|
|
1091
2633
|
}
|
|
1092
2634
|
function App() {
|
|
1093
2635
|
const { exit } = useApp();
|
|
1094
2636
|
const [workspace] = useState(detectWorkspaceRoot());
|
|
1095
2637
|
const [agents] = useState(detectAgents());
|
|
1096
|
-
const [
|
|
2638
|
+
const [claudePerms] = useState(() => readClaudePermissions(workspace));
|
|
2639
|
+
const [openclawCfg] = useState(() => readOpenClawConfig());
|
|
2640
|
+
const [cursorStatus, setCursorStatus] = useState(
|
|
2641
|
+
void 0
|
|
2642
|
+
);
|
|
2643
|
+
const { stdout } = useStdout();
|
|
1097
2644
|
const [state, dispatch] = useReducer(reducer, {
|
|
1098
2645
|
events: [],
|
|
1099
2646
|
filterAgent: null,
|
|
1100
2647
|
showAgents: true,
|
|
1101
2648
|
showPermissions: false,
|
|
1102
|
-
paused: false
|
|
2649
|
+
paused: false,
|
|
2650
|
+
selectedIdx: null,
|
|
2651
|
+
detailOpen: false,
|
|
2652
|
+
detailScroll: 0,
|
|
2653
|
+
searchOpen: false,
|
|
2654
|
+
searchQuery: "",
|
|
2655
|
+
subAgentScope: null,
|
|
2656
|
+
projectsOpen: false,
|
|
2657
|
+
projectsSelectedIdx: 0,
|
|
2658
|
+
projectFilter: null,
|
|
2659
|
+
permissionsScroll: 0,
|
|
2660
|
+
sessionsForProject: null,
|
|
2661
|
+
sessionsSelectedIdx: 0,
|
|
2662
|
+
sessionsScroll: 0,
|
|
2663
|
+
sessionFilter: null,
|
|
2664
|
+
flashMessage: null,
|
|
2665
|
+
showHelp: false
|
|
1103
2666
|
});
|
|
1104
2667
|
useEffect(() => {
|
|
1105
|
-
const
|
|
1106
|
-
const
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
2668
|
+
const launchedAt = Date.now();
|
|
2669
|
+
const sink = {
|
|
2670
|
+
emit: (e) => {
|
|
2671
|
+
dispatch({ type: "event", event: e });
|
|
2672
|
+
const eventMs = new Date(e.ts).getTime();
|
|
2673
|
+
if (eventMs < launchedAt) return;
|
|
2674
|
+
const alert = shouldNotify(e);
|
|
2675
|
+
if (alert) notify(alert.title, alert.body);
|
|
2676
|
+
},
|
|
2677
|
+
enrich: (eventId, patch) => {
|
|
2678
|
+
dispatch({ type: "enrich", eventId, patch });
|
|
2679
|
+
}
|
|
1115
2680
|
};
|
|
2681
|
+
const adapters = startAllAdapters(sink, workspace);
|
|
2682
|
+
const cursorAdapter = adapters.find((a) => a.name === "cursor");
|
|
2683
|
+
if (cursorAdapter?.status) setCursorStatus(cursorAdapter.status);
|
|
2684
|
+
return () => stopAllAdapters(adapters);
|
|
1116
2685
|
}, [workspace]);
|
|
2686
|
+
const agentFiltered = state.filterAgent ? state.events.filter((e) => e.agent === state.filterAgent) : state.events;
|
|
2687
|
+
const scoped = state.subAgentScope ? agentFiltered.filter(
|
|
2688
|
+
(e) => e.sessionId === `agent-${state.subAgentScope}` || e.sessionId === state.subAgentScope || e.details?.subAgentId === state.subAgentScope
|
|
2689
|
+
) : agentFiltered;
|
|
2690
|
+
const projectScoped = state.projectFilter ? scoped.filter(
|
|
2691
|
+
(e) => (e.summary ?? "").startsWith(`[${state.projectFilter}`)
|
|
2692
|
+
) : scoped;
|
|
2693
|
+
const sessionScoped = state.sessionFilter ? projectScoped.filter((e) => e.sessionId === state.sessionFilter) : projectScoped;
|
|
2694
|
+
const filtered = state.searchQuery ? sessionScoped.filter((e) => matchesQuery(e, state.searchQuery)) : sessionScoped;
|
|
2695
|
+
const projects = buildProjectIndex(state.events);
|
|
2696
|
+
const sessionsForOpen = state.sessionsForProject ? buildSessionRows(state.events, state.sessionsForProject) : [];
|
|
2697
|
+
const childCountByAgentId = /* @__PURE__ */ new Map();
|
|
2698
|
+
for (const e of state.events) {
|
|
2699
|
+
if (e.sessionId?.startsWith("agent-")) {
|
|
2700
|
+
const aid = e.sessionId.slice("agent-".length);
|
|
2701
|
+
childCountByAgentId.set(aid, (childCountByAgentId.get(aid) ?? 0) + 1);
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
const cols = stdout.columns || 120;
|
|
2705
|
+
const rows = stdout.rows || 30;
|
|
2706
|
+
const tooNarrow = cols < 60;
|
|
2707
|
+
const tooShort = rows < 12;
|
|
2708
|
+
const selectedEvent = state.selectedIdx !== null ? filtered[state.selectedIdx] : void 0;
|
|
2709
|
+
const detailRowCount = selectedEvent ? totalDetailRows(selectedEvent, cols - 6) : 0;
|
|
1117
2710
|
useInput((input, key) => {
|
|
1118
|
-
if (
|
|
2711
|
+
if (key.ctrl && input === "c") {
|
|
2712
|
+
exit();
|
|
2713
|
+
setImmediate(() => process.exit(0));
|
|
2714
|
+
return;
|
|
2715
|
+
}
|
|
2716
|
+
if (state.searchOpen) {
|
|
2717
|
+
if (key.escape) {
|
|
2718
|
+
dispatch({ type: "close-search" });
|
|
2719
|
+
return;
|
|
2720
|
+
}
|
|
2721
|
+
if (key.return) {
|
|
2722
|
+
dispatch({ type: "confirm-search" });
|
|
2723
|
+
return;
|
|
2724
|
+
}
|
|
2725
|
+
if (key.backspace || key.delete) {
|
|
2726
|
+
dispatch({ type: "search-backspace" });
|
|
2727
|
+
return;
|
|
2728
|
+
}
|
|
2729
|
+
if (input && !key.ctrl && !key.meta) {
|
|
2730
|
+
dispatch({ type: "search-input", char: input });
|
|
2731
|
+
return;
|
|
2732
|
+
}
|
|
2733
|
+
return;
|
|
2734
|
+
}
|
|
2735
|
+
if (input === "q") {
|
|
1119
2736
|
exit();
|
|
1120
2737
|
setImmediate(() => process.exit(0));
|
|
1121
2738
|
return;
|
|
1122
2739
|
}
|
|
2740
|
+
if (state.projectsOpen) {
|
|
2741
|
+
if (key.escape) {
|
|
2742
|
+
dispatch({ type: "back" });
|
|
2743
|
+
return;
|
|
2744
|
+
}
|
|
2745
|
+
if (key.downArrow || input === "j") {
|
|
2746
|
+
dispatch({ type: "projects-move", delta: 1, max: projects.length });
|
|
2747
|
+
return;
|
|
2748
|
+
}
|
|
2749
|
+
if (key.upArrow || input === "k") {
|
|
2750
|
+
dispatch({ type: "projects-move", delta: -1, max: projects.length });
|
|
2751
|
+
return;
|
|
2752
|
+
}
|
|
2753
|
+
if (key.return) {
|
|
2754
|
+
const p = projects[state.projectsSelectedIdx];
|
|
2755
|
+
if (p) dispatch({ type: "projects-select", name: p.name });
|
|
2756
|
+
return;
|
|
2757
|
+
}
|
|
2758
|
+
return;
|
|
2759
|
+
}
|
|
2760
|
+
if (state.sessionsForProject) {
|
|
2761
|
+
const lineCount = sessionLineCount(sessionsForOpen);
|
|
2762
|
+
const viewport = Math.max(3, rows - 8);
|
|
2763
|
+
const maxScroll = Math.max(0, lineCount - viewport);
|
|
2764
|
+
if (key.escape) {
|
|
2765
|
+
dispatch({ type: "back" });
|
|
2766
|
+
return;
|
|
2767
|
+
}
|
|
2768
|
+
if (key.downArrow || input === "j") {
|
|
2769
|
+
dispatch({
|
|
2770
|
+
type: "sessions-move",
|
|
2771
|
+
delta: 1,
|
|
2772
|
+
max: sessionsForOpen.length
|
|
2773
|
+
});
|
|
2774
|
+
dispatch({ type: "sessions-scroll", delta: 1, max: maxScroll });
|
|
2775
|
+
return;
|
|
2776
|
+
}
|
|
2777
|
+
if (key.upArrow || input === "k") {
|
|
2778
|
+
dispatch({
|
|
2779
|
+
type: "sessions-move",
|
|
2780
|
+
delta: -1,
|
|
2781
|
+
max: sessionsForOpen.length
|
|
2782
|
+
});
|
|
2783
|
+
dispatch({ type: "sessions-scroll", delta: -1, max: maxScroll });
|
|
2784
|
+
return;
|
|
2785
|
+
}
|
|
2786
|
+
if (key.return) {
|
|
2787
|
+
const s = sessionsForOpen[state.sessionsSelectedIdx];
|
|
2788
|
+
if (s)
|
|
2789
|
+
dispatch({ type: "sessions-open-selected", sessionId: s.sessionId });
|
|
2790
|
+
return;
|
|
2791
|
+
}
|
|
2792
|
+
return;
|
|
2793
|
+
}
|
|
2794
|
+
if (state.showPermissions) {
|
|
2795
|
+
const total = permissionRowCount(claudePerms, cursorStatus, openclawCfg);
|
|
2796
|
+
const viewport = Math.max(3, rows - 8);
|
|
2797
|
+
const maxScroll = Math.max(0, total - viewport);
|
|
2798
|
+
if (key.escape || input === "p") {
|
|
2799
|
+
dispatch({ type: "back" });
|
|
2800
|
+
return;
|
|
2801
|
+
}
|
|
2802
|
+
if (key.downArrow || input === "j") {
|
|
2803
|
+
dispatch({ type: "scroll-permissions", delta: 1, max: maxScroll });
|
|
2804
|
+
return;
|
|
2805
|
+
}
|
|
2806
|
+
if (key.upArrow || input === "k") {
|
|
2807
|
+
dispatch({ type: "scroll-permissions", delta: -1, max: maxScroll });
|
|
2808
|
+
return;
|
|
2809
|
+
}
|
|
2810
|
+
return;
|
|
2811
|
+
}
|
|
2812
|
+
if (state.detailOpen) {
|
|
2813
|
+
if (key.escape) {
|
|
2814
|
+
dispatch({ type: "back" });
|
|
2815
|
+
return;
|
|
2816
|
+
}
|
|
2817
|
+
if (key.downArrow || input === "j") {
|
|
2818
|
+
dispatch({ type: "scroll-detail", delta: 1, max: Math.max(0, detailRowCount - (rows - 10)) });
|
|
2819
|
+
return;
|
|
2820
|
+
}
|
|
2821
|
+
if (key.upArrow || input === "k") {
|
|
2822
|
+
dispatch({ type: "scroll-detail", delta: -1, max: Math.max(0, detailRowCount - (rows - 10)) });
|
|
2823
|
+
return;
|
|
2824
|
+
}
|
|
2825
|
+
return;
|
|
2826
|
+
}
|
|
2827
|
+
if (input === "/") dispatch({ type: "open-search" });
|
|
2828
|
+
if (input === "x" && state.selectedIdx !== null) {
|
|
2829
|
+
const ev = filtered[state.selectedIdx];
|
|
2830
|
+
const sid = ev?.details?.subAgentId;
|
|
2831
|
+
if (sid) dispatch({ type: "scope-subagent", subAgentId: sid });
|
|
2832
|
+
}
|
|
2833
|
+
if (input === "X") dispatch({ type: "unscope-subagent" });
|
|
2834
|
+
if (input === "y" && state.selectedIdx !== null) {
|
|
2835
|
+
const ev = filtered[state.selectedIdx];
|
|
2836
|
+
if (ev) {
|
|
2837
|
+
const text = eventToYankText(
|
|
2838
|
+
ev.summary,
|
|
2839
|
+
ev.path,
|
|
2840
|
+
ev.cmd,
|
|
2841
|
+
ev.details?.toolResult,
|
|
2842
|
+
ev.details?.fullText
|
|
2843
|
+
);
|
|
2844
|
+
if (text) {
|
|
2845
|
+
const res = copyToClipboard(text);
|
|
2846
|
+
const message = res.ok ? `\u2713 copied ${text.length} chars to clipboard` : `\u2717 ${res.reason}`;
|
|
2847
|
+
dispatch({ type: "flash", text: message });
|
|
2848
|
+
setTimeout(() => dispatch({ type: "flash-clear" }), 2e3);
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
if (input === "P") dispatch({ type: "toggle-projects" });
|
|
2853
|
+
if (input === "A") {
|
|
2854
|
+
dispatch({ type: "set-project-filter", project: null });
|
|
2855
|
+
}
|
|
1123
2856
|
if (input === "a") dispatch({ type: "toggle-agents" });
|
|
1124
2857
|
if (input === "f") {
|
|
1125
2858
|
const presentAgents = agents.filter((a) => a.present).map((a) => a.name);
|
|
@@ -1129,10 +2862,31 @@ function App() {
|
|
|
1129
2862
|
if (input === " ") dispatch({ type: "toggle-pause" });
|
|
1130
2863
|
if (input === "p") dispatch({ type: "toggle-permissions" });
|
|
1131
2864
|
if (input === "c") dispatch({ type: "clear" });
|
|
2865
|
+
if (key.downArrow || input === "j")
|
|
2866
|
+
dispatch({ type: "move", delta: 1, max: filtered.length });
|
|
2867
|
+
if (key.upArrow || input === "k")
|
|
2868
|
+
dispatch({ type: "move", delta: -1, max: filtered.length });
|
|
2869
|
+
if (key.return || input === "l") dispatch({ type: "open-detail" });
|
|
2870
|
+
if (key.escape) dispatch({ type: "back" });
|
|
1132
2871
|
});
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
2872
|
+
if (tooNarrow || tooShort) {
|
|
2873
|
+
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", padding: 1, children: [
|
|
2874
|
+
/* @__PURE__ */ jsx10(Text10, { color: "yellow", bold: true, children: "Terminal too small for the agentwatch TUI" }),
|
|
2875
|
+
/* @__PURE__ */ jsxs10(Text10, { children: [
|
|
2876
|
+
"Detected: ",
|
|
2877
|
+
cols,
|
|
2878
|
+
" cols \xD7 ",
|
|
2879
|
+
rows,
|
|
2880
|
+
" rows"
|
|
2881
|
+
] }),
|
|
2882
|
+
/* @__PURE__ */ jsx10(Text10, { children: "Minimum: 60 cols \xD7 12 rows" }),
|
|
2883
|
+
/* @__PURE__ */ jsx10(Text10, { children: " " }),
|
|
2884
|
+
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "Resize the window and restart, or run `agentwatch doctor` for a compact view." }),
|
|
2885
|
+
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "Press q to quit." })
|
|
2886
|
+
] });
|
|
2887
|
+
}
|
|
2888
|
+
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
|
|
2889
|
+
/* @__PURE__ */ jsx10(
|
|
1136
2890
|
Header,
|
|
1137
2891
|
{
|
|
1138
2892
|
workspace,
|
|
@@ -1141,20 +2895,80 @@ function App() {
|
|
|
1141
2895
|
paused: state.paused
|
|
1142
2896
|
}
|
|
1143
2897
|
),
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
2898
|
+
/* @__PURE__ */ jsx10(
|
|
2899
|
+
Breadcrumb,
|
|
2900
|
+
{
|
|
2901
|
+
projectFilter: state.projectFilter,
|
|
2902
|
+
sessionFilter: state.sessionFilter,
|
|
2903
|
+
sessionsForProject: state.sessionsForProject,
|
|
2904
|
+
subAgentScope: state.subAgentScope,
|
|
2905
|
+
agentFilter: state.filterAgent,
|
|
2906
|
+
searchQuery: state.searchQuery,
|
|
2907
|
+
view: state.showHelp ? "help" : state.detailOpen ? "detail" : state.showPermissions ? "permissions" : state.sessionsForProject ? "sessions" : state.projectsOpen ? "projects" : "timeline"
|
|
2908
|
+
}
|
|
2909
|
+
),
|
|
2910
|
+
state.showHelp ? /* @__PURE__ */ jsx10(HelpView, {}) : state.sessionsForProject ? /* @__PURE__ */ jsx10(
|
|
2911
|
+
SessionsView,
|
|
2912
|
+
{
|
|
2913
|
+
project: state.sessionsForProject,
|
|
2914
|
+
sessions: sessionsForOpen,
|
|
2915
|
+
selectedIdx: state.sessionsSelectedIdx,
|
|
2916
|
+
viewportRows: Math.max(3, rows - 8),
|
|
2917
|
+
scrollOffset: state.sessionsScroll
|
|
2918
|
+
}
|
|
2919
|
+
) : state.projectsOpen ? /* @__PURE__ */ jsx10(
|
|
2920
|
+
ProjectsView,
|
|
2921
|
+
{
|
|
2922
|
+
projects,
|
|
2923
|
+
selectedIdx: state.projectsSelectedIdx,
|
|
2924
|
+
searchQuery: state.searchQuery
|
|
2925
|
+
}
|
|
2926
|
+
) : state.detailOpen && selectedEvent ? /* @__PURE__ */ jsx10(
|
|
2927
|
+
EventDetail,
|
|
2928
|
+
{
|
|
2929
|
+
event: selectedEvent,
|
|
2930
|
+
width: cols,
|
|
2931
|
+
height: rows - 4,
|
|
2932
|
+
scrollOffset: state.detailScroll
|
|
2933
|
+
}
|
|
2934
|
+
) : state.showPermissions ? /* @__PURE__ */ jsx10(
|
|
2935
|
+
PermissionView,
|
|
2936
|
+
{
|
|
2937
|
+
claude: claudePerms,
|
|
2938
|
+
cursor: cursorStatus,
|
|
2939
|
+
openclaw: openclawCfg,
|
|
2940
|
+
viewportRows: Math.max(3, rows - 8),
|
|
2941
|
+
scrollOffset: state.permissionsScroll
|
|
2942
|
+
}
|
|
2943
|
+
) : /* @__PURE__ */ jsxs10(Box10, { flexDirection: "row", children: [
|
|
2944
|
+
/* @__PURE__ */ jsx10(Box10, { flexGrow: 1, flexDirection: "column", children: /* @__PURE__ */ jsx10(
|
|
2945
|
+
Timeline,
|
|
2946
|
+
{
|
|
2947
|
+
events: filtered,
|
|
2948
|
+
selectedIdx: state.selectedIdx,
|
|
2949
|
+
childCountByAgentId
|
|
2950
|
+
}
|
|
2951
|
+
) }),
|
|
2952
|
+
state.showAgents && /* @__PURE__ */ jsx10(Box10, { width: 32, marginLeft: 1, children: /* @__PURE__ */ jsx10(AgentPanel, { agents, events: state.events }) })
|
|
1147
2953
|
] }),
|
|
1148
|
-
/* @__PURE__ */
|
|
1149
|
-
|
|
1150
|
-
state.
|
|
1151
|
-
|
|
1152
|
-
|
|
2954
|
+
/* @__PURE__ */ jsxs10(Box10, { marginTop: 1, flexDirection: "column", children: [
|
|
2955
|
+
state.flashMessage && /* @__PURE__ */ jsx10(Text10, { color: state.flashMessage.startsWith("\u2713") ? "green" : "red", children: state.flashMessage }),
|
|
2956
|
+
(state.searchOpen || state.searchQuery) && /* @__PURE__ */ jsxs10(Text10, { children: [
|
|
2957
|
+
/* @__PURE__ */ jsx10(Text10, { color: "yellow", children: "/ " }),
|
|
2958
|
+
/* @__PURE__ */ jsx10(Text10, { children: state.searchQuery }),
|
|
2959
|
+
state.searchOpen && /* @__PURE__ */ jsx10(Text10, { color: "yellow", children: "\u258C" }),
|
|
2960
|
+
state.searchQuery && /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
|
|
2961
|
+
" matches: ",
|
|
2962
|
+
filtered.length
|
|
2963
|
+
] })
|
|
2964
|
+
] }),
|
|
2965
|
+
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: state.searchOpen ? "[type to search] [enter] confirm [esc] clear" : state.sessionsForProject ? "[\u2191\u2193] select session [enter] open [esc] back to projects" : state.projectsOpen ? "[\u2191\u2193] select project [enter] sessions [esc] close" : state.detailOpen ? "[esc] close [\u2191\u2193] scroll" : `[?] help [q] quit [0] home [esc] back [\u2191\u2193] select [enter] detail [/] search [P] projects [p] permissions [Z] clear filters` })
|
|
2966
|
+
] })
|
|
1153
2967
|
] });
|
|
1154
2968
|
}
|
|
1155
2969
|
|
|
1156
2970
|
// src/index.tsx
|
|
1157
|
-
import { jsx as
|
|
2971
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
1158
2972
|
var arg = process.argv[2];
|
|
1159
2973
|
var ENTER_ALT_SCREEN = "\x1B[?1049h\x1B[2J\x1B[H";
|
|
1160
2974
|
var LEAVE_ALT_SCREEN = "\x1B[?1049l";
|
|
@@ -1185,7 +2999,7 @@ Environment:
|
|
|
1185
2999
|
process.exit(0);
|
|
1186
3000
|
}
|
|
1187
3001
|
if (arg === "doctor") {
|
|
1188
|
-
const { detectAgents: detectAgents2 } = await import("./detect-
|
|
3002
|
+
const { detectAgents: detectAgents2 } = await import("./detect-JH6COHZ5.js");
|
|
1189
3003
|
const { detectWorkspaceRoot: detectWorkspaceRoot2 } = await import("./workspace-N6FQVHKD.js");
|
|
1190
3004
|
const agents = detectAgents2();
|
|
1191
3005
|
console.log(`workspace: ${detectWorkspaceRoot2()}
|
|
@@ -1193,10 +3007,23 @@ if (arg === "doctor") {
|
|
|
1193
3007
|
console.log("agents:");
|
|
1194
3008
|
for (const a of agents) {
|
|
1195
3009
|
const mark = a.present ? "\u25CF" : "\u25CB";
|
|
1196
|
-
const status = a.present ? "installed" : "not
|
|
1197
|
-
console.log(` ${mark} ${a.label.padEnd(
|
|
3010
|
+
const status = !a.present ? "not detected" : a.instrumented ? "installed (events captured)" : "detected (events not yet captured \u2014 help us ship this)";
|
|
3011
|
+
console.log(` ${mark} ${a.label.padEnd(18)} ${status}`);
|
|
1198
3012
|
if (a.configPath) console.log(` config: ${a.configPath}`);
|
|
1199
3013
|
}
|
|
3014
|
+
const notInstrumented = agents.filter((a) => a.present && !a.instrumented);
|
|
3015
|
+
if (notInstrumented.length > 0) {
|
|
3016
|
+
console.log("");
|
|
3017
|
+
console.log("Agents detected but not yet instrumented:");
|
|
3018
|
+
for (const a of notInstrumented) {
|
|
3019
|
+
console.log(` - ${a.label}`);
|
|
3020
|
+
}
|
|
3021
|
+
console.log("");
|
|
3022
|
+
console.log(
|
|
3023
|
+
"If you want events captured for these, open an issue with a redacted session file:"
|
|
3024
|
+
);
|
|
3025
|
+
console.log(" https://github.com/mishanefedov/agentwatch/issues/new");
|
|
3026
|
+
}
|
|
1200
3027
|
process.exit(0);
|
|
1201
3028
|
}
|
|
1202
3029
|
enterAltScreen();
|
|
@@ -1206,5 +3033,5 @@ for (const sig of ["exit", "SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
|
1206
3033
|
if (sig !== "exit") process.exit(0);
|
|
1207
3034
|
});
|
|
1208
3035
|
}
|
|
1209
|
-
var { waitUntilExit } = render(/* @__PURE__ */
|
|
3036
|
+
var { waitUntilExit } = render(/* @__PURE__ */ jsx11(App, {}));
|
|
1210
3037
|
waitUntilExit().finally(() => leaveAltScreen());
|