metheus-governance-mcp-cli 0.2.37 → 0.2.39
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 +28 -0
- package/cli.mjs +185 -13
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -103,6 +103,11 @@ Antigravity note:
|
|
|
103
103
|
Cursor note:
|
|
104
104
|
- this CLI manages MCP registration via Cursor global MCP config (`~/.cursor/mcp.json`).
|
|
105
105
|
|
|
106
|
+
Tool naming compatibility:
|
|
107
|
+
- Claude/Codex/Gemini keep canonical MCP tool names (`project.summary`, `ctxpack.merge.brief`, ...).
|
|
108
|
+
- Cursor/Antigravity sessions may receive safe aliases (`project_summary`, `ctxpack_merge_brief`, ...).
|
|
109
|
+
- Proxy maps alias calls back to canonical names automatically.
|
|
110
|
+
|
|
106
111
|
Local bootstrap tools exposed by proxy:
|
|
107
112
|
|
|
108
113
|
- `project.summary`
|
|
@@ -239,6 +244,7 @@ npm publish --access public
|
|
|
239
244
|
|
|
240
245
|
```bash
|
|
241
246
|
npm run check
|
|
247
|
+
npm run test:compat
|
|
242
248
|
npm run publish:dry
|
|
243
249
|
```
|
|
244
250
|
|
|
@@ -255,3 +261,25 @@ If npm account uses 2FA, pass OTP:
|
|
|
255
261
|
```bash
|
|
256
262
|
node release.mjs --otp <6-digit-code>
|
|
257
263
|
```
|
|
264
|
+
|
|
265
|
+
## Regression and smoke checks
|
|
266
|
+
|
|
267
|
+
Local compatibility selftest (no network):
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
npm run test:compat
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Proxy smoke test (initialize -> tools/list -> project summary -> ctxpack local sync):
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
npm run smoke:proxy -- --project-id <project_uuid> --ctxpack-key "<ctxpack_key>" --workspace-dir <workspace_path>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Optional:
|
|
280
|
+
- `--client cursor-vscode` (default) or `--client antigravity`
|
|
281
|
+
- `--base-url https://metheus.gesiaplatform.com/governance/mcp`
|
|
282
|
+
|
|
283
|
+
Workspace fallback guardrail:
|
|
284
|
+
- If `METHEUS_WORKSPACE_DIR` is accidentally set to a non-existing boolean suffix path like `C:\code_test\true`,
|
|
285
|
+
proxy now recovers to the parent workspace (`C:\code_test`) instead of pinning to the bad path.
|
package/cli.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import process from "node:process";
|
|
6
7
|
import readline from "node:readline";
|
|
@@ -42,6 +43,7 @@ function printUsage() {
|
|
|
42
43
|
` ${cmd} setup [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--workspace-dir <path|auto>] [--workspace-fallback-dir <path>] [--name <server_name>]`,
|
|
43
44
|
` ${cmd} doctor [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--timeout-seconds <n>]`,
|
|
44
45
|
` ${cmd} proxy [--project-id <uuid>] [--ctxpack-key <key>] [--base-url <url>] [--workspace-dir <path|auto>] [--include-drafts <true|false>] [--auto-pull-on-conflict <true|false>] [--timeout-seconds <n>]`,
|
|
46
|
+
` ${cmd} selftest [--json <true|false>]`,
|
|
45
47
|
` ${cmd} ctxpack pull [--project-id <uuid>] [--base-url <url>] [--workspace-dir <path|auto>] [--paths <csv>] [--timeout-seconds <n>]`,
|
|
46
48
|
` ${cmd} auth status`,
|
|
47
49
|
` ${cmd} auth login [--base-url <url>] [--flow <auto|device|callback|manual>] [--keycloak-url <url>] [--realm <name>] [--client-id <id>] [--open-browser <true|false>] [--callback-port <n>] [--timeout-seconds <n>] [--manual <true|false>]`,
|
|
@@ -604,7 +606,7 @@ function extractWeakWorkspaceCandidateFromRequest(requestObj) {
|
|
|
604
606
|
}
|
|
605
607
|
|
|
606
608
|
function extractStrongWorkspaceCandidateFromEnv() {
|
|
607
|
-
const
|
|
609
|
+
const rawCandidates = [
|
|
608
610
|
process.env.METHEUS_WORKSPACE_DIR,
|
|
609
611
|
process.env.METHEUS_WORKSPACE_URI,
|
|
610
612
|
process.env.CODEX_WORKSPACE_DIR,
|
|
@@ -612,8 +614,12 @@ function extractStrongWorkspaceCandidateFromEnv() {
|
|
|
612
614
|
process.env.CLAUDE_WORKSPACE_DIR,
|
|
613
615
|
process.env.CLAUDE_WORKSPACE_URI,
|
|
614
616
|
process.env.CLAUDE_PROJECT_DIR,
|
|
615
|
-
]
|
|
616
|
-
|
|
617
|
+
];
|
|
618
|
+
for (const rawCandidate of rawCandidates) {
|
|
619
|
+
const normalized = sanitizeWorkspaceFallbackEnvCandidate(rawCandidate);
|
|
620
|
+
if (normalized) return normalized;
|
|
621
|
+
}
|
|
622
|
+
return "";
|
|
617
623
|
}
|
|
618
624
|
|
|
619
625
|
function extractWorkspaceCandidateFromEnv() {
|
|
@@ -635,6 +641,35 @@ function extractWeakWorkspaceCandidateFromEnv() {
|
|
|
635
641
|
return sanitizeWorkspaceCandidate(rawCandidate);
|
|
636
642
|
}
|
|
637
643
|
|
|
644
|
+
function sanitizeWorkspaceFallbackEnvCandidate(rawCandidate) {
|
|
645
|
+
const input = String(rawCandidate || "").trim();
|
|
646
|
+
if (!input) return "";
|
|
647
|
+
|
|
648
|
+
const direct = sanitizeWorkspaceCandidate(input);
|
|
649
|
+
if (direct && fs.existsSync(direct)) {
|
|
650
|
+
return direct;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const fileCandidate = fileURIToLocalPath(input);
|
|
654
|
+
const candidate = firstNonEmptyString([fileCandidate, input]);
|
|
655
|
+
if (!candidate) return direct;
|
|
656
|
+
|
|
657
|
+
const resolved = resolveWorkspaceDir(candidate);
|
|
658
|
+
if (!resolved || isEditorInstallDirectory(resolved)) {
|
|
659
|
+
return direct;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const leaf = path.basename(resolved).trim().toLowerCase();
|
|
663
|
+
if ((leaf === "true" || leaf === "false") && !fs.existsSync(resolved)) {
|
|
664
|
+
const parent = sanitizeWorkspaceCandidate(path.dirname(resolved));
|
|
665
|
+
if (parent && fs.existsSync(parent)) {
|
|
666
|
+
return parent;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
return direct;
|
|
671
|
+
}
|
|
672
|
+
|
|
638
673
|
function resolveWorkspaceDirForRequest(defaultWorkspaceDir, requestObj, toolArgs) {
|
|
639
674
|
const strongRequestCandidate = extractStrongWorkspaceCandidateFromRequest(requestObj, toolArgs);
|
|
640
675
|
const strongEnvCandidate = extractStrongWorkspaceCandidateFromEnv();
|
|
@@ -3568,6 +3603,14 @@ function shouldUseSafeToolAliasesForClient(initParamsRaw) {
|
|
|
3568
3603
|
return name.includes("cursor") || name.includes("antigravity");
|
|
3569
3604
|
}
|
|
3570
3605
|
|
|
3606
|
+
function displayToolNameForClient(canonicalName, useSafeToolAliases = false) {
|
|
3607
|
+
const canonical = String(canonicalName || "").trim();
|
|
3608
|
+
if (!canonical) return "";
|
|
3609
|
+
if (!useSafeToolAliases) return canonical;
|
|
3610
|
+
const alias = normalizeSafeToolAliasName(canonical);
|
|
3611
|
+
return alias || canonical;
|
|
3612
|
+
}
|
|
3613
|
+
|
|
3571
3614
|
function injectWorkspaceDirIntoToolSchemas(tools) {
|
|
3572
3615
|
const workspaceDirProp = {
|
|
3573
3616
|
type: "string",
|
|
@@ -3598,14 +3641,25 @@ function appendLocalToolToToolsList(responseObj) {
|
|
|
3598
3641
|
return responseObj;
|
|
3599
3642
|
}
|
|
3600
3643
|
|
|
3601
|
-
function appendProjectHintToInitialize(responseObj, args) {
|
|
3644
|
+
function appendProjectHintToInitialize(responseObj, args, options = {}) {
|
|
3645
|
+
const useSafeToolAliases = Boolean(options.useSafeToolAliases);
|
|
3602
3646
|
const result = safeObject(responseObj.result);
|
|
3647
|
+
const projectSummaryTool = displayToolNameForClient("project.summary", useSafeToolAliases);
|
|
3648
|
+
const projectDescribeTool = displayToolNameForClient("project.describe", useSafeToolAliases);
|
|
3649
|
+
const projectGetTool = displayToolNameForClient("project.get", useSafeToolAliases);
|
|
3650
|
+
const ctxpackMergeBriefTool = displayToolNameForClient("ctxpack.merge.brief", useSafeToolAliases);
|
|
3651
|
+
const ctxpackMergeExecuteTool = displayToolNameForClient("ctxpack.merge.execute", useSafeToolAliases);
|
|
3603
3652
|
const hintLines = [
|
|
3604
3653
|
"High-priority project bootstrap rule:",
|
|
3605
|
-
|
|
3606
|
-
|
|
3654
|
+
...(useSafeToolAliases
|
|
3655
|
+
? [
|
|
3656
|
+
"- This session uses safe MCP tool aliases (underscore format). Use `project_summary` / `ctxpack_ensure` / `workitem_list` style names, not dotted names.",
|
|
3657
|
+
]
|
|
3658
|
+
: []),
|
|
3659
|
+
`- MUST call \`${projectSummaryTool}\` first when the user provides only a Project ID or asks project overview/agenda.`,
|
|
3660
|
+
`- \`${projectDescribeTool}\` and \`${projectGetTool}\` are aliases of \`${projectSummaryTool}\`.`,
|
|
3607
3661
|
"- After project summary, use workitem/evidence/decision tools as follow-up.",
|
|
3608
|
-
|
|
3662
|
+
`- Before any ctxpack merge, call \`${ctxpackMergeBriefTool}\` first, share recommendation to owner, get explicit owner confirmation, then call \`${ctxpackMergeExecuteTool}\`.`,
|
|
3609
3663
|
"- IMPORTANT: When calling ANY tool, always include your current working directory / project folder path as the `workspace_dir` argument. This enables ctxpack files to be synced to the correct workspace location.",
|
|
3610
3664
|
];
|
|
3611
3665
|
if (args.projectID) {
|
|
@@ -3732,7 +3786,16 @@ function appendAutoCtxpackSyncHint(responseObj, summary) {
|
|
|
3732
3786
|
return responseObj;
|
|
3733
3787
|
}
|
|
3734
3788
|
|
|
3735
|
-
async function appendWorkitemListHints(
|
|
3789
|
+
async function appendWorkitemListHints(
|
|
3790
|
+
responseObj,
|
|
3791
|
+
args,
|
|
3792
|
+
toolArgs,
|
|
3793
|
+
token,
|
|
3794
|
+
workspaceSignalTrusted = true,
|
|
3795
|
+
options = {},
|
|
3796
|
+
) {
|
|
3797
|
+
const useSafeToolAliases = Boolean(options.useSafeToolAliases);
|
|
3798
|
+
const projectSummaryTool = displayToolNameForClient("project.summary", useSafeToolAliases);
|
|
3736
3799
|
const result = safeObject(responseObj.result);
|
|
3737
3800
|
const content = ensureArray(result.content);
|
|
3738
3801
|
if (!content.length) return responseObj;
|
|
@@ -3742,7 +3805,7 @@ async function appendWorkitemListHints(responseObj, args, toolArgs, token, works
|
|
|
3742
3805
|
|
|
3743
3806
|
const text = String(first.text || "");
|
|
3744
3807
|
if (!text) return responseObj;
|
|
3745
|
-
if (text.includes("Call `project.summary`")) return responseObj;
|
|
3808
|
+
if (text.includes(`Call \`${projectSummaryTool}\``) || text.includes("Call `project.summary`")) return responseObj;
|
|
3746
3809
|
|
|
3747
3810
|
const parsed = parseGatewayResponseText(text);
|
|
3748
3811
|
if (!parsed) return responseObj;
|
|
@@ -3759,7 +3822,7 @@ async function appendWorkitemListHints(responseObj, args, toolArgs, token, works
|
|
|
3759
3822
|
const nextLines = [""];
|
|
3760
3823
|
if (isEmptyBody) {
|
|
3761
3824
|
nextLines.push("No work items found for this project.");
|
|
3762
|
-
nextLines.push(
|
|
3825
|
+
nextLines.push(`- Call \`${projectSummaryTool}\` to confirm project context/agenda and access state first.`);
|
|
3763
3826
|
}
|
|
3764
3827
|
|
|
3765
3828
|
let responseProjectID = "";
|
|
@@ -4525,7 +4588,9 @@ async function runProxy(flags) {
|
|
|
4525
4588
|
patched = applyToolAliasesToToolsListResponse(patched, sessionToolCanonicalToAlias);
|
|
4526
4589
|
}
|
|
4527
4590
|
} else if (isJsonRpcMethod(requestObj, "initialize")) {
|
|
4528
|
-
patched = appendProjectHintToInitialize(patched, args
|
|
4591
|
+
patched = appendProjectHintToInitialize(patched, args, {
|
|
4592
|
+
useSafeToolAliases: sessionUseSafeToolAliases,
|
|
4593
|
+
});
|
|
4529
4594
|
// Log initialize params for workspace debugging.
|
|
4530
4595
|
try {
|
|
4531
4596
|
const _diagDir = path.join(String(process.env.USERPROFILE || process.env.HOME || "."), ".metheus");
|
|
@@ -4542,7 +4607,14 @@ async function runProxy(flags) {
|
|
|
4542
4607
|
setImmediate(() => sendRootsListProbe());
|
|
4543
4608
|
}
|
|
4544
4609
|
} else if (isJsonRpcMethod(requestObj, "tools/call") && toolName === "workitem.list") {
|
|
4545
|
-
patched = await appendWorkitemListHints(
|
|
4610
|
+
patched = await appendWorkitemListHints(
|
|
4611
|
+
patched,
|
|
4612
|
+
args,
|
|
4613
|
+
toolArgs,
|
|
4614
|
+
token,
|
|
4615
|
+
workspaceSignalTrusted,
|
|
4616
|
+
{ useSafeToolAliases: sessionUseSafeToolAliases },
|
|
4617
|
+
);
|
|
4546
4618
|
} else if (isJsonRpcMethod(requestObj, "tools/call") && toolName === "ctxpack.ensure") {
|
|
4547
4619
|
patched = appendCtxpackEnsureSyncHints(
|
|
4548
4620
|
patched,
|
|
@@ -4984,7 +5056,7 @@ function resolveSetupContext(flags) {
|
|
|
4984
5056
|
const workspaceMeta = loadWorkspaceMeta(process.cwd());
|
|
4985
5057
|
const projectID = String(flags["project-id"] || workspaceMeta.project_id || "").trim();
|
|
4986
5058
|
const ctxpackKey = String(flags["ctxpack-key"] || buildCtxpackKeyFromMeta(workspaceMeta) || "").trim();
|
|
4987
|
-
const baseURL =
|
|
5059
|
+
const baseURL = normalizeSiteBaseURL(flags["base-url"] || DEFAULT_SITE_URL);
|
|
4988
5060
|
const workspaceDirRaw = String(flags["workspace-dir"] || "").trim();
|
|
4989
5061
|
const workspaceFallbackDirRaw = String(flags["workspace-fallback-dir"] || "").trim();
|
|
4990
5062
|
const hasWorkspaceDirFlag = Object.prototype.hasOwnProperty.call(flags, "workspace-dir");
|
|
@@ -5099,6 +5171,102 @@ function runSetup(flags) {
|
|
|
5099
5171
|
runSetupInternal(flags, { ensureOnly: false });
|
|
5100
5172
|
}
|
|
5101
5173
|
|
|
5174
|
+
function runSelftest(flags = {}) {
|
|
5175
|
+
const jsonMode = boolFromRaw(flags.json, false);
|
|
5176
|
+
const checks = [];
|
|
5177
|
+
const push = (name, ok, detail = "") => checks.push({ name, ok: Boolean(ok), detail: String(detail || "") });
|
|
5178
|
+
|
|
5179
|
+
const aliasMaps = buildToolAliasMaps([
|
|
5180
|
+
{ name: "project.summary" },
|
|
5181
|
+
{ name: "ctxpack.merge.brief" },
|
|
5182
|
+
{ name: "workitem.list" },
|
|
5183
|
+
]);
|
|
5184
|
+
const summaryAlias = String(aliasMaps.canonicalToAlias.get("project.summary") || "");
|
|
5185
|
+
const roundTrip = rewriteAliasedToolCallToCanonical(
|
|
5186
|
+
{
|
|
5187
|
+
jsonrpc: "2.0",
|
|
5188
|
+
id: 1,
|
|
5189
|
+
method: "tools/call",
|
|
5190
|
+
params: {
|
|
5191
|
+
name: summaryAlias,
|
|
5192
|
+
arguments: {},
|
|
5193
|
+
},
|
|
5194
|
+
},
|
|
5195
|
+
aliasMaps.aliasToCanonical,
|
|
5196
|
+
);
|
|
5197
|
+
const roundTripOk = summaryAlias === "project_summary" && String(roundTrip?.params?.name || "") === "project.summary";
|
|
5198
|
+
push(
|
|
5199
|
+
"alias_roundtrip_project_summary",
|
|
5200
|
+
roundTripOk,
|
|
5201
|
+
`alias=${summaryAlias || "(none)"} canonical=${String(roundTrip?.params?.name || "(none)")}`,
|
|
5202
|
+
);
|
|
5203
|
+
|
|
5204
|
+
const collisionMaps = buildToolAliasMaps([{ name: "a.b" }, { name: "a_b" }]);
|
|
5205
|
+
const aliasDot = String(collisionMaps.canonicalToAlias.get("a.b") || "");
|
|
5206
|
+
const aliasUnderscore = String(collisionMaps.canonicalToAlias.get("a_b") || "");
|
|
5207
|
+
const collisionOk = aliasDot === "a_b_2" && aliasUnderscore === "";
|
|
5208
|
+
push(
|
|
5209
|
+
"alias_collision_safe_suffix",
|
|
5210
|
+
collisionOk,
|
|
5211
|
+
`a.b->${aliasDot || "(none)"} a_b->${aliasUnderscore || "(none)"}`,
|
|
5212
|
+
);
|
|
5213
|
+
|
|
5214
|
+
let tempRoot = "";
|
|
5215
|
+
try {
|
|
5216
|
+
tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-selftest-"));
|
|
5217
|
+
const parent = path.join(tempRoot, "workspace");
|
|
5218
|
+
fs.mkdirSync(parent, { recursive: true });
|
|
5219
|
+
const suspicious = path.join(parent, "true");
|
|
5220
|
+
const recovered = sanitizeWorkspaceFallbackEnvCandidate(suspicious);
|
|
5221
|
+
const recoveredOk = normalizedPathForCompare(recovered) === normalizedPathForCompare(parent);
|
|
5222
|
+
push(
|
|
5223
|
+
"workspace_env_true_suffix_recovery",
|
|
5224
|
+
recoveredOk,
|
|
5225
|
+
`input=${suspicious} recovered=${recovered || "(none)"}`,
|
|
5226
|
+
);
|
|
5227
|
+
|
|
5228
|
+
fs.mkdirSync(suspicious, { recursive: true });
|
|
5229
|
+
const keptExisting = sanitizeWorkspaceFallbackEnvCandidate(suspicious);
|
|
5230
|
+
const keptExistingOk = normalizedPathForCompare(keptExisting) === normalizedPathForCompare(suspicious);
|
|
5231
|
+
push(
|
|
5232
|
+
"workspace_env_existing_true_dir_kept",
|
|
5233
|
+
keptExistingOk,
|
|
5234
|
+
`input=${suspicious} recovered=${keptExisting || "(none)"}`,
|
|
5235
|
+
);
|
|
5236
|
+
} catch (err) {
|
|
5237
|
+
push("workspace_env_guard_test_setup", false, String(err?.message || err));
|
|
5238
|
+
} finally {
|
|
5239
|
+
if (tempRoot) {
|
|
5240
|
+
try {
|
|
5241
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
5242
|
+
} catch {
|
|
5243
|
+
// ignore selftest cleanup error
|
|
5244
|
+
}
|
|
5245
|
+
}
|
|
5246
|
+
}
|
|
5247
|
+
|
|
5248
|
+
const failed = checks.filter((row) => !row.ok);
|
|
5249
|
+
const payload = {
|
|
5250
|
+
ok: failed.length === 0,
|
|
5251
|
+
pass_count: checks.length - failed.length,
|
|
5252
|
+
fail_count: failed.length,
|
|
5253
|
+
checks,
|
|
5254
|
+
};
|
|
5255
|
+
|
|
5256
|
+
if (jsonMode) {
|
|
5257
|
+
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
5258
|
+
} else {
|
|
5259
|
+
process.stdout.write(`Selftest: ${payload.ok ? "PASS" : "FAIL"} (${payload.pass_count}/${checks.length})\n`);
|
|
5260
|
+
for (const row of checks) {
|
|
5261
|
+
process.stdout.write(`- ${row.ok ? "PASS" : "FAIL"} ${row.name}${row.detail ? ` :: ${row.detail}` : ""}\n`);
|
|
5262
|
+
}
|
|
5263
|
+
}
|
|
5264
|
+
|
|
5265
|
+
if (!payload.ok) {
|
|
5266
|
+
process.exitCode = 1;
|
|
5267
|
+
}
|
|
5268
|
+
}
|
|
5269
|
+
|
|
5102
5270
|
async function runBootstrap(flags) {
|
|
5103
5271
|
process.stdout.write("Bootstrap start.\n");
|
|
5104
5272
|
let resolved = resolveCurrentAccessToken();
|
|
@@ -5159,6 +5327,10 @@ async function main() {
|
|
|
5159
5327
|
await runDoctor(flags);
|
|
5160
5328
|
return;
|
|
5161
5329
|
}
|
|
5330
|
+
if (command === "selftest") {
|
|
5331
|
+
runSelftest(flags);
|
|
5332
|
+
return;
|
|
5333
|
+
}
|
|
5162
5334
|
if (command === "auth") {
|
|
5163
5335
|
await runAuth(rest);
|
|
5164
5336
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metheus-governance-mcp-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.39",
|
|
4
4
|
"description": "Metheus Governance MCP CLI (setup + stdio proxy)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
],
|
|
12
12
|
"scripts": {
|
|
13
13
|
"check": "node --check cli.mjs && node --check release.mjs",
|
|
14
|
+
"test:compat": "node cli.mjs selftest --json",
|
|
15
|
+
"smoke:proxy": "node scripts/smoke-proxy.mjs",
|
|
14
16
|
"pack:dry": "npm pack --dry-run",
|
|
15
17
|
"publish:dry": "node release.mjs --dry-run",
|
|
16
18
|
"publish:public": "node release.mjs"
|