@ggui-ai/cli 0.1.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +96 -0
- package/dist/agent-resolution.d.ts +41 -0
- package/dist/agent-resolution.d.ts.map +1 -0
- package/dist/agent-resolution.js +80 -0
- package/dist/api-client.d.ts +59 -0
- package/dist/api-client.d.ts.map +1 -0
- package/dist/api-client.js +145 -0
- package/dist/auth-keys.d.ts +10 -0
- package/dist/auth-keys.d.ts.map +1 -0
- package/dist/auth-keys.js +382 -0
- package/dist/auth-login.d.ts +11 -0
- package/dist/auth-login.d.ts.map +1 -0
- package/dist/auth-login.js +231 -0
- package/dist/auth-logout.d.ts +3 -0
- package/dist/auth-logout.d.ts.map +1 -0
- package/dist/auth-logout.js +32 -0
- package/dist/auth-store.d.ts +67 -0
- package/dist/auth-store.d.ts.map +1 -0
- package/dist/auth-store.js +150 -0
- package/dist/auth-whoami.d.ts +3 -0
- package/dist/auth-whoami.d.ts.map +1 -0
- package/dist/auth-whoami.js +52 -0
- package/dist/blueprint-command.d.ts +3 -0
- package/dist/blueprint-command.d.ts.map +1 -0
- package/dist/blueprint-command.js +203 -0
- package/dist/blueprint-create.d.ts +71 -0
- package/dist/blueprint-create.d.ts.map +1 -0
- package/dist/blueprint-create.js +377 -0
- package/dist/byok-resolver.d.ts +141 -0
- package/dist/byok-resolver.d.ts.map +1 -0
- package/dist/byok-resolver.js +89 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1066 -0
- package/dist/dev-command.d.ts +169 -0
- package/dist/dev-command.d.ts.map +1 -0
- package/dist/dev-command.js +222 -0
- package/dist/gadget-command.d.ts +3 -0
- package/dist/gadget-command.d.ts.map +1 -0
- package/dist/gadget-command.js +181 -0
- package/dist/gadget-create.d.ts +106 -0
- package/dist/gadget-create.d.ts.map +1 -0
- package/dist/gadget-create.js +492 -0
- package/dist/generation-probe.d.ts +135 -0
- package/dist/generation-probe.d.ts.map +1 -0
- package/dist/generation-probe.js +132 -0
- package/dist/internal/artifact-install.d.ts +150 -0
- package/dist/internal/artifact-install.d.ts.map +1 -0
- package/dist/internal/artifact-install.js +1019 -0
- package/dist/internal/artifact-publish.d.ts +204 -0
- package/dist/internal/artifact-publish.d.ts.map +1 -0
- package/dist/internal/artifact-publish.js +1141 -0
- package/dist/internal/artifact-search.d.ts +155 -0
- package/dist/internal/artifact-search.d.ts.map +1 -0
- package/dist/internal/artifact-search.js +539 -0
- package/dist/internal/artifact-uninstall.d.ts +30 -0
- package/dist/internal/artifact-uninstall.d.ts.map +1 -0
- package/dist/internal/artifact-uninstall.js +205 -0
- package/dist/internal/auth-cache.d.ts +40 -0
- package/dist/internal/auth-cache.d.ts.map +1 -0
- package/dist/internal/auth-cache.js +133 -0
- package/dist/internal/auth-strategy.d.ts +89 -0
- package/dist/internal/auth-strategy.d.ts.map +1 -0
- package/dist/internal/auth-strategy.js +132 -0
- package/dist/internal/ggui-json.d.ts +53 -0
- package/dist/internal/ggui-json.d.ts.map +1 -0
- package/dist/internal/ggui-json.js +92 -0
- package/dist/internal/key-store.d.ts +43 -0
- package/dist/internal/key-store.d.ts.map +1 -0
- package/dist/internal/key-store.js +128 -0
- package/dist/internal/oidc-token.d.ts +52 -0
- package/dist/internal/oidc-token.d.ts.map +1 -0
- package/dist/internal/oidc-token.js +351 -0
- package/dist/internal/register-author-key.d.ts +44 -0
- package/dist/internal/register-author-key.d.ts.map +1 -0
- package/dist/internal/register-author-key.js +212 -0
- package/dist/local-keys.d.ts +47 -0
- package/dist/local-keys.d.ts.map +1 -0
- package/dist/local-keys.js +115 -0
- package/dist/mcp-backend.d.ts +356 -0
- package/dist/mcp-backend.d.ts.map +1 -0
- package/dist/mcp-backend.js +668 -0
- package/dist/paths.d.ts +77 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +119 -0
- package/dist/persistent-secrets.d.ts +28 -0
- package/dist/persistent-secrets.d.ts.map +1 -0
- package/dist/persistent-secrets.js +94 -0
- package/dist/persistent-short-code-index.d.ts +14 -0
- package/dist/persistent-short-code-index.d.ts.map +1 -0
- package/dist/persistent-short-code-index.js +39 -0
- package/dist/persistent-stores.d.ts +12 -0
- package/dist/persistent-stores.d.ts.map +1 -0
- package/dist/persistent-stores.js +42 -0
- package/dist/serve-command.d.ts +500 -0
- package/dist/serve-command.d.ts.map +1 -0
- package/dist/serve-command.js +616 -0
- package/dist/serve-fallback.d.ts +64 -0
- package/dist/serve-fallback.d.ts.map +1 -0
- package/dist/serve-fallback.js +122 -0
- package/dist/spawn-cloudflared.d.ts +61 -0
- package/dist/spawn-cloudflared.d.ts.map +1 -0
- package/dist/spawn-cloudflared.js +167 -0
- package/dist/theme-command.d.ts +4 -0
- package/dist/theme-command.d.ts.map +1 -0
- package/dist/theme-command.js +154 -0
- package/dist/theme-file-uploader.d.ts +8 -0
- package/dist/theme-file-uploader.d.ts.map +1 -0
- package/dist/theme-file-uploader.js +58 -0
- package/dist/theme-writer.d.ts +8 -0
- package/dist/theme-writer.d.ts.map +1 -0
- package/dist/theme-writer.js +103 -0
- package/dist/tunnel-provider.d.ts +273 -0
- package/dist/tunnel-provider.d.ts.map +1 -0
- package/dist/tunnel-provider.js +142 -0
- package/package.json +81 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ArtifactKind } from './artifact-install.js';
|
|
2
|
+
export declare function buildUninstallHelp(kind: ArtifactKind): string;
|
|
3
|
+
export interface ParsedArtifactUninstallFlags {
|
|
4
|
+
readonly kind: ArtifactKind;
|
|
5
|
+
readonly artifactId: string;
|
|
6
|
+
readonly version: string;
|
|
7
|
+
}
|
|
8
|
+
export type ParsedArtifactUninstall = ParsedArtifactUninstallFlags | {
|
|
9
|
+
readonly error: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function parseArtifactUninstallFlags(kind: ArtifactKind, args: readonly string[]): ParsedArtifactUninstall;
|
|
12
|
+
export interface ArtifactUninstallDeps {
|
|
13
|
+
readonly cwd: string;
|
|
14
|
+
readonly stdout?: (s: string) => void;
|
|
15
|
+
readonly stderr?: (s: string) => void;
|
|
16
|
+
}
|
|
17
|
+
export interface ArtifactUninstallResult {
|
|
18
|
+
readonly exitCode: number;
|
|
19
|
+
/** `true` when a directory was actually removed. `false` on idempotent no-ops. */
|
|
20
|
+
readonly removed: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* `true` when `INSTALLED_BLUEPRINTS_GLOB` was stripped from
|
|
23
|
+
* `ggui.json#blueprints.include` (i.e. no installed-blueprints
|
|
24
|
+
* subdirs remained). `false` when the glob was kept (other installs
|
|
25
|
+
* still on disk) or wasn't present to begin with.
|
|
26
|
+
*/
|
|
27
|
+
readonly globRemoved: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare function runArtifactUninstall(flags: ParsedArtifactUninstallFlags, deps: ArtifactUninstallDeps): Promise<ArtifactUninstallResult>;
|
|
30
|
+
//# sourceMappingURL=artifact-uninstall.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"artifact-uninstall.d.ts","sourceRoot":"","sources":["../../src/internal/artifact-uninstall.ts"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAM1D,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CA4B7D;AAMD,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,MAAM,uBAAuB,GAC/B,4BAA4B,GAC5B;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/B,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,YAAY,EAClB,IAAI,EAAE,SAAS,MAAM,EAAE,GACtB,uBAAuB,CAwCzB;AAMD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,kFAAkF;IAClF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC/B;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,4BAA4B,EACnC,IAAI,EAAE,qBAAqB,GAC1B,OAAO,CAAC,uBAAuB,CAAC,CAmFlC"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ggui blueprint uninstall <scope/name>@<version>`.
|
|
3
|
+
*
|
|
4
|
+
* Reverse of `runArtifactInstall`'s on-disk materialization. Removes
|
|
5
|
+
* `.ggui/installed-blueprints/<scope-bare>__<name>__<version>/`
|
|
6
|
+
* from the project root and — when this was the LAST remaining
|
|
7
|
+
* installed-blueprint subdir — strips the `INSTALLED_BLUEPRINTS_GLOB`
|
|
8
|
+
* entry from `ggui.json#blueprints.include` so a future `ggui serve`
|
|
9
|
+
* doesn't walk an empty glob.
|
|
10
|
+
*
|
|
11
|
+
* What this does NOT touch:
|
|
12
|
+
*
|
|
13
|
+
* - **The runtime cache row.** The install-to-cache bridge is
|
|
14
|
+
* per-scope idempotent within a single server lifetime; the row
|
|
15
|
+
* stays in the `vectorStore` until the operator restarts
|
|
16
|
+
* `ggui serve`. Sqlite-backed stores survive restart too. A
|
|
17
|
+
* restart is the documented step to reclaim the cache slot.
|
|
18
|
+
* - **The marketplace registry.** Uninstall is purely local; the
|
|
19
|
+
* published artifact is unaffected. Registry-side withdrawal is a
|
|
20
|
+
* separate operation.
|
|
21
|
+
* - **`ggui.json#blueprints.include` entries that aren't the
|
|
22
|
+
* installed-blueprints glob.** Hand-authored UI globs survive
|
|
23
|
+
* verbatim; only the install-managed glob is auto-removed when
|
|
24
|
+
* no installed-blueprints subdirs remain.
|
|
25
|
+
*
|
|
26
|
+
* Posture: idempotent. Uninstalling a blueprint that was never
|
|
27
|
+
* installed succeeds with exit code 0 and a stderr note — operators
|
|
28
|
+
* shouldn't be punished for re-running the command.
|
|
29
|
+
*/
|
|
30
|
+
import { existsSync, readdirSync, rmSync } from 'node:fs';
|
|
31
|
+
import { join } from 'node:path';
|
|
32
|
+
import { findGguiJson, readGguiJson, writeGguiJson, } from './ggui-json.js';
|
|
33
|
+
import { blueprintInstallSubdir, INSTALLED_BLUEPRINTS_GLOB, INSTALLED_BLUEPRINTS_SUBDIR, } from './artifact-install.js';
|
|
34
|
+
/* -------------------------------------------------------------------------- */
|
|
35
|
+
/* Help text */
|
|
36
|
+
/* -------------------------------------------------------------------------- */
|
|
37
|
+
export function buildUninstallHelp(kind) {
|
|
38
|
+
const noun = kind === 'blueprint' ? 'blueprint' : 'gadget';
|
|
39
|
+
const verb = `ggui ${noun} uninstall`;
|
|
40
|
+
return `${verb} — remove a marketplace-installed ${noun} from this project.
|
|
41
|
+
|
|
42
|
+
Usage:
|
|
43
|
+
${verb} <scope/name>@<version> [options]
|
|
44
|
+
|
|
45
|
+
Arguments:
|
|
46
|
+
<scope/name>@<version> Full install identifier. Same shape as install.
|
|
47
|
+
|
|
48
|
+
Options:
|
|
49
|
+
--help Show this help.
|
|
50
|
+
|
|
51
|
+
Behavior:
|
|
52
|
+
- Removes \`.ggui/installed-blueprints/<scope-bare>__<name>__<version>/\` from disk.
|
|
53
|
+
- When this was the LAST installed-blueprint subdir, strips the
|
|
54
|
+
\`${INSTALLED_BLUEPRINTS_GLOB}\` entry from \`ggui.json#blueprints.include\`.
|
|
55
|
+
Other globs in the include list are preserved verbatim.
|
|
56
|
+
- Idempotent: uninstalling a never-installed identifier exits 0
|
|
57
|
+
with a stderr note. Re-runs are safe.
|
|
58
|
+
|
|
59
|
+
Caveats:
|
|
60
|
+
- The runtime cache row stays warm until the next \`ggui serve\` restart
|
|
61
|
+
(the install-to-cache bridge is per-scope idempotent within a server
|
|
62
|
+
lifetime). Restart to reclaim the cache slot.
|
|
63
|
+
- This is local-only. The published marketplace artifact is unchanged.
|
|
64
|
+
`;
|
|
65
|
+
}
|
|
66
|
+
export function parseArtifactUninstallFlags(kind, args) {
|
|
67
|
+
let positional;
|
|
68
|
+
for (const arg of args) {
|
|
69
|
+
if (arg === '--help' || arg === '-h') {
|
|
70
|
+
return { error: '__help__' };
|
|
71
|
+
}
|
|
72
|
+
if (arg.startsWith('--')) {
|
|
73
|
+
return { error: `unknown flag: ${arg}` };
|
|
74
|
+
}
|
|
75
|
+
if (positional === undefined) {
|
|
76
|
+
positional = arg;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
return { error: `unexpected positional argument: ${arg}` };
|
|
80
|
+
}
|
|
81
|
+
if (positional === undefined) {
|
|
82
|
+
return {
|
|
83
|
+
error: 'missing positional argument: <scope/name>@<version> (e.g. @my-org/notepad@1.0.0)',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const lastAt = positional.lastIndexOf('@');
|
|
87
|
+
if (lastAt <= 0) {
|
|
88
|
+
return {
|
|
89
|
+
error: `invalid uninstall identifier: ${positional} — expected <scope/name>@<version>`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const artifactId = positional.slice(0, lastAt);
|
|
93
|
+
const version = positional.slice(lastAt + 1);
|
|
94
|
+
if (!artifactId.startsWith('@') || artifactId.indexOf('/') === -1) {
|
|
95
|
+
return {
|
|
96
|
+
error: `invalid artifactId: ${artifactId} — expected \`@scope/name\``,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (version.length === 0) {
|
|
100
|
+
return {
|
|
101
|
+
error: `invalid version: empty (expected SemVer after \`@\`)`,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return { kind, artifactId, version };
|
|
105
|
+
}
|
|
106
|
+
export async function runArtifactUninstall(flags, deps) {
|
|
107
|
+
const verb = `ggui ${flags.kind} uninstall`;
|
|
108
|
+
const stdout = deps.stdout ?? ((s) => void process.stdout.write(s));
|
|
109
|
+
const stderr = deps.stderr ?? ((s) => void process.stderr.write(s));
|
|
110
|
+
const gguiPath = findGguiJson(deps.cwd);
|
|
111
|
+
if (gguiPath === null) {
|
|
112
|
+
stderr(`${verb}: no ggui.json found in ${deps.cwd} or any ancestor (up to 8 levels).\n`);
|
|
113
|
+
return { exitCode: 2, removed: false, globRemoved: false };
|
|
114
|
+
}
|
|
115
|
+
const loaded = readGguiJson(gguiPath);
|
|
116
|
+
if ('error' in loaded) {
|
|
117
|
+
stderr(`${verb}: ${loaded.error}\n`);
|
|
118
|
+
return { exitCode: 2, removed: false, globRemoved: false };
|
|
119
|
+
}
|
|
120
|
+
const gguiJson = loaded.value;
|
|
121
|
+
const projectRoot = gguiPath.slice(0, gguiPath.lastIndexOf('/'));
|
|
122
|
+
const scope = flags.artifactId.slice(1, flags.artifactId.indexOf('/'));
|
|
123
|
+
const name = flags.artifactId.slice(flags.artifactId.indexOf('/') + 1);
|
|
124
|
+
const subdir = blueprintInstallSubdir({
|
|
125
|
+
scope: `@${scope}`,
|
|
126
|
+
name,
|
|
127
|
+
version: flags.version,
|
|
128
|
+
});
|
|
129
|
+
const installDir = join(projectRoot, INSTALLED_BLUEPRINTS_SUBDIR, subdir);
|
|
130
|
+
let removed = false;
|
|
131
|
+
if (existsSync(installDir)) {
|
|
132
|
+
try {
|
|
133
|
+
rmSync(installDir, { recursive: true, force: true });
|
|
134
|
+
removed = true;
|
|
135
|
+
stdout(`removed: ${installDir}\n`);
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
stderr(`${verb}: failed to remove ${installDir}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
139
|
+
return { exitCode: 1, removed: false, globRemoved: false };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
stderr(`${verb}: ${flags.artifactId}@${flags.version} is not installed at ${installDir} — nothing to remove.\n`);
|
|
144
|
+
}
|
|
145
|
+
// Auto-cleanup of the include glob: only when zero installed-
|
|
146
|
+
// blueprint subdirs remain. The base directory's continued existence
|
|
147
|
+
// doesn't matter — we walk it for a non-empty subdir list (the
|
|
148
|
+
// INSTALLED_BLUEPRINTS_GLOB matches `<subdir>/ggui.ui.json`, so the
|
|
149
|
+
// absence of any subdir means the glob matches nothing).
|
|
150
|
+
const installRoot = join(projectRoot, INSTALLED_BLUEPRINTS_SUBDIR);
|
|
151
|
+
let remainingSubdirs = 0;
|
|
152
|
+
if (existsSync(installRoot)) {
|
|
153
|
+
try {
|
|
154
|
+
remainingSubdirs = readdirSync(installRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).length;
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// If we can't enumerate the dir, leave the glob alone — better
|
|
158
|
+
// than risk-stripping an include the operator still relies on.
|
|
159
|
+
remainingSubdirs = Number.POSITIVE_INFINITY;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
let globRemoved = false;
|
|
163
|
+
if (remainingSubdirs === 0) {
|
|
164
|
+
if (removeBlueprintGlob(gguiJson)) {
|
|
165
|
+
writeGguiJson(gguiPath, gguiJson);
|
|
166
|
+
globRemoved = true;
|
|
167
|
+
stdout(`removed glob from ggui.json#blueprints.include (no installed blueprints remain)\n`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (removed || globRemoved) {
|
|
171
|
+
stdout(`\nUninstall complete.\n`);
|
|
172
|
+
}
|
|
173
|
+
return { exitCode: 0, removed, globRemoved };
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Strip `INSTALLED_BLUEPRINTS_GLOB` from `gguiJson.blueprints.include`
|
|
177
|
+
* when present. Returns `true` when a write is needed. Leaves the
|
|
178
|
+
* `blueprints` block intact when it still contains other include
|
|
179
|
+
* entries — operator-authored UI globs are preserved.
|
|
180
|
+
*/
|
|
181
|
+
function removeBlueprintGlob(gguiJson) {
|
|
182
|
+
const blueprints = gguiJson['blueprints'];
|
|
183
|
+
if (blueprints === undefined ||
|
|
184
|
+
typeof blueprints !== 'object' ||
|
|
185
|
+
blueprints === null ||
|
|
186
|
+
Array.isArray(blueprints)) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
const blueprintsObj = blueprints;
|
|
190
|
+
const include = blueprintsObj['include'];
|
|
191
|
+
if (!Array.isArray(include))
|
|
192
|
+
return false;
|
|
193
|
+
const idx = include.indexOf(INSTALLED_BLUEPRINTS_GLOB);
|
|
194
|
+
if (idx === -1)
|
|
195
|
+
return false;
|
|
196
|
+
include.splice(idx, 1);
|
|
197
|
+
// When the include list is now empty AND no other blueprints-block
|
|
198
|
+
// fields are present, remove the whole block so the manifest stays
|
|
199
|
+
// tidy. Operator-edited blueprints blocks with additional fields
|
|
200
|
+
// survive — we only touch the install-managed glob.
|
|
201
|
+
if (include.length === 0 && Object.keys(blueprintsObj).length === 1) {
|
|
202
|
+
delete gguiJson['blueprints'];
|
|
203
|
+
}
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** v1 on-disk shape. */
|
|
2
|
+
export interface RegistryTokenDocument {
|
|
3
|
+
readonly version: 1;
|
|
4
|
+
/** Full registry origin (e.g. `https://dev.registry.sandbox.ggui.ai`). */
|
|
5
|
+
readonly registry: string;
|
|
6
|
+
/** Cognito IdToken — sent as `Authorization: Bearer <idToken>`. */
|
|
7
|
+
readonly idToken: string;
|
|
8
|
+
/** Cognito AccessToken — surfaced separately for handlers that need it. */
|
|
9
|
+
readonly accessToken: string;
|
|
10
|
+
/** Cognito RefreshToken — used to mint fresh id/access tokens. */
|
|
11
|
+
readonly refreshToken: string;
|
|
12
|
+
/** Unix epoch SECONDS — past this, the id token is rejected by the registry. */
|
|
13
|
+
readonly expiresAt: number;
|
|
14
|
+
/** Username the tokens belong to (informational; for `--whoami` reuse). */
|
|
15
|
+
readonly username: string;
|
|
16
|
+
/** ISO 8601 timestamp this document was written. */
|
|
17
|
+
readonly writtenAt: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Compute the on-disk path for a registry's token cache. Pure (no
|
|
21
|
+
* IO) so callers can derive the path before existence checks.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getRegistryTokenPath(registryUrl: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Load a cached token document. Returns null when missing OR
|
|
26
|
+
* malformed — callers treat both as "need to log in".
|
|
27
|
+
*/
|
|
28
|
+
export declare function loadRegistryToken(registryUrl: string): RegistryTokenDocument | null;
|
|
29
|
+
/**
|
|
30
|
+
* Atomically write the document with mode `0o600`. write-temp +
|
|
31
|
+
* rename pattern so a crash mid-write doesn't strand a partial doc.
|
|
32
|
+
*/
|
|
33
|
+
export declare function saveRegistryToken(doc: RegistryTokenDocument): void;
|
|
34
|
+
/**
|
|
35
|
+
* Decide whether a cached document is still usable for the next
|
|
36
|
+
* request. Subtracts a 60-second safety margin so we don't race a
|
|
37
|
+
* boundary expiry that the registry would refuse.
|
|
38
|
+
*/
|
|
39
|
+
export declare function isTokenFresh(doc: RegistryTokenDocument, nowSeconds?: number): boolean;
|
|
40
|
+
//# sourceMappingURL=auth-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-cache.d.ts","sourceRoot":"","sources":["../../src/internal/auth-cache.ts"],"names":[],"mappings":"AA6BA,wBAAwB;AACxB,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,mEAAmE;IACnE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,2EAA2E;IAC3E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,gFAAgF;IAChF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,oDAAoD;IACpD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAGhE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,GAClB,qBAAqB,GAAG,IAAI,CAiB9B;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,qBAAqB,GAAG,IAAI,CA4BlE;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,qBAAqB,EAC1B,UAAU,GAAE,MAAsC,GACjD,OAAO,CAET"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-registry JWT cache for `ggui gadget publish`.
|
|
3
|
+
*
|
|
4
|
+
* Layout: `~/.ggui/auth/<registry-hostname>/token.json`. One file per
|
|
5
|
+
* registry hostname so an operator who publishes to a dev sandbox
|
|
6
|
+
* AND production doesn't paper over one session with the other. The
|
|
7
|
+
* hostname is the directory name (no path component leaks into disk
|
|
8
|
+
* names) — full URL is captured inside the document.
|
|
9
|
+
*
|
|
10
|
+
* Document version is `1`. Bumped on breaking-change shape edits;
|
|
11
|
+
* loaders that see `version > 1` refuse rather than mis-parse.
|
|
12
|
+
*
|
|
13
|
+
* **Distinct from `~/.ggui/auth.json`.** That file holds the
|
|
14
|
+
* `api.ggui.ai` device-flow session for hosted-key management. The
|
|
15
|
+
* registry tokens here are a SEPARATE auth surface (different IdP,
|
|
16
|
+
* different audience), so they live in a parallel namespace.
|
|
17
|
+
*/
|
|
18
|
+
import { URL } from 'node:url';
|
|
19
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, } from 'node:fs';
|
|
20
|
+
import { dirname, join } from 'node:path';
|
|
21
|
+
import { getConfigDir } from '../paths.js';
|
|
22
|
+
/**
|
|
23
|
+
* Compute the on-disk path for a registry's token cache. Pure (no
|
|
24
|
+
* IO) so callers can derive the path before existence checks.
|
|
25
|
+
*/
|
|
26
|
+
export function getRegistryTokenPath(registryUrl) {
|
|
27
|
+
const host = safeHostname(registryUrl);
|
|
28
|
+
return join(getConfigDir(), 'auth', host, 'token.json');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Load a cached token document. Returns null when missing OR
|
|
32
|
+
* malformed — callers treat both as "need to log in".
|
|
33
|
+
*/
|
|
34
|
+
export function loadRegistryToken(registryUrl) {
|
|
35
|
+
const path = getRegistryTokenPath(registryUrl);
|
|
36
|
+
if (!existsSync(path))
|
|
37
|
+
return null;
|
|
38
|
+
let raw;
|
|
39
|
+
try {
|
|
40
|
+
raw = readFileSync(path, 'utf8');
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
let decoded;
|
|
46
|
+
try {
|
|
47
|
+
decoded = JSON.parse(raw);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
if (!isRegistryTokenDocument(decoded))
|
|
53
|
+
return null;
|
|
54
|
+
return decoded;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Atomically write the document with mode `0o600`. write-temp +
|
|
58
|
+
* rename pattern so a crash mid-write doesn't strand a partial doc.
|
|
59
|
+
*/
|
|
60
|
+
export function saveRegistryToken(doc) {
|
|
61
|
+
const target = getRegistryTokenPath(doc.registry);
|
|
62
|
+
const dir = dirname(target);
|
|
63
|
+
if (!existsSync(dir)) {
|
|
64
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
try {
|
|
68
|
+
chmodSync(dir, 0o700);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// best-effort on Windows
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const tmp = `${target}.tmp-${process.pid}-${Date.now()}`;
|
|
75
|
+
writeFileSync(tmp, `${JSON.stringify(doc, null, 2)}\n`, {
|
|
76
|
+
encoding: 'utf8',
|
|
77
|
+
mode: 0o600,
|
|
78
|
+
});
|
|
79
|
+
try {
|
|
80
|
+
chmodSync(tmp, 0o600);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// best-effort
|
|
84
|
+
}
|
|
85
|
+
renameSync(tmp, target);
|
|
86
|
+
try {
|
|
87
|
+
chmodSync(target, 0o600);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// best-effort
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Decide whether a cached document is still usable for the next
|
|
95
|
+
* request. Subtracts a 60-second safety margin so we don't race a
|
|
96
|
+
* boundary expiry that the registry would refuse.
|
|
97
|
+
*/
|
|
98
|
+
export function isTokenFresh(doc, nowSeconds = Math.floor(Date.now() / 1000)) {
|
|
99
|
+
return doc.expiresAt - nowSeconds > 60;
|
|
100
|
+
}
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// internal
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
function isRegistryTokenDocument(x) {
|
|
105
|
+
if (typeof x !== 'object' || x === null)
|
|
106
|
+
return false;
|
|
107
|
+
const r = x;
|
|
108
|
+
return (r['version'] === 1 &&
|
|
109
|
+
typeof r['registry'] === 'string' &&
|
|
110
|
+
typeof r['idToken'] === 'string' &&
|
|
111
|
+
typeof r['accessToken'] === 'string' &&
|
|
112
|
+
typeof r['refreshToken'] === 'string' &&
|
|
113
|
+
typeof r['expiresAt'] === 'number' &&
|
|
114
|
+
typeof r['username'] === 'string' &&
|
|
115
|
+
typeof r['writtenAt'] === 'string');
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Extract a filesystem-safe hostname from a registry URL. Replaces
|
|
119
|
+
* non-alphanumeric chars with `-` so the result is always a valid
|
|
120
|
+
* directory name on every supported OS.
|
|
121
|
+
*/
|
|
122
|
+
function safeHostname(registryUrl) {
|
|
123
|
+
let host;
|
|
124
|
+
try {
|
|
125
|
+
host = new URL(registryUrl).hostname;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Caller validated earlier; if we got here with a bogus URL the
|
|
129
|
+
// cleanest thing is to refuse the path entirely.
|
|
130
|
+
throw new Error(`auth-cache: invalid registry URL: ${registryUrl}`);
|
|
131
|
+
}
|
|
132
|
+
return host.replace(/[^a-z0-9.-]/gi, '-');
|
|
133
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vendor-neutral auth strategy for marketplace CLI verbs
|
|
3
|
+
* (`ggui {gadget,blueprint} publish`).
|
|
4
|
+
*
|
|
5
|
+
* Two strategies — explicit `--auth=bearer` for self-hosted and local
|
|
6
|
+
* registries, and the (default) hosted-auth flow for the cloud
|
|
7
|
+
* registry. The default flow is implemented behind a vendor-neutral
|
|
8
|
+
* function name (`acquireHostedAuthJwt`) so this surface never names
|
|
9
|
+
* any specific identity provider; the underlying implementation in
|
|
10
|
+
* `artifact-publish` calls the cloud's hosted auth provider.
|
|
11
|
+
*
|
|
12
|
+
* The public surface (this file, its consumers, and the CLI help
|
|
13
|
+
* text) stays vendor-neutral.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { parseAuthFlags, acquireAuthToken } from './internal/auth-strategy.js';
|
|
18
|
+
*
|
|
19
|
+
* const authFlags = parseAuthFlags(args);
|
|
20
|
+
* const jwt = await acquireAuthToken({
|
|
21
|
+
* flags: authFlags,
|
|
22
|
+
* env: process.env,
|
|
23
|
+
* registryUrl,
|
|
24
|
+
* acquireHostedAuthJwt,
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Parsed `--auth=<mode>` + `--token <token>` flags. Vendor-neutral.
|
|
30
|
+
*
|
|
31
|
+
* `auth: undefined` means "use the default (hosted auth via the
|
|
32
|
+
* registry)". The only public flag value is `'bearer'`.
|
|
33
|
+
*/
|
|
34
|
+
export interface AuthFlags {
|
|
35
|
+
/** Auth mode. Only `'bearer'` is admitted as a public value; absent
|
|
36
|
+
* means "default = hosted auth via the configured registry". */
|
|
37
|
+
readonly auth?: 'bearer';
|
|
38
|
+
/** Explicit bearer token. Used only when `auth === 'bearer'`. */
|
|
39
|
+
readonly token?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Help-text fragment for the auth flags, inlined into each subcommand's
|
|
43
|
+
* help block. Vendor-neutral copy — never name the identity provider.
|
|
44
|
+
*/
|
|
45
|
+
export declare const AUTH_HELP_FRAGMENT = "Auth:\n --auth=bearer Use a bearer token (for self-hosted or local\n registries). Pair with --token <token> or set\n GGUI_REGISTRY_TOKEN in the environment.\n --token <token> Bearer token (paired with --auth=bearer).\n (default) Hosted auth via the configured registry \u2014\n no flag needed; the CLI prompts for credentials\n on first publish and caches the resulting\n session at ~/.ggui/auth/<registry-host>/token.json.\n";
|
|
46
|
+
/**
|
|
47
|
+
* Pop the auth-related flags out of an argv-like list, returning the
|
|
48
|
+
* parsed flags + the residual args (so subcommand parsers can keep
|
|
49
|
+
* their own flag loops simple). Returns `{ error }` on usage failure.
|
|
50
|
+
*
|
|
51
|
+
* Accepts both `--auth bearer` / `--auth=bearer` forms, and similarly
|
|
52
|
+
* `--token <value>` / `--token=<value>`.
|
|
53
|
+
*/
|
|
54
|
+
export declare function parseAuthFlags(args: readonly string[]): {
|
|
55
|
+
flags: AuthFlags;
|
|
56
|
+
rest: readonly string[];
|
|
57
|
+
} | {
|
|
58
|
+
error: string;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Hosted-auth callback shape. Vendor-neutral by design — the caller
|
|
62
|
+
* injects the actual implementation (which today calls the cloud
|
|
63
|
+
* registry's hosted auth vendor).
|
|
64
|
+
*
|
|
65
|
+
* Returns the bearer JWT the CLI sends as `Authorization: Bearer <jwt>`.
|
|
66
|
+
*/
|
|
67
|
+
export type HostedAuthAcquirer = (deps: {
|
|
68
|
+
readonly registryUrl: string;
|
|
69
|
+
readonly env: NodeJS.ProcessEnv;
|
|
70
|
+
readonly cwd: string;
|
|
71
|
+
}) => Promise<string>;
|
|
72
|
+
/**
|
|
73
|
+
* Acquire the auth token to send to the registry. Routes:
|
|
74
|
+
*
|
|
75
|
+
* - `flags.auth === 'bearer'` → use `flags.token` || `GGUI_REGISTRY_TOKEN`
|
|
76
|
+
* - otherwise → call `acquireHostedAuthJwt`
|
|
77
|
+
*
|
|
78
|
+
* Throws (with an operator-readable message) when bearer auth is
|
|
79
|
+
* selected but no token is supplied; the message names the env var so
|
|
80
|
+
* the operator can fix it without reading help.
|
|
81
|
+
*/
|
|
82
|
+
export declare function acquireAuthToken(deps: {
|
|
83
|
+
readonly flags: AuthFlags;
|
|
84
|
+
readonly env: NodeJS.ProcessEnv;
|
|
85
|
+
readonly cwd: string;
|
|
86
|
+
readonly registryUrl: string;
|
|
87
|
+
readonly acquireHostedAuthJwt: HostedAuthAcquirer;
|
|
88
|
+
}): Promise<string>;
|
|
89
|
+
//# sourceMappingURL=auth-strategy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-strategy.d.ts","sourceRoot":"","sources":["../../src/internal/auth-strategy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB;oEACgE;IAChE,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC;IACzB,iEAAiE;IACjE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,sjBAS9B,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,SAAS,MAAM,EAAE,GACtB;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAuDnE;AAED;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,IAAI,EAAE;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAEtB;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,oBAAoB,EAAE,kBAAkB,CAAC;CACnD,GAAG,OAAO,CAAC,MAAM,CAAC,CAsBlB"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vendor-neutral auth strategy for marketplace CLI verbs
|
|
3
|
+
* (`ggui {gadget,blueprint} publish`).
|
|
4
|
+
*
|
|
5
|
+
* Two strategies — explicit `--auth=bearer` for self-hosted and local
|
|
6
|
+
* registries, and the (default) hosted-auth flow for the cloud
|
|
7
|
+
* registry. The default flow is implemented behind a vendor-neutral
|
|
8
|
+
* function name (`acquireHostedAuthJwt`) so this surface never names
|
|
9
|
+
* any specific identity provider; the underlying implementation in
|
|
10
|
+
* `artifact-publish` calls the cloud's hosted auth provider.
|
|
11
|
+
*
|
|
12
|
+
* The public surface (this file, its consumers, and the CLI help
|
|
13
|
+
* text) stays vendor-neutral.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { parseAuthFlags, acquireAuthToken } from './internal/auth-strategy.js';
|
|
18
|
+
*
|
|
19
|
+
* const authFlags = parseAuthFlags(args);
|
|
20
|
+
* const jwt = await acquireAuthToken({
|
|
21
|
+
* flags: authFlags,
|
|
22
|
+
* env: process.env,
|
|
23
|
+
* registryUrl,
|
|
24
|
+
* acquireHostedAuthJwt,
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Help-text fragment for the auth flags, inlined into each subcommand's
|
|
30
|
+
* help block. Vendor-neutral copy — never name the identity provider.
|
|
31
|
+
*/
|
|
32
|
+
export const AUTH_HELP_FRAGMENT = `Auth:
|
|
33
|
+
--auth=bearer Use a bearer token (for self-hosted or local
|
|
34
|
+
registries). Pair with --token <token> or set
|
|
35
|
+
GGUI_REGISTRY_TOKEN in the environment.
|
|
36
|
+
--token <token> Bearer token (paired with --auth=bearer).
|
|
37
|
+
(default) Hosted auth via the configured registry —
|
|
38
|
+
no flag needed; the CLI prompts for credentials
|
|
39
|
+
on first publish and caches the resulting
|
|
40
|
+
session at ~/.ggui/auth/<registry-host>/token.json.
|
|
41
|
+
`;
|
|
42
|
+
/**
|
|
43
|
+
* Pop the auth-related flags out of an argv-like list, returning the
|
|
44
|
+
* parsed flags + the residual args (so subcommand parsers can keep
|
|
45
|
+
* their own flag loops simple). Returns `{ error }` on usage failure.
|
|
46
|
+
*
|
|
47
|
+
* Accepts both `--auth bearer` / `--auth=bearer` forms, and similarly
|
|
48
|
+
* `--token <value>` / `--token=<value>`.
|
|
49
|
+
*/
|
|
50
|
+
export function parseAuthFlags(args) {
|
|
51
|
+
let auth;
|
|
52
|
+
let token;
|
|
53
|
+
const rest = [];
|
|
54
|
+
for (let i = 0; i < args.length; i++) {
|
|
55
|
+
const arg = args[i];
|
|
56
|
+
if (arg === '--auth' || arg.startsWith('--auth=')) {
|
|
57
|
+
const eq = arg.indexOf('=');
|
|
58
|
+
let value;
|
|
59
|
+
if (eq !== -1) {
|
|
60
|
+
value = arg.slice(eq + 1);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
const next = args[i + 1];
|
|
64
|
+
if (typeof next !== 'string' || next.length === 0) {
|
|
65
|
+
return { error: '--auth requires a value' };
|
|
66
|
+
}
|
|
67
|
+
value = next;
|
|
68
|
+
i += 1;
|
|
69
|
+
}
|
|
70
|
+
if (value !== 'bearer') {
|
|
71
|
+
return { error: `--auth must be "bearer" (got "${value}")` };
|
|
72
|
+
}
|
|
73
|
+
auth = value;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (arg === '--token' || arg.startsWith('--token=')) {
|
|
77
|
+
const eq = arg.indexOf('=');
|
|
78
|
+
let value;
|
|
79
|
+
if (eq !== -1) {
|
|
80
|
+
value = arg.slice(eq + 1);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
const next = args[i + 1];
|
|
84
|
+
if (typeof next !== 'string' || next.length === 0) {
|
|
85
|
+
return { error: '--token requires a value' };
|
|
86
|
+
}
|
|
87
|
+
value = next;
|
|
88
|
+
i += 1;
|
|
89
|
+
}
|
|
90
|
+
token = value;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
rest.push(arg);
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
flags: {
|
|
97
|
+
...(auth !== undefined ? { auth } : {}),
|
|
98
|
+
...(token !== undefined ? { token } : {}),
|
|
99
|
+
},
|
|
100
|
+
rest,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Acquire the auth token to send to the registry. Routes:
|
|
105
|
+
*
|
|
106
|
+
* - `flags.auth === 'bearer'` → use `flags.token` || `GGUI_REGISTRY_TOKEN`
|
|
107
|
+
* - otherwise → call `acquireHostedAuthJwt`
|
|
108
|
+
*
|
|
109
|
+
* Throws (with an operator-readable message) when bearer auth is
|
|
110
|
+
* selected but no token is supplied; the message names the env var so
|
|
111
|
+
* the operator can fix it without reading help.
|
|
112
|
+
*/
|
|
113
|
+
export async function acquireAuthToken(deps) {
|
|
114
|
+
if (deps.flags.auth === 'bearer') {
|
|
115
|
+
const fromFlag = deps.flags.token;
|
|
116
|
+
const fromEnv = deps.env['GGUI_REGISTRY_TOKEN'];
|
|
117
|
+
const token = typeof fromFlag === 'string' && fromFlag.length > 0
|
|
118
|
+
? fromFlag
|
|
119
|
+
: typeof fromEnv === 'string' && fromEnv.length > 0
|
|
120
|
+
? fromEnv
|
|
121
|
+
: undefined;
|
|
122
|
+
if (token === undefined) {
|
|
123
|
+
throw new Error('bearer auth requires --token <value> or GGUI_REGISTRY_TOKEN');
|
|
124
|
+
}
|
|
125
|
+
return token;
|
|
126
|
+
}
|
|
127
|
+
return deps.acquireHostedAuthJwt({
|
|
128
|
+
registryUrl: deps.registryUrl,
|
|
129
|
+
env: deps.env,
|
|
130
|
+
cwd: deps.cwd,
|
|
131
|
+
});
|
|
132
|
+
}
|