@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.
Files changed (80) hide show
  1. package/README.md +38 -7
  2. package/dist/api-client.d.ts +42 -1
  3. package/dist/api-client.d.ts.map +1 -1
  4. package/dist/api-client.js +30 -2
  5. package/dist/auth-keys.d.ts +1 -1
  6. package/dist/auth-keys.d.ts.map +1 -1
  7. package/dist/auth-keys.js +22 -7
  8. package/dist/auth-store.d.ts +1 -1
  9. package/dist/auth-store.d.ts.map +1 -1
  10. package/dist/cli.js +112 -30
  11. package/dist/config-push.d.ts +79 -0
  12. package/dist/config-push.d.ts.map +1 -0
  13. package/dist/config-push.js +301 -0
  14. package/dist/create-app-command.d.ts +17 -0
  15. package/dist/create-app-command.d.ts.map +1 -0
  16. package/dist/create-app-command.js +58 -0
  17. package/dist/deploy-command.d.ts +67 -0
  18. package/dist/deploy-command.d.ts.map +1 -0
  19. package/dist/deploy-command.js +386 -0
  20. package/dist/dev-command.d.ts +5 -1
  21. package/dist/dev-command.d.ts.map +1 -1
  22. package/dist/dev-command.js +31 -1
  23. package/dist/export-pool-command.d.ts +25 -0
  24. package/dist/export-pool-command.d.ts.map +1 -0
  25. package/dist/export-pool-command.js +116 -0
  26. package/dist/filesystem-seed-pool-source.d.ts +13 -0
  27. package/dist/filesystem-seed-pool-source.d.ts.map +1 -0
  28. package/dist/filesystem-seed-pool-source.js +22 -0
  29. package/dist/generation-probe.d.ts +53 -7
  30. package/dist/generation-probe.d.ts.map +1 -1
  31. package/dist/generation-probe.js +37 -1
  32. package/dist/internal/artifact-install.d.ts +4 -2
  33. package/dist/internal/artifact-install.d.ts.map +1 -1
  34. package/dist/internal/artifact-install.js +11 -10
  35. package/dist/internal/artifact-publish.d.ts +34 -31
  36. package/dist/internal/artifact-publish.d.ts.map +1 -1
  37. package/dist/internal/artifact-publish.js +133 -241
  38. package/dist/internal/artifact-search.d.ts +2 -28
  39. package/dist/internal/artifact-search.d.ts.map +1 -1
  40. package/dist/internal/artifact-search.js +5 -4
  41. package/dist/internal/auth-strategy.d.ts +21 -31
  42. package/dist/internal/auth-strategy.d.ts.map +1 -1
  43. package/dist/internal/auth-strategy.js +15 -23
  44. package/dist/internal/register-author-key.d.ts +7 -1
  45. package/dist/internal/register-author-key.d.ts.map +1 -1
  46. package/dist/internal/register-author-key.js +56 -19
  47. package/dist/mcp-backend.d.ts +25 -25
  48. package/dist/mcp-backend.d.ts.map +1 -1
  49. package/dist/mcp-backend.js +65 -61
  50. package/dist/paths.d.ts +3 -3
  51. package/dist/paths.js +3 -3
  52. package/dist/persistent-secrets.js +1 -1
  53. package/dist/persistent-stores.d.ts +2 -2
  54. package/dist/persistent-stores.d.ts.map +1 -1
  55. package/dist/persistent-stores.js +5 -5
  56. package/dist/pool-artifact.d.ts +19 -0
  57. package/dist/pool-artifact.d.ts.map +1 -0
  58. package/dist/pool-artifact.js +59 -0
  59. package/dist/provider-key-command.d.ts +51 -0
  60. package/dist/provider-key-command.d.ts.map +1 -0
  61. package/dist/provider-key-command.js +222 -0
  62. package/dist/push-command.d.ts +47 -0
  63. package/dist/push-command.d.ts.map +1 -0
  64. package/dist/push-command.js +230 -0
  65. package/dist/serve-command.d.ts +23 -19
  66. package/dist/serve-command.d.ts.map +1 -1
  67. package/dist/serve-command.js +13 -26
  68. package/dist/theme-command.js +0 -2
  69. package/dist/tunnel-provider.d.ts +8 -7
  70. package/dist/tunnel-provider.d.ts.map +1 -1
  71. package/package.json +25 -21
  72. package/dist/internal/auth-cache.d.ts +0 -40
  73. package/dist/internal/auth-cache.d.ts.map +0 -1
  74. package/dist/internal/auth-cache.js +0 -133
  75. package/dist/persistent-short-code-index.d.ts +0 -14
  76. package/dist/persistent-short-code-index.d.ts.map +0 -1
  77. package/dist/persistent-short-code-index.js +0 -39
  78. package/dist/spawn-cloudflared.d.ts +0 -61
  79. package/dist/spawn-cloudflared.d.ts.map +0 -1
  80. package/dist/spawn-cloudflared.js +0 -167
