@ggui-ai/cli 0.2.0-alpha.4 → 0.3.0-rc.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 +38 -7
- package/dist/api-client.d.ts +42 -1
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +30 -2
- package/dist/auth-keys.d.ts +1 -1
- package/dist/auth-keys.d.ts.map +1 -1
- package/dist/auth-keys.js +22 -7
- package/dist/auth-store.d.ts +1 -1
- package/dist/auth-store.d.ts.map +1 -1
- package/dist/cli.js +112 -30
- package/dist/config-push.d.ts +79 -0
- package/dist/config-push.d.ts.map +1 -0
- package/dist/config-push.js +301 -0
- package/dist/create-app-command.d.ts +17 -0
- package/dist/create-app-command.d.ts.map +1 -0
- package/dist/create-app-command.js +58 -0
- package/dist/deploy-command.d.ts +67 -0
- package/dist/deploy-command.d.ts.map +1 -0
- package/dist/deploy-command.js +386 -0
- package/dist/dev-command.d.ts +5 -1
- package/dist/dev-command.d.ts.map +1 -1
- package/dist/dev-command.js +31 -1
- package/dist/export-pool-command.d.ts +25 -0
- package/dist/export-pool-command.d.ts.map +1 -0
- package/dist/export-pool-command.js +116 -0
- package/dist/filesystem-seed-pool-source.d.ts +13 -0
- package/dist/filesystem-seed-pool-source.d.ts.map +1 -0
- package/dist/filesystem-seed-pool-source.js +22 -0
- package/dist/generation-probe.d.ts +53 -7
- package/dist/generation-probe.d.ts.map +1 -1
- package/dist/generation-probe.js +37 -1
- package/dist/internal/artifact-install.d.ts +4 -2
- package/dist/internal/artifact-install.d.ts.map +1 -1
- package/dist/internal/artifact-install.js +11 -10
- package/dist/internal/artifact-publish.d.ts +34 -31
- package/dist/internal/artifact-publish.d.ts.map +1 -1
- package/dist/internal/artifact-publish.js +133 -241
- package/dist/internal/artifact-search.d.ts +2 -28
- package/dist/internal/artifact-search.d.ts.map +1 -1
- package/dist/internal/artifact-search.js +5 -4
- package/dist/internal/auth-strategy.d.ts +21 -31
- package/dist/internal/auth-strategy.d.ts.map +1 -1
- package/dist/internal/auth-strategy.js +15 -23
- package/dist/internal/register-author-key.d.ts +7 -1
- package/dist/internal/register-author-key.d.ts.map +1 -1
- package/dist/internal/register-author-key.js +56 -19
- package/dist/mcp-backend.d.ts +25 -25
- package/dist/mcp-backend.d.ts.map +1 -1
- package/dist/mcp-backend.js +65 -61
- package/dist/paths.d.ts +3 -3
- package/dist/paths.js +3 -3
- package/dist/persistent-secrets.js +1 -1
- package/dist/persistent-stores.d.ts +2 -2
- package/dist/persistent-stores.d.ts.map +1 -1
- package/dist/persistent-stores.js +5 -5
- package/dist/pool-artifact.d.ts +19 -0
- package/dist/pool-artifact.d.ts.map +1 -0
- package/dist/pool-artifact.js +59 -0
- package/dist/provider-key-command.d.ts +51 -0
- package/dist/provider-key-command.d.ts.map +1 -0
- package/dist/provider-key-command.js +222 -0
- package/dist/push-command.d.ts +47 -0
- package/dist/push-command.d.ts.map +1 -0
- package/dist/push-command.js +230 -0
- package/dist/serve-command.d.ts +23 -19
- package/dist/serve-command.d.ts.map +1 -1
- package/dist/serve-command.js +13 -26
- package/dist/theme-command.js +0 -2
- package/dist/tunnel-provider.d.ts +8 -7
- package/dist/tunnel-provider.d.ts.map +1 -1
- package/package.json +25 -21
- package/dist/internal/auth-cache.d.ts +0 -40
- package/dist/internal/auth-cache.d.ts.map +0 -1
- package/dist/internal/auth-cache.js +0 -133
- package/dist/persistent-short-code-index.d.ts +0 -14
- package/dist/persistent-short-code-index.d.ts.map +0 -1
- package/dist/persistent-short-code-index.js +0 -39
- package/dist/spawn-cloudflared.d.ts +0 -61
- package/dist/spawn-cloudflared.d.ts.map +0 -1
- package/dist/spawn-cloudflared.js +0 -167
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
* platform=neutral) with manifest.peerDeps + react family
|
|
21
21
|
* hoisted to externals.
|
|
22
22
|
* 4. Authenticate — either `--auth=bearer` (explicit token for self-
|
|
23
|
-
* hosted / local registries) OR the default
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
23
|
+
* hosted / local registries) OR the default: reuse the session
|
|
24
|
+
* `ggui login` already stored at `~/.ggui/auth.json`, refreshing
|
|
25
|
+
* the access token via `POST /v1/auth/refresh` when it has
|
|
26
|
+
* expired (see `./auth-strategy.ts` for the routing and
|
|
27
|
+
* `../auth-store.ts` for the on-disk document).
|
|
28
28
|
* 5. Conformance preflight — POST `/conformance/check` with the
|
|
29
29
|
* manifest + bundle text. Fail-fast so a malformed bundle
|
|
30
30
|
* doesn't hit S3.
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
* **HTTP contract (locked).**
|
|
43
43
|
*
|
|
44
44
|
* POST <registry>/publish
|
|
45
|
-
* Headers: `Authorization: Bearer <
|
|
45
|
+
* Headers: `Authorization: Bearer <token>`, `Content-Type: application/json`
|
|
46
46
|
* Body : { manifest, bundle?, bundleSha384?, signature }
|
|
47
47
|
* 201 : { artifactId, version, manifestUrl, bundleUrl?, signatureUrl?, installCommand }
|
|
48
48
|
* 400 : { code: 'version_exists' | 'unknown_key' | 'conformance_failed' | … , message }
|
|
@@ -50,26 +50,23 @@
|
|
|
50
50
|
* 501 : endpoint stubbed
|
|
51
51
|
*
|
|
52
52
|
* POST <registry>/conformance/check
|
|
53
|
-
* Headers: `Authorization: Bearer <
|
|
53
|
+
* Headers: `Authorization: Bearer <token>`, `Content-Type: application/json`
|
|
54
54
|
* Body : { manifest, bundle? }
|
|
55
55
|
* 200 : { ok: true }
|
|
56
56
|
* 200 : { ok: false, issues: [{code, message, path?}] }
|
|
57
57
|
* 5xx : transient — caller retries or surfaces
|
|
58
58
|
*/
|
|
59
59
|
import { build as esbuild } from 'esbuild';
|
|
60
|
-
// Cloud-internal hosted-auth implementation. The vendor SDK is named
|
|
61
|
-
// here because this is the leaf implementation file the OSS-facing
|
|
62
|
-
// `acquireHostedAuthJwt` wraps; the public surface (CLI flags, help
|
|
63
|
-
// text, README, the auth-strategy module) is vendor-neutral.
|
|
64
|
-
import { CognitoIdentityProviderClient, InitiateAuthCommand, } from '@aws-sdk/client-cognito-identity-provider';
|
|
65
60
|
import { existsSync, readFileSync } from 'node:fs';
|
|
66
61
|
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
67
62
|
import { URL } from 'node:url';
|
|
68
63
|
import { GGUI_BLUEPRINT_JSON_FILENAME, GGUI_GADGET_JSON_FILENAME, parseBlueprintManifest, parseGadgetManifest, } from '@ggui-ai/artifact-manifest';
|
|
69
64
|
import { canonicalJson, derivePublicKeyId, generateEd25519Keypair, publicKeyFromPrivate, signBundleEd25519, signBundleSigstore, } from '@ggui-ai/gadget-signing';
|
|
65
|
+
import { PROTOCOL_VERSION } from '@ggui-ai/protocol';
|
|
70
66
|
import { resolveOidcToken, OidcResolutionError, } from './oidc-token.js';
|
|
71
67
|
import { sha384 } from '@noble/hashes/sha512.js';
|
|
72
|
-
import {
|
|
68
|
+
import { saveAuthSession, tryLoadAuthSession, } from '../auth-store.js';
|
|
69
|
+
import { ApiError, postAuthRefresh } from '../api-client.js';
|
|
73
70
|
import { getPrivateKeyPath, hasKeypair, readPrivateKey, scopeOf, writePrivateKey, } from './key-store.js';
|
|
74
71
|
import { acquireAuthToken, parseAuthFlags, AUTH_HELP_FRAGMENT, } from './auth-strategy.js';
|
|
75
72
|
/**
|
|
@@ -224,8 +221,21 @@ export async function runArtifactPublish(opts) {
|
|
|
224
221
|
}
|
|
225
222
|
return { ok: false, exitCode: 1, error: manifestRes.error };
|
|
226
223
|
}
|
|
227
|
-
const {
|
|
228
|
-
// ---- step 2a:
|
|
224
|
+
const { manifestPath } = manifestRes;
|
|
225
|
+
// ---- step 2a: stamp the persistence-contract era (blueprints) ----
|
|
226
|
+
// `generatorProtocolVersion` is the import-gate era stamp (mirrors
|
|
227
|
+
// `PortableBlueprint`): deployments that feed installed blueprints
|
|
228
|
+
// into a matcher pool REQUIRE it and drop unstamped rows loudly.
|
|
229
|
+
// The SIGNING toolchain owns the claim — it validates the manifest
|
|
230
|
+
// against its own protocol era and signs the stamped bytes — so the
|
|
231
|
+
// stamp is set here, BEFORE the signature payload is computed, and
|
|
232
|
+
// overwrites any hand-authored value (an author cannot truthfully
|
|
233
|
+
// claim a different toolchain's era). Gadgets never enter a matcher
|
|
234
|
+
// pool; they stay unstamped.
|
|
235
|
+
const manifest = manifestRes.manifest.kind === 'blueprint'
|
|
236
|
+
? { ...manifestRes.manifest, generatorProtocolVersion: PROTOCOL_VERSION }
|
|
237
|
+
: manifestRes.manifest;
|
|
238
|
+
// ---- step 2b: enforce verb / manifest kind alignment ----
|
|
229
239
|
if (manifest.kind !== opts.kind) {
|
|
230
240
|
const foundFile = manifest.kind === 'blueprint'
|
|
231
241
|
? GGUI_BLUEPRINT_JSON_FILENAME
|
|
@@ -259,34 +269,26 @@ export async function runArtifactPublish(opts) {
|
|
|
259
269
|
// ---- step 4: authenticate ----
|
|
260
270
|
// Two paths:
|
|
261
271
|
// --auth=bearer → token from flag or GGUI_REGISTRY_TOKEN
|
|
262
|
-
// default →
|
|
263
|
-
let
|
|
272
|
+
// default → the `ggui login` session stored at ~/.ggui/auth.json
|
|
273
|
+
let token;
|
|
264
274
|
const authFlags = opts.auth ?? {};
|
|
275
|
+
const authKind = authFlags.auth === 'bearer' ? 'bearer' : 'session';
|
|
265
276
|
try {
|
|
266
|
-
|
|
277
|
+
token = await acquireAuthToken({
|
|
267
278
|
flags: authFlags,
|
|
268
279
|
env: process.env,
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
registryUrl: deps.registryUrl,
|
|
274
|
-
env: deps.env,
|
|
275
|
-
cwd: deps.cwd,
|
|
276
|
-
hostedAuthClient: opts.hostedAuthClient,
|
|
277
|
-
prompt: opts.prompt,
|
|
278
|
-
now,
|
|
279
|
-
});
|
|
280
|
-
if (!hosted.ok) {
|
|
281
|
-
throw new HostedAuthError(hosted.error);
|
|
280
|
+
acquireSessionToken: async () => {
|
|
281
|
+
const session = await acquireLoginSessionToken({ now, fetchImpl });
|
|
282
|
+
if (!session.ok) {
|
|
283
|
+
throw new SessionAuthError(session.error);
|
|
282
284
|
}
|
|
283
|
-
stdout(`auth: ${
|
|
284
|
-
return
|
|
285
|
+
stdout(`auth: ggui login session${session.refreshed ? ' (refreshed)' : ''}\n`);
|
|
286
|
+
return session.accessToken;
|
|
285
287
|
},
|
|
286
288
|
});
|
|
287
289
|
}
|
|
288
290
|
catch (err) {
|
|
289
|
-
if (err instanceof
|
|
291
|
+
if (err instanceof SessionAuthError) {
|
|
290
292
|
stderr(`${verb}: ${err.payload.message}\n`);
|
|
291
293
|
return { ok: false, exitCode: 1, error: err.payload };
|
|
292
294
|
}
|
|
@@ -294,18 +296,24 @@ export async function runArtifactPublish(opts) {
|
|
|
294
296
|
stderr(`${verb}: ${msg}\n`);
|
|
295
297
|
return { ok: false, exitCode: 1, error: { code: 'auth_failed', message: msg } };
|
|
296
298
|
}
|
|
297
|
-
if (
|
|
298
|
-
stdout(`auth: bearer token (length=${
|
|
299
|
+
if (authKind === 'bearer') {
|
|
300
|
+
stdout(`auth: bearer token (length=${token.length})\n`);
|
|
299
301
|
}
|
|
300
302
|
// ---- step 5: conformance preflight ----
|
|
301
303
|
const preflightRes = await runConformancePreflight({
|
|
302
304
|
registryUrl,
|
|
303
|
-
|
|
305
|
+
token,
|
|
306
|
+
authKind,
|
|
304
307
|
manifest,
|
|
305
308
|
bundleBytes,
|
|
306
309
|
fetchImpl,
|
|
307
310
|
});
|
|
308
311
|
if (!preflightRes.ok) {
|
|
312
|
+
if (preflightRes.error.code === 'auth_failed') {
|
|
313
|
+
// 401 from the registry — an auth problem, not a conformance one.
|
|
314
|
+
stderr(`${verb}: ${preflightRes.error.message}\n`);
|
|
315
|
+
return { ok: false, exitCode: 1, error: preflightRes.error };
|
|
316
|
+
}
|
|
309
317
|
stderr(`${verb}: conformance preflight failed\n`);
|
|
310
318
|
if (preflightRes.error.code === 'conformance_failed' && preflightRes.error.issues) {
|
|
311
319
|
for (const issue of preflightRes.error.issues) {
|
|
@@ -402,7 +410,8 @@ export async function runArtifactPublish(opts) {
|
|
|
402
410
|
}
|
|
403
411
|
const publishRes = await postPublish({
|
|
404
412
|
registryUrl,
|
|
405
|
-
|
|
413
|
+
token,
|
|
414
|
+
authKind,
|
|
406
415
|
manifest,
|
|
407
416
|
bundleBytes,
|
|
408
417
|
bundleSha384,
|
|
@@ -625,240 +634,105 @@ async function bundleGadget(manifest, manifestDir) {
|
|
|
625
634
|
}
|
|
626
635
|
}
|
|
627
636
|
/**
|
|
628
|
-
* Sentinel error that lets the bearer/
|
|
637
|
+
* Sentinel error that lets the bearer/session strategy router surface
|
|
629
638
|
* a structured `PublishError` without leaking through `throw any`.
|
|
630
639
|
*/
|
|
631
|
-
class
|
|
640
|
+
class SessionAuthError extends Error {
|
|
632
641
|
payload;
|
|
633
642
|
constructor(payload) {
|
|
634
643
|
super(payload.message);
|
|
635
644
|
this.payload = payload;
|
|
636
645
|
}
|
|
637
646
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
647
|
+
/** Safety margin (seconds) before access-token expiry at which the CLI
|
|
648
|
+
* proactively refreshes instead of racing a boundary rejection. */
|
|
649
|
+
const ACCESS_TOKEN_FRESHNESS_MARGIN_SECONDS = 60;
|
|
650
|
+
/**
|
|
651
|
+
* Resolve the `ggui login` session token for registry calls.
|
|
652
|
+
*
|
|
653
|
+
* 1. Load `~/.ggui/auth.json` (written by `ggui login`). Missing /
|
|
654
|
+
* malformed → `auth_config_missing` telling the operator to either
|
|
655
|
+
* `ggui login` or use `--auth=bearer`.
|
|
656
|
+
* 2. Fresh access token → return it as-is.
|
|
657
|
+
* 3. Expired access token + live refresh token → `POST
|
|
658
|
+
* <session.endpoint>/v1/auth/refresh`, persist the rotated session,
|
|
659
|
+
* return the new access token.
|
|
660
|
+
* 4. Expired refresh token (or refresh rejected) → `auth_failed`
|
|
661
|
+
* telling the operator to `ggui login` again.
|
|
662
|
+
*/
|
|
663
|
+
export async function acquireLoginSessionToken(opts) {
|
|
664
|
+
const session = tryLoadAuthSession();
|
|
665
|
+
if (!session) {
|
|
650
666
|
return {
|
|
651
667
|
ok: false,
|
|
652
668
|
error: {
|
|
653
669
|
code: 'auth_config_missing',
|
|
654
|
-
message: 'registry
|
|
655
|
-
' -
|
|
656
|
-
' - set
|
|
670
|
+
message: 'no registry credentials found. Either:\n' +
|
|
671
|
+
' - run `ggui login` to sign in, or\n' +
|
|
672
|
+
' - pass --auth=bearer --token <token> (or set GGUI_REGISTRY_TOKEN) for self-hosted / local registries.',
|
|
657
673
|
},
|
|
658
674
|
};
|
|
659
675
|
}
|
|
660
|
-
|
|
661
|
-
if (
|
|
662
|
-
|
|
663
|
-
...(opts.hostedAuthClient ? { hostedAuthClient: opts.hostedAuthClient } : {}),
|
|
664
|
-
authConfig,
|
|
665
|
-
refreshToken: cached.refreshToken,
|
|
666
|
-
registryUrl: opts.registryUrl,
|
|
667
|
-
username: cached.username,
|
|
668
|
-
now: opts.now,
|
|
669
|
-
});
|
|
670
|
-
if (refreshed.ok) {
|
|
671
|
-
saveRegistryToken(refreshed.token);
|
|
672
|
-
return { ok: true, token: refreshed.token, fromCache: false };
|
|
673
|
-
}
|
|
674
|
-
// refresh failed — fall through to password flow
|
|
676
|
+
const nowSeconds = opts.now();
|
|
677
|
+
if (session.accessExpiresAt - nowSeconds > ACCESS_TOKEN_FRESHNESS_MARGIN_SECONDS) {
|
|
678
|
+
return { ok: true, accessToken: session.accessToken, refreshed: false };
|
|
675
679
|
}
|
|
676
|
-
|
|
677
|
-
const promptFn = opts.prompt ?? defaultPrompt;
|
|
678
|
-
let username;
|
|
679
|
-
let password;
|
|
680
|
-
try {
|
|
681
|
-
username = await promptFn('Registry username: ');
|
|
682
|
-
password = await promptFn('Registry password: ', { hidden: true });
|
|
683
|
-
}
|
|
684
|
-
catch (err) {
|
|
680
|
+
if (session.refreshExpiresAt <= nowSeconds) {
|
|
685
681
|
return {
|
|
686
682
|
ok: false,
|
|
687
683
|
error: {
|
|
688
684
|
code: 'auth_failed',
|
|
689
|
-
message:
|
|
685
|
+
message: 'login session expired. Run `ggui login` again.',
|
|
690
686
|
},
|
|
691
687
|
};
|
|
692
688
|
}
|
|
693
|
-
|
|
694
|
-
const auth = await runUserPasswordAuth({
|
|
695
|
-
hostedAuthClient: idp,
|
|
696
|
-
authConfig,
|
|
697
|
-
username,
|
|
698
|
-
password,
|
|
699
|
-
});
|
|
700
|
-
if (!auth.ok) {
|
|
701
|
-
return { ok: false, error: { code: 'auth_failed', message: auth.error } };
|
|
702
|
-
}
|
|
703
|
-
const token = {
|
|
704
|
-
version: 1,
|
|
705
|
-
registry: opts.registryUrl,
|
|
706
|
-
idToken: auth.idToken,
|
|
707
|
-
accessToken: auth.accessToken,
|
|
708
|
-
refreshToken: auth.refreshToken,
|
|
709
|
-
expiresAt: opts.now() + auth.expiresIn,
|
|
710
|
-
username,
|
|
711
|
-
writtenAt: new Date().toISOString(),
|
|
712
|
-
};
|
|
713
|
-
saveRegistryToken(token);
|
|
714
|
-
return { ok: true, token, fromCache: false };
|
|
715
|
-
}
|
|
716
|
-
/**
|
|
717
|
-
* Resolve Cognito auth config from environment variables only.
|
|
718
|
-
*
|
|
719
|
-
* `ggui.json` is source-controlled, so personal credentials never go
|
|
720
|
-
* in git. The publish CLI's auth config comes exclusively from the
|
|
721
|
-
* canonical env-var pair:
|
|
722
|
-
*
|
|
723
|
-
* `GGUI_REGISTRY_COGNITO_POOL_ID` + `GGUI_REGISTRY_COGNITO_APP_CLIENT_ID`
|
|
724
|
-
*
|
|
725
|
-
* The `GGUI_REGISTRY_` prefix matches other registry-scoped vars
|
|
726
|
-
* (`GGUI_REGISTRY_TOKEN`, `GGUI_REGISTRY`). There is deliberately no
|
|
727
|
-
* unprefixed `GGUI_COGNITO_*` fallback — a second accepted form would
|
|
728
|
-
* re-introduce env-var ambiguity.
|
|
729
|
-
*
|
|
730
|
-
* No project-config fallback. Operators who want per-project auth
|
|
731
|
-
* pinning use a per-project `.envrc` (direnv) or shell wrapper.
|
|
732
|
-
*/
|
|
733
|
-
function resolveCognitoConfig(env) {
|
|
734
|
-
const poolId = env['GGUI_REGISTRY_COGNITO_POOL_ID'];
|
|
735
|
-
const appClientId = env['GGUI_REGISTRY_COGNITO_APP_CLIENT_ID'];
|
|
736
|
-
if (poolId && appClientId) {
|
|
737
|
-
return { poolId, appClientId };
|
|
738
|
-
}
|
|
739
|
-
return null;
|
|
740
|
-
}
|
|
741
|
-
async function runUserPasswordAuth(opts) {
|
|
742
|
-
const input = {
|
|
743
|
-
AuthFlow: 'USER_PASSWORD_AUTH',
|
|
744
|
-
ClientId: opts.authConfig.appClientId,
|
|
745
|
-
AuthParameters: {
|
|
746
|
-
USERNAME: opts.username,
|
|
747
|
-
PASSWORD: opts.password,
|
|
748
|
-
},
|
|
749
|
-
};
|
|
689
|
+
let tokens;
|
|
750
690
|
try {
|
|
751
|
-
|
|
752
|
-
const res = await opts.hostedAuthClient.send(cmd);
|
|
753
|
-
return extractAuthResult(res);
|
|
691
|
+
tokens = await postAuthRefresh(session.endpoint, session.refreshToken, opts.fetchImpl);
|
|
754
692
|
}
|
|
755
693
|
catch (err) {
|
|
694
|
+
if (err instanceof ApiError && (err.status === 400 || err.status === 401)) {
|
|
695
|
+
return {
|
|
696
|
+
ok: false,
|
|
697
|
+
error: {
|
|
698
|
+
code: 'auth_failed',
|
|
699
|
+
message: 'login session expired or was revoked. Run `ggui login` again.',
|
|
700
|
+
},
|
|
701
|
+
};
|
|
702
|
+
}
|
|
756
703
|
return {
|
|
757
704
|
ok: false,
|
|
758
|
-
error:
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
}
|
|
762
|
-
async function tryRefresh(opts) {
|
|
763
|
-
const idp = opts.hostedAuthClient ?? new CognitoIdentityProviderClient({});
|
|
764
|
-
const input = {
|
|
765
|
-
AuthFlow: 'REFRESH_TOKEN_AUTH',
|
|
766
|
-
ClientId: opts.authConfig.appClientId,
|
|
767
|
-
AuthParameters: {
|
|
768
|
-
REFRESH_TOKEN: opts.refreshToken,
|
|
769
|
-
},
|
|
770
|
-
};
|
|
771
|
-
try {
|
|
772
|
-
const res = await idp.send(new InitiateAuthCommand(input));
|
|
773
|
-
const extracted = extractAuthResult(res);
|
|
774
|
-
if (!extracted.ok)
|
|
775
|
-
return { ok: false, error: extracted.error };
|
|
776
|
-
// Cognito's REFRESH_TOKEN flow returns a new RefreshToken only
|
|
777
|
-
// when rotation is enabled on the pool. When absent, reuse the
|
|
778
|
-
// existing refresh token (still valid until its TTL).
|
|
779
|
-
const refreshToken = extracted.refreshToken.length > 0 ? extracted.refreshToken : opts.refreshToken;
|
|
780
|
-
return {
|
|
781
|
-
ok: true,
|
|
782
|
-
token: {
|
|
783
|
-
version: 1,
|
|
784
|
-
registry: opts.registryUrl,
|
|
785
|
-
idToken: extracted.idToken,
|
|
786
|
-
accessToken: extracted.accessToken,
|
|
787
|
-
refreshToken,
|
|
788
|
-
expiresAt: opts.now() + extracted.expiresIn,
|
|
789
|
-
username: opts.username,
|
|
790
|
-
writtenAt: new Date().toISOString(),
|
|
705
|
+
error: {
|
|
706
|
+
code: 'auth_failed',
|
|
707
|
+
message: `token refresh failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
791
708
|
},
|
|
792
709
|
};
|
|
793
710
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
if (!result) {
|
|
801
|
-
return {
|
|
802
|
-
ok: false,
|
|
803
|
-
error: `Cognito did not return AuthenticationResult (challenge=${res.ChallengeName ?? 'none'})`,
|
|
804
|
-
};
|
|
805
|
-
}
|
|
806
|
-
if (typeof result.IdToken !== 'string' ||
|
|
807
|
-
typeof result.AccessToken !== 'string' ||
|
|
808
|
-
typeof result.ExpiresIn !== 'number') {
|
|
809
|
-
return {
|
|
810
|
-
ok: false,
|
|
811
|
-
error: 'Cognito AuthenticationResult missing IdToken/AccessToken/ExpiresIn',
|
|
812
|
-
};
|
|
813
|
-
}
|
|
814
|
-
return {
|
|
815
|
-
ok: true,
|
|
816
|
-
idToken: result.IdToken,
|
|
817
|
-
accessToken: result.AccessToken,
|
|
818
|
-
refreshToken: typeof result.RefreshToken === 'string' ? result.RefreshToken : '',
|
|
819
|
-
expiresIn: result.ExpiresIn,
|
|
711
|
+
const refreshedSession = {
|
|
712
|
+
...session,
|
|
713
|
+
accessToken: tokens.access_token,
|
|
714
|
+
accessExpiresAt: nowSeconds + tokens.expires_in,
|
|
715
|
+
refreshToken: tokens.refresh_token,
|
|
716
|
+
writtenAt: new Date().toISOString(),
|
|
820
717
|
};
|
|
718
|
+
saveAuthSession(refreshedSession);
|
|
719
|
+
return { ok: true, accessToken: refreshedSession.accessToken, refreshed: true };
|
|
821
720
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
if (
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
for (const ch of chunk) {
|
|
838
|
-
if (ch === '\r' || ch === '\n') {
|
|
839
|
-
if (opts.hidden)
|
|
840
|
-
stdin.setRawMode(false);
|
|
841
|
-
process.stdout.write('\n');
|
|
842
|
-
return buf;
|
|
843
|
-
}
|
|
844
|
-
if (ch === '') {
|
|
845
|
-
// Ctrl-C — propagate via throw so the caller exits cleanly.
|
|
846
|
-
if (opts.hidden)
|
|
847
|
-
stdin.setRawMode(false);
|
|
848
|
-
throw new Error('prompt cancelled');
|
|
849
|
-
}
|
|
850
|
-
if (ch === '' || ch === '\b') {
|
|
851
|
-
buf = buf.slice(0, -1);
|
|
852
|
-
continue;
|
|
853
|
-
}
|
|
854
|
-
buf += ch;
|
|
855
|
-
if (!opts.hidden)
|
|
856
|
-
process.stdout.write(ch);
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
if (opts.hidden)
|
|
860
|
-
stdin.setRawMode(false);
|
|
861
|
-
return buf;
|
|
721
|
+
/**
|
|
722
|
+
* Operator-facing diagnosis for an HTTP 401 from the registry,
|
|
723
|
+
* specialized on which credential was sent. Used by both the
|
|
724
|
+
* conformance preflight and the publish POST.
|
|
725
|
+
*/
|
|
726
|
+
function describeRegistryAuthRejection(authKind, path) {
|
|
727
|
+
if (authKind === 'bearer') {
|
|
728
|
+
return (`registry rejected the bearer token (HTTP 401 from ${path}). ` +
|
|
729
|
+
'Check that --token / GGUI_REGISTRY_TOKEN matches the publish token the registry is configured with.');
|
|
730
|
+
}
|
|
731
|
+
return ('registry rejected the `ggui login` session token (HTTP 401 from ' +
|
|
732
|
+
`${path}). Re-run \`ggui login\` — the session may have expired ` +
|
|
733
|
+
'or been revoked. For self-hosted registries that authenticate ' +
|
|
734
|
+
'with a static publish token instead of login sessions, pass ' +
|
|
735
|
+
'--auth=bearer --token <token> (or set GGUI_REGISTRY_TOKEN).');
|
|
862
736
|
}
|
|
863
737
|
async function runConformancePreflight(opts) {
|
|
864
738
|
const url = `${opts.registryUrl}/conformance/check`;
|
|
@@ -874,7 +748,7 @@ async function runConformancePreflight(opts) {
|
|
|
874
748
|
method: 'POST',
|
|
875
749
|
headers: {
|
|
876
750
|
'Content-Type': 'application/json',
|
|
877
|
-
Authorization: `Bearer ${opts.
|
|
751
|
+
Authorization: `Bearer ${opts.token}`,
|
|
878
752
|
},
|
|
879
753
|
body,
|
|
880
754
|
});
|
|
@@ -888,6 +762,15 @@ async function runConformancePreflight(opts) {
|
|
|
888
762
|
},
|
|
889
763
|
};
|
|
890
764
|
}
|
|
765
|
+
if (res.status === 401) {
|
|
766
|
+
return {
|
|
767
|
+
ok: false,
|
|
768
|
+
error: {
|
|
769
|
+
code: 'auth_failed',
|
|
770
|
+
message: describeRegistryAuthRejection(opts.authKind, '/conformance/check'),
|
|
771
|
+
},
|
|
772
|
+
};
|
|
773
|
+
}
|
|
891
774
|
if (!res.ok) {
|
|
892
775
|
let text = '';
|
|
893
776
|
try {
|
|
@@ -1043,7 +926,7 @@ async function postPublish(opts) {
|
|
|
1043
926
|
method: 'POST',
|
|
1044
927
|
headers: {
|
|
1045
928
|
'Content-Type': 'application/json',
|
|
1046
|
-
Authorization: `Bearer ${opts.
|
|
929
|
+
Authorization: `Bearer ${opts.token}`,
|
|
1047
930
|
},
|
|
1048
931
|
body,
|
|
1049
932
|
});
|
|
@@ -1057,6 +940,15 @@ async function postPublish(opts) {
|
|
|
1057
940
|
},
|
|
1058
941
|
};
|
|
1059
942
|
}
|
|
943
|
+
if (res.status === 401) {
|
|
944
|
+
return {
|
|
945
|
+
ok: false,
|
|
946
|
+
error: {
|
|
947
|
+
code: 'auth_failed',
|
|
948
|
+
message: describeRegistryAuthRejection(opts.authKind, '/publish'),
|
|
949
|
+
},
|
|
950
|
+
};
|
|
951
|
+
}
|
|
1060
952
|
if (res.status === 501) {
|
|
1061
953
|
return {
|
|
1062
954
|
ok: false,
|
|
@@ -1,31 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* `blueprintManifestSchema` discriminators in
|
|
4
|
-
* `@ggui-ai/artifact-manifest`. Inlined here because the manifest
|
|
5
|
-
* package does not currently export a named `ArtifactKind` type — when
|
|
6
|
-
* it does, swap to a structural `import type` from there.
|
|
7
|
-
*/
|
|
8
|
-
export type ArtifactKind = 'gadget' | 'blueprint';
|
|
9
|
-
/**
|
|
10
|
-
* One row of `GET /search?…` results. Mirrors the registry's
|
|
11
|
-
* `SearchResultEntry` response type.
|
|
12
|
-
*/
|
|
13
|
-
export interface SearchResultEntry {
|
|
14
|
-
readonly artifactId: string;
|
|
15
|
-
readonly latestVersion: string;
|
|
16
|
-
readonly kind: ArtifactKind;
|
|
17
|
-
readonly description?: string;
|
|
18
|
-
readonly tags?: readonly string[];
|
|
19
|
-
readonly publishedAt: string;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* `GET /search?…` response body. `nextCursor` is an opaque base64
|
|
23
|
-
* string the client roundtrips verbatim — never inspected client-side.
|
|
24
|
-
*/
|
|
25
|
-
export interface SearchResponse {
|
|
26
|
-
readonly results: readonly SearchResultEntry[];
|
|
27
|
-
readonly nextCursor?: string;
|
|
28
|
-
}
|
|
1
|
+
import { type ArtifactKind, type SearchResponse, type SearchResultEntry } from '@ggui-ai/registry-core';
|
|
2
|
+
export type { ArtifactKind, SearchResponse, SearchResultEntry };
|
|
29
3
|
/**
|
|
30
4
|
* Flag bundle accepted by {@link runArtifactSearch}. Mirrors the CLI's
|
|
31
5
|
* positional + flag surface. Built either from `process.argv` (via
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"artifact-search.d.ts","sourceRoot":"","sources":["../../src/internal/artifact-search.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"artifact-search.d.ts","sourceRoot":"","sources":["../../src/internal/artifact-search.ts"],"names":[],"mappings":"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"}
|
|
@@ -18,13 +18,14 @@
|
|
|
18
18
|
* Returns a `SearchOutput` discriminated union; the CLI driver in
|
|
19
19
|
* `gadget-command.ts` renders copy + picks the exit code.
|
|
20
20
|
*
|
|
21
|
-
* The wire shapes
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* The wire shapes (`SearchResultEntry` / `SearchResponse` /
|
|
22
|
+
* `ArtifactKind`) are imported from `@ggui-ai/registry-core` — the
|
|
23
|
+
* canonical owner of the registry HTTP response types — and
|
|
24
|
+
* re-exported for this module's consumers.
|
|
24
25
|
*/
|
|
25
26
|
import { existsSync, readFileSync } from 'node:fs';
|
|
26
27
|
import { dirname, join, resolve } from 'node:path';
|
|
27
|
-
import { SEARCH_ERROR_CODES } from '@ggui-ai/registry-core';
|
|
28
|
+
import { SEARCH_ERROR_CODES, } from '@ggui-ai/registry-core';
|
|
28
29
|
/**
|
|
29
30
|
* Filename constant for the project manifest. Duplicated from
|
|
30
31
|
* `@ggui-ai/project-config` rather than imported to keep this
|