codeharbor 0.1.24 → 0.1.25

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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +215 -51
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -422,7 +422,7 @@ If any check fails, it prints actionable fix commands (for example `codeharbor i
422
422
  - each accepted request activates the sender's conversation in that room
423
423
  - activation TTL: `SESSION_ACTIVE_WINDOW_MINUTES` (default: `20`)
424
424
  - Control commands
425
- - `/status` show session + limiter + metrics + runtime worker status
425
+ - `/status` show session + limiter + metrics + runtime worker status, current version, and update hint
426
426
  - `/reset` clear bound Codex session and keep conversation active
427
427
  - `/stop` cancel in-flight execution (if running) and reset session context
428
428
  - `/agents status` show multi-agent workflow status for current session (when enabled)
package/dist/cli.js CHANGED
@@ -25,8 +25,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
 
26
26
  // src/cli.ts
27
27
  var import_node_child_process8 = require("child_process");
28
- var import_node_fs11 = __toESM(require("fs"));
29
- var import_node_path15 = __toESM(require("path"));
28
+ var import_node_fs12 = __toESM(require("fs"));
29
+ var import_node_path16 = __toESM(require("path"));
30
30
  var import_commander = require("commander");
31
31
 
32
32
  // src/app.ts
@@ -4463,6 +4463,163 @@ var CodexSessionRuntime = class {
4463
4463
  }
4464
4464
  };
4465
4465
 
4466
+ // src/package-update-checker.ts
4467
+ var import_node_fs7 = __toESM(require("fs"));
4468
+ var import_node_path10 = __toESM(require("path"));
4469
+ var NpmRegistryUpdateChecker = class {
4470
+ packageName;
4471
+ currentVersion;
4472
+ ttlMs;
4473
+ timeoutMs;
4474
+ fetchImpl;
4475
+ enabled;
4476
+ upgradeCommand;
4477
+ cachedStatus = null;
4478
+ cacheExpiresAt = 0;
4479
+ constructor(options) {
4480
+ this.packageName = options?.packageName?.trim() || "codeharbor";
4481
+ this.currentVersion = options?.currentVersion?.trim() || resolvePackageVersion();
4482
+ this.ttlMs = options?.ttlMs ?? 6 * 60 * 60 * 1e3;
4483
+ this.timeoutMs = options?.timeoutMs ?? 3e3;
4484
+ this.fetchImpl = options?.fetchImpl ?? fetch;
4485
+ this.enabled = options?.enabled ?? process.env.NODE_ENV !== "test";
4486
+ this.upgradeCommand = `npm install -g ${this.packageName}@latest`;
4487
+ }
4488
+ async getStatus() {
4489
+ const now = Date.now();
4490
+ if (this.cachedStatus && now < this.cacheExpiresAt) {
4491
+ return this.cachedStatus;
4492
+ }
4493
+ let nextStatus;
4494
+ if (!this.enabled) {
4495
+ nextStatus = this.buildStatus({
4496
+ latestVersion: null,
4497
+ state: "unknown",
4498
+ error: "update check disabled"
4499
+ });
4500
+ } else {
4501
+ nextStatus = await this.fetchLatestStatus();
4502
+ }
4503
+ this.cachedStatus = nextStatus;
4504
+ this.cacheExpiresAt = now + this.ttlMs;
4505
+ return nextStatus;
4506
+ }
4507
+ async fetchLatestStatus() {
4508
+ const controller = new AbortController();
4509
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
4510
+ timer.unref?.();
4511
+ try {
4512
+ const response = await this.fetchImpl(`https://registry.npmjs.org/${this.packageName}/latest`, {
4513
+ signal: controller.signal
4514
+ });
4515
+ if (!response.ok) {
4516
+ return this.buildStatus({
4517
+ latestVersion: null,
4518
+ state: "unknown",
4519
+ error: `HTTP ${response.status}`
4520
+ });
4521
+ }
4522
+ const payload = await response.json();
4523
+ const latest = typeof payload.version === "string" ? payload.version.trim() : "";
4524
+ if (!latest) {
4525
+ return this.buildStatus({
4526
+ latestVersion: null,
4527
+ state: "unknown",
4528
+ error: "invalid npm response"
4529
+ });
4530
+ }
4531
+ const comparison = compareSemver(this.currentVersion, latest);
4532
+ if (comparison === null) {
4533
+ return this.buildStatus({
4534
+ latestVersion: latest,
4535
+ state: "unknown",
4536
+ error: "version compare unavailable"
4537
+ });
4538
+ }
4539
+ return this.buildStatus({
4540
+ latestVersion: latest,
4541
+ state: comparison < 0 ? "update_available" : "up_to_date",
4542
+ error: null
4543
+ });
4544
+ } catch (error) {
4545
+ return this.buildStatus({
4546
+ latestVersion: null,
4547
+ state: "unknown",
4548
+ error: normalizeError(error)
4549
+ });
4550
+ } finally {
4551
+ clearTimeout(timer);
4552
+ }
4553
+ }
4554
+ buildStatus(input) {
4555
+ return {
4556
+ packageName: this.packageName,
4557
+ currentVersion: this.currentVersion,
4558
+ latestVersion: input.latestVersion,
4559
+ state: input.state,
4560
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
4561
+ error: input.error,
4562
+ upgradeCommand: this.upgradeCommand
4563
+ };
4564
+ }
4565
+ };
4566
+ function formatPackageUpdateHint(status) {
4567
+ if (status.state === "update_available" && status.latestVersion) {
4568
+ return `\u53D1\u73B0\u65B0\u7248\u672C ${status.latestVersion}\uFF0C\u5EFA\u8BAE\u6267\u884C\uFF1A${status.upgradeCommand}`;
4569
+ }
4570
+ if (status.state === "up_to_date") {
4571
+ return `\u5DF2\u662F\u6700\u65B0\u7248\u672C${status.latestVersion ? `\uFF08${status.latestVersion}\uFF09` : ""}`;
4572
+ }
4573
+ return `\u6682\u65F6\u65E0\u6CD5\u68C0\u67E5\u66F4\u65B0${status.error ? `\uFF08${status.error}\uFF09` : ""}`;
4574
+ }
4575
+ function resolvePackageVersion(packagePath) {
4576
+ const resolvedPath = packagePath ?? import_node_path10.default.resolve(__dirname, "..", "package.json");
4577
+ try {
4578
+ const raw = import_node_fs7.default.readFileSync(resolvedPath, "utf8");
4579
+ const payload = JSON.parse(raw);
4580
+ if (typeof payload.version === "string" && payload.version.trim()) {
4581
+ return payload.version.trim();
4582
+ }
4583
+ } catch {
4584
+ }
4585
+ return "0.0.0";
4586
+ }
4587
+ function compareSemver(current, latest) {
4588
+ const currentParts = parseSemver(current);
4589
+ const latestParts = parseSemver(latest);
4590
+ if (!currentParts || !latestParts) {
4591
+ return null;
4592
+ }
4593
+ for (let i = 0; i < 3; i += 1) {
4594
+ if (currentParts[i] < latestParts[i]) {
4595
+ return -1;
4596
+ }
4597
+ if (currentParts[i] > latestParts[i]) {
4598
+ return 1;
4599
+ }
4600
+ }
4601
+ return 0;
4602
+ }
4603
+ function parseSemver(raw) {
4604
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(raw.trim());
4605
+ if (!match) {
4606
+ return null;
4607
+ }
4608
+ const major = Number.parseInt(match[1], 10);
4609
+ const minor = Number.parseInt(match[2], 10);
4610
+ const patch = Number.parseInt(match[3], 10);
4611
+ if (!Number.isFinite(major) || !Number.isFinite(minor) || !Number.isFinite(patch)) {
4612
+ return null;
4613
+ }
4614
+ return [major, minor, patch];
4615
+ }
4616
+ function normalizeError(error) {
4617
+ if (error instanceof Error && error.message.trim()) {
4618
+ return error.message.trim();
4619
+ }
4620
+ return String(error);
4621
+ }
4622
+
4466
4623
  // src/rate-limiter.ts
