agentverify 1.2.0 → 1.3.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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Execution trust analysis for AI agents — CLI and SDK**
4
4
 
5
- Scan AI agent configurations for security issues, credential exposure, and governance gaps. Results save automatically to your Agent Verify dashboard.
5
+ Scan AI agent configurations for security issues, credential exposure, and governance gaps. Saved CLI reports appear in your Agent Verify dashboard when API report save succeeds.
6
6
 
7
7
  ## Install
8
8
 
@@ -37,6 +37,20 @@ agentverify scan ./agents
37
37
  agentverify scan . --key av_your_key --json
38
38
  ```
39
39
 
40
+ ### Markdown summary
41
+
42
+ ```bash
43
+ agentverify scan ./agents --markdown
44
+ ```
45
+
46
+ ### CI mode
47
+
48
+ ```bash
49
+ agentverify scan ./agents --ci
50
+ ```
51
+
52
+ Normal scans print findings without failing the shell just because issues exist. CI mode is stricter: it exits non-zero when an agent is `NOT_VERIFIED`, when a scan errors, or when submitted content cannot be assessed.
53
+
40
54
  ## SDK Usage
41
55
 
42
56
  ```ts
@@ -51,7 +65,7 @@ const result = await scan(
51
65
  { apiKey: process.env.AGENTVERIFY_API_KEY }
52
66
  )
53
67
 
54
- result.verdict // 'VERIFIED' | 'NOT VERIFIED'
68
+ result.verdict // 'VERIFIED' | 'NOT_VERIFIED' | 'NOT_ASSESSED'
55
69
  result.riskScore // 0-100
56
70
  result.findings // Array of security findings
57
71
  ```
@@ -73,10 +87,28 @@ const results = await scanMany(
73
87
  ### GitHub Actions
74
88
 
75
89
  ```yaml
