@ai-ecoverse/mcpcat 0.17.9 → 0.17.11
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 +1 -3
- package/dist/host/mcpcat.mjs +38 -27
- package/dist/kernel/mcpcat.mjs +38 -27
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -28,9 +28,7 @@ mcpcat runs natively on macOS, Linux and Windows (Node ≥ 24), and inside Slicc
|
|
|
28
28
|
|
|
29
29
|
Every change that alters what mcpcat ships is certified before it reaches `main`. A pull request that touches `src/`, the build, the dependencies or the release workflows gets mcpcat's cert-review on a pack of its head, and again in the merge queue on the exact tree that lands on `main`; a FAIL blocks the merge. Docs-, test- and CI-only changes skip it as "not behaviour". A draft pull request is not reviewed: its `cert-review` is red with `draft: not reviewed, mark it ready for review`, and marking it ready starts the review (converted_to_draft cancels a running one). Mark ready = reviewed, and merges on green; a pull request held for anything stays a draft. For a PR that needs the coordinator's security read, the order is: the coordinator reads the draft, it is marked ready, the cert runs, then the queue. Each merge to `main` is then released to npm as `latest` in one step by semantic-release, with provenance, so `npm install @ai-ecoverse/mcpcat` and the installer always get certified code. The verdict line is a comment on the pull request and the `cert-review` commit status. It reads `certified …`, `cert-ci: FAIL …` when the reviewer or a check in code found a defect (status `failure`), or `cert-ci: INCOMPLETE …` when the review couldn't decide: the review job didn't succeed or was skipped, no verdict could be read, or the artifact or the runner's own `gh` was missing (status `error`). A fork's change to shipped behaviour also reads INCOMPLETE, since it gets no review. INCOMPLETE never certifies; re-run the failed jobs. A run that ends without `record` posting its status (a crashed plan, record or verdict job) still posts one on the head: `error` with its INCOMPLETE line, or `failure` for a FAIL. So the required `cert-review` check never waits forever. The reviewer's verdict can add defects but never clear one found in code: `record` re-reads the suites' evidence (`stage.json`, `suites.json`), uploaded before the reviewer runs and bound by the artifact id and digest the review job output. A suite or security row that failed on the host or a pinned kernel, a changed test that fails on the head, an import in the kernel bundle or an unexplained rebuild difference is a FAIL. Missing suites, kernels or rows are INCOMPLETE. The NEGATIVE runs each changed test on the base's code: the subject's tests, helpers and fixtures with the base's `dist`, `scripts/` and `.github/workflows/`, all from the base sha, so a test of the cert tooling runs against main's tooling. The record names both shas. A changed test that also passes on the base is named in the line as `gaps: no-negative <file>`. Every artifact a later job reads (the tarball, the verdict, the suites' evidence) is downloaded by the artifact id its producer output, after `bind.mjs` checks the artifact's name, run and digest against that producer's outputs. Any other artifact from this attempt of the run under `mcpcat-tarball-`, `mcpcat-npm-tarball-` or `cert-review-` makes the line INCOMPLETE. An earlier attempt's leftovers, created before this attempt started, are never read.
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
Silence never passes: every gate in the workflows prints a verdict line, and its caller requires that line, not just an exit code. These are `all-green: PASS|FAIL …`, the cert job's `cert: kernel <v> — N kernel tests passed`, shipped.mjs's `behaviour:`/`not behaviour:`, release verify's `verified …`, and the installer tests' `All installer tests passed.`. A gate that exits 0 without its line fails.
|
|
31
|
+
Silence never passes: every gate in the workflows prints a verdict line, and its caller requires that line, not just an exit code. These are `all-green: PASS|FAIL …`, the cert job's `cert: kernel <v> — N kernel tests passed`, shipped.mjs's `behaviour:`/`not behaviour:`, and the installer tests' `All installer tests passed.`. A gate that exits 0 without its line fails.
|
|
34
32
|
|
|
35
33
|
## Quick start
|
|
36
34
|
|
package/dist/host/mcpcat.mjs
CHANGED
|
@@ -8421,10 +8421,11 @@ var KERNEL_REFUSAL = "nodeTransport refuses ";
|
|
|
8421
8421
|
function firstLine(text) {
|
|
8422
8422
|
return text.trim().split("\n")[0].trim().slice(0, 200);
|
|
8423
8423
|
}
|
|
8424
|
-
async function authReason(res) {
|
|
8424
|
+
async function authReason(res, redact2) {
|
|
8425
8425
|
const type = res.headers.get("content-type") ?? "";
|
|
8426
|
-
if (!type.toLowerCase().startsWith("text/plain")) return
|
|
8427
|
-
|
|
8426
|
+
if (!type.toLowerCase().startsWith("text/plain")) return {};
|
|
8427
|
+
const text = await res.text();
|
|
8428
|
+
return { raw: firstLine(text), reason: firstLine(redact2(text)) || void 0 };
|
|
8428
8429
|
}
|
|
8429
8430
|
function selectSseFrame(body, targetId) {
|
|
8430
8431
|
const text = new TextDecoder().decode(body);
|
|
@@ -8541,7 +8542,8 @@ async function createStatelessConnection(options) {
|
|
|
8541
8542
|
signal: externalSignal,
|
|
8542
8543
|
verbose,
|
|
8543
8544
|
authProvider,
|
|
8544
|
-
transportRefusals
|
|
8545
|
+
transportRefusals,
|
|
8546
|
+
redact: redact2 = (text) => text
|
|
8545
8547
|
} = options;
|
|
8546
8548
|
let nextId2 = 1;
|
|
8547
8549
|
let negotiatedVersion;
|
|
@@ -8560,9 +8562,9 @@ async function createStatelessConnection(options) {
|
|
|
8560
8562
|
}
|
|
8561
8563
|
async function handleErrorStatus(res, method) {
|
|
8562
8564
|
if (res.status === 401 || res.status === 403) {
|
|
8563
|
-
const reason = await authReason(res);
|
|
8564
|
-
if (transportRefusals && res.status === 403 && !res.headers.has("www-authenticate") &&
|
|
8565
|
-
throw new TransportRefusalError(403, reason);
|
|
8565
|
+
const { raw, reason } = await authReason(res, redact2);
|
|
8566
|
+
if (transportRefusals && res.status === 403 && !res.headers.has("www-authenticate") && raw?.startsWith(KERNEL_REFUSAL)) {
|
|
8567
|
+
throw new TransportRefusalError(403, reason ?? "");
|
|
8566
8568
|
}
|
|
8567
8569
|
if (authProvider) throw new AuthRetryableError(res, reason);
|
|
8568
8570
|
throw new McpAuthError(res.status, url, reason);
|
|
@@ -8575,7 +8577,7 @@ async function createStatelessConnection(options) {
|
|
|
8575
8577
|
} catch {
|
|
8576
8578
|
}
|
|
8577
8579
|
if (rpc) throw new StatelessRpcError(rpc.code, rpc.message, rpc.data);
|
|
8578
|
-
throw new StatelessHttpError(res.status, res.statusText, method, text.slice(0, 512));
|
|
8580
|
+
throw new StatelessHttpError(res.status, res.statusText, method, redact2(text).slice(0, 512));
|
|
8579
8581
|
}
|
|
8580
8582
|
async function doFetch(method, params, body) {
|
|
8581
8583
|
const token = await authProvider?.token();
|
|
@@ -9103,7 +9105,8 @@ async function connect(platform2, options) {
|
|
|
9103
9105
|
signal: options.signal,
|
|
9104
9106
|
verbose: options.verbose,
|
|
9105
9107
|
authProvider: options.authProvider,
|
|
9106
|
-
transportRefusals: platform2.kind === "kernel"
|
|
9108
|
+
transportRefusals: platform2.kind === "kernel",
|
|
9109
|
+
redact: options.redact
|
|
9107
9110
|
});
|
|
9108
9111
|
} catch (err) {
|
|
9109
9112
|
const isMethodNotFound = err instanceof StatelessRpcError && err.code === -32601 || err instanceof StatelessHttpError && err.status >= 400 && err.status < 500;
|
|
@@ -12209,7 +12212,7 @@ function createProxyHandler(options) {
|
|
|
12209
12212
|
let connecting = null;
|
|
12210
12213
|
const scrub = secretMasker(options.hiddenHeaders, "***");
|
|
12211
12214
|
async function doConnect() {
|
|
12212
|
-
const c = await connect(options.platform, options.connectOpts);
|
|
12215
|
+
const c = await connect(options.platform, { ...options.connectOpts, redact: scrub });
|
|
12213
12216
|
cachedInfo = await c.info();
|
|
12214
12217
|
return c;
|
|
12215
12218
|
}
|
|
@@ -13281,23 +13284,29 @@ function resolveProxyUrl(target, headers, version) {
|
|
|
13281
13284
|
const url = target.startsWith("http") ? target : `https://${target}`;
|
|
13282
13285
|
return { url, headers, version };
|
|
13283
13286
|
}
|
|
13287
|
+
function unsetIn(header, value, env) {
|
|
13288
|
+
const missing = missingPlaceholders(value, env);
|
|
13289
|
+
if (missing.length === 0) return void 0;
|
|
13290
|
+
return `unset variable ${missing.join(", ")} in header '${header}'`;
|
|
13291
|
+
}
|
|
13292
|
+
function hide(hidden, template, expanded, env) {
|
|
13293
|
+
if (template) hidden.add(template);
|
|
13294
|
+
if (expanded) hidden.add(expanded);
|
|
13295
|
+
for (const p of placeholderValues(template, env)) hidden.add(p);
|
|
13296
|
+
}
|
|
13284
13297
|
async function resolveProxyConfig(platform2, target, connectHeaders, hiddenValues, version) {
|
|
13285
13298
|
const config = await MergedConfigSource.load(platform2);
|
|
13286
13299
|
const entry = await config.get(target);
|
|
13287
13300
|
if (!entry) return `unknown target '${target}'`;
|
|
13288
13301
|
if ("url" in entry && entry.url) {
|
|
13289
|
-
const expanded =
|
|
13290
|
-
|
|
13291
|
-
|
|
13292
|
-
|
|
13293
|
-
|
|
13294
|
-
|
|
13295
|
-
if (entry.headers) {
|
|
13296
|
-
for (const v of Object.values(entry.headers)) {
|
|
13297
|
-
hiddenValues.add(v);
|
|
13298
|
-
for (const p of placeholderValues(v, platform2.env)) hiddenValues.add(p);
|
|
13299
|
-
}
|
|
13302
|
+
const expanded = {};
|
|
13303
|
+
for (const [k, v] of Object.entries(entry.headers ?? {})) {
|
|
13304
|
+
const missing = unsetIn(k, v, platform2.env);
|
|
13305
|
+
if (missing) return missing;
|
|
13306
|
+
expanded[k] = expandPlaceholders(v, platform2.env);
|
|
13307
|
+
hide(hiddenValues, v, expanded[k], platform2.env);
|
|
13300
13308
|
}
|
|
13309
|
+
const merged = { ...expanded, ...connectHeaders };
|
|
13301
13310
|
return { url: entry.url, headers: merged, version };
|
|
13302
13311
|
}
|
|
13303
13312
|
if ("command" in entry && entry.command) {
|
|
@@ -13316,12 +13325,12 @@ async function buildProxyConnectOpts(platform2, options, version) {
|
|
|
13316
13325
|
const connectHeaders = {};
|
|
13317
13326
|
const hiddenValues = /* @__PURE__ */ new Set();
|
|
13318
13327
|
for (const [k, v] of options.proxyHeaders) {
|
|
13319
|
-
const
|
|
13320
|
-
if (
|
|
13328
|
+
const missing = unsetIn(k, v, platform2.env);
|
|
13329
|
+
if (missing) return { error: missing };
|
|
13330
|
+
const expanded = expandPlaceholders(v, platform2.env);
|
|
13321
13331
|
const key = k.toLowerCase() === "authorization" ? "Authorization" : k;
|
|
13322
13332
|
connectHeaders[key] = expanded;
|
|
13323
|
-
hiddenValues.
|
|
13324
|
-
for (const p of placeholderValues(v, platform2.env)) hiddenValues.add(p);
|
|
13333
|
+
hide(hiddenValues, v, expanded, platform2.env);
|
|
13325
13334
|
}
|
|
13326
13335
|
const isUrl = target.startsWith("http://") || target.startsWith("https://") || target.includes(".");
|
|
13327
13336
|
let connectOpts;
|
|
@@ -16137,6 +16146,7 @@ async function probeServer(_name, config, platform2) {
|
|
|
16137
16146
|
} else {
|
|
16138
16147
|
return void 0;
|
|
16139
16148
|
}
|
|
16149
|
+
opts.redact = (text) => redactSecrets(text, secrets);
|
|
16140
16150
|
const conn = await connect(platform2, opts);
|
|
16141
16151
|
try {
|
|
16142
16152
|
await conn.info();
|
|
@@ -16730,7 +16740,8 @@ async function runClientVerb(platform2, version, parsed) {
|
|
|
16730
16740
|
signal: sigintController.signal,
|
|
16731
16741
|
verbose,
|
|
16732
16742
|
authProvider,
|
|
16733
|
-
roots
|
|
16743
|
+
roots,
|
|
16744
|
+
redact: redact2
|
|
16734
16745
|
});
|
|
16735
16746
|
activeConn = conn;
|
|
16736
16747
|
} catch (err) {
|
|
@@ -17067,5 +17078,5 @@ try {
|
|
|
17067
17078
|
});
|
|
17068
17079
|
} catch {
|
|
17069
17080
|
}
|
|
17070
|
-
var code = await run(platform, "0.17.
|
|
17081
|
+
var code = await run(platform, "0.17.11", args);
|
|
17071
17082
|
process.exit(code);
|
package/dist/kernel/mcpcat.mjs
CHANGED
|
@@ -8042,10 +8042,11 @@ var KERNEL_REFUSAL = "nodeTransport refuses ";
|
|
|
8042
8042
|
function firstLine(text) {
|
|
8043
8043
|
return text.trim().split("\n")[0].trim().slice(0, 200);
|
|
8044
8044
|
}
|
|
8045
|
-
async function authReason(res) {
|
|
8045
|
+
async function authReason(res, redact2) {
|
|
8046
8046
|
const type = res.headers.get("content-type") ?? "";
|
|
8047
|
-
if (!type.toLowerCase().startsWith("text/plain")) return
|
|
8048
|
-
|
|
8047
|
+
if (!type.toLowerCase().startsWith("text/plain")) return {};
|
|
8048
|
+
const text = await res.text();
|
|
8049
|
+
return { raw: firstLine(text), reason: firstLine(redact2(text)) || void 0 };
|
|
8049
8050
|
}
|
|
8050
8051
|
function selectSseFrame(body, targetId) {
|
|
8051
8052
|
const text = new TextDecoder().decode(body);
|
|
@@ -8162,7 +8163,8 @@ async function createStatelessConnection(options) {
|
|
|
8162
8163
|
signal: externalSignal,
|
|
8163
8164
|
verbose,
|
|
8164
8165
|
authProvider,
|
|
8165
|
-
transportRefusals
|
|
8166
|
+
transportRefusals,
|
|
8167
|
+
redact: redact2 = (text) => text
|
|
8166
8168
|
} = options;
|
|
8167
8169
|
let nextId2 = 1;
|
|
8168
8170
|
let negotiatedVersion;
|
|
@@ -8181,9 +8183,9 @@ async function createStatelessConnection(options) {
|
|
|
8181
8183
|
}
|
|
8182
8184
|
async function handleErrorStatus(res, method) {
|
|
8183
8185
|
if (res.status === 401 || res.status === 403) {
|
|
8184
|
-
const reason = await authReason(res);
|
|
8185
|
-
if (transportRefusals && res.status === 403 && !res.headers.has("www-authenticate") &&
|
|
8186
|
-
throw new TransportRefusalError(403, reason);
|
|
8186
|
+
const { raw, reason } = await authReason(res, redact2);
|
|
8187
|
+
if (transportRefusals && res.status === 403 && !res.headers.has("www-authenticate") && raw?.startsWith(KERNEL_REFUSAL)) {
|
|
8188
|
+
throw new TransportRefusalError(403, reason ?? "");
|
|
8187
8189
|
}
|
|
8188
8190
|
if (authProvider) throw new AuthRetryableError(res, reason);
|
|
8189
8191
|
throw new McpAuthError(res.status, url, reason);
|
|
@@ -8196,7 +8198,7 @@ async function createStatelessConnection(options) {
|
|
|
8196
8198
|
} catch {
|
|
8197
8199
|
}
|
|
8198
8200
|
if (rpc) throw new StatelessRpcError(rpc.code, rpc.message, rpc.data);
|
|
8199
|
-
throw new StatelessHttpError(res.status, res.statusText, method, text.slice(0, 512));
|
|
8201
|
+
throw new StatelessHttpError(res.status, res.statusText, method, redact2(text).slice(0, 512));
|
|
8200
8202
|
}
|
|
8201
8203
|
async function doFetch(method, params, body) {
|
|
8202
8204
|
const token = await authProvider?.token();
|
|
@@ -8724,7 +8726,8 @@ async function connect(platform2, options) {
|
|
|
8724
8726
|
signal: options.signal,
|
|
8725
8727
|
verbose: options.verbose,
|
|
8726
8728
|
authProvider: options.authProvider,
|
|
8727
|
-
transportRefusals: platform2.kind === "kernel"
|
|
8729
|
+
transportRefusals: platform2.kind === "kernel",
|
|
8730
|
+
redact: options.redact
|
|
8728
8731
|
});
|
|
8729
8732
|
} catch (err) {
|
|
8730
8733
|
const isMethodNotFound = err instanceof StatelessRpcError && err.code === -32601 || err instanceof StatelessHttpError && err.status >= 400 && err.status < 500;
|
|
@@ -11830,7 +11833,7 @@ function createProxyHandler(options) {
|
|
|
11830
11833
|
let connecting = null;
|
|
11831
11834
|
const scrub = secretMasker(options.hiddenHeaders, "***");
|
|
11832
11835
|
async function doConnect() {
|
|
11833
|
-
const c = await connect(options.platform, options.connectOpts);
|
|
11836
|
+
const c = await connect(options.platform, { ...options.connectOpts, redact: scrub });
|
|
11834
11837
|
cachedInfo = await c.info();
|
|
11835
11838
|
return c;
|
|
11836
11839
|
}
|
|
@@ -12902,23 +12905,29 @@ function resolveProxyUrl(target, headers, version) {
|
|
|
12902
12905
|
const url = target.startsWith("http") ? target : `https://${target}`;
|
|
12903
12906
|
return { url, headers, version };
|
|
12904
12907
|
}
|
|
12908
|
+
function unsetIn(header, value, env2) {
|
|
12909
|
+
const missing = missingPlaceholders(value, env2);
|
|
12910
|
+
if (missing.length === 0) return void 0;
|
|
12911
|
+
return `unset variable ${missing.join(", ")} in header '${header}'`;
|
|
12912
|
+
}
|
|
12913
|
+
function hide(hidden, template, expanded, env2) {
|
|
12914
|
+
if (template) hidden.add(template);
|
|
12915
|
+
if (expanded) hidden.add(expanded);
|
|
12916
|
+
for (const p of placeholderValues(template, env2)) hidden.add(p);
|
|
12917
|
+
}
|
|
12905
12918
|
async function resolveProxyConfig(platform2, target, connectHeaders, hiddenValues, version) {
|
|
12906
12919
|
const config = await MergedConfigSource.load(platform2);
|
|
12907
12920
|
const entry = await config.get(target);
|
|
12908
12921
|
if (!entry) return `unknown target '${target}'`;
|
|
12909
12922
|
if ("url" in entry && entry.url) {
|
|
12910
|
-
const expanded =
|
|
12911
|
-
|
|
12912
|
-
|
|
12913
|
-
|
|
12914
|
-
|
|
12915
|
-
|
|
12916
|
-
if (entry.headers) {
|
|
12917
|
-
for (const v of Object.values(entry.headers)) {
|
|
12918
|
-
hiddenValues.add(v);
|
|
12919
|
-
for (const p of placeholderValues(v, platform2.env)) hiddenValues.add(p);
|
|
12920
|
-
}
|
|
12923
|
+
const expanded = {};
|
|
12924
|
+
for (const [k, v] of Object.entries(entry.headers ?? {})) {
|
|
12925
|
+
const missing = unsetIn(k, v, platform2.env);
|
|
12926
|
+
if (missing) return missing;
|
|
12927
|
+
expanded[k] = expandPlaceholders(v, platform2.env);
|
|
12928
|
+
hide(hiddenValues, v, expanded[k], platform2.env);
|
|
12921
12929
|
}
|
|
12930
|
+
const merged = { ...expanded, ...connectHeaders };
|
|
12922
12931
|
return { url: entry.url, headers: merged, version };
|
|
12923
12932
|
}
|
|
12924
12933
|
if ("command" in entry && entry.command) {
|
|
@@ -12937,12 +12946,12 @@ async function buildProxyConnectOpts(platform2, options, version) {
|
|
|
12937
12946
|
const connectHeaders = {};
|
|
12938
12947
|
const hiddenValues = /* @__PURE__ */ new Set();
|
|
12939
12948
|
for (const [k, v] of options.proxyHeaders) {
|
|
12940
|
-
const
|
|
12941
|
-
if (
|
|
12949
|
+
const missing = unsetIn(k, v, platform2.env);
|
|
12950
|
+
if (missing) return { error: missing };
|
|
12951
|
+
const expanded = expandPlaceholders(v, platform2.env);
|
|
12942
12952
|
const key = k.toLowerCase() === "authorization" ? "Authorization" : k;
|
|
12943
12953
|
connectHeaders[key] = expanded;
|
|
12944
|
-
hiddenValues.
|
|
12945
|
-
for (const p of placeholderValues(v, platform2.env)) hiddenValues.add(p);
|
|
12954
|
+
hide(hiddenValues, v, expanded, platform2.env);
|
|
12946
12955
|
}
|
|
12947
12956
|
const isUrl = target.startsWith("http://") || target.startsWith("https://") || target.includes(".");
|
|
12948
12957
|
let connectOpts;
|
|
@@ -15758,6 +15767,7 @@ async function probeServer(_name, config, platform2) {
|
|
|
15758
15767
|
} else {
|
|
15759
15768
|
return void 0;
|
|
15760
15769
|
}
|
|
15770
|
+
opts.redact = (text) => redactSecrets(text, secrets);
|
|
15761
15771
|
const conn = await connect(platform2, opts);
|
|
15762
15772
|
try {
|
|
15763
15773
|
await conn.info();
|
|
@@ -16351,7 +16361,8 @@ async function runClientVerb(platform2, version, parsed) {
|
|
|
16351
16361
|
signal: sigintController.signal,
|
|
16352
16362
|
verbose,
|
|
16353
16363
|
authProvider,
|
|
16354
|
-
roots
|
|
16364
|
+
roots,
|
|
16365
|
+
redact: redact2
|
|
16355
16366
|
});
|
|
16356
16367
|
activeConn = conn;
|
|
16357
16368
|
} catch (err) {
|
|
@@ -16789,7 +16800,7 @@ async function main(ctx) {
|
|
|
16789
16800
|
packageDir: "/node_modules/@ai-ecoverse/mcpcat"
|
|
16790
16801
|
});
|
|
16791
16802
|
const args = ctx.argv.slice(1);
|
|
16792
|
-
return run(platform2, "0.17.
|
|
16803
|
+
return run(platform2, "0.17.11", args);
|
|
16793
16804
|
}
|
|
16794
16805
|
export {
|
|
16795
16806
|
main as default
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/mcpcat",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.11",
|
|
4
4
|
"description": "A curl for the Model Context Protocol",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -61,9 +61,9 @@
|
|
|
61
61
|
"trust:dry": "npx fledgling sync --dry-run -y --skip-publish"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
|
-
"@ai-ecoverse/slicc-bios": "1.
|
|
64
|
+
"@ai-ecoverse/slicc-bios": "1.38.0",
|
|
65
65
|
"@ai-ecoverse/slicc-kernel": "1.142.1",
|
|
66
|
-
"@ai-ecoverse/slicc-shared-web": "1.29.
|
|
66
|
+
"@ai-ecoverse/slicc-shared-web": "1.29.16",
|
|
67
67
|
"@biomejs/biome": "2.5.15",
|
|
68
68
|
"@earendil-works/pi-codemode": "1.2.0",
|
|
69
69
|
"@earendil-works/pi-mcp": "1.2.0",
|