nexus-agents 4.14.0 → 4.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{child-mcp-config-OULJI7VH.js → child-mcp-config-FNVCS5MQ.js} +5 -5
- package/dist/{chunk-4WVGF3SL.js → chunk-2RSNO5SP.js} +6 -6
- package/dist/{chunk-B6C3JCQB.js → chunk-6JEPV4XW.js} +2 -2
- package/dist/{chunk-VPTDFOAK.js → chunk-BLJY4YKP.js} +3 -3
- package/dist/{chunk-YHY5AZL2.js → chunk-CHRJKE7S.js} +2 -2
- package/dist/{chunk-LUBCO6ZN.js → chunk-CPRPZPX7.js} +2 -2
- package/dist/{chunk-IZUT5OHH.js → chunk-E536V52C.js} +2 -2
- package/dist/{chunk-IZUT5OHH.js.map → chunk-E536V52C.js.map} +1 -1
- package/dist/{chunk-56LJJOBN.js → chunk-ED47MBY5.js} +4 -4
- package/dist/{chunk-XWIFBBBH.js → chunk-EEJFMRHJ.js} +2 -2
- package/dist/{chunk-NMNTYAV2.js → chunk-FFMDYBGA.js} +3 -3
- package/dist/{chunk-V253VL7S.js → chunk-GEOALLHA.js} +4 -4
- package/dist/{chunk-F6SRPGDK.js → chunk-GFCCNGI7.js} +9 -9
- package/dist/{chunk-P45XNRHB.js → chunk-GT5B35X3.js} +2 -2
- package/dist/{chunk-ARTQO7XZ.js → chunk-H7LRUN27.js} +6 -6
- package/dist/{chunk-2HUC6NZN.js → chunk-HHLCAWAV.js} +2 -2
- package/dist/{chunk-HBF7O6CN.js → chunk-HJK5RZBX.js} +6 -6
- package/dist/{chunk-J6JEW4QZ.js → chunk-JD4HTIMG.js} +7 -7
- package/dist/{chunk-6454KSCM.js → chunk-JPA3GAWT.js} +2 -2
- package/dist/{chunk-XIIUIBSX.js → chunk-KGFFEQTN.js} +2 -2
- package/dist/{chunk-WATNLQS7.js → chunk-L6QLJ4VK.js} +2 -2
- package/dist/{chunk-EHMMTCCS.js → chunk-LLVEIPOZ.js} +8 -10
- package/dist/{chunk-EHMMTCCS.js.map → chunk-LLVEIPOZ.js.map} +1 -1
- package/dist/{chunk-232RDSCM.js → chunk-LWR2K2VM.js} +2 -2
- package/dist/{chunk-F6MUN523.js → chunk-MU7J7D5Q.js} +2 -2
- package/dist/{chunk-OQLFRJCW.js → chunk-NBMWONHE.js} +53 -53
- package/dist/chunk-NBMWONHE.js.map +1 -0
- package/dist/{chunk-VENIFAMX.js → chunk-NOROX25U.js} +4 -4
- package/dist/{chunk-7STKQ3IH.js → chunk-NX5VK5NW.js} +2 -2
- package/dist/{chunk-SNJNRDKZ.js → chunk-RQV5ZUXT.js} +2 -2
- package/dist/{chunk-KUGBMQAP.js → chunk-SQUEE5ZA.js} +4 -4
- package/dist/{chunk-W6IZVEIM.js → chunk-T5WQAYX2.js} +2 -2
- package/dist/{chunk-VZVGT4MN.js → chunk-UZRVS7MK.js} +5 -5
- package/dist/{chunk-PWSW5Z4L.js → chunk-VE3PAJ5M.js} +20 -10
- package/dist/chunk-VE3PAJ5M.js.map +1 -0
- package/dist/{chunk-TANMACVQ.js → chunk-VFGP57F5.js} +2 -2
- package/dist/{chunk-I6JMAM4J.js → chunk-VLDYF7EQ.js} +2 -2
- package/dist/{chunk-WPLXWTUK.js → chunk-W4Q5NZYZ.js} +5 -3
- package/dist/chunk-W4Q5NZYZ.js.map +1 -0
- package/dist/{chunk-TEWDRLCM.js → chunk-WCRXZJRW.js} +3426 -2208
- package/dist/chunk-WCRXZJRW.js.map +1 -0
- package/dist/{chunk-5PCTGUMF.js → chunk-WHOODKBB.js} +3 -3
- package/dist/chunk-WMSWVHFZ.js +1240 -0
- package/dist/chunk-WMSWVHFZ.js.map +1 -0
- package/dist/{chunk-XGCYJLTP.js → chunk-XT4J7QAO.js} +10 -10
- package/dist/{cli-circuit-breaker-4AVWA333.js → cli-circuit-breaker-A7GM4LXS.js} +5 -5
- package/dist/cli.js +2987 -1098
- package/dist/cli.js.map +1 -1
- package/dist/{composite-router-7OTZN7NS.js → composite-router-SVZA4OCQ.js} +4 -4
- package/dist/{consensus-vote-KFLDRUJJ.js → consensus-vote-7HGSTZKF.js} +20 -20
- package/dist/{context-retriever-2NZWXF6Z.js → context-retriever-QTASR42L.js} +11 -12
- package/dist/doctor-deep-NBDOSO4E.js +13 -0
- package/dist/{doctor-live-25LRXKBO.js → doctor-live-HZWIMIT6.js} +13 -13
- package/dist/{expert-bridge-2YLK3E6Q.js → expert-bridge-GY7RL4DF.js} +6 -6
- package/dist/factory-4FZ5FPZW.js +15 -0
- package/dist/factory-73NHOTVL.js +25 -0
- package/dist/{improvement-review-FFADRM4I.js → improvement-review-ZF3VHZEG.js} +7 -7
- package/dist/index.js +47 -48
- package/dist/index.js.map +1 -1
- package/dist/{init-opencode-OSZF74AL.js → init-opencode-EOCW7P4L.js} +8 -8
- package/dist/issue-triage-L6UDFHF4.js +17 -0
- package/dist/{learning-persistence-EGY4VKKO.js → learning-persistence-SR5F4DSY.js} +3 -3
- package/dist/{pr-reviewer-helpers-E3QXOVMT.js → pr-reviewer-helpers-KOE3X72Y.js} +6 -6
- package/dist/{registry-command-ZMKZ5CRZ.js → registry-command-7ZENMDK4.js} +4 -4
- package/dist/{repo-security-plan-BPET6KTP.js → repo-security-plan-PCHVMGHX.js} +5 -5
- package/dist/research-helpers-synthesize-KTVQVLII.js +14 -0
- package/dist/{routing-memory-3UWP6NOX.js → routing-memory-QTBLYGMC.js} +4 -4
- package/dist/{session-memory-N2ABITF7.js → session-memory-S7L4K5R5.js} +5 -5
- package/dist/setup-config-7SWEJXN4.js +11 -0
- package/dist/{setup-custom-api-PE2M5HWL.js → setup-custom-api-WYXYQMQF.js} +6 -6
- package/dist/setup-data-dir-EMXRXEFQ.js +28 -0
- package/dist/{tool-memory-CARCDHTN.js → tool-memory-PSQQ4NYD.js} +8 -8
- package/dist/{tool-memory-registry-adapters-QEC3YHB2.js → tool-memory-registry-adapters-UMPIJLHS.js} +3 -3
- package/dist/unified-registry-UEDZXMH3.js +26 -0
- package/dist/{weather-report-ZA7DBOC3.js → weather-report-CKEPMK5R.js} +4 -4
- package/package.json +1 -1
- package/dist/chunk-AH74OHVO.js +0 -1942
- package/dist/chunk-AH74OHVO.js.map +0 -1
- package/dist/chunk-NUBSJGQZ.js +0 -14
- package/dist/chunk-NUBSJGQZ.js.map +0 -1
- package/dist/chunk-OQLFRJCW.js.map +0 -1
- package/dist/chunk-PWSW5Z4L.js.map +0 -1
- package/dist/chunk-TEWDRLCM.js.map +0 -1
- package/dist/chunk-WPLXWTUK.js.map +0 -1
- package/dist/chunk-XOKWIL6I.js +0 -2455
- package/dist/chunk-XOKWIL6I.js.map +0 -1
- package/dist/doctor-deep-MC2FGSAF.js +0 -13
- package/dist/factory-G57M6CD4.js +0 -15
- package/dist/factory-T7KN4CKD.js +0 -25
- package/dist/issue-triage-KVYM5FQ5.js +0 -17
- package/dist/research-helpers-synthesize-M4SONOWL.js +0 -15
- package/dist/setup-command-LXMVWIKJ.js +0 -45
- package/dist/setup-config-PJNXP57G.js +0 -11
- package/dist/unified-registry-HVALHHPY.js +0 -26
- /package/dist/{child-mcp-config-OULJI7VH.js.map → child-mcp-config-FNVCS5MQ.js.map} +0 -0
- /package/dist/{chunk-4WVGF3SL.js.map → chunk-2RSNO5SP.js.map} +0 -0
- /package/dist/{chunk-B6C3JCQB.js.map → chunk-6JEPV4XW.js.map} +0 -0
- /package/dist/{chunk-VPTDFOAK.js.map → chunk-BLJY4YKP.js.map} +0 -0
- /package/dist/{chunk-YHY5AZL2.js.map → chunk-CHRJKE7S.js.map} +0 -0
- /package/dist/{chunk-LUBCO6ZN.js.map → chunk-CPRPZPX7.js.map} +0 -0
- /package/dist/{chunk-56LJJOBN.js.map → chunk-ED47MBY5.js.map} +0 -0
- /package/dist/{chunk-XWIFBBBH.js.map → chunk-EEJFMRHJ.js.map} +0 -0
- /package/dist/{chunk-NMNTYAV2.js.map → chunk-FFMDYBGA.js.map} +0 -0
- /package/dist/{chunk-V253VL7S.js.map → chunk-GEOALLHA.js.map} +0 -0
- /package/dist/{chunk-F6SRPGDK.js.map → chunk-GFCCNGI7.js.map} +0 -0
- /package/dist/{chunk-P45XNRHB.js.map → chunk-GT5B35X3.js.map} +0 -0
- /package/dist/{chunk-ARTQO7XZ.js.map → chunk-H7LRUN27.js.map} +0 -0
- /package/dist/{chunk-2HUC6NZN.js.map → chunk-HHLCAWAV.js.map} +0 -0
- /package/dist/{chunk-HBF7O6CN.js.map → chunk-HJK5RZBX.js.map} +0 -0
- /package/dist/{chunk-J6JEW4QZ.js.map → chunk-JD4HTIMG.js.map} +0 -0
- /package/dist/{chunk-6454KSCM.js.map → chunk-JPA3GAWT.js.map} +0 -0
- /package/dist/{chunk-XIIUIBSX.js.map → chunk-KGFFEQTN.js.map} +0 -0
- /package/dist/{chunk-WATNLQS7.js.map → chunk-L6QLJ4VK.js.map} +0 -0
- /package/dist/{chunk-232RDSCM.js.map → chunk-LWR2K2VM.js.map} +0 -0
- /package/dist/{chunk-F6MUN523.js.map → chunk-MU7J7D5Q.js.map} +0 -0
- /package/dist/{chunk-VENIFAMX.js.map → chunk-NOROX25U.js.map} +0 -0
- /package/dist/{chunk-7STKQ3IH.js.map → chunk-NX5VK5NW.js.map} +0 -0
- /package/dist/{chunk-SNJNRDKZ.js.map → chunk-RQV5ZUXT.js.map} +0 -0
- /package/dist/{chunk-KUGBMQAP.js.map → chunk-SQUEE5ZA.js.map} +0 -0
- /package/dist/{chunk-W6IZVEIM.js.map → chunk-T5WQAYX2.js.map} +0 -0
- /package/dist/{chunk-VZVGT4MN.js.map → chunk-UZRVS7MK.js.map} +0 -0
- /package/dist/{chunk-TANMACVQ.js.map → chunk-VFGP57F5.js.map} +0 -0
- /package/dist/{chunk-I6JMAM4J.js.map → chunk-VLDYF7EQ.js.map} +0 -0
- /package/dist/{chunk-5PCTGUMF.js.map → chunk-WHOODKBB.js.map} +0 -0
- /package/dist/{chunk-XGCYJLTP.js.map → chunk-XT4J7QAO.js.map} +0 -0
- /package/dist/{cli-circuit-breaker-4AVWA333.js.map → cli-circuit-breaker-A7GM4LXS.js.map} +0 -0
- /package/dist/{composite-router-7OTZN7NS.js.map → composite-router-SVZA4OCQ.js.map} +0 -0
- /package/dist/{consensus-vote-KFLDRUJJ.js.map → consensus-vote-7HGSTZKF.js.map} +0 -0
- /package/dist/{context-retriever-2NZWXF6Z.js.map → context-retriever-QTASR42L.js.map} +0 -0
- /package/dist/{doctor-deep-MC2FGSAF.js.map → doctor-deep-NBDOSO4E.js.map} +0 -0
- /package/dist/{doctor-live-25LRXKBO.js.map → doctor-live-HZWIMIT6.js.map} +0 -0
- /package/dist/{expert-bridge-2YLK3E6Q.js.map → expert-bridge-GY7RL4DF.js.map} +0 -0
- /package/dist/{factory-G57M6CD4.js.map → factory-4FZ5FPZW.js.map} +0 -0
- /package/dist/{factory-T7KN4CKD.js.map → factory-73NHOTVL.js.map} +0 -0
- /package/dist/{improvement-review-FFADRM4I.js.map → improvement-review-ZF3VHZEG.js.map} +0 -0
- /package/dist/{init-opencode-OSZF74AL.js.map → init-opencode-EOCW7P4L.js.map} +0 -0
- /package/dist/{issue-triage-KVYM5FQ5.js.map → issue-triage-L6UDFHF4.js.map} +0 -0
- /package/dist/{learning-persistence-EGY4VKKO.js.map → learning-persistence-SR5F4DSY.js.map} +0 -0
- /package/dist/{pr-reviewer-helpers-E3QXOVMT.js.map → pr-reviewer-helpers-KOE3X72Y.js.map} +0 -0
- /package/dist/{registry-command-ZMKZ5CRZ.js.map → registry-command-7ZENMDK4.js.map} +0 -0
- /package/dist/{repo-security-plan-BPET6KTP.js.map → repo-security-plan-PCHVMGHX.js.map} +0 -0
- /package/dist/{research-helpers-synthesize-M4SONOWL.js.map → research-helpers-synthesize-KTVQVLII.js.map} +0 -0
- /package/dist/{routing-memory-3UWP6NOX.js.map → routing-memory-QTBLYGMC.js.map} +0 -0
- /package/dist/{session-memory-N2ABITF7.js.map → session-memory-S7L4K5R5.js.map} +0 -0
- /package/dist/{setup-command-LXMVWIKJ.js.map → setup-config-7SWEJXN4.js.map} +0 -0
- /package/dist/{setup-custom-api-PE2M5HWL.js.map → setup-custom-api-WYXYQMQF.js.map} +0 -0
- /package/dist/{setup-config-PJNXP57G.js.map → setup-data-dir-EMXRXEFQ.js.map} +0 -0
- /package/dist/{tool-memory-CARCDHTN.js.map → tool-memory-PSQQ4NYD.js.map} +0 -0
- /package/dist/{tool-memory-registry-adapters-QEC3YHB2.js.map → tool-memory-registry-adapters-UMPIJLHS.js.map} +0 -0
- /package/dist/{unified-registry-HVALHHPY.js.map → unified-registry-UEDZXMH3.js.map} +0 -0
- /package/dist/{weather-report-ZA7DBOC3.js.map → weather-report-CKEPMK5R.js.map} +0 -0
|
@@ -0,0 +1,1240 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readOpenAICompatEnv
|
|
3
|
+
} from "./chunk-UZRVS7MK.js";
|
|
4
|
+
import {
|
|
5
|
+
allOf
|
|
6
|
+
} from "./chunk-OQB3XC5O.js";
|
|
7
|
+
import {
|
|
8
|
+
capitalize
|
|
9
|
+
} from "./chunk-6T3EPABN.js";
|
|
10
|
+
import {
|
|
11
|
+
createAllAdapters,
|
|
12
|
+
probeCli
|
|
13
|
+
} from "./chunk-GFCCNGI7.js";
|
|
14
|
+
import {
|
|
15
|
+
getNexusTmpDir
|
|
16
|
+
} from "./chunk-RQV5ZUXT.js";
|
|
17
|
+
import {
|
|
18
|
+
DEFAULT_CAPABILITIES,
|
|
19
|
+
colors,
|
|
20
|
+
createLogger,
|
|
21
|
+
err,
|
|
22
|
+
getErrorMessage,
|
|
23
|
+
getInTreeCapabilitiesMatrix,
|
|
24
|
+
getTimeProvider,
|
|
25
|
+
ok,
|
|
26
|
+
symbols,
|
|
27
|
+
writeLine
|
|
28
|
+
} from "./chunk-2RSNO5SP.js";
|
|
29
|
+
import {
|
|
30
|
+
getLearningDir,
|
|
31
|
+
getOutcomesFile,
|
|
32
|
+
getRulesFile,
|
|
33
|
+
isPersistenceEnabled
|
|
34
|
+
} from "./chunk-HHLCAWAV.js";
|
|
35
|
+
import {
|
|
36
|
+
detectSandbox,
|
|
37
|
+
getNexusDataDir,
|
|
38
|
+
getNexusRepoDir,
|
|
39
|
+
getPerRepoSubdirs,
|
|
40
|
+
nexusDataPath
|
|
41
|
+
} from "./chunk-NBMWONHE.js";
|
|
42
|
+
|
|
43
|
+
// src/cli/setup-data-dir.ts
|
|
44
|
+
import { mkdirSync, existsSync as existsSync3 } from "fs";
|
|
45
|
+
|
|
46
|
+
// src/cli/doctor.ts
|
|
47
|
+
import {
|
|
48
|
+
existsSync as existsSync2,
|
|
49
|
+
readFileSync as readFileSync2,
|
|
50
|
+
readdirSync,
|
|
51
|
+
accessSync,
|
|
52
|
+
constants as fsConstants
|
|
53
|
+
} from "fs";
|
|
54
|
+
|
|
55
|
+
// src/cli/doctor-scratch-space.ts
|
|
56
|
+
import { statSync, statfsSync } from "fs";
|
|
57
|
+
import { tmpdir } from "os";
|
|
58
|
+
var MIB = 1024 * 1024;
|
|
59
|
+
var GIB = 1024 * 1024 * 1024;
|
|
60
|
+
var CRITICAL_FREE_BYTES = 512 * MIB;
|
|
61
|
+
var WARN_FREE_BYTES = 2 * GIB;
|
|
62
|
+
function formatBytes(bytes) {
|
|
63
|
+
if (bytes >= GIB) return `${(bytes / GIB).toFixed(1)} GiB`;
|
|
64
|
+
return `${(bytes / MIB).toFixed(0)} MiB`;
|
|
65
|
+
}
|
|
66
|
+
function grade(freeBytes) {
|
|
67
|
+
if (freeBytes < CRITICAL_FREE_BYTES) return "critical";
|
|
68
|
+
if (freeBytes < WARN_FREE_BYTES) return "warn";
|
|
69
|
+
return "ok";
|
|
70
|
+
}
|
|
71
|
+
function checkScratchSpace(root = getNexusTmpDir(), statfs = statfsSync, label = "nexus") {
|
|
72
|
+
let reading;
|
|
73
|
+
try {
|
|
74
|
+
reading = statfs(root);
|
|
75
|
+
} catch {
|
|
76
|
+
return {
|
|
77
|
+
label,
|
|
78
|
+
root,
|
|
79
|
+
available: false,
|
|
80
|
+
freeBytes: 0,
|
|
81
|
+
totalBytes: 0,
|
|
82
|
+
percentUsed: 0,
|
|
83
|
+
severity: "ok",
|
|
84
|
+
message: `Scratch filesystem at ${root} could not be read (statfs unavailable)`
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const totalBytes = reading.blocks * reading.bsize;
|
|
88
|
+
const freeBytes = reading.bavail * reading.bsize;
|
|
89
|
+
const percentUsed = totalBytes > 0 ? Math.round((totalBytes - freeBytes) / totalBytes * 100) : 100;
|
|
90
|
+
const severity = grade(freeBytes);
|
|
91
|
+
return {
|
|
92
|
+
label,
|
|
93
|
+
root,
|
|
94
|
+
available: true,
|
|
95
|
+
freeBytes,
|
|
96
|
+
totalBytes,
|
|
97
|
+
percentUsed,
|
|
98
|
+
severity,
|
|
99
|
+
message: `${formatBytes(freeBytes)} free of ${formatBytes(totalBytes)} (${String(percentUsed)}% used)`
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
var SEVERITY_ORDER = ["ok", "warn", "critical"];
|
|
103
|
+
function defaultRoots() {
|
|
104
|
+
return [
|
|
105
|
+
{ label: "nexus", root: getNexusTmpDir() },
|
|
106
|
+
{ label: "system", root: tmpdir() }
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
function checkScratchFilesystems(options = {}) {
|
|
110
|
+
const roots = options.roots ?? defaultRoots();
|
|
111
|
+
const statfs = options.statfs ?? statfsSync;
|
|
112
|
+
const deviceOf = options.deviceOf ?? ((path) => statSync(path).dev);
|
|
113
|
+
const seen = /* @__PURE__ */ new Set();
|
|
114
|
+
const checks = [];
|
|
115
|
+
for (const { label, root } of roots) {
|
|
116
|
+
let device;
|
|
117
|
+
try {
|
|
118
|
+
device = deviceOf(root);
|
|
119
|
+
} catch {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (seen.has(device)) continue;
|
|
123
|
+
seen.add(device);
|
|
124
|
+
checks.push(checkScratchSpace(root, statfs, label));
|
|
125
|
+
}
|
|
126
|
+
return checks;
|
|
127
|
+
}
|
|
128
|
+
function worstSeverity(checks) {
|
|
129
|
+
return checks.reduce(
|
|
130
|
+
(worst, check) => SEVERITY_ORDER.indexOf(check.severity) > SEVERITY_ORDER.indexOf(worst) ? check.severity : worst,
|
|
131
|
+
"ok"
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
function formatScratchFilesystems(checks) {
|
|
135
|
+
if (checks.length === 0) {
|
|
136
|
+
return " \u26A0 Scratch space: no scratch filesystem could be identified";
|
|
137
|
+
}
|
|
138
|
+
return checks.map(formatScratchSpace).join("\n");
|
|
139
|
+
}
|
|
140
|
+
function formatScratchSpace(check) {
|
|
141
|
+
if (!check.available) {
|
|
142
|
+
return ` \u26A0 Scratch space [${check.label}]: ${check.message}`;
|
|
143
|
+
}
|
|
144
|
+
const icon = check.severity === "ok" ? "\u2713" : check.severity === "warn" ? "\u26A0" : "\u2717";
|
|
145
|
+
const line = ` ${icon} Scratch space [${check.label}] (${check.root}): ${check.message}`;
|
|
146
|
+
if (check.severity === "ok") return line;
|
|
147
|
+
return [
|
|
148
|
+
line,
|
|
149
|
+
" A full scratch filesystem fails subprocesses at the write step, after",
|
|
150
|
+
" they have done their work, so output is lost rather than refused.",
|
|
151
|
+
" Free space, or point NEXUS_TMPDIR at a roomier filesystem."
|
|
152
|
+
].join("\n");
|
|
153
|
+
}
|
|
154
|
+
function scratchSeverityIsAcceptable(severity) {
|
|
155
|
+
switch (severity) {
|
|
156
|
+
case "ok":
|
|
157
|
+
case "warn":
|
|
158
|
+
return true;
|
|
159
|
+
case "critical":
|
|
160
|
+
return false;
|
|
161
|
+
default: {
|
|
162
|
+
const exhaustive = severity;
|
|
163
|
+
throw new Error(`Unhandled scratch severity: ${String(exhaustive)}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/mcp/server.ts
|
|
169
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
170
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
171
|
+
|
|
172
|
+
// src/version.ts
|
|
173
|
+
var VERSION = true ? "4.14.2" : "dev";
|
|
174
|
+
|
|
175
|
+
// src/mcp/task-store.ts
|
|
176
|
+
import { InMemoryTaskStore } from "@modelcontextprotocol/sdk/experimental/tasks";
|
|
177
|
+
var MAX_TASK_TTL_MS = 6e5;
|
|
178
|
+
var DEFAULT_TASK_TTL_MS = 3e5;
|
|
179
|
+
var MAX_TASK_CAPACITY = 50;
|
|
180
|
+
var CLEANUP_INTERVAL_MS = 6e4;
|
|
181
|
+
var logger = createLogger({ component: "task-store" });
|
|
182
|
+
var singletonStore;
|
|
183
|
+
var cleanupTimer;
|
|
184
|
+
function getTaskStore() {
|
|
185
|
+
if (singletonStore === void 0) {
|
|
186
|
+
singletonStore = new InMemoryTaskStore();
|
|
187
|
+
logger.info("Task store created", { maxCapacity: MAX_TASK_CAPACITY });
|
|
188
|
+
cleanupTimer = setInterval(() => {
|
|
189
|
+
evictExcessTasks();
|
|
190
|
+
}, CLEANUP_INTERVAL_MS);
|
|
191
|
+
if (typeof cleanupTimer === "object" && "unref" in cleanupTimer) {
|
|
192
|
+
cleanupTimer.unref();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return singletonStore;
|
|
196
|
+
}
|
|
197
|
+
function clampTaskTtl(requestedTtl) {
|
|
198
|
+
if (requestedTtl === void 0 || requestedTtl === null) {
|
|
199
|
+
return DEFAULT_TASK_TTL_MS;
|
|
200
|
+
}
|
|
201
|
+
if (requestedTtl > MAX_TASK_TTL_MS) {
|
|
202
|
+
logger.warn("Task TTL clamped to maximum", {
|
|
203
|
+
requested: requestedTtl,
|
|
204
|
+
max: MAX_TASK_TTL_MS
|
|
205
|
+
});
|
|
206
|
+
return MAX_TASK_TTL_MS;
|
|
207
|
+
}
|
|
208
|
+
return requestedTtl;
|
|
209
|
+
}
|
|
210
|
+
function evictExcessTasks() {
|
|
211
|
+
if (singletonStore === void 0) return;
|
|
212
|
+
const allTasks = singletonStore.getAllTasks();
|
|
213
|
+
if (allTasks.length <= MAX_TASK_CAPACITY) return;
|
|
214
|
+
const sorted = [...allTasks].sort(
|
|
215
|
+
(a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
|
|
216
|
+
);
|
|
217
|
+
const evictCount = sorted.length - MAX_TASK_CAPACITY;
|
|
218
|
+
for (let i = 0; i < evictCount; i++) {
|
|
219
|
+
const task = sorted[i];
|
|
220
|
+
if (task === void 0) continue;
|
|
221
|
+
singletonStore.updateTaskStatus(task.taskId, "cancelled", "Evicted: capacity exceeded").catch((err2) => {
|
|
222
|
+
logger.debug("Failed to evict task", {
|
|
223
|
+
taskId: task.taskId,
|
|
224
|
+
error: String(err2)
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
logger.info("Evicted excess tasks", { evicted: evictCount, total: sorted.length });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/mcp/server.ts
|
|
232
|
+
var DEFAULT_SERVER_NAME = "nexus-agents";
|
|
233
|
+
function createServerError(code, message, error) {
|
|
234
|
+
const serverError = { code, message };
|
|
235
|
+
if (error instanceof Error) {
|
|
236
|
+
serverError.cause = error;
|
|
237
|
+
}
|
|
238
|
+
return serverError;
|
|
239
|
+
}
|
|
240
|
+
function createServer(config) {
|
|
241
|
+
const serverName = config?.name ?? DEFAULT_SERVER_NAME;
|
|
242
|
+
const serverVersion = config?.version ?? VERSION;
|
|
243
|
+
const logger2 = config?.logger ?? createLogger({ component: "mcp-server" });
|
|
244
|
+
try {
|
|
245
|
+
initDataDirectories();
|
|
246
|
+
logger2.info("Creating MCP server", {
|
|
247
|
+
name: serverName,
|
|
248
|
+
version: serverVersion
|
|
249
|
+
});
|
|
250
|
+
const server = new McpServer(
|
|
251
|
+
{
|
|
252
|
+
name: serverName,
|
|
253
|
+
version: serverVersion
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
capabilities: {
|
|
257
|
+
logging: {},
|
|
258
|
+
prompts: {},
|
|
259
|
+
resources: {},
|
|
260
|
+
tasks: {}
|
|
261
|
+
},
|
|
262
|
+
taskStore: getTaskStore()
|
|
263
|
+
}
|
|
264
|
+
);
|
|
265
|
+
logger2.debug("MCP server created successfully");
|
|
266
|
+
return ok({ server, logger: logger2 });
|
|
267
|
+
} catch (error) {
|
|
268
|
+
const errorMessage = getErrorMessage(error);
|
|
269
|
+
logger2.error("Failed to create MCP server", error instanceof Error ? error : void 0);
|
|
270
|
+
return err(
|
|
271
|
+
createServerError(
|
|
272
|
+
"SERVER_CREATION_FAILED",
|
|
273
|
+
`Failed to create MCP server: ${errorMessage}`,
|
|
274
|
+
error
|
|
275
|
+
)
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async function connectTransport(server, transport, logger2) {
|
|
280
|
+
const log = logger2 ?? createLogger({ component: "mcp-server" });
|
|
281
|
+
try {
|
|
282
|
+
log.info("Connecting server to transport");
|
|
283
|
+
await server.connect(transport);
|
|
284
|
+
log.debug("Server connected to transport successfully");
|
|
285
|
+
return ok(void 0);
|
|
286
|
+
} catch (error) {
|
|
287
|
+
const errorMessage = getErrorMessage(error);
|
|
288
|
+
log.error("Failed to connect to transport", error instanceof Error ? error : void 0);
|
|
289
|
+
return err(
|
|
290
|
+
createServerError(
|
|
291
|
+
"SERVER_START_FAILED",
|
|
292
|
+
`Failed to connect to transport: ${errorMessage}`,
|
|
293
|
+
error
|
|
294
|
+
)
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
async function startStdioServer(config) {
|
|
299
|
+
const serverResult = createServer(config);
|
|
300
|
+
if (!serverResult.ok) {
|
|
301
|
+
return serverResult;
|
|
302
|
+
}
|
|
303
|
+
const { server, logger: logger2 } = serverResult.value;
|
|
304
|
+
try {
|
|
305
|
+
logger2.setDestination?.("stderr");
|
|
306
|
+
logger2.info("Starting stdio transport");
|
|
307
|
+
const transport = new StdioServerTransport();
|
|
308
|
+
const connectResult = await connectTransport(server, transport, logger2);
|
|
309
|
+
if (!connectResult.ok) {
|
|
310
|
+
return connectResult;
|
|
311
|
+
}
|
|
312
|
+
logger2.info("MCP server running with stdio transport");
|
|
313
|
+
return ok({ server, logger: logger2 });
|
|
314
|
+
} catch (error) {
|
|
315
|
+
const errorMessage = getErrorMessage(error);
|
|
316
|
+
logger2.error("Failed to start stdio server", error instanceof Error ? error : void 0);
|
|
317
|
+
return err(
|
|
318
|
+
createServerError(
|
|
319
|
+
"SERVER_START_FAILED",
|
|
320
|
+
`Failed to start stdio server: ${errorMessage}`,
|
|
321
|
+
error
|
|
322
|
+
)
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
async function closeServer(server, logger2) {
|
|
327
|
+
const log = logger2 ?? createLogger({ component: "mcp-server" });
|
|
328
|
+
try {
|
|
329
|
+
log.info("Closing MCP server");
|
|
330
|
+
await server.close();
|
|
331
|
+
log.info("MCP server closed successfully");
|
|
332
|
+
return ok(void 0);
|
|
333
|
+
} catch (error) {
|
|
334
|
+
const errorMessage = getErrorMessage(error);
|
|
335
|
+
log.error("Failed to close server", error instanceof Error ? error : void 0);
|
|
336
|
+
return err(
|
|
337
|
+
createServerError("SERVER_STOP_FAILED", `Failed to close server: ${errorMessage}`, error)
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// src/cli/doctor-formatting.ts
|
|
343
|
+
var REQUIRED_NODE_MAJOR = 22;
|
|
344
|
+
function formatStatus(healthy, warn = false) {
|
|
345
|
+
if (healthy) return `${colors.green}${symbols.check}${colors.reset}`;
|
|
346
|
+
if (warn) return `${colors.yellow}${symbols.warn}${colors.reset}`;
|
|
347
|
+
return `${colors.red}${symbols.cross}${colors.reset}`;
|
|
348
|
+
}
|
|
349
|
+
function formatVersionStatus(status) {
|
|
350
|
+
switch (status) {
|
|
351
|
+
case "supported":
|
|
352
|
+
return `${colors.green}supported${colors.reset}`;
|
|
353
|
+
case "outdated":
|
|
354
|
+
return `${colors.yellow}outdated${colors.reset}`;
|
|
355
|
+
case "unsupported":
|
|
356
|
+
case "breaking":
|
|
357
|
+
return `${colors.red}${status}${colors.reset}`;
|
|
358
|
+
default:
|
|
359
|
+
return status;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
function formatCapacity(capacity) {
|
|
363
|
+
if (capacity === void 0) return "Unknown";
|
|
364
|
+
if (!capacity.observed) {
|
|
365
|
+
return `${colors.yellow}unknown (no usage observed this session)${colors.reset}`;
|
|
366
|
+
}
|
|
367
|
+
const remaining = 100 - capacity.utilizationPercent;
|
|
368
|
+
const remainingStr = String(remaining);
|
|
369
|
+
if (remaining > 80) return `${colors.green}${remainingStr}% remaining${colors.reset}`;
|
|
370
|
+
if (remaining > 20) return `${colors.yellow}${remainingStr}% remaining${colors.reset}`;
|
|
371
|
+
return `${colors.red}${remainingStr}% remaining${colors.reset}`;
|
|
372
|
+
}
|
|
373
|
+
function printInstalledCliDetails(cli) {
|
|
374
|
+
writeLine(` Version: ${cli.version} (${formatVersionStatus(cli.versionStatus)})`);
|
|
375
|
+
const authText = cli.authState === "authenticated" ? `${colors.green}${cli.authMethod ?? "Authenticated"}${colors.reset}` : cli.authState === "unverified" ? `${colors.yellow}unverified (no non-interactive auth check)${colors.reset}` : `${colors.red}Not authenticated${colors.reset}`;
|
|
376
|
+
writeLine(` Auth: ${authText}`);
|
|
377
|
+
if (cli.capacity !== void 0) {
|
|
378
|
+
writeLine(` Capacity: ${formatCapacity(cli.capacity)}`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
function printCliResult(cli) {
|
|
382
|
+
const status = cli.installed && cli.authenticated;
|
|
383
|
+
const warn = cli.installed && (!cli.authenticated || cli.versionStatus === "outdated");
|
|
384
|
+
writeLine(
|
|
385
|
+
`${formatStatus(status, warn)} ${colors.bold}${capitalize(cli.name)} CLI${colors.reset}`
|
|
386
|
+
);
|
|
387
|
+
if (cli.installed) {
|
|
388
|
+
printInstalledCliDetails(cli);
|
|
389
|
+
} else {
|
|
390
|
+
const errorText = cli.error ?? "Not installed";
|
|
391
|
+
writeLine(` ${colors.red}Error: ${errorText}${colors.reset}`);
|
|
392
|
+
}
|
|
393
|
+
if (cli.fix !== void 0 && cli.fix !== "") {
|
|
394
|
+
writeLine(` ${colors.dim}Fix: ${cli.fix}${colors.reset}`);
|
|
395
|
+
}
|
|
396
|
+
writeLine("");
|
|
397
|
+
}
|
|
398
|
+
function printCapabilities(clis) {
|
|
399
|
+
const installedClis = clis.filter((c) => c.installed);
|
|
400
|
+
if (installedClis.length === 0) {
|
|
401
|
+
writeLine(`${formatStatus(false)} No CLIs installed`);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const caps = DEFAULT_CAPABILITIES;
|
|
405
|
+
const bestReasoning = installedClis.reduce(
|
|
406
|
+
(best, c) => caps[c.name].reasoning > caps[best.name].reasoning ? c : best
|
|
407
|
+
);
|
|
408
|
+
const bestContext = installedClis.reduce(
|
|
409
|
+
(best, c) => caps[c.name].contextWindow > caps[best.name].contextWindow ? c : best
|
|
410
|
+
);
|
|
411
|
+
const bestSpeed = installedClis.reduce(
|
|
412
|
+
(best, c) => caps[c.name].speed > caps[best.name].speed ? c : best
|
|
413
|
+
);
|
|
414
|
+
const contextTokensK = (caps[bestContext.name].contextWindow / 1e3).toFixed(0);
|
|
415
|
+
writeLine(
|
|
416
|
+
`${formatStatus(true)} Complex reasoning: ${colors.bold}${capitalize(bestReasoning.name)}${colors.reset}`
|
|
417
|
+
);
|
|
418
|
+
writeLine(
|
|
419
|
+
`${formatStatus(true)} Large context: ${colors.bold}${capitalize(bestContext.name)}${colors.reset} (${contextTokensK}K tokens)`
|
|
420
|
+
);
|
|
421
|
+
writeLine(
|
|
422
|
+
`${formatStatus(true)} Fast execution: ${colors.bold}${capitalize(bestSpeed.name)}${colors.reset}`
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
function printNodeVersionCheck(check) {
|
|
426
|
+
const versionText = check.supported ? `${colors.green}${check.version}${colors.reset}` : `${colors.yellow}${check.version}${colors.reset}`;
|
|
427
|
+
writeLine(`${formatStatus(check.supported, !check.supported)} Node.js version: ${versionText}`);
|
|
428
|
+
if (!check.supported) {
|
|
429
|
+
writeLine(
|
|
430
|
+
` ${colors.dim}Warning: Node.js ${String(REQUIRED_NODE_MAJOR)}.x LTS required${colors.reset}`
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
function printApiKeysCheck(keys) {
|
|
435
|
+
const configuredCount = keys.filter((k) => k.configured).length;
|
|
436
|
+
const configuredNames = keys.filter((k) => k.configured).map((k) => k.name);
|
|
437
|
+
const hasAny = configuredCount > 0;
|
|
438
|
+
writeLine(
|
|
439
|
+
`${formatStatus(hasAny, !hasAny)} API keys configured: ${String(configuredCount)} of ${String(keys.length)}`
|
|
440
|
+
);
|
|
441
|
+
if (hasAny) {
|
|
442
|
+
writeLine(` ${colors.dim}Keys: ${configuredNames.join(", ")}${colors.reset}`);
|
|
443
|
+
} else {
|
|
444
|
+
writeLine(
|
|
445
|
+
` ${colors.dim}Set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_AI_API_KEY${colors.reset}`
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
function printConfigFileCheck(check) {
|
|
450
|
+
if (check.found && check.path !== null) {
|
|
451
|
+
writeLine(`${formatStatus(true)} Configuration loaded: ${check.path}`);
|
|
452
|
+
} else {
|
|
453
|
+
writeLine(`${formatStatus(false, true)} Configuration file: Not found`);
|
|
454
|
+
writeLine(` ${colors.dim}Run: nexus-agents config init${colors.reset}`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
function printRegistryAdvisory(advisory) {
|
|
458
|
+
const allAvailable = advisory.unavailableModels === 0;
|
|
459
|
+
const countText = `${String(advisory.availableModels)} of ${String(advisory.totalModels)}`;
|
|
460
|
+
writeLine(`${formatStatus(allAvailable, !allAvailable)} Models available: ${countText}`);
|
|
461
|
+
if (advisory.unavailableModels > 0) {
|
|
462
|
+
const missing = advisory.models.filter((m) => !m.available);
|
|
463
|
+
for (const m of missing) {
|
|
464
|
+
writeLine(` ${colors.dim}${m.displayName} \u2014 ${m.reason}${colors.reset}`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
const ageText = `${String(advisory.registryAgeDays)} days old`;
|
|
468
|
+
if (advisory.registryStale) {
|
|
469
|
+
writeLine(
|
|
470
|
+
`${colors.yellow}${symbols.warn}${colors.reset} Model registry is ${ageText} \u2014 may have stale model data`
|
|
471
|
+
);
|
|
472
|
+
writeLine(
|
|
473
|
+
` ${colors.dim}Update with 'npm update -g nexus-agents' to pick up the latest registry,${colors.reset}`
|
|
474
|
+
);
|
|
475
|
+
writeLine(
|
|
476
|
+
` ${colors.dim}or run 'nexus-agents registry refresh' to probe currently-installed models.${colors.reset}`
|
|
477
|
+
);
|
|
478
|
+
} else {
|
|
479
|
+
writeLine(`${formatStatus(true)} Model registry: ${ageText}`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
function printLearningPersistence(check) {
|
|
483
|
+
if (!check.enabled) {
|
|
484
|
+
writeLine(`${formatStatus(true)} Learning persistence: ${colors.dim}Disabled${colors.reset}`);
|
|
485
|
+
writeLine(` ${colors.dim}Set NEXUS_PERSIST_LEARNING=true to enable${colors.reset}`);
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
const healthy = check.dirExists && check.dirWritable && check.error === null;
|
|
489
|
+
writeLine(`${formatStatus(healthy, !healthy)} Learning persistence: Enabled`);
|
|
490
|
+
if (check.error !== null) {
|
|
491
|
+
writeLine(` ${colors.red}Error: ${check.error}${colors.reset}`);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const dirStatus = check.dirExists ? check.dirWritable ? `${colors.green}writable${colors.reset}` : `${colors.red}not writable${colors.reset}` : `${colors.yellow}not created yet${colors.reset}`;
|
|
495
|
+
writeLine(` Data directory: ${dirStatus}`);
|
|
496
|
+
writeLine(` Outcomes: ${String(check.outcomeCount)} recorded`);
|
|
497
|
+
writeLine(` Distilled rules: ${String(check.ruleCount)} active`);
|
|
498
|
+
if (check.rulesLastSaved !== null) {
|
|
499
|
+
writeLine(` Rules last saved: ${check.rulesLastSaved}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
function printSqliteCheck(check) {
|
|
503
|
+
if (check.available) {
|
|
504
|
+
writeLine(
|
|
505
|
+
`${formatStatus(true)} SQLite (better-sqlite3): ${colors.green}Available${colors.reset}`
|
|
506
|
+
);
|
|
507
|
+
} else {
|
|
508
|
+
writeLine(
|
|
509
|
+
`${formatStatus(false, true)} SQLite (better-sqlite3): ${colors.yellow}Not available${colors.reset}`
|
|
510
|
+
);
|
|
511
|
+
writeLine(
|
|
512
|
+
` ${colors.dim}Memory backends (agentic, adaptive, typed) require it${colors.reset}`
|
|
513
|
+
);
|
|
514
|
+
writeLine(` ${colors.dim}Fix: npm install -g better-sqlite3${colors.reset}`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function printDataDirGroup(label, rootHint, subdirs) {
|
|
518
|
+
if (subdirs.length === 0) return;
|
|
519
|
+
const existCount = subdirs.filter((d) => d.exists).length;
|
|
520
|
+
writeLine(
|
|
521
|
+
` ${colors.dim}${label} \u2014 ${rootHint} (${String(existCount)}/${String(subdirs.length)})${colors.reset}`
|
|
522
|
+
);
|
|
523
|
+
for (const dir of subdirs) {
|
|
524
|
+
let marker;
|
|
525
|
+
if (!dir.exists) marker = `${colors.dim}\xB7${colors.reset}`;
|
|
526
|
+
else if (!dir.writable) marker = `${colors.yellow}!${colors.reset}`;
|
|
527
|
+
else marker = `${colors.green}\u2713${colors.reset}`;
|
|
528
|
+
const note = !dir.exists ? ` ${colors.dim}(missing \u2014 created on first use)${colors.reset}` : !dir.writable ? ` ${colors.yellow}(not writable)${colors.reset}` : "";
|
|
529
|
+
writeLine(` ${marker} ${dir.name} ${colors.dim}${dir.path}${colors.reset}${note}`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function printDataDirectory(check) {
|
|
533
|
+
const allExist = allOf(check.subdirectories, (d) => d.exists, false);
|
|
534
|
+
const allWritable = allOf(check.subdirectories, (d) => !d.exists || d.writable, false);
|
|
535
|
+
const healthy = check.rootExists && allWritable;
|
|
536
|
+
writeLine(`${formatStatus(healthy, !healthy)} Data directory layout:`);
|
|
537
|
+
const perRepo = check.subdirectories.filter((d) => d.scope === "per-repo");
|
|
538
|
+
const crossRepo = check.subdirectories.filter((d) => d.scope === "cross-repo");
|
|
539
|
+
if (check.repoRoot !== null) {
|
|
540
|
+
printDataDirGroup("Per-repo", check.repoRoot, perRepo);
|
|
541
|
+
} else {
|
|
542
|
+
printDataDirGroup("Per-repo (homedir \u2014 repo-preferred off)", check.rootPath, perRepo);
|
|
543
|
+
}
|
|
544
|
+
printDataDirGroup("Cross-repo", check.rootPath, crossRepo);
|
|
545
|
+
if (!allExist) {
|
|
546
|
+
writeLine(` ${colors.dim}Fix: nexus-agents setup${colors.reset}`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
function printSandboxHeader(check) {
|
|
550
|
+
if (check.active) {
|
|
551
|
+
writeLine(`${formatStatus(true)} Sandbox flavor: ${check.flavor ?? "(unknown)"}`);
|
|
552
|
+
writeLine(` ${colors.dim}NEXUS_SANDBOX_ROOT: ${check.root ?? "(unset)"}${colors.reset}`);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
writeLine(
|
|
556
|
+
`${formatStatus(false, true)} Container detected (${check.heuristicMatch ?? "unknown"}) but ${colors.yellow}NEXUS_SANDBOX is unset${colors.reset}`
|
|
557
|
+
);
|
|
558
|
+
writeLine(
|
|
559
|
+
` ${colors.dim}Set NEXUS_SANDBOX=<flavor> in the image to opt into sandbox-aware behaviour.${colors.reset}`
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
function printSandboxWarnings(check) {
|
|
563
|
+
if (check.mismatch && check.active) {
|
|
564
|
+
writeLine(
|
|
565
|
+
` ${colors.yellow}Mismatch: NEXUS_SANDBOX is set but no container heuristic matched (heuristic=${String(check.heuristicMatch)}).${colors.reset}`
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
if (check.dataDirInsideRepo) {
|
|
569
|
+
writeLine(
|
|
570
|
+
` ${colors.yellow}NEXUS_DATA_DIR resolves inside a single repo subfolder of NEXUS_SANDBOX_ROOT \u2014 state will be lost when switching repos. Set NEXUS_DATA_DIR at the multi-repo root.${colors.reset}`
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
function printSandbox(check) {
|
|
575
|
+
const heuristicSaysContainer = check.heuristicMatch === "docker" || check.heuristicMatch === "podman";
|
|
576
|
+
if (!check.active && !heuristicSaysContainer) {
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
writeLine(`${colors.cyan}Checking sandbox awareness...${colors.reset}`);
|
|
580
|
+
writeLine("");
|
|
581
|
+
printSandboxHeader(check);
|
|
582
|
+
printSandboxWarnings(check);
|
|
583
|
+
writeLine("");
|
|
584
|
+
}
|
|
585
|
+
function printDoctorSummary(result) {
|
|
586
|
+
const unhealthyCount = result.clis.filter((c) => !c.installed || !c.authenticated).length;
|
|
587
|
+
const nodeIssue = result.nodeVersion.supported ? 0 : 1;
|
|
588
|
+
const scratchIssue = scratchSeverityIsAcceptable(worstSeverity(result.scratchSpace)) ? 0 : 1;
|
|
589
|
+
const totalIssues = unhealthyCount + nodeIssue + (result.mcpServerReady ? 0 : 1) + scratchIssue;
|
|
590
|
+
const summary = result.allHealthy ? `${colors.green}${colors.bold}Status: Ready${colors.reset}` : `${colors.yellow}${colors.bold}Summary: ${String(totalIssues)} issue(s) found${colors.reset}`;
|
|
591
|
+
writeLine(summary);
|
|
592
|
+
writeLine("");
|
|
593
|
+
}
|
|
594
|
+
function printVoterTransportCheck(check) {
|
|
595
|
+
if (check.configured) {
|
|
596
|
+
writeLine(`${formatStatus(true)} Voter transport: In-process gateway`);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
writeLine(`${formatStatus(true)} Voter transport: ${colors.dim}CLI subprocess${colors.reset}`);
|
|
600
|
+
writeLine(
|
|
601
|
+
` ${colors.dim}Set NEXUS_OPENAI_COMPAT_URL and NEXUS_OPENAI_COMPAT_KEY for faster in-process voting${colors.reset}`
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
function printDoctorResults(result) {
|
|
605
|
+
writeLine("");
|
|
606
|
+
writeLine(`${colors.bold}Nexus Agents Doctor${colors.reset}`);
|
|
607
|
+
writeLine("===================");
|
|
608
|
+
writeLine("");
|
|
609
|
+
writeLine(`${colors.cyan}Checking environment...${colors.reset}`);
|
|
610
|
+
writeLine("");
|
|
611
|
+
printNodeVersionCheck(result.nodeVersion);
|
|
612
|
+
printApiKeysCheck(result.apiKeys);
|
|
613
|
+
printConfigFileCheck(result.configFile);
|
|
614
|
+
writeLine("");
|
|
615
|
+
writeLine(`${colors.cyan}Checking CLI installations...${colors.reset}`);
|
|
616
|
+
writeLine("");
|
|
617
|
+
for (const cli of result.clis) {
|
|
618
|
+
printCliResult(cli);
|
|
619
|
+
}
|
|
620
|
+
writeLine(`${colors.cyan}Checking MCP configuration...${colors.reset}`);
|
|
621
|
+
writeLine("");
|
|
622
|
+
writeLine(
|
|
623
|
+
`${formatStatus(result.mcpServerReady)} MCP Server mode: ${result.mcpServerReady ? "Ready" : "Not ready"}`
|
|
624
|
+
);
|
|
625
|
+
writeLine(
|
|
626
|
+
`${formatStatus(result.mcpClientReady)} MCP Client mode: ${result.mcpClientReady ? "Ready (Codex mcp-server)" : "Not ready (Codex not installed)"}`
|
|
627
|
+
);
|
|
628
|
+
printVoterTransportCheck(result.voterTransport);
|
|
629
|
+
writeLine("");
|
|
630
|
+
writeLine(`${colors.cyan}Checking capabilities...${colors.reset}`);
|
|
631
|
+
writeLine("");
|
|
632
|
+
printCapabilities(result.clis);
|
|
633
|
+
writeLine("");
|
|
634
|
+
writeLine(`${colors.cyan}Checking model registry...${colors.reset}`);
|
|
635
|
+
writeLine("");
|
|
636
|
+
printRegistryAdvisory(result.registryAdvisory);
|
|
637
|
+
writeLine("");
|
|
638
|
+
writeLine(`${colors.cyan}Checking learning subsystem...${colors.reset}`);
|
|
639
|
+
writeLine("");
|
|
640
|
+
printLearningPersistence(result.learningPersistence);
|
|
641
|
+
writeLine("");
|
|
642
|
+
writeLine(`${colors.cyan}Checking data storage...${colors.reset}`);
|
|
643
|
+
writeLine("");
|
|
644
|
+
printSqliteCheck(result.sqliteCheck);
|
|
645
|
+
printDataDirectory(result.dataDirectory);
|
|
646
|
+
writeLine(formatScratchFilesystems(result.scratchSpace));
|
|
647
|
+
writeLine("");
|
|
648
|
+
printSandbox(result.sandbox);
|
|
649
|
+
printHarnessAlignment(result.harnessAlignment);
|
|
650
|
+
printDoctorSummary(result);
|
|
651
|
+
}
|
|
652
|
+
function printHarnessAlignment(check) {
|
|
653
|
+
writeLine(`${colors.cyan}Checking agent-harness alignment...${colors.reset}`);
|
|
654
|
+
writeLine("");
|
|
655
|
+
writeLine(
|
|
656
|
+
`${formatStatus(check.agentsMdExists)} AGENTS.md: ${check.agentsMdExists ? "present (federated surface)" : "MISSING \u2014 federation invariant broken"}`
|
|
657
|
+
);
|
|
658
|
+
for (const f of check.files) {
|
|
659
|
+
if (!f.exists) {
|
|
660
|
+
writeLine(` ${colors.gray}\u25CB${colors.reset} ${f.harness}: not present (${f.path})`);
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
if (f.error !== null) {
|
|
664
|
+
writeLine(` ${colors.red}\u2717${colors.reset} ${f.harness}: ${f.error}`);
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
if (f.redirectsToAgentsMd) {
|
|
668
|
+
writeLine(` ${colors.green}\u2713${colors.reset} ${f.harness}: aligned (${f.path})`);
|
|
669
|
+
} else {
|
|
670
|
+
writeLine(
|
|
671
|
+
` ${colors.yellow}\u26A0${colors.reset} ${f.harness}: drift \u2014 ${f.path} exists but does NOT mention AGENTS.md`
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
writeLine("");
|
|
676
|
+
writeLine(
|
|
677
|
+
` Summary: ${String(check.alignedCount)} aligned, ${String(check.driftCount)} drift, ${String(check.missingCount)} absent`
|
|
678
|
+
);
|
|
679
|
+
if (check.driftCount > 0) {
|
|
680
|
+
writeLine(
|
|
681
|
+
` ${colors.yellow}Drift detected.${colors.reset} Per docs/architecture/AGENT_COMPATIBILITY.md, harness configs must redirect to AGENTS.md \u2014 never duplicate content.`
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
writeLine("");
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// src/cli/doctor-harness-alignment.ts
|
|
688
|
+
import { existsSync, readFileSync } from "fs";
|
|
689
|
+
import { join } from "path";
|
|
690
|
+
var HARNESS_FILES = [
|
|
691
|
+
{ harness: "Cursor", path: ".cursor/rules/agents.mdc" },
|
|
692
|
+
{ harness: "Windsurf", path: ".windsurf/rules/agents.md" },
|
|
693
|
+
{ harness: "Aider", path: ".aider.conf.yml" },
|
|
694
|
+
{ harness: "Continue", path: ".continue/rules/agents.md" },
|
|
695
|
+
{ harness: "Cline", path: ".clinerules/agents.md" },
|
|
696
|
+
// Gemini CLI reads a root-level GEMINI.md natively (unlike the .rules/-dir
|
|
697
|
+
// harnesses); it redirects to AGENTS.md per the federation (#3446 Phase 4).
|
|
698
|
+
{ harness: "Gemini", path: "GEMINI.md" }
|
|
699
|
+
];
|
|
700
|
+
function checkHarnessAlignment(cwd = process.cwd()) {
|
|
701
|
+
const agentsMdPath = join(cwd, "AGENTS.md");
|
|
702
|
+
const agentsMdExists = existsSync(agentsMdPath);
|
|
703
|
+
const files = HARNESS_FILES.map(
|
|
704
|
+
({ harness, path }) => inspectFile(harness, path, join(cwd, path))
|
|
705
|
+
);
|
|
706
|
+
const alignedCount = files.filter((f) => f.exists && f.redirectsToAgentsMd).length;
|
|
707
|
+
const driftCount = files.filter((f) => f.exists && !f.redirectsToAgentsMd).length;
|
|
708
|
+
const missingCount = files.filter((f) => !f.exists).length;
|
|
709
|
+
return {
|
|
710
|
+
agentsMdExists,
|
|
711
|
+
files,
|
|
712
|
+
alignedCount,
|
|
713
|
+
driftCount,
|
|
714
|
+
missingCount
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
function inspectFile(harness, relativePath, absolutePath) {
|
|
718
|
+
if (!existsSync(absolutePath)) {
|
|
719
|
+
return {
|
|
720
|
+
harness,
|
|
721
|
+
path: relativePath,
|
|
722
|
+
exists: false,
|
|
723
|
+
redirectsToAgentsMd: false,
|
|
724
|
+
error: null
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
try {
|
|
728
|
+
const content = readFileSync(absolutePath, "utf-8");
|
|
729
|
+
return {
|
|
730
|
+
harness,
|
|
731
|
+
path: relativePath,
|
|
732
|
+
exists: true,
|
|
733
|
+
redirectsToAgentsMd: content.includes("AGENTS.md"),
|
|
734
|
+
error: null
|
|
735
|
+
};
|
|
736
|
+
} catch (error) {
|
|
737
|
+
return {
|
|
738
|
+
harness,
|
|
739
|
+
path: relativePath,
|
|
740
|
+
exists: true,
|
|
741
|
+
redirectsToAgentsMd: false,
|
|
742
|
+
error: error instanceof Error ? error.message : String(error)
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// src/cli/doctor.ts
|
|
748
|
+
var REQUIRED_NODE_MAJOR2 = 22;
|
|
749
|
+
var API_KEY_VARS = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GOOGLE_AI_API_KEY"];
|
|
750
|
+
var CONFIG_FILE_PATHS = [
|
|
751
|
+
"./.nexus-agents/nexus-agents.yaml",
|
|
752
|
+
"./.nexus-agents/nexus-agents.yml",
|
|
753
|
+
"./nexus-agents.yaml",
|
|
754
|
+
"./nexus-agents.yml"
|
|
755
|
+
];
|
|
756
|
+
var DATA_SUBDIRECTORIES = [
|
|
757
|
+
"memory",
|
|
758
|
+
"memory/beliefs",
|
|
759
|
+
"learning",
|
|
760
|
+
"sessions",
|
|
761
|
+
"audit",
|
|
762
|
+
"voting",
|
|
763
|
+
"auth",
|
|
764
|
+
"research",
|
|
765
|
+
"checkpoints"
|
|
766
|
+
];
|
|
767
|
+
function getFixCommand(name, issue) {
|
|
768
|
+
const commands = {
|
|
769
|
+
claude: {
|
|
770
|
+
install: "npm install -g @anthropic-ai/claude-code",
|
|
771
|
+
upgrade: "npm update -g @anthropic-ai/claude-code",
|
|
772
|
+
auth: "claude auth login"
|
|
773
|
+
},
|
|
774
|
+
gemini: {
|
|
775
|
+
install: "npm install -g @google/gemini-cli",
|
|
776
|
+
upgrade: "npm update -g @google/gemini-cli",
|
|
777
|
+
auth: "gemini auth login"
|
|
778
|
+
},
|
|
779
|
+
codex: {
|
|
780
|
+
install: "npm install -g @openai/codex",
|
|
781
|
+
upgrade: "npm update -g @openai/codex",
|
|
782
|
+
auth: "codex auth login"
|
|
783
|
+
},
|
|
784
|
+
opencode: {
|
|
785
|
+
install: "npm install -g opencode-ai",
|
|
786
|
+
upgrade: "npm update -g opencode-ai",
|
|
787
|
+
auth: "opencode auth login"
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
return commands[name][issue] ?? "";
|
|
791
|
+
}
|
|
792
|
+
function createNotFoundResult(name, errorMsg) {
|
|
793
|
+
return {
|
|
794
|
+
name,
|
|
795
|
+
// Not installed, so auth was never probed. `not-authenticated` would claim
|
|
796
|
+
// a determination nobody made; the auth line is not printed for an
|
|
797
|
+
// uninstalled CLI anyway (#4661).
|
|
798
|
+
authState: "unverified",
|
|
799
|
+
installed: false,
|
|
800
|
+
version: "N/A",
|
|
801
|
+
versionStatus: "unsupported",
|
|
802
|
+
authenticated: false,
|
|
803
|
+
error: errorMsg,
|
|
804
|
+
fix: getFixCommand(name, "install")
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
function detectAuthMethod(name) {
|
|
808
|
+
const authMethods = {
|
|
809
|
+
claude: "CLI auth",
|
|
810
|
+
gemini: "ADC/CLI auth",
|
|
811
|
+
codex: "CLI auth",
|
|
812
|
+
opencode: "CLI auth"
|
|
813
|
+
};
|
|
814
|
+
return authMethods[name];
|
|
815
|
+
}
|
|
816
|
+
function resolveAuthState(authenticated, probeState) {
|
|
817
|
+
if (authenticated) return "authenticated";
|
|
818
|
+
return probeState === "unknown" ? "unverified" : "not-authenticated";
|
|
819
|
+
}
|
|
820
|
+
function createHealthyResult(name, health, authProbe, capacity) {
|
|
821
|
+
const versionOk = health.healthy;
|
|
822
|
+
const authenticated = versionOk && authProbe.state === "authenticated";
|
|
823
|
+
const authState = resolveAuthState(authenticated, authProbe.state);
|
|
824
|
+
const result = {
|
|
825
|
+
name,
|
|
826
|
+
installed: true,
|
|
827
|
+
version: health.version,
|
|
828
|
+
versionStatus: health.versionStatus,
|
|
829
|
+
authenticated,
|
|
830
|
+
authState,
|
|
831
|
+
...authenticated && { authMethod: detectAuthMethod(name) },
|
|
832
|
+
...capacity !== void 0 && { capacity }
|
|
833
|
+
};
|
|
834
|
+
if (health.message !== void 0 && health.message !== "") {
|
|
835
|
+
return { ...result, error: health.message };
|
|
836
|
+
}
|
|
837
|
+
if (!authenticated && authProbe.state === "needs-login") {
|
|
838
|
+
return {
|
|
839
|
+
...result,
|
|
840
|
+
error: authProbe.reason,
|
|
841
|
+
fix: authProbe.fixCommand
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
if (authState === "not-authenticated") {
|
|
845
|
+
return { ...result, fix: getFixCommand(name, "auth") };
|
|
846
|
+
}
|
|
847
|
+
if (health.versionStatus === "outdated") {
|
|
848
|
+
return { ...result, fix: getFixCommand(name, "upgrade") };
|
|
849
|
+
}
|
|
850
|
+
return result;
|
|
851
|
+
}
|
|
852
|
+
async function checkCli(name) {
|
|
853
|
+
const adapters = createAllAdapters();
|
|
854
|
+
const adapter = adapters.get(name);
|
|
855
|
+
if (!adapter) {
|
|
856
|
+
return createNotFoundResult(name, "Adapter not available");
|
|
857
|
+
}
|
|
858
|
+
try {
|
|
859
|
+
const [health, authProbe] = await Promise.all([adapter.healthCheck(), probeCli(name)]);
|
|
860
|
+
let capacity;
|
|
861
|
+
try {
|
|
862
|
+
capacity = await adapter.getCapacity();
|
|
863
|
+
} catch (capErr) {
|
|
864
|
+
void capErr;
|
|
865
|
+
}
|
|
866
|
+
return createHealthyResult(name, health, authProbe, capacity);
|
|
867
|
+
} catch (error) {
|
|
868
|
+
const message = getErrorMessage(error);
|
|
869
|
+
const isNotFound = message.includes("ENOENT") || message.includes("not found");
|
|
870
|
+
return createNotFoundResult(name, isNotFound ? "Not found in PATH" : message);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
function checkNodeVersion() {
|
|
874
|
+
const version = process.version;
|
|
875
|
+
const major = Number(version.slice(1).split(".")[0]);
|
|
876
|
+
return {
|
|
877
|
+
version,
|
|
878
|
+
major,
|
|
879
|
+
supported: major >= REQUIRED_NODE_MAJOR2
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
function checkApiKeys() {
|
|
883
|
+
return API_KEY_VARS.map((name) => ({
|
|
884
|
+
name,
|
|
885
|
+
configured: typeof process.env[name] === "string" && process.env[name] !== ""
|
|
886
|
+
}));
|
|
887
|
+
}
|
|
888
|
+
function checkConfigFile() {
|
|
889
|
+
for (const configPath of CONFIG_FILE_PATHS) {
|
|
890
|
+
if (existsSync2(configPath)) {
|
|
891
|
+
return { found: true, path: configPath };
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return { found: false, path: null };
|
|
895
|
+
}
|
|
896
|
+
function checkMcpServerReady() {
|
|
897
|
+
try {
|
|
898
|
+
const result = createServer({ name: "nexus-agents-doctor-check" });
|
|
899
|
+
return result.ok;
|
|
900
|
+
} catch {
|
|
901
|
+
return false;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
function buildRegistryAdvisory(cliResults) {
|
|
905
|
+
const installedClis = new Set(cliResults.filter((c) => c.installed).map((c) => c.name));
|
|
906
|
+
const matrix = getInTreeCapabilitiesMatrix();
|
|
907
|
+
const models = matrix.models.filter((m) => m.cliName !== void 0).map((m) => {
|
|
908
|
+
const cliName = m.cliName ?? "";
|
|
909
|
+
const available = cliName.length > 0 && installedClis.has(cliName);
|
|
910
|
+
const reason = available ? `${cliName} CLI is installed` : `${cliName} CLI is not installed`;
|
|
911
|
+
return { modelId: m.id, displayName: m.displayName, cliName, available, reason };
|
|
912
|
+
});
|
|
913
|
+
const STALE_THRESHOLD_DAYS = 90;
|
|
914
|
+
const updatedAt = new Date(matrix.updatedAt);
|
|
915
|
+
const nowMs = getTimeProvider().now();
|
|
916
|
+
const ageDays = Math.floor((nowMs - updatedAt.getTime()) / (1e3 * 60 * 60 * 24));
|
|
917
|
+
const isFreshInstall = !hasPriorUsage();
|
|
918
|
+
const exceedsThreshold = ageDays > STALE_THRESHOLD_DAYS;
|
|
919
|
+
return {
|
|
920
|
+
totalModels: models.length,
|
|
921
|
+
availableModels: models.filter((m) => m.available).length,
|
|
922
|
+
unavailableModels: models.filter((m) => !m.available).length,
|
|
923
|
+
models,
|
|
924
|
+
registryAgeDays: ageDays,
|
|
925
|
+
registryStale: exceedsThreshold && !isFreshInstall
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
function hasPriorUsage() {
|
|
929
|
+
try {
|
|
930
|
+
const root = getNexusDataDir();
|
|
931
|
+
if (!existsSync2(root)) return false;
|
|
932
|
+
for (const sub of ["audit", "learning", "sessions", "voting"]) {
|
|
933
|
+
const p = `${root}/${sub}`;
|
|
934
|
+
try {
|
|
935
|
+
if (existsSync2(p) && readdirSync(p).length > 0) return true;
|
|
936
|
+
} catch {
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
return false;
|
|
940
|
+
} catch {
|
|
941
|
+
return false;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
function countJsonlLines(filePath) {
|
|
945
|
+
if (!existsSync2(filePath)) return 0;
|
|
946
|
+
return readFileSync2(filePath, "utf-8").split("\n").filter((l) => l.trim().length > 0).length;
|
|
947
|
+
}
|
|
948
|
+
function readRulesMetadata(filePath) {
|
|
949
|
+
if (!existsSync2(filePath)) return { count: 0, savedAt: null };
|
|
950
|
+
try {
|
|
951
|
+
const raw = JSON.parse(readFileSync2(filePath, "utf-8"));
|
|
952
|
+
const rules = raw["rules"];
|
|
953
|
+
const saved = raw["savedAt"];
|
|
954
|
+
return {
|
|
955
|
+
count: Array.isArray(rules) ? rules.length : 0,
|
|
956
|
+
savedAt: typeof saved === "string" ? saved : null
|
|
957
|
+
};
|
|
958
|
+
} catch {
|
|
959
|
+
return { count: 0, savedAt: null };
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
function checkDirAccess(dir) {
|
|
963
|
+
const exists = existsSync2(dir);
|
|
964
|
+
if (!exists) return { exists: false, writable: false };
|
|
965
|
+
try {
|
|
966
|
+
accessSync(dir, fsConstants.W_OK);
|
|
967
|
+
return { exists: true, writable: true };
|
|
968
|
+
} catch {
|
|
969
|
+
return { exists: true, writable: false };
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
var DISABLED_CHECK = {
|
|
973
|
+
enabled: false,
|
|
974
|
+
dirExists: false,
|
|
975
|
+
dirWritable: false,
|
|
976
|
+
outcomeCount: 0,
|
|
977
|
+
ruleCount: 0,
|
|
978
|
+
rulesLastSaved: null,
|
|
979
|
+
error: null
|
|
980
|
+
};
|
|
981
|
+
function checkLearningPersistence() {
|
|
982
|
+
if (!isPersistenceEnabled()) return DISABLED_CHECK;
|
|
983
|
+
try {
|
|
984
|
+
const { exists: dirExists, writable: dirWritable } = checkDirAccess(getLearningDir());
|
|
985
|
+
const outcomeCount = countJsonlLines(getOutcomesFile());
|
|
986
|
+
const { count: ruleCount, savedAt: rulesLastSaved } = readRulesMetadata(getRulesFile());
|
|
987
|
+
return {
|
|
988
|
+
enabled: true,
|
|
989
|
+
dirExists,
|
|
990
|
+
dirWritable,
|
|
991
|
+
outcomeCount,
|
|
992
|
+
ruleCount,
|
|
993
|
+
rulesLastSaved,
|
|
994
|
+
error: null
|
|
995
|
+
};
|
|
996
|
+
} catch (error) {
|
|
997
|
+
return {
|
|
998
|
+
enabled: true,
|
|
999
|
+
dirExists: false,
|
|
1000
|
+
dirWritable: false,
|
|
1001
|
+
outcomeCount: 0,
|
|
1002
|
+
ruleCount: 0,
|
|
1003
|
+
rulesLastSaved: null,
|
|
1004
|
+
error: getErrorMessage(error)
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
async function checkSqlite() {
|
|
1009
|
+
try {
|
|
1010
|
+
await import("better-sqlite3");
|
|
1011
|
+
return { available: true, error: null };
|
|
1012
|
+
} catch (error) {
|
|
1013
|
+
const msg = getErrorMessage(error);
|
|
1014
|
+
const isNotFound = msg.includes("Cannot find") || msg.includes("MODULE_NOT_FOUND");
|
|
1015
|
+
return {
|
|
1016
|
+
available: false,
|
|
1017
|
+
error: isNotFound ? "better-sqlite3 not installed \u2014 5 memory backends unavailable" : `better-sqlite3 load error: ${msg}`
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
function checkDataDirectory() {
|
|
1022
|
+
const rootPath = getNexusDataDir();
|
|
1023
|
+
const rootExists = existsSync2(rootPath);
|
|
1024
|
+
const repoRoot = getNexusRepoDir();
|
|
1025
|
+
const perRepoSet = getPerRepoSubdirs();
|
|
1026
|
+
const subdirectories = DATA_SUBDIRECTORIES.map((name) => {
|
|
1027
|
+
const segments = name.split("/");
|
|
1028
|
+
const fullPath = nexusDataPath(...segments);
|
|
1029
|
+
const exists = existsSync2(fullPath);
|
|
1030
|
+
const scope = perRepoSet.has(segments[0] ?? "") ? "per-repo" : "cross-repo";
|
|
1031
|
+
return { name, path: fullPath, scope, exists, writable: exists && isWritable(fullPath) };
|
|
1032
|
+
});
|
|
1033
|
+
return { rootExists, rootPath, repoRoot, subdirectories };
|
|
1034
|
+
}
|
|
1035
|
+
function isWritable(dirPath) {
|
|
1036
|
+
try {
|
|
1037
|
+
accessSync(dirPath, fsConstants.W_OK);
|
|
1038
|
+
return true;
|
|
1039
|
+
} catch {
|
|
1040
|
+
return false;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
function checkSandbox() {
|
|
1044
|
+
const info = detectSandbox();
|
|
1045
|
+
const dataDir = getNexusDataDir();
|
|
1046
|
+
const root = info.root;
|
|
1047
|
+
const dataDirInsideRepo = info.active && root !== void 0 && dataDir.startsWith(`${root.replace(/\/$/, "")}/`) && // path segments BETWEEN root and `.nexus-agents/` indicate the data dir
|
|
1048
|
+
// lives in a subfolder rather than at the multi-repo root.
|
|
1049
|
+
dataDir.replace(`${root.replace(/\/$/, "")}/`, "").split("/").length > 1;
|
|
1050
|
+
const heuristicSaysContainer = info.heuristicMatch === "docker" || info.heuristicMatch === "podman";
|
|
1051
|
+
const mismatch = info.active && info.heuristicMatch === "unknown" || !info.active && heuristicSaysContainer;
|
|
1052
|
+
return {
|
|
1053
|
+
active: info.active,
|
|
1054
|
+
flavor: info.flavor,
|
|
1055
|
+
root: info.root,
|
|
1056
|
+
heuristicMatch: info.heuristicMatch,
|
|
1057
|
+
mismatch,
|
|
1058
|
+
dataDirInsideRepo
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
function checkVoterTransport() {
|
|
1062
|
+
return { configured: readOpenAICompatEnv() !== null };
|
|
1063
|
+
}
|
|
1064
|
+
function isAllHealthy(input) {
|
|
1065
|
+
return input.nodeSupported && input.hasAuthMethod && input.mcpServerReady && scratchSeverityIsAcceptable(worstSeverity(input.scratchSpace)) && // whenEmpty = false: zero detected CLIs is not a healthy install (#4581).
|
|
1066
|
+
allOf(
|
|
1067
|
+
input.clis,
|
|
1068
|
+
(c) => c.installed && c.authenticated && c.versionStatus !== "unsupported",
|
|
1069
|
+
false
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
async function runDoctor() {
|
|
1073
|
+
const clis = await Promise.all([
|
|
1074
|
+
checkCli("claude"),
|
|
1075
|
+
checkCli("gemini"),
|
|
1076
|
+
checkCli("codex"),
|
|
1077
|
+
checkCli("opencode")
|
|
1078
|
+
]);
|
|
1079
|
+
const nodeVersion = checkNodeVersion();
|
|
1080
|
+
const apiKeys = checkApiKeys();
|
|
1081
|
+
const configFile = checkConfigFile();
|
|
1082
|
+
const mcpServerReady = checkMcpServerReady();
|
|
1083
|
+
const codexCheck = clis.find((c) => c.name === "codex");
|
|
1084
|
+
const mcpClientReady = codexCheck?.installed ?? false;
|
|
1085
|
+
const registryAdvisory = buildRegistryAdvisory(clis);
|
|
1086
|
+
const learningPersistence = checkLearningPersistence();
|
|
1087
|
+
const sqliteCheck = await checkSqlite();
|
|
1088
|
+
const dataDirectory = checkDataDirectory();
|
|
1089
|
+
const sandbox = checkSandbox();
|
|
1090
|
+
const harnessAlignment = checkHarnessAlignment();
|
|
1091
|
+
const voterTransport = checkVoterTransport();
|
|
1092
|
+
const scratchSpace = checkScratchFilesystems();
|
|
1093
|
+
const hasAuthMethod = apiKeys.some((k) => k.configured) || clis.some((c) => c.installed && c.authenticated);
|
|
1094
|
+
const allHealthy = isAllHealthy({
|
|
1095
|
+
nodeSupported: nodeVersion.supported,
|
|
1096
|
+
hasAuthMethod,
|
|
1097
|
+
mcpServerReady,
|
|
1098
|
+
scratchSpace,
|
|
1099
|
+
clis
|
|
1100
|
+
});
|
|
1101
|
+
return {
|
|
1102
|
+
clis,
|
|
1103
|
+
nodeVersion,
|
|
1104
|
+
apiKeys,
|
|
1105
|
+
configFile,
|
|
1106
|
+
mcpServerReady,
|
|
1107
|
+
mcpClientReady,
|
|
1108
|
+
registryAdvisory,
|
|
1109
|
+
learningPersistence,
|
|
1110
|
+
sqliteCheck,
|
|
1111
|
+
dataDirectory,
|
|
1112
|
+
sandbox,
|
|
1113
|
+
harnessAlignment,
|
|
1114
|
+
voterTransport,
|
|
1115
|
+
scratchSpace,
|
|
1116
|
+
allHealthy,
|
|
1117
|
+
timestamp: new Date(getTimeProvider().now())
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
async function doctorCommand(options = {}) {
|
|
1121
|
+
const result = await runDoctor();
|
|
1122
|
+
printDoctorResults(result);
|
|
1123
|
+
if (options.fix === true) {
|
|
1124
|
+
await runDoctorFix(result);
|
|
1125
|
+
}
|
|
1126
|
+
return result.allHealthy ? 0 : 1;
|
|
1127
|
+
}
|
|
1128
|
+
async function runDoctorFix(result) {
|
|
1129
|
+
const writeLine2 = (text) => {
|
|
1130
|
+
process.stdout.write(text + "\n");
|
|
1131
|
+
};
|
|
1132
|
+
writeLine2("");
|
|
1133
|
+
writeLine2("\x1B[1mAuto-fix\x1B[0m");
|
|
1134
|
+
writeLine2("\u2500".repeat(40));
|
|
1135
|
+
let fixCount = 0;
|
|
1136
|
+
if (!result.dataDirectory.rootExists || result.dataDirectory.subdirectories.some((d) => !d.exists || !d.writable)) {
|
|
1137
|
+
const { initDataDirectories: initDataDirectories2, describeDataDirFix: describeDataDirFix2 } = await import("./setup-data-dir-EMXRXEFQ.js");
|
|
1138
|
+
const outcome = describeDataDirFix2(initDataDirectories2());
|
|
1139
|
+
writeLine2(outcome.line);
|
|
1140
|
+
if (outcome.counted) fixCount++;
|
|
1141
|
+
}
|
|
1142
|
+
if (!result.configFile.found) {
|
|
1143
|
+
const { runConfigInitSync } = await import("./setup-config-7SWEJXN4.js");
|
|
1144
|
+
const configResult = runConfigInitSync(process.cwd(), false, false);
|
|
1145
|
+
if (configResult.success && configResult.created) {
|
|
1146
|
+
writeLine2(`\u2713 Generated config: ${configResult.path}`);
|
|
1147
|
+
fixCount++;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
if (!result.sqliteCheck.available) {
|
|
1151
|
+
writeLine2("");
|
|
1152
|
+
writeLine2("\u26A0 better-sqlite3 not installed (manual step required):");
|
|
1153
|
+
writeLine2(" npm install -g better-sqlite3");
|
|
1154
|
+
}
|
|
1155
|
+
if (fixCount > 0) {
|
|
1156
|
+
writeLine2("");
|
|
1157
|
+
writeLine2(
|
|
1158
|
+
`\x1B[32m${String(fixCount)} issue(s) fixed.\x1B[0m Re-run \x1B[1mnexus-agents doctor\x1B[0m to verify.`
|
|
1159
|
+
);
|
|
1160
|
+
} else {
|
|
1161
|
+
writeLine2("No auto-fixable issues found.");
|
|
1162
|
+
}
|
|
1163
|
+
writeLine2("");
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// src/cli/setup-data-dir.ts
|
|
1167
|
+
var NEXUS_DATA_DIR = getNexusDataDir();
|
|
1168
|
+
var RESTRICTED_DIRS = /* @__PURE__ */ new Set(["auth"]);
|
|
1169
|
+
function initDataDirectories(dryRun = false) {
|
|
1170
|
+
const created = [];
|
|
1171
|
+
const alreadyExisted = [];
|
|
1172
|
+
const failures = [];
|
|
1173
|
+
for (const subdir of DATA_SUBDIRECTORIES) {
|
|
1174
|
+
const mode = RESTRICTED_DIRS.has(subdir) ? 448 : void 0;
|
|
1175
|
+
const target = nexusDataPath(...subdir.split("/"));
|
|
1176
|
+
try {
|
|
1177
|
+
ensureDir(target, dryRun, created, alreadyExisted, mode);
|
|
1178
|
+
} catch (error) {
|
|
1179
|
+
failures.push(`${target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
if (failures.length > 0) {
|
|
1183
|
+
return {
|
|
1184
|
+
success: false,
|
|
1185
|
+
rootPath: NEXUS_DATA_DIR,
|
|
1186
|
+
created,
|
|
1187
|
+
alreadyExisted,
|
|
1188
|
+
error: `Failed to create ${String(failures.length)} dir(s): ${failures.join("; ")}`
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
return { success: true, rootPath: NEXUS_DATA_DIR, created, alreadyExisted, error: null };
|
|
1192
|
+
}
|
|
1193
|
+
function ensureDir(dirPath, dryRun, created, alreadyExisted, mode) {
|
|
1194
|
+
if (existsSync3(dirPath)) {
|
|
1195
|
+
alreadyExisted.push(dirPath);
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
if (!dryRun) {
|
|
1199
|
+
mkdirSync(dirPath, { recursive: true, ...mode !== void 0 ? { mode } : {} });
|
|
1200
|
+
}
|
|
1201
|
+
created.push(dirPath);
|
|
1202
|
+
}
|
|
1203
|
+
function describeDataDirFix(result) {
|
|
1204
|
+
if (!result.success) {
|
|
1205
|
+
return {
|
|
1206
|
+
line: `\u2717 Could not create data directories: ${result.error ?? "unknown error"}`,
|
|
1207
|
+
counted: false
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
if (result.created.length === 0) {
|
|
1211
|
+
return {
|
|
1212
|
+
line: "\u2717 Data directories already exist but are not writable \u2014 setup creates missing directories and cannot repair permissions. Fix them manually (chmod u+w).",
|
|
1213
|
+
counted: false
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
return {
|
|
1217
|
+
line: `\u2713 Created ${String(result.created.length)} missing data director${result.created.length === 1 ? "y" : "ies"}`,
|
|
1218
|
+
counted: true
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
export {
|
|
1223
|
+
VERSION,
|
|
1224
|
+
DEFAULT_TASK_TTL_MS,
|
|
1225
|
+
clampTaskTtl,
|
|
1226
|
+
DATA_SUBDIRECTORIES,
|
|
1227
|
+
checkApiKeys,
|
|
1228
|
+
checkSqlite,
|
|
1229
|
+
checkDataDirectory,
|
|
1230
|
+
runDoctor,
|
|
1231
|
+
doctorCommand,
|
|
1232
|
+
NEXUS_DATA_DIR,
|
|
1233
|
+
initDataDirectories,
|
|
1234
|
+
describeDataDirFix,
|
|
1235
|
+
createServer,
|
|
1236
|
+
connectTransport,
|
|
1237
|
+
startStdioServer,
|
|
1238
|
+
closeServer
|
|
1239
|
+
};
|
|
1240
|
+
//# sourceMappingURL=chunk-WMSWVHFZ.js.map
|