ai-trust 0.6.0 → 0.7.1
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 +119 -111
- package/dist/check/narrative-fetch.d.ts +50 -0
- package/dist/check/narrative-fetch.d.ts.map +1 -0
- package/dist/check/narrative-fetch.js +99 -0
- package/dist/check/narrative-fetch.js.map +1 -0
- package/dist/check/render-rich-block.d.ts +42 -0
- package/dist/check/render-rich-block.d.ts.map +1 -0
- package/dist/check/render-rich-block.js +59 -0
- package/dist/check/render-rich-block.js.map +1 -0
- package/dist/check/rich-block-adapter.d.ts +68 -0
- package/dist/check/rich-block-adapter.d.ts.map +1 -0
- package/dist/check/rich-block-adapter.js +296 -0
- package/dist/check/rich-block-adapter.js.map +1 -0
- package/dist/check/skill-mcp-check.d.ts +60 -0
- package/dist/check/skill-mcp-check.d.ts.map +1 -0
- package/dist/check/skill-mcp-check.js +136 -0
- package/dist/check/skill-mcp-check.js.map +1 -0
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +84 -5
- package/dist/commands/check.js.map +1 -1
- package/dist/index.js +81 -2
- package/dist/index.js.map +1 -1
- package/dist/scanner/index.d.ts +18 -0
- package/dist/scanner/index.d.ts.map +1 -1
- package/dist/scanner/index.js +106 -1
- package/dist/scanner/index.js.map +1 -1
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -1,99 +1,106 @@
|
|
|
1
|
-
> **[OpenA2A](https://github.com/opena2a-org/opena2a)**: [CLI](https://github.com/opena2a-org/opena2a) · [HackMyAgent](https://github.com/opena2a-org/hackmyagent) · [Secretless](https://github.com/opena2a-org/secretless-ai) · [AIM](https://github.com/opena2a-org/agent-identity-management) · [Browser Guard](https://github.com/opena2a-org/AI-BrowserGuard) · [DVAA](https://github.com/opena2a-org/damn-vulnerable-ai-agent)
|
|
2
1
|
# ai-trust
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
> **[OpenA2A](https://github.com/opena2a-org/opena2a)**: [CLI](https://github.com/opena2a-org/opena2a) · [HackMyAgent](https://github.com/opena2a-org/hackmyagent) · [Secretless](https://github.com/opena2a-org/secretless-ai) · [AIM](https://github.com/opena2a-org/agent-identity-management) · [Browser Guard](https://github.com/opena2a-org/AI-BrowserGuard) · [DVAA](https://github.com/opena2a-org/damn-vulnerable-ai-agent)
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
Trust verification CLI for AI packages. MCP servers, A2A agents, skills, AI tools, LLMs. Queries the OpenA2A Registry trust graph for security scans, community consensus, dependency risk, and known advisories. Apache 2.0.
|
|
7
6
|
|
|
8
|
-
[](https://opensource.org/licenses/Apache-2.0)
|
|
9
7
|
[](https://www.npmjs.com/package/ai-trust)
|
|
8
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
9
|
+
[](https://github.com/opena2a-org/ai-trust)
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
[Website](https://opena2a.org/ai-trust) · [Registry](https://registry.opena2a.org) · [Discord](https://discord.gg/uRZa3KXgEn)
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
brew install opena2a-org/tap/ai-trust
|
|
15
|
-
```
|
|
13
|
+
For general-purpose libraries (express, typescript, chalk, etc.) use [HackMyAgent](https://github.com/opena2a-org/hackmyagent) instead. ai-trust is scoped to AI-native packages only.
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
## Quick start
|
|
18
16
|
|
|
19
17
|
```bash
|
|
20
|
-
|
|
18
|
+
npx ai-trust check @modelcontextprotocol/server-filesystem
|
|
21
19
|
```
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
```
|
|
22
|
+
@modelcontextprotocol/server-filesystem mcp_server · scanned 2 days ago
|
|
23
|
+
No known issues
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
Trust ━━━━━━━━━━━━━━━━━━━━ 87/100
|
|
26
|
+
Level Scanned (3/4)
|
|
27
|
+
Blocked > Warning > Listed > Scanned > Verified
|
|
28
|
+
|
|
29
|
+
── Next Steps ──────────────────────────────────────────────
|
|
30
|
+
Fresh scan: ai-trust check @modelcontextprotocol/server-filesystem
|
|
31
|
+
Full project audit: ai-trust audit package.json
|
|
27
32
|
```
|
|
28
33
|
|
|
29
|
-
|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
### npm
|
|
30
39
|
|
|
31
40
|
```bash
|
|
32
|
-
npx
|
|
41
|
+
npx ai-trust check <pkg> # run once, no install
|
|
42
|
+
npm install -g ai-trust # install globally
|
|
33
43
|
```
|
|
34
44
|
|
|
35
|
-
|
|
45
|
+
Requires Node.js 18 or later.
|
|
46
|
+
|
|
47
|
+
### Homebrew
|
|
36
48
|
|
|
37
49
|
```bash
|
|
38
|
-
|
|
50
|
+
brew install opena2a-org/tap/ai-trust
|
|
39
51
|
```
|
|
40
52
|
|
|
41
|
-
|
|
53
|
+
### From source
|
|
42
54
|
|
|
55
|
+
```bash
|
|
56
|
+
git clone https://github.com/opena2a-org/ai-trust.git
|
|
57
|
+
cd ai-trust
|
|
58
|
+
npm install
|
|
59
|
+
npm run build
|
|
60
|
+
node dist/index.js check express
|
|
43
61
|
```
|
|
44
|
-
@modelcontextprotocol/server-filesystem mcp_server · scanned 2 days ago
|
|
45
|
-
No known issues
|
|
46
62
|
|
|
47
|
-
|
|
48
|
-
Level Scanned (3/4)
|
|
49
|
-
Blocked > Warning > Listed > Scanned > Verified
|
|
63
|
+
### Verifying what was installed
|
|
50
64
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
65
|
+
Every release publishes via npm Trusted Publishing with SLSA v1 provenance. No long-lived `NPM_TOKEN`. GitHub Actions exchanges its OIDC token with npm at publish time.
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm view ai-trust dist.attestations --json
|
|
69
|
+
# Expects non-empty result with predicateType "https://slsa.dev/provenance/v1"
|
|
54
70
|
```
|
|
55
71
|
|
|
56
72
|
## Scope: AI packages only
|
|
57
73
|
|
|
58
|
-
ai-trust verifies trust for
|
|
74
|
+
ai-trust verifies trust for AI-native packages. For everything else, use HMA.
|
|
59
75
|
|
|
60
76
|
| Your package is... | Use |
|
|
61
77
|
|---|---|
|
|
62
|
-
| MCP server
|
|
78
|
+
| MCP server, A2A agent, skill, AI tool, LLM | `ai-trust` |
|
|
63
79
|
| General-purpose library (express, chalk, typescript, etc.) | `hackmyagent check <pkg>` |
|
|
64
80
|
| Full codebase security audit | `hackmyagent secure .` |
|
|
65
81
|
|
|
66
82
|
`ai-trust audit package.json` audits AI packages in the trust table and separately lists libraries in an "Out of scope" section with an HMA pointer.
|
|
67
83
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
## Built-in Help
|
|
71
|
-
|
|
72
|
-
```bash
|
|
73
|
-
ai-trust --help # All commands and flags
|
|
74
|
-
ai-trust --version # Current version
|
|
75
|
-
ai-trust [command] -h # Help for a specific command
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
---
|
|
84
|
+
Running `ai-trust check express` on a general-purpose library returns an "out of scope" verdict with a redirect to `hackmyagent check express`. Intentional. ai-trust is for AI packages only.
|
|
79
85
|
|
|
80
86
|
## Commands
|
|
81
87
|
|
|
82
|
-
### check
|
|
88
|
+
### `check`
|
|
83
89
|
|
|
84
90
|
Look up the trust verdict for a single AI package.
|
|
85
91
|
|
|
86
92
|
```bash
|
|
87
93
|
ai-trust check @modelcontextprotocol/server-filesystem
|
|
88
94
|
ai-trust check my-custom-agent --type a2a_agent
|
|
89
|
-
ai-trust check @modelcontextprotocol/server-postgres --json
|
|
95
|
+
ai-trust check @modelcontextprotocol/server-postgres --json
|
|
96
|
+
ai-trust check mcp-server-xyz --scan-if-missing --contribute # download + scan + share
|
|
97
|
+
ai-trust check server-filesystem --no-scan # registry lookup only
|
|
98
|
+
ai-trust check /path/to/local --scan-path /path/to/local # scan a local directory
|
|
90
99
|
```
|
|
91
100
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
### MCP Server Trust
|
|
101
|
+
Flags: `--type`, `--scan-if-missing`, `--contribute`, `--no-scan`, `--no-deep`, `--scan-path`, `--json`.
|
|
95
102
|
|
|
96
|
-
MCP
|
|
103
|
+
### MCP server shorthand
|
|
97
104
|
|
|
98
105
|
```bash
|
|
99
106
|
# These are equivalent:
|
|
@@ -106,29 +113,28 @@ ai-trust check @supabase/mcp-server-supabase
|
|
|
106
113
|
ai-trust check @cloudflare/mcp-server-cloudflare
|
|
107
114
|
```
|
|
108
115
|
|
|
109
|
-
|
|
116
|
+
`server-*` resolves to `@modelcontextprotocol/server-*`. Third-party `mcp-server-*` packages are looked up by their actual name.
|
|
110
117
|
|
|
111
|
-
|
|
118
|
+
### Scan on demand
|
|
112
119
|
|
|
113
|
-
When a package is not in the registry, ai-trust can download and scan it locally using [HackMyAgent](https://github.com/opena2a-org/hackmyagent). In interactive mode, you
|
|
120
|
+
When a package is not in the registry, ai-trust can download and scan it locally using [HackMyAgent](https://github.com/opena2a-org/hackmyagent). In interactive mode, you are prompted. In CI:
|
|
114
121
|
|
|
115
122
|
```bash
|
|
116
|
-
|
|
117
|
-
ai-trust check
|
|
118
|
-
|
|
119
|
-
# Registry lookup only (skip local scan)
|
|
120
|
-
ai-trust check server-filesystem --no-scan
|
|
123
|
+
ai-trust check mcp-server-xyz --scan-if-missing --contribute # auto-scan + share
|
|
124
|
+
ai-trust check server-filesystem --no-scan # skip scanning entirely
|
|
121
125
|
```
|
|
122
126
|
|
|
123
|
-
|
|
127
|
+
Local scans run HMA with NanoMind semantic analysis enabled by default. Pass `--no-deep` for static-only.
|
|
128
|
+
|
|
129
|
+
### `audit`
|
|
124
130
|
|
|
125
|
-
Parse dependency files and audit AI packages. Supports
|
|
131
|
+
Parse dependency files and audit AI packages. Supports `.json` (package.json format) and `.txt` (requirements.txt format). Libraries get partitioned into an "Out of scope" section.
|
|
126
132
|
|
|
127
133
|
```bash
|
|
128
134
|
ai-trust audit package.json
|
|
129
135
|
ai-trust audit requirements.txt
|
|
130
|
-
ai-trust audit package.json --min-trust 2
|
|
131
|
-
ai-trust audit package.json --scan-missing --contribute
|
|
136
|
+
ai-trust audit package.json --min-trust 2 # custom threshold (default 3)
|
|
137
|
+
ai-trust audit package.json --scan-missing --contribute # scan unknown AI packages
|
|
132
138
|
```
|
|
133
139
|
|
|
134
140
|
Example output (mixed AI + libraries):
|
|
@@ -139,7 +145,7 @@ Example output (mixed AI + libraries):
|
|
|
139
145
|
PACKAGE TYPE VERDICT TRUST SCORE SCAN
|
|
140
146
|
──────────────────────────────────────────────────────────────────────────────────────
|
|
141
147
|
@modelcontextprotocol/sdk mcp_server SAFE Scanned ━━━━━━━━ 87 passed
|
|
142
|
-
@opena2a/aim-core a2a_agent
|
|
148
|
+
@opena2a/aim-core a2a_agent SAFE Scanned ━━━━━━━━ 81 passed
|
|
143
149
|
...
|
|
144
150
|
|
|
145
151
|
── Out of scope (libraries) ────────────────────────────────
|
|
@@ -150,97 +156,99 @@ Example output (mixed AI + libraries):
|
|
|
150
156
|
Library security: npx hackmyagent secure .
|
|
151
157
|
```
|
|
152
158
|
|
|
153
|
-
### batch
|
|
159
|
+
### `batch`
|
|
154
160
|
|
|
155
161
|
Look up trust verdicts for multiple AI packages at once. Non-AI packages get partitioned into the "Out of scope" footer.
|
|
156
162
|
|
|
157
163
|
```bash
|
|
158
164
|
ai-trust batch @modelcontextprotocol/server-filesystem @modelcontextprotocol/server-postgres
|
|
159
165
|
ai-trust batch my-server-a my-server-b --type mcp_server
|
|
166
|
+
ai-trust batch react vue express lodash chalk
|
|
160
167
|
```
|
|
161
168
|
|
|
162
|
-
|
|
169
|
+
Flags: `--type`, `--min-trust`.
|
|
163
170
|
|
|
164
|
-
## Output
|
|
171
|
+
## Output options
|
|
165
172
|
|
|
166
173
|
```bash
|
|
167
|
-
ai-trust check express --json
|
|
168
|
-
ai-trust audit package.json --json
|
|
169
|
-
ai-trust check express --no-color
|
|
170
|
-
ai-trust check express --registry-url http://localhost:8080
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
---
|
|
174
|
-
|
|
175
|
-
## Community Contribution
|
|
176
|
-
|
|
177
|
-
Every scan you run can improve trust data for the entire community. Scan results are shared as anonymized telemetry (check pass/fail and severity only -- no file paths, source code, or descriptions).
|
|
178
|
-
|
|
179
|
-
On first scan, ai-trust asks whether you want to contribute. Your choice is saved in `~/.opena2a/config.json` and shared across all OpenA2A tools (opena2a-cli, hackmyagent).
|
|
180
|
-
|
|
181
|
-
```bash
|
|
182
|
-
# Contribute for this scan (non-interactive / CI)
|
|
183
|
-
ai-trust check chalk --contribute
|
|
184
|
-
|
|
185
|
-
# Configure globally via opena2a-cli
|
|
186
|
-
opena2a config set contribute true # opt in
|
|
187
|
-
opena2a config set contribute false # opt out
|
|
174
|
+
ai-trust check express --json # JSON output for scripting
|
|
175
|
+
ai-trust audit package.json --json # JSON audit output
|
|
176
|
+
ai-trust check express --no-color # disable colored output
|
|
177
|
+
ai-trust check express --registry-url http://localhost:8080 # custom registry endpoint
|
|
188
178
|
```
|
|
189
179
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
---
|
|
193
|
-
|
|
194
|
-
## Trust Levels
|
|
180
|
+
## Trust levels
|
|
195
181
|
|
|
196
182
|
| Level | Label | Description |
|
|
197
|
-
|
|
183
|
+
|---|---|---|
|
|
198
184
|
| 0 | Blocked | Package is blocked due to security concerns |
|
|
199
185
|
| 1 | Warning | Package has known issues |
|
|
200
186
|
| 2 | Listed | Package is listed but not yet scanned |
|
|
201
187
|
| 3 | Scanned | Package has been scanned by HackMyAgent |
|
|
202
188
|
| 4 | Verified | Package is verified by the publisher |
|
|
203
189
|
|
|
204
|
-
## Exit
|
|
190
|
+
## Exit codes
|
|
205
191
|
|
|
206
192
|
| Code | Meaning |
|
|
207
|
-
|
|
208
|
-
| 0 | All queried packages are safe
|
|
193
|
+
|---|---|
|
|
194
|
+
| 0 | All queried packages are safe and meet the trust threshold |
|
|
209
195
|
| 1 | Operational error (network failure, file not found, server error) |
|
|
210
|
-
| 2 | Policy signal: one or more packages have warning
|
|
196
|
+
| 2 | Policy signal: one or more packages have warning or blocked verdict, or fall below `--min-trust` |
|
|
211
197
|
|
|
212
|
-
|
|
198
|
+
## Community contribution
|
|
213
199
|
|
|
214
|
-
|
|
200
|
+
Every scan you run can improve trust data for the entire community. Scan results are shared as anonymised telemetry: check pass/fail and severity only. No file paths, source code, or descriptions.
|
|
215
201
|
|
|
216
|
-
-
|
|
217
|
-
- [HackMyAgent](https://github.com/opena2a-org/hackmyagent) (optional, required for local scanning)
|
|
202
|
+
On first scan, ai-trust asks whether you want to contribute. Your choice is saved in `~/.opena2a/config.json` and shared across all OpenA2A tools (`opena2a-cli`, `hackmyagent`).
|
|
218
203
|
|
|
219
|
-
|
|
204
|
+
```bash
|
|
205
|
+
ai-trust check chalk --contribute # contribute for this scan (non-interactive / CI)
|
|
206
|
+
opena2a config set contribute true # opt in globally
|
|
207
|
+
opena2a config set contribute false # opt out globally
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
More scans contributed means packages move from "Listed" to "Scanned" faster, reducing risk for everyone.
|
|
211
|
+
|
|
212
|
+
## Using with opena2a-cli
|
|
213
|
+
|
|
214
|
+
[`opena2a-cli`](https://github.com/opena2a-org/opena2a) is the unified CLI for the OpenA2A security toolchain. ai-trust powers `opena2a trust`.
|
|
220
215
|
|
|
221
216
|
```bash
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
217
|
+
npm install -g opena2a-cli
|
|
218
|
+
opena2a trust @modelcontextprotocol/server-filesystem
|
|
219
|
+
opena2a review # full security dashboard
|
|
225
220
|
```
|
|
226
221
|
|
|
227
|
-
## Use
|
|
222
|
+
## Use cases
|
|
228
223
|
|
|
229
|
-
|
|
224
|
+
| Guide | Time |
|
|
225
|
+
|---|---|
|
|
226
|
+
| [Check if a package is safe before installing](docs/use-cases/check-before-install.md) | 2 min |
|
|
227
|
+
| [Verify an MCP server's trust score](docs/use-cases/check-mcp-server.md) | 3 min |
|
|
228
|
+
| [Contribute trust data to the community](docs/use-cases/contribute-scans.md) | 3 min |
|
|
229
|
+
|
|
230
|
+
Full index: [docs/USE-CASES.md](docs/USE-CASES.md).
|
|
231
|
+
|
|
232
|
+
## Contributing
|
|
233
|
+
|
|
234
|
+
Apache 2.0. PRs from outside the org welcome.
|
|
230
235
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
-
|
|
236
|
+
```bash
|
|
237
|
+
git clone https://github.com/opena2a-org/ai-trust.git
|
|
238
|
+
cd ai-trust && npm install && npm run build && npm test
|
|
239
|
+
```
|
|
234
240
|
|
|
235
|
-
|
|
241
|
+
Security issues: `security@opena2a.org` (coordinated disclosure, response within 24 hours).
|
|
236
242
|
|
|
237
243
|
## Links
|
|
238
244
|
|
|
239
|
-
- [
|
|
240
|
-
- [OpenA2A
|
|
241
|
-
- [
|
|
242
|
-
- [
|
|
245
|
+
- [Website](https://opena2a.org/ai-trust)
|
|
246
|
+
- [OpenA2A Registry](https://registry.opena2a.org). Trust scores and scan data.
|
|
247
|
+
- [OpenA2A CLI](https://github.com/opena2a-org/opena2a). Unified security CLI.
|
|
248
|
+
- [HackMyAgent](https://github.com/opena2a-org/hackmyagent). Local scanning for unverified packages.
|
|
249
|
+
|
|
250
|
+
Part of the [OpenA2A](https://opena2a.org) security platform.
|
|
243
251
|
|
|
244
252
|
## License
|
|
245
253
|
|
|
246
|
-
Apache-2.0
|
|
254
|
+
Apache-2.0.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Narrative fetch helper — GET /api/v1/trust/narrative.
|
|
3
|
+
*
|
|
4
|
+
* Brief: opena2a-org/briefs/check-rich-context-skills-mcp-v1.md (§8 task 3a).
|
|
5
|
+
*
|
|
6
|
+
* Returns the parsed `PackageNarrative` shape, or `null` on any
|
|
7
|
+
* non-success response (404 narrative_not_available, 4xx, 5xx, network
|
|
8
|
+
* timeout). Always best-effort — the caller falls back to the legacy
|
|
9
|
+
* check block + v1 footer when the registry has no fresh narrative.
|
|
10
|
+
*
|
|
11
|
+
* Wire shape mirrors `packageNarrativeResponse` from the Registry's
|
|
12
|
+
* `internal/interfaces/http/handlers/package_narrative_handler.go`.
|
|
13
|
+
* Inner JSON fields (`hardcodedSecrets`, `skillNarrative`, `mcpNarrative`,
|
|
14
|
+
* `verdictReasoning`, `nextSteps`) ship as opaque JSON values; the
|
|
15
|
+
* adapter parses + validates them before they reach cli-ui renderers.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Wire shape for `GET /api/v1/trust/narrative`. Inner JSON fields are
|
|
19
|
+
* left as `unknown` because the registry handler ships them as raw
|
|
20
|
+
* `json.RawMessage` — the adapter is responsible for shape validation.
|
|
21
|
+
*/
|
|
22
|
+
export interface FetchedPackageNarrative {
|
|
23
|
+
artifactType: "skill" | "mcp";
|
|
24
|
+
packageName: string;
|
|
25
|
+
packageVersion: string;
|
|
26
|
+
schemaVersion: number;
|
|
27
|
+
generatedAt: string;
|
|
28
|
+
summary: string;
|
|
29
|
+
hardcodedSecrets: unknown;
|
|
30
|
+
skillNarrative?: unknown;
|
|
31
|
+
mcpNarrative?: unknown;
|
|
32
|
+
verdictReasoning: unknown;
|
|
33
|
+
nextSteps: unknown;
|
|
34
|
+
}
|
|
35
|
+
export interface NarrativeFetchOptions {
|
|
36
|
+
registryUrl: string;
|
|
37
|
+
artifactType: "skill" | "mcp";
|
|
38
|
+
name: string;
|
|
39
|
+
version: string;
|
|
40
|
+
userAgent: string;
|
|
41
|
+
/** Override timeout (defaults to 5000ms). */
|
|
42
|
+
timeoutMs?: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Fetch a fresh narrative for the given (type, name, version) tuple.
|
|
46
|
+
* Returns null on 404 (no narrative available) or any error condition.
|
|
47
|
+
* Never throws.
|
|
48
|
+
*/
|
|
49
|
+
export declare function fetchNarrative(options: NarrativeFetchOptions): Promise<FetchedPackageNarrative | null>;
|
|
50
|
+
//# sourceMappingURL=narrative-fetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"narrative-fetch.d.ts","sourceRoot":"","sources":["../../src/check/narrative-fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC,YAAY,EAAE,OAAO,GAAG,KAAK,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,OAAO,GAAG,KAAK,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC,CAqCzC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Narrative fetch helper — GET /api/v1/trust/narrative.
|
|
3
|
+
*
|
|
4
|
+
* Brief: opena2a-org/briefs/check-rich-context-skills-mcp-v1.md (§8 task 3a).
|
|
5
|
+
*
|
|
6
|
+
* Returns the parsed `PackageNarrative` shape, or `null` on any
|
|
7
|
+
* non-success response (404 narrative_not_available, 4xx, 5xx, network
|
|
8
|
+
* timeout). Always best-effort — the caller falls back to the legacy
|
|
9
|
+
* check block + v1 footer when the registry has no fresh narrative.
|
|
10
|
+
*
|
|
11
|
+
* Wire shape mirrors `packageNarrativeResponse` from the Registry's
|
|
12
|
+
* `internal/interfaces/http/handlers/package_narrative_handler.go`.
|
|
13
|
+
* Inner JSON fields (`hardcodedSecrets`, `skillNarrative`, `mcpNarrative`,
|
|
14
|
+
* `verdictReasoning`, `nextSteps`) ship as opaque JSON values; the
|
|
15
|
+
* adapter parses + validates them before they reach cli-ui renderers.
|
|
16
|
+
*/
|
|
17
|
+
const NARRATIVE_FETCH_TIMEOUT_MS = 5000;
|
|
18
|
+
/**
|
|
19
|
+
* Fetch a fresh narrative for the given (type, name, version) tuple.
|
|
20
|
+
* Returns null on 404 (no narrative available) or any error condition.
|
|
21
|
+
* Never throws.
|
|
22
|
+
*/
|
|
23
|
+
export async function fetchNarrative(options) {
|
|
24
|
+
const { registryUrl, artifactType, name, version, userAgent } = options;
|
|
25
|
+
const timeoutMs = options.timeoutMs ?? NARRATIVE_FETCH_TIMEOUT_MS;
|
|
26
|
+
const url = new URL("/api/v1/trust/narrative", registryUrl);
|
|
27
|
+
url.searchParams.set("type", artifactType);
|
|
28
|
+
url.searchParams.set("name", name);
|
|
29
|
+
url.searchParams.set("version", version);
|
|
30
|
+
const controller = new AbortController();
|
|
31
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
32
|
+
try {
|
|
33
|
+
const res = await fetch(url.toString(), {
|
|
34
|
+
method: "GET",
|
|
35
|
+
headers: {
|
|
36
|
+
accept: "application/json",
|
|
37
|
+
"user-agent": userAgent,
|
|
38
|
+
},
|
|
39
|
+
signal: controller.signal,
|
|
40
|
+
});
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
// 404 narrative_not_available is the documented "graceful
|
|
43
|
+
// degrade" signal. Any other 4xx / 5xx is also treated as a
|
|
44
|
+
// miss — caller falls back to legacy block.
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const body = (await res.json());
|
|
48
|
+
const parsed = validateWireShape(body);
|
|
49
|
+
return parsed;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Network error, JSON parse error, abort timeout — all degrade to
|
|
53
|
+
// null and let the caller render the legacy block.
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Minimal shape validation. The handler ships well-formed responses,
|
|
62
|
+
* so this is defense-in-depth. Returns null when required top-level
|
|
63
|
+
* fields are missing or the artifactType isn't skill/mcp.
|
|
64
|
+
*/
|
|
65
|
+
function validateWireShape(body) {
|
|
66
|
+
const artifactType = body.artifactType;
|
|
67
|
+
if (artifactType !== "skill" && artifactType !== "mcp")
|
|
68
|
+
return null;
|
|
69
|
+
if (typeof body.packageName !== "string" || body.packageName.length === 0) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
if (typeof body.packageVersion !== "string" ||
|
|
73
|
+
body.packageVersion.length === 0) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
if (typeof body.schemaVersion !== "number")
|
|
77
|
+
return null;
|
|
78
|
+
if (typeof body.generatedAt !== "string")
|
|
79
|
+
return null;
|
|
80
|
+
if (typeof body.summary !== "string")
|
|
81
|
+
return null;
|
|
82
|
+
return {
|
|
83
|
+
artifactType,
|
|
84
|
+
packageName: body.packageName,
|
|
85
|
+
packageVersion: body.packageVersion,
|
|
86
|
+
schemaVersion: body.schemaVersion,
|
|
87
|
+
generatedAt: body.generatedAt,
|
|
88
|
+
summary: body.summary,
|
|
89
|
+
hardcodedSecrets: body.hardcodedSecrets,
|
|
90
|
+
// Wire shape uses `skill` / `mcp` (matches the registry handler's
|
|
91
|
+
// `json:"skill,omitempty"` tag); the typed value carries the same
|
|
92
|
+
// payload as the internal `SkillNarrative` / `McpNarrative` fields.
|
|
93
|
+
skillNarrative: body.skill,
|
|
94
|
+
mcpNarrative: body.mcp,
|
|
95
|
+
verdictReasoning: body.verdictReasoning,
|
|
96
|
+
nextSteps: body.nextSteps,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=narrative-fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"narrative-fetch.js","sourceRoot":"","sources":["../../src/check/narrative-fetch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal-side renderer for `RenderedRichBlock` — applies HMA's
|
|
3
|
+
* chalk palette to the structured tone-tagged output produced by
|
|
4
|
+
* `renderCheckRichBlock` from `@opena2a/cli-ui`.
|
|
5
|
+
*
|
|
6
|
+
* Brief: opena2a-org/briefs/check-rich-context-skills-mcp-v1.md (§3).
|
|
7
|
+
*
|
|
8
|
+
* cli-ui returns tone-tagged lines (no chalk imports); this module
|
|
9
|
+
* paints them in HMA's terminal style and writes to stdout. ai-trust
|
|
10
|
+
* has a parallel module — the dividers, indents, and labels are
|
|
11
|
+
* byte-identical across CLIs (parity F12 / F13).
|
|
12
|
+
*/
|
|
13
|
+
import type { RenderedRichBlock } from "@opena2a/cli-ui";
|
|
14
|
+
type ColorFn = (s: string) => string;
|
|
15
|
+
/**
|
|
16
|
+
* Color palette interface — kept structural so callers can pass HMA's
|
|
17
|
+
* existing `colors` object without dragging chalk into this module's
|
|
18
|
+
* imports.
|
|
19
|
+
*/
|
|
20
|
+
export interface RichBlockPalette {
|
|
21
|
+
reset: string;
|
|
22
|
+
dim: ColorFn;
|
|
23
|
+
bold: ColorFn;
|
|
24
|
+
white: ColorFn;
|
|
25
|
+
green: ColorFn;
|
|
26
|
+
yellow: ColorFn;
|
|
27
|
+
red: ColorFn;
|
|
28
|
+
brightRed: ColorFn;
|
|
29
|
+
cyan: ColorFn;
|
|
30
|
+
}
|
|
31
|
+
export interface PrintRichBlockOptions {
|
|
32
|
+
palette: RichBlockPalette;
|
|
33
|
+
/** Width of section divider rule. Defaults to 62 chars. */
|
|
34
|
+
dividerWidth?: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Print the rich block to stdout. The input is the typed render output
|
|
38
|
+
* from cli-ui; this function only translates tones to ANSI and writes.
|
|
39
|
+
*/
|
|
40
|
+
export declare function printRichBlock(block: RenderedRichBlock, options: PrintRichBlockOptions): void;
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=render-rich-block.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-rich-block.d.ts","sourceRoot":"","sources":["../../src/check/render-rich-block.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,KAAK,OAAO,GAAG,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;AAErC;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,OAAO,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,GAAG,EAAE,OAAO,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,gBAAgB,CAAC;IAC1B,2DAA2D;IAC3D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,iBAAiB,EACxB,OAAO,EAAE,qBAAqB,GAC7B,IAAI,CAoBN"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal-side renderer for `RenderedRichBlock` — applies HMA's
|
|
3
|
+
* chalk palette to the structured tone-tagged output produced by
|
|
4
|
+
* `renderCheckRichBlock` from `@opena2a/cli-ui`.
|
|
5
|
+
*
|
|
6
|
+
* Brief: opena2a-org/briefs/check-rich-context-skills-mcp-v1.md (§3).
|
|
7
|
+
*
|
|
8
|
+
* cli-ui returns tone-tagged lines (no chalk imports); this module
|
|
9
|
+
* paints them in HMA's terminal style and writes to stdout. ai-trust
|
|
10
|
+
* has a parallel module — the dividers, indents, and labels are
|
|
11
|
+
* byte-identical across CLIs (parity F12 / F13).
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Print the rich block to stdout. The input is the typed render output
|
|
15
|
+
* from cli-ui; this function only translates tones to ANSI and writes.
|
|
16
|
+
*/
|
|
17
|
+
export function printRichBlock(block, options) {
|
|
18
|
+
const { palette } = options;
|
|
19
|
+
const dividerWidth = options.dividerWidth ?? 62;
|
|
20
|
+
// -- Header --------------------------------------------------------------
|
|
21
|
+
console.log();
|
|
22
|
+
console.log(` ${palette.bold(palette.white(block.header.name))}`);
|
|
23
|
+
for (const line of block.header.metaLines) {
|
|
24
|
+
console.log(` ${paintTone(line.text, line.tone, palette)}`);
|
|
25
|
+
}
|
|
26
|
+
// -- Sections ------------------------------------------------------------
|
|
27
|
+
for (const section of block.sections) {
|
|
28
|
+
printDivider(section.divider, section.dividerTone, palette, dividerWidth);
|
|
29
|
+
for (const line of section.lines) {
|
|
30
|
+
const indent = " " + " ".repeat(line.indent);
|
|
31
|
+
console.log(`${indent}${paintTone(line.text, line.tone, palette)}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
console.log();
|
|
35
|
+
}
|
|
36
|
+
function printDivider(label, tone, palette, width) {
|
|
37
|
+
const dashCount = Math.max(1, width - label.length - 4);
|
|
38
|
+
const labelPainted = paintTone(label, tone, palette);
|
|
39
|
+
const dashesLeft = palette.dim("──");
|
|
40
|
+
const dashesRight = palette.dim("─".repeat(dashCount));
|
|
41
|
+
console.log();
|
|
42
|
+
console.log(` ${dashesLeft} ${palette.bold(labelPainted)} ${dashesRight}`);
|
|
43
|
+
}
|
|
44
|
+
function paintTone(text, tone, palette) {
|
|
45
|
+
switch (tone) {
|
|
46
|
+
case "good":
|
|
47
|
+
return palette.green(text);
|
|
48
|
+
case "warning":
|
|
49
|
+
return palette.yellow(text);
|
|
50
|
+
case "critical":
|
|
51
|
+
return palette.brightRed(text);
|
|
52
|
+
case "dim":
|
|
53
|
+
return palette.dim(text);
|
|
54
|
+
case "default":
|
|
55
|
+
default:
|
|
56
|
+
return text;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=render-rich-block.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-rich-block.js","sourceRoot":"","sources":["../../src/check/render-rich-block.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA6BH;;;GAGG;AACH,MAAM,UAAU,cAAc,CAC5B,KAAwB,EACxB,OAA8B;IAE9B,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAC5B,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC;IAEhD,2EAA2E;IAC3E,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;IACnE,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,2EAA2E;IAC3E,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACrC,YAAY,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,WAAW,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;QAC1E,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YACjC,MAAM,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC/C,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,EAAE,CAAC;AAChB,CAAC;AAED,SAAS,YAAY,CACnB,KAAa,EACb,IAA0D,EAC1D,OAAyB,EACzB,KAAa;IAEb,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACxD,MAAM,YAAY,GAAG,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACrD,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACrC,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;IACvD,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,KAAK,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,WAAW,EAAE,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,SAAS,CAChB,IAAY,EACZ,IAAoE,EACpE,OAAyB;IAEzB,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,MAAM;YACT,OAAO,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC7B,KAAK,SAAS;YACZ,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC9B,KAAK,UAAU;YACb,OAAO,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACjC,KAAK,KAAK;YACR,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC3B,KAAK,SAAS,CAAC;QACf;YACE,OAAO,IAAI,CAAC;IAChB,CAAC;AACH,CAAC"}
|