@ggui-ai/registry-server 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/filesystem-bundle-storage.d.ts.map +1 -1
- package/dist/filesystem-bundle-storage.js +41 -39
- package/dist/filesystem-registry-storage.d.ts.map +1 -1
- package/dist/filesystem-registry-storage.js +13 -2
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/server.d.ts +38 -9
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +142 -35
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ ggui gadget publish --auth=bearer --token=$GGUI_REGISTRY_TOKEN \
|
|
|
42
42
|
| GET | `/bundles/:scope/:name/:version/bundle.js.sig` | none | Serve the signature envelope (immutable cache) |
|
|
43
43
|
| GET | `/bundles/:scope/:name/:version/manifest.json` | none | Serve the manifest verbatim (immutable cache) |
|
|
44
44
|
|
|
45
|
-
\* Reads of `visibility: 'private'` rows
|
|
45
|
+
\* Reads of `visibility: 'private'` rows are ownership-gated: a private version is served only to the verified caller who published it or who owns the artifact's scope. Every other caller — unauthenticated included — receives the same `not_found` response as a genuinely missing artifact, and the version list filters unreadable versions (answering `not_found` when none are visible), so responses never reveal that a private artifact exists.
|
|
46
46
|
|
|
47
47
|
## Storage modes
|
|
48
48
|
|
|
@@ -60,7 +60,7 @@ This server is **MVP-scoped** for self-hosters who want the marketplace surface
|
|
|
60
60
|
- Rate limiting (use a reverse proxy)
|
|
61
61
|
- TLS termination (use a reverse proxy)
|
|
62
62
|
- Backup automation (the filesystem mode is single-machine)
|
|
63
|
-
- Per-org private artifact scoping (
|
|
63
|
+
- Per-org private artifact scoping (enforcement is per-subject — publisher or scope owner; org-membership admission is not implemented)
|
|
64
64
|
- Sigstore transparency log integration
|
|
65
65
|
|
|
66
66
|
If you need any of the above, build your own transport on top of [`@ggui-ai/registry-core`](https://github.com/ggui-ai/ggui/tree/main/packages/registry-core).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filesystem-bundle-storage.d.ts","sourceRoot":"","sources":["../src/filesystem-bundle-storage.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"filesystem-bundle-storage.d.ts","sourceRoot":"","sources":["../src/filesystem-bundle-storage.ts"],"names":[],"mappings":"AA2CA,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,wBAAwB,CAAC;AAExE,MAAM,WAAW,8BAA8B;IAC7C,gDAAgD;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,8BAA8B,GACtC,aAAa,CA0Ff"}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Filesystem-backed {@link BundleStorage} for the OSS registry server.
|
|
3
3
|
*
|
|
4
|
-
* Blob layout
|
|
4
|
+
* Blob layout (H1 visibility split — placement is the access-control
|
|
5
|
+
* boundary):
|
|
5
6
|
*
|
|
6
|
-
* <root>/bundles/<scope>/<name>/<version>/bundle.js
|
|
7
|
-
* <root>/bundles/<scope>/<name>/<version>/bundle.js.sig
|
|
8
|
-
* <root>/bundles/<scope>/<name>/<version>/manifest.json
|
|
7
|
+
* <root>/bundles/public/<scope>/<name>/<version>/bundle.js
|
|
8
|
+
* <root>/bundles/public/<scope>/<name>/<version>/bundle.js.sig
|
|
9
|
+
* <root>/bundles/public/<scope>/<name>/<version>/manifest.json
|
|
10
|
+
* <root>/bundles/private/<scope>/<name>/<version>/{...}
|
|
9
11
|
*
|
|
10
12
|
* Scope is written verbatim (`@my-org/...`) because the leading `@`
|
|
11
13
|
* is filename-safe on every supported OS. The URL composition mirrors
|
|
12
|
-
* the same path so the
|
|
13
|
-
*
|
|
14
|
-
*
|
|
14
|
+
* the same path so the URL the install CLI follows resolves directly
|
|
15
|
+
* against the file: the hono server serves `/bundles/public/*`
|
|
16
|
+
* anonymously and `/bundles/private/*` through its authenticated
|
|
17
|
+
* bundle route.
|
|
15
18
|
*
|
|
16
19
|
* ## Path-traversal defense
|
|
17
20
|
*
|
|
@@ -40,31 +43,39 @@ import { isGadgetSignature } from '@ggui-ai/gadget-signing';
|
|
|
40
43
|
export function createFilesystemBundleStorage(options) {
|
|
41
44
|
const bundlesRoot = join(options.root, 'bundles');
|
|
42
45
|
const bundleHost = stripTrailingSlash(options.bundleHost);
|
|
43
|
-
const dirFor = (scope, name, version) => {
|
|
46
|
+
const dirFor = (scope, name, version, visibility) => {
|
|
47
|
+
// `visibility` is a closed union ('public' | 'private') — no
|
|
48
|
+
// traversal surface; the free-form triple still gets the check.
|
|
44
49
|
rejectTraversal(scope, 'scope');
|
|
45
50
|
rejectTraversal(name, 'name');
|
|
46
51
|
rejectTraversal(version, 'version');
|
|
47
|
-
return join(bundlesRoot, scope, name, version);
|
|
52
|
+
return join(bundlesRoot, visibility, scope, name, version);
|
|
53
|
+
};
|
|
54
|
+
const urlFor = (scope, name, version, visibility, file) => {
|
|
55
|
+
rejectTraversal(scope, 'scope');
|
|
56
|
+
rejectTraversal(name, 'name');
|
|
57
|
+
rejectTraversal(version, 'version');
|
|
58
|
+
return `${bundleHost}/bundles/${visibility}/${scope}/${name}/${version}/${file}`;
|
|
48
59
|
};
|
|
49
60
|
return {
|
|
50
|
-
async putBundle(scope, name, version, bytes) {
|
|
51
|
-
const dir = dirFor(scope, name, version);
|
|
61
|
+
async putBundle(scope, name, version, visibility, bytes) {
|
|
62
|
+
const dir = dirFor(scope, name, version, visibility);
|
|
52
63
|
await mkdir(dir, { recursive: true });
|
|
53
64
|
await writeFile(join(dir, 'bundle.js'), bytes);
|
|
54
|
-
return this.bundleUrl(scope, name, version);
|
|
65
|
+
return this.bundleUrl(scope, name, version, visibility);
|
|
55
66
|
},
|
|
56
|
-
async getBundle(scope, name, version) {
|
|
57
|
-
const path = join(dirFor(scope, name, version), 'bundle.js');
|
|
67
|
+
async getBundle(scope, name, version, visibility) {
|
|
68
|
+
const path = join(dirFor(scope, name, version, visibility), 'bundle.js');
|
|
58
69
|
return readFileOrNull(path);
|
|
59
70
|
},
|
|
60
|
-
async putSignature(scope, name, version, signature) {
|
|
61
|
-
const dir = dirFor(scope, name, version);
|
|
71
|
+
async putSignature(scope, name, version, visibility, signature) {
|
|
72
|
+
const dir = dirFor(scope, name, version, visibility);
|
|
62
73
|
await mkdir(dir, { recursive: true });
|
|
63
74
|
await writeFile(join(dir, 'bundle.js.sig'), JSON.stringify(signature, null, 2), 'utf8');
|
|
64
|
-
return this.signatureUrl(scope, name, version);
|
|
75
|
+
return this.signatureUrl(scope, name, version, visibility);
|
|
65
76
|
},
|
|
66
|
-
async getSignature(scope, name, version) {
|
|
67
|
-
const path = join(dirFor(scope, name, version), 'bundle.js.sig');
|
|
77
|
+
async getSignature(scope, name, version, visibility) {
|
|
78
|
+
const path = join(dirFor(scope, name, version, visibility), 'bundle.js.sig');
|
|
68
79
|
const text = await readTextOrNull(path);
|
|
69
80
|
if (text === null)
|
|
70
81
|
return null;
|
|
@@ -78,34 +89,25 @@ export function createFilesystemBundleStorage(options) {
|
|
|
78
89
|
return null;
|
|
79
90
|
return parsed;
|
|
80
91
|
},
|
|
81
|
-
async putManifest(scope, name, version, manifest) {
|
|
82
|
-
const dir = dirFor(scope, name, version);
|
|
92
|
+
async putManifest(scope, name, version, visibility, manifest) {
|
|
93
|
+
const dir = dirFor(scope, name, version, visibility);
|
|
83
94
|
await mkdir(dir, { recursive: true });
|
|
84
95
|
await writeFile(join(dir, 'manifest.json'), JSON.stringify(manifest, null, 2), 'utf8');
|
|
85
|
-
return this.manifestUrl(scope, name, version);
|
|
96
|
+
return this.manifestUrl(scope, name, version, visibility);
|
|
86
97
|
},
|
|
87
|
-
async getManifest(scope, name, version) {
|
|
88
|
-
const path = join(dirFor(scope, name, version), 'manifest.json');
|
|
98
|
+
async getManifest(scope, name, version, visibility) {
|
|
99
|
+
const path = join(dirFor(scope, name, version, visibility), 'manifest.json');
|
|
89
100
|
const text = await readTextOrNull(path);
|
|
90
101
|
return text === null ? null : JSON.parse(text);
|
|
91
102
|
},
|
|
92
|
-
bundleUrl(scope, name, version) {
|
|
93
|
-
|
|
94
|
-
rejectTraversal(name, 'name');
|
|
95
|
-
rejectTraversal(version, 'version');
|
|
96
|
-
return `${bundleHost}/bundles/${scope}/${name}/${version}/bundle.js`;
|
|
103
|
+
bundleUrl(scope, name, version, visibility) {
|
|
104
|
+
return urlFor(scope, name, version, visibility, 'bundle.js');
|
|
97
105
|
},
|
|
98
|
-
signatureUrl(scope, name, version) {
|
|
99
|
-
|
|
100
|
-
rejectTraversal(name, 'name');
|
|
101
|
-
rejectTraversal(version, 'version');
|
|
102
|
-
return `${bundleHost}/bundles/${scope}/${name}/${version}/bundle.js.sig`;
|
|
106
|
+
signatureUrl(scope, name, version, visibility) {
|
|
107
|
+
return urlFor(scope, name, version, visibility, 'bundle.js.sig');
|
|
103
108
|
},
|
|
104
|
-
manifestUrl(scope, name, version) {
|
|
105
|
-
|
|
106
|
-
rejectTraversal(name, 'name');
|
|
107
|
-
rejectTraversal(version, 'version');
|
|
108
|
-
return `${bundleHost}/bundles/${scope}/${name}/${version}/manifest.json`;
|
|
109
|
+
manifestUrl(scope, name, version, visibility) {
|
|
110
|
+
return urlFor(scope, name, version, visibility, 'manifest.json');
|
|
109
111
|
},
|
|
110
112
|
};
|
|
111
113
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filesystem-registry-storage.d.ts","sourceRoot":"","sources":["../src/filesystem-registry-storage.ts"],"names":[],"mappings":"AAoEA,OAAO,
|
|
1
|
+
{"version":3,"file":"filesystem-registry-storage.d.ts","sourceRoot":"","sources":["../src/filesystem-registry-storage.ts"],"names":[],"mappings":"AAoEA,OAAO,EAQL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAKhC,MAAM,WAAW,gCAAgC;IAC/C,iFAAiF;IACjF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,gCAAgC,GACxC,eAAe,CA2SjB"}
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
*/
|
|
67
67
|
import { mkdir, readFile, readdir, unlink, writeFile } from 'node:fs/promises';
|
|
68
68
|
import { dirname, join } from 'node:path';
|
|
69
|
-
import { AuthorKeyAlreadyExistsError, } from '@ggui-ai/registry-core';
|
|
69
|
+
import { AuthorKeyAlreadyExistsError, matchesMcpToolFilters, } from '@ggui-ai/registry-core';
|
|
70
70
|
const DEFAULT_LIMIT = 50;
|
|
71
71
|
const MAX_LIMIT = 200;
|
|
72
72
|
export function createFilesystemRegistryStorage(options) {
|
|
@@ -448,13 +448,24 @@ function parseCursor(cursor) {
|
|
|
448
448
|
* Mirrors {@link inMemoryRegistryStorage}'s filter — same
|
|
449
449
|
* AND-composition, same case-insensitive substring match on q, same
|
|
450
450
|
* publisher-sub OR author-name match. Kept inline so the filesystem
|
|
451
|
-
* impl doesn't depend on a private helper from registry-core
|
|
451
|
+
* impl doesn't depend on a private helper from registry-core; the
|
|
452
|
+
* tool/server dimensions delegate to the PUBLIC shared predicate
|
|
453
|
+
* `matchesMcpToolFilters` so the reference semantics cannot drift.
|
|
452
454
|
*/
|
|
453
455
|
function rowMatchesFilter(row, q) {
|
|
456
|
+
// #474 — visibility gating lives INSIDE every scanArtifacts impl
|
|
457
|
+
// (the ruled canonical location); the search op's post-filter stays
|
|
458
|
+
// as defense-in-depth. Without this, private rows would consume
|
|
459
|
+
// page slots on self-host impls even though the op filters them out
|
|
460
|
+
// before they ever reach the wire.
|
|
461
|
+
if (row.visibility !== 'public')
|
|
462
|
+
return false;
|
|
454
463
|
if (q.kind !== undefined && row.kind !== q.kind)
|
|
455
464
|
return false;
|
|
456
465
|
if (q.hook !== undefined && row.hook !== q.hook)
|
|
457
466
|
return false;
|
|
467
|
+
if (!matchesMcpToolFilters(row.mcpTools, q))
|
|
468
|
+
return false;
|
|
458
469
|
if (q.tag !== undefined) {
|
|
459
470
|
if (row.tags === undefined || !row.tags.includes(q.tag))
|
|
460
471
|
return false;
|
package/dist/index.d.ts
CHANGED
|
@@ -46,6 +46,13 @@ export interface CreateRegistryServerOptions {
|
|
|
46
46
|
* {@link RegistryAppOptions.sigstoreTuf}).
|
|
47
47
|
*/
|
|
48
48
|
readonly sigstoreTuf?: PublishArtifactDeps['sigstoreTuf'];
|
|
49
|
+
/**
|
|
50
|
+
* Optional verified-email lookup for the publish gate's identity
|
|
51
|
+
* binding. Forwarded to `createRegistryApp` (see
|
|
52
|
+
* {@link RegistryAppOptions.verifiedEmailResolver} for the default
|
|
53
|
+
* allowlist-only posture when unset).
|
|
54
|
+
*/
|
|
55
|
+
readonly verifiedEmailResolver?: PublishArtifactDeps['verifiedEmailResolver'];
|
|
49
56
|
}
|
|
50
57
|
export interface RegistryServerHandle {
|
|
51
58
|
/** The port the server is actually bound to. Resolves after {@link start}. */
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EACV,oBAAoB,EACpB,aAAa,EACb,mBAAmB,EACnB,eAAe,EAChB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,EAAE,iBAAiB,EAAE,KAAK,WAAW,EAAE,KAAK,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AACvG,OAAO,EACL,6BAA6B,EAC7B,KAAK,8BAA8B,GACpC,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,+BAA+B,EAC/B,KAAK,gCAAgC,GACtC,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEzE,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,4BAA4B;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,kEAAkE;IAClE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,iEAAiE;IACjE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,oBAAoB,CAAC;IAC/C;;;;OAIG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,mBAAmB,CAAC,aAAa,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EACV,oBAAoB,EACpB,aAAa,EACb,mBAAmB,EACnB,eAAe,EAChB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,EAAE,iBAAiB,EAAE,KAAK,WAAW,EAAE,KAAK,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AACvG,OAAO,EACL,6BAA6B,EAC7B,KAAK,8BAA8B,GACpC,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,+BAA+B,EAC/B,KAAK,gCAAgC,GACtC,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEzE,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,4BAA4B;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,kEAAkE;IAClE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,iEAAiE;IACjE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,oBAAoB,CAAC;IAC/C;;;;OAIG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,mBAAmB,CAAC,aAAa,CAAC,CAAC;IAC1D;;;;;OAKG;IACH,QAAQ,CAAC,qBAAqB,CAAC,EAAE,mBAAmB,CAAC,uBAAuB,CAAC,CAAC;CAC/E;AAED,MAAM,WAAW,oBAAoB;IACnC,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,2BAA2B,GACnC,oBAAoB,CAuDtB"}
|
package/dist/index.js
CHANGED
|
@@ -37,6 +37,9 @@ export function createRegistryServer(options) {
|
|
|
37
37
|
...(options.sigstoreTuf !== undefined
|
|
38
38
|
? { sigstoreTuf: options.sigstoreTuf }
|
|
39
39
|
: {}),
|
|
40
|
+
...(options.verifiedEmailResolver !== undefined
|
|
41
|
+
? { verifiedEmailResolver: options.verifiedEmailResolver }
|
|
42
|
+
: {}),
|
|
40
43
|
});
|
|
41
44
|
const host = options.host ?? '0.0.0.0';
|
|
42
45
|
const requestedPort = options.port ?? 9001;
|
package/dist/server.d.ts
CHANGED
|
@@ -10,21 +10,40 @@
|
|
|
10
10
|
* GET /author-keys → listAuthorKeys (bearer-gated)
|
|
11
11
|
* DELETE /author-keys/:keyId → deleteAuthorKey (bearer-gated)
|
|
12
12
|
* POST /conformance/check → checkConformance (pre-flight gate)
|
|
13
|
-
* GET /bundles/:scope/:name/:version/bundle.js
|
|
14
|
-
* GET /bundles/:scope/:name/:version/bundle.js.sig
|
|
15
|
-
* GET /bundles/:scope/:name/:version/manifest.json
|
|
13
|
+
* GET /bundles/public/:scope/:name/:version/bundle.js (anonymous)
|
|
14
|
+
* GET /bundles/public/:scope/:name/:version/bundle.js.sig (anonymous)
|
|
15
|
+
* GET /bundles/public/:scope/:name/:version/manifest.json (anonymous)
|
|
16
|
+
* GET /bundles/private/:scope/:name/:version/bundle.js (bearer-gated)
|
|
17
|
+
* GET /bundles/private/:scope/:name/:version/bundle.js.sig (bearer-gated)
|
|
18
|
+
* GET /bundles/private/:scope/:name/:version/manifest.json (bearer-gated)
|
|
16
19
|
*
|
|
17
20
|
* Bundle / signature / manifest serves go through {@link BundleStorage}
|
|
18
21
|
* directly so the operator can plug a different storage backend
|
|
19
22
|
* (memory for tests; filesystem for self-hosting) without rewriting
|
|
20
|
-
* the route table.
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
+
* the route table.
|
|
24
|
+
*
|
|
25
|
+
* H1 visibility split: blob placement mirrors the manifest's
|
|
26
|
+
* `visibility` (`bundles/public/…` vs `bundles/private/…`), and the
|
|
27
|
+
* ROUTE decides the auth posture — the public prefix serves
|
|
28
|
+
* anonymously; the private prefix requires a bearer AND authorizes the
|
|
29
|
+
* caller against the artifact's publisher or the scope owner (403
|
|
30
|
+
* otherwise; unknown triples answer 404). A blob is only findable
|
|
31
|
+
* through the prefix it was published under, so a private artifact can
|
|
32
|
+
* never be fetched via the anonymous route.
|
|
33
|
+
*
|
|
34
|
+
* Cache headers: public-prefix responses emit
|
|
35
|
+
* `Cache-Control: public, max-age=31536000, immutable` (bundles are
|
|
36
|
+
* SRI-pinned + immutable post-publish); private-prefix responses stay
|
|
37
|
+
* immutable but non-shared-cacheable
|
|
38
|
+
* (`Cache-Control: private, max-age=31536000, immutable`).
|
|
23
39
|
*
|
|
24
40
|
* CORS: permissive on public-read routes (`/search`, `/pkg/*`,
|
|
25
|
-
* `/bundles/*`, `/conformance/check`); strict on `/publish`
|
|
26
|
-
* or no Origin header — the install CLI runs
|
|
27
|
-
* browser-side)
|
|
41
|
+
* `/bundles/public/*`, `/conformance/check`); strict on `/publish`
|
|
42
|
+
* (same-origin or no Origin header — the install CLI runs
|
|
43
|
+
* server-to-server, not browser-side) AND on `/bundles/private/*`
|
|
44
|
+
* (per-caller authorized content — no ACAO, no permissive preflight;
|
|
45
|
+
* browser consumers of private bundles go through presigned URLs
|
|
46
|
+
* rather than cross-origin credentialed fetches).
|
|
28
47
|
*/
|
|
29
48
|
import { Hono } from 'hono';
|
|
30
49
|
import { type BlueprintProbeRunner, type PublishArtifactDeps, type BundleStorage, type RegistryStorage } from '@ggui-ai/registry-core';
|
|
@@ -53,6 +72,16 @@ export interface RegistryAppOptions {
|
|
|
53
72
|
* the sigstore public-good trust root.
|
|
54
73
|
*/
|
|
55
74
|
readonly sigstoreTuf?: PublishArtifactDeps['sigstoreTuf'];
|
|
75
|
+
/**
|
|
76
|
+
* Optional verified-email lookup for the publish gate's F4 identity
|
|
77
|
+
* binding — see {@link PublishArtifactDeps.verifiedEmailResolver}
|
|
78
|
+
* for the contract. Unset (the default here): publisher identity is
|
|
79
|
+
* enforced only on scopes whose ownership row carries a
|
|
80
|
+
* `sanAllowlist`; scopes without one accept any identity a valid
|
|
81
|
+
* sigstore bundle proves. Wire a resolver backed by your identity
|
|
82
|
+
* layer to bind default publishes to account emails.
|
|
83
|
+
*/
|
|
84
|
+
readonly verifiedEmailResolver?: PublishArtifactDeps['verifiedEmailResolver'];
|
|
56
85
|
}
|
|
57
86
|
/**
|
|
58
87
|
* Build the hono app. Does NOT bind a port — callers (the CLI, the
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAUL,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EAGxB,KAAK,aAAa,EAIlB,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AASrD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,6EAA6E;IAC7E,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,iEAAiE;IACjE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,oBAAoB,CAAC;IAC/C;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,mBAAmB,CAAC,aAAa,CAAC,CAAC;IAC1D;;;;;;;;OAQG;IACH,QAAQ,CAAC,qBAAqB,CAAC,EAAE,mBAAmB,CAAC,uBAAuB,CAAC,CAAC;CAC/E;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAyXnE"}
|
package/dist/server.js
CHANGED
|
@@ -10,25 +10,49 @@
|
|
|
10
10
|
* GET /author-keys → listAuthorKeys (bearer-gated)
|
|
11
11
|
* DELETE /author-keys/:keyId → deleteAuthorKey (bearer-gated)
|
|
12
12
|
* POST /conformance/check → checkConformance (pre-flight gate)
|
|
13
|
-
* GET /bundles/:scope/:name/:version/bundle.js
|
|
14
|
-
* GET /bundles/:scope/:name/:version/bundle.js.sig
|
|
15
|
-
* GET /bundles/:scope/:name/:version/manifest.json
|
|
13
|
+
* GET /bundles/public/:scope/:name/:version/bundle.js (anonymous)
|
|
14
|
+
* GET /bundles/public/:scope/:name/:version/bundle.js.sig (anonymous)
|
|
15
|
+
* GET /bundles/public/:scope/:name/:version/manifest.json (anonymous)
|
|
16
|
+
* GET /bundles/private/:scope/:name/:version/bundle.js (bearer-gated)
|
|
17
|
+
* GET /bundles/private/:scope/:name/:version/bundle.js.sig (bearer-gated)
|
|
18
|
+
* GET /bundles/private/:scope/:name/:version/manifest.json (bearer-gated)
|
|
16
19
|
*
|
|
17
20
|
* Bundle / signature / manifest serves go through {@link BundleStorage}
|
|
18
21
|
* directly so the operator can plug a different storage backend
|
|
19
22
|
* (memory for tests; filesystem for self-hosting) without rewriting
|
|
20
|
-
* the route table.
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
+
* the route table.
|
|
24
|
+
*
|
|
25
|
+
* H1 visibility split: blob placement mirrors the manifest's
|
|
26
|
+
* `visibility` (`bundles/public/…` vs `bundles/private/…`), and the
|
|
27
|
+
* ROUTE decides the auth posture — the public prefix serves
|
|
28
|
+
* anonymously; the private prefix requires a bearer AND authorizes the
|
|
29
|
+
* caller against the artifact's publisher or the scope owner (403
|
|
30
|
+
* otherwise; unknown triples answer 404). A blob is only findable
|
|
31
|
+
* through the prefix it was published under, so a private artifact can
|
|
32
|
+
* never be fetched via the anonymous route.
|
|
33
|
+
*
|
|
34
|
+
* Cache headers: public-prefix responses emit
|
|
35
|
+
* `Cache-Control: public, max-age=31536000, immutable` (bundles are
|
|
36
|
+
* SRI-pinned + immutable post-publish); private-prefix responses stay
|
|
37
|
+
* immutable but non-shared-cacheable
|
|
38
|
+
* (`Cache-Control: private, max-age=31536000, immutable`).
|
|
23
39
|
*
|
|
24
40
|
* CORS: permissive on public-read routes (`/search`, `/pkg/*`,
|
|
25
|
-
* `/bundles/*`, `/conformance/check`); strict on `/publish`
|
|
26
|
-
* or no Origin header — the install CLI runs
|
|
27
|
-
* browser-side)
|
|
41
|
+
* `/bundles/public/*`, `/conformance/check`); strict on `/publish`
|
|
42
|
+
* (same-origin or no Origin header — the install CLI runs
|
|
43
|
+
* server-to-server, not browser-side) AND on `/bundles/private/*`
|
|
44
|
+
* (per-caller authorized content — no ACAO, no permissive preflight;
|
|
45
|
+
* browser consumers of private bundles go through presigned URLs
|
|
46
|
+
* rather than cross-origin credentialed fetches).
|
|
28
47
|
*/
|
|
29
48
|
import { Hono } from 'hono';
|
|
30
|
-
import { checkConformance, deleteAuthorKey, unauthorizedErrorBody, listArtifactVersions, listAuthorKeys, publishArtifact, registerAuthorKey, readArtifact, searchArtifacts, } from '@ggui-ai/registry-core';
|
|
49
|
+
import { canReadPrivateArtifact, checkConformance, createScopeOwnerResolver, deleteAuthorKey, unauthorizedErrorBody, listArtifactVersions, listAuthorKeys, publishArtifact, registerAuthorKey, readArtifact, searchArtifacts, } from '@ggui-ai/registry-core';
|
|
31
50
|
const IMMUTABLE_CACHE_HEADER = 'public, max-age=31536000, immutable';
|
|
51
|
+
/**
|
|
52
|
+
* Private-prefix responses are immutable too, but MUST NOT land in
|
|
53
|
+
* shared caches — the authorization decision is per-caller.
|
|
54
|
+
*/
|
|
55
|
+
const PRIVATE_IMMUTABLE_CACHE_HEADER = 'private, max-age=31536000, immutable';
|
|
32
56
|
/**
|
|
33
57
|
* Build the hono app. Does NOT bind a port — callers (the CLI, the
|
|
34
58
|
* `createRegistryServer` factory, programmatic embeds) decide how to
|
|
@@ -40,18 +64,24 @@ export function createRegistryApp(options) {
|
|
|
40
64
|
const clock = options.clock ?? (() => new Date());
|
|
41
65
|
const blueprintProbe = options.blueprintProbe;
|
|
42
66
|
const sigstoreTuf = options.sigstoreTuf;
|
|
67
|
+
const verifiedEmailResolver = options.verifiedEmailResolver;
|
|
43
68
|
const app = new Hono();
|
|
44
69
|
// Shared bearer gate for every authed route — ONE verification call
|
|
45
70
|
// shape, ONE 401 body (`unauthorizedErrorBody`, exported beside the
|
|
46
71
|
// registry-core error enums so every transport of this wire contract
|
|
47
72
|
// agrees).
|
|
48
73
|
const verifyBearer = (c) => authn.verify(c.req.header('authorization'));
|
|
49
|
-
// CORS — permissive read; strict for /publish
|
|
50
|
-
//
|
|
74
|
+
// CORS — permissive read; strict for /publish AND the private
|
|
75
|
+
// bundle prefix (review finding 5). Private bundles are per-caller
|
|
76
|
+
// authorized content: no cross-origin page has any business reading
|
|
77
|
+
// them, and the browser consumers that need private bundle bytes go
|
|
78
|
+
// through presigned URLs, not cross-origin credentialed fetches.
|
|
79
|
+
// Adds ACAO headers AFTER the route runs so they overlay on the
|
|
80
|
+
// route's response.
|
|
51
81
|
app.use('*', async (c, next) => {
|
|
52
82
|
const path = c.req.path;
|
|
53
|
-
const
|
|
54
|
-
if (c.req.method === 'OPTIONS' && !
|
|
83
|
+
const isCorsExcluded = path === '/publish' || path.startsWith('/bundles/private/');
|
|
84
|
+
if (c.req.method === 'OPTIONS' && !isCorsExcluded) {
|
|
55
85
|
// Preflight short-circuit for read routes. DELETE is listed for
|
|
56
86
|
// the author-key management route — a browser console revokes
|
|
57
87
|
// keys cross-origin ([E]).
|
|
@@ -65,7 +95,7 @@ export function createRegistryApp(options) {
|
|
|
65
95
|
});
|
|
66
96
|
}
|
|
67
97
|
await next();
|
|
68
|
-
if (!
|
|
98
|
+
if (!isCorsExcluded) {
|
|
69
99
|
c.res.headers.set('Access-Control-Allow-Origin', '*');
|
|
70
100
|
c.res.headers.set('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
|
|
71
101
|
c.res.headers.set('Access-Control-Allow-Headers', 'content-type, authorization');
|
|
@@ -85,6 +115,8 @@ export function createRegistryApp(options) {
|
|
|
85
115
|
limit: q.limit,
|
|
86
116
|
cursor: q.cursor,
|
|
87
117
|
sort: q.sort,
|
|
118
|
+
tool: q.tool,
|
|
119
|
+
server: q.server,
|
|
88
120
|
}, { storage });
|
|
89
121
|
if (!result.ok) {
|
|
90
122
|
return c.json(result.body, result.status);
|
|
@@ -170,6 +202,7 @@ export function createRegistryApp(options) {
|
|
|
170
202
|
registryHostname,
|
|
171
203
|
...(blueprintProbe !== undefined ? { blueprintProbe } : {}),
|
|
172
204
|
...(sigstoreTuf !== undefined ? { sigstoreTuf } : {}),
|
|
205
|
+
...(verifiedEmailResolver !== undefined ? { verifiedEmailResolver } : {}),
|
|
173
206
|
});
|
|
174
207
|
return c.json(result.body, result.status);
|
|
175
208
|
});
|
|
@@ -247,38 +280,112 @@ export function createRegistryApp(options) {
|
|
|
247
280
|
catch {
|
|
248
281
|
return c.json({ ok: false, errors: [{ code: 'manifest_invalid', message: 'request body is not valid JSON' }] }, 400);
|
|
249
282
|
}
|
|
250
|
-
const result = checkConformance(payload);
|
|
283
|
+
const result = await checkConformance(payload);
|
|
251
284
|
// Domain-vs-transport split: a "non-conformant" submission is a
|
|
252
285
|
// valid request that produced a `ok: false` body. Mirrors the cloud
|
|
253
286
|
// conformance handler's wire shape.
|
|
254
287
|
return c.json(result, 200);
|
|
255
288
|
});
|
|
256
|
-
// ── /bundles
|
|
257
|
-
app.get('/bundles/:scope/:name/:version/bundle.js', async (c) => {
|
|
289
|
+
// ── /bundles/public/… — anonymous, CDN-parity serving ────────────────
|
|
290
|
+
app.get('/bundles/public/:scope/:name/:version/bundle.js', async (c) => {
|
|
291
|
+
const { scope, name, version } = c.req.param();
|
|
292
|
+
return serveBundle(c, bundleStorage, scope, name, version, 'public');
|
|
293
|
+
});
|
|
294
|
+
app.get('/bundles/public/:scope/:name/:version/bundle.js.sig', async (c) => {
|
|
295
|
+
const { scope, name, version } = c.req.param();
|
|
296
|
+
return serveSignature(c, bundleStorage, scope, name, version, 'public');
|
|
297
|
+
});
|
|
298
|
+
app.get('/bundles/public/:scope/:name/:version/manifest.json', async (c) => {
|
|
299
|
+
const { scope, name, version } = c.req.param();
|
|
300
|
+
return serveManifest(c, bundleStorage, scope, name, version, 'public');
|
|
301
|
+
});
|
|
302
|
+
// ── /bundles/private/… — authenticated + ownership-authorized ────────
|
|
303
|
+
// Auth rule (H1): a valid bearer is required (401 — uniform, fires
|
|
304
|
+
// before any storage read, so it can't distinguish triples); then
|
|
305
|
+
// authorization goes through H2's shared `canReadPrivateArtifact`
|
|
306
|
+
// predicate (publisher OR scope owner; owner lookup lazy + memoized
|
|
307
|
+
// + fail-closed via `createScopeOwnerResolver`). Unknown triples,
|
|
308
|
+
// rows that are not private (their blobs live on the public prefix),
|
|
309
|
+
// AND authorization denials all answer the SAME opaque 404 — a
|
|
310
|
+
// distinguishable denial is an existence oracle for private
|
|
311
|
+
// artifacts. Storage faults answer the documented `server_error`
|
|
312
|
+
// envelope with NO raw error text (fail closed, log server-side).
|
|
313
|
+
const privateBundleGate = async (c, rawScope, name, version) => {
|
|
314
|
+
const verified = authn.verify(c.req.header('authorization'));
|
|
315
|
+
if (verified === null) {
|
|
316
|
+
return c.json({
|
|
317
|
+
error: 'unauthorized',
|
|
318
|
+
message: 'private bundles require a valid bearer token in the Authorization header',
|
|
319
|
+
}, 401);
|
|
320
|
+
}
|
|
321
|
+
const scope = rawScope.startsWith('@') ? rawScope : `@${rawScope}`;
|
|
322
|
+
const artifactId = `${scope}/${name}`;
|
|
323
|
+
let row;
|
|
324
|
+
try {
|
|
325
|
+
row = await storage.getArtifactVersion(artifactId, version);
|
|
326
|
+
}
|
|
327
|
+
catch (err) {
|
|
328
|
+
// Fail closed with the documented envelope — the raw fault text
|
|
329
|
+
// stays server-side (a wire-visible internals string is both a
|
|
330
|
+
// leak and a fingerprint).
|
|
331
|
+
// eslint-disable-next-line no-console -- server-side operator signal; the wire stays opaque
|
|
332
|
+
console.error('registry: private bundle gate storage read failed', {
|
|
333
|
+
artifactId,
|
|
334
|
+
version,
|
|
335
|
+
error: err instanceof Error ? err.message : String(err),
|
|
336
|
+
});
|
|
337
|
+
return c.json(errorBody('server_error', 'internal error'), 500);
|
|
338
|
+
}
|
|
339
|
+
if (row === null || row.visibility !== 'private') {
|
|
340
|
+
return c.json(errorBody('not_found', 'bundle not found'), 404);
|
|
341
|
+
}
|
|
342
|
+
// `createScopeOwnerResolver` already resolves owner-lookup faults
|
|
343
|
+
// to null (deny) with its own server-side log, so no try/catch is
|
|
344
|
+
// needed around the predicate.
|
|
345
|
+
const authorized = await canReadPrivateArtifact(verified, row, createScopeOwnerResolver(storage, artifactId));
|
|
346
|
+
if (!authorized) {
|
|
347
|
+
return c.json(errorBody('not_found', 'bundle not found'), 404);
|
|
348
|
+
}
|
|
349
|
+
return null;
|
|
350
|
+
};
|
|
351
|
+
app.get('/bundles/private/:scope/:name/:version/bundle.js', async (c) => {
|
|
258
352
|
const { scope, name, version } = c.req.param();
|
|
259
|
-
|
|
353
|
+
const denied = await privateBundleGate(c, scope, name, version);
|
|
354
|
+
if (denied !== null)
|
|
355
|
+
return denied;
|
|
356
|
+
return serveBundle(c, bundleStorage, scope, name, version, 'private');
|
|
260
357
|
});
|
|
261
|
-
|
|
262
|
-
app.get('/bundles/:scope/:name/:version/bundle.js.sig', async (c) => {
|
|
358
|
+
app.get('/bundles/private/:scope/:name/:version/bundle.js.sig', async (c) => {
|
|
263
359
|
const { scope, name, version } = c.req.param();
|
|
264
|
-
|
|
360
|
+
const denied = await privateBundleGate(c, scope, name, version);
|
|
361
|
+
if (denied !== null)
|
|
362
|
+
return denied;
|
|
363
|
+
return serveSignature(c, bundleStorage, scope, name, version, 'private');
|
|
265
364
|
});
|
|
266
|
-
|
|
267
|
-
app.get('/bundles/:scope/:name/:version/manifest.json', async (c) => {
|
|
365
|
+
app.get('/bundles/private/:scope/:name/:version/manifest.json', async (c) => {
|
|
268
366
|
const { scope, name, version } = c.req.param();
|
|
269
|
-
|
|
367
|
+
const denied = await privateBundleGate(c, scope, name, version);
|
|
368
|
+
if (denied !== null)
|
|
369
|
+
return denied;
|
|
370
|
+
return serveManifest(c, bundleStorage, scope, name, version, 'private');
|
|
270
371
|
});
|
|
271
372
|
return app;
|
|
272
373
|
}
|
|
374
|
+
/** Cache header for the given prefix — see the module docstring. */
|
|
375
|
+
function cacheHeaderFor(visibility) {
|
|
376
|
+
return visibility === 'public'
|
|
377
|
+
? IMMUTABLE_CACHE_HEADER
|
|
378
|
+
: PRIVATE_IMMUTABLE_CACHE_HEADER;
|
|
379
|
+
}
|
|
273
380
|
/**
|
|
274
381
|
* Serve the bundle bytes. `application/javascript` MIME with the
|
|
275
|
-
* immutable cache header. 404 on miss.
|
|
382
|
+
* visibility-appropriate immutable cache header. 404 on miss.
|
|
276
383
|
*/
|
|
277
|
-
async function serveBundle(c, bundleStorage, rawScope, name, version) {
|
|
384
|
+
async function serveBundle(c, bundleStorage, rawScope, name, version, visibility) {
|
|
278
385
|
const scope = rawScope.startsWith('@') ? rawScope : `@${rawScope}`;
|
|
279
386
|
let bytes;
|
|
280
387
|
try {
|
|
281
|
-
bytes = await bundleStorage.getBundle(scope, name, version);
|
|
388
|
+
bytes = await bundleStorage.getBundle(scope, name, version, visibility);
|
|
282
389
|
}
|
|
283
390
|
catch (err) {
|
|
284
391
|
return c.json(errorBody('server_error', errorMessage(err)), 500);
|
|
@@ -297,15 +404,15 @@ async function serveBundle(c, bundleStorage, rawScope, name, version) {
|
|
|
297
404
|
status: 200,
|
|
298
405
|
headers: {
|
|
299
406
|
'Content-Type': 'application/javascript; charset=utf-8',
|
|
300
|
-
'Cache-Control':
|
|
407
|
+
'Cache-Control': cacheHeaderFor(visibility),
|
|
301
408
|
},
|
|
302
409
|
});
|
|
303
410
|
}
|
|
304
|
-
async function serveSignature(c, bundleStorage, rawScope, name, version) {
|
|
411
|
+
async function serveSignature(c, bundleStorage, rawScope, name, version, visibility) {
|
|
305
412
|
const scope = rawScope.startsWith('@') ? rawScope : `@${rawScope}`;
|
|
306
413
|
let sig;
|
|
307
414
|
try {
|
|
308
|
-
sig = await bundleStorage.getSignature(scope, name, version);
|
|
415
|
+
sig = await bundleStorage.getSignature(scope, name, version, visibility);
|
|
309
416
|
}
|
|
310
417
|
catch (err) {
|
|
311
418
|
return c.json(errorBody('server_error', errorMessage(err)), 500);
|
|
@@ -313,14 +420,14 @@ async function serveSignature(c, bundleStorage, rawScope, name, version) {
|
|
|
313
420
|
if (sig === null) {
|
|
314
421
|
return c.json(errorBody('not_found', 'signature not found'), 404);
|
|
315
422
|
}
|
|
316
|
-
c.header('Cache-Control',
|
|
423
|
+
c.header('Cache-Control', cacheHeaderFor(visibility));
|
|
317
424
|
return c.json(sig, 200);
|
|
318
425
|
}
|
|
319
|
-
async function serveManifest(c, bundleStorage, rawScope, name, version) {
|
|
426
|
+
async function serveManifest(c, bundleStorage, rawScope, name, version, visibility) {
|
|
320
427
|
const scope = rawScope.startsWith('@') ? rawScope : `@${rawScope}`;
|
|
321
428
|
let manifest;
|
|
322
429
|
try {
|
|
323
|
-
manifest = await bundleStorage.getManifest(scope, name, version);
|
|
430
|
+
manifest = await bundleStorage.getManifest(scope, name, version, visibility);
|
|
324
431
|
}
|
|
325
432
|
catch (err) {
|
|
326
433
|
return c.json(errorBody('server_error', errorMessage(err)), 500);
|
|
@@ -328,7 +435,7 @@ async function serveManifest(c, bundleStorage, rawScope, name, version) {
|
|
|
328
435
|
if (manifest === null) {
|
|
329
436
|
return c.json(errorBody('not_found', 'manifest not found'), 404);
|
|
330
437
|
}
|
|
331
|
-
c.header('Cache-Control',
|
|
438
|
+
c.header('Cache-Control', cacheHeaderFor(visibility));
|
|
332
439
|
return c.json(manifest, 200);
|
|
333
440
|
}
|
|
334
441
|
function errorBody(error, message) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/registry-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Self-hostable HTTP server for the ggui marketplace registry. Wraps @ggui-ai/registry-core with hono, filesystem storage, and bearer-token auth. Run via npx for local dev, CI, and on-prem deployments.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ggui",
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@hono/node-server": "^2.0.12",
|
|
33
33
|
"hono": "^4.12.8",
|
|
34
|
-
"@ggui-ai/artifact-manifest": "0.
|
|
35
|
-
"@ggui-ai/gadget-signing": "0.
|
|
36
|
-
"@ggui-ai/registry-core": "0.
|
|
34
|
+
"@ggui-ai/artifact-manifest": "0.9.0",
|
|
35
|
+
"@ggui-ai/gadget-signing": "0.9.0",
|
|
36
|
+
"@ggui-ai/registry-core": "0.9.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@sigstore/mock": "~0.12.1",
|