@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.
Files changed (117) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +96 -0
  3. package/dist/agent-resolution.d.ts +41 -0
  4. package/dist/agent-resolution.d.ts.map +1 -0
  5. package/dist/agent-resolution.js +80 -0
  6. package/dist/api-client.d.ts +59 -0
  7. package/dist/api-client.d.ts.map +1 -0
  8. package/dist/api-client.js +145 -0
  9. package/dist/auth-keys.d.ts +10 -0
  10. package/dist/auth-keys.d.ts.map +1 -0
  11. package/dist/auth-keys.js +382 -0
  12. package/dist/auth-login.d.ts +11 -0
  13. package/dist/auth-login.d.ts.map +1 -0
  14. package/dist/auth-login.js +231 -0
  15. package/dist/auth-logout.d.ts +3 -0
  16. package/dist/auth-logout.d.ts.map +1 -0
  17. package/dist/auth-logout.js +32 -0
  18. package/dist/auth-store.d.ts +67 -0
  19. package/dist/auth-store.d.ts.map +1 -0
  20. package/dist/auth-store.js +150 -0
  21. package/dist/auth-whoami.d.ts +3 -0
  22. package/dist/auth-whoami.d.ts.map +1 -0
  23. package/dist/auth-whoami.js +52 -0
  24. package/dist/blueprint-command.d.ts +3 -0
  25. package/dist/blueprint-command.d.ts.map +1 -0
  26. package/dist/blueprint-command.js +203 -0
  27. package/dist/blueprint-create.d.ts +71 -0
  28. package/dist/blueprint-create.d.ts.map +1 -0
  29. package/dist/blueprint-create.js +377 -0
  30. package/dist/byok-resolver.d.ts +141 -0
  31. package/dist/byok-resolver.d.ts.map +1 -0
  32. package/dist/byok-resolver.js +89 -0
  33. package/dist/cli.d.ts +3 -0
  34. package/dist/cli.d.ts.map +1 -0
  35. package/dist/cli.js +1066 -0
  36. package/dist/dev-command.d.ts +169 -0
  37. package/dist/dev-command.d.ts.map +1 -0
  38. package/dist/dev-command.js +222 -0
  39. package/dist/gadget-command.d.ts +3 -0
  40. package/dist/gadget-command.d.ts.map +1 -0
  41. package/dist/gadget-command.js +181 -0
  42. package/dist/gadget-create.d.ts +106 -0
  43. package/dist/gadget-create.d.ts.map +1 -0
  44. package/dist/gadget-create.js +492 -0
  45. package/dist/generation-probe.d.ts +135 -0
  46. package/dist/generation-probe.d.ts.map +1 -0
  47. package/dist/generation-probe.js +132 -0
  48. package/dist/internal/artifact-install.d.ts +150 -0
  49. package/dist/internal/artifact-install.d.ts.map +1 -0
  50. package/dist/internal/artifact-install.js +1019 -0
  51. package/dist/internal/artifact-publish.d.ts +204 -0
  52. package/dist/internal/artifact-publish.d.ts.map +1 -0
  53. package/dist/internal/artifact-publish.js +1141 -0
  54. package/dist/internal/artifact-search.d.ts +155 -0
  55. package/dist/internal/artifact-search.d.ts.map +1 -0
  56. package/dist/internal/artifact-search.js +539 -0
  57. package/dist/internal/artifact-uninstall.d.ts +30 -0
  58. package/dist/internal/artifact-uninstall.d.ts.map +1 -0
  59. package/dist/internal/artifact-uninstall.js +205 -0
  60. package/dist/internal/auth-cache.d.ts +40 -0
  61. package/dist/internal/auth-cache.d.ts.map +1 -0
  62. package/dist/internal/auth-cache.js +133 -0
  63. package/dist/internal/auth-strategy.d.ts +89 -0
  64. package/dist/internal/auth-strategy.d.ts.map +1 -0
  65. package/dist/internal/auth-strategy.js +132 -0
  66. package/dist/internal/ggui-json.d.ts +53 -0
  67. package/dist/internal/ggui-json.d.ts.map +1 -0
  68. package/dist/internal/ggui-json.js +92 -0
  69. package/dist/internal/key-store.d.ts +43 -0
  70. package/dist/internal/key-store.d.ts.map +1 -0
  71. package/dist/internal/key-store.js +128 -0
  72. package/dist/internal/oidc-token.d.ts +52 -0
  73. package/dist/internal/oidc-token.d.ts.map +1 -0
  74. package/dist/internal/oidc-token.js +351 -0
  75. package/dist/internal/register-author-key.d.ts +44 -0
  76. package/dist/internal/register-author-key.d.ts.map +1 -0
  77. package/dist/internal/register-author-key.js +212 -0
  78. package/dist/local-keys.d.ts +47 -0
  79. package/dist/local-keys.d.ts.map +1 -0
  80. package/dist/local-keys.js +115 -0
  81. package/dist/mcp-backend.d.ts +356 -0
  82. package/dist/mcp-backend.d.ts.map +1 -0
  83. package/dist/mcp-backend.js +668 -0
  84. package/dist/paths.d.ts +77 -0
  85. package/dist/paths.d.ts.map +1 -0
  86. package/dist/paths.js +119 -0
  87. package/dist/persistent-secrets.d.ts +28 -0
  88. package/dist/persistent-secrets.d.ts.map +1 -0
  89. package/dist/persistent-secrets.js +94 -0
  90. package/dist/persistent-short-code-index.d.ts +14 -0
  91. package/dist/persistent-short-code-index.d.ts.map +1 -0
  92. package/dist/persistent-short-code-index.js +39 -0
  93. package/dist/persistent-stores.d.ts +12 -0
  94. package/dist/persistent-stores.d.ts.map +1 -0
  95. package/dist/persistent-stores.js +42 -0
  96. package/dist/serve-command.d.ts +500 -0
  97. package/dist/serve-command.d.ts.map +1 -0
  98. package/dist/serve-command.js +616 -0
  99. package/dist/serve-fallback.d.ts +64 -0
  100. package/dist/serve-fallback.d.ts.map +1 -0
  101. package/dist/serve-fallback.js +122 -0
  102. package/dist/spawn-cloudflared.d.ts +61 -0
  103. package/dist/spawn-cloudflared.d.ts.map +1 -0
  104. package/dist/spawn-cloudflared.js +167 -0
  105. package/dist/theme-command.d.ts +4 -0
  106. package/dist/theme-command.d.ts.map +1 -0
  107. package/dist/theme-command.js +154 -0
  108. package/dist/theme-file-uploader.d.ts +8 -0
  109. package/dist/theme-file-uploader.d.ts.map +1 -0
  110. package/dist/theme-file-uploader.js +58 -0
  111. package/dist/theme-writer.d.ts +8 -0
  112. package/dist/theme-writer.d.ts.map +1 -0
  113. package/dist/theme-writer.js +103 -0
  114. package/dist/tunnel-provider.d.ts +273 -0
  115. package/dist/tunnel-provider.d.ts.map +1 -0
  116. package/dist/tunnel-provider.js +142 -0
  117. 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
+ }