@chimix/agentscan 0.3.0 → 0.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 +12 -1
- package/dist/cli.js +134 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -164,6 +164,7 @@ Flags for `check`:
|
|
|
164
164
|
| `--json` | JSON report (alias for `--output json`) |
|
|
165
165
|
| `--output <format>` | `human` (default) · `json` · `prompt` |
|
|
166
166
|
| `--copy` | Also copy the report to the system clipboard |
|
|
167
|
+
| `--no-color` | Never colour, even on a terminal (`NO_COLOR=1` does the same) |
|
|
167
168
|
| `--quiet` | Summary line only |
|
|
168
169
|
| `--verbose` | Show KEEP + info-severity findings |
|
|
169
170
|
| `--fail-on <level>` | `never` (default) · `warning` · `error` |
|
|
@@ -176,7 +177,12 @@ Flags for `check`:
|
|
|
176
177
|
## Sample output
|
|
177
178
|
|
|
178
179
|
```text
|
|
179
|
-
|
|
180
|
+
┌─────┐ 40/100 broken · touchagency
|
|
181
|
+
│ ✕ ✕ │ ██████████░░░░░░░░░░░░░░░
|
|
182
|
+
│ ⌒ │ 6 warn · 4 info hidden (--verbose)
|
|
183
|
+
└─────┘
|
|
184
|
+
|
|
185
|
+
agentscan v0.4.0 — touchagency
|
|
180
186
|
|
|
181
187
|
Scanned: 46 deps · 54 skills · 1 mcp · 2 agents · packageManager=bun
|
|
182
188
|
|
|
@@ -190,6 +196,11 @@ WARN rule:hook.missing-script ×6
|
|
|
190
196
|
Summary: 6 warn · 4 info hidden (--verbose) · score 40/100 broken
|
|
191
197
|
```
|
|
192
198
|
|
|
199
|
+
On a terminal the header is coloured green, yellow or red and the face tracks
|
|
200
|
+
the score. Redirect or pipe it and you get exactly the text above, with no
|
|
201
|
+
escape sequences — the header box is dropped too, so `--json`, `--output
|
|
202
|
+
prompt`, CI logs and `--copy` are unchanged by any of this.
|
|
203
|
+
|
|
193
204
|
The `Stack:` line is orientation only — which project, how big. The summary lists
|
|
194
205
|
only actions that actually occurred; a clean project prints `Summary: no findings`.
|
|
195
206
|
|
package/dist/cli.js
CHANGED
|
@@ -12549,6 +12549,30 @@ function scoreLabel(points) {
|
|
|
12549
12549
|
}
|
|
12550
12550
|
return "broken";
|
|
12551
12551
|
}
|
|
12552
|
+
function scoreTone(points) {
|
|
12553
|
+
if (points >= 90) {
|
|
12554
|
+
return "green";
|
|
12555
|
+
}
|
|
12556
|
+
if (points >= 50) {
|
|
12557
|
+
return "yellow";
|
|
12558
|
+
}
|
|
12559
|
+
return "red";
|
|
12560
|
+
}
|
|
12561
|
+
function scoreFace(points) {
|
|
12562
|
+
if (points === 100) {
|
|
12563
|
+
return ["◠ ◠", " ▽ "];
|
|
12564
|
+
}
|
|
12565
|
+
if (points >= 90) {
|
|
12566
|
+
return ["• •", " ‿ "];
|
|
12567
|
+
}
|
|
12568
|
+
if (points >= 70) {
|
|
12569
|
+
return ["• •", " ─ "];
|
|
12570
|
+
}
|
|
12571
|
+
if (points >= 50) {
|
|
12572
|
+
return ["◡ ◡", " ⌒ "];
|
|
12573
|
+
}
|
|
12574
|
+
return ["✕ ✕", " ⌒ "];
|
|
12575
|
+
}
|
|
12552
12576
|
|
|
12553
12577
|
// src/report/exit-code.ts
|
|
12554
12578
|
function exitCode(findings, failOn, failUnder) {
|
|
@@ -12673,6 +12697,50 @@ function headingFor(severity) {
|
|
|
12673
12697
|
|
|
12674
12698
|
// src/report/text.ts
|
|
12675
12699
|
import { basename as basename4 } from "node:path";
|
|
12700
|
+
|
|
12701
|
+
// src/report/ansi.ts
|
|
12702
|
+
var CODES = {
|
|
12703
|
+
green: "\x1B[32m",
|
|
12704
|
+
yellow: "\x1B[33m",
|
|
12705
|
+
red: "\x1B[31m",
|
|
12706
|
+
dim: "\x1B[2m",
|
|
12707
|
+
bold: "\x1B[1m",
|
|
12708
|
+
reset: "\x1B[0m"
|
|
12709
|
+
};
|
|
12710
|
+
var PLAIN = {
|
|
12711
|
+
tone: (_tone, text) => text,
|
|
12712
|
+
strong: (_tone, text) => text,
|
|
12713
|
+
dim: (text) => text,
|
|
12714
|
+
bold: (text) => text,
|
|
12715
|
+
enabled: false
|
|
12716
|
+
};
|
|
12717
|
+
function paint(enabled) {
|
|
12718
|
+
if (!enabled) {
|
|
12719
|
+
return PLAIN;
|
|
12720
|
+
}
|
|
12721
|
+
return {
|
|
12722
|
+
tone: (tone, text) => `${CODES[tone]}${text}${CODES.reset}`,
|
|
12723
|
+
strong: (tone, text) => `${CODES.bold}${CODES[tone]}${text}${CODES.reset}`,
|
|
12724
|
+
dim: (text) => `${CODES.dim}${text}${CODES.reset}`,
|
|
12725
|
+
bold: (text) => `${CODES.bold}${text}${CODES.reset}`,
|
|
12726
|
+
enabled: true
|
|
12727
|
+
};
|
|
12728
|
+
}
|
|
12729
|
+
function shouldColour(args) {
|
|
12730
|
+
if (args.noColorFlag === true) {
|
|
12731
|
+
return false;
|
|
12732
|
+
}
|
|
12733
|
+
const { NO_COLOR, FORCE_COLOR } = args.env;
|
|
12734
|
+
if (NO_COLOR !== undefined && NO_COLOR !== "") {
|
|
12735
|
+
return false;
|
|
12736
|
+
}
|
|
12737
|
+
if (FORCE_COLOR !== undefined && FORCE_COLOR !== "" && FORCE_COLOR !== "0") {
|
|
12738
|
+
return true;
|
|
12739
|
+
}
|
|
12740
|
+
return args.isTTY;
|
|
12741
|
+
}
|
|
12742
|
+
|
|
12743
|
+
// src/report/text.ts
|
|
12676
12744
|
var ACTION_LABEL = {
|
|
12677
12745
|
keep: "KEEP",
|
|
12678
12746
|
delete: "DELETE",
|
|
@@ -12686,24 +12754,28 @@ function actionColumn(action) {
|
|
|
12686
12754
|
}
|
|
12687
12755
|
function renderText(args) {
|
|
12688
12756
|
const { version, facts, verbose, quiet } = args;
|
|
12757
|
+
const ink = paint(args.colour === true);
|
|
12689
12758
|
const sorted = sortFindings(args.findings);
|
|
12690
12759
|
const visible = verbose ? sorted : sorted.filter((f) => f.action !== "keep" && f.severity !== "info");
|
|
12691
|
-
const summary = formatSummary(sorted, verbose);
|
|
12760
|
+
const summary = formatSummary(sorted, verbose, ink);
|
|
12692
12761
|
if (quiet) {
|
|
12693
12762
|
return `${summary}
|
|
12694
12763
|
`;
|
|
12695
12764
|
}
|
|
12696
12765
|
const lines = [];
|
|
12697
12766
|
const name = projectLabel(facts.root);
|
|
12698
|
-
|
|
12767
|
+
if (ink.enabled) {
|
|
12768
|
+
lines.push(...formatHeaderBox(sorted, verbose, safe(name), ink));
|
|
12769
|
+
}
|
|
12770
|
+
lines.push(ink.dim(`agentscan v${version} — ${safe(name)}`));
|
|
12699
12771
|
if (args.resolvedFrom !== undefined) {
|
|
12700
12772
|
lines.push(` no package.json in ${safe(args.resolvedFrom)} — scanned ${safe(facts.root)}`);
|
|
12701
12773
|
}
|
|
12702
12774
|
lines.push("");
|
|
12703
|
-
lines.push(`Scanned: ${formatStack(facts)}`);
|
|
12775
|
+
lines.push(ink.dim(`Scanned: ${formatStack(facts)}`));
|
|
12704
12776
|
lines.push("");
|
|
12705
12777
|
for (const group of groupByRule(visible)) {
|
|
12706
|
-
lines.push(...formatGroup(group, facts.root));
|
|
12778
|
+
lines.push(...formatGroup(group, facts.root, ink));
|
|
12707
12779
|
lines.push("");
|
|
12708
12780
|
}
|
|
12709
12781
|
lines.push(summary);
|
|
@@ -12711,6 +12783,22 @@ function renderText(args) {
|
|
|
12711
12783
|
return lines.join(`
|
|
12712
12784
|
`);
|
|
12713
12785
|
}
|
|
12786
|
+
var BAR_CELLS = 25;
|
|
12787
|
+
function formatHeaderBox(findings, verbose, name, ink) {
|
|
12788
|
+
const points = score(findings);
|
|
12789
|
+
const tone = scoreTone(points);
|
|
12790
|
+
const [eyes, mouth] = scoreFace(points);
|
|
12791
|
+
const filled = Math.round(points / 100 * BAR_CELLS);
|
|
12792
|
+
const bar = ink.tone(tone, "█".repeat(filled)) + ink.dim("░".repeat(BAR_CELLS - filled));
|
|
12793
|
+
const verdict = `${ink.strong(tone, `${points}/100`)} ${ink.tone(tone, scoreLabel(points))}${ink.dim(` · ${name}`)}`;
|
|
12794
|
+
return [
|
|
12795
|
+
` ${ink.tone(tone, "┌─────┐")} ${verdict}`,
|
|
12796
|
+
` ${ink.tone(tone, `│ ${eyes} │`)} ${bar}`,
|
|
12797
|
+
` ${ink.tone(tone, `│ ${mouth} │`)} ${ink.dim(countsLine(findings, verbose))}`,
|
|
12798
|
+
` ${ink.tone(tone, "└─────┘")}`,
|
|
12799
|
+
""
|
|
12800
|
+
];
|
|
12801
|
+
}
|
|
12714
12802
|
function projectLabel(root) {
|
|
12715
12803
|
const base = basename4(root);
|
|
12716
12804
|
return base.length > 0 ? base : root;
|
|
@@ -12742,16 +12830,25 @@ function groupByRule(findings) {
|
|
|
12742
12830
|
function shortPath(value, root) {
|
|
12743
12831
|
return value.split(`${root}/`).join("");
|
|
12744
12832
|
}
|
|
12745
|
-
|
|
12833
|
+
var ACTION_TONE = {
|
|
12834
|
+
delete: "red",
|
|
12835
|
+
warn: "yellow",
|
|
12836
|
+
drift: "yellow",
|
|
12837
|
+
add: "green",
|
|
12838
|
+
refresh: "green"
|
|
12839
|
+
};
|
|
12840
|
+
function formatGroup(group, root, ink) {
|
|
12746
12841
|
const first = group[0];
|
|
12747
12842
|
const count = group.length;
|
|
12748
12843
|
const out = [];
|
|
12749
|
-
|
|
12844
|
+
const tone = ACTION_TONE[first.action];
|
|
12845
|
+
const label = actionColumn(first.action);
|
|
12846
|
+
out.push(`${tone === undefined ? ink.dim(label) : ink.tone(tone, label)} ${ink.dim(`rule:${safe(first.ruleId)}`)}${count > 1 ? ink.bold(` ×${count}`) : ""}`);
|
|
12750
12847
|
out.push(` ${safe(count === 1 ? first.message : ruleHeadline(first))}`);
|
|
12751
12848
|
for (const f of group) {
|
|
12752
12849
|
const where = f.evidence.filter((e) => e.kind !== "count" && e.kind !== "threshold").map((e) => shortPath(e.value, root));
|
|
12753
12850
|
const location = where.length > 0 ? where.join(" · ") : f.subject;
|
|
12754
|
-
out.push(` ${safe(shortPath(location, root))}`);
|
|
12851
|
+
out.push(ink.dim(` ${safe(shortPath(location, root))}`));
|
|
12755
12852
|
}
|
|
12756
12853
|
return out;
|
|
12757
12854
|
}
|
|
@@ -12759,7 +12856,11 @@ function ruleHeadline(f) {
|
|
|
12759
12856
|
const cut = f.message.indexOf(": ");
|
|
12760
12857
|
return cut === -1 ? f.message : f.message.slice(0, cut);
|
|
12761
12858
|
}
|
|
12762
|
-
function
|
|
12859
|
+
function countsLine(findings, verbose) {
|
|
12860
|
+
const parts = actionParts(findings, verbose);
|
|
12861
|
+
return parts.length === 0 ? "no findings" : parts.join(" · ");
|
|
12862
|
+
}
|
|
12863
|
+
function actionParts(findings, verbose) {
|
|
12763
12864
|
const counts = {
|
|
12764
12865
|
keep: 0,
|
|
12765
12866
|
delete: 0,
|
|
@@ -12781,8 +12882,12 @@ function formatSummary(findings, verbose) {
|
|
|
12781
12882
|
if (!verbose && infoHidden > 0) {
|
|
12782
12883
|
parts.push(`${infoHidden} info hidden (--verbose)`);
|
|
12783
12884
|
}
|
|
12885
|
+
return parts;
|
|
12886
|
+
}
|
|
12887
|
+
function formatSummary(findings, verbose, ink) {
|
|
12888
|
+
const parts = actionParts(findings, verbose);
|
|
12784
12889
|
const points = score(findings);
|
|
12785
|
-
const scored = `score ${points}/100 ${scoreLabel(points)}`;
|
|
12890
|
+
const scored = `score ${ink.tone(scoreTone(points), `${points}/100 ${scoreLabel(points)}`)}`;
|
|
12786
12891
|
if (parts.length === 0) {
|
|
12787
12892
|
return `Summary: no findings · ${scored}`;
|
|
12788
12893
|
}
|
|
@@ -12790,7 +12895,7 @@ function formatSummary(findings, verbose) {
|
|
|
12790
12895
|
}
|
|
12791
12896
|
|
|
12792
12897
|
// src/version.ts
|
|
12793
|
-
var VERSION = "0.
|
|
12898
|
+
var VERSION = "0.4.0";
|
|
12794
12899
|
|
|
12795
12900
|
// src/commands/check.ts
|
|
12796
12901
|
async function runCheck(options) {
|
|
@@ -12802,23 +12907,30 @@ async function runCheck(options) {
|
|
|
12802
12907
|
});
|
|
12803
12908
|
const format = options.output ?? (options.json === true ? "json" : "human");
|
|
12804
12909
|
let stdout;
|
|
12910
|
+
let plain;
|
|
12805
12911
|
if (format === "json") {
|
|
12806
12912
|
stdout = renderJson({ version: VERSION, root, facts, findings, resolvedFrom });
|
|
12807
12913
|
} else if (format === "prompt") {
|
|
12808
12914
|
stdout = renderPrompt({ version: VERSION, facts, findings });
|
|
12809
12915
|
} else {
|
|
12810
|
-
|
|
12916
|
+
const text = (colour) => renderText({
|
|
12811
12917
|
version: VERSION,
|
|
12812
12918
|
facts,
|
|
12813
12919
|
findings,
|
|
12814
12920
|
resolvedFrom,
|
|
12815
12921
|
verbose: options.verbose ?? false,
|
|
12816
|
-
quiet: options.quiet ?? false
|
|
12922
|
+
quiet: options.quiet ?? false,
|
|
12923
|
+
colour
|
|
12817
12924
|
});
|
|
12925
|
+
stdout = text(options.colour === true);
|
|
12926
|
+
if (options.colour === true) {
|
|
12927
|
+
plain = text(false);
|
|
12928
|
+
}
|
|
12818
12929
|
}
|
|
12819
12930
|
return {
|
|
12820
12931
|
exitCode: exitCode(findings, config.failOn, options.failUnder),
|
|
12821
|
-
stdout
|
|
12932
|
+
stdout,
|
|
12933
|
+
...plain === undefined ? {} : { plain }
|
|
12822
12934
|
};
|
|
12823
12935
|
}
|
|
12824
12936
|
|
|
@@ -12987,6 +13099,7 @@ check options:
|
|
|
12987
13099
|
--output <format> human (default) | json | prompt
|
|
12988
13100
|
prompt: a paste-ready handoff for a fixing agent
|
|
12989
13101
|
--copy Also copy the report to the system clipboard
|
|
13102
|
+
--no-color Never colour, even on a terminal (also: NO_COLOR=1)
|
|
12990
13103
|
--quiet Summary only
|
|
12991
13104
|
--verbose Show KEEP findings
|
|
12992
13105
|
--fail-on <level> never | warning | error (default: never)
|
|
@@ -13016,6 +13129,7 @@ async function main(argv) {
|
|
|
13016
13129
|
json: { type: "boolean", default: false },
|
|
13017
13130
|
output: { type: "string" },
|
|
13018
13131
|
copy: { type: "boolean", default: false },
|
|
13132
|
+
"no-color": { type: "boolean", default: false },
|
|
13019
13133
|
quiet: { type: "boolean", default: false },
|
|
13020
13134
|
verbose: { type: "boolean", default: false },
|
|
13021
13135
|
"fail-on": { type: "string" },
|
|
@@ -13082,11 +13196,16 @@ async function main(argv) {
|
|
|
13082
13196
|
failOn: failOnRaw,
|
|
13083
13197
|
...failUnder === undefined ? {} : { failUnder },
|
|
13084
13198
|
global: values.global,
|
|
13085
|
-
configPath: values.config
|
|
13199
|
+
configPath: values.config,
|
|
13200
|
+
colour: shouldColour({
|
|
13201
|
+
isTTY: process.stdout.isTTY === true,
|
|
13202
|
+
env: process.env,
|
|
13203
|
+
noColorFlag: values["no-color"]
|
|
13204
|
+
})
|
|
13086
13205
|
});
|
|
13087
13206
|
process.stdout.write(result.stdout);
|
|
13088
13207
|
if (values.copy === true) {
|
|
13089
|
-
const copied = await copyToClipboard(result.stdout);
|
|
13208
|
+
const copied = await copyToClipboard(result.plain ?? result.stdout);
|
|
13090
13209
|
process.stderr.write(copied.ok ? `Copied to clipboard via ${copied.tool}.
|
|
13091
13210
|
` : `Could not copy: ${safe(copied.reason)}. Redirect instead: agentscan check > report.md
|
|
13092
13211
|
`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chimix/agentscan",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Find broken agent configuration in a project — hooks whose scripts are gone, MCP servers that cannot start, skills that disagree with their lockfile",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|