pkgxray 0.1.0 ā 0.2.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/bin/audit.js +39 -12
- package/package.json +1 -1
- package/src/providers/index.js +58 -1
package/bin/audit.js
CHANGED
|
@@ -5,6 +5,7 @@ const fs = require("node:fs");
|
|
|
5
5
|
const { auditEvidence, renderMarkdown } = require("../src/auditor");
|
|
6
6
|
const { guardExtension } = require("../src/quarantine");
|
|
7
7
|
const { reasonAbout } = require("../src/reasoner");
|
|
8
|
+
const { detectAvailableProvider, reasoningSetupHint } = require("../src/providers");
|
|
8
9
|
|
|
9
10
|
function printUsage() {
|
|
10
11
|
process.stderr.write(
|
|
@@ -19,11 +20,11 @@ function printUsage() {
|
|
|
19
20
|
"Evidence JSON fields:",
|
|
20
21
|
" packageName, npmMetadata, githubMetadata, webPresence, sourceFiles",
|
|
21
22
|
"",
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
23
|
+
"LLM reasoning runs automatically when any of ANTHROPIC_API_KEY,",
|
|
24
|
+
"OPENAI_API_KEY, or GEMINI_API_KEY is set AND the matching SDK is",
|
|
25
|
+
"installed (@anthropic-ai/sdk, openai, or @google/generative-ai).",
|
|
26
|
+
"Use --no-reason to force static-only, --reason-provider <name> to pin a",
|
|
27
|
+
"provider, --reason-model <id> to pin a model.",
|
|
27
28
|
""
|
|
28
29
|
].join("\n")
|
|
29
30
|
);
|
|
@@ -59,6 +60,9 @@ function parseArgs(argv) {
|
|
|
59
60
|
options.vulnerabilityCheck = false;
|
|
60
61
|
} else if (arg === "--reason") {
|
|
61
62
|
options.reason = true;
|
|
63
|
+
} else if (arg === "--no-reason") {
|
|
64
|
+
options.reason = false;
|
|
65
|
+
options.reasonExplicitOff = true;
|
|
62
66
|
} else if (arg === "--reason-model") {
|
|
63
67
|
options.reasonModel = argv[++i];
|
|
64
68
|
} else if (arg === "--reason-provider") {
|
|
@@ -82,11 +86,21 @@ function readInput(file) {
|
|
|
82
86
|
return fs.readFileSync(0, "utf8");
|
|
83
87
|
}
|
|
84
88
|
|
|
85
|
-
|
|
86
|
-
if (
|
|
89
|
+
function resolveReasonMode(options) {
|
|
90
|
+
if (options.reasonExplicitOff) return { enabled: false, autoDetected: false };
|
|
91
|
+
if (options.reason) return { enabled: true, autoDetected: false };
|
|
92
|
+
const detected = detectAvailableProvider();
|
|
93
|
+
if (detected) {
|
|
94
|
+
return { enabled: true, autoDetected: true, detectedProvider: detected.name };
|
|
95
|
+
}
|
|
96
|
+
return { enabled: false, autoDetected: false };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function maybeReason(evidence, options, mode) {
|
|
100
|
+
if (!mode || !mode.enabled) return null;
|
|
87
101
|
try {
|
|
88
102
|
return await reasonAbout(evidence, {
|
|
89
|
-
provider: options.reasonProvider,
|
|
103
|
+
provider: options.reasonProvider || mode.detectedProvider,
|
|
90
104
|
model: options.reasonModel,
|
|
91
105
|
maxFiles: options.reasonMaxFiles
|
|
92
106
|
});
|
|
@@ -114,7 +128,8 @@ async function main() {
|
|
|
114
128
|
throw new Error("guard requires an extension reference");
|
|
115
129
|
}
|
|
116
130
|
const result = await guardExtension(options.reference, options);
|
|
117
|
-
|
|
131
|
+
const reasonMode = resolveReasonMode(options);
|
|
132
|
+
if (reasonMode.enabled) {
|
|
118
133
|
const evidenceForReason = {
|
|
119
134
|
packageName: result.resolved && result.resolved.packageName,
|
|
120
135
|
npmMetadata: result.resolved && result.resolved.npmMetadata,
|
|
@@ -122,7 +137,7 @@ async function main() {
|
|
|
122
137
|
webPresence: null,
|
|
123
138
|
sourceFiles: result.sourceFiles || {}
|
|
124
139
|
};
|
|
125
|
-
const reasoning = await maybeReason(evidenceForReason, options);
|
|
140
|
+
const reasoning = await maybeReason(evidenceForReason, options, reasonMode);
|
|
126
141
|
result.reasoning = reasoning;
|
|
127
142
|
if (reasoning && !reasoning.error && reasoning.verdict) {
|
|
128
143
|
result.decision = reasoning.verdict === "block"
|
|
@@ -131,6 +146,8 @@ async function main() {
|
|
|
131
146
|
? "allow"
|
|
132
147
|
: "review";
|
|
133
148
|
}
|
|
149
|
+
} else {
|
|
150
|
+
result.reasoningHint = reasoningSetupHint();
|
|
134
151
|
}
|
|
135
152
|
if (options.format === "json") {
|
|
136
153
|
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
@@ -148,14 +165,22 @@ async function main() {
|
|
|
148
165
|
|
|
149
166
|
const evidence = JSON.parse(raw);
|
|
150
167
|
const report = auditEvidence(evidence);
|
|
151
|
-
const
|
|
168
|
+
const reasonMode = resolveReasonMode(options);
|
|
169
|
+
const reasoning = await maybeReason(evidence, options, reasonMode);
|
|
170
|
+
const hint = reasonMode.enabled ? null : reasoningSetupHint();
|
|
152
171
|
|
|
153
|
-
const payload = reasoning
|
|
172
|
+
const payload = reasoning
|
|
173
|
+
? { report, reasoning }
|
|
174
|
+
: hint
|
|
175
|
+
? { report, reasoningHint: hint }
|
|
176
|
+
: report;
|
|
154
177
|
|
|
155
178
|
if (options.format === "json") {
|
|
156
179
|
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
157
180
|
} else if (reasoning) {
|
|
158
181
|
process.stdout.write(`${renderMarkdown(report)}\n\n---\n\n${renderReasoningMarkdown(reasoning)}\n`);
|
|
182
|
+
} else if (hint) {
|
|
183
|
+
process.stdout.write(`${renderMarkdown(report)}\n\nš” ${hint}\n`);
|
|
159
184
|
} else {
|
|
160
185
|
process.stdout.write(`${renderMarkdown(report)}\n`);
|
|
161
186
|
}
|
|
@@ -182,6 +207,8 @@ function renderGuardMarkdown(result) {
|
|
|
182
207
|
|
|
183
208
|
if (result.reasoning) {
|
|
184
209
|
lines.push("", "---", "", renderReasoningMarkdown(result.reasoning));
|
|
210
|
+
} else if (result.reasoningHint) {
|
|
211
|
+
lines.push("", `š” ${result.reasoningHint}`);
|
|
185
212
|
}
|
|
186
213
|
|
|
187
214
|
return lines.join("\n");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pkgxray",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Local CLI and MCP server that audits AI-agent extensions and npm packages for supply-chain risk. Zero-dep static heuristics + sandboxed quarantine + optional multi-provider (Claude / GPT / Gemini) reasoning layer.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Jack Adams-Lovell",
|
package/src/providers/index.js
CHANGED
|
@@ -37,4 +37,61 @@ function resolveProvider({ provider, model } = {}) {
|
|
|
37
37
|
return anthropic;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
function tryLoadSdk(provider) {
|
|
41
|
+
try {
|
|
42
|
+
if (typeof provider._loadSdk === "function") {
|
|
43
|
+
provider._loadSdk();
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
// Each provider lazy-loads inside call(); fall back to a probe require here.
|
|
47
|
+
if (provider.name === "anthropic") require("@anthropic-ai/sdk");
|
|
48
|
+
else if (provider.name === "openai") require("openai");
|
|
49
|
+
else if (provider.name === "gemini") require("@google/generative-ai");
|
|
50
|
+
return true;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function detectAvailableProvider() {
|
|
57
|
+
// Priority order: anthropic, openai, gemini. First one with both env key set
|
|
58
|
+
// AND SDK loadable wins.
|
|
59
|
+
for (const name of ["anthropic", "openai", "gemini"]) {
|
|
60
|
+
const provider = PROVIDERS[name];
|
|
61
|
+
const keyPresent = Boolean(process.env[provider.envKey]);
|
|
62
|
+
if (!keyPresent) continue;
|
|
63
|
+
if (!tryLoadSdk(provider)) continue;
|
|
64
|
+
return provider;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function reasoningSetupHint() {
|
|
70
|
+
const missing = [];
|
|
71
|
+
for (const name of ["anthropic", "openai", "gemini"]) {
|
|
72
|
+
const provider = PROVIDERS[name];
|
|
73
|
+
if (process.env[provider.envKey]) {
|
|
74
|
+
if (!tryLoadSdk(provider)) {
|
|
75
|
+
const pkg = provider.name === "anthropic"
|
|
76
|
+
? "@anthropic-ai/sdk"
|
|
77
|
+
: provider.name === "openai"
|
|
78
|
+
? "openai"
|
|
79
|
+
: "@google/generative-ai";
|
|
80
|
+
return `${provider.envKey} is set but ${pkg} is not installed. Run: npm install -g ${pkg}`;
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
missing.push(provider.envKey);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return `For LLM-grade verdicts, set one of ${missing.join(" / ")} and install the matching SDK (@anthropic-ai/sdk, openai, or @google/generative-ai).`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = {
|
|
90
|
+
PROVIDERS,
|
|
91
|
+
listProviders,
|
|
92
|
+
getProvider,
|
|
93
|
+
detectProvider,
|
|
94
|
+
resolveProvider,
|
|
95
|
+
detectAvailableProvider,
|
|
96
|
+
reasoningSetupHint
|
|
97
|
+
};
|