chat-logbook 0.8.0 → 0.9.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.
package/README.md CHANGED
@@ -69,6 +69,10 @@ The full problem statement, user stories, and direction live in the
69
69
  - **Live updates.** While Claude Code is actively writing to a chat,
70
70
  new messages appear in chat-logbook within seconds — no restart
71
71
  needed.
72
+ - **Chat metadata at a glance.** A ⓘ button on the conversation
73
+ header opens a popover showing when the chat started, when it was
74
+ last updated, the agent, the project working directory, and the
75
+ chat's IDs — with one-click copy.
72
76
 
73
77
  ## Quick start
74
78
 
@@ -0,0 +1 @@
1
+ ALTER TABLE `chats` ADD `project_path` text;
@@ -43,6 +43,13 @@
43
43
  "when": 1779000000000,
44
44
  "tag": "0005_rename_session_to_chat",
45
45
  "breakpoints": true
46
+ },
47
+ {
48
+ "idx": 6,
49
+ "version": "6",
50
+ "when": 1779100000000,
51
+ "tag": "0006_add_project_path",
52
+ "breakpoints": true
46
53
  }
47
54
  ]
48
55
  }
package/api/dist/index.js CHANGED
@@ -8187,7 +8187,8 @@ var chats = sqliteTable(
8187
8187
  agent: text("agent").notNull(),
8188
8188
  sourceId: text("source_id").notNull(),
8189
8189
  firstSeenAt: integer("first_seen_at", { mode: "timestamp_ms" }).notNull(),
8190
- project: text("project")
8190
+ project: text("project"),
8191
+ projectPath: text("project_path")
8191
8192
  },
8192
8193
  (t) => [uniqueIndex("chats_agent_source_idx").on(t.agent, t.sourceId)]
8193
8194
  );
@@ -8285,18 +8286,32 @@ function createApp({ archive: archive2, metadata: metadata2, webDistDir: webDist
8285
8286
  for (const row of rows) {
8286
8287
  if (!visibility.isVisible(row.id)) continue;
8287
8288
  const isDeleted = visibility.isTrashed(row.id);
8288
- const lastMessage = archive2.db.select({ ts: messages.ts }).from(messages).where(
8289
+ const latestRaw = archive2.db.select({ sourcePath: rawMessages.sourcePath }).from(rawMessages).where(
8290
+ and(
8291
+ eq(rawMessages.agent, CLAUDE_CODE_AGENT),
8292
+ eq(rawMessages.sourceId, row.sourceId)
8293
+ )
8294
+ ).orderBy(desc(rawMessages.ingestedAt)).limit(1).get();
8295
+ const tsRange = archive2.db.select({
8296
+ minTs: sql`min(${messages.ts})`,
8297
+ maxTs: sql`max(${messages.ts})`
8298
+ }).from(messages).where(
8289
8299
  and(
8290
8300
  eq(messages.agent, CLAUDE_CODE_AGENT),
8291
8301
  eq(messages.sourceId, row.sourceId)
8292
8302
  )
8293
- ).orderBy(desc(messages.ts)).limit(1).get();
8303
+ ).get();
8304
+ const firstSeenAtMs = row.firstSeenAt.getTime();
8294
8305
  const chat = {
8295
8306
  id: row.sourceId,
8307
+ chatId: row.chatId,
8308
+ agent: row.agent,
8296
8309
  title: deriveTitle(archive2, metadata2, row),
8297
8310
  project: row.project ?? "",
8298
- createdAt: row.firstSeenAt.getTime(),
8299
- updatedAt: lastMessage ? lastMessage.ts.getTime() : row.firstSeenAt.getTime()
8311
+ projectPath: row.projectPath ?? null,
8312
+ sourceFilePath: latestRaw?.sourcePath ?? null,
8313
+ createdAt: tsRange?.minTs ?? firstSeenAtMs,
8314
+ updatedAt: tsRange?.maxTs ?? firstSeenAtMs
8300
8315
  };
8301
8316
  if (isDeleted) chat.isDeleted = true;
8302
8317
  chats2.push(chat);
@@ -8795,7 +8810,14 @@ async function runIngestion(opts) {
8795
8810
  eq(chatScanState.sourceId, ref.sourceId)
8796
8811
  )
8797
8812
  ).get();
8798
- ensureChat(opts.archive, plugin.id, ref.sourceId, now(), ref.project);
8813
+ ensureChat(
8814
+ opts.archive,
8815
+ plugin.id,
8816
+ ref.sourceId,
8817
+ now(),
8818
+ ref.project,
8819
+ ref.projectPath
8820
+ );
8799
8821
  if (stat4 && prior && prior.lastMtimeMs === stat4.mtimeMs && prior.lastSizeBytes === stat4.size) {
8800
8822
  result.skippedByMtime += 1;
8801
8823
  continue;
@@ -8880,11 +8902,16 @@ function recordScanState(archive2, agent, sourceId, sourcePath, stat4, scannedAt
8880
8902
  }).run();
8881
8903
  }
8882
8904
  }
8883
- function ensureChat(archive2, agent, sourceId, firstSeenAt, project) {
8905
+ function ensureChat(archive2, agent, sourceId, firstSeenAt, project, projectPath) {
8884
8906
  const existing = archive2.db.select().from(chats).where(and(eq(chats.agent, agent), eq(chats.sourceId, sourceId))).get();
8885
8907
  if (existing) {
8886
- if (project && existing.project !== project) {
8887
- archive2.db.update(chats).set({ project }).where(eq(chats.id, existing.id)).run();
8908
+ const updates = {};
8909
+ if (project && existing.project !== project) updates.project = project;
8910
+ if (projectPath && existing.projectPath !== projectPath) {
8911
+ updates.projectPath = projectPath;
8912
+ }
8913
+ if (Object.keys(updates).length > 0) {
8914
+ archive2.db.update(chats).set(updates).where(eq(chats.id, existing.id)).run();
8888
8915
  }
8889
8916
  return existing.id;
8890
8917
  }
@@ -8896,7 +8923,8 @@ function ensureChat(archive2, agent, sourceId, firstSeenAt, project) {
8896
8923
  agent,
8897
8924
  sourceId,
8898
8925
  firstSeenAt,
8899
- project: project ?? null
8926
+ project: project ?? null,
8927
+ projectPath: projectPath ?? null
8900
8928
  }).run();
8901
8929
  return id;
8902
8930
  }
@@ -10786,7 +10814,8 @@ var ClaudeCodePlugin = class {
10786
10814
  sourceId,
10787
10815
  sourcePath,
10788
10816
  watchPaths: [sourcePath],
10789
- project: cwd ? path3.basename(cwd) : void 0
10817
+ project: cwd ? path3.basename(cwd) : void 0,
10818
+ projectPath: cwd ?? void 0
10790
10819
  };
10791
10820
  }
10792
10821
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chat-logbook",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "A local-first conversation manager for Claude Code",
5
5
  "license": "AGPL-3.0-only",
6
6
  "type": "module",