@askexenow/exe-os 0.9.69 → 0.9.71
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/deploy/stack-manifests/v0.9.json +96 -16
- package/dist/bin/agentic-ontology-backfill.js +33 -0
- package/dist/bin/agentic-reflection-backfill.js +33 -0
- package/dist/bin/agentic-semantic-label.js +33 -0
- package/dist/bin/backfill-conversations.js +33 -0
- package/dist/bin/backfill-responses.js +33 -0
- package/dist/bin/backfill-vectors.js +33 -0
- package/dist/bin/bulk-sync-postgres.js +33 -0
- package/dist/bin/cleanup-stale-review-tasks.js +33 -0
- package/dist/bin/cli.js +1284 -178
- package/dist/bin/exe-agent.js +6 -0
- package/dist/bin/exe-assign.js +33 -0
- package/dist/bin/exe-boot.js +33 -0
- package/dist/bin/exe-call.js +6 -0
- package/dist/bin/exe-cloud.js +33 -0
- package/dist/bin/exe-dispatch.js +33 -0
- package/dist/bin/exe-doctor.js +33 -0
- package/dist/bin/exe-export-behaviors.js +33 -0
- package/dist/bin/exe-forget.js +33 -0
- package/dist/bin/exe-gateway.js +178 -110
- package/dist/bin/exe-heartbeat.js +33 -0
- package/dist/bin/exe-kill.js +33 -0
- package/dist/bin/exe-launch-agent.js +33 -0
- package/dist/bin/exe-new-employee.js +6 -0
- package/dist/bin/exe-pending-messages.js +33 -0
- package/dist/bin/exe-pending-notifications.js +33 -0
- package/dist/bin/exe-pending-reviews.js +33 -0
- package/dist/bin/exe-rename.js +40 -4
- package/dist/bin/exe-review.js +33 -0
- package/dist/bin/exe-search.js +33 -0
- package/dist/bin/exe-session-cleanup.js +33 -0
- package/dist/bin/exe-start-codex.js +33 -0
- package/dist/bin/exe-start-opencode.js +33 -0
- package/dist/bin/exe-status.js +33 -0
- package/dist/bin/exe-team.js +33 -0
- package/dist/bin/git-sweep.js +33 -0
- package/dist/bin/graph-backfill.js +177 -110
- package/dist/bin/graph-export.js +33 -0
- package/dist/bin/intercom-check.js +33 -0
- package/dist/bin/registry-proxy.js +207 -0
- package/dist/bin/scan-tasks.js +33 -0
- package/dist/bin/setup.js +33 -0
- package/dist/bin/shard-migrate.js +33 -0
- package/dist/bin/stack-update.js +128 -0
- package/dist/gateway/index.js +178 -110
- package/dist/hooks/bug-report-worker.js +33 -0
- package/dist/hooks/codex-stop-task-finalizer.js +33 -0
- package/dist/hooks/commit-complete.js +33 -0
- package/dist/hooks/error-recall.js +33 -0
- package/dist/hooks/ingest.js +33 -0
- package/dist/hooks/instructions-loaded.js +33 -0
- package/dist/hooks/notification.js +33 -0
- package/dist/hooks/post-compact.js +33 -0
- package/dist/hooks/post-tool-combined.js +698 -17
- package/dist/hooks/pre-compact.js +33 -0
- package/dist/hooks/pre-tool-use.js +33 -0
- package/dist/hooks/prompt-submit.js +314 -0
- package/dist/hooks/session-end.js +33 -0
- package/dist/hooks/session-start.js +33 -0
- package/dist/hooks/stop.js +279 -12
- package/dist/hooks/subagent-stop.js +33 -0
- package/dist/hooks/summary-worker.js +33 -0
- package/dist/index.js +178 -110
- package/dist/lib/cloud-sync.js +27 -0
- package/dist/lib/database.js +27 -0
- package/dist/lib/db.js +27 -0
- package/dist/lib/device-registry.js +27 -0
- package/dist/lib/employee-templates.js +6 -0
- package/dist/lib/exe-daemon.js +639 -259
- package/dist/lib/hybrid-search.js +33 -0
- package/dist/lib/registry-proxy.js +162 -0
- package/dist/lib/schedules.js +33 -0
- package/dist/lib/store.js +33 -0
- package/dist/mcp/server.js +561 -244
- package/dist/runtime/index.js +33 -0
- package/dist/tui/App.js +33 -0
- package/package.json +3 -2
- package/stack.release.json +6 -4
- package/stack.release.schema.json +89 -18
package/dist/bin/cli.js
CHANGED
|
@@ -2391,12 +2391,12 @@ function enforceBackgroundJobGuardrails(options = {}) {
|
|
|
2391
2391
|
const jobs = readJobsRaw();
|
|
2392
2392
|
const actions = [];
|
|
2393
2393
|
let changed = false;
|
|
2394
|
-
const
|
|
2394
|
+
const ts2 = now();
|
|
2395
2395
|
for (const job of jobs) {
|
|
2396
2396
|
if (job.status !== "running") continue;
|
|
2397
2397
|
if (!isAlive(job.pid)) {
|
|
2398
2398
|
job.status = "stale";
|
|
2399
|
-
job.updatedAt =
|
|
2399
|
+
job.updatedAt = ts2;
|
|
2400
2400
|
actions.push({ jobId: job.id, name: job.name, pid: job.pid, action: "marked-stale" });
|
|
2401
2401
|
changed = true;
|
|
2402
2402
|
continue;
|
|
@@ -2404,7 +2404,7 @@ function enforceBackgroundJobGuardrails(options = {}) {
|
|
|
2404
2404
|
const heartbeatAge = Date.now() - Date.parse(job.lastHeartbeatAt || job.updatedAt || job.startedAt);
|
|
2405
2405
|
if (Number.isFinite(heartbeatAge) && heartbeatAge > staleAfter) {
|
|
2406
2406
|
job.status = "stale";
|
|
2407
|
-
job.updatedAt =
|
|
2407
|
+
job.updatedAt = ts2;
|
|
2408
2408
|
actions.push({ jobId: job.id, name: job.name, pid: job.pid, action: "marked-stale" });
|
|
2409
2409
|
changed = true;
|
|
2410
2410
|
continue;
|
|
@@ -2420,7 +2420,7 @@ function enforceBackgroundJobGuardrails(options = {}) {
|
|
|
2420
2420
|
} catch {
|
|
2421
2421
|
}
|
|
2422
2422
|
job.status = "cancelled";
|
|
2423
|
-
job.updatedAt =
|
|
2423
|
+
job.updatedAt = ts2;
|
|
2424
2424
|
job.progressLabel = `Cancelled by heat guardrail after sustained high CPU (${cpu.toFixed(1)}%).`;
|
|
2425
2425
|
releaseJobLock(job.type);
|
|
2426
2426
|
actions.push({ jobId: job.id, name: job.name, pid: job.pid, action: "cancelled-hot", cpuPercent: cpu, heatStrikes: job.heatStrikes });
|
|
@@ -2432,7 +2432,7 @@ function enforceBackgroundJobGuardrails(options = {}) {
|
|
|
2432
2432
|
}
|
|
2433
2433
|
if (cpu >= reniceAt) {
|
|
2434
2434
|
renicePid(job.pid, 15);
|
|
2435
|
-
job.updatedAt =
|
|
2435
|
+
job.updatedAt = ts2;
|
|
2436
2436
|
job.progressLabel = `Throttled by heat guardrail (${cpu.toFixed(1)}% CPU).`;
|
|
2437
2437
|
actions.push({ jobId: job.id, name: job.name, pid: job.pid, action: "reniced", cpuPercent: cpu, heatStrikes: job.heatStrikes });
|
|
2438
2438
|
changed = true;
|
|
@@ -2600,7 +2600,7 @@ async function tryKeytar() {
|
|
|
2600
2600
|
}
|
|
2601
2601
|
function deriveMachineKey() {
|
|
2602
2602
|
try {
|
|
2603
|
-
const
|
|
2603
|
+
const crypto15 = __require("crypto");
|
|
2604
2604
|
const material = [
|
|
2605
2605
|
os8.hostname(),
|
|
2606
2606
|
os8.userInfo().username,
|
|
@@ -2609,23 +2609,23 @@ function deriveMachineKey() {
|
|
|
2609
2609
|
// Machine ID on Linux (stable across reboots)
|
|
2610
2610
|
process.platform === "linux" ? readMachineId() : ""
|
|
2611
2611
|
].join("|");
|
|
2612
|
-
return
|
|
2612
|
+
return crypto15.createHash("sha256").update(material).digest();
|
|
2613
2613
|
} catch {
|
|
2614
2614
|
return null;
|
|
2615
2615
|
}
|
|
2616
2616
|
}
|
|
2617
2617
|
function readMachineId() {
|
|
2618
2618
|
try {
|
|
2619
|
-
const { readFileSync:
|
|
2620
|
-
return
|
|
2619
|
+
const { readFileSync: readFileSync36 } = __require("fs");
|
|
2620
|
+
return readFileSync36("/etc/machine-id", "utf-8").trim();
|
|
2621
2621
|
} catch {
|
|
2622
2622
|
return "";
|
|
2623
2623
|
}
|
|
2624
2624
|
}
|
|
2625
2625
|
function encryptWithMachineKey(plaintext, machineKey) {
|
|
2626
|
-
const
|
|
2627
|
-
const iv =
|
|
2628
|
-
const cipher =
|
|
2626
|
+
const crypto15 = __require("crypto");
|
|
2627
|
+
const iv = crypto15.randomBytes(12);
|
|
2628
|
+
const cipher = crypto15.createCipheriv("aes-256-gcm", machineKey, iv);
|
|
2629
2629
|
let encrypted = cipher.update(plaintext, "utf-8", "base64");
|
|
2630
2630
|
encrypted += cipher.final("base64");
|
|
2631
2631
|
const authTag = cipher.getAuthTag().toString("base64");
|
|
@@ -2634,13 +2634,13 @@ function encryptWithMachineKey(plaintext, machineKey) {
|
|
|
2634
2634
|
function decryptWithMachineKey(encrypted, machineKey) {
|
|
2635
2635
|
if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
|
|
2636
2636
|
try {
|
|
2637
|
-
const
|
|
2637
|
+
const crypto15 = __require("crypto");
|
|
2638
2638
|
const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
|
|
2639
2639
|
if (parts.length !== 3) return null;
|
|
2640
2640
|
const [ivB64, tagB64, cipherB64] = parts;
|
|
2641
2641
|
const iv = Buffer.from(ivB64, "base64");
|
|
2642
2642
|
const authTag = Buffer.from(tagB64, "base64");
|
|
2643
|
-
const decipher =
|
|
2643
|
+
const decipher = crypto15.createDecipheriv("aes-256-gcm", machineKey, iv);
|
|
2644
2644
|
decipher.setAuthTag(authTag);
|
|
2645
2645
|
let decrypted = decipher.update(cipherB64, "base64", "utf-8");
|
|
2646
2646
|
decrypted += decipher.final("utf-8");
|
|
@@ -5043,6 +5043,33 @@ async function ensureSchema() {
|
|
|
5043
5043
|
CREATE INDEX IF NOT EXISTS idx_chat_history_session
|
|
5044
5044
|
ON chat_history(session_id, id);
|
|
5045
5045
|
`);
|
|
5046
|
+
await client.executeMultiple(`
|
|
5047
|
+
CREATE TABLE IF NOT EXISTS session_events (
|
|
5048
|
+
id TEXT PRIMARY KEY,
|
|
5049
|
+
agent_id TEXT NOT NULL,
|
|
5050
|
+
agent_role TEXT NOT NULL,
|
|
5051
|
+
session_id TEXT NOT NULL,
|
|
5052
|
+
session_scope TEXT,
|
|
5053
|
+
project_name TEXT NOT NULL,
|
|
5054
|
+
event_index INTEGER NOT NULL,
|
|
5055
|
+
event_type TEXT NOT NULL,
|
|
5056
|
+
tool_name TEXT,
|
|
5057
|
+
tool_use_id TEXT,
|
|
5058
|
+
content TEXT NOT NULL,
|
|
5059
|
+
payload_json TEXT,
|
|
5060
|
+
has_error INTEGER NOT NULL DEFAULT 0,
|
|
5061
|
+
created_at TEXT NOT NULL
|
|
5062
|
+
);
|
|
5063
|
+
|
|
5064
|
+
CREATE INDEX IF NOT EXISTS idx_session_events_agent_time
|
|
5065
|
+
ON session_events(agent_id, created_at DESC);
|
|
5066
|
+
|
|
5067
|
+
CREATE INDEX IF NOT EXISTS idx_session_events_session_index
|
|
5068
|
+
ON session_events(session_id, event_index);
|
|
5069
|
+
|
|
5070
|
+
CREATE INDEX IF NOT EXISTS idx_session_events_scope_agent_time
|
|
5071
|
+
ON session_events(session_scope, agent_id, created_at DESC);
|
|
5072
|
+
`);
|
|
5046
5073
|
await client.executeMultiple(`
|
|
5047
5074
|
CREATE TABLE IF NOT EXISTS workspaces (
|
|
5048
5075
|
id TEXT PRIMARY KEY,
|
|
@@ -6044,8 +6071,8 @@ async function validateLicense(apiKey, deviceId) {
|
|
|
6044
6071
|
}
|
|
6045
6072
|
function getCacheAgeMs() {
|
|
6046
6073
|
try {
|
|
6047
|
-
const { statSync:
|
|
6048
|
-
const s =
|
|
6074
|
+
const { statSync: statSync8 } = __require("fs");
|
|
6075
|
+
const s = statSync8(CACHE_PATH);
|
|
6049
6076
|
return Date.now() - s.mtimeMs;
|
|
6050
6077
|
} catch {
|
|
6051
6078
|
return Infinity;
|
|
@@ -6732,8 +6759,8 @@ async function withRosterLock(fn) {
|
|
|
6732
6759
|
} catch (err) {
|
|
6733
6760
|
if (err.code === "EEXIST") {
|
|
6734
6761
|
try {
|
|
6735
|
-
const
|
|
6736
|
-
if (Date.now() -
|
|
6762
|
+
const ts2 = parseInt(readFileSync12(ROSTER_LOCK_PATH, "utf-8"), 10);
|
|
6763
|
+
if (Date.now() - ts2 < LOCK_STALE_MS) {
|
|
6737
6764
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
6738
6765
|
}
|
|
6739
6766
|
unlinkSync6(ROSTER_LOCK_PATH);
|
|
@@ -8657,6 +8684,12 @@ var init_platform_procedures = __esm({
|
|
|
8657
8684
|
priority: "p0",
|
|
8658
8685
|
content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
|
|
8659
8686
|
},
|
|
8687
|
+
{
|
|
8688
|
+
title: "Code context first for repository orientation",
|
|
8689
|
+
domain: "workflow",
|
|
8690
|
+
priority: "p1",
|
|
8691
|
+
content: "Before broad repo exploration, symbol tracing, blast-radius review, or codebase Q&A, agents should use the consolidated code_context MCP tool instead of manual grep/read loops. Use action=index or stats to refresh/check the index; action=search with query, limit, offset, languages, paths, refresh_index for fresh multi-language code/doc search; action=trace for symbol imports/dependents; action=blast_radius for impact analysis before edits. CLI parity exists via exe-os code-context init|index|status|stats|search|doctor. Keep code_context separate from durable employee memory: promote only validated decisions, procedures, or lessons into store_memory/commit_memory."
|
|
8692
|
+
},
|
|
8660
8693
|
{
|
|
8661
8694
|
title: "Commit discipline \u2014 never leave verified work floating",
|
|
8662
8695
|
domain: "workflow",
|
|
@@ -11085,10 +11118,10 @@ async function parseConversation(filePath) {
|
|
|
11085
11118
|
if (entry.cwd && typeof entry.cwd === "string") {
|
|
11086
11119
|
conv.cwd = entry.cwd;
|
|
11087
11120
|
}
|
|
11088
|
-
const
|
|
11089
|
-
if (
|
|
11090
|
-
if (!conv.startTime ||
|
|
11091
|
-
if (!conv.endTime ||
|
|
11121
|
+
const ts2 = entry.timestamp;
|
|
11122
|
+
if (ts2) {
|
|
11123
|
+
if (!conv.startTime || ts2 < conv.startTime) conv.startTime = ts2;
|
|
11124
|
+
if (!conv.endTime || ts2 > conv.endTime) conv.endTime = ts2;
|
|
11092
11125
|
}
|
|
11093
11126
|
const entryType = entry.type;
|
|
11094
11127
|
if (entryType === "user") {
|
|
@@ -11774,9 +11807,9 @@ Unclassified: ${unclassified}
|
|
|
11774
11807
|
}
|
|
11775
11808
|
async function exportBatches(options) {
|
|
11776
11809
|
const fs8 = await import("fs");
|
|
11777
|
-
const
|
|
11810
|
+
const path55 = await import("path");
|
|
11778
11811
|
const client = getClient();
|
|
11779
|
-
const outDir =
|
|
11812
|
+
const outDir = path55.join(process.cwd(), "exe/output/classifications/input");
|
|
11780
11813
|
fs8.mkdirSync(outDir, { recursive: true });
|
|
11781
11814
|
const countResult = await client.execute({
|
|
11782
11815
|
sql: "SELECT COUNT(*) as cnt FROM memories WHERE intent IS NULL AND outcome IS NULL AND domain IS NULL",
|
|
@@ -11800,7 +11833,7 @@ async function exportBatches(options) {
|
|
|
11800
11833
|
const text = String(row.text || "").replace(/\n/g, " ");
|
|
11801
11834
|
return JSON.stringify({ id: row.id, text });
|
|
11802
11835
|
});
|
|
11803
|
-
const batchFile =
|
|
11836
|
+
const batchFile = path55.join(outDir, `batch-${String(batchNum).padStart(4, "0")}.jsonl`);
|
|
11804
11837
|
fs8.writeFileSync(batchFile, lines.join("\n") + "\n");
|
|
11805
11838
|
exported += batch.rows.length;
|
|
11806
11839
|
offset += options.batchSize;
|
|
@@ -11816,7 +11849,7 @@ async function exportBatches(options) {
|
|
|
11816
11849
|
}
|
|
11817
11850
|
async function importClassifications(importDir) {
|
|
11818
11851
|
const fs8 = await import("fs");
|
|
11819
|
-
const
|
|
11852
|
+
const path55 = await import("path");
|
|
11820
11853
|
const client = getClient();
|
|
11821
11854
|
const files = fs8.readdirSync(importDir).filter((f) => f.endsWith(".jsonl")).sort();
|
|
11822
11855
|
process.stderr.write(`[backfill-metadata] Found ${files.length} JSONL files to import from ${importDir}
|
|
@@ -11824,7 +11857,7 @@ async function importClassifications(importDir) {
|
|
|
11824
11857
|
let imported = 0;
|
|
11825
11858
|
let invalid = 0;
|
|
11826
11859
|
for (const file of files) {
|
|
11827
|
-
const lines = fs8.readFileSync(
|
|
11860
|
+
const lines = fs8.readFileSync(path55.join(importDir, file), "utf-8").split("\n").filter(Boolean);
|
|
11828
11861
|
for (const line of lines) {
|
|
11829
11862
|
try {
|
|
11830
11863
|
const rec = JSON.parse(line);
|
|
@@ -14462,10 +14495,10 @@ async function disposeEmbedder() {
|
|
|
14462
14495
|
async function embedDirect(text) {
|
|
14463
14496
|
const llamaCpp = await import("node-llama-cpp");
|
|
14464
14497
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
14465
|
-
const { existsSync:
|
|
14466
|
-
const
|
|
14467
|
-
const modelPath =
|
|
14468
|
-
if (!
|
|
14498
|
+
const { existsSync: existsSync41 } = await import("fs");
|
|
14499
|
+
const path55 = await import("path");
|
|
14500
|
+
const modelPath = path55.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
14501
|
+
if (!existsSync41(modelPath)) {
|
|
14469
14502
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
14470
14503
|
}
|
|
14471
14504
|
const llama = await llamaCpp.getLlama();
|
|
@@ -17279,10 +17312,7 @@ async function renameEmployee(oldName, newName, opts = {}) {
|
|
|
17279
17312
|
const newIdentityPath = path35.join(identityDir, `${newName}.md`);
|
|
17280
17313
|
if (existsSync29(oldIdentityPath)) {
|
|
17281
17314
|
const content = readFileSync24(oldIdentityPath, "utf-8");
|
|
17282
|
-
const updatedContent = content
|
|
17283
|
-
/^(agent_id:\s*)\S+/m,
|
|
17284
|
-
`$1${newName}`
|
|
17285
|
-
);
|
|
17315
|
+
const updatedContent = rewriteRenamedEmployeeContent(content, rosterOldName, newName);
|
|
17286
17316
|
renameSync6(oldIdentityPath, newIdentityPath);
|
|
17287
17317
|
writeFileSync21(newIdentityPath, updatedContent, "utf-8");
|
|
17288
17318
|
rollbackStack.push({
|
|
@@ -17299,7 +17329,9 @@ async function renameEmployee(oldName, newName, opts = {}) {
|
|
|
17299
17329
|
const newAgentPath = path35.join(agentsDir, `${newName}.md`);
|
|
17300
17330
|
if (existsSync29(oldAgentPath)) {
|
|
17301
17331
|
const agentContent = readFileSync24(oldAgentPath, "utf-8");
|
|
17332
|
+
const updatedAgentContent = rewriteRenamedEmployeeContent(agentContent, rosterOldName, newName);
|
|
17302
17333
|
renameSync6(oldAgentPath, newAgentPath);
|
|
17334
|
+
writeFileSync21(newAgentPath, updatedAgentContent, "utf-8");
|
|
17303
17335
|
rollbackStack.push({
|
|
17304
17336
|
description: "restore agent file",
|
|
17305
17337
|
undo: () => {
|
|
@@ -17372,6 +17404,10 @@ async function renameEmployee(oldName, newName, opts = {}) {
|
|
|
17372
17404
|
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
17373
17405
|
}
|
|
17374
17406
|
}
|
|
17407
|
+
function rewriteRenamedEmployeeContent(content, oldName, newName) {
|
|
17408
|
+
const withAgentId = content.replace(/^(agent_id:\s*)\S+/m, `$1${newName}`);
|
|
17409
|
+
return personalizePrompt(withAgentId, oldName, newName);
|
|
17410
|
+
}
|
|
17375
17411
|
function findExeBin2() {
|
|
17376
17412
|
try {
|
|
17377
17413
|
return execSync12(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
@@ -18295,8 +18331,8 @@ async function validateModel(log) {
|
|
|
18295
18331
|
log("Skipping in-memory model validation (low memory \u2014 will validate on first use).");
|
|
18296
18332
|
const modelPath = path38.join(MODELS_DIR, LOCAL_FILENAME);
|
|
18297
18333
|
if (existsSync32(modelPath)) {
|
|
18298
|
-
const { statSync:
|
|
18299
|
-
const size =
|
|
18334
|
+
const { statSync: statSync8 } = await import("fs");
|
|
18335
|
+
const size = statSync8(modelPath).size;
|
|
18300
18336
|
if (size > 300 * 1e6) {
|
|
18301
18337
|
log(`Model file verified (${(size / 1e6).toFixed(0)} MB).`);
|
|
18302
18338
|
return;
|
|
@@ -19500,6 +19536,101 @@ function createStackUpdatePlan(manifest, envRaw, targetVersion) {
|
|
|
19500
19536
|
breakingChanges: release.breakingChanges ?? []
|
|
19501
19537
|
};
|
|
19502
19538
|
}
|
|
19539
|
+
function validatePinnedGhcrImage(image, label) {
|
|
19540
|
+
const trimmed = image.trim().replace(/^['"]|['"]$/g, "");
|
|
19541
|
+
if (!trimmed) return `${label} is empty`;
|
|
19542
|
+
if (trimmed.includes("${")) return null;
|
|
19543
|
+
if (!trimmed.startsWith("ghcr.io/askexe/") && !trimmed.startsWith("registry.askexe.com/askexe/")) return `${label} must use ghcr.io/askexe/* or registry.askexe.com/askexe/*, got ${trimmed}`;
|
|
19544
|
+
if (/:latest(?:$|[\s#])/.test(trimmed)) return `${label} must not use :latest (${trimmed})`;
|
|
19545
|
+
if (!ASKEXE_GHCR_IMAGE.test(trimmed)) return `${label} must be pinned with an explicit tag or sha256 digest from ghcr.io/askexe or registry.askexe.com/askexe, got ${trimmed}`;
|
|
19546
|
+
return null;
|
|
19547
|
+
}
|
|
19548
|
+
function validateComposeImageLiteral(image, label) {
|
|
19549
|
+
const trimmed = image.trim().replace(/^['"]|['"]$/g, "");
|
|
19550
|
+
if (!trimmed) return `${label} is empty`;
|
|
19551
|
+
if (trimmed.startsWith("ghcr.io/askexe/") || trimmed.startsWith("registry.askexe.com/askexe/")) return validatePinnedGhcrImage(trimmed, label);
|
|
19552
|
+
if (/^(postgres|pgvector\/pgvector|clickhouse\/clickhouse-server|redis|nginx|postgrest\/postgrest|supabase\/gotrue):[^:]+$/i.test(trimmed)) return null;
|
|
19553
|
+
return `${label} uses unsupported non-AskExe image ${trimmed}; customer app images must come from pinned ghcr.io/askexe images`;
|
|
19554
|
+
}
|
|
19555
|
+
function collectProductionDeployGateIssues(plan, envRaw, composeRaw) {
|
|
19556
|
+
const issues = [];
|
|
19557
|
+
const env = parseEnv(envRaw);
|
|
19558
|
+
for (const [serviceName, service] of Object.entries(plan.release.services)) {
|
|
19559
|
+
const manifestIssue = validatePinnedGhcrImage(service.image, `manifest ${plan.targetVersion}.${serviceName}.image`);
|
|
19560
|
+
if (manifestIssue) issues.push({ kind: "manifest-image", message: manifestIssue });
|
|
19561
|
+
const envImage = env.get(service.env);
|
|
19562
|
+
if (envImage) {
|
|
19563
|
+
const envIssue = validatePinnedGhcrImage(envImage, `env ${service.env}`);
|
|
19564
|
+
if (envIssue) issues.push({ kind: "env-image", message: envIssue });
|
|
19565
|
+
}
|
|
19566
|
+
}
|
|
19567
|
+
const lines = composeRaw.split(/\r?\n/);
|
|
19568
|
+
lines.forEach((line, index) => {
|
|
19569
|
+
if (/^\s*build\s*:/.test(line)) {
|
|
19570
|
+
issues.push({ kind: "compose-build", message: `compose line ${index + 1} contains build:, production deploys must pull images` });
|
|
19571
|
+
}
|
|
19572
|
+
const imageMatch = line.match(/^\s*image\s*:\s*(.+?)\s*(?:#.*)?$/);
|
|
19573
|
+
if (imageMatch) {
|
|
19574
|
+
const image = imageMatch[1].trim();
|
|
19575
|
+
if (image.includes("${")) {
|
|
19576
|
+
const fallback = image.match(/:-([^}]+)}/)?.[1];
|
|
19577
|
+
if (fallback) {
|
|
19578
|
+
const composeIssue = validateComposeImageLiteral(fallback, `compose image fallback on line ${index + 1}`);
|
|
19579
|
+
if (composeIssue) issues.push({ kind: "compose-image", message: composeIssue });
|
|
19580
|
+
}
|
|
19581
|
+
} else {
|
|
19582
|
+
const composeIssue = validateComposeImageLiteral(image, `compose image on line ${index + 1}`);
|
|
19583
|
+
if (composeIssue) issues.push({ kind: "compose-image", message: composeIssue });
|
|
19584
|
+
}
|
|
19585
|
+
}
|
|
19586
|
+
});
|
|
19587
|
+
return issues;
|
|
19588
|
+
}
|
|
19589
|
+
function assertDeploymentScopeAllowed(plan, persona = "customer") {
|
|
19590
|
+
if (persona === "askexe-control-plane") return;
|
|
19591
|
+
const blocked = Object.entries(plan.release.services).filter(([, service]) => service.deploymentScope === "askexe-control-plane").map(([name]) => name);
|
|
19592
|
+
if (blocked.length > 0) {
|
|
19593
|
+
throw new Error(
|
|
19594
|
+
`Customer deployment manifest includes AskExe control-plane service(s): ${blocked.join(", ")}. Customer VPSs may deploy customer services and optional agents only.`
|
|
19595
|
+
);
|
|
19596
|
+
}
|
|
19597
|
+
}
|
|
19598
|
+
function assertProductionDeployGate(plan, envRaw, composeRaw, options = {}) {
|
|
19599
|
+
const issues = collectProductionDeployGateIssues(plan, envRaw, composeRaw);
|
|
19600
|
+
if (issues.length === 0) return;
|
|
19601
|
+
if (options.breakGlassReason?.trim()) {
|
|
19602
|
+
writeBreakGlassAudit(plan, issues, options);
|
|
19603
|
+
return;
|
|
19604
|
+
}
|
|
19605
|
+
const details = issues.map((issue) => `- [${issue.kind}] ${issue.message}`).join("\n");
|
|
19606
|
+
throw new Error(
|
|
19607
|
+
`Production deploy gate failed. Exe OS deploys must use pinned ghcr.io/askexe or registry.askexe.com/askexe images and must not build from source on the VPS.
|
|
19608
|
+
${details}
|
|
19609
|
+
Emergency override requires --break-glass <reason> and writes an audit file.`
|
|
19610
|
+
);
|
|
19611
|
+
}
|
|
19612
|
+
function writeBreakGlassAudit(plan, issues, options) {
|
|
19613
|
+
const now2 = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
19614
|
+
const stamp = now2().toISOString().replace(/[:.]/g, "-");
|
|
19615
|
+
const defaultDir = existsSync34("exe/output") ? "exe/output" : path41.dirname(options.envFile ?? ".");
|
|
19616
|
+
const auditFile = options.breakGlassAuditFile ?? path41.join(defaultDir, `stack-update-break-glass-${stamp}.md`);
|
|
19617
|
+
mkdirSync23(path41.dirname(auditFile), { recursive: true });
|
|
19618
|
+
const body = [
|
|
19619
|
+
`# Stack Update Break-Glass Audit \u2014 ${now2().toISOString()}`,
|
|
19620
|
+
"",
|
|
19621
|
+
`Target version: ${plan.targetVersion}`,
|
|
19622
|
+
`Reason: ${options.breakGlassReason?.trim()}`,
|
|
19623
|
+
"",
|
|
19624
|
+
"## Gate failures overridden",
|
|
19625
|
+
...issues.map((issue) => `- [${issue.kind}] ${issue.message}`),
|
|
19626
|
+
"",
|
|
19627
|
+
"## Required follow-up",
|
|
19628
|
+
"Return this deployment to the standard pinned GHCR image path immediately after the emergency is resolved.",
|
|
19629
|
+
""
|
|
19630
|
+
].join("\n");
|
|
19631
|
+
writeFileSync24(auditFile, body, { mode: 384 });
|
|
19632
|
+
console.warn(`[stack-update] BREAK-GLASS deploy override recorded: ${auditFile}`);
|
|
19633
|
+
}
|
|
19503
19634
|
function assertBreakingChangesAllowed(plan, allowedIds) {
|
|
19504
19635
|
const required = plan.breakingChanges.filter((c) => c.requiresConfirmation !== false);
|
|
19505
19636
|
const missing = required.filter((c) => !allowedIds.includes(c.id));
|
|
@@ -19522,6 +19653,15 @@ async function runStackUpdate(options) {
|
|
|
19522
19653
|
const envRaw = readFileSync28(options.envFile, "utf8");
|
|
19523
19654
|
const plan = createStackUpdatePlan(manifest, envRaw, options.targetVersion);
|
|
19524
19655
|
assertBreakingChangesAllowed(plan, options.allowedBreakingChangeIds ?? []);
|
|
19656
|
+
assertDeploymentScopeAllowed(plan, options.deploymentPersona ?? "customer");
|
|
19657
|
+
const plannedEnvRaw = patchEnv(envRaw, Object.fromEntries(plan.changes.map((c) => [c.key, c.after])));
|
|
19658
|
+
const composeRaw = readFileSync28(options.composeFile, "utf8");
|
|
19659
|
+
assertProductionDeployGate(plan, plannedEnvRaw, composeRaw, {
|
|
19660
|
+
breakGlassReason: options.breakGlassReason,
|
|
19661
|
+
breakGlassAuditFile: options.breakGlassAuditFile,
|
|
19662
|
+
now: now2,
|
|
19663
|
+
envFile: options.envFile
|
|
19664
|
+
});
|
|
19525
19665
|
const lockFile = options.lockFile ?? path41.join(path41.dirname(options.envFile), ".exe-stack-lock.json");
|
|
19526
19666
|
const previousVersion = readCurrentStackVersion(lockFile);
|
|
19527
19667
|
if (options.dryRun || plan.changes.length === 0) {
|
|
@@ -19693,9 +19833,11 @@ function loadDefaultPublicKey() {
|
|
|
19693
19833
|
}
|
|
19694
19834
|
return void 0;
|
|
19695
19835
|
}
|
|
19836
|
+
var ASKEXE_GHCR_IMAGE;
|
|
19696
19837
|
var init_stack_update = __esm({
|
|
19697
19838
|
"src/lib/stack-update.ts"() {
|
|
19698
19839
|
"use strict";
|
|
19840
|
+
ASKEXE_GHCR_IMAGE = /^(?:ghcr\.io\/askexe|registry\.askexe\.com\/askexe)\/[a-z0-9._/-]+(?::[^:@$/{]+|@sha256:[a-f0-9]{64})$/i;
|
|
19699
19841
|
}
|
|
19700
19842
|
});
|
|
19701
19843
|
|
|
@@ -19720,6 +19862,7 @@ function parseArgs4(args2) {
|
|
|
19720
19862
|
dryRun: false,
|
|
19721
19863
|
check: false,
|
|
19722
19864
|
rollback: false,
|
|
19865
|
+
deploymentPersona: process.env.EXE_STACK_DEPLOYMENT_PERSONA === "askexe-control-plane" ? "askexe-control-plane" : "customer",
|
|
19723
19866
|
yes: false,
|
|
19724
19867
|
allowedBreakingChangeIds: []
|
|
19725
19868
|
};
|
|
@@ -19750,6 +19893,18 @@ function parseArgs4(args2) {
|
|
|
19750
19893
|
else if (arg === "--license-key") opts.licenseKey = next();
|
|
19751
19894
|
else if (arg.startsWith("--license-key=")) opts.licenseKey = arg.split("=").slice(1).join("=");
|
|
19752
19895
|
else if (arg === "--rollback") opts.rollback = true;
|
|
19896
|
+
else if (arg === "--deployment-persona") {
|
|
19897
|
+
const value = next();
|
|
19898
|
+
if (value !== "customer" && value !== "askexe-control-plane") throw new Error(`Invalid --deployment-persona: ${value}`);
|
|
19899
|
+
opts.deploymentPersona = value;
|
|
19900
|
+
} else if (arg.startsWith("--deployment-persona=")) {
|
|
19901
|
+
const value = arg.split("=").slice(1).join("=");
|
|
19902
|
+
if (value !== "customer" && value !== "askexe-control-plane") throw new Error(`Invalid --deployment-persona: ${value}`);
|
|
19903
|
+
opts.deploymentPersona = value;
|
|
19904
|
+
} else if (arg === "--break-glass") opts.breakGlassReason = next();
|
|
19905
|
+
else if (arg.startsWith("--break-glass=")) opts.breakGlassReason = arg.split("=").slice(1).join("=");
|
|
19906
|
+
else if (arg === "--break-glass-audit-file") opts.breakGlassAuditFile = next();
|
|
19907
|
+
else if (arg.startsWith("--break-glass-audit-file=")) opts.breakGlassAuditFile = arg.split("=").slice(1).join("=");
|
|
19753
19908
|
else if (arg === "--dry-run") opts.dryRun = true;
|
|
19754
19909
|
else if (arg === "--check") opts.check = true;
|
|
19755
19910
|
else if (arg === "--yes" || arg === "-y") opts.yes = true;
|
|
@@ -19787,6 +19942,9 @@ Options:
|
|
|
19787
19942
|
--device-id <id> Device ID to include in deploy audit
|
|
19788
19943
|
--license-key <key> License key to include in deploy audit
|
|
19789
19944
|
--rollback Restore latest backed-up .env and restart stack
|
|
19945
|
+
--deployment-persona <customer|askexe-control-plane> Scope gate (default: customer)
|
|
19946
|
+
--break-glass <reason> Emergency override for production deploy gate; writes audit file
|
|
19947
|
+
--break-glass-audit-file <path> Audit file path for --break-glass
|
|
19790
19948
|
--allow-breaking <ids> Confirm breaking changes, comma-separated
|
|
19791
19949
|
-y, --yes Non-interactive confirmation
|
|
19792
19950
|
`);
|
|
@@ -19826,6 +19984,13 @@ async function main6() {
|
|
|
19826
19984
|
const manifest = await loadStackManifest(opts.manifestRef, void 0, opts.manifestPublicKey, opts.manifestAuthToken);
|
|
19827
19985
|
const envRaw = readFileSync29(opts.envFile, "utf8");
|
|
19828
19986
|
const plan = createStackUpdatePlan(manifest, envRaw, opts.targetVersion);
|
|
19987
|
+
assertDeploymentScopeAllowed(plan, opts.deploymentPersona);
|
|
19988
|
+
const plannedEnvRaw = patchEnv(envRaw, Object.fromEntries(plan.changes.map((c) => [c.key, c.after])));
|
|
19989
|
+
assertProductionDeployGate(plan, plannedEnvRaw, readFileSync29(opts.composeFile, "utf8"), {
|
|
19990
|
+
breakGlassReason: opts.breakGlassReason,
|
|
19991
|
+
breakGlassAuditFile: opts.breakGlassAuditFile,
|
|
19992
|
+
envFile: opts.envFile
|
|
19993
|
+
});
|
|
19829
19994
|
console.log(`Exe OS stack target: ${plan.targetVersion}`);
|
|
19830
19995
|
console.log(`Manifest: ${opts.manifestRef}`);
|
|
19831
19996
|
console.log(`Compose: ${opts.composeFile}`);
|
|
@@ -19858,6 +20023,210 @@ var init_stack_update2 = __esm({
|
|
|
19858
20023
|
}
|
|
19859
20024
|
});
|
|
19860
20025
|
|
|
20026
|
+
// src/lib/registry-proxy.ts
|
|
20027
|
+
import { createServer } from "http";
|
|
20028
|
+
import { Readable } from "stream";
|
|
20029
|
+
function parsePullTokens(raw) {
|
|
20030
|
+
return (raw ?? "").split(/[\n,]/).map((s) => s.trim()).filter(Boolean);
|
|
20031
|
+
}
|
|
20032
|
+
function registryProxyOptionsFromEnv(env = process.env) {
|
|
20033
|
+
const upstreamToken = env.EXE_REGISTRY_PROXY_UPSTREAM_TOKEN || env.GHCR_TOKEN || "";
|
|
20034
|
+
const pullTokens = parsePullTokens(env.EXE_REGISTRY_PROXY_PULL_TOKENS);
|
|
20035
|
+
return {
|
|
20036
|
+
port: Number(env.EXE_REGISTRY_PROXY_PORT || 3201),
|
|
20037
|
+
host: env.EXE_REGISTRY_PROXY_HOST || "0.0.0.0",
|
|
20038
|
+
upstream: env.EXE_REGISTRY_PROXY_UPSTREAM || "https://ghcr.io",
|
|
20039
|
+
upstreamUsername: env.EXE_REGISTRY_PROXY_UPSTREAM_USERNAME || env.GHCR_USERNAME || "askexe",
|
|
20040
|
+
upstreamToken,
|
|
20041
|
+
pullTokens,
|
|
20042
|
+
allowedNamespace: env.EXE_REGISTRY_PROXY_ALLOWED_NAMESPACE || "askexe"
|
|
20043
|
+
};
|
|
20044
|
+
}
|
|
20045
|
+
function assertRegistryProxyConfig(options) {
|
|
20046
|
+
if (!options.upstreamToken) throw new Error("EXE_REGISTRY_PROXY_UPSTREAM_TOKEN or GHCR_TOKEN is required");
|
|
20047
|
+
if (options.pullTokens.length === 0) throw new Error("EXE_REGISTRY_PROXY_PULL_TOKENS is required");
|
|
20048
|
+
if (!options.allowedNamespace || !/^[a-z0-9._-]+$/i.test(options.allowedNamespace)) {
|
|
20049
|
+
throw new Error("EXE_REGISTRY_PROXY_ALLOWED_NAMESPACE must be a registry-safe namespace");
|
|
20050
|
+
}
|
|
20051
|
+
}
|
|
20052
|
+
function timingSafeIncludes(values, candidate) {
|
|
20053
|
+
return values.some((value) => value === candidate);
|
|
20054
|
+
}
|
|
20055
|
+
function parseBasicAuth(header) {
|
|
20056
|
+
if (!header?.startsWith("Basic ")) return null;
|
|
20057
|
+
try {
|
|
20058
|
+
const decoded = Buffer.from(header.slice("Basic ".length), "base64").toString("utf8");
|
|
20059
|
+
const idx = decoded.indexOf(":");
|
|
20060
|
+
if (idx < 0) return null;
|
|
20061
|
+
return { username: decoded.slice(0, idx), password: decoded.slice(idx + 1) };
|
|
20062
|
+
} catch {
|
|
20063
|
+
return null;
|
|
20064
|
+
}
|
|
20065
|
+
}
|
|
20066
|
+
function unauthorized(res) {
|
|
20067
|
+
res.writeHead(401, {
|
|
20068
|
+
"www-authenticate": 'Basic realm="AskExe Registry Proxy"',
|
|
20069
|
+
"content-type": "application/json"
|
|
20070
|
+
});
|
|
20071
|
+
res.end(JSON.stringify({ error: "registry proxy authentication required" }));
|
|
20072
|
+
}
|
|
20073
|
+
function isAllowedPath(pathname, namespace) {
|
|
20074
|
+
if (pathname === "/health") return true;
|
|
20075
|
+
if (pathname === "/v2/" || pathname === "/v2") return true;
|
|
20076
|
+
const prefix = `/v2/${namespace}/`;
|
|
20077
|
+
if (!pathname.startsWith(prefix)) return false;
|
|
20078
|
+
if (pathname.includes("..") || /%2f/i.test(pathname)) return false;
|
|
20079
|
+
return true;
|
|
20080
|
+
}
|
|
20081
|
+
function parseBearerChallenge(header) {
|
|
20082
|
+
if (!header?.toLowerCase().startsWith("bearer ")) return null;
|
|
20083
|
+
const raw = header.slice("Bearer ".length);
|
|
20084
|
+
const params = new URLSearchParams();
|
|
20085
|
+
for (const part of raw.match(/(?:[^,"]+|"[^"]*")+/g) ?? []) {
|
|
20086
|
+
const idx = part.indexOf("=");
|
|
20087
|
+
if (idx < 0) continue;
|
|
20088
|
+
const key = part.slice(0, idx).trim();
|
|
20089
|
+
const value = part.slice(idx + 1).trim().replace(/^"|"$/g, "");
|
|
20090
|
+
params.set(key, value);
|
|
20091
|
+
}
|
|
20092
|
+
const realm = params.get("realm");
|
|
20093
|
+
if (!realm) return null;
|
|
20094
|
+
params.delete("realm");
|
|
20095
|
+
return { realm, params };
|
|
20096
|
+
}
|
|
20097
|
+
async function fetchUpstreamWithRegistryAuth(url, init, upstreamBasicAuth) {
|
|
20098
|
+
const firstHeaders = new Headers(init.headers);
|
|
20099
|
+
firstHeaders.set("authorization", `Basic ${upstreamBasicAuth}`);
|
|
20100
|
+
const first = await fetch(url, { ...init, headers: firstHeaders });
|
|
20101
|
+
if (first.status !== 401) return first;
|
|
20102
|
+
const challenge = parseBearerChallenge(first.headers.get("www-authenticate"));
|
|
20103
|
+
if (!challenge) return first;
|
|
20104
|
+
const tokenUrl = new URL(challenge.realm);
|
|
20105
|
+
for (const [key, value] of challenge.params.entries()) tokenUrl.searchParams.set(key, value);
|
|
20106
|
+
const tokenRes = await fetch(tokenUrl, { headers: { authorization: `Basic ${upstreamBasicAuth}` } });
|
|
20107
|
+
if (!tokenRes.ok) return first;
|
|
20108
|
+
const tokenJson = await tokenRes.json();
|
|
20109
|
+
const token = tokenJson.token ?? tokenJson.access_token;
|
|
20110
|
+
if (!token) return first;
|
|
20111
|
+
const retryHeaders = new Headers(init.headers);
|
|
20112
|
+
retryHeaders.set("authorization", `Bearer ${token}`);
|
|
20113
|
+
return fetch(url, { ...init, headers: retryHeaders });
|
|
20114
|
+
}
|
|
20115
|
+
function copyResponseHeaders(from, to) {
|
|
20116
|
+
for (const [key, value] of from.entries()) {
|
|
20117
|
+
const lower = key.toLowerCase();
|
|
20118
|
+
if (["connection", "keep-alive", "transfer-encoding", "content-encoding"].includes(lower)) continue;
|
|
20119
|
+
to.setHeader(key, value);
|
|
20120
|
+
}
|
|
20121
|
+
}
|
|
20122
|
+
function createRegistryProxyServer(options) {
|
|
20123
|
+
assertRegistryProxyConfig(options);
|
|
20124
|
+
const upstream = new URL(options.upstream ?? "https://ghcr.io");
|
|
20125
|
+
const namespace = options.allowedNamespace ?? "askexe";
|
|
20126
|
+
const upstreamAuth = Buffer.from(`${options.upstreamUsername ?? "askexe"}:${options.upstreamToken}`).toString("base64");
|
|
20127
|
+
return createServer(async (req, res) => {
|
|
20128
|
+
const requestUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
20129
|
+
if (requestUrl.pathname === "/health") {
|
|
20130
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
20131
|
+
res.end(JSON.stringify({ ok: true, service: "exe-registry-proxy", upstream: upstream.origin, namespace }));
|
|
20132
|
+
return;
|
|
20133
|
+
}
|
|
20134
|
+
if (!isAllowedPath(requestUrl.pathname, namespace)) {
|
|
20135
|
+
res.writeHead(404, { "content-type": "application/json" });
|
|
20136
|
+
res.end(JSON.stringify({ error: "registry path not allowed" }));
|
|
20137
|
+
return;
|
|
20138
|
+
}
|
|
20139
|
+
const auth = parseBasicAuth(req.headers.authorization);
|
|
20140
|
+
if (!auth || !timingSafeIncludes(options.pullTokens, auth.password)) {
|
|
20141
|
+
unauthorized(res);
|
|
20142
|
+
return;
|
|
20143
|
+
}
|
|
20144
|
+
const upstreamUrl = new URL(requestUrl.pathname + requestUrl.search, upstream.origin);
|
|
20145
|
+
const headers = new Headers();
|
|
20146
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
20147
|
+
if (!value) continue;
|
|
20148
|
+
const lower = key.toLowerCase();
|
|
20149
|
+
if (["host", "connection", "authorization", "content-length"].includes(lower)) continue;
|
|
20150
|
+
headers.set(key, Array.isArray(value) ? value.join(",") : value);
|
|
20151
|
+
}
|
|
20152
|
+
headers.set("authorization", `Basic ${upstreamAuth}`);
|
|
20153
|
+
try {
|
|
20154
|
+
const upstreamRes = await fetchUpstreamWithRegistryAuth(upstreamUrl, {
|
|
20155
|
+
method: req.method,
|
|
20156
|
+
headers,
|
|
20157
|
+
body: req.method === "GET" || req.method === "HEAD" ? void 0 : req,
|
|
20158
|
+
// Required by undici when streaming request bodies.
|
|
20159
|
+
duplex: "half"
|
|
20160
|
+
}, upstreamAuth);
|
|
20161
|
+
res.statusCode = upstreamRes.status;
|
|
20162
|
+
res.statusMessage = upstreamRes.statusText;
|
|
20163
|
+
copyResponseHeaders(upstreamRes.headers, res);
|
|
20164
|
+
if (!upstreamRes.body || req.method === "HEAD") {
|
|
20165
|
+
res.end();
|
|
20166
|
+
return;
|
|
20167
|
+
}
|
|
20168
|
+
Readable.fromWeb(upstreamRes.body).pipe(res);
|
|
20169
|
+
} catch (err) {
|
|
20170
|
+
res.writeHead(502, { "content-type": "application/json" });
|
|
20171
|
+
res.end(JSON.stringify({ error: "upstream registry proxy failed", detail: err instanceof Error ? err.message : String(err) }));
|
|
20172
|
+
}
|
|
20173
|
+
});
|
|
20174
|
+
}
|
|
20175
|
+
async function runRegistryProxy(options = registryProxyOptionsFromEnv()) {
|
|
20176
|
+
const server = createRegistryProxyServer(options);
|
|
20177
|
+
await new Promise((resolve) => server.listen(options.port, options.host, resolve));
|
|
20178
|
+
console.log(`exe-registry-proxy listening on ${options.host ?? "0.0.0.0"}:${options.port}`);
|
|
20179
|
+
console.log(`proxying /v2/${options.allowedNamespace ?? "askexe"}/* -> ${options.upstream ?? "https://ghcr.io"}`);
|
|
20180
|
+
}
|
|
20181
|
+
var init_registry_proxy = __esm({
|
|
20182
|
+
"src/lib/registry-proxy.ts"() {
|
|
20183
|
+
"use strict";
|
|
20184
|
+
}
|
|
20185
|
+
});
|
|
20186
|
+
|
|
20187
|
+
// src/bin/registry-proxy.ts
|
|
20188
|
+
var registry_proxy_exports = {};
|
|
20189
|
+
__export(registry_proxy_exports, {
|
|
20190
|
+
main: () => main7
|
|
20191
|
+
});
|
|
20192
|
+
function printHelp2() {
|
|
20193
|
+
console.log(`exe-os registry-proxy \u2014 authenticated pull-through proxy for AskExe customer images
|
|
20194
|
+
|
|
20195
|
+
Environment:
|
|
20196
|
+
EXE_REGISTRY_PROXY_PORT=3201
|
|
20197
|
+
EXE_REGISTRY_PROXY_HOST=0.0.0.0
|
|
20198
|
+
EXE_REGISTRY_PROXY_UPSTREAM=https://ghcr.io
|
|
20199
|
+
EXE_REGISTRY_PROXY_UPSTREAM_USERNAME=<github-user>
|
|
20200
|
+
EXE_REGISTRY_PROXY_UPSTREAM_TOKEN=<askexe-ghcr-read-token>
|
|
20201
|
+
EXE_REGISTRY_PROXY_PULL_TOKENS=<customer-token-1,customer-token-2>
|
|
20202
|
+
EXE_REGISTRY_PROXY_ALLOWED_NAMESPACE=askexe
|
|
20203
|
+
|
|
20204
|
+
Docker image shape for clients:
|
|
20205
|
+
registry.askexe.com/askexe/exed:v0.9.3
|
|
20206
|
+
registry.askexe.com/askexe/exe-crm:v0.9.3
|
|
20207
|
+
`);
|
|
20208
|
+
}
|
|
20209
|
+
async function main7(args2 = process.argv.slice(2)) {
|
|
20210
|
+
if (args2.includes("--help") || args2.includes("-h")) {
|
|
20211
|
+
printHelp2();
|
|
20212
|
+
return;
|
|
20213
|
+
}
|
|
20214
|
+
await runRegistryProxy(registryProxyOptionsFromEnv());
|
|
20215
|
+
}
|
|
20216
|
+
var init_registry_proxy2 = __esm({
|
|
20217
|
+
"src/bin/registry-proxy.ts"() {
|
|
20218
|
+
"use strict";
|
|
20219
|
+
init_is_main();
|
|
20220
|
+
init_registry_proxy();
|
|
20221
|
+
if (isMainModule(import.meta.url)) {
|
|
20222
|
+
main7().catch((err) => {
|
|
20223
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
20224
|
+
process.exit(1);
|
|
20225
|
+
});
|
|
20226
|
+
}
|
|
20227
|
+
}
|
|
20228
|
+
});
|
|
20229
|
+
|
|
19861
20230
|
// node_modules/es-toolkit/dist/function/debounce.mjs
|
|
19862
20231
|
function debounce(func, debounceMs, { signal, edges } = {}) {
|
|
19863
20232
|
let pendingThis = void 0;
|
|
@@ -24216,8 +24585,8 @@ var init_ErrorOverview = __esm({
|
|
|
24216
24585
|
"use strict";
|
|
24217
24586
|
init_Box();
|
|
24218
24587
|
init_Text();
|
|
24219
|
-
cleanupPath = (
|
|
24220
|
-
return
|
|
24588
|
+
cleanupPath = (path55) => {
|
|
24589
|
+
return path55?.replace(`file://${cwd()}/`, "");
|
|
24221
24590
|
};
|
|
24222
24591
|
stackUtils = new StackUtils({
|
|
24223
24592
|
cwd: cwd(),
|
|
@@ -26625,11 +26994,11 @@ function Footer() {
|
|
|
26625
26994
|
} catch {
|
|
26626
26995
|
}
|
|
26627
26996
|
try {
|
|
26628
|
-
const { existsSync:
|
|
26997
|
+
const { existsSync: existsSync41 } = await import("fs");
|
|
26629
26998
|
const { join } = await import("path");
|
|
26630
26999
|
const home = process.env.HOME ?? "";
|
|
26631
27000
|
const pidPath = join(home, ".exe-os", "exed.pid");
|
|
26632
|
-
setDaemon(
|
|
27001
|
+
setDaemon(existsSync41(pidPath) ? "running" : "stopped");
|
|
26633
27002
|
} catch {
|
|
26634
27003
|
setDaemon("unknown");
|
|
26635
27004
|
}
|
|
@@ -29413,15 +29782,15 @@ function CommandCenterView({
|
|
|
29413
29782
|
const { createPermissionsFromPreset: createPermissionsFromPreset2, EMPLOYEE_PERMISSIONS: EMPLOYEE_PERMISSIONS2 } = await Promise.resolve().then(() => (init_permissions(), permissions_exports));
|
|
29414
29783
|
const { getPresetByRole: getPresetByRole2 } = await Promise.resolve().then(() => (init_permission_presets(), permission_presets_exports));
|
|
29415
29784
|
const { createDefaultHooks: createDefaultHooks2 } = await Promise.resolve().then(() => (init_hooks(), hooks_exports));
|
|
29416
|
-
const { readFileSync:
|
|
29785
|
+
const { readFileSync: readFileSync36, existsSync: existsSync41 } = await import("fs");
|
|
29417
29786
|
const { join } = await import("path");
|
|
29418
29787
|
const { homedir: homedir8 } = await import("os");
|
|
29419
29788
|
const configPath = join(homedir8(), ".exe-os", "config.json");
|
|
29420
29789
|
let failoverChain = ["anthropic", "opencode", "gemini", "openai"];
|
|
29421
29790
|
let providerConfigs = {};
|
|
29422
|
-
if (
|
|
29791
|
+
if (existsSync41(configPath)) {
|
|
29423
29792
|
try {
|
|
29424
|
-
const raw = JSON.parse(
|
|
29793
|
+
const raw = JSON.parse(readFileSync36(configPath, "utf8"));
|
|
29425
29794
|
if (Array.isArray(raw.failoverChain)) failoverChain = raw.failoverChain;
|
|
29426
29795
|
if (raw.providers && typeof raw.providers === "object") {
|
|
29427
29796
|
providerConfigs = raw.providers;
|
|
@@ -29482,7 +29851,7 @@ function CommandCenterView({
|
|
|
29482
29851
|
const markerDir = join(homedir8(), ".exe-os", "session-cache");
|
|
29483
29852
|
const agentFiles = (await import("fs")).readdirSync(markerDir).filter((f) => f.startsWith("active-agent-"));
|
|
29484
29853
|
for (const f of agentFiles) {
|
|
29485
|
-
const data = JSON.parse(
|
|
29854
|
+
const data = JSON.parse(readFileSync36(join(markerDir, f), "utf8"));
|
|
29486
29855
|
if (data.agentRole) {
|
|
29487
29856
|
agentRole = data.agentRole;
|
|
29488
29857
|
break;
|
|
@@ -29665,7 +30034,7 @@ function CommandCenterView({
|
|
|
29665
30034
|
const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
|
|
29666
30035
|
const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
|
|
29667
30036
|
const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
29668
|
-
const { existsSync:
|
|
30037
|
+
const { existsSync: existsSync41 } = await import("fs");
|
|
29669
30038
|
const { join } = await import("path");
|
|
29670
30039
|
const client = getClient2();
|
|
29671
30040
|
if (!client) {
|
|
@@ -29736,7 +30105,7 @@ function CommandCenterView({
|
|
|
29736
30105
|
}
|
|
29737
30106
|
const memoryCount = memoryCounts.get(name) ?? 0;
|
|
29738
30107
|
const openTaskCount = openTaskCounts.get(name) ?? 0;
|
|
29739
|
-
const hasGit = projectDir ?
|
|
30108
|
+
const hasGit = projectDir ? existsSync41(join(projectDir, ".git")) : false;
|
|
29740
30109
|
const type = hasGit ? "code" : memoryCount > 0 ? "code" : "automation";
|
|
29741
30110
|
projectList.push({
|
|
29742
30111
|
projectName: name,
|
|
@@ -29761,7 +30130,7 @@ function CommandCenterView({
|
|
|
29761
30130
|
setHealth((h) => ({ ...h, memories: Number(totalResult.rows[0]?.cnt ?? 0) }));
|
|
29762
30131
|
try {
|
|
29763
30132
|
const pidPath = join(process.env.HOME ?? "", ".exe-os", "exed.pid");
|
|
29764
|
-
setHealth((h) => ({ ...h, daemon:
|
|
30133
|
+
setHealth((h) => ({ ...h, daemon: existsSync41(pidPath) ? "running" : "stopped" }));
|
|
29765
30134
|
} catch {
|
|
29766
30135
|
}
|
|
29767
30136
|
const activityResult = await client.execute(
|
|
@@ -31884,12 +32253,12 @@ async function loadGatewayConfig() {
|
|
|
31884
32253
|
state.running = false;
|
|
31885
32254
|
}
|
|
31886
32255
|
try {
|
|
31887
|
-
const { existsSync:
|
|
32256
|
+
const { existsSync: existsSync41, readFileSync: readFileSync36 } = await import("fs");
|
|
31888
32257
|
const { join } = await import("path");
|
|
31889
32258
|
const home = process.env.HOME ?? "";
|
|
31890
32259
|
const configPath = join(home, ".exe-os", "gateway.json");
|
|
31891
|
-
if (
|
|
31892
|
-
const raw = JSON.parse(
|
|
32260
|
+
if (existsSync41(configPath)) {
|
|
32261
|
+
const raw = JSON.parse(readFileSync36(configPath, "utf8"));
|
|
31893
32262
|
state.port = raw.port ?? 3100;
|
|
31894
32263
|
state.gatewayUrl = raw.gatewayUrl ?? "";
|
|
31895
32264
|
if (raw.adapters) {
|
|
@@ -32487,12 +32856,12 @@ function TeamView({ onBack, onViewSessions }) {
|
|
|
32487
32856
|
setMembers(teamData);
|
|
32488
32857
|
setDbError(null);
|
|
32489
32858
|
try {
|
|
32490
|
-
const { existsSync:
|
|
32859
|
+
const { existsSync: existsSync41, readFileSync: readFileSync36 } = await import("fs");
|
|
32491
32860
|
const { join } = await import("path");
|
|
32492
32861
|
const home = process.env.HOME ?? "";
|
|
32493
32862
|
const gatewayConfig = join(home, ".exe-os", "gateway.json");
|
|
32494
|
-
if (
|
|
32495
|
-
const raw = JSON.parse(
|
|
32863
|
+
if (existsSync41(gatewayConfig)) {
|
|
32864
|
+
const raw = JSON.parse(readFileSync36(gatewayConfig, "utf8"));
|
|
32496
32865
|
if (raw.agents && raw.agents.length > 0) {
|
|
32497
32866
|
setExternals(raw.agents.map((a) => ({
|
|
32498
32867
|
name: a.name,
|
|
@@ -32672,8 +33041,8 @@ __export(wiki_client_exports, {
|
|
|
32672
33041
|
listDocuments: () => listDocuments,
|
|
32673
33042
|
listWorkspaces: () => listWorkspaces
|
|
32674
33043
|
});
|
|
32675
|
-
async function wikiFetch(config,
|
|
32676
|
-
const url = `${config.baseUrl}/api/v1${
|
|
33044
|
+
async function wikiFetch(config, path55, method = "GET", body) {
|
|
33045
|
+
const url = `${config.baseUrl}/api/v1${path55}`;
|
|
32677
33046
|
const headers = {
|
|
32678
33047
|
Authorization: `Bearer ${config.apiKey}`,
|
|
32679
33048
|
"Content-Type": "application/json"
|
|
@@ -32706,7 +33075,7 @@ async function wikiFetch(config, path54, method = "GET", body) {
|
|
|
32706
33075
|
}
|
|
32707
33076
|
}
|
|
32708
33077
|
if (!response.ok) {
|
|
32709
|
-
throw new Error(`Wiki API ${method} ${
|
|
33078
|
+
throw new Error(`Wiki API ${method} ${path55}: ${response.status} ${response.statusText}`);
|
|
32710
33079
|
}
|
|
32711
33080
|
return response.json();
|
|
32712
33081
|
} finally {
|
|
@@ -33300,12 +33669,12 @@ function SettingsView({ onBack }) {
|
|
|
33300
33669
|
}
|
|
33301
33670
|
setProviders(providerList);
|
|
33302
33671
|
try {
|
|
33303
|
-
const { existsSync:
|
|
33672
|
+
const { existsSync: existsSync41 } = await import("fs");
|
|
33304
33673
|
const { join } = await import("path");
|
|
33305
33674
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
33306
33675
|
const cfg = await loadConfig2();
|
|
33307
33676
|
const home = process.env.HOME ?? "";
|
|
33308
|
-
const hasKey =
|
|
33677
|
+
const hasKey = existsSync41(join(home, ".exe-os", "master.key"));
|
|
33309
33678
|
if (cfg.cloud) {
|
|
33310
33679
|
setCloud({
|
|
33311
33680
|
configured: true,
|
|
@@ -33318,22 +33687,22 @@ function SettingsView({ onBack }) {
|
|
|
33318
33687
|
const pidPath = join(home, ".exe-os", "exed.pid");
|
|
33319
33688
|
let daemon = "unknown";
|
|
33320
33689
|
try {
|
|
33321
|
-
daemon =
|
|
33690
|
+
daemon = existsSync41(pidPath) ? "running" : "stopped";
|
|
33322
33691
|
} catch {
|
|
33323
33692
|
}
|
|
33324
33693
|
let version = "unknown";
|
|
33325
33694
|
try {
|
|
33326
|
-
const { readFileSync:
|
|
33695
|
+
const { readFileSync: readFileSync36 } = await import("fs");
|
|
33327
33696
|
const { createRequire: createRequire3 } = await import("module");
|
|
33328
33697
|
const require2 = createRequire3(import.meta.url);
|
|
33329
33698
|
const pkgPath = require2.resolve("@askexenow/exe-os/package.json");
|
|
33330
|
-
const pkg = JSON.parse(
|
|
33699
|
+
const pkg = JSON.parse(readFileSync36(pkgPath, "utf8"));
|
|
33331
33700
|
version = pkg.version;
|
|
33332
33701
|
} catch {
|
|
33333
33702
|
try {
|
|
33334
|
-
const { readFileSync:
|
|
33703
|
+
const { readFileSync: readFileSync36 } = await import("fs");
|
|
33335
33704
|
const { join: joinPath } = await import("path");
|
|
33336
|
-
const pkg = JSON.parse(
|
|
33705
|
+
const pkg = JSON.parse(readFileSync36(joinPath(process.cwd(), "package.json"), "utf8"));
|
|
33337
33706
|
version = pkg.version;
|
|
33338
33707
|
} catch {
|
|
33339
33708
|
}
|
|
@@ -33941,6 +34310,662 @@ Unhandled rejection: ${reason}
|
|
|
33941
34310
|
}
|
|
33942
34311
|
});
|
|
33943
34312
|
|
|
34313
|
+
// src/lib/code-chunker.ts
|
|
34314
|
+
import ts from "typescript";
|
|
34315
|
+
function languageForFile(filePath) {
|
|
34316
|
+
const base = filePath.split(/[\\/]/).pop()?.toLowerCase() ?? "";
|
|
34317
|
+
if (["dockerfile", "makefile", "rakefile", "gemfile"].includes(base)) return base;
|
|
34318
|
+
const ext = base.includes(".") ? base.split(".").pop() : void 0;
|
|
34319
|
+
return ext ? LANGUAGE_BY_EXTENSION[ext] : void 0;
|
|
34320
|
+
}
|
|
34321
|
+
function chunkSourceFile(source, fileName = "file.ts") {
|
|
34322
|
+
const language = languageForFile(fileName);
|
|
34323
|
+
if (language === "typescript" || language === "javascript") {
|
|
34324
|
+
return chunkTypeScriptLike(source, fileName);
|
|
34325
|
+
}
|
|
34326
|
+
return chunkGenericSource(source, fileName, language);
|
|
34327
|
+
}
|
|
34328
|
+
function chunkTypeScriptLike(source, fileName) {
|
|
34329
|
+
const sourceFile = ts.createSourceFile(
|
|
34330
|
+
fileName,
|
|
34331
|
+
source,
|
|
34332
|
+
ts.ScriptTarget.Latest,
|
|
34333
|
+
true,
|
|
34334
|
+
fileName.endsWith(".tsx") || fileName.endsWith(".jsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS
|
|
34335
|
+
);
|
|
34336
|
+
const chunks = [];
|
|
34337
|
+
const lines = source.split("\n");
|
|
34338
|
+
const importLines = [];
|
|
34339
|
+
function getLineNumber(pos) {
|
|
34340
|
+
return sourceFile.getLineAndCharacterOfPosition(pos).line + 1;
|
|
34341
|
+
}
|
|
34342
|
+
function getLeadingComment(node) {
|
|
34343
|
+
const fullText = sourceFile.getFullText();
|
|
34344
|
+
const ranges = ts.getLeadingCommentRanges(fullText, node.getFullStart());
|
|
34345
|
+
if (!ranges || ranges.length === 0) return void 0;
|
|
34346
|
+
return ranges.map((r) => fullText.slice(r.pos, r.end)).join("\n");
|
|
34347
|
+
}
|
|
34348
|
+
function getNodeText(node) {
|
|
34349
|
+
return node.getText(sourceFile);
|
|
34350
|
+
}
|
|
34351
|
+
function getName(node) {
|
|
34352
|
+
if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node)) {
|
|
34353
|
+
return node.name?.getText(sourceFile) ?? "(anonymous)";
|
|
34354
|
+
}
|
|
34355
|
+
if (ts.isClassDeclaration(node)) return node.name?.getText(sourceFile) ?? "(anonymous class)";
|
|
34356
|
+
if (ts.isInterfaceDeclaration(node)) return node.name.getText(sourceFile);
|
|
34357
|
+
if (ts.isTypeAliasDeclaration(node)) return node.name.getText(sourceFile);
|
|
34358
|
+
if (ts.isEnumDeclaration(node)) return node.name.getText(sourceFile);
|
|
34359
|
+
if (ts.isVariableStatement(node)) return node.declarationList.declarations.map((d) => d.name.getText(sourceFile)).join(", ");
|
|
34360
|
+
if (ts.isExportAssignment(node)) return "default export";
|
|
34361
|
+
return "(unknown)";
|
|
34362
|
+
}
|
|
34363
|
+
function visitTopLevel(node) {
|
|
34364
|
+
if (ts.isImportDeclaration(node)) {
|
|
34365
|
+
importLines.push({ start: getLineNumber(node.getStart(sourceFile)), end: getLineNumber(node.getEnd()) });
|
|
34366
|
+
return;
|
|
34367
|
+
}
|
|
34368
|
+
if (ts.isFunctionDeclaration(node)) {
|
|
34369
|
+
chunks.push({ kind: "function", name: getName(node), text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
|
|
34370
|
+
return;
|
|
34371
|
+
}
|
|
34372
|
+
if (ts.isClassDeclaration(node)) {
|
|
34373
|
+
chunks.push({ kind: "class", name: getName(node), text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
|
|
34374
|
+
return;
|
|
34375
|
+
}
|
|
34376
|
+
if (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node)) {
|
|
34377
|
+
chunks.push({ kind: "type", name: getName(node), text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
|
|
34378
|
+
return;
|
|
34379
|
+
}
|
|
34380
|
+
if (ts.isVariableStatement(node)) {
|
|
34381
|
+
const isFnLike = node.declarationList.declarations.some((d) => d.initializer && (ts.isArrowFunction(d.initializer) || ts.isFunctionExpression(d.initializer)));
|
|
34382
|
+
chunks.push({ kind: isFnLike ? "function" : "variable", name: getName(node), text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
|
|
34383
|
+
return;
|
|
34384
|
+
}
|
|
34385
|
+
if (ts.isExportAssignment(node)) {
|
|
34386
|
+
chunks.push({ kind: "export", name: "default export", text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
|
|
34387
|
+
return;
|
|
34388
|
+
}
|
|
34389
|
+
if (ts.isExpressionStatement(node)) {
|
|
34390
|
+
const text = getNodeText(node);
|
|
34391
|
+
if (text.length > 10) chunks.push({ kind: "other", name: text.slice(0, 40).replace(/\n/g, " "), text, startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()) });
|
|
34392
|
+
}
|
|
34393
|
+
}
|
|
34394
|
+
sourceFile.statements.forEach(visitTopLevel);
|
|
34395
|
+
if (importLines.length > 0) {
|
|
34396
|
+
const startLine = importLines[0].start;
|
|
34397
|
+
const endLine = importLines[importLines.length - 1].end;
|
|
34398
|
+
chunks.unshift({ kind: "import", name: `${importLines.length} imports`, text: lines.slice(startLine - 1, endLine).join("\n"), startLine, endLine });
|
|
34399
|
+
}
|
|
34400
|
+
chunks.sort((a, b) => a.startLine - b.startLine);
|
|
34401
|
+
return chunks.length > 0 ? chunks : chunkByWindows(source, 80);
|
|
34402
|
+
}
|
|
34403
|
+
function chunkGenericSource(source, _fileName, language) {
|
|
34404
|
+
const lines = source.split("\n");
|
|
34405
|
+
if (source.trim().length === 0) return [];
|
|
34406
|
+
if (language && TEXT_LIKE_LANGUAGES.has(language)) return chunkTextLike(lines, language);
|
|
34407
|
+
const chunks = [];
|
|
34408
|
+
const patterns = [
|
|
34409
|
+
{ kind: "function", regex: /^\s*(?:async\s+)?def\s+([A-Za-z_][\w]*)\s*\(/, name: (m) => m[1] },
|
|
34410
|
+
{ kind: "class", regex: /^\s*class\s+([A-Za-z_][\w]*)\b/, name: (m) => m[1] },
|
|
34411
|
+
{ kind: "function", regex: /^\s*(?:pub\s+)?fn\s+([A-Za-z_][\w]*)\s*[<(]/, name: (m) => m[1] },
|
|
34412
|
+
{ kind: "class", regex: /^\s*(?:pub\s+)?(?:struct|enum|trait)\s+([A-Za-z_][\w]*)\b/, name: (m) => m[1] },
|
|
34413
|
+
{ kind: "function", regex: /^\s*func\s+(?:\([^)]*\)\s*)?([A-Za-z_][\w]*)\s*\(/, name: (m) => m[1] },
|
|
34414
|
+
{ kind: "function", regex: /^\s*(?:public|private|protected|static|final|suspend|fun|override|open|internal|export|async|func|function|subroutine)\s+.*?([A-Za-z_][\w]*)\s*\(/, name: (m) => m[1] },
|
|
34415
|
+
{ kind: "function", regex: /^\s*function\s+([A-Za-z_][\w]*)\s*\(/, name: (m) => m[1] },
|
|
34416
|
+
{ kind: "type", regex: /^\s*(?:interface|type|enum|record|data\s+class|case\s+class|contract|library)\s+([A-Za-z_][\w]*)\b/, name: (m) => m[1] },
|
|
34417
|
+
{ kind: "section", regex: /^\s{0,3}#{1,6}\s+(.+)$/, name: (m) => m[1].trim() }
|
|
34418
|
+
];
|
|
34419
|
+
const starts = [];
|
|
34420
|
+
for (let i = 0; i < lines.length; i++) {
|
|
34421
|
+
const line = lines[i];
|
|
34422
|
+
for (const pattern of patterns) {
|
|
34423
|
+
const match = line.match(pattern.regex);
|
|
34424
|
+
if (match) {
|
|
34425
|
+
starts.push({ line: i + 1, kind: pattern.kind, name: pattern.name(match) });
|
|
34426
|
+
break;
|
|
34427
|
+
}
|
|
34428
|
+
}
|
|
34429
|
+
}
|
|
34430
|
+
if (starts.length === 0) return chunkByWindows(source, 80);
|
|
34431
|
+
for (let i = 0; i < starts.length; i++) {
|
|
34432
|
+
const start = starts[i];
|
|
34433
|
+
const next = starts[i + 1]?.line ?? lines.length + 1;
|
|
34434
|
+
const endLine = Math.max(start.line, next - 1);
|
|
34435
|
+
chunks.push({
|
|
34436
|
+
kind: start.kind,
|
|
34437
|
+
name: start.name,
|
|
34438
|
+
text: lines.slice(start.line - 1, endLine).join("\n"),
|
|
34439
|
+
startLine: start.line,
|
|
34440
|
+
endLine
|
|
34441
|
+
});
|
|
34442
|
+
}
|
|
34443
|
+
return chunks;
|
|
34444
|
+
}
|
|
34445
|
+
function chunkTextLike(lines, language) {
|
|
34446
|
+
if (language === "markdown" || language === "mdx") {
|
|
34447
|
+
const starts = lines.map((line, i) => ({ line, i })).filter(({ line }) => /^\s{0,3}#{1,6}\s+/.test(line));
|
|
34448
|
+
if (starts.length > 0) {
|
|
34449
|
+
return starts.map((start, idx) => {
|
|
34450
|
+
const end = starts[idx + 1]?.i ?? lines.length;
|
|
34451
|
+
const title = start.line.replace(/^\s{0,3}#{1,6}\s+/, "").trim();
|
|
34452
|
+
return { kind: "section", name: title, text: lines.slice(start.i, end).join("\n"), startLine: start.i + 1, endLine: end };
|
|
34453
|
+
});
|
|
34454
|
+
}
|
|
34455
|
+
}
|
|
34456
|
+
return chunkByWindows(lines.join("\n"), 80);
|
|
34457
|
+
}
|
|
34458
|
+
function chunkByWindows(source, windowLines) {
|
|
34459
|
+
const lines = source.split("\n");
|
|
34460
|
+
const chunks = [];
|
|
34461
|
+
for (let i = 0; i < lines.length; i += windowLines) {
|
|
34462
|
+
const end = Math.min(lines.length, i + windowLines);
|
|
34463
|
+
const text = lines.slice(i, end).join("\n");
|
|
34464
|
+
if (text.trim()) chunks.push({ kind: "other", name: `lines ${i + 1}-${end}`, text, startLine: i + 1, endLine: end });
|
|
34465
|
+
}
|
|
34466
|
+
return chunks;
|
|
34467
|
+
}
|
|
34468
|
+
function summarizeChunk(chunk, filePath) {
|
|
34469
|
+
const location = `${filePath}:${chunk.startLine}-${chunk.endLine}`;
|
|
34470
|
+
const comment = chunk.comment ? chunk.comment.replace(/\/\*\*|\*\/|\*\s?/g, "").trim().split("\n")[0] : "";
|
|
34471
|
+
switch (chunk.kind) {
|
|
34472
|
+
case "function":
|
|
34473
|
+
return `Function ${chunk.name} in ${location}${comment ? ` \u2014 ${comment}` : ""}`;
|
|
34474
|
+
case "class":
|
|
34475
|
+
return `Class ${chunk.name} in ${location}${comment ? ` \u2014 ${comment}` : ""}`;
|
|
34476
|
+
case "type":
|
|
34477
|
+
return `Type ${chunk.name} in ${location}`;
|
|
34478
|
+
case "import":
|
|
34479
|
+
return `Imports (${chunk.name}) in ${filePath}`;
|
|
34480
|
+
case "variable":
|
|
34481
|
+
return `Variable ${chunk.name} in ${location}`;
|
|
34482
|
+
case "export":
|
|
34483
|
+
return `Default export in ${location}`;
|
|
34484
|
+
case "section":
|
|
34485
|
+
return `Section ${chunk.name} in ${location}`;
|
|
34486
|
+
default:
|
|
34487
|
+
return `${chunk.kind} in ${location}`;
|
|
34488
|
+
}
|
|
34489
|
+
}
|
|
34490
|
+
function isChunkable(filePath) {
|
|
34491
|
+
return Boolean(languageForFile(filePath));
|
|
34492
|
+
}
|
|
34493
|
+
var LANGUAGE_BY_EXTENSION, TEXT_LIKE_LANGUAGES;
|
|
34494
|
+
var init_code_chunker = __esm({
|
|
34495
|
+
"src/lib/code-chunker.ts"() {
|
|
34496
|
+
"use strict";
|
|
34497
|
+
LANGUAGE_BY_EXTENSION = {
|
|
34498
|
+
c: "c",
|
|
34499
|
+
cc: "cpp",
|
|
34500
|
+
cpp: "cpp",
|
|
34501
|
+
cxx: "cpp",
|
|
34502
|
+
h: "c",
|
|
34503
|
+
hh: "cpp",
|
|
34504
|
+
hpp: "cpp",
|
|
34505
|
+
cs: "csharp",
|
|
34506
|
+
css: "css",
|
|
34507
|
+
scss: "scss",
|
|
34508
|
+
f: "fortran",
|
|
34509
|
+
f90: "fortran",
|
|
34510
|
+
f95: "fortran",
|
|
34511
|
+
go: "go",
|
|
34512
|
+
html: "html",
|
|
34513
|
+
htm: "html",
|
|
34514
|
+
java: "java",
|
|
34515
|
+
js: "javascript",
|
|
34516
|
+
jsx: "javascript",
|
|
34517
|
+
json: "json",
|
|
34518
|
+
kt: "kotlin",
|
|
34519
|
+
kts: "kotlin",
|
|
34520
|
+
lua: "lua",
|
|
34521
|
+
md: "markdown",
|
|
34522
|
+
mdx: "mdx",
|
|
34523
|
+
pas: "pascal",
|
|
34524
|
+
php: "php",
|
|
34525
|
+
py: "python",
|
|
34526
|
+
r: "r",
|
|
34527
|
+
rb: "ruby",
|
|
34528
|
+
rs: "rust",
|
|
34529
|
+
scala: "scala",
|
|
34530
|
+
sc: "scala",
|
|
34531
|
+
sol: "solidity",
|
|
34532
|
+
sql: "sql",
|
|
34533
|
+
svelte: "svelte",
|
|
34534
|
+
swift: "swift",
|
|
34535
|
+
toml: "toml",
|
|
34536
|
+
ts: "typescript",
|
|
34537
|
+
tsx: "typescript",
|
|
34538
|
+
vue: "vue",
|
|
34539
|
+
xml: "xml",
|
|
34540
|
+
yaml: "yaml",
|
|
34541
|
+
yml: "yaml",
|
|
34542
|
+
sh: "shell",
|
|
34543
|
+
bash: "shell",
|
|
34544
|
+
zsh: "shell"
|
|
34545
|
+
};
|
|
34546
|
+
TEXT_LIKE_LANGUAGES = /* @__PURE__ */ new Set(["json", "markdown", "mdx", "toml", "yaml", "xml", "html", "css", "scss", "sql"]);
|
|
34547
|
+
}
|
|
34548
|
+
});
|
|
34549
|
+
|
|
34550
|
+
// src/lib/code-context-index.ts
|
|
34551
|
+
var code_context_index_exports = {};
|
|
34552
|
+
__export(code_context_index_exports, {
|
|
34553
|
+
analyzeBlastRadius: () => analyzeBlastRadius,
|
|
34554
|
+
buildCodeContextIndex: () => buildCodeContextIndex,
|
|
34555
|
+
getCodeContextIndexPath: () => getCodeContextIndexPath,
|
|
34556
|
+
getCodeContextStats: () => getCodeContextStats,
|
|
34557
|
+
loadOrBuildCodeContextIndex: () => loadOrBuildCodeContextIndex,
|
|
34558
|
+
searchCodeContext: () => searchCodeContext,
|
|
34559
|
+
traceCodeSymbol: () => traceCodeSymbol
|
|
34560
|
+
});
|
|
34561
|
+
import crypto14 from "crypto";
|
|
34562
|
+
import path50 from "path";
|
|
34563
|
+
import { existsSync as existsSync36, mkdirSync as mkdirSync24, readFileSync as readFileSync31, readdirSync as readdirSync11, statSync as statSync7, writeFileSync as writeFileSync25 } from "fs";
|
|
34564
|
+
import { spawnSync } from "child_process";
|
|
34565
|
+
function normalizeProjectRoot(projectRoot) {
|
|
34566
|
+
return path50.resolve(projectRoot || process.cwd());
|
|
34567
|
+
}
|
|
34568
|
+
function hashText(text) {
|
|
34569
|
+
return crypto14.createHash("sha256").update(text).digest("hex");
|
|
34570
|
+
}
|
|
34571
|
+
function indexDir() {
|
|
34572
|
+
const dir = path50.join(EXE_AI_DIR, "code-context");
|
|
34573
|
+
mkdirSync24(dir, { recursive: true });
|
|
34574
|
+
return dir;
|
|
34575
|
+
}
|
|
34576
|
+
function getCodeContextIndexPath(projectRoot) {
|
|
34577
|
+
const root = normalizeProjectRoot(projectRoot);
|
|
34578
|
+
const rootHash = hashText(root).slice(0, 16);
|
|
34579
|
+
return path50.join(indexDir(), `${rootHash}.json`);
|
|
34580
|
+
}
|
|
34581
|
+
function currentBranch(projectRoot) {
|
|
34582
|
+
const result = spawnSync("git", ["branch", "--show-current"], { cwd: projectRoot, encoding: "utf8", timeout: 2e3 });
|
|
34583
|
+
const branch = result.status === 0 ? result.stdout.trim() : "";
|
|
34584
|
+
return branch || "detached-or-unknown";
|
|
34585
|
+
}
|
|
34586
|
+
function shouldIgnore(relPath) {
|
|
34587
|
+
const parts = relPath.split(/[\\/]/);
|
|
34588
|
+
return parts.some((part) => IGNORE_SEGMENTS.has(part));
|
|
34589
|
+
}
|
|
34590
|
+
function listRecursive(projectRoot, dir = projectRoot, out = []) {
|
|
34591
|
+
for (const entry of readdirSync11(dir, { withFileTypes: true })) {
|
|
34592
|
+
const abs = path50.join(dir, entry.name);
|
|
34593
|
+
const rel = path50.relative(projectRoot, abs).replaceAll(path50.sep, "/");
|
|
34594
|
+
if (shouldIgnore(rel)) continue;
|
|
34595
|
+
if (entry.isDirectory()) listRecursive(projectRoot, abs, out);
|
|
34596
|
+
else if (entry.isFile()) out.push(rel);
|
|
34597
|
+
}
|
|
34598
|
+
return out;
|
|
34599
|
+
}
|
|
34600
|
+
function listCodeFiles(projectRoot, maxFiles) {
|
|
34601
|
+
const git = spawnSync("git", ["ls-files"], { cwd: projectRoot, encoding: "utf8", timeout: 5e3, maxBuffer: 1024 * 1024 * 16 });
|
|
34602
|
+
let files = [];
|
|
34603
|
+
if (git.status === 0 && git.stdout.trim()) {
|
|
34604
|
+
files = git.stdout.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
34605
|
+
} else {
|
|
34606
|
+
const rg = spawnSync("rg", ["--files"], { cwd: projectRoot, encoding: "utf8", timeout: 5e3, maxBuffer: 1024 * 1024 * 16 });
|
|
34607
|
+
files = rg.status === 0 && rg.stdout.trim() ? rg.stdout.split("\n").map((s) => s.trim()).filter(Boolean) : listRecursive(projectRoot);
|
|
34608
|
+
}
|
|
34609
|
+
return files.map((file) => file.replaceAll(path50.sep, "/")).filter((file) => isChunkable(file) && !shouldIgnore(file)).slice(0, maxFiles).sort();
|
|
34610
|
+
}
|
|
34611
|
+
function parseImportPaths(importText) {
|
|
34612
|
+
const paths = [];
|
|
34613
|
+
const patterns = [/from\s+["']([^"']+)["']/g, /^import\s+["']([^"']+)["']/gm, /require\(["']([^"']+)["']\)/g, /use\s+([A-Za-z0-9_:]+)::/g, /#include\s+[<"]([^>"]+)[>"]/g];
|
|
34614
|
+
for (const regex of patterns) {
|
|
34615
|
+
let match;
|
|
34616
|
+
while ((match = regex.exec(importText)) !== null) if (!paths.includes(match[1])) paths.push(match[1]);
|
|
34617
|
+
}
|
|
34618
|
+
return paths;
|
|
34619
|
+
}
|
|
34620
|
+
function resolveImport(fromFile, importPath, allFiles) {
|
|
34621
|
+
if (!importPath.startsWith(".")) return null;
|
|
34622
|
+
const base = path50.posix.normalize(path50.posix.join(path50.posix.dirname(fromFile.replaceAll(path50.sep, "/")), importPath));
|
|
34623
|
+
const withoutKnownExt = base.replace(/\.(?:[a-z0-9]+)$/i, "");
|
|
34624
|
+
const candidates = [base];
|
|
34625
|
+
for (const ext of ["ts", "tsx", "js", "jsx", "py", "rs", "go", "java", "cs", "cpp", "c", "rb", "php", "swift", "kt", "scala", "sql", "md", "json", "yaml", "yml"]) {
|
|
34626
|
+
candidates.push(`${withoutKnownExt}.${ext}`, `${base}.${ext}`);
|
|
34627
|
+
}
|
|
34628
|
+
for (const indexName of ["index.ts", "index.tsx", "index.js", "mod.rs", "__init__.py"]) candidates.push(path50.posix.join(base, indexName));
|
|
34629
|
+
return candidates.find((candidate) => allFiles.has(candidate)) ?? null;
|
|
34630
|
+
}
|
|
34631
|
+
function symbolId(filePath, chunk) {
|
|
34632
|
+
return hashText(`${filePath}:${chunk.kind}:${chunk.name}:${chunk.startLine}:${chunk.endLine}`).slice(0, 24);
|
|
34633
|
+
}
|
|
34634
|
+
function loadIndex(projectRoot) {
|
|
34635
|
+
const file = getCodeContextIndexPath(projectRoot);
|
|
34636
|
+
if (!existsSync36(file)) return null;
|
|
34637
|
+
try {
|
|
34638
|
+
const parsed = JSON.parse(readFileSync31(file, "utf8"));
|
|
34639
|
+
if (parsed.version !== INDEX_VERSION || parsed.projectRoot !== projectRoot) return null;
|
|
34640
|
+
return parsed;
|
|
34641
|
+
} catch {
|
|
34642
|
+
return null;
|
|
34643
|
+
}
|
|
34644
|
+
}
|
|
34645
|
+
function saveIndex(index) {
|
|
34646
|
+
writeFileSync25(getCodeContextIndexPath(index.projectRoot), JSON.stringify(index, null, 2));
|
|
34647
|
+
}
|
|
34648
|
+
function buildFileRecord(projectRoot, relPath, allFiles, previous) {
|
|
34649
|
+
const absPath = path50.join(projectRoot, relPath);
|
|
34650
|
+
let stat2;
|
|
34651
|
+
try {
|
|
34652
|
+
stat2 = statSync7(absPath);
|
|
34653
|
+
} catch {
|
|
34654
|
+
return { record: null, reused: false };
|
|
34655
|
+
}
|
|
34656
|
+
if (!stat2.isFile()) return { record: null, reused: false };
|
|
34657
|
+
const language = languageForFile(relPath);
|
|
34658
|
+
if (!language || !isChunkable(relPath)) return { record: null, reused: false };
|
|
34659
|
+
const source = readFileSync31(absPath, "utf8");
|
|
34660
|
+
const hash = hashText(source);
|
|
34661
|
+
if (previous && previous.hash === hash && previous.mtimeMs === stat2.mtimeMs && previous.size === stat2.size && previous.language === language) {
|
|
34662
|
+
return { record: previous, reused: true };
|
|
34663
|
+
}
|
|
34664
|
+
const chunks = chunkSourceFile(source, relPath);
|
|
34665
|
+
const imports = chunks.filter((chunk) => chunk.kind === "import").flatMap((chunk) => parseImportPaths(chunk.text));
|
|
34666
|
+
const resolvedImports = imports.map((importPath) => resolveImport(relPath, importPath, allFiles)).filter((file) => Boolean(file));
|
|
34667
|
+
const symbols = chunks.filter((chunk) => chunk.name !== "(unknown)").map((chunk) => ({
|
|
34668
|
+
id: symbolId(relPath, chunk),
|
|
34669
|
+
name: chunk.name,
|
|
34670
|
+
kind: chunk.kind,
|
|
34671
|
+
filePath: relPath,
|
|
34672
|
+
language,
|
|
34673
|
+
startLine: chunk.startLine,
|
|
34674
|
+
endLine: chunk.endLine,
|
|
34675
|
+
summary: summarizeChunk(chunk, relPath),
|
|
34676
|
+
text: chunk.text.slice(0, 8e3),
|
|
34677
|
+
comment: chunk.comment
|
|
34678
|
+
}));
|
|
34679
|
+
return { record: { path: relPath, absPath, language, hash, mtimeMs: stat2.mtimeMs, size: stat2.size, imports, resolvedImports, symbols }, reused: false };
|
|
34680
|
+
}
|
|
34681
|
+
function buildCodeContextIndex(options = {}) {
|
|
34682
|
+
const projectRoot = normalizeProjectRoot(options.projectRoot);
|
|
34683
|
+
const maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;
|
|
34684
|
+
const branch = currentBranch(projectRoot);
|
|
34685
|
+
const previous = options.force ? null : loadIndex(projectRoot);
|
|
34686
|
+
const files = listCodeFiles(projectRoot, maxFiles);
|
|
34687
|
+
const allFiles = new Set(files.map((file) => file.replaceAll(path50.sep, "/")));
|
|
34688
|
+
const fileRecords = {};
|
|
34689
|
+
let rebuiltFiles = 0;
|
|
34690
|
+
let reusedFiles = 0;
|
|
34691
|
+
let skippedFiles = 0;
|
|
34692
|
+
for (const rel of files) {
|
|
34693
|
+
const normalized = rel.replaceAll(path50.sep, "/");
|
|
34694
|
+
const { record, reused } = buildFileRecord(projectRoot, normalized, allFiles, previous?.files[normalized]);
|
|
34695
|
+
if (record) {
|
|
34696
|
+
fileRecords[normalized] = record;
|
|
34697
|
+
if (reused) reusedFiles++;
|
|
34698
|
+
else rebuiltFiles++;
|
|
34699
|
+
} else skippedFiles++;
|
|
34700
|
+
}
|
|
34701
|
+
const languageBreakdown = {};
|
|
34702
|
+
for (const file of Object.values(fileRecords)) languageBreakdown[file.language] = (languageBreakdown[file.language] ?? 0) + 1;
|
|
34703
|
+
const index = {
|
|
34704
|
+
version: INDEX_VERSION,
|
|
34705
|
+
projectRoot,
|
|
34706
|
+
rootHash: hashText(projectRoot).slice(0, 16),
|
|
34707
|
+
branch,
|
|
34708
|
+
indexedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
34709
|
+
stats: { filesSeen: files.length, filesIndexed: Object.keys(fileRecords).length, rebuiltFiles, reusedFiles, skippedFiles, languageBreakdown },
|
|
34710
|
+
files: fileRecords
|
|
34711
|
+
};
|
|
34712
|
+
saveIndex(index);
|
|
34713
|
+
return index;
|
|
34714
|
+
}
|
|
34715
|
+
function loadOrBuildCodeContextIndex(options = {}) {
|
|
34716
|
+
const projectRoot = normalizeProjectRoot(options.projectRoot);
|
|
34717
|
+
if (!options.force) {
|
|
34718
|
+
const loaded = loadIndex(projectRoot);
|
|
34719
|
+
if (loaded) {
|
|
34720
|
+
const currentFiles = listCodeFiles(projectRoot, options.maxFiles ?? DEFAULT_MAX_FILES);
|
|
34721
|
+
const unchanged = currentFiles.every((rel) => {
|
|
34722
|
+
const normalized = rel.replaceAll(path50.sep, "/");
|
|
34723
|
+
const existing = loaded.files[normalized];
|
|
34724
|
+
if (!existing) return false;
|
|
34725
|
+
try {
|
|
34726
|
+
const stat2 = statSync7(path50.join(projectRoot, normalized));
|
|
34727
|
+
return stat2.mtimeMs === existing.mtimeMs && stat2.size === existing.size;
|
|
34728
|
+
} catch {
|
|
34729
|
+
return false;
|
|
34730
|
+
}
|
|
34731
|
+
});
|
|
34732
|
+
if (unchanged && Object.keys(loaded.files).length === currentFiles.length) return loaded;
|
|
34733
|
+
}
|
|
34734
|
+
}
|
|
34735
|
+
return buildCodeContextIndex(options);
|
|
34736
|
+
}
|
|
34737
|
+
function normalizeLanguage(language) {
|
|
34738
|
+
return language.toLowerCase().replace(/^c\+\+$/, "cpp").replace(/^c#$/, "csharp").replace(/^js$/, "javascript").replace(/^ts$/, "typescript");
|
|
34739
|
+
}
|
|
34740
|
+
function tokenize2(query) {
|
|
34741
|
+
const raw = query.toLowerCase().replace(/([a-z])([A-Z])/g, "$1 $2").split(/[^a-z0-9_.$/-]+/).map((s) => s.trim()).filter((s) => s.length >= 2);
|
|
34742
|
+
const expanded = /* @__PURE__ */ new Set();
|
|
34743
|
+
for (const term of raw) {
|
|
34744
|
+
expanded.add(term);
|
|
34745
|
+
for (const part of term.split(/[_.$/-]+/)) if (part.length >= 2) expanded.add(part);
|
|
34746
|
+
const dashed = term.replace(/[_.$/]+/g, "-");
|
|
34747
|
+
if (dashed.length >= 2) expanded.add(dashed);
|
|
34748
|
+
if (!term.includes("-")) expanded.add(`${term}s`);
|
|
34749
|
+
if (term.endsWith("s") && term.length > 3) expanded.add(term.slice(0, -1));
|
|
34750
|
+
}
|
|
34751
|
+
return [...expanded];
|
|
34752
|
+
}
|
|
34753
|
+
function ngrams(terms) {
|
|
34754
|
+
const grams = [...terms];
|
|
34755
|
+
for (let i = 0; i < terms.length - 1; i++) grams.push(`${terms[i]} ${terms[i + 1]}`);
|
|
34756
|
+
return grams;
|
|
34757
|
+
}
|
|
34758
|
+
function globToRegex(pattern) {
|
|
34759
|
+
let out = "";
|
|
34760
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
34761
|
+
const ch = pattern[i];
|
|
34762
|
+
const next = pattern[i + 1];
|
|
34763
|
+
if (ch === "*" && next === "*") {
|
|
34764
|
+
out += ".*";
|
|
34765
|
+
i++;
|
|
34766
|
+
continue;
|
|
34767
|
+
}
|
|
34768
|
+
if (ch === "*") {
|
|
34769
|
+
out += "[^/]*";
|
|
34770
|
+
continue;
|
|
34771
|
+
}
|
|
34772
|
+
if (".+^${}()|[]\\".includes(ch)) out += `\\${ch}`;
|
|
34773
|
+
else out += ch;
|
|
34774
|
+
}
|
|
34775
|
+
return new RegExp(`^${out}$`);
|
|
34776
|
+
}
|
|
34777
|
+
function matchesPath(filePath, patterns) {
|
|
34778
|
+
if (!patterns || patterns.length === 0) return true;
|
|
34779
|
+
const normalized = filePath.replaceAll(path50.sep, "/");
|
|
34780
|
+
return patterns.some((pattern) => {
|
|
34781
|
+
const p = pattern.replaceAll(path50.sep, "/").replace(/^\.\//, "");
|
|
34782
|
+
return normalized === p || normalized.startsWith(`${p}/`) || normalized.endsWith(p) || globToRegex(p).test(normalized);
|
|
34783
|
+
});
|
|
34784
|
+
}
|
|
34785
|
+
function scoreSymbol(symbol, terms) {
|
|
34786
|
+
const grams = ngrams(terms);
|
|
34787
|
+
const haystacks = {
|
|
34788
|
+
name: symbol.name.toLowerCase(),
|
|
34789
|
+
path: symbol.filePath.toLowerCase(),
|
|
34790
|
+
language: symbol.language.toLowerCase(),
|
|
34791
|
+
summary: symbol.summary.toLowerCase(),
|
|
34792
|
+
text: symbol.text.toLowerCase()
|
|
34793
|
+
};
|
|
34794
|
+
let score = 0;
|
|
34795
|
+
const matches = [];
|
|
34796
|
+
for (const term of terms) {
|
|
34797
|
+
if (haystacks.name === term) {
|
|
34798
|
+
score += 100;
|
|
34799
|
+
matches.push(`name=${term}`);
|
|
34800
|
+
continue;
|
|
34801
|
+
}
|
|
34802
|
+
if (haystacks.name.includes(term)) {
|
|
34803
|
+
score += 45;
|
|
34804
|
+
matches.push(`name~${term}`);
|
|
34805
|
+
}
|
|
34806
|
+
if (haystacks.path.includes(term)) {
|
|
34807
|
+
score += 18;
|
|
34808
|
+
matches.push(`path~${term}`);
|
|
34809
|
+
}
|
|
34810
|
+
if (haystacks.language === term) {
|
|
34811
|
+
score += 18;
|
|
34812
|
+
matches.push(`language=${term}`);
|
|
34813
|
+
}
|
|
34814
|
+
if (haystacks.summary.includes(term)) {
|
|
34815
|
+
score += 16;
|
|
34816
|
+
matches.push(`summary~${term}`);
|
|
34817
|
+
}
|
|
34818
|
+
if (haystacks.text.includes(term)) {
|
|
34819
|
+
score += 5;
|
|
34820
|
+
matches.push(`text~${term}`);
|
|
34821
|
+
}
|
|
34822
|
+
}
|
|
34823
|
+
for (const gram of grams.filter((g) => g.includes(" "))) {
|
|
34824
|
+
if (haystacks.text.includes(gram) || haystacks.summary.includes(gram)) {
|
|
34825
|
+
score += 20;
|
|
34826
|
+
matches.push(`phrase~${gram}`);
|
|
34827
|
+
}
|
|
34828
|
+
}
|
|
34829
|
+
const uniqueMatches = new Set(matches.map((m) => m.replace(/^[^=~]+[=~]/, ""))).size;
|
|
34830
|
+
score += uniqueMatches * 3;
|
|
34831
|
+
return { score, matches };
|
|
34832
|
+
}
|
|
34833
|
+
function filteredFiles(index, options = {}) {
|
|
34834
|
+
const languages = options.languages?.map(normalizeLanguage).filter(Boolean);
|
|
34835
|
+
return Object.values(index.files).filter((file) => {
|
|
34836
|
+
if (languages && languages.length > 0 && !languages.includes(normalizeLanguage(file.language))) return false;
|
|
34837
|
+
return matchesPath(file.path, options.paths);
|
|
34838
|
+
});
|
|
34839
|
+
}
|
|
34840
|
+
function searchCodeContext(query, options = {}) {
|
|
34841
|
+
const terms = tokenize2(query);
|
|
34842
|
+
if (terms.length === 0) return [];
|
|
34843
|
+
const index = loadOrBuildCodeContextIndex({ projectRoot: options.projectRoot, force: options.force || options.refreshIndex, maxFiles: options.maxFiles });
|
|
34844
|
+
const results = [];
|
|
34845
|
+
for (const file of filteredFiles(index, options)) {
|
|
34846
|
+
for (const symbol of file.symbols) {
|
|
34847
|
+
const scored = scoreSymbol(symbol, terms);
|
|
34848
|
+
if (scored.score > 0) {
|
|
34849
|
+
results.push({
|
|
34850
|
+
symbol,
|
|
34851
|
+
score: scored.score,
|
|
34852
|
+
matches: scored.matches,
|
|
34853
|
+
filePath: symbol.filePath,
|
|
34854
|
+
language: symbol.language,
|
|
34855
|
+
content: symbol.text,
|
|
34856
|
+
startLine: symbol.startLine,
|
|
34857
|
+
endLine: symbol.endLine
|
|
34858
|
+
});
|
|
34859
|
+
}
|
|
34860
|
+
}
|
|
34861
|
+
}
|
|
34862
|
+
const offset = Math.max(0, options.offset ?? 0);
|
|
34863
|
+
const limit = options.limit ?? 20;
|
|
34864
|
+
return results.sort((a, b) => b.score - a.score || a.filePath.localeCompare(b.filePath)).slice(offset, offset + limit);
|
|
34865
|
+
}
|
|
34866
|
+
function dependentsMap(index) {
|
|
34867
|
+
const map = /* @__PURE__ */ new Map();
|
|
34868
|
+
for (const file of Object.values(index.files)) {
|
|
34869
|
+
for (const dep of file.resolvedImports) {
|
|
34870
|
+
if (!map.has(dep)) map.set(dep, /* @__PURE__ */ new Set());
|
|
34871
|
+
map.get(dep).add(file.path);
|
|
34872
|
+
}
|
|
34873
|
+
}
|
|
34874
|
+
return map;
|
|
34875
|
+
}
|
|
34876
|
+
function findSymbols(index, symbolName, limit = 20) {
|
|
34877
|
+
const q = symbolName.toLowerCase();
|
|
34878
|
+
const exact = [];
|
|
34879
|
+
const fuzzy = [];
|
|
34880
|
+
for (const file of Object.values(index.files)) {
|
|
34881
|
+
for (const symbol of file.symbols) {
|
|
34882
|
+
if (symbol.name.toLowerCase() === q) exact.push(symbol);
|
|
34883
|
+
else if (symbol.name.toLowerCase().includes(q) || symbol.summary.toLowerCase().includes(q)) fuzzy.push(symbol);
|
|
34884
|
+
}
|
|
34885
|
+
}
|
|
34886
|
+
return [...exact, ...fuzzy].slice(0, limit);
|
|
34887
|
+
}
|
|
34888
|
+
function traceCodeSymbol(symbolName, options = {}) {
|
|
34889
|
+
const index = loadOrBuildCodeContextIndex(options);
|
|
34890
|
+
const matches = findSymbols(index, symbolName, options.limit ?? 20);
|
|
34891
|
+
const dependents = dependentsMap(index);
|
|
34892
|
+
return { query: symbolName, matches, definitions: matches.map((symbol) => {
|
|
34893
|
+
const file = index.files[symbol.filePath];
|
|
34894
|
+
return { symbol, imports: file.resolvedImports, dependents: [...dependents.get(symbol.filePath) ?? /* @__PURE__ */ new Set()].sort(), relatedSymbols: file.symbols.filter((s) => s.id !== symbol.id).slice(0, 20) };
|
|
34895
|
+
}) };
|
|
34896
|
+
}
|
|
34897
|
+
function resolveTargetFile(index, input) {
|
|
34898
|
+
if (input.filePath) {
|
|
34899
|
+
const normalized = input.filePath.replaceAll(path50.sep, "/").replace(/^\.\//, "");
|
|
34900
|
+
if (index.files[normalized]) return { filePath: normalized, target: normalized };
|
|
34901
|
+
const suffix = Object.keys(index.files).find((file) => file.endsWith(normalized));
|
|
34902
|
+
if (suffix) return { filePath: suffix, target: input.filePath };
|
|
34903
|
+
}
|
|
34904
|
+
if (input.symbol) {
|
|
34905
|
+
const match = findSymbols(index, input.symbol, 1)[0];
|
|
34906
|
+
if (match) return { filePath: match.filePath, target: input.symbol };
|
|
34907
|
+
}
|
|
34908
|
+
return null;
|
|
34909
|
+
}
|
|
34910
|
+
function analyzeBlastRadius(input) {
|
|
34911
|
+
const index = loadOrBuildCodeContextIndex({ projectRoot: input.projectRoot, force: input.force });
|
|
34912
|
+
const target = resolveTargetFile(index, { filePath: input.filePath, symbol: input.symbol });
|
|
34913
|
+
if (!target) return null;
|
|
34914
|
+
const dependents = dependentsMap(index);
|
|
34915
|
+
const maxDepth = input.depth ?? 2;
|
|
34916
|
+
const impacted = /* @__PURE__ */ new Map();
|
|
34917
|
+
const queue = [{ filePath: target.filePath, distance: 0 }];
|
|
34918
|
+
impacted.set(target.filePath, { distance: 0, reason: "target" });
|
|
34919
|
+
while (queue.length > 0) {
|
|
34920
|
+
const item = queue.shift();
|
|
34921
|
+
if (item.distance >= maxDepth) continue;
|
|
34922
|
+
for (const dep of dependents.get(item.filePath) ?? []) {
|
|
34923
|
+
if (!impacted.has(dep)) {
|
|
34924
|
+
impacted.set(dep, { distance: item.distance + 1, reason: `imports ${item.filePath}` });
|
|
34925
|
+
queue.push({ filePath: dep, distance: item.distance + 1 });
|
|
34926
|
+
}
|
|
34927
|
+
}
|
|
34928
|
+
}
|
|
34929
|
+
const targetBase = path50.basename(target.filePath).replace(/\.[^.]+$/, "").toLowerCase();
|
|
34930
|
+
const symbolLower = input.symbol?.toLowerCase();
|
|
34931
|
+
const tests = Object.keys(index.files).filter((file) => {
|
|
34932
|
+
const lower = file.toLowerCase();
|
|
34933
|
+
return (lower.includes("test") || lower.includes("spec")) && (lower.includes(targetBase) || (symbolLower ? index.files[file].symbols.some((s) => s.text.toLowerCase().includes(symbolLower)) : false));
|
|
34934
|
+
});
|
|
34935
|
+
for (const test of tests) if (!impacted.has(test)) impacted.set(test, { distance: 1, reason: "related test/spec" });
|
|
34936
|
+
const impactedFiles = [...impacted.entries()].map(([filePath, value]) => ({ filePath, distance: value.distance, reason: value.reason })).sort((a, b) => a.distance - b.distance || a.filePath.localeCompare(b.filePath));
|
|
34937
|
+
const nonTestImpacted = impactedFiles.filter((f) => !f.filePath.match(/(test|spec)\./i)).length;
|
|
34938
|
+
return { target: target.target, targetFile: target.filePath, impactedFiles, tests, symbolsInTarget: index.files[target.filePath]?.symbols ?? [], riskLevel: nonTestImpacted >= 8 ? "high" : nonTestImpacted >= 4 ? "medium" : "low" };
|
|
34939
|
+
}
|
|
34940
|
+
function getCodeContextStats(options = {}) {
|
|
34941
|
+
const index = loadOrBuildCodeContextIndex(options);
|
|
34942
|
+
return {
|
|
34943
|
+
projectRoot: index.projectRoot,
|
|
34944
|
+
branch: index.branch,
|
|
34945
|
+
indexedAt: index.indexedAt,
|
|
34946
|
+
indexAgeMs: Math.max(0, Date.now() - Date.parse(index.indexedAt)),
|
|
34947
|
+
files: Object.keys(index.files).length,
|
|
34948
|
+
symbols: Object.values(index.files).reduce((sum, file) => sum + file.symbols.length, 0),
|
|
34949
|
+
imports: Object.values(index.files).reduce((sum, file) => sum + file.resolvedImports.length, 0),
|
|
34950
|
+
languageBreakdown: index.stats.languageBreakdown,
|
|
34951
|
+
rebuiltFiles: index.stats.rebuiltFiles,
|
|
34952
|
+
reusedFiles: index.stats.reusedFiles,
|
|
34953
|
+
skippedFiles: index.stats.skippedFiles,
|
|
34954
|
+
indexPath: getCodeContextIndexPath(index.projectRoot)
|
|
34955
|
+
};
|
|
34956
|
+
}
|
|
34957
|
+
var INDEX_VERSION, DEFAULT_MAX_FILES, IGNORE_SEGMENTS;
|
|
34958
|
+
var init_code_context_index = __esm({
|
|
34959
|
+
"src/lib/code-context-index.ts"() {
|
|
34960
|
+
"use strict";
|
|
34961
|
+
init_config();
|
|
34962
|
+
init_code_chunker();
|
|
34963
|
+
INDEX_VERSION = 2;
|
|
34964
|
+
DEFAULT_MAX_FILES = 5e3;
|
|
34965
|
+
IGNORE_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", "coverage", ".worktrees", ".next", "build", "target", "vendor"]);
|
|
34966
|
+
}
|
|
34967
|
+
});
|
|
34968
|
+
|
|
33944
34969
|
// src/adapters/opencode/plugin-template.ts
|
|
33945
34970
|
var PLUGIN_TEMPLATE;
|
|
33946
34971
|
var init_plugin_template = __esm({
|
|
@@ -34134,16 +35159,16 @@ __export(installer_exports2, {
|
|
|
34134
35159
|
verifyOpenCodeHooks: () => verifyOpenCodeHooks
|
|
34135
35160
|
});
|
|
34136
35161
|
import { readFile as readFile7, writeFile as writeFile8, mkdir as mkdir8 } from "fs/promises";
|
|
34137
|
-
import { existsSync as
|
|
34138
|
-
import
|
|
35162
|
+
import { existsSync as existsSync37, readFileSync as readFileSync32 } from "fs";
|
|
35163
|
+
import path51 from "path";
|
|
34139
35164
|
import os22 from "os";
|
|
34140
35165
|
async function registerOpenCodeMcp(packageRoot, homeDir = os22.homedir()) {
|
|
34141
35166
|
void packageRoot;
|
|
34142
|
-
const configDir =
|
|
34143
|
-
const configPath =
|
|
35167
|
+
const configDir = path51.join(homeDir, ".config", "opencode");
|
|
35168
|
+
const configPath = path51.join(configDir, "opencode.json");
|
|
34144
35169
|
await mkdir8(configDir, { recursive: true });
|
|
34145
35170
|
let config = {};
|
|
34146
|
-
if (
|
|
35171
|
+
if (existsSync37(configPath)) {
|
|
34147
35172
|
try {
|
|
34148
35173
|
config = JSON.parse(await readFile7(configPath, "utf-8"));
|
|
34149
35174
|
} catch {
|
|
@@ -34171,14 +35196,14 @@ async function registerOpenCodeMcp(packageRoot, homeDir = os22.homedir()) {
|
|
|
34171
35196
|
return true;
|
|
34172
35197
|
}
|
|
34173
35198
|
async function installOpenCodePlugin(packageRoot, homeDir = os22.homedir()) {
|
|
34174
|
-
const pluginDir =
|
|
34175
|
-
const pluginPath =
|
|
35199
|
+
const pluginDir = path51.join(homeDir, ".config", "opencode", "plugins");
|
|
35200
|
+
const pluginPath = path51.join(pluginDir, "exe-os.mjs");
|
|
34176
35201
|
await mkdir8(pluginDir, { recursive: true });
|
|
34177
35202
|
const pluginContent = PLUGIN_TEMPLATE.replace(
|
|
34178
35203
|
/__PACKAGE_ROOT__/g,
|
|
34179
35204
|
packageRoot.replace(/\\/g, "\\\\")
|
|
34180
35205
|
);
|
|
34181
|
-
if (
|
|
35206
|
+
if (existsSync37(pluginPath)) {
|
|
34182
35207
|
const existing = await readFile7(pluginPath, "utf-8");
|
|
34183
35208
|
if (existing === pluginContent) {
|
|
34184
35209
|
return false;
|
|
@@ -34188,18 +35213,18 @@ async function installOpenCodePlugin(packageRoot, homeDir = os22.homedir()) {
|
|
|
34188
35213
|
return true;
|
|
34189
35214
|
}
|
|
34190
35215
|
function verifyOpenCodeHooks(homeDir = os22.homedir()) {
|
|
34191
|
-
const configPath =
|
|
34192
|
-
const pluginPath =
|
|
34193
|
-
if (!
|
|
35216
|
+
const configPath = path51.join(homeDir, ".config", "opencode", "opencode.json");
|
|
35217
|
+
const pluginPath = path51.join(homeDir, ".config", "opencode", "plugins", "exe-os.mjs");
|
|
35218
|
+
if (!existsSync37(configPath)) return false;
|
|
34194
35219
|
try {
|
|
34195
|
-
const config = JSON.parse(
|
|
35220
|
+
const config = JSON.parse(readFileSync32(configPath, "utf-8"));
|
|
34196
35221
|
if (!config.mcp?.["exe-os"]?.enabled) return false;
|
|
34197
35222
|
} catch {
|
|
34198
35223
|
return false;
|
|
34199
35224
|
}
|
|
34200
|
-
if (!
|
|
35225
|
+
if (!existsSync37(pluginPath)) return false;
|
|
34201
35226
|
try {
|
|
34202
|
-
const plugin =
|
|
35227
|
+
const plugin = readFileSync32(pluginPath, "utf-8");
|
|
34203
35228
|
if (!plugin.includes(EXE_HOOK_FILES.postToolCombined)) return false;
|
|
34204
35229
|
if (textHasLegacySplitPostToolHook(plugin)) return false;
|
|
34205
35230
|
} catch {
|
|
@@ -34241,19 +35266,19 @@ __export(installer_exports3, {
|
|
|
34241
35266
|
verifyCodexHooks: () => verifyCodexHooks
|
|
34242
35267
|
});
|
|
34243
35268
|
import { readFile as readFile8, writeFile as writeFile9, mkdir as mkdir9 } from "fs/promises";
|
|
34244
|
-
import { existsSync as
|
|
34245
|
-
import
|
|
35269
|
+
import { existsSync as existsSync38, readFileSync as readFileSync33 } from "fs";
|
|
35270
|
+
import path52 from "path";
|
|
34246
35271
|
import os23 from "os";
|
|
34247
35272
|
async function mergeCodexHooks(packageRoot, homeDir = os23.homedir()) {
|
|
34248
|
-
const codexDir =
|
|
34249
|
-
const hooksPath =
|
|
34250
|
-
const logsDir =
|
|
34251
|
-
const hookLogPath =
|
|
35273
|
+
const codexDir = path52.join(homeDir, ".codex");
|
|
35274
|
+
const hooksPath = path52.join(codexDir, "hooks.json");
|
|
35275
|
+
const logsDir = path52.join(homeDir, ".exe-os", "logs");
|
|
35276
|
+
const hookLogPath = path52.join(logsDir, "hooks.log");
|
|
34252
35277
|
const logSuffix = ` 2>> "${hookLogPath}"`;
|
|
34253
35278
|
await mkdir9(codexDir, { recursive: true });
|
|
34254
35279
|
await mkdir9(logsDir, { recursive: true });
|
|
34255
35280
|
let hooksJson = {};
|
|
34256
|
-
if (
|
|
35281
|
+
if (existsSync38(hooksPath)) {
|
|
34257
35282
|
try {
|
|
34258
35283
|
hooksJson = JSON.parse(await readFile8(hooksPath, "utf-8"));
|
|
34259
35284
|
} catch {
|
|
@@ -34273,7 +35298,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os23.homedir()) {
|
|
|
34273
35298
|
// Combined hook: runs ingest + error-recall in one Node process.
|
|
34274
35299
|
// Eliminates a cold-start cycle per tool call (~3-6s savings on Codex).
|
|
34275
35300
|
type: "command",
|
|
34276
|
-
command: `node "${
|
|
35301
|
+
command: `node "${path52.join(packageRoot, "dist", "hooks", "post-tool-combined.js")}"${logSuffix}`
|
|
34277
35302
|
}
|
|
34278
35303
|
]
|
|
34279
35304
|
},
|
|
@@ -34287,7 +35312,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os23.homedir()) {
|
|
|
34287
35312
|
// Single hook: prompt-submit handles memory retrieval + entity boost.
|
|
34288
35313
|
// exe-heartbeat-hook is CC-specific (intercom) — omitted on Codex.
|
|
34289
35314
|
type: "command",
|
|
34290
|
-
command: `node "${
|
|
35315
|
+
command: `node "${path52.join(packageRoot, "dist", "hooks", "prompt-submit.js")}"${logSuffix}`
|
|
34291
35316
|
}
|
|
34292
35317
|
]
|
|
34293
35318
|
},
|
|
@@ -34299,7 +35324,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os23.homedir()) {
|
|
|
34299
35324
|
hooks: [
|
|
34300
35325
|
{
|
|
34301
35326
|
type: "command",
|
|
34302
|
-
command: `node "${
|
|
35327
|
+
command: `node "${path52.join(packageRoot, "dist", "hooks", "stop.js")}"${logSuffix}`
|
|
34303
35328
|
}
|
|
34304
35329
|
]
|
|
34305
35330
|
},
|
|
@@ -34312,7 +35337,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os23.homedir()) {
|
|
|
34312
35337
|
hooks: [
|
|
34313
35338
|
{
|
|
34314
35339
|
type: "command",
|
|
34315
|
-
command: `node "${
|
|
35340
|
+
command: `node "${path52.join(packageRoot, "dist", "hooks", "pre-tool-use.js")}"${logSuffix}`
|
|
34316
35341
|
}
|
|
34317
35342
|
]
|
|
34318
35343
|
},
|
|
@@ -34361,10 +35386,10 @@ async function mergeCodexHooks(packageRoot, homeDir = os23.homedir()) {
|
|
|
34361
35386
|
return { added, skipped };
|
|
34362
35387
|
}
|
|
34363
35388
|
function verifyCodexHooks(homeDir = os23.homedir()) {
|
|
34364
|
-
const hooksPath =
|
|
34365
|
-
if (!
|
|
35389
|
+
const hooksPath = path52.join(homeDir, ".codex", "hooks.json");
|
|
35390
|
+
if (!existsSync38(hooksPath)) return false;
|
|
34366
35391
|
try {
|
|
34367
|
-
const hooksJson = JSON.parse(
|
|
35392
|
+
const hooksJson = JSON.parse(readFileSync33(hooksPath, "utf-8"));
|
|
34368
35393
|
if (!hooksJson.hooks) return false;
|
|
34369
35394
|
const required = ["PostToolUse", "UserPromptSubmit", "Stop", "PreToolUse"];
|
|
34370
35395
|
for (const event of required) {
|
|
@@ -34396,11 +35421,11 @@ function verifyCodexHooks(homeDir = os23.homedir()) {
|
|
|
34396
35421
|
async function installCodexStatusLine(homeDir = os23.homedir()) {
|
|
34397
35422
|
const prefs = loadPreferences(homeDir);
|
|
34398
35423
|
if (prefs.codexStatusLine === false) return "opted-out";
|
|
34399
|
-
const codexDir =
|
|
34400
|
-
const configPath =
|
|
35424
|
+
const codexDir = path52.join(homeDir, ".codex");
|
|
35425
|
+
const configPath = path52.join(codexDir, "config.toml");
|
|
34401
35426
|
await mkdir9(codexDir, { recursive: true });
|
|
34402
35427
|
let content = "";
|
|
34403
|
-
if (
|
|
35428
|
+
if (existsSync38(configPath)) {
|
|
34404
35429
|
content = await readFile8(configPath, "utf-8");
|
|
34405
35430
|
if (/\[tui\][\s\S]*?status_line\s*=/.test(content)) {
|
|
34406
35431
|
return "already-configured";
|
|
@@ -34452,12 +35477,12 @@ ${line}
|
|
|
34452
35477
|
return { content: next, changed: next !== sectionContent };
|
|
34453
35478
|
}
|
|
34454
35479
|
async function registerCodexMcpServer(packageRoot, homeDir = os23.homedir()) {
|
|
34455
|
-
const codexDir =
|
|
34456
|
-
const configPath =
|
|
35480
|
+
const codexDir = path52.join(homeDir, ".codex");
|
|
35481
|
+
const configPath = path52.join(codexDir, "config.toml");
|
|
34457
35482
|
void packageRoot;
|
|
34458
35483
|
await mkdir9(codexDir, { recursive: true });
|
|
34459
35484
|
let content = "";
|
|
34460
|
-
if (
|
|
35485
|
+
if (existsSync38(configPath)) {
|
|
34461
35486
|
content = await readFile8(configPath, "utf-8");
|
|
34462
35487
|
}
|
|
34463
35488
|
const sectionHeader = "[mcp_servers.exe-os]";
|
|
@@ -34482,10 +35507,10 @@ async function registerCodexMcpServer(packageRoot, homeDir = os23.homedir()) {
|
|
|
34482
35507
|
return "registered";
|
|
34483
35508
|
}
|
|
34484
35509
|
async function ensureCodexHooksFeature(homeDir = os23.homedir()) {
|
|
34485
|
-
const configPath =
|
|
34486
|
-
await mkdir9(
|
|
35510
|
+
const configPath = path52.join(homeDir, ".codex", "config.toml");
|
|
35511
|
+
await mkdir9(path52.join(homeDir, ".codex"), { recursive: true });
|
|
34487
35512
|
let content = "";
|
|
34488
|
-
if (
|
|
35513
|
+
if (existsSync38(configPath)) {
|
|
34489
35514
|
content = await readFile8(configPath, "utf-8");
|
|
34490
35515
|
}
|
|
34491
35516
|
if (/\[features\][\s\S]*?codex_hooks\s*=\s*true/.test(content)) {
|
|
@@ -34560,32 +35585,32 @@ __export(mcp_diagnostics_exports, {
|
|
|
34560
35585
|
diagnoseClaudeMcpConfig: () => diagnoseClaudeMcpConfig,
|
|
34561
35586
|
formatMcpDiagnosticReport: () => formatMcpDiagnosticReport
|
|
34562
35587
|
});
|
|
34563
|
-
import { existsSync as
|
|
34564
|
-
import
|
|
35588
|
+
import { existsSync as existsSync39, readFileSync as readFileSync34 } from "fs";
|
|
35589
|
+
import path53 from "path";
|
|
34565
35590
|
import os24 from "os";
|
|
34566
35591
|
function readJson(filePath) {
|
|
34567
|
-
if (!
|
|
35592
|
+
if (!existsSync39(filePath)) return null;
|
|
34568
35593
|
try {
|
|
34569
|
-
return JSON.parse(
|
|
35594
|
+
return JSON.parse(readFileSync34(filePath, "utf8"));
|
|
34570
35595
|
} catch {
|
|
34571
35596
|
return null;
|
|
34572
35597
|
}
|
|
34573
35598
|
}
|
|
34574
35599
|
function pathApplies2(projectPath, cwd2) {
|
|
34575
|
-
const project =
|
|
34576
|
-
const current =
|
|
34577
|
-
return current === project || current.startsWith(project +
|
|
35600
|
+
const project = path53.resolve(projectPath);
|
|
35601
|
+
const current = path53.resolve(cwd2);
|
|
35602
|
+
return current === project || current.startsWith(project + path53.sep);
|
|
34578
35603
|
}
|
|
34579
35604
|
function findAncestorMcpJsons2(cwd2, homeDir) {
|
|
34580
35605
|
const files = [];
|
|
34581
|
-
let dir =
|
|
34582
|
-
const root =
|
|
34583
|
-
const stop =
|
|
35606
|
+
let dir = path53.resolve(cwd2);
|
|
35607
|
+
const root = path53.parse(dir).root;
|
|
35608
|
+
const stop = path53.resolve(homeDir);
|
|
34584
35609
|
while (dir !== root) {
|
|
34585
|
-
const candidate =
|
|
34586
|
-
if (
|
|
35610
|
+
const candidate = path53.join(dir, ".mcp.json");
|
|
35611
|
+
if (existsSync39(candidate)) files.push(candidate);
|
|
34587
35612
|
if (dir === stop) break;
|
|
34588
|
-
dir =
|
|
35613
|
+
dir = path53.dirname(dir);
|
|
34589
35614
|
}
|
|
34590
35615
|
return files;
|
|
34591
35616
|
}
|
|
@@ -34602,8 +35627,8 @@ function serverAllowedByPermissions(serverName, prefixes) {
|
|
|
34602
35627
|
return prefixes.has(serverName) || prefixes.has(serverName.replace(/-/g, "_"));
|
|
34603
35628
|
}
|
|
34604
35629
|
function diagnoseClaudeMcpConfig(homeDir = os24.homedir(), cwd2 = process.cwd(), env = process.env) {
|
|
34605
|
-
const claudeJsonPath =
|
|
34606
|
-
const settingsPath =
|
|
35630
|
+
const claudeJsonPath = path53.join(homeDir, ".claude.json");
|
|
35631
|
+
const settingsPath = path53.join(homeDir, ".claude", "settings.json");
|
|
34607
35632
|
const claudeJson = readJson(claudeJsonPath);
|
|
34608
35633
|
const settings = readJson(settingsPath);
|
|
34609
35634
|
const issues = [];
|
|
@@ -34723,7 +35748,7 @@ function diagnoseClaudeMcpConfig(homeDir = os24.homedir(), cwd2 = process.cwd(),
|
|
|
34723
35748
|
});
|
|
34724
35749
|
}
|
|
34725
35750
|
return {
|
|
34726
|
-
cwd:
|
|
35751
|
+
cwd: path53.resolve(cwd2),
|
|
34727
35752
|
globalServers,
|
|
34728
35753
|
projectServers: projectServers.sort((a, b) => a.name.localeCompare(b.name)),
|
|
34729
35754
|
mcpJsonServers: mcpJsonServers.sort((a, b) => a.name.localeCompare(b.name)),
|
|
@@ -34760,14 +35785,14 @@ var init_mcp_diagnostics = __esm({
|
|
|
34760
35785
|
});
|
|
34761
35786
|
|
|
34762
35787
|
// src/bin/cli.ts
|
|
34763
|
-
import { existsSync as
|
|
34764
|
-
import
|
|
35788
|
+
import { existsSync as existsSync40, readFileSync as readFileSync35, writeFileSync as writeFileSync26, readdirSync as readdirSync12, rmSync as rmSync2 } from "fs";
|
|
35789
|
+
import path54 from "path";
|
|
34765
35790
|
import os25 from "os";
|
|
34766
35791
|
var args = process.argv.slice(2);
|
|
34767
35792
|
if (args.includes("--version") || args.includes("-v")) {
|
|
34768
35793
|
try {
|
|
34769
|
-
const pkgPath =
|
|
34770
|
-
const pkg = JSON.parse(
|
|
35794
|
+
const pkgPath = path54.join(path54.dirname(new URL(import.meta.url).pathname), "..", "..", "package.json");
|
|
35795
|
+
const pkg = JSON.parse(readFileSync35(pkgPath, "utf8"));
|
|
34771
35796
|
console.log(pkg.version);
|
|
34772
35797
|
} catch {
|
|
34773
35798
|
console.log("unknown");
|
|
@@ -34842,6 +35867,8 @@ if (args.includes("--global")) {
|
|
|
34842
35867
|
} else if (args[0] === "org") {
|
|
34843
35868
|
const { main: runOrg } = await Promise.resolve().then(() => (init_exe_org(), exe_org_exports));
|
|
34844
35869
|
await runOrg(args.slice(1));
|
|
35870
|
+
} else if (args[0] === "code-context") {
|
|
35871
|
+
await runCodeContext(args.slice(1));
|
|
34845
35872
|
} else if (args[0] === "jobs") {
|
|
34846
35873
|
const { cancelBackgroundJob: cancelBackgroundJob2, formatJobs: formatJobs2, listBackgroundJobs: listBackgroundJobs2 } = await Promise.resolve().then(() => (init_background_jobs(), background_jobs_exports));
|
|
34847
35874
|
const sub = args[1] ?? "status";
|
|
@@ -34982,16 +36009,19 @@ ID: ${result.id}`);
|
|
|
34982
36009
|
} else if (args[0] === "stack-update") {
|
|
34983
36010
|
const { runStackUpdateCli } = await Promise.resolve().then(() => (init_stack_update2(), stack_update_exports));
|
|
34984
36011
|
await runStackUpdateCli();
|
|
36012
|
+
} else if (args[0] === "registry-proxy") {
|
|
36013
|
+
const { main: runRegistryProxy2 } = await Promise.resolve().then(() => (init_registry_proxy2(), registry_proxy_exports));
|
|
36014
|
+
await runRegistryProxy2(args.slice(1));
|
|
34985
36015
|
} else if (args.includes("--tui") || args.includes("--demo") || args[0] === "tui") {
|
|
34986
36016
|
checkForUpdateOnBoot().catch(() => {
|
|
34987
36017
|
});
|
|
34988
36018
|
await init_App2().then(() => App_exports);
|
|
34989
36019
|
} else {
|
|
34990
|
-
const claudeDir =
|
|
34991
|
-
const settingsPath =
|
|
34992
|
-
const hasClaudeCode =
|
|
36020
|
+
const claudeDir = path54.join(os25.homedir(), ".claude");
|
|
36021
|
+
const settingsPath = path54.join(claudeDir, "settings.json");
|
|
36022
|
+
const hasClaudeCode = existsSync40(settingsPath) && (() => {
|
|
34993
36023
|
try {
|
|
34994
|
-
const raw =
|
|
36024
|
+
const raw = readFileSync35(settingsPath, "utf8");
|
|
34995
36025
|
return raw.includes("exe-os") || raw.includes("exe-mem");
|
|
34996
36026
|
} catch {
|
|
34997
36027
|
return false;
|
|
@@ -35001,9 +36031,9 @@ ID: ${result.id}`);
|
|
|
35001
36031
|
const { DEFAULT_COORDINATOR_TEMPLATE_NAME: DEFAULT_COORDINATOR_TEMPLATE_NAME2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
35002
36032
|
let cooName = DEFAULT_COORDINATOR_TEMPLATE_NAME2;
|
|
35003
36033
|
try {
|
|
35004
|
-
const rosterPath =
|
|
35005
|
-
if (
|
|
35006
|
-
const roster = JSON.parse(
|
|
36034
|
+
const rosterPath = path54.join(os25.homedir(), ".exe-os", "exe-employees.json");
|
|
36035
|
+
if (existsSync40(rosterPath)) {
|
|
36036
|
+
const roster = JSON.parse(readFileSync35(rosterPath, "utf8"));
|
|
35007
36037
|
const coo = roster.find((e) => e.role === "COO");
|
|
35008
36038
|
if (coo) cooName = coo.name;
|
|
35009
36039
|
}
|
|
@@ -35042,6 +36072,82 @@ cd into a project folder and run \x1B[1m${cooName}1\x1B[0m to boot your COO.
|
|
|
35042
36072
|
await init_App2().then(() => App_exports);
|
|
35043
36073
|
}
|
|
35044
36074
|
}
|
|
36075
|
+
async function runCodeContext(ccArgs) {
|
|
36076
|
+
const { buildCodeContextIndex: buildCodeContextIndex2, getCodeContextStats: getCodeContextStats2, searchCodeContext: searchCodeContext2 } = await Promise.resolve().then(() => (init_code_context_index(), code_context_index_exports));
|
|
36077
|
+
const sub = ccArgs[0] ?? "status";
|
|
36078
|
+
const projectRoot = valueAfter(ccArgs, "--project-root") ?? process.cwd();
|
|
36079
|
+
if (sub === "init" || sub === "index") {
|
|
36080
|
+
const force = ccArgs.includes("--force") || ccArgs.includes("--refresh");
|
|
36081
|
+
const index = buildCodeContextIndex2({ projectRoot, force });
|
|
36082
|
+
console.log(JSON.stringify({ projectRoot: index.projectRoot, branch: index.branch, indexedAt: index.indexedAt, files: Object.keys(index.files).length, symbols: Object.values(index.files).reduce((sum, f) => sum + f.symbols.length, 0), languageBreakdown: index.stats.languageBreakdown, rebuiltFiles: index.stats.rebuiltFiles, reusedFiles: index.stats.reusedFiles }, null, 2));
|
|
36083
|
+
return;
|
|
36084
|
+
}
|
|
36085
|
+
if (sub === "status" || sub === "stats") {
|
|
36086
|
+
console.log(JSON.stringify(getCodeContextStats2({ projectRoot }), null, 2));
|
|
36087
|
+
return;
|
|
36088
|
+
}
|
|
36089
|
+
if (sub === "search") {
|
|
36090
|
+
const query = valueAfter(ccArgs, "--query") ?? positionalSearchQuery(ccArgs.slice(1));
|
|
36091
|
+
if (!query) {
|
|
36092
|
+
console.error("Usage: exe-os code-context search <query> [--limit N] [--offset N] [--language python] [--path 'src/**'] [--refresh]");
|
|
36093
|
+
process.exit(1);
|
|
36094
|
+
}
|
|
36095
|
+
const languageFilters = [...valuesAfter(ccArgs, "--language") ?? [], ...valuesAfter(ccArgs, "--languages") ?? []];
|
|
36096
|
+
const pathFilters = [...valuesAfter(ccArgs, "--path") ?? [], ...valuesAfter(ccArgs, "--paths") ?? []];
|
|
36097
|
+
const results = searchCodeContext2(query, {
|
|
36098
|
+
projectRoot,
|
|
36099
|
+
limit: numberAfter(ccArgs, "--limit") ?? 20,
|
|
36100
|
+
offset: numberAfter(ccArgs, "--offset") ?? 0,
|
|
36101
|
+
refreshIndex: ccArgs.includes("--refresh") || ccArgs.includes("--refresh-index"),
|
|
36102
|
+
languages: languageFilters.length > 0 ? languageFilters : void 0,
|
|
36103
|
+
paths: pathFilters.length > 0 ? pathFilters : void 0
|
|
36104
|
+
});
|
|
36105
|
+
console.log(JSON.stringify({ query, results }, null, 2));
|
|
36106
|
+
return;
|
|
36107
|
+
}
|
|
36108
|
+
if (sub === "doctor") {
|
|
36109
|
+
const stats = getCodeContextStats2({ projectRoot });
|
|
36110
|
+
console.log(JSON.stringify({ ok: stats.files > 0, ...stats }, null, 2));
|
|
36111
|
+
process.exit(stats.files > 0 ? 0 : 1);
|
|
36112
|
+
}
|
|
36113
|
+
console.error("Usage: exe-os code-context init|index|status|stats|search|doctor");
|
|
36114
|
+
process.exit(1);
|
|
36115
|
+
}
|
|
36116
|
+
function positionalSearchQuery(args2) {
|
|
36117
|
+
const valueFlags = /* @__PURE__ */ new Set(["--project-root", "--limit", "--offset", "--language", "--languages", "--path", "--paths", "--query"]);
|
|
36118
|
+
const parts = [];
|
|
36119
|
+
for (let i = 0; i < args2.length; i++) {
|
|
36120
|
+
const arg = args2[i];
|
|
36121
|
+
if (valueFlags.has(arg)) {
|
|
36122
|
+
i++;
|
|
36123
|
+
continue;
|
|
36124
|
+
}
|
|
36125
|
+
if (arg.startsWith("--")) continue;
|
|
36126
|
+
parts.push(arg);
|
|
36127
|
+
}
|
|
36128
|
+
return parts.join(" ").trim();
|
|
36129
|
+
}
|
|
36130
|
+
function valueAfter(args2, flag) {
|
|
36131
|
+
const i = args2.indexOf(flag);
|
|
36132
|
+
if (i >= 0) return args2[i + 1];
|
|
36133
|
+
const prefixed = args2.find((arg) => arg.startsWith(`${flag}=`));
|
|
36134
|
+
return prefixed?.slice(flag.length + 1);
|
|
36135
|
+
}
|
|
36136
|
+
function valuesAfter(args2, flag) {
|
|
36137
|
+
const values = [];
|
|
36138
|
+
for (let i = 0; i < args2.length; i++) {
|
|
36139
|
+
const arg = args2[i];
|
|
36140
|
+
if (arg === flag && args2[i + 1]) values.push(args2[++i]);
|
|
36141
|
+
else if (arg.startsWith(`${flag}=`)) values.push(arg.slice(flag.length + 1));
|
|
36142
|
+
}
|
|
36143
|
+
return values.length > 0 ? values.flatMap((v) => v.split(",").map((s) => s.trim()).filter(Boolean)) : void 0;
|
|
36144
|
+
}
|
|
36145
|
+
function numberAfter(args2, flag) {
|
|
36146
|
+
const value = valueAfter(args2, flag);
|
|
36147
|
+
if (!value) return void 0;
|
|
36148
|
+
const parsed = Number(value);
|
|
36149
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
36150
|
+
}
|
|
35045
36151
|
async function runClaudeInstall() {
|
|
35046
36152
|
const { runInstaller: runInstaller2 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
|
|
35047
36153
|
try {
|
|
@@ -35080,14 +36186,14 @@ async function runCodexInstall() {
|
|
|
35080
36186
|
}
|
|
35081
36187
|
async function runClaudeCheck() {
|
|
35082
36188
|
const { detectMcpNameCollisions: detectMcpNameCollisions2 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
|
|
35083
|
-
const claudeDir =
|
|
35084
|
-
const settingsPath =
|
|
35085
|
-
const claudeJsonPath =
|
|
36189
|
+
const claudeDir = path54.join(os25.homedir(), ".claude");
|
|
36190
|
+
const settingsPath = path54.join(claudeDir, "settings.json");
|
|
36191
|
+
const claudeJsonPath = path54.join(os25.homedir(), ".claude.json");
|
|
35086
36192
|
let ok = true;
|
|
35087
|
-
if (
|
|
36193
|
+
if (existsSync40(settingsPath)) {
|
|
35088
36194
|
let settings;
|
|
35089
36195
|
try {
|
|
35090
|
-
settings = JSON.parse(
|
|
36196
|
+
settings = JSON.parse(readFileSync35(settingsPath, "utf8"));
|
|
35091
36197
|
} catch {
|
|
35092
36198
|
console.log("\x1B[31m\u2717\x1B[0m settings.json is malformed (invalid JSON)");
|
|
35093
36199
|
ok = false;
|
|
@@ -35113,10 +36219,10 @@ async function runClaudeCheck() {
|
|
|
35113
36219
|
console.log("\x1B[31m\u2717\x1B[0m settings.json not found");
|
|
35114
36220
|
ok = false;
|
|
35115
36221
|
}
|
|
35116
|
-
if (
|
|
36222
|
+
if (existsSync40(claudeJsonPath)) {
|
|
35117
36223
|
let claudeJson;
|
|
35118
36224
|
try {
|
|
35119
|
-
claudeJson = JSON.parse(
|
|
36225
|
+
claudeJson = JSON.parse(readFileSync35(claudeJsonPath, "utf8"));
|
|
35120
36226
|
} catch {
|
|
35121
36227
|
console.log("\x1B[31m\u2717\x1B[0m claude.json is malformed (invalid JSON)");
|
|
35122
36228
|
ok = false;
|
|
@@ -35149,8 +36255,8 @@ async function runClaudeCheck() {
|
|
|
35149
36255
|
} else {
|
|
35150
36256
|
console.log("\x1B[32m\u2713\x1B[0m No .mcp.json/project MCP name collisions detected");
|
|
35151
36257
|
}
|
|
35152
|
-
const skillsDir =
|
|
35153
|
-
if (
|
|
36258
|
+
const skillsDir = path54.join(claudeDir, "skills");
|
|
36259
|
+
if (existsSync40(skillsDir)) {
|
|
35154
36260
|
console.log("\x1B[32m\u2713\x1B[0m Slash skills directory exists");
|
|
35155
36261
|
} else {
|
|
35156
36262
|
console.log("\x1B[31m\u2717\x1B[0m Slash skills directory missing");
|
|
@@ -35174,16 +36280,16 @@ async function runClaudeUninstall(flags = []) {
|
|
|
35174
36280
|
const dryRun = flags.includes("--dry-run");
|
|
35175
36281
|
const purge = flags.includes("--purge");
|
|
35176
36282
|
const homeDir = os25.homedir();
|
|
35177
|
-
const claudeDir =
|
|
35178
|
-
const settingsPath =
|
|
35179
|
-
const claudeJsonPath =
|
|
35180
|
-
const exeOsDir =
|
|
36283
|
+
const claudeDir = path54.join(homeDir, ".claude");
|
|
36284
|
+
const settingsPath = path54.join(claudeDir, "settings.json");
|
|
36285
|
+
const claudeJsonPath = path54.join(homeDir, ".claude.json");
|
|
36286
|
+
const exeOsDir = path54.join(homeDir, ".exe-os");
|
|
35181
36287
|
let removed = 0;
|
|
35182
36288
|
const log = (msg) => console.log(dryRun ? `[dry-run] ${msg}` : msg);
|
|
35183
36289
|
let settings = {};
|
|
35184
|
-
if (
|
|
36290
|
+
if (existsSync40(settingsPath)) {
|
|
35185
36291
|
try {
|
|
35186
|
-
settings = JSON.parse(
|
|
36292
|
+
settings = JSON.parse(readFileSync35(settingsPath, "utf8"));
|
|
35187
36293
|
} catch {
|
|
35188
36294
|
console.error("Your ~/.claude/settings.json appears malformed.");
|
|
35189
36295
|
if (purge) {
|
|
@@ -35221,15 +36327,15 @@ async function runClaudeUninstall(flags = []) {
|
|
|
35221
36327
|
permCount = before - settings.permissions.allow.length;
|
|
35222
36328
|
}
|
|
35223
36329
|
if (!dryRun) {
|
|
35224
|
-
|
|
36330
|
+
writeFileSync26(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
35225
36331
|
}
|
|
35226
36332
|
log("\u2713 Removed exe-os hooks from settings.json");
|
|
35227
36333
|
if (permCount > 0) log(`\u2713 Removed ${permCount} MCP permission entries`);
|
|
35228
36334
|
removed++;
|
|
35229
36335
|
}
|
|
35230
36336
|
}
|
|
35231
|
-
if (
|
|
35232
|
-
const raw =
|
|
36337
|
+
if (existsSync40(claudeJsonPath)) {
|
|
36338
|
+
const raw = readFileSync35(claudeJsonPath, "utf8");
|
|
35233
36339
|
if (raw.length > 1e6) {
|
|
35234
36340
|
console.error("claude.json exceeds 1 MB \u2014 skipping parse.");
|
|
35235
36341
|
} else {
|
|
@@ -35250,7 +36356,7 @@ async function runClaudeUninstall(flags = []) {
|
|
|
35250
36356
|
}
|
|
35251
36357
|
if (removedMcp) {
|
|
35252
36358
|
if (!dryRun) {
|
|
35253
|
-
|
|
36359
|
+
writeFileSync26(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
35254
36360
|
}
|
|
35255
36361
|
log("\u2713 Removed exe-os MCP server from claude.json");
|
|
35256
36362
|
removed++;
|
|
@@ -35258,14 +36364,14 @@ async function runClaudeUninstall(flags = []) {
|
|
|
35258
36364
|
}
|
|
35259
36365
|
}
|
|
35260
36366
|
}
|
|
35261
|
-
const skillsDir =
|
|
35262
|
-
if (
|
|
36367
|
+
const skillsDir = path54.join(claudeDir, "skills");
|
|
36368
|
+
if (existsSync40(skillsDir)) {
|
|
35263
36369
|
let skillCount = 0;
|
|
35264
36370
|
try {
|
|
35265
|
-
const entries =
|
|
36371
|
+
const entries = readdirSync12(skillsDir);
|
|
35266
36372
|
for (const entry of entries) {
|
|
35267
36373
|
if (entry.startsWith("exe")) {
|
|
35268
|
-
const fullPath =
|
|
36374
|
+
const fullPath = path54.join(skillsDir, entry);
|
|
35269
36375
|
if (!dryRun) rmSync2(fullPath, { recursive: true, force: true });
|
|
35270
36376
|
skillCount++;
|
|
35271
36377
|
}
|
|
@@ -35277,30 +36383,30 @@ async function runClaudeUninstall(flags = []) {
|
|
|
35277
36383
|
removed++;
|
|
35278
36384
|
}
|
|
35279
36385
|
}
|
|
35280
|
-
const claudeMdPath =
|
|
35281
|
-
if (
|
|
35282
|
-
const content =
|
|
36386
|
+
const claudeMdPath = path54.join(claudeDir, "CLAUDE.md");
|
|
36387
|
+
if (existsSync40(claudeMdPath)) {
|
|
36388
|
+
const content = readFileSync35(claudeMdPath, "utf8");
|
|
35283
36389
|
const startMarker = "<!-- exe-os:orchestration-start -->";
|
|
35284
36390
|
const endMarker = "<!-- exe-os:orchestration-end -->";
|
|
35285
36391
|
const startIdx = content.indexOf(startMarker);
|
|
35286
36392
|
const endIdx = content.indexOf(endMarker);
|
|
35287
36393
|
if (startIdx !== -1 && endIdx !== -1) {
|
|
35288
36394
|
const cleaned = (content.slice(0, startIdx) + content.slice(endIdx + endMarker.length)).replace(/\n{3,}/g, "\n\n").trim() + "\n";
|
|
35289
|
-
if (!dryRun)
|
|
36395
|
+
if (!dryRun) writeFileSync26(claudeMdPath, cleaned);
|
|
35290
36396
|
log("\u2713 Removed orchestration block from CLAUDE.md");
|
|
35291
36397
|
removed++;
|
|
35292
36398
|
}
|
|
35293
36399
|
}
|
|
35294
|
-
const agentsDir =
|
|
35295
|
-
if (
|
|
36400
|
+
const agentsDir = path54.join(claudeDir, "agents");
|
|
36401
|
+
if (existsSync40(agentsDir)) {
|
|
35296
36402
|
let agentCount = 0;
|
|
35297
36403
|
try {
|
|
35298
|
-
const entries =
|
|
36404
|
+
const entries = readdirSync12(agentsDir).filter((f) => f.endsWith(".md"));
|
|
35299
36405
|
let knownNames = /* @__PURE__ */ new Set();
|
|
35300
|
-
const rosterPath =
|
|
35301
|
-
if (
|
|
36406
|
+
const rosterPath = path54.join(exeOsDir, "exe-employees.json");
|
|
36407
|
+
if (existsSync40(rosterPath)) {
|
|
35302
36408
|
try {
|
|
35303
|
-
const roster = JSON.parse(
|
|
36409
|
+
const roster = JSON.parse(readFileSync35(rosterPath, "utf8"));
|
|
35304
36410
|
knownNames = new Set(roster.map((e) => e.name));
|
|
35305
36411
|
} catch {
|
|
35306
36412
|
}
|
|
@@ -35308,7 +36414,7 @@ async function runClaudeUninstall(flags = []) {
|
|
|
35308
36414
|
for (const entry of entries) {
|
|
35309
36415
|
const name = entry.replace(/\.md$/, "");
|
|
35310
36416
|
if (knownNames.has(name)) {
|
|
35311
|
-
if (!dryRun) rmSync2(
|
|
36417
|
+
if (!dryRun) rmSync2(path54.join(agentsDir, entry), { force: true });
|
|
35312
36418
|
agentCount++;
|
|
35313
36419
|
}
|
|
35314
36420
|
}
|
|
@@ -35319,16 +36425,16 @@ async function runClaudeUninstall(flags = []) {
|
|
|
35319
36425
|
removed++;
|
|
35320
36426
|
}
|
|
35321
36427
|
}
|
|
35322
|
-
const projectsDir =
|
|
35323
|
-
if (
|
|
36428
|
+
const projectsDir = path54.join(claudeDir, "projects");
|
|
36429
|
+
if (existsSync40(projectsDir)) {
|
|
35324
36430
|
let projectCount = 0;
|
|
35325
36431
|
try {
|
|
35326
|
-
const projects =
|
|
36432
|
+
const projects = readdirSync12(projectsDir);
|
|
35327
36433
|
for (const proj of projects) {
|
|
35328
|
-
const projSettings =
|
|
35329
|
-
if (!
|
|
36434
|
+
const projSettings = path54.join(projectsDir, proj, "settings.json");
|
|
36435
|
+
if (!existsSync40(projSettings)) continue;
|
|
35330
36436
|
try {
|
|
35331
|
-
const pSettings = JSON.parse(
|
|
36437
|
+
const pSettings = JSON.parse(readFileSync35(projSettings, "utf8"));
|
|
35332
36438
|
let changed = false;
|
|
35333
36439
|
if (Array.isArray(pSettings.permissions?.allow)) {
|
|
35334
36440
|
const before = pSettings.permissions.allow.length;
|
|
@@ -35338,7 +36444,7 @@ async function runClaudeUninstall(flags = []) {
|
|
|
35338
36444
|
if (pSettings.permissions.allow.length < before) changed = true;
|
|
35339
36445
|
}
|
|
35340
36446
|
if (changed && !dryRun) {
|
|
35341
|
-
|
|
36447
|
+
writeFileSync26(projSettings, JSON.stringify(pSettings, null, 2) + "\n");
|
|
35342
36448
|
}
|
|
35343
36449
|
if (changed) projectCount++;
|
|
35344
36450
|
} catch {
|
|
@@ -35362,18 +36468,18 @@ async function runClaudeUninstall(flags = []) {
|
|
|
35362
36468
|
};
|
|
35363
36469
|
const exeBinPath = findExeBin3();
|
|
35364
36470
|
if (!exeBinPath) throw new Error("exe-os not found in PATH");
|
|
35365
|
-
const binDir =
|
|
36471
|
+
const binDir = path54.dirname(exeBinPath);
|
|
35366
36472
|
let symlinkCount = 0;
|
|
35367
|
-
const rosterPath =
|
|
35368
|
-
if (
|
|
35369
|
-
const roster = JSON.parse(
|
|
36473
|
+
const rosterPath = path54.join(exeOsDir, "exe-employees.json");
|
|
36474
|
+
if (existsSync40(rosterPath)) {
|
|
36475
|
+
const roster = JSON.parse(readFileSync35(rosterPath, "utf8"));
|
|
35370
36476
|
const { DEFAULT_COORDINATOR_TEMPLATE_NAME: DEFAULT_COORDINATOR_TEMPLATE_NAME2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
35371
36477
|
const coordinatorName = roster.find((e) => e.role?.toLowerCase() === "coo")?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME2;
|
|
35372
36478
|
for (const emp of roster) {
|
|
35373
36479
|
if (emp.name === coordinatorName) continue;
|
|
35374
36480
|
for (const suffix of ["", "-opencode"]) {
|
|
35375
|
-
const linkPath =
|
|
35376
|
-
if (
|
|
36481
|
+
const linkPath = path54.join(binDir, `${emp.name}${suffix}`);
|
|
36482
|
+
if (existsSync40(linkPath)) {
|
|
35377
36483
|
if (!dryRun) rmSync2(linkPath, { force: true });
|
|
35378
36484
|
symlinkCount++;
|
|
35379
36485
|
}
|
|
@@ -35386,7 +36492,7 @@ async function runClaudeUninstall(flags = []) {
|
|
|
35386
36492
|
}
|
|
35387
36493
|
} catch {
|
|
35388
36494
|
}
|
|
35389
|
-
if (purge &&
|
|
36495
|
+
if (purge && existsSync40(exeOsDir)) {
|
|
35390
36496
|
if (!dryRun) {
|
|
35391
36497
|
process.stdout.write("\x1B[33m\u26A0 This will delete all memories, identities, and agent data.\x1B[0m\n");
|
|
35392
36498
|
process.stdout.write(" Removing ~/.exe-os...\n");
|
|
@@ -35411,7 +36517,7 @@ async function checkForUpdateOnBoot() {
|
|
|
35411
36517
|
const config = await loadConfig2();
|
|
35412
36518
|
if (!config.autoUpdate.checkOnBoot) return;
|
|
35413
36519
|
const { checkForUpdate: checkForUpdate2 } = await Promise.resolve().then(() => (init_update(), update_exports));
|
|
35414
|
-
const packageRoot =
|
|
36520
|
+
const packageRoot = path54.resolve(
|
|
35415
36521
|
new URL("../..", import.meta.url).pathname
|
|
35416
36522
|
);
|
|
35417
36523
|
const result = checkForUpdate2(packageRoot);
|
|
@@ -35472,7 +36578,7 @@ async function runActivate(key) {
|
|
|
35472
36578
|
const idTemplate = getIdentityTemplate(identityKey);
|
|
35473
36579
|
if (idTemplate) {
|
|
35474
36580
|
const idPath = identityPath2(name);
|
|
35475
|
-
const dir =
|
|
36581
|
+
const dir = path54.dirname(idPath);
|
|
35476
36582
|
if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
|
|
35477
36583
|
fs8.writeFileSync(idPath, idTemplate.replace(/^agent_id: \w+/m, `agent_id: ${name}`), "utf-8");
|
|
35478
36584
|
}
|