@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.
package/dist/bin/cli.js CHANGED
@@ -820,7 +820,7 @@ function isLegacySplitPostToolCommand(command) {
820
820
  function textHasLegacySplitPostToolHook(text) {
821
821
  return [EXE_HOOK_FILES.ingest, EXE_HOOK_FILES.errorRecall, EXE_HOOK_FILES.ingestWorker].some((file) => text.includes(file));
822
822
  }
823
- var EXE_HOOK_FILES, EXE_HOOKS, EXE_HOOK_MANIFEST, LEGACY_SPLIT_POST_TOOL_HOOK_MARKERS;
823
+ var EXE_HOOK_FILES, EXE_HOOKS, LEGACY_SPLIT_POST_TOOL_HOOK_MARKERS;
824
824
  var init_runtime_hook_manifest = __esm({
825
825
  "src/adapters/runtime-hook-manifest.ts"() {
826
826
  "use strict";
@@ -844,116 +844,6 @@ var init_runtime_hook_manifest = __esm({
844
844
  notification: "dist/hooks/notification.js",
845
845
  instructionsLoaded: "dist/hooks/instructions-loaded.js"
846
846
  };
847
- EXE_HOOK_MANIFEST = [
848
- {
849
- key: "postToolCombined",
850
- event: "PostToolUse",
851
- commandMarker: EXE_HOOKS.postToolCombined,
852
- owner: "exe-os",
853
- purpose: "Single PostToolUse entrypoint for ingestion, error recall, summaries, and bug detection.",
854
- runtimes: ["claude", "codex", "opencode"],
855
- checkpointRole: "none"
856
- },
857
- {
858
- key: "sessionStart",
859
- event: "SessionStart",
860
- commandMarker: EXE_HOOKS.sessionStart,
861
- owner: "exe-os",
862
- purpose: "Loads agent identity, procedures, and boot context.",
863
- runtimes: ["claude", "codex", "opencode"],
864
- checkpointRole: "none"
865
- },
866
- {
867
- key: "promptSubmit",
868
- event: "UserPromptSubmit",
869
- commandMarker: EXE_HOOKS.promptSubmit,
870
- owner: "exe-os",
871
- purpose: "Injects current tasks, pending reviews, and local context before each prompt.",
872
- runtimes: ["claude", "codex", "opencode"],
873
- checkpointRole: "none"
874
- },
875
- {
876
- key: "heartbeat",
877
- event: "UserPromptSubmit",
878
- commandMarker: EXE_HOOKS.heartbeat,
879
- owner: "exe-os",
880
- purpose: "Lightweight heartbeat/status sidecar for Claude Code sessions.",
881
- runtimes: ["claude"],
882
- checkpointRole: "none"
883
- },
884
- {
885
- key: "stop",
886
- event: "Stop",
887
- commandMarker: EXE_HOOKS.stop,
888
- owner: "exe-os",
889
- purpose: "Finalizes task state, capacity signals, and emergency checkpointing.",
890
- runtimes: ["claude", "codex", "opencode"],
891
- checkpointRole: "capacity_checkpoint"
892
- },
893
- {
894
- key: "preToolUse",
895
- event: "PreToolUse",
896
- commandMarker: EXE_HOOKS.preToolUse,
897
- owner: "exe-os",
898
- purpose: "Preflight guardrails before shell/tool execution.",
899
- runtimes: ["claude", "codex", "opencode"],
900
- checkpointRole: "none"
901
- },
902
- {
903
- key: "subagentStop",
904
- event: "SubagentStop",
905
- commandMarker: EXE_HOOKS.subagentStop,
906
- owner: "exe-os",
907
- purpose: "Captures subagent completion context.",
908
- runtimes: ["claude"],
909
- checkpointRole: "none"
910
- },
911
- {
912
- key: "preCompact",
913
- event: "PreCompact",
914
- commandMarker: EXE_HOOKS.preCompact,
915
- owner: "exe-os",
916
- purpose: "Writes active-task snapshot and compaction recovery context.",
917
- runtimes: ["claude"],
918
- checkpointRole: "recovery_context"
919
- },
920
- {
921
- key: "postCompact",
922
- event: "PostCompact",
923
- commandMarker: EXE_HOOKS.postCompact,
924
- owner: "exe-os",
925
- purpose: "Rehydrates recovery context after compaction.",
926
- runtimes: ["claude"],
927
- checkpointRole: "recovery_context"
928
- },
929
- {
930
- key: "sessionEnd",
931
- event: "SessionEnd",
932
- commandMarker: EXE_HOOKS.sessionEnd,
933
- owner: "exe-os",
934
- purpose: "Stores session-end checkpoint and triages orphaned in-progress tasks.",
935
- runtimes: ["claude"],
936
- checkpointRole: "session_summary"
937
- },
938
- {
939
- key: "notification",
940
- event: "Notification",
941
- commandMarker: EXE_HOOKS.notification,
942
- owner: "exe-os",
943
- purpose: "Captures runtime notifications and nudges.",
944
- runtimes: ["claude"],
945
- checkpointRole: "none"
946
- },
947
- {
948
- key: "instructionsLoaded",
949
- event: "InstructionsLoaded",
950
- commandMarker: EXE_HOOKS.instructionsLoaded,
951
- owner: "exe-os",
952
- purpose: "Applies runtime instruction post-processing.",
953
- runtimes: ["claude"],
954
- checkpointRole: "none"
955
- }
956
- ];
957
847
  LEGACY_SPLIT_POST_TOOL_HOOK_MARKERS = [
958
848
  "dist/hooks/ingest.js",
959
849
  "dist/hooks/error-recall.js",
@@ -5781,31 +5671,43 @@ function logError(msg) {
5781
5671
  } catch {
5782
5672
  }
5783
5673
  }
5674
+ function isTruthyEnv(value) {
5675
+ return /^(1|true|yes|on)$/i.test(value ?? "");
5676
+ }
5784
5677
  function loadPgClient() {
5785
5678
  if (_pgFailed) return null;
5786
- const postgresUrl = process.env.DATABASE_URL;
5787
5679
  const configPath = path14.join(EXE_AI_DIR, "config.json");
5788
5680
  let cloudPostgresUrl;
5681
+ let configEnabled = false;
5789
5682
  try {
5790
5683
  if (existsSync14(configPath)) {
5791
5684
  const cfg = JSON.parse(readFileSync10(configPath, "utf8"));
5792
5685
  cloudPostgresUrl = cfg.cloud?.postgresUrl;
5793
- if (cfg.cloud?.syncToPostgres === false) {
5794
- _pgFailed = true;
5795
- return null;
5796
- }
5686
+ configEnabled = cfg.cloud?.syncToPostgres === true;
5797
5687
  }
5798
5688
  } catch {
5799
5689
  }
5800
- const url = postgresUrl || cloudPostgresUrl;
5690
+ const envEnabled = isTruthyEnv(process.env.EXE_CLOUD_SYNC_TO_POSTGRES);
5691
+ if (!envEnabled && !configEnabled) {
5692
+ return null;
5693
+ }
5694
+ const url = process.env.DATABASE_URL || cloudPostgresUrl;
5801
5695
  if (!url) {
5802
5696
  _pgFailed = true;
5803
5697
  return null;
5804
5698
  }
5805
5699
  if (!_pgPromise) {
5806
5700
  _pgPromise = (async () => {
5701
+ if (!process.env.DATABASE_URL) process.env.DATABASE_URL = url;
5807
5702
  const { createRequire: createRequire3 } = await import("module");
5808
5703
  const { pathToFileURL: pathToFileURL3 } = await import("url");
5704
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
5705
+ if (explicitPath) {
5706
+ const mod2 = await import(pathToFileURL3(explicitPath).href);
5707
+ const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
5708
+ if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
5709
+ return new Ctor2();
5710
+ }
5809
5711
  const exeDbRoot = process.env.EXE_DB_ROOT ?? path14.join(homedir2(), "exe-db");
5810
5712
  const req = createRequire3(path14.join(exeDbRoot, "package.json"));
5811
5713
  const entry = req.resolve("@prisma/client");
@@ -6002,6 +5904,15 @@ async function cloudSync(config) {
6002
5904
  } catch {
6003
5905
  throw new Error("[cloud-sync] Database not initialized. Call initStore() before cloudSync().");
6004
5906
  }
5907
+ try {
5908
+ const relink = await client.execute("SELECT value FROM sync_meta WHERE key = 'cloud_relink_required' LIMIT 1");
5909
+ if (String(relink.rows[0]?.value ?? "") === "1") {
5910
+ throw new Error("[cloud-sync] Paused after key rotation. Re-link/reupload cloud sync with the new recovery phrase before syncing.");
5911
+ }
5912
+ } catch (err) {
5913
+ const msg = err instanceof Error ? err.message : String(err);
5914
+ if (msg.includes("Paused after key rotation")) throw err;
5915
+ }
6005
5916
  try {
6006
5917
  const { getRawClient: getRawClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
6007
5918
  await getRawClient2().execute("PRAGMA wal_checkpoint(PASSIVE)");
@@ -17385,7 +17296,13 @@ async function runStackUpdate(options) {
17385
17296
  writeFileSync20(tmp, patched, { mode: 384 });
17386
17297
  renameSync7(tmp, options.envFile);
17387
17298
  const composeArgs = ["compose", "--file", options.composeFile, "--env-file", options.envFile];
17299
+ let registryForLogout;
17388
17300
  try {
17301
+ const creds = await fetchImageCredentials(options);
17302
+ if (creds) {
17303
+ (options.dockerLogin ?? defaultDockerLogin)(creds);
17304
+ registryForLogout = creds.registry;
17305
+ }
17389
17306
  exec2("docker", [...composeArgs, "pull"]);
17390
17307
  exec2("docker", [...composeArgs, "up", "-d"]);
17391
17308
  await verifyReleaseHealth(plan.release, options.healthRetries ?? 12, options.healthDelayMs ?? 5e3);
@@ -17401,8 +17318,28 @@ async function runStackUpdate(options) {
17401
17318
  const reason = err instanceof Error ? err.message : String(err);
17402
17319
  await postDeployAudit(options, "failed", plan.targetVersion, previousVersion, reason, { rollbackAttempted: true });
17403
17320
  throw new Error(`Stack update failed and rollback was attempted: ${reason}`);
17321
+ } finally {
17322
+ if (registryForLogout) {
17323
+ try {
17324
+ (options.dockerLogout ?? defaultDockerLogout)(registryForLogout);
17325
+ } catch {
17326
+ }
17327
+ }
17404
17328
  }
17405
17329
  }
17330
+ async function fetchImageCredentials(options) {
17331
+ if (!options.imageCredentialsUrl) return null;
17332
+ const res = await fetch(options.imageCredentialsUrl, {
17333
+ method: "POST",
17334
+ headers: {
17335
+ "content-type": "application/json",
17336
+ ...options.manifestAuthToken ? { authorization: `Bearer ${options.manifestAuthToken}` } : {}
17337
+ },
17338
+ body: JSON.stringify({ deviceId: options.deviceId, licenseKey: options.licenseKey })
17339
+ });
17340
+ if (!res.ok) throw new Error(`Failed to fetch image credentials: HTTP ${res.status}`);
17341
+ return await res.json();
17342
+ }
17406
17343
  function readCurrentStackVersion(lockFile) {
17407
17344
  if (!existsSync30(lockFile)) return void 0;
17408
17345
  try {
@@ -17466,6 +17403,15 @@ function httpStatus(urlString) {
17466
17403
  function defaultExec(cmd, args2, opts) {
17467
17404
  execFileSync3(cmd, args2, { stdio: "inherit", cwd: opts?.cwd });
17468
17405
  }
17406
+ function defaultDockerLogin(creds) {
17407
+ execFileSync3("docker", ["login", creds.registry, "-u", creds.username, "--password-stdin"], {
17408
+ input: creds.password,
17409
+ stdio: ["pipe", "inherit", "inherit"]
17410
+ });
17411
+ }
17412
+ function defaultDockerLogout(registry) {
17413
+ execFileSync3("docker", ["logout", registry], { stdio: "ignore" });
17414
+ }
17469
17415
  async function defaultFetchText(ref, authToken) {
17470
17416
  const res = await fetch(ref, {
17471
17417
  headers: authToken ? { authorization: `Bearer ${authToken}` } : void 0
@@ -17492,6 +17438,7 @@ function defaultStackPaths() {
17492
17438
  envFile: process.env.EXE_STACK_ENV_FILE || (existsSync30(cwdEnv) ? cwdEnv : "/opt/exe-stack/.env"),
17493
17439
  manifestRef: process.env.EXE_STACK_MANIFEST || "https://update.askexe.com/stack-manifest.json",
17494
17440
  auditUrl: process.env.EXE_STACK_AUDIT_URL || "https://update.askexe.com/v1/deploy-audits",
17441
+ imageCredentialsUrl: process.env.EXE_STACK_IMAGE_CREDENTIALS_URL || "https://update.askexe.com/v1/image-credentials",
17495
17442
  manifestAuthToken: process.env.EXE_STACK_UPDATE_TOKEN,
17496
17443
  manifestPublicKey: loadDefaultPublicKey()
17497
17444
  };
@@ -17522,6 +17469,7 @@ function parseArgs4(args2) {
17522
17469
  composeFile: defaults.composeFile,
17523
17470
  envFile: defaults.envFile,
17524
17471
  auditUrl: defaults.auditUrl,
17472
+ imageCredentialsUrl: defaults.imageCredentialsUrl,
17525
17473
  manifestAuthToken: defaults.manifestAuthToken,
17526
17474
  manifestPublicKey: defaults.manifestPublicKey,
17527
17475
  deviceId: process.env.EXE_DEVICE_ID,
@@ -17551,6 +17499,9 @@ function parseArgs4(args2) {
17551
17499
  else if (arg === "--auth-token-env") opts.manifestAuthToken = process.env[next()] ?? "";
17552
17500
  else if (arg === "--audit-url") opts.auditUrl = next();
17553
17501
  else if (arg.startsWith("--audit-url=")) opts.auditUrl = arg.split("=").slice(1).join("=");
17502
+ else if (arg === "--image-credentials-url") opts.imageCredentialsUrl = next();
17503
+ else if (arg.startsWith("--image-credentials-url=")) opts.imageCredentialsUrl = arg.split("=").slice(1).join("=");
17504
+ else if (arg === "--no-image-credentials") opts.imageCredentialsUrl = void 0;
17554
17505
  else if (arg === "--device-id") opts.deviceId = next();
17555
17506
  else if (arg.startsWith("--device-id=")) opts.deviceId = arg.split("=").slice(1).join("=");
17556
17507
  else if (arg === "--license-key") opts.licenseKey = next();
@@ -17588,6 +17539,8 @@ Options:
17588
17539
  --auth-token <token> Bearer token for private update manifest/audit API
17589
17540
  --auth-token-env <name> Read bearer token from an environment variable
17590
17541
  --audit-url <url> Deploy-audit endpoint (default: EXE_STACK_AUDIT_URL/update.askexe.com)
17542
+ --image-credentials-url <url> Registry credential broker endpoint
17543
+ --no-image-credentials Skip registry credential broker / Docker login
17591
17544
  --device-id <id> Device ID to include in deploy audit
17592
17545
  --license-key <key> License key to include in deploy audit
17593
17546
  --rollback Restore latest backed-up .env and restart stack
@@ -9061,31 +9061,43 @@ function logError(msg) {
9061
9061
  } catch {
9062
9062
  }
9063
9063
  }
9064
+ function isTruthyEnv(value) {
9065
+ return /^(1|true|yes|on)$/i.test(value ?? "");
9066
+ }
9064
9067
  function loadPgClient() {
9065
9068
  if (_pgFailed) return null;
9066
- const postgresUrl = process.env.DATABASE_URL;
9067
9069
  const configPath = path25.join(EXE_AI_DIR, "config.json");
9068
9070
  let cloudPostgresUrl;
9071
+ let configEnabled = false;
9069
9072
  try {
9070
9073
  if (existsSync21(configPath)) {
9071
9074
  const cfg = JSON.parse(readFileSync15(configPath, "utf8"));
9072
9075
  cloudPostgresUrl = cfg.cloud?.postgresUrl;
9073
- if (cfg.cloud?.syncToPostgres === false) {
9074
- _pgFailed = true;
9075
- return null;
9076
- }
9076
+ configEnabled = cfg.cloud?.syncToPostgres === true;
9077
9077
  }
9078
9078
  } catch {
9079
9079
  }
9080
- const url = postgresUrl || cloudPostgresUrl;
9080
+ const envEnabled = isTruthyEnv(process.env.EXE_CLOUD_SYNC_TO_POSTGRES);
9081
+ if (!envEnabled && !configEnabled) {
9082
+ return null;
9083
+ }
9084
+ const url = process.env.DATABASE_URL || cloudPostgresUrl;
9081
9085
  if (!url) {
9082
9086
  _pgFailed = true;
9083
9087
  return null;
9084
9088
  }
9085
9089
  if (!_pgPromise) {
9086
9090
  _pgPromise = (async () => {
9091
+ if (!process.env.DATABASE_URL) process.env.DATABASE_URL = url;
9087
9092
  const { createRequire: createRequire3 } = await import("module");
9088
9093
  const { pathToFileURL: pathToFileURL3 } = await import("url");
9094
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
9095
+ if (explicitPath) {
9096
+ const mod2 = await import(pathToFileURL3(explicitPath).href);
9097
+ const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
9098
+ if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
9099
+ return new Ctor2();
9100
+ }
9089
9101
  const exeDbRoot = process.env.EXE_DB_ROOT ?? path25.join(homedir2(), "exe-db");
9090
9102
  const req = createRequire3(path25.join(exeDbRoot, "package.json"));
9091
9103
  const entry = req.resolve("@prisma/client");
@@ -9282,6 +9294,15 @@ async function cloudSync(config) {
9282
9294
  } catch {
9283
9295
  throw new Error("[cloud-sync] Database not initialized. Call initStore() before cloudSync().");
9284
9296
  }
9297
+ try {
9298
+ const relink = await client.execute("SELECT value FROM sync_meta WHERE key = 'cloud_relink_required' LIMIT 1");
9299
+ if (String(relink.rows[0]?.value ?? "") === "1") {
9300
+ throw new Error("[cloud-sync] Paused after key rotation. Re-link/reupload cloud sync with the new recovery phrase before syncing.");
9301
+ }
9302
+ } catch (err) {
9303
+ const msg = err instanceof Error ? err.message : String(err);
9304
+ if (msg.includes("Paused after key rotation")) throw err;
9305
+ }
9285
9306
  try {
9286
9307
  const { getRawClient: getRawClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
9287
9308
  await getRawClient2().execute("PRAGMA wal_checkpoint(PASSIVE)");