@compr/opscontext-mcp 2.3.1 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/dist/activation.d.ts +19 -1
- package/dist/activation.js +49 -2
- package/dist/agents.js +73 -4
- package/dist/cli.js +11 -11
- package/dist/rubric.js +15 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -495,10 +495,23 @@ Everything happens locally — search, scoring, learnings, sessions, embeddings.
|
|
|
495
495
|
|---|---|---|
|
|
496
496
|
| License key (`CE-XXXX-...`) | Activation + daily heartbeat | Validate subscription |
|
|
497
497
|
| Machine ID (SHA-256 hash) | Activation + daily heartbeat | Enforce machine limit |
|
|
498
|
+
| Email | Activation only | Tie the licence to an account |
|
|
499
|
+
| Package version | Activation only | Serve a compatible module bundle |
|
|
498
500
|
| Platform/arch (e.g., `darwin/arm64`) | Activation only | Compatibility check |
|
|
501
|
+
| Delta bundle version | Daily heartbeat | Detect an out-of-date module cache |
|
|
502
|
+
|
|
503
|
+
That is the complete list. The activation request sends exactly six fields and the heartbeat exactly three — enforced by a lock comment in `src/activation.ts` that forbids adding a seventh field reflecting usage.
|
|
499
504
|
|
|
500
505
|
**The server never receives:** project names, file contents, learnings, sessions, git history, dependencies, code, .env variables, or anything about your actual work.
|
|
501
506
|
|
|
507
|
+
**These are the only two network calls the tool makes.** `activate` and `heartbeat`, both in `src/activation.ts`. Nothing else in the codebase opens a connection — verify it yourself with `grep -rn "fetch(" src/`.
|
|
508
|
+
|
|
509
|
+
### What's obfuscated, and what isn't
|
|
510
|
+
|
|
511
|
+
One file in the published package is deliberately unreadable: `dist/rubric.js`, which holds the scoring thresholds (what earns which points). Those values are commercial IP under [BSL-1.1](LICENSE), and knowing them exactly makes an AI-readiness score easy to game by padding files to hit a number rather than doing the work.
|
|
512
|
+
|
|
513
|
+
**What that hides: values. What it does not hide: behaviour.** No code path, network call, file access, or data flow is concealed anywhere in this package. The scoring logic itself, every collector, the search ranker, and both network calls above ship as readable JavaScript — and the full source is public at [FASTPROD/ContextEngine](https://github.com/FASTPROD/ContextEngine). If a privacy claim on this page were false, the code that broke it would be right there to find.
|
|
514
|
+
|
|
502
515
|
### Why this matters
|
|
503
516
|
|
|
504
517
|
Most AI coding tools (Copilot, Cursor, Codeium) send your code to external servers for processing. ContextEngine takes the opposite approach — **embeddings run locally on CPU**, search runs locally, and all persistent state stays in `~/.contextengine/` on your disk. The only network call is a lightweight license check for PRO users.
|
package/dist/activation.d.ts
CHANGED
|
@@ -21,9 +21,27 @@ export declare function activate(licenseKey: string, email: string): Promise<{
|
|
|
21
21
|
* Check if delta modules are installed and valid.
|
|
22
22
|
*/
|
|
23
23
|
export declare function isDeltaInstalled(): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Version of the delta bundle currently cached on disk, or null if none/unreadable.
|
|
26
|
+
* Exported so callers can report the mismatch rather than guess at it.
|
|
27
|
+
*/
|
|
28
|
+
export declare function installedDeltaVersion(): string | null;
|
|
24
29
|
/**
|
|
25
30
|
* Dynamically import a delta module.
|
|
26
|
-
* Returns null if not activated or
|
|
31
|
+
* Returns null if not activated, module missing, or the cached delta is stale.
|
|
32
|
+
*
|
|
33
|
+
* 🔒 LOCKED [DELTA-VERSION-PIN] — 2026-08-14
|
|
34
|
+
* ⛔ NEVER import a delta module without checking its manifest version against this package.
|
|
35
|
+
* WHY: the cache at ~/.contextengine/delta/ is written once at activation and never expires. On
|
|
36
|
+
* the author's own machine it held version 1.19.1 while the installed package was 2.3.1 —
|
|
37
|
+
* two months and three sessions of scorer fixes out of date. Because this function imported
|
|
38
|
+
* whatever .mjs happened to be on disk, wiring it up would have silently run the OLD scorer
|
|
39
|
+
* inside the NEW package: no error, no symptom, just quietly wrong scores. The canary cannot
|
|
40
|
+
* catch this — a stale delta carries its own stale canary and its own stale pins, so it
|
|
41
|
+
* passes against itself.
|
|
42
|
+
* FIX: refuse to load a delta whose version does not match the running package, and say so on
|
|
43
|
+
* stderr. A stale module is an unknown, not a usable one — [ABSENCE-IS-NOT-A-VERDICT]
|
|
44
|
+
* applied to code delivery rather than to a check result.
|
|
27
45
|
*/
|
|
28
46
|
export declare function loadDeltaModule(name: string): Promise<any | null>;
|
|
29
47
|
export declare function heartbeat(): Promise<boolean>;
|
package/dist/activation.js
CHANGED
|
@@ -29,8 +29,9 @@
|
|
|
29
29
|
* 4. Premium tools become available
|
|
30
30
|
*/
|
|
31
31
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, unlinkSync } from "fs";
|
|
32
|
-
import { join } from "path";
|
|
32
|
+
import { join, dirname } from "path";
|
|
33
33
|
import { homedir } from "os";
|
|
34
|
+
import { fileURLToPath } from "url";
|
|
34
35
|
import { createHash, createDecipheriv } from "crypto";
|
|
35
36
|
import { safeAppend } from "./audit.js";
|
|
36
37
|
import { verifyLicenseSignature } from "./license-sig.js";
|
|
@@ -38,6 +39,20 @@ import { verifyLicenseSignature } from "./license-sig.js";
|
|
|
38
39
|
// Constants
|
|
39
40
|
// ---------------------------------------------------------------------------
|
|
40
41
|
const DELTA_DIR = join(homedir(), ".contextengine", "delta");
|
|
42
|
+
/**
|
|
43
|
+
* Version of the running package. Read from package.json at module load, the same way
|
|
44
|
+
* agents.ts does it, so [DELTA-VERSION-PIN] compares against the real installed version
|
|
45
|
+
* rather than a constant someone forgets to bump.
|
|
46
|
+
*/
|
|
47
|
+
const PACKAGE_VERSION = (() => {
|
|
48
|
+
try {
|
|
49
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
50
|
+
return JSON.parse(readFileSync(join(here, "..", "package.json"), "utf-8")).version ?? "unknown";
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return "unknown";
|
|
54
|
+
}
|
|
55
|
+
})();
|
|
41
56
|
const LICENSE_FILE = join(homedir(), ".contextengine", "license.json");
|
|
42
57
|
const ACTIVATION_API_BASE = process.env.CONTEXTENGINE_API || "https://api.compr.ch/contextengine";
|
|
43
58
|
const ACTIVATION_API = `${ACTIVATION_API_BASE}/activate`;
|
|
@@ -252,13 +267,45 @@ export function isDeltaInstalled() {
|
|
|
252
267
|
return false;
|
|
253
268
|
}
|
|
254
269
|
}
|
|
270
|
+
/**
|
|
271
|
+
* Version of the delta bundle currently cached on disk, or null if none/unreadable.
|
|
272
|
+
* Exported so callers can report the mismatch rather than guess at it.
|
|
273
|
+
*/
|
|
274
|
+
export function installedDeltaVersion() {
|
|
275
|
+
try {
|
|
276
|
+
const manifest = JSON.parse(readFileSync(join(DELTA_DIR, "manifest.json"), "utf-8"));
|
|
277
|
+
return typeof manifest.version === "string" ? manifest.version : null;
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
255
283
|
/**
|
|
256
284
|
* Dynamically import a delta module.
|
|
257
|
-
* Returns null if not activated or
|
|
285
|
+
* Returns null if not activated, module missing, or the cached delta is stale.
|
|
286
|
+
*
|
|
287
|
+
* 🔒 LOCKED [DELTA-VERSION-PIN] — 2026-08-14
|
|
288
|
+
* ⛔ NEVER import a delta module without checking its manifest version against this package.
|
|
289
|
+
* WHY: the cache at ~/.contextengine/delta/ is written once at activation and never expires. On
|
|
290
|
+
* the author's own machine it held version 1.19.1 while the installed package was 2.3.1 —
|
|
291
|
+
* two months and three sessions of scorer fixes out of date. Because this function imported
|
|
292
|
+
* whatever .mjs happened to be on disk, wiring it up would have silently run the OLD scorer
|
|
293
|
+
* inside the NEW package: no error, no symptom, just quietly wrong scores. The canary cannot
|
|
294
|
+
* catch this — a stale delta carries its own stale canary and its own stale pins, so it
|
|
295
|
+
* passes against itself.
|
|
296
|
+
* FIX: refuse to load a delta whose version does not match the running package, and say so on
|
|
297
|
+
* stderr. A stale module is an unknown, not a usable one — [ABSENCE-IS-NOT-A-VERDICT]
|
|
298
|
+
* applied to code delivery rather than to a check result.
|
|
258
299
|
*/
|
|
259
300
|
export async function loadDeltaModule(name) {
|
|
260
301
|
if (!isDeltaInstalled())
|
|
261
302
|
return null;
|
|
303
|
+
const cached = installedDeltaVersion();
|
|
304
|
+
if (cached !== PACKAGE_VERSION) {
|
|
305
|
+
console.error(`[ContextEngine] ⚠ Delta module "${name}" is version ${cached ?? "unknown"} but this package is ` +
|
|
306
|
+
`${PACKAGE_VERSION} — refusing to load a stale module. Re-run \`contextengine activate\` to refresh.`);
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
262
309
|
const modulePath = join(DELTA_DIR, `${name}.mjs`);
|
|
263
310
|
if (!existsSync(modulePath))
|
|
264
311
|
return null;
|
package/dist/agents.js
CHANGED
|
@@ -86,6 +86,36 @@ const AGENT_DOC_TOPICS = [
|
|
|
86
86
|
function matchDocTopics(content, topics) {
|
|
87
87
|
return topics.filter(t => t.patterns.test(content)).map(t => t.label);
|
|
88
88
|
}
|
|
89
|
+
function detectLanguage(p) {
|
|
90
|
+
if (existsSync(join(p, "package.json")))
|
|
91
|
+
return "js";
|
|
92
|
+
const pythonMarkers = ["pyproject.toml", "setup.py", "setup.cfg", "requirements.txt", "Pipfile", "__manifest__.py"];
|
|
93
|
+
if (pythonMarkers.some(m => existsSync(join(p, m))))
|
|
94
|
+
return "python";
|
|
95
|
+
if (existsSync(join(p, "composer.json")))
|
|
96
|
+
return "php";
|
|
97
|
+
// Odoo addons and src-layout packages keep their markers one level down.
|
|
98
|
+
for (const sub of safeSubdirs(p)) {
|
|
99
|
+
if (pythonMarkers.some(m => existsSync(join(p, sub, m))))
|
|
100
|
+
return "python";
|
|
101
|
+
if (existsSync(join(p, sub, "package.json")))
|
|
102
|
+
return "js";
|
|
103
|
+
}
|
|
104
|
+
return "other";
|
|
105
|
+
}
|
|
106
|
+
/** Immediate subdirectories worth searching — skips vendored, hidden and build output. */
|
|
107
|
+
function safeSubdirs(p) {
|
|
108
|
+
const SKIP = new Set(["node_modules", "vendor", "dist", "build", ".git", "__pycache__", "venv", ".venv", "coverage", "_deprecated"]);
|
|
109
|
+
try {
|
|
110
|
+
return readdirSync(p, { withFileTypes: true })
|
|
111
|
+
.filter(d => d.isDirectory() && !d.name.startsWith(".") && !SKIP.has(d.name))
|
|
112
|
+
.map(d => d.name)
|
|
113
|
+
.slice(0, 24); // bounded — this runs for every project on every fleet scan
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
89
119
|
/** Symlink target for diagnostics, or "?" if unreadable. Never throws. */
|
|
90
120
|
function readlinkSafe(filePath) {
|
|
91
121
|
try {
|
|
@@ -1081,6 +1111,8 @@ export function runScoreCanary() {
|
|
|
1081
1111
|
export function scoreProject(dir) {
|
|
1082
1112
|
const checks = [];
|
|
1083
1113
|
const p = dir.path;
|
|
1114
|
+
// Language decides which tooling checks apply at all — see [SCORE-LANGUAGE-AWARE].
|
|
1115
|
+
const lang = detectLanguage(p);
|
|
1084
1116
|
// --- Documentation (30 points max) ---
|
|
1085
1117
|
// copilot-instructions.md (6 pts) — scored on CONTENT, not length.
|
|
1086
1118
|
// 🔒 LOCKED [SCORE-CONTENT-NOT-LENGTH] — 2026-08-14
|
|
@@ -1384,7 +1416,15 @@ export function scoreProject(dir) {
|
|
|
1384
1416
|
}
|
|
1385
1417
|
// --- Code Quality (20 points max) ---
|
|
1386
1418
|
// Tests directory (8 pts) — checks for real test files, detects symlinks
|
|
1387
|
-
|
|
1419
|
+
// Search the repo root first, then one level down — Odoo addons, src-layout packages and
|
|
1420
|
+
// single-package monorepos keep tests at `<module>/tests/`. Reporting "No test directory" for
|
|
1421
|
+
// a project with 15 passing tests is [ABSENCE-IS-NOT-A-VERDICT] applied to a path assumption;
|
|
1422
|
+
// it is the same defect as [DOC-PATH-DUAL], one directory deeper.
|
|
1423
|
+
const testDirNames = ["tests", "test", "__tests__", "spec", "src/__tests__"];
|
|
1424
|
+
const testDirs = [
|
|
1425
|
+
...testDirNames,
|
|
1426
|
+
...safeSubdirs(p).flatMap(sub => testDirNames.map(td => `${sub}/${td}`)),
|
|
1427
|
+
];
|
|
1388
1428
|
const foundTests = testDirs.filter(td => existsSync(join(p, td)));
|
|
1389
1429
|
if (foundTests.length > 0) {
|
|
1390
1430
|
const testDirPath = join(p, foundTests[0]);
|
|
@@ -1437,6 +1477,21 @@ export function scoreProject(dir) {
|
|
|
1437
1477
|
else if (existsSync(join(p, "jsconfig.json"))) {
|
|
1438
1478
|
checks.push({ name: "Type checking", category: "Code Quality", points: 2, maxPoints: 5, status: "partial", detail: "jsconfig.json only" });
|
|
1439
1479
|
}
|
|
1480
|
+
else if (lang === "python") {
|
|
1481
|
+
// See [SCORE-LANGUAGE-AWARE]. Python type checking is mypy/pyright, not tsconfig.
|
|
1482
|
+
const pyTypeMarkers = ["mypy.ini", ".mypy.ini", "pyrightconfig.json"];
|
|
1483
|
+
const foundPyType = pyTypeMarkers.filter(m => existsSync(join(p, m)));
|
|
1484
|
+
const pyproject = existsSync(join(p, "pyproject.toml")) ? readFileSync(join(p, "pyproject.toml"), "utf-8") : "";
|
|
1485
|
+
if (foundPyType.length > 0 || /\[tool\.(mypy|pyright)\]/.test(pyproject)) {
|
|
1486
|
+
checks.push({ name: "Type checking", category: "Code Quality", points: 5, maxPoints: 5, status: "pass", detail: `${foundPyType[0] ?? "pyproject.toml"} — static type checking configured` });
|
|
1487
|
+
}
|
|
1488
|
+
else {
|
|
1489
|
+
checks.push({ name: "Type checking", category: "Code Quality", points: 0, maxPoints: 5, status: "fail", detail: "Python project with no mypy/pyright config — add one for static type checking" });
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
else if (lang === "php" || lang === "other") {
|
|
1493
|
+
checks.push({ name: "Type checking", category: "Code Quality", points: 0, maxPoints: 5, status: "unknown", detail: `❔ No type-checking convention known for this project type (${lang}) — not assessed` });
|
|
1494
|
+
}
|
|
1440
1495
|
else {
|
|
1441
1496
|
checks.push({ name: "Type checking", category: "Code Quality", points: 0, maxPoints: 5, status: "fail", detail: "No tsconfig/jsconfig" });
|
|
1442
1497
|
}
|
|
@@ -1456,8 +1511,22 @@ export function scoreProject(dir) {
|
|
|
1456
1511
|
checks.push({ name: "Linting", category: "Code Quality", points: 4, maxPoints: 4, status: "pass", detail: foundLint.join(", ") });
|
|
1457
1512
|
}
|
|
1458
1513
|
}
|
|
1514
|
+
else if (lang === "python") {
|
|
1515
|
+
// See [SCORE-LANGUAGE-AWARE]. Python linting is ruff/flake8/pylint, not eslint.
|
|
1516
|
+
const pyLint = ["ruff.toml", ".ruff.toml", ".flake8", ".pylintrc", "tox.ini", "setup.cfg"].filter(l => existsSync(join(p, l)));
|
|
1517
|
+
const pyproject = existsSync(join(p, "pyproject.toml")) ? readFileSync(join(p, "pyproject.toml"), "utf-8") : "";
|
|
1518
|
+
if (pyLint.length > 0 || /\[tool\.(ruff|flake8|pylint|black)\]/.test(pyproject)) {
|
|
1519
|
+
checks.push({ name: "Linting", category: "Code Quality", points: 4, maxPoints: 4, status: "pass", detail: pyLint[0] ?? "pyproject.toml" });
|
|
1520
|
+
}
|
|
1521
|
+
else {
|
|
1522
|
+
checks.push({ name: "Linting", category: "Code Quality", points: 0, maxPoints: 4, status: "fail", detail: "Python project with no ruff/flake8/pylint config — add one" });
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
else if (lang === "other") {
|
|
1526
|
+
checks.push({ name: "Linting", category: "Code Quality", points: 0, maxPoints: 4, status: "unknown", detail: "❔ No linting convention known for this project type — not assessed" });
|
|
1527
|
+
}
|
|
1459
1528
|
else {
|
|
1460
|
-
checks.push({ name: "Linting", category: "Code Quality", points: 0, maxPoints: 4, status: "fail", detail: "No lint config" });
|
|
1529
|
+
checks.push({ name: "Linting", category: "Code Quality", points: 0, maxPoints: 4, status: "fail", detail: "No lint config (looked for eslint/prettier/phpcs at repo root)" });
|
|
1461
1530
|
}
|
|
1462
1531
|
// Package scripts / build commands (3 pts)
|
|
1463
1532
|
const pkgPath = join(p, "package.json");
|
|
@@ -1722,7 +1791,7 @@ export function generateProjectScoreMD(score) {
|
|
|
1722
1791
|
lines.push(`- ❔ **${u.name}**: ${u.detail}`);
|
|
1723
1792
|
}
|
|
1724
1793
|
}
|
|
1725
|
-
lines.push(`\n---\n*Generated by [ContextEngine](https://www.npmjs.com/package/@compr/
|
|
1794
|
+
lines.push(`\n---\n*Generated by [ContextEngine](https://www.npmjs.com/package/@compr/opscontext-mcp) on ${date}*\n`);
|
|
1726
1795
|
return lines.join("\n");
|
|
1727
1796
|
}
|
|
1728
1797
|
/**
|
|
@@ -1917,7 +1986,7 @@ export function generateScoreHTML(scores) {
|
|
|
1917
1986
|
${projectCards}
|
|
1918
1987
|
|
|
1919
1988
|
<div class="footer">
|
|
1920
|
-
<p>ContextEngine · <a href="https://www.npmjs.com/package/@compr/
|
|
1989
|
+
<p>ContextEngine · <a href="https://www.npmjs.com/package/@compr/opscontext-mcp" style="color:var(--accent)">npm</a></p>
|
|
1921
1990
|
<p style="margin-top:4px">Scoring: Documentation (30pts) · Infrastructure (30pts) · Code Quality (20pts) · Security (20pts)</p>
|
|
1922
1991
|
</div>
|
|
1923
1992
|
</body>
|
package/dist/cli.js
CHANGED
|
@@ -217,7 +217,7 @@ function generateMcpJson() {
|
|
|
217
217
|
contextengine: {
|
|
218
218
|
type: "stdio",
|
|
219
219
|
command: npxPath,
|
|
220
|
-
args: ["-y", "@compr/
|
|
220
|
+
args: ["-y", "@compr/opscontext-mcp"],
|
|
221
221
|
},
|
|
222
222
|
},
|
|
223
223
|
};
|
|
@@ -2123,17 +2123,17 @@ Flags:
|
|
|
2123
2123
|
--yes, -y Skip all interactive prompts (auto-accept defaults)
|
|
2124
2124
|
|
|
2125
2125
|
Examples:
|
|
2126
|
-
npx @compr/
|
|
2127
|
-
npx @compr/
|
|
2128
|
-
npx @compr/
|
|
2129
|
-
npx @compr/
|
|
2130
|
-
npx @compr/
|
|
2131
|
-
npx @compr/
|
|
2132
|
-
npx @compr/
|
|
2133
|
-
npx @compr/
|
|
2134
|
-
echo "value" | npx @compr/
|
|
2126
|
+
npx @compr/opscontext-mcp search "docker nginx"
|
|
2127
|
+
npx @compr/opscontext-mcp score ContextEngine
|
|
2128
|
+
npx @compr/opscontext-mcp score --html
|
|
2129
|
+
npx @compr/opscontext-mcp save-session my-project summary "Deployed v2, fixed auth"
|
|
2130
|
+
npx @compr/opscontext-mcp load-session my-project
|
|
2131
|
+
npx @compr/opscontext-mcp end-session
|
|
2132
|
+
npx @compr/opscontext-mcp import-learnings rules.md -c deployment
|
|
2133
|
+
npx @compr/opscontext-mcp init --yes
|
|
2134
|
+
echo "value" | npx @compr/opscontext-mcp save-session my-project notes --stdin
|
|
2135
2135
|
|
|
2136
|
-
npm: https://www.npmjs.com/package/@compr/
|
|
2136
|
+
npm: https://www.npmjs.com/package/@compr/opscontext-mcp
|
|
2137
2137
|
`);
|
|
2138
2138
|
}
|
|
2139
2139
|
else if (command === "search") {
|
package/dist/rubric.js
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
/*__RUBRIC_ENCODED__*/
|
|
2
|
+
/*
|
|
3
|
+
* Scoring thresholds — encoded deliberately.
|
|
4
|
+
*
|
|
5
|
+
* WHAT THIS IS: the point values and cut-offs behind the AI-readiness score. They are commercial
|
|
6
|
+
* IP under BSL-1.1, and publishing them exactly makes the score trivial to game by padding files
|
|
7
|
+
* to hit a number instead of doing the work.
|
|
8
|
+
*
|
|
9
|
+
* WHAT THIS IS NOT: hidden behaviour. This file contains numbers and nothing else — no network
|
|
10
|
+
* calls, no file access, no data collection. Every code path in this package ships readable,
|
|
11
|
+
* including the two (and only two) network calls the tool ever makes: licence activation and the
|
|
12
|
+
* daily heartbeat, both in activation.js. Full source: https://github.com/FASTPROD/ContextEngine
|
|
13
|
+
*
|
|
14
|
+
* Your project data never leaves your machine. See the Privacy section of the README, and verify
|
|
15
|
+
* it in the source rather than taking our word for it.
|
|
16
|
+
*/
|
|
2
17
|
const _k = "ce-rubric-v1";
|
|
3
18
|
const _d = (b) => {
|
|
4
19
|
const raw = Buffer.from(b, "base64").toString("binary");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@compr/opscontext-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "OpsContext for AI Agents — read-only fleet visibility (PM2/nginx/Docker/git/cron) + tamper-evident audit log + policy-as-code hooks. The ops + compliance layer Claude Code can't grow natively.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|