codesesh 0.1.0 → 0.1.3

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 ADDED
@@ -0,0 +1,83 @@
1
+ # CodeSesh
2
+
3
+ <p align="center">
4
+ <img src="https://codesesh.xingkaixin.me/logo.svg" alt="CodeSesh" width="120" height="120">
5
+ </p>
6
+
7
+ <p align="center"><strong>One place to see every AI coding session you've ever had.</strong></p>
8
+
9
+ CodeSesh scans your local machine, finds every AI agent session (Claude Code, Cursor, Kimi, Codex, OpenCode), and surfaces them in a unified, beautiful Web UI.
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ npx codesesh
15
+ ```
16
+
17
+ Your browser will open at `http://localhost:4321` with all your sessions ready to browse.
18
+
19
+ ## Features
20
+
21
+ - **Unified Timeline** — Browse sessions across all your AI agents in a single, searchable interface
22
+ - **Full Conversation Replay** — Read every message, tool call, and reasoning step exactly as it happened
23
+ - **Cost & Token Visibility** — See exactly how many tokens and dollars each session consumed
24
+ - **Zero Configuration** — Just run it. CodeSesh auto-discovers everything on your filesystem
25
+ - **100% Local & Private** — Nothing leaves your machine. No accounts, no cloud sync, no telemetry
26
+
27
+ ## Supported Agents
28
+
29
+ | Agent | Status |
30
+ | ----------- | ------------ |
31
+ | Claude Code | ✅ Supported |
32
+ | Cursor | ✅ Supported |
33
+ | Kimi | ✅ Supported |
34
+ | Codex | ✅ Supported |
35
+ | OpenCode | ✅ Supported |
36
+
37
+ ## Usage
38
+
39
+ ```bash
40
+ # Start the web UI (default port 4321)
41
+ npx codesesh
42
+
43
+ # Choose a custom port
44
+ npx codesesh --port 8080
45
+
46
+ # Only show sessions from the last 3 days
47
+ npx codesesh --days 3
48
+
49
+ # Filter to sessions from current project
50
+ npx codesesh --cwd .
51
+
52
+ # Only show specific agent
53
+ npx codesesh --agent claudecode
54
+
55
+ # Output JSON instead of starting server
56
+ npx codesesh --json
57
+ ```
58
+
59
+ ## CLI Options
60
+
61
+ | Flag | Alias | Default | Description |
62
+ | ----------- | ----- | ------- | ----------------------------------------------------------- |
63
+ | `--port` | `-p` | `4321` | HTTP server port |
64
+ | `--days` | `-d` | `7` | Only include sessions from the last N days (`0` = all time) |
65
+ | `--cwd` | — | — | Filter to sessions from a project directory |
66
+ | `--agent` | `-a` | all | Filter to specific agent(s), comma-separated |
67
+ | `--from` | — | — | Sessions created after this date `YYYY-MM-DD` |
68
+ | `--to` | — | — | Sessions created before this date `YYYY-MM-DD` |
69
+ | `--json` | `-j` | `false` | Output JSON and exit (no server) |
70
+ | `--no-open` | — | `false` | Don't auto-open the browser |
71
+
72
+ ## Requirements
73
+
74
+ - Node.js 18+
75
+
76
+ ## Links
77
+
78
+ - [GitHub](https://github.com/xingkaixin/codesesh)
79
+ - [Issues](https://github.com/xingkaixin/codesesh/issues)
80
+
81
+ ## License
82
+
83
+ MIT
@@ -494,6 +494,8 @@ var ClaudeCodeAgent = class extends BaseAgent {
494
494
  let messageCount = 0;
495
495
  let model = null;
496
496
  let cwd = null;
497
+ let totalInputTokens = 0;
498
+ let totalOutputTokens = 0;
497
499
  for (const line of lines) {
498
500
  try {
499
501
  const data = JSON.parse(line);
@@ -512,6 +514,13 @@ var ClaudeCodeAgent = class extends BaseAgent {
512
514
  const m = msg["model"];
513
515
  if (typeof m === "string" && m.trim()) model = m.trim();
514
516
  }
517
+ if (role === "assistant") {
518
+ const usage = msg["usage"];
519
+ if (usage && typeof usage === "object") {
520
+ totalInputTokens += (usage["input_tokens"] ?? 0) + (usage["cache_creation_input_tokens"] ?? 0) + (usage["cache_read_input_tokens"] ?? 0);
521
+ totalOutputTokens += usage["output_tokens"] ?? 0;
522
+ }
523
+ }
515
524
  }
516
525
  } catch {
517
526
  }
@@ -529,8 +538,8 @@ var ClaudeCodeAgent = class extends BaseAgent {
529
538
  time_updated: updatedAt,
530
539
  stats: {
531
540
  message_count: messageCount,
532
- total_input_tokens: 0,
533
- total_output_tokens: 0,
541
+ total_input_tokens: totalInputTokens,
542
+ total_output_tokens: totalOutputTokens,
534
543
  total_cost: 0
535
544
  }
536
545
  };
@@ -1447,6 +1456,7 @@ var KimiAgent = class extends BaseAgent {
1447
1456
  const pendingToolCalls = /* @__PURE__ */ new Map();
1448
1457
  const ignoredToolCallIds = /* @__PURE__ */ new Set();
1449
1458
  let seq = 0;
1459
+ const fallbackTs = meta.createdAt;
1450
1460
  for (const record of parseJsonlLines(content)) {
1451
1461
  seq++;
1452
1462
  try {
@@ -1459,8 +1469,8 @@ var KimiAgent = class extends BaseAgent {
1459
1469
  this.buildMessage({
1460
1470
  messageId: `context-${seq}`,
1461
1471
  role: "user",
1462
- timestampMs: 0,
1463
- parts: [{ type: "text", text, time_created: 0 }]
1472
+ timestampMs: fallbackTs,
1473
+ parts: [{ type: "text", text, time_created: fallbackTs }]
1464
1474
  })
1465
1475
  );
1466
1476
  }
@@ -1470,7 +1480,8 @@ var KimiAgent = class extends BaseAgent {
1470
1480
  const { message, toolIndexes } = this.buildContextAssistantMessage(
1471
1481
  record,
1472
1482
  seq,
1473
- ignoredToolCallIds
1483
+ ignoredToolCallIds,
1484
+ fallbackTs
1474
1485
  );
1475
1486
  if (!message) continue;
1476
1487
  const msgIndex = messages.length;
@@ -1483,7 +1494,7 @@ var KimiAgent = class extends BaseAgent {
1483
1494
  if (role === "tool") {
1484
1495
  const callId = String(record.tool_call_id ?? "").trim();
1485
1496
  if (callId && ignoredToolCallIds.has(callId)) continue;
1486
- const outputParts = normalizeToolOutputParts(record.content, 0);
1497
+ const outputParts = normalizeToolOutputParts(record.content, fallbackTs);
1487
1498
  if (callId && this.backfillToolOutput(messages, pendingToolCalls, callId, outputParts)) {
1488
1499
  continue;
1489
1500
  }
@@ -1492,7 +1503,7 @@ var KimiAgent = class extends BaseAgent {
1492
1503
  this.buildMessage({
1493
1504
  messageId: `context-${seq}`,
1494
1505
  role: "tool",
1495
- timestampMs: 0,
1506
+ timestampMs: fallbackTs,
1496
1507
  parts: outputParts
1497
1508
  })
1498
1509
  );
@@ -1523,6 +1534,20 @@ var KimiAgent = class extends BaseAgent {
1523
1534
  const payload = message.payload ?? {};
1524
1535
  const timestamp = Number(record.timestamp ?? 0);
1525
1536
  const timestampMs = Number.isFinite(timestamp) ? Math.floor(timestamp * 1e3) : 0;
1537
+ const usage = message["usage"];
1538
+ if (usage && typeof usage === "object") {
1539
+ const inputTokens = Number(usage["input_tokens"] ?? 0);
1540
+ const outputTokens = Number(usage["output_tokens"] ?? 0);
1541
+ if (inputTokens || outputTokens) {
1542
+ for (let i = messages.length - 1; i >= 0; i--) {
1543
+ const msg = messages[i];
1544
+ if (msg.role === "assistant" && !msg.tokens) {
1545
+ msg.tokens = { input: inputTokens, output: outputTokens };
1546
+ break;
1547
+ }
1548
+ }
1549
+ }
1550
+ }
1526
1551
  if (msgType === "TurnBegin") {
1527
1552
  const userInput = payload.user_input;
1528
1553
  if (Array.isArray(userInput) && userInput.length > 0) {
@@ -1653,7 +1678,7 @@ var KimiAgent = class extends BaseAgent {
1653
1678
  parts: opts.parts
1654
1679
  };
1655
1680
  }
1656
- buildContextAssistantMessage(record, seq, ignoredToolCallIds) {
1681
+ buildContextAssistantMessage(record, seq, ignoredToolCallIds, fallbackTs) {
1657
1682
  const parts = [];
1658
1683
  const toolIndexes = /* @__PURE__ */ new Map();
1659
1684
  const content = record.content;
@@ -1664,10 +1689,10 @@ var KimiAgent = class extends BaseAgent {
1664
1689
  const partType = String(ci.type ?? "");
1665
1690
  if (partType === "think") {
1666
1691
  const text = String(ci.think ?? "");
1667
- if (text.trim()) parts.push({ type: "reasoning", text, time_created: 0 });
1692
+ if (text.trim()) parts.push({ type: "reasoning", text, time_created: fallbackTs });
1668
1693
  } else if (partType === "text") {
1669
1694
  const text = String(ci.text ?? "");
1670
- if (text.trim()) parts.push({ type: "text", text, time_created: 0 });
1695
+ if (text.trim()) parts.push({ type: "text", text, time_created: fallbackTs });
1671
1696
  }
1672
1697
  }
1673
1698
  }
@@ -1691,7 +1716,7 @@ var KimiAgent = class extends BaseAgent {
1691
1716
  callID: callId,
1692
1717
  title: mapToolTitle(toolName),
1693
1718
  state: { arguments: normalizeToolArguments(function_.arguments), output: null },
1694
- time_created: 0
1719
+ time_created: fallbackTs
1695
1720
  };
1696
1721
  toolIndexes.set(callId, parts.length);
1697
1722
  parts.push(part);
@@ -1702,7 +1727,7 @@ var KimiAgent = class extends BaseAgent {
1702
1727
  message: this.buildMessage({
1703
1728
  messageId: `context-${seq}`,
1704
1729
  role: "assistant",
1705
- timestampMs: 0,
1730
+ timestampMs: fallbackTs,
1706
1731
  parts: []
1707
1732
  }),
1708
1733
  toolIndexes
@@ -1712,7 +1737,7 @@ var KimiAgent = class extends BaseAgent {
1712
1737
  const message = this.buildMessage({
1713
1738
  messageId: `context-${seq}`,
1714
1739
  role: "assistant",
1715
- timestampMs: 0,
1740
+ timestampMs: fallbackTs,
1716
1741
  parts,
1717
1742
  agent: "kimi",
1718
1743
  mode: allTools ? "tool" : void 0
@@ -2119,6 +2144,11 @@ var CodexAgent = class extends BaseAgent {
2119
2144
  let currentAssistantIndex = null;
2120
2145
  let latestAssistantTextIndex = null;
2121
2146
  let pendingPlan = null;
2147
+ let prevCumulativeTotal = 0;
2148
+ let prevInput = 0;
2149
+ let prevCached = 0;
2150
+ let prevOutput = 0;
2151
+ let prevReasoning = 0;
2122
2152
  for (const record of parseJsonlLines(content)) {
2123
2153
  try {
2124
2154
  const result = this.convertRecord(
@@ -2133,10 +2163,54 @@ var CodexAgent = class extends BaseAgent {
2133
2163
  currentAssistantIndex = result.currentAssistantIndex;
2134
2164
  latestAssistantTextIndex = result.latestAssistantTextIndex;
2135
2165
  pendingPlan = result.pendingPlan;
2136
- this.extractTokenUsage(record, (input, output) => {
2137
- totalInputTokens += input;
2138
- totalOutputTokens += output;
2139
- });
2166
+ const recordType = String(record["type"] ?? "");
2167
+ if (recordType === "event_msg") {
2168
+ const payload = record["payload"] ?? {};
2169
+ if (String(payload["type"] ?? "") === "token_count") {
2170
+ const info = payload["info"];
2171
+ const totalUsage = info?.["total_token_usage"];
2172
+ const cumulativeTotal = Number(totalUsage?.["total_tokens"] ?? 0);
2173
+ if (cumulativeTotal > 0 && cumulativeTotal === prevCumulativeTotal) {
2174
+ } else {
2175
+ prevCumulativeTotal = cumulativeTotal;
2176
+ const lastUsage = info?.["last_token_usage"];
2177
+ let inputTokens = 0;
2178
+ let cachedInputTokens = 0;
2179
+ let outputTokens = 0;
2180
+ let reasoningTokens = 0;
2181
+ if (lastUsage) {
2182
+ inputTokens = Number(lastUsage["input_tokens"] ?? 0);
2183
+ cachedInputTokens = Number(lastUsage["cached_input_tokens"] ?? 0);
2184
+ outputTokens = Number(lastUsage["output_tokens"] ?? 0);
2185
+ reasoningTokens = Number(lastUsage["reasoning_output_tokens"] ?? 0);
2186
+ } else if (cumulativeTotal > 0 && totalUsage) {
2187
+ inputTokens = Number(totalUsage["input_tokens"] ?? 0) - prevInput;
2188
+ cachedInputTokens = Number(totalUsage["cached_input_tokens"] ?? 0) - prevCached;
2189
+ outputTokens = Number(totalUsage["output_tokens"] ?? 0) - prevOutput;
2190
+ reasoningTokens = Number(totalUsage["reasoning_output_tokens"] ?? 0) - prevReasoning;
2191
+ prevInput = Number(totalUsage["input_tokens"] ?? 0);
2192
+ prevCached = Number(totalUsage["cached_input_tokens"] ?? 0);
2193
+ prevOutput = Number(totalUsage["output_tokens"] ?? 0);
2194
+ prevReasoning = Number(totalUsage["reasoning_output_tokens"] ?? 0);
2195
+ }
2196
+ const uncachedInput = Math.max(0, inputTokens - cachedInputTokens);
2197
+ if (uncachedInput || outputTokens || reasoningTokens) {
2198
+ totalInputTokens += uncachedInput;
2199
+ totalOutputTokens += outputTokens + reasoningTokens;
2200
+ for (let i = messages.length - 1; i >= 0; i--) {
2201
+ const msg = messages[i];
2202
+ if (msg.role === "assistant" && !msg.tokens) {
2203
+ msg.tokens = {
2204
+ input: uncachedInput,
2205
+ output: outputTokens + reasoningTokens
2206
+ };
2207
+ break;
2208
+ }
2209
+ }
2210
+ }
2211
+ }
2212
+ }
2213
+ }
2140
2214
  } catch {
2141
2215
  }
2142
2216
  }
@@ -2226,6 +2300,13 @@ var CodexAgent = class extends BaseAgent {
2226
2300
  let updatedAt = createdAt;
2227
2301
  let messageCount = 0;
2228
2302
  let model = null;
2303
+ let totalInputTokens = 0;
2304
+ let totalOutputTokens = 0;
2305
+ let scanPrevCumulativeTotal = 0;
2306
+ let scanPrevInput = 0;
2307
+ let scanPrevCached = 0;
2308
+ let scanPrevOutput = 0;
2309
+ let scanPrevReasoning = 0;
2229
2310
  const COUNTED_TYPES = /* @__PURE__ */ new Set(["message", "function_call", "function_call_output"]);
2230
2311
  for (const line of lines) {
2231
2312
  try {
@@ -2249,6 +2330,40 @@ var CodexAgent = class extends BaseAgent {
2249
2330
  if (typeof m === "string" && m.trim()) model = m.trim();
2250
2331
  }
2251
2332
  }
2333
+ if (recordType === "event_msg") {
2334
+ const p = data["payload"] ?? {};
2335
+ if (String(p["type"] ?? "") === "token_count") {
2336
+ const info = p["info"];
2337
+ const totalUsage = info?.["total_token_usage"];
2338
+ const cumulativeTotal = Number(totalUsage?.["total_tokens"] ?? 0);
2339
+ if (cumulativeTotal > 0 && cumulativeTotal !== scanPrevCumulativeTotal) {
2340
+ scanPrevCumulativeTotal = cumulativeTotal;
2341
+ const lastUsage = info?.["last_token_usage"];
2342
+ let inputTokens = 0;
2343
+ let cachedInputTokens = 0;
2344
+ let outputTokens = 0;
2345
+ let reasoningTokens = 0;
2346
+ if (lastUsage) {
2347
+ inputTokens = Number(lastUsage["input_tokens"] ?? 0);
2348
+ cachedInputTokens = Number(lastUsage["cached_input_tokens"] ?? 0);
2349
+ outputTokens = Number(lastUsage["output_tokens"] ?? 0);
2350
+ reasoningTokens = Number(lastUsage["reasoning_output_tokens"] ?? 0);
2351
+ } else if (cumulativeTotal > 0 && totalUsage) {
2352
+ inputTokens = Number(totalUsage["input_tokens"] ?? 0) - scanPrevInput;
2353
+ cachedInputTokens = Number(totalUsage["cached_input_tokens"] ?? 0) - scanPrevCached;
2354
+ outputTokens = Number(totalUsage["output_tokens"] ?? 0) - scanPrevOutput;
2355
+ reasoningTokens = Number(totalUsage["reasoning_output_tokens"] ?? 0) - scanPrevReasoning;
2356
+ scanPrevInput = Number(totalUsage["input_tokens"] ?? 0);
2357
+ scanPrevCached = Number(totalUsage["cached_input_tokens"] ?? 0);
2358
+ scanPrevOutput = Number(totalUsage["output_tokens"] ?? 0);
2359
+ scanPrevReasoning = Number(totalUsage["reasoning_output_tokens"] ?? 0);
2360
+ }
2361
+ const uncachedInput = Math.max(0, inputTokens - cachedInputTokens);
2362
+ totalInputTokens += uncachedInput;
2363
+ totalOutputTokens += outputTokens + reasoningTokens;
2364
+ }
2365
+ }
2366
+ }
2252
2367
  } catch {
2253
2368
  }
2254
2369
  }
@@ -2262,8 +2377,8 @@ var CodexAgent = class extends BaseAgent {
2262
2377
  time_updated: updatedAt,
2263
2378
  stats: {
2264
2379
  message_count: messageCount,
2265
- total_input_tokens: 0,
2266
- total_output_tokens: 0,
2380
+ total_input_tokens: totalInputTokens,
2381
+ total_output_tokens: totalOutputTokens,
2267
2382
  total_cost: 0
2268
2383
  }
2269
2384
  };
@@ -2294,21 +2409,6 @@ var CodexAgent = class extends BaseAgent {
2294
2409
  }
2295
2410
  return null;
2296
2411
  }
2297
- // ---- Token usage extraction ----
2298
- extractTokenUsage(record, accumulate) {
2299
- const recordType = String(record["type"] ?? "");
2300
- if (recordType !== "response_item") return;
2301
- const payload = record["payload"] ?? {};
2302
- const info = payload["info"];
2303
- if (!info) return;
2304
- const totalUsage = info["total_token_usage"];
2305
- if (!totalUsage) return;
2306
- const inputTokens = Number(totalUsage["input_tokens"] ?? 0);
2307
- const outputTokens = Number(totalUsage["output_tokens"] ?? 0);
2308
- if (inputTokens || outputTokens) {
2309
- accumulate(inputTokens, outputTokens);
2310
- }
2311
- }
2312
2412
  // ---- Record conversion ----
2313
2413
  convertRecord(data, messages, pendingToolCalls, sessionId, currentAssistantIndex, latestAssistantTextIndex, pendingPlan) {
2314
2414
  const recordType = String(data["type"] ?? "");
@@ -2320,7 +2420,7 @@ var CodexAgent = class extends BaseAgent {
2320
2420
  }
2321
2421
  const payload = data["payload"] ?? {};
2322
2422
  const payloadType = String(payload["type"] ?? "");
2323
- const timestampMs = parseTimestampMs2(payload);
2423
+ const timestampMs = parseTimestampMs2(data) || parseTimestampMs2(payload);
2324
2424
  switch (payloadType) {
2325
2425
  case "message": {
2326
2426
  const role = String(payload["role"] ?? "");
@@ -2902,7 +3002,12 @@ var CursorAgent = class extends BaseAgent {
2902
3002
  const title = this.extractTitle(composer);
2903
3003
  const createdAt = composer.createdAt ?? 0;
2904
3004
  const updatedAt = composer.updatedAt ?? createdAt;
2905
- const messageCount = this.countMessagesFromBubbles(db, composerId);
3005
+ const messages = this.loadMessagesFromBubbles(db, composerId, sessionId);
3006
+ const hasSubagents = Array.isArray(composer.subagentInfos) && composer.subagentInfos.length > 0;
3007
+ if (messages.length === 0 && !hasSubagents) {
3008
+ continue;
3009
+ }
3010
+ const messageCount = messages.length;
2906
3011
  const directory = workspacePathMap.get(composerId) ?? "";
2907
3012
  heads.push({
2908
3013
  id: sessionId,
@@ -3571,4 +3676,4 @@ export {
3571
3676
  scanSessions,
3572
3677
  scanSessionsAsync
3573
3678
  };
3574
- //# sourceMappingURL=chunk-H3D3GNQK.js.map
3679
+ //# sourceMappingURL=chunk-BQ22Y6H4.js.map