@ggui-ai/cli 0.2.0-alpha.3 → 0.3.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -7
- package/dist/api-client.d.ts +42 -1
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +30 -2
- package/dist/auth-keys.d.ts +1 -1
- package/dist/auth-keys.d.ts.map +1 -1
- package/dist/auth-keys.js +22 -7
- package/dist/auth-store.d.ts +1 -1
- package/dist/auth-store.d.ts.map +1 -1
- package/dist/cli.js +112 -30
- package/dist/config-push.d.ts +79 -0
- package/dist/config-push.d.ts.map +1 -0
- package/dist/config-push.js +301 -0
- package/dist/create-app-command.d.ts +17 -0
- package/dist/create-app-command.d.ts.map +1 -0
- package/dist/create-app-command.js +58 -0
- package/dist/deploy-command.d.ts +67 -0
- package/dist/deploy-command.d.ts.map +1 -0
- package/dist/deploy-command.js +386 -0
- package/dist/dev-command.d.ts +5 -1
- package/dist/dev-command.d.ts.map +1 -1
- package/dist/dev-command.js +31 -1
- package/dist/export-pool-command.d.ts +25 -0
- package/dist/export-pool-command.d.ts.map +1 -0
- package/dist/export-pool-command.js +116 -0
- package/dist/filesystem-seed-pool-source.d.ts +13 -0
- package/dist/filesystem-seed-pool-source.d.ts.map +1 -0
- package/dist/filesystem-seed-pool-source.js +22 -0
- package/dist/generation-probe.d.ts +53 -7
- package/dist/generation-probe.d.ts.map +1 -1
- package/dist/generation-probe.js +37 -1
- package/dist/internal/artifact-install.d.ts +4 -2
- package/dist/internal/artifact-install.d.ts.map +1 -1
- package/dist/internal/artifact-install.js +11 -10
- package/dist/internal/artifact-publish.d.ts +34 -31
- package/dist/internal/artifact-publish.d.ts.map +1 -1
- package/dist/internal/artifact-publish.js +133 -241
- package/dist/internal/artifact-search.d.ts +2 -28
- package/dist/internal/artifact-search.d.ts.map +1 -1
- package/dist/internal/artifact-search.js +5 -4
- package/dist/internal/auth-strategy.d.ts +21 -31
- package/dist/internal/auth-strategy.d.ts.map +1 -1
- package/dist/internal/auth-strategy.js +15 -23
- package/dist/internal/register-author-key.d.ts +7 -1
- package/dist/internal/register-author-key.d.ts.map +1 -1
- package/dist/internal/register-author-key.js +56 -19
- package/dist/mcp-backend.d.ts +25 -25
- package/dist/mcp-backend.d.ts.map +1 -1
- package/dist/mcp-backend.js +65 -61
- package/dist/paths.d.ts +3 -3
- package/dist/paths.js +3 -3
- package/dist/persistent-secrets.js +1 -1
- package/dist/persistent-stores.d.ts +2 -2
- package/dist/persistent-stores.d.ts.map +1 -1
- package/dist/persistent-stores.js +5 -5
- package/dist/pool-artifact.d.ts +19 -0
- package/dist/pool-artifact.d.ts.map +1 -0
- package/dist/pool-artifact.js +59 -0
- package/dist/provider-key-command.d.ts +51 -0
- package/dist/provider-key-command.d.ts.map +1 -0
- package/dist/provider-key-command.js +222 -0
- package/dist/push-command.d.ts +47 -0
- package/dist/push-command.d.ts.map +1 -0
- package/dist/push-command.js +230 -0
- package/dist/serve-command.d.ts +23 -19
- package/dist/serve-command.d.ts.map +1 -1
- package/dist/serve-command.js +13 -26
- package/dist/theme-command.js +0 -2
- package/dist/tunnel-provider.d.ts +8 -7
- package/dist/tunnel-provider.d.ts.map +1 -1
- package/package.json +25 -21
- package/dist/internal/auth-cache.d.ts +0 -40
- package/dist/internal/auth-cache.d.ts.map +0 -1
- package/dist/internal/auth-cache.js +0 -133
- package/dist/persistent-short-code-index.d.ts +0 -14
- package/dist/persistent-short-code-index.d.ts.map +0 -1
- package/dist/persistent-short-code-index.js +0 -39
- package/dist/spawn-cloudflared.d.ts +0 -61
- package/dist/spawn-cloudflared.d.ts.map +0 -1
- package/dist/spawn-cloudflared.js +0 -167
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ggui deploy` — idempotent orchestrator that provisions and wires a
|
|
3
|
+
* ggui.ai cloud app for the current project.
|
|
4
|
+
*
|
|
5
|
+
* Sequencing (ggui-side only):
|
|
6
|
+
* 1. login — device-flow auth (if not already signed in)
|
|
7
|
+
* 2. create-app — POST /v1/apps, persist appId into ggui.json
|
|
8
|
+
* 3. mint-key — POST /v1/keys, write GGUI_MCP_BEARER into .env.local
|
|
9
|
+
* 4. push — export local blueprint pool + upload to the app
|
|
10
|
+
* 5. push-config — PATCH /v1/apps/{appId}: gadgets + publicEnv from ggui.json
|
|
11
|
+
* 6. wire-env — write GGUI_MCP_URL into .env.local
|
|
12
|
+
*
|
|
13
|
+
* Each step is skipped when its gate is already satisfied, making the
|
|
14
|
+
* command idempotent — run it again after a partial failure and it
|
|
15
|
+
* resumes from the first unsatisfied step.
|
|
16
|
+
*
|
|
17
|
+
* Agent / MCP HOSTING (guuey.com) is out of scope for `ggui deploy`.
|
|
18
|
+
* This command wires only the ggui UI layer.
|
|
19
|
+
*/
|
|
20
|
+
/* eslint-disable no-console */
|
|
21
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
22
|
+
import { dirname, join } from 'node:path';
|
|
23
|
+
import { tryLoadAuthSession } from './auth-store.js';
|
|
24
|
+
import { runLoginCommand } from './auth-login.js';
|
|
25
|
+
import { createApp, createKey } from './api-client.js';
|
|
26
|
+
import { findGguiJson, readGguiJson, writeGguiJson } from './internal/ggui-json.js';
|
|
27
|
+
import { runPushCommand } from './push-command.js';
|
|
28
|
+
import { runConfigPushStep } from './config-push.js';
|
|
29
|
+
import { runProviderKeyCommand } from './provider-key-command.js';
|
|
30
|
+
/**
|
|
31
|
+
* Derive the ordered list of steps to execute given the current state.
|
|
32
|
+
* Pure function — no I/O.
|
|
33
|
+
*
|
|
34
|
+
* Step order:
|
|
35
|
+
* login? → create-app? → mint-key? → push → push-config → push-keys? → wire-env
|
|
36
|
+
*/
|
|
37
|
+
export function planDeploySteps(input) {
|
|
38
|
+
const { state: s, pushKeys } = input;
|
|
39
|
+
const steps = [];
|
|
40
|
+
if (!s.authed)
|
|
41
|
+
steps.push({ kind: 'login' });
|
|
42
|
+
if (!s.appId)
|
|
43
|
+
steps.push({ kind: 'create-app' });
|
|
44
|
+
if (!s.hasKey)
|
|
45
|
+
steps.push({ kind: 'mint-key' });
|
|
46
|
+
steps.push({ kind: 'push' }, { kind: 'push-config' });
|
|
47
|
+
if (pushKeys)
|
|
48
|
+
steps.push({ kind: 'push-keys' });
|
|
49
|
+
steps.push({ kind: 'wire-env' });
|
|
50
|
+
return steps;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Resolve the `GGUI_MCP_URL` value `ggui deploy` wires into `.env.local`.
|
|
54
|
+
*
|
|
55
|
+
* The per-app cloud pod serves the MCP transport at the BARE per-app endpoint
|
|
56
|
+
* `<base>/apps/<appId>` (the `connectUrl` the backend advertises) — NOT a
|
|
57
|
+
* `/mcp` sub-path. The `/mcp` suffix is the LOCAL `ggui serve` convention;
|
|
58
|
+
* appending it here produced a URL the per-app pod 404s, so the agent (which
|
|
59
|
+
* reads GGUI_MCP_URL verbatim) could never connect to a deployed app.
|
|
60
|
+
*
|
|
61
|
+
* - `connectUrl` present (a create-app ran this session) → use it VERBATIM —
|
|
62
|
+
* the deployment-correct base (prod `mcp.ggui.ai`, sandbox
|
|
63
|
+
* `<env>.mcp.sandbox.ggui.ai`, derived from whichever backend `GGUI_API_URL`
|
|
64
|
+
* pointed at).
|
|
65
|
+
* - absent → the PRODUCTION default `https://mcp.ggui.ai/apps/<appId>`.
|
|
66
|
+
*
|
|
67
|
+
* The caller leaves any pre-existing `GGUI_MCP_URL` untouched (env override).
|
|
68
|
+
*/
|
|
69
|
+
export function resolveDeployMcpUrl(connectUrl, appId) {
|
|
70
|
+
return connectUrl ?? `https://mcp.ggui.ai/apps/${appId}`;
|
|
71
|
+
}
|
|
72
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
73
|
+
// .env.local upsert helper
|
|
74
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
75
|
+
/**
|
|
76
|
+
* Read-modify-write `.env.local` at `envLocalPath`, upserting `key=value`.
|
|
77
|
+
*
|
|
78
|
+
* - If the file doesn't exist, creates it with only this line.
|
|
79
|
+
* - If the key already exists, replaces its value in-place.
|
|
80
|
+
* - Other keys/comments are preserved unchanged.
|
|
81
|
+
* - No trailing newline is added beyond the final `\n`.
|
|
82
|
+
*/
|
|
83
|
+
export function upsertEnvLocal(envLocalPath, key, value) {
|
|
84
|
+
const line = `${key}=${value}`;
|
|
85
|
+
if (!existsSync(envLocalPath)) {
|
|
86
|
+
writeFileSync(envLocalPath, `${line}\n`, 'utf-8');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const raw = readFileSync(envLocalPath, 'utf-8');
|
|
90
|
+
const lines = raw.split('\n');
|
|
91
|
+
let found = false;
|
|
92
|
+
const updated = lines.map((l) => {
|
|
93
|
+
// Match `KEY=...` or `export KEY=...` (no leading spaces per convention).
|
|
94
|
+
const trimmed = l.startsWith('export ') ? l.slice(7) : l;
|
|
95
|
+
const eq = trimmed.indexOf('=');
|
|
96
|
+
if (eq !== -1 && trimmed.slice(0, eq).trim() === key) {
|
|
97
|
+
found = true;
|
|
98
|
+
return line;
|
|
99
|
+
}
|
|
100
|
+
return l;
|
|
101
|
+
});
|
|
102
|
+
if (!found) {
|
|
103
|
+
// Append: ensure one blank line before the new entry if the file is
|
|
104
|
+
// non-empty and doesn't already end with a blank line.
|
|
105
|
+
const last = updated[updated.length - 1];
|
|
106
|
+
if (last !== undefined && last.trim() !== '') {
|
|
107
|
+
updated.push('');
|
|
108
|
+
}
|
|
109
|
+
updated.push(line);
|
|
110
|
+
}
|
|
111
|
+
// Rejoin preserving the original trailing-newline behaviour.
|
|
112
|
+
const result = updated.join('\n');
|
|
113
|
+
writeFileSync(envLocalPath, result.endsWith('\n') ? result : `${result}\n`, 'utf-8');
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Read the non-empty value of `key` from `.env.local`, or `undefined` if
|
|
117
|
+
* the file doesn't exist, the key is absent, or its value is empty.
|
|
118
|
+
* Strips surrounding quotes + `export ` prefix the same way `upsertEnvLocal`
|
|
119
|
+
* recognises them.
|
|
120
|
+
*/
|
|
121
|
+
export function readEnvLocalValue(envLocalPath, key) {
|
|
122
|
+
if (!existsSync(envLocalPath))
|
|
123
|
+
return undefined;
|
|
124
|
+
const raw = readFileSync(envLocalPath, 'utf-8');
|
|
125
|
+
for (const rawLine of raw.split('\n')) {
|
|
126
|
+
const l = rawLine.trim().startsWith('export ')
|
|
127
|
+
? rawLine.trim().slice(7)
|
|
128
|
+
: rawLine.trim();
|
|
129
|
+
const eq = l.indexOf('=');
|
|
130
|
+
if (eq !== -1 && l.slice(0, eq).trim() === key) {
|
|
131
|
+
const val = l
|
|
132
|
+
.slice(eq + 1)
|
|
133
|
+
.replace(/^["']|["']$/g, '')
|
|
134
|
+
.trim();
|
|
135
|
+
if (val.length > 0)
|
|
136
|
+
return val;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Read the current deploy state from disk:
|
|
143
|
+
* - authed : ~/.ggui/auth.json is present + valid
|
|
144
|
+
* - appId : ggui.json#appId if found
|
|
145
|
+
* - hasKey : GGUI_MCP_BEARER present in .env.local (not empty)
|
|
146
|
+
*/
|
|
147
|
+
function readDeployContext(cwd) {
|
|
148
|
+
const gguiJsonPath = findGguiJson(cwd);
|
|
149
|
+
// .env.local lives next to ggui.json when one is found, else at cwd.
|
|
150
|
+
const projectRoot = gguiJsonPath ? dirname(gguiJsonPath) : cwd;
|
|
151
|
+
const envLocalPath = join(projectRoot, '.env.local');
|
|
152
|
+
const authed = tryLoadAuthSession() !== null;
|
|
153
|
+
let appId;
|
|
154
|
+
if (gguiJsonPath) {
|
|
155
|
+
const result = readGguiJson(gguiJsonPath);
|
|
156
|
+
if ('value' in result) {
|
|
157
|
+
const v = result.value['appId'];
|
|
158
|
+
if (typeof v === 'string' && v.length > 0)
|
|
159
|
+
appId = v;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const hasKey = readEnvLocalValue(envLocalPath, 'GGUI_MCP_BEARER') !== undefined;
|
|
163
|
+
return {
|
|
164
|
+
gguiJsonPath,
|
|
165
|
+
envLocalPath,
|
|
166
|
+
state: { authed, appId, hasKey },
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
170
|
+
// Orchestrator
|
|
171
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
172
|
+
export const DEPLOY_HELP = `ggui deploy — provision + wire a ggui.ai cloud app for this project
|
|
173
|
+
|
|
174
|
+
Idempotent: run it again after a partial failure to resume.
|
|
175
|
+
|
|
176
|
+
Steps (ggui-side only):
|
|
177
|
+
1. login Sign in (skipped if already authed)
|
|
178
|
+
2. create-app Create a cloud app + write appId into ggui.json
|
|
179
|
+
3. mint-key Create a connector key + write GGUI_MCP_BEARER into .env.local
|
|
180
|
+
4. push Export + upload blueprints to the app
|
|
181
|
+
5. push-config Push declared gadgets + publicEnv to the app
|
|
182
|
+
6. push-keys Push provider API key from env to the app (only with --push-keys)
|
|
183
|
+
7. wire-env Write GGUI_MCP_URL into .env.local
|
|
184
|
+
|
|
185
|
+
Agent / MCP HOSTING (guuey.com) is handled separately.
|
|
186
|
+
|
|
187
|
+
Usage:
|
|
188
|
+
ggui deploy [--push-keys]
|
|
189
|
+
|
|
190
|
+
Options:
|
|
191
|
+
--push-keys Read the provider API key from the appropriate env var
|
|
192
|
+
(e.g. ANTHROPIC_API_KEY) and push it to the cloud app so
|
|
193
|
+
keySource=own renders work. Derives the provider from
|
|
194
|
+
ggui.json#generation.model (or pass --provider to
|
|
195
|
+
\`ggui provider-key set\` directly).
|
|
196
|
+
`;
|
|
197
|
+
/**
|
|
198
|
+
* Orchestrate a full ggui-side deploy. Returns a process exit code.
|
|
199
|
+
*/
|
|
200
|
+
export async function runDeployCommand(_args) {
|
|
201
|
+
if (_args.includes('--help') || _args.includes('-h')) {
|
|
202
|
+
process.stdout.write(DEPLOY_HELP);
|
|
203
|
+
return 0;
|
|
204
|
+
}
|
|
205
|
+
const pushKeys = _args.includes('--push-keys');
|
|
206
|
+
const cwd = process.cwd();
|
|
207
|
+
// Read current state.
|
|
208
|
+
const ctx = readDeployContext(cwd);
|
|
209
|
+
const { gguiJsonPath, envLocalPath, state } = ctx;
|
|
210
|
+
if (!gguiJsonPath) {
|
|
211
|
+
process.stderr.write('ggui deploy: no ggui.json found in this directory or its parents.\n' +
|
|
212
|
+
' Run from a scaffolded project directory (one created by `npx @ggui-ai/create-agentic-app`).\n');
|
|
213
|
+
return 1;
|
|
214
|
+
}
|
|
215
|
+
const steps = planDeploySteps({ state, pushKeys });
|
|
216
|
+
// Mutable state for values discovered mid-run that subsequent steps need.
|
|
217
|
+
let appId = state.appId;
|
|
218
|
+
let connectUrl;
|
|
219
|
+
for (const step of steps) {
|
|
220
|
+
switch (step.kind) {
|
|
221
|
+
case 'login': {
|
|
222
|
+
process.stdout.write('\n[ggui deploy] Step: login\n');
|
|
223
|
+
const code = await runLoginCommand([]);
|
|
224
|
+
if (code !== 0) {
|
|
225
|
+
process.stderr.write('ggui deploy: login failed. Aborting.\n');
|
|
226
|
+
return code;
|
|
227
|
+
}
|
|
228
|
+
// runLoginCommand persisted the session to ~/.ggui/auth.json;
|
|
229
|
+
// createApp / createKey load it internally, so nothing to thread
|
|
230
|
+
// forward here.
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
case 'create-app': {
|
|
234
|
+
process.stdout.write('\n[ggui deploy] Step: create-app\n');
|
|
235
|
+
let app;
|
|
236
|
+
try {
|
|
237
|
+
app = await createApp({});
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
process.stderr.write(`ggui deploy: create-app failed — ${err instanceof Error ? err.message : String(err)}\n`);
|
|
241
|
+
return 1;
|
|
242
|
+
}
|
|
243
|
+
appId = app.appId;
|
|
244
|
+
connectUrl = app.connectUrl;
|
|
245
|
+
// Persist appId into ggui.json. The app already exists server-side
|
|
246
|
+
// now, so a persist failure is LOUD: without the appId on disk a
|
|
247
|
+
// re-run would call create-app again and create a DUPLICATE app.
|
|
248
|
+
const read = readGguiJson(gguiJsonPath);
|
|
249
|
+
if ('error' in read) {
|
|
250
|
+
process.stderr.write(`ggui deploy: create-app: app ${app.appId} was created, but ggui.json ` +
|
|
251
|
+
`could not be read to persist appId — ${read.error}\n` +
|
|
252
|
+
` Add "appId": "${app.appId}" to ggui.json manually before re-running, ` +
|
|
253
|
+
`or a re-run will create a DUPLICATE app.\n`);
|
|
254
|
+
return 1;
|
|
255
|
+
}
|
|
256
|
+
writeGguiJson(gguiJsonPath, { ...read.value, appId });
|
|
257
|
+
process.stdout.write(` appId: ${app.appId}\n`);
|
|
258
|
+
process.stdout.write(` connectUrl: ${app.connectUrl}\n`);
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
case 'mint-key': {
|
|
262
|
+
process.stdout.write('\n[ggui deploy] Step: mint-key\n');
|
|
263
|
+
let key;
|
|
264
|
+
try {
|
|
265
|
+
key = await createKey({ name: 'ggui deploy' });
|
|
266
|
+
}
|
|
267
|
+
catch (err) {
|
|
268
|
+
process.stderr.write(`ggui deploy: mint-key failed — ${err instanceof Error ? err.message : String(err)}\n`);
|
|
269
|
+
return 1;
|
|
270
|
+
}
|
|
271
|
+
// Write the one-time-reveal secret into .env.local.
|
|
272
|
+
upsertEnvLocal(envLocalPath, 'GGUI_MCP_BEARER', key.apiKey);
|
|
273
|
+
process.stdout.write(` Key created (prefix: ${key.prefix}). Written to .env.local.\n`);
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
case 'push': {
|
|
277
|
+
process.stdout.write('\n[ggui deploy] Step: push\n');
|
|
278
|
+
if (!appId) {
|
|
279
|
+
process.stderr.write('ggui deploy: push: no appId resolved. This is a bug.\n');
|
|
280
|
+
return 1;
|
|
281
|
+
}
|
|
282
|
+
const code = await runPushCommand(['--app', appId]);
|
|
283
|
+
if (code !== 0) {
|
|
284
|
+
process.stderr.write('ggui deploy: push failed. Aborting.\n');
|
|
285
|
+
return code;
|
|
286
|
+
}
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
case 'push-config': {
|
|
290
|
+
process.stdout.write('\n[ggui deploy] Step: push-config\n');
|
|
291
|
+
if (!appId) {
|
|
292
|
+
process.stderr.write('ggui deploy: push-config: no appId resolved. This is a bug.\n');
|
|
293
|
+
return 1;
|
|
294
|
+
}
|
|
295
|
+
const code = await runConfigPushStep(appId);
|
|
296
|
+
if (code !== 0) {
|
|
297
|
+
process.stderr.write('ggui deploy: push-config failed. Aborting.\n');
|
|
298
|
+
return code;
|
|
299
|
+
}
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
case 'push-keys': {
|
|
303
|
+
process.stdout.write('\n[ggui deploy] Step: push-keys\n');
|
|
304
|
+
if (!appId) {
|
|
305
|
+
process.stderr.write('ggui deploy: push-keys: no appId resolved. This is a bug.\n');
|
|
306
|
+
return 1;
|
|
307
|
+
}
|
|
308
|
+
const code = await runProviderKeyCommand(['set', '--app', appId]);
|
|
309
|
+
if (code !== 0) {
|
|
310
|
+
process.stderr.write('ggui deploy: push-keys failed. Aborting.\n');
|
|
311
|
+
return code;
|
|
312
|
+
}
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
case 'wire-env': {
|
|
316
|
+
process.stdout.write('\n[ggui deploy] Step: wire-env\n');
|
|
317
|
+
if (!appId) {
|
|
318
|
+
process.stderr.write('ggui deploy: wire-env: no appId resolved. This is a bug.\n');
|
|
319
|
+
return 1;
|
|
320
|
+
}
|
|
321
|
+
// NEVER clobber an existing GGUI_MCP_URL. The FIRST deploy writes
|
|
322
|
+
// the deployment-correct base derived from create-app's connectUrl
|
|
323
|
+
// (e.g. sandbox `<env>.mcp.sandbox.ggui.ai`, prod `mcp.ggui.ai`).
|
|
324
|
+
// On REDEPLOY we have only the appId — reconstructing a hardcoded
|
|
325
|
+
// prod URL here would silently break a sandbox/non-prod deployment.
|
|
326
|
+
// So: leave any existing value alone; only write when absent.
|
|
327
|
+
const existing = readEnvLocalValue(envLocalPath, 'GGUI_MCP_URL');
|
|
328
|
+
if (existing) {
|
|
329
|
+
process.stdout.write(` GGUI_MCP_URL already set (${existing}) — leaving it unchanged.\n`);
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
// No prior value → resolve the deployment-correct BARE per-app URL
|
|
333
|
+
// (see resolveDeployMcpUrl: the per-app pod serves MCP at the bare
|
|
334
|
+
// endpoint, never `/mcp`). The fallback to the production default only
|
|
335
|
+
// fires when create-app didn't run this session — a first deploy always
|
|
336
|
+
// has connectUrl, so it never reaches the fallback in practice.
|
|
337
|
+
const mcpUrl = resolveDeployMcpUrl(connectUrl, appId);
|
|
338
|
+
upsertEnvLocal(envLocalPath, 'GGUI_MCP_URL', mcpUrl);
|
|
339
|
+
process.stdout.write(` GGUI_MCP_URL=${mcpUrl}\n`);
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
default: {
|
|
343
|
+
// Exhaustiveness check — DeployStepKind is a union; this branch
|
|
344
|
+
// is unreachable in correct code.
|
|
345
|
+
const _exhaustive = step.kind;
|
|
346
|
+
process.stderr.write(`ggui deploy: unknown step "${_exhaustive}"\n`);
|
|
347
|
+
return 1;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
// keySource=own WARN — if the project declared keySource='own' but the
|
|
352
|
+
// user didn't pass --push-keys this deploy, remind them that cloud renders
|
|
353
|
+
// will reject until a key is set.
|
|
354
|
+
if (!pushKeys && appId) {
|
|
355
|
+
const gguiReadResult = readGguiJson(gguiJsonPath);
|
|
356
|
+
if ('value' in gguiReadResult) {
|
|
357
|
+
const { z } = await import('zod');
|
|
358
|
+
const schema = z.object({
|
|
359
|
+
generation: z.object({ keySource: z.string().optional() }).optional(),
|
|
360
|
+
});
|
|
361
|
+
const parsed = schema.safeParse(gguiReadResult.value);
|
|
362
|
+
if (parsed.success && parsed.data.generation?.keySource === 'own') {
|
|
363
|
+
process.stderr.write(`\n WARN: keySource=own but no provider key pushed — run\n` +
|
|
364
|
+
` ggui deploy --push-keys\n` +
|
|
365
|
+
` or\n` +
|
|
366
|
+
` ggui provider-key set --app ${appId}\n` +
|
|
367
|
+
` else cloud renders will reject.\n`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
// Report the URL actually in .env.local — after wire-env that file is
|
|
372
|
+
// authoritative (a preserved sandbox/non-prod value is reflected
|
|
373
|
+
// accurately rather than re-derived as the prod default).
|
|
374
|
+
const finalMcpUrl = readEnvLocalValue(envLocalPath, 'GGUI_MCP_URL') ?? '(unknown)';
|
|
375
|
+
process.stdout.write('\n');
|
|
376
|
+
process.stdout.write(' ggui deploy complete.\n');
|
|
377
|
+
process.stdout.write(`\n`);
|
|
378
|
+
process.stdout.write(` App MCP endpoint: ${finalMcpUrl}\n`);
|
|
379
|
+
process.stdout.write(` Bearer key: in .env.local (GGUI_MCP_BEARER)\n`);
|
|
380
|
+
process.stdout.write('\n');
|
|
381
|
+
process.stdout.write(' The agent will use the cloud ggui endpoint on next `pnpm dev`.\n' +
|
|
382
|
+
' Local ggui service is skipped automatically when GGUI_MCP_URL is remote.\n' +
|
|
383
|
+
'\n' +
|
|
384
|
+
' Agent / MCP hosting (guuey.com) is handled separately.\n');
|
|
385
|
+
return 0;
|
|
386
|
+
}
|
package/dist/dev-command.d.ts
CHANGED
|
@@ -28,14 +28,18 @@ export interface ParsedDevFlags {
|
|
|
28
28
|
*/
|
|
29
29
|
tunnel: boolean;
|
|
30
30
|
agent?: string;
|
|
31
|
+
/** `'__help__'` for `--help` / `-h`; other strings = usage error. */
|
|
31
32
|
error?: string;
|
|
32
33
|
}
|
|
33
34
|
/**
|
|
34
35
|
* Parse the raw `ggui dev` argv tail. Returns a discriminated
|
|
35
36
|
* shape — `error` field is non-undefined when the input was
|
|
36
|
-
* malformed
|
|
37
|
+
* malformed (`'__help__'` is the help sentinel, mirroring
|
|
38
|
+
* `parseServeFlags`); callers render it and bail with exit code 1.
|
|
37
39
|
*/
|
|
38
40
|
export declare function parseDevFlags(args: readonly string[]): ParsedDevFlags;
|
|
41
|
+
/** Help text for `ggui dev` — printed on `--help` / `-h`. */
|
|
42
|
+
export declare const DEV_HELP = "ggui dev \u2014 run the local development stack\n\nBoots the local dev hub (UI registry + dev server) and, when\n`--agent` is supplied, supervises your agent entry alongside it.\nSets GGUI_MODE=dev (unless already set) so the server mounts the\n/devtools/* namespace and the SPA shows the link. In an interactive\nterminal the hub URL auto-opens in your browser (skipped in CI, when\nstdout is piped, with BROWSER=none, or with --no-open).\n\nUsage:\n ggui dev [options]\n\nOptions:\n --port <n> Bind port (default: 6780, 0 = OS-assigned).\n --host <addr> Bind host (default: 127.0.0.1).\n --agent <entry> Agent entry file to supervise (e.g. src/agent.ts).\n Resolved before the socket binds \u2014 bad paths fail fast.\n --no-serve Do not bind the HTTP dev hub; run registry-only.\n --no-open Skip the browser auto-open.\n --tunnel Opt into managed-mode: after the local stack is\n listening, the configured tunnel provider exposes it\n remotely. The local loop runs unchanged either way.\n --help, -h Show this help.\n";
|
|
39
43
|
export { resolveAgentCommand, buildAgentRuntime };
|
|
40
44
|
export type { AgentCommandResolution };
|
|
41
45
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-command.d.ts","sourceRoot":"","sources":["../src/dev-command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,YAAY,EAClB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"dev-command.d.ts","sourceRoot":"","sources":["../src/dev-command.ts"],"names":[],"mappings":"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"}
|
package/dist/dev-command.js
CHANGED
|
@@ -13,11 +13,13 @@
|
|
|
13
13
|
* tunnel, a test harness) pick their own launch policy.
|
|
14
14
|
*/
|
|
15
15
|
import { spawn as nodeSpawn, } from 'node:child_process';
|
|
16
|
+
import { DEFAULT_DEV_HOST, DEFAULT_DEV_PORT } from '@ggui-ai/dev-stack';
|
|
16
17
|
import { buildAgentRuntime, resolveAgentCommand, } from './agent-resolution.js';
|
|
17
18
|
/**
|
|
18
19
|
* Parse the raw `ggui dev` argv tail. Returns a discriminated
|
|
19
20
|
* shape — `error` field is non-undefined when the input was
|
|
20
|
-
* malformed
|
|
21
|
+
* malformed (`'__help__'` is the help sentinel, mirroring
|
|
22
|
+
* `parseServeFlags`); callers render it and bail with exit code 1.
|
|
21
23
|
*/
|
|
22
24
|
export function parseDevFlags(args) {
|
|
23
25
|
const out = { noServe: false, noOpen: false, tunnel: false };
|
|
@@ -61,10 +63,38 @@ export function parseDevFlags(args) {
|
|
|
61
63
|
out.agent = v;
|
|
62
64
|
continue;
|
|
63
65
|
}
|
|
66
|
+
if (arg === '--help' || arg === '-h') {
|
|
67
|
+
return { ...out, error: '__help__' };
|
|
68
|
+
}
|
|
64
69
|
return { ...out, error: `unknown flag "${arg ?? ''}"` };
|
|
65
70
|
}
|
|
66
71
|
return out;
|
|
67
72
|
}
|
|
73
|
+
/** Help text for `ggui dev` — printed on `--help` / `-h`. */
|
|
74
|
+
export const DEV_HELP = `ggui dev — run the local development stack
|
|
75
|
+
|
|
76
|
+
Boots the local dev hub (UI registry + dev server) and, when
|
|
77
|
+
\`--agent\` is supplied, supervises your agent entry alongside it.
|
|
78
|
+
Sets GGUI_MODE=dev (unless already set) so the server mounts the
|
|
79
|
+
/devtools/* namespace and the SPA shows the link. In an interactive
|
|
80
|
+
terminal the hub URL auto-opens in your browser (skipped in CI, when
|
|
81
|
+
stdout is piped, with BROWSER=none, or with --no-open).
|
|
82
|
+
|
|
83
|
+
Usage:
|
|
84
|
+
ggui dev [options]
|
|
85
|
+
|
|
86
|
+
Options:
|
|
87
|
+
--port <n> Bind port (default: ${DEFAULT_DEV_PORT}, 0 = OS-assigned).
|
|
88
|
+
--host <addr> Bind host (default: ${DEFAULT_DEV_HOST}).
|
|
89
|
+
--agent <entry> Agent entry file to supervise (e.g. src/agent.ts).
|
|
90
|
+
Resolved before the socket binds — bad paths fail fast.
|
|
91
|
+
--no-serve Do not bind the HTTP dev hub; run registry-only.
|
|
92
|
+
--no-open Skip the browser auto-open.
|
|
93
|
+
--tunnel Opt into managed-mode: after the local stack is
|
|
94
|
+
listening, the configured tunnel provider exposes it
|
|
95
|
+
remotely. The local loop runs unchanged either way.
|
|
96
|
+
--help, -h Show this help.
|
|
97
|
+
`;
|
|
68
98
|
// Agent-entry resolution + adapter construction live in
|
|
69
99
|
// `./agent-resolution.ts`. They are re-exported here so call sites
|
|
70
100
|
// that reach for these from `dev-command.js` keep working — and
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare const EXPORT_POOL_HELP = "ggui export-pool \u2014 export this app's reusable blueprints as a shared pool\n\nWrites a directory artifact (manifest.json + codes/) that another ggui\ndeployment can load with `ggui serve --seed-pool <dir>`.\n\nUsage:\n ggui export-pool [--out <dir>]\n\nOptions:\n --out <dir> Output directory (default: ./ggui-pool).\n\nRequires a persistent vectors store (ggui.json: storage.vectors.driver = \"sqlite\").\n";
|
|
2
|
+
/** Result of {@link exportLocalPool}: the artifact directory + how many records it holds. */
|
|
3
|
+
export interface ExportLocalPoolResult {
|
|
4
|
+
/** Absolute path of the written artifact directory. */
|
|
5
|
+
readonly dir: string;
|
|
6
|
+
/** Number of blueprint records written (may be 0 — the artifact dir is still written). */
|
|
7
|
+
readonly count: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the storage stack → list blueprints → write a pool artifact to disk.
|
|
11
|
+
*
|
|
12
|
+
* When `outDir` is omitted a fresh temp directory is created (suitable for
|
|
13
|
+
* a subsequent `ggui push` that immediately reads the artifact). When provided,
|
|
14
|
+
* the artifact is written to `resolve(process.cwd(), outDir)`.
|
|
15
|
+
*
|
|
16
|
+
* Throws on hard errors (no vectors store, list failure). An empty pool is
|
|
17
|
+
* NOT an error here — the artifact directory is written regardless (possibly
|
|
18
|
+
* holding zero records) so callers can decide whether "nothing to export /
|
|
19
|
+
* push" is graceful (push) or an error (explicit `export-pool`).
|
|
20
|
+
*
|
|
21
|
+
* Returns the artifact directory + record count.
|
|
22
|
+
*/
|
|
23
|
+
export declare function exportLocalPool(outDir?: string): Promise<ExportLocalPoolResult>;
|
|
24
|
+
export declare function runExportPoolCommand(args: readonly string[]): Promise<number>;
|
|
25
|
+
//# sourceMappingURL=export-pool-command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export-pool-command.d.ts","sourceRoot":"","sources":["../src/export-pool-command.ts"],"names":[],"mappings":"AAkCA,eAAO,MAAM,gBAAgB,oaAY5B,CAAC;AAEF,6FAA6F;AAC7F,MAAM,WAAW,qBAAqB;IACpC,uDAAuD;IACvD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,0FAA0F;IAC1F,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA0CrF;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAuBnF"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { dirname, resolve } from 'node:path';
|
|
2
|
+
import { mkdtemp } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { listRegistryBlueprintsForExport, } from '@ggui-ai/mcp-server-handlers';
|
|
6
|
+
import { toPortableBlueprint } from '@ggui-ai/protocol/blueprint-key';
|
|
7
|
+
import { resolveStorageFromConfig, DEFAULT_BUILDER_APP_ID } from '@ggui-ai/mcp-server';
|
|
8
|
+
import { findGguiJson, safeLoadGguiJson, } from '@ggui-ai/project-config/node';
|
|
9
|
+
import { writePoolArtifact } from './pool-artifact.js';
|
|
10
|
+
function parseExportPoolFlags(args) {
|
|
11
|
+
let out = './ggui-pool';
|
|
12
|
+
for (let i = 0; i < args.length; i++) {
|
|
13
|
+
const a = args[i];
|
|
14
|
+
if (a === '--out' || a === '-o') {
|
|
15
|
+
const v = args[++i];
|
|
16
|
+
if (v === undefined)
|
|
17
|
+
return { out, error: '--out requires a path' };
|
|
18
|
+
out = v;
|
|
19
|
+
}
|
|
20
|
+
else if (a === '--help' || a === '-h') {
|
|
21
|
+
return { out, error: '__help__' };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return { out };
|
|
25
|
+
}
|
|
26
|
+
export const EXPORT_POOL_HELP = `ggui export-pool — export this app's reusable blueprints as a shared pool
|
|
27
|
+
|
|
28
|
+
Writes a directory artifact (manifest.json + codes/) that another ggui
|
|
29
|
+
deployment can load with \`ggui serve --seed-pool <dir>\`.
|
|
30
|
+
|
|
31
|
+
Usage:
|
|
32
|
+
ggui export-pool [--out <dir>]
|
|
33
|
+
|
|
34
|
+
Options:
|
|
35
|
+
--out <dir> Output directory (default: ./ggui-pool).
|
|
36
|
+
|
|
37
|
+
Requires a persistent vectors store (ggui.json: storage.vectors.driver = "sqlite").
|
|
38
|
+
`;
|
|
39
|
+
/**
|
|
40
|
+
* Resolve the storage stack → list blueprints → write a pool artifact to disk.
|
|
41
|
+
*
|
|
42
|
+
* When `outDir` is omitted a fresh temp directory is created (suitable for
|
|
43
|
+
* a subsequent `ggui push` that immediately reads the artifact). When provided,
|
|
44
|
+
* the artifact is written to `resolve(process.cwd(), outDir)`.
|
|
45
|
+
*
|
|
46
|
+
* Throws on hard errors (no vectors store, list failure). An empty pool is
|
|
47
|
+
* NOT an error here — the artifact directory is written regardless (possibly
|
|
48
|
+
* holding zero records) so callers can decide whether "nothing to export /
|
|
49
|
+
* push" is graceful (push) or an error (explicit `export-pool`).
|
|
50
|
+
*
|
|
51
|
+
* Returns the artifact directory + record count.
|
|
52
|
+
*/
|
|
53
|
+
export async function exportLocalPool(outDir) {
|
|
54
|
+
// Resolve the SAME storage stack `ggui serve` uses, from ggui.json in cwd.
|
|
55
|
+
// A missing manifest is fine (MCP-only / default storage); a manifest that
|
|
56
|
+
// exists but fails schema validation is a hard error — surface it rather
|
|
57
|
+
// than silently falling through to the generic "no vectors store" message.
|
|
58
|
+
const gguiJsonPath = findGguiJson(process.cwd());
|
|
59
|
+
let manifest;
|
|
60
|
+
if (gguiJsonPath) {
|
|
61
|
+
const loaded = safeLoadGguiJson(gguiJsonPath);
|
|
62
|
+
if (!loaded.success) {
|
|
63
|
+
throw new Error(loaded.error.message);
|
|
64
|
+
}
|
|
65
|
+
manifest = loaded.data;
|
|
66
|
+
}
|
|
67
|
+
const projectRoot = gguiJsonPath ? dirname(gguiJsonPath) : process.cwd();
|
|
68
|
+
const storage = await resolveStorageFromConfig(manifest?.storage, { baseDir: projectRoot });
|
|
69
|
+
if (!storage.vectors) {
|
|
70
|
+
throw new Error('no persistent vectors store. Set storage.vectors.driver="sqlite" in ggui.json (nothing to export from an in-memory store).');
|
|
71
|
+
}
|
|
72
|
+
const rows = await listRegistryBlueprintsForExport(storage.vectors, DEFAULT_BUILDER_APP_ID);
|
|
73
|
+
// Tool-identity catalog is NOT available here: `export-pool` is a
|
|
74
|
+
// batch offline export with no live MCP connections. The catalog is
|
|
75
|
+
// a runtime artifact built from active `initialize` + `tools/list`
|
|
76
|
+
// handshakes during `ggui serve`. Passing it here would require
|
|
77
|
+
// either persisting it to disk or starting the server just for export
|
|
78
|
+
// — both are the wrong trade-off for an offline command. The
|
|
79
|
+
// `generatorProtocolVersion` stamp (always set by toPortableBlueprint)
|
|
80
|
+
// is sufficient for era-compatibility gating; the `toolIdentityCatalogHash`
|
|
81
|
+
// remains absent, triggering an "unstamped → warn" policy on importers.
|
|
82
|
+
const records = rows.map((r) => toPortableBlueprint(r));
|
|
83
|
+
const resolvedDir = outDir !== undefined
|
|
84
|
+
? resolve(process.cwd(), outDir)
|
|
85
|
+
: await mkdtemp(join(tmpdir(), 'ggui-pool-'));
|
|
86
|
+
await writePoolArtifact(resolvedDir, records);
|
|
87
|
+
return { dir: resolvedDir, count: records.length };
|
|
88
|
+
}
|
|
89
|
+
export async function runExportPoolCommand(args) {
|
|
90
|
+
const flags = parseExportPoolFlags(args);
|
|
91
|
+
if (flags.error === '__help__') {
|
|
92
|
+
process.stdout.write(EXPORT_POOL_HELP);
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
if (flags.error) {
|
|
96
|
+
process.stderr.write(`ggui export-pool: ${flags.error}\n`);
|
|
97
|
+
return 1;
|
|
98
|
+
}
|
|
99
|
+
let result;
|
|
100
|
+
try {
|
|
101
|
+
result = await exportLocalPool(flags.out);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
process.stderr.write(`ggui export-pool: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
105
|
+
return 1;
|
|
106
|
+
}
|
|
107
|
+
// Explicit `export-pool` of an empty pool is an error: the operator asked
|
|
108
|
+
// to export something and there is nothing. (The on-the-fly export path
|
|
109
|
+
// used by `ggui push` treats 0 as a graceful "nothing to push" instead.)
|
|
110
|
+
if (result.count === 0) {
|
|
111
|
+
process.stderr.write('ggui export-pool: 0 blueprints found for this app; nothing written.\n');
|
|
112
|
+
return 1;
|
|
113
|
+
}
|
|
114
|
+
process.stdout.write(`ggui export-pool: wrote ${result.count} blueprint(s) to ${result.dir}\n`);
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SeedPoolSource } from '@ggui-ai/mcp-server-handlers';
|
|
2
|
+
/**
|
|
3
|
+
* Reads a seed-pool directory artifact (B2 OSS adapter). Records come
|
|
4
|
+
* back raw/unvalidated — `buildSeedPool` runs each one through the
|
|
5
|
+
* `fromPortableBlueprint` trust boundary.
|
|
6
|
+
*/
|
|
7
|
+
export declare class FileSystemSeedPoolSource implements SeedPoolSource {
|
|
8
|
+
private readonly dir;
|
|
9
|
+
readonly label: string;
|
|
10
|
+
constructor(dir: string);
|
|
11
|
+
loadAll(): Promise<readonly unknown[]>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=filesystem-seed-pool-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filesystem-seed-pool-source.d.ts","sourceRoot":"","sources":["../src/filesystem-seed-pool-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAGnE;;;;GAIG;AACH,qBAAa,wBAAyB,YAAW,cAAc;IAEjD,OAAO,CAAC,QAAQ,CAAC,GAAG;IADhC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBACM,GAAG,EAAE,MAAM;IAGlC,OAAO,IAAI,OAAO,CAAC,SAAS,OAAO,EAAE,CAAC;CAQ7C"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readPoolArtifact } from './pool-artifact.js';
|
|
2
|
+
/**
|
|
3
|
+
* Reads a seed-pool directory artifact (B2 OSS adapter). Records come
|
|
4
|
+
* back raw/unvalidated — `buildSeedPool` runs each one through the
|
|
5
|
+
* `fromPortableBlueprint` trust boundary.
|
|
6
|
+
*/
|
|
7
|
+
export class FileSystemSeedPoolSource {
|
|
8
|
+
dir;
|
|
9
|
+
label;
|
|
10
|
+
constructor(dir) {
|
|
11
|
+
this.dir = dir;
|
|
12
|
+
this.label = `seed:${dir}`;
|
|
13
|
+
}
|
|
14
|
+
async loadAll() {
|
|
15
|
+
const { records, issues } = await readPoolArtifact(this.dir);
|
|
16
|
+
for (const issue of issues) {
|
|
17
|
+
// eslint-disable-next-line no-console -- operator-visible load warning
|
|
18
|
+
console.warn(`[ggui] ${issue}`);
|
|
19
|
+
}
|
|
20
|
+
return records;
|
|
21
|
+
}
|
|
22
|
+
}
|