4467
4624
  var RateLimiter = class {
4468
4625
  options;
@@ -4846,7 +5003,7 @@ function createIdleWorkflowSnapshot() {
4846
5003
 
4847
5004
  // src/workflow/autodev.ts
4848
5005
  var import_promises4 = __toESM(require("fs/promises"));
4849
- var import_node_path10 = __toESM(require("path"));
5006
+ var import_node_path11 = __toESM(require("path"));
4850
5007
  function parseAutoDevCommand(text) {
4851
5008
  const normalized = text.trim();
4852
5009
  if (!/^\/autodev(?:\s|$)/i.test(normalized)) {
@@ -4866,8 +5023,8 @@ function parseAutoDevCommand(text) {
4866
5023
  };
4867
5024
  }
4868
5025
  async function loadAutoDevContext(workdir) {
4869
- const requirementsPath = import_node_path10.default.join(workdir, "REQUIREMENTS.md");
4870
- const taskListPath = import_node_path10.default.join(workdir, "TASK_LIST.md");
5026
+ const requirementsPath = import_node_path11.default.join(workdir, "REQUIREMENTS.md");
5027
+ const taskListPath = import_node_path11.default.join(workdir, "TASK_LIST.md");
4871
5028
  const requirementsContent = await readOptionalFile(requirementsPath);
4872
5029
  const taskListContent = await readOptionalFile(taskListPath);
4873
5030
  return {
@@ -5225,6 +5382,7 @@ var Orchestrator = class {
5225
5382
  cliCompatRecorder;
5226
5383
  audioTranscriber;
5227
5384
  workflowRunner;
5385
+ packageUpdateChecker;
5228
5386
  workflowSnapshots = /* @__PURE__ */ new Map();
5229
5387
  autoDevSnapshots = /* @__PURE__ */ new Map();
5230
5388
  metrics = new RequestMetrics();
@@ -5298,6 +5456,9 @@ var Orchestrator = class {
5298
5456
  enabled: options?.multiAgentWorkflow?.enabled ?? false,
5299
5457
  autoRepairMaxRounds: options?.multiAgentWorkflow?.autoRepairMaxRounds ?? 1
5300
5458
  });
5459
+ this.packageUpdateChecker = options?.packageUpdateChecker ?? new NpmRegistryUpdateChecker({
5460
+ packageName: "codeharbor"
5461
+ });
5301
5462
  this.sessionRuntime = new CodexSessionRuntime(this.executor);
5302
5463
  }
5303
5464
  async handleMessage(message) {
@@ -5661,6 +5822,7 @@ var Orchestrator = class {
5661
5822
  const runtime = this.sessionRuntime.getRuntimeStats();
5662
5823
  const workflow = this.workflowSnapshots.get(sessionKey) ?? createIdleWorkflowSnapshot();
5663
5824
  const autoDev = this.autoDevSnapshots.get(sessionKey) ?? createIdleAutoDevSnapshot();
5825
+ const packageUpdate = await this.packageUpdateChecker.getStatus();
5664
5826
  await this.channel.sendNotice(
5665
5827
  message.conversationId,
5666
5828
  `[CodeHarbor] \u5F53\u524D\u72B6\u6001
@@ -5669,6 +5831,8 @@ var Orchestrator = class {
5669
5831
  - activeUntil: ${activeUntil}
5670
5832
  - \u5DF2\u7ED1\u5B9A Codex \u4F1A\u8BDD: ${status.hasCodexSession ? "\u662F" : "\u5426"}
5671
5833
  - \u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55: ${roomConfig.workdir}
5834
+ - \u5F53\u524D\u7248\u672C: ${packageUpdate.currentVersion}
5835
+ - \u66F4\u65B0\u68C0\u67E5: ${formatPackageUpdateHint(packageUpdate)}
5672
5836
  - \u8FD0\u884C\u4E2D\u4EFB\u52A1: ${metrics.activeExecutions}
5673
5837
  - \u6307\u6807: total=${metrics.total}, success=${metrics.success}, failed=${metrics.failed}, timeout=${metrics.timeout}, cancelled=${metrics.cancelled}, rate_limited=${metrics.rateLimited}
5674
5838
  - \u5E73\u5747\u8017\u65F6: queue=${metrics.avgQueueMs}ms, exec=${metrics.avgExecMs}ms, send=${metrics.avgSendMs}ms
@@ -6466,8 +6630,8 @@ ${result.review}
6466
6630
  }
6467
6631
 
6468
6632
  // src/store/state-store.ts
6469
- var import_node_fs7 = __toESM(require("fs"));
6470
- var import_node_path11 = __toESM(require("path"));
6633
+ var import_node_fs8 = __toESM(require("fs"));
6634
+ var import_node_path12 = __toESM(require("path"));
6471
6635
  var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
6472
6636
  var PRUNE_INTERVAL_MS = 5 * 60 * 1e3;
6473
6637
  var SQLITE_MODULE_ID = `node:${"sqlite"}`;
@@ -6493,7 +6657,7 @@ var StateStore = class {
6493
6657
  this.maxProcessedEventsPerSession = maxProcessedEventsPerSession;
6494
6658
  this.maxSessionAgeMs = maxSessionAgeDays * ONE_DAY_MS;
6495
6659
  this.maxSessions = maxSessions;
6496
- import_node_fs7.default.mkdirSync(import_node_path11.default.dirname(this.dbPath), { recursive: true });
6660
+ import_node_fs8.default.mkdirSync(import_node_path12.default.dirname(this.dbPath), { recursive: true });
6497
6661
  this.db = new DatabaseSync(this.dbPath);
6498
6662
  this.initializeSchema();
6499
6663
  this.importLegacyStateIfNeeded();
@@ -6738,7 +6902,7 @@ var StateStore = class {
6738
6902
  `);
6739
6903
  }
6740
6904
  importLegacyStateIfNeeded() {
6741
- if (!this.legacyJsonPath || !import_node_fs7.default.existsSync(this.legacyJsonPath)) {
6905
+ if (!this.legacyJsonPath || !import_node_fs8.default.existsSync(this.legacyJsonPath)) {
6742
6906
  return;
6743
6907
  }
6744
6908
  const countRow = this.db.prepare("SELECT COUNT(*) AS count FROM sessions").get();
@@ -6821,7 +6985,7 @@ var StateStore = class {
6821
6985
  };
6822
6986
  function loadLegacyState(filePath) {
6823
6987
  try {
6824
- const raw = import_node_fs7.default.readFileSync(filePath, "utf8");
6988
+ const raw = import_node_fs8.default.readFileSync(filePath, "utf8");
6825
6989
  const parsed = JSON.parse(raw);
6826
6990
  if (!parsed.sessions || typeof parsed.sessions !== "object") {
6827
6991
  return null;
@@ -7015,8 +7179,8 @@ function isNonLoopbackHost(host) {
7015
7179
  }
7016
7180
 
7017
7181
  // src/config.ts
7018
- var import_node_fs8 = __toESM(require("fs"));
7019
- var import_node_path12 = __toESM(require("path"));
7182
+ var import_node_fs9 = __toESM(require("fs"));
7183
+ var import_node_path13 = __toESM(require("path"));
7020
7184
  var import_dotenv2 = __toESM(require("dotenv"));
7021
7185
  var import_zod = require("zod");
7022
7186
  var configSchema = import_zod.z.object({
@@ -7088,7 +7252,7 @@ var configSchema = import_zod.z.object({
7088
7252
  matrixCommandPrefix: v.MATRIX_COMMAND_PREFIX,
7089
7253
  codexBin: v.CODEX_BIN,
7090
7254
  codexModel: v.CODEX_MODEL?.trim() || null,
7091
- codexWorkdir: import_node_path12.default.resolve(v.CODEX_WORKDIR),
7255
+ codexWorkdir: import_node_path13.default.resolve(v.CODEX_WORKDIR),
7092
7256
  codexDangerousBypass: v.CODEX_DANGEROUS_BYPASS,
7093
7257
  codexExecTimeoutMs: v.CODEX_EXEC_TIMEOUT_MS,
7094
7258
  codexSandboxMode: v.CODEX_SANDBOX_MODE?.trim() || null,
@@ -7099,8 +7263,8 @@ var configSchema = import_zod.z.object({
7099
7263
  enabled: v.AGENT_WORKFLOW_ENABLED,
7100
7264
  autoRepairMaxRounds: v.AGENT_WORKFLOW_AUTO_REPAIR_MAX_ROUNDS
7101
7265
  },
7102
- stateDbPath: import_node_path12.default.resolve(v.STATE_DB_PATH),
7103
- legacyStateJsonPath: v.STATE_PATH.trim() ? import_node_path12.default.resolve(v.STATE_PATH) : null,
7266
+ stateDbPath: import_node_path13.default.resolve(v.STATE_DB_PATH),
7267
+ legacyStateJsonPath: v.STATE_PATH.trim() ? import_node_path13.default.resolve(v.STATE_PATH) : null,
7104
7268
  maxProcessedEventsPerSession: v.MAX_PROCESSED_EVENTS_PER_SESSION,
7105
7269
  maxSessionAgeDays: v.MAX_SESSION_AGE_DAYS,
7106
7270
  maxSessions: v.MAX_SESSIONS,
@@ -7141,7 +7305,7 @@ var configSchema = import_zod.z.object({
7141
7305
  audioTranscribeMaxBytes: v.CLI_COMPAT_AUDIO_TRANSCRIBE_MAX_BYTES,
7142
7306
  audioLocalWhisperCommand: v.CLI_COMPAT_AUDIO_LOCAL_WHISPER_COMMAND.trim() ? v.CLI_COMPAT_AUDIO_LOCAL_WHISPER_COMMAND.trim() : null,
7143
7307
  audioLocalWhisperTimeoutMs: v.CLI_COMPAT_AUDIO_LOCAL_WHISPER_TIMEOUT_MS,
7144
- recordPath: v.CLI_COMPAT_RECORD_PATH.trim() ? import_node_path12.default.resolve(v.CLI_COMPAT_RECORD_PATH) : null
7308
+ recordPath: v.CLI_COMPAT_RECORD_PATH.trim() ? import_node_path13.default.resolve(v.CLI_COMPAT_RECORD_PATH) : null
7145
7309
  },
7146
7310
  doctorHttpTimeoutMs: v.DOCTOR_HTTP_TIMEOUT_MS,
7147
7311
  adminBindHost: v.ADMIN_BIND_HOST.trim() || "127.0.0.1",
@@ -7152,7 +7316,7 @@ var configSchema = import_zod.z.object({
7152
7316
  adminAllowedOrigins: parseCsvList(v.ADMIN_ALLOWED_ORIGINS),
7153
7317
  logLevel: v.LOG_LEVEL
7154
7318
  }));
7155
- function loadEnvFromFile(filePath = import_node_path12.default.resolve(process.cwd(), ".env"), env = process.env) {
7319
+ function loadEnvFromFile(filePath = import_node_path13.default.resolve(process.cwd(), ".env"), env = process.env) {
7156
7320
  import_dotenv2.default.config({
7157
7321
  path: filePath,
7158
7322
  processEnv: env,
@@ -7165,9 +7329,9 @@ function loadConfig(env = process.env) {
7165
7329
  const message = parsed.error.issues.map((issue) => `${issue.path.join(".") || "config"}: ${issue.message}`).join("; ");
7166
7330
  throw new Error(`Invalid configuration: ${message}`);
7167
7331
  }
7168
- import_node_fs8.default.mkdirSync(import_node_path12.default.dirname(parsed.data.stateDbPath), { recursive: true });
7332
+ import_node_fs9.default.mkdirSync(import_node_path13.default.dirname(parsed.data.stateDbPath), { recursive: true });
7169
7333
  if (parsed.data.legacyStateJsonPath) {
7170
- import_node_fs8.default.mkdirSync(import_node_path12.default.dirname(parsed.data.legacyStateJsonPath), { recursive: true });
7334
+ import_node_fs9.default.mkdirSync(import_node_path13.default.dirname(parsed.data.legacyStateJsonPath), { recursive: true });
7171
7335
  }
7172
7336
  return parsed.data;
7173
7337
  }
@@ -7334,8 +7498,8 @@ function parseAdminTokens(raw) {
7334
7498
  }
7335
7499
 
7336
7500
  // src/config-snapshot.ts
7337
- var import_node_fs9 = __toESM(require("fs"));
7338
- var import_node_path13 = __toESM(require("path"));
7501
+ var import_node_fs10 = __toESM(require("fs"));
7502
+ var import_node_path14 = __toESM(require("path"));
7339
7503
  var import_zod2 = require("zod");
7340
7504
  var CONFIG_SNAPSHOT_SCHEMA_VERSION = 1;
7341
7505
  var CONFIG_SNAPSHOT_ENV_KEYS = [
@@ -7558,9 +7722,9 @@ async function runConfigExportCommand(options = {}) {
7558
7722
  const snapshot = buildConfigSnapshot(config, stateStore.listRoomSettings(), options.now ?? /* @__PURE__ */ new Date());
7559
7723
  const serialized = serializeConfigSnapshot(snapshot);
7560
7724
  if (options.outputPath) {
7561
- const targetPath = import_node_path13.default.resolve(cwd, options.outputPath);
7562
- import_node_fs9.default.mkdirSync(import_node_path13.default.dirname(targetPath), { recursive: true });
7563
- import_node_fs9.default.writeFileSync(targetPath, serialized, "utf8");
7725
+ const targetPath = import_node_path14.default.resolve(cwd, options.outputPath);
7726
+ import_node_fs10.default.mkdirSync(import_node_path14.default.dirname(targetPath), { recursive: true });
7727
+ import_node_fs10.default.writeFileSync(targetPath, serialized, "utf8");
7564
7728
  output.write(`Exported config snapshot to ${targetPath}
7565
7729
  `);
7566
7730
  return;
@@ -7574,8 +7738,8 @@ async function runConfigImportCommand(options) {
7574
7738
  const cwd = options.cwd ?? process.cwd();
7575
7739
  const output = options.output ?? process.stdout;
7576
7740
  const actor = options.actor?.trim() || "cli:config-import";
7577
- const sourcePath = import_node_path13.default.resolve(cwd, options.filePath);
7578
- if (!import_node_fs9.default.existsSync(sourcePath)) {
7741
+ const sourcePath = import_node_path14.default.resolve(cwd, options.filePath);
7742
+ if (!import_node_fs10.default.existsSync(sourcePath)) {
7579
7743
  throw new Error(`Config snapshot file not found: ${sourcePath}`);
7580
7744
  }
7581
7745
  const snapshot = parseConfigSnapshot(parseJsonFile(sourcePath));
@@ -7605,7 +7769,7 @@ async function runConfigImportCommand(options) {
7605
7769
  synchronizeRoomSettings(stateStore, normalizedRooms);
7606
7770
  stateStore.appendConfigRevision(
7607
7771
  actor,
7608
- `import config snapshot from ${import_node_path13.default.basename(sourcePath)}`,
7772
+ `import config snapshot from ${import_node_path14.default.basename(sourcePath)}`,
7609
7773
  JSON.stringify({
7610
7774
  type: "config_snapshot_import",
7611
7775
  sourcePath,
@@ -7619,7 +7783,7 @@ async function runConfigImportCommand(options) {
7619
7783
  output.write(
7620
7784
  [
7621
7785
  `Imported config snapshot from ${sourcePath}`,
7622
- `- updated .env in ${import_node_path13.default.resolve(cwd, ".env")}`,
7786
+ `- updated .env in ${import_node_path14.default.resolve(cwd, ".env")}`,
7623
7787
  `- synchronized room settings: ${normalizedRooms.length}`,
7624
7788
  "- restart required: yes (global env settings are restart-scoped)"
7625
7789
  ].join("\n") + "\n"
@@ -7692,7 +7856,7 @@ function buildSnapshotEnv(config) {
7692
7856
  }
7693
7857
  function parseJsonFile(filePath) {
7694
7858
  try {
7695
- const raw = import_node_fs9.default.readFileSync(filePath, "utf8");
7859
+ const raw = import_node_fs10.default.readFileSync(filePath, "utf8");
7696
7860
  return JSON.parse(raw);
7697
7861
  } catch (error) {
7698
7862
  const message = error instanceof Error ? error.message : String(error);
@@ -7704,10 +7868,10 @@ function parseJsonFile(filePath) {
7704
7868
  function normalizeSnapshotEnv(env, cwd) {
7705
7869
  return {
7706
7870
  ...env,
7707
- CODEX_WORKDIR: import_node_path13.default.resolve(cwd, env.CODEX_WORKDIR),
7708
- STATE_DB_PATH: import_node_path13.default.resolve(cwd, env.STATE_DB_PATH),
7709
- STATE_PATH: env.STATE_PATH.trim() ? import_node_path13.default.resolve(cwd, env.STATE_PATH) : "",
7710
- CLI_COMPAT_RECORD_PATH: env.CLI_COMPAT_RECORD_PATH.trim() ? import_node_path13.default.resolve(cwd, env.CLI_COMPAT_RECORD_PATH) : ""
7871
+ CODEX_WORKDIR: import_node_path14.default.resolve(cwd, env.CODEX_WORKDIR),
7872
+ STATE_DB_PATH: import_node_path14.default.resolve(cwd, env.STATE_DB_PATH),
7873
+ STATE_PATH: env.STATE_PATH.trim() ? import_node_path14.default.resolve(cwd, env.STATE_PATH) : "",
7874
+ CLI_COMPAT_RECORD_PATH: env.CLI_COMPAT_RECORD_PATH.trim() ? import_node_path14.default.resolve(cwd, env.CLI_COMPAT_RECORD_PATH) : ""
7711
7875
  };
7712
7876
  }
7713
7877
  function normalizeSnapshotRooms(rooms, cwd) {
@@ -7722,7 +7886,7 @@ function normalizeSnapshotRooms(rooms, cwd) {
7722
7886
  throw new Error(`Duplicate roomId in snapshot: ${roomId}`);
7723
7887
  }
7724
7888
  seen.add(roomId);
7725
- const workdir = import_node_path13.default.resolve(cwd, room.workdir);
7889
+ const workdir = import_node_path14.default.resolve(cwd, room.workdir);
7726
7890
  ensureDirectory2(workdir, `room workdir (${roomId})`);
7727
7891
  normalized.push({
7728
7892
  roomId,
@@ -7749,18 +7913,18 @@ function synchronizeRoomSettings(stateStore, rooms) {
7749
7913
  }
7750
7914
  }
7751
7915
  function persistEnvSnapshot(cwd, env) {
7752
- const envPath = import_node_path13.default.resolve(cwd, ".env");
7753
- const examplePath = import_node_path13.default.resolve(cwd, ".env.example");
7754
- const template = import_node_fs9.default.existsSync(envPath) ? import_node_fs9.default.readFileSync(envPath, "utf8") : import_node_fs9.default.existsSync(examplePath) ? import_node_fs9.default.readFileSync(examplePath, "utf8") : "";
7916
+ const envPath = import_node_path14.default.resolve(cwd, ".env");
7917
+ const examplePath = import_node_path14.default.resolve(cwd, ".env.example");
7918
+ const template = import_node_fs10.default.existsSync(envPath) ? import_node_fs10.default.readFileSync(envPath, "utf8") : import_node_fs10.default.existsSync(examplePath) ? import_node_fs10.default.readFileSync(examplePath, "utf8") : "";
7755
7919
  const overrides = {};
7756
7920
  for (const key of CONFIG_SNAPSHOT_ENV_KEYS) {
7757
7921
  overrides[key] = env[key];
7758
7922
  }
7759
7923
  const next = applyEnvOverrides(template, overrides);
7760
- import_node_fs9.default.writeFileSync(envPath, next, "utf8");
7924
+ import_node_fs10.default.writeFileSync(envPath, next, "utf8");
7761
7925
  }
7762
7926
  function ensureDirectory2(dirPath, label) {
7763
- if (!import_node_fs9.default.existsSync(dirPath) || !import_node_fs9.default.statSync(dirPath).isDirectory()) {
7927
+ if (!import_node_fs10.default.existsSync(dirPath) || !import_node_fs10.default.statSync(dirPath).isDirectory()) {
7764
7928
  throw new Error(`${label} does not exist or is not a directory: ${dirPath}`);
7765
7929
  }
7766
7930
  }
@@ -7834,8 +7998,8 @@ function jsonArrayStringSchema(key, allowEmpty) {
7834
7998
 
7835
7999
  // src/preflight.ts
7836
8000
  var import_node_child_process7 = require("child_process");
7837
- var import_node_fs10 = __toESM(require("fs"));
7838
- var import_node_path14 = __toESM(require("path"));
8001
+ var import_node_fs11 = __toESM(require("fs"));
8002
+ var import_node_path15 = __toESM(require("path"));
7839
8003
  var import_node_util5 = require("util");
7840
8004
  var execFileAsync4 = (0, import_node_util5.promisify)(import_node_child_process7.execFile);
7841
8005
  var REQUIRED_ENV_KEYS = ["MATRIX_HOMESERVER", "MATRIX_USER_ID", "MATRIX_ACCESS_TOKEN"];
@@ -7843,10 +8007,10 @@ async function runStartupPreflight(options = {}) {
7843
8007
  const env = options.env ?? process.env;
7844
8008
  const cwd = options.cwd ?? process.cwd();
7845
8009
  const checkCodexBinary = options.checkCodexBinary ?? defaultCheckCodexBinary;
7846
- const fileExists = options.fileExists ?? import_node_fs10.default.existsSync;
8010
+ const fileExists = options.fileExists ?? import_node_fs11.default.existsSync;
7847
8011
  const isDirectory = options.isDirectory ?? defaultIsDirectory;
7848
8012
  const issues = [];
7849
- const envPath = import_node_path14.default.resolve(cwd, ".env");
8013
+ const envPath = import_node_path15.default.resolve(cwd, ".env");
7850
8014
  let resolvedCodexBin = null;
7851
8015
  let usedCodexFallback = false;
7852
8016
  if (!fileExists(envPath)) {
@@ -7923,7 +8087,7 @@ async function runStartupPreflight(options = {}) {
7923
8087
  }
7924
8088
  }
7925
8089
  const configuredWorkdir = readEnv(env, "CODEX_WORKDIR");
7926
- const workdir = import_node_path14.default.resolve(cwd, configuredWorkdir || cwd);
8090
+ const workdir = import_node_path15.default.resolve(cwd, configuredWorkdir || cwd);
7927
8091
  if (!fileExists(workdir) || !isDirectory(workdir)) {
7928
8092
  issues.push({
7929
8093
  level: "error",
@@ -7964,7 +8128,7 @@ async function defaultCheckCodexBinary(bin) {
7964
8128
  }
7965
8129
  function defaultIsDirectory(targetPath) {
7966
8130
  try {
7967
- return import_node_fs10.default.statSync(targetPath).isDirectory();
8131
+ return import_node_fs11.default.statSync(targetPath).isDirectory();
7968
8132
  } catch {
7969
8133
  return false;
7970
8134
  }
@@ -8223,7 +8387,7 @@ function ensureRuntimeHomeOrExit() {
8223
8387
  }
8224
8388
  const home = resolveRuntimeHome();
8225
8389
  try {
8226
- import_node_fs11.default.mkdirSync(home, { recursive: true });
8390
+ import_node_fs12.default.mkdirSync(home, { recursive: true });
8227
8391
  } catch (error) {
8228
8392
  const message = error instanceof Error ? error.message : String(error);
8229
8393
  process.stderr.write(`Runtime setup failed: cannot create ${home}. ${message}
@@ -8238,7 +8402,7 @@ function ensureRuntimeHomeOrExit() {
8238
8402
  `);
8239
8403
  process.exit(1);
8240
8404
  }
8241
- loadEnvFromFile(import_node_path15.default.resolve(home, ".env"));
8405
+ loadEnvFromFile(import_node_path16.default.resolve(home, ".env"));
8242
8406
  runtimeHome = home;
8243
8407
  return runtimeHome;
8244
8408
  }
@@ -8253,8 +8417,8 @@ function parsePortOption(raw, fallback) {
8253
8417
  }
8254
8418
  function resolveCliVersion() {
8255
8419
  try {
8256
- const packagePath = import_node_path15.default.resolve(__dirname, "..", "package.json");
8257
- const content = import_node_fs11.default.readFileSync(packagePath, "utf8");
8420
+ const packagePath = import_node_path16.default.resolve(__dirname, "..", "package.json");
8421
+ const content = import_node_fs12.default.readFileSync(packagePath, "utf8");
8258
8422
  const parsed = JSON.parse(content);
8259
8423
  return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version : "0.0.0";
8260
8424
  } catch {
@@ -8264,9 +8428,9 @@ function resolveCliVersion() {
8264
8428
  function resolveCliScriptPath() {
8265
8429
  const argvPath = process.argv[1];
8266
8430
  if (argvPath && argvPath.trim()) {
8267
- return import_node_path15.default.resolve(argvPath);
8431
+ return import_node_path16.default.resolve(argvPath);
8268
8432
  }
8269
- return import_node_path15.default.resolve(__dirname, "cli.js");
8433
+ return import_node_path16.default.resolve(__dirname, "cli.js");
8270
8434
  }
8271
8435
  function maybeReexecServiceCommandWithSudo() {
8272
8436
  if (typeof process.getuid !== "function" || process.getuid() === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeharbor",
3
- "version": "0.1.24",
3
+ "version": "0.1.25",
4
4
  "description": "Instant-messaging bridge for Codex CLI sessions",
5
5
  "license": "MIT",
6
6
  "main": "dist/cli.js",