chat-logbook 0.8.0 → 0.10.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 +7 -0
- package/api/dist/drizzle/0003_add_deleted_at.sql +2 -0
- package/api/dist/drizzle/archive/0006_add_project_path.sql +1 -0
- package/api/dist/drizzle/archive/meta/_journal.json +7 -0
- package/api/dist/drizzle/meta/_journal.json +7 -0
- package/api/dist/index.js +63 -20
- package/package.json +1 -1
- package/web/dist/assets/index-Bt6Wu3MG.js +57 -0
- package/web/dist/assets/index-Dy8ynnur.css +2 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-1hCPKEIk.js +0 -46
- package/web/dist/assets/index-BCVd1SRQ.css +0 -2
package/README.md
CHANGED
|
@@ -60,6 +60,9 @@ The full problem statement, user stories, and direction live in the
|
|
|
60
60
|
Code's terminal look.
|
|
61
61
|
- **Soft delete with Trash.** Hide chats you don't want to see; restore
|
|
62
62
|
them anytime.
|
|
63
|
+
- **Sortable lists.** Sort your chats by title, created time, or updated
|
|
64
|
+
time, and the choice sticks between visits. Trash sorts independently —
|
|
65
|
+
by deleted time by default.
|
|
63
66
|
- **Custom chat titles.** Rename any chat — click its title in the
|
|
64
67
|
list or conversation header, or select it and press `F2` / `↵`. Clear
|
|
65
68
|
the title to fall back to the first message.
|
|
@@ -69,6 +72,10 @@ The full problem statement, user stories, and direction live in the
|
|
|
69
72
|
- **Live updates.** While Claude Code is actively writing to a chat,
|
|
70
73
|
new messages appear in chat-logbook within seconds — no restart
|
|
71
74
|
needed.
|
|
75
|
+
- **Chat metadata at a glance.** A ⓘ button on the conversation
|
|
76
|
+
header opens a popover showing when the chat started, when it was
|
|
77
|
+
last updated, the agent, the project working directory, and the
|
|
78
|
+
chat's IDs — with one-click copy.
|
|
72
79
|
|
|
73
80
|
## Quick start
|
|
74
81
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE `chats` ADD `project_path` text;
|
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
|
);
|
|
@@ -8251,11 +8252,16 @@ var ingestionEvents = sqliteTable("ingestion_events", {
|
|
|
8251
8252
|
|
|
8252
8253
|
// src/visibility.ts
|
|
8253
8254
|
function loadChatVisibility(metadata2, opts) {
|
|
8254
|
-
const
|
|
8255
|
+
const deleted = metadata2.listDeleted();
|
|
8256
|
+
const trashed = new Set(deleted.map((r) => r.id));
|
|
8257
|
+
const deletedAtById = new Map(
|
|
8258
|
+
deleted.map((r) => [r.id, r.deletedAt?.getTime() ?? null])
|
|
8259
|
+
);
|
|
8255
8260
|
const showTrashed = opts.includeTrashed === true;
|
|
8256
8261
|
return {
|
|
8257
8262
|
isTrashed: (internalId) => trashed.has(internalId),
|
|
8258
|
-
isVisible: (internalId) => showTrashed || !trashed.has(internalId)
|
|
8263
|
+
isVisible: (internalId) => showTrashed || !trashed.has(internalId),
|
|
8264
|
+
deletedAt: (internalId) => deletedAtById.get(internalId) ?? null
|
|
8259
8265
|
};
|
|
8260
8266
|
}
|
|
8261
8267
|
|
|
@@ -8285,18 +8291,33 @@ function createApp({ archive: archive2, metadata: metadata2, webDistDir: webDist
|
|
|
8285
8291
|
for (const row of rows) {
|
|
8286
8292
|
if (!visibility.isVisible(row.id)) continue;
|
|
8287
8293
|
const isDeleted = visibility.isTrashed(row.id);
|
|
8288
|
-
const
|
|
8294
|
+
const latestRaw = archive2.db.select({ sourcePath: rawMessages.sourcePath }).from(rawMessages).where(
|
|
8295
|
+
and(
|
|
8296
|
+
eq(rawMessages.agent, CLAUDE_CODE_AGENT),
|
|
8297
|
+
eq(rawMessages.sourceId, row.sourceId)
|
|
8298
|
+
)
|
|
8299
|
+
).orderBy(desc(rawMessages.ingestedAt)).limit(1).get();
|
|
8300
|
+
const tsRange = archive2.db.select({
|
|
8301
|
+
minTs: sql`min(${messages.ts})`,
|
|
8302
|
+
maxTs: sql`max(${messages.ts})`
|
|
8303
|
+
}).from(messages).where(
|
|
8289
8304
|
and(
|
|
8290
8305
|
eq(messages.agent, CLAUDE_CODE_AGENT),
|
|
8291
8306
|
eq(messages.sourceId, row.sourceId)
|
|
8292
8307
|
)
|
|
8293
|
-
).
|
|
8308
|
+
).get();
|
|
8309
|
+
const firstSeenAtMs = row.firstSeenAt.getTime();
|
|
8294
8310
|
const chat = {
|
|
8295
8311
|
id: row.sourceId,
|
|
8312
|
+
chatId: row.chatId,
|
|
8313
|
+
agent: row.agent,
|
|
8296
8314
|
title: deriveTitle(archive2, metadata2, row),
|
|
8297
8315
|
project: row.project ?? "",
|
|
8298
|
-
|
|
8299
|
-
|
|
8316
|
+
projectPath: row.projectPath ?? null,
|
|
8317
|
+
sourceFilePath: latestRaw?.sourcePath ?? null,
|
|
8318
|
+
createdAt: tsRange?.minTs ?? firstSeenAtMs,
|
|
8319
|
+
updatedAt: tsRange?.maxTs ?? firstSeenAtMs,
|
|
8320
|
+
deletedAt: visibility.deletedAt(row.id)
|
|
8300
8321
|
};
|
|
8301
8322
|
if (isDeleted) chat.isDeleted = true;
|
|
8302
8323
|
chats2.push(chat);
|
|
@@ -8685,7 +8706,10 @@ var chatsMeta = sqliteTable("chats_meta", {
|
|
|
8685
8706
|
isDeleted: integer("is_deleted", { mode: "boolean" }).notNull().default(false),
|
|
8686
8707
|
customTitle: text("custom_title"),
|
|
8687
8708
|
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
|
|
8688
|
-
updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull()
|
|
8709
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull(),
|
|
8710
|
+
// Set when a chat is moved to Trash; null while active. Drives the Trash
|
|
8711
|
+
// view's independent "Deleted time" sort axis.
|
|
8712
|
+
deletedAt: integer("deleted_at", { mode: "timestamp_ms" })
|
|
8689
8713
|
});
|
|
8690
8714
|
|
|
8691
8715
|
// src/metadata/repository.ts
|
|
@@ -8721,23 +8745,28 @@ function createMetadataRepository({
|
|
|
8721
8745
|
id: internalId,
|
|
8722
8746
|
isDeleted: true,
|
|
8723
8747
|
createdAt: now,
|
|
8724
|
-
updatedAt: now
|
|
8748
|
+
updatedAt: now,
|
|
8749
|
+
deletedAt: now
|
|
8725
8750
|
}).onConflictDoUpdate({
|
|
8726
8751
|
target: chatsMeta.id,
|
|
8727
|
-
set: { isDeleted: true, updatedAt: now }
|
|
8752
|
+
set: { isDeleted: true, updatedAt: now, deletedAt: now }
|
|
8728
8753
|
}).run();
|
|
8729
8754
|
},
|
|
8730
8755
|
restore(internalId) {
|
|
8731
8756
|
const now = /* @__PURE__ */ new Date();
|
|
8732
|
-
db.update(chatsMeta).set({ isDeleted: false, updatedAt: now }).where(eq(chatsMeta.id, internalId)).run();
|
|
8757
|
+
db.update(chatsMeta).set({ isDeleted: false, updatedAt: now, deletedAt: null }).where(eq(chatsMeta.id, internalId)).run();
|
|
8733
8758
|
},
|
|
8734
8759
|
isDeleted(internalId) {
|
|
8735
8760
|
const row = db.select({ isDeleted: chatsMeta.isDeleted }).from(chatsMeta).where(eq(chatsMeta.id, internalId)).get();
|
|
8736
8761
|
return row?.isDeleted ?? false;
|
|
8737
8762
|
},
|
|
8738
|
-
|
|
8739
|
-
const
|
|
8740
|
-
return
|
|
8763
|
+
getDeletedAt(internalId) {
|
|
8764
|
+
const row = db.select({ deletedAt: chatsMeta.deletedAt }).from(chatsMeta).where(eq(chatsMeta.id, internalId)).get();
|
|
8765
|
+
return row?.deletedAt ?? null;
|
|
8766
|
+
},
|
|
8767
|
+
listDeleted() {
|
|
8768
|
+
const rows = db.select({ id: chatsMeta.id, deletedAt: chatsMeta.deletedAt }).from(chatsMeta).where(eq(chatsMeta.isDeleted, true)).all();
|
|
8769
|
+
return rows.map((r) => ({ id: r.id, deletedAt: r.deletedAt ?? null }));
|
|
8741
8770
|
},
|
|
8742
8771
|
getCustomTitle(internalId) {
|
|
8743
8772
|
const row = db.select({ customTitle: chatsMeta.customTitle }).from(chatsMeta).where(eq(chatsMeta.id, internalId)).get();
|
|
@@ -8795,7 +8824,14 @@ async function runIngestion(opts) {
|
|
|
8795
8824
|
eq(chatScanState.sourceId, ref.sourceId)
|
|
8796
8825
|
)
|
|
8797
8826
|
).get();
|
|
8798
|
-
ensureChat(
|
|
8827
|
+
ensureChat(
|
|
8828
|
+
opts.archive,
|
|
8829
|
+
plugin.id,
|
|
8830
|
+
ref.sourceId,
|
|
8831
|
+
now(),
|
|
8832
|
+
ref.project,
|
|
8833
|
+
ref.projectPath
|
|
8834
|
+
);
|
|
8799
8835
|
if (stat4 && prior && prior.lastMtimeMs === stat4.mtimeMs && prior.lastSizeBytes === stat4.size) {
|
|
8800
8836
|
result.skippedByMtime += 1;
|
|
8801
8837
|
continue;
|
|
@@ -8880,11 +8916,16 @@ function recordScanState(archive2, agent, sourceId, sourcePath, stat4, scannedAt
|
|
|
8880
8916
|
}).run();
|
|
8881
8917
|
}
|
|
8882
8918
|
}
|
|
8883
|
-
function ensureChat(archive2, agent, sourceId, firstSeenAt, project) {
|
|
8919
|
+
function ensureChat(archive2, agent, sourceId, firstSeenAt, project, projectPath) {
|
|
8884
8920
|
const existing = archive2.db.select().from(chats).where(and(eq(chats.agent, agent), eq(chats.sourceId, sourceId))).get();
|
|
8885
8921
|
if (existing) {
|
|
8886
|
-
|
|
8887
|
-
|
|
8922
|
+
const updates = {};
|
|
8923
|
+
if (project && existing.project !== project) updates.project = project;
|
|
8924
|
+
if (projectPath && existing.projectPath !== projectPath) {
|
|
8925
|
+
updates.projectPath = projectPath;
|
|
8926
|
+
}
|
|
8927
|
+
if (Object.keys(updates).length > 0) {
|
|
8928
|
+
archive2.db.update(chats).set(updates).where(eq(chats.id, existing.id)).run();
|
|
8888
8929
|
}
|
|
8889
8930
|
return existing.id;
|
|
8890
8931
|
}
|
|
@@ -8896,7 +8937,8 @@ function ensureChat(archive2, agent, sourceId, firstSeenAt, project) {
|
|
|
8896
8937
|
agent,
|
|
8897
8938
|
sourceId,
|
|
8898
8939
|
firstSeenAt,
|
|
8899
|
-
project: project ?? null
|
|
8940
|
+
project: project ?? null,
|
|
8941
|
+
projectPath: projectPath ?? null
|
|
8900
8942
|
}).run();
|
|
8901
8943
|
return id;
|
|
8902
8944
|
}
|
|
@@ -10786,7 +10828,8 @@ var ClaudeCodePlugin = class {
|
|
|
10786
10828
|
sourceId,
|
|
10787
10829
|
sourcePath,
|
|
10788
10830
|
watchPaths: [sourcePath],
|
|
10789
|
-
project: cwd ? path3.basename(cwd) : void 0
|
|
10831
|
+
project: cwd ? path3.basename(cwd) : void 0,
|
|
10832
|
+
projectPath: cwd ?? void 0
|
|
10790
10833
|
};
|
|
10791
10834
|
}
|
|
10792
10835
|
}
|