modelfunction-cli 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -10
- package/SCHEMA.md +83 -5
- package/lib/audit.js +149 -0
- package/lib/budget.js +30 -0
- package/lib/check.d.ts +31 -0
- package/lib/check.js +117 -22
- package/lib/mcp-probe.js +59 -9
- package/lib/mcp-server.js +105 -9
- package/lib/scan.js +20 -2
- package/lib/score.js +14 -7
- package/lib/usage.js +21 -1
- package/mf.js +187 -45
- package/package.json +1 -1
package/mf.js
CHANGED
|
@@ -4,14 +4,15 @@ import { mkdir, readFile } from "node:fs/promises";
|
|
|
4
4
|
import { basename, dirname, extname, relative, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { loadSuite, toModelTools } from "./lib/suite.js";
|
|
7
|
-
import { scoreModel, pickRepresentative, pct } from "./lib/score.js";
|
|
7
|
+
import { scoreModel, pickRepresentative, pct, costCnyOf } from "./lib/score.js";
|
|
8
8
|
import { formatTable, formatCaseLines, formatMarkdown } from "./lib/report.js";
|
|
9
9
|
import { scanProject, buildSuiteDraft, formatScanSummary, addTools, redactReport } from "./lib/scan.js";
|
|
10
|
-
import { probeStdioServer } from "./lib/mcp-probe.js";
|
|
10
|
+
import { probeStdioServer, probeHttpServer } from "./lib/mcp-probe.js";
|
|
11
11
|
import { loadAdapterTools } from "./lib/adapter-import.js";
|
|
12
12
|
import { addCases, computeCoverage, formatCoverage } from "./lib/cases.js";
|
|
13
13
|
import { lintSuite, formatLint } from "./lib/lint.js";
|
|
14
|
-
import { check, formatCheck } from "./lib/check.js";
|
|
14
|
+
import { check, checkMany, formatCheck, formatCheckMany } from "./lib/check.js";
|
|
15
|
+
import { auditSuite, formatAudit } from "./lib/audit.js";
|
|
15
16
|
import { suiteFingerprint, hashText, hashOf } from "./lib/fingerprint.js";
|
|
16
17
|
import { runExecutionChecks } from "./lib/eval-exec.js";
|
|
17
18
|
import { checkSandbox, formatSandboxReport } from "./lib/sandbox-check.js";
|
|
@@ -30,6 +31,7 @@ import {
|
|
|
30
31
|
formatProfileMarkdown
|
|
31
32
|
} from "./lib/profile.js";
|
|
32
33
|
import { runExec } from "./lib/exec.js";
|
|
34
|
+
import { createBudget, CostLimitError } from "./lib/budget.js";
|
|
33
35
|
import { writeFileAtomic } from "./lib/fsutil.js";
|
|
34
36
|
import {
|
|
35
37
|
applyModelCredentials,
|
|
@@ -71,10 +73,11 @@ Usage:
|
|
|
71
73
|
mf scan [options]
|
|
72
74
|
mf case add --suite <file> (--input <file> | stdin) [--source agent] [--reviewed] [--dry-run]
|
|
73
75
|
mf coverage [--suite <file>] [--json]
|
|
76
|
+
mf audit [--suite <file>] [--out <file>] [--json] # 静态审计:lint + 分组边界 + 用例覆盖 + 挂载
|
|
74
77
|
mf lint [--suite <file>] [--json] # 静态检查 tools 与 skills,不调用模型
|
|
75
|
-
mf check --manifest <file> [--installed <file>] [--json] # 安装门禁:新能力 ×
|
|
78
|
+
mf check (--manifest <file> | --manifests <file>) [--installed <file>] [--json] # 安装门禁:新能力 × 已装能力(--manifests 支持一包)
|
|
76
79
|
mf compare <before.json> <after.json> [--json]
|
|
77
|
-
mf ablate --tool <name> [--suite <file>] [--profile <group>] [--models <ids>] [--json]
|
|
80
|
+
mf ablate --tool <name> [--suite <file>] [--profile <group>] [--models <ids>] [--max-cost <cny>] [--progress] [--json]
|
|
78
81
|
mf cluster [--suite <file>] [--threshold <0.5>] [--max-size <10>] [--out <file>] [--json] # 建议挂载分组 + 同族候选
|
|
79
82
|
mf exec --entry <file> (--input <json> | --input-json <json>) [--runtime node|python] [--python <cmd>] [--export run] [--allow-read <paths>] [--allow-write <paths>] [--allow-net <hosts>] [--timeout <ms>] [--warmup <n>] [--json]
|
|
80
83
|
mf sandbox check [--json] # probe available isolation engines on this machine
|
|
@@ -93,6 +96,8 @@ Eval options:
|
|
|
93
96
|
--case <ids> run only these case ids (comma-separated, repeatable)
|
|
94
97
|
--profile <names> score each mounted group separately ("all" or names from suite.profiles)
|
|
95
98
|
--repeat <n> run each case n times and score the majority; reports unstable cases (default 1)
|
|
99
|
+
--max-cost <cny> stop after the estimated spend crosses this limit (exit 2; no partial scorecard)
|
|
100
|
+
--progress print per-case progress to stderr (stdout stays clean for --json)
|
|
96
101
|
--show-cases print per-case results
|
|
97
102
|
--json print JSON instead of the table
|
|
98
103
|
--out <file> write scorecard (.md => markdown, otherwise JSON)
|
|
@@ -106,7 +111,7 @@ Eval options:
|
|
|
106
111
|
Scan options:
|
|
107
112
|
--root <dir> repository to scan (default: .)
|
|
108
113
|
--from <file> import tools from a module (export default [...] or export const tools)
|
|
109
|
-
--probe
|
|
114
|
+
--probe connect stdio or streamable-HTTP MCP servers and read tools/list
|
|
110
115
|
--timeout <ms> probe timeout per server (default: 5000)
|
|
111
116
|
--out <file> suite draft to write (default: mf.suite.json)
|
|
112
117
|
--report <file> scan report to write (default: mf.scan.json)
|
|
@@ -134,17 +139,31 @@ export async function runEval(options, io = console) {
|
|
|
134
139
|
const { registry, adapters } = await resolveModels(options.models, options.modelsFile, io);
|
|
135
140
|
const tools = toModelTools(suite.tools);
|
|
136
141
|
|
|
142
|
+
const budget = createBudget(options.maxCost ?? null);
|
|
137
143
|
const runs = [];
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
144
|
+
try {
|
|
145
|
+
for (const adapter of adapters) {
|
|
146
|
+
const pricing = registry[adapter.id]?.pricing ?? null;
|
|
147
|
+
const evaluated = await evaluate(adapter, tools, cases, options.repeat ?? 1, {
|
|
148
|
+
pricing,
|
|
149
|
+
budget,
|
|
150
|
+
onProgress: makeProgress(options, io, adapter.id, cases.length)
|
|
151
|
+
});
|
|
152
|
+
const summary = scoreModel({
|
|
153
|
+
model: adapter.id,
|
|
154
|
+
cases,
|
|
155
|
+
results: evaluated.results,
|
|
156
|
+
pricing
|
|
157
|
+
});
|
|
158
|
+
if (evaluated.stability) summary.stability = evaluated.stability;
|
|
159
|
+
runs.push({ adapter, results: evaluated.results, summary });
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
if (error instanceof CostLimitError) {
|
|
163
|
+
io.error(costLimitMessage(error));
|
|
164
|
+
return 2;
|
|
165
|
+
}
|
|
166
|
+
throw error;
|
|
148
167
|
}
|
|
149
168
|
const summaries = runs.map((run) => run.summary);
|
|
150
169
|
|
|
@@ -173,16 +192,28 @@ export async function runEval(options, io = console) {
|
|
|
173
192
|
};
|
|
174
193
|
}
|
|
175
194
|
|
|
176
|
-
|
|
177
|
-
|
|
195
|
+
let profileReport = null;
|
|
196
|
+
if (options.profiles) {
|
|
197
|
+
try {
|
|
198
|
+
profileReport = await evaluateProfiles({
|
|
178
199
|
suite,
|
|
179
200
|
cases,
|
|
180
201
|
runs,
|
|
181
202
|
registry,
|
|
182
203
|
selection: options.profiles,
|
|
183
|
-
repeat: options.repeat ?? 1
|
|
184
|
-
|
|
185
|
-
|
|
204
|
+
repeat: options.repeat ?? 1,
|
|
205
|
+
budget,
|
|
206
|
+
progress: options.progress,
|
|
207
|
+
io
|
|
208
|
+
});
|
|
209
|
+
} catch (error) {
|
|
210
|
+
if (error instanceof CostLimitError) {
|
|
211
|
+
io.error(costLimitMessage(error));
|
|
212
|
+
return 2;
|
|
213
|
+
}
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
186
217
|
|
|
187
218
|
if (options.out) {
|
|
188
219
|
const target = resolve(options.out);
|
|
@@ -357,14 +388,35 @@ export async function runAblate(options, io = console) {
|
|
|
357
388
|
const fullTools = toModelTools(baseTools);
|
|
358
389
|
const reducedTools = toModelTools(baseTools.filter((tool) => tool.name !== ablated.name));
|
|
359
390
|
const models = [];
|
|
391
|
+
const budget = createBudget(options.maxCost ?? null);
|
|
360
392
|
|
|
361
|
-
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
393
|
+
try {
|
|
394
|
+
for (const adapter of adapters) {
|
|
395
|
+
const pricing = registry[adapter.id]?.pricing ?? null;
|
|
396
|
+
const fullResults = (
|
|
397
|
+
await evaluate(adapter, fullTools, baseCases, 1, {
|
|
398
|
+
pricing,
|
|
399
|
+
budget,
|
|
400
|
+
onProgress: makeProgress(options, io, `${adapter.id}/full`, baseCases.length)
|
|
401
|
+
})
|
|
402
|
+
).results;
|
|
403
|
+
const full = scoreModel({ model: adapter.id, cases: baseCases, results: fullResults, pricing });
|
|
404
|
+
const reducedResults = (
|
|
405
|
+
await evaluate(adapter, reducedTools, reducedCases, 1, {
|
|
406
|
+
pricing,
|
|
407
|
+
budget,
|
|
408
|
+
onProgress: makeProgress(options, io, `${adapter.id}/ablated`, reducedCases.length)
|
|
409
|
+
})
|
|
410
|
+
).results;
|
|
411
|
+
const reduced = scoreModel({ model: adapter.id, cases: reducedCases, results: reducedResults, pricing });
|
|
412
|
+
models.push(analyzeAblation(adapter.id, full, reduced));
|
|
413
|
+
}
|
|
414
|
+
} catch (error) {
|
|
415
|
+
if (error instanceof CostLimitError) {
|
|
416
|
+
io.error(costLimitMessage(error));
|
|
417
|
+
return 2;
|
|
418
|
+
}
|
|
419
|
+
throw error;
|
|
368
420
|
}
|
|
369
421
|
|
|
370
422
|
const payload = {
|
|
@@ -399,11 +451,10 @@ export async function runScan(options, io = console) {
|
|
|
399
451
|
|
|
400
452
|
if (options.probe) {
|
|
401
453
|
for (const server of [...report.mcpServers]) {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const result = await probeStdioServer(server, { timeoutMs: options.timeoutMs });
|
|
454
|
+
const result =
|
|
455
|
+
server.transport === "stdio"
|
|
456
|
+
? await probeStdioServer(server, { timeoutMs: options.timeoutMs })
|
|
457
|
+
: await probeHttpServer(server, { timeoutMs: options.timeoutMs });
|
|
407
458
|
if (!result.ok) {
|
|
408
459
|
report.probes.push({ server: server.name, ok: false, error: result.error, stderr: result.stderr });
|
|
409
460
|
continue;
|
|
@@ -495,6 +546,35 @@ export async function runCoverage(options, io = console) {
|
|
|
495
546
|
return 0;
|
|
496
547
|
}
|
|
497
548
|
|
|
549
|
+
export async function runAudit(options, io = console) {
|
|
550
|
+
const suite = await loadSuite(resolve(options.suite ?? DEFAULT_SUITE), {
|
|
551
|
+
allowEmptyCases: true,
|
|
552
|
+
allowEmptyTools: true
|
|
553
|
+
});
|
|
554
|
+
const report = auditSuite(suite);
|
|
555
|
+
const rules = {};
|
|
556
|
+
for (const finding of report.lint.findings) rules[finding.code] = (rules[finding.code] ?? 0) + 1;
|
|
557
|
+
noteUsage({
|
|
558
|
+
toolCount: report.suite.tools,
|
|
559
|
+
caseCount: report.suite.cases,
|
|
560
|
+
lintErrors: report.lint.counts.error,
|
|
561
|
+
lintWarns: report.lint.counts.warn,
|
|
562
|
+
lintInfos: report.lint.counts.info,
|
|
563
|
+
lintRules: rules,
|
|
564
|
+
clusters: report.clusters.clusters.length,
|
|
565
|
+
profileGroups: report.profiles?.groups.length ?? 0
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
if (options.out) {
|
|
569
|
+
const target = resolve(options.out);
|
|
570
|
+
const markdown = extname(target).toLowerCase() === ".md";
|
|
571
|
+
await writeFileAtomic(target, markdown ? `${formatAudit(report)}\n` : `${JSON.stringify(report, null, 2)}\n`);
|
|
572
|
+
io.log(`wrote ${target}`);
|
|
573
|
+
}
|
|
574
|
+
io.log(options.json ? JSON.stringify(report, null, 2) : formatAudit(report));
|
|
575
|
+
return 0;
|
|
576
|
+
}
|
|
577
|
+
|
|
498
578
|
export async function runSandboxCheck(options, io = console) {
|
|
499
579
|
const report = checkSandbox();
|
|
500
580
|
io.log(options.json ? JSON.stringify(report, null, 2) : formatSandboxReport(report));
|
|
@@ -504,11 +584,24 @@ export async function runSandboxCheck(options, io = console) {
|
|
|
504
584
|
// Advisory install gate: static only, never blocks. Exit code stays 0 for a
|
|
505
585
|
// warn verdict so a platform can log it without failing the install.
|
|
506
586
|
export async function runCheck(options, io = console) {
|
|
507
|
-
if (!options.manifest) throw new Error("--manifest <file> 必填(新能力的 [name, description, parameters?])");
|
|
508
|
-
const manifest = JSON.parse(await readFile(resolve(options.manifest), "utf8"));
|
|
509
587
|
const installed = options.installed
|
|
510
588
|
? JSON.parse(await readFile(resolve(options.installed), "utf8"))
|
|
511
589
|
: [];
|
|
590
|
+
|
|
591
|
+
if (options.manifests) {
|
|
592
|
+
const manifests = JSON.parse(await readFile(resolve(options.manifests), "utf8"));
|
|
593
|
+
const result = checkMany({ manifests, installed });
|
|
594
|
+
noteUsage({
|
|
595
|
+
checkVerdict: result.verdict,
|
|
596
|
+
checkFindings: Object.values(result.summary).reduce((sum, count) => sum + count, 0)
|
|
597
|
+
});
|
|
598
|
+
io.log(options.json ? JSON.stringify(result, null, 2) : formatCheckMany(result));
|
|
599
|
+
return 0;
|
|
600
|
+
}
|
|
601
|
+
if (!options.manifest) {
|
|
602
|
+
throw new Error("--manifest 或 --manifests <file> 必填([name, description, parameters?])");
|
|
603
|
+
}
|
|
604
|
+
const manifest = JSON.parse(await readFile(resolve(options.manifest), "utf8"));
|
|
512
605
|
const result = check({ manifest, installed });
|
|
513
606
|
noteUsage({ checkVerdict: result.verdict, checkFindings: result.findings.length });
|
|
514
607
|
io.log(options.json ? JSON.stringify(result, null, 2) : formatCheck(result));
|
|
@@ -719,25 +812,47 @@ function autoSelect(registry) {
|
|
|
719
812
|
.map(([id]) => id);
|
|
720
813
|
}
|
|
721
814
|
|
|
722
|
-
async function evaluate(
|
|
815
|
+
async function evaluate(
|
|
816
|
+
adapter,
|
|
817
|
+
tools,
|
|
818
|
+
cases,
|
|
819
|
+
repeat = 1,
|
|
820
|
+
{ pricing = null, budget = null, onProgress = null } = {}
|
|
821
|
+
) {
|
|
723
822
|
const results = [];
|
|
724
823
|
const unstable = [];
|
|
725
|
-
for (const testCase of cases) {
|
|
824
|
+
for (const [index, testCase] of cases.entries()) {
|
|
726
825
|
const runs = [];
|
|
727
826
|
for (let attempt = 0; attempt < repeat; attempt += 1) {
|
|
728
|
-
|
|
827
|
+
const result = await callOnce(adapter, tools, testCase);
|
|
828
|
+
budget?.spend(costCnyOf(result.usage, pricing));
|
|
829
|
+
runs.push(result);
|
|
729
830
|
}
|
|
730
831
|
if (repeat === 1) {
|
|
731
832
|
results.push(runs[0]);
|
|
732
|
-
|
|
833
|
+
} else {
|
|
834
|
+
const picked = pickRepresentative(runs);
|
|
835
|
+
results.push(picked.result);
|
|
836
|
+
if (picked.unstable) unstable.push({ id: testCase.id, outcomes: picked.outcomes });
|
|
733
837
|
}
|
|
734
|
-
|
|
735
|
-
results.push(picked.result);
|
|
736
|
-
if (picked.unstable) unstable.push({ id: testCase.id, outcomes: picked.outcomes });
|
|
838
|
+
onProgress?.(index + 1, cases.length);
|
|
737
839
|
}
|
|
738
840
|
return { results, stability: repeat > 1 ? { runs: repeat, unstable } : null };
|
|
739
841
|
}
|
|
740
842
|
|
|
843
|
+
function makeProgress(options, io, label, total) {
|
|
844
|
+
if (!options.progress) return null;
|
|
845
|
+
const step = Math.max(1, Math.ceil(total / 10));
|
|
846
|
+
return (done, count) => {
|
|
847
|
+
if (done !== count && done % step !== 0) return;
|
|
848
|
+
io.error(`[${label}] ${done}/${count}`);
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
function costLimitMessage(error) {
|
|
853
|
+
return `${error.message};已发出的请求已计费,本次不计分。减少 --case/--models/--repeat 或提高 --max-cost 后重跑。`;
|
|
854
|
+
}
|
|
855
|
+
|
|
741
856
|
async function callOnce(adapter, tools, testCase) {
|
|
742
857
|
const messages = [
|
|
743
858
|
{ role: "system", content: SYSTEM_PROMPT },
|
|
@@ -782,7 +897,17 @@ function ablationScope(suite, names) {
|
|
|
782
897
|
return { name: profile.name, tools: suite.tools.filter((tool) => mounted.has(tool.name)) };
|
|
783
898
|
}
|
|
784
899
|
|
|
785
|
-
async function evaluateProfiles({
|
|
900
|
+
async function evaluateProfiles({
|
|
901
|
+
suite,
|
|
902
|
+
cases,
|
|
903
|
+
runs,
|
|
904
|
+
registry,
|
|
905
|
+
selection,
|
|
906
|
+
repeat = 1,
|
|
907
|
+
budget = null,
|
|
908
|
+
progress = false,
|
|
909
|
+
io = console
|
|
910
|
+
}) {
|
|
786
911
|
const profiles = normalizeProfiles(suite.profiles, suite.tools.map((tool) => tool.name));
|
|
787
912
|
if (!profiles) {
|
|
788
913
|
throw new Error(`suite ${suite.name} 没有 profiles;先运行 mf cluster --out <suite> 生成分组`);
|
|
@@ -812,7 +937,11 @@ async function evaluateProfiles({ suite, cases, runs, registry, selection, repea
|
|
|
812
937
|
const profileCases = caseSets[index];
|
|
813
938
|
if (profileCases.length === 0) continue;
|
|
814
939
|
const mountedTools = suite.tools.filter((tool) => profile.tools.includes(tool.name));
|
|
815
|
-
const mountedEval = await evaluate(run.adapter, toModelTools(mountedTools), profileCases, repeat
|
|
940
|
+
const mountedEval = await evaluate(run.adapter, toModelTools(mountedTools), profileCases, repeat, {
|
|
941
|
+
pricing,
|
|
942
|
+
budget,
|
|
943
|
+
onProgress: makeProgress({ progress }, io, `${run.adapter.id}/${profile.name}`, profileCases.length)
|
|
944
|
+
});
|
|
816
945
|
const mounted = scoreModel({
|
|
817
946
|
model: run.adapter.id,
|
|
818
947
|
cases: profileCases,
|
|
@@ -875,6 +1004,8 @@ function parseArgs(argv) {
|
|
|
875
1004
|
cases: null,
|
|
876
1005
|
profiles: null,
|
|
877
1006
|
repeat: 1,
|
|
1007
|
+
maxCost: null,
|
|
1008
|
+
progress: false,
|
|
878
1009
|
showCases: false,
|
|
879
1010
|
json: false,
|
|
880
1011
|
out: null,
|
|
@@ -891,6 +1022,7 @@ function parseArgs(argv) {
|
|
|
891
1022
|
dryRun: false,
|
|
892
1023
|
tool: null,
|
|
893
1024
|
manifest: null,
|
|
1025
|
+
manifests: null,
|
|
894
1026
|
installed: null,
|
|
895
1027
|
threshold: 0.5,
|
|
896
1028
|
maxSize: 10,
|
|
@@ -932,6 +1064,13 @@ function parseArgs(argv) {
|
|
|
932
1064
|
options.repeat = raw;
|
|
933
1065
|
break;
|
|
934
1066
|
}
|
|
1067
|
+
case "--max-cost": {
|
|
1068
|
+
const raw = Number(value());
|
|
1069
|
+
if (!Number.isFinite(raw) || raw <= 0) throw new Error("--max-cost expects a positive CNY amount");
|
|
1070
|
+
options.maxCost = raw;
|
|
1071
|
+
break;
|
|
1072
|
+
}
|
|
1073
|
+
case "--progress": options.progress = true; break;
|
|
935
1074
|
case "--show-cases": options.showCases = true; break;
|
|
936
1075
|
case "--json": options.json = true; break;
|
|
937
1076
|
case "--out": options.out = value(); break;
|
|
@@ -952,6 +1091,7 @@ function parseArgs(argv) {
|
|
|
952
1091
|
case "--dry-run": options.dryRun = true; break;
|
|
953
1092
|
case "--tool": options.tool = value(); break;
|
|
954
1093
|
case "--manifest": options.manifest = resolve(value()); break;
|
|
1094
|
+
case "--manifests": options.manifests = resolve(value()); break;
|
|
955
1095
|
case "--installed": options.installed = resolve(value()); break;
|
|
956
1096
|
case "--threshold": {
|
|
957
1097
|
const raw = Number(value());
|
|
@@ -1104,7 +1244,7 @@ if (isMain) {
|
|
|
1104
1244
|
}
|
|
1105
1245
|
} else if (argv.length === 0 || argv[0] === "help" || argv[0] === "-h" || argv[0] === "--help") {
|
|
1106
1246
|
process.stdout.write(USAGE);
|
|
1107
|
-
} else if (!["eval", "scan", "case", "coverage", "lint", "check", "ablate", "cluster", "exec"].includes(argv[0])) {
|
|
1247
|
+
} else if (!["eval", "scan", "case", "coverage", "audit", "lint", "check", "ablate", "cluster", "exec"].includes(argv[0])) {
|
|
1108
1248
|
process.stderr.write(`unknown command "${argv[0]}"\n\n${USAGE}`);
|
|
1109
1249
|
process.exit(1);
|
|
1110
1250
|
} else if (argv[0] === "case" && argv[1] !== "add") {
|
|
@@ -1126,6 +1266,8 @@ if (isMain) {
|
|
|
1126
1266
|
? await runCaseAdd(options)
|
|
1127
1267
|
: argv[0] === "coverage"
|
|
1128
1268
|
? await runCoverage(options)
|
|
1269
|
+
: argv[0] === "audit"
|
|
1270
|
+
? await runAudit(options)
|
|
1129
1271
|
: argv[0] === "lint"
|
|
1130
1272
|
? await runLint(options)
|
|
1131
1273
|
: argv[0] === "check"
|