chat-recall 0.7.18 → 0.7.19

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/dist/cli.js CHANGED
@@ -4586,6 +4586,8 @@ function declaredOrigin(value) {
4586
4586
  function userRecordOrigin(obj, text) {
4587
4587
  if (obj.isSidechain === true)
4588
4588
  return "subagent";
4589
+ if (obj.isCompactSummary === true)
4590
+ return "compact-summary";
4589
4591
  const declared = declaredOrigin(obj.origin);
4590
4592
  if (declared && declared !== "human")
4591
4593
  return declared;
@@ -4769,6 +4771,7 @@ function defaultSourcesEnabled() {
4769
4771
  commands: true
4770
4772
  },
4771
4773
  cursor: { sessions: true, skills: true, agents: true, commands: true },
4774
+ hermes: { sessions: true },
4772
4775
  shared: { skills: true },
4773
4776
  common: { mcps: true, agentMd: true }
4774
4777
  };
@@ -4845,6 +4848,7 @@ function mergeSources(base4, partial) {
4845
4848
  codex: { ...base4.enabled.codex, ...partial.enabled?.codex ?? {} },
4846
4849
  agy: { ...base4.enabled.agy, ...inherited, ...partial.enabled?.agy ?? {} },
4847
4850
  cursor: { ...base4.enabled.cursor, ...partial.enabled?.cursor ?? {} },
4851
+ hermes: { ...base4.enabled.hermes, ...partial.enabled?.hermes ?? {} },
4848
4852
  shared: { ...base4.enabled.shared, ...partial.enabled?.shared ?? {} },
4849
4853
  common: { ...base4.enabled.common, ...partial.enabled?.common ?? {} }
4850
4854
  };
@@ -4856,6 +4860,7 @@ function mergeSources(base4, partial) {
4856
4860
  agyHome: partial.agyHome ?? base4.agyHome,
4857
4861
  cursorHome: partial.cursorHome ?? base4.cursorHome,
4858
4862
  cursorIdeHome: partial.cursorIdeHome ?? base4.cursorIdeHome,
4863
+ hermesHome: partial.hermesHome ?? base4.hermesHome,
4859
4864
  extraClaudeHomes: partial.extraClaudeHomes ?? base4.extraClaudeHomes,
4860
4865
  approvedHomes: partial.approvedHomes ?? base4.approvedHomes,
4861
4866
  declinedHomes: partial.declinedHomes ?? base4.declinedHomes,
@@ -5180,6 +5185,7 @@ var home_approval_exports = {};
5180
5185
  __export(home_approval_exports, {
5181
5186
  approveHome: () => approveHome,
5182
5187
  declineHome: () => declineHome,
5188
+ defaultHermesHome: () => defaultHermesHome,
5183
5189
  grandfatherLegacyHomes: () => grandfatherLegacyHomes,
5184
5190
  homeDecision: () => homeDecision,
5185
5191
  isHomeSynced: () => isHomeSynced,
@@ -5262,6 +5268,13 @@ function grandfatherLegacyHomes() {
5262
5268
  }
5263
5269
  return { seeded };
5264
5270
  }
5271
+ function defaultHermesHome() {
5272
+ if (process.platform === "win32") {
5273
+ const local = process.env.LOCALAPPDATA?.trim() || join3(homedir2(), "AppData", "Local");
5274
+ return join3(local, "hermes");
5275
+ }
5276
+ return join3(homedir2(), ".hermes");
5277
+ }
5265
5278
  function primaryHomes() {
5266
5279
  const home = homedir2();
5267
5280
  const env = process.env;
@@ -5278,7 +5291,8 @@ function primaryHomes() {
5278
5291
  env.CHAT_RECALL_CODEX_HOME || s?.codexHome || join3(home, ".codex"),
5279
5292
  env.CHAT_RECALL_AGY_HOME || s?.agyHome || join3(home, ".gemini", "antigravity-cli"),
5280
5293
  env.CHAT_RECALL_CURSOR_HOME || s?.cursorHome || join3(home, ".cursor"),
5281
- dirOf(opencodeDb)
5294
+ dirOf(opencodeDb),
5295
+ env.CHAT_RECALL_HERMES_HOME || s?.hermesHome || env.HERMES_HOME?.trim() || defaultHermesHome()
5282
5296
  ];
5283
5297
  return candidates2.map(normalizeHomePath);
5284
5298
  }
@@ -5405,6 +5419,9 @@ function geminiHomeDir() {
5405
5419
  function opencodeDbPath() {
5406
5420
  return process.env.CHAT_RECALL_OPENCODE_DB || sources().opencodeDbPath || join4(homedir3(), ".local", "share", "opencode", "opencode.db");
5407
5421
  }
5422
+ function hermesHomeDir() {
5423
+ return process.env.CHAT_RECALL_HERMES_HOME || sources().hermesHome || process.env.HERMES_HOME?.trim() || defaultHermesHome();
5424
+ }
5408
5425
  function codexHomeDir() {
5409
5426
  return process.env.CHAT_RECALL_CODEX_HOME || sources().codexHome || join4(homedir3(), ".codex");
5410
5427
  }
@@ -5506,6 +5523,22 @@ function opencodeDbPaths(opts = {}) {
5506
5523
  dbs.push(primary);
5507
5524
  return applyExclusions(dbs, opts.includeExcluded);
5508
5525
  }
5526
+ function hermesDbPaths(opts = {}) {
5527
+ const root = hermesHomeDir();
5528
+ const dbs = [join4(root, "state.db")];
5529
+ try {
5530
+ const profiles = join4(root, "profiles");
5531
+ for (const entry of readdirSync3(profiles, { withFileTypes: true })) {
5532
+ if (!entry.isDirectory())
5533
+ continue;
5534
+ const p = join4(profiles, entry.name, "state.db");
5535
+ if (existsSync4(p))
5536
+ dbs.push(p);
5537
+ }
5538
+ } catch {
5539
+ }
5540
+ return applyExclusions(dbs, opts.includeExcluded);
5541
+ }
5509
5542
  function extraClaudeHomeDirs() {
5510
5543
  const extras = sources().extraClaudeHomes ?? [];
5511
5544
  const primary = claudeHomeDir();
@@ -6574,7 +6607,7 @@ function canonicalEventsToMessages(events) {
6574
6607
  switch (e.kind) {
6575
6608
  case "user":
6576
6609
  cur = null;
6577
- messages.push({ line: e.line, role: "user", content: e.text ?? "", timestamp: e.tsIso });
6610
+ messages.push({ line: e.line, role: "user", content: e.text ?? "", timestamp: e.tsIso, ...e.origin ? { origin: e.origin } : {} });
6578
6611
  break;
6579
6612
  case "assistant_text":
6580
6613
  cur = { line: e.line, role: "assistant", content: e.text ?? "", timestamp: e.tsIso };
@@ -8459,6 +8492,71 @@ function readTailFromOffset(path, offset, maxBytes = TAIL_READ_MAX_BYTES) {
8459
8492
  }
8460
8493
  }
8461
8494
  }
8495
+ function readSourcesFromOffset(paths, offset, maxBytes = TAIL_READ_MAX_BYTES) {
8496
+ let start = 0;
8497
+ let linesBefore = 0;
8498
+ for (let i = 0; i < paths.length; i++) {
8499
+ const path = paths[i];
8500
+ let size = 0;
8501
+ try {
8502
+ size = statSync4(path).size;
8503
+ } catch {
8504
+ size = 0;
8505
+ }
8506
+ const last = i === paths.length - 1;
8507
+ if (!last && offset >= start + size) {
8508
+ const lines = linesBeforeOffset(path, size);
8509
+ if (lines === void 0)
8510
+ return { text: "", newOffset: offset };
8511
+ linesBefore += lines + (size > 0 && !endsWithNewline(path, size) ? 1 : 0);
8512
+ start += size;
8513
+ continue;
8514
+ }
8515
+ const local = offset - start;
8516
+ const read = readTailFromOffset(path, local, maxBytes);
8517
+ const shift = (r) => ({
8518
+ ...r,
8519
+ newOffset: start + r.newOffset,
8520
+ ...r.baseLine !== void 0 ? { baseLine: linesBefore + r.baseLine } : {}
8521
+ });
8522
+ if (last || read.newOffset > local || local >= size)
8523
+ return shift(read);
8524
+ if (size - local > maxBytes)
8525
+ return { text: "", newOffset: start + size, skippedBytes: size - local };
8526
+ let fd = null;
8527
+ try {
8528
+ fd = openSync2(path, "r");
8529
+ const text = readAt(fd, local, size - local).toString("utf-8");
8530
+ return shift({ text, newOffset: size, baseLine: linesBeforeOffset(path, local) });
8531
+ } catch {
8532
+ return { text: "", newOffset: offset };
8533
+ } finally {
8534
+ if (fd !== null) {
8535
+ try {
8536
+ closeSync2(fd);
8537
+ } catch {
8538
+ }
8539
+ }
8540
+ }
8541
+ }
8542
+ return { text: "", newOffset: offset };
8543
+ }
8544
+ function endsWithNewline(path, size) {
8545
+ let fd = null;
8546
+ try {
8547
+ fd = openSync2(path, "r");
8548
+ return readAt(fd, size - 1, 1)[0] === 10;
8549
+ } catch {
8550
+ return true;
8551
+ } finally {
8552
+ if (fd !== null) {
8553
+ try {
8554
+ closeSync2(fd);
8555
+ } catch {
8556
+ }
8557
+ }
8558
+ }
8559
+ }
8462
8560
  function findNewline(fd, from, size) {
8463
8561
  const block = Buffer.allocUnsafe(SKIP_SCAN_BYTES);
8464
8562
  let pos = from;
@@ -10937,6 +11035,473 @@ var init_cursor = __esm({
10937
11035
  }
10938
11036
  });
10939
11037
 
11038
+ // ../engine/dist/core/backends/hermes.js
11039
+ import { existsSync as existsSync12 } from "fs";
11040
+ function firstUserPrompt(db, sessionId) {
11041
+ try {
11042
+ const row = db.prepare(`
11043
+ SELECT substr(content, 1, 400) AS text FROM messages
11044
+ WHERE session_id = ? AND role = 'user' AND active = 1 AND _compressed_summary = 0
11045
+ AND (display_kind IS NULL OR display_kind = 'steer')
11046
+ AND length(trim(content)) > 0
11047
+ ORDER BY id LIMIT 1
11048
+ `).get(sessionId);
11049
+ return row?.text ? flatString(row.text.slice(0, 200)) : "";
11050
+ } catch {
11051
+ return "";
11052
+ }
11053
+ }
11054
+ function messageRowsToEvents(rows) {
11055
+ const events = [];
11056
+ let line = 0;
11057
+ for (const r of rows) {
11058
+ line++;
11059
+ if (r.active === 0)
11060
+ continue;
11061
+ if (r.display_kind === "failed_turn" || r.display_kind === "hidden")
11062
+ continue;
11063
+ const ts = Math.round((r.timestamp || 0) * 1e3);
11064
+ const tsIso = ts ? new Date(ts).toISOString() : void 0;
11065
+ const text = (r.content ?? "").trim();
11066
+ if (r._compressed_summary === 1) {
11067
+ if (text)
11068
+ events.push({ kind: "summary", ts, tsIso, line, text });
11069
+ continue;
11070
+ }
11071
+ if (r.role === "user") {
11072
+ if (!text)
11073
+ continue;
11074
+ events.push({
11075
+ kind: "user",
11076
+ ts,
11077
+ tsIso,
11078
+ line,
11079
+ text,
11080
+ ...r.display_kind === "process_complete" ? { origin: "task-notification" } : {}
11081
+ });
11082
+ continue;
11083
+ }
11084
+ if (r.role === "assistant") {
11085
+ if (text)
11086
+ events.push({ kind: "assistant_text", ts, tsIso, line, text });
11087
+ for (const call2 of parseToolCalls(r.tool_calls)) {
11088
+ const toolName = call2.function?.name ?? "";
11089
+ const toolInput = parseArguments(call2.function?.arguments);
11090
+ const command = toolName === "terminal" && typeof toolInput?.command === "string" ? toolInput.command : void 0;
11091
+ events.push({
11092
+ kind: "tool_use",
11093
+ ts,
11094
+ tsIso,
11095
+ line,
11096
+ toolName,
11097
+ toolUseId: call2.id || call2.call_id || `${r.id}:${toolName}`,
11098
+ toolInput,
11099
+ ...command ? { command } : {}
11100
+ });
11101
+ }
11102
+ continue;
11103
+ }
11104
+ if (r.role === "tool") {
11105
+ const body2 = r.content ?? "";
11106
+ const { exitCode, isError } = toolResultStatus(body2);
11107
+ events.push({
11108
+ kind: "tool_result",
11109
+ ts,
11110
+ tsIso,
11111
+ line,
11112
+ toolUseId: r.tool_call_id ?? void 0,
11113
+ toolName: r.tool_name ?? void 0,
11114
+ resultBody: body2,
11115
+ resultIsError: isError,
11116
+ ...exitCode !== void 0 ? { resultExitCode: exitCode } : {},
11117
+ resultBytes: Buffer.byteLength(body2)
11118
+ });
11119
+ }
11120
+ }
11121
+ return events;
11122
+ }
11123
+ function parseToolCalls(json) {
11124
+ if (!json)
11125
+ return [];
11126
+ try {
11127
+ const calls = JSON.parse(json);
11128
+ return Array.isArray(calls) ? calls : [];
11129
+ } catch {
11130
+ return [];
11131
+ }
11132
+ }
11133
+ function parseArguments(args) {
11134
+ if (typeof args !== "string")
11135
+ return args ?? {};
11136
+ try {
11137
+ return JSON.parse(args);
11138
+ } catch {
11139
+ return { raw: args };
11140
+ }
11141
+ }
11142
+ function toolResultStatus(body2) {
11143
+ if (!body2.startsWith("{"))
11144
+ return { isError: false };
11145
+ try {
11146
+ const r = JSON.parse(body2);
11147
+ const exitCode = typeof r.exit_code === "number" ? r.exit_code : void 0;
11148
+ const hasError = r.error !== void 0 && r.error !== null && r.error !== "";
11149
+ return { exitCode, isError: hasError || exitCode !== void 0 && exitCode !== 0 };
11150
+ } catch {
11151
+ return { isError: false };
11152
+ }
11153
+ }
11154
+ var PREFIX5, MESSAGE_COLUMNS, ARCHIVED_SESSION_COLUMNS, SESSION_MTIME_SQL, HermesBackend, hermesBackend;
11155
+ var init_hermes = __esm({
11156
+ "../engine/dist/core/backends/hermes.js"() {
11157
+ "use strict";
11158
+ init_sqlite_reader();
11159
+ init_tool_paths();
11160
+ init_session_outcome();
11161
+ init_session_git();
11162
+ init_generic_engine();
11163
+ init_flat_string();
11164
+ PREFIX5 = "hermes_";
11165
+ MESSAGE_COLUMNS = "id, role, content, tool_calls, tool_call_id, tool_name, timestamp, display_kind, active, _compressed_summary";
11166
+ ARCHIVED_SESSION_COLUMNS = [
11167
+ "id",
11168
+ "source",
11169
+ "title",
11170
+ "model",
11171
+ "cwd",
11172
+ "git_branch",
11173
+ "git_repo_root",
11174
+ "parent_session_id",
11175
+ "started_at",
11176
+ "ended_at",
11177
+ "end_reason",
11178
+ "message_count",
11179
+ "tool_call_count",
11180
+ "input_tokens",
11181
+ "output_tokens",
11182
+ "cache_read_tokens",
11183
+ "cache_write_tokens",
11184
+ "reasoning_tokens",
11185
+ "estimated_cost_usd",
11186
+ "actual_cost_usd"
11187
+ ];
11188
+ SESSION_MTIME_SQL = `MAX(
11189
+ s.started_at,
11190
+ COALESCE(s.ended_at, 0),
11191
+ COALESCE(s.last_activity_at, 0),
11192
+ COALESCE((SELECT MAX(m.timestamp) FROM messages m WHERE m.session_id = s.id), 0)
11193
+ )`;
11194
+ HermesBackend = class {
11195
+ id = "hermes";
11196
+ idPrefix = PREFIX5;
11197
+ displayName = "Hermes";
11198
+ homeDir() {
11199
+ return hermesHomeDir();
11200
+ }
11201
+ /** Every Hermes database on this machine, the root profile first. */
11202
+ dbPaths() {
11203
+ return hermesDbPaths();
11204
+ }
11205
+ isAvailable() {
11206
+ return this.dbPaths().some((p) => existsSync12(p));
11207
+ }
11208
+ /** The database that holds this session. A session id is unique within a
11209
+ * database, and each named profile has its own. */
11210
+ dbPathFor(rawId) {
11211
+ const cached = this._dbForSession.get(rawId);
11212
+ if (cached)
11213
+ return cached;
11214
+ for (const path of this.dbPaths()) {
11215
+ const db = openSqliteReadonly(path);
11216
+ if (!db)
11217
+ continue;
11218
+ try {
11219
+ if (db.prepare("SELECT 1 AS ok FROM sessions WHERE id = ?").get(rawId)) {
11220
+ this._dbForSession.set(rawId, path);
11221
+ return path;
11222
+ }
11223
+ } catch {
11224
+ } finally {
11225
+ db.close();
11226
+ }
11227
+ }
11228
+ return null;
11229
+ }
11230
+ _dbForSession = /* @__PURE__ */ new Map();
11231
+ /** Tests, and anything that adds a profile mid-process. */
11232
+ _clearDbRouting() {
11233
+ this._dbForSession.clear();
11234
+ }
11235
+ // ── ID handling ────────────────────────────────────────────────
11236
+ matchesId(id) {
11237
+ return id.startsWith(PREFIX5);
11238
+ }
11239
+ toRawId(id) {
11240
+ return id.startsWith(PREFIX5) ? id.slice(PREFIX5.length) : id;
11241
+ }
11242
+ toPrefixedId(rawId) {
11243
+ return rawId.startsWith(PREFIX5) ? rawId : PREFIX5 + rawId;
11244
+ }
11245
+ // ── Location ───────────────────────────────────────────────────
11246
+ findSession(id) {
11247
+ const rawId = this.toRawId(id);
11248
+ const path = this.dbPathFor(rawId);
11249
+ if (!path)
11250
+ return null;
11251
+ const db = openSqliteReadonly(path);
11252
+ if (!db)
11253
+ return null;
11254
+ try {
11255
+ const row = db.prepare(`
11256
+ SELECT s.cwd, s.git_repo_root, ${SESSION_MTIME_SQL} AS mtime
11257
+ FROM sessions s WHERE s.id = ?
11258
+ `).get(rawId);
11259
+ if (!row)
11260
+ return null;
11261
+ return {
11262
+ path,
11263
+ format: "sqlite",
11264
+ projectDir: "",
11265
+ projectPath: row.git_repo_root || row.cwd || "",
11266
+ mtime: Math.round((row.mtime || 0) * 1e3)
11267
+ };
11268
+ } catch {
11269
+ return null;
11270
+ } finally {
11271
+ db.close();
11272
+ }
11273
+ }
11274
+ listSessions(opts = {}) {
11275
+ const byId = /* @__PURE__ */ new Map();
11276
+ for (const dbPath of this.dbPaths()) {
11277
+ if (!existsSync12(dbPath))
11278
+ continue;
11279
+ for (const ref of this.listSessionsInDb(dbPath, opts)) {
11280
+ const prior = byId.get(ref.rawId);
11281
+ if (!prior || ref.mtime > prior.mtime)
11282
+ byId.set(ref.rawId, ref);
11283
+ }
11284
+ }
11285
+ const all = [...byId.values()].sort((a, b) => b.mtime - a.mtime);
11286
+ return opts.limit ? all.slice(0, opts.limit) : all;
11287
+ }
11288
+ listSessionsInDb(dbPath, opts) {
11289
+ const cutoffSec = (opts.sinceMs ?? 0) / 1e3;
11290
+ const filter = opts.projectFilter?.toLowerCase();
11291
+ const db = openSqliteReadonly(dbPath);
11292
+ if (!db)
11293
+ return [];
11294
+ try {
11295
+ const rows = db.prepare(`
11296
+ SELECT * FROM (
11297
+ SELECT s.id, s.cwd, s.git_repo_root, s.started_at, s.message_count,
11298
+ ${SESSION_MTIME_SQL} AS mtime
11299
+ FROM sessions s
11300
+ ) WHERE mtime >= ? ORDER BY mtime DESC
11301
+ `).all(cutoffSec);
11302
+ const out = [];
11303
+ for (const row of rows) {
11304
+ const projectPath = row.git_repo_root || row.cwd || "";
11305
+ if (filter && !projectPath.toLowerCase().includes(filter))
11306
+ continue;
11307
+ const mtime = Math.round(row.mtime * 1e3);
11308
+ this._dbForSession.set(row.id, dbPath);
11309
+ out.push({
11310
+ toolId: "hermes",
11311
+ rawId: row.id,
11312
+ prefixedId: this.toPrefixedId(row.id),
11313
+ projectPath,
11314
+ projectDir: "",
11315
+ fullPath: dbPath,
11316
+ created: new Date(Math.round(row.started_at * 1e3)).toISOString(),
11317
+ modified: new Date(mtime).toISOString(),
11318
+ mtime,
11319
+ firstPrompt: opts.previews === false ? "" : firstUserPrompt(db, row.id),
11320
+ messageCount: opts.previews === false ? 0 : row.message_count ?? 0
11321
+ });
11322
+ if (opts.limit && out.length >= opts.limit)
11323
+ break;
11324
+ }
11325
+ return out;
11326
+ } catch {
11327
+ return [];
11328
+ } finally {
11329
+ db.close();
11330
+ }
11331
+ }
11332
+ /** Hermes writes a title for each session (a model-written one, or the
11333
+ * first prompt for a one-shot run). */
11334
+ getNativeTitle(rawId) {
11335
+ const path = this.dbPathFor(this.toRawId(rawId));
11336
+ if (!path)
11337
+ return null;
11338
+ const db = openSqliteReadonly(path);
11339
+ if (!db)
11340
+ return null;
11341
+ try {
11342
+ const row = db.prepare("SELECT title FROM sessions WHERE id = ?").get(this.toRawId(rawId));
11343
+ const t = row?.title?.trim();
11344
+ return t ? t.slice(0, 200) : null;
11345
+ } catch {
11346
+ return null;
11347
+ } finally {
11348
+ db.close();
11349
+ }
11350
+ }
11351
+ // ── Generic-engine inputs ───────────────────────────────────────
11352
+ fileToolMap = {
11353
+ write_file: "write",
11354
+ patch: "edit",
11355
+ read_file: "read"
11356
+ };
11357
+ /**
11358
+ * `write_file` carries `{ path, content }`. `patch` carries
11359
+ * `{ mode: 'replace', path, old_string, new_string }`; its other mode,
11360
+ * `patch`, is a multi-file V4A patch with no single before and after.
11361
+ */
11362
+ extractEditDelta(toolName, input) {
11363
+ if (input == null || typeof input !== "object")
11364
+ return null;
11365
+ const inp = input;
11366
+ if (toolName === "write_file") {
11367
+ return { before: "", after: typeof inp.content === "string" ? inp.content : null };
11368
+ }
11369
+ if (toolName === "patch" && (inp.mode === void 0 || inp.mode === "replace")) {
11370
+ const before = typeof inp.old_string === "string" ? inp.old_string : null;
11371
+ const after = typeof inp.new_string === "string" ? inp.new_string : null;
11372
+ if (before === null && after === null)
11373
+ return null;
11374
+ return { before, after };
11375
+ }
11376
+ return null;
11377
+ }
11378
+ readEvents(rawId) {
11379
+ const id = this.toRawId(rawId);
11380
+ const path = this.dbPathFor(id);
11381
+ if (!path)
11382
+ return [];
11383
+ const db = openSqliteReadonly(path);
11384
+ if (!db)
11385
+ return [];
11386
+ try {
11387
+ const rows = db.prepare(`SELECT ${MESSAGE_COLUMNS} FROM messages WHERE session_id = ? ORDER BY id`).all(id);
11388
+ return messageRowsToEvents(rows);
11389
+ } catch {
11390
+ return [];
11391
+ } finally {
11392
+ db.close();
11393
+ }
11394
+ }
11395
+ /**
11396
+ * Events from the dump `exportRawSession` writes, for a session rebuilt from
11397
+ * the server's archive. The archive joins the lines of every dump it got, so
11398
+ * a row that changed (a rewind sets `active` to 0) is there more than once.
11399
+ * The last copy of each row id is the newest.
11400
+ */
11401
+ readEventsFromText(text) {
11402
+ const rows = /* @__PURE__ */ new Map();
11403
+ for (const line of text.split("\n")) {
11404
+ if (!line.trim())
11405
+ continue;
11406
+ try {
11407
+ const rec = JSON.parse(line);
11408
+ if (rec.kind === "message" && rec.row) {
11409
+ rows.delete(rec.row.id);
11410
+ rows.set(rec.row.id, rec.row);
11411
+ }
11412
+ } catch {
11413
+ }
11414
+ }
11415
+ return messageRowsToEvents([...rows.values()].sort((a, b) => a.id - b.id));
11416
+ }
11417
+ // ── Per-session operations — all delegate to the generic engine ─
11418
+ extractTurns(id, opts = {}) {
11419
+ return extractTurnsFromEvents(this.toPrefixedId(id), this.readEvents(id), opts);
11420
+ }
11421
+ liveScanEdits(id) {
11422
+ const located = this.findSession(id);
11423
+ if (!located) {
11424
+ return { found: false, projectPath: "", projectDir: "", edits: [], fileMtime: 0, tool: "hermes" };
11425
+ }
11426
+ return liveScanEditsFromEvents(this.readEvents(id), this.fileToolMap, {
11427
+ sessionId: this.toPrefixedId(id),
11428
+ tool: "hermes",
11429
+ projectPath: located.projectPath,
11430
+ projectDir: located.projectDir,
11431
+ fileMtime: located.mtime,
11432
+ found: true
11433
+ });
11434
+ }
11435
+ replay(id) {
11436
+ const located = this.findSession(id);
11437
+ if (!located) {
11438
+ return { sessionId: this.toPrefixedId(id), found: false, projectPath: "", files: [], totalLinesAdded: 0, totalLinesRemoved: 0 };
11439
+ }
11440
+ return replayFromEvents(this.toPrefixedId(id), this.readEvents(id), this.fileToolMap, this.extractEditDelta.bind(this), {
11441
+ projectPath: located.projectPath,
11442
+ found: true
11443
+ });
11444
+ }
11445
+ computeOutcome(id, opts) {
11446
+ return computeOutcome(this.toPrefixedId(id), opts);
11447
+ }
11448
+ /** File edits since `sinceMs` across the sessions active since then. */
11449
+ collectRecentEdits(opts) {
11450
+ const refs = this.listSessions({ sinceMs: opts.sinceMs, projectFilter: opts.projectFilter, previews: false });
11451
+ const edits = [];
11452
+ for (const ref of opts.limitSessions ? refs.slice(0, opts.limitSessions) : refs) {
11453
+ for (const e of this.liveScanEdits(ref.rawId).edits) {
11454
+ if (e.ts >= opts.sinceMs)
11455
+ edits.push(e);
11456
+ }
11457
+ }
11458
+ return edits.sort((a, b) => b.ts - a.ts);
11459
+ }
11460
+ getCommits(id, files, startMs, endMs, bufferMinutes) {
11461
+ return getSessionCommits(this.toPrefixedId(id), files, startMs, endMs, bufferMinutes);
11462
+ }
11463
+ /**
11464
+ * The session row and its messages as JSONL, one row per line in id order.
11465
+ * Hermes rewrites and compacts its database, so the archive keeps the rows
11466
+ * as they were synced.
11467
+ */
11468
+ exportRawSession(id) {
11469
+ const rawId = this.toRawId(id);
11470
+ const path = this.dbPathFor(rawId);
11471
+ if (!path)
11472
+ return null;
11473
+ const db = openSqliteReadonly(path);
11474
+ if (!db)
11475
+ return null;
11476
+ try {
11477
+ const have = new Set(db.prepare(`SELECT name FROM pragma_table_info('sessions')`).all().map((c) => c.name));
11478
+ const cols = ARCHIVED_SESSION_COLUMNS.filter((c) => have.has(c)).map((c) => `s.${c}`);
11479
+ const session = db.prepare(`
11480
+ SELECT ${[...cols, `${SESSION_MTIME_SQL} AS mtime`].join(", ")}
11481
+ FROM sessions s WHERE s.id = ?
11482
+ `).get(rawId);
11483
+ if (!session)
11484
+ return null;
11485
+ const rows = db.prepare(`SELECT ${MESSAGE_COLUMNS} FROM messages WHERE session_id = ? ORDER BY id`).all(rawId);
11486
+ const lines = [JSON.stringify({ kind: "session", row: session })];
11487
+ for (const r of rows)
11488
+ lines.push(JSON.stringify({ kind: "message", row: r }));
11489
+ return {
11490
+ tool: "hermes",
11491
+ mtime: Math.round(Number(session.mtime || 0) * 1e3),
11492
+ files: [{ name: `${rawId}.dump.jsonl`, bytes: Buffer.from(lines.join("\n") + "\n", "utf-8") }]
11493
+ };
11494
+ } catch {
11495
+ return null;
11496
+ } finally {
11497
+ db.close();
11498
+ }
11499
+ }
11500
+ };
11501
+ hermesBackend = new HermesBackend();
11502
+ }
11503
+ });
11504
+
10940
11505
  // ../engine/dist/core/backends/index.js
10941
11506
  var backends_exports = {};
10942
11507
  __export(backends_exports, {
@@ -10944,6 +11509,7 @@ __export(backends_exports, {
10944
11509
  ClaudeBackend: () => ClaudeBackend,
10945
11510
  CodexBackend: () => CodexBackend,
10946
11511
  CursorBackend: () => CursorBackend,
11512
+ HermesBackend: () => HermesBackend,
10947
11513
  OpencodeBackend: () => OpencodeBackend,
10948
11514
  agyBackend: () => agyBackend,
10949
11515
  bootstrapBackends: () => bootstrapBackends,
@@ -10952,6 +11518,7 @@ __export(backends_exports, {
10952
11518
  cursorBackend: () => cursorBackend,
10953
11519
  getBackend: () => getBackend,
10954
11520
  getBackendForId: () => getBackendForId,
11521
+ hermesBackend: () => hermesBackend,
10955
11522
  listAllBackends: () => listAllBackends,
10956
11523
  listAvailableBackends: () => listAvailableBackends,
10957
11524
  opencodeBackend: () => opencodeBackend,
@@ -10964,6 +11531,7 @@ function bootstrapBackends() {
10964
11531
  registerBackend(codexBackend);
10965
11532
  registerBackend(agyBackend);
10966
11533
  registerBackend(cursorBackend);
11534
+ registerBackend(hermesBackend);
10967
11535
  }
10968
11536
  var init_backends = __esm({
10969
11537
  "../engine/dist/core/backends/index.js"() {
@@ -10974,6 +11542,7 @@ var init_backends = __esm({
10974
11542
  init_codex2();
10975
11543
  init_agy();
10976
11544
  init_cursor();
11545
+ init_hermes();
10977
11546
  init_tool_backend();
10978
11547
  _setRegistryBootstrapper(bootstrapBackends);
10979
11548
  }
@@ -10982,7 +11551,7 @@ var init_backends = __esm({
10982
11551
  // ../engine/dist/transcript/shadow.js
10983
11552
  import { gzipSync as gzipSync2, gunzipSync as gunzipSync2 } from "zlib";
10984
11553
  import { createHash as createHash2 } from "crypto";
10985
- import { existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync2, renameSync as renameSync2, statSync as statSync10, openSync as openSync4, readSync as readSync4, closeSync as closeSync4 } from "fs";
11554
+ import { existsSync as existsSync13, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync2, renameSync as renameSync2, statSync as statSync10, openSync as openSync4, readSync as readSync4, closeSync as closeSync4 } from "fs";
10986
11555
  import { dirname as dirname12, join as join14 } from "path";
10987
11556
  function shadowRoot() {
10988
11557
  return join14(getDataDir(), "shadow");
@@ -10993,7 +11562,7 @@ function shadowFileFor(tool, rawId) {
10993
11562
  }
10994
11563
  function readShadowContainer(tool, rawId) {
10995
11564
  const path = shadowFileFor(tool, rawId);
10996
- if (!existsSync12(path))
11565
+ if (!existsSync13(path))
10997
11566
  return null;
10998
11567
  try {
10999
11568
  const c = JSON.parse(gunzipSync2(readFileSync8(path)).toString("utf-8"));
@@ -11251,7 +11820,7 @@ var init_shadow = __esm({
11251
11820
  });
11252
11821
 
11253
11822
  // ../engine/dist/core/live-session-scan.js
11254
- import { existsSync as existsSync13, readdirSync as readdirSync10, readFileSync as readFileSync9, statSync as statSync11, openSync as openSync5, readSync as readSync5, closeSync as closeSync5 } from "fs";
11823
+ import { existsSync as existsSync14, readdirSync as readdirSync10, readFileSync as readFileSync9, statSync as statSync11, openSync as openSync5, readSync as readSync5, closeSync as closeSync5 } from "fs";
11255
11824
  import { join as join15, basename as basename9 } from "path";
11256
11825
  function detectTool(sessionId) {
11257
11826
  return getBackendForId(sessionId)?.id ?? "claude";
@@ -11308,7 +11877,7 @@ function sessionFileIndex() {
11308
11877
  return p;
11309
11878
  };
11310
11879
  for (const root of roots) {
11311
- if (seenRoots.has(root) || !existsSync13(root))
11880
+ if (seenRoots.has(root) || !existsSync14(root))
11312
11881
  continue;
11313
11882
  seenRoots.add(root);
11314
11883
  dirs.set(root, dirMtime(root));
@@ -11434,7 +12003,7 @@ function resolveSessionContentPaths(sessionFile) {
11434
12003
  if (!hasSubagentsDir(sessionFile))
11435
12004
  return [sessionFile];
11436
12005
  const subDir = join15(sessionFile.slice(0, -6), "subagents");
11437
- if (!existsSync13(subDir))
12006
+ if (!existsSync14(subDir))
11438
12007
  return [sessionFile];
11439
12008
  const subPaths = readdirSync10(subDir).filter((f) => f.endsWith(".jsonl")).sort().map((f) => join15(subDir, f));
11440
12009
  return [sessionFile, ...subPaths];
@@ -11451,7 +12020,7 @@ function findCodexSessionFile(sessionId) {
11451
12020
  return null;
11452
12021
  }
11453
12022
  function findCodexSessionFileIn(root, sessionId) {
11454
- if (!existsSync13(root))
12023
+ if (!existsSync14(root))
11455
12024
  return null;
11456
12025
  for (const year of readdirSync10(root, { withFileTypes: true })) {
11457
12026
  if (!year.isDirectory())
@@ -11534,7 +12103,7 @@ var init_live_session_scan = __esm({
11534
12103
  });
11535
12104
 
11536
12105
  // ../engine/dist/core/first-prompt.js
11537
- import { createReadStream as createReadStream2, existsSync as existsSync14, openSync as openSync6, readSync as readSync6, closeSync as closeSync6 } from "fs";
12106
+ import { createReadStream as createReadStream2, existsSync as existsSync15, openSync as openSync6, readSync as readSync6, closeSync as closeSync6 } from "fs";
11538
12107
  import { StringDecoder } from "string_decoder";
11539
12108
  function stripBanners2(s) {
11540
12109
  let out = s;
@@ -11645,7 +12214,7 @@ var init_first_prompt = __esm({
11645
12214
  });
11646
12215
 
11647
12216
  // ../engine/dist/core/backends/claude.js
11648
- import { existsSync as existsSync15, readdirSync as readdirSync11, readFileSync as readFileSync10, statSync as statSync12, openSync as openSync7, readSync as readSync7, closeSync as closeSync7 } from "fs";
12217
+ import { existsSync as existsSync16, readdirSync as readdirSync11, readFileSync as readFileSync10, statSync as statSync12, openSync as openSync7, readSync as readSync7, closeSync as closeSync7 } from "fs";
11649
12218
  import { join as join16, basename as basename10 } from "path";
11650
12219
  function stripReminders(text) {
11651
12220
  return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
@@ -11715,7 +12284,7 @@ var init_claude2 = __esm({
11715
12284
  return join16(this.homeDir(), "history.jsonl");
11716
12285
  }
11717
12286
  isAvailable() {
11718
- return existsSync15(this.projectsDir());
12287
+ return existsSync16(this.projectsDir());
11719
12288
  }
11720
12289
  // ── ID handling ────────────────────────────────────────────────
11721
12290
  /**
@@ -11821,7 +12390,7 @@ var init_claude2 = __esm({
11821
12390
  prior.modified = ref.modified;
11822
12391
  }
11823
12392
  };
11824
- if (!existsSync15(root))
12393
+ if (!existsSync16(root))
11825
12394
  return;
11826
12395
  for (const proj of readdirSync11(root, { withFileTypes: true })) {
11827
12396
  if (!proj.isDirectory())
@@ -11832,12 +12401,12 @@ var init_claude2 = __esm({
11832
12401
  const projectPath = resolveProjectDirName(proj.name);
11833
12402
  const indexPath = join16(projPath, "sessions-index.json");
11834
12403
  const indexed = /* @__PURE__ */ new Set();
11835
- if (existsSync15(indexPath)) {
12404
+ if (existsSync16(indexPath)) {
11836
12405
  try {
11837
12406
  const indexData = JSON.parse(readFileSync10(indexPath, "utf-8"));
11838
12407
  const entries = indexData.entries || [];
11839
12408
  for (const entry of entries) {
11840
- if (!entry.fullPath || !existsSync15(entry.fullPath))
12409
+ if (!entry.fullPath || !existsSync16(entry.fullPath))
11841
12410
  continue;
11842
12411
  indexed.add(entry.sessionId);
11843
12412
  add({
@@ -12162,7 +12731,7 @@ var init_claude2 = __esm({
12162
12731
  collectRecentEdits(opts) {
12163
12732
  const candidates2 = [];
12164
12733
  for (const root of claudeProjectDirs()) {
12165
- if (!existsSync15(root))
12734
+ if (!existsSync16(root))
12166
12735
  continue;
12167
12736
  for (const projEntry of readdirSync11(root, { withFileTypes: true })) {
12168
12737
  if (!projEntry.isDirectory())
@@ -12184,7 +12753,7 @@ var init_claude2 = __esm({
12184
12753
  const st = statSync12(fp);
12185
12754
  let mtime = st.mtimeMs;
12186
12755
  const subDir = join16(projPath, f.slice(0, -6), "subagents");
12187
- if (existsSync15(subDir)) {
12756
+ if (existsSync16(subDir)) {
12188
12757
  for (const sub of readdirSync11(subDir)) {
12189
12758
  try {
12190
12759
  const subSt = statSync12(join16(subDir, sub));
@@ -12261,27 +12830,45 @@ var init_claude2 = __esm({
12261
12830
  /**
12262
12831
  * True when this session exists in MORE THAN ONE home.
12263
12832
  *
12264
- * Such a session cannot be tail-appended: `fileSize()` is the SUM across
12265
- * copies while a byte offset addresses ONE file, so the two disagree by
12266
- * construction and the "tail" is either empty or the wrong bytes. The caller
12267
- * uses this to force a FULL sync, which ships the unioned container and is
12268
- * correct regardless of how the halves are arranged.
12269
- *
12270
12833
  * Measured on ec05f266: ~/.claude 1715 records, ~/.claude-work 205 records,
12271
- * zero overlap. An offset-based append against a 5846662-byte "size" that no
12272
- * single file has could only ever ship nonsense.
12834
+ * zero overlap. `fileSize()` is the sum across the copies, and
12835
+ * `readFromOffset()` reads them joined end to end, primary first.
12273
12836
  */
12274
12837
  spansMultipleSources(prefixedId) {
12275
12838
  return findSessionFiles(this.toRawId(prefixedId)).length > 1;
12276
12839
  }
12840
+ /**
12841
+ * The copies before the last one, each with its size. Claude Code writes to
12842
+ * the home of the profile that runs the session, so the live copy can grow,
12843
+ * appear, vanish or merge back into the primary. Each of those moves bytes
12844
+ * under an offset into the joined copies; this key changes with all of them.
12845
+ */
12846
+ sourceLayout(prefixedId) {
12847
+ const copies = findSessionFiles(this.toRawId(prefixedId));
12848
+ if (copies.length < 2)
12849
+ return "";
12850
+ return copies.slice(0, -1).map((c) => {
12851
+ let size = -1;
12852
+ try {
12853
+ size = statSync12(c.path).size;
12854
+ } catch {
12855
+ }
12856
+ return `${c.path}:${size}`;
12857
+ }).join("|");
12858
+ }
12859
+ /**
12860
+ * Session 8de13d0a had 31 MB in ~/.claude and 2 MB in ~/.claude-work. Over
12861
+ * the 24 MB build ceiling a sync ships the transcript in chunks read here,
12862
+ * and this returned no text for a session in two homes. Each sync shipped
12863
+ * nothing, and the server stayed three hours behind.
12864
+ */
12277
12865
  async readFromOffset(prefixedId, offset, maxBytes) {
12278
- const rawId = this.toRawId(prefixedId);
12279
- const copies = findSessionFiles(rawId);
12866
+ const copies = findSessionFiles(this.toRawId(prefixedId));
12280
12867
  if (copies.length === 0)
12281
12868
  return { text: "", newOffset: offset };
12282
- if (copies.length > 1)
12283
- return { text: "", newOffset: offset };
12284
- return readTailFromOffset(copies[0].path, offset, maxBytes);
12869
+ if (copies.length === 1)
12870
+ return readTailFromOffset(copies[0].path, offset, maxBytes);
12871
+ return readSourcesFromOffset(copies.map((c) => c.path), offset, maxBytes);
12285
12872
  }
12286
12873
  };
12287
12874
  claudeBackend = new ClaudeBackend();
@@ -12302,7 +12889,7 @@ __export(project_resolver_exports, {
12302
12889
  });
12303
12890
  import { execSync } from "child_process";
12304
12891
  import { createHash as createHash3 } from "crypto";
12305
- import { existsSync as existsSync16, readFileSync as readFileSync11, realpathSync as realpathSync2, readdirSync as readdirSync12, statSync as statSync13 } from "fs";
12892
+ import { existsSync as existsSync17, readFileSync as readFileSync11, realpathSync as realpathSync2, readdirSync as readdirSync12, statSync as statSync13 } from "fs";
12306
12893
  import { basename as basename11, dirname as dirname13, isAbsolute, join as join17, resolve, sep } from "path";
12307
12894
  function getProjectsConfigPath() {
12308
12895
  return join17(getDataDir(), "projects.json");
@@ -12312,7 +12899,7 @@ function loadProjectsConfig() {
12312
12899
  return cachedConfig.cfg;
12313
12900
  }
12314
12901
  const path = getProjectsConfigPath();
12315
- if (!existsSync16(path)) {
12902
+ if (!existsSync17(path)) {
12316
12903
  cachedConfig = { mtime: 0, cfg: {} };
12317
12904
  return cachedConfig.cfg;
12318
12905
  }
@@ -12486,7 +13073,7 @@ function findGitToplevel(realPath) {
12486
13073
  let cur = realPath;
12487
13074
  let last = "";
12488
13075
  while (cur && cur !== last) {
12489
- if (existsSync16(join17(cur, ".git")))
13076
+ if (existsSync17(join17(cur, ".git")))
12490
13077
  return cur;
12491
13078
  last = cur;
12492
13079
  cur = dirname13(cur);
@@ -12553,7 +13140,7 @@ function resolveAutoWorkspace(realPath, minRepos) {
12553
13140
  let last = "";
12554
13141
  let depth = 0;
12555
13142
  while (cur && cur !== last && depth < AUTO_WORKSPACE_MAX_DEPTH) {
12556
- if (!existsSync16(join17(cur, ".git"))) {
13143
+ if (!existsSync17(join17(cur, ".git"))) {
12557
13144
  const cached = workspaceCache.get(cur);
12558
13145
  let qualifies;
12559
13146
  if (cached === void 0) {
@@ -12589,7 +13176,7 @@ function countGitChildren(dir) {
12589
13176
  const st = statSync13(child);
12590
13177
  if (!st.isDirectory())
12591
13178
  continue;
12592
- if (existsSync16(join17(child, ".git")))
13179
+ if (existsSync17(join17(child, ".git")))
12593
13180
  count++;
12594
13181
  } catch {
12595
13182
  }
@@ -13179,7 +13766,7 @@ var init_secret_redactor = __esm({
13179
13766
 
13180
13767
  // ../engine/dist/core/source-discovery.js
13181
13768
  import { createHash as createHash5 } from "crypto";
13182
- import { existsSync as existsSync17, readdirSync as readdirSync13, statSync as statSync15 } from "fs";
13769
+ import { existsSync as existsSync18, readdirSync as readdirSync13, statSync as statSync15 } from "fs";
13183
13770
  import { join as join18 } from "path";
13184
13771
  function sourceId(path) {
13185
13772
  return "src_" + createHash5("sha256").update(path).digest("hex").slice(0, 12);
@@ -13238,7 +13825,7 @@ function countTranscripts(root, tool) {
13238
13825
  walk(root, 0);
13239
13826
  return { sessions, newestMtime };
13240
13827
  }
13241
- function countOpencodeSessions(dbPath) {
13828
+ function countDbSessions(dbPath) {
13242
13829
  try {
13243
13830
  const st = statSync15(dbPath);
13244
13831
  return { sessions: 0, newestMtime: st.mtimeMs };
@@ -13256,18 +13843,24 @@ function discoverSessionSources() {
13256
13843
  ];
13257
13844
  for (const [tool, dirs] of dirTools) {
13258
13845
  for (const [i, path] of dirs.entries()) {
13259
- if (!existsSync17(path))
13846
+ if (!existsSync18(path))
13260
13847
  continue;
13261
13848
  const { sessions, newestMtime } = countTranscripts(path, tool);
13262
13849
  out.push({ id: sourceId(path), tool, path, sessions, newestMtime, isPrimary: i === 0 });
13263
13850
  }
13264
13851
  }
13265
13852
  for (const [i, path] of opencodeDbPaths({ includeExcluded: true }).entries()) {
13266
- if (!existsSync17(path))
13853
+ if (!existsSync18(path))
13267
13854
  continue;
13268
- const { sessions, newestMtime } = countOpencodeSessions(path);
13855
+ const { sessions, newestMtime } = countDbSessions(path);
13269
13856
  out.push({ id: sourceId(path), tool: "opencode", path, sessions, newestMtime, isPrimary: i === 0 });
13270
13857
  }
13858
+ for (const [i, path] of hermesDbPaths({ includeExcluded: true }).entries()) {
13859
+ if (!existsSync18(path))
13860
+ continue;
13861
+ const { sessions, newestMtime } = countDbSessions(path);
13862
+ out.push({ id: sourceId(path), tool: "hermes", path, sessions, newestMtime, isPrimary: i === 0 });
13863
+ }
13271
13864
  return out;
13272
13865
  }
13273
13866
  var excludedIds, SOURCE_ID_RE, MAX_DEPTH;
@@ -13292,7 +13885,7 @@ __export(home_discovery_exports, {
13292
13885
  sessionIdsInHome: () => sessionIdsInHome
13293
13886
  });
13294
13887
  import { execFileSync as execFileSync2 } from "child_process";
13295
- import { existsSync as existsSync18, readdirSync as readdirSync14, readFileSync as readFileSync12, realpathSync as realpathSync3, statSync as statSync16 } from "fs";
13888
+ import { existsSync as existsSync19, readdirSync as readdirSync14, readFileSync as readFileSync12, realpathSync as realpathSync3, statSync as statSync16 } from "fs";
13296
13889
  import { homedir as homedir5, platform as osPlatform } from "os";
13297
13890
  import { join as join19 } from "path";
13298
13891
  function candidateRoots(plat = osPlatform(), env = process.env) {
@@ -13313,10 +13906,10 @@ function candidateRoots(plat = osPlatform(), env = process.env) {
13313
13906
  add(env.XDG_CONFIG_HOME || join19(home, ".config"));
13314
13907
  add(env.XDG_DATA_HOME || join19(home, ".local", "share"));
13315
13908
  }
13316
- return roots.filter((r) => existsSync18(r));
13909
+ return roots.filter((r) => existsSync19(r));
13317
13910
  }
13318
13911
  function identifyHome(dir) {
13319
- const has = (...parts) => existsSync18(join19(dir, ...parts));
13912
+ const has = (...parts) => existsSync19(join19(dir, ...parts));
13320
13913
  if (has("projects") && dirHasTranscript(join19(dir, "projects"), (f) => f.endsWith(".jsonl"), 2))
13321
13914
  return "claude";
13322
13915
  if (has("sessions") && dirHasTranscript(join19(dir, "sessions"), (f) => f.startsWith("rollout-") && f.endsWith(".jsonl"), 4))
@@ -13327,6 +13920,8 @@ function identifyHome(dir) {
13327
13920
  return "cursor";
13328
13921
  if (has("opencode.db"))
13329
13922
  return "opencode";
13923
+ if (has("state.db") && has("SOUL.md"))
13924
+ return "hermes";
13330
13925
  return null;
13331
13926
  }
13332
13927
  function dirHasTranscript(root, match, depth) {
@@ -13354,7 +13949,8 @@ function countSessions(dir, tool) {
13354
13949
  codex: { sub: "sessions", depth: 4, match: (f) => f.startsWith("rollout-") && f.endsWith(".jsonl") },
13355
13950
  agy: { sub: "brain", depth: 4, match: (f) => f.endsWith(".jsonl") },
13356
13951
  cursor: { sub: "chats", depth: 3, match: (f) => f === "store.db" },
13357
- opencode: { sub: "", depth: 0, match: () => false }
13952
+ opencode: { sub: "", depth: 0, match: () => false },
13953
+ hermes: { sub: "", depth: 0, match: () => false }
13358
13954
  };
13359
13955
  const s = spec[tool];
13360
13956
  if (!s.sub)
@@ -13404,7 +14000,9 @@ function sessionIdsInHome(dir, tool) {
13404
14000
  // id is the DIRECTORY name
13405
14001
  cursor: { sub: "chats", depth: 2, idOf: () => null },
13406
14002
  // id is the DIRECTORY name
13407
- opencode: { sub: "", depth: 0, idOf: () => null }
14003
+ opencode: { sub: "", depth: 0, idOf: () => null },
14004
+ // rows, not files
14005
+ hermes: { sub: "", depth: 0, idOf: () => null }
13408
14006
  // rows, not files
13409
14007
  };
13410
14008
  const s = spec[tool];
@@ -13477,6 +14075,8 @@ function declaredHomes(env = process.env, plat = osPlatform()) {
13477
14075
  add(env.CHAT_RECALL_AGY_HOME);
13478
14076
  add(env.CHAT_RECALL_CURSOR_HOME);
13479
14077
  add(env.CHAT_RECALL_CURSOR_IDE_HOME);
14078
+ add(env.CHAT_RECALL_HERMES_HOME);
14079
+ add(env.HERMES_HOME);
13480
14080
  for (const d of (env.CLAUDE_DIRS || "").split(","))
13481
14081
  add(d);
13482
14082
  if (plat === "win32") {
@@ -13576,6 +14176,15 @@ function discoverHomes(opts = {}) {
13576
14176
  if (prior && !(via === "declared" && prior.via !== "declared"))
13577
14177
  return;
13578
14178
  found.set(real, { tool, path: real, via, sessions: countSessions(real, tool) });
14179
+ if (tool === "hermes") {
14180
+ try {
14181
+ for (const e of readdirSync14(join19(real, "profiles"), { withFileTypes: true })) {
14182
+ if (e.isDirectory())
14183
+ consider(join19(real, "profiles", e.name), via);
14184
+ }
14185
+ } catch {
14186
+ }
14187
+ }
13579
14188
  };
13580
14189
  for (const d of declaredHomes(env, plat))
13581
14190
  consider(d, "declared");
@@ -13655,7 +14264,7 @@ __export(detector_install_exports, {
13655
14264
  });
13656
14265
  import { spawnSync } from "child_process";
13657
14266
  import { createHash as createHash6 } from "crypto";
13658
- import { existsSync as existsSync19, mkdirSync as mkdirSync4, mkdtempSync, chmodSync as chmodSync2, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync3, readFileSync as readFileSync13, statSync as statSync17, copyFileSync as copyFileSync2 } from "fs";
14267
+ import { existsSync as existsSync20, mkdirSync as mkdirSync4, mkdtempSync, chmodSync as chmodSync2, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync3, readFileSync as readFileSync13, statSync as statSync17, copyFileSync as copyFileSync2 } from "fs";
13659
14268
  import { join as join20 } from "path";
13660
14269
  function managedDetectorPath(name) {
13661
14270
  return join20(getDetectorBinDir(), name + exeSuffix());
@@ -13698,7 +14307,7 @@ function resolveDetector(name) {
13698
14307
  const dir = configuredDir();
13699
14308
  if (dir) {
13700
14309
  const p2 = join20(dir, name + exeSuffix());
13701
- if (existsSync19(p2))
14310
+ if (existsSync20(p2))
13702
14311
  return { path: p2, version: "operator-supplied", source: "configured" };
13703
14312
  return { error: "not-installed" };
13704
14313
  }
@@ -13706,7 +14315,7 @@ function resolveDetector(name) {
13706
14315
  if (!spec.assets[platformKey()])
13707
14316
  return { error: "unsupported-platform" };
13708
14317
  const p = managedDetectorPath(name);
13709
- if (!existsSync19(p))
14318
+ if (!existsSync20(p))
13710
14319
  return { error: "not-installed" };
13711
14320
  const receipt = readReceipt(name);
13712
14321
  if (!receipt)
@@ -13726,7 +14335,7 @@ function downloadUrl(spec, asset) {
13726
14335
  }
13727
14336
  function extractMember(archive, member, destDir) {
13728
14337
  const r = spawnSync("tar", ["-xf", archive, "-C", destDir, member], { encoding: "utf-8", timeout: 12e4 });
13729
- if (r.status === 0 && existsSync19(join20(destDir, member)))
14338
+ if (r.status === 0 && existsSync20(join20(destDir, member)))
13730
14339
  return true;
13731
14340
  log4.error({ status: r.status, stderr: (r.stderr || "").slice(0, 400), archive, member }, "extract failed");
13732
14341
  return false;
@@ -13795,7 +14404,7 @@ function removeDetector(name) {
13795
14404
  let removed = false;
13796
14405
  for (const p of [managedDetectorPath(name), receiptPath(name)]) {
13797
14406
  try {
13798
- if (existsSync19(p)) {
14407
+ if (existsSync20(p)) {
13799
14408
  rmSync2(p, { force: true });
13800
14409
  removed = true;
13801
14410
  }
@@ -13821,7 +14430,7 @@ function detectorStatus() {
13821
14430
  }
13822
14431
  function ensureDetectorBinDir() {
13823
14432
  const d = getDetectorBinDir();
13824
- if (!existsSync19(d))
14433
+ if (!existsSync20(d))
13825
14434
  mkdirSync4(d, { recursive: true, mode: 493 });
13826
14435
  return d;
13827
14436
  }
@@ -13879,7 +14488,7 @@ __export(secret_scanner_exports, {
13879
14488
  scanTenantRules: () => scanTenantRules
13880
14489
  });
13881
14490
  import { spawnSync as spawnSync2 } from "child_process";
13882
- import { existsSync as existsSync20, unlinkSync, readFileSync as readFileSync14, mkdtempSync as mkdtempSync2, rmSync as rmSync3 } from "fs";
14491
+ import { existsSync as existsSync21, unlinkSync, readFileSync as readFileSync14, mkdtempSync as mkdtempSync2, rmSync as rmSync3 } from "fs";
13883
14492
  import { tmpdir } from "os";
13884
14493
  import { join as join21 } from "path";
13885
14494
  function mask(s) {
@@ -14048,7 +14657,7 @@ function runTrufflehogDir(binary, dir, opts = {}) {
14048
14657
  return out;
14049
14658
  }
14050
14659
  function scanDirForSecrets(dir, opts = {}) {
14051
- if (!existsSync20(dir))
14660
+ if (!existsSync21(dir))
14052
14661
  return [];
14053
14662
  const gitleaks = whichDetector("gitleaks");
14054
14663
  const trufflehog = whichDetector("trufflehog");
@@ -14085,7 +14694,7 @@ function scanTenantRules(text, tenantRules) {
14085
14694
  return out;
14086
14695
  }
14087
14696
  function scanFileForSecrets(filePath, opts = {}) {
14088
- if (!existsSync20(filePath))
14697
+ if (!existsSync21(filePath))
14089
14698
  return [];
14090
14699
  const gitleaks = whichDetector("gitleaks");
14091
14700
  const trufflehog = whichDetector("trufflehog");
@@ -14107,7 +14716,7 @@ function scanFileForSecrets(filePath, opts = {}) {
14107
14716
  }
14108
14717
  async function scanSessionForSecrets(opts) {
14109
14718
  const result = { sessionId: opts.sessionId, byDetector: {}, total: 0, ran: false };
14110
- if (!existsSync20(opts.filePath))
14719
+ if (!existsSync21(opts.filePath))
14111
14720
  return result;
14112
14721
  const gitleaks = whichDetector("gitleaks");
14113
14722
  const trufflehog = whichDetector("trufflehog");
@@ -14263,10 +14872,10 @@ var init_types = __esm({
14263
14872
 
14264
14873
  // ../engine/dist/transcript/opencode.js
14265
14874
  async function parseOpenCodeSubagents(parentSessionId) {
14266
- const { existsSync: existsSync58 } = await import("fs");
14875
+ const { existsSync: existsSync59 } = await import("fs");
14267
14876
  const { opencodeBackend: opencodeBackend2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
14268
14877
  const dbPath = opencodeBackend2.dbPath();
14269
- if (!existsSync58(dbPath))
14878
+ if (!existsSync59(dbPath))
14270
14879
  return [];
14271
14880
  const realParent = parentSessionId.replace("opencode_", "");
14272
14881
  const db = openSqliteReadonlyOrThrow(dbPath);
@@ -14307,10 +14916,10 @@ async function parseOpenCodeSubagents(parentSessionId) {
14307
14916
  }
14308
14917
  }
14309
14918
  async function parseOpenCodeTranscript(sessionId) {
14310
- const { existsSync: existsSync58 } = await import("fs");
14919
+ const { existsSync: existsSync59 } = await import("fs");
14311
14920
  const { opencodeBackend: opencodeBackend2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
14312
14921
  const dbPath = opencodeBackend2.dbPath();
14313
- if (!existsSync58(dbPath))
14922
+ if (!existsSync59(dbPath))
14314
14923
  return [];
14315
14924
  const realId = sessionId.replace("opencode_", "");
14316
14925
  const db = openSqliteReadonlyOrThrow(dbPath);
@@ -15068,7 +15677,7 @@ var init_source_policy = __esm({
15068
15677
  init_project_path_match();
15069
15678
  init_settings();
15070
15679
  cache2 = null;
15071
- TOOLS = ["claude", "opencode", "codex", "agy", "cursor"];
15680
+ TOOLS = ["claude", "opencode", "codex", "agy", "cursor", "hermes"];
15072
15681
  }
15073
15682
  });
15074
15683
 
@@ -15163,7 +15772,7 @@ var init_source_registry = __esm({
15163
15772
  });
15164
15773
 
15165
15774
  // ../engine/dist/core/utils.js
15166
- import { existsSync as existsSync21, readdirSync as readdirSync15 } from "fs";
15775
+ import { existsSync as existsSync22, readdirSync as readdirSync15 } from "fs";
15167
15776
  import { homedir as homedir6 } from "os";
15168
15777
  import { join as join22 } from "path";
15169
15778
  function splitByHeaders(markdown) {
@@ -15199,7 +15808,7 @@ function getClaudeDirs() {
15199
15808
  const dirs = [];
15200
15809
  const seen = /* @__PURE__ */ new Set();
15201
15810
  const add = (dir) => {
15202
- if (!seen.has(dir) && existsSync21(dir)) {
15811
+ if (!seen.has(dir) && existsSync22(dir)) {
15203
15812
  seen.add(dir);
15204
15813
  dirs.push(dir);
15205
15814
  }
@@ -15218,7 +15827,7 @@ function getClaudeDirs() {
15218
15827
  for (const entry of entries) {
15219
15828
  if (entry.isDirectory() && entry.name.startsWith(".claude-") && entry.name !== ".claude-code") {
15220
15829
  const candidate = join22(home, entry.name);
15221
- if (existsSync21(join22(candidate, "projects"))) {
15830
+ if (existsSync22(join22(candidate, "projects"))) {
15222
15831
  add(candidate);
15223
15832
  }
15224
15833
  }
@@ -15231,7 +15840,7 @@ function discoverSubdirs(subdir) {
15231
15840
  const result = [];
15232
15841
  for (const claudeDir of getClaudeDirs()) {
15233
15842
  const path = join22(claudeDir, subdir);
15234
- if (existsSync21(path)) {
15843
+ if (existsSync22(path)) {
15235
15844
  result.push(path);
15236
15845
  }
15237
15846
  }
@@ -15244,7 +15853,7 @@ var init_utils = __esm({
15244
15853
  });
15245
15854
 
15246
15855
  // ../engine/dist/parsers/plan-source.js
15247
- import { existsSync as existsSync22, readdirSync as readdirSync16, readFileSync as readFileSync15, statSync as statSync20 } from "fs";
15856
+ import { existsSync as existsSync23, readdirSync as readdirSync16, readFileSync as readFileSync15, statSync as statSync20 } from "fs";
15248
15857
  import { homedir as homedir7 } from "os";
15249
15858
  import { join as join23, basename as basename12 } from "path";
15250
15859
  function parseFrontmatter(content) {
@@ -15302,7 +15911,7 @@ var init_plan_source = __esm({
15302
15911
  return this._knownProjectDirs;
15303
15912
  const projectsDir = claudeBackend.projectsDir();
15304
15913
  this._knownProjectDirs = [];
15305
- if (!existsSync22(projectsDir))
15914
+ if (!existsSync23(projectsDir))
15306
15915
  return this._knownProjectDirs;
15307
15916
  try {
15308
15917
  const entries = readdirSync16(projectsDir, { withFileTypes: true });
@@ -15310,7 +15919,7 @@ var init_plan_source = __esm({
15310
15919
  if (!entry.isDirectory() || entry.name.startsWith("."))
15311
15920
  continue;
15312
15921
  const projectPath = resolveProjectDirName(entry.name);
15313
- if (existsSync22(projectPath)) {
15922
+ if (existsSync23(projectPath)) {
15314
15923
  this._knownProjectDirs.push(projectPath);
15315
15924
  }
15316
15925
  }
@@ -15334,7 +15943,7 @@ var init_plan_source = __esm({
15334
15943
  async *discoverClaudePlans() {
15335
15944
  const seenClaude = /* @__PURE__ */ new Set();
15336
15945
  for (const plansDir of this.plansDirs) {
15337
- if (!existsSync22(plansDir))
15946
+ if (!existsSync23(plansDir))
15338
15947
  continue;
15339
15948
  const files = readdirSync16(plansDir).filter((f) => f.endsWith(".md"));
15340
15949
  for (const file of files) {
@@ -15376,7 +15985,7 @@ var init_plan_source = __esm({
15376
15985
  }
15377
15986
  async *discoverOpenCodePlans() {
15378
15987
  const plansDir = opencodeBackend.plansDir();
15379
- if (!existsSync22(plansDir))
15988
+ if (!existsSync23(plansDir))
15380
15989
  return;
15381
15990
  let files;
15382
15991
  try {
@@ -15463,7 +16072,7 @@ var init_plan_source = __esm({
15463
16072
  return "";
15464
16073
  }
15465
16074
  async parse(item) {
15466
- if (!existsSync22(item.filePath))
16075
+ if (!existsSync23(item.filePath))
15467
16076
  return [];
15468
16077
  const content = readFileSync15(item.filePath, "utf-8");
15469
16078
  const chunks = [];
@@ -15504,7 +16113,7 @@ var init_plan_source = __esm({
15504
16113
  const projPath = join23(root, proj);
15505
16114
  for (const sessionEntry of safeReaddir(projPath)) {
15506
16115
  const subDir = join23(projPath, sessionEntry, "subagents");
15507
- if (!existsSync22(subDir))
16116
+ if (!existsSync23(subDir))
15508
16117
  continue;
15509
16118
  for (const f of safeReaddir(subDir)) {
15510
16119
  const m = f.match(/^agent-([a-z0-9_-]+)\.jsonl$/i);
@@ -15579,7 +16188,7 @@ var init_plan_source = __esm({
15579
16188
  }
15580
16189
  async *discoverAgyPlans() {
15581
16190
  const brainDir = join23(agyBackend.homeDir(), "brain");
15582
- if (!existsSync22(brainDir))
16191
+ if (!existsSync23(brainDir))
15583
16192
  return;
15584
16193
  let sessionDirs;
15585
16194
  try {
@@ -15634,7 +16243,7 @@ var init_plan_source = __esm({
15634
16243
  });
15635
16244
 
15636
16245
  // ../engine/dist/parsers/task-source.js
15637
- import { existsSync as existsSync23, readdirSync as readdirSync17, readFileSync as readFileSync16, statSync as statSync21 } from "fs";
16246
+ import { existsSync as existsSync24, readdirSync as readdirSync17, readFileSync as readFileSync16, statSync as statSync21 } from "fs";
15638
16247
  import { join as join24 } from "path";
15639
16248
  var MAX_CHUNK_CHARS2, TaskSource;
15640
16249
  var init_task_source = __esm({
@@ -15664,7 +16273,7 @@ var init_task_source = __esm({
15664
16273
  }
15665
16274
  async *discoverClaudeTodos() {
15666
16275
  const todosDir = claudeBackend.todosDir();
15667
- if (!existsSync23(todosDir))
16276
+ if (!existsSync24(todosDir))
15668
16277
  return;
15669
16278
  let files;
15670
16279
  try {
@@ -15715,7 +16324,7 @@ var init_task_source = __esm({
15715
16324
  }
15716
16325
  }
15717
16326
  async *discoverInDir(tasksDir) {
15718
- if (!existsSync23(tasksDir))
16327
+ if (!existsSync24(tasksDir))
15719
16328
  return;
15720
16329
  const entries = readdirSync17(tasksDir, { withFileTypes: true });
15721
16330
  for (const entry of entries) {
@@ -15757,7 +16366,7 @@ var init_task_source = __esm({
15757
16366
  return this.parseAgentTodo(item);
15758
16367
  }
15759
16368
  const taskDir = item.filePath;
15760
- if (!existsSync23(taskDir))
16369
+ if (!existsSync24(taskDir))
15761
16370
  return [];
15762
16371
  const taskFiles = readdirSync17(taskDir).filter((f) => f.endsWith(".json"));
15763
16372
  const tasks2 = this.readTasks(taskDir, taskFiles);
@@ -15818,7 +16427,7 @@ ${task.description}`.slice(0, MAX_CHUNK_CHARS2);
15818
16427
  }];
15819
16428
  }
15820
16429
  async parseAgentTodo(item) {
15821
- if (!existsSync23(item.filePath))
16430
+ if (!existsSync24(item.filePath))
15822
16431
  return [];
15823
16432
  let parsed;
15824
16433
  try {
@@ -15870,7 +16479,7 @@ ${task.description}`.slice(0, MAX_CHUNK_CHARS2);
15870
16479
  });
15871
16480
 
15872
16481
  // ../engine/dist/parsers/claude-md-source.js
15873
- import { existsSync as existsSync24, readFileSync as readFileSync17, readdirSync as readdirSync18, statSync as statSync22 } from "fs";
16482
+ import { existsSync as existsSync25, readFileSync as readFileSync17, readdirSync as readdirSync18, statSync as statSync22 } from "fs";
15874
16483
  import { homedir as homedir8 } from "os";
15875
16484
  import { join as join25, basename as basename13, dirname as dirname14 } from "path";
15876
16485
  import { createHash as createHash7 } from "crypto";
@@ -15878,13 +16487,13 @@ function discoverProjectDirs(claudeDir) {
15878
16487
  const dirs = [];
15879
16488
  const seen = /* @__PURE__ */ new Set();
15880
16489
  const addProject = (path) => {
15881
- if (!seen.has(path) && existsSync24(path)) {
16490
+ if (!seen.has(path) && existsSync25(path)) {
15882
16491
  seen.add(path);
15883
16492
  dirs.push(path);
15884
16493
  }
15885
16494
  };
15886
16495
  const scanProjectsDir = (projectsDir) => {
15887
- if (!existsSync24(projectsDir))
16496
+ if (!existsSync25(projectsDir))
15888
16497
  return;
15889
16498
  try {
15890
16499
  const entries = readdirSync18(projectsDir, { withFileTypes: true });
@@ -15912,7 +16521,7 @@ function discoverProjectDirs(claudeDir) {
15912
16521
  } catch {
15913
16522
  }
15914
16523
  const gemProjects = agyBackend.sharedProjectsJson();
15915
- if (existsSync24(gemProjects)) {
16524
+ if (existsSync25(gemProjects)) {
15916
16525
  try {
15917
16526
  const data = JSON.parse(readFileSync17(gemProjects, "utf-8"));
15918
16527
  for (const path of Object.keys(data.projects || {}))
@@ -15921,7 +16530,7 @@ function discoverProjectDirs(claudeDir) {
15921
16530
  }
15922
16531
  }
15923
16532
  const ocDb = opencodeBackend.dbPath();
15924
- if (existsSync24(ocDb)) {
16533
+ if (existsSync25(ocDb)) {
15925
16534
  try {
15926
16535
  const db = openSqliteReadonlyOrThrow(ocDb);
15927
16536
  try {
@@ -15972,7 +16581,7 @@ var init_claude_md_source = __esm({
15972
16581
  for (const note of NOTE_FILES) {
15973
16582
  const p = join25(homedir8(), note.filename);
15974
16583
  const key2 = seenKey(p, note.tool);
15975
- if (existsSync24(p) && !seen.has(key2)) {
16584
+ if (existsSync25(p) && !seen.has(key2)) {
15976
16585
  seen.add(key2);
15977
16586
  const item = this.fileToItem(p, note.filename, note.tool);
15978
16587
  if (item)
@@ -15984,7 +16593,7 @@ var init_claude_md_source = __esm({
15984
16593
  for (const note of NOTE_FILES) {
15985
16594
  const p = join25(projectDir, note.filename);
15986
16595
  const key2 = seenKey(p, note.tool);
15987
- if (existsSync24(p) && !seen.has(key2)) {
16596
+ if (existsSync25(p) && !seen.has(key2)) {
15988
16597
  seen.add(key2);
15989
16598
  const item = this.fileToItem(p, note.filename, note.tool);
15990
16599
  if (item)
@@ -15993,12 +16602,12 @@ var init_claude_md_source = __esm({
15993
16602
  }
15994
16603
  }
15995
16604
  for (const searchDir of this.searchDirs) {
15996
- if (!existsSync24(searchDir))
16605
+ if (!existsSync25(searchDir))
15997
16606
  continue;
15998
16607
  for (const note of NOTE_FILES) {
15999
16608
  const directPath = join25(searchDir, note.filename);
16000
16609
  const key2 = seenKey(directPath, note.tool);
16001
- if (existsSync24(directPath) && !seen.has(key2)) {
16610
+ if (existsSync25(directPath) && !seen.has(key2)) {
16002
16611
  seen.add(key2);
16003
16612
  const item = this.fileToItem(directPath, note.filename, note.tool);
16004
16613
  if (item)
@@ -16013,7 +16622,7 @@ var init_claude_md_source = __esm({
16013
16622
  for (const note of NOTE_FILES) {
16014
16623
  const p = join25(searchDir, entry.name, note.filename);
16015
16624
  const key2 = seenKey(p, note.tool);
16016
- if (existsSync24(p) && !seen.has(key2)) {
16625
+ if (existsSync25(p) && !seen.has(key2)) {
16017
16626
  seen.add(key2);
16018
16627
  const item = this.fileToItem(p, note.filename, note.tool);
16019
16628
  if (item)
@@ -16027,7 +16636,7 @@ var init_claude_md_source = __esm({
16027
16636
  }
16028
16637
  }
16029
16638
  async parse(item) {
16030
- if (!existsSync24(item.filePath))
16639
+ if (!existsSync25(item.filePath))
16031
16640
  return [];
16032
16641
  const content = readFileSync17(item.filePath, "utf-8");
16033
16642
  const chunks = [];
@@ -16096,7 +16705,7 @@ var init_claude_md_source = __esm({
16096
16705
  });
16097
16706
 
16098
16707
  // ../engine/dist/parsers/agent-memory-source.js
16099
- import { existsSync as existsSync25, readFileSync as readFileSync18, readdirSync as readdirSync19, statSync as statSync23 } from "fs";
16708
+ import { existsSync as existsSync26, readFileSync as readFileSync18, readdirSync as readdirSync19, statSync as statSync23 } from "fs";
16100
16709
  import { homedir as homedir9 } from "os";
16101
16710
  import { join as join26, basename as basename14 } from "path";
16102
16711
  function parseFrontmatter2(content) {
@@ -16130,7 +16739,7 @@ function discoverMemoryDirs() {
16130
16739
  const out = [];
16131
16740
  const seen = /* @__PURE__ */ new Set();
16132
16741
  const add = (memoryDir, projectHash) => {
16133
- if (!seen.has(memoryDir) && existsSync25(memoryDir)) {
16742
+ if (!seen.has(memoryDir) && existsSync26(memoryDir)) {
16134
16743
  seen.add(memoryDir);
16135
16744
  out.push({ memoryDir, projectHash });
16136
16745
  }
@@ -16146,7 +16755,7 @@ function discoverMemoryDirs() {
16146
16755
  } catch {
16147
16756
  }
16148
16757
  for (const projectsDir of claudeRoots) {
16149
- if (!existsSync25(projectsDir))
16758
+ if (!existsSync26(projectsDir))
16150
16759
  continue;
16151
16760
  try {
16152
16761
  for (const entry of readdirSync19(projectsDir, { withFileTypes: true })) {
@@ -16195,7 +16804,7 @@ var init_agent_memory_source = __esm({
16195
16804
  }
16196
16805
  }
16197
16806
  async parse(item) {
16198
- if (!existsSync25(item.filePath))
16807
+ if (!existsSync26(item.filePath))
16199
16808
  return [];
16200
16809
  const content = readFileSync18(item.filePath, "utf-8");
16201
16810
  const { body: body2 } = parseFrontmatter2(content);
@@ -16281,7 +16890,7 @@ var init_agent_memory_source = __esm({
16281
16890
  });
16282
16891
 
16283
16892
  // ../engine/dist/parsers/history-source.js
16284
- import { existsSync as existsSync26, readdirSync as readdirSync20, statSync as statSync24 } from "fs";
16893
+ import { existsSync as existsSync27, readdirSync as readdirSync20, statSync as statSync24 } from "fs";
16285
16894
  import { createReadStream as createReadStream3 } from "fs";
16286
16895
  import { createInterface as createInterface2 } from "readline";
16287
16896
  import { homedir as homedir10 } from "os";
@@ -16303,7 +16912,7 @@ var init_history_source = __esm({
16303
16912
  const home = homedir10();
16304
16913
  const paths = [];
16305
16914
  const tryAdd = (p) => {
16306
- if (existsSync26(p) && !paths.includes(p))
16915
+ if (existsSync27(p) && !paths.includes(p))
16307
16916
  paths.push(p);
16308
16917
  };
16309
16918
  tryAdd(claudeBackend.historyFile());
@@ -16321,7 +16930,7 @@ var init_history_source = __esm({
16321
16930
  async *discover() {
16322
16931
  if (!isSourceEnabled("claude", "history"))
16323
16932
  return;
16324
- const historyPath = this.historyPaths.find((p) => existsSync26(p));
16933
+ const historyPath = this.historyPaths.find((p) => existsSync27(p));
16325
16934
  if (!historyPath)
16326
16935
  return;
16327
16936
  const stat3 = statSync24(historyPath);
@@ -16417,7 +17026,7 @@ var init_history_source = __esm({
16417
17026
  async groupByProject() {
16418
17027
  const groups = /* @__PURE__ */ new Map();
16419
17028
  for (const historyPath of this.historyPaths) {
16420
- if (!existsSync26(historyPath))
17029
+ if (!existsSync27(historyPath))
16421
17030
  continue;
16422
17031
  const rl = createInterface2({
16423
17032
  input: createReadStream3(historyPath),
@@ -16465,7 +17074,7 @@ var init_history_source = __esm({
16465
17074
  });
16466
17075
 
16467
17076
  // ../engine/dist/parsers/paste-source.js
16468
- import { existsSync as existsSync27, readdirSync as readdirSync21, readFileSync as readFileSync19, statSync as statSync25 } from "fs";
17077
+ import { existsSync as existsSync28, readdirSync as readdirSync21, readFileSync as readFileSync19, statSync as statSync25 } from "fs";
16469
17078
  import { homedir as homedir11 } from "os";
16470
17079
  import { join as join28, basename as basename15 } from "path";
16471
17080
  var MAX_CHUNK_CHARS6, MAX_FILE_SIZE, PasteSource;
@@ -16486,7 +17095,7 @@ var init_paste_source = __esm({
16486
17095
  const home = homedir11();
16487
17096
  const dirs = [];
16488
17097
  const tryAdd = (p) => {
16489
- if (existsSync27(p) && !dirs.includes(p))
17098
+ if (existsSync28(p) && !dirs.includes(p))
16490
17099
  dirs.push(p);
16491
17100
  };
16492
17101
  tryAdd(claudeBackend.pasteCacheDir());
@@ -16506,7 +17115,7 @@ var init_paste_source = __esm({
16506
17115
  return;
16507
17116
  const seen = /* @__PURE__ */ new Set();
16508
17117
  for (const cacheDir of this.cacheDirs) {
16509
- if (!existsSync27(cacheDir))
17118
+ if (!existsSync28(cacheDir))
16510
17119
  continue;
16511
17120
  const files = readdirSync21(cacheDir).filter((f) => f.endsWith(".txt"));
16512
17121
  for (const file of files) {
@@ -16540,7 +17149,7 @@ var init_paste_source = __esm({
16540
17149
  }
16541
17150
  }
16542
17151
  async parse(item) {
16543
- if (!existsSync27(item.filePath))
17152
+ if (!existsSync28(item.filePath))
16544
17153
  return [];
16545
17154
  const content = readFileSync19(item.filePath, "utf-8");
16546
17155
  if (content.trim().length < 20)
@@ -16565,7 +17174,7 @@ var init_paste_source = __esm({
16565
17174
  });
16566
17175
 
16567
17176
  // ../engine/dist/parsers/antigravity-brain-source.js
16568
- import { existsSync as existsSync28, readdirSync as readdirSync22, readFileSync as readFileSync20, statSync as statSync26 } from "fs";
17177
+ import { existsSync as existsSync29, readdirSync as readdirSync22, readFileSync as readFileSync20, statSync as statSync26 } from "fs";
16569
17178
  import { join as join29 } from "path";
16570
17179
  var MAX_CHUNK_CHARS7, AntigravityBrainSource;
16571
17180
  var init_antigravity_brain_source = __esm({
@@ -16584,7 +17193,7 @@ var init_antigravity_brain_source = __esm({
16584
17193
  async *discover() {
16585
17194
  if (!isSourceEnabled("agy", "brain"))
16586
17195
  return;
16587
- if (!existsSync28(this.brainDir))
17196
+ if (!existsSync29(this.brainDir))
16588
17197
  return;
16589
17198
  const sessions = readdirSync22(this.brainDir, { withFileTypes: true });
16590
17199
  for (const entry of sessions) {
@@ -16603,7 +17212,7 @@ var init_antigravity_brain_source = __esm({
16603
17212
  const heading = content.split("\n").find((l) => l.startsWith("#"))?.replace(/^#+\s*/, "").trim() || file;
16604
17213
  const metaPath = `${filePath}.metadata.json`;
16605
17214
  let metaSummary = "";
16606
- if (existsSync28(metaPath)) {
17215
+ if (existsSync29(metaPath)) {
16607
17216
  try {
16608
17217
  const meta = JSON.parse(readFileSync20(metaPath, "utf-8"));
16609
17218
  metaSummary = meta.summary || "";
@@ -16634,7 +17243,7 @@ var init_antigravity_brain_source = __esm({
16634
17243
  }
16635
17244
  }
16636
17245
  async parse(item) {
16637
- if (!existsSync28(item.filePath))
17246
+ if (!existsSync29(item.filePath))
16638
17247
  return [];
16639
17248
  const content = readFileSync20(item.filePath, "utf-8");
16640
17249
  const chunks = [];
@@ -16677,7 +17286,7 @@ var init_antigravity_brain_source = __esm({
16677
17286
  });
16678
17287
 
16679
17288
  // ../engine/dist/parsers/opencode-source.js
16680
- import { existsSync as existsSync29 } from "fs";
17289
+ import { existsSync as existsSync30 } from "fs";
16681
17290
  var MAX_CHUNK_CHARS8, OpenCodeSource;
16682
17291
  var init_opencode_source = __esm({
16683
17292
  "../engine/dist/parsers/opencode-source.js"() {
@@ -16695,7 +17304,7 @@ var init_opencode_source = __esm({
16695
17304
  async *discover() {
16696
17305
  if (!isSourceEnabled("opencode", "sessions"))
16697
17306
  return;
16698
- if (!existsSync29(this.dbPath))
17307
+ if (!existsSync30(this.dbPath))
16699
17308
  return;
16700
17309
  let db;
16701
17310
  try {
@@ -16793,7 +17402,7 @@ var init_opencode_source = __esm({
16793
17402
  }
16794
17403
  }
16795
17404
  async parse(item) {
16796
- if (!existsSync29(this.dbPath))
17405
+ if (!existsSync30(this.dbPath))
16797
17406
  return [];
16798
17407
  const sessionId = item.id.replace("opencode_", "");
16799
17408
  const db = openSqliteReadonlyOrThrow(this.dbPath);
@@ -16865,7 +17474,7 @@ var init_opencode_source = __esm({
16865
17474
  });
16866
17475
 
16867
17476
  // ../engine/dist/parsers/opencode-todo-source.js
16868
- import { existsSync as existsSync30 } from "fs";
17477
+ import { existsSync as existsSync31 } from "fs";
16869
17478
  var OpenCodeTodoSource;
16870
17479
  var init_opencode_todo_source = __esm({
16871
17480
  "../engine/dist/parsers/opencode-todo-source.js"() {
@@ -16882,7 +17491,7 @@ var init_opencode_todo_source = __esm({
16882
17491
  async *discover() {
16883
17492
  if (!isSourceEnabled("opencode", "todos"))
16884
17493
  return;
16885
- if (!existsSync30(this.dbPath))
17494
+ if (!existsSync31(this.dbPath))
16886
17495
  return;
16887
17496
  let db;
16888
17497
  try {
@@ -16931,7 +17540,7 @@ var init_opencode_todo_source = __esm({
16931
17540
  }
16932
17541
  }
16933
17542
  async parse(item) {
16934
- if (!existsSync30(this.dbPath))
17543
+ if (!existsSync31(this.dbPath))
16935
17544
  return [];
16936
17545
  const sessionId = item.extra?.sessionId || item.id.replace(`${opencodeBackend.idPrefix}todo_`, "");
16937
17546
  const db = openSqliteReadonlyOrThrow(this.dbPath);
@@ -16979,7 +17588,7 @@ var init_opencode_todo_source = __esm({
16979
17588
  });
16980
17589
 
16981
17590
  // ../engine/dist/parsers/codex-session-source.js
16982
- import { existsSync as existsSync31, readdirSync as readdirSync23, readFileSync as readFileSync21, statSync as statSync27 } from "fs";
17591
+ import { existsSync as existsSync32, readdirSync as readdirSync23, readFileSync as readFileSync21, statSync as statSync27 } from "fs";
16983
17592
  import { join as join30, basename as basename16 } from "path";
16984
17593
  function getCodexSessionsDir() {
16985
17594
  return codexBackend.sessionsDir();
@@ -17036,7 +17645,7 @@ var init_codex_session_source = __esm({
17036
17645
  async *discover() {
17037
17646
  if (!isSourceEnabled("codex", "sessions"))
17038
17647
  return;
17039
- if (!existsSync31(this.sessionsDir))
17648
+ if (!existsSync32(this.sessionsDir))
17040
17649
  return;
17041
17650
  const jsonlFiles = this.collectJsonlFiles(this.sessionsDir);
17042
17651
  for (const filePath of jsonlFiles) {
@@ -17095,7 +17704,7 @@ var init_codex_session_source = __esm({
17095
17704
  }
17096
17705
  }
17097
17706
  async parse(item) {
17098
- if (!existsSync31(item.filePath))
17707
+ if (!existsSync32(item.filePath))
17099
17708
  return [];
17100
17709
  const events = readJsonlLines(item.filePath);
17101
17710
  const chunks = [];
@@ -17198,7 +17807,7 @@ var init_codex_session_source = __esm({
17198
17807
  }
17199
17808
  collectJsonlFiles(dir) {
17200
17809
  const results = [];
17201
- if (!existsSync31(dir))
17810
+ if (!existsSync32(dir))
17202
17811
  return results;
17203
17812
  for (const entry of readdirSync23(dir, { withFileTypes: true })) {
17204
17813
  const fullPath = join30(dir, entry.name);
@@ -17215,7 +17824,7 @@ var init_codex_session_source = __esm({
17215
17824
  });
17216
17825
 
17217
17826
  // ../engine/dist/parsers/diary-source.js
17218
- import { existsSync as existsSync32, mkdirSync as mkdirSync5, readdirSync as readdirSync24, readFileSync as readFileSync22, writeFileSync as writeFileSync4, statSync as statSync28 } from "fs";
17827
+ import { existsSync as existsSync33, mkdirSync as mkdirSync5, readdirSync as readdirSync24, readFileSync as readFileSync22, writeFileSync as writeFileSync4, statSync as statSync28 } from "fs";
17219
17828
  import { join as join31, basename as basename17 } from "path";
17220
17829
  var MAX_CHUNK_CHARS10, DiarySource;
17221
17830
  var init_diary_source = __esm({
@@ -17231,7 +17840,7 @@ var init_diary_source = __esm({
17231
17840
  }
17232
17841
  /** Discover all diary entries across all agents. */
17233
17842
  async *discover() {
17234
- if (!existsSync32(this.diaryDir))
17843
+ if (!existsSync33(this.diaryDir))
17235
17844
  return;
17236
17845
  const agents = readdirSync24(this.diaryDir, { withFileTypes: true }).filter((d) => d.isDirectory());
17237
17846
  for (const agentDir of agents) {
@@ -17266,7 +17875,7 @@ var init_diary_source = __esm({
17266
17875
  }
17267
17876
  }
17268
17877
  async parse(item) {
17269
- if (!existsSync32(item.filePath))
17878
+ if (!existsSync33(item.filePath))
17270
17879
  return [];
17271
17880
  const raw = readFileSync22(item.filePath, "utf-8");
17272
17881
  const entry = JSON.parse(raw);
@@ -17306,7 +17915,7 @@ var init_diary_source = __esm({
17306
17915
  static write(entry, diaryDir) {
17307
17916
  const dir = diaryDir || getDiaryDir();
17308
17917
  const agentDir = join31(dir, entry.agent.toLowerCase().replace(/[^a-z0-9_-]/g, "_"));
17309
- if (!existsSync32(agentDir)) {
17918
+ if (!existsSync33(agentDir)) {
17310
17919
  mkdirSync5(agentDir, { recursive: true });
17311
17920
  }
17312
17921
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -17325,7 +17934,7 @@ var init_diary_source = __esm({
17325
17934
  static read(agentName, lastN = 10, diaryDir) {
17326
17935
  const dir = diaryDir || getDiaryDir();
17327
17936
  const agentDir = join31(dir, agentName.toLowerCase().replace(/[^a-z0-9_-]/g, "_"));
17328
- if (!existsSync32(agentDir))
17937
+ if (!existsSync33(agentDir))
17329
17938
  return [];
17330
17939
  const files = readdirSync24(agentDir).filter((f) => f.endsWith(".json")).sort().reverse().slice(0, lastN);
17331
17940
  const entries = [];
@@ -17376,7 +17985,7 @@ var init_redact_inline = __esm({
17376
17985
  });
17377
17986
 
17378
17987
  // ../engine/dist/parsers/skills-source.js
17379
- import { existsSync as existsSync33, readdirSync as readdirSync25, readFileSync as readFileSync23, statSync as statSync29 } from "fs";
17988
+ import { existsSync as existsSync34, readdirSync as readdirSync25, readFileSync as readFileSync23, statSync as statSync29 } from "fs";
17380
17989
  import { homedir as homedir12 } from "os";
17381
17990
  import { join as join32 } from "path";
17382
17991
  function rebuildBody(content) {
@@ -17416,7 +18025,7 @@ function resolveRoots() {
17416
18025
  all.push({ path: join32(cursorHomeDir(), "skills"), tool: "cursor" });
17417
18026
  all.push({ path: join32(cursorHomeDir(), "skills-cursor"), tool: "cursor", readonly: true });
17418
18027
  }
17419
- return all.filter((r) => existsSync33(r.path));
18028
+ return all.filter((r) => existsSync34(r.path));
17420
18029
  }
17421
18030
  function parseFrontmatter3(text) {
17422
18031
  const m = text.match(/^---\n([\s\S]*?)\n---/);
@@ -17458,7 +18067,7 @@ var init_skills_source = __esm({
17458
18067
  return;
17459
18068
  for (const { name: pluginName, dir } of codexBackend.installedPluginDirs()) {
17460
18069
  const skillsDir = join32(dir, "skills");
17461
- if (!existsSync33(skillsDir))
18070
+ if (!existsSync34(skillsDir))
17462
18071
  continue;
17463
18072
  yield* this.fromSkillRoot({ path: skillsDir, tool: "codex" }, pluginName);
17464
18073
  }
@@ -17483,7 +18092,7 @@ var init_skills_source = __esm({
17483
18092
  if (!st.isDirectory())
17484
18093
  continue;
17485
18094
  const skillMd = join32(skillDir, "SKILL.md");
17486
- if (!existsSync33(skillMd))
18095
+ if (!existsSync34(skillMd))
17487
18096
  continue;
17488
18097
  let content;
17489
18098
  try {
@@ -17548,7 +18157,7 @@ var init_skills_source = __esm({
17548
18157
  }
17549
18158
  }
17550
18159
  async parse(item) {
17551
- if (!existsSync33(item.filePath))
18160
+ if (!existsSync34(item.filePath))
17552
18161
  return [];
17553
18162
  const content = readFileSync23(item.filePath, "utf-8");
17554
18163
  if (!content.trim())
@@ -17573,11 +18182,11 @@ var init_skills_source = __esm({
17573
18182
  });
17574
18183
 
17575
18184
  // ../engine/dist/parsers/mcps-source.js
17576
- import { existsSync as existsSync34, readFileSync as readFileSync24, statSync as statSync30 } from "fs";
18185
+ import { existsSync as existsSync35, readFileSync as readFileSync24, statSync as statSync30 } from "fs";
17577
18186
  import { homedir as homedir13 } from "os";
17578
18187
  import { join as join33 } from "path";
17579
18188
  function readJson(path) {
17580
- if (!existsSync34(path))
18189
+ if (!existsSync35(path))
17581
18190
  return null;
17582
18191
  try {
17583
18192
  const raw = readFileSync24(path, "utf-8");
@@ -17740,7 +18349,7 @@ var init_mcps_source = __esm({
17740
18349
  }
17741
18350
  }
17742
18351
  async *fromCodexToml(path) {
17743
- if (!existsSync34(path))
18352
+ if (!existsSync35(path))
17744
18353
  return;
17745
18354
  let content;
17746
18355
  try {
@@ -17913,7 +18522,7 @@ var init_toolkit_format = __esm({
17913
18522
  });
17914
18523
 
17915
18524
  // ../engine/dist/parsers/slash-commands-source.js
17916
- import { existsSync as existsSync35, readdirSync as readdirSync26, readFileSync as readFileSync25, statSync as statSync31 } from "fs";
18525
+ import { existsSync as existsSync36, readdirSync as readdirSync26, readFileSync as readFileSync25, statSync as statSync31 } from "fs";
17917
18526
  import { join as join34, basename as basename18 } from "path";
17918
18527
  function rebuildBody2(content) {
17919
18528
  const { text, redacted } = redactInlineSecrets(content);
@@ -17966,14 +18575,14 @@ var init_slash_commands_source = __esm({
17966
18575
  if (isSourceEnabled("claude", "commands")) {
17967
18576
  roots.push({ path: claudeBackend.commandsDir(), tool: "claude", scope: "user", projectPath: "", format: "md" });
17968
18577
  const projectsRoot = claudeBackend.projectsDir();
17969
- if (existsSync35(projectsRoot)) {
18578
+ if (existsSync36(projectsRoot)) {
17970
18579
  try {
17971
18580
  for (const entry of readdirSync26(projectsRoot, { withFileTypes: true })) {
17972
18581
  if (!entry.isDirectory() || entry.name.startsWith("."))
17973
18582
  continue;
17974
18583
  const projectPath = resolveProjectDirName(entry.name);
17975
18584
  const cmdDir = join34(projectPath, ".claude", "commands");
17976
- if (existsSync35(cmdDir))
18585
+ if (existsSync36(cmdDir))
17977
18586
  roots.push({ path: cmdDir, tool: "claude", scope: "project", projectPath, format: "md" });
17978
18587
  }
17979
18588
  } catch {
@@ -18035,7 +18644,7 @@ var init_slash_commands_source = __esm({
18035
18644
  }
18036
18645
  }
18037
18646
  async parse(item) {
18038
- if (!existsSync35(item.filePath))
18647
+ if (!existsSync36(item.filePath))
18039
18648
  return [];
18040
18649
  const content = readFileSync25(item.filePath, "utf-8");
18041
18650
  if (!content.trim())
@@ -18060,7 +18669,7 @@ var init_slash_commands_source = __esm({
18060
18669
  });
18061
18670
 
18062
18671
  // ../engine/dist/parsers/subagents-source.js
18063
- import { existsSync as existsSync36, readdirSync as readdirSync27, readFileSync as readFileSync26, statSync as statSync32 } from "fs";
18672
+ import { existsSync as existsSync37, readdirSync as readdirSync27, readFileSync as readFileSync26, statSync as statSync32 } from "fs";
18064
18673
  import { join as join35, basename as basename19 } from "path";
18065
18674
  function rebuildBody3(content) {
18066
18675
  const { text, redacted } = redactInlineSecrets(content);
@@ -18115,14 +18724,14 @@ var init_subagents_source = __esm({
18115
18724
  if (isSourceEnabled("claude", "agents")) {
18116
18725
  roots.push({ path: claudeBackend.agentsDir(), tool: "claude", scope: "user", projectPath: "", format: "md" });
18117
18726
  const projectsRoot = claudeBackend.projectsDir();
18118
- if (existsSync36(projectsRoot)) {
18727
+ if (existsSync37(projectsRoot)) {
18119
18728
  try {
18120
18729
  for (const entry of readdirSync27(projectsRoot, { withFileTypes: true })) {
18121
18730
  if (!entry.isDirectory() || entry.name.startsWith("."))
18122
18731
  continue;
18123
18732
  const projectPath = resolveProjectDirName(entry.name);
18124
18733
  const agentDir = join35(projectPath, ".claude", "agents");
18125
- if (existsSync36(agentDir))
18734
+ if (existsSync37(agentDir))
18126
18735
  roots.push({ path: agentDir, tool: "claude", scope: "project", projectPath, format: "md" });
18127
18736
  }
18128
18737
  } catch {
@@ -18185,7 +18794,7 @@ var init_subagents_source = __esm({
18185
18794
  }
18186
18795
  }
18187
18796
  async parse(item) {
18188
- if (!existsSync36(item.filePath))
18797
+ if (!existsSync37(item.filePath))
18189
18798
  return [];
18190
18799
  const content = readFileSync26(item.filePath, "utf-8");
18191
18800
  if (!content.trim())
@@ -18210,10 +18819,10 @@ var init_subagents_source = __esm({
18210
18819
  });
18211
18820
 
18212
18821
  // ../engine/dist/parsers/hooks-source.js
18213
- import { existsSync as existsSync37, readFileSync as readFileSync27, readdirSync as readdirSync28, statSync as statSync33 } from "fs";
18822
+ import { existsSync as existsSync38, readFileSync as readFileSync27, readdirSync as readdirSync28, statSync as statSync33 } from "fs";
18214
18823
  import { join as join36 } from "path";
18215
18824
  function readJson2(path) {
18216
- if (!existsSync37(path))
18825
+ if (!existsSync38(path))
18217
18826
  return null;
18218
18827
  try {
18219
18828
  return JSON.parse(readFileSync27(path, "utf-8"));
@@ -18235,14 +18844,14 @@ var init_hooks_source = __esm({
18235
18844
  return;
18236
18845
  yield* this.fromSettings(claudeBackend.settingsFile(), "user", "");
18237
18846
  const projectsRoot = claudeBackend.projectsDir();
18238
- if (existsSync37(projectsRoot)) {
18847
+ if (existsSync38(projectsRoot)) {
18239
18848
  try {
18240
18849
  for (const entry of readdirSync28(projectsRoot, { withFileTypes: true })) {
18241
18850
  if (!entry.isDirectory() || entry.name.startsWith("."))
18242
18851
  continue;
18243
18852
  const projectPath = resolveProjectDirName(entry.name);
18244
18853
  const settingsPath2 = join36(projectPath, ".claude", "settings.json");
18245
- if (existsSync37(settingsPath2)) {
18854
+ if (existsSync38(settingsPath2)) {
18246
18855
  yield* this.fromSettings(settingsPath2, "project", projectPath);
18247
18856
  }
18248
18857
  }
@@ -18333,11 +18942,11 @@ var init_hooks_source = __esm({
18333
18942
  });
18334
18943
 
18335
18944
  // ../engine/dist/parsers/plugins-source.js
18336
- import { existsSync as existsSync38, readFileSync as readFileSync28, readdirSync as readdirSync29, statSync as statSync34 } from "fs";
18945
+ import { existsSync as existsSync39, readFileSync as readFileSync28, readdirSync as readdirSync29, statSync as statSync34 } from "fs";
18337
18946
  import { homedir as homedir14 } from "os";
18338
18947
  import { join as join37 } from "path";
18339
18948
  function readJson3(path) {
18340
- if (!existsSync38(path))
18949
+ if (!existsSync39(path))
18341
18950
  return null;
18342
18951
  try {
18343
18952
  return JSON.parse(readFileSync28(path, "utf-8"));
@@ -18392,7 +19001,7 @@ var init_plugins_source = __esm({
18392
19001
  }
18393
19002
  }
18394
19003
  const gemRoot = agyBackend.sharedExtensionsDir();
18395
- if (isSourceEnabled("agy", "extensions") && existsSync38(gemRoot)) {
19004
+ if (isSourceEnabled("agy", "extensions") && existsSync39(gemRoot)) {
18396
19005
  let entries;
18397
19006
  try {
18398
19007
  entries = readdirSync29(gemRoot);
@@ -18568,6 +19177,8 @@ function toolOfId(id) {
18568
19177
  return "codex";
18569
19178
  if (id.startsWith("cursor_"))
18570
19179
  return "cursor";
19180
+ if (id.startsWith("hermes_"))
19181
+ return "hermes";
18571
19182
  return "claude";
18572
19183
  }
18573
19184
  function extractorVersionForTool(tool) {
@@ -18828,6 +19439,8 @@ function markSynced(server, rows) {
18828
19439
  else if (prev?.F !== void 0) base4.F = prev.F;
18829
19440
  if (cursorFromCaller && r.chunkedHead) base4.k = 1;
18830
19441
  else if (prev?.k && !completesChunked && !(cursorFromCaller && r.full)) base4.k = 1;
19442
+ const layout = cursorFromCaller ? r.layout : prev?.L;
19443
+ if (layout) base4.L = layout;
18831
19444
  forServer[r.id] = base4;
18832
19445
  }
18833
19446
  persist(data);
@@ -18968,12 +19581,12 @@ var init_sync_fields = __esm({
18968
19581
  });
18969
19582
 
18970
19583
  // ../engine/dist/core/index-lock.js
18971
- import { existsSync as existsSync39, mkdirSync as mkdirSync7, openSync as openSync8, writeFileSync as writeFileSync6, readFileSync as readFileSync30, rmSync as rmSync4, statSync as statSync36, closeSync as closeSync8 } from "fs";
19584
+ import { existsSync as existsSync40, mkdirSync as mkdirSync7, openSync as openSync8, writeFileSync as writeFileSync6, readFileSync as readFileSync30, rmSync as rmSync4, statSync as statSync36, closeSync as closeSync8 } from "fs";
18972
19585
  import { dirname as dirname16, join as join39 } from "path";
18973
19586
  function acquireIndexLock(opts) {
18974
19587
  const lockPath = lockFilePath(opts.file);
18975
19588
  mkdirSync7(dirname16(lockPath), { recursive: true });
18976
- if (existsSync39(lockPath)) {
19589
+ if (existsSync40(lockPath)) {
18977
19590
  if (!stealStaleLock(lockPath, opts.staleAfterMs ?? 10 * 6e4)) {
18978
19591
  return null;
18979
19592
  }
@@ -19090,7 +19703,7 @@ var init_http = __esm({
19090
19703
  CLI_VERSION = JSON.parse(readFileSync31(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
19091
19704
  } catch {
19092
19705
  try {
19093
- if (true) CLI_VERSION = "0.7.18";
19706
+ if (true) CLI_VERSION = "0.7.19";
19094
19707
  } catch {
19095
19708
  }
19096
19709
  }
@@ -19187,7 +19800,7 @@ var init_derived = __esm({
19187
19800
 
19188
19801
  // src/scan-pool.ts
19189
19802
  import { Worker } from "node:worker_threads";
19190
- import { existsSync as existsSync40 } from "node:fs";
19803
+ import { existsSync as existsSync41 } from "node:fs";
19191
19804
  import { fileURLToPath } from "node:url";
19192
19805
  import { cpus } from "node:os";
19193
19806
  function poolSize() {
@@ -19202,7 +19815,7 @@ function workerScript() {
19202
19815
  for (const rel of ["./scan-worker.js", "../dist/scan-worker.js"]) {
19203
19816
  try {
19204
19817
  const p = fileURLToPath(new URL(rel, import.meta.url));
19205
- if (existsSync40(p)) return p;
19818
+ if (existsSync41(p)) return p;
19206
19819
  } catch {
19207
19820
  }
19208
19821
  }
@@ -19588,7 +20201,7 @@ var init_telemetry = __esm({
19588
20201
  init_sync_client();
19589
20202
  cliVersion2 = () => {
19590
20203
  try {
19591
- return true ? "0.7.18" : "dev";
20204
+ return true ? "0.7.19" : "dev";
19592
20205
  } catch {
19593
20206
  return "dev";
19594
20207
  }
@@ -20121,7 +20734,7 @@ __export(auto_update_exports, {
20121
20734
  tarballUrl: () => tarballUrl
20122
20735
  });
20123
20736
  import { execSync as execSync2, execFileSync as execFileSync3 } from "node:child_process";
20124
- import { readFileSync as readFileSync35, existsSync as existsSync41 } from "node:fs";
20737
+ import { readFileSync as readFileSync35, existsSync as existsSync42 } from "node:fs";
20125
20738
  import { fileURLToPath as fileURLToPath2 } from "node:url";
20126
20739
  import { writeFileSync as writeFileSync8, mkdtempSync as mkdtempSync3, rmSync as rmSync6, readdirSync as readdirSync30, statSync as statSync38, mkdirSync as mkdirSync9 } from "node:fs";
20127
20740
  import { join as join42, dirname as dirname18, sep as sep2, posix as pathPosix, win32 as pathWin32 } from "node:path";
@@ -20282,7 +20895,7 @@ function resolveNpm(loc) {
20282
20895
  }
20283
20896
  function realInstall(tgz) {
20284
20897
  const prefix = runningPrefix();
20285
- const npm = resolveNpm({ execPath: process.execPath, prefix, platform: process.platform, env: process.env, exists: existsSync41 });
20898
+ const npm = resolveNpm({ execPath: process.execPath, prefix, platform: process.platform, env: process.env, exists: existsSync42 });
20286
20899
  if (prefix) {
20287
20900
  npmInstall(npm, ["install", "-g", "--prefix", prefix, tgz]);
20288
20901
  return;
@@ -21135,6 +21748,11 @@ async function syncToTargetInScope(cred, opts = {}) {
21135
21748
  };
21136
21749
  const localSessionIds = /* @__PURE__ */ new Set();
21137
21750
  const shippedHash = /* @__PURE__ */ new Map();
21751
+ const shippedLayout = /* @__PURE__ */ new Map();
21752
+ const layoutOf = (id) => {
21753
+ const layout = shippedLayout.get(id);
21754
+ return layout ? { layout } : {};
21755
+ };
21138
21756
  const makeBatcher = (field, maxItems, flatten = false, onFlush, barrier) => {
21139
21757
  let batch = [];
21140
21758
  let batchBytes = 0;
@@ -21167,7 +21785,7 @@ async function syncToTargetInScope(cred, opts = {}) {
21167
21785
  markSynced(base4, rows.map((r) => {
21168
21786
  const off = typeof r.from_offset === "number" ? r.from_offset : 0;
21169
21787
  const hash = shippedHash.get(r.session_id);
21170
- return off > 0 ? { id: r.session_id, mtime: r.mtime, offset: off, size: off, hash, acked: true, full: true } : { id: r.session_id, mtime: r.mtime, hash };
21788
+ return off > 0 ? { id: r.session_id, mtime: r.mtime, offset: off, size: off, hash, acked: true, full: true, ...layoutOf(r.session_id) } : { id: r.session_id, mtime: r.mtime, hash };
21171
21789
  }));
21172
21790
  } catch {
21173
21791
  }
@@ -21210,8 +21828,9 @@ async function syncToTargetInScope(cred, opts = {}) {
21210
21828
  // size = new offset (file grew to here)
21211
21829
  acked: true,
21212
21830
  // server merged this tail
21213
- chunkFinal: c.final === true
21831
+ chunkFinal: c.final === true,
21214
21832
  // completes a chunked full sync
21833
+ ...layoutOf(c.session_id)
21215
21834
  })));
21216
21835
  } catch {
21217
21836
  }
@@ -21243,9 +21862,9 @@ async function syncToTargetInScope(cred, opts = {}) {
21243
21862
  const mtime = conv.mtime;
21244
21863
  try {
21245
21864
  if (staged || !guarded) {
21246
- markSynced(base4, [{ id, mtime, offset: conv.from_offset, size: conv.from_offset, acked: true, chunkedHead: true }]);
21865
+ markSynced(base4, [{ id, mtime, offset: conv.from_offset, size: conv.from_offset, acked: true, chunkedHead: true, ...layoutOf(id) }]);
21247
21866
  } else if (typeof guarded.o === "number" && guarded.o > 0 && guarded.o <= fileSize) {
21248
- markSynced(base4, [{ id, mtime, offset: guarded.o, size: guarded.o, acked: true, chunkedHead: true }]);
21867
+ markSynced(base4, [{ id, mtime, offset: guarded.o, size: guarded.o, acked: true, chunkedHead: true, ...layoutOf(id) }]);
21249
21868
  } else {
21250
21869
  markSynced(base4, [{ id, mtime }]);
21251
21870
  }
@@ -21340,8 +21959,8 @@ async function syncToTargetInScope(cred, opts = {}) {
21340
21959
  if (!ledger) return ref.mtime >= (opts.sinceMs ?? 0) ? "full" : "skip";
21341
21960
  const ack = ledger.get(ref.prefixedId);
21342
21961
  const backend = backendFor(ref);
21343
- const split = !!backend?.spansMultipleSources?.(ref.prefixedId);
21344
- const isAO = !!backend?.isAppendOnly?.() && !split;
21962
+ const sameLayout = (ack?.L ?? "") === (backend?.sourceLayout?.(ref.prefixedId) ?? "");
21963
+ const isAO = !!backend?.isAppendOnly?.() && sameLayout;
21345
21964
  const size = isAO ? backend?.fileSize?.(ref.prefixedId) ?? 0 : 0;
21346
21965
  return syncMode(ack, ref.mtime, size, extractorVersionForTool(ref.toolId), isAO);
21347
21966
  };
@@ -21388,6 +22007,9 @@ async function syncToTargetInScope(cred, opts = {}) {
21388
22007
  skipped++;
21389
22008
  continue;
21390
22009
  }
22010
+ const layout = backendFor(ref)?.sourceLayout?.(ref.prefixedId) ?? "";
22011
+ if (layout) shippedLayout.set(ref.prefixedId, layout);
22012
+ else shippedLayout.delete(ref.prefixedId);
21391
22013
  trace?.(`build (${mode}) ${ref.prefixedId} \xB7 ${(sessionFileBytes(ref) / 1048576).toFixed(1)}MB`);
21392
22014
  let promotedForFindings = false;
21393
22015
  if (mode === "append" && ledger) {
@@ -21433,7 +22055,7 @@ async function syncToTargetInScope(cred, opts = {}) {
21433
22055
  const off = getBackendForId(ref.prefixedId)?.fileSize?.(ref.rawId) ?? 0;
21434
22056
  const priorCursor = ackRow?.s ?? 0;
21435
22057
  const safeToAck = off > 0 && off <= priorCursor;
21436
- markSynced(base4, [safeToAck ? { id: ref.prefixedId, mtime: ref.mtime, offset: off, size: off, hash: built.srcHash, acked: true } : { id: ref.prefixedId, mtime: ref.mtime, hash: built.srcHash }]);
22058
+ markSynced(base4, [safeToAck ? { id: ref.prefixedId, mtime: ref.mtime, offset: off, size: off, hash: built.srcHash, acked: true, ...layoutOf(ref.prefixedId) } : { id: ref.prefixedId, mtime: ref.mtime, hash: built.srcHash }]);
21437
22059
  } catch {
21438
22060
  }
21439
22061
  }
@@ -22188,7 +22810,7 @@ var init_sync_client = __esm({
22188
22810
  ownVersion = JSON.parse(readFileSync36(join43(dir, "../package.json"), "utf-8")).version || ownVersion;
22189
22811
  } catch {
22190
22812
  try {
22191
- if (true) ownVersion = "0.7.18";
22813
+ if (true) ownVersion = "0.7.19";
22192
22814
  } catch {
22193
22815
  }
22194
22816
  }
@@ -22339,7 +22961,7 @@ var init_update_notice = __esm({
22339
22961
  });
22340
22962
 
22341
22963
  // ../engine/dist/core/which.js
22342
- import { existsSync as existsSync42, statSync as statSync40, accessSync, constants } from "node:fs";
22964
+ import { existsSync as existsSync43, statSync as statSync40, accessSync, constants } from "node:fs";
22343
22965
  import { join as join45, isAbsolute as isAbsolute2, delimiter as delimiter2, sep as sep3 } from "node:path";
22344
22966
  function executable(p) {
22345
22967
  try {
@@ -22380,7 +23002,7 @@ function resolveOnPath(name) {
22380
23002
  continue;
22381
23003
  for (const c of candidates(name)) {
22382
23004
  const full = join45(dir, c);
22383
- if (existsSync42(full) && executable(full))
23005
+ if (existsSync43(full) && executable(full))
22384
23006
  return full;
22385
23007
  }
22386
23008
  }
@@ -22443,7 +23065,7 @@ __export(mcp_clients_exports, {
22443
23065
  inspectMcpClients: () => inspectMcpClients,
22444
23066
  registerMcpEverywhere: () => registerMcpEverywhere
22445
23067
  });
22446
- import { existsSync as existsSync44, mkdirSync as mkdirSync12, readFileSync as readFileSync38, writeFileSync as writeFileSync12 } from "fs";
23068
+ import { existsSync as existsSync45, mkdirSync as mkdirSync12, readFileSync as readFileSync38, writeFileSync as writeFileSync12 } from "fs";
22447
23069
  import { homedir as homedir16 } from "os";
22448
23070
  import { dirname as dirname21, join as join47 } from "path";
22449
23071
  function envHome(varName, fallback) {
@@ -22462,7 +23084,7 @@ function readJsonObject(path) {
22462
23084
  }
22463
23085
  function registerMcpServersJson(target, path, spec) {
22464
23086
  let cfg = {};
22465
- if (existsSync44(path)) {
23087
+ if (existsSync45(path)) {
22466
23088
  try {
22467
23089
  cfg = readJsonObject(path);
22468
23090
  } catch (err) {
@@ -22491,7 +23113,7 @@ function registerOpencodeJson(path, spec) {
22491
23113
  const id = "opencode";
22492
23114
  const label = "OpenCode";
22493
23115
  let cfg = {};
22494
- if (existsSync44(path)) {
23116
+ if (existsSync45(path)) {
22495
23117
  try {
22496
23118
  cfg = readJsonObject(path);
22497
23119
  } catch (err) {
@@ -22530,7 +23152,7 @@ function registerCodexToml(path, spec) {
22530
23152
  const id = "codex";
22531
23153
  const label = "Codex";
22532
23154
  const block = codexBlock(spec);
22533
- const prev = existsSync44(path) ? readFileSync38(path, "utf-8") : "";
23155
+ const prev = existsSync45(path) ? readFileSync38(path, "utf-8") : "";
22534
23156
  const lines = prev.length ? prev.split("\n") : [];
22535
23157
  const start = lines.findIndex((l) => l.trim() === CODEX_HEADER);
22536
23158
  if (start === -1) {
@@ -22571,7 +23193,7 @@ function inspectMcpClients(opts = {}) {
22571
23193
  return MCP_CLIENTS.map((c) => {
22572
23194
  const path = c.configPath(home);
22573
23195
  let registered = false;
22574
- if (existsSync44(path)) {
23196
+ if (existsSync45(path)) {
22575
23197
  try {
22576
23198
  const raw = readFileSync38(path, "utf-8");
22577
23199
  registered = c.id === "codex" ? raw.includes(CODEX_HEADER) : !!(JSON.parse(raw).mcpServers?.[MCP_SERVER_NAME] ?? JSON.parse(raw).mcp?.[MCP_SERVER_NAME]);
@@ -22608,7 +23230,7 @@ var init_mcp_clients = __esm({
22608
23230
  configPath: (home) => join47(envHome("CHAT_RECALL_CODEX_HOME", join47(home, ".codex")), "config.toml"),
22609
23231
  isPresent(home) {
22610
23232
  const p = this.configPath(home);
22611
- return existsSync44(p) || existsSync44(dirname21(p));
23233
+ return existsSync45(p) || existsSync45(dirname21(p));
22612
23234
  },
22613
23235
  register(spec, home) {
22614
23236
  return registerCodexToml(this.configPath(home), spec);
@@ -22621,7 +23243,7 @@ var init_mcp_clients = __esm({
22621
23243
  configPath: (home) => join47(envHome("XDG_CONFIG_HOME", join47(home, ".config")), "opencode", "opencode.json"),
22622
23244
  isPresent(home) {
22623
23245
  const p = this.configPath(home);
22624
- return existsSync44(p) || existsSync44(dirname21(p));
23246
+ return existsSync45(p) || existsSync45(dirname21(p));
22625
23247
  },
22626
23248
  register(spec, home) {
22627
23249
  return registerOpencodeJson(this.configPath(home), spec);
@@ -22635,7 +23257,7 @@ var init_mcp_clients = __esm({
22635
23257
  bin: "agy",
22636
23258
  configPath: (home) => join47(home, ".gemini", "config", "mcp_config.json"),
22637
23259
  isPresent(home) {
22638
- return existsSync44(join47(home, ".gemini", "antigravity-cli")) || existsSync44(this.configPath(home));
23260
+ return existsSync45(join47(home, ".gemini", "antigravity-cli")) || existsSync45(this.configPath(home));
22639
23261
  },
22640
23262
  register(spec, home) {
22641
23263
  return registerMcpServersJson(this, this.configPath(home), spec);
@@ -22648,7 +23270,7 @@ var init_mcp_clients = __esm({
22648
23270
  configPath: (home) => join47(home, ".cursor", "mcp.json"),
22649
23271
  isPresent(home) {
22650
23272
  const p = this.configPath(home);
22651
- return existsSync44(p) || existsSync44(dirname21(p));
23273
+ return existsSync45(p) || existsSync45(dirname21(p));
22652
23274
  },
22653
23275
  register(spec, home) {
22654
23276
  return registerMcpServersJson(this, this.configPath(home), spec);
@@ -22672,7 +23294,7 @@ __export(companions_exports, {
22672
23294
  uninstallCodeindex: () => uninstallCodeindex,
22673
23295
  unregisterCodeindexMcp: () => unregisterCodeindexMcp
22674
23296
  });
22675
- import { existsSync as existsSync45, mkdirSync as mkdirSync13, statSync as statSync41, chmodSync as chmodSync4, unlinkSync as unlinkSync3, renameSync as renameSync5, createWriteStream as createWriteStream2, readFileSync as readFileSync39, writeFileSync as writeFileSync13 } from "node:fs";
23297
+ import { existsSync as existsSync46, mkdirSync as mkdirSync13, statSync as statSync41, chmodSync as chmodSync4, unlinkSync as unlinkSync3, renameSync as renameSync5, createWriteStream as createWriteStream2, readFileSync as readFileSync39, writeFileSync as writeFileSync13 } from "node:fs";
22676
23298
  import { homedir as homedir17 } from "node:os";
22677
23299
  import { join as join48 } from "node:path";
22678
23300
  import { execSync as execSync3 } from "node:child_process";
@@ -22795,7 +23417,7 @@ Refusing to install. The release may have been re-uploaded, or something is serv
22795
23417
  renameSync5(tmpPath, CODEINDEX_BIN_PATH);
22796
23418
  } catch (err) {
22797
23419
  try {
22798
- if (existsSync45(tmpPath))
23420
+ if (existsSync46(tmpPath))
22799
23421
  unlinkSync3(tmpPath);
22800
23422
  } catch {
22801
23423
  }
@@ -22804,7 +23426,7 @@ Refusing to install. The release may have been re-uploaded, or something is serv
22804
23426
  return checkCodeindexStatus();
22805
23427
  }
22806
23428
  function uninstallCodeindex() {
22807
- if (existsSync45(CODEINDEX_BIN_PATH)) {
23429
+ if (existsSync46(CODEINDEX_BIN_PATH)) {
22808
23430
  unlinkSync3(CODEINDEX_BIN_PATH);
22809
23431
  return { removed: true, path: CODEINDEX_BIN_PATH };
22810
23432
  }
@@ -22812,7 +23434,7 @@ function uninstallCodeindex() {
22812
23434
  }
22813
23435
  function registerCodeindexMcp(mcpJsonPath, binaryPath) {
22814
23436
  let cfg = {};
22815
- if (existsSync45(mcpJsonPath)) {
23437
+ if (existsSync46(mcpJsonPath)) {
22816
23438
  try {
22817
23439
  cfg = JSON.parse(readFileSync39(mcpJsonPath, "utf-8"));
22818
23440
  } catch {
@@ -22827,7 +23449,7 @@ function registerCodeindexMcp(mcpJsonPath, binaryPath) {
22827
23449
  return { added: true };
22828
23450
  }
22829
23451
  function unregisterCodeindexMcp(mcpJsonPath) {
22830
- if (!existsSync45(mcpJsonPath))
23452
+ if (!existsSync46(mcpJsonPath))
22831
23453
  return { removed: false };
22832
23454
  let cfg;
22833
23455
  try {
@@ -22873,7 +23495,7 @@ __export(install_skills_exports, {
22873
23495
  skillsSourceDir: () => skillsSourceDir,
22874
23496
  uninstallSkills: () => uninstallSkills
22875
23497
  });
22876
- import { readdirSync as readdirSync32, existsSync as existsSync46, mkdirSync as mkdirSync14, rmSync as rmSync8, cpSync, writeFileSync as writeFileSync14, readFileSync as readFileSync40, statSync as statSync42, realpathSync as realpathSync4 } from "fs";
23498
+ import { readdirSync as readdirSync32, existsSync as existsSync47, mkdirSync as mkdirSync14, rmSync as rmSync8, cpSync, writeFileSync as writeFileSync14, readFileSync as readFileSync40, statSync as statSync42, realpathSync as realpathSync4 } from "fs";
22877
23499
  import { createHash as createHash11 } from "crypto";
22878
23500
  import { join as join49, basename as basename21 } from "path";
22879
23501
  function skillsSourceDir() {
@@ -22912,8 +23534,8 @@ function skillStamp() {
22912
23534
  }
22913
23535
  function bundledSkillNames() {
22914
23536
  const src = skillsSourceDir();
22915
- if (!existsSync46(src)) return [];
22916
- return readdirSync32(src, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync46(join49(src, e.name, "SKILL.md"))).map((e) => e.name).sort();
23537
+ if (!existsSync47(src)) return [];
23538
+ return readdirSync32(src, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync47(join49(src, e.name, "SKILL.md"))).map((e) => e.name).sort();
22917
23539
  }
22918
23540
  function realPathOrSelf(p) {
22919
23541
  try {
@@ -22937,7 +23559,7 @@ function claudeSkillTargets() {
22937
23559
  dir,
22938
23560
  // Judge each profile on ITS OWN transcripts, not the primary's: a home
22939
23561
  // with no projects/ is a directory someone made, not a Claude install.
22940
- available: existsSync46(join49(home, "projects"))
23562
+ available: existsSync47(join49(home, "projects"))
22941
23563
  });
22942
23564
  }
22943
23565
  if (targets.length === 0) {
@@ -22961,7 +23583,7 @@ function skillTargets() {
22961
23583
  ];
22962
23584
  }
22963
23585
  function isManaged(dir) {
22964
- return existsSync46(join49(dir, MARKER));
23586
+ return existsSync47(join49(dir, MARKER));
22965
23587
  }
22966
23588
  function installSkills(opts = {}) {
22967
23589
  const onlyAvailable = opts.onlyAvailable ?? true;
@@ -22979,7 +23601,7 @@ function installSkills(opts = {}) {
22979
23601
  }
22980
23602
  for (const name of names) {
22981
23603
  const dest = join49(t.dir, name);
22982
- if (existsSync46(dest) && !isManaged(dest)) {
23604
+ if (existsSync47(dest) && !isManaged(dest)) {
22983
23605
  skippedUserOwned.push(name);
22984
23606
  continue;
22985
23607
  }
@@ -22999,7 +23621,7 @@ function uninstallSkills() {
22999
23621
  const perTarget = [];
23000
23622
  for (const t of skillTargets()) {
23001
23623
  const removed = [];
23002
- if (!existsSync46(t.dir)) {
23624
+ if (!existsSync47(t.dir)) {
23003
23625
  perTarget.push({ id: t.id, label: t.label, removed });
23004
23626
  continue;
23005
23627
  }
@@ -23023,7 +23645,7 @@ function skillsNeedRefresh() {
23023
23645
  if (!t.available) continue;
23024
23646
  for (const name of names) {
23025
23647
  const marker = join49(t.dir, name, MARKER);
23026
- if (!existsSync46(marker)) return true;
23648
+ if (!existsSync47(marker)) return true;
23027
23649
  try {
23028
23650
  if (readFileSync40(marker, "utf-8").trim() !== cur) return true;
23029
23651
  } catch {
@@ -23041,7 +23663,7 @@ function skillStatus() {
23041
23663
  let ver = null;
23042
23664
  for (const name of names) {
23043
23665
  const marker = join49(t.dir, name, MARKER);
23044
- if (existsSync46(marker)) {
23666
+ if (existsSync47(marker)) {
23045
23667
  installed++;
23046
23668
  try {
23047
23669
  ver = readFileSync40(marker, "utf-8").trim();
@@ -23229,7 +23851,7 @@ __export(toolkit_sync_exports, {
23229
23851
  supportedTargetsFor: () => supportedTargetsFor,
23230
23852
  writeMcpEntry: () => writeMcpEntry
23231
23853
  });
23232
- import { existsSync as existsSync47, mkdirSync as mkdirSync15, readFileSync as readFileSync42, writeFileSync as writeFileSync15, cpSync as cpSync2 } from "fs";
23854
+ import { existsSync as existsSync48, mkdirSync as mkdirSync15, readFileSync as readFileSync42, writeFileSync as writeFileSync15, cpSync as cpSync2 } from "fs";
23233
23855
  import { dirname as dirname22, join as join51, basename as basename23 } from "path";
23234
23856
  import { homedir as homedir19 } from "os";
23235
23857
  function supportedTargetsFor(type) {
@@ -23284,11 +23906,11 @@ function skillsDirFor(tool) {
23284
23906
  }
23285
23907
  function copySkill(row, toTool) {
23286
23908
  const skillDir = row.extra.skillDir || dirname22(row.filePath);
23287
- if (!existsSync47(skillDir))
23909
+ if (!existsSync48(skillDir))
23288
23910
  return { ok: false, status: 404, error: `Source dir missing: ${skillDir}` };
23289
23911
  const name = row.extra.skillName || basename23(skillDir);
23290
23912
  const targetDir = join51(skillsDirFor(toTool), name);
23291
- if (existsSync47(targetDir))
23913
+ if (existsSync48(targetDir))
23292
23914
  return { ok: false, status: 409, error: `Already exists: ${targetDir}` };
23293
23915
  try {
23294
23916
  mkdirSync15(dirname22(targetDir), { recursive: true });
@@ -23320,7 +23942,7 @@ function readMcpEntry(tool, name) {
23320
23942
  return entry;
23321
23943
  }
23322
23944
  for (const t of tries) {
23323
- if (!existsSync47(t.path))
23945
+ if (!existsSync48(t.path))
23324
23946
  continue;
23325
23947
  try {
23326
23948
  const cfg = JSON.parse(readFileSync42(t.path, "utf-8"));
@@ -23333,7 +23955,7 @@ function readMcpEntry(tool, name) {
23333
23955
  return null;
23334
23956
  }
23335
23957
  function readCodexMcpEntry(path, target) {
23336
- if (!existsSync47(path))
23958
+ if (!existsSync48(path))
23337
23959
  return null;
23338
23960
  let content;
23339
23961
  try {
@@ -23387,7 +24009,7 @@ function readCodexMcpEntry(path, target) {
23387
24009
  }
23388
24010
  function writeCodexMcpEntry(path, name, entry) {
23389
24011
  let content = "";
23390
- if (existsSync47(path))
24012
+ if (existsSync48(path))
23391
24013
  content = readFileSync42(path, "utf-8");
23392
24014
  const header = `[mcp_servers.${name}]`;
23393
24015
  if (content.includes(header))
@@ -23445,7 +24067,7 @@ function writeMcpEntry(toTool, name, entry) {
23445
24067
  key2 = "mcp";
23446
24068
  }
23447
24069
  let cfg = {};
23448
- if (existsSync47(path)) {
24070
+ if (existsSync48(path)) {
23449
24071
  try {
23450
24072
  cfg = JSON.parse(readFileSync42(path, "utf-8"));
23451
24073
  } catch {
@@ -23509,7 +24131,7 @@ function copyCommandOrAgent(type, row, toTool) {
23509
24131
  return { ok: false, status: 404, error: `Source unreadable: ${e instanceof Error ? e.message : "failed"}` };
23510
24132
  }
23511
24133
  const out = emit(type, art, toTool);
23512
- if (existsSync47(out.path))
24134
+ if (existsSync48(out.path))
23513
24135
  return { ok: false, status: 409, error: `Already exists: ${out.path}` };
23514
24136
  try {
23515
24137
  mkdirSync15(dirname22(out.path), { recursive: true });
@@ -23529,7 +24151,7 @@ function copyInstructions(row, toTool) {
23529
24151
  return { ok: false, status: 404, error: `Source unreadable: ${e instanceof Error ? e.message : "failed"}` };
23530
24152
  }
23531
24153
  const out = emit("instructions", art, toTool, row.projectPath || void 0);
23532
- if (existsSync47(out.path))
24154
+ if (existsSync48(out.path))
23533
24155
  return { ok: false, status: 409, error: `Already exists: ${out.path}` };
23534
24156
  try {
23535
24157
  mkdirSync15(dirname22(out.path), { recursive: true });
@@ -23680,7 +24302,7 @@ __export(toolkit_pull_exports, {
23680
24302
  platformRefusal: () => platformRefusal,
23681
24303
  portableCommand: () => portableCommand
23682
24304
  });
23683
- import { existsSync as existsSync48, mkdirSync as mkdirSync16, statSync as statSync43, writeFileSync as writeFileSync16 } from "node:fs";
24305
+ import { existsSync as existsSync49, mkdirSync as mkdirSync16, statSync as statSync43, writeFileSync as writeFileSync16 } from "node:fs";
23684
24306
  import { dirname as dirname23, join as join52, basename as basename24, isAbsolute as isAbsolute3 } from "node:path";
23685
24307
  function isExecutableFile(p) {
23686
24308
  try {
@@ -23853,7 +24475,7 @@ function executePull(rows, opts = {}) {
23853
24475
  for (const tool of tools) {
23854
24476
  const dir = join52(skillsDirFor(tool), name);
23855
24477
  const file = join52(dir, "SKILL.md");
23856
- if (existsSync48(file)) {
24478
+ if (existsSync49(file)) {
23857
24479
  outcomes.push({ type: "skill", name, tool, status: "present" });
23858
24480
  continue;
23859
24481
  }
@@ -23898,7 +24520,7 @@ function executePull(rows, opts = {}) {
23898
24520
  outcomes.push({ type, name, tool, status: "failed", reason: e instanceof Error ? e.message : "emit failed" });
23899
24521
  continue;
23900
24522
  }
23901
- if (existsSync48(out.path)) {
24523
+ if (existsSync49(out.path)) {
23902
24524
  outcomes.push({ type, name, tool, status: "present" });
23903
24525
  continue;
23904
24526
  }
@@ -24061,7 +24683,7 @@ __export(board_hooks_exports, {
24061
24683
  removeBoardHooks: () => removeBoardHooks,
24062
24684
  resolveCliEntry: () => resolveCliEntry
24063
24685
  });
24064
- import { existsSync as existsSync49, mkdirSync as mkdirSync17, readFileSync as readFileSync43, writeFileSync as writeFileSync17 } from "node:fs";
24686
+ import { existsSync as existsSync50, mkdirSync as mkdirSync17, readFileSync as readFileSync43, writeFileSync as writeFileSync17 } from "node:fs";
24065
24687
  import { dirname as dirname24, join as join53 } from "node:path";
24066
24688
  function boardHookCommand(execPath, cliEntry, mode) {
24067
24689
  return `"${execPath}" "${cliEntry}" task-hook --${mode}`;
@@ -24101,7 +24723,7 @@ function removeBoardHooks(config2) {
24101
24723
  return removed;
24102
24724
  }
24103
24725
  function readConfig(file) {
24104
- if (!existsSync49(file)) return { hooks: {} };
24726
+ if (!existsSync50(file)) return { hooks: {} };
24105
24727
  try {
24106
24728
  const parsed = JSON.parse(readFileSync43(file, "utf-8"));
24107
24729
  if (!parsed.hooks || typeof parsed.hooks !== "object") parsed.hooks = {};
@@ -24113,7 +24735,7 @@ function readConfig(file) {
24113
24735
  function registerBoardHooks(files, execPath, cliEntry) {
24114
24736
  const written = [];
24115
24737
  for (const file of files) {
24116
- if (existsSync49(file)) {
24738
+ if (existsSync50(file)) {
24117
24739
  try {
24118
24740
  JSON.parse(readFileSync43(file, "utf-8"));
24119
24741
  } catch {
@@ -24133,7 +24755,7 @@ function registerBoardHooks(files, execPath, cliEntry) {
24133
24755
  }
24134
24756
  function resolveCliEntry(distDir) {
24135
24757
  const bundled = join53(distDir, "cli.js");
24136
- return existsSync49(bundled) ? bundled : join53(distDir, "cli.ts");
24758
+ return existsSync50(bundled) ? bundled : join53(distDir, "cli.ts");
24137
24759
  }
24138
24760
  var BOARD_HOOKS;
24139
24761
  var init_board_hooks = __esm({
@@ -24805,11 +25427,11 @@ var verify_repair_exports = {};
24805
25427
  __export(verify_repair_exports, {
24806
25428
  forceFullResync: () => forceFullResync
24807
25429
  });
24808
- import { copyFileSync as copyFileSync3, existsSync as existsSync50 } from "fs";
25430
+ import { copyFileSync as copyFileSync3, existsSync as existsSync51 } from "fs";
24809
25431
  function forceFullResync(server, sessionIds) {
24810
25432
  if (sessionIds.length === 0) return 0;
24811
25433
  const path = ledgerFilePath();
24812
- if (existsSync50(path)) {
25434
+ if (existsSync51(path)) {
24813
25435
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").replace(/-/g, "");
24814
25436
  try {
24815
25437
  copyFileSync3(path, `${path}.bak-${stamp}`);
@@ -25966,7 +26588,7 @@ __export(project_tasks_exports, {
25966
26588
  pushProjectTaskStatuses: () => pushProjectTaskStatuses,
25967
26589
  renderTeamTasksMd: () => renderTeamTasksMd
25968
26590
  });
25969
- import { existsSync as existsSync51, readFileSync as readFileSync45, writeFileSync as writeFileSync18, mkdirSync as mkdirSync18, statSync as statSync44 } from "node:fs";
26591
+ import { existsSync as existsSync52, readFileSync as readFileSync45, writeFileSync as writeFileSync18, mkdirSync as mkdirSync18, statSync as statSync44 } from "node:fs";
25970
26592
  import { join as join56, dirname as dirname25 } from "node:path";
25971
26593
  import { homedir as homedir21 } from "node:os";
25972
26594
  function isTeamTaskStatus(v) {
@@ -26052,7 +26674,7 @@ async function pushProjectTaskStatuses(base4, authHeaders, opts = {}) {
26052
26674
  }
26053
26675
  for (const project of projects) {
26054
26676
  const file = join56(project, fk.filename);
26055
- if (!existsSync51(file)) continue;
26677
+ if (!existsSync52(file)) continue;
26056
26678
  let mtime;
26057
26679
  try {
26058
26680
  mtime = statSync44(file).mtimeMs;
@@ -26134,7 +26756,7 @@ __export(intent_drain_exports, {
26134
26756
  drainSyncIntents: () => drainSyncIntents,
26135
26757
  instructionTargets: () => instructionTargets
26136
26758
  });
26137
- import { existsSync as existsSync52, readFileSync as readFileSync46, writeFileSync as writeFileSync19, appendFileSync, mkdirSync as mkdirSync19 } from "node:fs";
26759
+ import { existsSync as existsSync53, readFileSync as readFileSync46, writeFileSync as writeFileSync19, appendFileSync, mkdirSync as mkdirSync19 } from "node:fs";
26138
26760
  import { join as join57 } from "node:path";
26139
26761
  import { fileURLToPath as fileURLToPath4 } from "node:url";
26140
26762
  import { dirname as dirname26 } from "node:path";
@@ -26149,7 +26771,7 @@ function instructionTargets(rootPath, isGlobal) {
26149
26771
  const tools = installed.length ? installed : [INSTRUCTION_TOOLS[0]];
26150
26772
  if (isGlobal) return [...new Set(tools.map((t) => instructionsPath(t.id)))];
26151
26773
  if (!rootPath) return [];
26152
- const present = INSTRUCTION_FILENAMES.map((f) => join57(rootPath, f)).filter((p) => existsSync52(p));
26774
+ const present = INSTRUCTION_FILENAMES.map((f) => join57(rootPath, f)).filter((p) => existsSync53(p));
26153
26775
  if (present.length) return present;
26154
26776
  return [...new Set(tools.map((t) => join57(rootPath, instructionsFilename(t.id))))];
26155
26777
  }
@@ -26177,7 +26799,7 @@ function applyCodeRecommendation(intent) {
26177
26799
  mkdirSync19(dir, { recursive: true });
26178
26800
  } catch {
26179
26801
  }
26180
- const existing = existsSync52(file) ? readFileSync46(file, "utf8") : "";
26802
+ const existing = existsSync53(file) ? readFileSync46(file, "utf8") : "";
26181
26803
  if (existing.includes(text)) {
26182
26804
  skipped.push(file);
26183
26805
  continue;
@@ -26355,7 +26977,7 @@ var init_intent_drain = __esm({
26355
26977
  ownVersion2 = JSON.parse(readFileSync46(join57(dir, "../package.json"), "utf-8")).version || ownVersion2;
26356
26978
  } catch {
26357
26979
  try {
26358
- if (true) ownVersion2 = "0.7.18";
26980
+ if (true) ownVersion2 = "0.7.19";
26359
26981
  } catch {
26360
26982
  }
26361
26983
  }
@@ -26556,7 +27178,7 @@ var team_merge_exports = {};
26556
27178
  __export(team_merge_exports, {
26557
27179
  mergePullResult: () => mergePullResult
26558
27180
  });
26559
- import { existsSync as existsSync53, mkdirSync as mkdirSync20, readFileSync as readFileSync47, rmSync as rmSync9, writeFileSync as writeFileSync20, statSync as statSync45 } from "fs";
27181
+ import { existsSync as existsSync54, mkdirSync as mkdirSync20, readFileSync as readFileSync47, rmSync as rmSync9, writeFileSync as writeFileSync20, statSync as statSync45 } from "fs";
26560
27182
  import { dirname as dirname27, join as join58 } from "path";
26561
27183
  import { createHash as createHash14 } from "crypto";
26562
27184
  function installPathFor(art, tool) {
@@ -26673,7 +27295,7 @@ async function mergePullResult(opts) {
26673
27295
  if (!path)
26674
27296
  continue;
26675
27297
  try {
26676
- if (existsSync53(path))
27298
+ if (existsSync54(path))
26677
27299
  rmSync9(path, { force: true });
26678
27300
  await teamInstallsForget(id, row.tool);
26679
27301
  report.removed.push({ artifactId: id, path });
@@ -26691,7 +27313,7 @@ async function mergePullResult(opts) {
26691
27313
  }
26692
27314
  function readFileIfExists(path) {
26693
27315
  try {
26694
- if (!existsSync53(path))
27316
+ if (!existsSync54(path))
26695
27317
  return null;
26696
27318
  const s = statSync45(path);
26697
27319
  if (!s.isFile())
@@ -26777,7 +27399,7 @@ var init_vault_crypto = __esm({
26777
27399
  });
26778
27400
 
26779
27401
  // ../engine/dist/core/vault-source.js
26780
- import { existsSync as existsSync54, readdirSync as readdirSync33, statSync as statSync46 } from "fs";
27402
+ import { existsSync as existsSync55, readdirSync as readdirSync33, statSync as statSync46 } from "fs";
26781
27403
  import { extname, join as join59 } from "path";
26782
27404
  function* walkVaultSources() {
26783
27405
  const t = loadSettings().team.vault;
@@ -26803,7 +27425,7 @@ function safeStat(path) {
26803
27425
  }
26804
27426
  function* walkClaude() {
26805
27427
  const root = claudeBackend.projectsDir();
26806
- if (!existsSync54(root))
27428
+ if (!existsSync55(root))
26807
27429
  return;
26808
27430
  let projectDirs = [];
26809
27431
  try {
@@ -26856,7 +27478,7 @@ function* walkCursor() {
26856
27478
  }
26857
27479
  function* walkCodex() {
26858
27480
  const sessionsRoot = join59(codexBackend.homeDir(), "sessions");
26859
- if (!existsSync54(sessionsRoot))
27481
+ if (!existsSync55(sessionsRoot))
26860
27482
  return;
26861
27483
  const stack = [sessionsRoot];
26862
27484
  while (stack.length) {
@@ -26885,7 +27507,7 @@ function* walkCodex() {
26885
27507
  }
26886
27508
  function* walkAgy() {
26887
27509
  const brainDir = join59(agyBackend.homeDir(), "brain");
26888
- if (!existsSync54(brainDir))
27510
+ if (!existsSync55(brainDir))
26889
27511
  return;
26890
27512
  let sessionDirs = [];
26891
27513
  try {
@@ -26902,10 +27524,10 @@ function* walkAgy() {
26902
27524
  continue;
26903
27525
  }
26904
27526
  let filePath = join59(sessionPath, ".system_generated", "logs", "transcript.jsonl");
26905
- if (!existsSync54(filePath)) {
27527
+ if (!existsSync55(filePath)) {
26906
27528
  filePath = join59(sessionPath, ".system_generated", "logs", "transcript_full.jsonl");
26907
27529
  }
26908
- if (!existsSync54(filePath))
27530
+ if (!existsSync55(filePath))
26909
27531
  continue;
26910
27532
  const s = safeStat(filePath);
26911
27533
  if (!s)
@@ -26941,7 +27563,7 @@ __export(vault_client_exports, {
26941
27563
  vaultStatus: () => vaultStatus,
26942
27564
  vaultSync: () => vaultSync
26943
27565
  });
26944
- import { mkdirSync as mkdirSync21, readFileSync as readFileSync48, writeFileSync as writeFileSync21, existsSync as existsSync55 } from "fs";
27566
+ import { mkdirSync as mkdirSync21, readFileSync as readFileSync48, writeFileSync as writeFileSync21, existsSync as existsSync56 } from "fs";
26945
27567
  import { dirname as dirname28, join as join60 } from "path";
26946
27568
  function context2() {
26947
27569
  const s = loadSettings().team;
@@ -27154,7 +27776,7 @@ async function vaultRestore(passphrase, opts = {}) {
27154
27776
  }
27155
27777
  try {
27156
27778
  const path = importedPathFor(blob.tool, blob.sessionId);
27157
- if (existsSync55(path)) {
27779
+ if (existsSync56(path)) {
27158
27780
  const localStat = await import("fs").then((m) => m.statSync(path));
27159
27781
  if (Math.abs(localStat.mtimeMs - blob.mtimeSourceMs) < 1) {
27160
27782
  report.skipped.push({ sessionId: blob.sessionId, tool: blob.tool, reason: "unchanged" });
@@ -27784,7 +28406,7 @@ __export(service_installer_exports, {
27784
28406
  uninstallService: () => uninstallService
27785
28407
  });
27786
28408
  import { execSync as execSync5 } from "child_process";
27787
- import { existsSync as existsSync56, mkdirSync as mkdirSync22, readFileSync as readFileSync49, rmSync as rmSync10, writeFileSync as writeFileSync22 } from "fs";
28409
+ import { existsSync as existsSync57, mkdirSync as mkdirSync22, readFileSync as readFileSync49, rmSync as rmSync10, writeFileSync as writeFileSync22 } from "fs";
27788
28410
  import { homedir as homedir22 } from "os";
27789
28411
  import { join as join61 } from "path";
27790
28412
  import { fileURLToPath as fileURLToPath5 } from "url";
@@ -27820,7 +28442,7 @@ function refreshServiceDefinition() {
27820
28442
  try {
27821
28443
  const { watchJs, node, logFile } = resolveDaemonPaths();
27822
28444
  const unitPath = join61(homedir22(), ".config", "systemd", "user", "chat-recall-watch.service");
27823
- if (!existsSync56(unitPath)) return false;
28445
+ if (!existsSync57(unitPath)) return false;
27824
28446
  const current = readFileSync49(unitPath, "utf8");
27825
28447
  if (!current.includes(watchJs)) return false;
27826
28448
  const wanted = renderSystemdUnit(watchJs, node, logFile);
@@ -27988,7 +28610,7 @@ function uninstallService() {
27988
28610
  } catch {
27989
28611
  }
27990
28612
  const unitPath = join61(homedir22(), ".config", "systemd", "user", "chat-recall-watch.service");
27991
- if (existsSync56(unitPath)) {
28613
+ if (existsSync57(unitPath)) {
27992
28614
  rmSync10(unitPath, { force: true });
27993
28615
  removed = true;
27994
28616
  }
@@ -28005,7 +28627,7 @@ function uninstallService() {
28005
28627
  } catch {
28006
28628
  }
28007
28629
  let removed = false;
28008
- if (existsSync56(plistPath)) {
28630
+ if (existsSync57(plistPath)) {
28009
28631
  rmSync10(plistPath, { force: true });
28010
28632
  removed = true;
28011
28633
  }
@@ -28377,6 +28999,7 @@ var SESSION_ID_PREFIXES = {
28377
28999
  opencode: "opencode_",
28378
29000
  agy: "agy_",
28379
29001
  cursor: "cursor_",
29002
+ hermes: "hermes_",
28380
29003
  gemini: "gemini_"
28381
29004
  };
28382
29005
  function toolOfSessionId(sessionId) {
@@ -28405,6 +29028,8 @@ function resumeCommandFor(sessionId, tool) {
28405
29028
  // available answer for either.
28406
29029
  case "cursor":
28407
29030
  return `cursor-agent --resume ${raw("cursor_")}`;
29031
+ case "hermes":
29032
+ return `hermes --resume ${raw("hermes_")}`;
28408
29033
  // Gemini's `--resume` takes an index or "latest", never a session id.
28409
29034
  default:
28410
29035
  return null;
@@ -28528,7 +29153,7 @@ init_project_resolver();
28528
29153
  import { config } from "dotenv";
28529
29154
  import { Command } from "commander";
28530
29155
  import chalk2 from "chalk";
28531
- import { existsSync as existsSync57, readFileSync as readFileSync50, writeFileSync as writeFileSync23, realpathSync as realpathSync5 } from "fs";
29156
+ import { existsSync as existsSync58, readFileSync as readFileSync50, writeFileSync as writeFileSync23, realpathSync as realpathSync5 } from "fs";
28532
29157
  import { homedir as homedir23 } from "os";
28533
29158
  import { join as join62, resolve as resolve2, dirname as dirname29, basename as basename27 } from "path";
28534
29159
 
@@ -28637,7 +29262,7 @@ function renderEnvNeeds(needs, installedCount) {
28637
29262
 
28638
29263
  // src/star-nudge.ts
28639
29264
  init_paths();
28640
- import { existsSync as existsSync43, mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "node:fs";
29265
+ import { existsSync as existsSync44, mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "node:fs";
28641
29266
  import { dirname as dirname20, join as join46 } from "node:path";
28642
29267
  var REPO = "https://github.com/munhq/chat-recall";
28643
29268
  function markerPath() {
@@ -28653,7 +29278,7 @@ function shouldAskForStar(resultCount) {
28653
29278
  if (suppressed()) return false;
28654
29279
  if (!process.stderr.isTTY) return false;
28655
29280
  try {
28656
- return !existsSync43(markerPath());
29281
+ return !existsSync44(markerPath());
28657
29282
  } catch {
28658
29283
  return false;
28659
29284
  }
@@ -28694,7 +29319,7 @@ function claudeHookConfigFiles() {
28694
29319
  const out = [];
28695
29320
  const seen = /* @__PURE__ */ new Set();
28696
29321
  for (const home of claudeHomeDirs()) {
28697
- if (!existsSync57(join62(home, "projects"))) continue;
29322
+ if (!existsSync58(join62(home, "projects"))) continue;
28698
29323
  const file = join62(home, "hooks.json");
28699
29324
  let key2;
28700
29325
  try {
@@ -29479,7 +30104,7 @@ memory.command("wake-up").description("Generate wake-up context (high-importance
29479
30104
  try {
29480
30105
  const identityFile = getIdentityFilePath();
29481
30106
  let identity = "AI coding assistant";
29482
- if (existsSync57(identityFile)) {
30107
+ if (existsSync58(identityFile)) {
29483
30108
  identity = readFileSync50(identityFile, "utf-8").trim();
29484
30109
  }
29485
30110
  const lines = [
@@ -29580,7 +30205,7 @@ program.command("feedback").argument("<message...>", "what you want to say \u201
29580
30205
  }
29581
30206
  });
29582
30207
  program.command("doctor").description("Quick health check across login, server, credentials, hooks, MCP server, and codeindex").option("--purge-local", "Remove the legacy local index (cache.db + vector/KG files) left by pre-thin-collector versions \u2014 storage is server-side now. Requires login; keeps credentials, sync ledger, identity, and the audit log.").action(async (opts) => {
29583
- const { existsSync: existsSync58, readFileSync: readFileSync51, statSync: statSync47 } = await import("fs");
30208
+ const { existsSync: existsSync59, readFileSync: readFileSync51, statSync: statSync47 } = await import("fs");
29584
30209
  const { execSync: execSync6 } = await import("child_process");
29585
30210
  const rows = [];
29586
30211
  const note = (ok, label, detail) => rows.push({ ok, label, detail });
@@ -29644,7 +30269,7 @@ program.command("doctor").description("Quick health check across login, server,
29644
30269
  }
29645
30270
  }
29646
30271
  const credFile = join62(getDataDir(), "credentials.json");
29647
- if (!existsSync58(credFile)) {
30272
+ if (!existsSync59(credFile)) {
29648
30273
  note(false, "Credentials file", `missing \u2014 run \`chat-recall login <server-url>\``);
29649
30274
  } else {
29650
30275
  try {
@@ -29659,13 +30284,13 @@ program.command("doctor").description("Quick health check across login, server,
29659
30284
  for (const hooksJson of claudeHookConfigFiles()) {
29660
30285
  const profile = basename27(dirname29(hooksJson));
29661
30286
  const label = `Claude hooks (${profile})`;
29662
- if (!existsSync58(hooksJson)) {
30287
+ if (!existsSync59(hooksJson)) {
29663
30288
  note(false, label, `no ${hooksJson} \u2014 run \`chat-recall install-hooks\``);
29664
30289
  continue;
29665
30290
  }
29666
30291
  try {
29667
30292
  const readHooks = (file) => {
29668
- if (!existsSync58(file)) return {};
30293
+ if (!existsSync59(file)) return {};
29669
30294
  try {
29670
30295
  return JSON.parse(readFileSync51(file, "utf-8")).hooks || {};
29671
30296
  } catch {
@@ -29708,9 +30333,9 @@ program.command("doctor").description("Quick health check across login, server,
29708
30333
  try {
29709
30334
  const reg = await Promise.resolve().then(() => (init_guard_registration(), guard_registration_exports));
29710
30335
  for (const target2 of reg.guardTargets()) {
29711
- if (!existsSync58(target2.homeDir)) continue;
30336
+ if (!existsSync59(target2.homeDir)) continue;
29712
30337
  const label = `Decision guard \u2014 ${target2.label}`;
29713
- if (!existsSync58(target2.configPath)) {
30338
+ if (!existsSync59(target2.configPath)) {
29714
30339
  note(false, label, `not registered \u2014 run \`chat-recall install-hooks\``);
29715
30340
  continue;
29716
30341
  }
@@ -29735,7 +30360,7 @@ program.command("doctor").description("Quick health check across login, server,
29735
30360
  const want = bundledSkillNames2().length;
29736
30361
  for (const t of skillTargets2()) {
29737
30362
  if (!t.available) continue;
29738
- const have = want === 0 ? 0 : bundledSkillNames2().filter((n) => existsSync58(join62(t.dir, n, ".chat-recall-managed"))).length;
30363
+ const have = want === 0 ? 0 : bundledSkillNames2().filter((n) => existsSync59(join62(t.dir, n, ".chat-recall-managed"))).length;
29739
30364
  note(
29740
30365
  have === want,
29741
30366
  `Skills \u2014 ${t.label}`,
@@ -29817,7 +30442,7 @@ program.command("doctor").description("Quick health check across login, server,
29817
30442
  join62(dataDir, "index", "knowledge_graph.db"),
29818
30443
  join62(dataDir, "index", "metadata.db")
29819
30444
  ];
29820
- const legacyPresent = legacyPaths.filter((p) => existsSync58(p));
30445
+ const legacyPresent = legacyPaths.filter((p) => existsSync59(p));
29821
30446
  if (opts.purgeLocal) {
29822
30447
  if (!target) {
29823
30448
  note(false, "Purge legacy index", "refused \u2014 log in and `chat-recall sync` first, so your history is on the server before removing local copies");
@@ -30462,10 +31087,10 @@ tasks.command("pull [dir]").description("Write TEAM_TASKS.md for your tasks in t
30462
31087
  try {
30463
31088
  const { resolveProjectId: resolveProjectId2 } = await Promise.resolve().then(() => (init_project_resolver(), project_resolver_exports));
30464
31089
  const { renderTeamTasksMd: renderTeamTasksMd2, parseTeamTasksFile: parseTeamTasksFile2 } = await Promise.resolve().then(() => (init_project_tasks(), project_tasks_exports));
30465
- const { writeFileSync: writeFileSync24, readFileSync: readFileSync51, existsSync: existsSync58 } = await import("node:fs");
31090
+ const { writeFileSync: writeFileSync24, readFileSync: readFileSync51, existsSync: existsSync59 } = await import("node:fs");
30466
31091
  const { join: join63 } = await import("node:path");
30467
31092
  const file = join63(cwd, "TEAM_TASKS.md");
30468
- if (!opts.force && existsSync58(file) && parseTeamTasksFile2(readFileSync51(file, "utf8")).length > 0) {
31093
+ if (!opts.force && existsSync59(file) && parseTeamTasksFile2(readFileSync51(file, "utf8")).length > 0) {
30469
31094
  console.error(chalk2.red("TEAM_TASKS.md has unpushed edits."), "Run", chalk2.bold("chat-recall tasks push"), "first, or", chalk2.bold("tasks pull --force"), "to discard.");
30470
31095
  process.exit(1);
30471
31096
  }
@@ -30484,10 +31109,10 @@ tasks.command("push [dir]").description("Push TEAM_TASKS.md status edits back to
30484
31109
  const cwd = dir || process.cwd();
30485
31110
  try {
30486
31111
  const { parseTeamTasksFile: parseTeamTasksFile2 } = await Promise.resolve().then(() => (init_project_tasks(), project_tasks_exports));
30487
- const { readFileSync: readFileSync51, existsSync: existsSync58 } = await import("node:fs");
31112
+ const { readFileSync: readFileSync51, existsSync: existsSync59 } = await import("node:fs");
30488
31113
  const { join: join63 } = await import("node:path");
30489
31114
  const file = join63(cwd, "TEAM_TASKS.md");
30490
- if (!existsSync58(file)) {
31115
+ if (!existsSync59(file)) {
30491
31116
  console.error(chalk2.red("no TEAM_TASKS.md here \u2014"), "run", chalk2.bold("chat-recall tasks pull"), "first");
30492
31117
  process.exit(1);
30493
31118
  }
@@ -30974,7 +31599,7 @@ program.command("task-hook").description("Move a board card when a prompt or a c
30974
31599
  }
30975
31600
  });
30976
31601
  program.command("install-hooks").description("Install Claude Code hooks (Stop + PreCompact + UserPromptSubmit + SessionEnd + SessionStart) into every Claude profile").option("--uninstall", "Remove hooks instead of installing them").option("--no-resume-hint", "Don't install the UserPromptSubmit resume-hint hook").option("--no-escalate", "Don't install the SessionEnd learnings-escalation hook").option("--no-wakeup", "Don't install the SessionStart wake-up hook").option("--no-guard", "Don't install the PreToolUse decision guard").option("--no-task-board", "Don't install the hooks that claim and close board cards").action(async (opts) => {
30977
- const { mkdirSync: mkdirSync23, copyFileSync: copyFileSync4, chmodSync: chmodSync5, existsSync: existsSync58, readFileSync: readFileSync51, writeFileSync: writeFileSync24, statSync: statSync47, unlinkSync: unlinkSync4 } = await import("fs");
31602
+ const { mkdirSync: mkdirSync23, copyFileSync: copyFileSync4, chmodSync: chmodSync5, existsSync: existsSync59, readFileSync: readFileSync51, writeFileSync: writeFileSync24, statSync: statSync47, unlinkSync: unlinkSync4 } = await import("fs");
30978
31603
  const { fileURLToPath: fileURLToPath6 } = await import("url");
30979
31604
  const { boardHookEntries: boardHookEntries2, isBoardHookEntry: isBoardHookEntry2, registerBoardHooks: registerBoardHooks2, resolveCliEntry: resolveCliEntry2 } = await Promise.resolve().then(() => (init_board_hooks(), board_hooks_exports));
30980
31605
  const here = fileURLToPath6(new URL(".", import.meta.url));
@@ -30987,7 +31612,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
30987
31612
  // freshly added script is only at the root until that build runs.
30988
31613
  join62(here, "..", "..", "..", "hooks", name)
30989
31614
  ];
30990
- return candidates2.find((p) => existsSync58(p));
31615
+ return candidates2.find((p) => existsSync59(p));
30991
31616
  };
30992
31617
  const sourceSaveHook = findHook("chat_recall_save_hook.sh");
30993
31618
  const sourceGuardHook = findHook("chat_recall_guard_hook.sh");
@@ -31006,7 +31631,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
31006
31631
  const installedWakeupHook = join62(hooksDir, "chat_recall_wakeup_hook.sh");
31007
31632
  const wakeupWiredInSettings = (home) => {
31008
31633
  const settings2 = join62(home, "settings.json");
31009
- if (!existsSync58(settings2)) return false;
31634
+ if (!existsSync59(settings2)) return false;
31010
31635
  try {
31011
31636
  const cfg = JSON.parse(readFileSync51(settings2, "utf-8"));
31012
31637
  const arr = Array.isArray(cfg.hooks?.SessionStart) ? cfg.hooks.SessionStart : [];
@@ -31017,7 +31642,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
31017
31642
  };
31018
31643
  const hookConfigFiles = claudeHookConfigFiles();
31019
31644
  const readHookConfig = (file) => {
31020
- if (!existsSync58(file)) return { hooks: {} };
31645
+ if (!existsSync59(file)) return { hooks: {} };
31021
31646
  try {
31022
31647
  const c = JSON.parse(readFileSync51(file, "utf-8"));
31023
31648
  if (!c.hooks || typeof c.hooks !== "object") c.hooks = {};
@@ -31034,7 +31659,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
31034
31659
  };
31035
31660
  const HOOK_EVENTS = ["Stop", "PreCompact", "UserPromptSubmit", "SessionEnd", "SessionStart", "PreToolUse"];
31036
31661
  const readJson4 = (file) => {
31037
- if (!existsSync58(file)) return {};
31662
+ if (!existsSync59(file)) return {};
31038
31663
  try {
31039
31664
  return JSON.parse(readFileSync51(file, "utf-8"));
31040
31665
  } catch {
@@ -31045,7 +31670,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
31045
31670
  const reg = await Promise.resolve().then(() => (init_guard_registration(), guard_registration_exports));
31046
31671
  const notes = [];
31047
31672
  for (const target of reg.guardTargets()) {
31048
- if (!existsSync58(target.homeDir)) continue;
31673
+ if (!existsSync59(target.homeDir)) continue;
31049
31674
  const wrapper = join62(hooksDir, reg.guardWrapperName(target.tool));
31050
31675
  writeFileSync24(wrapper, reg.guardWrapperSource(target.tool, hookPath));
31051
31676
  chmodSync5(wrapper, 493);
@@ -31066,7 +31691,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
31066
31691
  const reg = await Promise.resolve().then(() => (init_guard_registration(), guard_registration_exports));
31067
31692
  let removed = 0;
31068
31693
  for (const target of reg.guardTargets()) {
31069
- if (!existsSync58(target.configPath)) continue;
31694
+ if (!existsSync59(target.configPath)) continue;
31070
31695
  if (target.tool === "opencode") {
31071
31696
  const body2 = readFileSync51(target.configPath, "utf-8");
31072
31697
  if (!body2.includes("chat-recall install-hooks")) continue;
@@ -31094,7 +31719,7 @@ program.command("install-hooks").description("Install Claude Code hooks (Stop +
31094
31719
  if (filtered.length) config2.hooks[event] = filtered;
31095
31720
  else delete config2.hooks[event];
31096
31721
  }
31097
- if (removed === 0 && !existsSync58(hooksJson)) continue;
31722
+ if (removed === 0 && !existsSync59(hooksJson)) continue;
31098
31723
  writeFileSync24(hooksJson, JSON.stringify(config2, null, 2) + "\n");
31099
31724
  total += removed;
31100
31725
  console.log(chalk2.green(`\u2713 Removed ${removed} chat-recall hook entr${removed === 1 ? "y" : "ies"} from ${hooksJson}`));
@@ -31669,8 +32294,8 @@ exclude.command("project <path>").description("Exclude a project path from synci
31669
32294
  saveSettings2(s);
31670
32295
  console.log(chalk2.green(`\u2713 Excluded ${abs}`) + chalk2.dim(" \u2014 takes effect next sync; already-synced rows stay until deleted."));
31671
32296
  });
31672
- exclude.command("tool <tool>").description("Exclude an AI tool entirely (claude | gemini | codex | opencode | agy | cursor)").action(async (tool) => {
31673
- const valid = ["claude", "gemini", "codex", "opencode", "agy", "cursor"];
32297
+ exclude.command("tool <tool>").description("Exclude an AI tool entirely (claude | gemini | codex | opencode | agy | cursor | hermes)").action(async (tool) => {
32298
+ const valid = ["claude", "gemini", "codex", "opencode", "agy", "cursor", "hermes"];
31674
32299
  if (!valid.includes(tool)) {
31675
32300
  console.error(chalk2.red(`Unknown tool '${tool}'.`) + chalk2.dim(` Use one of: ${valid.join(" | ")}`));
31676
32301
  process.exit(1);