76
- - name: Scan agents
77
- run: npx agentverify scan ./agents --key ${{ secrets.AGENTVERIFY_API_KEY }} --json
90
+ - name: Agent Verify
91
+ run: npx agentverify scan ./agents --ci
92
+ env:
93
+ AGENTVERIFY_API_KEY: ${{ secrets.AGENTVERIFY_API_KEY }}
78
94
  ```
79
95
 
96
+ Use this in pull requests to block merges when an agent is `NOT_VERIFIED`. The CLI prints score, verdict, risk level, top blocker, fix-first guidance, threat categories, A2SPA docs, and the canonical report URL:
97
+
98
+ ```text
99
+ https://aimodularity.com/agentverify/report/?id=REPORT_ID
100
+ ```
101
+
102
+ For PR comments, run with `--markdown` and post the output with your preferred GitHub Action. For machine-readable pipelines, run with `--json`.
103
+
104
+ The workflow requires a valid `AGENTVERIFY_API_KEY`. The local Worker code path fails closed for missing, malformed, invalid, disabled, or revoked keys before findings are returned; validate the deployed API path before relying on this as production enforcement. Wire the workflow into branch protection so the non-zero `--ci` exit code blocks merges when an agent is `NOT_VERIFIED`, not assessed, or the API key is unauthorized.
105
+
106
+ Saved CLI reports sync to the dashboard through the API `cliReports` save path. If the API cannot save the report, the CLI still prints local scan output and tells you dashboard sync did not complete.
107
+
108
+ Verified reports can expose a README badge from the report page. A packaged Agent Verify GitHub Action with automatic PR comments is planned after the v1.3.0 app/CLI release candidate stabilizes.
109
+
110
+ See the full GitHub Action guide at `docs/github-action.md` for composite action inputs, JSON/Markdown examples, branch protection, optional PR comments, and badge guidance.
111
+
80
112
  ## Get your API key
81
113
 
82
114
  Sign up at https://aimodularity.com/agentverify/dashboard and go to Dashboard -> API
@@ -1,6 +1,56 @@
1
1
  // src/sdk.ts
2
2
  var DEFAULT_API_URL = "https://agentverify-api.agentverify.workers.dev/v1/scan";
3
3
  var DEFAULT_TIMEOUT = 3e4;
4
+ function normalizeVerdict(value) {
5
+ if (value === "VERIFIED") return "VERIFIED";
6
+ if (value === "NOT_ASSESSED" || value === "NOT ASSESSED") return "NOT_ASSESSED";
7
+ return "NOT_VERIFIED";
8
+ }
9
+ function normalizeScanResult(data) {
10
+ const findings = Array.isArray(data.findings) ? data.findings : [];
11
+ const riskScore = typeof data.riskScore === "number" ? data.riskScore : 0;
12
+ return {
13
+ ...data,
14
+ reportId: typeof data.reportId === "string" ? data.reportId : "",
15
+ verdict: normalizeVerdict(data.verdict),
16
+ riskScore,
17
+ riskLevel: data.riskLevel ?? "High Risk",
18
+ confidence: typeof data.confidence === "number" ? data.confidence : riskScore > 0 ? 80 : 0,
19
+ optimizationScore: typeof data.optimizationScore === "number" ? data.optimizationScore : 0,
20
+ reportInsights: data.reportInsights,
21
+ threatCategories: Array.isArray(data.threatCategories) ? data.threatCategories : [],
22
+ findings,
23
+ categoryScores: Array.isArray(data.categoryScores) ? data.categoryScores : [],
24
+ bom: data.bom ?? {
25
+ detectedLanguage: "Unknown",
26
+ detectedFramework: null,
27
+ detectedPlatform: null,
28
+ agentName: null,
29
+ toolAccessLevel: "Unknown",
30
+ credentialExposure: "Not Detected",
31
+ memoryPersistence: "Unknown",
32
+ auditLogging: "Unknown",
33
+ humanGates: "Unknown",
34
+ rateLimiting: "Unknown",
35
+ promptInjectionSurface: "Unknown",
36
+ delegationScope: "Unknown"
37
+ },
38
+ metadata: data.metadata ?? {
39
+ schemaVersion: "1.3.0",
40
+ scannerVersion: "unknown",
41
+ fileName: "unknown",
42
+ fileSize: 0,
43
+ scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
44
+ detectedLanguage: "Unknown",
45
+ detectedFramework: null,
46
+ selectedPlatform: null,
47
+ agentName: null,
48
+ scanDuration: 0
49
+ },
50
+ saved: data.saved === true,
51
+ reportUrl: data.reportUrl ?? null
52
+ };
53
+ }
4
54
  async function scan(input, options) {
5
55
  const { apiKey, apiUrl = DEFAULT_API_URL, timeout = DEFAULT_TIMEOUT } = options;
6
56
  if (!apiKey) {
@@ -30,7 +80,7 @@ async function scan(input, options) {
30
80
  });
31
81
  if (response.status === 401) {
32
82
  throw new Error(
33
- "Invalid API key. Generate one at https://aimodularity.com/agentverify/dashboard"
83
+ "Invalid or unauthorized Agent Verify API key. Generate a valid key at https://aimodularity.com/agentverify/dashboard/"
34
84
  );
35
85
  }
36
86
  if (response.status === 400) {
@@ -41,7 +91,7 @@ async function scan(input, options) {
41
91
  throw new Error(`API error: ${response.status} ${response.statusText}`);
42
92
  }
43
93
  const data = await response.json();
44
- return data;
94
+ return normalizeScanResult(data);
45
95
  } catch (err) {
46
96
  if (err instanceof Error && err.name === "AbortError") {
47
97
  throw new Error(`Request timed out after ${timeout}ms`);
package/dist/cli.js CHANGED
@@ -8,6 +8,56 @@ var import_path = require("path");
8
8
  // src/sdk.ts
9
9
  var DEFAULT_API_URL = "https://agentverify-api.agentverify.workers.dev/v1/scan";
10
10
  var DEFAULT_TIMEOUT = 3e4;
11
+ function normalizeVerdict(value) {
12
+ if (value === "VERIFIED") return "VERIFIED";
13
+ if (value === "NOT_ASSESSED" || value === "NOT ASSESSED") return "NOT_ASSESSED";
14
+ return "NOT_VERIFIED";
15
+ }
16
+ function normalizeScanResult(data) {
17
+ const findings = Array.isArray(data.findings) ? data.findings : [];
18
+ const riskScore = typeof data.riskScore === "number" ? data.riskScore : 0;
19
+ return {
20
+ ...data,
21
+ reportId: typeof data.reportId === "string" ? data.reportId : "",
22
+ verdict: normalizeVerdict(data.verdict),
23
+ riskScore,
24
+ riskLevel: data.riskLevel ?? "High Risk",
25
+ confidence: typeof data.confidence === "number" ? data.confidence : riskScore > 0 ? 80 : 0,
26
+ optimizationScore: typeof data.optimizationScore === "number" ? data.optimizationScore : 0,
27
+ reportInsights: data.reportInsights,
28
+ threatCategories: Array.isArray(data.threatCategories) ? data.threatCategories : [],
29
+ findings,
30
+ categoryScores: Array.isArray(data.categoryScores) ? data.categoryScores : [],
31
+ bom: data.bom ?? {
32
+ detectedLanguage: "Unknown",
33
+ detectedFramework: null,
34
+ detectedPlatform: null,
35
+ agentName: null,
36
+ toolAccessLevel: "Unknown",
37
+ credentialExposure: "Not Detected",
38
+ memoryPersistence: "Unknown",
39
+ auditLogging: "Unknown",
40
+ humanGates: "Unknown",
41
+ rateLimiting: "Unknown",
42
+ promptInjectionSurface: "Unknown",
43
+ delegationScope: "Unknown"
44
+ },
45
+ metadata: data.metadata ?? {
46
+ schemaVersion: "1.3.0",
47
+ scannerVersion: "unknown",
48
+ fileName: "unknown",
49
+ fileSize: 0,
50
+ scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
51
+ detectedLanguage: "Unknown",
52
+ detectedFramework: null,
53
+ selectedPlatform: null,
54
+ agentName: null,
55
+ scanDuration: 0
56
+ },
57
+ saved: data.saved === true,
58
+ reportUrl: data.reportUrl ?? null
59
+ };
60
+ }
11
61
  async function scan(input, options) {
12
62
  const { apiKey, apiUrl = DEFAULT_API_URL, timeout = DEFAULT_TIMEOUT } = options;
13
63
  if (!apiKey) {
@@ -37,7 +87,7 @@ async function scan(input, options) {
37
87
  });
38
88
  if (response.status === 401) {
39
89
  throw new Error(
40
- "Invalid API key. Generate one at https://aimodularity.com/agentverify/dashboard"
90
+ "Invalid or unauthorized Agent Verify API key. Generate a valid key at https://aimodularity.com/agentverify/dashboard/"
41
91
  );
42
92
  }
43
93
  if (response.status === 400) {
@@ -48,7 +98,7 @@ async function scan(input, options) {
48
98
  throw new Error(`API error: ${response.status} ${response.statusText}`);
49
99
  }
50
100
  const data = await response.json();
51
- return data;
101
+ return normalizeScanResult(data);
52
102
  } catch (err) {
53
103
  if (err instanceof Error && err.name === "AbortError") {
54
104
  throw new Error(`Request timed out after ${timeout}ms`);
@@ -60,8 +110,9 @@ async function scan(input, options) {
60
110
  }
61
111
 
62
112
  // src/cli.ts
63
- var VERSION = "1.2.0";
64
- var API_URL = "https://agentverify-api.agentverify.workers.dev/v1/scan";
113
+ var VERSION = "1.3.0";
114
+ var API_URL = process.env.AGENTVERIFY_API_URL ?? "https://agentverify-api.agentverify.workers.dev/v1/scan";
115
+ var A2SPA_DOCS_URL = "https://aimodularity.com/A2SPA/docs";
65
116
  var c = {
66
117
  reset: "\x1B[0m",
67
118
  bold: "\x1B[1m",
@@ -103,6 +154,9 @@ function parseArgs(argv) {
103
154
  key: "",
104
155
  file: "",
105
156
  json: false,
157
+ markdown: false,
158
+ ci: false,
159
+ allowNotAssessed: false,
106
160
  help: false,
107
161
  version: false
108
162
  };
@@ -119,6 +173,9 @@ function parseArgs(argv) {
119
173
  if (arg === "--key" || arg === "-k") result.key = args[++i] ?? "";
120
174
  else if (arg === "--file" || arg === "-f") result.file = args[++i] ?? "";
121
175
  else if (arg === "--json") result.json = true;
176
+ else if (arg === "--markdown") result.markdown = true;
177
+ else if (arg === "--ci") result.ci = true;
178
+ else if (arg === "--allow-not-assessed") result.allowNotAssessed = true;
122
179
  else if (arg === "--help" || arg === "-h") result.help = true;
123
180
  else if (arg === "--version" || arg === "-v") result.version = true;
124
181
  else if (!arg.startsWith("-")) result.dir = arg;
@@ -142,6 +199,10 @@ ${c.bold}Options:${c.reset}
142
199
  --key, -k API key (or set AGENTVERIFY_API_KEY env var)
143
200
  --file, -f Scan a single file instead of a directory
144
201
  --json Output results as JSON
202
+ --markdown Output a Markdown summary
203
+ --ci CI mode: concise output and non-zero exit for NOT VERIFIED
204
+ --allow-not-assessed
205
+ In CI mode, do not fail only because content is NOT_ASSESSED
145
206
  --version, -v Print version
146
207
  --help, -h Show this help message
147
208
 
@@ -158,17 +219,81 @@ ${c.bold}Examples:${c.reset}
158
219
  ${c.gray}# Output as JSON (useful for CI/CD)${c.reset}
159
220
  agentverify scan . --key av_your_key --json
160
221
 
222
+ ${c.gray}# Markdown summary for pull requests${c.reset}
223
+ agentverify scan . --key av_your_key --markdown
224
+
161
225
  ${c.bold}Get your API key:${c.reset}
162
226
  https://aimodularity.com/agentverify/dashboard
163
227
 
164
228
  ${c.bold}Docs:${c.reset}
165
229
  https://github.com/AI-Blockchain-Ventures/agentverify
230
+
231
+ ${c.bold}A2SPA docs:${c.reset}
232
+ ${A2SPA_DOCS_URL}
166
233
  `);
