cogpit-memory 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +147 -0
  2. package/dist/cli.js +2228 -0
  3. package/dist/index.js +2175 -0
  4. package/package.json +27 -0
package/dist/cli.js ADDED
@@ -0,0 +1,2228 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/cli.ts
32
+ var cli_exports = {};
33
+ __export(cli_exports, {
34
+ parseArgs: () => parseArgs
35
+ });
36
+ module.exports = __toCommonJS(cli_exports);
37
+
38
+ // src/commands/search.ts
39
+ var import_node_fs2 = require("node:fs");
40
+ var import_promises2 = require("node:fs/promises");
41
+ var import_node_path4 = require("node:path");
42
+
43
+ // src/lib/sqlite-node-shim.ts
44
+ var import_better_sqlite3 = __toESM(require("better-sqlite3"));
45
+ var Database = import_better_sqlite3.default;
46
+
47
+ // src/lib/search-index.ts
48
+ var import_node_fs = require("node:fs");
49
+ var import_node_path = require("node:path");
50
+
51
+ // src/lib/turnBuilder.ts
52
+ function isUserMessage(msg) {
53
+ return msg.type === "user";
54
+ }
55
+ function isAssistantMessage(msg) {
56
+ return msg.type === "assistant";
57
+ }
58
+ function isProgressMessage(msg) {
59
+ return msg.type === "progress";
60
+ }
61
+ function isSystemMessage(msg) {
62
+ return msg.type === "system";
63
+ }
64
+ function isSummaryMessage(msg) {
65
+ return msg.type === "summary";
66
+ }
67
+ function extractTextFromContent(content) {
68
+ if (typeof content === "string") return content;
69
+ return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
70
+ }
71
+ function extractToolResultText(content) {
72
+ if (!content) return "";
73
+ if (typeof content === "string") return content;
74
+ return content.map((b) => {
75
+ if (b.type === "text") return b.text;
76
+ return "";
77
+ }).filter(Boolean).join("\n");
78
+ }
79
+ function mergeTokenUsage(existing, incoming) {
80
+ if (!existing) {
81
+ return { ...incoming };
82
+ }
83
+ return {
84
+ input_tokens: existing.input_tokens + incoming.input_tokens,
85
+ output_tokens: existing.output_tokens + incoming.output_tokens,
86
+ cache_creation_input_tokens: (existing.cache_creation_input_tokens ?? 0) + (incoming.cache_creation_input_tokens ?? 0),
87
+ cache_read_input_tokens: (existing.cache_read_input_tokens ?? 0) + (incoming.cache_read_input_tokens ?? 0)
88
+ };
89
+ }
90
+ function buildCompactionSummary(turns, title) {
91
+ if (turns.length === 0) return title;
92
+ const toolCounts = {};
93
+ for (const turn of turns) {
94
+ for (const tc of turn.toolCalls) {
95
+ toolCounts[tc.name] = (toolCounts[tc.name] || 0) + 1;
96
+ }
97
+ }
98
+ const prompts = [];
99
+ for (const turn of turns) {
100
+ if (!turn.userMessage) continue;
101
+ const text = extractTextFromContent(
102
+ typeof turn.userMessage === "string" ? turn.userMessage : turn.userMessage
103
+ );
104
+ const firstLine = text.split("\n")[0].trim();
105
+ if (firstLine.length > 0) {
106
+ prompts.push(firstLine.length > 120 ? firstLine.slice(0, 117) + "..." : firstLine);
107
+ }
108
+ }
109
+ const topTools = Object.entries(toolCounts).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name, count]) => `${name} x${count}`).join(", ");
110
+ const parts = [`**${title}**`, `${turns.length} turns compacted`];
111
+ if (topTools) parts.push(`Tools: ${topTools}`);
112
+ if (prompts.length > 0) {
113
+ parts.push("Prompts:");
114
+ const shown = prompts.slice(0, 6);
115
+ for (const p of shown) parts.push(`- ${p}`);
116
+ if (prompts.length > 6) parts.push(`- ...and ${prompts.length - 6} more`);
117
+ }
118
+ return parts.join("\n");
119
+ }
120
+ function buildTurns(messages) {
121
+ const turns = [];
122
+ let current = null;
123
+ let pendingCompaction = null;
124
+ const pendingToolUses = /* @__PURE__ */ new Map();
125
+ const seenMessageIds = /* @__PURE__ */ new Set();
126
+ const subAgentMap = /* @__PURE__ */ new Map();
127
+ const agentBlockMap = /* @__PURE__ */ new Map();
128
+ const backgroundAgentParentIds = /* @__PURE__ */ new Set();
129
+ const taskMetaMap = /* @__PURE__ */ new Map();
130
+ function flushSubAgentMessages(parentId) {
131
+ if (!current) return;
132
+ const agentMsgs = subAgentMap.get(parentId);
133
+ if (!agentMsgs || agentMsgs.length === 0) return;
134
+ current.subAgentActivity.push(...agentMsgs);
135
+ subAgentMap.delete(parentId);
136
+ const kind = backgroundAgentParentIds.has(parentId) ? "background_agent" : "sub_agent";
137
+ const existingBlock = agentBlockMap.get(parentId);
138
+ if (existingBlock) {
139
+ existingBlock.messages.push(...agentMsgs);
140
+ } else {
141
+ const block = { kind, messages: [...agentMsgs] };
142
+ current.contentBlocks.push(block);
143
+ agentBlockMap.set(parentId, block);
144
+ }
145
+ }
146
+ function finalizeTurn() {
147
+ if (!current) return;
148
+ for (const tc of current.toolCalls) {
149
+ flushSubAgentMessages(tc.id);
150
+ }
151
+ for (const [parentId] of subAgentMap) {
152
+ flushSubAgentMessages(parentId);
153
+ }
154
+ turns.push(current);
155
+ current = null;
156
+ agentBlockMap.clear();
157
+ }
158
+ for (const msg of messages) {
159
+ if (isSummaryMessage(msg)) {
160
+ finalizeTurn();
161
+ pendingCompaction = buildCompactionSummary(
162
+ turns,
163
+ msg.summary ?? "Conversation compacted"
164
+ );
165
+ continue;
166
+ }
167
+ if (isUserMessage(msg) && !msg.isMeta) {
168
+ const content = msg.message.content;
169
+ if (typeof content !== "string" && Array.isArray(content)) {
170
+ const hasToolResult = content.some((b) => b.type === "tool_result");
171
+ if (hasToolResult && current) {
172
+ for (const block of content) {
173
+ if (block.type === "tool_result") {
174
+ const pending = pendingToolUses.get(block.tool_use_id);
175
+ if (pending) {
176
+ pending.turn.toolCalls[pending.index].result = extractToolResultText(block.content);
177
+ pending.turn.toolCalls[pending.index].isError = block.is_error;
178
+ pendingToolUses.delete(block.tool_use_id);
179
+ }
180
+ }
181
+ }
182
+ const toolUseResult = msg.toolUseResult;
183
+ if (toolUseResult?.agentId) {
184
+ const toolUseId = content.find((b) => b.type === "tool_result")?.tool_use_id ?? "";
185
+ const taskMeta = taskMetaMap.get(toolUseId);
186
+ const isBackground = backgroundAgentParentIds.has(toolUseId);
187
+ const resultText = [];
188
+ if (Array.isArray(toolUseResult.content)) {
189
+ for (const block of toolUseResult.content) {
190
+ if (block.type === "text") resultText.push(block.text);
191
+ }
192
+ }
193
+ const agentMsg = {
194
+ agentId: toolUseResult.agentId,
195
+ agentName: taskMeta?.name ?? null,
196
+ subagentType: taskMeta?.subagentType ?? null,
197
+ type: "assistant",
198
+ content: toolUseResult.content,
199
+ toolCalls: [],
200
+ thinking: [],
201
+ text: resultText,
202
+ timestamp: msg.timestamp ?? "",
203
+ tokenUsage: toolUseResult.usage ? {
204
+ input_tokens: toolUseResult.usage.input_tokens ?? 0,
205
+ output_tokens: toolUseResult.usage.output_tokens ?? 0,
206
+ cache_creation_input_tokens: toolUseResult.usage.cache_creation_input_tokens ?? 0,
207
+ cache_read_input_tokens: toolUseResult.usage.cache_read_input_tokens ?? 0
208
+ } : null,
209
+ model: null,
210
+ isBackground,
211
+ prompt: toolUseResult.prompt,
212
+ status: toolUseResult.status,
213
+ durationMs: toolUseResult.totalDurationMs,
214
+ toolUseCount: toolUseResult.totalToolUseCount
215
+ };
216
+ const existingBlock = agentBlockMap.get(toolUseId);
217
+ if (!existingBlock) {
218
+ current.subAgentActivity.push(agentMsg);
219
+ const kind = isBackground ? "background_agent" : "sub_agent";
220
+ const block = { kind, messages: [agentMsg] };
221
+ current.contentBlocks.push(block);
222
+ agentBlockMap.set(toolUseId, block);
223
+ }
224
+ }
225
+ continue;
226
+ }
227
+ }
228
+ finalizeTurn();
229
+ current = {
230
+ id: msg.uuid ?? crypto.randomUUID(),
231
+ userMessage: msg.message.content,
232
+ contentBlocks: [],
233
+ thinking: [],
234
+ assistantText: [],
235
+ toolCalls: [],
236
+ subAgentActivity: [],
237
+ timestamp: msg.timestamp ?? "",
238
+ durationMs: null,
239
+ tokenUsage: null,
240
+ model: null
241
+ };
242
+ if (pendingCompaction) {
243
+ current.compactionSummary = pendingCompaction;
244
+ pendingCompaction = null;
245
+ }
246
+ continue;
247
+ }
248
+ if (isAssistantMessage(msg)) {
249
+ let flushToolCalls2 = function() {
250
+ if (msgToolCalls.length > 0) {
251
+ activeTurn.contentBlocks.push({ kind: "tool_calls", toolCalls: [...msgToolCalls], timestamp: msgTs });
252
+ msgToolCalls.length = 0;
253
+ }
254
+ }, flushThinking2 = function() {
255
+ if (msgThinking.length > 0) {
256
+ const last = activeTurn.contentBlocks[activeTurn.contentBlocks.length - 1];
257
+ if (last && last.kind === "thinking") {
258
+ last.blocks.push(...msgThinking);
259
+ } else {
260
+ activeTurn.contentBlocks.push({ kind: "thinking", blocks: [...msgThinking], timestamp: msgTs });
261
+ }
262
+ msgThinking.length = 0;
263
+ }
264
+ };
265
+ var flushToolCalls = flushToolCalls2, flushThinking = flushThinking2;
266
+ if (!current) {
267
+ current = {
268
+ id: msg.uuid ?? crypto.randomUUID(),
269
+ userMessage: null,
270
+ contentBlocks: [],
271
+ thinking: [],
272
+ assistantText: [],
273
+ toolCalls: [],
274
+ subAgentActivity: [],
275
+ timestamp: msg.timestamp ?? "",
276
+ durationMs: null,
277
+ tokenUsage: null,
278
+ model: null
279
+ };
280
+ }
281
+ current.model = msg.message.model;
282
+ const msgId = msg.message.id;
283
+ if (!seenMessageIds.has(msgId)) {
284
+ seenMessageIds.add(msgId);
285
+ current.tokenUsage = mergeTokenUsage(current.tokenUsage, msg.message.usage);
286
+ }
287
+ const msgTs = msg.timestamp ?? "";
288
+ const msgThinking = [];
289
+ const msgToolCalls = [];
290
+ const activeTurn = current;
291
+ for (const block of msg.message.content) {
292
+ if (block.type === "thinking") {
293
+ flushToolCalls2();
294
+ const tb = block;
295
+ current.thinking.push(tb);
296
+ msgThinking.push(tb);
297
+ } else if (block.type === "text") {
298
+ flushToolCalls2();
299
+ flushThinking2();
300
+ const thinkingRegex = /<thinking>([\s\S]*?)<\/thinking>/g;
301
+ let remaining = block.text;
302
+ let match;
303
+ while ((match = thinkingRegex.exec(block.text)) !== null) {
304
+ const thinkingText = match[1].trim();
305
+ if (thinkingText) {
306
+ const tb = { type: "thinking", thinking: thinkingText, signature: "" };
307
+ current.thinking.push(tb);
308
+ current.contentBlocks.push({ kind: "thinking", blocks: [tb], timestamp: msgTs });
309
+ }
310
+ remaining = remaining.replace(match[0], "");
311
+ }
312
+ remaining = remaining.trim();
313
+ if (remaining) {
314
+ current.assistantText.push(remaining);
315
+ const last = current.contentBlocks[current.contentBlocks.length - 1];
316
+ if (last && last.kind === "text") {
317
+ last.text.push(remaining);
318
+ } else {
319
+ current.contentBlocks.push({ kind: "text", text: [remaining], timestamp: msgTs });
320
+ }
321
+ }
322
+ } else if (block.type === "tool_use") {
323
+ flushThinking2();
324
+ const tc = {
325
+ id: block.id,
326
+ name: block.name,
327
+ input: block.input,
328
+ result: null,
329
+ isError: false,
330
+ timestamp: msg.timestamp ?? ""
331
+ };
332
+ const idx = current.toolCalls.length;
333
+ current.toolCalls.push(tc);
334
+ msgToolCalls.push(tc);
335
+ pendingToolUses.set(block.id, { turn: current, index: idx });
336
+ if (block.name === "Task" || block.name === "Agent") {
337
+ const input = block.input;
338
+ if (input.run_in_background === true) {
339
+ backgroundAgentParentIds.add(block.id);
340
+ }
341
+ taskMetaMap.set(block.id, {
342
+ name: input.name ?? null,
343
+ subagentType: input.subagent_type ?? null
344
+ });
345
+ }
346
+ }
347
+ }
348
+ flushThinking2();
349
+ flushToolCalls2();
350
+ continue;
351
+ }
352
+ if (isProgressMessage(msg) && msg.data.type === "agent_progress") {
353
+ const data = msg.data;
354
+ const parentId = msg.parentToolUseID ?? "";
355
+ let subAgentUsage = null;
356
+ if (data.message.type === "assistant") {
357
+ const innerMsg = data.message.message;
358
+ const msgId = innerMsg.id;
359
+ const usage = innerMsg.usage;
360
+ if (usage && msgId && !seenMessageIds.has(msgId)) {
361
+ seenMessageIds.add(msgId);
362
+ subAgentUsage = {
363
+ input_tokens: usage.input_tokens ?? 0,
364
+ output_tokens: usage.output_tokens ?? 0,
365
+ cache_creation_input_tokens: usage.cache_creation_input_tokens ?? 0,
366
+ cache_read_input_tokens: usage.cache_read_input_tokens ?? 0
367
+ };
368
+ }
369
+ }
370
+ const innerModel = data.message.type === "assistant" ? data.message.message.model ?? null : null;
371
+ const taskMeta = taskMetaMap.get(parentId);
372
+ const agentMsg = {
373
+ agentId: data.agentId,
374
+ agentName: taskMeta?.name ?? null,
375
+ subagentType: taskMeta?.subagentType ?? null,
376
+ type: data.message.type,
377
+ content: data.message.message.content,
378
+ toolCalls: [],
379
+ thinking: [],
380
+ text: [],
381
+ timestamp: data.message.timestamp ?? msg.timestamp ?? "",
382
+ tokenUsage: subAgentUsage,
383
+ model: innerModel,
384
+ isBackground: backgroundAgentParentIds.has(parentId)
385
+ };
386
+ if (data.message.type === "assistant") {
387
+ const innerContent = data.message.message.content;
388
+ if (Array.isArray(innerContent)) {
389
+ for (const block of innerContent) {
390
+ if (block.type === "thinking") {
391
+ agentMsg.thinking.push(block.thinking);
392
+ } else if (block.type === "text") {
393
+ agentMsg.text.push(block.text);
394
+ } else if (block.type === "tool_use") {
395
+ agentMsg.toolCalls.push({
396
+ id: block.id,
397
+ name: block.name,
398
+ input: block.input,
399
+ result: null,
400
+ isError: false,
401
+ timestamp: data.message.timestamp ?? msg.timestamp ?? ""
402
+ });
403
+ }
404
+ }
405
+ }
406
+ } else if (data.message.type === "user") {
407
+ const innerContent = data.message.message.content;
408
+ if (Array.isArray(innerContent)) {
409
+ for (const block of innerContent) {
410
+ if (block.type === "tool_result") {
411
+ const existing = subAgentMap.get(parentId);
412
+ if (existing) {
413
+ for (const prev of existing) {
414
+ const match = prev.toolCalls.find(
415
+ (tc) => tc.id === block.tool_use_id
416
+ );
417
+ if (match) {
418
+ match.result = extractToolResultText(block.content);
419
+ match.isError = block.is_error;
420
+ }
421
+ }
422
+ }
423
+ }
424
+ }
425
+ }
426
+ }
427
+ let agentMsgs = subAgentMap.get(parentId);
428
+ if (!agentMsgs) {
429
+ agentMsgs = [];
430
+ subAgentMap.set(parentId, agentMsgs);
431
+ }
432
+ agentMsgs.push(agentMsg);
433
+ if (current) {
434
+ flushSubAgentMessages(parentId);
435
+ }
436
+ continue;
437
+ }
438
+ if (isSystemMessage(msg) && msg.subtype === "turn_duration" && current) {
439
+ current.durationMs = msg.durationMs ?? null;
440
+ continue;
441
+ }
442
+ }
443
+ finalizeTurn();
444
+ return turns;
445
+ }
446
+
447
+ // src/lib/pricingTiers.ts
448
+ var EXTENDED_CONTEXT_THRESHOLD = 2e5;
449
+ var TIER_HAIKU_35 = { input: 0.8, output: 4, cacheWrite: 1, cacheRead: 0.08, webSearch: 0.01 };
450
+ var TIER_HAIKU_45 = { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1, webSearch: 0.01 };
451
+ var TIER_SONNET_LEGACY = { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3, webSearch: 0.01 };
452
+ var TIER_SONNET_LATEST = { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5, webSearch: 0.01 };
453
+ var TIER_OPUS_LEGACY = { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5, webSearch: 0.01 };
454
+ var TIER_SONNET_LEGACY_EXT = { input: 6, output: 22.5, cacheWrite: 7.5, cacheRead: 0.6, webSearch: 0.01 };
455
+ var TIER_EXTENDED = { input: 10, output: 37.5, cacheWrite: 12.5, cacheRead: 1, webSearch: 0.01 };
456
+ var MODEL_TIERS = [
457
+ // Haiku
458
+ { match: "haiku-4-5", tier: TIER_HAIKU_45 },
459
+ { match: "haiku-4-0", tier: TIER_HAIKU_35 },
460
+ { match: "3-5-haiku", tier: TIER_HAIKU_35 },
461
+ // Sonnet latest (4.5+)
462
+ { match: "sonnet-4-6", tier: TIER_SONNET_LATEST, extendedTier: TIER_EXTENDED },
463
+ { match: "sonnet-4-5", tier: TIER_SONNET_LATEST, extendedTier: TIER_EXTENDED },
464
+ // Sonnet legacy (3.5, 3.7, 4.0)
465
+ { match: "sonnet-4-0", tier: TIER_SONNET_LEGACY, extendedTier: TIER_SONNET_LEGACY_EXT },
466
+ { match: "3-7-sonnet", tier: TIER_SONNET_LEGACY, extendedTier: TIER_SONNET_LEGACY_EXT },
467
+ { match: "3-5-sonnet", tier: TIER_SONNET_LEGACY, extendedTier: TIER_SONNET_LEGACY_EXT },
468
+ // Opus latest (4.5+) — same tier as sonnet latest
469
+ { match: "opus-4-6", tier: TIER_SONNET_LATEST, extendedTier: TIER_EXTENDED },
470
+ { match: "opus-4-5", tier: TIER_SONNET_LATEST, extendedTier: TIER_EXTENDED },
471
+ // Opus legacy (4.0, 4.1)
472
+ { match: "opus-4-1", tier: TIER_OPUS_LEGACY },
473
+ { match: "opus-4-0", tier: TIER_OPUS_LEGACY }
474
+ ];
475
+ var DEFAULT_TIER = TIER_SONNET_LATEST;
476
+ var DEFAULT_EXTENDED_TIER = TIER_EXTENDED;
477
+ var FAMILY_FALLBACKS = [
478
+ { match: "haiku", tier: TIER_HAIKU_45 },
479
+ { match: "sonnet", tier: TIER_SONNET_LATEST },
480
+ { match: "opus", tier: TIER_SONNET_LATEST }
481
+ ];
482
+ function resolveTier(model, totalInputTokens) {
483
+ const isExtended = (totalInputTokens ?? 0) > EXTENDED_CONTEXT_THRESHOLD;
484
+ for (const entry of MODEL_TIERS) {
485
+ if (model.includes(entry.match)) {
486
+ return isExtended && entry.extendedTier ? entry.extendedTier : entry.tier;
487
+ }
488
+ }
489
+ for (const entry of FAMILY_FALLBACKS) {
490
+ if (model.includes(entry.match)) return entry.tier;
491
+ }
492
+ return isExtended ? DEFAULT_EXTENDED_TIER : DEFAULT_TIER;
493
+ }
494
+
495
+ // src/lib/token-costs.ts
496
+ var CHARS_PER_TOKEN = 4;
497
+ function calculateCost(c) {
498
+ const totalInput = c.inputTokens + c.cacheWriteTokens + c.cacheReadTokens;
499
+ const p = resolveTier(c.model ?? "", totalInput);
500
+ return c.inputTokens / 1e6 * p.input + c.outputTokens / 1e6 * p.output + c.cacheWriteTokens / 1e6 * p.cacheWrite + c.cacheReadTokens / 1e6 * p.cacheRead + (c.webSearchRequests ?? 0) * p.webSearch;
501
+ }
502
+ function charsToTokens(chars) {
503
+ return Math.ceil(chars / CHARS_PER_TOKEN);
504
+ }
505
+ function totalLength(strings) {
506
+ let n = 0;
507
+ for (const s of strings) n += s.length;
508
+ return n;
509
+ }
510
+ function totalToolInputLength(toolCalls) {
511
+ let n = 0;
512
+ for (const tc of toolCalls) n += JSON.stringify(tc.input).length;
513
+ return n;
514
+ }
515
+ function estimateThinkingTokens(turn) {
516
+ return charsToTokens(totalLength(turn.thinking.map((b) => b.thinking)));
517
+ }
518
+ function estimateVisibleOutputTokens(turn) {
519
+ return charsToTokens(totalLength(turn.assistantText) + totalToolInputLength(turn.toolCalls));
520
+ }
521
+ function estimateTotalOutputTokens(turn) {
522
+ const estimated = estimateThinkingTokens(turn) + estimateVisibleOutputTokens(turn);
523
+ return Math.max(estimated, turn.tokenUsage?.output_tokens ?? 0);
524
+ }
525
+ function estimateSubAgentOutput(sa) {
526
+ const chars = totalLength(sa.thinking) + totalLength(sa.text) + totalToolInputLength(sa.toolCalls);
527
+ return Math.max(charsToTokens(chars), sa.tokenUsage?.output_tokens ?? 0);
528
+ }
529
+ function calculateTurnCostEstimated(turn) {
530
+ if (!turn.tokenUsage) return 0;
531
+ const u = turn.tokenUsage;
532
+ return calculateCost({
533
+ model: turn.model,
534
+ inputTokens: u.input_tokens,
535
+ outputTokens: estimateTotalOutputTokens(turn),
536
+ cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
537
+ cacheReadTokens: u.cache_read_input_tokens ?? 0
538
+ });
539
+ }
540
+ function calculateSubAgentCostEstimated(sa) {
541
+ if (!sa.tokenUsage) return 0;
542
+ const u = sa.tokenUsage;
543
+ return calculateCost({
544
+ model: sa.model,
545
+ inputTokens: u.input_tokens,
546
+ outputTokens: estimateSubAgentOutput(sa),
547
+ cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
548
+ cacheReadTokens: u.cache_read_input_tokens ?? 0
549
+ });
550
+ }
551
+
552
+ // src/lib/sessionStats.ts
553
+ function countToolCalls(toolCalls, counts) {
554
+ let errors = 0;
555
+ for (const tc of toolCalls) {
556
+ counts[tc.name] = (counts[tc.name] ?? 0) + 1;
557
+ if (tc.isError) errors++;
558
+ }
559
+ return errors;
560
+ }
561
+ function addUsageToStats(stats, usage, estimatedOutput, cost) {
562
+ stats.totalInputTokens += usage.input_tokens;
563
+ stats.totalOutputTokens += estimatedOutput;
564
+ stats.totalCacheCreationTokens += usage.cache_creation_input_tokens ?? 0;
565
+ stats.totalCacheReadTokens += usage.cache_read_input_tokens ?? 0;
566
+ stats.totalCostUSD += cost;
567
+ }
568
+ function computeStats(turns) {
569
+ const stats = {
570
+ totalInputTokens: 0,
571
+ totalOutputTokens: 0,
572
+ totalCacheCreationTokens: 0,
573
+ totalCacheReadTokens: 0,
574
+ totalCostUSD: 0,
575
+ toolCallCounts: {},
576
+ errorCount: 0,
577
+ totalDurationMs: 0,
578
+ turnCount: turns.length
579
+ };
580
+ for (const turn of turns) {
581
+ if (turn.tokenUsage) {
582
+ addUsageToStats(stats, turn.tokenUsage, estimateTotalOutputTokens(turn), calculateTurnCostEstimated(turn));
583
+ }
584
+ if (turn.durationMs) stats.totalDurationMs += turn.durationMs;
585
+ stats.errorCount += countToolCalls(turn.toolCalls, stats.toolCallCounts);
586
+ for (const sa of turn.subAgentActivity) {
587
+ stats.errorCount += countToolCalls(sa.toolCalls, stats.toolCallCounts);
588
+ if (sa.tokenUsage) {
589
+ addUsageToStats(stats, sa.tokenUsage, estimateSubAgentOutput(sa), calculateSubAgentCostEstimated(sa));
590
+ }
591
+ }
592
+ }
593
+ return stats;
594
+ }
595
+
596
+ // src/lib/parser.ts
597
+ function isAssistantMessage2(msg) {
598
+ return msg.type === "assistant";
599
+ }
600
+ function extractTextFromContent2(content) {
601
+ if (typeof content === "string") return content;
602
+ return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
603
+ }
604
+ function parseLines(jsonlText) {
605
+ const messages = [];
606
+ for (const line of jsonlText.split("\n")) {
607
+ const trimmed = line.trim();
608
+ if (!trimmed) continue;
609
+ try {
610
+ messages.push(JSON.parse(trimmed));
611
+ } catch {
612
+ }
613
+ }
614
+ return messages;
615
+ }
616
+ function extractSessionMetadata(messages) {
617
+ const meta = { sessionId: "", version: "", gitBranch: "", cwd: "", slug: "", model: "", branchedFrom: void 0 };
618
+ for (const msg of messages) {
619
+ if (msg.sessionId && !meta.sessionId) meta.sessionId = msg.sessionId;
620
+ if (msg.version && !meta.version) meta.version = msg.version;
621
+ if (msg.gitBranch && !meta.gitBranch) meta.gitBranch = msg.gitBranch;
622
+ if (msg.cwd && !meta.cwd) meta.cwd = msg.cwd;
623
+ if (msg.slug && !meta.slug) meta.slug = msg.slug;
624
+ if (msg.branchedFrom && !meta.branchedFrom) {
625
+ meta.branchedFrom = msg.branchedFrom;
626
+ }
627
+ if (isAssistantMessage2(msg) && msg.message.model && !meta.model) {
628
+ meta.model = msg.message.model;
629
+ }
630
+ if (meta.sessionId && meta.version && meta.gitBranch && meta.cwd && meta.model) break;
631
+ }
632
+ return meta;
633
+ }
634
+ function parseSession(jsonlText) {
635
+ const rawMessages = parseLines(jsonlText);
636
+ const metadata = extractSessionMetadata(rawMessages);
637
+ const turns = buildTurns(rawMessages);
638
+ const stats = computeStats(turns);
639
+ return {
640
+ ...metadata,
641
+ turns,
642
+ stats,
643
+ rawMessages
644
+ };
645
+ }
646
+ function getUserMessageText(content) {
647
+ if (content === null) return "";
648
+ if (typeof content === "string") return content;
649
+ return extractTextFromContent2(content);
650
+ }
651
+
652
+ // src/lib/search-index.ts
653
+ var SearchIndex = class {
654
+ db;
655
+ dbPath;
656
+ projectsDir = null;
657
+ _watcherRunning = false;
658
+ _lastFullBuild = null;
659
+ _lastUpdate = null;
660
+ watcher = null;
661
+ debounceTimers = /* @__PURE__ */ new Map();
662
+ constructor(dbPath) {
663
+ this.dbPath = dbPath;
664
+ this.db = new Database(dbPath);
665
+ this.db.exec("PRAGMA journal_mode = WAL");
666
+ this.db.exec("PRAGMA synchronous = NORMAL");
667
+ this.initSchema();
668
+ }
669
+ initSchema() {
670
+ this.db.exec(`CREATE TABLE IF NOT EXISTS indexed_files (
671
+ file_path TEXT PRIMARY KEY,
672
+ mtime_ms REAL NOT NULL,
673
+ session_id TEXT NOT NULL,
674
+ is_subagent INTEGER NOT NULL DEFAULT 0,
675
+ parent_session_id TEXT
676
+ )`);
677
+ const ftsExists = this.db.prepare(
678
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='search_content'"
679
+ ).get();
680
+ if (!ftsExists) {
681
+ this.db.exec(`CREATE VIRTUAL TABLE search_content USING fts5(
682
+ session_id,
683
+ source_file,
684
+ location,
685
+ content,
686
+ tokenize = 'trigram'
687
+ )`);
688
+ }
689
+ }
690
+ getStats() {
691
+ const { count: indexedFiles } = this.db.prepare("SELECT COUNT(*) as count FROM indexed_files").get();
692
+ const { count: indexedSessions } = this.db.prepare("SELECT COUNT(*) as count FROM indexed_files WHERE is_subagent = 0").get();
693
+ const { count: indexedSubagents } = this.db.prepare("SELECT COUNT(*) as count FROM indexed_files WHERE is_subagent = 1").get();
694
+ const { count: totalRows } = this.db.prepare("SELECT COUNT(*) as count FROM search_content").get();
695
+ let dbSizeBytes = 0;
696
+ try {
697
+ dbSizeBytes = (0, import_node_fs.statSync)(this.dbPath).size;
698
+ } catch {
699
+ }
700
+ return {
701
+ dbPath: this.dbPath,
702
+ dbSizeBytes,
703
+ dbSizeMB: Math.round(dbSizeBytes / 1024 / 1024 * 10) / 10,
704
+ indexedFiles,
705
+ indexedSessions,
706
+ indexedSubagents,
707
+ totalRows,
708
+ watcherRunning: this._watcherRunning,
709
+ lastFullBuild: this._lastFullBuild,
710
+ lastUpdate: this._lastUpdate
711
+ };
712
+ }
713
+ /**
714
+ * Parse a JSONL file and insert all searchable content into the FTS5 index.
715
+ * Idempotent: deletes old data for the file before re-indexing.
716
+ * All inserts run in a single transaction for performance.
717
+ */
718
+ indexFile(filePath, sessionId, mtimeMs, opts) {
719
+ if (!sessionId) {
720
+ sessionId = (0, import_node_path.basename)(filePath, ".jsonl");
721
+ }
722
+ if (mtimeMs == null) {
723
+ mtimeMs = (0, import_node_fs.statSync)(filePath).mtimeMs;
724
+ }
725
+ const content = (0, import_node_fs.readFileSync)(filePath, "utf-8");
726
+ const session = parseSession(content);
727
+ const isSubagent = opts?.isSubagent ? 1 : 0;
728
+ const parentSessionId = opts?.parentSessionId ?? null;
729
+ const insert = this.db.prepare(
730
+ "INSERT INTO search_content (session_id, source_file, location, content) VALUES (?, ?, ?, ?)"
731
+ );
732
+ const deleteContent = this.db.prepare(
733
+ "DELETE FROM search_content WHERE source_file = ?"
734
+ );
735
+ const deleteFile = this.db.prepare(
736
+ "DELETE FROM indexed_files WHERE file_path = ?"
737
+ );
738
+ const insertFile = this.db.prepare(
739
+ "INSERT OR REPLACE INTO indexed_files (file_path, mtime_ms, session_id, is_subagent, parent_session_id) VALUES (?, ?, ?, ?, ?)"
740
+ );
741
+ const txn = this.db.transaction(() => {
742
+ deleteContent.run(filePath);
743
+ deleteFile.run(filePath);
744
+ for (let i = 0; i < session.turns.length; i++) {
745
+ const turn = session.turns[i];
746
+ const prefix = `turn/${i}`;
747
+ const userText = getUserMessageText(turn.userMessage);
748
+ if (userText.trim()) {
749
+ insert.run(sessionId, filePath, `${prefix}/userMessage`, userText);
750
+ }
751
+ const assistantJoined = turn.assistantText.join("\n\n").trim();
752
+ if (assistantJoined) {
753
+ insert.run(sessionId, filePath, `${prefix}/assistantMessage`, assistantJoined);
754
+ }
755
+ const thinkingText = turn.thinking.filter((t) => t.thinking && t.thinking.length > 0).map((t) => t.thinking).join("\n\n").trim();
756
+ if (thinkingText) {
757
+ insert.run(sessionId, filePath, `${prefix}/thinking`, thinkingText);
758
+ }
759
+ for (const tc of turn.toolCalls) {
760
+ const inputStr = JSON.stringify(tc.input);
761
+ if (inputStr && inputStr !== "{}") {
762
+ insert.run(sessionId, filePath, `${prefix}/toolCall/${tc.id}/input`, inputStr);
763
+ }
764
+ if (tc.result) {
765
+ insert.run(sessionId, filePath, `${prefix}/toolCall/${tc.id}/result`, tc.result);
766
+ }
767
+ }
768
+ for (const sa of turn.subAgentActivity) {
769
+ const saPrefix = `agent/${sa.agentId}`;
770
+ const saText = sa.text.join("\n\n").trim();
771
+ if (saText) {
772
+ insert.run(sessionId, filePath, `${saPrefix}/assistantMessage`, saText);
773
+ }
774
+ const saThinking = sa.thinking.filter((t) => t.length > 0).join("\n\n").trim();
775
+ if (saThinking) {
776
+ insert.run(sessionId, filePath, `${saPrefix}/thinking`, saThinking);
777
+ }
778
+ for (const tc of sa.toolCalls) {
779
+ const inputStr = JSON.stringify(tc.input);
780
+ if (inputStr && inputStr !== "{}") {
781
+ insert.run(sessionId, filePath, `${saPrefix}/toolCall/${tc.id}/input`, inputStr);
782
+ }
783
+ if (tc.result) {
784
+ insert.run(sessionId, filePath, `${saPrefix}/toolCall/${tc.id}/result`, tc.result);
785
+ }
786
+ }
787
+ }
788
+ if (turn.compactionSummary) {
789
+ insert.run(sessionId, filePath, `${prefix}/compactionSummary`, turn.compactionSummary);
790
+ }
791
+ }
792
+ insertFile.run(filePath, mtimeMs, sessionId, isSubagent, parentSessionId);
793
+ });
794
+ txn();
795
+ this._lastUpdate = (/* @__PURE__ */ new Date()).toISOString();
796
+ }
797
+ /**
798
+ * Query the FTS5 index and return structured search results.
799
+ *
800
+ * - FTS5 trigram tokenizer is case-insensitive by default.
801
+ * - When `caseSensitive` is true, a post-filter checks the original query
802
+ * against the snippet text (exact case match).
803
+ * - When `maxAgeMs` is provided, only files whose mtime in `indexed_files`
804
+ * falls within the window are included (join on source_file).
805
+ * - `sessionId` restricts results to a single session.
806
+ * - `limit` defaults to 200 and is clamped to a max of 200.
807
+ */
808
+ search(query, opts) {
809
+ const limit = Math.min(Math.max(1, opts?.limit ?? 200), 200);
810
+ const sessionId = opts?.sessionId;
811
+ const maxAgeMs = opts?.maxAgeMs;
812
+ const caseSensitive = opts?.caseSensitive ?? false;
813
+ const ftsQuery = `"${query.replace(/"/g, '""')}"`;
814
+ let sql = `
815
+ SELECT sc.session_id, sc.location,
816
+ snippet(search_content, 3, '', '', '...', 40) as snippet
817
+ FROM search_content sc
818
+ `;
819
+ const params = [];
820
+ const conditions = ["sc.content MATCH ?"];
821
+ params.push(ftsQuery);
822
+ if (maxAgeMs != null) {
823
+ sql += " JOIN indexed_files fi ON fi.file_path = sc.source_file";
824
+ conditions.push("fi.mtime_ms >= ?");
825
+ params.push(Date.now() - maxAgeMs);
826
+ }
827
+ if (sessionId) {
828
+ conditions.push("sc.session_id = ?");
829
+ params.push(sessionId);
830
+ }
831
+ sql += " WHERE " + conditions.join(" AND ");
832
+ sql += " ORDER BY sc.rowid DESC";
833
+ sql += " LIMIT ?";
834
+ params.push(limit);
835
+ const rows = this.db.prepare(sql).all(...params);
836
+ let hits = rows.map((row) => ({
837
+ sessionId: row.session_id,
838
+ location: row.location,
839
+ snippet: row.snippet,
840
+ matchCount: 1
841
+ // FTS5 trigram doesn't expose per-row match count; 1 = "at least one match"
842
+ }));
843
+ if (caseSensitive) {
844
+ hits = hits.filter((h) => h.snippet.includes(query));
845
+ }
846
+ return hits;
847
+ }
848
+ /**
849
+ * Count total matching rows and distinct sessions for a query (without LIMIT).
850
+ * Used by the route to report accurate totalHits and sessionsSearched.
851
+ */
852
+ countMatches(query, opts) {
853
+ const ftsQuery = `"${query.replace(/"/g, '""')}"`;
854
+ let sql = `
855
+ SELECT COUNT(*) as total,
856
+ COUNT(DISTINCT sc.session_id) as sessions
857
+ FROM search_content sc
858
+ `;
859
+ const params = [];
860
+ const conditions = ["sc.content MATCH ?"];
861
+ params.push(ftsQuery);
862
+ if (opts?.maxAgeMs != null) {
863
+ sql += " JOIN indexed_files fi ON fi.file_path = sc.source_file";
864
+ conditions.push("fi.mtime_ms >= ?");
865
+ params.push(Date.now() - opts.maxAgeMs);
866
+ }
867
+ if (opts?.sessionId) {
868
+ conditions.push("sc.session_id = ?");
869
+ params.push(opts.sessionId);
870
+ }
871
+ sql += " WHERE " + conditions.join(" AND ");
872
+ const row = this.db.prepare(sql).get(...params);
873
+ return { totalHits: row.total, sessionsSearched: row.sessions };
874
+ }
875
+ /**
876
+ * Clear all indexed data and re-index every JSONL file under `projectsDir`.
877
+ * Structure: projectsDir/{projectName}/{sessionId}.jsonl
878
+ * Subagents: projectsDir/{projectName}/{sessionId}/subagents/agent-{id}.jsonl
879
+ *
880
+ * Stores `projectsDir` as a class field so `rebuild()` can reuse it.
881
+ */
882
+ buildFull(projectsDir) {
883
+ this.projectsDir = projectsDir;
884
+ this.db.exec("DELETE FROM search_content");
885
+ this.db.exec("DELETE FROM indexed_files");
886
+ this.indexProjectsDir(projectsDir);
887
+ this._lastFullBuild = (/* @__PURE__ */ new Date()).toISOString();
888
+ this._lastUpdate = (/* @__PURE__ */ new Date()).toISOString();
889
+ }
890
+ /**
891
+ * Incrementally re-index only files whose mtime has changed since last index.
892
+ * New files (not in indexed_files) are always indexed.
893
+ */
894
+ updateStale(projectsDir) {
895
+ this.projectsDir = projectsDir;
896
+ const getIndexed = this.db.prepare(
897
+ "SELECT mtime_ms FROM indexed_files WHERE file_path = ?"
898
+ );
899
+ const filesToIndex = [];
900
+ this.discoverFiles(projectsDir, (filePath, sessionId, mtimeMs, isSubagent, parentSessionId) => {
901
+ const existing = getIndexed.get(filePath);
902
+ if (!existing || existing.mtime_ms < mtimeMs) {
903
+ filesToIndex.push({ path: filePath, sessionId, mtimeMs, isSubagent, parentSessionId });
904
+ }
905
+ });
906
+ for (const file of filesToIndex) {
907
+ try {
908
+ this.indexFile(file.path, file.sessionId, file.mtimeMs, {
909
+ isSubagent: file.isSubagent,
910
+ parentSessionId: file.parentSessionId
911
+ });
912
+ } catch {
913
+ }
914
+ }
915
+ if (filesToIndex.length > 0) {
916
+ this._lastUpdate = (/* @__PURE__ */ new Date()).toISOString();
917
+ }
918
+ }
919
+ /**
920
+ * Re-run buildFull using the previously stored projectsDir.
921
+ * No-op if projectsDir was never set.
922
+ */
923
+ rebuild() {
924
+ if (!this.projectsDir) return;
925
+ this.buildFull(this.projectsDir);
926
+ }
927
+ /**
928
+ * Walk all project directories under `projectsDir` and index every discovered
929
+ * JSONL file (both sessions and subagents).
930
+ */
931
+ indexProjectsDir(projectsDir) {
932
+ this.discoverFiles(projectsDir, (filePath, sessionId, mtimeMs, isSubagent, parentSessionId) => {
933
+ try {
934
+ this.indexFile(filePath, sessionId, mtimeMs, { isSubagent, parentSessionId });
935
+ } catch {
936
+ }
937
+ });
938
+ }
939
+ /**
940
+ * Walk the projects directory tree and invoke `callback` for every JSONL file.
941
+ *
942
+ * Directory structure:
943
+ * projectsDir/
944
+ * {projectName}/
945
+ * {sessionId}.jsonl <- session file
946
+ * {sessionId}/subagents/
947
+ * agent-{agentId}.jsonl <- subagent file (recursive)
948
+ *
949
+ * Skips the "memory" directory.
950
+ */
951
+ discoverFiles(projectsDir, callback) {
952
+ let entries;
953
+ try {
954
+ entries = (0, import_node_fs.readdirSync)(projectsDir);
955
+ } catch {
956
+ return;
957
+ }
958
+ for (const projectName of entries) {
959
+ if (projectName === "memory") continue;
960
+ const projectDir = (0, import_node_path.join)(projectsDir, projectName);
961
+ try {
962
+ const s = (0, import_node_fs.statSync)(projectDir);
963
+ if (!s.isDirectory()) continue;
964
+ } catch {
965
+ continue;
966
+ }
967
+ let files;
968
+ try {
969
+ files = (0, import_node_fs.readdirSync)(projectDir);
970
+ } catch {
971
+ continue;
972
+ }
973
+ for (const file of files) {
974
+ if (!file.endsWith(".jsonl")) continue;
975
+ const filePath = (0, import_node_path.join)(projectDir, file);
976
+ const sessionId = (0, import_node_path.basename)(file, ".jsonl");
977
+ try {
978
+ const s = (0, import_node_fs.statSync)(filePath);
979
+ callback(filePath, sessionId, s.mtimeMs, false, null);
980
+ } catch {
981
+ continue;
982
+ }
983
+ this.discoverSubagents(filePath, sessionId, callback, 0, 4);
984
+ }
985
+ }
986
+ }
987
+ /**
988
+ * Recursively discover subagent JSONL files under the subagents directory
989
+ * that corresponds to `parentPath`.
990
+ *
991
+ * For a parent at `/path/to/session-1.jsonl`, looks for subagents at
992
+ * `/path/to/session-1/subagents/agent-*.jsonl`.
993
+ *
994
+ * Recurses up to `maxDepth` levels (default 4).
995
+ */
996
+ discoverSubagents(parentPath, parentSessionId, callback, depth, maxDepth) {
997
+ if (depth >= maxDepth) return;
998
+ const subDir = parentPath.replace(/\.jsonl$/, "") + "/subagents";
999
+ let files;
1000
+ try {
1001
+ files = (0, import_node_fs.readdirSync)(subDir);
1002
+ } catch {
1003
+ return;
1004
+ }
1005
+ for (const file of files) {
1006
+ if (!file.startsWith("agent-") || !file.endsWith(".jsonl")) continue;
1007
+ const filePath = (0, import_node_path.join)(subDir, file);
1008
+ try {
1009
+ const s = (0, import_node_fs.statSync)(filePath);
1010
+ callback(filePath, parentSessionId, s.mtimeMs, true, parentSessionId);
1011
+ } catch {
1012
+ continue;
1013
+ }
1014
+ this.discoverSubagents(filePath, parentSessionId, callback, depth + 1, maxDepth);
1015
+ }
1016
+ }
1017
+ /**
1018
+ * Start watching `projectsDir` for JSONL file changes.
1019
+ * Runs `updateStale()` immediately for an initial sync, then sets up
1020
+ * `fs.watch` with `{ recursive: true }` (macOS-compatible) to detect
1021
+ * subsequent file changes and trigger debounced re-indexing.
1022
+ */
1023
+ startWatching(projectsDir) {
1024
+ this.projectsDir = projectsDir;
1025
+ this.updateStale(projectsDir);
1026
+ try {
1027
+ this.watcher = (0, import_node_fs.watch)(projectsDir, { recursive: true }, (_event, filename) => {
1028
+ if (!filename || !filename.endsWith(".jsonl")) return;
1029
+ this.debouncedReindex((0, import_node_path.join)(projectsDir, filename));
1030
+ });
1031
+ this._watcherRunning = true;
1032
+ } catch (err) {
1033
+ console.warn("[search-index] fs.watch failed (recursive may not be supported):", err);
1034
+ this._watcherRunning = false;
1035
+ }
1036
+ }
1037
+ /**
1038
+ * Stop the file watcher and clear any pending debounce timers.
1039
+ * Safe to call even when not watching (no-op).
1040
+ */
1041
+ stopWatching() {
1042
+ if (this.watcher) {
1043
+ this.watcher.close();
1044
+ this.watcher = null;
1045
+ }
1046
+ for (const timer of this.debounceTimers.values()) {
1047
+ clearTimeout(timer);
1048
+ }
1049
+ this.debounceTimers.clear();
1050
+ this._watcherRunning = false;
1051
+ }
1052
+ /**
1053
+ * Private helper: debounce re-indexing of a single file.
1054
+ * Waits 2 seconds after the last change event for a given file path
1055
+ * before actually calling `indexFile()`. This coalesces rapid writes
1056
+ * (e.g. streaming JSONL appends) into a single index operation.
1057
+ */
1058
+ debouncedReindex(filePath) {
1059
+ const existing = this.debounceTimers.get(filePath);
1060
+ if (existing) clearTimeout(existing);
1061
+ this.debounceTimers.set(
1062
+ filePath,
1063
+ setTimeout(() => {
1064
+ this.debounceTimers.delete(filePath);
1065
+ try {
1066
+ const s = (0, import_node_fs.statSync)(filePath);
1067
+ const parts = filePath.split("/");
1068
+ const fileName = parts[parts.length - 1];
1069
+ const isSubagent = parts.includes("subagents");
1070
+ let sessionId;
1071
+ let parentSessionId = null;
1072
+ if (isSubagent) {
1073
+ const subagentsIdx = parts.lastIndexOf("subagents");
1074
+ const parentDir = parts[subagentsIdx - 1];
1075
+ sessionId = parentDir;
1076
+ parentSessionId = parentDir;
1077
+ } else {
1078
+ sessionId = (0, import_node_path.basename)(fileName, ".jsonl");
1079
+ }
1080
+ this.indexFile(filePath, sessionId, s.mtimeMs, {
1081
+ isSubagent,
1082
+ parentSessionId
1083
+ });
1084
+ } catch {
1085
+ }
1086
+ }, 2e3)
1087
+ );
1088
+ }
1089
+ close() {
1090
+ this.stopWatching();
1091
+ this.db.close();
1092
+ }
1093
+ };
1094
+
1095
+ // src/lib/dirs.ts
1096
+ var import_node_path2 = require("node:path");
1097
+ var import_node_os = require("node:os");
1098
+ var dirs = {
1099
+ PROJECTS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "projects"),
1100
+ TEAMS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "teams"),
1101
+ TASKS_DIR: (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "tasks")
1102
+ };
1103
+ var DEFAULT_DB_PATH = (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "cogpit-memory", "search-index.db");
1104
+
1105
+ // src/lib/response.ts
1106
+ function parseMaxAge(raw) {
1107
+ const match = raw.match(/^(\d+)([dhm])$/);
1108
+ if (!match) return 5 * 24 * 60 * 60 * 1e3;
1109
+ const value = parseInt(match[1], 10);
1110
+ const unit = match[2];
1111
+ switch (unit) {
1112
+ case "d":
1113
+ return value * 24 * 60 * 60 * 1e3;
1114
+ case "h":
1115
+ return value * 60 * 60 * 1e3;
1116
+ case "m":
1117
+ return value * 60 * 1e3;
1118
+ default:
1119
+ return 5 * 24 * 60 * 60 * 1e3;
1120
+ }
1121
+ }
1122
+
1123
+ // src/lib/helpers.ts
1124
+ var import_promises = require("node:fs/promises");
1125
+ var import_node_path3 = require("node:path");
1126
+ var import_node_os2 = require("node:os");
1127
+ async function findJsonlPath(sessionId) {
1128
+ const targetFile = `${sessionId}.jsonl`;
1129
+ try {
1130
+ const entries = await (0, import_promises.readdir)(dirs.PROJECTS_DIR, { withFileTypes: true });
1131
+ for (const entry of entries) {
1132
+ if (!entry.isDirectory() || entry.name === "memory") continue;
1133
+ const projectDir = (0, import_node_path3.join)(dirs.PROJECTS_DIR, entry.name);
1134
+ try {
1135
+ const files = await (0, import_promises.readdir)(projectDir);
1136
+ if (files.includes(targetFile)) {
1137
+ return (0, import_node_path3.join)(projectDir, targetFile);
1138
+ }
1139
+ } catch {
1140
+ continue;
1141
+ }
1142
+ }
1143
+ } catch {
1144
+ }
1145
+ return null;
1146
+ }
1147
+ async function matchSubagentToMember(leadSessionId, subagentFileName, members) {
1148
+ const entries = await (0, import_promises.readdir)(dirs.PROJECTS_DIR, { withFileTypes: true });
1149
+ for (const entry of entries) {
1150
+ if (!entry.isDirectory() || entry.name === "memory") continue;
1151
+ const filePath = (0, import_node_path3.join)(
1152
+ dirs.PROJECTS_DIR,
1153
+ entry.name,
1154
+ leadSessionId,
1155
+ "subagents",
1156
+ subagentFileName
1157
+ );
1158
+ try {
1159
+ const fh = await (0, import_promises.open)(filePath, "r");
1160
+ try {
1161
+ const buf = Buffer.alloc(16384);
1162
+ const { bytesRead } = await fh.read(buf, 0, 16384, 0);
1163
+ const firstLine = buf.subarray(0, bytesRead).toString("utf-8").split("\n")[0] || "";
1164
+ for (const member of members) {
1165
+ if (member.agentType === "team-lead") continue;
1166
+ const prompt = member.prompt || "";
1167
+ const snippet = prompt.slice(0, 120);
1168
+ const terms = [
1169
+ member.name,
1170
+ member.name.replace(/-/g, " "),
1171
+ ...snippet ? [snippet, snippet.replace(/"/g, '\\"')] : []
1172
+ ];
1173
+ if (terms.some((t) => firstLine.includes(t))) {
1174
+ return member.name;
1175
+ }
1176
+ }
1177
+ } finally {
1178
+ await fh.close();
1179
+ }
1180
+ } catch {
1181
+ continue;
1182
+ }
1183
+ }
1184
+ return null;
1185
+ }
1186
+ var HOME_PREFIX = (0, import_node_os2.homedir)().replace(/\//g, "-").replace(/^-/, "").toLowerCase();
1187
+
1188
+ // src/commands/search.ts
1189
+ async function searchSessions(query, opts, searchIndex) {
1190
+ if (!query || query.length < 2) {
1191
+ return { error: "Query must be at least 2 characters" };
1192
+ }
1193
+ const limit = Math.min(Math.max(1, opts.limit ?? 20), 200);
1194
+ const caseSensitive = opts.caseSensitive ?? false;
1195
+ const maxAgeMs = parseMaxAge(opts.maxAge ?? "5d");
1196
+ const depth = Math.min(Math.max(1, opts.depth ?? 4), 4);
1197
+ let index = searchIndex ?? null;
1198
+ let ownedIndex = false;
1199
+ if (!index && (0, import_node_fs2.existsSync)(DEFAULT_DB_PATH)) {
1200
+ try {
1201
+ index = new SearchIndex(DEFAULT_DB_PATH);
1202
+ ownedIndex = true;
1203
+ } catch {
1204
+ }
1205
+ }
1206
+ if (index) {
1207
+ try {
1208
+ const hits = index.search(query, {
1209
+ limit,
1210
+ sessionId: opts.sessionId,
1211
+ maxAgeMs,
1212
+ caseSensitive
1213
+ });
1214
+ const grouped = /* @__PURE__ */ new Map();
1215
+ for (const hit of hits) {
1216
+ let sessionHits = grouped.get(hit.sessionId);
1217
+ if (!sessionHits) {
1218
+ sessionHits = [];
1219
+ grouped.set(hit.sessionId, sessionHits);
1220
+ }
1221
+ sessionHits.push({
1222
+ location: hit.location,
1223
+ snippet: hit.snippet,
1224
+ matchCount: hit.matchCount
1225
+ });
1226
+ }
1227
+ const results = [];
1228
+ for (const [sid, sessionHits] of grouped) {
1229
+ results.push({ sessionId: sid, hits: sessionHits });
1230
+ }
1231
+ let totalHits = hits.length;
1232
+ let sessionsSearched = grouped.size;
1233
+ if (hits.length >= limit) {
1234
+ const counts = index.countMatches(query, {
1235
+ sessionId: opts.sessionId,
1236
+ maxAgeMs
1237
+ });
1238
+ totalHits = counts.totalHits;
1239
+ sessionsSearched = counts.sessionsSearched;
1240
+ }
1241
+ if (ownedIndex) index.close();
1242
+ return {
1243
+ query,
1244
+ totalHits,
1245
+ returnedHits: hits.length,
1246
+ sessionsSearched,
1247
+ results
1248
+ };
1249
+ } catch {
1250
+ if (ownedIndex) index.close();
1251
+ }
1252
+ }
1253
+ return rawScanSearch(query, opts.sessionId ?? null, maxAgeMs, limit, caseSensitive, depth);
1254
+ }
1255
+ var SNIPPET_WINDOW = 150;
1256
+ function generateSnippet(text, matchIdx, queryLen) {
1257
+ if (matchIdx === -1) return text.slice(0, SNIPPET_WINDOW);
1258
+ const halfWindow = Math.floor((SNIPPET_WINDOW - queryLen) / 2);
1259
+ const start = Math.max(0, matchIdx - halfWindow);
1260
+ const end = Math.min(text.length, start + SNIPPET_WINDOW);
1261
+ const adjustedStart = Math.max(0, end - SNIPPET_WINDOW);
1262
+ let snippet = text.slice(adjustedStart, end);
1263
+ if (adjustedStart > 0) snippet = "..." + snippet;
1264
+ if (end < text.length) snippet = snippet + "...";
1265
+ return snippet;
1266
+ }
1267
+ function countMatches(haystack, needle) {
1268
+ let count = 0;
1269
+ let pos = 0;
1270
+ while ((pos = haystack.indexOf(needle, pos)) !== -1) {
1271
+ count++;
1272
+ pos += needle.length;
1273
+ }
1274
+ return count;
1275
+ }
1276
+ function searchField(text, location, query, caseSensitive, extras) {
1277
+ if (!text) return null;
1278
+ const haystack = caseSensitive ? text : text.toLowerCase();
1279
+ const needle = caseSensitive ? query : query.toLowerCase();
1280
+ const idx = haystack.indexOf(needle);
1281
+ if (idx === -1) return null;
1282
+ return {
1283
+ location,
1284
+ snippet: generateSnippet(text, idx, query.length),
1285
+ matchCount: countMatches(haystack, needle),
1286
+ ...extras?.toolName && { toolName: extras.toolName },
1287
+ ...extras?.agentName && { agentName: extras.agentName }
1288
+ };
1289
+ }
1290
+ async function discoverSingleSession(sessionId) {
1291
+ const jsonlPath = await findJsonlPath(sessionId);
1292
+ if (!jsonlPath) return [];
1293
+ const s = await (0, import_promises2.stat)(jsonlPath);
1294
+ return [{ path: jsonlPath, mtimeMs: s.mtimeMs }];
1295
+ }
1296
+ async function discoverAllSessions(maxAgeMs) {
1297
+ const cutoff = Date.now() - maxAgeMs;
1298
+ let entries;
1299
+ try {
1300
+ entries = await (0, import_promises2.readdir)(dirs.PROJECTS_DIR, { withFileTypes: true });
1301
+ } catch {
1302
+ return [];
1303
+ }
1304
+ const projectDirs = entries.filter((e) => e.isDirectory() && e.name !== "memory").map((e) => (0, import_node_path4.join)(dirs.PROJECTS_DIR, e.name));
1305
+ const nested = await Promise.all(
1306
+ projectDirs.map(async (projectDir) => {
1307
+ try {
1308
+ const files = await (0, import_promises2.readdir)(projectDir);
1309
+ const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
1310
+ const statResults = await Promise.all(
1311
+ jsonlFiles.map(async (f) => {
1312
+ const filePath = (0, import_node_path4.join)(projectDir, f);
1313
+ try {
1314
+ const s = await (0, import_promises2.stat)(filePath);
1315
+ return s.mtimeMs >= cutoff ? { path: filePath, mtimeMs: s.mtimeMs } : null;
1316
+ } catch {
1317
+ return null;
1318
+ }
1319
+ })
1320
+ );
1321
+ return statResults.filter((r) => r !== null);
1322
+ } catch {
1323
+ return [];
1324
+ }
1325
+ })
1326
+ );
1327
+ const results = nested.flat();
1328
+ results.sort((a, b) => b.mtimeMs - a.mtimeMs);
1329
+ return results;
1330
+ }
1331
+ async function rawTextMatch(filePath, query, caseSensitive) {
1332
+ try {
1333
+ const content = await (0, import_promises2.readFile)(filePath, "utf-8");
1334
+ const haystack = caseSensitive ? content : content.toLowerCase();
1335
+ const needle = caseSensitive ? query : query.toLowerCase();
1336
+ if (haystack.includes(needle)) return content;
1337
+ return null;
1338
+ } catch {
1339
+ return null;
1340
+ }
1341
+ }
1342
+ function walkSession(session, query, caseSensitive, locationPrefix = "") {
1343
+ const hits = [];
1344
+ for (let i = 0; i < session.turns.length; i++) {
1345
+ const turn = session.turns[i];
1346
+ const prefix = `${locationPrefix}turn/${i}`;
1347
+ const userText = getUserMessageText(turn.userMessage);
1348
+ const userHit = searchField(userText || null, `${prefix}/userMessage`, query, caseSensitive);
1349
+ if (userHit) hits.push(userHit);
1350
+ const assistantText = turn.assistantText.length > 0 ? turn.assistantText.join("\n\n") : null;
1351
+ const assistantHit = searchField(assistantText, `${prefix}/assistantMessage`, query, caseSensitive);
1352
+ if (assistantHit) hits.push(assistantHit);
1353
+ for (const tb of turn.thinking) {
1354
+ if (!tb.thinking) continue;
1355
+ const thinkHit = searchField(tb.thinking, `${prefix}/thinking`, query, caseSensitive);
1356
+ if (thinkHit) {
1357
+ hits.push(thinkHit);
1358
+ break;
1359
+ }
1360
+ }
1361
+ for (const tc of turn.toolCalls) {
1362
+ const inputStr = JSON.stringify(tc.input);
1363
+ const inputHit = searchField(inputStr, `${prefix}/toolCall/${tc.id}/input`, query, caseSensitive, { toolName: tc.name });
1364
+ if (inputHit) hits.push(inputHit);
1365
+ const resultHit = searchField(tc.result, `${prefix}/toolCall/${tc.id}/result`, query, caseSensitive, { toolName: tc.name });
1366
+ if (resultHit) hits.push(resultHit);
1367
+ }
1368
+ for (const sa of turn.subAgentActivity) {
1369
+ const saPrefix = `${locationPrefix}agent/${sa.agentId}/`;
1370
+ const saText = sa.text.length > 0 ? sa.text.join("\n\n") : null;
1371
+ const saTextHit = searchField(saText, `${saPrefix}assistantMessage`, query, caseSensitive, {
1372
+ agentName: sa.agentName ?? void 0
1373
+ });
1374
+ if (saTextHit) hits.push(saTextHit);
1375
+ for (const t of sa.thinking) {
1376
+ const tHit = searchField(t, `${saPrefix}thinking`, query, caseSensitive, { agentName: sa.agentName ?? void 0 });
1377
+ if (tHit) {
1378
+ hits.push(tHit);
1379
+ break;
1380
+ }
1381
+ }
1382
+ for (const tc of sa.toolCalls) {
1383
+ const inputStr = JSON.stringify(tc.input);
1384
+ const inputHit = searchField(inputStr, `${saPrefix}toolCall/${tc.id}/input`, query, caseSensitive, {
1385
+ toolName: tc.name,
1386
+ agentName: sa.agentName ?? void 0
1387
+ });
1388
+ if (inputHit) hits.push(inputHit);
1389
+ const resultHit = searchField(tc.result, `${saPrefix}toolCall/${tc.id}/result`, query, caseSensitive, {
1390
+ toolName: tc.name,
1391
+ agentName: sa.agentName ?? void 0
1392
+ });
1393
+ if (resultHit) hits.push(resultHit);
1394
+ }
1395
+ }
1396
+ if (turn.compactionSummary) {
1397
+ const compHit = searchField(turn.compactionSummary, `${prefix}/compactionSummary`, query, caseSensitive);
1398
+ if (compHit) hits.push(compHit);
1399
+ }
1400
+ }
1401
+ return hits;
1402
+ }
1403
+ async function walkSubagentFiles(parentJsonlPath, query, caseSensitive, currentDepth, maxDepth) {
1404
+ if (currentDepth >= maxDepth) return [];
1405
+ const subDir = parentJsonlPath.replace(/\.jsonl$/, "") + "/subagents";
1406
+ let files;
1407
+ try {
1408
+ files = await (0, import_promises2.readdir)(subDir);
1409
+ } catch {
1410
+ return [];
1411
+ }
1412
+ const agentFiles = files.filter((f) => f.startsWith("agent-") && f.endsWith(".jsonl")).map((f) => ({
1413
+ agentId: f.slice("agent-".length, -".jsonl".length),
1414
+ filePath: (0, import_node_path4.join)(subDir, f)
1415
+ }));
1416
+ const matched = await Promise.all(
1417
+ agentFiles.map(async ({ agentId, filePath }) => ({
1418
+ agentId,
1419
+ filePath,
1420
+ rawContent: await rawTextMatch(filePath, query, caseSensitive)
1421
+ }))
1422
+ );
1423
+ const hitArrays = await Promise.all(
1424
+ matched.filter((m) => m.rawContent).map(async ({ agentId, filePath, rawContent }) => {
1425
+ const subSession = parseSession(rawContent);
1426
+ const subHits = walkSession(subSession, query, caseSensitive, `agent/${agentId}/`);
1427
+ const nestedHits = await walkSubagentFiles(filePath, query, caseSensitive, currentDepth + 1, maxDepth);
1428
+ return [...subHits, ...nestedHits];
1429
+ })
1430
+ );
1431
+ return hitArrays.flat();
1432
+ }
1433
+ async function rawScanSearch(query, sessionId, maxAgeMs, limit, caseSensitive, depth) {
1434
+ try {
1435
+ const files = sessionId ? await discoverSingleSession(sessionId) : await discoverAllSessions(maxAgeMs);
1436
+ let totalHits = 0;
1437
+ let returnedHits = 0;
1438
+ const results = [];
1439
+ let sessionsSearched = 0;
1440
+ for (const file of files) {
1441
+ if (returnedHits >= limit) break;
1442
+ const rawContent = await rawTextMatch(file.path, query, caseSensitive);
1443
+ sessionsSearched++;
1444
+ if (!rawContent) continue;
1445
+ const session = parseSession(rawContent);
1446
+ const sessionHits = walkSession(session, query, caseSensitive);
1447
+ const subagentHits = await walkSubagentFiles(file.path, query, caseSensitive, 0, depth);
1448
+ const allHits = [...sessionHits, ...subagentHits];
1449
+ if (allHits.length === 0) continue;
1450
+ totalHits += allHits.length;
1451
+ const sessionResult = {
1452
+ sessionId: session.sessionId || (0, import_node_path4.basename)(file.path, ".jsonl"),
1453
+ hits: []
1454
+ };
1455
+ for (const hit of allHits) {
1456
+ if (returnedHits < limit) {
1457
+ sessionResult.hits.push(hit);
1458
+ returnedHits++;
1459
+ }
1460
+ }
1461
+ if (sessionResult.hits.length > 0) {
1462
+ results.push(sessionResult);
1463
+ }
1464
+ }
1465
+ return {
1466
+ query,
1467
+ totalHits,
1468
+ returnedHits,
1469
+ sessionsSearched,
1470
+ results
1471
+ };
1472
+ } catch (err) {
1473
+ return {
1474
+ query,
1475
+ totalHits: 0,
1476
+ returnedHits: 0,
1477
+ sessionsSearched: 0,
1478
+ results: []
1479
+ };
1480
+ }
1481
+ }
1482
+
1483
+ // src/commands/context.ts
1484
+ var import_promises3 = require("node:fs/promises");
1485
+ var import_node_path5 = require("node:path");
1486
+ var RESULT_TRUNCATE_LIMIT = 1e4;
1487
+ var L1_RESPONSE_LIMIT = 15e4;
1488
+ function extractUserMessageText(userMessage) {
1489
+ if (userMessage === null) return null;
1490
+ if (typeof userMessage === "string") return userMessage;
1491
+ const parts = [];
1492
+ for (const block of userMessage) {
1493
+ if (block.type === "text") parts.push(block.text);
1494
+ else if (block.type === "image") parts.push("[image attached]");
1495
+ }
1496
+ return parts.length > 0 ? parts.join("\n") : null;
1497
+ }
1498
+ function truncateResult(result) {
1499
+ if (result === null) return { result: null, resultTruncated: false };
1500
+ if (result.length <= RESULT_TRUNCATE_LIMIT) return { result, resultTruncated: false };
1501
+ return { result: result.slice(0, RESULT_TRUNCATE_LIMIT), resultTruncated: true };
1502
+ }
1503
+ function mapSubAgentSummary(msg) {
1504
+ return {
1505
+ agentId: msg.agentId,
1506
+ name: msg.agentName ?? null,
1507
+ type: msg.subagentType ?? null,
1508
+ status: msg.status ?? null,
1509
+ durationMs: msg.durationMs ?? null,
1510
+ toolUseCount: msg.toolUseCount ?? null,
1511
+ isBackground: msg.isBackground
1512
+ };
1513
+ }
1514
+ function mapSubAgentDetail(msg) {
1515
+ return {
1516
+ ...mapSubAgentSummary(msg),
1517
+ prompt: msg.prompt ?? null,
1518
+ resultText: msg.text.join("\n\n") || null
1519
+ };
1520
+ }
1521
+ function mapSessionToOverview(session) {
1522
+ const turns = session.turns.map((turn, i) => mapTurnToSummary(turn, i));
1523
+ const compacted = session.turns.some((t) => t.compactionSummary != null);
1524
+ const overview = {
1525
+ sessionId: session.sessionId,
1526
+ cwd: session.cwd,
1527
+ model: session.model,
1528
+ branchedFrom: session.branchedFrom ?? null,
1529
+ compacted,
1530
+ turns,
1531
+ stats: {
1532
+ totalTurns: session.stats.turnCount,
1533
+ totalToolCalls: Object.values(session.stats.toolCallCounts).reduce((a, b) => a + b, 0),
1534
+ totalTokens: {
1535
+ input: session.stats.totalInputTokens,
1536
+ output: session.stats.totalOutputTokens
1537
+ }
1538
+ }
1539
+ };
1540
+ let serialized = JSON.stringify(overview);
1541
+ if (serialized.length > L1_RESPONSE_LIMIT) {
1542
+ const entries = [];
1543
+ for (const t of overview.turns) {
1544
+ if (t.assistantMessage) entries.push({ turn: t, field: "assistantMessage", len: t.assistantMessage.length });
1545
+ if (t.userMessage) entries.push({ turn: t, field: "userMessage", len: t.userMessage.length });
1546
+ }
1547
+ entries.sort((a, b) => b.len - a.len);
1548
+ for (const entry of entries) {
1549
+ const current = entry.turn[entry.field];
1550
+ if (current && current.length > 200) {
1551
+ entry.turn[entry.field] = current.slice(0, 200) + "... [truncated, use L2 for full text]";
1552
+ serialized = JSON.stringify(overview);
1553
+ if (serialized.length <= L1_RESPONSE_LIMIT) break;
1554
+ }
1555
+ }
1556
+ }
1557
+ return overview;
1558
+ }
1559
+ function mapTurnToSummary(turn, turnIndex) {
1560
+ const toolSummary = {};
1561
+ for (const tc of turn.toolCalls) {
1562
+ toolSummary[tc.name] = (toolSummary[tc.name] ?? 0) + 1;
1563
+ }
1564
+ const agentById = /* @__PURE__ */ new Map();
1565
+ for (const msg of turn.subAgentActivity) {
1566
+ agentById.set(msg.agentId, msg);
1567
+ }
1568
+ const subAgents = [...agentById.values()].map(mapSubAgentSummary);
1569
+ return {
1570
+ turnIndex,
1571
+ userMessage: extractUserMessageText(turn.userMessage),
1572
+ assistantMessage: turn.assistantText.length > 0 ? turn.assistantText.join("\n\n") : null,
1573
+ toolSummary,
1574
+ subAgents,
1575
+ hasThinking: turn.thinking.length > 0,
1576
+ isError: turn.toolCalls.some((tc) => tc.isError),
1577
+ compactionSummary: turn.compactionSummary ?? null
1578
+ };
1579
+ }
1580
+ function mapTurnToDetail(session, turnIndex) {
1581
+ const turn = session.turns[turnIndex];
1582
+ const contentBlocks = turn.contentBlocks.map((block) => mapContentBlock(block));
1583
+ return {
1584
+ sessionId: session.sessionId,
1585
+ turnIndex,
1586
+ userMessage: extractUserMessageText(turn.userMessage),
1587
+ contentBlocks,
1588
+ tokenUsage: turn.tokenUsage ? { input: turn.tokenUsage.input_tokens, output: turn.tokenUsage.output_tokens } : null,
1589
+ model: turn.model,
1590
+ durationMs: turn.durationMs
1591
+ };
1592
+ }
1593
+ function mapContentBlock(block) {
1594
+ switch (block.kind) {
1595
+ case "thinking": {
1596
+ const text = block.blocks.filter((b) => b.thinking.length > 0).map((b) => b.thinking).join("\n\n");
1597
+ return { kind: "thinking", text, timestamp: block.timestamp ?? null };
1598
+ }
1599
+ case "text":
1600
+ return { kind: "text", text: block.text.join("\n\n"), timestamp: block.timestamp ?? null };
1601
+ case "tool_calls":
1602
+ return {
1603
+ kind: "tool_calls",
1604
+ toolCalls: block.toolCalls.map((tc) => {
1605
+ const { result, resultTruncated } = truncateResult(tc.result);
1606
+ return { id: tc.id, name: tc.name, input: tc.input, result, resultTruncated, isError: tc.isError };
1607
+ }),
1608
+ timestamp: block.timestamp ?? null
1609
+ };
1610
+ case "sub_agent":
1611
+ case "background_agent":
1612
+ return {
1613
+ kind: block.kind,
1614
+ agents: block.messages.map(mapSubAgentDetail),
1615
+ timestamp: block.timestamp ?? null
1616
+ };
1617
+ }
1618
+ }
1619
+ async function findSubagentFile(parentJsonlPath, agentId) {
1620
+ const subagentsDir = parentJsonlPath.replace(/\.jsonl$/, "") + "/subagents";
1621
+ try {
1622
+ const files = await (0, import_promises3.readdir)(subagentsDir);
1623
+ for (const f of files) {
1624
+ if (!f.startsWith("agent-") || !f.endsWith(".jsonl")) continue;
1625
+ const fileAgentId = f.replace("agent-", "").replace(".jsonl", "");
1626
+ if (fileAgentId === agentId) {
1627
+ return { filePath: (0, import_node_path5.join)(subagentsDir, f), fileName: f };
1628
+ }
1629
+ }
1630
+ } catch {
1631
+ }
1632
+ return null;
1633
+ }
1634
+ async function findTeamContext(sessionId, subagentFileName) {
1635
+ try {
1636
+ const teamEntries = await (0, import_promises3.readdir)(dirs.TEAMS_DIR, { withFileTypes: true });
1637
+ for (const entry of teamEntries) {
1638
+ if (!entry.isDirectory()) continue;
1639
+ const configPath = (0, import_node_path5.join)(dirs.TEAMS_DIR, entry.name, "config.json");
1640
+ try {
1641
+ const raw = await (0, import_promises3.readFile)(configPath, "utf-8");
1642
+ const config = JSON.parse(raw);
1643
+ if (config.leadSessionId !== sessionId) continue;
1644
+ const members = config.members ?? [];
1645
+ const memberName = await matchSubagentToMember(sessionId, subagentFileName, members);
1646
+ if (!memberName) continue;
1647
+ let currentTask = null;
1648
+ try {
1649
+ const taskDir = (0, import_node_path5.join)(dirs.TASKS_DIR, entry.name);
1650
+ const taskFiles = await (0, import_promises3.readdir)(taskDir);
1651
+ for (const tf of taskFiles) {
1652
+ if (!tf.endsWith(".json")) continue;
1653
+ const taskRaw = await (0, import_promises3.readFile)((0, import_node_path5.join)(taskDir, tf), "utf-8");
1654
+ const task = JSON.parse(taskRaw);
1655
+ if (task.owner === memberName && task.status === "in_progress") {
1656
+ currentTask = { id: task.id, subject: task.subject, status: task.status };
1657
+ break;
1658
+ }
1659
+ }
1660
+ } catch {
1661
+ }
1662
+ return { teamName: entry.name, role: memberName, currentTask };
1663
+ } catch {
1664
+ }
1665
+ }
1666
+ } catch {
1667
+ }
1668
+ return null;
1669
+ }
1670
+ function findParentToolCallId(session, agentId) {
1671
+ for (const turn of session.turns) {
1672
+ for (const msg of turn.subAgentActivity) {
1673
+ if (msg.agentId === agentId) {
1674
+ for (const tc of turn.toolCalls) {
1675
+ if (tc.name === "Task" || tc.name === "Agent") {
1676
+ return tc.id;
1677
+ }
1678
+ }
1679
+ }
1680
+ }
1681
+ }
1682
+ return null;
1683
+ }
1684
+ function findAgentMetadata(session, agentId) {
1685
+ for (const turn of session.turns) {
1686
+ for (const msg of turn.subAgentActivity) {
1687
+ if (msg.agentId === agentId) {
1688
+ return {
1689
+ name: msg.agentName,
1690
+ type: msg.subagentType,
1691
+ isBackground: msg.isBackground
1692
+ };
1693
+ }
1694
+ }
1695
+ }
1696
+ return { name: null, type: null, isBackground: false };
1697
+ }
1698
+ async function getSessionOverview(sessionId) {
1699
+ const jsonlPath = await findJsonlPath(sessionId);
1700
+ if (!jsonlPath) return { error: "Session not found" };
1701
+ const content = await (0, import_promises3.readFile)(jsonlPath, "utf-8");
1702
+ const session = parseSession(content);
1703
+ return mapSessionToOverview(session);
1704
+ }
1705
+ async function getTurnDetail(sessionId, turnIndex) {
1706
+ const jsonlPath = await findJsonlPath(sessionId);
1707
+ if (!jsonlPath) return { error: "Session not found" };
1708
+ const content = await (0, import_promises3.readFile)(jsonlPath, "utf-8");
1709
+ const session = parseSession(content);
1710
+ if (turnIndex < 0 || turnIndex >= session.turns.length) return { error: "Turn not found" };
1711
+ return mapTurnToDetail(session, turnIndex);
1712
+ }
1713
+ async function getAgentOverview(sessionId, agentId) {
1714
+ const jsonlPath = await findJsonlPath(sessionId);
1715
+ if (!jsonlPath) return { error: "Session not found" };
1716
+ const content = await (0, import_promises3.readFile)(jsonlPath, "utf-8");
1717
+ const session = parseSession(content);
1718
+ const subagentFile = await findSubagentFile(jsonlPath, agentId);
1719
+ if (!subagentFile) return { error: "Agent not found" };
1720
+ const subagentContent = await (0, import_promises3.readFile)(subagentFile.filePath, "utf-8");
1721
+ const subagentSession = parseSession(subagentContent);
1722
+ const metadata = findAgentMetadata(session, agentId);
1723
+ const parentToolCallId = findParentToolCallId(session, agentId);
1724
+ const teamContext = await findTeamContext(sessionId, subagentFile.fileName);
1725
+ return {
1726
+ sessionId,
1727
+ agentId,
1728
+ name: metadata.name,
1729
+ type: metadata.type,
1730
+ parentToolCallId,
1731
+ isBackground: metadata.isBackground,
1732
+ teamContext,
1733
+ overview: mapSessionToOverview(subagentSession)
1734
+ };
1735
+ }
1736
+ async function getAgentTurnDetail(sessionId, agentId, turnIndex) {
1737
+ const jsonlPath = await findJsonlPath(sessionId);
1738
+ if (!jsonlPath) return { error: "Session not found" };
1739
+ const content = await (0, import_promises3.readFile)(jsonlPath, "utf-8");
1740
+ const subagentFile = await findSubagentFile(jsonlPath, agentId);
1741
+ if (!subagentFile) return { error: "Agent not found" };
1742
+ const subagentContent = await (0, import_promises3.readFile)(subagentFile.filePath, "utf-8");
1743
+ const subagentSession = parseSession(subagentContent);
1744
+ if (turnIndex < 0 || turnIndex >= subagentSession.turns.length) return { error: "Turn not found" };
1745
+ return mapTurnToDetail(subagentSession, turnIndex);
1746
+ }
1747
+
1748
+ // src/commands/sessions.ts
1749
+ var import_promises5 = require("node:fs/promises");
1750
+ var import_node_path6 = require("node:path");
1751
+
1752
+ // src/lib/metadata.ts
1753
+ var import_promises4 = require("node:fs/promises");
1754
+
1755
+ // src/lib/sessionStatus.ts
1756
+ function deriveSessionStatus(rawMessages) {
1757
+ let pendingEnqueues = 0;
1758
+ function result(status, toolName) {
1759
+ const info = { status, pendingQueue: Math.max(0, pendingEnqueues) };
1760
+ if (toolName) info.toolName = toolName;
1761
+ return info;
1762
+ }
1763
+ for (let i = rawMessages.length - 1; i >= 0; i--) {
1764
+ const msg = rawMessages[i];
1765
+ if (msg.type === "queue-operation") {
1766
+ const op = msg.operation;
1767
+ if (op === "enqueue") pendingEnqueues++;
1768
+ else if (op === "dequeue" || op === "remove") pendingEnqueues--;
1769
+ continue;
1770
+ }
1771
+ if (msg.type === "assistant") {
1772
+ const message = msg.message;
1773
+ const stopReason = message?.stop_reason;
1774
+ if (stopReason === "end_turn") {
1775
+ let hasActivity = false;
1776
+ for (let j = i - 1; j >= 0; j--) {
1777
+ const m = rawMessages[j];
1778
+ if (m.type === "user" && !m.isMeta) {
1779
+ hasActivity = true;
1780
+ break;
1781
+ }
1782
+ }
1783
+ return result(hasActivity ? "completed" : "idle");
1784
+ }
1785
+ if (stopReason === "tool_use") {
1786
+ const content = message?.content;
1787
+ const toolUseBlock = content?.findLast?.((b) => b.type === "tool_use");
1788
+ return result("tool_use", toolUseBlock?.name);
1789
+ }
1790
+ return result("thinking");
1791
+ }
1792
+ if (msg.type === "user") {
1793
+ const isMeta = msg.isMeta;
1794
+ if (isMeta) continue;
1795
+ return result("processing");
1796
+ }
1797
+ if (msg.type === "summary") return result("compacting");
1798
+ }
1799
+ return { status: "idle" };
1800
+ }
1801
+
1802
+ // src/lib/metadata.ts
1803
+ async function getSessionMeta(filePath) {
1804
+ let lines;
1805
+ let isPartialRead = false;
1806
+ const fileStat = await (0, import_promises4.stat)(filePath);
1807
+ if (fileStat.size > 65536) {
1808
+ const fh = await (0, import_promises4.open)(filePath, "r");
1809
+ try {
1810
+ const buf = Buffer.alloc(32768);
1811
+ const { bytesRead } = await fh.read(buf, 0, 32768, 0);
1812
+ const text = buf.subarray(0, bytesRead).toString("utf-8");
1813
+ const lastNewline = text.lastIndexOf("\n");
1814
+ lines = (lastNewline > 0 ? text.slice(0, lastNewline) : text).split("\n").filter(Boolean);
1815
+ isPartialRead = true;
1816
+ } finally {
1817
+ await fh.close();
1818
+ }
1819
+ } else {
1820
+ const content = await (0, import_promises4.readFile)(filePath, "utf-8");
1821
+ lines = content.split("\n").filter(Boolean);
1822
+ }
1823
+ let sessionId = "";
1824
+ let version = "";
1825
+ let gitBranch = "";
1826
+ let model = "";
1827
+ let slug = "";
1828
+ let cwd = "";
1829
+ let firstUserMessage = "";
1830
+ let lastUserMessage = "";
1831
+ let timestamp = "";
1832
+ let turnCount = 0;
1833
+ let branchedFrom;
1834
+ for (const line of lines) {
1835
+ try {
1836
+ const obj = JSON.parse(line);
1837
+ if (obj.sessionId && !sessionId) sessionId = obj.sessionId;
1838
+ if (obj.version && !version) version = obj.version;
1839
+ if (obj.gitBranch && !gitBranch) gitBranch = obj.gitBranch;
1840
+ if (obj.slug && !slug) slug = obj.slug;
1841
+ if (obj.cwd && !cwd) cwd = obj.cwd;
1842
+ if (obj.branchedFrom && !branchedFrom) branchedFrom = obj.branchedFrom;
1843
+ if (obj.type === "assistant" && obj.message?.model && !model) {
1844
+ model = obj.message.model;
1845
+ }
1846
+ if (obj.type === "user" && !obj.isMeta && !timestamp) {
1847
+ timestamp = obj.timestamp || "";
1848
+ }
1849
+ if (obj.type === "user" && !obj.isMeta) {
1850
+ const c = obj.message?.content;
1851
+ let extracted = "";
1852
+ if (typeof c === "string") {
1853
+ const cleaned = c.replace(/<[^>]+>[\s\S]*?<\/[^>]+>/g, "").trim();
1854
+ if (cleaned && cleaned.length > 5) extracted = cleaned.slice(0, 120);
1855
+ } else if (Array.isArray(c)) {
1856
+ for (const block of c) {
1857
+ if (block.type === "text") {
1858
+ const cleaned = block.text.replace(/<[^>]+>[\s\S]*?<\/[^>]+>/g, "").trim();
1859
+ if (cleaned && cleaned.length > 5) {
1860
+ extracted = cleaned.slice(0, 120);
1861
+ break;
1862
+ }
1863
+ }
1864
+ }
1865
+ }
1866
+ if (extracted) {
1867
+ if (!firstUserMessage) firstUserMessage = extracted;
1868
+ lastUserMessage = extracted;
1869
+ }
1870
+ turnCount++;
1871
+ }
1872
+ } catch {
1873
+ }
1874
+ }
1875
+ return {
1876
+ sessionId,
1877
+ version,
1878
+ gitBranch,
1879
+ model,
1880
+ slug,
1881
+ cwd,
1882
+ firstUserMessage,
1883
+ lastUserMessage,
1884
+ timestamp,
1885
+ turnCount,
1886
+ lineCount: isPartialRead ? Math.round(fileStat.size / (32768 / lines.length)) : lines.length,
1887
+ branchedFrom
1888
+ };
1889
+ }
1890
+ async function getSessionStatus(filePath) {
1891
+ const CHUNK = 4096;
1892
+ const MAX_CHUNKS = 64;
1893
+ try {
1894
+ const fileStat = await (0, import_promises4.stat)(filePath);
1895
+ if (fileStat.size === 0) return { status: "idle" };
1896
+ const fh = await (0, import_promises4.open)(filePath, "r");
1897
+ try {
1898
+ const meaningful = [];
1899
+ let cursor = fileStat.size;
1900
+ let leftover = "";
1901
+ for (let chunk = 0; chunk < MAX_CHUNKS && cursor > 0; chunk++) {
1902
+ const readSize = Math.min(CHUNK, cursor);
1903
+ cursor -= readSize;
1904
+ const buf = Buffer.alloc(readSize);
1905
+ const { bytesRead } = await fh.read(buf, 0, readSize, cursor);
1906
+ const text = buf.subarray(0, bytesRead).toString("utf-8") + leftover;
1907
+ const lines = text.split("\n");
1908
+ leftover = cursor > 0 ? lines[0] : "";
1909
+ const startIdx = cursor > 0 ? 1 : 0;
1910
+ for (let i = lines.length - 1; i >= startIdx; i--) {
1911
+ const line = lines[i];
1912
+ if (!line) continue;
1913
+ let obj;
1914
+ try {
1915
+ obj = JSON.parse(line);
1916
+ } catch {
1917
+ continue;
1918
+ }
1919
+ if (obj.type === "assistant" || obj.type === "user" || obj.type === "queue-operation") {
1920
+ meaningful.unshift(obj);
1921
+ const isEndTurn = obj.type === "assistant" && obj.message?.stop_reason === "end_turn";
1922
+ const canDerive = obj.type === "assistant" && !isEndTurn || obj.type === "user" && !obj.isMeta;
1923
+ if (canDerive) return deriveSessionStatus(meaningful);
1924
+ }
1925
+ }
1926
+ }
1927
+ return meaningful.length > 0 ? deriveSessionStatus(meaningful) : { status: "idle" };
1928
+ } finally {
1929
+ await fh.close();
1930
+ }
1931
+ } catch {
1932
+ return { status: "idle" };
1933
+ }
1934
+ }
1935
+
1936
+ // src/commands/sessions.ts
1937
+ async function listSessions(opts = {}) {
1938
+ const limit = Math.min(Math.max(1, opts.limit ?? 20), 100);
1939
+ const maxAgeMs = parseMaxAge(opts.maxAge ?? "7d");
1940
+ const cutoff = Date.now() - maxAgeMs;
1941
+ let entries;
1942
+ try {
1943
+ entries = await (0, import_promises5.readdir)(dirs.PROJECTS_DIR, { withFileTypes: true });
1944
+ } catch {
1945
+ return [];
1946
+ }
1947
+ const projectDirs = entries.filter((e) => e.isDirectory() && e.name !== "memory").map((e) => (0, import_node_path6.join)(dirs.PROJECTS_DIR, e.name));
1948
+ const nested = await Promise.all(
1949
+ projectDirs.map(async (projectDir) => {
1950
+ try {
1951
+ const files = await (0, import_promises5.readdir)(projectDir);
1952
+ const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
1953
+ const statResults = await Promise.all(
1954
+ jsonlFiles.map(async (f) => {
1955
+ const filePath = (0, import_node_path6.join)(projectDir, f);
1956
+ try {
1957
+ const s = await (0, import_promises5.stat)(filePath);
1958
+ return s.mtimeMs >= cutoff ? { path: filePath, mtimeMs: s.mtimeMs } : null;
1959
+ } catch {
1960
+ return null;
1961
+ }
1962
+ })
1963
+ );
1964
+ return statResults.filter((r) => r !== null);
1965
+ } catch {
1966
+ return [];
1967
+ }
1968
+ })
1969
+ );
1970
+ const allFiles = nested.flat();
1971
+ allFiles.sort((a, b) => b.mtimeMs - a.mtimeMs);
1972
+ const results = [];
1973
+ for (const file of allFiles) {
1974
+ if (results.length >= limit) break;
1975
+ try {
1976
+ const [meta, statusInfo] = await Promise.all([
1977
+ getSessionMeta(file.path),
1978
+ getSessionStatus(file.path)
1979
+ ]);
1980
+ if (opts.cwd && meta.cwd !== opts.cwd) continue;
1981
+ results.push({
1982
+ sessionId: meta.sessionId,
1983
+ timestamp: meta.timestamp,
1984
+ model: meta.model,
1985
+ cwd: meta.cwd,
1986
+ gitBranch: meta.gitBranch,
1987
+ slug: meta.slug,
1988
+ firstMessage: meta.firstUserMessage,
1989
+ lastMessage: meta.lastUserMessage,
1990
+ turnCount: meta.turnCount,
1991
+ status: statusInfo.status,
1992
+ mtime: file.mtimeMs
1993
+ });
1994
+ } catch {
1995
+ }
1996
+ }
1997
+ return results;
1998
+ }
1999
+ async function currentSession(cwd) {
2000
+ const projectDirName = cwd.replace(/[/.]/g, "-");
2001
+ const projectDir = (0, import_node_path6.join)(dirs.PROJECTS_DIR, projectDirName);
2002
+ let files;
2003
+ try {
2004
+ files = await (0, import_promises5.readdir)(projectDir);
2005
+ } catch {
2006
+ return null;
2007
+ }
2008
+ const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
2009
+ if (jsonlFiles.length === 0) return null;
2010
+ const statResults = await Promise.all(
2011
+ jsonlFiles.map(async (f) => {
2012
+ const filePath = (0, import_node_path6.join)(projectDir, f);
2013
+ try {
2014
+ const s = await (0, import_promises5.stat)(filePath);
2015
+ return { path: filePath, mtimeMs: s.mtimeMs };
2016
+ } catch {
2017
+ return null;
2018
+ }
2019
+ })
2020
+ );
2021
+ const valid = statResults.filter((r) => r !== null);
2022
+ valid.sort((a, b) => b.mtimeMs - a.mtimeMs);
2023
+ const latest = valid[0];
2024
+ if (!latest) return null;
2025
+ const [meta, statusInfo] = await Promise.all([
2026
+ getSessionMeta(latest.path),
2027
+ getSessionStatus(latest.path)
2028
+ ]);
2029
+ return {
2030
+ sessionId: meta.sessionId,
2031
+ timestamp: meta.timestamp,
2032
+ model: meta.model,
2033
+ cwd: meta.cwd,
2034
+ gitBranch: meta.gitBranch,
2035
+ slug: meta.slug,
2036
+ firstMessage: meta.firstUserMessage,
2037
+ lastMessage: meta.lastUserMessage,
2038
+ turnCount: meta.turnCount,
2039
+ status: statusInfo.status,
2040
+ mtime: latest.mtimeMs
2041
+ };
2042
+ }
2043
+
2044
+ // src/commands/index-cmd.ts
2045
+ var import_node_fs3 = require("node:fs");
2046
+ var import_node_path7 = require("node:path");
2047
+ async function indexStats(dbPath) {
2048
+ const path = dbPath ?? DEFAULT_DB_PATH;
2049
+ if (!(0, import_node_fs3.existsSync)(path)) {
2050
+ return { error: `Database not found at ${path}. Run 'cogpit-memory index rebuild' to create it.` };
2051
+ }
2052
+ const index = new SearchIndex(path);
2053
+ const stats = index.getStats();
2054
+ index.close();
2055
+ return stats;
2056
+ }
2057
+ async function indexRebuild(dbPath) {
2058
+ const path = dbPath ?? DEFAULT_DB_PATH;
2059
+ (0, import_node_fs3.mkdirSync)((0, import_node_path7.dirname)(path), { recursive: true });
2060
+ const index = new SearchIndex(path);
2061
+ index.buildFull(dirs.PROJECTS_DIR);
2062
+ const stats = index.getStats();
2063
+ index.close();
2064
+ return { status: "rebuilt", stats };
2065
+ }
2066
+
2067
+ // src/cli.ts
2068
+ function parseArgs(argv) {
2069
+ const command = argv[0];
2070
+ const args = {};
2071
+ switch (command) {
2072
+ case "search": {
2073
+ args.query = argv[1];
2074
+ for (let i = 2; i < argv.length; i++) {
2075
+ switch (argv[i]) {
2076
+ case "--session":
2077
+ args.session = argv[++i];
2078
+ break;
2079
+ case "--max-age":
2080
+ args.maxAge = argv[++i];
2081
+ break;
2082
+ case "--limit":
2083
+ args.limit = parseInt(argv[++i], 10);
2084
+ break;
2085
+ case "--case-sensitive":
2086
+ args.caseSensitive = true;
2087
+ break;
2088
+ }
2089
+ }
2090
+ break;
2091
+ }
2092
+ case "context": {
2093
+ args.sessionId = argv[1];
2094
+ for (let i = 2; i < argv.length; i++) {
2095
+ switch (argv[i]) {
2096
+ case "--turn":
2097
+ args.turnIndex = parseInt(argv[++i], 10);
2098
+ break;
2099
+ case "--agent":
2100
+ args.agentId = argv[++i];
2101
+ break;
2102
+ }
2103
+ }
2104
+ break;
2105
+ }
2106
+ case "sessions": {
2107
+ for (let i = 1; i < argv.length; i++) {
2108
+ switch (argv[i]) {
2109
+ case "--cwd":
2110
+ args.cwd = argv[++i];
2111
+ break;
2112
+ case "--limit":
2113
+ args.limit = parseInt(argv[++i], 10);
2114
+ break;
2115
+ case "--max-age":
2116
+ args.maxAge = argv[++i];
2117
+ break;
2118
+ case "--current":
2119
+ args.current = true;
2120
+ break;
2121
+ }
2122
+ }
2123
+ break;
2124
+ }
2125
+ case "index": {
2126
+ args.subcommand = argv[1] ?? "stats";
2127
+ break;
2128
+ }
2129
+ }
2130
+ return { command, args };
2131
+ }
2132
+ async function main() {
2133
+ const argv = process.argv.slice(2);
2134
+ if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
2135
+ printUsage();
2136
+ process.exit(0);
2137
+ }
2138
+ const cmd = parseArgs(argv);
2139
+ let result;
2140
+ switch (cmd.command) {
2141
+ case "search":
2142
+ if (!cmd.args.query) {
2143
+ console.error(JSON.stringify({ error: "Usage: cogpit-memory search <query>" }));
2144
+ process.exit(1);
2145
+ }
2146
+ result = await searchSessions(cmd.args.query, {
2147
+ sessionId: cmd.args.session,
2148
+ maxAge: cmd.args.maxAge,
2149
+ limit: cmd.args.limit,
2150
+ caseSensitive: cmd.args.caseSensitive
2151
+ });
2152
+ break;
2153
+ case "context":
2154
+ if (!cmd.args.sessionId) {
2155
+ console.error(JSON.stringify({ error: "Usage: cogpit-memory context <sessionId>" }));
2156
+ process.exit(1);
2157
+ }
2158
+ if (cmd.args.agentId && cmd.args.turnIndex !== void 0) {
2159
+ result = await getAgentTurnDetail(cmd.args.sessionId, cmd.args.agentId, cmd.args.turnIndex);
2160
+ } else if (cmd.args.agentId) {
2161
+ result = await getAgentOverview(cmd.args.sessionId, cmd.args.agentId);
2162
+ } else if (cmd.args.turnIndex !== void 0) {
2163
+ result = await getTurnDetail(cmd.args.sessionId, cmd.args.turnIndex);
2164
+ } else {
2165
+ result = await getSessionOverview(cmd.args.sessionId);
2166
+ }
2167
+ break;
2168
+ case "sessions":
2169
+ if (cmd.args.current) {
2170
+ result = await currentSession(cmd.args.cwd ?? process.cwd());
2171
+ } else {
2172
+ result = await listSessions({
2173
+ cwd: cmd.args.cwd,
2174
+ limit: cmd.args.limit,
2175
+ maxAge: cmd.args.maxAge
2176
+ });
2177
+ }
2178
+ break;
2179
+ case "index":
2180
+ if (cmd.args.subcommand === "rebuild") {
2181
+ result = await indexRebuild();
2182
+ } else {
2183
+ result = await indexStats();
2184
+ }
2185
+ break;
2186
+ default:
2187
+ console.error(JSON.stringify({ error: `Unknown command: ${cmd.command}` }));
2188
+ printUsage();
2189
+ process.exit(1);
2190
+ }
2191
+ console.log(JSON.stringify(result, null, 2));
2192
+ }
2193
+ function printUsage() {
2194
+ console.log(`cogpit-memory - query Claude Code session history
2195
+
2196
+ Commands:
2197
+ search <query> [options] Search across sessions
2198
+ --session <id> Scope to single session
2199
+ --max-age <5d> Time window (default: 5d)
2200
+ --limit <20> Max hits (default: 20)
2201
+ --case-sensitive Case sensitive matching
2202
+
2203
+ context <sessionId> Session overview (L1)
2204
+ --turn <N> Turn detail (L2)
2205
+ --agent <id> Sub-agent overview (L3)
2206
+
2207
+ sessions [options] List sessions
2208
+ --cwd <path> Filter by working directory
2209
+ --limit <20> Max results (default: 20)
2210
+ --max-age <7d> Time window (default: 7d)
2211
+ --current Most recent session for --cwd
2212
+
2213
+ index stats Show index stats
2214
+ index rebuild Rebuild full index
2215
+ `);
2216
+ }
2217
+ var isBunCompiled = false;
2218
+ var isScript = process.argv[1]?.endsWith("/cli.ts") || process.argv[1]?.endsWith("/cli.js") || process.argv[1]?.endsWith("/cli.cjs");
2219
+ if (isBunCompiled || isScript) {
2220
+ main().catch((err) => {
2221
+ console.error(JSON.stringify({ error: String(err) }));
2222
+ process.exit(1);
2223
+ });
2224
+ }
2225
+ // Annotate the CommonJS export names for ESM import in node:
2226
+ 0 && (module.exports = {
2227
+ parseArgs
2228
+ });