chat-logbook 0.7.1 → 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 +12 -8
- package/api/dist/drizzle/0002_rename_sessions_meta_to_chats_meta.sql +1 -0
- package/api/dist/drizzle/archive/0005_rename_session_to_chat.sql +17 -0
- package/api/dist/drizzle/archive/0006_add_project_path.sql +1 -0
- package/api/dist/drizzle/archive/meta/_journal.json +14 -0
- package/api/dist/drizzle/meta/_journal.json +7 -0
- package/api/dist/index.js +144 -128
- package/package.json +1 -1
- package/web/dist/assets/index-BCIIypHs.js +57 -0
- package/web/dist/assets/index-rSLP4i1P.css +2 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-BCVd1SRQ.css +0 -2
- package/web/dist/assets/index-Cl7dcz04.js +0 -46
package/api/dist/index.js
CHANGED
|
@@ -4758,12 +4758,12 @@ function mapRelationalRow(tablesConfig, tableConfig, row, buildQueryResultSelect
|
|
|
4758
4758
|
var schema_exports = {};
|
|
4759
4759
|
__export(schema_exports, {
|
|
4760
4760
|
archiveMeta: () => archiveMeta,
|
|
4761
|
+
chatScanState: () => chatScanState,
|
|
4762
|
+
chats: () => chats,
|
|
4761
4763
|
ingestionEvents: () => ingestionEvents,
|
|
4762
4764
|
messages: () => messages,
|
|
4763
4765
|
rawMessages: () => rawMessages,
|
|
4764
|
-
schemaVersion: () => schemaVersion
|
|
4765
|
-
sessionScanState: () => sessionScanState,
|
|
4766
|
-
sessions: () => sessions
|
|
4766
|
+
schemaVersion: () => schemaVersion
|
|
4767
4767
|
});
|
|
4768
4768
|
|
|
4769
4769
|
// ../node_modules/.pnpm/drizzle-orm@0.45.2_@types+better-sqlite3@7.6.13_better-sqlite3@12.9.0/node_modules/drizzle-orm/sqlite-core/foreign-keys.js
|
|
@@ -8179,26 +8179,25 @@ var schemaVersion = sqliteTable("schema_version", {
|
|
|
8179
8179
|
version: integer("version").primaryKey(),
|
|
8180
8180
|
appliedAt: integer("applied_at", { mode: "timestamp_ms" }).notNull()
|
|
8181
8181
|
});
|
|
8182
|
-
var
|
|
8183
|
-
"
|
|
8182
|
+
var chats = sqliteTable(
|
|
8183
|
+
"chats",
|
|
8184
8184
|
{
|
|
8185
8185
|
id: text("id").primaryKey(),
|
|
8186
|
-
|
|
8186
|
+
chatId: text("chat_id").notNull().unique(),
|
|
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
|
-
(t) => [
|
|
8193
|
-
uniqueIndex("sessions_agent_source_idx").on(t.agent, t.sourceSessionId)
|
|
8194
|
-
]
|
|
8193
|
+
(t) => [uniqueIndex("chats_agent_source_idx").on(t.agent, t.sourceId)]
|
|
8195
8194
|
);
|
|
8196
8195
|
var rawMessages = sqliteTable(
|
|
8197
8196
|
"raw_messages",
|
|
8198
8197
|
{
|
|
8199
8198
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
8200
8199
|
agent: text("agent").notNull(),
|
|
8201
|
-
|
|
8200
|
+
sourceId: text("source_id").notNull(),
|
|
8202
8201
|
sourcePath: text("source_path").notNull(),
|
|
8203
8202
|
sourceLocator: text("source_locator").notNull(),
|
|
8204
8203
|
rawPayload: text("raw_payload").notNull(),
|
|
@@ -8206,11 +8205,7 @@ var rawMessages = sqliteTable(
|
|
|
8206
8205
|
ingestedAt: integer("ingested_at", { mode: "timestamp_ms" }).notNull()
|
|
8207
8206
|
},
|
|
8208
8207
|
(t) => [
|
|
8209
|
-
uniqueIndex("raw_messages_idem_idx").on(
|
|
8210
|
-
t.agent,
|
|
8211
|
-
t.sessionId,
|
|
8212
|
-
t.payloadHash
|
|
8213
|
-
)
|
|
8208
|
+
uniqueIndex("raw_messages_idem_idx").on(t.agent, t.sourceId, t.payloadHash)
|
|
8214
8209
|
]
|
|
8215
8210
|
);
|
|
8216
8211
|
var messages = sqliteTable(
|
|
@@ -8218,7 +8213,7 @@ var messages = sqliteTable(
|
|
|
8218
8213
|
{
|
|
8219
8214
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
8220
8215
|
agent: text("agent").notNull(),
|
|
8221
|
-
|
|
8216
|
+
sourceId: text("source_id").notNull(),
|
|
8222
8217
|
messageId: text("message_id").notNull(),
|
|
8223
8218
|
role: text("role").notNull(),
|
|
8224
8219
|
ts: integer("ts", { mode: "timestamp_ms" }).notNull(),
|
|
@@ -8227,15 +8222,15 @@ var messages = sqliteTable(
|
|
|
8227
8222
|
rawId: integer("raw_id").notNull().references(() => rawMessages.id)
|
|
8228
8223
|
},
|
|
8229
8224
|
(t) => [
|
|
8230
|
-
uniqueIndex("messages_canonical_idx").on(t.agent, t.
|
|
8225
|
+
uniqueIndex("messages_canonical_idx").on(t.agent, t.sourceId, t.messageId)
|
|
8231
8226
|
]
|
|
8232
8227
|
);
|
|
8233
|
-
var
|
|
8228
|
+
var chatScanState = sqliteTable(
|
|
8234
8229
|
"session_scan_state",
|
|
8235
8230
|
{
|
|
8236
8231
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
8237
8232
|
agent: text("agent").notNull(),
|
|
8238
|
-
|
|
8233
|
+
sourceId: text("source_id").notNull(),
|
|
8239
8234
|
sourcePath: text("source_path").notNull(),
|
|
8240
8235
|
lastMtimeMs: integer("last_mtime_ms").notNull(),
|
|
8241
8236
|
lastSizeBytes: integer("last_size_bytes").notNull(),
|
|
@@ -8243,12 +8238,12 @@ var sessionScanState = sqliteTable(
|
|
|
8243
8238
|
mode: "timestamp_ms"
|
|
8244
8239
|
}).notNull()
|
|
8245
8240
|
},
|
|
8246
|
-
(t) => [uniqueIndex("session_scan_state_idx").on(t.agent, t.
|
|
8241
|
+
(t) => [uniqueIndex("session_scan_state_idx").on(t.agent, t.sourceId)]
|
|
8247
8242
|
);
|
|
8248
8243
|
var ingestionEvents = sqliteTable("ingestion_events", {
|
|
8249
8244
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
8250
8245
|
agent: text("agent").notNull(),
|
|
8251
|
-
|
|
8246
|
+
sourceId: text("source_id").notNull(),
|
|
8252
8247
|
sourcePath: text("source_path").notNull(),
|
|
8253
8248
|
eventType: text("event_type").notNull(),
|
|
8254
8249
|
detail: text("detail", { mode: "json" }).notNull(),
|
|
@@ -8256,7 +8251,7 @@ var ingestionEvents = sqliteTable("ingestion_events", {
|
|
|
8256
8251
|
});
|
|
8257
8252
|
|
|
8258
8253
|
// src/visibility.ts
|
|
8259
|
-
function
|
|
8254
|
+
function loadChatVisibility(metadata2, opts) {
|
|
8260
8255
|
const trashed = new Set(metadata2.listDeletedIds());
|
|
8261
8256
|
const showTrashed = opts.includeTrashed === true;
|
|
8262
8257
|
return {
|
|
@@ -8279,59 +8274,73 @@ function toApiBlock(block) {
|
|
|
8279
8274
|
var CLAUDE_CODE_AGENT = "claude-code";
|
|
8280
8275
|
function createApp({ archive: archive2, metadata: metadata2, webDistDir: webDistDir2 }) {
|
|
8281
8276
|
const app2 = new Hono2();
|
|
8282
|
-
function
|
|
8283
|
-
return archive2.db.select().from(
|
|
8277
|
+
function findArchiveChatBySourceId(sourceId) {
|
|
8278
|
+
return archive2.db.select().from(chats).where(eq(chats.sourceId, sourceId)).get();
|
|
8284
8279
|
}
|
|
8285
|
-
app2.get("/api/
|
|
8286
|
-
const visibility =
|
|
8280
|
+
app2.get("/api/chats", (c) => {
|
|
8281
|
+
const visibility = loadChatVisibility(metadata2, {
|
|
8287
8282
|
includeTrashed: c.req.query("includeTrashed") === "true"
|
|
8288
8283
|
});
|
|
8289
|
-
const rows = archive2.db.select().from(
|
|
8290
|
-
const
|
|
8284
|
+
const rows = archive2.db.select().from(chats).all();
|
|
8285
|
+
const chats2 = [];
|
|
8291
8286
|
for (const row of rows) {
|
|
8292
8287
|
if (!visibility.isVisible(row.id)) continue;
|
|
8293
8288
|
const isDeleted = visibility.isTrashed(row.id);
|
|
8294
|
-
const
|
|
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(
|
|
8295
8299
|
and(
|
|
8296
8300
|
eq(messages.agent, CLAUDE_CODE_AGENT),
|
|
8297
|
-
eq(messages.
|
|
8301
|
+
eq(messages.sourceId, row.sourceId)
|
|
8298
8302
|
)
|
|
8299
|
-
).
|
|
8300
|
-
const
|
|
8301
|
-
|
|
8303
|
+
).get();
|
|
8304
|
+
const firstSeenAtMs = row.firstSeenAt.getTime();
|
|
8305
|
+
const chat = {
|
|
8306
|
+
id: row.sourceId,
|
|
8307
|
+
chatId: row.chatId,
|
|
8308
|
+
agent: row.agent,
|
|
8302
8309
|
title: deriveTitle(archive2, metadata2, row),
|
|
8303
8310
|
project: row.project ?? "",
|
|
8304
|
-
|
|
8305
|
-
|
|
8311
|
+
projectPath: row.projectPath ?? null,
|
|
8312
|
+
sourceFilePath: latestRaw?.sourcePath ?? null,
|
|
8313
|
+
createdAt: tsRange?.minTs ?? firstSeenAtMs,
|
|
8314
|
+
updatedAt: tsRange?.maxTs ?? firstSeenAtMs
|
|
8306
8315
|
};
|
|
8307
|
-
if (isDeleted)
|
|
8308
|
-
|
|
8316
|
+
if (isDeleted) chat.isDeleted = true;
|
|
8317
|
+
chats2.push(chat);
|
|
8309
8318
|
}
|
|
8310
|
-
return c.json({
|
|
8319
|
+
return c.json({ chats: chats2 });
|
|
8311
8320
|
});
|
|
8312
|
-
app2.delete("/api/
|
|
8313
|
-
const
|
|
8314
|
-
const row =
|
|
8321
|
+
app2.delete("/api/chats/:id", (c) => {
|
|
8322
|
+
const id = c.req.param("id");
|
|
8323
|
+
const row = findArchiveChatBySourceId(id);
|
|
8315
8324
|
if (!row) {
|
|
8316
|
-
return c.json({ error: "
|
|
8325
|
+
return c.json({ error: "Chat not found" }, 404);
|
|
8317
8326
|
}
|
|
8318
8327
|
metadata2.softDelete(row.id);
|
|
8319
8328
|
return c.body(null, 204);
|
|
8320
8329
|
});
|
|
8321
|
-
app2.post("/api/
|
|
8322
|
-
const
|
|
8323
|
-
const row =
|
|
8330
|
+
app2.post("/api/chats/:id/restore", (c) => {
|
|
8331
|
+
const id = c.req.param("id");
|
|
8332
|
+
const row = findArchiveChatBySourceId(id);
|
|
8324
8333
|
if (!row) {
|
|
8325
|
-
return c.json({ error: "
|
|
8334
|
+
return c.json({ error: "Chat not found" }, 404);
|
|
8326
8335
|
}
|
|
8327
8336
|
metadata2.restore(row.id);
|
|
8328
8337
|
return c.body(null, 204);
|
|
8329
8338
|
});
|
|
8330
|
-
app2.patch("/api/
|
|
8331
|
-
const
|
|
8332
|
-
const row =
|
|
8339
|
+
app2.patch("/api/chats/:id/title", async (c) => {
|
|
8340
|
+
const id = c.req.param("id");
|
|
8341
|
+
const row = findArchiveChatBySourceId(id);
|
|
8333
8342
|
if (!row) {
|
|
8334
|
-
return c.json({ error: "
|
|
8343
|
+
return c.json({ error: "Chat not found" }, 404);
|
|
8335
8344
|
}
|
|
8336
8345
|
let body;
|
|
8337
8346
|
try {
|
|
@@ -8350,22 +8359,22 @@ function createApp({ archive: archive2, metadata: metadata2, webDistDir: webDist
|
|
|
8350
8359
|
metadata2.setCustomTitle(row.id, trimmed.length > 0 ? trimmed : null);
|
|
8351
8360
|
return c.body(null, 204);
|
|
8352
8361
|
});
|
|
8353
|
-
app2.get("/api/
|
|
8354
|
-
const
|
|
8355
|
-
const row =
|
|
8362
|
+
app2.get("/api/chats/:id", (c) => {
|
|
8363
|
+
const id = c.req.param("id");
|
|
8364
|
+
const row = findArchiveChatBySourceId(id);
|
|
8356
8365
|
if (!row) {
|
|
8357
|
-
return c.json({ error: "
|
|
8366
|
+
return c.json({ error: "Chat not found" }, 404);
|
|
8358
8367
|
}
|
|
8359
|
-
const visibility =
|
|
8368
|
+
const visibility = loadChatVisibility(metadata2, {
|
|
8360
8369
|
includeTrashed: c.req.query("includeTrashed") === "true"
|
|
8361
8370
|
});
|
|
8362
8371
|
if (!visibility.isVisible(row.id)) {
|
|
8363
|
-
return c.json({ error: "
|
|
8372
|
+
return c.json({ error: "Chat not found" }, 404);
|
|
8364
8373
|
}
|
|
8365
8374
|
const rows = archive2.db.select().from(messages).where(
|
|
8366
8375
|
and(
|
|
8367
8376
|
eq(messages.agent, CLAUDE_CODE_AGENT),
|
|
8368
|
-
eq(messages.
|
|
8377
|
+
eq(messages.sourceId, id)
|
|
8369
8378
|
)
|
|
8370
8379
|
).orderBy(asc(messages.ts)).all();
|
|
8371
8380
|
const messages2 = rows.map((m) => ({
|
|
@@ -8381,7 +8390,7 @@ function createApp({ archive: archive2, metadata: metadata2, webDistDir: webDist
|
|
|
8381
8390
|
const firstUser = archive3.db.select({ text: messages.text }).from(messages).where(
|
|
8382
8391
|
and(
|
|
8383
8392
|
eq(messages.agent, CLAUDE_CODE_AGENT),
|
|
8384
|
-
eq(messages.
|
|
8393
|
+
eq(messages.sourceId, row.sourceId),
|
|
8385
8394
|
eq(messages.role, "user")
|
|
8386
8395
|
)
|
|
8387
8396
|
).orderBy(asc(messages.ts)).limit(1).get();
|
|
@@ -8601,18 +8610,18 @@ function migrate(db, config) {
|
|
|
8601
8610
|
db.dialect.migrate(migrations, db.session, config);
|
|
8602
8611
|
}
|
|
8603
8612
|
|
|
8604
|
-
// src/archive/
|
|
8613
|
+
// src/archive/chat-id.ts
|
|
8605
8614
|
import crypto4 from "crypto";
|
|
8606
8615
|
var CROCKFORD_ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz";
|
|
8607
|
-
var
|
|
8616
|
+
var CHAT_ID_LENGTH = 6;
|
|
8608
8617
|
var MAX_RETRIES = 5;
|
|
8609
|
-
function
|
|
8618
|
+
function generateChatId({
|
|
8610
8619
|
isTaken,
|
|
8611
8620
|
randomIndex = () => crypto4.randomInt(CROCKFORD_ALPHABET.length)
|
|
8612
8621
|
}) {
|
|
8613
8622
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
8614
8623
|
let code = "";
|
|
8615
|
-
for (let i = 0; i <
|
|
8624
|
+
for (let i = 0; i < CHAT_ID_LENGTH; i++) {
|
|
8616
8625
|
code += CROCKFORD_ALPHABET[randomIndex()];
|
|
8617
8626
|
}
|
|
8618
8627
|
if (!isTaken(code)) {
|
|
@@ -8620,7 +8629,7 @@ function generateShortCode({
|
|
|
8620
8629
|
}
|
|
8621
8630
|
}
|
|
8622
8631
|
throw new Error(
|
|
8623
|
-
`Failed to generate unique
|
|
8632
|
+
`Failed to generate unique chat_id after ${MAX_RETRIES + 1} attempts`
|
|
8624
8633
|
);
|
|
8625
8634
|
}
|
|
8626
8635
|
|
|
@@ -8668,9 +8677,9 @@ function createArchiveRepository({
|
|
|
8668
8677
|
getAppliedMigrations() {
|
|
8669
8678
|
return db.select().from(schemaVersion).all().map((r) => ({ version: r.version, appliedAt: r.appliedAt }));
|
|
8670
8679
|
},
|
|
8671
|
-
|
|
8672
|
-
return
|
|
8673
|
-
isTaken: (candidate) => db.select({ id:
|
|
8680
|
+
generateChatId() {
|
|
8681
|
+
return generateChatId({
|
|
8682
|
+
isTaken: (candidate) => db.select({ id: chats.id }).from(chats).where(eq(chats.chatId, candidate)).get() !== void 0
|
|
8674
8683
|
});
|
|
8675
8684
|
},
|
|
8676
8685
|
close() {
|
|
@@ -8686,7 +8695,7 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
8686
8695
|
import Database2 from "better-sqlite3";
|
|
8687
8696
|
|
|
8688
8697
|
// src/metadata/schema.ts
|
|
8689
|
-
var
|
|
8698
|
+
var chatsMeta = sqliteTable("chats_meta", {
|
|
8690
8699
|
id: text("id").primaryKey(),
|
|
8691
8700
|
isDeleted: integer("is_deleted", { mode: "boolean" }).notNull().default(false),
|
|
8692
8701
|
customTitle: text("custom_title"),
|
|
@@ -8711,71 +8720,71 @@ var CLAUDE_CODE_AGENT2 = "claude-code";
|
|
|
8711
8720
|
function createMetadataRepository({
|
|
8712
8721
|
dataDir: dataDir2,
|
|
8713
8722
|
lookupInternalId,
|
|
8714
|
-
|
|
8723
|
+
ensureChat: ensureChat2
|
|
8715
8724
|
}) {
|
|
8716
8725
|
fs3.mkdirSync(dataDir2, { recursive: true });
|
|
8717
8726
|
const sqlite = new Database2(path2.join(dataDir2, "data.db"));
|
|
8718
8727
|
const db = drizzle(sqlite);
|
|
8719
8728
|
migrate(db, { migrationsFolder: resolveMigrationsFolder2() });
|
|
8720
8729
|
if (lookupInternalId) {
|
|
8721
|
-
rekeyLegacyRows(sqlite, db, lookupInternalId,
|
|
8730
|
+
rekeyLegacyRows(sqlite, db, lookupInternalId, ensureChat2);
|
|
8722
8731
|
}
|
|
8723
8732
|
return {
|
|
8724
8733
|
softDelete(internalId) {
|
|
8725
8734
|
const now = /* @__PURE__ */ new Date();
|
|
8726
|
-
db.insert(
|
|
8735
|
+
db.insert(chatsMeta).values({
|
|
8727
8736
|
id: internalId,
|
|
8728
8737
|
isDeleted: true,
|
|
8729
8738
|
createdAt: now,
|
|
8730
8739
|
updatedAt: now
|
|
8731
8740
|
}).onConflictDoUpdate({
|
|
8732
|
-
target:
|
|
8741
|
+
target: chatsMeta.id,
|
|
8733
8742
|
set: { isDeleted: true, updatedAt: now }
|
|
8734
8743
|
}).run();
|
|
8735
8744
|
},
|
|
8736
8745
|
restore(internalId) {
|
|
8737
8746
|
const now = /* @__PURE__ */ new Date();
|
|
8738
|
-
db.update(
|
|
8747
|
+
db.update(chatsMeta).set({ isDeleted: false, updatedAt: now }).where(eq(chatsMeta.id, internalId)).run();
|
|
8739
8748
|
},
|
|
8740
8749
|
isDeleted(internalId) {
|
|
8741
|
-
const row = db.select({ isDeleted:
|
|
8750
|
+
const row = db.select({ isDeleted: chatsMeta.isDeleted }).from(chatsMeta).where(eq(chatsMeta.id, internalId)).get();
|
|
8742
8751
|
return row?.isDeleted ?? false;
|
|
8743
8752
|
},
|
|
8744
8753
|
listDeletedIds() {
|
|
8745
|
-
const rows = db.select({ id:
|
|
8754
|
+
const rows = db.select({ id: chatsMeta.id }).from(chatsMeta).where(eq(chatsMeta.isDeleted, true)).all();
|
|
8746
8755
|
return rows.map((r) => r.id);
|
|
8747
8756
|
},
|
|
8748
8757
|
getCustomTitle(internalId) {
|
|
8749
|
-
const row = db.select({ customTitle:
|
|
8758
|
+
const row = db.select({ customTitle: chatsMeta.customTitle }).from(chatsMeta).where(eq(chatsMeta.id, internalId)).get();
|
|
8750
8759
|
return row?.customTitle ?? null;
|
|
8751
8760
|
},
|
|
8752
8761
|
setCustomTitle(internalId, title) {
|
|
8753
8762
|
const now = /* @__PURE__ */ new Date();
|
|
8754
|
-
db.insert(
|
|
8763
|
+
db.insert(chatsMeta).values({
|
|
8755
8764
|
id: internalId,
|
|
8756
8765
|
customTitle: title,
|
|
8757
8766
|
createdAt: now,
|
|
8758
8767
|
updatedAt: now
|
|
8759
8768
|
}).onConflictDoUpdate({
|
|
8760
|
-
target:
|
|
8769
|
+
target: chatsMeta.id,
|
|
8761
8770
|
set: { customTitle: title, updatedAt: now }
|
|
8762
8771
|
}).run();
|
|
8763
8772
|
}
|
|
8764
8773
|
};
|
|
8765
8774
|
}
|
|
8766
8775
|
var REKEY_USER_VERSION = 4;
|
|
8767
|
-
function rekeyLegacyRows(sqlite, db, lookupInternalId,
|
|
8776
|
+
function rekeyLegacyRows(sqlite, db, lookupInternalId, ensureChat2) {
|
|
8768
8777
|
const current = sqlite.pragma("user_version", { simple: true });
|
|
8769
8778
|
if (current >= REKEY_USER_VERSION) return;
|
|
8770
|
-
const rows = db.select({ id:
|
|
8779
|
+
const rows = db.select({ id: chatsMeta.id }).from(chatsMeta).all();
|
|
8771
8780
|
for (const row of rows) {
|
|
8772
8781
|
let target = lookupInternalId(CLAUDE_CODE_AGENT2, row.id);
|
|
8773
8782
|
if (target === null) {
|
|
8774
|
-
if (!
|
|
8775
|
-
target =
|
|
8783
|
+
if (!ensureChat2) continue;
|
|
8784
|
+
target = ensureChat2(CLAUDE_CODE_AGENT2, row.id);
|
|
8776
8785
|
}
|
|
8777
8786
|
if (target === row.id) continue;
|
|
8778
|
-
db.update(
|
|
8787
|
+
db.update(chatsMeta).set({ id: target, updatedAt: /* @__PURE__ */ new Date() }).where(eq(chatsMeta.id, row.id)).run();
|
|
8779
8788
|
}
|
|
8780
8789
|
sqlite.pragma(`user_version = ${REKEY_USER_VERSION}`);
|
|
8781
8790
|
}
|
|
@@ -8795,13 +8804,20 @@ async function runIngestion(opts) {
|
|
|
8795
8804
|
for await (const ref of plugin.discover(opts.env)) {
|
|
8796
8805
|
result.scanned += 1;
|
|
8797
8806
|
const stat4 = safeStat(ref.sourcePath);
|
|
8798
|
-
const prior = opts.archive.db.select().from(
|
|
8807
|
+
const prior = opts.archive.db.select().from(chatScanState).where(
|
|
8799
8808
|
and(
|
|
8800
|
-
eq(
|
|
8801
|
-
eq(
|
|
8809
|
+
eq(chatScanState.agent, plugin.id),
|
|
8810
|
+
eq(chatScanState.sourceId, ref.sourceId)
|
|
8802
8811
|
)
|
|
8803
8812
|
).get();
|
|
8804
|
-
|
|
8813
|
+
ensureChat(
|
|
8814
|
+
opts.archive,
|
|
8815
|
+
plugin.id,
|
|
8816
|
+
ref.sourceId,
|
|
8817
|
+
now(),
|
|
8818
|
+
ref.project,
|
|
8819
|
+
ref.projectPath
|
|
8820
|
+
);
|
|
8805
8821
|
if (stat4 && prior && prior.lastMtimeMs === stat4.mtimeMs && prior.lastSizeBytes === stat4.size) {
|
|
8806
8822
|
result.skippedByMtime += 1;
|
|
8807
8823
|
continue;
|
|
@@ -8812,7 +8828,7 @@ async function runIngestion(opts) {
|
|
|
8812
8828
|
const existing = opts.archive.db.select({ id: rawMessages.id }).from(rawMessages).where(
|
|
8813
8829
|
and(
|
|
8814
8830
|
eq(rawMessages.agent, plugin.id),
|
|
8815
|
-
eq(rawMessages.
|
|
8831
|
+
eq(rawMessages.sourceId, ref.sourceId),
|
|
8816
8832
|
eq(rawMessages.payloadHash, payloadHash)
|
|
8817
8833
|
)
|
|
8818
8834
|
).get();
|
|
@@ -8822,7 +8838,7 @@ async function runIngestion(opts) {
|
|
|
8822
8838
|
} else {
|
|
8823
8839
|
const inserted = opts.archive.db.insert(rawMessages).values({
|
|
8824
8840
|
agent: plugin.id,
|
|
8825
|
-
|
|
8841
|
+
sourceId: ref.sourceId,
|
|
8826
8842
|
sourcePath: raw2.sourcePath,
|
|
8827
8843
|
sourceLocator: raw2.sourceLocator,
|
|
8828
8844
|
rawPayload: payloadJson,
|
|
@@ -8837,7 +8853,7 @@ async function runIngestion(opts) {
|
|
|
8837
8853
|
const upserted = upsertCanonical(
|
|
8838
8854
|
opts.archive,
|
|
8839
8855
|
plugin.id,
|
|
8840
|
-
ref.
|
|
8856
|
+
ref.sourceId,
|
|
8841
8857
|
canonical,
|
|
8842
8858
|
rawId
|
|
8843
8859
|
);
|
|
@@ -8847,7 +8863,7 @@ async function runIngestion(opts) {
|
|
|
8847
8863
|
recordScanState(
|
|
8848
8864
|
opts.archive,
|
|
8849
8865
|
plugin.id,
|
|
8850
|
-
ref.
|
|
8866
|
+
ref.sourceId,
|
|
8851
8867
|
ref.sourcePath,
|
|
8852
8868
|
stat4,
|
|
8853
8869
|
now()
|
|
@@ -8864,24 +8880,21 @@ function safeStat(p) {
|
|
|
8864
8880
|
return null;
|
|
8865
8881
|
}
|
|
8866
8882
|
}
|
|
8867
|
-
function recordScanState(archive2, agent,
|
|
8868
|
-
const existing = archive2.db.select({ id:
|
|
8869
|
-
and(
|
|
8870
|
-
eq(sessionScanState.agent, agent),
|
|
8871
|
-
eq(sessionScanState.sessionId, sessionId)
|
|
8872
|
-
)
|
|
8883
|
+
function recordScanState(archive2, agent, sourceId, sourcePath, stat4, scannedAt) {
|
|
8884
|
+
const existing = archive2.db.select({ id: chatScanState.id }).from(chatScanState).where(
|
|
8885
|
+
and(eq(chatScanState.agent, agent), eq(chatScanState.sourceId, sourceId))
|
|
8873
8886
|
).get();
|
|
8874
8887
|
if (existing) {
|
|
8875
|
-
archive2.db.update(
|
|
8888
|
+
archive2.db.update(chatScanState).set({
|
|
8876
8889
|
sourcePath,
|
|
8877
8890
|
lastMtimeMs: stat4.mtimeMs,
|
|
8878
8891
|
lastSizeBytes: stat4.size,
|
|
8879
8892
|
lastScannedAt: scannedAt
|
|
8880
|
-
}).where(eq(
|
|
8893
|
+
}).where(eq(chatScanState.id, existing.id)).run();
|
|
8881
8894
|
} else {
|
|
8882
|
-
archive2.db.insert(
|
|
8895
|
+
archive2.db.insert(chatScanState).values({
|
|
8883
8896
|
agent,
|
|
8884
|
-
|
|
8897
|
+
sourceId,
|
|
8885
8898
|
sourcePath,
|
|
8886
8899
|
lastMtimeMs: stat4.mtimeMs,
|
|
8887
8900
|
lastSizeBytes: stat4.size,
|
|
@@ -8889,36 +8902,37 @@ function recordScanState(archive2, agent, sessionId, sourcePath, stat4, scannedA
|
|
|
8889
8902
|
}).run();
|
|
8890
8903
|
}
|
|
8891
8904
|
}
|
|
8892
|
-
function
|
|
8893
|
-
const existing = archive2.db.select().from(
|
|
8894
|
-
and(
|
|
8895
|
-
eq(sessions.agent, agent),
|
|
8896
|
-
eq(sessions.sourceSessionId, sourceSessionId)
|
|
8897
|
-
)
|
|
8898
|
-
).get();
|
|
8905
|
+
function ensureChat(archive2, agent, sourceId, firstSeenAt, project, projectPath) {
|
|
8906
|
+
const existing = archive2.db.select().from(chats).where(and(eq(chats.agent, agent), eq(chats.sourceId, sourceId))).get();
|
|
8899
8907
|
if (existing) {
|
|
8900
|
-
|
|
8901
|
-
|
|
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();
|
|
8902
8915
|
}
|
|
8903
8916
|
return existing.id;
|
|
8904
8917
|
}
|
|
8905
8918
|
const id = crypto6.randomUUID();
|
|
8906
|
-
const
|
|
8907
|
-
archive2.db.insert(
|
|
8919
|
+
const chatId = archive2.generateChatId();
|
|
8920
|
+
archive2.db.insert(chats).values({
|
|
8908
8921
|
id,
|
|
8909
|
-
|
|
8922
|
+
chatId,
|
|
8910
8923
|
agent,
|
|
8911
|
-
|
|
8924
|
+
sourceId,
|
|
8912
8925
|
firstSeenAt,
|
|
8913
|
-
project: project ?? null
|
|
8926
|
+
project: project ?? null,
|
|
8927
|
+
projectPath: projectPath ?? null
|
|
8914
8928
|
}).run();
|
|
8915
8929
|
return id;
|
|
8916
8930
|
}
|
|
8917
|
-
function upsertCanonical(archive2, agent,
|
|
8931
|
+
function upsertCanonical(archive2, agent, sourceId, msg, rawId) {
|
|
8918
8932
|
const existing = archive2.db.select().from(messages).where(
|
|
8919
8933
|
and(
|
|
8920
8934
|
eq(messages.agent, agent),
|
|
8921
|
-
eq(messages.
|
|
8935
|
+
eq(messages.sourceId, sourceId),
|
|
8922
8936
|
eq(messages.messageId, msg.messageId)
|
|
8923
8937
|
)
|
|
8924
8938
|
).get();
|
|
@@ -8926,7 +8940,7 @@ function upsertCanonical(archive2, agent, sessionId, msg, rawId) {
|
|
|
8926
8940
|
if (!existing) {
|
|
8927
8941
|
archive2.db.insert(messages).values({
|
|
8928
8942
|
agent,
|
|
8929
|
-
|
|
8943
|
+
sourceId,
|
|
8930
8944
|
messageId: msg.messageId,
|
|
8931
8945
|
role: msg.role,
|
|
8932
8946
|
ts,
|
|
@@ -10741,7 +10755,7 @@ function startWatcher(opts) {
|
|
|
10741
10755
|
try {
|
|
10742
10756
|
opts.archive.db.insert(ingestionEvents).values({
|
|
10743
10757
|
agent: binding.plugin.id,
|
|
10744
|
-
|
|
10758
|
+
sourceId: binding.ref.sourceId,
|
|
10745
10759
|
sourcePath: removedPath,
|
|
10746
10760
|
eventType: "unlink_observed",
|
|
10747
10761
|
detail: { path: removedPath },
|
|
@@ -10794,13 +10808,14 @@ var ClaudeCodePlugin = class {
|
|
|
10794
10808
|
for (const file of files) {
|
|
10795
10809
|
if (!file.isFile() || !file.name.endsWith(".jsonl")) continue;
|
|
10796
10810
|
const sourcePath = path3.join(projectPath, file.name);
|
|
10797
|
-
const
|
|
10811
|
+
const sourceId = file.name.replace(/\.jsonl$/, "");
|
|
10798
10812
|
const cwd = await readCwdFromJsonl(sourcePath);
|
|
10799
10813
|
yield {
|
|
10800
|
-
|
|
10814
|
+
sourceId,
|
|
10801
10815
|
sourcePath,
|
|
10802
10816
|
watchPaths: [sourcePath],
|
|
10803
|
-
project: cwd ? path3.basename(cwd) : void 0
|
|
10817
|
+
project: cwd ? path3.basename(cwd) : void 0,
|
|
10818
|
+
projectPath: cwd ?? void 0
|
|
10804
10819
|
};
|
|
10805
10820
|
}
|
|
10806
10821
|
}
|
|
@@ -10814,7 +10829,7 @@ var ClaudeCodePlugin = class {
|
|
|
10814
10829
|
lineNo += 1;
|
|
10815
10830
|
if (!line) continue;
|
|
10816
10831
|
yield {
|
|
10817
|
-
|
|
10832
|
+
sourceId: ref.sourceId,
|
|
10818
10833
|
sourcePath: ref.sourcePath,
|
|
10819
10834
|
sourceLocator: `L${lineNo}`,
|
|
10820
10835
|
payload: JSON.parse(line)
|
|
@@ -10917,6 +10932,7 @@ function parseCliArgs(argv, env = {}) {
|
|
|
10917
10932
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
10918
10933
|
return { kind: "help" };
|
|
10919
10934
|
}
|
|
10935
|
+
const open2 = !env.CHAT_LOGBOOK_NO_OPEN;
|
|
10920
10936
|
const portIdx = argv.findIndex((a) => a === "--port" || a === "-p");
|
|
10921
10937
|
if (portIdx !== -1) {
|
|
10922
10938
|
const raw2 = argv[portIdx + 1];
|
|
@@ -10939,12 +10955,12 @@ function parseCliArgs(argv, env = {}) {
|
|
|
10939
10955
|
message: `Invalid port "${raw2}": must be between 1 and 65535`
|
|
10940
10956
|
};
|
|
10941
10957
|
}
|
|
10942
|
-
return { kind: "run", port: port2 };
|
|
10958
|
+
return { kind: "run", port: port2, open: open2 };
|
|
10943
10959
|
}
|
|
10944
10960
|
if (env.PORT) {
|
|
10945
|
-
return { kind: "run", port: Number(env.PORT) };
|
|
10961
|
+
return { kind: "run", port: Number(env.PORT), open: open2 };
|
|
10946
10962
|
}
|
|
10947
|
-
return { kind: "run", port: DEFAULT_PORT };
|
|
10963
|
+
return { kind: "run", port: DEFAULT_PORT, open: open2 };
|
|
10948
10964
|
}
|
|
10949
10965
|
|
|
10950
10966
|
// src/cli/help.ts
|
|
@@ -11011,7 +11027,7 @@ function openBrowser(url) {
|
|
|
11011
11027
|
var server = serve({ fetch: app.fetch, port }, (info) => {
|
|
11012
11028
|
const url = `http://localhost:${info.port}`;
|
|
11013
11029
|
console.log(`chat-logbook is running at \x1B[36m${url}\x1B[0m`);
|
|
11014
|
-
openBrowser(url);
|
|
11030
|
+
if (action.open) openBrowser(url);
|
|
11015
11031
|
});
|
|
11016
11032
|
server.on("error", (err) => {
|
|
11017
11033
|
if (err.code === "EADDRINUSE") {
|