codeharbor 0.1.23 → 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.
- package/README.md +5 -1
- package/dist/cli.js +261 -51
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -200,6 +200,8 @@ PLAYWRIGHT_USE_SYSTEM_CHROME=false npm run test:e2e
|
|
|
200
200
|
|
|
201
201
|
- `REQUIREMENTS.md`: current baseline + next-stage requirements
|
|
202
202
|
- `TASK_LIST.md`: implementation task breakdown and status
|
|
203
|
+
- `docs/USER_MANUAL_ZH.md`: Chinese user manual (installation, configuration, verification)
|
|
204
|
+
- `docs/COMPLETE_CONFIGURATION_GUIDE.md`: end-to-end setup flow + full feature-to-config mapping
|
|
203
205
|
- `docs/CONFIG_UI_DESIGN.md`: configuration UI MVP design
|
|
204
206
|
- `docs/CONFIG_CATALOG.md`: consolidated configuration matrix (required/runtime/UI/effective timing)
|
|
205
207
|
- `docs/ADMIN_STANDALONE_DEPLOY.md`: standalone admin deployment and Cloudflare Tunnel exposure guide
|
|
@@ -249,6 +251,7 @@ node dist/cli.js start
|
|
|
249
251
|
Use this layered reference to avoid mixing boot-only and runtime tuning items:
|
|
250
252
|
|
|
251
253
|
- [`docs/CONFIG_CATALOG.md`](docs/CONFIG_CATALOG.md)
|
|
254
|
+
- [`docs/COMPLETE_CONFIGURATION_GUIDE.md`](docs/COMPLETE_CONFIGURATION_GUIDE.md)
|
|
252
255
|
|
|
253
256
|
It documents:
|
|
254
257
|
|
|
@@ -256,6 +259,7 @@ It documents:
|
|
|
256
259
|
- which keys can be edited in Admin UI
|
|
257
260
|
- whether changes are immediate or restart-scoped
|
|
258
261
|
- recommended profiles for local/internal/public deployment
|
|
262
|
+
- a complete setup sequence from install to production operations
|
|
259
263
|
|
|
260
264
|
## Commands
|
|
261
265
|
|
|
@@ -418,7 +422,7 @@ If any check fails, it prints actionable fix commands (for example `codeharbor i
|
|
|
418
422
|
- each accepted request activates the sender's conversation in that room
|
|
419
423
|
- activation TTL: `SESSION_ACTIVE_WINDOW_MINUTES` (default: `20`)
|
|
420
424
|
- Control commands
|
|
421
|
-
- `/status` show session + limiter + metrics + runtime worker status
|
|
425
|
+
- `/status` show session + limiter + metrics + runtime worker status, current version, and update hint
|
|
422
426
|
- `/reset` clear bound Codex session and keep conversation active
|
|
423
427
|
- `/stop` cancel in-flight execution (if running) and reset session context
|
|
424
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
|
|
29
|
-
var
|
|
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
|
|
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 =
|
|
4870
|
-
const taskListPath =
|
|
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
|
|
6470
|
-
var
|
|
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
|
-
|
|
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 || !
|
|
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 =
|
|
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
|
|
7019
|
-
var
|
|
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:
|
|
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:
|
|
7103
|
-
legacyStateJsonPath: v.STATE_PATH.trim() ?
|
|
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() ?
|
|
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 =
|
|
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
|
-
|
|
7332
|
+
import_node_fs9.default.mkdirSync(import_node_path13.default.dirname(parsed.data.stateDbPath), { recursive: true });
|
|
7169
7333
|
if (parsed.data.legacyStateJsonPath) {
|
|
7170
|
-
|
|
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
|
|
7338
|
-
var
|
|
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 =
|
|
7562
|
-
|
|
7563
|
-
|
|
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 =
|
|
7578
|
-
if (!
|
|
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 ${
|
|
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 ${
|
|
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 =
|
|
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:
|
|
7708
|
-
STATE_DB_PATH:
|
|
7709
|
-
STATE_PATH: env.STATE_PATH.trim() ?
|
|
7710
|
-
CLI_COMPAT_RECORD_PATH: env.CLI_COMPAT_RECORD_PATH.trim() ?
|
|
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 =
|
|
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 =
|
|
7753
|
-
const examplePath =
|
|
7754
|
-
const template =
|
|
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
|
-
|
|
7924
|
+
import_node_fs10.default.writeFileSync(envPath, next, "utf8");
|
|
7761
7925
|
}
|
|
7762
7926
|
function ensureDirectory2(dirPath, label) {
|
|
7763
|
-
if (!
|
|
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
|
|
7838
|
-
var
|
|
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 ??
|
|
8010
|
+
const fileExists = options.fileExists ?? import_node_fs11.default.existsSync;
|
|
7847
8011
|
const isDirectory = options.isDirectory ?? defaultIsDirectory;
|
|
7848
8012
|
const issues = [];
|
|
7849
|
-
const envPath =
|
|
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 =
|
|
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
|
|
8131
|
+
return import_node_fs11.default.statSync(targetPath).isDirectory();
|
|
7968
8132
|
} catch {
|
|
7969
8133
|
return false;
|
|
7970
8134
|
}
|
|
@@ -7978,6 +8142,25 @@ var runtimeHome = null;
|
|
|
7978
8142
|
var cliVersion = resolveCliVersion();
|
|
7979
8143
|
var program = new import_commander.Command();
|
|
7980
8144
|
program.name("codeharbor").description("Instant-messaging bridge for Codex CLI sessions").version(cliVersion);
|
|
8145
|
+
program.addHelpText(
|
|
8146
|
+
"after",
|
|
8147
|
+
[
|
|
8148
|
+
"",
|
|
8149
|
+
"Prerequisites:",
|
|
8150
|
+
" - codex CLI installed and authenticated (run: codex login)",
|
|
8151
|
+
" - Matrix bot credentials in .env: MATRIX_HOMESERVER, MATRIX_USER_ID, MATRIX_ACCESS_TOKEN",
|
|
8152
|
+
"",
|
|
8153
|
+
"Runtime:",
|
|
8154
|
+
" - default CODEHARBOR_HOME: ~/.codeharbor (legacy /opt/codeharbor/.env is auto-detected)",
|
|
8155
|
+
" - running without subcommand defaults to: codeharbor start",
|
|
8156
|
+
"",
|
|
8157
|
+
"Common commands:",
|
|
8158
|
+
" - codeharbor init",
|
|
8159
|
+
" - codeharbor doctor",
|
|
8160
|
+
" - codeharbor service install --with-admin",
|
|
8161
|
+
" - codeharbor admin serve --host 127.0.0.1 --port 8787"
|
|
8162
|
+
].join("\n")
|
|
8163
|
+
);
|
|
7981
8164
|
program.command("init").description("Create or update .env via guided prompts").option("-f, --force", "overwrite existing .env without confirmation").action(async (options) => {
|
|
7982
8165
|
const home = ensureRuntimeHomeOrExit();
|
|
7983
8166
|
await runInitCommand({ force: options.force ?? false, cwd: home });
|
|
@@ -8014,6 +8197,33 @@ program.command("doctor").description("Check codex and matrix connectivity").act
|
|
|
8014
8197
|
var admin = program.command("admin").description("Admin utilities");
|
|
8015
8198
|
var configCommand = program.command("config").description("Config snapshot utilities");
|
|
8016
8199
|
var serviceCommand = program.command("service").description("Systemd service management");
|
|
8200
|
+
admin.addHelpText(
|
|
8201
|
+
"after",
|
|
8202
|
+
[
|
|
8203
|
+
"",
|
|
8204
|
+
"Notes:",
|
|
8205
|
+
" - For non-loopback host binding, set ADMIN_TOKEN or ADMIN_TOKENS_JSON.",
|
|
8206
|
+
" - Admin UI routes: /settings/global, /settings/rooms, /health, /audit."
|
|
8207
|
+
].join("\n")
|
|
8208
|
+
);
|
|
8209
|
+
configCommand.addHelpText(
|
|
8210
|
+
"after",
|
|
8211
|
+
[
|
|
8212
|
+
"",
|
|
8213
|
+
"Examples:",
|
|
8214
|
+
" - codeharbor config export -o backup.json",
|
|
8215
|
+
" - codeharbor config import backup.json --dry-run"
|
|
8216
|
+
].join("\n")
|
|
8217
|
+
);
|
|
8218
|
+
serviceCommand.addHelpText(
|
|
8219
|
+
"after",
|
|
8220
|
+
[
|
|
8221
|
+
"",
|
|
8222
|
+
"Notes:",
|
|
8223
|
+
" - Service subcommands auto-elevate with sudo when required.",
|
|
8224
|
+
" - Use --with-admin to manage both main and admin services together."
|
|
8225
|
+
].join("\n")
|
|
8226
|
+
);
|
|
8017
8227
|
admin.command("serve").description("Start admin config API server").option("--host <host>", "override admin bind host").option("--port <port>", "override admin bind port").option(
|
|
8018
8228
|
"--allow-insecure-no-token",
|
|
8019
8229
|
"allow serving admin API without ADMIN_TOKEN on non-loopback host (not recommended)"
|
|
@@ -8177,7 +8387,7 @@ function ensureRuntimeHomeOrExit() {
|
|
|
8177
8387
|
}
|
|
8178
8388
|
const home = resolveRuntimeHome();
|
|
8179
8389
|
try {
|
|
8180
|
-
|
|
8390
|
+
import_node_fs12.default.mkdirSync(home, { recursive: true });
|
|
8181
8391
|
} catch (error) {
|
|
8182
8392
|
const message = error instanceof Error ? error.message : String(error);
|
|
8183
8393
|
process.stderr.write(`Runtime setup failed: cannot create ${home}. ${message}
|
|
@@ -8192,7 +8402,7 @@ function ensureRuntimeHomeOrExit() {
|
|
|
8192
8402
|
`);
|
|
8193
8403
|
process.exit(1);
|
|
8194
8404
|
}
|
|
8195
|
-
loadEnvFromFile(
|
|
8405
|
+
loadEnvFromFile(import_node_path16.default.resolve(home, ".env"));
|
|
8196
8406
|
runtimeHome = home;
|
|
8197
8407
|
return runtimeHome;
|
|
8198
8408
|
}
|
|
@@ -8207,8 +8417,8 @@ function parsePortOption(raw, fallback) {
|
|
|
8207
8417
|
}
|
|
8208
8418
|
function resolveCliVersion() {
|
|
8209
8419
|
try {
|
|
8210
|
-
const packagePath =
|
|
8211
|
-
const content =
|
|
8420
|
+
const packagePath = import_node_path16.default.resolve(__dirname, "..", "package.json");
|
|
8421
|
+
const content = import_node_fs12.default.readFileSync(packagePath, "utf8");
|
|
8212
8422
|
const parsed = JSON.parse(content);
|
|
8213
8423
|
return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version : "0.0.0";
|
|
8214
8424
|
} catch {
|
|
@@ -8218,9 +8428,9 @@ function resolveCliVersion() {
|
|
|
8218
8428
|
function resolveCliScriptPath() {
|
|
8219
8429
|
const argvPath = process.argv[1];
|
|
8220
8430
|
if (argvPath && argvPath.trim()) {
|
|
8221
|
-
return
|
|
8431
|
+
return import_node_path16.default.resolve(argvPath);
|
|
8222
8432
|
}
|
|
8223
|
-
return
|
|
8433
|
+
return import_node_path16.default.resolve(__dirname, "cli.js");
|
|
8224
8434
|
}
|
|
8225
8435
|
function maybeReexecServiceCommandWithSudo() {
|
|
8226
8436
|
if (typeof process.getuid !== "function" || process.getuid() === 0) {
|