From 9cf3abd3e7f2d428ac2f9dd8f169a57c0074edad Mon Sep 17 00:00:00 2001
From: Claude
Date: Wed, 10 Jun 2026 11:12:34 +0000
Subject: [PATCH] Add public certification API: trust badge as a service
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Implements the Trust Badge API from the product analysis roadmap
(docs/product-analysis-2026-06.md §5.1) — turn the review engine into
an acquisition channel by certifying skills we don't host:
- POST /api/public/certify runs any skill file through the same
proprietary engine as the review_skill MCP tool (now exported),
screens it with the prompt-injection guard, and stores the verdict
keyed by the file's SHA-256 (new external_certifications table,
RLS-enabled with public read).
- GET /api/badges/certified/.svg renders an embeddable
'skill audit' badge — deliberately distinct from the registry
Trust Score, which carries adversarial + real-world evidence.
- GET /api/public/certifications/ is the public verification
record: hash the file you were given, compare content_sha256.
- /certify documents the flow (linked from the home Proof section,
docs and sitemap). Pure helpers live in src/lib/certify/core.ts
with unit tests wired into npm test.
Also fixes all 26 strict type errors surfaced when main committed the
router type registration: route search schemas now declare optional
keys (login, signup, billing, generate), discover's search reducers
are typed against the route schema, and shared_runs / leaderboard
rows get explicit shapes.
https://claude.ai/code/session_01YFTRsBwhQuQQkR6cQVcxZJ
---
package-lock.json | 1111 +++++++++++++++--
package.json | 2 +-
src/components/site/home/Proof.tsx | 20 +-
src/lib/badges/trust-badge.ts | 34 +-
src/lib/certify/certify.server.ts | 108 ++
src/lib/certify/core.ts | 78 ++
src/lib/mcp/tools/skills.ts | 6 +-
src/routeTree.gen.ts | 85 ++
src/routes/account.billing.tsx | 50 +-
src/routes/api/badges.certified.$id.svg.ts | 41 +
src/routes/api/public/certifications.$id.ts | 48 +
src/routes/api/public/certify.ts | 94 ++
src/routes/certify.tsx | 132 ++
src/routes/discover.tsx | 76 +-
src/routes/docs.tsx | 115 +-
src/routes/generate.tsx | 2 +-
src/routes/leaderboard.tsx | 96 +-
src/routes/login.tsx | 51 +-
src/routes/run.$slug.tsx | 220 ++--
src/routes/signup.tsx | 55 +-
src/routes/sitemap[.]xml.tsx | 1 +
...20260610130000_external_certifications.sql | 31 +
tests/certify.test.mjs | 79 ++
23 files changed, 2178 insertions(+), 357 deletions(-)
create mode 100644 src/lib/certify/certify.server.ts
create mode 100644 src/lib/certify/core.ts
create mode 100644 src/routes/api/badges.certified.$id.svg.ts
create mode 100644 src/routes/api/public/certifications.$id.ts
create mode 100644 src/routes/api/public/certify.ts
create mode 100644 src/routes/certify.tsx
create mode 100644 supabase/migrations/20260610130000_external_certifications.sql
create mode 100644 tests/certify.test.mjs
diff --git a/package-lock.json b/package-lock.json
index f9673dde..53f3c4a2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -58,6 +58,7 @@
"jszip": "^3.10.1",
"lucide-react": "^0.575.0",
"mcp-tanstack-start": "^0.4.1",
+ "nitro": "3.0.260603-beta",
"react": "^19.2.0",
"react-day-picker": "^9.14.0",
"react-dom": "^19.2.0",
@@ -75,7 +76,7 @@
},
"devDependencies": {
"@eslint/js": "^9.32.0",
- "@lovable.dev/vite-tanstack-config": "^1.5.1",
+ "@lovable.dev/vite-tanstack-config": "2.3.2",
"@types/node": "^22.16.5",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
@@ -623,6 +624,17 @@
"integrity": "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==",
"license": "MIT"
},
+ "node_modules/@emnapi/core": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
+ "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.1",
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/@emnapi/runtime": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
@@ -633,6 +645,16 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
+ "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
@@ -1931,26 +1953,27 @@
}
},
"node_modules/@lovable.dev/vite-tanstack-config": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/@lovable.dev/vite-tanstack-config/-/vite-tanstack-config-1.5.1.tgz",
- "integrity": "sha512-GVqSHhGzrSls7Qg8YKAGr0vSKTHvUPwRWbm41/zFlkIZITTXjQwITT0MwuuysMjoeCz9MP8DgOHSvnh0Q+drAg==",
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/@lovable.dev/vite-tanstack-config/-/vite-tanstack-config-2.3.2.tgz",
+ "integrity": "sha512-pxS3Saco2KwTYGxgD4krBu25jGXNjT4m7hnAXYRq/j2xNSPBXkfEOu47GIx8cySHlnyjKgw3nRdkwnEkJzWldw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@lovable.dev/vite-plugin-dev-server-bridge": "^1.0.0",
"@lovable.dev/vite-plugin-hmr-gate": "^1.0.0",
+ "lightningcss": "^1.30.0",
"lovable-tagger": "1.2.0"
},
"peerDependencies": {
- "@cloudflare/vite-plugin": ">=1.0.0",
"@tailwindcss/vite": ">=4.0.0",
"@tanstack/react-start": ">=1.100.0",
"@vitejs/plugin-react": ">=4.0.0",
+ "nitro": ">=3.0.260603-beta",
"vite": ">=5.0.0 <9.0.0",
"vite-tsconfig-paths": ">=6.0.0"
},
"peerDependenciesMeta": {
- "@cloudflare/vite-plugin": {
+ "nitro": {
"optional": true
}
}
@@ -1995,6 +2018,24 @@
}
}
},
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz",
+ "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@tybys/wasm-util": "^0.10.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ },
+ "peerDependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -2090,6 +2131,15 @@
"node": ">=8.0.0"
}
},
+ "node_modules/@oxc-project/types": {
+ "version": "0.134.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.134.0.tgz",
+ "integrity": "sha512-T0xuRRKrQFmocH8y+jGfpmSkGcheaJExY9lEihmR1Gm2aH+75B8CzgU2rABRQSzzDxLjZ15Sc0bRVLj5lVeNXQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
"node_modules/@pkgr/core": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
@@ -3791,6 +3841,248 @@
"integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
"license": "MIT"
},
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.0.tgz",
+ "integrity": "sha512-gCYzGOSkYY6Z034suzd20euvds7lPzMEEla62DJGE/ZAlR4OMBnNbvnBSsIGUCAr52gaWMsloGxP4tVGtN5aCA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.0.tgz",
+ "integrity": "sha512-JQBD77MNgu+4Z6RAyg69acugdrhhVoWesr3l47zohYZ2YV2fwkWMArkN/2p4l6Ei+Sno7W5q+UsKdVWq5Ens0w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.0.tgz",
+ "integrity": "sha512-p/8cXUTK4Sob604e+xxPhVSbDFf29E6J0l/xESM9rdCfn3aDai3nEs6TnMHUsdD5aNlFz0+gDbiGlozLKGa2YA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.0.tgz",
+ "integrity": "sha512-KbtOSlVv6fElujiZWMcC3aQYhEwLVVf073RcwlSmpGQvIsKZFUqc0ef4sjUuurRwfbiI6JJXji9DQn+86hawmQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.0.tgz",
+ "integrity": "sha512-9fZ9i0o0/MQaw7om6Z6TsT7tfCk0jtbEFtC+aPqZL5RNsGWNcHvn6EHgL3dAprjq+AZzPTAQjg2JtpJaMt+6pg==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.0.tgz",
+ "integrity": "sha512-+tog7T66i+yFyIuuAnjL6xmW182W/qTBOUt6BtQ6lBIM1Eikh/fSMz4HGgvuCp5uU0zuIVWng7kDYthjCMOHcg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.0.tgz",
+ "integrity": "sha512-4b7yruLIIj/oZ3GpcLOvxcLCLDMraohn3IhQfN2hBP4w9UekG0DTIajWguJosRGfySf/+h/NwRUiMKoCpxCrqQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.0.tgz",
+ "integrity": "sha512-QRDOVZd0bhQ5jLsUsCC3dUxDWdTSVY9WMznowZgCGOrZfLLgctWpelhUASEiBwsXfat/JwYnVd1EaxMhqyT+UQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.0.tgz",
+ "integrity": "sha512-ypxT+Hq76NFG7woFbNbySnGEajFuYuIXeKz/jfCU+lXUoxfi3zLE6OG/ZQNeK3RpZSYJlAe2bokpsQ046CaieQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.0.tgz",
+ "integrity": "sha512-IdovCmfROFmpTLahdecTDFL74aLERVYN68F/mLZjfVh6LfoplPfI6deyHNMTcVujbokDV5k05XrFO22zfv+qjg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.0.tgz",
+ "integrity": "sha512-pcA8xlFp2tyk9T2R6Fi/rPe3bQ1MA+sSMDNUU5Ogu80GHOatkE4P8YCreGAvZErm5Ho2YRXnyvNrWiRncfVysQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.0.tgz",
+ "integrity": "sha512-4+fexHayrLCWpriPh4c6dNvL4an34DEZCG7zOM/FD5QNF6h8DT+bDXzyB/kfC8lDJbaFb7jKShtnjDQFXVQEjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-wasm32-wasi": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.0.tgz",
+ "integrity": "sha512-SbL++MNmOw6QamrwIGDMSSfM4ceTzFr+RjbOExJSLLBinScU4WI5OdA413h1qwPw2yH7lVF1+H4svQ+6mSXKTQ==",
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "1.10.0",
+ "@emnapi/runtime": "1.10.0",
+ "@napi-rs/wasm-runtime": "^1.1.4"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.0.tgz",
+ "integrity": "sha512-+xTE6XC7wBgk0VKRXGG+QAnyW5S9b8vfsFpiMjf0waQTmSQSU8onsH/beyZ8X4aXVveJnotiy7VDjLOaW8bTrg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.0.tgz",
+ "integrity": "sha512-Ogji1TQNqH3ACLnYr+1Ns1nyrJ0CO2P585u9Hsh02pXvtFiFpgtgT2b3P4PnCOU86VVCvqtAeCN4OftMT8KU4w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.40",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.40.tgz",
@@ -3798,9 +4090,9 @@
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.3.tgz",
- "integrity": "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.1.tgz",
+ "integrity": "sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==",
"cpu": [
"arm"
],
@@ -3811,9 +4103,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.3.tgz",
- "integrity": "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.61.1.tgz",
+ "integrity": "sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==",
"cpu": [
"arm64"
],
@@ -3824,9 +4116,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.3.tgz",
- "integrity": "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.61.1.tgz",
+ "integrity": "sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==",
"cpu": [
"arm64"
],
@@ -3837,9 +4129,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.3.tgz",
- "integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.61.1.tgz",
+ "integrity": "sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==",
"cpu": [
"x64"
],
@@ -3850,9 +4142,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.3.tgz",
- "integrity": "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.61.1.tgz",
+ "integrity": "sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==",
"cpu": [
"arm64"
],
@@ -3863,9 +4155,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.3.tgz",
- "integrity": "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.61.1.tgz",
+ "integrity": "sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==",
"cpu": [
"x64"
],
@@ -3876,9 +4168,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.3.tgz",
- "integrity": "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.61.1.tgz",
+ "integrity": "sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==",
"cpu": [
"arm"
],
@@ -3889,9 +4181,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.3.tgz",
- "integrity": "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.61.1.tgz",
+ "integrity": "sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==",
"cpu": [
"arm"
],
@@ -3902,9 +4194,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.3.tgz",
- "integrity": "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.61.1.tgz",
+ "integrity": "sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==",
"cpu": [
"arm64"
],
@@ -3915,9 +4207,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.3.tgz",
- "integrity": "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.61.1.tgz",
+ "integrity": "sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==",
"cpu": [
"arm64"
],
@@ -3928,9 +4220,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.3.tgz",
- "integrity": "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.61.1.tgz",
+ "integrity": "sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==",
"cpu": [
"loong64"
],
@@ -3941,9 +4233,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-musl": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.3.tgz",
- "integrity": "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.61.1.tgz",
+ "integrity": "sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==",
"cpu": [
"loong64"
],
@@ -3954,9 +4246,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.3.tgz",
- "integrity": "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.61.1.tgz",
+ "integrity": "sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==",
"cpu": [
"ppc64"
],
@@ -3967,9 +4259,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-musl": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.3.tgz",
- "integrity": "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.61.1.tgz",
+ "integrity": "sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==",
"cpu": [
"ppc64"
],
@@ -3980,9 +4272,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.3.tgz",
- "integrity": "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.61.1.tgz",
+ "integrity": "sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==",
"cpu": [
"riscv64"
],
@@ -3993,9 +4285,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.3.tgz",
- "integrity": "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.61.1.tgz",
+ "integrity": "sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==",
"cpu": [
"riscv64"
],
@@ -4006,9 +4298,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.3.tgz",
- "integrity": "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.61.1.tgz",
+ "integrity": "sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==",
"cpu": [
"s390x"
],
@@ -4019,9 +4311,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.3.tgz",
- "integrity": "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.1.tgz",
+ "integrity": "sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==",
"cpu": [
"x64"
],
@@ -4032,9 +4324,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.3.tgz",
- "integrity": "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.61.1.tgz",
+ "integrity": "sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==",
"cpu": [
"x64"
],
@@ -4045,9 +4337,9 @@
]
},
"node_modules/@rollup/rollup-openbsd-x64": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.3.tgz",
- "integrity": "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.61.1.tgz",
+ "integrity": "sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==",
"cpu": [
"x64"
],
@@ -4058,9 +4350,9 @@
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.3.tgz",
- "integrity": "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.61.1.tgz",
+ "integrity": "sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==",
"cpu": [
"arm64"
],
@@ -4071,9 +4363,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.3.tgz",
- "integrity": "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.61.1.tgz",
+ "integrity": "sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==",
"cpu": [
"arm64"
],
@@ -4084,9 +4376,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.3.tgz",
- "integrity": "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.61.1.tgz",
+ "integrity": "sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==",
"cpu": [
"ia32"
],
@@ -4097,9 +4389,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.3.tgz",
- "integrity": "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.61.1.tgz",
+ "integrity": "sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==",
"cpu": [
"x64"
],
@@ -4110,9 +4402,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.3.tgz",
- "integrity": "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.61.1.tgz",
+ "integrity": "sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==",
"cpu": [
"x64"
],
@@ -5073,6 +5365,16 @@
"url": "https://github.com/sponsors/tannerlinsley"
}
},
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
+ "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -5185,7 +5487,6 @@
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
- "dev": true,
"license": "MIT"
},
"node_modules/@types/json-schema": {
@@ -6316,6 +6617,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/consola": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
+ "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.18.0 || >=16.10.0"
+ }
+ },
"node_modules/content-disposition": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
@@ -6444,6 +6754,20 @@
"node": ">= 8"
}
},
+ "node_modules/crossws": {
+ "version": "0.4.6",
+ "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.4.6.tgz",
+ "integrity": "sha512-/Wxe9Z007EbJ496j88nToZEvyPZ8PY/wjZJ18Agh/GCA9cYHyLbxtrpdFlFzAw3TV20F0SUYGl0g6PzChbwUrg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "srvx": ">=0.11.5"
+ },
+ "peerDependenciesMeta": {
+ "srvx": {
+ "optional": true
+ }
+ }
+ },
"node_modules/css-line-break": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
@@ -6638,15 +6962,49 @@
"integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==",
"license": "MIT"
},
- "node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "node_modules/db0": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/db0/-/db0-0.3.4.tgz",
+ "integrity": "sha512-RiXXi4WaNzPTHEOu8UPQKMooIbqOEyqA1t7Z6MsdxSCeb8iUC9ko3LcmsLmeUt2SM5bctfArZKkRQggKZz7JNw==",
"license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
+ "peerDependencies": {
+ "@electric-sql/pglite": "*",
+ "@libsql/client": "*",
+ "better-sqlite3": "*",
+ "drizzle-orm": "*",
+ "mysql2": "*",
+ "sqlite3": "*"
},
- "engines": {
+ "peerDependenciesMeta": {
+ "@electric-sql/pglite": {
+ "optional": true
+ },
+ "@libsql/client": {
+ "optional": true
+ },
+ "better-sqlite3": {
+ "optional": true
+ },
+ "drizzle-orm": {
+ "optional": true
+ },
+ "mysql2": {
+ "optional": true
+ },
+ "sqlite3": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
@@ -7783,6 +8141,30 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
+ "node_modules/h3": {
+ "version": "2.0.1-rc.22",
+ "resolved": "https://registry.npmjs.org/h3/-/h3-2.0.1-rc.22.tgz",
+ "integrity": "sha512-Esv0DMIuPkCTSWCA0vO73vcTqwzH1wjSrAO1TXNu/K3up1sZHa9EKMapbmxCDYBeymC3fVTk4qxp7ogQWQ+KgA==",
+ "license": "MIT",
+ "dependencies": {
+ "rou3": "^0.8.1",
+ "srvx": "^0.11.15"
+ },
+ "bin": {
+ "h3": "bin/h3.mjs"
+ },
+ "engines": {
+ "node": ">=20.11.1"
+ },
+ "peerDependencies": {
+ "crossws": "^0.4.1"
+ },
+ "peerDependenciesMeta": {
+ "crossws": {
+ "optional": true
+ }
+ }
+ },
"node_modules/h3-v2": {
"name": "h3",
"version": "2.0.1-rc.20",
@@ -7851,6 +8233,12 @@
"node": ">=16.9.0"
}
},
+ "node_modules/hookable": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/hookable/-/hookable-6.1.1.tgz",
+ "integrity": "sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==",
+ "license": "MIT"
+ },
"node_modules/html2canvas": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
@@ -7916,6 +8304,12 @@
"url": "https://opencollective.com/express"
}
},
+ "node_modules/httpxy": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/httpxy/-/httpxy-0.5.3.tgz",
+ "integrity": "sha512-SMS9V6Sn7VWaS11lYhoAr0ceoaiolTWf4jYdJn0NJhCdKMu9R2H9Fh0LBDWBHQF6HRLI1PmaePYsjanSpE5PEw==",
+ "license": "MIT"
+ },
"node_modules/iceberg-js": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz",
@@ -8924,6 +9318,415 @@
"node": ">= 0.6"
}
},
+ "node_modules/nf3": {
+ "version": "0.3.17",
+ "resolved": "https://registry.npmjs.org/nf3/-/nf3-0.3.17.tgz",
+ "integrity": "sha512-N9zEWySuJFw+gR0lhS5863YsvNeudOdqRyFvNb+jMXbeTJOdrjDqkCpDginIZfUm0LzT1t1nCRiDeqQm/8kirQ==",
+ "license": "MIT"
+ },
+ "node_modules/nitro": {
+ "version": "3.0.260603-beta",
+ "resolved": "https://registry.npmjs.org/nitro/-/nitro-3.0.260603-beta.tgz",
+ "integrity": "sha512-ffaSHK00a7YDlDizoEHwcxPwpQpdBRRA8k42ymTsRnfl3ipGeKgv4gnPr6DgmCNTo4tYVPK3bHBEv1gNhWpo/A==",
+ "license": "MIT",
+ "dependencies": {
+ "consola": "^3.4.2",
+ "crossws": "^0.4.5",
+ "db0": "^0.3.4",
+ "env-runner": "^0.1.9",
+ "h3": "^2.0.1-rc.22",
+ "hookable": "^6.1.1",
+ "nf3": "^0.3.17",
+ "ocache": "^0.1.4",
+ "ofetch": "^2.0.0-alpha.3",
+ "ohash": "^2.0.11",
+ "rolldown": "^1.0.3",
+ "srvx": "^0.11.16",
+ "unenv": "^2.0.0-rc.24",
+ "unstorage": "^2.0.0-alpha.7"
+ },
+ "bin": {
+ "nitro": "dist/cli/index.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "@vercel/queue": "^0.2.0",
+ "dotenv": "*",
+ "giget": "*",
+ "jiti": "^2.7.0",
+ "rollup": "^4.60.4",
+ "vite": "^7 || ^8",
+ "xml2js": "^0.6.2",
+ "zephyr-agent": "^0.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@vercel/queue": {
+ "optional": true
+ },
+ "dotenv": {
+ "optional": true
+ },
+ "giget": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "rollup": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ },
+ "xml2js": {
+ "optional": true
+ },
+ "zephyr-agent": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/nitro/node_modules/@cloudflare/workerd-darwin-64": {
+ "version": "1.20260609.1",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260609.1.tgz",
+ "integrity": "sha512-AK8tYLQm+8BqQMzjZ55ZfuhfIm1eCkj+Ykxz6kWXojdACwjjU03MrwdM9fBDdgzU3upXOs4e1scOFHySlfVQjA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/nitro/node_modules/@cloudflare/workerd-darwin-arm64": {
+ "version": "1.20260609.1",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260609.1.tgz",
+ "integrity": "sha512-4kKXfr7ZHU6xQ/R9ShdSuj1A1bEouoRcHzUWdjnuMPBlRsAAVanlxAVYISotFUulLEinayOpRFbhpsfwzrpSSw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/nitro/node_modules/@cloudflare/workerd-linux-64": {
+ "version": "1.20260609.1",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260609.1.tgz",
+ "integrity": "sha512-T2Ebir2OPHAvvZ0HUh5mi1lN8q30sVi4lf7LIpc28AHoWtoOmJ0jA5AJK4IYJm1MKEbBldq+QsckaHOCQFmRpQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/nitro/node_modules/@cloudflare/workerd-linux-arm64": {
+ "version": "1.20260609.1",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260609.1.tgz",
+ "integrity": "sha512-INfcYoSsKqEIvPL69/3RkqYoP8WUR0VEN6loWN/3tekXLoJrVOj3E5NjIetsdS8MJN6zc3st/ae4bMuWRRzoDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/nitro/node_modules/@cloudflare/workerd-windows-64": {
+ "version": "1.20260609.1",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260609.1.tgz",
+ "integrity": "sha512-EWhfxKI1aqUr7S8xuGxgmRCumEzB8iSsCIz6oEqJN+3pZuW3EWiKDGFW4EY1BmwNINLW1eO5VMGYb8Fj6FVYxA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/nitro/node_modules/chokidar": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
+ "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "readdirp": "^5.0.0"
+ },
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/nitro/node_modules/env-runner": {
+ "version": "0.1.10",
+ "resolved": "https://registry.npmjs.org/env-runner/-/env-runner-0.1.10.tgz",
+ "integrity": "sha512-3Q8BN6NuZ5XsSGs+URKoy5tq4bx4a7HYDoZ3MIp5iXJColHrnZoIhWcCRfb0hUVIdkt1JiBww4YRHR7Ea24m6w==",
+ "license": "MIT",
+ "dependencies": {
+ "crossws": "^0.4.5",
+ "exsolve": "^1.0.8",
+ "httpxy": "^0.5.3",
+ "srvx": "^0.11.16"
+ },
+ "bin": {
+ "env-runner": "dist/cli.mjs"
+ },
+ "peerDependencies": {
+ "@netlify/runtime": "^4.1.23",
+ "@vercel/queue": "^0.2.0",
+ "miniflare": "^4.20260515.0"
+ },
+ "peerDependenciesMeta": {
+ "@netlify/runtime": {
+ "optional": true
+ },
+ "@vercel/queue": {
+ "optional": true
+ },
+ "miniflare": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/nitro/node_modules/lru-cache": {
+ "version": "11.2.7",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz",
+ "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==",
+ "license": "BlueOak-1.0.0",
+ "optional": true,
+ "peer": true,
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/nitro/node_modules/miniflare": {
+ "version": "4.20260609.0",
+ "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20260609.0.tgz",
+ "integrity": "sha512-4ZfNh9ACDa/mKKQvTSO2vigyQS2MB7dEU02KRPle4FqL7S6nek+2Fq6WGzazZbt1OORYgb4OGVLnOCx+My2NNA==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "@cspotcode/source-map-support": "0.8.1",
+ "sharp": "0.34.5",
+ "undici": "7.24.8",
+ "workerd": "1.20260609.1",
+ "ws": "8.20.1",
+ "youch": "4.1.0-beta.10"
+ },
+ "bin": {
+ "miniflare": "bootstrap.js"
+ },
+ "engines": {
+ "node": ">=22.0.0"
+ }
+ },
+ "node_modules/nitro/node_modules/readdirp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
+ "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/nitro/node_modules/undici": {
+ "version": "7.24.8",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.8.tgz",
+ "integrity": "sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "engines": {
+ "node": ">=20.18.1"
+ }
+ },
+ "node_modules/nitro/node_modules/unstorage": {
+ "version": "2.0.0-alpha.7",
+ "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-2.0.0-alpha.7.tgz",
+ "integrity": "sha512-ELPztchk2zgFJnakyodVY3vJWGW9jy//keJ32IOJVGUMyaPydwcA1FtVvWqT0TNRch9H+cMNEGllfVFfScImog==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@azure/app-configuration": "^1.11.0",
+ "@azure/cosmos": "^4.9.1",
+ "@azure/data-tables": "^13.3.2",
+ "@azure/identity": "^4.13.0",
+ "@azure/keyvault-secrets": "^4.10.0",
+ "@azure/storage-blob": "^12.31.0",
+ "@capacitor/preferences": "^6 || ^7 || ^8",
+ "@deno/kv": ">=0.13.0",
+ "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0",
+ "@planetscale/database": "^1.19.0",
+ "@upstash/redis": "^1.36.2",
+ "@vercel/blob": ">=0.27.3",
+ "@vercel/functions": "^2.2.12 || ^3.0.0",
+ "@vercel/kv": "^1.0.1",
+ "aws4fetch": "^1.0.20",
+ "chokidar": "^4 || ^5",
+ "db0": ">=0.3.4",
+ "idb-keyval": "^6.2.2",
+ "ioredis": "^5.9.3",
+ "lru-cache": "^11.2.6",
+ "mongodb": "^6 || ^7",
+ "ofetch": "*",
+ "uploadthing": "^7.7.4"
+ },
+ "peerDependenciesMeta": {
+ "@azure/app-configuration": {
+ "optional": true
+ },
+ "@azure/cosmos": {
+ "optional": true
+ },
+ "@azure/data-tables": {
+ "optional": true
+ },
+ "@azure/identity": {
+ "optional": true
+ },
+ "@azure/keyvault-secrets": {
+ "optional": true
+ },
+ "@azure/storage-blob": {
+ "optional": true
+ },
+ "@capacitor/preferences": {
+ "optional": true
+ },
+ "@deno/kv": {
+ "optional": true
+ },
+ "@netlify/blobs": {
+ "optional": true
+ },
+ "@planetscale/database": {
+ "optional": true
+ },
+ "@upstash/redis": {
+ "optional": true
+ },
+ "@vercel/blob": {
+ "optional": true
+ },
+ "@vercel/functions": {
+ "optional": true
+ },
+ "@vercel/kv": {
+ "optional": true
+ },
+ "aws4fetch": {
+ "optional": true
+ },
+ "chokidar": {
+ "optional": true
+ },
+ "db0": {
+ "optional": true
+ },
+ "idb-keyval": {
+ "optional": true
+ },
+ "ioredis": {
+ "optional": true
+ },
+ "lru-cache": {
+ "optional": true
+ },
+ "mongodb": {
+ "optional": true
+ },
+ "ofetch": {
+ "optional": true
+ },
+ "uploadthing": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/nitro/node_modules/workerd": {
+ "version": "1.20260609.1",
+ "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260609.1.tgz",
+ "integrity": "sha512-KF/Y/8f4VoXCk87NuU6RqmO0X5fdzcrxU3XzAgoPUpnH9t1ZyzRgX1O/9sJvjItxroCBTEBzKssda02Dz9i6BA==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "peer": true,
+ "bin": {
+ "workerd": "bin/workerd"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "optionalDependencies": {
+ "@cloudflare/workerd-darwin-64": "1.20260609.1",
+ "@cloudflare/workerd-darwin-arm64": "1.20260609.1",
+ "@cloudflare/workerd-linux-64": "1.20260609.1",
+ "@cloudflare/workerd-linux-arm64": "1.20260609.1",
+ "@cloudflare/workerd-windows-64": "1.20260609.1"
+ }
+ },
+ "node_modules/nitro/node_modules/ws": {
+ "version": "8.20.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz",
+ "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
"node_modules/node-releases": {
"version": "2.0.38",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz",
@@ -8982,6 +9785,27 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/ocache": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/ocache/-/ocache-0.1.4.tgz",
+ "integrity": "sha512-e7geNdWjxSnvsSgvLuPvgKgu7ubM10ZmTPOgpr7mz2BXYtvjMKTiLhjFi/gWU8chkuP6hNkZBsa9LzOusyaqkQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ohash": "^2.0.11"
+ }
+ },
+ "node_modules/ofetch": {
+ "version": "2.0.0-alpha.3",
+ "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-2.0.0-alpha.3.tgz",
+ "integrity": "sha512-zpYTCs2byOuft65vI3z43Dd6iSdFbOZZLb9/d21aCpx2rGastVU9dOCv0lu4ykc1Ur1anAYjDi3SUvR0vq50JA==",
+ "license": "MIT"
+ },
+ "node_modules/ohash": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
+ "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
+ "license": "MIT"
+ },
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@@ -10006,13 +10830,52 @@
"node": ">= 0.8.15"
}
},
+ "node_modules/rolldown": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.0.tgz",
+ "integrity": "sha512-zpMvlJhs5PkXRTtKc0CaLBVI9AR/VDiJFpM+kx//hgToEca7FgMlGjaRIisXBcb19T76LswgmKECSQ96hjWr5A==",
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.134.0",
+ "@rolldown/pluginutils": "^1.0.0"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm64": "1.1.0",
+ "@rolldown/binding-darwin-arm64": "1.1.0",
+ "@rolldown/binding-darwin-x64": "1.1.0",
+ "@rolldown/binding-freebsd-x64": "1.1.0",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.1.0",
+ "@rolldown/binding-linux-arm64-gnu": "1.1.0",
+ "@rolldown/binding-linux-arm64-musl": "1.1.0",
+ "@rolldown/binding-linux-ppc64-gnu": "1.1.0",
+ "@rolldown/binding-linux-s390x-gnu": "1.1.0",
+ "@rolldown/binding-linux-x64-gnu": "1.1.0",
+ "@rolldown/binding-linux-x64-musl": "1.1.0",
+ "@rolldown/binding-openharmony-arm64": "1.1.0",
+ "@rolldown/binding-wasm32-wasi": "1.1.0",
+ "@rolldown/binding-win32-arm64-msvc": "1.1.0",
+ "@rolldown/binding-win32-x64-msvc": "1.1.0"
+ }
+ },
+ "node_modules/rolldown/node_modules/@rolldown/pluginutils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
+ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
+ "license": "MIT"
+ },
"node_modules/rollup": {
- "version": "4.60.3",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.3.tgz",
- "integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==",
+ "version": "4.61.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.1.tgz",
+ "integrity": "sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==",
"license": "MIT",
"dependencies": {
- "@types/estree": "1.0.8"
+ "@types/estree": "1.0.9"
},
"bin": {
"rollup": "dist/bin/rollup"
@@ -10022,40 +10885,34 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.60.3",
- "@rollup/rollup-android-arm64": "4.60.3",
- "@rollup/rollup-darwin-arm64": "4.60.3",
- "@rollup/rollup-darwin-x64": "4.60.3",
- "@rollup/rollup-freebsd-arm64": "4.60.3",
- "@rollup/rollup-freebsd-x64": "4.60.3",
- "@rollup/rollup-linux-arm-gnueabihf": "4.60.3",
- "@rollup/rollup-linux-arm-musleabihf": "4.60.3",
- "@rollup/rollup-linux-arm64-gnu": "4.60.3",
- "@rollup/rollup-linux-arm64-musl": "4.60.3",
- "@rollup/rollup-linux-loong64-gnu": "4.60.3",
- "@rollup/rollup-linux-loong64-musl": "4.60.3",
- "@rollup/rollup-linux-ppc64-gnu": "4.60.3",
- "@rollup/rollup-linux-ppc64-musl": "4.60.3",
- "@rollup/rollup-linux-riscv64-gnu": "4.60.3",
- "@rollup/rollup-linux-riscv64-musl": "4.60.3",
- "@rollup/rollup-linux-s390x-gnu": "4.60.3",
- "@rollup/rollup-linux-x64-gnu": "4.60.3",
- "@rollup/rollup-linux-x64-musl": "4.60.3",
- "@rollup/rollup-openbsd-x64": "4.60.3",
- "@rollup/rollup-openharmony-arm64": "4.60.3",
- "@rollup/rollup-win32-arm64-msvc": "4.60.3",
- "@rollup/rollup-win32-ia32-msvc": "4.60.3",
- "@rollup/rollup-win32-x64-gnu": "4.60.3",
- "@rollup/rollup-win32-x64-msvc": "4.60.3",
+ "@rollup/rollup-android-arm-eabi": "4.61.1",
+ "@rollup/rollup-android-arm64": "4.61.1",
+ "@rollup/rollup-darwin-arm64": "4.61.1",
+ "@rollup/rollup-darwin-x64": "4.61.1",
+ "@rollup/rollup-freebsd-arm64": "4.61.1",
+ "@rollup/rollup-freebsd-x64": "4.61.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.61.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.61.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.61.1",
+ "@rollup/rollup-linux-arm64-musl": "4.61.1",
+ "@rollup/rollup-linux-loong64-gnu": "4.61.1",
+ "@rollup/rollup-linux-loong64-musl": "4.61.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.61.1",
+ "@rollup/rollup-linux-ppc64-musl": "4.61.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.61.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.61.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.61.1",
+ "@rollup/rollup-linux-x64-gnu": "4.61.1",
+ "@rollup/rollup-linux-x64-musl": "4.61.1",
+ "@rollup/rollup-openbsd-x64": "4.61.1",
+ "@rollup/rollup-openharmony-arm64": "4.61.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.61.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.61.1",
+ "@rollup/rollup-win32-x64-gnu": "4.61.1",
+ "@rollup/rollup-win32-x64-msvc": "4.61.1",
"fsevents": "~2.3.2"
}
},
- "node_modules/rollup/node_modules/@types/estree": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
- "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
- "license": "MIT"
- },
"node_modules/rou3": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/rou3/-/rou3-0.8.1.tgz",
@@ -10385,9 +11242,9 @@
}
},
"node_modules/srvx": {
- "version": "0.11.15",
- "resolved": "https://registry.npmjs.org/srvx/-/srvx-0.11.15.tgz",
- "integrity": "sha512-iXsux0UcOjdvs0LCMa2Ws3WwcDUozA3JN3BquNXkaFPP7TpRqgunKdEgoZ/uwb1J6xaYHfxtz9Twlh6yzwM6Tg==",
+ "version": "0.11.16",
+ "resolved": "https://registry.npmjs.org/srvx/-/srvx-0.11.16.tgz",
+ "integrity": "sha512-bp07zRuycfTY43IjAvvTFnmnJi8ikW0VFiHwOhhYcVW/L4xQ1XY4PAd4Nuum1rsA17C39zL7x+CDhrn5AL32Rw==",
"license": "MIT",
"bin": {
"srvx": "bin/srvx.mjs"
diff --git a/package.json b/package.json
index 473337b9..474d1604 100644
--- a/package.json
+++ b/package.json
@@ -28,7 +28,7 @@
"trust:verify": "node scripts/verify-trust-attestation.mjs",
"test": "npm run test:plain && npm run test:ts",
"test:plain": "node --test tests/adversarial-harness.test.mjs tests/trust.test.mjs tests/release-signing.test.mjs tests/cli-install.test.mjs tests/trust-attestation.test.mjs",
- "test:ts": "node --experimental-strip-types --test tests/site-stats.test.mjs tests/prompt-injection-guard.test.mjs tests/audit-skills.test.mjs tests/runtime.test.mjs tests/integrations.test.mjs tests/growth-revenue-split.test.mjs tests/trust-badge.test.mjs tests/bounties.test.mjs tests/trust-scoring.test.mjs tests/adversarial-holdout.test.mjs tests/adversarial-judge.test.mjs"
+ "test:ts": "node --experimental-strip-types --test tests/site-stats.test.mjs tests/certify.test.mjs tests/prompt-injection-guard.test.mjs tests/audit-skills.test.mjs tests/runtime.test.mjs tests/integrations.test.mjs tests/growth-revenue-split.test.mjs tests/trust-badge.test.mjs tests/bounties.test.mjs tests/trust-scoring.test.mjs tests/adversarial-holdout.test.mjs tests/adversarial-judge.test.mjs"
},
"dependencies": {
"@ai-sdk/openai-compatible": "^2.0.47",
diff --git a/src/components/site/home/Proof.tsx b/src/components/site/home/Proof.tsx
index 03f47d25..161cedcc 100644
--- a/src/components/site/home/Proof.tsx
+++ b/src/components/site/home/Proof.tsx
@@ -69,12 +69,20 @@ export function Proof() {
execution feeds the Trust Score. Skills that survive real workloads bubble up; ones that
drift get re-scored and patched automatically.
-
- See how skills are evaluated →
-
+
+
+ See how skills are evaluated →
+
+
+ Certify your own skill →
+
+
diff --git a/src/lib/badges/trust-badge.ts b/src/lib/badges/trust-badge.ts
index 3fe431e4..489f86c8 100644
--- a/src/lib/badges/trust-badge.ts
+++ b/src/lib/badges/trust-badge.ts
@@ -26,19 +26,38 @@ export function renderTrustBadge(b: BadgeInput): string {
return svg(label, valueText, color);
}
-export function renderAdversarialBadge(opts: { vertical?: string; pass_rate?: number | null }): string {
+export function renderAdversarialBadge(opts: {
+ vertical?: string;
+ pass_rate?: number | null;
+}): string {
const label = `adversarial${opts.vertical ? `:${opts.vertical}` : ""}`;
const has = typeof opts.pass_rate === "number" && Number.isFinite(opts.pass_rate);
const valueText = has ? `${(opts.pass_rate! * 100).toFixed(0)}%` : "—";
const color = has
- ? (opts.pass_rate! >= 0.9 ? COLOR_HEX.green
- : opts.pass_rate! >= 0.75 ? COLOR_HEX.yellow
- : opts.pass_rate! >= 0.5 ? COLOR_HEX.orange
- : COLOR_HEX.red)
+ ? opts.pass_rate! >= 0.9
+ ? COLOR_HEX.green
+ : opts.pass_rate! >= 0.75
+ ? COLOR_HEX.yellow
+ : opts.pass_rate! >= 0.5
+ ? COLOR_HEX.orange
+ : COLOR_HEX.red
: COLOR_HEX.gray;
return svg(label, valueText, color);
}
+// Badge for external certifications (/api/public/certify). The label is
+// "skill audit" — NOT "trust score" — because a content audit alone doesn't
+// carry the adversarial + real-world evidence the registry Trust Score does.
+export function renderCertifiedBadge(opts: {
+ value?: string | null;
+ score0to100?: number | null;
+}): string {
+ const has = typeof opts.score0to100 === "number" && Number.isFinite(opts.score0to100);
+ const valueText = has && opts.value ? opts.value : "—";
+ const color = has ? COLOR_HEX[badgeColor(opts.score0to100! / 100)] : COLOR_HEX.gray;
+ return svg("skill audit", valueText, color);
+}
+
// Minimal shields.io-shaped badge — no external font needed.
function svg(label: string, value: string, color: string): string {
const labelW = textWidth(label) + 10;
@@ -70,5 +89,8 @@ function textWidth(s: string): number {
}
function esc(s: string): string {
- return s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c]!));
+ return s.replace(
+ /[&<>"]/g,
+ (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]!,
+ );
}
diff --git a/src/lib/certify/certify.server.ts b/src/lib/certify/certify.server.ts
new file mode 100644
index 00000000..11b6b591
--- /dev/null
+++ b/src/lib/certify/certify.server.ts
@@ -0,0 +1,108 @@
+// Server-side core of the public certification API.
+// Runs the same proprietary review engine as the `review_skill` MCP tool,
+// records the result keyed by content hash, and hands back a badge the
+// author can embed anywhere — even for skills never hosted on the registry.
+
+import { supabaseAdmin as _supabaseAdmin } from "@/integrations/supabase/client.server";
+import { computeReview } from "@/lib/mcp/tools/skills";
+import { inspectContent } from "@/lib/security/prompt-injection-guard";
+import {
+ CERTIFY_ENGINE_VERSION,
+ type CertificationRecord,
+ type CertifyRequest,
+ sha256Hex,
+} from "./core";
+
+// external_certifications is newer than the generated Supabase types; the
+// admin client is cast like the other API routes until types are regenerated.
+const supabaseAdmin = _supabaseAdmin as any;
+
+export interface CertifyResult {
+ record: CertificationRecord;
+ report: {
+ verdict_score: number;
+ structural_score: number;
+ content_quality_score: number;
+ top_actions: unknown[];
+ input_warning: unknown;
+ injection_findings: { severity: string; count: number };
+ };
+}
+
+export async function certifyContent(req: CertifyRequest): Promise {
+ // The guard never blocks a certification — a hostile file simply scores
+ // what it scores — but findings are persisted so the verify endpoint can
+ // surface them and the badge can't launder an injection attempt.
+ const guard = inspectContent(req.content);
+
+ const core = await computeReview({
+ name: req.name,
+ type: req.type,
+ content: req.content,
+ doc_class: "auto",
+ semantic_check: true,
+ });
+
+ const content_sha256 = await sha256Hex(req.content);
+ const verdict_score = Number(core.verdict_score ?? 0);
+ const overall_score = Math.round(verdict_score);
+ const grade = String(core.grade ?? "F — rewrite recommended");
+
+ const { data, error } = await supabaseAdmin
+ .from("external_certifications")
+ .insert({
+ name: req.name,
+ type: req.type,
+ content_sha256,
+ overall_score,
+ grade,
+ engine_version: CERTIFY_ENGINE_VERSION,
+ report: {
+ structural_score: core.structural_score,
+ content_quality_score: core.content_quality_score,
+ doc_class: core.doc_class,
+ language: core.language,
+ input_warning: core.input_warning ?? null,
+ guard: { severity: guard.severity, findings: guard.findings.length },
+ },
+ })
+ .select("id, created_at")
+ .single();
+ if (error || !data) {
+ throw new Error(`failed to persist certification: ${error?.message ?? "no row returned"}`);
+ }
+
+ return {
+ record: {
+ id: data.id,
+ name: req.name,
+ type: req.type,
+ content_sha256,
+ overall_score,
+ grade,
+ engine_version: CERTIFY_ENGINE_VERSION,
+ created_at: data.created_at,
+ },
+ report: {
+ verdict_score,
+ structural_score: Number(core.structural_score ?? 0),
+ content_quality_score: Number(core.content_quality_score ?? 0),
+ top_actions: (core.top_actions as unknown[]) ?? [],
+ input_warning: core.input_warning ?? null,
+ injection_findings: { severity: guard.severity, count: guard.findings.length },
+ },
+ };
+}
+
+export async function getCertification(
+ id: string,
+): Promise<(CertificationRecord & { report: unknown }) | null> {
+ const { data } = await supabaseAdmin
+ .from("external_certifications")
+ .select(
+ "id, name, type, content_sha256, overall_score, grade, engine_version, report, created_at",
+ )
+ .eq("id", id)
+ .maybeSingle();
+ return data ?? null;
+}
diff --git a/src/lib/certify/core.ts b/src/lib/certify/core.ts
new file mode 100644
index 00000000..3151cc58
--- /dev/null
+++ b/src/lib/certify/core.ts
@@ -0,0 +1,78 @@
+// Pure helpers for the public certification API ("trust badge as a service").
+// Kept free of Supabase/network imports so they can be unit-tested directly.
+
+export const CERTIFY_ENGINE_VERSION = "1.0.0";
+
+export const CERTIFIABLE_TYPES = ["skill", "playbook", "soul", "guardrail"] as const;
+export type CertifiableType = (typeof CERTIFIABLE_TYPES)[number];
+
+export const CONTENT_MIN_CHARS = 20;
+export const CONTENT_MAX_CHARS = 120_000;
+
+export interface CertificationRecord {
+ id: string;
+ name: string;
+ type: CertifiableType;
+ content_sha256: string;
+ overall_score: number; // 0-100, review engine verdict axis
+ grade: string; // "A — battle-ready" … "F — rewrite recommended"
+ engine_version: string;
+ created_at: string;
+}
+
+/** SHA-256 hex via Web Crypto — works on Cloudflare Workers and Node 18+. */
+export async function sha256Hex(content: string): Promise {
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(content));
+ return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
+}
+
+export interface CertifyRequest {
+ name: string;
+ type: CertifiableType;
+ content: string;
+}
+
+/** Validate the POST body without zod so the contract is testable in isolation. */
+export function parseCertifyRequest(
+ body: unknown,
+): { ok: true; value: CertifyRequest } | { ok: false; error: string } {
+ if (typeof body !== "object" || body === null)
+ return { ok: false, error: "body must be a JSON object" };
+ const b = body as Record;
+ const name = typeof b.name === "string" ? b.name.trim() : "";
+ if (!name || name.length > 200) return { ok: false, error: "name is required (1-200 chars)" };
+ const type = typeof b.type === "string" ? b.type : "skill";
+ if (!CERTIFIABLE_TYPES.includes(type as CertifiableType)) {
+ return { ok: false, error: `type must be one of: ${CERTIFIABLE_TYPES.join(", ")}` };
+ }
+ const content = typeof b.content === "string" ? b.content : "";
+ if (content.length < CONTENT_MIN_CHARS) {
+ return {
+ ok: false,
+ error: `content is required (>= ${CONTENT_MIN_CHARS} chars) — pass the raw file, not a summary`,
+ };
+ }
+ if (content.length > CONTENT_MAX_CHARS) {
+ return { ok: false, error: `content too large (max ${CONTENT_MAX_CHARS} chars)` };
+ }
+ return { ok: true, value: { name, type: type as CertifiableType, content } };
+}
+
+/** Short value shown on the badge, e.g. "B · 84/100". */
+export function badgeValue(record: Pick): string {
+ const letter = record.grade.charAt(0);
+ return `${letter} · ${record.overall_score}/100`;
+}
+
+export function badgeUrl(origin: string, id: string): string {
+ return `${origin}/api/badges/certified/${id}.svg`;
+}
+
+export function verifyUrl(origin: string, id: string): string {
+ return `${origin}/api/public/certifications/${id}`;
+}
+
+/** Markdown snippet authors paste into their README. */
+export function embedMarkdown(origin: string, id: string): string {
+ return `[})](${verifyUrl(origin, id)})`;
+}
diff --git a/src/lib/mcp/tools/skills.ts b/src/lib/mcp/tools/skills.ts
index 430d22d5..f2adc94e 100644
--- a/src/lib/mcp/tools/skills.ts
+++ b/src/lib/mcp/tools/skills.ts
@@ -1325,7 +1325,7 @@ export const getMethodologyTool = defineTool({
// review_skills_batch. Produces the full scored payload EXCEPT the per-call
// concerns (caller delta + feedback request + next_steps), which the tools add.
// ----------------------------------------------------------------------------
-type ReviewArgs = {
+export type ReviewArgs = {
name: string;
type: "skill" | "playbook" | "soul" | "guardrail";
content: string;
@@ -1346,7 +1346,9 @@ function reviewCacheKey(a: ReviewArgs): string {
return [a.type, a.doc_class, a.semantic_check ? "sem" : "nosem", a.language ?? "auto", fnv1aHex(a.content)].join("|");
}
-async function computeReview(a: ReviewArgs): Promise> {
+// Exported so the public certification API (/api/public/certify) runs the
+// exact same engine as the review_skill MCP tool — one scorer, two doors.
+export async function computeReview(a: ReviewArgs): Promise> {
const cacheKey = reviewCacheKey(a);
const hit = REVIEW_CACHE.get(cacheKey);
if (hit && Date.now() - hit.at < REVIEW_CACHE_TTL_MS) {
diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts
index 6ac449c8..b7ce64f0 100644
--- a/src/routeTree.gen.ts
+++ b/src/routeTree.gen.ts
@@ -37,6 +37,7 @@ import { Route as DiscoverRouteImport } from './routes/discover'
import { Route as ContributorFaqRouteImport } from './routes/contributor-faq'
import { Route as ConnectRouteImport } from './routes/connect'
import { Route as CommunityRouteImport } from './routes/community'
+import { Route as CertifyRouteImport } from './routes/certify'
import { Route as BountiesRouteImport } from './routes/bounties'
import { Route as AdminRouteImport } from './routes/admin'
import { Route as IndexRouteImport } from './routes/index'
@@ -90,6 +91,7 @@ import { Route as ForgeReportSlugRouteImport } from './routes/forge.report.$slug
import { Route as ApiPublicTelemetryRouteImport } from './routes/api/public/telemetry'
import { Route as ApiPublicSearchRouteImport } from './routes/api/public/search'
import { Route as ApiPublicPackagesRouteImport } from './routes/api/public/packages'
+import { Route as ApiPublicCertifyRouteImport } from './routes/api/public/certify'
import { Route as ApiPackagesUploadRouteImport } from './routes/api/packages.upload'
import { Route as ApiMcpHealthRouteImport } from './routes/api/mcp/health'
import { Route as ApiJobsDrainUploadQueueRouteImport } from './routes/api/jobs/drain-upload-queue'
@@ -106,6 +108,7 @@ import { Route as ApiPublicOauthRevokeRouteImport } from './routes/api/public/oa
import { Route as ApiPublicOauthRegisterRouteImport } from './routes/api/public/oauth/register'
import { Route as ApiPublicMcpHealthRouteImport } from './routes/api/public/mcp.health'
import { Route as ApiPublicHooksPrewarmSharePromosRouteImport } from './routes/api/public/hooks/prewarm-share-promos'
+import { Route as ApiPublicCertificationsIdRouteImport } from './routes/api/public/certifications.$id'
import { Route as ApiPublicDotwellKnownOauthProtectedResourceRouteImport } from './routes/api/public/[.]well-known.oauth-protected-resource'
import { Route as ApiPublicDotwellKnownOauthAuthorizationServerRouteImport } from './routes/api/public/[.]well-known.oauth-authorization-server'
import { Route as ApiOgSkillOfTheWeekSvgRouteImport } from './routes/api/og.skill-of-the-week.svg'
@@ -113,6 +116,7 @@ import { Route as ApiIntegrationsSlugInstallRouteImport } from './routes/api/int
import { Route as DotwellKnownOauthProtectedResourceApiMcpRouteImport } from './routes/[.]well-known.oauth-protected-resource.api.mcp'
import { Route as DotwellKnownOauthAuthorizationServerApiMcpRouteImport } from './routes/[.]well-known.oauth-authorization-server.api.mcp'
import { Route as ApiBadgesTrustSlugSvgRouteImport } from './routes/api/badges.trust.$slug.svg'
+import { Route as ApiBadgesCertifiedIdSvgRouteImport } from './routes/api/badges.certified.$id.svg'
import { Route as ApiPacksCustomizationIdDownloadExtRouteImport } from './routes/api/packs.customization.$id.download.$ext'
const WelcomeRoute = WelcomeRouteImport.update({
@@ -255,6 +259,11 @@ const CommunityRoute = CommunityRouteImport.update({
path: '/community',
getParentRoute: () => rootRouteImport,
} as any)
+const CertifyRoute = CertifyRouteImport.update({
+ id: '/certify',
+ path: '/certify',
+ getParentRoute: () => rootRouteImport,
+} as any)
const BountiesRoute = BountiesRouteImport.update({
id: '/bounties',
path: '/bounties',
@@ -522,6 +531,11 @@ const ApiPublicPackagesRoute = ApiPublicPackagesRouteImport.update({
path: '/api/public/packages',
getParentRoute: () => rootRouteImport,
} as any)
+const ApiPublicCertifyRoute = ApiPublicCertifyRouteImport.update({
+ id: '/api/public/certify',
+ path: '/api/public/certify',
+ getParentRoute: () => rootRouteImport,
+} as any)
const ApiPackagesUploadRoute = ApiPackagesUploadRouteImport.update({
id: '/api/packages/upload',
path: '/api/packages/upload',
@@ -605,6 +619,12 @@ const ApiPublicHooksPrewarmSharePromosRoute =
path: '/api/public/hooks/prewarm-share-promos',
getParentRoute: () => rootRouteImport,
} as any)
+const ApiPublicCertificationsIdRoute =
+ ApiPublicCertificationsIdRouteImport.update({
+ id: '/api/public/certifications/$id',
+ path: '/api/public/certifications/$id',
+ getParentRoute: () => rootRouteImport,
+ } as any)
const ApiPublicDotwellKnownOauthProtectedResourceRoute =
ApiPublicDotwellKnownOauthProtectedResourceRouteImport.update({
id: '/api/public/.well-known/oauth-protected-resource',
@@ -645,6 +665,11 @@ const ApiBadgesTrustSlugSvgRoute = ApiBadgesTrustSlugSvgRouteImport.update({
path: '/api/badges/trust/$slug/svg',
getParentRoute: () => rootRouteImport,
} as any)
+const ApiBadgesCertifiedIdSvgRoute = ApiBadgesCertifiedIdSvgRouteImport.update({
+ id: '/api/badges/certified/$id/svg',
+ path: '/api/badges/certified/$id/svg',
+ getParentRoute: () => rootRouteImport,
+} as any)
const ApiPacksCustomizationIdDownloadExtRoute =
ApiPacksCustomizationIdDownloadExtRouteImport.update({
id: '/api/packs/customization/$id/download/$ext',
@@ -656,6 +681,7 @@ export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/admin': typeof AdminRouteWithChildren
'/bounties': typeof BountiesRouteWithChildren
+ '/certify': typeof CertifyRoute
'/community': typeof CommunityRoute
'/connect': typeof ConnectRouteWithChildren
'/contributor-faq': typeof ContributorFaqRoute
@@ -734,6 +760,7 @@ export interface FileRoutesByFullPath {
'/api/jobs/drain-upload-queue': typeof ApiJobsDrainUploadQueueRoute
'/api/mcp/health': typeof ApiMcpHealthRoute
'/api/packages/upload': typeof ApiPackagesUploadRoute
+ '/api/public/certify': typeof ApiPublicCertifyRoute
'/api/public/packages': typeof ApiPublicPackagesRouteWithChildren
'/api/public/search': typeof ApiPublicSearchRoute
'/api/public/telemetry': typeof ApiPublicTelemetryRoute
@@ -747,6 +774,7 @@ export interface FileRoutesByFullPath {
'/api/og/skill-of-the-week/svg': typeof ApiOgSkillOfTheWeekSvgRoute
'/api/public/.well-known/oauth-authorization-server': typeof ApiPublicDotwellKnownOauthAuthorizationServerRoute
'/api/public/.well-known/oauth-protected-resource': typeof ApiPublicDotwellKnownOauthProtectedResourceRoute
+ '/api/public/certifications/$id': typeof ApiPublicCertificationsIdRoute
'/api/public/hooks/prewarm-share-promos': typeof ApiPublicHooksPrewarmSharePromosRoute
'/api/public/mcp/health': typeof ApiPublicMcpHealthRoute
'/api/public/oauth/register': typeof ApiPublicOauthRegisterRoute
@@ -756,12 +784,14 @@ export interface FileRoutesByFullPath {
'/api/public/payments/webhook': typeof ApiPublicPaymentsWebhookRoute
'/api/skills/$slug/export': typeof ApiSkillsSlugExportRoute
'/api/skills/$slug/export.md': typeof ApiSkillsSlugExportDotmdRoute
+ '/api/badges/certified/$id/svg': typeof ApiBadgesCertifiedIdSvgRoute
'/api/badges/trust/$slug/svg': typeof ApiBadgesTrustSlugSvgRoute
'/api/packs/customization/$id/download/$ext': typeof ApiPacksCustomizationIdDownloadExtRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/bounties': typeof BountiesRouteWithChildren
+ '/certify': typeof CertifyRoute
'/community': typeof CommunityRoute
'/connect': typeof ConnectRouteWithChildren
'/contributor-faq': typeof ContributorFaqRoute
@@ -840,6 +870,7 @@ export interface FileRoutesByTo {
'/api/jobs/drain-upload-queue': typeof ApiJobsDrainUploadQueueRoute
'/api/mcp/health': typeof ApiMcpHealthRoute
'/api/packages/upload': typeof ApiPackagesUploadRoute
+ '/api/public/certify': typeof ApiPublicCertifyRoute
'/api/public/packages': typeof ApiPublicPackagesRouteWithChildren
'/api/public/search': typeof ApiPublicSearchRoute
'/api/public/telemetry': typeof ApiPublicTelemetryRoute
@@ -853,6 +884,7 @@ export interface FileRoutesByTo {
'/api/og/skill-of-the-week/svg': typeof ApiOgSkillOfTheWeekSvgRoute
'/api/public/.well-known/oauth-authorization-server': typeof ApiPublicDotwellKnownOauthAuthorizationServerRoute
'/api/public/.well-known/oauth-protected-resource': typeof ApiPublicDotwellKnownOauthProtectedResourceRoute
+ '/api/public/certifications/$id': typeof ApiPublicCertificationsIdRoute
'/api/public/hooks/prewarm-share-promos': typeof ApiPublicHooksPrewarmSharePromosRoute
'/api/public/mcp/health': typeof ApiPublicMcpHealthRoute
'/api/public/oauth/register': typeof ApiPublicOauthRegisterRoute
@@ -862,6 +894,7 @@ export interface FileRoutesByTo {
'/api/public/payments/webhook': typeof ApiPublicPaymentsWebhookRoute
'/api/skills/$slug/export': typeof ApiSkillsSlugExportRoute
'/api/skills/$slug/export.md': typeof ApiSkillsSlugExportDotmdRoute
+ '/api/badges/certified/$id/svg': typeof ApiBadgesCertifiedIdSvgRoute
'/api/badges/trust/$slug/svg': typeof ApiBadgesTrustSlugSvgRoute
'/api/packs/customization/$id/download/$ext': typeof ApiPacksCustomizationIdDownloadExtRoute
}
@@ -870,6 +903,7 @@ export interface FileRoutesById {
'/': typeof IndexRoute
'/admin': typeof AdminRouteWithChildren
'/bounties': typeof BountiesRouteWithChildren
+ '/certify': typeof CertifyRoute
'/community': typeof CommunityRoute
'/connect': typeof ConnectRouteWithChildren
'/contributor-faq': typeof ContributorFaqRoute
@@ -948,6 +982,7 @@ export interface FileRoutesById {
'/api/jobs/drain-upload-queue': typeof ApiJobsDrainUploadQueueRoute
'/api/mcp/health': typeof ApiMcpHealthRoute
'/api/packages/upload': typeof ApiPackagesUploadRoute
+ '/api/public/certify': typeof ApiPublicCertifyRoute
'/api/public/packages': typeof ApiPublicPackagesRouteWithChildren
'/api/public/search': typeof ApiPublicSearchRoute
'/api/public/telemetry': typeof ApiPublicTelemetryRoute
@@ -961,6 +996,7 @@ export interface FileRoutesById {
'/api/og/skill-of-the-week/svg': typeof ApiOgSkillOfTheWeekSvgRoute
'/api/public/.well-known/oauth-authorization-server': typeof ApiPublicDotwellKnownOauthAuthorizationServerRoute
'/api/public/.well-known/oauth-protected-resource': typeof ApiPublicDotwellKnownOauthProtectedResourceRoute
+ '/api/public/certifications/$id': typeof ApiPublicCertificationsIdRoute
'/api/public/hooks/prewarm-share-promos': typeof ApiPublicHooksPrewarmSharePromosRoute
'/api/public/mcp/health': typeof ApiPublicMcpHealthRoute
'/api/public/oauth/register': typeof ApiPublicOauthRegisterRoute
@@ -970,6 +1006,7 @@ export interface FileRoutesById {
'/api/public/payments/webhook': typeof ApiPublicPaymentsWebhookRoute
'/api/skills/$slug/export': typeof ApiSkillsSlugExportRoute
'/api/skills/$slug/export.md': typeof ApiSkillsSlugExportDotmdRoute
+ '/api/badges/certified/$id/svg': typeof ApiBadgesCertifiedIdSvgRoute
'/api/badges/trust/$slug/svg': typeof ApiBadgesTrustSlugSvgRoute
'/api/packs/customization/$id/download/$ext': typeof ApiPacksCustomizationIdDownloadExtRoute
}
@@ -979,6 +1016,7 @@ export interface FileRouteTypes {
| '/'
| '/admin'
| '/bounties'
+ | '/certify'
| '/community'
| '/connect'
| '/contributor-faq'
@@ -1057,6 +1095,7 @@ export interface FileRouteTypes {
| '/api/jobs/drain-upload-queue'
| '/api/mcp/health'
| '/api/packages/upload'
+ | '/api/public/certify'
| '/api/public/packages'
| '/api/public/search'
| '/api/public/telemetry'
@@ -1070,6 +1109,7 @@ export interface FileRouteTypes {
| '/api/og/skill-of-the-week/svg'
| '/api/public/.well-known/oauth-authorization-server'
| '/api/public/.well-known/oauth-protected-resource'
+ | '/api/public/certifications/$id'
| '/api/public/hooks/prewarm-share-promos'
| '/api/public/mcp/health'
| '/api/public/oauth/register'
@@ -1079,12 +1119,14 @@ export interface FileRouteTypes {
| '/api/public/payments/webhook'
| '/api/skills/$slug/export'
| '/api/skills/$slug/export.md'
+ | '/api/badges/certified/$id/svg'
| '/api/badges/trust/$slug/svg'
| '/api/packs/customization/$id/download/$ext'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
| '/bounties'
+ | '/certify'
| '/community'
| '/connect'
| '/contributor-faq'
@@ -1163,6 +1205,7 @@ export interface FileRouteTypes {
| '/api/jobs/drain-upload-queue'
| '/api/mcp/health'
| '/api/packages/upload'
+ | '/api/public/certify'
| '/api/public/packages'
| '/api/public/search'
| '/api/public/telemetry'
@@ -1176,6 +1219,7 @@ export interface FileRouteTypes {
| '/api/og/skill-of-the-week/svg'
| '/api/public/.well-known/oauth-authorization-server'
| '/api/public/.well-known/oauth-protected-resource'
+ | '/api/public/certifications/$id'
| '/api/public/hooks/prewarm-share-promos'
| '/api/public/mcp/health'
| '/api/public/oauth/register'
@@ -1185,6 +1229,7 @@ export interface FileRouteTypes {
| '/api/public/payments/webhook'
| '/api/skills/$slug/export'
| '/api/skills/$slug/export.md'
+ | '/api/badges/certified/$id/svg'
| '/api/badges/trust/$slug/svg'
| '/api/packs/customization/$id/download/$ext'
id:
@@ -1192,6 +1237,7 @@ export interface FileRouteTypes {
| '/'
| '/admin'
| '/bounties'
+ | '/certify'
| '/community'
| '/connect'
| '/contributor-faq'
@@ -1270,6 +1316,7 @@ export interface FileRouteTypes {
| '/api/jobs/drain-upload-queue'
| '/api/mcp/health'
| '/api/packages/upload'
+ | '/api/public/certify'
| '/api/public/packages'
| '/api/public/search'
| '/api/public/telemetry'
@@ -1283,6 +1330,7 @@ export interface FileRouteTypes {
| '/api/og/skill-of-the-week/svg'
| '/api/public/.well-known/oauth-authorization-server'
| '/api/public/.well-known/oauth-protected-resource'
+ | '/api/public/certifications/$id'
| '/api/public/hooks/prewarm-share-promos'
| '/api/public/mcp/health'
| '/api/public/oauth/register'
@@ -1292,6 +1340,7 @@ export interface FileRouteTypes {
| '/api/public/payments/webhook'
| '/api/skills/$slug/export'
| '/api/skills/$slug/export.md'
+ | '/api/badges/certified/$id/svg'
| '/api/badges/trust/$slug/svg'
| '/api/packs/customization/$id/download/$ext'
fileRoutesById: FileRoutesById
@@ -1300,6 +1349,7 @@ export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
AdminRoute: typeof AdminRouteWithChildren
BountiesRoute: typeof BountiesRouteWithChildren
+ CertifyRoute: typeof CertifyRoute
CommunityRoute: typeof CommunityRoute
ConnectRoute: typeof ConnectRouteWithChildren
ContributorFaqRoute: typeof ContributorFaqRoute
@@ -1358,6 +1408,7 @@ export interface RootRouteChildren {
ApiAdminAiGatewayProbeRoute: typeof ApiAdminAiGatewayProbeRoute
ApiJobsDrainUploadQueueRoute: typeof ApiJobsDrainUploadQueueRoute
ApiPackagesUploadRoute: typeof ApiPackagesUploadRoute
+ ApiPublicCertifyRoute: typeof ApiPublicCertifyRoute
ApiPublicPackagesRoute: typeof ApiPublicPackagesRouteWithChildren
ApiPublicSearchRoute: typeof ApiPublicSearchRoute
ApiPublicTelemetryRoute: typeof ApiPublicTelemetryRoute
@@ -1367,6 +1418,7 @@ export interface RootRouteChildren {
ApiOgSkillOfTheWeekSvgRoute: typeof ApiOgSkillOfTheWeekSvgRoute
ApiPublicDotwellKnownOauthAuthorizationServerRoute: typeof ApiPublicDotwellKnownOauthAuthorizationServerRoute
ApiPublicDotwellKnownOauthProtectedResourceRoute: typeof ApiPublicDotwellKnownOauthProtectedResourceRoute
+ ApiPublicCertificationsIdRoute: typeof ApiPublicCertificationsIdRoute
ApiPublicHooksPrewarmSharePromosRoute: typeof ApiPublicHooksPrewarmSharePromosRoute
ApiPublicMcpHealthRoute: typeof ApiPublicMcpHealthRoute
ApiPublicOauthRegisterRoute: typeof ApiPublicOauthRegisterRoute
@@ -1375,6 +1427,7 @@ export interface RootRouteChildren {
ApiPublicPaymentsWebhookRoute: typeof ApiPublicPaymentsWebhookRoute
ApiSkillsSlugExportRoute: typeof ApiSkillsSlugExportRoute
ApiSkillsSlugExportDotmdRoute: typeof ApiSkillsSlugExportDotmdRoute
+ ApiBadgesCertifiedIdSvgRoute: typeof ApiBadgesCertifiedIdSvgRoute
ApiBadgesTrustSlugSvgRoute: typeof ApiBadgesTrustSlugSvgRoute
ApiPacksCustomizationIdDownloadExtRoute: typeof ApiPacksCustomizationIdDownloadExtRoute
}
@@ -1577,6 +1630,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof CommunityRouteImport
parentRoute: typeof rootRouteImport
}
+ '/certify': {
+ id: '/certify'
+ path: '/certify'
+ fullPath: '/certify'
+ preLoaderRoute: typeof CertifyRouteImport
+ parentRoute: typeof rootRouteImport
+ }
'/bounties': {
id: '/bounties'
path: '/bounties'
@@ -1948,6 +2008,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ApiPublicPackagesRouteImport
parentRoute: typeof rootRouteImport
}
+ '/api/public/certify': {
+ id: '/api/public/certify'
+ path: '/api/public/certify'
+ fullPath: '/api/public/certify'
+ preLoaderRoute: typeof ApiPublicCertifyRouteImport
+ parentRoute: typeof rootRouteImport
+ }
'/api/packages/upload': {
id: '/api/packages/upload'
path: '/api/packages/upload'
@@ -2060,6 +2127,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ApiPublicHooksPrewarmSharePromosRouteImport
parentRoute: typeof rootRouteImport
}
+ '/api/public/certifications/$id': {
+ id: '/api/public/certifications/$id'
+ path: '/api/public/certifications/$id'
+ fullPath: '/api/public/certifications/$id'
+ preLoaderRoute: typeof ApiPublicCertificationsIdRouteImport
+ parentRoute: typeof rootRouteImport
+ }
'/api/public/.well-known/oauth-protected-resource': {
id: '/api/public/.well-known/oauth-protected-resource'
path: '/api/public/.well-known/oauth-protected-resource'
@@ -2109,6 +2183,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ApiBadgesTrustSlugSvgRouteImport
parentRoute: typeof rootRouteImport
}
+ '/api/badges/certified/$id/svg': {
+ id: '/api/badges/certified/$id/svg'
+ path: '/api/badges/certified/$id/svg'
+ fullPath: '/api/badges/certified/$id/svg'
+ preLoaderRoute: typeof ApiBadgesCertifiedIdSvgRouteImport
+ parentRoute: typeof rootRouteImport
+ }
'/api/packs/customization/$id/download/$ext': {
id: '/api/packs/customization/$id/download/$ext'
path: '/api/packs/customization/$id/download/$ext'
@@ -2280,6 +2361,7 @@ const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
AdminRoute: AdminRouteWithChildren,
BountiesRoute: BountiesRouteWithChildren,
+ CertifyRoute: CertifyRoute,
CommunityRoute: CommunityRoute,
ConnectRoute: ConnectRouteWithChildren,
ContributorFaqRoute: ContributorFaqRoute,
@@ -2340,6 +2422,7 @@ const rootRouteChildren: RootRouteChildren = {
ApiAdminAiGatewayProbeRoute: ApiAdminAiGatewayProbeRoute,
ApiJobsDrainUploadQueueRoute: ApiJobsDrainUploadQueueRoute,
ApiPackagesUploadRoute: ApiPackagesUploadRoute,
+ ApiPublicCertifyRoute: ApiPublicCertifyRoute,
ApiPublicPackagesRoute: ApiPublicPackagesRouteWithChildren,
ApiPublicSearchRoute: ApiPublicSearchRoute,
ApiPublicTelemetryRoute: ApiPublicTelemetryRoute,
@@ -2351,6 +2434,7 @@ const rootRouteChildren: RootRouteChildren = {
ApiPublicDotwellKnownOauthAuthorizationServerRoute,
ApiPublicDotwellKnownOauthProtectedResourceRoute:
ApiPublicDotwellKnownOauthProtectedResourceRoute,
+ ApiPublicCertificationsIdRoute: ApiPublicCertificationsIdRoute,
ApiPublicHooksPrewarmSharePromosRoute: ApiPublicHooksPrewarmSharePromosRoute,
ApiPublicMcpHealthRoute: ApiPublicMcpHealthRoute,
ApiPublicOauthRegisterRoute: ApiPublicOauthRegisterRoute,
@@ -2359,6 +2443,7 @@ const rootRouteChildren: RootRouteChildren = {
ApiPublicPaymentsWebhookRoute: ApiPublicPaymentsWebhookRoute,
ApiSkillsSlugExportRoute: ApiSkillsSlugExportRoute,
ApiSkillsSlugExportDotmdRoute: ApiSkillsSlugExportDotmdRoute,
+ ApiBadgesCertifiedIdSvgRoute: ApiBadgesCertifiedIdSvgRoute,
ApiBadgesTrustSlugSvgRoute: ApiBadgesTrustSlugSvgRoute,
ApiPacksCustomizationIdDownloadExtRoute:
ApiPacksCustomizationIdDownloadExtRoute,
diff --git a/src/routes/account.billing.tsx b/src/routes/account.billing.tsx
index dc029900..3881542f 100644
--- a/src/routes/account.billing.tsx
+++ b/src/routes/account.billing.tsx
@@ -13,11 +13,14 @@ export const Route = createFileRoute("/account/billing")({
head: () => ({
meta: [
{ title: "Billing — Super Agent Skill" },
- { name: "description", content: "Manage your Super Agent Skill subscription, plan and invoices." },
+ {
+ name: "description",
+ content: "Manage your Super Agent Skill subscription, plan and invoices.",
+ },
{ name: "robots", content: "noindex" },
],
}),
- validateSearch: (s: Record) => ({
+ validateSearch: (s: Record): { checkout?: string; session_id?: string } => ({
checkout: typeof s.checkout === "string" ? s.checkout : undefined,
session_id: typeof s.session_id === "string" ? s.session_id : undefined,
}),
@@ -40,7 +43,10 @@ function BillingPage() {
toast.success("Payment received — finalizing your subscription...");
const t = setInterval(refetch, 2500);
const stop = setTimeout(() => clearInterval(t), 30000);
- return () => { clearInterval(t); clearTimeout(stop); };
+ return () => {
+ clearInterval(t);
+ clearTimeout(stop);
+ };
}
}, [search.checkout, refetch]);
@@ -75,13 +81,19 @@ function BillingPage() {
-
Account
+
+ Account
+
Billing
{user?.email}
- Usage
- Tokens
+
+ Usage
+
+
+ Tokens
+
@@ -100,9 +112,11 @@ function BillingPage() {
)}
{subscription && (
-
+
{subscription.status}
@@ -141,8 +155,14 @@ function BillingPage() {
{isOpen && (
-
-
e.stopPropagation()}>
+
+
e.stopPropagation()}
+ >
-
+
{included ? "✓" : "—"}
{title}
diff --git a/src/routes/api/badges.certified.$id.svg.ts b/src/routes/api/badges.certified.$id.svg.ts
new file mode 100644
index 00000000..af146d67
--- /dev/null
+++ b/src/routes/api/badges.certified.$id.svg.ts
@@ -0,0 +1,41 @@
+import { createFileRoute } from "@tanstack/react-router";
+import { renderCertifiedBadge } from "@/lib/badges/trust-badge";
+import { getCertification } from "@/lib/certify/certify.server";
+import { badgeValue } from "@/lib/certify/core";
+
+// GET /api/badges/certified/.svg
+// → SVG "skill audit" badge for an external certification issued by
+// POST /api/public/certify. Embeddable in any README:
+// 
+// Cached for 5 minutes at the edge; renders a gray "—" badge on any error.
+
+export const Route = createFileRoute("/api/badges/certified/$id/svg")({
+ server: {
+ handlers: {
+ GET: async ({ params }) => {
+ const id = params.id.replace(/\.svg$/i, "");
+ let value: string | null = null;
+ let score: number | null = null;
+ try {
+ const record = await getCertification(id);
+ if (record) {
+ score = record.overall_score;
+ value = badgeValue(record);
+ }
+ } catch {
+ /* render gray "—" badge on any error */
+ }
+
+ const svg = renderCertifiedBadge({ value, score0to100: score });
+ return new Response(svg, {
+ status: 200,
+ headers: {
+ "content-type": "image/svg+xml; charset=utf-8",
+ "cache-control": "public, max-age=300, s-maxage=300",
+ "x-content-type-options": "nosniff",
+ },
+ });
+ },
+ },
+ },
+});
diff --git a/src/routes/api/public/certifications.$id.ts b/src/routes/api/public/certifications.$id.ts
new file mode 100644
index 00000000..5f11dd7b
--- /dev/null
+++ b/src/routes/api/public/certifications.$id.ts
@@ -0,0 +1,48 @@
+import { createFileRoute } from "@tanstack/react-router";
+import { getCertification } from "@/lib/certify/certify.server";
+import { badgeUrl, embedMarkdown } from "@/lib/certify/core";
+
+const CORS = {
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Methods": "GET, OPTIONS",
+ "Access-Control-Allow-Headers": "Content-Type, Accept",
+};
+
+// GET /api/public/certifications/
+// Public verification record for a certified skill: anyone holding the file
+// can sha256 it and compare with content_sha256 to confirm the badge refers
+// to exactly this content.
+export const Route = createFileRoute("/api/public/certifications/$id")({
+ server: {
+ handlers: {
+ OPTIONS: async () => new Response(null, { status: 204, headers: CORS }),
+ GET: async ({ request, params }) => {
+ const record = await getCertification(params.id).catch(() => null);
+ if (!record) {
+ return new Response(JSON.stringify({ error: "not_found" }), {
+ status: 404,
+ headers: { "Content-Type": "application/json", ...CORS },
+ });
+ }
+ const origin = new URL(request.url).origin;
+ return new Response(
+ JSON.stringify({
+ ...record,
+ badge_url: badgeUrl(origin, record.id),
+ embed_markdown: embedMarkdown(origin, record.id),
+ how_to_verify:
+ "sha256 the skill file you received and compare with content_sha256. A match proves this exact content earned this score on the issued date.",
+ }),
+ {
+ status: 200,
+ headers: {
+ "Content-Type": "application/json",
+ "cache-control": "public, max-age=300, s-maxage=300",
+ ...CORS,
+ },
+ },
+ );
+ },
+ },
+ },
+});
diff --git a/src/routes/api/public/certify.ts b/src/routes/api/public/certify.ts
new file mode 100644
index 00000000..b1109735
--- /dev/null
+++ b/src/routes/api/public/certify.ts
@@ -0,0 +1,94 @@
+import { createFileRoute } from "@tanstack/react-router";
+import { certifyContent } from "@/lib/certify/certify.server";
+import { badgeUrl, embedMarkdown, parseCertifyRequest, verifyUrl } from "@/lib/certify/core";
+
+const CORS = {
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
+ "Access-Control-Allow-Headers": "Content-Type, Accept",
+};
+
+function json(body: unknown, status = 200): Response {
+ return new Response(JSON.stringify(body), {
+ status,
+ headers: { "Content-Type": "application/json", ...CORS },
+ });
+}
+
+// Best-effort per-isolate limiter. A warm Workers isolate serves many requests,
+// so this blunts abuse loops without a shared store; real quota enforcement can
+// move to a durable counter when the endpoint gets traction.
+const RATE = new Map();
+const RATE_LIMIT = 10;
+const RATE_WINDOW_MS = 60 * 60 * 1000;
+
+function rateLimited(ip: string): boolean {
+ const now = Date.now();
+ const bucket = RATE.get(ip);
+ if (!bucket || now - bucket.windowStart > RATE_WINDOW_MS) {
+ RATE.set(ip, { count: 1, windowStart: now });
+ if (RATE.size > 5000) RATE.clear();
+ return false;
+ }
+ bucket.count += 1;
+ return bucket.count > RATE_LIMIT;
+}
+
+// POST /api/public/certify
+// { name, type, content } → runs the same proprietary engine as the
+// `review_skill` MCP tool, stores the result keyed by content SHA-256, and
+// returns a permanent badge + verification URL. Works for ANY skill file —
+// the author doesn't need to host it on the registry.
+export const Route = createFileRoute("/api/public/certify")({
+ server: {
+ handlers: {
+ OPTIONS: async () => new Response(null, { status: 204, headers: CORS }),
+ POST: async ({ request }) => {
+ const ip =
+ request.headers.get("cf-connecting-ip") ??
+ request.headers.get("x-forwarded-for") ??
+ "unknown";
+ if (rateLimited(ip)) {
+ return json(
+ { error: "rate_limited", detail: `max ${RATE_LIMIT} certifications per hour` },
+ 429,
+ );
+ }
+
+ let body: unknown;
+ try {
+ body = await request.json();
+ } catch {
+ return json({ error: "invalid_json" }, 400);
+ }
+ const parsed = parseCertifyRequest(body);
+ if (!parsed.ok) return json({ error: "invalid_request", detail: parsed.error }, 400);
+
+ try {
+ const { record, report } = await certifyContent(parsed.value);
+ const origin = new URL(request.url).origin;
+ return json({
+ id: record.id,
+ name: record.name,
+ type: record.type,
+ content_sha256: record.content_sha256,
+ overall_score: record.overall_score,
+ grade: record.grade,
+ engine_version: record.engine_version,
+ created_at: record.created_at,
+ badge_url: badgeUrl(origin, record.id),
+ verify_url: verifyUrl(origin, record.id),
+ embed_markdown: embedMarkdown(origin, record.id),
+ report,
+ note:
+ "This is a content audit by the SuperAgentSkill review engine — the same scorer behind the review_skill MCP tool. " +
+ "To verify a badge: fetch verify_url, sha256 the file you were given, and compare with content_sha256.",
+ });
+ } catch (e) {
+ console.error("[api/public/certify] failed:", e);
+ return json({ error: "internal_server_error" }, 500);
+ }
+ },
+ },
+ },
+});
diff --git a/src/routes/certify.tsx b/src/routes/certify.tsx
new file mode 100644
index 00000000..a4a99ce5
--- /dev/null
+++ b/src/routes/certify.tsx
@@ -0,0 +1,132 @@
+import { createFileRoute, Link } from "@tanstack/react-router";
+import { BadgeCheck, FileCheck, ShieldCheck } from "lucide-react";
+import { SitePage } from "@/components/site/SitePage";
+import { CodeBlock } from "@/components/site/CodeBlock";
+
+export const Route = createFileRoute("/certify")({
+ head: () => ({
+ meta: [
+ { title: "Certify any skill — Super Agent Skill" },
+ {
+ name: "description",
+ content:
+ "Run any skill file through the SuperAgentSkill review engine and get a permanent, verifiable audit badge — even for skills you host yourself. One POST, one badge, public verification.",
+ },
+ { property: "og:title", content: "Certify any skill file — audit badge as a service" },
+ { property: "og:url", content: "https://superagentskill.com/certify" },
+ ],
+ links: [{ rel: "canonical", href: "https://superagentskill.com/certify" }],
+ }),
+ component: CertifyPage,
+});
+
+const CURL_EXAMPLE = `curl -X POST https://superagentskill.com/api/public/certify \\
+ -H 'Content-Type: application/json' \\
+ -d '{
+ "name": "my-incident-response.md",
+ "type": "skill",
+ "content": ""
+ }'`;
+
+const RESPONSE_EXAMPLE = `{
+ "id": "0b7c…",
+ "overall_score": 84,
+ "grade": "B — solid, minor gaps",
+ "content_sha256": "9f2a…",
+ "badge_url": "https://superagentskill.com/api/badges/certified/0b7c….svg",
+ "verify_url": "https://superagentskill.com/api/public/certifications/0b7c…",
+ "embed_markdown": "[](…/certifications/0b7c…)",
+ "report": { "top_actions": [ … ], "injection_findings": { "severity": "none" } }
+}`;
+
+function CertifyPage() {
+ return (
+
+
+
+ Certification API
+
+
+ Certify any skill. Even ones we don't host.
+
+
+ One POST runs your skill file through the same proprietary review engine behind the{" "}
+ review_skill MCP tool — and issues a permanent
+ audit badge bound to the file's SHA-256. Anyone holding the file can verify the badge
+ refers to exactly that content. No account, no registry upload.
+
+
+
+ {[
+ {
+ icon: FileCheck,
+ title: "Content-bound",
+ body: "The badge is keyed to the file's SHA-256 — change one byte and verification fails.",
+ },
+ {
+ icon: ShieldCheck,
+ title: "Injection-screened",
+ body: "Submissions run through the prompt-injection guard; findings are recorded on the public verification record.",
+ },
+ {
+ icon: BadgeCheck,
+ title: "Embeddable anywhere",
+ body: "A shields-style SVG for your README, docs or marketplace listing — every impression links back to the verification record.",
+ },
+ ].map((h) => (
+
+
+
+ {h.title}
+
+
{h.body}
+
+ ))}
+
+
+ 1. Submit the file
+
+ Pass the raw file content — not a summary. Up to 120k characters; 10 certifications per
+ hour per IP.
+
+
+
+
+
+ 2. Get the badge
+
+ The response includes the score, the grade, the permanent badge URL and a ready-to-paste
+ markdown snippet.
+
+
+
+
+
+ 3. Anyone can verify
+
+ The verification record at verify_url is
+ public. A reviewer hashes the file they were given (
+ sha256sum my-skill.md) and compares it with{" "}
+ content_sha256 — a match proves this exact
+ content earned this score on the issued date.
+
+
+
+
+ Audit badge vs. Trust Score
+
+
+ The skill audit badge scores the content
+ itself. The registry Trust Score goes further —
+ adversarial pass rate per attack class, real-world success telemetry and Ed25519-signed
+ releases. To earn the full Trust Score,{" "}
+
+ publish your skill to the registry
+
+ .
+
+
+
+
+ );
+}
diff --git a/src/routes/discover.tsx b/src/routes/discover.tsx
index d159d2ac..4191223f 100644
--- a/src/routes/discover.tsx
+++ b/src/routes/discover.tsx
@@ -1,4 +1,10 @@
-import { createFileRoute, Link, useRouter, useNavigate, useRouterState } from "@tanstack/react-router";
+import {
+ createFileRoute,
+ Link,
+ useRouter,
+ useNavigate,
+ useRouterState,
+} from "@tanstack/react-router";
import { useEffect, useMemo, useState } from "react";
import { useServerFn } from "@tanstack/react-start";
import { useMutation } from "@tanstack/react-query";
@@ -31,6 +37,8 @@ const SearchSchema = z.object({
page: z.number().int().min(1).catch(1).default(1),
});
+type DiscoverSearch = z.infer;
+
const PAGE_SIZE = 24;
function DiscoverPending() {
@@ -130,7 +138,11 @@ const STYLES = [
const GOVERNANCE_LEVELS = [
{ key: "L1", label: "L1 · Observability", body: "Logs decisions and outputs. No blocking." },
- { key: "L2", label: "L2 · Soft warnings", body: "Flags risky outputs. User confirms to proceed." },
+ {
+ key: "L2",
+ label: "L2 · Soft warnings",
+ body: "Flags risky outputs. User confirms to proceed.",
+ },
{ key: "L3", label: "L3 · Hard block", body: "Blocks unsafe actions. Escalates to a human." },
{ key: "L4", label: "L4 · Sandboxed", body: "Read-only mode. No writes, no external calls." },
];
@@ -225,9 +237,7 @@ function Pagination({
onPage(p)}
- className={
- btn + (p === page ? " border-primary bg-primary/10 text-foreground" : "")
- }
+ className={btn + (p === page ? " border-primary bg-primary/10 text-foreground" : "")}
>
{p}
@@ -256,7 +266,10 @@ function pageItemToItem(p: DiscoverItem): Item {
new Set(
[
(p.vertical ?? "general").toLowerCase(),
- ...p.slug.toLowerCase().split(/[\s-]+/).filter((w) => w.length > 2),
+ ...p.slug
+ .toLowerCase()
+ .split(/[\s-]+/)
+ .filter((w) => w.length > 2),
].slice(0, 5),
),
);
@@ -273,7 +286,8 @@ function pageItemToItem(p: DiscoverItem): Item {
}
function DiscoverPage() {
- const loaderData = Route.useLoaderData() as import("@/lib/marketplace/discover.functions").DiscoverPage;
+ const loaderData =
+ Route.useLoaderData() as import("@/lib/marketplace/discover.functions").DiscoverPage;
const search = Route.useSearch();
const router = useRouter();
const navigate = useNavigate({ from: "/discover" });
@@ -286,10 +300,7 @@ function DiscoverPage() {
const total = loaderData.total;
const fetchedAt = loaderData.fetchedAt;
- const items: Item[] = useMemo(
- () => loaderData.items.map(pageItemToItem),
- [loaderData.items],
- );
+ const items: Item[] = useMemo(() => loaderData.items.map(pageItemToItem), [loaderData.items]);
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
// Detect any in-flight loader/navigation so we can show subtle loading UI.
@@ -305,7 +316,11 @@ function DiscoverPage() {
if (query === current) return;
const id = window.setTimeout(() => {
navigate({
- search: (s: Record) => ({ ...s, q: query.trim() ? query.trim() : undefined, page: 1 }),
+ search: (s: DiscoverSearch) => ({
+ ...s,
+ q: query.trim() ? query.trim() : undefined,
+ page: 1,
+ }),
replace: true,
});
}, 300);
@@ -344,23 +359,23 @@ function DiscoverPage() {
if (t === type) return;
setPendingType(t);
navigate({
- search: (s: Record) => ({ ...s, type: t, category: undefined, page: 1 }),
+ search: (s: DiscoverSearch) => ({ ...s, type: t, category: undefined, page: 1 }),
replace: true,
});
};
const setCategory = (c: string | null) =>
navigate({
- search: (s: Record) => ({ ...s, category: c ?? undefined, page: 1 }),
+ search: (s: DiscoverSearch) => ({ ...s, category: c ?? undefined, page: 1 }),
replace: true,
});
const goToPage = (p: number) =>
- navigate({ search: (s: Record) => ({ ...s, page: p }), replace: false });
+ navigate({ search: (s: DiscoverSearch) => ({ ...s, page: p }), replace: false });
const setSort = (sortValue: SortKey) =>
navigate({
- search: (s: Record) => ({ ...s, sort: sortValue, page: 1 }),
+ search: (s: DiscoverSearch) => ({ ...s, sort: sortValue, page: 1 }),
replace: true,
});
const sort: SortKey = (search.sort ?? "popular") as SortKey;
@@ -504,7 +519,8 @@ function DiscoverPage() {
{total === 1 ? "" : "s"}
{category && (
<>
- {" "}in {category}
+ {" "}
+ in {category}
setCategory(null)}
className="ml-2 text-xs text-muted-foreground underline hover:text-foreground"
@@ -564,11 +580,7 @@ function DiscoverPage() {
{totalPages > 1 && (
-
+
)}
>
)}
@@ -669,7 +681,9 @@ function FacetGroup({
onClick={() => onChange(null)}
className={
"flex w-full items-center justify-between rounded-md px-2 py-1.5 text-left text-sm transition-colors " +
- (value === null ? "bg-accent text-foreground" : "text-muted-foreground hover:bg-accent/50 hover:text-foreground")
+ (value === null
+ ? "bg-accent text-foreground"
+ : "text-muted-foreground hover:bg-accent/50 hover:text-foreground")
}
>
All
@@ -721,7 +735,9 @@ function EmptyState({
›_
-
No matching {TYPE_META[type].plural.toLowerCase()} yet.
+
+ No matching {TYPE_META[type].plural.toLowerCase()} yet.
+
SkillForge can research the state of the art and ship a beta — usually in under a minute.
@@ -731,7 +747,9 @@ function EmptyState({
disabled={isPending}
className="inline-flex h-9 items-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground hover:opacity-95 disabled:opacity-60"
>
- {isPending ? "Researching → authoring → evaluating…" : `Auto-create "${query || `custom ${type}`}"`}
+ {isPending
+ ? "Researching → authoring → evaluating…"
+ : `Auto-create "${query || `custom ${type}`}"`}
Shipped: {result.package.name}
- slug {result.package.slug} · research {result.research_used ? "✓" : "—"} ·
- {" "}{result.stages.length} pipeline stages
+ slug {result.package.slug} · research {result.research_used ? "✓" : "—"} ·{" "}
+ {result.stages.length} pipeline stages
{result.evaluation && (
- Evaluator: overall {Math.round(result.evaluation.overall_score)} ·
- verdict {result.evaluation.verdict}
+ Evaluator: overall {Math.round(result.evaluation.overall_score)} · verdict{" "}
+ {result.evaluation.verdict}
)}
({
meta: [
{ title: "Docs — Super Agent Skill" },
- { name: "description", content: "Connect any AI agent to Super Agent Skill through MCP. Quickstart, capabilities, and SkillForge." },
+ {
+ name: "description",
+ content:
+ "Connect any AI agent to Super Agent Skill through MCP. Quickstart, capabilities, and SkillForge.",
+ },
{ property: "og:title", content: "Docs — Super Agent Skill" },
- { property: "og:description", content: "Quickstart, MCP gateway, skills, playbooks, souls, guardrails and SkillForge." },
+ {
+ property: "og:description",
+ content: "Quickstart, MCP gateway, skills, playbooks, souls, guardrails and SkillForge.",
+ },
{ property: "og:url", content: "https://superagentskill.com/docs" },
{ property: "og:type", content: "article" },
],
@@ -21,7 +28,8 @@ export const Route = createFileRoute("/docs")({
"@context": "https://schema.org",
"@type": "Article",
headline: "Super Agent Skill — Documentation",
- description: "Connect any AI agent to Super Agent Skill through MCP. Quickstart, capabilities, and SkillForge.",
+ description:
+ "Connect any AI agent to Super Agent Skill through MCP. Quickstart, capabilities, and SkillForge.",
url: "https://superagentskill.com/docs",
author: { "@type": "Organization", name: "Super Agent Skill" },
}),
@@ -37,10 +45,26 @@ function Docs() {
- Documentation
+
+ Documentation
+
- {["Quickstart", "MCP Gateway", "Skills", "Playbooks", "Souls", "Guardrails", "SkillForge", "API Reference"].map((s) => (
-
+ {[
+ "Quickstart",
+ "MCP Gateway",
+ "Skills",
+ "Playbooks",
+ "Souls",
+ "Guardrails",
+ "SkillForge",
+ "Certification API",
+ "API Reference",
+ ].map((s) => (
+
{s}
))}
@@ -48,14 +72,24 @@ function Docs() {
- Quickstart
- Connect your agent in 30 seconds.
+
+ Quickstart
+
+
+ Connect your agent in 30 seconds.
+
- Super Agent Skill speaks MCP natively. Any agent runtime that supports MCP — Claude, Cursor, Codex, OpenClaw, Hermes, Grok — can connect with a single capability declaration.
+ Super Agent Skill speaks MCP natively. Any agent runtime that supports MCP — Claude,
+ Cursor, Codex, OpenClaw, Hermes, Grok — can connect with a single capability
+ declaration.
- 1. Add the MCP server
- Drop the Super Agent Skill gateway into your agent's MCP config.
+
+ 1. Add the MCP server
+
+
+ Drop the Super Agent Skill gateway into your agent's MCP config.
+
- 2. Trigger the upgrade
- Once connected, give your agent the keywords. It does the rest.
+
+ 2. Trigger the upgrade
+
+
+ Once connected, give your agent the keywords. It does the rest.
+
Super Agent Skill: check for updates and improvements
@@ -85,14 +123,31 @@ function Docs() {
/>
- 3. The four primitives
- Every agent runs on the same stack: skills , playbooks , souls , and guardrails .
+
+ 3. The four primitives
+
+
+ Every agent runs on the same stack:{" "}
+ skills ,{" "}
+ playbooks ,{" "}
+ souls , and{" "}
+ guardrails .
+
{[
- { t: "Skills", d: "Discrete capabilities — domain reasoning, tool use, structured outputs." },
- { t: "Playbooks", d: "Multi-step workflows that orchestrate skills toward an outcome." },
- { t: "Souls", d: "Personality, tone, and decision-making style as installable packages." },
+ {
+ t: "Skills",
+ d: "Discrete capabilities — domain reasoning, tool use, structured outputs.",
+ },
+ {
+ t: "Playbooks",
+ d: "Multi-step workflows that orchestrate skills toward an outcome.",
+ },
+ {
+ t: "Souls",
+ d: "Personality, tone, and decision-making style as installable packages.",
+ },
{ t: "Guardrails", d: "Safety boundaries enforced before output reaches the user." },
].map((x) => (
@@ -102,10 +157,28 @@ function Docs() {
))}
-
4. SkillForge: the evolution loop
+
+ 4. SkillForge: the evolution loop
+
Every install reports anonymized feedback. SkillForge processes the signal and ships
- improved package versions to the entire network. Your agent gets smarter while you sleep.
+ improved package versions to the entire network. Your agent gets smarter while you
+ sleep.
+
+
+
+ 5. Certification API
+
+
+ POST /api/public/certify{" "}
+ runs any skill file — even one you host yourself — through the same review engine as the{" "}
+ review_skill MCP tool and
+ issues a permanent audit badge bound to the file's SHA-256, with a public verification
+ record. See the{" "}
+
+ certification guide
+ {" "}
+ for the full flow.
diff --git a/src/routes/generate.tsx b/src/routes/generate.tsx
index 87341f2f..d9d8d376 100644
--- a/src/routes/generate.tsx
+++ b/src/routes/generate.tsx
@@ -28,7 +28,7 @@ import { LeadGateModal } from "@/components/generate/LeadGateModal";
export const Route = createFileRoute("/generate")({
component: GeneratePage,
- validateSearch: (search: Record) => ({
+ validateSearch: (search: Record): { prompt?: string } => ({
prompt: typeof search.prompt === "string" ? search.prompt : undefined,
}),
head: () => ({
diff --git a/src/routes/leaderboard.tsx b/src/routes/leaderboard.tsx
index 596b512c..0677bbfe 100644
--- a/src/routes/leaderboard.tsx
+++ b/src/routes/leaderboard.tsx
@@ -6,7 +6,12 @@ import { supabaseAdmin as _supabaseAdmin } from "@/integrations/supabase/client.
const supabaseAdmin = _supabaseAdmin as any;
import { rankLeaderboard } from "@/lib/growth/bounties";
-type Row = { referrer_user_id: string; activated_referrals: number; referral_earnings_cents: number; handle?: string | null };
+type Row = {
+ referrer_user_id: string;
+ activated_referrals: number;
+ referral_earnings_cents: number;
+ handle?: string | null;
+};
export const Route = createFileRoute("/leaderboard")({
loader: async () => {
@@ -21,13 +26,21 @@ export const Route = createFileRoute("/leaderboard")({
const { data: handles } = ids.length
? await supabaseAdmin.from("profiles").select("user_id,handle").in("user_id", ids)
: { data: [] };
- const handleMap = new Map((handles ?? []).map((h: { user_id: string; handle: string }) => [h.user_id, h.handle]));
- return { rows: ranked.map((r) => ({ ...r, handle: handleMap.get(r.referrer_user_id) ?? null })) };
+ const handleMap = new Map(
+ ((handles ?? []) as { user_id: string; handle: string }[]).map((h) => [h.user_id, h.handle]),
+ );
+ return {
+ rows: ranked.map((r) => ({ ...r, handle: handleMap.get(r.referrer_user_id) ?? null })),
+ };
},
head: () => ({
meta: [
{ title: "Affiliate Leaderboard — top referrers of the last 30 days" },
- { name: "description", content: "Top contributors driving signups to Super Agent Skill, ranked by activations and referral earnings over the past 30 days." },
+ {
+ name: "description",
+ content:
+ "Top contributors driving signups to Super Agent Skill, ranked by activations and referral earnings over the past 30 days.",
+ },
],
}),
component: LeaderboardPage,
@@ -37,40 +50,51 @@ function LeaderboardPage() {
const { rows } = Route.useLoaderData();
return (
-
- Affiliate Leaderboard
-
- Top referrers over the last 30 days. Earn 10% of every referred author's revenue share for 12 months.
- Get your link at /account/referrals .
-
+
+ Affiliate Leaderboard
+
+ Top referrers over the last 30 days. Earn 10% of every referred author's revenue share for
+ 12 months. Get your link at{" "}
+
+ /account/referrals
+
+ .
+
- {rows.length === 0 ? (
-
- ) : (
-
-
-
- # Referrer Activations Earnings
-
-
- {rows.map((r, i) => (
-
- {i + 1}
- @{r.handle ?? r.referrer_user_id.slice(0, 8)}
- {r.activated_referrals}
- ${(r.referral_earnings_cents / 100).toLocaleString()}
+ {rows.length === 0 ? (
+
+ ) : (
+
+
+
+
+ #
+ Referrer
+ Activations
+ Earnings
- ))}
-
-
-
- )}
-
+
+
+ {rows.map((r, i) => (
+
+ {i + 1}
+ @{r.handle ?? r.referrer_user_id.slice(0, 8)}
+ {r.activated_referrals}
+
+ ${(r.referral_earnings_cents / 100).toLocaleString()}
+
+
+ ))}
+
+
+
+ )}
+
);
}
diff --git a/src/routes/login.tsx b/src/routes/login.tsx
index 4f5d61e5..6442e830 100644
--- a/src/routes/login.tsx
+++ b/src/routes/login.tsx
@@ -13,12 +13,17 @@ export const Route = createFileRoute("/login")({
head: () => ({
meta: [
{ title: "Sign in — Super Agent Skill" },
- { name: "description", content: "Sign in to manage your connected agents, packages and subscription." },
+ {
+ name: "description",
+ content: "Sign in to manage your connected agents, packages and subscription.",
+ },
{ name: "robots", content: "noindex" },
],
links: [{ rel: "canonical", href: "https://superagentskill.com/login" }],
}),
- validateSearch: (s: Record) => ({ next: typeof s.next === "string" ? s.next : undefined }),
+ validateSearch: (s: Record): { next?: string } => ({
+ next: typeof s.next === "string" ? s.next : undefined,
+ }),
component: LoginPage,
});
@@ -50,13 +55,21 @@ function LoginPage() {
try {
const r = await claimReferral({ data: { code, source_url: window.location.href } });
if (r?.ok) clearStoredRef();
- } catch { /* non-fatal */ }
+ } catch {
+ /* non-fatal */
+ }
};
supabase.auth.getSession().then(async ({ data }) => {
- if (data.session) { await tryClaim(); goNext(); }
+ if (data.session) {
+ await tryClaim();
+ goNext();
+ }
});
const { data: sub } = supabase.auth.onAuthStateChange(async (_e, s) => {
- if (s) { await tryClaim(); goNext(); }
+ if (s) {
+ await tryClaim();
+ goNext();
+ }
});
return () => sub.subscription.unsubscribe();
}, [navigate, next]);
@@ -138,8 +151,12 @@ function LoginPage() {
- Forgot password?
- Create account →
+
+ Forgot password?
+
+
+ Create account →
+
@@ -151,10 +168,22 @@ function LoginPage() {
function GoogleIcon() {
return (
-
-
-
-
+
+
+
+
);
}
diff --git a/src/routes/run.$slug.tsx b/src/routes/run.$slug.tsx
index 91883692..49416d7b 100644
--- a/src/routes/run.$slug.tsx
+++ b/src/routes/run.$slug.tsx
@@ -6,7 +6,13 @@ import { supabaseAdmin as _supabaseAdmin } from "@/integrations/supabase/client.
const supabaseAdmin = _supabaseAdmin as any;
import { createSharedRun } from "@/lib/runs/shared.functions";
-type Pkg = { id: string; slug: string; name: string; description: string | null; is_published: boolean };
+type Pkg = {
+ id: string;
+ slug: string;
+ name: string;
+ description: string | null;
+ is_published: boolean;
+};
export const Route = createFileRoute("/run/$slug")({
validateSearch: (s: Record) => ({
@@ -20,7 +26,8 @@ export const Route = createFileRoute("/run/$slug")({
.maybeSingle();
if (error || !pkg) throw new Response("not found", { status: 404 });
- let sharedRun = null;
+ type SharedRun = { prompt: string; output: string; created_at: string; view_count: number };
+ let sharedRun: SharedRun | null = null;
const token = (deps as { token?: string } | undefined)?.token;
if (token) {
const { data: sr } = await supabaseAdmin
@@ -29,7 +36,7 @@ export const Route = createFileRoute("/run/$slug")({
.eq("share_token", token)
.eq("package_slug", params.slug)
.maybeSingle();
- sharedRun = sr ?? null;
+ sharedRun = (sr as SharedRun | null) ?? null;
if (sr) {
await supabaseAdmin
.from("shared_runs")
@@ -44,7 +51,10 @@ export const Route = createFileRoute("/run/$slug")({
head: ({ params, loaderData }) => ({
meta: [
{ title: `Try ${loaderData?.pkg.name ?? params.slug} — Super Agent Skill` },
- { name: "description", content: `Run ${loaderData?.pkg.name ?? params.slug} on a sample prompt and share the result.` },
+ {
+ name: "description",
+ content: `Run ${loaderData?.pkg.name ?? params.slug} on a sample prompt and share the result.`,
+ },
{ property: "og:title", content: `${loaderData?.pkg.name ?? params.slug} — try it now` },
],
}),
@@ -80,99 +90,129 @@ function RunPage() {
return (
-
- ← {pkg.name}
+
+
+ ← {pkg.name}
+
- Try {pkg.name}
- {pkg.description}
+ Try {pkg.name}
+ {pkg.description}
- {!sharedRun && (
-
- )}
+ {!sharedRun && (
+
+ setPrompt(e.target.value)}
+ />
+
+ {busy && (
+
+
+
+
+ )}
+ {busy ? "Running on the registry…" : "Run and share"}
+
+
+ )}
- {busy && !sharedRun && (
-
- )}
+ {busy && !sharedRun && (
+
+ )}
- {sharedRun && (
-
-
-
-
-
+
+
+
- SUPER · AGENT · SKILL
-
-
-
-
-
-
-
Prompt
-
{sharedRun.prompt}
-
Output
-
{sharedRun.output}
-
- Shared run · {new Date(sharedRun.created_at).toLocaleString()} · {sharedRun.view_count + 1} views
- Open in {pkg.name} →
-
-
-
- )}
+
+ SUPER · AGENT · SKILL
+
+
+
+
+
+
+
Prompt
+
{sharedRun.prompt}
+
Output
+
{sharedRun.output}
+
+
+ Shared run · {new Date(sharedRun.created_at).toLocaleString()} ·{" "}
+ {sharedRun.view_count + 1} views
+
+
+ Open in {pkg.name} →
+
+
+
+
+ )}
- {token && (
-
- Share link copied to your clipboard.
-
- )}
+ {token && (
+ Share link copied to your clipboard.
+ )}
-
- Want full output without watermark?{" "}
- Create an account {" "}
- or install locally with npx super-agent install {pkg.slug}.
-
-
+
+ Want full output without watermark?{" "}
+
+ Create an account
+ {" "}
+ or install locally with{" "}
+ npx super-agent install {pkg.slug}.
+
+
);
}
diff --git a/src/routes/signup.tsx b/src/routes/signup.tsx
index c8b9383a..777c136e 100644
--- a/src/routes/signup.tsx
+++ b/src/routes/signup.tsx
@@ -13,14 +13,24 @@ export const Route = createFileRoute("/signup")({
head: () => ({
meta: [
{ title: "Create account — Super Agent Skill" },
- { name: "description", content: "Create your Super Agent Skill account and connect any MCP-compatible agent in 30 seconds." },
+ {
+ name: "description",
+ content:
+ "Create your Super Agent Skill account and connect any MCP-compatible agent in 30 seconds.",
+ },
{ property: "og:title", content: "Create account — Super Agent Skill" },
- { property: "og:description", content: "Create your account and connect Claude, Cursor, Codex or Grok via MCP in 30 seconds." },
+ {
+ property: "og:description",
+ content:
+ "Create your account and connect Claude, Cursor, Codex or Grok via MCP in 30 seconds.",
+ },
{ name: "robots", content: "noindex" },
],
links: [{ rel: "canonical", href: "https://superagentskill.com/signup" }],
}),
- validateSearch: (s: Record) => ({ next: typeof s.next === "string" ? s.next : undefined }),
+ validateSearch: (s: Record): { next?: string } => ({
+ next: typeof s.next === "string" ? s.next : undefined,
+ }),
component: SignupPage,
});
@@ -44,13 +54,21 @@ function SignupPage() {
try {
const r = await claimReferral({ data: { code, source_url: window.location.href } });
if (r?.ok) clearStoredRef();
- } catch { /* non-fatal */ }
+ } catch {
+ /* non-fatal */
+ }
};
supabase.auth.getSession().then(async ({ data }) => {
- if (data.session) { await tryClaim(); navigate({ to: target, replace: true }); }
+ if (data.session) {
+ await tryClaim();
+ navigate({ to: target, replace: true });
+ }
});
const { data: sub } = supabase.auth.onAuthStateChange(async (_e, s) => {
- if (s) { await tryClaim(); navigate({ to: target, replace: true }); }
+ if (s) {
+ await tryClaim();
+ navigate({ to: target, replace: true });
+ }
});
return () => sub.subscription.unsubscribe();
}, [navigate, next]);
@@ -67,7 +85,11 @@ function SignupPage() {
password,
options: {
emailRedirectTo: window.location.origin,
- data: { full_name: name, accepted_terms_at: new Date().toISOString(), accepted_ip_assignment: true },
+ data: {
+ full_name: name,
+ accepted_terms_at: new Date().toISOString(),
+ accepted_ip_assignment: true,
+ },
},
});
setBusy(false);
@@ -101,7 +123,9 @@ function SignupPage() {
Create your account
-
Start with the free Hacker plan. Upgrade anytime.
+
+ Start with the free Hacker plan. Upgrade anytime.
+
→
- Any skill, playbook, soul or guardrail you
- publish, submit for review, or upload to be improved becomes the{" "}
- exclusive property of Super Agent Skill, Inc.
+ Any skill, playbook, soul or guardrail {" "}
+ you publish, submit for review, or upload to be improved becomes the{" "}
+
+ exclusive property of Super Agent Skill, Inc.
+
→
- We may use, modify, sublicense and resell it
+ We may{" "}
+ use, modify, sublicense and resell it
worldwide, without further notice or compensation.
@@ -229,7 +256,9 @@ function SignupPage() {
Already have an account?{" "}
- Sign in
+
+ Sign in
+
diff --git a/src/routes/sitemap[.]xml.tsx b/src/routes/sitemap[.]xml.tsx
index 7b0c88fc..94753959 100644
--- a/src/routes/sitemap[.]xml.tsx
+++ b/src/routes/sitemap[.]xml.tsx
@@ -17,6 +17,7 @@ const STATIC_ROUTES: Array<{ path: string; priority: number; changefreq: string
{ path: "/generate", priority: 0.65, changefreq: "weekly" },
{ path: "/match", priority: 0.6, changefreq: "monthly" },
{ path: "/evaluation", priority: 0.6, changefreq: "monthly" },
+ { path: "/certify", priority: 0.6, changefreq: "monthly" },
{ path: "/community", priority: 0.6, changefreq: "weekly" },
{ path: "/login", priority: 0.4, changefreq: "yearly" },
{ path: "/signup", priority: 0.5, changefreq: "yearly" },
diff --git a/supabase/migrations/20260610130000_external_certifications.sql b/supabase/migrations/20260610130000_external_certifications.sql
new file mode 100644
index 00000000..908ffedd
--- /dev/null
+++ b/supabase/migrations/20260610130000_external_certifications.sql
@@ -0,0 +1,31 @@
+-- External certifications: "trust badge as a service".
+-- POST /api/public/certify runs the review engine on a skill file that may
+-- live anywhere (not necessarily on the registry), records the verdict keyed
+-- by content SHA-256, and issues a permanent badge + verification URL.
+
+create table if not exists public.external_certifications (
+ id uuid primary key default gen_random_uuid(),
+ name text not null check (char_length(name) between 1 and 200),
+ type text not null check (type in ('skill', 'playbook', 'soul', 'guardrail')),
+ content_sha256 text not null check (content_sha256 ~ '^[0-9a-f]{64}$'),
+ overall_score int not null check (overall_score between 0 and 100),
+ grade text not null,
+ engine_version text not null,
+ report jsonb not null default '{}'::jsonb,
+ created_at timestamptz not null default now()
+);
+
+create index if not exists external_certifications_sha_idx
+ on public.external_certifications (content_sha256);
+create index if not exists external_certifications_created_idx
+ on public.external_certifications (created_at desc);
+
+alter table public.external_certifications enable row level security;
+
+-- Certifications are public by design: the whole point is third-party
+-- verification. Writes only happen through the service role (the API route).
+drop policy if exists external_certifications_public_read on public.external_certifications;
+create policy external_certifications_public_read
+ on public.external_certifications
+ for select
+ using (true);
diff --git a/tests/certify.test.mjs b/tests/certify.test.mjs
new file mode 100644
index 00000000..cc3b6795
--- /dev/null
+++ b/tests/certify.test.mjs
@@ -0,0 +1,79 @@
+import { test } from "node:test";
+import assert from "node:assert/strict";
+
+import {
+ badgeUrl,
+ badgeValue,
+ embedMarkdown,
+ parseCertifyRequest,
+ sha256Hex,
+ verifyUrl,
+ CONTENT_MAX_CHARS,
+} from "../src/lib/certify/core.ts";
+import { renderCertifiedBadge } from "../src/lib/badges/trust-badge.ts";
+
+test("sha256Hex matches a known vector", async () => {
+ // echo -n "abc" | sha256sum
+ assert.equal(
+ await sha256Hex("abc"),
+ "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
+ );
+});
+
+test("parseCertifyRequest accepts a valid body and trims the name", () => {
+ const r = parseCertifyRequest({
+ name: " my-skill.md ",
+ type: "guardrail",
+ content: "x".repeat(50),
+ });
+ assert.ok(r.ok);
+ assert.equal(r.value.name, "my-skill.md");
+ assert.equal(r.value.type, "guardrail");
+});
+
+test("parseCertifyRequest defaults type to skill", () => {
+ const r = parseCertifyRequest({ name: "a", content: "x".repeat(50) });
+ assert.ok(r.ok);
+ assert.equal(r.value.type, "skill");
+});
+
+test("parseCertifyRequest rejects bad input", () => {
+ assert.equal(parseCertifyRequest(null).ok, false);
+ assert.equal(parseCertifyRequest({ name: "", content: "x".repeat(50) }).ok, false);
+ assert.equal(
+ parseCertifyRequest({ name: "a", type: "agent", content: "x".repeat(50) }).ok,
+ false,
+ );
+ assert.equal(parseCertifyRequest({ name: "a", content: "too short" }).ok, false);
+ assert.equal(
+ parseCertifyRequest({ name: "a", content: "x".repeat(CONTENT_MAX_CHARS + 1) }).ok,
+ false,
+ );
+});
+
+test("badgeValue renders letter + score", () => {
+ assert.equal(badgeValue({ overall_score: 84, grade: "B — solid, minor gaps" }), "B · 84/100");
+ assert.equal(badgeValue({ overall_score: 12, grade: "F — rewrite recommended" }), "F · 12/100");
+});
+
+test("urls and embed snippet are consistent", () => {
+ const origin = "https://superagentskill.com";
+ const id = "0b7c1234";
+ assert.equal(badgeUrl(origin, id), `${origin}/api/badges/certified/${id}.svg`);
+ assert.equal(verifyUrl(origin, id), `${origin}/api/public/certifications/${id}`);
+ const md = embedMarkdown(origin, id);
+ assert.ok(md.includes(badgeUrl(origin, id)));
+ assert.ok(md.includes(verifyUrl(origin, id)));
+ assert.ok(md.startsWith("[![skill audit]"));
+});
+
+test("renderCertifiedBadge renders score and gray fallback", () => {
+ const ok = renderCertifiedBadge({ value: "B · 84/100", score0to100: 84 });
+ assert.ok(ok.includes("