@danielblomma/cortex-mcp 2.2.5 → 2.4.1
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 +201 -0
- package/README.md +73 -3
- package/bin/cortex.mjs +339 -242
- package/bin/daemon-control.mjs +162 -0
- package/package.json +7 -3
- package/scaffold/mcp/dist/.cortex-build-hash +1 -0
- package/scaffold/mcp/dist/cli/enterprise-setup.js +134 -0
- package/scaffold/mcp/dist/cli/govern.js +937 -0
- package/scaffold/mcp/dist/cli/query.js +409 -0
- package/scaffold/mcp/dist/cli/run.js +295 -0
- package/scaffold/mcp/dist/cli/stage.js +308 -0
- package/scaffold/mcp/dist/cli/telemetry-test.js +141 -0
- package/scaffold/mcp/dist/cli/ungoverned-detector.js +133 -0
- package/scaffold/mcp/dist/contextEntities.js +282 -0
- package/scaffold/mcp/dist/core/audit/query.js +72 -0
- package/scaffold/mcp/dist/core/audit/writer.js +28 -0
- package/scaffold/mcp/dist/core/config.js +235 -0
- package/scaffold/mcp/dist/core/enterprise-host-identity.js +193 -0
- package/scaffold/mcp/dist/core/enterprise-identity.js +18 -0
- package/scaffold/mcp/dist/core/enterprise-rotation.js +158 -0
- package/scaffold/mcp/dist/core/govern-paths.js +21 -0
- package/scaffold/mcp/dist/core/index.js +14 -0
- package/scaffold/mcp/dist/core/license.js +278 -0
- package/scaffold/mcp/dist/core/policy/enforce.js +66 -0
- package/scaffold/mcp/dist/core/policy/injection.js +172 -0
- package/scaffold/mcp/dist/core/policy/store.js +179 -0
- package/scaffold/mcp/dist/core/rbac/check.js +30 -0
- package/scaffold/mcp/dist/core/secure-endpoint.js +25 -0
- package/scaffold/mcp/dist/core/telemetry/collector.js +285 -0
- package/scaffold/mcp/dist/core/telemetry/state-dir.js +31 -0
- package/scaffold/mcp/dist/core/validators/builtins.js +632 -0
- package/scaffold/mcp/dist/core/validators/config.js +44 -0
- package/scaffold/mcp/dist/core/validators/engine.js +120 -0
- package/scaffold/mcp/dist/core/validators/evaluators/code_comments.js +236 -0
- package/scaffold/mcp/dist/core/validators/evaluators/regex.js +116 -0
- package/scaffold/mcp/dist/core/workflow/artifact-io.js +103 -0
- package/scaffold/mcp/dist/core/workflow/capabilities.js +88 -0
- package/scaffold/mcp/dist/core/workflow/default-workflows.js +102 -0
- package/scaffold/mcp/dist/core/workflow/enforcement.js +164 -0
- package/scaffold/mcp/dist/core/workflow/envelope.js +132 -0
- package/scaffold/mcp/dist/core/workflow/index.js +11 -0
- package/scaffold/mcp/dist/core/workflow/mcp-tools.js +175 -0
- package/scaffold/mcp/dist/core/workflow/resolution.js +69 -0
- package/scaffold/mcp/dist/core/workflow/run-lifecycle.js +123 -0
- package/scaffold/mcp/dist/core/workflow/schemas.js +141 -0
- package/scaffold/mcp/dist/core/workflow/synced-capability-registry.js +60 -0
- package/scaffold/mcp/dist/core/workflow/synced-registry.js +60 -0
- package/scaffold/mcp/dist/daemon/capability-sync-checker.js +242 -0
- package/scaffold/mcp/dist/daemon/client.js +130 -0
- package/scaffold/mcp/dist/daemon/egress-proxy.js +288 -0
- package/scaffold/mcp/dist/daemon/global-host-events.js +222 -0
- package/scaffold/mcp/dist/daemon/heartbeat-pusher.js +116 -0
- package/scaffold/mcp/dist/daemon/heartbeat-tracker.js +165 -0
- package/scaffold/mcp/dist/daemon/host-events-pusher.js +249 -0
- package/scaffold/mcp/dist/daemon/main.js +449 -0
- package/scaffold/mcp/dist/daemon/paths.js +36 -0
- package/scaffold/mcp/dist/daemon/project-service-registry.js +78 -0
- package/scaffold/mcp/dist/daemon/protocol.js +8 -0
- package/scaffold/mcp/dist/daemon/server.js +180 -0
- package/scaffold/mcp/dist/daemon/skill-sync-checker.js +557 -0
- package/scaffold/mcp/dist/daemon/sync-checker.js +165 -0
- package/scaffold/mcp/dist/daemon/ungoverned-scanner.js +136 -0
- package/scaffold/mcp/dist/daemon/workflow-sync-checker.js +249 -0
- package/scaffold/mcp/dist/defaults.js +6 -0
- package/scaffold/mcp/dist/embed.js +627 -0
- package/scaffold/mcp/dist/embedScheduler.js +479 -0
- package/scaffold/mcp/dist/embeddings.js +167 -0
- package/scaffold/mcp/dist/enterprise/audit/push.js +66 -0
- package/scaffold/mcp/dist/enterprise/index.js +327 -0
- package/scaffold/mcp/dist/enterprise/model/deploy.js +27 -0
- package/scaffold/mcp/dist/enterprise/policy/sync.js +129 -0
- package/scaffold/mcp/dist/enterprise/privacy/boundary.js +184 -0
- package/scaffold/mcp/dist/enterprise/reviews/changed-files.js +33 -0
- package/scaffold/mcp/dist/enterprise/reviews/pattern-context.js +202 -0
- package/scaffold/mcp/dist/enterprise/reviews/policy-selection.js +46 -0
- package/scaffold/mcp/dist/enterprise/reviews/push.js +102 -0
- package/scaffold/mcp/dist/enterprise/reviews/trust-state.js +186 -0
- package/scaffold/mcp/dist/enterprise/telemetry/sync.js +57 -0
- package/scaffold/mcp/dist/enterprise/tools/enterprise.js +826 -0
- package/scaffold/mcp/dist/enterprise/tools/harness.js +40 -0
- package/scaffold/mcp/dist/enterprise/tools/walk.js +73 -0
- package/scaffold/mcp/dist/enterprise/violations/push.js +77 -0
- package/scaffold/mcp/dist/enterprise/workflow/push.js +44 -0
- package/scaffold/mcp/dist/enterprise/workflow/state.js +329 -0
- package/scaffold/mcp/dist/frontmatter.js +33 -0
- package/scaffold/mcp/dist/graph.js +769 -0
- package/scaffold/mcp/dist/graphCsv.js +55 -0
- package/scaffold/mcp/dist/graphMetrics.js +8 -0
- package/scaffold/mcp/dist/hooks/permission-request.js +89 -0
- package/scaffold/mcp/dist/hooks/post-tool-use.js +105 -0
- package/scaffold/mcp/dist/hooks/pre-compact.js +29 -0
- package/scaffold/mcp/dist/hooks/pre-tool-use.js +78 -0
- package/scaffold/mcp/dist/hooks/session-end.js +43 -0
- package/scaffold/mcp/dist/hooks/session-start.js +41 -0
- package/scaffold/mcp/dist/hooks/shared.js +194 -0
- package/scaffold/mcp/dist/hooks/stop.js +28 -0
- package/scaffold/mcp/dist/hooks/user-prompt-submit.js +33 -0
- package/scaffold/mcp/dist/impactPresentation.js +137 -0
- package/scaffold/mcp/dist/impactRanking.js +191 -0
- package/scaffold/mcp/dist/impactResponse.js +30 -0
- package/scaffold/mcp/dist/impactResults.js +105 -0
- package/scaffold/mcp/dist/impactSeed.js +20 -0
- package/scaffold/mcp/dist/impactTraversal.js +64 -0
- package/scaffold/mcp/dist/jsonl.js +77 -0
- package/scaffold/mcp/dist/loadGraph.js +759 -0
- package/scaffold/mcp/dist/lruCache.js +38 -0
- package/scaffold/mcp/dist/paths.js +97 -0
- package/scaffold/mcp/dist/patternEvidence.js +272 -0
- package/scaffold/mcp/dist/plugin.js +81 -0
- package/scaffold/mcp/dist/presets.js +78 -0
- package/scaffold/mcp/dist/relatedResponse.js +18 -0
- package/scaffold/mcp/dist/relatedTraversal.js +78 -0
- package/scaffold/mcp/dist/rules.js +23 -0
- package/scaffold/mcp/dist/search.js +212 -0
- package/scaffold/mcp/dist/searchCore.js +457 -0
- package/scaffold/mcp/dist/searchResults.js +230 -0
- package/scaffold/mcp/dist/server.js +317 -0
- package/scaffold/mcp/dist/types.js +1 -0
- package/scaffold/mcp/package-lock.json +336 -212
- package/scaffold/mcp/package.json +15 -6
- package/scaffold/mcp/src/cli/enterprise-setup.ts +82 -8
- package/scaffold/mcp/src/cli/govern.ts +137 -52
- package/scaffold/mcp/src/cli/query.ts +36 -2
- package/scaffold/mcp/src/cli/run.ts +53 -19
- package/scaffold/mcp/src/cli/stage.ts +8 -3
- package/scaffold/mcp/src/core/config.ts +9 -2
- package/scaffold/mcp/src/core/enterprise-host-identity.ts +259 -0
- package/scaffold/mcp/src/core/enterprise-identity.ts +20 -0
- package/scaffold/mcp/src/core/enterprise-rotation.ts +185 -0
- package/scaffold/mcp/src/core/govern-paths.ts +30 -0
- package/scaffold/mcp/src/core/license.ts +186 -17
- package/scaffold/mcp/src/core/secure-endpoint.ts +23 -0
- package/scaffold/mcp/src/core/workflow/enforcement.ts +8 -1
- package/scaffold/mcp/src/core/workflow/mcp-tools.ts +3 -0
- package/scaffold/mcp/src/core/workflow/resolution.ts +9 -1
- package/scaffold/mcp/src/core/workflow/synced-capability-registry.ts +15 -0
- package/scaffold/mcp/src/core/workflow/synced-registry.ts +15 -0
- package/scaffold/mcp/src/daemon/capability-sync-checker.ts +38 -2
- package/scaffold/mcp/src/daemon/egress-proxy.ts +14 -8
- package/scaffold/mcp/src/daemon/global-host-events.ts +288 -0
- package/scaffold/mcp/src/daemon/heartbeat-pusher.ts +4 -0
- package/scaffold/mcp/src/daemon/heartbeat-tracker.ts +2 -0
- package/scaffold/mcp/src/daemon/host-events-pusher.ts +5 -0
- package/scaffold/mcp/src/daemon/main.ts +99 -25
- package/scaffold/mcp/src/daemon/project-service-registry.ts +107 -0
- package/scaffold/mcp/src/daemon/skill-sync-checker.ts +368 -31
- package/scaffold/mcp/src/daemon/sync-checker.ts +4 -0
- package/scaffold/mcp/src/daemon/ungoverned-scanner.ts +57 -25
- package/scaffold/mcp/src/daemon/workflow-sync-checker.ts +41 -2
- package/scaffold/mcp/src/embed.ts +211 -10
- package/scaffold/mcp/src/enterprise/audit/push.ts +8 -0
- package/scaffold/mcp/src/enterprise/index.ts +1 -1
- package/scaffold/mcp/src/enterprise/policy/sync.ts +12 -0
- package/scaffold/mcp/src/enterprise/reviews/changed-files.ts +34 -0
- package/scaffold/mcp/src/enterprise/reviews/pattern-context.ts +231 -0
- package/scaffold/mcp/src/enterprise/reviews/push.ts +9 -0
- package/scaffold/mcp/src/enterprise/reviews/trust-state.ts +3 -1
- package/scaffold/mcp/src/enterprise/telemetry/sync.ts +9 -0
- package/scaffold/mcp/src/enterprise/tools/enterprise.ts +49 -36
- package/scaffold/mcp/src/enterprise/violations/push.ts +9 -0
- package/scaffold/mcp/src/enterprise/workflow/push.ts +7 -0
- package/scaffold/mcp/src/paths.ts +3 -5
- package/scaffold/mcp/src/patternEvidence.ts +347 -0
- package/scaffold/mcp/src/plugin.ts +20 -0
- package/scaffold/mcp/src/search.ts +35 -9
- package/scaffold/mcp/src/searchCore.ts +254 -12
- package/scaffold/mcp/src/searchResults.ts +96 -9
- package/scaffold/mcp/src/types.ts +7 -0
- package/scaffold/mcp/tests/changed-files.test.mjs +41 -0
- package/scaffold/mcp/tests/copilot-shim.test.mjs +19 -1
- package/scaffold/mcp/tests/egress-proxy.test.mjs +37 -0
- package/scaffold/mcp/tests/embed-entities.test.mjs +109 -1
- package/scaffold/mcp/tests/enterprise-identity-sync.test.mjs +401 -0
- package/scaffold/mcp/tests/enterprise-pattern-context.test.mjs +322 -0
- package/scaffold/mcp/tests/enterprise-setup.test.mjs +189 -0
- package/scaffold/mcp/tests/global-host-events.test.mjs +81 -0
- package/scaffold/mcp/tests/govern-install.test.mjs +95 -2
- package/scaffold/mcp/tests/govern-repair.test.mjs +20 -3
- package/scaffold/mcp/tests/heartbeat-tracker.test.mjs +26 -0
- package/scaffold/mcp/tests/license.test.mjs +367 -0
- package/scaffold/mcp/tests/paths.test.mjs +11 -3
- package/scaffold/mcp/tests/pattern-evidence.test.mjs +321 -0
- package/scaffold/mcp/tests/project-service-registry.test.mjs +172 -0
- package/scaffold/mcp/tests/query-cli.test.mjs +73 -1
- package/scaffold/mcp/tests/review-trust-contract.test.mjs +18 -0
- package/scaffold/mcp/tests/search-graph-score.test.mjs +167 -0
- package/scaffold/mcp/tests/secure-enterprise-endpoint.test.mjs +46 -0
- package/scaffold/mcp/tests/skill-sync-checker.test.mjs +446 -2
- package/scaffold/mcp/tests/ungoverned-scanner.test.mjs +104 -22
- package/scaffold/mcp/tests/workflow-cli.test.mjs +14 -1
- package/scaffold/mcp/tests/workflow-synced-capabilities.test.mjs +37 -0
- package/scaffold/mcp/tests/workflow-synced-registry.test.mjs +40 -1
- package/scaffold/scripts/dashboard.mjs +29 -18
- package/scaffold/scripts/doctor.sh +26 -8
- package/scaffold/scripts/ingest.mjs +37 -4
- package/scaffold/scripts/status.sh +13 -6
|
@@ -5,6 +5,8 @@ import os from "node:os";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
|
|
7
7
|
import { runScanOnce, writeHostAuditEvent, startUngovernedScanner } from "../dist/daemon/ungoverned-scanner.js";
|
|
8
|
+
import { claimEnterpriseHostIdentity } from "../dist/core/enterprise-host-identity.js";
|
|
9
|
+
import { enterpriseCredentialId } from "../dist/core/license.js";
|
|
8
10
|
|
|
9
11
|
function makeWorkspace(governMode, installs = null) {
|
|
10
12
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "cortex-ungoverned-"));
|
|
@@ -41,7 +43,7 @@ test("writeHostAuditEvent appends one JSONL line per call", async () => {
|
|
|
41
43
|
}
|
|
42
44
|
});
|
|
43
45
|
|
|
44
|
-
test("runScanOnce:
|
|
46
|
+
test("runScanOnce: does not write unattributed host process metadata into project audit", async () => {
|
|
45
47
|
const { root } = makeWorkspace("advisory");
|
|
46
48
|
try {
|
|
47
49
|
const fakeProcs = [
|
|
@@ -54,20 +56,64 @@ test("runScanOnce: writes audit event with action=logged in advisory mode", asyn
|
|
|
54
56
|
});
|
|
55
57
|
assert.equal(findings.length, 1);
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
assert.equal(events[0].event_type, "ungoverned_ai_session_detected");
|
|
62
|
-
assert.equal(events[0].cli, "claude");
|
|
63
|
-
assert.equal(events[0].mode, "advisory");
|
|
64
|
-
assert.equal(events[0].action, "logged");
|
|
65
|
-
assert.equal(events[0].host_id, "test-host");
|
|
59
|
+
assert.equal(
|
|
60
|
+
fs.existsSync(path.join(root, ".context", "audit")),
|
|
61
|
+
false,
|
|
62
|
+
);
|
|
66
63
|
} finally {
|
|
67
64
|
fs.rmSync(root, { recursive: true, force: true });
|
|
68
65
|
}
|
|
69
66
|
});
|
|
70
67
|
|
|
68
|
+
test("runScanOnce: writes detections only to the credential-bound user-global queue", async () => {
|
|
69
|
+
const { root } = makeWorkspace("advisory");
|
|
70
|
+
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), "cortex-global-events-"));
|
|
71
|
+
const endpoint = "https://events.example.com";
|
|
72
|
+
const apiKey = "ent_global_events_12345678";
|
|
73
|
+
const credentialId = enterpriseCredentialId(endpoint, apiKey);
|
|
74
|
+
process.env.HOME = homeDir;
|
|
75
|
+
try {
|
|
76
|
+
assert.equal(
|
|
77
|
+
claimEnterpriseHostIdentity(credentialId, endpoint),
|
|
78
|
+
true,
|
|
79
|
+
);
|
|
80
|
+
await runScanOnce({
|
|
81
|
+
cwd: root,
|
|
82
|
+
credentialId,
|
|
83
|
+
detectorOptions: {
|
|
84
|
+
hostId: "host-global",
|
|
85
|
+
processes: [
|
|
86
|
+
{
|
|
87
|
+
pid: 4321,
|
|
88
|
+
ppid: 1,
|
|
89
|
+
user: os.userInfo().username,
|
|
90
|
+
comm: "claude",
|
|
91
|
+
args: "claude --prompt private",
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
assert.equal(fs.existsSync(path.join(root, ".context", "audit")), false);
|
|
98
|
+
const queueDir = path.join(homeDir, ".cortex", "host-events");
|
|
99
|
+
const queueFile = fs.readdirSync(queueDir).find((name) =>
|
|
100
|
+
name.startsWith("ungoverned-")
|
|
101
|
+
);
|
|
102
|
+
assert.ok(queueFile);
|
|
103
|
+
assert.equal(fs.statSync(path.join(queueDir, queueFile)).mode & 0o777, 0o600);
|
|
104
|
+
const event = JSON.parse(
|
|
105
|
+
fs.readFileSync(path.join(queueDir, queueFile), "utf8").trim(),
|
|
106
|
+
);
|
|
107
|
+
assert.equal(event.credential_id, credentialId);
|
|
108
|
+
assert.equal(event.pid, 4321);
|
|
109
|
+
assert.match(event.args, /private/);
|
|
110
|
+
} finally {
|
|
111
|
+
delete process.env.HOME;
|
|
112
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
113
|
+
fs.rmSync(homeDir, { recursive: true, force: true });
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
71
117
|
test("runScanOnce: enforced mode marks action=sigterm but our mock doesn't actually signal real procs", async () => {
|
|
72
118
|
const { root } = makeWorkspace("enforced");
|
|
73
119
|
try {
|
|
@@ -91,12 +137,11 @@ test("runScanOnce: enforced mode marks action=sigterm but our mock doesn't actua
|
|
|
91
137
|
process.kill = origKill;
|
|
92
138
|
}
|
|
93
139
|
|
|
94
|
-
const date = new Date().toISOString().slice(0, 10);
|
|
95
|
-
const file = path.join(root, ".context", "audit", `host-events-${date}.jsonl`);
|
|
96
|
-
const events = fs.readFileSync(file, "utf8").trim().split("\n").map(JSON.parse);
|
|
97
|
-
assert.equal(events[0].mode, "enforced");
|
|
98
|
-
assert.equal(events[0].action, "sigterm");
|
|
99
140
|
assert.deepEqual(killed, [99999, "SIGTERM"]);
|
|
141
|
+
assert.equal(
|
|
142
|
+
fs.existsSync(path.join(root, ".context", "audit")),
|
|
143
|
+
false,
|
|
144
|
+
);
|
|
100
145
|
} finally {
|
|
101
146
|
fs.rmSync(root, { recursive: true, force: true });
|
|
102
147
|
}
|
|
@@ -127,9 +172,15 @@ test("runScanOnce: emits onFinding callback per detection", async () => {
|
|
|
127
172
|
test("runScanOnce: skips Tier 1 CLIs that already have managed installs", async () => {
|
|
128
173
|
const { root } = makeWorkspace("enforced");
|
|
129
174
|
const managedClaudePath = path.join(root, "managed-settings.json");
|
|
130
|
-
fs.writeFileSync(
|
|
175
|
+
fs.writeFileSync(
|
|
176
|
+
managedClaudePath,
|
|
177
|
+
JSON.stringify({ allowManagedHooksOnly: true }) + "\n",
|
|
178
|
+
);
|
|
131
179
|
const managedCodexPath = path.join(root, "requirements.toml");
|
|
132
|
-
fs.writeFileSync(
|
|
180
|
+
fs.writeFileSync(
|
|
181
|
+
managedCodexPath,
|
|
182
|
+
"# Cortex govern — codex requirements (test).\n",
|
|
183
|
+
);
|
|
133
184
|
fs.writeFileSync(
|
|
134
185
|
path.join(root, ".context", "govern.local.json"),
|
|
135
186
|
JSON.stringify({
|
|
@@ -160,16 +211,47 @@ test("runScanOnce: skips Tier 1 CLIs that already have managed installs", async
|
|
|
160
211
|
const findings = await runScanOnce({
|
|
161
212
|
cwd: root,
|
|
162
213
|
mode: "enforced",
|
|
214
|
+
managedPathOverrides: {
|
|
215
|
+
claude: managedClaudePath,
|
|
216
|
+
codex: managedCodexPath,
|
|
217
|
+
},
|
|
163
218
|
detectorOptions: { processes: fakeProcs, hostId: "test-host" },
|
|
164
219
|
});
|
|
165
220
|
assert.equal(findings.length, 1);
|
|
166
221
|
assert.equal(findings[0].cli, "copilot");
|
|
167
222
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
223
|
+
assert.equal(fs.existsSync(path.join(root, ".context", "audit")), false);
|
|
224
|
+
} finally {
|
|
225
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("runScanOnce: forged persisted paths cannot suppress Tier 1 findings", async () => {
|
|
230
|
+
const { root } = makeWorkspace("advisory", {
|
|
231
|
+
claude: {
|
|
232
|
+
mode: "advisory",
|
|
233
|
+
path: "/bin/sh",
|
|
234
|
+
version: "forged",
|
|
235
|
+
frameworks: [],
|
|
236
|
+
installed_at: "now",
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
try {
|
|
240
|
+
const findings = await runScanOnce({
|
|
241
|
+
cwd: root,
|
|
242
|
+
mode: "advisory",
|
|
243
|
+
detectorOptions: {
|
|
244
|
+
processes: [{
|
|
245
|
+
pid: 7654,
|
|
246
|
+
ppid: 1,
|
|
247
|
+
user: os.userInfo().username,
|
|
248
|
+
comm: "claude",
|
|
249
|
+
args: "claude --prompt hi",
|
|
250
|
+
}],
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
assert.equal(findings.length, 1);
|
|
254
|
+
assert.equal(findings[0].cli, "claude");
|
|
173
255
|
} finally {
|
|
174
256
|
fs.rmSync(root, { recursive: true, force: true });
|
|
175
257
|
}
|
|
@@ -5,6 +5,10 @@ import os from "node:os";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
|
|
7
7
|
import { runStageCommand } from "../dist/cli/stage.js";
|
|
8
|
+
import { enterpriseCredentialId } from "../dist/core/license.js";
|
|
9
|
+
|
|
10
|
+
const ENTERPRISE_ENDPOINT = "https://licenses.example.com";
|
|
11
|
+
const ENTERPRISE_KEY = "ent_workflow_cli_12345678";
|
|
8
12
|
|
|
9
13
|
const TINY_WORKFLOW = {
|
|
10
14
|
id: "tiny",
|
|
@@ -41,7 +45,12 @@ function makeWorkspace() {
|
|
|
41
45
|
fs.mkdirSync(path.join(dir, ".context"), { recursive: true });
|
|
42
46
|
fs.writeFileSync(
|
|
43
47
|
path.join(dir, ".context", "enterprise.yml"),
|
|
44
|
-
|
|
48
|
+
[
|
|
49
|
+
"enterprise:",
|
|
50
|
+
` api_key: ${ENTERPRISE_KEY}`,
|
|
51
|
+
` endpoint: ${ENTERPRISE_ENDPOINT}`,
|
|
52
|
+
"",
|
|
53
|
+
].join("\n"),
|
|
45
54
|
"utf8",
|
|
46
55
|
);
|
|
47
56
|
return dir;
|
|
@@ -53,6 +62,10 @@ function writeSyncedWorkflow(homeDir, workflowId, definition) {
|
|
|
53
62
|
fs.writeFileSync(
|
|
54
63
|
path.join(cortexDir, "workflows.local.json"),
|
|
55
64
|
JSON.stringify({
|
|
65
|
+
credential_id: enterpriseCredentialId(
|
|
66
|
+
ENTERPRISE_ENDPOINT,
|
|
67
|
+
ENTERPRISE_KEY,
|
|
68
|
+
),
|
|
56
69
|
workflows: {
|
|
57
70
|
[workflowId]: {
|
|
58
71
|
workflow_id: workflowId,
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from "../dist/core/workflow/synced-capability-registry.js";
|
|
11
11
|
import { evaluateToolCall } from "../dist/core/workflow/enforcement.js";
|
|
12
12
|
import { createRun } from "../dist/core/workflow/run-lifecycle.js";
|
|
13
|
+
import { enterpriseCredentialId } from "../dist/core/license.js";
|
|
13
14
|
|
|
14
15
|
function makeWorkspace() {
|
|
15
16
|
return fs.mkdtempSync(path.join(os.tmpdir(), "cortex-synced-caps-"));
|
|
@@ -49,6 +50,23 @@ function writeCache(dir, payload) {
|
|
|
49
50
|
);
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
function configureEnterprise(cwd) {
|
|
54
|
+
const endpoint = "https://licenses.example.com";
|
|
55
|
+
const apiKey = "ent_capability_12345678";
|
|
56
|
+
fs.mkdirSync(path.join(cwd, ".context"), { recursive: true });
|
|
57
|
+
fs.writeFileSync(
|
|
58
|
+
path.join(cwd, ".context", "enterprise.yml"),
|
|
59
|
+
[
|
|
60
|
+
"enterprise:",
|
|
61
|
+
` api_key: ${apiKey}`,
|
|
62
|
+
` endpoint: ${endpoint}`,
|
|
63
|
+
"",
|
|
64
|
+
].join("\n"),
|
|
65
|
+
"utf8",
|
|
66
|
+
);
|
|
67
|
+
return enterpriseCredentialId(endpoint, apiKey);
|
|
68
|
+
}
|
|
69
|
+
|
|
52
70
|
test("syncedCapabilitiesCachePath: defaults to ~/.cortex/capabilities.local.json", () => {
|
|
53
71
|
const expected = path.join(os.homedir(), ".cortex", "capabilities.local.json");
|
|
54
72
|
assert.equal(syncedCapabilitiesCachePath(), expected);
|
|
@@ -107,16 +125,33 @@ test("loadSyncedCapabilities: returns valid capability definitions", () => {
|
|
|
107
125
|
assert.deepEqual(loaded["frontend-builder"].write_globs, ["src/components/**"]);
|
|
108
126
|
});
|
|
109
127
|
|
|
128
|
+
test("loadSyncedCapabilities: rejects a cache bound to another Enterprise identity", () => {
|
|
129
|
+
const dir = makeWorkspace();
|
|
130
|
+
writeCache(dir, {
|
|
131
|
+
credential_id: "identity-a",
|
|
132
|
+
capabilities: {
|
|
133
|
+
"frontend-builder": {
|
|
134
|
+
capability_name: "frontend-builder",
|
|
135
|
+
updated_at: "2026-05-07T12:00:00.000Z",
|
|
136
|
+
definition: FRONTEND_BUILDER,
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
assert.deepEqual(loadSyncedCapabilities(dir, "identity-b"), {});
|
|
141
|
+
});
|
|
142
|
+
|
|
110
143
|
test("evaluateToolCall integration: synced capability is consulted via merged registry", () => {
|
|
111
144
|
const cwd = makeWorkspace();
|
|
112
145
|
// Sandbox the home-dir-based loader.
|
|
113
146
|
const fakeHome = makeWorkspace();
|
|
114
147
|
process.env.HOME = fakeHome;
|
|
115
148
|
try {
|
|
149
|
+
const credentialId = configureEnterprise(cwd);
|
|
116
150
|
fs.mkdirSync(path.join(fakeHome, ".cortex"), { recursive: true });
|
|
117
151
|
fs.writeFileSync(
|
|
118
152
|
path.join(fakeHome, ".cortex", "capabilities.local.json"),
|
|
119
153
|
JSON.stringify({
|
|
154
|
+
credential_id: credentialId,
|
|
120
155
|
capabilities: {
|
|
121
156
|
"frontend-builder": {
|
|
122
157
|
capability_name: "frontend-builder",
|
|
@@ -163,6 +198,7 @@ test("evaluateToolCall integration: synced capability with same name as bundled
|
|
|
163
198
|
const fakeHome = makeWorkspace();
|
|
164
199
|
process.env.HOME = fakeHome;
|
|
165
200
|
try {
|
|
201
|
+
const credentialId = configureEnterprise(cwd);
|
|
166
202
|
// Override the bundled "builder" capability with a much stricter version
|
|
167
203
|
// — only test files writable.
|
|
168
204
|
const stricterBuilder = {
|
|
@@ -176,6 +212,7 @@ test("evaluateToolCall integration: synced capability with same name as bundled
|
|
|
176
212
|
fs.writeFileSync(
|
|
177
213
|
path.join(fakeHome, ".cortex", "capabilities.local.json"),
|
|
178
214
|
JSON.stringify({
|
|
215
|
+
credential_id: credentialId,
|
|
179
216
|
capabilities: {
|
|
180
217
|
builder: {
|
|
181
218
|
capability_name: "builder",
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { resolveWorkflowDefinition } from "../dist/core/workflow/resolution.js";
|
|
12
12
|
import { runWorkflowStart } from "../dist/core/workflow/mcp-tools.js";
|
|
13
13
|
import { SECURE_BUILD_WORKFLOW } from "../dist/core/workflow/default-workflows.js";
|
|
14
|
+
import { enterpriseCredentialId } from "../dist/core/license.js";
|
|
14
15
|
|
|
15
16
|
function makeWorkspace() {
|
|
16
17
|
return fs.mkdtempSync(path.join(os.tmpdir(), "cortex-synced-registry-"));
|
|
@@ -49,6 +50,23 @@ function writeCache(dir, payload) {
|
|
|
49
50
|
);
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
function configureEnterprise(cwd) {
|
|
54
|
+
const endpoint = "https://licenses.example.com";
|
|
55
|
+
const apiKey = "ent_workflow_12345678";
|
|
56
|
+
fs.mkdirSync(path.join(cwd, ".context"), { recursive: true });
|
|
57
|
+
fs.writeFileSync(
|
|
58
|
+
path.join(cwd, ".context", "enterprise.yml"),
|
|
59
|
+
[
|
|
60
|
+
"enterprise:",
|
|
61
|
+
` api_key: ${apiKey}`,
|
|
62
|
+
` endpoint: ${endpoint}`,
|
|
63
|
+
"",
|
|
64
|
+
].join("\n"),
|
|
65
|
+
"utf8",
|
|
66
|
+
);
|
|
67
|
+
return enterpriseCredentialId(endpoint, apiKey);
|
|
68
|
+
}
|
|
69
|
+
|
|
52
70
|
test("syncedWorkflowsCachePath: defaults to ~/.cortex/workflows.local.json", () => {
|
|
53
71
|
const expected = path.join(os.homedir(), ".cortex", "workflows.local.json");
|
|
54
72
|
assert.equal(syncedWorkflowsCachePath(), expected);
|
|
@@ -110,6 +128,22 @@ test("loadSyncedWorkflows: returns valid workflow definitions keyed by workflow_
|
|
|
110
128
|
assert.equal(loaded.tiny.stages.length, 2);
|
|
111
129
|
});
|
|
112
130
|
|
|
131
|
+
test("loadSyncedWorkflows: rejects a cache bound to another Enterprise identity", () => {
|
|
132
|
+
const dir = makeWorkspace();
|
|
133
|
+
writeCache(dir, {
|
|
134
|
+
credential_id: "identity-a",
|
|
135
|
+
workflows: {
|
|
136
|
+
tiny: {
|
|
137
|
+
workflow_id: "tiny",
|
|
138
|
+
version: 1,
|
|
139
|
+
updated_at: "2026-05-06T12:00:00.000Z",
|
|
140
|
+
definition: TINY_WORKFLOW,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
assert.deepEqual(loadSyncedWorkflows(dir, "identity-b"), {});
|
|
145
|
+
});
|
|
146
|
+
|
|
113
147
|
test("resolveWorkflow integration: synced workflow takes precedence over bundled default", () => {
|
|
114
148
|
// We can't easily intercept loadSyncedWorkflows() from inside
|
|
115
149
|
// mcp-tools.ts (it reads from a fixed home-dir path). Instead, exercise
|
|
@@ -148,10 +182,12 @@ test("resolveWorkflow integration: synced cache adds new workflow_ids beyond def
|
|
|
148
182
|
const fakeHome = makeWorkspace();
|
|
149
183
|
process.env.HOME = fakeHome;
|
|
150
184
|
try {
|
|
185
|
+
const credentialId = configureEnterprise(cwd);
|
|
151
186
|
fs.mkdirSync(path.join(fakeHome, ".cortex"), { recursive: true });
|
|
152
187
|
fs.writeFileSync(
|
|
153
188
|
path.join(fakeHome, ".cortex", "workflows.local.json"),
|
|
154
189
|
JSON.stringify({
|
|
190
|
+
credential_id: credentialId,
|
|
155
191
|
workflows: {
|
|
156
192
|
tiny: {
|
|
157
193
|
workflow_id: "tiny",
|
|
@@ -215,7 +251,10 @@ test("resolveWorkflowDefinition: unknown workflow errors list bundled and synced
|
|
|
215
251
|
const fakeHome = makeWorkspace();
|
|
216
252
|
process.env.HOME = fakeHome;
|
|
217
253
|
try {
|
|
254
|
+
const cwd = makeWorkspace();
|
|
255
|
+
const credentialId = configureEnterprise(cwd);
|
|
218
256
|
writeCache(path.join(fakeHome, ".cortex"), {
|
|
257
|
+
credential_id: credentialId,
|
|
219
258
|
workflows: {
|
|
220
259
|
tiny: {
|
|
221
260
|
workflow_id: "tiny",
|
|
@@ -226,7 +265,7 @@ test("resolveWorkflowDefinition: unknown workflow errors list bundled and synced
|
|
|
226
265
|
},
|
|
227
266
|
});
|
|
228
267
|
assert.throws(
|
|
229
|
-
() => resolveWorkflowDefinition("missing-workflow"),
|
|
268
|
+
() => resolveWorkflowDefinition("missing-workflow", { cwd }),
|
|
230
269
|
/Available bundled: secure-build\. Available synced: tiny\./,
|
|
231
270
|
);
|
|
232
271
|
} finally {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { execSync } from "node:child_process";
|
|
5
|
+
import { execFileSync, execSync } from "node:child_process";
|
|
6
6
|
|
|
7
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
8
|
const __dirname = path.dirname(__filename);
|
|
@@ -286,12 +286,11 @@ function getLocalCliVersion() {
|
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
try {
|
|
289
|
-
const output =
|
|
289
|
+
const output = execFileSync("cortex", ["--version"], {
|
|
290
290
|
cwd: REPO_ROOT,
|
|
291
291
|
stdio: ["ignore", "pipe", "ignore"],
|
|
292
292
|
encoding: "utf8",
|
|
293
293
|
timeout: 1500,
|
|
294
|
-
shell: true,
|
|
295
294
|
}).trim();
|
|
296
295
|
if (parseVersion(output)) {
|
|
297
296
|
return output;
|
|
@@ -331,13 +330,12 @@ function getVersionStatus() {
|
|
|
331
330
|
} else {
|
|
332
331
|
try {
|
|
333
332
|
const npmCache = path.join(CACHE_DIR, "npm-cache");
|
|
334
|
-
const latestRaw =
|
|
333
|
+
const latestRaw = execFileSync("npm", ["view", "github:DanielBlomma/cortex", "version", "--json"], {
|
|
335
334
|
cwd: REPO_ROOT,
|
|
336
335
|
stdio: ["ignore", "pipe", "pipe"],
|
|
337
336
|
encoding: "utf8",
|
|
338
337
|
timeout: VERSION_LOOKUP_TIMEOUT_MS,
|
|
339
338
|
env: { ...process.env, NPM_CONFIG_CACHE: npmCache },
|
|
340
|
-
shell: true,
|
|
341
339
|
}).trim();
|
|
342
340
|
const parsedLatest = JSON.parse(latestRaw);
|
|
343
341
|
const latest = Array.isArray(parsedLatest)
|
|
@@ -352,20 +350,30 @@ function getVersionStatus() {
|
|
|
352
350
|
latest,
|
|
353
351
|
message: "unsupported latest version format",
|
|
354
352
|
};
|
|
355
|
-
} else if (compareVersions(latestParsed, localParsed) > 0) {
|
|
356
|
-
value = {
|
|
357
|
-
state: "update-available",
|
|
358
|
-
local,
|
|
359
|
-
latest,
|
|
360
|
-
message: null,
|
|
361
|
-
};
|
|
362
353
|
} else {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
354
|
+
const comparison = compareVersions(latestParsed, localParsed);
|
|
355
|
+
if (comparison > 0) {
|
|
356
|
+
value = {
|
|
357
|
+
state: "update-available",
|
|
358
|
+
local,
|
|
359
|
+
latest,
|
|
360
|
+
message: null,
|
|
361
|
+
};
|
|
362
|
+
} else if (comparison < 0) {
|
|
363
|
+
value = {
|
|
364
|
+
state: "local-newer",
|
|
365
|
+
local,
|
|
366
|
+
latest,
|
|
367
|
+
message: "local build is newer than published version",
|
|
368
|
+
};
|
|
369
|
+
} else {
|
|
370
|
+
value = {
|
|
371
|
+
state: "current",
|
|
372
|
+
local,
|
|
373
|
+
latest,
|
|
374
|
+
message: null,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
369
377
|
}
|
|
370
378
|
} catch (error) {
|
|
371
379
|
value = {
|
|
@@ -690,6 +698,9 @@ function render(data, isTTY) {
|
|
|
690
698
|
if (data.version.state === "update-available") {
|
|
691
699
|
lines.push(sideBorder(
|
|
692
700
|
`Version: ${bold(col("UPDATE AVAILABLE", C.red))} ${col(`${data.version.local} -> ${data.version.latest}`, C.red)}`, w));
|
|
701
|
+
} else if (data.version.state === "local-newer") {
|
|
702
|
+
lines.push(sideBorder(
|
|
703
|
+
`Version: ${col(data.version.local, C.yellow)} ${dim(`Published: ${data.version.latest} local newer`)}`, w));
|
|
693
704
|
} else if (data.version.state === "current") {
|
|
694
705
|
lines.push(sideBorder(
|
|
695
706
|
`Version: ${col(data.version.local, C.green)} ${dim(`Latest: ${data.version.latest}`)}`, w));
|
|
@@ -180,15 +180,33 @@ fi
|
|
|
180
180
|
|
|
181
181
|
# Quick runtime import check
|
|
182
182
|
if [[ -f "$MCP_DIR/dist/server.js" ]] && [[ -d "$MCP_DIR/node_modules" ]]; then
|
|
183
|
-
MCP_CHECK=$(cd "$REPO_ROOT" &&
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
183
|
+
MCP_CHECK=$(cd "$REPO_ROOT" && node -e '
|
|
184
|
+
const { spawnSync } = require("node:child_process");
|
|
185
|
+
const timeoutMs = Number(process.env.CORTEX_DOCTOR_GRAPH_TIMEOUT_MS || 30000);
|
|
186
|
+
const probe = [
|
|
187
|
+
"const start = Date.now();",
|
|
188
|
+
"try {",
|
|
189
|
+
" require(\"./.context/mcp/dist/graph.js\");",
|
|
190
|
+
" console.log(\"ok \" + (Date.now() - start));",
|
|
191
|
+
"} catch (e) {",
|
|
192
|
+
" console.log(\"fail \" + (e && e.message ? e.message : String(e)));",
|
|
193
|
+
" process.exitCode = 1;",
|
|
194
|
+
"}"
|
|
195
|
+
].join("\n");
|
|
196
|
+
const result = spawnSync(process.execPath, ["-e", probe], {
|
|
197
|
+
encoding: "utf8",
|
|
198
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
199
|
+
timeout: timeoutMs
|
|
200
|
+
});
|
|
201
|
+
if (result.error && result.error.code === "ETIMEDOUT") {
|
|
202
|
+
console.log("fail timeout after " + timeoutMs + "ms");
|
|
203
|
+
} else if (result.error) {
|
|
204
|
+
console.log("fail " + result.error.message);
|
|
205
|
+
} else {
|
|
206
|
+
const lines = String(result.stdout || "").trim().split(/\r?\n/).filter(Boolean);
|
|
207
|
+
console.log(lines[lines.length - 1] || ("fail exit " + (result.status ?? "unknown")));
|
|
190
208
|
}
|
|
191
|
-
' 2>/dev/null || echo "fail
|
|
209
|
+
' 2>/dev/null || echo "fail graph check crashed")
|
|
192
210
|
if [[ "$MCP_CHECK" == ok* ]]; then
|
|
193
211
|
MS="${MCP_CHECK#ok }"
|
|
194
212
|
pass "Graph module loads (${MS}ms)"
|
|
@@ -457,11 +457,11 @@ function parseRules(rulesText) {
|
|
|
457
457
|
function walkDirectory(directoryPath, files) {
|
|
458
458
|
const entries = fs.readdirSync(directoryPath, { withFileTypes: true });
|
|
459
459
|
for (const entry of entries) {
|
|
460
|
-
|
|
460
|
+
const absolutePath = path.join(directoryPath, entry.name);
|
|
461
|
+
if (entry.isDirectory() && shouldSkipDirectory(absolutePath, entry.name)) {
|
|
461
462
|
continue;
|
|
462
463
|
}
|
|
463
464
|
|
|
464
|
-
const absolutePath = path.join(directoryPath, entry.name);
|
|
465
465
|
if (entry.isDirectory()) {
|
|
466
466
|
walkDirectory(absolutePath, files);
|
|
467
467
|
continue;
|
|
@@ -473,10 +473,40 @@ function walkDirectory(directoryPath, files) {
|
|
|
473
473
|
}
|
|
474
474
|
}
|
|
475
475
|
|
|
476
|
+
function shouldSkipDirectory(absolutePath, entryName) {
|
|
477
|
+
if (entryName === "bin" && path.resolve(path.dirname(absolutePath)) === REPO_ROOT) {
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
return SKIP_DIRECTORIES.has(entryName);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function normalizeSourcePrefix(sourcePath) {
|
|
484
|
+
const source = toPosixPath(sourcePath).replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
485
|
+
return source === "." ? "" : source;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function normalizeRelativePath(relPath) {
|
|
489
|
+
return toPosixPath(relPath).replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function hasSkippedDirectorySegment(relPath) {
|
|
493
|
+
const parts = normalizeRelativePath(relPath).split("/").filter(Boolean);
|
|
494
|
+
return parts.some((part, index) => {
|
|
495
|
+
if (part === "bin" && index === 0) {
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
return SKIP_DIRECTORIES.has(part);
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
|
|
476
502
|
function hasSourcePrefix(relPath, sourcePaths) {
|
|
503
|
+
const normalizedRelPath = normalizeRelativePath(relPath);
|
|
504
|
+
if (!normalizedRelPath || hasSkippedDirectorySegment(normalizedRelPath)) {
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
477
507
|
return sourcePaths.some((sourcePath) => {
|
|
478
|
-
const source =
|
|
479
|
-
return
|
|
508
|
+
const source = normalizeSourcePrefix(sourcePath);
|
|
509
|
+
return source === "" || normalizedRelPath === source || normalizedRelPath.startsWith(`${source}/`);
|
|
480
510
|
});
|
|
481
511
|
}
|
|
482
512
|
|
|
@@ -627,6 +657,9 @@ function collectCandidateFiles(sourcePaths, mode) {
|
|
|
627
657
|
}
|
|
628
658
|
|
|
629
659
|
for (const sourcePath of sourcePaths) {
|
|
660
|
+
if (hasSkippedDirectorySegment(sourcePath)) {
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
630
663
|
const absoluteSourcePath = path.resolve(REPO_ROOT, sourcePath);
|
|
631
664
|
if (!fs.existsSync(absoluteSourcePath)) {
|
|
632
665
|
continue;
|
|
@@ -198,11 +198,12 @@ try {
|
|
|
198
198
|
|
|
199
199
|
node -e '
|
|
200
200
|
const path = require("node:path");
|
|
201
|
-
const { execSync } = require("node:child_process");
|
|
201
|
+
const { execFileSync, execSync } = require("node:child_process");
|
|
202
202
|
|
|
203
203
|
const repoRoot = process.argv[1];
|
|
204
204
|
const cacheDir = process.argv[2];
|
|
205
205
|
const localVersionEnv = process.argv[3] || "";
|
|
206
|
+
const VERSION_LOOKUP_TIMEOUT_MS = Number(process.env.CORTEX_VERSION_LOOKUP_TIMEOUT_MS || 8000);
|
|
206
207
|
|
|
207
208
|
function parseVersion(value) {
|
|
208
209
|
const match = String(value || "").trim().match(/^v?(\d+)\.(\d+)\.(\d+)$/);
|
|
@@ -225,7 +226,7 @@ function getLocalVersion() {
|
|
|
225
226
|
}
|
|
226
227
|
|
|
227
228
|
try {
|
|
228
|
-
const output =
|
|
229
|
+
const output = execFileSync("cortex", ["--version"], {
|
|
229
230
|
cwd: repoRoot,
|
|
230
231
|
stdio: ["ignore", "pipe", "ignore"],
|
|
231
232
|
encoding: "utf8",
|
|
@@ -282,11 +283,11 @@ try {
|
|
|
282
283
|
const npmCache = path.join(cacheDir, "npm-cache");
|
|
283
284
|
let latestRaw = "";
|
|
284
285
|
try {
|
|
285
|
-
latestRaw =
|
|
286
|
+
latestRaw = execFileSync("npm", ["view", "github:DanielBlomma/cortex", "version", "--json"], {
|
|
286
287
|
cwd: repoRoot,
|
|
287
288
|
stdio: ["ignore", "pipe", "pipe"],
|
|
288
289
|
encoding: "utf8",
|
|
289
|
-
timeout:
|
|
290
|
+
timeout: VERSION_LOOKUP_TIMEOUT_MS,
|
|
290
291
|
env: { ...process.env, NPM_CONFIG_CACHE: npmCache }
|
|
291
292
|
}).trim();
|
|
292
293
|
} catch (error) {
|
|
@@ -317,13 +318,19 @@ try {
|
|
|
317
318
|
process.exit(0);
|
|
318
319
|
}
|
|
319
320
|
|
|
320
|
-
const
|
|
321
|
+
const comparison = compareVersions(latestParsed, localParsed);
|
|
321
322
|
console.log(`[status] cortex_latest_version=${latestVersion}`);
|
|
322
323
|
|
|
323
|
-
if (
|
|
324
|
+
if (comparison > 0) {
|
|
325
|
+
console.log("[status] cortex_version_state=update-available");
|
|
324
326
|
console.log("[status] cortex_update_available=yes");
|
|
325
327
|
console.log("[status] run: npm i -g github:DanielBlomma/cortex");
|
|
328
|
+
} else if (comparison < 0) {
|
|
329
|
+
console.log("[status] cortex_version_state=local-newer");
|
|
330
|
+
console.log("[status] cortex_update_available=no");
|
|
331
|
+
console.log("[status] note: local Cortex is newer than the published version");
|
|
326
332
|
} else {
|
|
333
|
+
console.log("[status] cortex_version_state=current");
|
|
327
334
|
console.log("[status] cortex_update_available=no");
|
|
328
335
|
}
|
|
329
336
|
} catch (error) {
|