167
234
  }
235
+ function getTopBlocker(result) {
236
+ return result.reportInsights?.topBlocker ?? result.findings[0]?.title ?? "No blocking finding detected";
237
+ }
238
+ function getFixFirst(result) {
239
+ const fromInsights = result.reportInsights?.fixPriority?.filter((item) => item.priority === "fix_first").map((item) => item.title) ?? [];
240
+ const fallback = result.findings.slice(0, 3).map((item) => item.title);
241
+ return (fromInsights.length ? fromInsights : fallback).slice(0, 3);
242
+ }
243
+ function getRelevantThreatCategories(result) {
244
+ return (result.threatCategories ?? []).filter((item) => item.status !== "not_assessed").slice(0, 6).map((item) => `${item.label} (${item.status.replace("_", " ")})`);
245
+ }
246
+ function printDetailedSummary(result, fileName) {
247
+ const verified = result.verdict === "VERIFIED";
248
+ const verdictColor = verified ? c.green : result.verdict === "NOT_ASSESSED" ? c.yellow : c.red;
249
+ write(`
250
+ ${c.bold}${c.cyan}Agent Verify${c.reset}`);
251
+ write(`${c.gray}${fileName}${c.reset}`);
252
+ write(`${c.bold}Score:${c.reset} ${result.riskScore}/100`);
253
+ write(`${c.bold}Verdict:${c.reset} ${verdictColor}${result.verdict === "NOT_VERIFIED" ? "NOT VERIFIED" : result.verdict.replace("_", " ")}${c.reset}`);
254
+ write(`${c.bold}Risk level:${c.reset} ${result.riskLevel}`);
255
+ write(`${c.bold}Confidence:${c.reset} ${result.confidence}/100`);
256
+ write(`${c.bold}Top blocker:${c.reset} ${getTopBlocker(result)}`);
257
+ write(`
258
+ ${c.bold}Fix first:${c.reset}`);
259
+ const fixes = getFixFirst(result);
260
+ if (fixes.length) fixes.forEach((item, index) => write(` ${index + 1}. ${item}`));
261
+ else write(` No blocking fixes detected. Re-scan after any material change.`);
262
+ if (result.reportInsights?.nextAction) write(`
263
+ ${c.bold}Next action:${c.reset} ${result.reportInsights.nextAction}`);
264
+ const threats = getRelevantThreatCategories(result);
265
+ if (threats.length) {
266
+ write(`
267
+ ${c.bold}Threat categories:${c.reset}`);
268
+ threats.forEach((item) => write(` - ${item}`));
269
+ }
270
+ if (result.saved && result.reportId) write(`${c.bold}View report:${c.reset} https://aimodularity.com/agentverify/report/?id=${result.reportId}`);
271
+ write(`${c.bold}A2SPA docs:${c.reset} ${A2SPA_DOCS_URL}`);
272
+ write(`${c.gray}Re-scan:${c.reset} agentverify scan --file ${fileName} --key $AGENTVERIFY_API_KEY
273
+ `);
274
+ }
275
+ function markdownSummary(results, summary) {
276
+ const lines = ["# Agent Verify", "", `Files scanned: ${summary.total}`, `Verified: ${summary.verified}`, `Not verified: ${summary.notVerified}`, `Not assessed: ${summary.notAssessed}`, `Errors: ${summary.errors}`, ""];
277
+ for (const item of results) {
278
+ if (!item.result) {
279
+ lines.push(`## ${item.file}`, "", `Error: ${item.error ?? "Unknown error"}`, "");
280
+ continue;
281
+ }
282
+ lines.push(`## ${item.file}`, "", `Score: ${item.result.riskScore}/100`, `Verdict: ${item.result.verdict === "NOT_VERIFIED" ? "NOT VERIFIED" : item.result.verdict.replace("_", " ")}`, `Risk level: ${item.result.riskLevel}`, `Top blocker: ${getTopBlocker(item.result)}`, "");
283
+ const fixes = getFixFirst(item.result);
284
+ if (fixes.length) lines.push("Fix first:", ...fixes.map((fix, index) => `${index + 1}. ${fix}`), "");
285
+ const threats = getRelevantThreatCategories(item.result);
286
+ if (threats.length) lines.push("Threat categories:", ...threats.map((threat) => `- ${threat}`), "");
287
+ if (item.result.reportId) lines.push(`Report: https://aimodularity.com/agentverify/report/?id=${item.result.reportId}`, "");
288
+ }
289
+ lines.push(`A2SPA docs: ${A2SPA_DOCS_URL}`);
290
+ return lines.join("\n");
291
+ }
168
292
  function printVerdict(result, fileName) {
169
293
  const verified = result.verdict === "VERIFIED";
170
- const icon = verified ? `${c.green}\u2713${c.reset}` : `${c.red}\u2717${c.reset}`;
171
- const verdict = verified ? `${c.green}VERIFIED${c.reset} ` : `${c.red}NOT VERIFIED${c.reset}`;
294
+ const notAssessed = result.verdict === "NOT_ASSESSED";
295
+ const icon = verified ? `${c.green}\u2713${c.reset}` : notAssessed ? `${c.yellow}\u26A0${c.reset}` : `${c.red}\u2717${c.reset}`;
296
+ const verdict = verified ? `${c.green}VERIFIED${c.reset} ` : notAssessed ? `${c.yellow}NOT ASSESSED${c.reset}` : `${c.red}NOT VERIFIED${c.reset}`;
172
297
  const score = verified ? `${c.green}${result.riskScore}/100${c.reset}` : result.riskScore >= 50 ? `${c.yellow}${result.riskScore}/100${c.reset}` : `${c.red}${result.riskScore}/100${c.reset}`;
173
298
  const findings = result.findings.length > 0 ? `${c.gray}\u2014 ${result.findings.length} finding${result.findings.length === 1 ? "" : "s"}${c.reset}` : "";
174
299
  const name = fileName.padEnd(30);
@@ -182,8 +307,9 @@ async function runScan(args) {
182
307
  console.error(`
183
308
  ${c.red}Error:${c.reset} API key required
184
309
  `);
185
- console.error(` Set with --key av_your_key or AGENTVERIFY_API_KEY env var`);
186
- console.error(` Get your key at https://aimodularity.com/agentverify/dashboard
310
+ console.error(` Set with --key av_your_key or AGENTVERIFY_API_KEY env var.`);
311
+ console.error(` Get your key at https://aimodularity.com/agentverify/dashboard/`);
312
+ console.error(` Example: AGENTVERIFY_API_KEY=av_your_key agentverify scan .
187
313
  `);
188
314
  process.exit(1);
189
315
  }
@@ -192,10 +318,12 @@ ${c.red}Error:${c.reset} API key required
192
318
  files = [args.file];
193
319
  } else {
194
320
  const dir = args.dir;
195
- write(`
321
+ if (!args.markdown && !args.json) {
322
+ write(`
196
323
  ${c.bold}Agent Verify${c.reset} ${c.gray}v${VERSION}${c.reset}`);
197
- write(`${c.gray}Scanning ${dir}...${c.reset}
324
+ write(`${c.gray}Scanning ${dir}...${c.reset}
198
325
  `);
326
+ }
199
327
  files = collectFiles(dir);
200
328
  if (files.length === 0) {
201
329
  write(`${c.yellow}No agent files found in ${dir}${c.reset}`);
@@ -207,6 +335,7 @@ ${c.bold}Agent Verify${c.reset} ${c.gray}v${VERSION}${c.reset}`);
207
335
  const jsonResults = [];
208
336
  let verified = 0;
209
337
  let notVerified = 0;
338
+ let notAssessed = 0;
210
339
  let errors = 0;
211
340
  const CONCURRENCY = 3;
212
341
  for (let i = 0; i < files.length; i += CONCURRENCY) {
@@ -226,9 +355,13 @@ ${c.bold}Agent Verify${c.reset} ${c.gray}v${VERSION}${c.reset}`);
226
355
  const outcome = batchResults[j];
227
356
  if (outcome.status === "fulfilled") {
228
357
  const { relPath, result } = outcome.value;
229
- if (!args.json) printVerdict(result, relPath);
358
+ if (!args.json && !args.markdown) {
359
+ if (args.file && !args.ci) printDetailedSummary(result, relPath);
360
+ else printVerdict(result, relPath);
361
+ }
230
362
  jsonResults.push({ file: relPath, result, error: null });
231
363
  if (result.verdict === "VERIFIED") verified++;
364
+ else if (result.verdict === "NOT_ASSESSED") notAssessed++;
232
365
  else notVerified++;
233
366
  } else {
234
367
  const filePath = batch[j];
@@ -242,22 +375,46 @@ ${c.bold}Agent Verify${c.reset} ${c.gray}v${VERSION}${c.reset}`);
242
375
  }
243
376
  }
244
377
  }
378
+ const summary = { total: files.length, verified, notVerified, notAssessed, errors };
245
379
  if (args.json) {
246
- write(JSON.stringify({ results: jsonResults, summary: { total: files.length, verified, notVerified, errors } }, null, 2));
380
+ write(JSON.stringify({ results: jsonResults, summary }, null, 2));
381
+ if (errors > 0) process.exit(1);
382
+ if (args.ci) {
383
+ if (notVerified > 0) process.exit(1);
384
+ if (notAssessed > 0 && !args.allowNotAssessed) process.exit(2);
385
+ }
386
+ return;
387
+ }
388
+ if (args.markdown) {
389
+ write(markdownSummary(jsonResults, summary));
390
+ if (errors > 0) process.exit(1);
391
+ if (args.ci) {
392
+ if (notVerified > 0) process.exit(1);
393
+ if (notAssessed > 0 && !args.allowNotAssessed) process.exit(2);
394
+ }
247
395
  return;
248
396
  }
397
+ const savedReports = jsonResults.filter((item) => item.result?.saved && item.result.reportId).length;
249
398
  write(`
250
399
  ${c.gray}${"\u2500".repeat(60)}${c.reset}`);
251
400
  write(`${c.bold}Agent Verify Scan Complete${c.reset}`);
252
401
  write(`${c.gray}Files scanned:${c.reset} ${files.length}`);
253
402
  write(`${c.green}Verified:${c.reset} ${verified}`);
254
403
  if (notVerified > 0) write(`${c.red}Not Verified:${c.reset} ${notVerified}`);
404
+ if (notAssessed > 0) write(`${c.yellow}Not Assessed:${c.reset} ${notAssessed}`);
255
405
  if (errors > 0) write(`${c.yellow}Errors:${c.reset} ${errors}`);
256
406
  write(`${c.gray}${"\u2500".repeat(60)}${c.reset}`);
257
- write(`
258
- ${c.gray}View reports: https://aimodularity.com/agentverify/dashboard${c.reset}
407
+ if (savedReports > 0) write(`
408
+ ${c.gray}Saved CLI reports appear in your dashboard: https://aimodularity.com/agentverify/dashboard/${c.reset}`);
409
+ else write(`
410
+ ${c.gray}No dashboard report was saved. Check API key access and retry if you need dashboard sync.${c.reset}`);
411
+ write(`${c.gray}A2SPA docs: ${A2SPA_DOCS_URL}${c.reset}
259
412
  `);
260
- if (notVerified > 0) process.exit(1);
413
+ if (errors > 0) process.exit(1);
414
+ if (args.ci) {
415
+ if (notVerified > 0) process.exit(1);
416
+ if (notAssessed > 0 && !args.allowNotAssessed) process.exit(2);
417
+ }
261
418
  }
262
419
  async function main() {
263
420
  const args = parseArgs(process.argv);
package/dist/cli.mjs CHANGED
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  scan
4
- } from "./chunk-YOD7H7MC.mjs";
4
+ } from "./chunk-PY3NS2YH.mjs";
5
5
 
6
6
  // src/cli.ts
7
7
  import { readFileSync, readdirSync, statSync } from "fs";
8
8
  import { join, relative, extname } from "path";
9
- var VERSION = "1.2.0";
10
- var API_URL = "https://agentverify-api.agentverify.workers.dev/v1/scan";
9
+ var VERSION = "1.3.0";
10
+ var API_URL = process.env.AGENTVERIFY_API_URL ?? "https://agentverify-api.agentverify.workers.dev/v1/scan";
11
+ var A2SPA_DOCS_URL = "https://aimodularity.com/A2SPA/docs";
11
12
  var c = {
12
13
  reset: "\x1B[0m",
13
14
  bold: "\x1B[1m",
@@ -49,6 +50,9 @@ function parseArgs(argv) {
49
50
  key: "",
50
51
  file: "",
51
52
  json: false,
53
+ markdown: false,
54
+ ci: false,
55
+ allowNotAssessed: false,
52
56
  help: false,
53
57
  version: false
54
58
  };
@@ -65,6 +69,9 @@ function parseArgs(argv) {
65
69
  if (arg === "--key" || arg === "-k") result.key = args[++i] ?? "";
66
70
  else if (arg === "--file" || arg === "-f") result.file = args[++i] ?? "";
67
71
  else if (arg === "--json") result.json = true;
72
+ else if (arg === "--markdown") result.markdown = true;
73
+ else if (arg === "--ci") result.ci = true;
74
+ else if (arg === "--allow-not-assessed") result.allowNotAssessed = true;
68
75
  else if (arg === "--help" || arg === "-h") result.help = true;
69
76
  else if (arg === "--version" || arg === "-v") result.version = true;
70
77
  else if (!arg.startsWith("-")) result.dir = arg;
@@ -88,6 +95,10 @@ ${c.bold}Options:${c.reset}
88
95
  --key, -k API key (or set AGENTVERIFY_API_KEY env var)
89
96
  --file, -f Scan a single file instead of a directory
90
97
  --json Output results as JSON
98
+ --markdown Output a Markdown summary
99
+ --ci CI mode: concise output and non-zero exit for NOT VERIFIED
100
+ --allow-not-assessed
101
+ In CI mode, do not fail only because content is NOT_ASSESSED
91
102
  --version, -v Print version
92
103
  --help, -h Show this help message
93
104
 
@@ -104,17 +115,81 @@ ${c.bold}Examples:${c.reset}
104
115
  ${c.gray}# Output as JSON (useful for CI/CD)${c.reset}
105
116
  agentverify scan . --key av_your_key --json
106
117
 
118
+ ${c.gray}# Markdown summary for pull requests${c.reset}
119
+ agentverify scan . --key av_your_key --markdown
120
+
107
121
  ${c.bold}Get your API key:${c.reset}
108
122
  https://aimodularity.com/agentverify/dashboard
109
123
 
110
124
  ${c.bold}Docs:${c.reset}
111
125
  https://github.com/AI-Blockchain-Ventures/agentverify
126
+
127
+ ${c.bold}A2SPA docs:${c.reset}
128
+ ${A2SPA_DOCS_URL}
129
+ `);
130
+ }
131
+ function getTopBlocker(result) {
132
+ return result.reportInsights?.topBlocker ?? result.findings[0]?.title ?? "No blocking finding detected";
133
+ }
134
+ function getFixFirst(result) {
135
+ const fromInsights = result.reportInsights?.fixPriority?.filter((item) => item.priority === "fix_first").map((item) => item.title) ?? [];
136
+ const fallback = result.findings.slice(0, 3).map((item) => item.title);
137
+ return (fromInsights.length ? fromInsights : fallback).slice(0, 3);
138
+ }
139
+ function getRelevantThreatCategories(result) {
140
+ return (result.threatCategories ?? []).filter((item) => item.status !== "not_assessed").slice(0, 6).map((item) => `${item.label} (${item.status.replace("_", " ")})`);
141
+ }
142
+ function printDetailedSummary(result, fileName) {
143
+ const verified = result.verdict === "VERIFIED";
144
+ const verdictColor = verified ? c.green : result.verdict === "NOT_ASSESSED" ? c.yellow : c.red;
145
+ write(`
146
+ ${c.bold}${c.cyan}Agent Verify${c.reset}`);
147
+ write(`${c.gray}${fileName}${c.reset}`);
148
+ write(`${c.bold}Score:${c.reset} ${result.riskScore}/100`);
149
+ write(`${c.bold}Verdict:${c.reset} ${verdictColor}${result.verdict === "NOT_VERIFIED" ? "NOT VERIFIED" : result.verdict.replace("_", " ")}${c.reset}`);
150
+ write(`${c.bold}Risk level:${c.reset} ${result.riskLevel}`);
151
+ write(`${c.bold}Confidence:${c.reset} ${result.confidence}/100`);
152
+ write(`${c.bold}Top blocker:${c.reset} ${getTopBlocker(result)}`);
153
+ write(`
154
+ ${c.bold}Fix first:${c.reset}`);
155
+ const fixes = getFixFirst(result);
156
+ if (fixes.length) fixes.forEach((item, index) => write(` ${index + 1}. ${item}`));
157
+ else write(` No blocking fixes detected. Re-scan after any material change.`);
158
+ if (result.reportInsights?.nextAction) write(`
159
+ ${c.bold}Next action:${c.reset} ${result.reportInsights.nextAction}`);
160
+ const threats = getRelevantThreatCategories(result);
161
+ if (threats.length) {
162
+ write(`
163
+ ${c.bold}Threat categories:${c.reset}`);
164
+ threats.forEach((item) => write(` - ${item}`));
165
+ }
166
+ if (result.saved && result.reportId) write(`${c.bold}View report:${c.reset} https://aimodularity.com/agentverify/report/?id=${result.reportId}`);
167
+ write(`${c.bold}A2SPA docs:${c.reset} ${A2SPA_DOCS_URL}`);
168
+ write(`${c.gray}Re-scan:${c.reset} agentverify scan --file ${fileName} --key $AGENTVERIFY_API_KEY
112
169
  `);
113
170
  }
171
+ function markdownSummary(results, summary) {
172
+ const lines = ["# Agent Verify", "", `Files scanned: ${summary.total}`, `Verified: ${summary.verified}`, `Not verified: ${summary.notVerified}`, `Not assessed: ${summary.notAssessed}`, `Errors: ${summary.errors}`, ""];
173
+ for (const item of results) {
174
+ if (!item.result) {
175
+ lines.push(`## ${item.file}`, "", `Error: ${item.error ?? "Unknown error"}`, "");
176
+ continue;
177
+ }
178
+ lines.push(`## ${item.file}`, "", `Score: ${item.result.riskScore}/100`, `Verdict: ${item.result.verdict === "NOT_VERIFIED" ? "NOT VERIFIED" : item.result.verdict.replace("_", " ")}`, `Risk level: ${item.result.riskLevel}`, `Top blocker: ${getTopBlocker(item.result)}`, "");
179
+ const fixes = getFixFirst(item.result);
180
+ if (fixes.length) lines.push("Fix first:", ...fixes.map((fix, index) => `${index + 1}. ${fix}`), "");
181
+ const threats = getRelevantThreatCategories(item.result);
182
+ if (threats.length) lines.push("Threat categories:", ...threats.map((threat) => `- ${threat}`), "");
183
+ if (item.result.reportId) lines.push(`Report: https://aimodularity.com/agentverify/report/?id=${item.result.reportId}`, "");
184
+ }
185
+ lines.push(`A2SPA docs: ${A2SPA_DOCS_URL}`);
186
+ return lines.join("\n");
187
+ }
114
188
  function printVerdict(result, fileName) {
115
189
  const verified = result.verdict === "VERIFIED";
116
- const icon = verified ? `${c.green}\u2713${c.reset}` : `${c.red}\u2717${c.reset}`;
117
- const verdict = verified ? `${c.green}VERIFIED${c.reset} ` : `${c.red}NOT VERIFIED${c.reset}`;
190
+ const notAssessed = result.verdict === "NOT_ASSESSED";
191
+ const icon = verified ? `${c.green}\u2713${c.reset}` : notAssessed ? `${c.yellow}\u26A0${c.reset}` : `${c.red}\u2717${c.reset}`;
192
+ const verdict = verified ? `${c.green}VERIFIED${c.reset} ` : notAssessed ? `${c.yellow}NOT ASSESSED${c.reset}` : `${c.red}NOT VERIFIED${c.reset}`;
118
193
  const score = verified ? `${c.green}${result.riskScore}/100${c.reset}` : result.riskScore >= 50 ? `${c.yellow}${result.riskScore}/100${c.reset}` : `${c.red}${result.riskScore}/100${c.reset}`;
119
194
  const findings = result.findings.length > 0 ? `${c.gray}\u2014 ${result.findings.length} finding${result.findings.length === 1 ? "" : "s"}${c.reset}` : "";
120
195
  const name = fileName.padEnd(30);
@@ -128,8 +203,9 @@ async function runScan(args) {
128
203
  console.error(`
129
204
  ${c.red}Error:${c.reset} API key required
130
205
  `);
131
- console.error(` Set with --key av_your_key or AGENTVERIFY_API_KEY env var`);
132
- console.error(` Get your key at https://aimodularity.com/agentverify/dashboard
206
+ console.error(` Set with --key av_your_key or AGENTVERIFY_API_KEY env var.`);
207
+ console.error(` Get your key at https://aimodularity.com/agentverify/dashboard/`);
208
+ console.error(` Example: AGENTVERIFY_API_KEY=av_your_key agentverify scan .
133
209
  `);
134
210
  process.exit(1);
135
211
  }
@@ -138,10 +214,12 @@ ${c.red}Error:${c.reset} API key required
138
214
  files = [args.file];
139
215
  } else {
140
216
  const dir = args.dir;
141
- write(`
217
+ if (!args.markdown && !args.json) {
218
+ write(`
142
219
  ${c.bold}Agent Verify${c.reset} ${c.gray}v${VERSION}${c.reset}`);
143
- write(`${c.gray}Scanning ${dir}...${c.reset}
220
+ write(`${c.gray}Scanning ${dir}...${c.reset}
144
221
  `);
222
+ }
145
223
  files = collectFiles(dir);
146
224
  if (files.length === 0) {
147
225
  write(`${c.yellow}No agent files found in ${dir}${c.reset}`);
@@ -153,6 +231,7 @@ ${c.bold}Agent Verify${c.reset} ${c.gray}v${VERSION}${c.reset}`);
153
231
  const jsonResults = [];
154
232
  let verified = 0;
155
233
  let notVerified = 0;
234
+ let notAssessed = 0;
156
235
  let errors = 0;
157
236
  const CONCURRENCY = 3;
158
237
  for (let i = 0; i < files.length; i += CONCURRENCY) {
@@ -172,9 +251,13 @@ ${c.bold}Agent Verify${c.reset} ${c.gray}v${VERSION}${c.reset}`);
172
251
  const outcome = batchResults[j];
173
252
  if (outcome.status === "fulfilled") {
174
253
  const { relPath, result } = outcome.value;
175
- if (!args.json) printVerdict(result, relPath);
254
+ if (!args.json && !args.markdown) {
255
+ if (args.file && !args.ci) printDetailedSummary(result, relPath);
256
+ else printVerdict(result, relPath);
257
+ }
176
258
  jsonResults.push({ file: relPath, result, error: null });
177
259
  if (result.verdict === "VERIFIED") verified++;
260
+ else if (result.verdict === "NOT_ASSESSED") notAssessed++;
178
261
  else notVerified++;
179
262
  } else {
180
263
  const filePath = batch[j];
@@ -188,22 +271,46 @@ ${c.bold}Agent Verify${c.reset} ${c.gray}v${VERSION}${c.reset}`);
188
271
  }
189
272
  }
190
273
  }
274
+ const summary = { total: files.length, verified, notVerified, notAssessed, errors };
191
275
  if (args.json) {
192
- write(JSON.stringify({ results: jsonResults, summary: { total: files.length, verified, notVerified, errors } }, null, 2));
276
+ write(JSON.stringify({ results: jsonResults, summary }, null, 2));
277
+ if (errors > 0) process.exit(1);
278
+ if (args.ci) {
279
+ if (notVerified > 0) process.exit(1);
280
+ if (notAssessed > 0 && !args.allowNotAssessed) process.exit(2);
281
+ }
282
+ return;
283
+ }
284
+ if (args.markdown) {
285
+ write(markdownSummary(jsonResults, summary));
286
+ if (errors > 0) process.exit(1);
287
+ if (args.ci) {
288
+ if (notVerified > 0) process.exit(1);
289
+ if (notAssessed > 0 && !args.allowNotAssessed) process.exit(2);
290
+ }
193
291
  return;
194
292
  }
293
+ const savedReports = jsonResults.filter((item) => item.result?.saved && item.result.reportId).length;
195
294
  write(`
196
295
  ${c.gray}${"\u2500".repeat(60)}${c.reset}`);
197
296
  write(`${c.bold}Agent Verify Scan Complete${c.reset}`);
198
297
  write(`${c.gray}Files scanned:${c.reset} ${files.length}`);
199
298
  write(`${c.green}Verified:${c.reset} ${verified}`);
200
299
  if (notVerified > 0) write(`${c.red}Not Verified:${c.reset} ${notVerified}`);
300
+ if (notAssessed > 0) write(`${c.yellow}Not Assessed:${c.reset} ${notAssessed}`);
201
301
  if (errors > 0) write(`${c.yellow}Errors:${c.reset} ${errors}`);
202
302
  write(`${c.gray}${"\u2500".repeat(60)}${c.reset}`);
203
- write(`
204
- ${c.gray}View reports: https://aimodularity.com/agentverify/dashboard${c.reset}
303
+ if (savedReports > 0) write(`
304
+ ${c.gray}Saved CLI reports appear in your dashboard: https://aimodularity.com/agentverify/dashboard/${c.reset}`);
305
+ else write(`
306
+ ${c.gray}No dashboard report was saved. Check API key access and retry if you need dashboard sync.${c.reset}`);
307
+ write(`${c.gray}A2SPA docs: ${A2SPA_DOCS_URL}${c.reset}
205
308
  `);
206
- if (notVerified > 0) process.exit(1);
309
+ if (errors > 0) process.exit(1);
310
+ if (args.ci) {
311
+ if (notVerified > 0) process.exit(1);
312
+ if (notAssessed > 0 && !args.allowNotAssessed) process.exit(2);
313
+ }
207
314
  }
208
315
  async function main() {
209
316
  const args = parseArgs(process.argv);
package/dist/sdk.d.mts CHANGED
@@ -3,24 +3,106 @@ interface ScanInput {
3
3
  fileName?: string;
4
4
  platform?: string;
5
5
  }
6
+ type Verdict = 'VERIFIED' | 'NOT_VERIFIED' | 'NOT_ASSESSED';
7
+ type RiskLevel = 'Low Risk' | 'Moderate Risk' | 'High Risk';
8
+ type Category = 'A' | 'B';
9
+ type Severity = 'critical' | 'high' | 'medium' | 'low';
10
+ type ThreatCategoryStatus = 'detected' | 'possible' | 'missing_evidence' | 'not_assessed';
11
+ interface ThreatCategoryAssessment {
12
+ id: string;
13
+ label: string;
14
+ status: ThreatCategoryStatus;
15
+ severity: Severity;
16
+ whatItMeans: string;
17
+ evidencePattern: string;
18
+ whyItMatters: string;
19
+ recommendedFix: string;
20
+ a2spaImpact: string;
21
+ }
6
22
  interface Finding {
23
+ id: string;
24
+ code: string;
7
25
  title: string;
8
- category: 'A' | 'B';
9
- severity: 'critical' | 'high' | 'medium' | 'low';
26
+ category: Category;
27
+ severity: Severity;
10
28
  whatIsWrong: string;
11
29
  whyItMatters: string;
12
- recommendedFix: string;
13
30
  evidence?: string;
31
+ recommendedFix: string;
32
+ fixCode?: string;
33
+ quickFix?: string;
34
+ compliance?: {
35
+ owasp?: string[];
36
+ nist?: string[];
37
+ soc2?: string[];
38
+ };
39
+ }
40
+ interface RuntimeBOM {
41
+ detectedLanguage: string;
42
+ detectedFramework: string | null;
43
+ detectedPlatform: string | null;
44
+ agentName: string | null;
45
+ toolAccessLevel: 'Restricted' | 'Unrestricted' | 'Unknown';
46
+ credentialExposure: 'Detected' | 'Not Detected';
47
+ memoryPersistence: 'Bounded' | 'Unbounded' | 'Unknown';
48
+ auditLogging: 'Present' | 'Absent' | 'Unknown';
49
+ humanGates: 'Present' | 'Absent' | 'Unknown';
50
+ rateLimiting: 'Present' | 'Absent' | 'Unknown';
51
+ promptInjectionSurface: 'Detected' | 'Not Detected' | 'Unknown';
52
+ delegationScope: 'Scoped' | 'Unscoped' | 'Unknown';
53
+ }
54
+ interface CategoryScore {
55
+ category: Category;
56
+ label: string;
57
+ score: number;
58
+ maxScore: number;
59
+ findingCount: number;
60
+ }
61
+ interface FixPriorityItem {
62
+ code: string;
63
+ title: string;
64
+ severity: Severity;
65
+ priority: 'fix_first' | 'fix_next' | 'nice_to_have';
66
+ reason: string;
67
+ }
68
+ interface ReportInsights {
69
+ executionReadinessScore: number;
70
+ a2spaReadinessScore: number;
71
+ remediationProgressScore: number;
72
+ topBlocker: string | null;
73
+ nextAction: string;
74
+ scoreExplanation: string[];
75
+ improvesScore: string[];
76
+ verificationBlockers: string[];
77
+ fixPriority: FixPriorityItem[];
14
78
  }
15
79
  interface ScanResult {
16
- verdict: 'VERIFIED' | 'NOT VERIFIED';
80
+ schemaVersion: '1.3.0';
81
+ reportId: string;
82
+ verdict: Verdict;
17
83
  riskScore: number;
18
- riskLevel: 'Low Risk' | 'Moderate Risk' | 'High Risk';
84
+ riskLevel: RiskLevel;
85
+ confidence: number;
86
+ optimizationScore: number;
87
+ reportInsights?: ReportInsights;
88
+ threatCategories?: ThreatCategoryAssessment[];
19
89
  findings: Finding[];
20
- reportId: string;
21
- scannedAt: string;
90
+ categoryScores: CategoryScore[];
91
+ bom: RuntimeBOM;
92
+ metadata: {
93
+ schemaVersion: '1.3.0';
94
+ scannerVersion: string;
95
+ fileName: string;
96
+ fileSize: number;
97
+ scannedAt: string;
98
+ detectedLanguage: string;
99
+ detectedFramework: string | null;
100
+ selectedPlatform: string | null;
101
+ agentName: string | null;
102
+ scanDuration: number;
103
+ };
22
104
  saved: boolean;
23
- reportUrl?: string;
105
+ reportUrl?: string | null;
24
106
  }
25
107
  interface ScanOptions {
26
108
  apiKey: string;
@@ -39,7 +121,7 @@ interface ScanOptions {
39
121
  * { apiKey: process.env.AGENTVERIFY_API_KEY }
40
122
  * )
41
123
  *
42
- * result.verdict // 'VERIFIED' | 'NOT VERIFIED'
124
+ * result.verdict // 'VERIFIED' | 'NOT_VERIFIED' | 'NOT_ASSESSED'
43
125
  * result.riskScore // 0-100
44
126
  * result.findings // Array of findings
45
127
  */
package/dist/sdk.d.ts CHANGED
@@ -3,24 +3,106 @@ interface ScanInput {
3
3
  fileName?: string;
4
4
  platform?: string;
5
5
  }
6
+ type Verdict = 'VERIFIED' | 'NOT_VERIFIED' | 'NOT_ASSESSED';
7
+ type RiskLevel = 'Low Risk' | 'Moderate Risk' | 'High Risk';
8
+ type Category = 'A' | 'B';
9
+ type Severity = 'critical' | 'high' | 'medium' | 'low';
10
+ type ThreatCategoryStatus = 'detected' | 'possible' | 'missing_evidence' | 'not_assessed';
11
+ interface ThreatCategoryAssessment {
12
+ id: string;
13
+ label: string;
14
+ status: ThreatCategoryStatus;
15
+ severity: Severity;
16
+ whatItMeans: string;
17
+ evidencePattern: string;
18
+ whyItMatters: string;
19
+ recommendedFix: string;
20
+ a2spaImpact: string;
21
+ }
6
22
  interface Finding {
23
+ id: string;
24
+ code: string;
7
25
  title: string;
8
- category: 'A' | 'B';
9
- severity: 'critical' | 'high' | 'medium' | 'low';
26
+ category: Category;
27
+ severity: Severity;
10
28
  whatIsWrong: string;
11
29
  whyItMatters: string;
12
- recommendedFix: string;
13
30
  evidence?: string;
31
+ recommendedFix: string;
32
+ fixCode?: string;
33
+ quickFix?: string;
34
+ compliance?: {
35
+ owasp?: string[];
36
+ nist?: string[];
37
+ soc2?: string[];
38
+ };
39
+ }
40
+ interface RuntimeBOM {
41
+ detectedLanguage: string;
42
+ detectedFramework: string | null;
43
+ detectedPlatform: string | null;
44
+ agentName: string | null;
45
+ toolAccessLevel: 'Restricted' | 'Unrestricted' | 'Unknown';
46
+ credentialExposure: 'Detected' | 'Not Detected';
47
+ memoryPersistence: 'Bounded' | 'Unbounded' | 'Unknown';
48
+ auditLogging: 'Present' | 'Absent' | 'Unknown';
49
+ humanGates: 'Present' | 'Absent' | 'Unknown';
50
+ rateLimiting: 'Present' | 'Absent' | 'Unknown';
51
+ promptInjectionSurface: 'Detected' | 'Not Detected' | 'Unknown';
52
+ delegationScope: 'Scoped' | 'Unscoped' | 'Unknown';
53
+ }
54
+ interface CategoryScore {
55
+ category: Category;
56
+ label: string;
57
+ score: number;
58
+ maxScore: number;
59
+ findingCount: number;
60
+ }
61
+ interface FixPriorityItem {
62
+ code: string;
63
+ title: string;
64
+ severity: Severity;
65
+ priority: 'fix_first' | 'fix_next' | 'nice_to_have';
66
+ reason: string;
67
+ }
68
+ interface ReportInsights {
69
+ executionReadinessScore: number;
70
+ a2spaReadinessScore: number;
71
+ remediationProgressScore: number;
72
+ topBlocker: string | null;
73
+ nextAction: string;
74
+ scoreExplanation: string[];
75
+ improvesScore: string[];
76
+ verificationBlockers: string[];
77
+ fixPriority: FixPriorityItem[];
14
78
  }
15
79
  interface ScanResult {
16
- verdict: 'VERIFIED' | 'NOT VERIFIED';
80
+ schemaVersion: '1.3.0';
81
+ reportId: string;
82
+ verdict: Verdict;
17
83
  riskScore: number;
18
- riskLevel: 'Low Risk' | 'Moderate Risk' | 'High Risk';
84
+ riskLevel: RiskLevel;
85
+ confidence: number;
86
+ optimizationScore: number;
87
+ reportInsights?: ReportInsights;
88
+ threatCategories?: ThreatCategoryAssessment[];
19
89
  findings: Finding[];
20
- reportId: string;
21
- scannedAt: string;
90
+ categoryScores: CategoryScore[];
91
+ bom: RuntimeBOM;
92
+ metadata: {
93
+ schemaVersion: '1.3.0';
94
+ scannerVersion: string;
95
+ fileName: string;
96
+ fileSize: number;
97
+ scannedAt: string;
98
+ detectedLanguage: string;
99
+ detectedFramework: string | null;
100
+ selectedPlatform: string | null;
101
+ agentName: string | null;
102
+ scanDuration: number;
103
+ };
22
104
  saved: boolean;
23
- reportUrl?: string;
105
+ reportUrl?: string | null;
24
106
  }
25
107
  interface ScanOptions {
26
108
  apiKey: string;
@@ -39,7 +121,7 @@ interface ScanOptions {
39
121
  * { apiKey: process.env.AGENTVERIFY_API_KEY }
40
122
  * )
41
123
  *
42
- * result.verdict // 'VERIFIED' | 'NOT VERIFIED'
124
+ * result.verdict // 'VERIFIED' | 'NOT_VERIFIED' | 'NOT_ASSESSED'
43
125
  * result.riskScore // 0-100
44
126
  * result.findings // Array of findings
45
127
  */
package/dist/sdk.js CHANGED
@@ -26,6 +26,56 @@ __export(sdk_exports, {
26
26
  module.exports = __toCommonJS(sdk_exports);
27
27
  var DEFAULT_API_URL = "https://agentverify-api.agentverify.workers.dev/v1/scan";
28
28
  var DEFAULT_TIMEOUT = 3e4;
29
+ function normalizeVerdict(value) {
30
+ if (value === "VERIFIED") return "VERIFIED";
31
+ if (value === "NOT_ASSESSED" || value === "NOT ASSESSED") return "NOT_ASSESSED";
32
+ return "NOT_VERIFIED";
33
+ }
34
+ function normalizeScanResult(data) {
35
+ const findings = Array.isArray(data.findings) ? data.findings : [];
36
+ const riskScore = typeof data.riskScore === "number" ? data.riskScore : 0;
37
+ return {
38
+ ...data,
39
+ reportId: typeof data.reportId === "string" ? data.reportId : "",
40
+ verdict: normalizeVerdict(data.verdict),
41
+ riskScore,
42
+ riskLevel: data.riskLevel ?? "High Risk",
43
+ confidence: typeof data.confidence === "number" ? data.confidence : riskScore > 0 ? 80 : 0,
44
+ optimizationScore: typeof data.optimizationScore === "number" ? data.optimizationScore : 0,
45
+ reportInsights: data.reportInsights,
46
+ threatCategories: Array.isArray(data.threatCategories) ? data.threatCategories : [],
47
+ findings,
48
+ categoryScores: Array.isArray(data.categoryScores) ? data.categoryScores : [],
49
+ bom: data.bom ?? {
50
+ detectedLanguage: "Unknown",
51
+ detectedFramework: null,
52
+ detectedPlatform: null,
53
+ agentName: null,
54
+ toolAccessLevel: "Unknown",
55
+ credentialExposure: "Not Detected",
56
+ memoryPersistence: "Unknown",
57
+ auditLogging: "Unknown",
58
+ humanGates: "Unknown",
59
+ rateLimiting: "Unknown",
60
+ promptInjectionSurface: "Unknown",
61
+ delegationScope: "Unknown"
62
+ },
63
+ metadata: data.metadata ?? {
64
+ schemaVersion: "1.3.0",
65
+ scannerVersion: "unknown",
66
+ fileName: "unknown",
67
+ fileSize: 0,
68
+ scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
69
+ detectedLanguage: "Unknown",
70
+ detectedFramework: null,
71
+ selectedPlatform: null,
72
+ agentName: null,
73
+ scanDuration: 0
74
+ },
75
+ saved: data.saved === true,
76
+ reportUrl: data.reportUrl ?? null
77
+ };
78
+ }
29
79
  async function scan(input, options) {
30
80
  const { apiKey, apiUrl = DEFAULT_API_URL, timeout = DEFAULT_TIMEOUT } = options;
31
81
  if (!apiKey) {
@@ -55,7 +105,7 @@ async function scan(input, options) {
55
105
  });
56
106
  if (response.status === 401) {
57
107
  throw new Error(
58
- "Invalid API key. Generate one at https://aimodularity.com/agentverify/dashboard"
108
+ "Invalid or unauthorized Agent Verify API key. Generate a valid key at https://aimodularity.com/agentverify/dashboard/"
59
109
  );
60
110
  }
61
111
  if (response.status === 400) {
@@ -66,7 +116,7 @@ async function scan(input, options) {
66
116
  throw new Error(`API error: ${response.status} ${response.statusText}`);
67
117
  }
68
118
  const data = await response.json();
69
- return data;
119
+ return normalizeScanResult(data);
70
120
  } catch (err) {
71
121
  if (err instanceof Error && err.name === "AbortError") {
72
122
  throw new Error(`Request timed out after ${timeout}ms`);
package/dist/sdk.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  scan,
3
3
  scanMany
4
- } from "./chunk-YOD7H7MC.mjs";
4
+ } from "./chunk-PY3NS2YH.mjs";
5
5
  export {
6
6
  scan,
7
7
  scanMany
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentverify",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "CLI and SDK for Agent Verify — execution trust analysis for AI agents",
5
5
  "bin": {
6
6
  "agentverify": "./dist/cli.js"
@@ -20,6 +20,7 @@
20
20
  "scripts": {
21
21
  "build": "tsup src/cli.ts src/sdk.ts --format cjs,esm --dts --clean",
22
22
  "dev": "tsup src/cli.ts src/sdk.ts --format cjs,esm --dts --watch",
23
+ "test": "npm run build && node test/cli.test.mjs",
23
24
  "prepublishOnly": "npm run build"
24
25
  },
25
26
  "engines": { "node": ">=18.0.0" },