engrm 0.4.45 → 0.4.47

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.
@@ -909,11 +909,20 @@ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, wr
909
909
  import { homedir, hostname, networkInterfaces } from "node:os";
910
910
  import { join as join2 } from "node:path";
911
911
  import { createHash } from "node:crypto";
912
- var CONFIG_DIR = join2(homedir(), ".engrm");
913
- var SETTINGS_PATH = join2(CONFIG_DIR, "settings.json");
914
- var DB_PATH = join2(CONFIG_DIR, "engrm.db");
912
+ function resolveConfigDir() {
913
+ return process.env["ENGRM_CONFIG_DIR"]?.trim() || join2(homedir(), ".engrm");
914
+ }
915
+ function resolveSettingsPath() {
916
+ return join2(resolveConfigDir(), "settings.json");
917
+ }
918
+ function resolveDbPath() {
919
+ return join2(resolveConfigDir(), "engrm.db");
920
+ }
921
+ function resolveAuthBackupPath() {
922
+ return join2(resolveConfigDir(), "auth-backup.json");
923
+ }
915
924
  function getDbPath() {
916
- return DB_PATH;
925
+ return resolveDbPath();
917
926
  }
918
927
  function generateDeviceId() {
919
928
  const host = hostname().toLowerCase().replace(/[^a-z0-9-]/g, "");
@@ -936,7 +945,7 @@ function generateDeviceId() {
936
945
  return `${host}-${suffix}`;
937
946
  }
938
947
  function createDefaultConfig() {
939
- return {
948
+ const merged = {
940
949
  candengo_url: "",
941
950
  candengo_api_key: "",
942
951
  site_id: "",
@@ -991,24 +1000,26 @@ function createDefaultConfig() {
991
1000
  },
992
1001
  tool_profile: "full"
993
1002
  };
1003
+ return merged;
994
1004
  }
995
1005
  function loadConfig() {
996
- if (!existsSync2(SETTINGS_PATH)) {
997
- throw new Error(`Config not found at ${SETTINGS_PATH}. Run 'engrm init --manual' to configure.`);
1006
+ const settingsPath = resolveSettingsPath();
1007
+ if (!existsSync2(settingsPath)) {
1008
+ throw new Error(`Config not found at ${settingsPath}. Run 'engrm init --manual' to configure.`);
998
1009
  }
999
- const raw = readFileSync2(SETTINGS_PATH, "utf-8");
1010
+ const raw = readFileSync2(settingsPath, "utf-8");
1000
1011
  let parsed;
1001
1012
  try {
1002
1013
  parsed = JSON.parse(raw);
1003
1014
  } catch {
1004
- throw new Error(`Invalid JSON in ${SETTINGS_PATH}`);
1015
+ throw new Error(`Invalid JSON in ${settingsPath}`);
1005
1016
  }
1006
1017
  if (typeof parsed !== "object" || parsed === null) {
1007
- throw new Error(`Config at ${SETTINGS_PATH} is not a JSON object`);
1018
+ throw new Error(`Config at ${settingsPath} is not a JSON object`);
1008
1019
  }
1009
1020
  const config = parsed;
1010
1021
  const defaults = createDefaultConfig();
1011
- return {
1022
+ const merged = {
1012
1023
  candengo_url: asString(config["candengo_url"], defaults.candengo_url),
1013
1024
  candengo_api_key: asString(config["candengo_api_key"], defaults.candengo_api_key),
1014
1025
  site_id: asString(config["site_id"], defaults.site_id),
@@ -1063,16 +1074,27 @@ function loadConfig() {
1063
1074
  },
1064
1075
  tool_profile: asToolProfile(config["tool_profile"], defaults.tool_profile)
1065
1076
  };
1077
+ if (looksLikePlaceholderAuth(merged)) {
1078
+ return restoreAuthBackup(merged) ?? merged;
1079
+ }
1080
+ return merged;
1066
1081
  }
1067
1082
  function saveConfig(config) {
1068
- if (!existsSync2(CONFIG_DIR)) {
1069
- mkdirSync(CONFIG_DIR, { recursive: true });
1083
+ const configDir = resolveConfigDir();
1084
+ const settingsPath = resolveSettingsPath();
1085
+ const authBackupPath = resolveAuthBackupPath();
1086
+ if (!existsSync2(configDir)) {
1087
+ mkdirSync(configDir, { recursive: true });
1070
1088
  }
1071
- writeFileSync(SETTINGS_PATH, JSON.stringify(config, null, 2) + `
1089
+ writeFileSync(settingsPath, JSON.stringify(config, null, 2) + `
1072
1090
  `, "utf-8");
1091
+ if (!looksLikePlaceholderAuth(config)) {
1092
+ writeFileSync(authBackupPath, JSON.stringify(extractAuthBackup(config), null, 2) + `
1093
+ `, "utf-8");
1094
+ }
1073
1095
  }
1074
1096
  function configExists() {
1075
- return existsSync2(SETTINGS_PATH);
1097
+ return existsSync2(resolveSettingsPath());
1076
1098
  }
1077
1099
  function asString(value, fallback) {
1078
1100
  return typeof value === "string" ? value : fallback;
@@ -1126,6 +1148,50 @@ function asTeams(value, fallback) {
1126
1148
  return fallback;
1127
1149
  return value.filter((t) => typeof t === "object" && t !== null && typeof t.id === "string" && typeof t.name === "string" && typeof t.namespace === "string");
1128
1150
  }
1151
+ function looksLikePlaceholderAuth(config) {
1152
+ const apiKey = config.candengo_api_key.trim();
1153
+ const siteId = config.site_id.trim();
1154
+ const namespace = config.namespace.trim();
1155
+ const email = config.user_email.trim().toLowerCase();
1156
+ if (apiKey === "cvk_org" && siteId === "site-1" && namespace === "org-ns")
1157
+ return true;
1158
+ if (siteId === "site-1" && namespace === "org-ns" && email.endsWith("@example.com"))
1159
+ return true;
1160
+ return false;
1161
+ }
1162
+ function extractAuthBackup(config) {
1163
+ return {
1164
+ candengo_url: config.candengo_url,
1165
+ candengo_api_key: config.candengo_api_key,
1166
+ site_id: config.site_id,
1167
+ namespace: config.namespace,
1168
+ user_id: config.user_id,
1169
+ user_email: config.user_email,
1170
+ teams: config.teams
1171
+ };
1172
+ }
1173
+ function restoreAuthBackup(config) {
1174
+ const authBackupPath = resolveAuthBackupPath();
1175
+ if (!existsSync2(authBackupPath))
1176
+ return null;
1177
+ try {
1178
+ const raw = readFileSync2(authBackupPath, "utf-8");
1179
+ const parsed = JSON.parse(raw);
1180
+ const restored = {
1181
+ ...config,
1182
+ candengo_url: asString(parsed["candengo_url"], config.candengo_url),
1183
+ candengo_api_key: asString(parsed["candengo_api_key"], config.candengo_api_key),
1184
+ site_id: asString(parsed["site_id"], config.site_id),
1185
+ namespace: asString(parsed["namespace"], config.namespace),
1186
+ user_id: asString(parsed["user_id"], config.user_id),
1187
+ user_email: asString(parsed["user_email"], config.user_email),
1188
+ teams: asTeams(parsed["teams"], config.teams)
1189
+ };
1190
+ return looksLikePlaceholderAuth(restored) ? null : restored;
1191
+ } catch {
1192
+ return null;
1193
+ }
1194
+ }
1129
1195
 
1130
1196
  // src/storage/migrations.ts
1131
1197
  var MIGRATIONS = [
@@ -1813,6 +1879,20 @@ function ensureChatMessageColumns(db) {
1813
1879
  db.exec("PRAGMA user_version = 17");
1814
1880
  }
1815
1881
  }
1882
+ function ensureObservationVectorTable(db) {
1883
+ if (!isVecExtensionLoaded(db))
1884
+ return;
1885
+ db.exec(`
1886
+ CREATE VIRTUAL TABLE IF NOT EXISTS vec_observations USING vec0(
1887
+ observation_id INTEGER PRIMARY KEY,
1888
+ embedding FLOAT[384]
1889
+ );
1890
+ `);
1891
+ const current = getSchemaVersion(db);
1892
+ if (current < 4) {
1893
+ db.exec("PRAGMA user_version = 4");
1894
+ }
1895
+ }
1816
1896
  function ensureChatVectorTable(db) {
1817
1897
  if (!isVecExtensionLoaded(db))
1818
1898
  return;
@@ -2041,6 +2121,7 @@ class MemDatabase {
2041
2121
  ensureObservationTypes(this.db);
2042
2122
  ensureSessionSummaryColumns(this.db);
2043
2123
  ensureChatMessageColumns(this.db);
2124
+ ensureObservationVectorTable(this.db);
2044
2125
  ensureChatVectorTable(this.db);
2045
2126
  ensureSyncOutboxSupportsChatMessages(this.db);
2046
2127
  }
@@ -213,11 +213,20 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
213
213
  import { homedir, hostname, networkInterfaces } from "node:os";
214
214
  import { join } from "node:path";
215
215
  import { createHash } from "node:crypto";
216
- var CONFIG_DIR = join(homedir(), ".engrm");
217
- var SETTINGS_PATH = join(CONFIG_DIR, "settings.json");
218
- var DB_PATH = join(CONFIG_DIR, "engrm.db");
216
+ function resolveConfigDir() {
217
+ return process.env["ENGRM_CONFIG_DIR"]?.trim() || join(homedir(), ".engrm");
218
+ }
219
+ function resolveSettingsPath() {
220
+ return join(resolveConfigDir(), "settings.json");
221
+ }
222
+ function resolveDbPath() {
223
+ return join(resolveConfigDir(), "engrm.db");
224
+ }
225
+ function resolveAuthBackupPath() {
226
+ return join(resolveConfigDir(), "auth-backup.json");
227
+ }
219
228
  function getDbPath() {
220
- return DB_PATH;
229
+ return resolveDbPath();
221
230
  }
222
231
  function generateDeviceId() {
223
232
  const host = hostname().toLowerCase().replace(/[^a-z0-9-]/g, "");
@@ -240,7 +249,7 @@ function generateDeviceId() {
240
249
  return `${host}-${suffix}`;
241
250
  }
242
251
  function createDefaultConfig() {
243
- return {
252
+ const merged = {
244
253
  candengo_url: "",
245
254
  candengo_api_key: "",
246
255
  site_id: "",
@@ -295,24 +304,26 @@ function createDefaultConfig() {
295
304
  },
296
305
  tool_profile: "full"
297
306
  };
307
+ return merged;
298
308
  }
299
309
  function loadConfig() {
300
- if (!existsSync(SETTINGS_PATH)) {
301
- throw new Error(`Config not found at ${SETTINGS_PATH}. Run 'engrm init --manual' to configure.`);
310
+ const settingsPath = resolveSettingsPath();
311
+ if (!existsSync(settingsPath)) {
312
+ throw new Error(`Config not found at ${settingsPath}. Run 'engrm init --manual' to configure.`);
302
313
  }
303
- const raw = readFileSync(SETTINGS_PATH, "utf-8");
314
+ const raw = readFileSync(settingsPath, "utf-8");
304
315
  let parsed;
305
316
  try {
306
317
  parsed = JSON.parse(raw);
307
318
  } catch {
308
- throw new Error(`Invalid JSON in ${SETTINGS_PATH}`);
319
+ throw new Error(`Invalid JSON in ${settingsPath}`);
309
320
  }
310
321
  if (typeof parsed !== "object" || parsed === null) {
311
- throw new Error(`Config at ${SETTINGS_PATH} is not a JSON object`);
322
+ throw new Error(`Config at ${settingsPath} is not a JSON object`);
312
323
  }
313
324
  const config = parsed;
314
325
  const defaults = createDefaultConfig();
315
- return {
326
+ const merged = {
316
327
  candengo_url: asString(config["candengo_url"], defaults.candengo_url),
317
328
  candengo_api_key: asString(config["candengo_api_key"], defaults.candengo_api_key),
318
329
  site_id: asString(config["site_id"], defaults.site_id),
@@ -367,16 +378,27 @@ function loadConfig() {
367
378
  },
368
379
  tool_profile: asToolProfile(config["tool_profile"], defaults.tool_profile)
369
380
  };
381
+ if (looksLikePlaceholderAuth(merged)) {
382
+ return restoreAuthBackup(merged) ?? merged;
383
+ }
384
+ return merged;
370
385
  }
371
386
  function saveConfig(config) {
372
- if (!existsSync(CONFIG_DIR)) {
373
- mkdirSync(CONFIG_DIR, { recursive: true });
387
+ const configDir = resolveConfigDir();
388
+ const settingsPath = resolveSettingsPath();
389
+ const authBackupPath = resolveAuthBackupPath();
390
+ if (!existsSync(configDir)) {
391
+ mkdirSync(configDir, { recursive: true });
374
392
  }
375
- writeFileSync(SETTINGS_PATH, JSON.stringify(config, null, 2) + `
393
+ writeFileSync(settingsPath, JSON.stringify(config, null, 2) + `
376
394
  `, "utf-8");
395
+ if (!looksLikePlaceholderAuth(config)) {
396
+ writeFileSync(authBackupPath, JSON.stringify(extractAuthBackup(config), null, 2) + `
397
+ `, "utf-8");
398
+ }
377
399
  }
378
400
  function configExists() {
379
- return existsSync(SETTINGS_PATH);
401
+ return existsSync(resolveSettingsPath());
380
402
  }
381
403
  function asString(value, fallback) {
382
404
  return typeof value === "string" ? value : fallback;
@@ -430,6 +452,50 @@ function asTeams(value, fallback) {
430
452
  return fallback;
431
453
  return value.filter((t) => typeof t === "object" && t !== null && typeof t.id === "string" && typeof t.name === "string" && typeof t.namespace === "string");
432
454
  }
455
+ function looksLikePlaceholderAuth(config) {
456
+ const apiKey = config.candengo_api_key.trim();
457
+ const siteId = config.site_id.trim();
458
+ const namespace = config.namespace.trim();
459
+ const email = config.user_email.trim().toLowerCase();
460
+ if (apiKey === "cvk_org" && siteId === "site-1" && namespace === "org-ns")
461
+ return true;
462
+ if (siteId === "site-1" && namespace === "org-ns" && email.endsWith("@example.com"))
463
+ return true;
464
+ return false;
465
+ }
466
+ function extractAuthBackup(config) {
467
+ return {
468
+ candengo_url: config.candengo_url,
469
+ candengo_api_key: config.candengo_api_key,
470
+ site_id: config.site_id,
471
+ namespace: config.namespace,
472
+ user_id: config.user_id,
473
+ user_email: config.user_email,
474
+ teams: config.teams
475
+ };
476
+ }
477
+ function restoreAuthBackup(config) {
478
+ const authBackupPath = resolveAuthBackupPath();
479
+ if (!existsSync(authBackupPath))
480
+ return null;
481
+ try {
482
+ const raw = readFileSync(authBackupPath, "utf-8");
483
+ const parsed = JSON.parse(raw);
484
+ const restored = {
485
+ ...config,
486
+ candengo_url: asString(parsed["candengo_url"], config.candengo_url),
487
+ candengo_api_key: asString(parsed["candengo_api_key"], config.candengo_api_key),
488
+ site_id: asString(parsed["site_id"], config.site_id),
489
+ namespace: asString(parsed["namespace"], config.namespace),
490
+ user_id: asString(parsed["user_id"], config.user_id),
491
+ user_email: asString(parsed["user_email"], config.user_email),
492
+ teams: asTeams(parsed["teams"], config.teams)
493
+ };
494
+ return looksLikePlaceholderAuth(restored) ? null : restored;
495
+ } catch {
496
+ return null;
497
+ }
498
+ }
433
499
 
434
500
  // src/storage/migrations.ts
435
501
  var MIGRATIONS = [
@@ -1117,6 +1183,20 @@ function ensureChatMessageColumns(db) {
1117
1183
  db.exec("PRAGMA user_version = 17");
1118
1184
  }
1119
1185
  }
1186
+ function ensureObservationVectorTable(db) {
1187
+ if (!isVecExtensionLoaded(db))
1188
+ return;
1189
+ db.exec(`
1190
+ CREATE VIRTUAL TABLE IF NOT EXISTS vec_observations USING vec0(
1191
+ observation_id INTEGER PRIMARY KEY,
1192
+ embedding FLOAT[384]
1193
+ );
1194
+ `);
1195
+ const current = getSchemaVersion(db);
1196
+ if (current < 4) {
1197
+ db.exec("PRAGMA user_version = 4");
1198
+ }
1199
+ }
1120
1200
  function ensureChatVectorTable(db) {
1121
1201
  if (!isVecExtensionLoaded(db))
1122
1202
  return;
@@ -1345,6 +1425,7 @@ class MemDatabase {
1345
1425
  ensureObservationTypes(this.db);
1346
1426
  ensureSessionSummaryColumns(this.db);
1347
1427
  ensureChatMessageColumns(this.db);
1428
+ ensureObservationVectorTable(this.db);
1348
1429
  ensureChatVectorTable(this.db);
1349
1430
  ensureSyncOutboxSupportsChatMessages(this.db);
1350
1431
  }
@@ -3826,14 +3907,18 @@ function parseJsonArray(value) {
3826
3907
 
3827
3908
  // src/capture/transcript.ts
3828
3909
  import { createHash as createHash3 } from "node:crypto";
3829
- import { readFileSync as readFileSync4, existsSync as existsSync4 } from "node:fs";
3910
+ import { readFileSync as readFileSync4, existsSync as existsSync4, statSync, readdirSync } from "node:fs";
3830
3911
  import { join as join4 } from "node:path";
3831
3912
  import { homedir as homedir3 } from "node:os";
3832
3913
  function resolveTranscriptPath(sessionId, cwd, transcriptPath) {
3833
3914
  if (transcriptPath)
3834
3915
  return transcriptPath;
3835
3916
  const encodedCwd = cwd.replace(/\//g, "-");
3836
- return join4(homedir3(), ".claude", "projects", encodedCwd, `${sessionId}.jsonl`);
3917
+ const directPath = join4(homedir3(), ".claude", "projects", encodedCwd, `${sessionId}.jsonl`);
3918
+ if (existsSync4(directPath))
3919
+ return directPath;
3920
+ const discovered = findTranscriptPathBySessionId(sessionId);
3921
+ return discovered ?? directPath;
3837
3922
  }
3838
3923
  function readTranscript(sessionId, cwd, transcriptPath) {
3839
3924
  const path = resolveTranscriptPath(sessionId, cwd, transcriptPath);
@@ -3856,10 +3941,10 @@ function readTranscript(sessionId, cwd, transcriptPath) {
3856
3941
  } catch {
3857
3942
  continue;
3858
3943
  }
3859
- const role = entry.role;
3944
+ const role = getTranscriptRole(entry);
3860
3945
  if (role !== "user" && role !== "assistant")
3861
3946
  continue;
3862
- const content = entry.content;
3947
+ const content = getTranscriptContent(entry);
3863
3948
  if (typeof content === "string") {
3864
3949
  messages.push({ role, text: content });
3865
3950
  continue;
@@ -3879,6 +3964,66 @@ function readTranscript(sessionId, cwd, transcriptPath) {
3879
3964
  }
3880
3965
  return messages;
3881
3966
  }
3967
+ function readTranscriptToolEvents(sessionId, cwd, transcriptPath) {
3968
+ const path = resolveTranscriptPath(sessionId, cwd, transcriptPath);
3969
+ if (!existsSync4(path))
3970
+ return [];
3971
+ let raw;
3972
+ try {
3973
+ raw = readFileSync4(path, "utf-8");
3974
+ } catch {
3975
+ return [];
3976
+ }
3977
+ const toolEvents = [];
3978
+ const toolEventIndexes = new Map;
3979
+ for (const line of raw.split(`
3980
+ `)) {
3981
+ if (!line.trim())
3982
+ continue;
3983
+ let entry;
3984
+ try {
3985
+ entry = JSON.parse(line);
3986
+ } catch {
3987
+ continue;
3988
+ }
3989
+ const createdAtEpoch = parseTranscriptTimestamp(entry);
3990
+ const content = getTranscriptContent(entry);
3991
+ if (!Array.isArray(content))
3992
+ continue;
3993
+ for (const block of content) {
3994
+ if (!block || typeof block !== "object")
3995
+ continue;
3996
+ if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
3997
+ const preview = extractToolResultPreview(block.content);
3998
+ const index = toolEventIndexes.get(block.tool_use_id);
3999
+ if (preview && index !== undefined) {
4000
+ toolEvents[index] = {
4001
+ ...toolEvents[index],
4002
+ tool_response_preview: preview
4003
+ };
4004
+ }
4005
+ continue;
4006
+ }
4007
+ if (block.type !== "tool_use")
4008
+ continue;
4009
+ const input = block.input && typeof block.input === "object" ? block.input : {};
4010
+ const toolUseId = typeof block.id === "string" ? block.id : null;
4011
+ const nextEvent = {
4012
+ tool_name: typeof block.name === "string" ? block.name : "Unknown",
4013
+ tool_input_json: JSON.stringify(input),
4014
+ tool_response_preview: null,
4015
+ file_path: extractToolFilePath(input),
4016
+ command: typeof input.command === "string" ? input.command : null,
4017
+ created_at_epoch: createdAtEpoch
4018
+ };
4019
+ toolEvents.push(nextEvent);
4020
+ if (toolUseId) {
4021
+ toolEventIndexes.set(toolUseId, toolEvents.length - 1);
4022
+ }
4023
+ }
4024
+ }
4025
+ return toolEvents;
4026
+ }
3882
4027
  function resolveHistoryPath(historyPath) {
3883
4028
  if (historyPath)
3884
4029
  return historyPath;
@@ -3944,9 +4089,22 @@ function readHistoryFallback(sessionId, cwd, opts) {
3944
4089
  createdAtEpoch: entry.timestamp
3945
4090
  })));
3946
4091
  }
3947
- async function syncTranscriptChat(db, config, sessionId, cwd, transcriptPath) {
4092
+ async function syncTranscriptChat(db, config, sessionId, cwd, transcriptPath, options = {}) {
4093
+ const embed = options.embed ?? true;
3948
4094
  const session = db.getSessionById(sessionId);
3949
- const transcriptMessages = readTranscript(sessionId, cwd, transcriptPath).map((message) => ({
4095
+ const resolvedTranscriptPath = resolveTranscriptPath(sessionId, cwd, transcriptPath);
4096
+ const syncCursorKey = `transcript_sync_cursor:${sessionId}`;
4097
+ if (existsSync4(resolvedTranscriptPath)) {
4098
+ try {
4099
+ const stat = statSync(resolvedTranscriptPath);
4100
+ const cursor = `${stat.size}:${Math.floor(stat.mtimeMs)}`;
4101
+ if (db.getSyncState(syncCursorKey) === cursor) {
4102
+ return { imported: 0, total: 0 };
4103
+ }
4104
+ db.setSyncState(syncCursorKey, cursor);
4105
+ } catch {}
4106
+ }
4107
+ const transcriptMessages = readTranscript(sessionId, cwd, resolvedTranscriptPath).map((message) => ({
3950
4108
  ...message,
3951
4109
  text: message.text.trim()
3952
4110
  })).filter((message) => message.text.length > 0);
@@ -4008,7 +4166,7 @@ async function syncTranscriptChat(db, config, sessionId, cwd, transcriptPath) {
4008
4166
  created_at_epoch: createdAtEpoch
4009
4167
  });
4010
4168
  }
4011
- if (db.vecAvailable) {
4169
+ if (embed && db.vecAvailable) {
4012
4170
  const embedding = await embedText(composeChatEmbeddingText(message.text));
4013
4171
  if (embedding) {
4014
4172
  db.vecChatInsert(row.id, embedding);
@@ -4018,6 +4176,35 @@ async function syncTranscriptChat(db, config, sessionId, cwd, transcriptPath) {
4018
4176
  }
4019
4177
  return { imported, total: messages.length };
4020
4178
  }
4179
+ function syncTranscriptToolEvents(db, config, sessionId, cwd, transcriptPath) {
4180
+ const session = db.getSessionById(sessionId);
4181
+ if (!session)
4182
+ return { imported: 0, total: 0 };
4183
+ if (db.getSessionToolEvents(sessionId, 1).length > 0) {
4184
+ return { imported: 0, total: 0 };
4185
+ }
4186
+ const toolEvents = readTranscriptToolEvents(sessionId, cwd, transcriptPath);
4187
+ if (toolEvents.length === 0)
4188
+ return { imported: 0, total: 0 };
4189
+ let imported = 0;
4190
+ for (const event of toolEvents) {
4191
+ db.insertToolEvent({
4192
+ session_id: sessionId,
4193
+ project_id: session.project_id,
4194
+ tool_name: event.tool_name,
4195
+ tool_input_json: event.tool_input_json,
4196
+ tool_response_preview: event.tool_response_preview,
4197
+ file_path: event.file_path,
4198
+ command: event.command,
4199
+ user_id: config.user_id,
4200
+ device_id: config.device_id,
4201
+ agent: "claude-code",
4202
+ created_at_epoch: event.created_at_epoch ?? undefined
4203
+ });
4204
+ imported++;
4205
+ }
4206
+ return { imported, total: toolEvents.length };
4207
+ }
4021
4208
  function dedupeHistoryMessages(messages) {
4022
4209
  const deduped = [];
4023
4210
  for (const message of messages) {
@@ -4035,6 +4222,59 @@ function buildHistorySourceId(sessionId, createdAtEpoch, text) {
4035
4222
  const digest = createHash3("sha1").update(text).digest("hex").slice(0, 12);
4036
4223
  return `history:${sessionId}:${createdAtEpoch}:${digest}`;
4037
4224
  }
4225
+ function getTranscriptRole(entry) {
4226
+ return entry.role ?? entry.message?.role ?? entry.type ?? entry.message?.type;
4227
+ }
4228
+ function getTranscriptContent(entry) {
4229
+ return entry.content ?? entry.message?.content;
4230
+ }
4231
+ function parseTranscriptTimestamp(entry) {
4232
+ const raw = entry.timestamp ?? entry.message?.timestamp;
4233
+ if (typeof raw !== "string")
4234
+ return null;
4235
+ const epoch = Date.parse(raw);
4236
+ return Number.isFinite(epoch) ? Math.floor(epoch / 1000) : null;
4237
+ }
4238
+ function extractToolResultPreview(content) {
4239
+ if (typeof content === "string")
4240
+ return content.slice(0, 4000);
4241
+ if (Array.isArray(content)) {
4242
+ const text = content.map((item) => {
4243
+ if (typeof item === "string")
4244
+ return item;
4245
+ if (item && typeof item === "object" && typeof item.text === "string")
4246
+ return item.text;
4247
+ return "";
4248
+ }).filter(Boolean).join(`
4249
+ `);
4250
+ return text ? text.slice(0, 4000) : null;
4251
+ }
4252
+ return null;
4253
+ }
4254
+ function extractToolFilePath(input) {
4255
+ for (const key of ["file_path", "path", "target_file"]) {
4256
+ if (typeof input[key] === "string")
4257
+ return input[key];
4258
+ }
4259
+ return null;
4260
+ }
4261
+ function findTranscriptPathBySessionId(sessionId) {
4262
+ const projectsDir = join4(homedir3(), ".claude", "projects");
4263
+ if (!existsSync4(projectsDir))
4264
+ return null;
4265
+ try {
4266
+ for (const entry of readdirSync(projectsDir, { withFileTypes: true })) {
4267
+ if (!entry.isDirectory())
4268
+ continue;
4269
+ const candidate = join4(projectsDir, entry.name, `${sessionId}.jsonl`);
4270
+ if (existsSync4(candidate))
4271
+ return candidate;
4272
+ }
4273
+ } catch {
4274
+ return null;
4275
+ }
4276
+ return null;
4277
+ }
4038
4278
  function truncateTranscript(messages, maxBytes = 50000) {
4039
4279
  const lines = [];
4040
4280
  for (const msg of messages) {
@@ -4588,7 +4828,9 @@ async function main() {
4588
4828
  try {
4589
4829
  if (event.session_id) {
4590
4830
  persistRawToolChronology(db, event, config.user_id, config.device_id);
4591
- await syncTranscriptChat(db, config, event.session_id, event.cwd);
4831
+ await syncTranscriptChat(db, config, event.session_id, event.cwd, undefined, {
4832
+ embed: false
4833
+ });
4592
4834
  }
4593
4835
  const textToScan = extractScanText(event);
4594
4836
  if (textToScan) {