@@ -0,0 +1,79 @@
1
+ import { type GadgetDescriptor, type AppTheme } from '@ggui-ai/protocol';
2
+ /**
3
+ * Extract and validate `app.gadgets` from a decoded ggui.json value.
4
+ *
5
+ * Returns `[]` when the field is absent.
6
+ * Throws with a descriptive message when any descriptor is malformed
7
+ * (schema parse fails).
8
+ */
9
+ export declare function readGadgetsFromGguiJson(gguiJson: unknown): GadgetDescriptor[];
10
+ /**
11
+ * Extract and validate `app.publicEnv` from a decoded ggui.json value.
12
+ *
13
+ * Returns `{}` when the field is absent.
14
+ * Throws with a descriptive message when any key/value is invalid.
15
+ * Keys must match `PUBLIC_ENV_APP_KEY_RE` (`GGUI_PUBLIC_APP_<NAME>`).
16
+ */
17
+ export declare function readPublicEnvFromGguiJson(gguiJson: unknown): Record<string, string>;
18
+ /**
19
+ * Extract and validate `generation` from a decoded ggui.json value.
20
+ *
21
+ * Reads the RAW model string (not a resolved `LlmRoute`) — the cloud
22
+ * re-validates via `parseAnyLlmRoute` server-side; the CLI just forwards.
23
+ *
24
+ * Returns `{ model, keySource }` when `generation.model` is present.
25
+ * Returns `undefined` when the generation block is absent or has no model.
26
+ * Throws when the generation block is structurally invalid.
27
+ */
28
+ export declare function readGenerationFromGguiJson(gguiJson: unknown): {
29
+ model: string;
30
+ keySource: 'own' | 'managed';
31
+ } | undefined;
32
+ /**
33
+ * Resolve the developer's local `ggui.json#theme` into a concrete,
34
+ * injection-safe {@link AppTheme} ready to PATCH to the cloud.
35
+ *
36
+ * Returns `undefined` when no `theme` field is declared (deploy leaves the
37
+ * app's theme untouched).
38
+ *
39
+ * When a theme IS declared we parse the FULL manifest (via `parseGguiJson`)
40
+ * — `loadTheme` requires the typed `GguiJsonV1` shape and resolves relative
41
+ * `theme.file` paths against `projectRoot`, so a partial slice will not do.
42
+ *
43
+ * Throws with a clear, local message when:
44
+ * - the manifest is structurally invalid (so deploy fails before the wire);
45
+ * - the theme cannot be loaded (e.g. an unregistered preset id);
46
+ * - the resolved overlay fails `appThemeSchema` — the SAME validator the
47
+ * backend enforces, so a deploy fails loud LOCALLY rather than with a
48
+ * confusing 422.
49
+ */
50
+ export declare function readThemeFromGguiJson(projectRoot: string, gguiJson: unknown): AppTheme | undefined;
51
+ /**
52
+ * Assert that none of the gadget bundle references point at a loopback host.
53
+ *
54
+ * `LOOPBACK_HOST_RE` matches bare hosts (no scheme, optional `:port`):
55
+ * - `localhost` / `localhost:3000`
56
+ * - `127.0.0.1` / `127.0.0.1:8080`
57
+ * - `0.0.0.0` / `0.0.0.0:4321`
58
+ *
59
+ * Resolution order for "bundle host":
60
+ * 1. `bundleHost` (bare hostname, already scheme-free)
61
+ * 2. `bundleUrl` parsed via `new URL()` → `.host` (includes port)
62
+ * 3. Neither present → gadget has no cloud bundle reference; passes.
63
+ *
64
+ * Throws an `Error` with a remediation hint when a loopback is detected.
65
+ */
66
+ export declare function assertGadgetBundlesReachable(gadgets: GadgetDescriptor[]): void;
67
+ /**
68
+ * Find + read `ggui.json`, validate gadgets + publicEnv, guard against
69
+ * loopback bundles, then PATCH the app config via the REST API.
70
+ *
71
+ * @param appId - The ggui app ID to patch.
72
+ * @param startDir - Optional start directory for `findGguiJson` (defaults to
73
+ * `process.cwd()`). Primarily used for testing.
74
+ * @returns 0 on success, 1 on any error (malformed ggui.json, loopback bundle,
75
+ * network failure). Errors are written to stderr; success summary to
76
+ * stdout.
77
+ */
78
+ export declare function runConfigPushStep(appId: string, startDir?: string): Promise<number>;
79
+ //# sourceMappingURL=config-push.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-push.d.ts","sourceRoot":"","sources":["../src/config-push.ts"],"names":[],"mappings":"AAUA,OAAO,EAKL,KAAK,gBAAgB,EACrB,KAAK,QAAQ,EACd,MAAM,mBAAmB,CAAC;AAU3B;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,OAAO,GAAG,gBAAgB,EAAE,CAqB7E;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAmBnF;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,OAAO,GAChB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,KAAK,GAAG,SAAS,CAAA;CAAE,GAAG,SAAS,CAkB7D;AA4BD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,OAAO,GAChB,QAAQ,GAAG,SAAS,CA4CtB;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,IAAI,CAiB9E;AAMD;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,QAAQ,GAAE,MAAsB,GAC/B,OAAO,CAAC,MAAM,CAAC,CA8FjB"}
@@ -0,0 +1,301 @@
1
+ /**
2
+ * `config-push` — read `ggui.json#app.{gadgets,publicEnv}` and
3
+ * `ggui.json#generation`, guard against loopback bundle URLs, and PATCH the
4
+ * cloud app config via the REST API.
5
+ *
6
+ * Consumed by `ggui deploy` (M3.3) — wiring into that command is a separate
7
+ * task. This module is the pure logic layer: readers + guard + network call.
8
+ */
9
+ import process from 'node:process';
10
+ import { dirname } from 'node:path';
11
+ import { strictGadgetDescriptorSchema, appPublicEnvSchema, appThemeSchema, LOOPBACK_HOST_RE, } from '@ggui-ai/protocol';
12
+ import { loadTheme, parseGguiJson } from '@ggui-ai/project-config/node';
13
+ import { z } from 'zod';
14
+ import { patchAppConfig } from './api-client.js';
15
+ import { findGguiJson, readGguiJson } from './internal/ggui-json.js';
16
+ // ─────────────────────────────────────────────────────────────────────────────
17
+ // Readers
18
+ // ─────────────────────────────────────────────────────────────────────────────
19
+ /**
20
+ * Extract and validate `app.gadgets` from a decoded ggui.json value.
21
+ *
22
+ * Returns `[]` when the field is absent.
23
+ * Throws with a descriptive message when any descriptor is malformed
24
+ * (schema parse fails).
25
+ */
26
+ export function readGadgetsFromGguiJson(gguiJson) {
27
+ // Navigate to `app.gadgets` via a nested schema rather than casting +
28
+ // index access. `z.object` is non-strict by default, so ggui.json's other
29
+ // fields (schema / protocol / generation / app.publicEnv / …) ride through
30
+ // as allowed-and-stripped extras — only `app.gadgets` is validated here.
31
+ const schema = z.object({
32
+ app: z
33
+ .object({
34
+ gadgets: z.array(strictGadgetDescriptorSchema).optional(),
35
+ })
36
+ .optional(),
37
+ });
38
+ const result = schema.safeParse(gguiJson);
39
+ if (!result.success) {
40
+ throw new Error(`ggui.json: app.gadgets is invalid — ${result.error.issues[0]?.message ?? 'malformed descriptor'}`);
41
+ }
42
+ // result.data.app?.gadgets is GadgetDescriptor[] — strictGadgetDescriptorSchema
43
+ // parses to the same shape as GadgetDescriptor. Spread to a fresh mutable array.
44
+ return [...(result.data.app?.gadgets ?? [])];
45
+ }
46
+ /**
47
+ * Extract and validate `app.publicEnv` from a decoded ggui.json value.
48
+ *
49
+ * Returns `{}` when the field is absent.
50
+ * Throws with a descriptive message when any key/value is invalid.
51
+ * Keys must match `PUBLIC_ENV_APP_KEY_RE` (`GGUI_PUBLIC_APP_<NAME>`).
52
+ */
53
+ export function readPublicEnvFromGguiJson(gguiJson) {
54
+ // Navigate to `app.publicEnv` via a nested schema (same posture as
55
+ // readGadgetsFromGguiJson) — other ggui.json fields ride through as
56
+ // allowed-and-stripped extras; only `app.publicEnv` is validated here.
57
+ const schema = z.object({
58
+ app: z
59
+ .object({
60
+ publicEnv: appPublicEnvSchema.optional(),
61
+ })
62
+ .optional(),
63
+ });
64
+ const result = schema.safeParse(gguiJson);
65
+ if (!result.success) {
66
+ throw new Error(`ggui.json: app.publicEnv is invalid — keys must match GGUI_PUBLIC_APP_<NAME>: ${result.error.issues[0]?.message ?? 'invalid entry'}`);
67
+ }
68
+ // appPublicEnvSchema returns a readonly record; spread to a fresh mutable one.
69
+ return { ...(result.data.app?.publicEnv ?? {}) };
70
+ }
71
+ /**
72
+ * Extract and validate `generation` from a decoded ggui.json value.
73
+ *
74
+ * Reads the RAW model string (not a resolved `LlmRoute`) — the cloud
75
+ * re-validates via `parseAnyLlmRoute` server-side; the CLI just forwards.
76
+ *
77
+ * Returns `{ model, keySource }` when `generation.model` is present.
78
+ * Returns `undefined` when the generation block is absent or has no model.
79
+ * Throws when the generation block is structurally invalid.
80
+ */
81
+ export function readGenerationFromGguiJson(gguiJson) {
82
+ const schema = z.object({
83
+ generation: z
84
+ .object({
85
+ model: z.string().min(1).optional(),
86
+ keySource: z.enum(['own', 'managed']).optional(),
87
+ })
88
+ .optional(),
89
+ });
90
+ const result = schema.safeParse(gguiJson);
91
+ if (!result.success) {
92
+ throw new Error(`ggui.json: generation is invalid — ${result.error.issues[0]?.message ?? 'malformed generation block'}`);
93
+ }
94
+ const model = result.data.generation?.model;
95
+ if (!model)
96
+ return undefined;
97
+ return { model, keySource: result.data.generation?.keySource ?? 'managed' };
98
+ }
99
+ /**
100
+ * Parse a generated `:root { --ggui-x: val; … }` block into a flat
101
+ * `{ '--ggui-x': 'val' }` map.
102
+ *
103
+ * The block is machine-generated by `@ggui-ai/design`'s theme parser
104
+ * (one `--ggui-*: value;` declaration per line, wrapped in a `:root {…}`
105
+ * selector), so a line-based parse is deterministic:
106
+ * - split on newlines;
107
+ * - on each line, split at the FIRST `:` (custom-property names never
108
+ * contain `:`, so the key is unambiguous even when the value does);
109
+ * - keep only `--ggui-*`-prefixed keys with a non-empty value. The
110
+ * `:root {` and `}` wrapper lines have no such key and drop out.
111
+ */
112
+ function parseCssVarBlock(css) {
113
+ const out = {};
114
+ for (const line of css.split('\n')) {
115
+ const i = line.indexOf(':');
116
+ if (i === -1)
117
+ continue;
118
+ const key = line.slice(0, i).trim();
119
+ let val = line.slice(i + 1).trim();
120
+ if (val.endsWith(';'))
121
+ val = val.slice(0, -1).trim();
122
+ if (key.startsWith('--ggui-') && val.length > 0)
123
+ out[key] = val;
124
+ }
125
+ return out;
126
+ }
127
+ /**
128
+ * Resolve the developer's local `ggui.json#theme` into a concrete,
129
+ * injection-safe {@link AppTheme} ready to PATCH to the cloud.
130
+ *
131
+ * Returns `undefined` when no `theme` field is declared (deploy leaves the
132
+ * app's theme untouched).
133
+ *
134
+ * When a theme IS declared we parse the FULL manifest (via `parseGguiJson`)
135
+ * — `loadTheme` requires the typed `GguiJsonV1` shape and resolves relative
136
+ * `theme.file` paths against `projectRoot`, so a partial slice will not do.
137
+ *
138
+ * Throws with a clear, local message when:
139
+ * - the manifest is structurally invalid (so deploy fails before the wire);
140
+ * - the theme cannot be loaded (e.g. an unregistered preset id);
141
+ * - the resolved overlay fails `appThemeSchema` — the SAME validator the
142
+ * backend enforces, so a deploy fails loud LOCALLY rather than with a
143
+ * confusing 422.
144
+ */
145
+ export function readThemeFromGguiJson(projectRoot, gguiJson) {
146
+ // Cheap pre-check on the raw value: no `theme` field → nothing to push,
147
+ // and we avoid forcing a full-manifest parse on theme-less deploys.
148
+ const presence = z
149
+ .object({ theme: z.unknown().optional() })
150
+ .safeParse(gguiJson);
151
+ if (!presence.success || presence.data.theme === undefined)
152
+ return undefined;
153
+ // A theme is declared — `loadTheme` needs the fully-typed manifest.
154
+ let manifest;
155
+ try {
156
+ manifest = parseGguiJson(gguiJson);
157
+ }
158
+ catch (err) {
159
+ throw new Error(`ggui.json: cannot resolve theme — manifest is invalid: ${err instanceof Error ? err.message : String(err)}`);
160
+ }
161
+ const result = loadTheme({ projectRoot, manifest });
162
+ if (!result.ok) {
163
+ throw new Error(`ggui.json: ${result.issue.message}`);
164
+ }
165
+ const loaded = result.theme;
166
+ const theme = {
167
+ mode: loaded.mode,
168
+ cssVariables: parseCssVarBlock(loaded.cssVariables),
169
+ ...(loaded.source === 'preset' ? { name: loaded.preset } : {}),
170
+ };
171
+ // Defense + clear local error: the resolved overlay must satisfy the same
172
+ // schema the backend enforces, so a deploy fails loud LOCALLY (not with a
173
+ // confusing 422) if a token ever produces an unsafe value.
174
+ const parsed = appThemeSchema.safeParse(theme);
175
+ if (!parsed.success) {
176
+ throw new Error(`ggui.json: resolved theme is invalid — ${parsed.error.issues[0]?.message ?? 'unexpected css value'}`);
177
+ }
178
+ return parsed.data;
179
+ }
180
+ // ─────────────────────────────────────────────────────────────────────────────
181
+ // Loopback guard
182
+ // ─────────────────────────────────────────────────────────────────────────────
183
+ /**
184
+ * Assert that none of the gadget bundle references point at a loopback host.
185
+ *
186
+ * `LOOPBACK_HOST_RE` matches bare hosts (no scheme, optional `:port`):
187
+ * - `localhost` / `localhost:3000`
188
+ * - `127.0.0.1` / `127.0.0.1:8080`
189
+ * - `0.0.0.0` / `0.0.0.0:4321`
190
+ *
191
+ * Resolution order for "bundle host":
192
+ * 1. `bundleHost` (bare hostname, already scheme-free)
193
+ * 2. `bundleUrl` parsed via `new URL()` → `.host` (includes port)
194
+ * 3. Neither present → gadget has no cloud bundle reference; passes.
195
+ *
196
+ * Throws an `Error` with a remediation hint when a loopback is detected.
197
+ */
198
+ export function assertGadgetBundlesReachable(gadgets) {
199
+ for (const g of gadgets) {
200
+ let host;
201
+ if (g.bundleHost !== undefined) {
202
+ host = g.bundleHost;
203
+ }
204
+ else if (g.bundleUrl !== undefined) {
205
+ host = new URL(g.bundleUrl).host;
206
+ }
207
+ if (host !== undefined && LOOPBACK_HOST_RE.test(host)) {
208
+ throw new Error(`gadget ${g.package}'s bundle is local-only (${host}) — publish it to a ` +
209
+ `cloud-reachable registry (run \`ggui gadget publish\`) before deploy.`);
210
+ }
211
+ }
212
+ }
213
+ // ─────────────────────────────────────────────────────────────────────────────
214
+ // Orchestrator
215
+ // ─────────────────────────────────────────────────────────────────────────────
216
+ /**
217
+ * Find + read `ggui.json`, validate gadgets + publicEnv, guard against
218
+ * loopback bundles, then PATCH the app config via the REST API.
219
+ *
220
+ * @param appId - The ggui app ID to patch.
221
+ * @param startDir - Optional start directory for `findGguiJson` (defaults to
222
+ * `process.cwd()`). Primarily used for testing.
223
+ * @returns 0 on success, 1 on any error (malformed ggui.json, loopback bundle,
224
+ * network failure). Errors are written to stderr; success summary to
225
+ * stdout.
226
+ */
227
+ export async function runConfigPushStep(appId, startDir = process.cwd()) {
228
+ const gguiJsonPath = findGguiJson(startDir);
229
+ if (!gguiJsonPath) {
230
+ process.stderr.write('ggui deploy: could not find ggui.json (looked up to 8 parent directories).\n');
231
+ return 1;
232
+ }
233
+ const readResult = readGguiJson(gguiJsonPath);
234
+ if ('error' in readResult) {
235
+ process.stderr.write(`ggui deploy: ${readResult.error}\n`);
236
+ return 1;
237
+ }
238
+ let gadgets;
239
+ let publicEnv;
240
+ let generation;
241
+ let theme;
242
+ try {
243
+ gadgets = readGadgetsFromGguiJson(readResult.value);
244
+ }
245
+ catch (err) {
246
+ process.stderr.write(`ggui deploy: ${err instanceof Error ? err.message : String(err)}\n`);
247
+ return 1;
248
+ }
249
+ try {
250
+ publicEnv = readPublicEnvFromGguiJson(readResult.value);
251
+ }
252
+ catch (err) {
253
+ process.stderr.write(`ggui deploy: ${err instanceof Error ? err.message : String(err)}\n`);
254
+ return 1;
255
+ }
256
+ try {
257
+ generation = readGenerationFromGguiJson(readResult.value);
258
+ }
259
+ catch (err) {
260
+ process.stderr.write(`ggui deploy: ${err instanceof Error ? err.message : String(err)}\n`);
261
+ return 1;
262
+ }
263
+ try {
264
+ // `loadTheme` resolves relative `theme.file` paths against the directory
265
+ // containing ggui.json — the same root convention `storage.*.path` uses.
266
+ theme = readThemeFromGguiJson(dirname(gguiJsonPath), readResult.value);
267
+ }
268
+ catch (err) {
269
+ process.stderr.write(`ggui deploy: ${err instanceof Error ? err.message : String(err)}\n`);
270
+ return 1;
271
+ }
272
+ try {
273
+ assertGadgetBundlesReachable(gadgets);
274
+ }
275
+ catch (err) {
276
+ process.stderr.write(`ggui deploy: ${err instanceof Error ? err.message : String(err)}\n`);
277
+ return 1;
278
+ }
279
+ try {
280
+ await patchAppConfig(appId, {
281
+ gadgets,
282
+ ...(Object.keys(publicEnv).length > 0 ? { publicEnv } : {}),
283
+ ...(generation !== undefined ? { generation } : {}),
284
+ ...(theme !== undefined ? { theme } : {}),
285
+ });
286
+ }
287
+ catch (err) {
288
+ process.stderr.write(`ggui deploy: config push failed — ${err instanceof Error ? err.message : String(err)}\n`);
289
+ return 1;
290
+ }
291
+ const gadgetLine = gadgets.length > 0
292
+ ? `pushed ${gadgets.length} gadget extension(s)`
293
+ : 'no gadget extensions — cleared (stdlib still available)';
294
+ const envLine = Object.keys(publicEnv).length > 0
295
+ ? ` + ${Object.keys(publicEnv).length} publicEnv key(s)`
296
+ : '';
297
+ const generationLine = generation !== undefined ? ` + model ${generation.model}` : '';
298
+ const themeLine = theme !== undefined ? ` + theme ${theme.name ?? theme.mode}` : '';
299
+ process.stdout.write(` ${gadgetLine}${envLine}${generationLine}${themeLine}\n`);
300
+ return 0;
301
+ }
@@ -0,0 +1,17 @@
1
+ export interface CreateAppFlags {
2
+ name?: string;
3
+ error?: string;
4
+ }
5
+ /**
6
+ * Parse the args that follow `ggui create`. The first element must be
7
+ * the literal string `'app'`; anything else is an error. Remaining
8
+ * elements are flag pairs (`--name <value>`).
9
+ */
10
+ export declare function parseCreateAppFlags(args: readonly string[]): CreateAppFlags;
11
+ /**
12
+ * Run `ggui create app [--name <displayName>]`.
13
+ *
14
+ * Returns 0 on success, 1 on API / I/O error, 2 on usage error.
15
+ */
16
+ export declare function runCreateCommand(args: readonly string[]): Promise<number>;
17
+ //# sourceMappingURL=create-app-command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-app-command.d.ts","sourceRoot":"","sources":["../src/create-app-command.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,cAAc,CAc3E;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,SAAS,MAAM,EAAE,GACtB,OAAO,CAAC,MAAM,CAAC,CAuBjB"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * `ggui create app` — create a new app in the ggui.ai cloud and write
3
+ * the returned appId back into the local ggui.json (if one is found).
4
+ *
5
+ * Calls `POST /v1/apps` with an optional display name and prints the
6
+ * created app's id, connect URL, and default flag. When a ggui.json is
7
+ * found in the working-directory ancestor chain, the command updates
8
+ * (or adds) the `appId` field so subsequent `ggui deploy` invocations
9
+ * can pick it up automatically.
10
+ */
11
+ import { createApp } from './api-client.js';
12
+ import { findGguiJson, readGguiJson, writeGguiJson, } from './internal/ggui-json.js';
13
+ /**
14
+ * Parse the args that follow `ggui create`. The first element must be
15
+ * the literal string `'app'`; anything else is an error. Remaining
16
+ * elements are flag pairs (`--name <value>`).
17
+ */
18
+ export function parseCreateAppFlags(args) {
19
+ if (args[0] !== 'app') {
20
+ return {
21
+ error: `unknown create target: ${args[0] ?? '(none)'} (expected 'app')`,
22
+ };
23
+ }
24
+ const rest = args.slice(1);
25
+ const flags = {};
26
+ for (let i = 0; i < rest.length; i++) {
27
+ if (rest[i] === '--name') {
28
+ flags.name = rest[++i];
29
+ }
30
+ }
31
+ return flags;
32
+ }
33
+ /**
34
+ * Run `ggui create app [--name <displayName>]`.
35
+ *
36
+ * Returns 0 on success, 1 on API / I/O error, 2 on usage error.
37
+ */
38
+ export async function runCreateCommand(args) {
39
+ const flags = parseCreateAppFlags(args);
40
+ if (flags.error) {
41
+ process.stderr.write(`ggui create: ${flags.error}\n`);
42
+ return 2;
43
+ }
44
+ const app = await createApp({ displayName: flags.name });
45
+ // Persist the returned appId into the local ggui.json when one exists.
46
+ const path = findGguiJson(process.cwd());
47
+ if (path) {
48
+ const read = readGguiJson(path);
49
+ if ('value' in read) {
50
+ writeGguiJson(path, { ...read.value, appId: app.appId });
51
+ }
52
+ }
53
+ process.stdout.write(`App created.\n`);
54
+ process.stdout.write(` appId: ${app.appId}\n`);
55
+ process.stdout.write(` connectUrl: ${app.connectUrl}\n`);
56
+ process.stdout.write(` default: ${app.isDefault}\n`);
57
+ return 0;
58
+ }
@@ -0,0 +1,67 @@
1
+ /** Current deployment state, read from disk at the start of the command. */
2
+ export interface DeployState {
3
+ /** True if a valid auth session exists in ~/.ggui/auth.json. */
4
+ readonly authed: boolean;
5
+ /** appId from ggui.json, if already set. */
6
+ readonly appId?: string;
7
+ /** True if GGUI_MCP_BEARER is already present in .env.local. */
8
+ readonly hasKey: boolean;
9
+ }
10
+ export type DeployStepKind = 'login' | 'create-app' | 'mint-key' | 'push' | 'push-config' | 'push-keys' | 'wire-env';
11
+ export interface DeployStep {
12
+ readonly kind: DeployStepKind;
13
+ }
14
+ /** Input to the planner — pure data, no I/O. */
15
+ export interface DeployInput {
16
+ readonly state: DeployState;
17
+ /** When true, insert a `push-keys` step after `push-config` and before `wire-env`. */
18
+ readonly pushKeys?: boolean;
19
+ }
20
+ /**
21
+ * Derive the ordered list of steps to execute given the current state.
22
+ * Pure function — no I/O.
23
+ *
24
+ * Step order:
25
+ * login? → create-app? → mint-key? → push → push-config → push-keys? → wire-env
26
+ */
27
+ export declare function planDeploySteps(input: DeployInput): DeployStep[];
28
+ /**
29
+ * Resolve the `GGUI_MCP_URL` value `ggui deploy` wires into `.env.local`.
30
+ *
31
+ * The per-app cloud pod serves the MCP transport at the BARE per-app endpoint
32
+ * `<base>/apps/<appId>` (the `connectUrl` the backend advertises) — NOT a
33
+ * `/mcp` sub-path. The `/mcp` suffix is the LOCAL `ggui serve` convention;
34
+ * appending it here produced a URL the per-app pod 404s, so the agent (which
35
+ * reads GGUI_MCP_URL verbatim) could never connect to a deployed app.
36
+ *
37
+ * - `connectUrl` present (a create-app ran this session) → use it VERBATIM —
38
+ * the deployment-correct base (prod `mcp.ggui.ai`, sandbox
39
+ * `<env>.mcp.sandbox.ggui.ai`, derived from whichever backend `GGUI_API_URL`
40
+ * pointed at).
41
+ * - absent → the PRODUCTION default `https://mcp.ggui.ai/apps/<appId>`.
42
+ *
43
+ * The caller leaves any pre-existing `GGUI_MCP_URL` untouched (env override).
44
+ */
45
+ export declare function resolveDeployMcpUrl(connectUrl: string | undefined, appId: string): string;
46
+ /**
47
+ * Read-modify-write `.env.local` at `envLocalPath`, upserting `key=value`.
48
+ *
49
+ * - If the file doesn't exist, creates it with only this line.
50
+ * - If the key already exists, replaces its value in-place.
51
+ * - Other keys/comments are preserved unchanged.
52
+ * - No trailing newline is added beyond the final `\n`.
53
+ */
54
+ export declare function upsertEnvLocal(envLocalPath: string, key: string, value: string): void;
55
+ /**
56
+ * Read the non-empty value of `key` from `.env.local`, or `undefined` if
57
+ * the file doesn't exist, the key is absent, or its value is empty.
58
+ * Strips surrounding quotes + `export ` prefix the same way `upsertEnvLocal`
59
+ * recognises them.
60
+ */
61
+ export declare function readEnvLocalValue(envLocalPath: string, key: string): string | undefined;
62
+ export declare const DEPLOY_HELP = "ggui deploy \u2014 provision + wire a ggui.ai cloud app for this project\n\nIdempotent: run it again after a partial failure to resume.\n\nSteps (ggui-side only):\n 1. login Sign in (skipped if already authed)\n 2. create-app Create a cloud app + write appId into ggui.json\n 3. mint-key Create a connector key + write GGUI_MCP_BEARER into .env.local\n 4. push Export + upload blueprints to the app\n 5. push-config Push declared gadgets + publicEnv to the app\n 6. push-keys Push provider API key from env to the app (only with --push-keys)\n 7. wire-env Write GGUI_MCP_URL into .env.local\n\nAgent / MCP HOSTING (guuey.com) is handled separately.\n\nUsage:\n ggui deploy [--push-keys]\n\nOptions:\n --push-keys Read the provider API key from the appropriate env var\n (e.g. ANTHROPIC_API_KEY) and push it to the cloud app so\n keySource=own renders work. Derives the provider from\n ggui.json#generation.model (or pass --provider to\n `ggui provider-key set` directly).\n";
63
+ /**
64
+ * Orchestrate a full ggui-side deploy. Returns a process exit code.
65
+ */
66
+ export declare function runDeployCommand(_args: readonly string[]): Promise<number>;
67
+ //# sourceMappingURL=deploy-command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deploy-command.d.ts","sourceRoot":"","sources":["../src/deploy-command.ts"],"names":[],"mappings":"AAkCA,4EAA4E;AAC5E,MAAM,WAAW,WAAW;IAC1B,gEAAgE;IAChE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,4CAA4C;IAC5C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,YAAY,GAAG,UAAU,GAAG,MAAM,GAAG,aAAa,GAAG,WAAW,GAAG,UAAU,CAAC;AAErH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;CAC/B;AAED,gDAAgD;AAChD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,sFAAsF;IACtF,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,UAAU,EAAE,CAUhE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEzF;AAMD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CA+BrF;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,MAAM,GACV,MAAM,GAAG,SAAS,CAiBpB;AAgDD,eAAO,MAAM,WAAW,ijCAwBvB,CAAC;AAEF;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAuNhF"}