@danielblomma/cortex-mcp 2.4.1 → 2.4.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/CHANGELOG.md +25 -0
- package/README.md +5 -0
- package/bin/cli/arguments.mjs +60 -0
- package/bin/cli/context-passthrough.mjs +129 -0
- package/bin/cli/daemon.mjs +157 -0
- package/bin/cli/enterprise.mjs +516 -0
- package/bin/cli/help.mjs +88 -0
- package/bin/cli/hooks.mjs +199 -0
- package/bin/cli/mcp-command.mjs +87 -0
- package/bin/cli/paths.mjs +14 -0
- package/bin/cli/process.mjs +49 -0
- package/bin/cli/project-commands.mjs +237 -0
- package/bin/cli/project-runtime.mjs +34 -0
- package/bin/cli/query-command.mjs +18 -0
- package/bin/cli/router.mjs +66 -0
- package/bin/cli/run-command.mjs +44 -0
- package/bin/cli/scaffold-ownership.mjs +936 -0
- package/bin/cli/scaffold.mjs +687 -0
- package/bin/cli/stage-command.mjs +9 -0
- package/bin/cli/telemetry-command.mjs +18 -0
- package/bin/cli/trusted-runtime.mjs +18 -0
- package/bin/cortex.mjs +6 -1834
- package/mcp-registry-submission.json +1 -1
- package/package.json +3 -2
- package/scaffold/ownership/baseline-v2.4.1.json +22 -0
- package/scaffold/ownership/current.json +4 -0
- package/scaffold/ownership/v1.json +456 -0
- package/scaffold/scripts/ingest-parsers.mjs +1 -387
- package/scaffold/scripts/ingest-worker.mjs +4 -1
- package/scaffold/scripts/ingest.mjs +5 -3899
- package/scaffold/scripts/lib/ingest/arguments.mjs +78 -0
- package/scaffold/scripts/lib/ingest/chunks.mjs +212 -0
- package/scaffold/scripts/lib/ingest/config.mjs +120 -0
- package/scaffold/scripts/lib/ingest/constants.mjs +222 -0
- package/scaffold/scripts/lib/ingest/files.mjs +387 -0
- package/scaffold/scripts/lib/ingest/incremental-state.mjs +131 -0
- package/scaffold/scripts/lib/ingest/io.mjs +78 -0
- package/scaffold/scripts/lib/ingest/main.mjs +76 -0
- package/scaffold/scripts/lib/ingest/parser-composition.mjs +140 -0
- package/scaffold/scripts/lib/ingest/parser-registry.mjs +387 -0
- package/scaffold/scripts/lib/ingest/pipeline-stages.mjs +1625 -0
- package/scaffold/scripts/lib/ingest/projects.mjs +272 -0
- package/scaffold/scripts/lib/ingest/relations.mjs +860 -0
- package/scaffold/scripts/lib/ingest/runtime-paths.mjs +11 -0
- package/scaffold/scripts/lib/ingest/workers.mjs +264 -0
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import {
|
|
5
|
+
accent,
|
|
6
|
+
bold,
|
|
7
|
+
bullet,
|
|
8
|
+
gradient,
|
|
9
|
+
headerBanner,
|
|
10
|
+
muted,
|
|
11
|
+
printBullet,
|
|
12
|
+
spinner,
|
|
13
|
+
} from "../style.mjs";
|
|
14
|
+
import { runDaemonCommand } from "./daemon.mjs";
|
|
15
|
+
import { helpRow } from "./help.mjs";
|
|
16
|
+
import { hasManagedClaudeHooks, runHooksCommand } from "./hooks.mjs";
|
|
17
|
+
import {
|
|
18
|
+
loadGovernModule,
|
|
19
|
+
resolveTrustedCliEntry,
|
|
20
|
+
} from "./trusted-runtime.mjs";
|
|
21
|
+
|
|
22
|
+
function requireSudoElevation() {
|
|
23
|
+
const isRoot = process.getuid && process.getuid() === 0;
|
|
24
|
+
if (!isRoot) {
|
|
25
|
+
process.stderr.write(
|
|
26
|
+
bullet(
|
|
27
|
+
"fail",
|
|
28
|
+
"This command requires admin privileges to install non-bypassable enforcement.",
|
|
29
|
+
process.stderr,
|
|
30
|
+
) + "\n",
|
|
31
|
+
);
|
|
32
|
+
process.stderr.write(
|
|
33
|
+
muted(
|
|
34
|
+
" Re-run as: sudo " + process.argv.slice(1).join(" "),
|
|
35
|
+
process.stderr,
|
|
36
|
+
) + "\n",
|
|
37
|
+
);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
const sudoUser = process.env.SUDO_USER;
|
|
41
|
+
const sudoUidRaw = process.env.SUDO_UID;
|
|
42
|
+
const sudoGidRaw = process.env.SUDO_GID;
|
|
43
|
+
if (!sudoUser || !sudoUidRaw || !sudoGidRaw) {
|
|
44
|
+
process.stderr.write(
|
|
45
|
+
bullet(
|
|
46
|
+
"fail",
|
|
47
|
+
"Use 'sudo' to elevate (not 'su' or a root login).",
|
|
48
|
+
process.stderr,
|
|
49
|
+
) + "\n",
|
|
50
|
+
);
|
|
51
|
+
process.stderr.write(
|
|
52
|
+
muted(
|
|
53
|
+
" Cortex needs SUDO_USER/SUDO_UID/SUDO_GID set so that enterprise.yml,",
|
|
54
|
+
process.stderr,
|
|
55
|
+
) + "\n",
|
|
56
|
+
);
|
|
57
|
+
process.stderr.write(
|
|
58
|
+
muted(
|
|
59
|
+
" Claude Code hooks and the daemon end up owned by your user.",
|
|
60
|
+
process.stderr,
|
|
61
|
+
) + "\n",
|
|
62
|
+
);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
const uid = parseInt(sudoUidRaw, 10);
|
|
66
|
+
const gid = parseInt(sudoGidRaw, 10);
|
|
67
|
+
if (!Number.isFinite(uid) || !Number.isFinite(gid)) {
|
|
68
|
+
process.stderr.write(
|
|
69
|
+
bullet(
|
|
70
|
+
"fail",
|
|
71
|
+
"SUDO_UID/SUDO_GID are not valid integers — refusing to drop privileges.",
|
|
72
|
+
process.stderr,
|
|
73
|
+
) + "\n",
|
|
74
|
+
);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
return { user: sudoUser, uid, gid };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function dropPrivileges(sudo) {
|
|
81
|
+
const sudoInfo = os.userInfo({ uid: sudo.uid });
|
|
82
|
+
process.setgid(sudo.gid);
|
|
83
|
+
process.setuid(sudo.uid);
|
|
84
|
+
process.env.HOME = sudoInfo.homedir;
|
|
85
|
+
process.env.USER = sudo.user;
|
|
86
|
+
process.env.LOGNAME = sudo.user;
|
|
87
|
+
return sudoInfo.homedir;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function runAsSudoUser(sudo, operation) {
|
|
91
|
+
const required = [
|
|
92
|
+
"geteuid",
|
|
93
|
+
"getegid",
|
|
94
|
+
"seteuid",
|
|
95
|
+
"setegid",
|
|
96
|
+
"getgroups",
|
|
97
|
+
"setgroups",
|
|
98
|
+
];
|
|
99
|
+
for (const name of required) {
|
|
100
|
+
if (typeof process[name] !== "function") {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Secure Enterprise installation requires process.${name} support.`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const previous = {
|
|
108
|
+
euid: process.geteuid(),
|
|
109
|
+
egid: process.getegid(),
|
|
110
|
+
groups: process.getgroups(),
|
|
111
|
+
home: process.env.HOME,
|
|
112
|
+
user: process.env.USER,
|
|
113
|
+
logname: process.env.LOGNAME,
|
|
114
|
+
};
|
|
115
|
+
const sudoInfo = os.userInfo({ uid: sudo.uid });
|
|
116
|
+
try {
|
|
117
|
+
process.setgroups([sudo.gid]);
|
|
118
|
+
process.setegid(sudo.gid);
|
|
119
|
+
process.seteuid(sudo.uid);
|
|
120
|
+
process.env.HOME = sudoInfo.homedir;
|
|
121
|
+
process.env.USER = sudo.user;
|
|
122
|
+
process.env.LOGNAME = sudo.user;
|
|
123
|
+
return await operation();
|
|
124
|
+
} finally {
|
|
125
|
+
process.seteuid(previous.euid);
|
|
126
|
+
process.setegid(previous.egid);
|
|
127
|
+
process.setgroups(previous.groups);
|
|
128
|
+
if (previous.home === undefined) delete process.env.HOME;
|
|
129
|
+
else process.env.HOME = previous.home;
|
|
130
|
+
if (previous.user === undefined) delete process.env.USER;
|
|
131
|
+
else process.env.USER = previous.user;
|
|
132
|
+
if (previous.logname === undefined) delete process.env.LOGNAME;
|
|
133
|
+
else process.env.LOGNAME = previous.logname;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const ENTERPRISE_SUBCOMMANDS = new Set([
|
|
138
|
+
"status",
|
|
139
|
+
"sync",
|
|
140
|
+
"uninstall",
|
|
141
|
+
"repair",
|
|
142
|
+
"help",
|
|
143
|
+
"--help",
|
|
144
|
+
"-h",
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
export async function runEnterpriseCommand(args) {
|
|
148
|
+
if (args[0] === "install") {
|
|
149
|
+
return runEnterpriseInstall(args.slice(1));
|
|
150
|
+
}
|
|
151
|
+
if (args.length === 0 || ENTERPRISE_SUBCOMMANDS.has(args[0])) {
|
|
152
|
+
return runEnterpriseSubcommand(args);
|
|
153
|
+
}
|
|
154
|
+
throw new Error(
|
|
155
|
+
"Positional enterprise API keys are not accepted. Use 'cortex enterprise install --api-key-stdin'.",
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function runEnterpriseSubcommand(args) {
|
|
160
|
+
const sub = args[0] ?? "help";
|
|
161
|
+
|
|
162
|
+
if (sub === "help" || sub === "--help" || sub === "-h" || !sub) {
|
|
163
|
+
console.log(
|
|
164
|
+
gradient("cortex enterprise") + muted(" · governance, armed."),
|
|
165
|
+
);
|
|
166
|
+
console.log(
|
|
167
|
+
helpRow(
|
|
168
|
+
"enterprise install --api-key-stdin",
|
|
169
|
+
"Install (sudo). Managed enforcement + hooks + daemon.",
|
|
170
|
+
),
|
|
171
|
+
);
|
|
172
|
+
console.log(
|
|
173
|
+
helpRow(
|
|
174
|
+
" ",
|
|
175
|
+
"[--endpoint <url>] [--frameworks <csv>] [--no-hooks] [--no-daemon]",
|
|
176
|
+
),
|
|
177
|
+
);
|
|
178
|
+
console.log(
|
|
179
|
+
helpRow(
|
|
180
|
+
"enterprise status [--verbose|--json]",
|
|
181
|
+
"Show local enforcement state",
|
|
182
|
+
),
|
|
183
|
+
);
|
|
184
|
+
console.log(
|
|
185
|
+
helpRow("enterprise sync", "Force re-fetch + re-apply (sudo)"),
|
|
186
|
+
);
|
|
187
|
+
console.log(
|
|
188
|
+
helpRow(
|
|
189
|
+
"enterprise uninstall",
|
|
190
|
+
'Remove. [--break-glass --reason "<text>"] in enforced mode (sudo)',
|
|
191
|
+
),
|
|
192
|
+
);
|
|
193
|
+
console.log(
|
|
194
|
+
helpRow(
|
|
195
|
+
"enterprise repair",
|
|
196
|
+
"Verify managed paths, clear .cortex-tamper.lock (sudo)",
|
|
197
|
+
),
|
|
198
|
+
);
|
|
199
|
+
console.log("");
|
|
200
|
+
console.log(
|
|
201
|
+
muted(
|
|
202
|
+
"Example: printf '%s\\n' \"$CORTEX_API_KEY\" | sudo cortex enterprise install --api-key-stdin",
|
|
203
|
+
),
|
|
204
|
+
);
|
|
205
|
+
console.log(
|
|
206
|
+
muted("Default endpoint: https://cortex-web-rho.vercel.app"),
|
|
207
|
+
);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (sub === "status") {
|
|
212
|
+
let verbose = false;
|
|
213
|
+
let json = false;
|
|
214
|
+
for (let i = 1; i < args.length; i++) {
|
|
215
|
+
if (args[i] === "--verbose" || args[i] === "-v") verbose = true;
|
|
216
|
+
else if (args[i] === "--json") json = true;
|
|
217
|
+
else if (args[i].startsWith("-")) {
|
|
218
|
+
throw new Error(`Unknown enterprise status option: ${args[i]}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const mod = await loadGovernModule();
|
|
222
|
+
mod.runGovernStatus({ cwd: process.cwd(), verbose, json });
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (sub === "sync") {
|
|
227
|
+
const sudo = requireSudoElevation();
|
|
228
|
+
const mod = await loadGovernModule();
|
|
229
|
+
const projectOperation = (operation) =>
|
|
230
|
+
runAsSudoUser(sudo, operation);
|
|
231
|
+
await mod.runGovernSync({
|
|
232
|
+
cwd: process.cwd(),
|
|
233
|
+
projectOperation,
|
|
234
|
+
});
|
|
235
|
+
dropPrivileges(sudo);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (sub === "uninstall") {
|
|
240
|
+
let breakGlass = false;
|
|
241
|
+
let reason;
|
|
242
|
+
for (let i = 1; i < args.length; i++) {
|
|
243
|
+
if (args[i] === "--break-glass") breakGlass = true;
|
|
244
|
+
else if (args[i] === "--reason" && args[i + 1]) {
|
|
245
|
+
reason = args[i + 1];
|
|
246
|
+
i++;
|
|
247
|
+
} else if (args[i].startsWith("-")) {
|
|
248
|
+
throw new Error(`Unknown enterprise uninstall option: ${args[i]}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const sudo = requireSudoElevation();
|
|
252
|
+
const mod = await loadGovernModule();
|
|
253
|
+
const projectOperation = (operation) =>
|
|
254
|
+
runAsSudoUser(sudo, operation);
|
|
255
|
+
const result = await mod.runGovernUninstall({
|
|
256
|
+
cli: "all",
|
|
257
|
+
breakGlass,
|
|
258
|
+
reason,
|
|
259
|
+
cwd: process.cwd(),
|
|
260
|
+
projectOperation,
|
|
261
|
+
});
|
|
262
|
+
dropPrivileges(sudo);
|
|
263
|
+
if (!result.ok) {
|
|
264
|
+
printBullet("fail", result.message, process.stderr);
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
printBullet("ok", result.message);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (sub === "repair") {
|
|
272
|
+
let reason;
|
|
273
|
+
for (let i = 1; i < args.length; i++) {
|
|
274
|
+
if (args[i] === "--reason" && args[i + 1]) {
|
|
275
|
+
reason = args[i + 1];
|
|
276
|
+
i++;
|
|
277
|
+
} else if (args[i].startsWith("-")) {
|
|
278
|
+
throw new Error(`Unknown enterprise repair option: ${args[i]}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const sudo = requireSudoElevation();
|
|
282
|
+
const mod = await loadGovernModule();
|
|
283
|
+
const projectOperation = (operation) =>
|
|
284
|
+
runAsSudoUser(sudo, operation);
|
|
285
|
+
const result = await mod.runGovernRepair({
|
|
286
|
+
cwd: process.cwd(),
|
|
287
|
+
reason,
|
|
288
|
+
projectOperation,
|
|
289
|
+
});
|
|
290
|
+
dropPrivileges(sudo);
|
|
291
|
+
if (!result.ok) {
|
|
292
|
+
printBullet("fail", result.message, process.stderr);
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
printBullet("ok", result.message);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
throw new Error(`Unknown enterprise subcommand: ${sub}`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export async function runEnterpriseInstall(args, injected = {}) {
|
|
303
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
304
|
+
return runEnterpriseSubcommand(["help"]);
|
|
305
|
+
}
|
|
306
|
+
let readKeyFromStdin = false;
|
|
307
|
+
let endpoint;
|
|
308
|
+
let frameworks;
|
|
309
|
+
let installHooks = true;
|
|
310
|
+
let startDaemon = true;
|
|
311
|
+
for (let i = 0; i < args.length; i++) {
|
|
312
|
+
if (args[i] === "--api-key-stdin") {
|
|
313
|
+
readKeyFromStdin = true;
|
|
314
|
+
} else if (args[i] === "--endpoint" && args[i + 1]) {
|
|
315
|
+
endpoint = args[i + 1];
|
|
316
|
+
i++;
|
|
317
|
+
} else if (args[i] === "--frameworks" && args[i + 1]) {
|
|
318
|
+
frameworks = args[i + 1]
|
|
319
|
+
.split(",")
|
|
320
|
+
.map((value) => value.trim())
|
|
321
|
+
.filter(Boolean);
|
|
322
|
+
i++;
|
|
323
|
+
} else if (args[i] === "--no-hooks") {
|
|
324
|
+
installHooks = false;
|
|
325
|
+
} else if (args[i] === "--no-daemon") {
|
|
326
|
+
startDaemon = false;
|
|
327
|
+
} else if (args[i].startsWith("-")) {
|
|
328
|
+
throw new Error(`Unknown enterprise install option: ${args[i]}`);
|
|
329
|
+
} else {
|
|
330
|
+
throw new Error(
|
|
331
|
+
"Positional enterprise API keys are not accepted. Use --api-key-stdin.",
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (!readKeyFromStdin) {
|
|
337
|
+
throw new Error(
|
|
338
|
+
"Enterprise installation requires --api-key-stdin so the key is not exposed in process arguments.",
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
const secretInput = injected.stdin ?? process.stdin;
|
|
342
|
+
if (secretInput.isTTY) {
|
|
343
|
+
throw new Error(
|
|
344
|
+
"Read the enterprise API key from a pipe or redirected stdin; interactive echo is disabled for secrets.",
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
let apiKey = "";
|
|
348
|
+
for await (const chunk of secretInput) {
|
|
349
|
+
apiKey += chunk.toString();
|
|
350
|
+
if (apiKey.length > 4096) {
|
|
351
|
+
throw new Error("Enterprise API key input is too large.");
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
const singleLineInput = apiKey.endsWith("\r\n")
|
|
355
|
+
? apiKey.slice(0, -2)
|
|
356
|
+
: apiKey.endsWith("\n")
|
|
357
|
+
? apiKey.slice(0, -1)
|
|
358
|
+
: apiKey;
|
|
359
|
+
if (
|
|
360
|
+
!singleLineInput ||
|
|
361
|
+
singleLineInput.includes("\n") ||
|
|
362
|
+
singleLineInput.includes("\r")
|
|
363
|
+
) {
|
|
364
|
+
throw new Error("Expected exactly one enterprise API key on stdin.");
|
|
365
|
+
}
|
|
366
|
+
apiKey = singleLineInput.trim();
|
|
367
|
+
if (!apiKey) {
|
|
368
|
+
throw new Error("Expected exactly one enterprise API key on stdin.");
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const sudo =
|
|
372
|
+
(injected.requireSudoElevation ?? requireSudoElevation)();
|
|
373
|
+
const projectOperation = (operation) =>
|
|
374
|
+
(injected.runAsSudoUser ?? runAsSudoUser)(sudo, operation);
|
|
375
|
+
|
|
376
|
+
process.stdout.write(
|
|
377
|
+
headerBanner({
|
|
378
|
+
tagline: " Cortex enterprise — activating governance",
|
|
379
|
+
}),
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
let enterpriseMod = injected.enterpriseMod;
|
|
383
|
+
if (!enterpriseMod) {
|
|
384
|
+
const enterpriseEntry = resolveTrustedCliEntry("enterprise-setup");
|
|
385
|
+
if (!fs.existsSync(enterpriseEntry)) {
|
|
386
|
+
printBullet(
|
|
387
|
+
"fail",
|
|
388
|
+
`The installed Cortex package is missing its trusted Enterprise runtime (${enterpriseEntry}). Reinstall Cortex.`,
|
|
389
|
+
);
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
enterpriseMod = await import(pathToFileURL(enterpriseEntry).href);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const step1 = spinner("Initializing Cortex core");
|
|
396
|
+
const setupResult = await projectOperation(() =>
|
|
397
|
+
enterpriseMod.runEnterpriseSetup({
|
|
398
|
+
apiKey,
|
|
399
|
+
endpoint,
|
|
400
|
+
cwd: process.cwd(),
|
|
401
|
+
}),
|
|
402
|
+
);
|
|
403
|
+
if (!setupResult.ok) {
|
|
404
|
+
step1.stop(
|
|
405
|
+
"fail",
|
|
406
|
+
`Initializing Cortex core — ${setupResult.message}`,
|
|
407
|
+
);
|
|
408
|
+
process.exit(1);
|
|
409
|
+
}
|
|
410
|
+
step1.stop(
|
|
411
|
+
"ok",
|
|
412
|
+
`Initializing Cortex core — license ${setupResult.edition}, expires ${setupResult.expiresAt}`,
|
|
413
|
+
);
|
|
414
|
+
printBullet("info", muted(`config: ${setupResult.configPath}`));
|
|
415
|
+
|
|
416
|
+
const sudoHome =
|
|
417
|
+
injected.sudoHome ?? os.userInfo({ uid: sudo.uid }).homedir;
|
|
418
|
+
const bindEnterpriseIdentity =
|
|
419
|
+
injected.bindEnterpriseIdentity ?? enterpriseMod.bindEnterpriseIdentity;
|
|
420
|
+
if (
|
|
421
|
+
typeof bindEnterpriseIdentity !== "function" ||
|
|
422
|
+
!(await projectOperation(() =>
|
|
423
|
+
bindEnterpriseIdentity({
|
|
424
|
+
apiKey,
|
|
425
|
+
endpoint,
|
|
426
|
+
homeDir: sudoHome,
|
|
427
|
+
}),
|
|
428
|
+
))
|
|
429
|
+
) {
|
|
430
|
+
throw new Error(
|
|
431
|
+
"Could not bind this user profile to the verified Enterprise identity. " +
|
|
432
|
+
"A different endpoint is already enrolled; use a separate OS user boundary.",
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const baseUrl = (
|
|
437
|
+
endpoint ?? "https://cortex-web-rho.vercel.app"
|
|
438
|
+
).replace(/\/$/, "");
|
|
439
|
+
const step2 = spinner("Loading policy engine");
|
|
440
|
+
const governMod = injected.governMod ?? (await loadGovernModule());
|
|
441
|
+
const governResult = await governMod.runGovernInstall({
|
|
442
|
+
cli: "all",
|
|
443
|
+
mode: "enforced",
|
|
444
|
+
cwd: process.cwd(),
|
|
445
|
+
apiKey,
|
|
446
|
+
baseUrl,
|
|
447
|
+
frameworks: frameworks ?? ["iso27001", "iso42001", "soc2"],
|
|
448
|
+
projectOperation,
|
|
449
|
+
});
|
|
450
|
+
if (!governResult.ok) {
|
|
451
|
+
step2.stop(
|
|
452
|
+
"fail",
|
|
453
|
+
`Loading policy engine — ${governResult.message}`,
|
|
454
|
+
);
|
|
455
|
+
process.exit(1);
|
|
456
|
+
}
|
|
457
|
+
step2.stop("ok", "Loading policy engine — policies armed");
|
|
458
|
+
|
|
459
|
+
const step3 = spinner("Connecting audit pipeline");
|
|
460
|
+
step3.stop("ok", `Connecting audit pipeline — endpoint ${baseUrl}`);
|
|
461
|
+
|
|
462
|
+
(injected.dropPrivileges ?? dropPrivileges)(sudo);
|
|
463
|
+
|
|
464
|
+
if (installHooks) {
|
|
465
|
+
const step4 = spinner("Preparing MCP gateway");
|
|
466
|
+
try {
|
|
467
|
+
if ((injected.hasManagedClaudeHooks ?? hasManagedClaudeHooks)()) {
|
|
468
|
+
step4.stop(
|
|
469
|
+
"ok",
|
|
470
|
+
"Preparing MCP gateway — managed Claude hooks active",
|
|
471
|
+
);
|
|
472
|
+
} else {
|
|
473
|
+
await (injected.runHooksCommand ?? runHooksCommand)(["install"]);
|
|
474
|
+
step4.stop("ok", "Preparing MCP gateway — hooks installed");
|
|
475
|
+
}
|
|
476
|
+
} catch (err) {
|
|
477
|
+
step4.stop(
|
|
478
|
+
"fail",
|
|
479
|
+
`Preparing MCP gateway — ${
|
|
480
|
+
err instanceof Error ? err.message : String(err)
|
|
481
|
+
}`,
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
} else {
|
|
485
|
+
printBullet("warn", "Preparing MCP gateway — skipped (--no-hooks)");
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (startDaemon) {
|
|
489
|
+
const step5 = spinner("Installing guardrails");
|
|
490
|
+
try {
|
|
491
|
+
await (injected.runDaemonCommand ?? runDaemonCommand)(["restart"]);
|
|
492
|
+
step5.stop("ok", "Installing guardrails — daemon online");
|
|
493
|
+
} catch (err) {
|
|
494
|
+
step5.stop(
|
|
495
|
+
"fail",
|
|
496
|
+
`Installing guardrails — ${
|
|
497
|
+
err instanceof Error ? err.message : String(err)
|
|
498
|
+
}`,
|
|
499
|
+
);
|
|
500
|
+
throw err;
|
|
501
|
+
}
|
|
502
|
+
} else {
|
|
503
|
+
printBullet("warn", "Installing guardrails — skipped (--no-daemon)");
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
console.log("");
|
|
507
|
+
console.log(bullet("ok", bold("Cortex is running.")));
|
|
508
|
+
console.log(muted(" Monitoring AI activity. No violations detected."));
|
|
509
|
+
console.log(
|
|
510
|
+
muted(" Next: ") +
|
|
511
|
+
accent("cortex enterprise status") +
|
|
512
|
+
muted(" · ") +
|
|
513
|
+
accent("cortex telemetry test"),
|
|
514
|
+
);
|
|
515
|
+
console.log("");
|
|
516
|
+
}
|
package/bin/cli/help.mjs
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import {
|
|
3
|
+
accent,
|
|
4
|
+
bold,
|
|
5
|
+
gradient,
|
|
6
|
+
headerBanner,
|
|
7
|
+
muted,
|
|
8
|
+
} from "../style.mjs";
|
|
9
|
+
import { PACKAGE_JSON_PATH } from "./paths.mjs";
|
|
10
|
+
|
|
11
|
+
export function printBanner(title) {
|
|
12
|
+
process.stdout.write(headerBanner({ tagline: title }));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function helpRow(cmd, desc) {
|
|
16
|
+
const target = 46;
|
|
17
|
+
const pad = cmd.length >= target ? " " : " ".repeat(target - cmd.length);
|
|
18
|
+
if (desc) {
|
|
19
|
+
return ` ${accent(cmd)}${pad}${muted(desc)}`;
|
|
20
|
+
}
|
|
21
|
+
return ` ${accent(cmd)}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function helpSection(title) {
|
|
25
|
+
return `\n${bold(muted(title))}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function printHelp() {
|
|
29
|
+
console.log(gradient("CORTEX CLI") + muted(" · governance for AI coding agents"));
|
|
30
|
+
console.log(muted(" Cortex is in control. Calm, intelligent, always monitoring."));
|
|
31
|
+
console.log(helpSection("USAGE"));
|
|
32
|
+
console.log(helpRow("cortex <command> [options]"));
|
|
33
|
+
|
|
34
|
+
console.log(helpSection("CONTEXT"));
|
|
35
|
+
console.log(helpRow("init [path]", "Scaffold a project with --force/--bootstrap/--connect/--watch"));
|
|
36
|
+
console.log(helpRow("connect [path]", "Register MCP clients (Codex + Claude Code)"));
|
|
37
|
+
console.log(helpRow("bootstrap", "Install deps, ingest, embed, load graph"));
|
|
38
|
+
console.log(helpRow("update", "Refresh context for changed files"));
|
|
39
|
+
console.log(helpRow("status", "Project context status"));
|
|
40
|
+
console.log(helpRow("doctor", "Diagnose setup health"));
|
|
41
|
+
console.log(helpRow("ingest [--changed] [--verbose]", "Re-index source files"));
|
|
42
|
+
console.log(helpRow("embed [--changed]", "Recompute embeddings"));
|
|
43
|
+
console.log(helpRow("graph-load [--no-reset]", "Reload the dependency graph"));
|
|
44
|
+
console.log(helpRow("search <query> [--json]", "Search local graph+RAG context"));
|
|
45
|
+
console.log(helpRow("related <entity-id> [--json]", "Show related context entities"));
|
|
46
|
+
console.log(helpRow("impact <query|entity-id> [--json]", "Trace likely impact paths"));
|
|
47
|
+
console.log(helpRow("rules [--json]", "List active context rules"));
|
|
48
|
+
console.log(helpRow("explain <query|entity-id> [--json]", "Show search score evidence"));
|
|
49
|
+
console.log(helpRow("pattern-evidence <file|entity-id> [--query <text>] [--top-k <n>] [--json]", "Collect cited repo-local pattern evidence"));
|
|
50
|
+
console.log(helpRow("dashboard [--interval <sec>]", "Live local dashboard"));
|
|
51
|
+
console.log(helpRow("memory-compile [--dry-run] [--verbose]", "Compile memory artifacts"));
|
|
52
|
+
console.log(helpRow("memory-lint [--verbose] [--json]", "Lint compiled memory"));
|
|
53
|
+
console.log(helpRow("watch [start|stop|status|run|once]", "Background sync (--interval, --debounce, --mode)"));
|
|
54
|
+
|
|
55
|
+
console.log(helpSection("GOVERNANCE"));
|
|
56
|
+
console.log(helpRow("enterprise install --api-key-stdin", "Install enforcement + hooks + daemon (sudo)"));
|
|
57
|
+
console.log(helpRow(" ", "[--endpoint <url>] [--frameworks <csv>] [--no-hooks] [--no-daemon]"));
|
|
58
|
+
console.log(helpRow("enterprise status", "Show local enforcement state"));
|
|
59
|
+
console.log(helpRow("enterprise sync", "Force re-fetch + re-apply (sudo)"));
|
|
60
|
+
console.log(helpRow("enterprise uninstall", "Remove enforcement (sudo, --break-glass --reason)"));
|
|
61
|
+
console.log(helpRow("enterprise repair", "Verify managed paths, clear tamper-lock (sudo)"));
|
|
62
|
+
console.log(helpRow("run <claude|codex|copilot> [args...]", "Wrap an AI CLI in cortex enforcement"));
|
|
63
|
+
console.log(helpRow("daemon [start|stop|restart|status]", "Local supervisor daemon"));
|
|
64
|
+
console.log(helpRow("hooks [install|uninstall|status] [--project]", "Claude Code hooks"));
|
|
65
|
+
console.log(helpRow("telemetry test", "Smoke-test the push pipeline"));
|
|
66
|
+
|
|
67
|
+
console.log(helpSection("HARNESS"));
|
|
68
|
+
console.log(helpRow("stage start --task-id <id> --description \"...\"", "Start a workflow run for a task"));
|
|
69
|
+
console.log(helpRow("stage status --task-id <id>", "Print run state JSON"));
|
|
70
|
+
console.log(helpRow("stage envelope --task-id <id> [--stage <name>]", "Compose stage prompt envelope"));
|
|
71
|
+
console.log(helpRow("stage advance --task-id <id> --stage <name> --body-file <path>", "Write artifact, advance run"));
|
|
72
|
+
console.log(helpRow("stage run --task-id <id> -- <command>", "Exec a command with CORTEX_ACTIVE_TASK_ID set"));
|
|
73
|
+
|
|
74
|
+
console.log(helpSection("MISC"));
|
|
75
|
+
console.log(helpRow("mcp", "Run the MCP stdio server for the current project"));
|
|
76
|
+
console.log(helpRow("version", "Print CLI version"));
|
|
77
|
+
console.log(helpRow("help", "This screen"));
|
|
78
|
+
console.log("");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function readCliVersion() {
|
|
82
|
+
try {
|
|
83
|
+
const parsed = JSON.parse(fs.readFileSync(PACKAGE_JSON_PATH, "utf8"));
|
|
84
|
+
return typeof parsed.version === "string" ? parsed.version : "0.0.0";
|
|
85
|
+
} catch {
|
|
86
|
+
return "0.0.0";
|
|
87
|
+
}
|
|
88
|
+
}
|