opmsec 0.1.3 → 0.1.5
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/.env.example +1 -0
- package/.husky/pre-commit +1 -0
- package/README.md +71 -275
- package/bun.lock +5 -5
- package/docs/architecture/agents.mdx +11 -59
- package/docs/architecture/benchmarks.mdx +20 -46
- package/docs/architecture/overview.mdx +31 -38
- package/docs/architecture/scanner.mdx +11 -37
- package/docs/cli/audit.mdx +9 -12
- package/docs/cli/check.mdx +12 -26
- package/docs/cli/fix.mdx +10 -30
- package/docs/cli/info.mdx +12 -19
- package/docs/cli/install.mdx +27 -39
- package/docs/cli/push.mdx +40 -57
- package/docs/cli/register-agent.mdx +21 -53
- package/docs/cli/view.mdx +12 -29
- package/docs/concepts/ens-records.mdx +44 -0
- package/docs/concepts/multi-agent-consensus.mdx +18 -36
- package/docs/concepts/on-chain-registry.mdx +22 -49
- package/docs/concepts/security-model.mdx +20 -52
- package/docs/concepts/zk-agent-verification.mdx +26 -64
- package/docs/contract/events.mdx +13 -74
- package/docs/contract/functions.mdx +40 -126
- package/docs/contract/overview.mdx +17 -36
- package/docs/introduction.mdx +22 -25
- package/docs/mint.json +3 -2
- package/docs/quickstart.mdx +34 -70
- package/docs/system-design.png +0 -0
- package/package.json +7 -6
- package/packages/cli/src/commands/author-view.tsx +87 -2
- package/packages/cli/src/commands/check.tsx +18 -5
- package/packages/cli/src/commands/fix.tsx +25 -12
- package/packages/cli/src/commands/info.tsx +92 -4
- package/packages/cli/src/commands/install.tsx +327 -23
- package/packages/cli/src/commands/push.tsx +112 -0
- package/packages/cli/src/commands/register-agent.tsx +72 -31
- package/packages/cli/src/index.tsx +7 -5
- package/packages/cli/src/services/ens-records.ts +525 -0
- package/packages/cli/src/services/version.ts +156 -5
- package/packages/core/src/benchmarks.ts +116 -0
- package/packages/core/src/constants.ts +18 -6
- package/packages/core/src/model-rankings.ts +40 -15
- package/packages/core/src/types.ts +10 -0
- package/packages/core/src/utils.ts +136 -1
- package/packages/scanner/src/index.ts +2 -1
- package/packages/scanner/src/queue/memory-queue.ts +7 -2
- package/packages/scanner/src/services/benchmark-runner.ts +86 -1
- package/packages/scanner/src/services/fileverse.ts +61 -12
- package/packages/scanner/src/services/openrouter.ts +18 -7
- package/packages/web/.next/BUILD_ID +1 -0
- package/packages/web/.next/app-path-routes-manifest.json +4 -0
- package/packages/web/.next/diagnostics/build-diagnostics.json +6 -0
- package/packages/web/.next/diagnostics/framework.json +1 -0
- package/packages/web/.next/export-marker.json +6 -0
- package/packages/web/.next/images-manifest.json +58 -0
- package/packages/web/.next/next-minimal-server.js.nft.json +1 -0
- package/packages/web/.next/next-server.js.nft.json +1 -0
- package/packages/web/.next/prerender-manifest.json +54 -4
- package/packages/web/.next/required-server-files.json +320 -0
- package/packages/web/.next/routes-manifest.json +53 -1
- package/packages/web/.next/server/app/_not-found/page.js +2 -0
- package/packages/web/.next/server/app/_not-found/page.js.nft.json +1 -0
- package/packages/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
- package/packages/web/.next/server/app/_not-found.html +1 -0
- package/packages/web/.next/server/app/_not-found.meta +8 -0
- package/packages/web/.next/server/app/_not-found.rsc +18 -0
- package/packages/web/.next/server/app/index.html +6 -0
- package/packages/web/.next/server/app/index.meta +7 -0
- package/packages/web/.next/server/app/index.rsc +22 -0
- package/packages/web/.next/server/app/page.js +24 -24
- package/packages/web/.next/server/app/page.js.nft.json +1 -0
- package/packages/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/packages/web/.next/server/chunks/611.js +6 -0
- package/packages/web/.next/server/chunks/778.js +30 -0
- package/packages/web/.next/server/functions-config-manifest.json +4 -0
- package/packages/web/.next/server/interception-route-rewrite-manifest.js +1 -1
- package/packages/web/.next/server/next-font-manifest.js +1 -1
- package/packages/web/.next/server/next-font-manifest.json +1 -1
- package/packages/web/.next/server/pages/404.html +1 -0
- package/packages/web/.next/server/pages/500.html +1 -0
- package/packages/web/.next/server/pages/_app.js +1 -0
- package/packages/web/.next/server/pages/_app.js.nft.json +1 -0
- package/packages/web/.next/server/pages/_document.js +1 -0
- package/packages/web/.next/server/pages/_document.js.nft.json +1 -0
- package/packages/web/.next/server/pages/_error.js +19 -0
- package/packages/web/.next/server/pages/_error.js.nft.json +1 -0
- package/packages/web/.next/server/webpack-runtime.js +2 -2
- package/packages/web/.next/static/0esGzFBCzREfVwijEGDfL/_buildManifest.js +1 -0
- package/packages/web/.next/static/0esGzFBCzREfVwijEGDfL/_ssgManifest.js +1 -0
- package/packages/web/.next/static/chunks/174-5b5efcb3b8efcc01.js +1 -0
- package/packages/web/.next/static/chunks/255-0dc49b7a6e8e5c05.js +1 -0
- package/packages/web/.next/static/chunks/4bd1b696-382748cc942d8a14.js +1 -0
- package/packages/web/.next/static/chunks/app/_not-found/page-0da542be7eb33a64.js +1 -0
- package/packages/web/.next/static/chunks/app/layout-de8e841104500505.js +1 -0
- package/packages/web/.next/static/chunks/app/layout.js +37 -7
- package/packages/web/.next/static/chunks/app/page-7e086379698b9fb0.js +1 -0
- package/packages/web/.next/static/chunks/app/page.js +297 -1
- package/packages/web/.next/static/chunks/framework-ac73abd125e371fe.js +1 -0
- package/packages/web/.next/static/chunks/main-4e8d71b5ef7ee7e3.js +1 -0
- package/packages/web/.next/static/chunks/main-app-dd261207182e5a23.js +1 -0
- package/packages/web/.next/static/chunks/pages/_app-7d307437aca18ad4.js +1 -0
- package/packages/web/.next/static/chunks/pages/_error-cb2a52f75f2162e2.js +1 -0
- package/packages/web/.next/static/chunks/webpack-0dcd67569eb46132.js +1 -0
- package/packages/web/.next/static/chunks/webpack.js +2 -2
- package/packages/web/.next/static/css/102562cf2d0ae9b0.css +3 -0
- package/packages/web/.next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
- package/packages/web/.next/static/media/747892c23ea88013-s.woff2 +0 -0
- package/packages/web/.next/static/media/8d697b304b401681-s.woff2 +0 -0
- package/packages/web/.next/static/media/93f479601ee12b01-s.p.woff2 +0 -0
- package/packages/web/.next/static/media/9610d9e46709d722-s.woff2 +0 -0
- package/packages/web/.next/static/media/ba015fad6dcf6784-s.woff2 +0 -0
- package/packages/web/.next/static/webpack/16f18baa938a434c.webpack.hot-update.json +1 -0
- package/packages/web/.next/static/webpack/5fe9fe8578f9c3d2.webpack.hot-update.json +1 -0
- package/packages/web/.next/static/webpack/73c7d02260cc80e4.webpack.hot-update.json +1 -0
- package/packages/web/.next/static/webpack/a2d85d19aa028de1.webpack.hot-update.json +1 -0
- package/packages/web/.next/static/webpack/app/{layout.73e341375c8d429e.hot-update.js → layout.16f18baa938a434c.hot-update.js} +1 -1
- package/packages/web/.next/static/webpack/app/{layout.6fee6306e0f98869.hot-update.js → layout.5fe9fe8578f9c3d2.hot-update.js} +1 -1
- package/packages/web/.next/static/webpack/app/layout.653e365406c0d9ac.hot-update.js +22 -0
- package/packages/web/.next/static/webpack/app/layout.6800169a899e3a8b.hot-update.js +22 -0
- package/packages/web/.next/static/webpack/app/layout.73c7d02260cc80e4.hot-update.js +22 -0
- package/packages/web/.next/static/webpack/app/layout.a2d85d19aa028de1.hot-update.js +22 -0
- package/packages/web/.next/static/webpack/app/page.653e365406c0d9ac.hot-update.js +22 -0
- package/packages/web/.next/static/webpack/app/page.6800169a899e3a8b.hot-update.js +22 -0
- package/packages/web/.next/static/webpack/app/page.73c7d02260cc80e4.hot-update.js +22 -0
- package/packages/web/.next/static/webpack/app/page.a2d85d19aa028de1.hot-update.js +22 -0
- package/packages/web/.next/static/webpack/{webpack.6fee6306e0f98869.hot-update.js → webpack.16f18baa938a434c.hot-update.js} +2 -2
- package/packages/web/.next/static/webpack/{webpack.73e341375c8d429e.hot-update.js → webpack.5fe9fe8578f9c3d2.hot-update.js} +2 -2
- package/packages/web/.next/static/webpack/webpack.653e365406c0d9ac.hot-update.js +12 -0
- package/packages/web/.next/static/webpack/webpack.6800169a899e3a8b.hot-update.js +12 -0
- package/packages/web/.next/static/webpack/webpack.73c7d02260cc80e4.hot-update.js +12 -0
- package/packages/web/.next/static/webpack/webpack.a2d85d19aa028de1.hot-update.js +12 -0
- package/packages/web/.next/trace +2 -5
- package/packages/web/app/globals.css +197 -51
- package/packages/web/app/layout.tsx +6 -3
- package/packages/web/app/page.tsx +791 -309
- package/packages/web/bun.lock +66 -105
- package/packages/web/next.config.ts +8 -1
- package/packages/web/package.json +5 -2
- package/packages/web/postcss.config.mjs +2 -2
- package/packages/web/public/apple-icon.png +1 -0
- package/packages/web/public/dependency-bottleneck.png +0 -0
- package/packages/web/public/icon-dark-32x32.png +1 -0
- package/packages/web/public/icon-light-32x32.png +1 -0
- package/packages/web/public/icon.svg +1 -0
- package/packages/web/public/nextjs-cve-announcement.png +0 -0
- package/packages/web/public/phantomraven-npm-attack.png +0 -0
- package/packages/web/public/placeholder-logo.png +1 -0
- package/packages/web/public/placeholder-logo.svg +1 -0
- package/packages/web/public/placeholder-user.jpg +1 -0
- package/packages/web/public/placeholder.jpg +1 -0
- package/packages/web/public/placeholder.svg +1 -0
- package/packages/web/public/react-cve-meme.png +0 -0
- package/packages/web/public/wallet-drain-exploit.png +0 -0
- package/packages/web/styles/globals.css +125 -0
- package/packages/web/.next/server/vendor-chunks/@swc.js +0 -55
- package/packages/web/.next/server/vendor-chunks/next.js +0 -3010
- package/packages/web/.next/static/chunks/app-pages-internals.js +0 -182
- package/packages/web/.next/static/chunks/main-app.js +0 -1882
- package/packages/web/.next/static/css/app/layout.css +0 -1237
- package/packages/web/.next/static/webpack/633457081244afec._.hot-update.json +0 -1
- package/packages/web/.next/static/webpack/app/page.6fee6306e0f98869.hot-update.js +0 -22
- package/packages/web/.next/static/webpack/app/page.73e341375c8d429e.hot-update.js +0 -22
- package/packages/web/tailwind.config.ts +0 -48
- /package/packages/web/.next/static/chunks/{polyfills.js → polyfills-42372ed130431b0a.js} +0 -0
- /package/packages/web/.next/static/webpack/{6fee6306e0f98869.webpack.hot-update.json → 653e365406c0d9ac.webpack.hot-update.json} +0 -0
- /package/packages/web/.next/static/webpack/{73e341375c8d429e.webpack.hot-update.json → 6800169a899e3a8b.webpack.hot-update.json} +0 -0
package/docs/mint.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"$schema": "https://mintlify.com/schema.json",
|
|
3
3
|
"name": "OPM Documentation",
|
|
4
4
|
"logo": {
|
|
5
|
-
"dark": "/
|
|
6
|
-
"light": "/
|
|
5
|
+
"dark": "/favicon.svg",
|
|
6
|
+
"light": "/favicon.svg"
|
|
7
7
|
},
|
|
8
8
|
"favicon": "/favicon.svg",
|
|
9
9
|
"colors": {
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"concepts/security-model",
|
|
66
66
|
"concepts/multi-agent-consensus",
|
|
67
67
|
"concepts/on-chain-registry",
|
|
68
|
+
"concepts/ens-records",
|
|
68
69
|
"concepts/zk-agent-verification"
|
|
69
70
|
]
|
|
70
71
|
},
|
package/docs/quickstart.mdx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: 'Quickstart'
|
|
3
|
-
description: 'Get OPM up and running in minutes.
|
|
3
|
+
description: 'Get OPM up and running in minutes.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Quickstart
|
|
@@ -19,115 +19,79 @@ bun add -g opmsec
|
|
|
19
19
|
|
|
20
20
|
</CodeGroup>
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
## 2. Set Environment Variables
|
|
22
|
+
## 2. Configure (optional)
|
|
25
23
|
|
|
26
24
|
<Note>
|
|
27
|
-
**Read-only commands** (`install`, `audit`, `info`, `view`, `
|
|
25
|
+
**Read-only commands** (`install`, `audit`, `info`, `view`, `check`) work with **zero configuration**.
|
|
28
26
|
</Note>
|
|
29
27
|
|
|
30
|
-
For
|
|
31
|
-
|
|
32
|
-
<CodeGroup>
|
|
33
|
-
|
|
34
|
-
```bash .env
|
|
35
|
-
# Required for opm push
|
|
36
|
-
OPM_SIGNING_KEY=0x... # Your Ethereum private key for package signing
|
|
37
|
-
AGENT_PRIVATE_KEY=0x... # Agent wallet key for score submission
|
|
38
|
-
NPM_TOKEN=... # npm automation token (optional; use --token flag otherwise)
|
|
39
|
-
|
|
40
|
-
# At least one required for AI scanning
|
|
41
|
-
OPENROUTER_API_KEY=... # Multi-model access (Claude, Gemini, DeepSeek)
|
|
42
|
-
# OR
|
|
43
|
-
OPENAI_API_KEY=... # Fallback (GPT-4.1 family)
|
|
28
|
+
For publishing (`opm push`) or agent registration, create a `.env`:
|
|
44
29
|
|
|
45
|
-
|
|
46
|
-
|
|
30
|
+
```bash
|
|
31
|
+
OPM_SIGNING_KEY=0x... # Ethereum key for package signing
|
|
32
|
+
AGENT_PRIVATE_KEY=0x... # Agent wallet for on-chain score submission
|
|
33
|
+
OPENROUTER_API_KEY=sk-or-... # Multi-model AI scanning (Claude, Gemini, DeepSeek)
|
|
34
|
+
FILEVERSE_API_KEY=... # Audit report uploads to IPFS
|
|
47
35
|
```
|
|
48
36
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
## 3. Basic Usage
|
|
37
|
+
See [Configuration](/configuration) for the full list.
|
|
52
38
|
|
|
53
|
-
|
|
39
|
+
## 3. Use It
|
|
54
40
|
|
|
55
|
-
|
|
41
|
+
### Install with security verification
|
|
56
42
|
|
|
57
|
-
```bash
|
|
43
|
+
```bash
|
|
58
44
|
opm install lodash
|
|
59
45
|
```
|
|
60
46
|
|
|
61
|
-
|
|
62
|
-
opm install lodash@4.17.21
|
|
63
|
-
```
|
|
47
|
+
Checks CVEs, verifies on-chain risk scores, validates signatures — then installs.
|
|
64
48
|
|
|
65
|
-
|
|
66
|
-
opm install
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
</CodeGroup>
|
|
70
|
-
|
|
71
|
-
`opm install` resolves versions against the on-chain registry, verifies ECDSA signatures, checks OSV for CVEs, and blocks installation if risk exceeds the threshold (80).
|
|
72
|
-
|
|
73
|
-
### Sign, Scan, and Publish
|
|
49
|
+
### Scan your dependencies
|
|
74
50
|
|
|
75
51
|
```bash
|
|
76
|
-
opm
|
|
52
|
+
opm check
|
|
77
53
|
```
|
|
78
54
|
|
|
79
|
-
|
|
55
|
+
Finds typosquats, CVEs, and AI-detected threats across all your deps.
|
|
80
56
|
|
|
81
|
-
###
|
|
57
|
+
### Auto-fix vulnerabilities
|
|
82
58
|
|
|
83
59
|
```bash
|
|
84
|
-
opm
|
|
60
|
+
opm fix
|
|
85
61
|
```
|
|
86
62
|
|
|
87
|
-
|
|
63
|
+
Patches typosquats and upgrades vulnerable versions in `package.json`.
|
|
88
64
|
|
|
89
|
-
###
|
|
65
|
+
### Publish securely
|
|
90
66
|
|
|
91
67
|
```bash
|
|
92
|
-
opm
|
|
68
|
+
opm push
|
|
93
69
|
```
|
|
94
70
|
|
|
95
|
-
|
|
71
|
+
Signs your package, scans with 3 AI agents, registers on-chain, writes ENS records, publishes to npm.
|
|
96
72
|
|
|
97
|
-
### View
|
|
73
|
+
### View package security
|
|
98
74
|
|
|
99
75
|
```bash
|
|
100
76
|
opm info lodash
|
|
101
77
|
```
|
|
102
78
|
|
|
103
|
-
|
|
79
|
+
Shows on-chain risk score, agent assessments, checksums, audit report link.
|
|
104
80
|
|
|
105
|
-
###
|
|
81
|
+
### Look up an author
|
|
106
82
|
|
|
107
83
|
```bash
|
|
108
|
-
opm view
|
|
84
|
+
opm view djpai.eth
|
|
109
85
|
```
|
|
110
86
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
## What Happens Under the Hood
|
|
87
|
+
ENS profile, published packages, reputation score.
|
|
114
88
|
|
|
115
|
-
###
|
|
89
|
+
### Register your own agent
|
|
116
90
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
4. **AI agents**: 3 models run in parallel—static analysis, risk scoring (0–100), structured JSON
|
|
121
|
-
5. **On-chain**: Agent wallets call `OPMRegistry.submitScore()`; aggregate computed; publish blocked if score ≥ 80
|
|
122
|
-
6. **Fileverse**: Upload formatted markdown report (encrypted, IPFS-synced)
|
|
123
|
-
7. **npm**: Publish tarball (automation token or OTP for 2FA)
|
|
124
|
-
8. **Registry**: `registerPackage()` stores checksum, signature, ENS name, report URI
|
|
91
|
+
```bash
|
|
92
|
+
opm register-agent --name sentinel --model anthropic/claude-sonnet-4-20250514
|
|
93
|
+
```
|
|
125
94
|
|
|
126
|
-
|
|
95
|
+
Runs 10 benchmark cases, generates ZK proof, registers on-chain if 100% accurate.
|
|
127
96
|
|
|
128
|
-
|
|
129
|
-
2. Query OSV API for CVE/GHSA advisories (CRITICAL blocks install)
|
|
130
|
-
3. Fetch on-chain risk score and agent consensus
|
|
131
|
-
4. Verify ECDSA signature against tarball checksum
|
|
132
|
-
5. ChainPatrol API fallback for packages not in registry
|
|
133
|
-
6. Delegate to `npm install` if all gates pass
|
|
97
|
+
All standard npm commands (`init`, `run`, `test`, `build`, `start`) pass through transparently.
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opmsec",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"bin": {
|
|
6
6
|
"opm": "packages/cli/src/index.tsx"
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"build:contracts": "cd packages/contracts && npx hardhat compile",
|
|
15
15
|
"deploy:contracts": "cd packages/contracts && npx hardhat run scripts/deploy.ts --network baseSepolia",
|
|
16
16
|
"test:contracts": "cd packages/contracts && npx hardhat test",
|
|
17
|
-
"scan": "bun run packages/scanner/src/index.ts"
|
|
17
|
+
"scan": "bun run packages/scanner/src/index.ts",
|
|
18
|
+
"prepare": "husky || true"
|
|
18
19
|
},
|
|
19
20
|
"dependencies": {
|
|
20
21
|
"@ensdomains/ensjs": "^4.2.2",
|
|
@@ -23,17 +24,17 @@
|
|
|
23
24
|
"ethers": "^6.13.0",
|
|
24
25
|
"ink": "^5.2.1",
|
|
25
26
|
"react": "^18.3.1",
|
|
26
|
-
"react-devtools-core": "^
|
|
27
|
+
"react-devtools-core": "^7.0.1",
|
|
27
28
|
"terminal-image": "^4.2.0",
|
|
28
|
-
"viem": "^2.47.
|
|
29
|
+
"viem": "^2.47.4"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
32
|
"bun-types": "latest"
|
|
32
33
|
},
|
|
33
34
|
"opm": {
|
|
34
|
-
"signature": "
|
|
35
|
+
"signature": "0xe975b8c26bb6dc450a51c01fa1e5cb2f04b35d7535cde3432def8b4ee209c31158a4743c7477d45b1da6445c3ad11608e515fff40850aba8848d22d01aaf64621b",
|
|
35
36
|
"author": "0x2a3942EbDd8c5ea3E66D3fC4301F56d0F15d4bE2",
|
|
36
37
|
"ensName": "djpaiethg.eth",
|
|
37
|
-
"checksum": "
|
|
38
|
+
"checksum": "0x2633d7353118b1451f120ebb71a31c2c18901388f37ac9943090bb57a3c5e454"
|
|
38
39
|
}
|
|
39
40
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import { classifyRisk, truncateAddress } from '@opm/core';
|
|
4
|
-
import type { AuthorProfile } from '@opm/core';
|
|
4
|
+
import type { AuthorProfile, OPMENSRecords } from '@opm/core';
|
|
5
5
|
import { Header } from '../components/Header';
|
|
6
6
|
import { StatusLine } from '../components/StatusLine';
|
|
7
7
|
import { RiskBadge } from '../components/RiskBadge';
|
|
8
8
|
import { Hyperlink } from '../components/Hyperlink';
|
|
9
9
|
import { resolveAddress, getENSTextRecords, type ENSProfile } from '../services/ens';
|
|
10
|
+
import { readOPMRecords, readENSContenthash, decodeContenthash } from '../services/ens-records';
|
|
10
11
|
import {
|
|
11
12
|
getAuthorProfile,
|
|
12
13
|
getPackagesByAuthor, getPackagesByAuthorDirect, type AuthorPackageSummary,
|
|
@@ -20,6 +21,7 @@ interface Steps {
|
|
|
20
21
|
onchain: StepStatus;
|
|
21
22
|
profile: StepStatus;
|
|
22
23
|
packages: StepStatus;
|
|
24
|
+
opmRecords: StepStatus;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
interface AuthorViewProps {
|
|
@@ -28,11 +30,13 @@ interface AuthorViewProps {
|
|
|
28
30
|
|
|
29
31
|
export function AuthorViewCommand({ ensName }: AuthorViewProps) {
|
|
30
32
|
const [steps, setSteps] = useState<Steps>({
|
|
31
|
-
resolve: 'pending', onchain: 'pending', profile: 'pending', packages: 'pending',
|
|
33
|
+
resolve: 'pending', onchain: 'pending', profile: 'pending', packages: 'pending', opmRecords: 'pending',
|
|
32
34
|
});
|
|
33
35
|
const [address, setAddress] = useState<string | null>(null);
|
|
34
36
|
const [author, setAuthor] = useState<AuthorProfile | null>(null);
|
|
35
37
|
const [ensProfile, setEnsProfile] = useState<ENSProfile | null>(null);
|
|
38
|
+
const [opmRecords, setOpmRecords] = useState<OPMENSRecords>({});
|
|
39
|
+
const [contenthash, setContenthash] = useState<string | null>(null);
|
|
36
40
|
const [packages, setPackages] = useState<AuthorPackageSummary[]>([]);
|
|
37
41
|
const [avatarArt, setAvatarArt] = useState<string | null>(null);
|
|
38
42
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -105,6 +109,21 @@ export function AuthorViewCommand({ ensName }: AuthorViewProps) {
|
|
|
105
109
|
update('packages', 'skip');
|
|
106
110
|
}
|
|
107
111
|
|
|
112
|
+
update('opmRecords', 'running');
|
|
113
|
+
try {
|
|
114
|
+
const [recs, ch] = await Promise.allSettled([
|
|
115
|
+
readOPMRecords(ensName),
|
|
116
|
+
readENSContenthash(ensName),
|
|
117
|
+
]);
|
|
118
|
+
if (recs.status === 'fulfilled') setOpmRecords(recs.value);
|
|
119
|
+
if (ch.status === 'fulfilled' && ch.value) setContenthash(ch.value);
|
|
120
|
+
const hasData = (recs.status === 'fulfilled' && Object.keys(recs.value).length > 0)
|
|
121
|
+
|| (ch.status === 'fulfilled' && ch.value);
|
|
122
|
+
update('opmRecords', hasData ? 'done' : 'skip');
|
|
123
|
+
} catch {
|
|
124
|
+
update('opmRecords', 'skip');
|
|
125
|
+
}
|
|
126
|
+
|
|
108
127
|
const art = await avatarPromise;
|
|
109
128
|
if (art) setAvatarArt(art);
|
|
110
129
|
|
|
@@ -126,6 +145,8 @@ export function AuthorViewCommand({ ensName }: AuthorViewProps) {
|
|
|
126
145
|
detail={steps.onchain === 'done' ? 'registered author' : steps.onchain === 'error' ? 'not found' : undefined} />
|
|
127
146
|
<StatusLine label="Fetch packages" status={steps.packages}
|
|
128
147
|
detail={steps.packages === 'done' ? `${packages.length} package(s)` : undefined} />
|
|
148
|
+
<StatusLine label="OPM ENS records" status={steps.opmRecords}
|
|
149
|
+
detail={steps.opmRecords === 'done' ? `${Object.keys(opmRecords).length} records` : steps.opmRecords === 'skip' ? 'none' : undefined} />
|
|
129
150
|
|
|
130
151
|
{done && (
|
|
131
152
|
<Box flexDirection="column" marginTop={1}>
|
|
@@ -198,6 +219,70 @@ export function AuthorViewCommand({ ensName }: AuthorViewProps) {
|
|
|
198
219
|
</>
|
|
199
220
|
)}
|
|
200
221
|
|
|
222
|
+
{(Object.keys(opmRecords).length > 0 || contenthash) && (
|
|
223
|
+
<>
|
|
224
|
+
<Text> </Text>
|
|
225
|
+
<Text color="white" bold> OPM ENS Records</Text>
|
|
226
|
+
<Box flexDirection="column" marginLeft={2}>
|
|
227
|
+
{contenthash && (() => {
|
|
228
|
+
const decoded = decodeContenthash(contenthash);
|
|
229
|
+
return decoded ? (
|
|
230
|
+
<Box>
|
|
231
|
+
<Text color="gray">contenthash: </Text>
|
|
232
|
+
<Text color="magenta">{decoded.length > 60 ? decoded.slice(0, 60) + '...' : decoded}</Text>
|
|
233
|
+
</Box>
|
|
234
|
+
) : null;
|
|
235
|
+
})()}
|
|
236
|
+
{opmRecords.version && (
|
|
237
|
+
<Box>
|
|
238
|
+
<Text color="gray">opm.version: </Text>
|
|
239
|
+
<Text color="cyan">{opmRecords.version}</Text>
|
|
240
|
+
</Box>
|
|
241
|
+
)}
|
|
242
|
+
{opmRecords.checksum && (
|
|
243
|
+
<Box>
|
|
244
|
+
<Text color="gray">opm.checksum: </Text>
|
|
245
|
+
<Text color="cyan">{truncateAddress(opmRecords.checksum)}</Text>
|
|
246
|
+
</Box>
|
|
247
|
+
)}
|
|
248
|
+
{opmRecords.fileverse && (
|
|
249
|
+
<Box>
|
|
250
|
+
<Text color="gray">opm.fileverse: </Text>
|
|
251
|
+
{opmRecords.fileverse.startsWith('http') ? (
|
|
252
|
+
<Hyperlink url={opmRecords.fileverse} label={opmRecords.fileverse.length > 50 ? opmRecords.fileverse.slice(0, 50) + '...' : opmRecords.fileverse} color="green" />
|
|
253
|
+
) : (
|
|
254
|
+
<Text color="green">{opmRecords.fileverse}</Text>
|
|
255
|
+
)}
|
|
256
|
+
</Box>
|
|
257
|
+
)}
|
|
258
|
+
{opmRecords.riskScore && (
|
|
259
|
+
<Box>
|
|
260
|
+
<Text color="gray">opm.risk_score: </Text>
|
|
261
|
+
<Text color="cyan">{opmRecords.riskScore}/100</Text>
|
|
262
|
+
</Box>
|
|
263
|
+
)}
|
|
264
|
+
{opmRecords.signature && (
|
|
265
|
+
<Box>
|
|
266
|
+
<Text color="gray">opm.signature: </Text>
|
|
267
|
+
<Text color="cyan">{truncateAddress(opmRecords.signature)}</Text>
|
|
268
|
+
</Box>
|
|
269
|
+
)}
|
|
270
|
+
{opmRecords.contract && (
|
|
271
|
+
<Box>
|
|
272
|
+
<Text color="gray">opm.contract: </Text>
|
|
273
|
+
<Hyperlink url={`https://sepolia.basescan.org/address/${opmRecords.contract}`} label={truncateAddress(opmRecords.contract)} color="cyan" />
|
|
274
|
+
</Box>
|
|
275
|
+
)}
|
|
276
|
+
{opmRecords.packages && (
|
|
277
|
+
<Box>
|
|
278
|
+
<Text color="gray">opm.packages: </Text>
|
|
279
|
+
<Text color="white">{opmRecords.packages}</Text>
|
|
280
|
+
</Box>
|
|
281
|
+
)}
|
|
282
|
+
</Box>
|
|
283
|
+
</>
|
|
284
|
+
)}
|
|
285
|
+
|
|
201
286
|
{packages.length > 0 && (
|
|
202
287
|
<>
|
|
203
288
|
<Text> </Text>
|
|
@@ -19,6 +19,7 @@ export function CheckCommand() {
|
|
|
19
19
|
const [phase, setPhase] = useState<Phase>('scanning');
|
|
20
20
|
const [report, setReport] = useState<CheckReport | null>(null);
|
|
21
21
|
const [reportLink, setReportLink] = useState<string | null>(null);
|
|
22
|
+
const [uploadError, setUploadError] = useState<string | null>(null);
|
|
22
23
|
const [error, setError] = useState<string | null>(null);
|
|
23
24
|
|
|
24
25
|
useEffect(() => {
|
|
@@ -120,10 +121,21 @@ export function CheckCommand() {
|
|
|
120
121
|
setReport(checkReport);
|
|
121
122
|
|
|
122
123
|
setPhase('upload');
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
if (!process.env.FILEVERSE_API_KEY) {
|
|
125
|
+
setUploadError('FILEVERSE_API_KEY not set');
|
|
126
|
+
} else {
|
|
127
|
+
try {
|
|
128
|
+
const uploadResult = await uploadCheckReportToFileverse(checkReport);
|
|
129
|
+
setReportLink(uploadResult.link);
|
|
130
|
+
} catch (e: any) {
|
|
131
|
+
const msg = String(e?.message || e);
|
|
132
|
+
if (msg.includes('fetch failed') || msg.includes('ECONNREFUSED')) {
|
|
133
|
+
setUploadError('Fileverse API not running — start with: npx @fileverse/api --apiKey <key>');
|
|
134
|
+
} else {
|
|
135
|
+
setUploadError(msg.length > 120 ? msg.slice(0, 120) + '...' : msg);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
127
139
|
|
|
128
140
|
setPhase('done');
|
|
129
141
|
}
|
|
@@ -154,7 +166,8 @@ export function CheckCommand() {
|
|
|
154
166
|
|
|
155
167
|
{(phase === 'upload' || phase === 'done') && (
|
|
156
168
|
<StatusLine label="Upload report to Fileverse"
|
|
157
|
-
status={phase === 'upload' ? 'running' : reportLink ? 'done' : 'skip'}
|
|
169
|
+
status={phase === 'upload' ? 'running' : reportLink ? 'done' : 'skip'}
|
|
170
|
+
detail={uploadError || undefined} />
|
|
158
171
|
)}
|
|
159
172
|
|
|
160
173
|
{phase === 'done' && report && (
|
|
@@ -28,6 +28,7 @@ export function FixCommand() {
|
|
|
28
28
|
const [total, setTotal] = useState(0);
|
|
29
29
|
const [applied, setApplied] = useState(false);
|
|
30
30
|
const [reportLink, setReportLink] = useState<string | null>(null);
|
|
31
|
+
const [uploadError, setUploadError] = useState<string | null>(null);
|
|
31
32
|
const [error, setError] = useState<string | null>(null);
|
|
32
33
|
|
|
33
34
|
useEffect(() => {
|
|
@@ -182,17 +183,28 @@ export function FixCommand() {
|
|
|
182
183
|
}
|
|
183
184
|
|
|
184
185
|
setPhase('upload');
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
186
|
+
const checkReport: CheckReport = {
|
|
187
|
+
project: projectName,
|
|
188
|
+
timestamp: new Date().toISOString(),
|
|
189
|
+
totalDeps: allEntries.length,
|
|
190
|
+
deps: depResults,
|
|
191
|
+
agents: agentResults,
|
|
192
|
+
};
|
|
193
|
+
if (!process.env.FILEVERSE_API_KEY) {
|
|
194
|
+
setUploadError('FILEVERSE_API_KEY not set');
|
|
195
|
+
} else {
|
|
196
|
+
try {
|
|
197
|
+
const uploadResult = await uploadCheckReportToFileverse(checkReport);
|
|
198
|
+
setReportLink(uploadResult.link);
|
|
199
|
+
} catch (e: any) {
|
|
200
|
+
const msg = String(e?.message || e);
|
|
201
|
+
if (msg.includes('fetch failed') || msg.includes('ECONNREFUSED')) {
|
|
202
|
+
setUploadError('Fileverse API not running — start with: npx @fileverse/api --apiKey <key>');
|
|
203
|
+
} else {
|
|
204
|
+
setUploadError(msg.length > 120 ? msg.slice(0, 120) + '...' : msg);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
196
208
|
|
|
197
209
|
setPhase('done');
|
|
198
210
|
}
|
|
@@ -213,7 +225,8 @@ export function FixCommand() {
|
|
|
213
225
|
|
|
214
226
|
{(phase === 'upload' || phase === 'done') && (
|
|
215
227
|
<StatusLine label="Upload report to Fileverse"
|
|
216
|
-
status={phase === 'upload' ? 'running' : reportLink ? 'done' : 'skip'}
|
|
228
|
+
status={phase === 'upload' ? 'running' : reportLink ? 'done' : 'skip'}
|
|
229
|
+
detail={uploadError || undefined} />
|
|
217
230
|
)}
|
|
218
231
|
|
|
219
232
|
{phase === 'done' && fixes.length === 0 && (
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
+
import { truncateAddress } from '@opm/core';
|
|
4
|
+
import type { OnChainPackageInfo, ScanReport as ScanReportType, OPMENSRecords } from '@opm/core';
|
|
3
5
|
import { Header } from '../components/Header';
|
|
4
6
|
import { PackageCard } from '../components/PackageCard';
|
|
5
|
-
import { StatusLine } from '../components/StatusLine';
|
|
7
|
+
import { StatusLine, type Status } from '../components/StatusLine';
|
|
8
|
+
import { Hyperlink } from '../components/Hyperlink';
|
|
6
9
|
import { getPackageInfo, getSafestVersion, getVersions } from '../services/contract';
|
|
7
|
-
import { getENSProfile, type ENSProfile } from '../services/ens';
|
|
10
|
+
import { getENSProfile, resolveENSName, type ENSProfile } from '../services/ens';
|
|
11
|
+
import { readOPMRecords, readPackageENSRecords, readENSContenthash, decodeContenthash } from '../services/ens-records';
|
|
8
12
|
import { fetchReportFromFileverse } from '../services/fileverse';
|
|
9
13
|
import { queryOSV, type OSVVulnerability } from '../services/osv';
|
|
10
14
|
import { resolveVersion } from '../services/version';
|
|
11
|
-
import type { OnChainPackageInfo, ScanReport as ScanReportType } from '@opm/core';
|
|
12
15
|
|
|
13
16
|
interface InfoCommandProps {
|
|
14
17
|
packageName: string;
|
|
@@ -24,6 +27,9 @@ export function InfoCommand({ packageName, version }: InfoCommandProps) {
|
|
|
24
27
|
const [versions, setVersions] = useState<string[]>([]);
|
|
25
28
|
const [safest, setSafest] = useState<string | undefined>();
|
|
26
29
|
const [cves, setCves] = useState<OSVVulnerability[]>([]);
|
|
30
|
+
const [ensRecords, setEnsRecords] = useState<OPMENSRecords>({});
|
|
31
|
+
const [pkgEnsRecords, setPkgEnsRecords] = useState<OPMENSRecords>({});
|
|
32
|
+
const [contenthash, setContenthash] = useState<string | null>(null);
|
|
27
33
|
const [error, setError] = useState<string | null>(null);
|
|
28
34
|
|
|
29
35
|
useEffect(() => {
|
|
@@ -31,7 +37,8 @@ export function InfoCommand({ packageName, version }: InfoCommandProps) {
|
|
|
31
37
|
}, []);
|
|
32
38
|
|
|
33
39
|
async function run() {
|
|
34
|
-
const
|
|
40
|
+
const resolved = await resolveVersion(packageName, version || 'latest');
|
|
41
|
+
const ver = resolved.version;
|
|
35
42
|
setResolvedVer(ver);
|
|
36
43
|
|
|
37
44
|
const pkgInfo = await getPackageInfo(packageName, ver);
|
|
@@ -55,9 +62,28 @@ export function InfoCommand({ packageName, version }: InfoCommandProps) {
|
|
|
55
62
|
setVersions(vers.status === 'fulfilled' ? vers.value : []);
|
|
56
63
|
setSafest(safe.status === 'fulfilled' ? safe.value : undefined);
|
|
57
64
|
setCves(osvResult.status === 'fulfilled' ? osvResult.value : []);
|
|
65
|
+
|
|
66
|
+
const authorEns = ep.status === 'fulfilled' && ep.value?.name
|
|
67
|
+
? ep.value.name
|
|
68
|
+
: await resolveENSName(pkgInfo.author).catch(() => null);
|
|
69
|
+
|
|
70
|
+
if (authorEns) {
|
|
71
|
+
const [opmRecs, pkgRecs, ch] = await Promise.allSettled([
|
|
72
|
+
readOPMRecords(authorEns),
|
|
73
|
+
readPackageENSRecords(authorEns, packageName),
|
|
74
|
+
readENSContenthash(authorEns),
|
|
75
|
+
]);
|
|
76
|
+
if (opmRecs.status === 'fulfilled') setEnsRecords(opmRecs.value);
|
|
77
|
+
if (pkgRecs.status === 'fulfilled') setPkgEnsRecords(pkgRecs.value);
|
|
78
|
+
if (ch.status === 'fulfilled' && ch.value) setContenthash(ch.value);
|
|
79
|
+
}
|
|
80
|
+
|
|
58
81
|
setStatus('done');
|
|
59
82
|
}
|
|
60
83
|
|
|
84
|
+
const hasEnsData = Object.keys(ensRecords).length > 0 || Object.keys(pkgEnsRecords).length > 0 || contenthash;
|
|
85
|
+
const fvHash = pkgEnsRecords.fileverse || ensRecords.fileverse;
|
|
86
|
+
|
|
61
87
|
return (
|
|
62
88
|
<Box flexDirection="column">
|
|
63
89
|
<Header subtitle="info" />
|
|
@@ -96,6 +122,68 @@ export function InfoCommand({ packageName, version }: InfoCommandProps) {
|
|
|
96
122
|
<Text color="green" bold>{safest}</Text>
|
|
97
123
|
</Box>
|
|
98
124
|
)}
|
|
125
|
+
|
|
126
|
+
{hasEnsData && (
|
|
127
|
+
<Box flexDirection="column" marginTop={1}>
|
|
128
|
+
<Text color="gray">────────────────────────────────────────</Text>
|
|
129
|
+
<Text color="white" bold> ENS Records</Text>
|
|
130
|
+
{contenthash && (() => {
|
|
131
|
+
const decoded = decodeContenthash(contenthash);
|
|
132
|
+
return decoded ? (
|
|
133
|
+
<Box marginLeft={2}>
|
|
134
|
+
<Text color="gray">contenthash: </Text>
|
|
135
|
+
<Text color="magenta">{decoded.length > 60 ? decoded.slice(0, 60) + '...' : decoded}</Text>
|
|
136
|
+
</Box>
|
|
137
|
+
) : null;
|
|
138
|
+
})()}
|
|
139
|
+
{(pkgEnsRecords.version || ensRecords.version) && (
|
|
140
|
+
<Box marginLeft={2}>
|
|
141
|
+
<Text color="gray">opm.version: </Text>
|
|
142
|
+
<Text color="cyan">{pkgEnsRecords.version || ensRecords.version}</Text>
|
|
143
|
+
</Box>
|
|
144
|
+
)}
|
|
145
|
+
{(pkgEnsRecords.checksum || ensRecords.checksum) && (
|
|
146
|
+
<Box marginLeft={2}>
|
|
147
|
+
<Text color="gray">opm.checksum: </Text>
|
|
148
|
+
<Text color="cyan">{truncateAddress(pkgEnsRecords.checksum || ensRecords.checksum || '')}</Text>
|
|
149
|
+
</Box>
|
|
150
|
+
)}
|
|
151
|
+
{fvHash && (
|
|
152
|
+
<Box marginLeft={2}>
|
|
153
|
+
<Text color="gray">opm.fileverse: </Text>
|
|
154
|
+
{fvHash.startsWith('http') ? (
|
|
155
|
+
<Hyperlink url={fvHash} label={fvHash.length > 50 ? fvHash.slice(0, 50) + '...' : fvHash} color="green" />
|
|
156
|
+
) : (
|
|
157
|
+
<Text color="green">{fvHash}</Text>
|
|
158
|
+
)}
|
|
159
|
+
</Box>
|
|
160
|
+
)}
|
|
161
|
+
{(pkgEnsRecords.riskScore || ensRecords.riskScore) && (
|
|
162
|
+
<Box marginLeft={2}>
|
|
163
|
+
<Text color="gray">opm.risk_score: </Text>
|
|
164
|
+
<Text color="cyan">{pkgEnsRecords.riskScore || ensRecords.riskScore}/100</Text>
|
|
165
|
+
</Box>
|
|
166
|
+
)}
|
|
167
|
+
{(pkgEnsRecords.signature || ensRecords.signature) && (
|
|
168
|
+
<Box marginLeft={2}>
|
|
169
|
+
<Text color="gray">opm.signature: </Text>
|
|
170
|
+
<Text color="cyan">{truncateAddress(pkgEnsRecords.signature || ensRecords.signature || '')}</Text>
|
|
171
|
+
</Box>
|
|
172
|
+
)}
|
|
173
|
+
{ensRecords.contract && (
|
|
174
|
+
<Box marginLeft={2}>
|
|
175
|
+
<Text color="gray">opm.contract: </Text>
|
|
176
|
+
<Text color="cyan">{truncateAddress(ensRecords.contract)}</Text>
|
|
177
|
+
</Box>
|
|
178
|
+
)}
|
|
179
|
+
{ensRecords.packages && (
|
|
180
|
+
<Box marginLeft={2}>
|
|
181
|
+
<Text color="gray">opm.packages: </Text>
|
|
182
|
+
<Text color="white">{ensRecords.packages}</Text>
|
|
183
|
+
</Box>
|
|
184
|
+
)}
|
|
185
|
+
</Box>
|
|
186
|
+
)}
|
|
99
187
|
</Box>
|
|
100
188
|
) : (
|
|
101
189
|
<Box marginLeft={2} marginTop={1}>
|