@askexenow/exe-os 0.9.63 → 0.9.65

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.
@@ -3421,31 +3421,43 @@ var ROSTER_LOCK_PATH = path10.join(EXE_AI_DIR, "roster-merge.lock");
3421
3421
  var LOCK_STALE_MS = 3e4;
3422
3422
  var _pgPromise = null;
3423
3423
  var _pgFailed = false;
3424
+ function isTruthyEnv(value) {
3425
+ return /^(1|true|yes|on)$/i.test(value ?? "");
3426
+ }
3424
3427
  function loadPgClient() {
3425
3428
  if (_pgFailed) return null;
3426
- const postgresUrl = process.env.DATABASE_URL;
3427
3429
  const configPath = path10.join(EXE_AI_DIR, "config.json");
3428
3430
  let cloudPostgresUrl;
3431
+ let configEnabled = false;
3429
3432
  try {
3430
3433
  if (existsSync10(configPath)) {
3431
3434
  const cfg = JSON.parse(readFileSync7(configPath, "utf8"));
3432
3435
  cloudPostgresUrl = cfg.cloud?.postgresUrl;
3433
- if (cfg.cloud?.syncToPostgres === false) {
3434
- _pgFailed = true;
3435
- return null;
3436
- }
3436
+ configEnabled = cfg.cloud?.syncToPostgres === true;
3437
3437
  }
3438
3438
  } catch {
3439
3439
  }
3440
- const url = postgresUrl || cloudPostgresUrl;
3440
+ const envEnabled = isTruthyEnv(process.env.EXE_CLOUD_SYNC_TO_POSTGRES);
3441
+ if (!envEnabled && !configEnabled) {
3442
+ return null;
3443
+ }
3444
+ const url = process.env.DATABASE_URL || cloudPostgresUrl;
3441
3445
  if (!url) {
3442
3446
  _pgFailed = true;
3443
3447
  return null;
3444
3448
  }
3445
3449
  if (!_pgPromise) {
3446
3450
  _pgPromise = (async () => {
3451
+ if (!process.env.DATABASE_URL) process.env.DATABASE_URL = url;
3447
3452
  const { createRequire: createRequire3 } = await import("module");
3448
3453
  const { pathToFileURL: pathToFileURL3 } = await import("url");
3454
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
3455
+ if (explicitPath) {
3456
+ const mod2 = await import(pathToFileURL3(explicitPath).href);
3457
+ const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
3458
+ if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
3459
+ return new Ctor2();
3460
+ }
3449
3461
  const exeDbRoot = process.env.EXE_DB_ROOT ?? path10.join(homedir2(), "exe-db");
3450
3462
  const req = createRequire3(path10.join(exeDbRoot, "package.json"));
3451
3463
  const entry = req.resolve("@prisma/client");
@@ -3642,6 +3654,15 @@ async function cloudSync(config) {
3642
3654
  } catch {
3643
3655
  throw new Error("[cloud-sync] Database not initialized. Call initStore() before cloudSync().");
3644
3656
  }
3657
+ try {
3658
+ const relink = await client.execute("SELECT value FROM sync_meta WHERE key = 'cloud_relink_required' LIMIT 1");
3659
+ if (String(relink.rows[0]?.value ?? "") === "1") {
3660
+ throw new Error("[cloud-sync] Paused after key rotation. Re-link/reupload cloud sync with the new recovery phrase before syncing.");
3661
+ }
3662
+ } catch (err) {
3663
+ const msg = err instanceof Error ? err.message : String(err);
3664
+ if (msg.includes("Paused after key rotation")) throw err;
3665
+ }
3645
3666
  try {
3646
3667
  const { getRawClient: getRawClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3647
3668
  await getRawClient2().execute("PRAGMA wal_checkpoint(PASSIVE)");
@@ -313,15 +313,6 @@ var init_memory = __esm({
313
313
  });
314
314
 
315
315
  // src/lib/daemon-protocol.ts
316
- var daemon_protocol_exports = {};
317
- __export(daemon_protocol_exports, {
318
- deserializeArgs: () => deserializeArgs,
319
- deserializeResultSet: () => deserializeResultSet,
320
- deserializeValue: () => deserializeValue,
321
- serializeArgs: () => serializeArgs,
322
- serializeResultSet: () => serializeResultSet,
323
- serializeValue: () => serializeValue
324
- });
325
316
  function serializeValue(v) {
326
317
  if (v === null || v === void 0) return null;
327
318
  if (typeof v === "bigint") return Number(v);
@@ -346,9 +337,6 @@ function deserializeValue(v) {
346
337
  }
347
338
  return v;
348
339
  }
349
- function serializeArgs(args) {
350
- return args.map(serializeValue);
351
- }
352
340
  function deserializeArgs(args) {
353
341
  return args.map(deserializeValue);
354
342
  }
@@ -3510,10 +3498,12 @@ var init_memory_write_governor = __esm({
3510
3498
  // src/lib/projection-worker.ts
3511
3499
  var projection_worker_exports = {};
3512
3500
  __export(projection_worker_exports, {
3501
+ computeProjectionBackoffMs: () => computeProjectionBackoffMs,
3513
3502
  processProjectionBatch: () => processProjectionBatch,
3514
3503
  projectionHandlersForTests: () => projectionHandlersForTests,
3515
3504
  resetProjectionWorkerForTests: () => resetProjectionWorkerForTests,
3516
3505
  setProjectionWorkerPrismaClientForTests: () => setProjectionWorkerPrismaClientForTests,
3506
+ shouldStartProjectionWorker: () => shouldStartProjectionWorker,
3517
3507
  startProjectionWorker: () => startProjectionWorker,
3518
3508
  stopProjectionWorker: () => stopProjectionWorker
3519
3509
  });
@@ -3552,8 +3542,12 @@ function resetProjectionWorkerForTests() {
3552
3542
  clearTimeout(pollTimer);
3553
3543
  pollTimer = null;
3554
3544
  }
3545
+ consecutivePollErrors = 0;
3555
3546
  prismaPromise = null;
3556
3547
  }
3548
+ function isTruthyEnv(value) {
3549
+ return /^(1|true|yes|on)$/i.test(value ?? "");
3550
+ }
3557
3551
  async function processBatch() {
3558
3552
  const prisma = await loadPrisma();
3559
3553
  const events = await prisma.$queryRawUnsafe(
@@ -3595,36 +3589,65 @@ async function processBatch() {
3595
3589
  }
3596
3590
  return processed;
3597
3591
  }
3592
+ async function shouldStartProjectionWorker() {
3593
+ try {
3594
+ const config2 = await loadConfig();
3595
+ const envEnabled = isTruthyEnv(process.env.EXE_CLOUD_SYNC_TO_POSTGRES);
3596
+ if (!envEnabled && config2.cloud?.syncToPostgres !== true) {
3597
+ return { start: false, reason: "cloud.syncToPostgres is not enabled" };
3598
+ }
3599
+ } catch (err) {
3600
+ return { start: false, reason: `config unavailable: ${err instanceof Error ? err.message : String(err)}` };
3601
+ }
3602
+ if (!process.env.DATABASE_URL) {
3603
+ return { start: false, reason: "DATABASE_URL is not set" };
3604
+ }
3605
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path8.join(os5.homedir(), "exe-db");
3606
+ if (!existsSync8(path8.join(exeDbRoot, "package.json")) && !process.env.EXE_OS_PRISMA_CLIENT_PATH) {
3607
+ return { start: false, reason: "exe-db Prisma client not found" };
3608
+ }
3609
+ return { start: true };
3610
+ }
3611
+ function computeProjectionBackoffMs(errorCount) {
3612
+ if (errorCount <= 0) return POLL_INTERVAL_MS;
3613
+ return Math.min(POLL_INTERVAL_MS * 2 ** Math.min(errorCount, 5), MAX_POLL_BACKOFF_MS);
3614
+ }
3598
3615
  function startProjectionWorker() {
3599
3616
  if (running) return;
3600
- if (!process.env.DATABASE_URL) {
3601
- const exeDbRoot = process.env.EXE_DB_ROOT ?? path8.join(os5.homedir(), "exe-db");
3602
- if (!existsSync8(path8.join(exeDbRoot, "package.json"))) {
3603
- process.stderr.write("[projection-worker] Skipped \u2014 no exe-db found. Set DATABASE_URL or EXE_DB_ROOT to enable.\n");
3617
+ void (async () => {
3618
+ const decision = await shouldStartProjectionWorker();
3619
+ if (!decision.start) {
3620
+ process.stderr.write(`[projection-worker] Skipped \u2014 ${decision.reason}.
3621
+ `);
3604
3622
  return;
3605
3623
  }
3606
- }
3607
- running = true;
3608
- process.stderr.write("[projection-worker] Starting...\n");
3609
- const tick = async () => {
3610
- if (!running) return;
3611
- try {
3612
- const count = await processBatch();
3613
- if (count > 0) {
3614
- process.stderr.write(`[projection-worker] Processed ${count} events.
3624
+ running = true;
3625
+ consecutivePollErrors = 0;
3626
+ process.stderr.write("[projection-worker] Starting...\n");
3627
+ const tick = async () => {
3628
+ if (!running) return;
3629
+ let nextDelay = POLL_INTERVAL_MS;
3630
+ try {
3631
+ const count = await processBatch();
3632
+ consecutivePollErrors = 0;
3633
+ if (count > 0) {
3634
+ process.stderr.write(`[projection-worker] Processed ${count} events.
3615
3635
  `);
3616
- }
3617
- } catch (err) {
3618
- process.stderr.write(
3619
- `[projection-worker] Poll error: ${err instanceof Error ? err.message : String(err)}
3636
+ }
3637
+ } catch (err) {
3638
+ consecutivePollErrors++;
3639
+ nextDelay = computeProjectionBackoffMs(consecutivePollErrors);
3640
+ process.stderr.write(
3641
+ `[projection-worker] Poll error (${consecutivePollErrors}; next retry ${Math.round(nextDelay / 1e3)}s): ${err instanceof Error ? err.message : String(err)}
3620
3642
  `
3621
- );
3622
- }
3623
- if (running) {
3624
- pollTimer = setTimeout(tick, POLL_INTERVAL_MS);
3625
- }
3626
- };
3627
- void tick();
3643
+ );
3644
+ }
3645
+ if (running) {
3646
+ pollTimer = setTimeout(tick, nextDelay);
3647
+ }
3648
+ };
3649
+ void tick();
3650
+ })();
3628
3651
  }
3629
3652
  function stopProjectionWorker() {
3630
3653
  running = false;
@@ -3637,10 +3660,11 @@ function stopProjectionWorker() {
3637
3660
  async function processProjectionBatch() {
3638
3661
  return processBatch();
3639
3662
  }
3640
- var prismaPromise, projectionHandlers, projectionHandlersForTests, defaultHandler, BATCH_SIZE, POLL_INTERVAL_MS, running, pollTimer;
3663
+ var prismaPromise, projectionHandlers, projectionHandlersForTests, defaultHandler, BATCH_SIZE, POLL_INTERVAL_MS, MAX_POLL_BACKOFF_MS, running, pollTimer, consecutivePollErrors;
3641
3664
  var init_projection_worker = __esm({
3642
3665
  "src/lib/projection-worker.ts"() {
3643
3666
  "use strict";
3667
+ init_config();
3644
3668
  prismaPromise = null;
3645
3669
  projectionHandlers = {
3646
3670
  async whatsapp(event, prisma) {
@@ -3751,8 +3775,10 @@ var init_projection_worker = __esm({
3751
3775
  };
3752
3776
  BATCH_SIZE = 50;
3753
3777
  POLL_INTERVAL_MS = 1e4;
3778
+ MAX_POLL_BACKOFF_MS = 5 * 6e4;
3754
3779
  running = false;
3755
3780
  pollTimer = null;
3781
+ consecutivePollErrors = 0;
3756
3782
  }
3757
3783
  });
3758
3784
 
@@ -21003,218 +21029,6 @@ var init_conflict_detector = __esm({
21003
21029
  }
21004
21030
  });
21005
21031
 
21006
- // src/adapters/runtime-hook-manifest.ts
21007
- function manifestEntryForCommand(command) {
21008
- return EXE_HOOK_MANIFEST.find((entry) => command.includes(entry.commandMarker));
21009
- }
21010
- var EXE_HOOKS, EXE_HOOK_MANIFEST, LEGACY_SPLIT_POST_TOOL_HOOK_MARKERS;
21011
- var init_runtime_hook_manifest = __esm({
21012
- "src/adapters/runtime-hook-manifest.ts"() {
21013
- "use strict";
21014
- EXE_HOOKS = {
21015
- postToolCombined: "dist/hooks/post-tool-combined.js",
21016
- sessionStart: "dist/hooks/session-start.js",
21017
- promptSubmit: "dist/hooks/prompt-submit.js",
21018
- heartbeat: "dist/hooks/exe-heartbeat-hook.js",
21019
- stop: "dist/hooks/stop.js",
21020
- preToolUse: "dist/hooks/pre-tool-use.js",
21021
- subagentStop: "dist/hooks/subagent-stop.js",
21022
- preCompact: "dist/hooks/pre-compact.js",
21023
- postCompact: "dist/hooks/post-compact.js",
21024
- sessionEnd: "dist/hooks/session-end.js",
21025
- notification: "dist/hooks/notification.js",
21026
- instructionsLoaded: "dist/hooks/instructions-loaded.js"
21027
- };
21028
- EXE_HOOK_MANIFEST = [
21029
- {
21030
- key: "postToolCombined",
21031
- event: "PostToolUse",
21032
- commandMarker: EXE_HOOKS.postToolCombined,
21033
- owner: "exe-os",
21034
- purpose: "Single PostToolUse entrypoint for ingestion, error recall, summaries, and bug detection.",
21035
- runtimes: ["claude", "codex", "opencode"],
21036
- checkpointRole: "none"
21037
- },
21038
- {
21039
- key: "sessionStart",
21040
- event: "SessionStart",
21041
- commandMarker: EXE_HOOKS.sessionStart,
21042
- owner: "exe-os",
21043
- purpose: "Loads agent identity, procedures, and boot context.",
21044
- runtimes: ["claude", "codex", "opencode"],
21045
- checkpointRole: "none"
21046
- },
21047
- {
21048
- key: "promptSubmit",
21049
- event: "UserPromptSubmit",
21050
- commandMarker: EXE_HOOKS.promptSubmit,
21051
- owner: "exe-os",
21052
- purpose: "Injects current tasks, pending reviews, and local context before each prompt.",
21053
- runtimes: ["claude", "codex", "opencode"],
21054
- checkpointRole: "none"
21055
- },
21056
- {
21057
- key: "heartbeat",
21058
- event: "UserPromptSubmit",
21059
- commandMarker: EXE_HOOKS.heartbeat,
21060
- owner: "exe-os",
21061
- purpose: "Lightweight heartbeat/status sidecar for Claude Code sessions.",
21062
- runtimes: ["claude"],
21063
- checkpointRole: "none"
21064
- },
21065
- {
21066
- key: "stop",
21067
- event: "Stop",
21068
- commandMarker: EXE_HOOKS.stop,
21069
- owner: "exe-os",
21070
- purpose: "Finalizes task state, capacity signals, and emergency checkpointing.",
21071
- runtimes: ["claude", "codex", "opencode"],
21072
- checkpointRole: "capacity_checkpoint"
21073
- },
21074
- {
21075
- key: "preToolUse",
21076
- event: "PreToolUse",
21077
- commandMarker: EXE_HOOKS.preToolUse,
21078
- owner: "exe-os",
21079
- purpose: "Preflight guardrails before shell/tool execution.",
21080
- runtimes: ["claude", "codex", "opencode"],
21081
- checkpointRole: "none"
21082
- },
21083
- {
21084
- key: "subagentStop",
21085
- event: "SubagentStop",
21086
- commandMarker: EXE_HOOKS.subagentStop,
21087
- owner: "exe-os",
21088
- purpose: "Captures subagent completion context.",
21089
- runtimes: ["claude"],
21090
- checkpointRole: "none"
21091
- },
21092
- {
21093
- key: "preCompact",
21094
- event: "PreCompact",
21095
- commandMarker: EXE_HOOKS.preCompact,
21096
- owner: "exe-os",
21097
- purpose: "Writes active-task snapshot and compaction recovery context.",
21098
- runtimes: ["claude"],
21099
- checkpointRole: "recovery_context"
21100
- },
21101
- {
21102
- key: "postCompact",
21103
- event: "PostCompact",
21104
- commandMarker: EXE_HOOKS.postCompact,
21105
- owner: "exe-os",
21106
- purpose: "Rehydrates recovery context after compaction.",
21107
- runtimes: ["claude"],
21108
- checkpointRole: "recovery_context"
21109
- },
21110
- {
21111
- key: "sessionEnd",
21112
- event: "SessionEnd",
21113
- commandMarker: EXE_HOOKS.sessionEnd,
21114
- owner: "exe-os",
21115
- purpose: "Stores session-end checkpoint and triages orphaned in-progress tasks.",
21116
- runtimes: ["claude"],
21117
- checkpointRole: "session_summary"
21118
- },
21119
- {
21120
- key: "notification",
21121
- event: "Notification",
21122
- commandMarker: EXE_HOOKS.notification,
21123
- owner: "exe-os",
21124
- purpose: "Captures runtime notifications and nudges.",
21125
- runtimes: ["claude"],
21126
- checkpointRole: "none"
21127
- },
21128
- {
21129
- key: "instructionsLoaded",
21130
- event: "InstructionsLoaded",
21131
- commandMarker: EXE_HOOKS.instructionsLoaded,
21132
- owner: "exe-os",
21133
- purpose: "Applies runtime instruction post-processing.",
21134
- runtimes: ["claude"],
21135
- checkpointRole: "none"
21136
- }
21137
- ];
21138
- LEGACY_SPLIT_POST_TOOL_HOOK_MARKERS = [
21139
- "dist/hooks/ingest.js",
21140
- "dist/hooks/error-recall.js",
21141
- "dist/hooks/ingest-worker.js"
21142
- ];
21143
- }
21144
- });
21145
-
21146
- // src/bin/fast-db-init.ts
21147
- var fast_db_init_exports = {};
21148
- __export(fast_db_init_exports, {
21149
- fastDbInit: () => fastDbInit
21150
- });
21151
- async function fastDbInit() {
21152
- const { isInitialized: isInitialized2, getClient: getClient2, setExternalClient: setExternalClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
21153
- if (isInitialized2()) {
21154
- return getClient2();
21155
- }
21156
- try {
21157
- const { connectEmbedDaemon: connectEmbedDaemon2, sendDaemonRequest: sendDaemonRequest2, isClientConnected: isClientConnected2 } = await Promise.resolve().then(() => (init_exe_daemon_client(), exe_daemon_client_exports));
21158
- const { deserializeResultSet: deserializeResultSet2 } = await Promise.resolve().then(() => (init_daemon_protocol(), daemon_protocol_exports));
21159
- await connectEmbedDaemon2();
21160
- if (isClientConnected2()) {
21161
- const daemonClient = {
21162
- async execute(stmt) {
21163
- const sql = typeof stmt === "string" ? stmt : stmt.sql;
21164
- const args = typeof stmt === "string" ? [] : Array.isArray(stmt.args) ? stmt.args : [];
21165
- const resp = await sendDaemonRequest2({ type: "db-execute", sql, args });
21166
- if (resp.error) throw new Error(String(resp.error));
21167
- if (resp.db) return deserializeResultSet2(resp.db);
21168
- throw new Error("Unexpected daemon response");
21169
- },
21170
- async batch(stmts, mode) {
21171
- const statements = stmts.map((s) => {
21172
- const sql = typeof s === "string" ? s : s.sql;
21173
- const args = typeof s === "string" ? [] : Array.isArray(s.args) ? s.args : [];
21174
- return { sql, args };
21175
- });
21176
- const resp = await sendDaemonRequest2({ type: "db-batch", statements, mode: mode ?? "deferred" });
21177
- if (resp.error) throw new Error(String(resp.error));
21178
- const batchResults = resp["db-batch"];
21179
- if (batchResults) return batchResults.map(deserializeResultSet2);
21180
- throw new Error("Unexpected daemon batch response");
21181
- },
21182
- async transaction(_mode) {
21183
- throw new Error("Transactions not supported via daemon socket");
21184
- },
21185
- async executeMultiple(_sql) {
21186
- throw new Error("executeMultiple not supported via daemon socket");
21187
- },
21188
- async migrate(_stmts) {
21189
- throw new Error("migrate not supported via daemon socket");
21190
- },
21191
- sync() {
21192
- return Promise.resolve(void 0);
21193
- },
21194
- close() {
21195
- },
21196
- get closed() {
21197
- return false;
21198
- },
21199
- get protocol() {
21200
- return "file";
21201
- }
21202
- };
21203
- setExternalClient2(daemonClient);
21204
- return daemonClient;
21205
- }
21206
- } catch {
21207
- }
21208
- const { initStore: initStore2 } = await Promise.resolve().then(() => (init_store(), store_exports));
21209
- await initStore2({ lightweight: true });
21210
- return getClient2();
21211
- }
21212
- var init_fast_db_init = __esm({
21213
- "src/bin/fast-db-init.ts"() {
21214
- "use strict";
21215
- }
21216
- });
21217
-
21218
21032
  // src/lib/db-backup.ts
21219
21033
  var db_backup_exports = {};
21220
21034
  __export(db_backup_exports, {
@@ -21521,121 +21335,6 @@ function auditHookHealth() {
21521
21335
  const topPatterns = [...patternCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([pattern, count]) => ({ pattern, count }));
21522
21336
  return { logExists: true, totalLines, errorsLastHour, topPatterns };
21523
21337
  }
21524
- function safeReadJson(filePath) {
21525
- if (!existsSync31(filePath)) return null;
21526
- try {
21527
- return JSON.parse(readFileSync23(filePath, "utf-8"));
21528
- } catch {
21529
- return null;
21530
- }
21531
- }
21532
- function collectHookCommandsFromClaudeSettings(settings) {
21533
- const hooks = settings?.hooks;
21534
- if (!hooks || typeof hooks !== "object") return [];
21535
- const commands = [];
21536
- for (const [event, groups] of Object.entries(hooks)) {
21537
- if (!Array.isArray(groups)) continue;
21538
- for (const group of groups) {
21539
- const hooksForGroup = group?.hooks;
21540
- if (!Array.isArray(hooksForGroup)) continue;
21541
- for (const hook of hooksForGroup) {
21542
- const command = hook?.command;
21543
- if (typeof command === "string") commands.push({ event, command });
21544
- }
21545
- }
21546
- }
21547
- return commands;
21548
- }
21549
- function collectHookCommandsFromCodexHooks(config2) {
21550
- const commands = [];
21551
- if (!config2 || typeof config2 !== "object") return commands;
21552
- const root = config2;
21553
- for (const [event, value] of Object.entries(root)) {
21554
- const entries = Array.isArray(value) ? value : value && typeof value === "object" ? Object.values(value) : [];
21555
- for (const entry of entries) {
21556
- if (typeof entry === "string") commands.push({ event, command: entry });
21557
- const command = entry?.command;
21558
- if (typeof command === "string") commands.push({ event, command });
21559
- }
21560
- }
21561
- return commands;
21562
- }
21563
- function buildHookOwnershipIssues(runtime, commands) {
21564
- const issues = [];
21565
- for (const marker of LEGACY_SPLIT_POST_TOOL_HOOK_MARKERS) {
21566
- const count = commands.filter((cmd) => cmd.command.includes(marker)).length;
21567
- if (count > 0) {
21568
- issues.push({
21569
- runtime,
21570
- event: "PostToolUse",
21571
- marker,
21572
- count,
21573
- message: `Legacy split PostToolUse hook still installed: ${marker}`
21574
- });
21575
- }
21576
- }
21577
- for (const entry of EXE_HOOK_MANIFEST.filter((hook) => hook.runtimes.includes(runtime))) {
21578
- const matching = commands.filter((cmd) => cmd.command.includes(entry.commandMarker));
21579
- if (matching.length > 1) {
21580
- issues.push({
21581
- runtime,
21582
- event: entry.event,
21583
- marker: entry.commandMarker,
21584
- count: matching.length,
21585
- message: `Duplicate exe-os hook owner for ${entry.event}: ${entry.commandMarker}`
21586
- });
21587
- }
21588
- for (const cmd of matching) {
21589
- if (cmd.event !== entry.event) {
21590
- issues.push({
21591
- runtime,
21592
- event: cmd.event,
21593
- marker: entry.commandMarker,
21594
- count: 1,
21595
- message: `exe-os hook ${entry.commandMarker} is registered under ${cmd.event}, expected ${entry.event}`
21596
- });
21597
- }
21598
- }
21599
- }
21600
- for (const cmd of commands) {
21601
- if (!cmd.command.includes("dist/hooks/")) continue;
21602
- if (!cmd.command.includes("exe-os")) continue;
21603
- const entry = manifestEntryForCommand(cmd.command);
21604
- const isLegacy = LEGACY_SPLIT_POST_TOOL_HOOK_MARKERS.some((marker) => cmd.command.includes(marker));
21605
- if (!entry && !isLegacy) {
21606
- issues.push({
21607
- runtime,
21608
- event: cmd.event,
21609
- marker: "dist/hooks/",
21610
- count: 1,
21611
- message: `Unknown exe-os hook command not present in ownership manifest: ${cmd.command.slice(0, 160)}`
21612
- });
21613
- }
21614
- }
21615
- return issues;
21616
- }
21617
- function auditHookOwnership() {
21618
- const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
21619
- const checkedFiles = [];
21620
- const issues = [];
21621
- const claudeSettingsPath = path36.join(home, ".claude", "settings.json");
21622
- const claudeSettings = safeReadJson(claudeSettingsPath);
21623
- if (claudeSettings) {
21624
- checkedFiles.push(claudeSettingsPath);
21625
- issues.push(...buildHookOwnershipIssues("claude", collectHookCommandsFromClaudeSettings(claudeSettings)));
21626
- }
21627
- const codexHooksPath = path36.join(home, ".codex", "hooks.json");
21628
- const codexHooks = safeReadJson(codexHooksPath);
21629
- if (codexHooks) {
21630
- checkedFiles.push(codexHooksPath);
21631
- issues.push(...buildHookOwnershipIssues("codex", collectHookCommandsFromCodexHooks(codexHooks)));
21632
- }
21633
- return {
21634
- checkedFiles,
21635
- issues,
21636
- staleLegacyHooks: issues.filter((issue) => issue.message.includes("Legacy split"))
21637
- };
21638
- }
21639
21338
  async function auditShards() {
21640
21339
  try {
21641
21340
  const { auditShardHealth: auditShardHealth2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -21680,8 +21379,7 @@ async function runAudit(client, flags) {
21680
21379
  }
21681
21380
  const duplicateCount = duplicates.reduce((sum, d) => sum + d.delete_ids.length, 0);
21682
21381
  const hookHealth = auditHookHealth();
21683
- const hookOwnership = auditHookOwnership();
21684
- return { stats, nullVectors, duplicates, duplicateCount, bloated, fts, orphanedProjects, conflicts, hookHealth, hookOwnership, shards };
21382
+ return { stats, nullVectors, duplicates, duplicateCount, bloated, fts, orphanedProjects, conflicts, hookHealth, shards };
21685
21383
  }
21686
21384
  function indicator(value, warn) {
21687
21385
  if (value === 0) return "\u{1F7E2}";
@@ -21760,15 +21458,6 @@ function formatReport(report, flags) {
21760
21458
  lines.push(` ${p.count}x: ${p.pattern}`);
21761
21459
  }
21762
21460
  }
21763
- const ho = report.hookOwnership;
21764
- if (ho.issues.length === 0) {
21765
- lines.push(`\u{1F7E2} Hook ownership: ${ho.checkedFiles.length > 0 ? "manifest clean" : "no local hook config found"}`);
21766
- } else {
21767
- lines.push(`\u{1F534} Hook ownership: ${fmtNum(ho.issues.length)} issue(s)`);
21768
- for (const issue of ho.issues.slice(0, 5)) {
21769
- lines.push(` [${issue.runtime}/${issue.event}] ${issue.message}`);
21770
- }
21771
- }
21772
21461
  const sh = report.shards;
21773
21462
  if (sh.total > 0) {
21774
21463
  if (sh.unreadable === 0) {
@@ -21838,9 +21527,6 @@ function formatReport(report, flags) {
21838
21527
  if (report.conflicts.superseded > 0) {
21839
21528
  recs.push(`${fmtNum(report.conflicts.superseded)} superseded memories can be deactivated`);
21840
21529
  }
21841
- if (report.hookOwnership.issues.length > 0) {
21842
- recs.push(`Run exe-os install to refresh hook config; remove stale exe-os hook commands if they remain`);
21843
- }
21844
21530
  if (recs.length > 0) {
21845
21531
  lines.push("Recommendations:");
21846
21532
  for (const r of recs) {
@@ -21974,8 +21660,8 @@ function splitAtSentences(text3, maxChunkSize) {
21974
21660
  }
21975
21661
  async function main(argv = process.argv.slice(2)) {
21976
21662
  const flags = parseFlags(argv);
21977
- const { fastDbInit: fastDbInit2 } = await Promise.resolve().then(() => (init_fast_db_init(), fast_db_init_exports));
21978
- const client = await fastDbInit2();
21663
+ await initStore();
21664
+ const client = getClient();
21979
21665
  const report = await runAudit(client, flags);
21980
21666
  console.log(formatReport(report, flags));
21981
21667
  if (flags.fix || flags.dryRun) {
@@ -22035,9 +21721,10 @@ ${mode} Complete.`);
22035
21721
  var init_exe_doctor = __esm({
22036
21722
  "src/bin/exe-doctor.ts"() {
22037
21723
  "use strict";
21724
+ init_store();
21725
+ init_database();
22038
21726
  init_is_main();
22039
21727
  init_conflict_detector();
22040
- init_runtime_hook_manifest();
22041
21728
  if (isMainModule(import.meta.url) && (process.argv[1] ?? "").includes("exe-doctor")) {
22042
21729
  main().catch((err) => {
22043
21730
  console.error(err instanceof Error ? err.message : String(err));
@@ -22524,31 +22211,43 @@ function logError(msg) {
22524
22211
  } catch {
22525
22212
  }
22526
22213
  }
22214
+ function isTruthyEnv2(value) {
22215
+ return /^(1|true|yes|on)$/i.test(value ?? "");
22216
+ }
22527
22217
  function loadPgClient() {
22528
22218
  if (_pgFailed) return null;
22529
- const postgresUrl = process.env.DATABASE_URL;
22530
22219
  const configPath = path38.join(EXE_AI_DIR, "config.json");
22531
22220
  let cloudPostgresUrl;
22221
+ let configEnabled = false;
22532
22222
  try {
22533
22223
  if (existsSync33(configPath)) {
22534
22224
  const cfg = JSON.parse(readFileSync25(configPath, "utf8"));
22535
22225
  cloudPostgresUrl = cfg.cloud?.postgresUrl;
22536
- if (cfg.cloud?.syncToPostgres === false) {
22537
- _pgFailed = true;
22538
- return null;
22539
- }
22226
+ configEnabled = cfg.cloud?.syncToPostgres === true;
22540
22227
  }
22541
22228
  } catch {
22542
22229
  }
22543
- const url = postgresUrl || cloudPostgresUrl;
22230
+ const envEnabled = isTruthyEnv2(process.env.EXE_CLOUD_SYNC_TO_POSTGRES);
22231
+ if (!envEnabled && !configEnabled) {
22232
+ return null;
22233
+ }
22234
+ const url = process.env.DATABASE_URL || cloudPostgresUrl;
22544
22235
  if (!url) {
22545
22236
  _pgFailed = true;
22546
22237
  return null;
22547
22238
  }
22548
22239
  if (!_pgPromise) {
22549
22240
  _pgPromise = (async () => {
22241
+ if (!process.env.DATABASE_URL) process.env.DATABASE_URL = url;
22550
22242
  const { createRequire: createRequire7 } = await import("module");
22551
22243
  const { pathToFileURL: pathToFileURL7 } = await import("url");
22244
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
22245
+ if (explicitPath) {
22246
+ const mod2 = await import(pathToFileURL7(explicitPath).href);
22247
+ const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
22248
+ if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
22249
+ return new Ctor2();
22250
+ }
22552
22251
  const exeDbRoot = process.env.EXE_DB_ROOT ?? path38.join(homedir6(), "exe-db");
22553
22252
  const req = createRequire7(path38.join(exeDbRoot, "package.json"));
22554
22253
  const entry = req.resolve("@prisma/client");
@@ -22745,6 +22444,15 @@ async function cloudSync(config2) {
22745
22444
  } catch {
22746
22445
  throw new Error("[cloud-sync] Database not initialized. Call initStore() before cloudSync().");
22747
22446
  }
22447
+ try {
22448
+ const relink = await client.execute("SELECT value FROM sync_meta WHERE key = 'cloud_relink_required' LIMIT 1");
22449
+ if (String(relink.rows[0]?.value ?? "") === "1") {
22450
+ throw new Error("[cloud-sync] Paused after key rotation. Re-link/reupload cloud sync with the new recovery phrase before syncing.");
22451
+ }
22452
+ } catch (err) {
22453
+ const msg = err instanceof Error ? err.message : String(err);
22454
+ if (msg.includes("Paused after key rotation")) throw err;
22455
+ }
22748
22456
  try {
22749
22457
  const { getRawClient: getRawClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
22750
22458
  await getRawClient2().execute("PRAGMA wal_checkpoint(PASSIVE)");