@ggui-ai/cli 0.1.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +96 -0
- package/dist/agent-resolution.d.ts +41 -0
- package/dist/agent-resolution.d.ts.map +1 -0
- package/dist/agent-resolution.js +80 -0
- package/dist/api-client.d.ts +59 -0
- package/dist/api-client.d.ts.map +1 -0
- package/dist/api-client.js +145 -0
- package/dist/auth-keys.d.ts +10 -0
- package/dist/auth-keys.d.ts.map +1 -0
- package/dist/auth-keys.js +382 -0
- package/dist/auth-login.d.ts +11 -0
- package/dist/auth-login.d.ts.map +1 -0
- package/dist/auth-login.js +231 -0
- package/dist/auth-logout.d.ts +3 -0
- package/dist/auth-logout.d.ts.map +1 -0
- package/dist/auth-logout.js +32 -0
- package/dist/auth-store.d.ts +67 -0
- package/dist/auth-store.d.ts.map +1 -0
- package/dist/auth-store.js +150 -0
- package/dist/auth-whoami.d.ts +3 -0
- package/dist/auth-whoami.d.ts.map +1 -0
- package/dist/auth-whoami.js +52 -0
- package/dist/blueprint-command.d.ts +3 -0
- package/dist/blueprint-command.d.ts.map +1 -0
- package/dist/blueprint-command.js +203 -0
- package/dist/blueprint-create.d.ts +71 -0
- package/dist/blueprint-create.d.ts.map +1 -0
- package/dist/blueprint-create.js +377 -0
- package/dist/byok-resolver.d.ts +141 -0
- package/dist/byok-resolver.d.ts.map +1 -0
- package/dist/byok-resolver.js +89 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1066 -0
- package/dist/dev-command.d.ts +169 -0
- package/dist/dev-command.d.ts.map +1 -0
- package/dist/dev-command.js +222 -0
- package/dist/gadget-command.d.ts +3 -0
- package/dist/gadget-command.d.ts.map +1 -0
- package/dist/gadget-command.js +181 -0
- package/dist/gadget-create.d.ts +106 -0
- package/dist/gadget-create.d.ts.map +1 -0
- package/dist/gadget-create.js +492 -0
- package/dist/generation-probe.d.ts +135 -0
- package/dist/generation-probe.d.ts.map +1 -0
- package/dist/generation-probe.js +132 -0
- package/dist/internal/artifact-install.d.ts +150 -0
- package/dist/internal/artifact-install.d.ts.map +1 -0
- package/dist/internal/artifact-install.js +1019 -0
- package/dist/internal/artifact-publish.d.ts +204 -0
- package/dist/internal/artifact-publish.d.ts.map +1 -0
- package/dist/internal/artifact-publish.js +1141 -0
- package/dist/internal/artifact-search.d.ts +155 -0
- package/dist/internal/artifact-search.d.ts.map +1 -0
- package/dist/internal/artifact-search.js +539 -0
- package/dist/internal/artifact-uninstall.d.ts +30 -0
- package/dist/internal/artifact-uninstall.d.ts.map +1 -0
- package/dist/internal/artifact-uninstall.js +205 -0
- package/dist/internal/auth-cache.d.ts +40 -0
- package/dist/internal/auth-cache.d.ts.map +1 -0
- package/dist/internal/auth-cache.js +133 -0
- package/dist/internal/auth-strategy.d.ts +89 -0
- package/dist/internal/auth-strategy.d.ts.map +1 -0
- package/dist/internal/auth-strategy.js +132 -0
- package/dist/internal/ggui-json.d.ts +53 -0
- package/dist/internal/ggui-json.d.ts.map +1 -0
- package/dist/internal/ggui-json.js +92 -0
- package/dist/internal/key-store.d.ts +43 -0
- package/dist/internal/key-store.d.ts.map +1 -0
- package/dist/internal/key-store.js +128 -0
- package/dist/internal/oidc-token.d.ts +52 -0
- package/dist/internal/oidc-token.d.ts.map +1 -0
- package/dist/internal/oidc-token.js +351 -0
- package/dist/internal/register-author-key.d.ts +44 -0
- package/dist/internal/register-author-key.d.ts.map +1 -0
- package/dist/internal/register-author-key.js +212 -0
- package/dist/local-keys.d.ts +47 -0
- package/dist/local-keys.d.ts.map +1 -0
- package/dist/local-keys.js +115 -0
- package/dist/mcp-backend.d.ts +356 -0
- package/dist/mcp-backend.d.ts.map +1 -0
- package/dist/mcp-backend.js +668 -0
- package/dist/paths.d.ts +77 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +119 -0
- package/dist/persistent-secrets.d.ts +28 -0
- package/dist/persistent-secrets.d.ts.map +1 -0
- package/dist/persistent-secrets.js +94 -0
- package/dist/persistent-short-code-index.d.ts +14 -0
- package/dist/persistent-short-code-index.d.ts.map +1 -0
- package/dist/persistent-short-code-index.js +39 -0
- package/dist/persistent-stores.d.ts +12 -0
- package/dist/persistent-stores.d.ts.map +1 -0
- package/dist/persistent-stores.js +42 -0
- package/dist/serve-command.d.ts +500 -0
- package/dist/serve-command.d.ts.map +1 -0
- package/dist/serve-command.js +616 -0
- package/dist/serve-fallback.d.ts +64 -0
- package/dist/serve-fallback.d.ts.map +1 -0
- package/dist/serve-fallback.js +122 -0
- package/dist/spawn-cloudflared.d.ts +61 -0
- package/dist/spawn-cloudflared.d.ts.map +1 -0
- package/dist/spawn-cloudflared.js +167 -0
- package/dist/theme-command.d.ts +4 -0
- package/dist/theme-command.d.ts.map +1 -0
- package/dist/theme-command.js +154 -0
- package/dist/theme-file-uploader.d.ts +8 -0
- package/dist/theme-file-uploader.d.ts.map +1 -0
- package/dist/theme-file-uploader.js +58 -0
- package/dist/theme-writer.d.ts +8 -0
- package/dist/theme-writer.d.ts.map +1 -0
- package/dist/theme-writer.js +103 -0
- package/dist/tunnel-provider.d.ts +273 -0
- package/dist/tunnel-provider.d.ts.map +1 -0
- package/dist/tunnel-provider.js +142 -0
- package/package.json +81 -0
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
import { launchBrowser } from './dev-command.js';
|
|
2
|
+
/**
|
|
3
|
+
* Default bind port. Distinct from `ggui dev`'s 6780 so both can run
|
|
4
|
+
* simultaneously without colliding. Not a registered port; chosen for
|
|
5
|
+
* memorability + non-collision with the dev hub.
|
|
6
|
+
*/
|
|
7
|
+
export const DEFAULT_SERVE_PORT = 6781;
|
|
8
|
+
/** Default bind host. Loopback only — matches `ggui dev`. */
|
|
9
|
+
export const DEFAULT_SERVE_HOST = '127.0.0.1';
|
|
10
|
+
/**
|
|
11
|
+
* Parse the raw `ggui serve` argv tail. Returns a discriminated
|
|
12
|
+
* shape — `error` field is non-undefined on malformed input.
|
|
13
|
+
*
|
|
14
|
+
* Flag surface per the §10.2a lock:
|
|
15
|
+
* - `--port <n>` / `--host <addr>`: bind target.
|
|
16
|
+
* - `--mcp-only`: opt-down to MCP-only.
|
|
17
|
+
* - `--all`: REJECTED with a pointer to the real default. See doc lock.
|
|
18
|
+
* - `--help` / `-h`: help sentinel.
|
|
19
|
+
*/
|
|
20
|
+
export function parseServeFlags(args) {
|
|
21
|
+
const out = {
|
|
22
|
+
port: DEFAULT_SERVE_PORT,
|
|
23
|
+
host: DEFAULT_SERVE_HOST,
|
|
24
|
+
mcpOnly: false,
|
|
25
|
+
devAllowAll: false,
|
|
26
|
+
publicDemo: false,
|
|
27
|
+
multiTenant: false,
|
|
28
|
+
oauth: false,
|
|
29
|
+
};
|
|
30
|
+
for (let i = 0; i < args.length; i++) {
|
|
31
|
+
const arg = args[i];
|
|
32
|
+
if (arg === '--port') {
|
|
33
|
+
const v = args[++i];
|
|
34
|
+
if (v === undefined) {
|
|
35
|
+
return { ...out, error: '--port requires a value' };
|
|
36
|
+
}
|
|
37
|
+
const n = Number(v);
|
|
38
|
+
if (!Number.isInteger(n) || n < 0 || n > 65535) {
|
|
39
|
+
return {
|
|
40
|
+
...out,
|
|
41
|
+
error: `--port must be an integer in [0, 65535], got "${v}"`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
out.port = n;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (arg === '--host') {
|
|
48
|
+
const v = args[++i];
|
|
49
|
+
if (v === undefined || v === '') {
|
|
50
|
+
return { ...out, error: '--host requires a value' };
|
|
51
|
+
}
|
|
52
|
+
out.host = v;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (arg === '--mcp-only') {
|
|
56
|
+
out.mcpOnly = true;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (arg === '--dev-allow-all') {
|
|
60
|
+
out.devAllowAll = true;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (arg === '--public-demo') {
|
|
64
|
+
out.publicDemo = true;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (arg === '--multi-tenant') {
|
|
68
|
+
out.multiTenant = true;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (arg === '--oauth') {
|
|
72
|
+
out.oauth = true;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (arg === '--public-base-url') {
|
|
76
|
+
const v = args[++i];
|
|
77
|
+
if (v === undefined || v === '') {
|
|
78
|
+
return { ...out, error: '--public-base-url requires a value' };
|
|
79
|
+
}
|
|
80
|
+
if (!/^https?:\/\//.test(v)) {
|
|
81
|
+
return {
|
|
82
|
+
...out,
|
|
83
|
+
error: `--public-base-url must start with http:// or https://, got "${v}"`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
// Strip trailing slash so callers don't have to think about
|
|
87
|
+
// `${baseUrl}/s/` vs `${baseUrl}//s/` — both are common typos.
|
|
88
|
+
out.publicBaseUrl = v.replace(/\/+$/, '');
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (arg === '--public') {
|
|
92
|
+
out.publicAutoTunnel = true;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (arg === '--i-know-its-public') {
|
|
96
|
+
out.iKnowItsPublic = true;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (arg === '--no-open') {
|
|
100
|
+
out.noOpen = true;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (arg === '--mcp-instructions') {
|
|
104
|
+
const v = args[++i];
|
|
105
|
+
if (v === undefined || v === '') {
|
|
106
|
+
return { ...out, error: '--mcp-instructions requires a value' };
|
|
107
|
+
}
|
|
108
|
+
if (v !== 'default' &&
|
|
109
|
+
v !== 'aggressive' &&
|
|
110
|
+
v !== 'always' &&
|
|
111
|
+
v !== 'minimal' &&
|
|
112
|
+
v !== 'off') {
|
|
113
|
+
return {
|
|
114
|
+
...out,
|
|
115
|
+
error: `--mcp-instructions must be one of [default, aggressive, always, minimal, off], got "${v}"`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
out.mcpInstructions = v;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (arg === '--keys-file') {
|
|
122
|
+
const v = args[++i];
|
|
123
|
+
if (v === undefined || v === '') {
|
|
124
|
+
return { ...out, error: '--keys-file requires a value' };
|
|
125
|
+
}
|
|
126
|
+
out.keysFile = v;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (arg === '--admin-token') {
|
|
130
|
+
const v = args[++i];
|
|
131
|
+
if (v === undefined || v === '') {
|
|
132
|
+
return { ...out, error: '--admin-token requires a value' };
|
|
133
|
+
}
|
|
134
|
+
out.adminToken = v;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (arg === '--ephemeral') {
|
|
138
|
+
out.ephemeral = true;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (arg === '--all') {
|
|
142
|
+
// §10.2a lock: --all is NOT a first-class flag. The default
|
|
143
|
+
// already runs everything. Reject with a pointer so users
|
|
144
|
+
// learn the real mental model in one interaction.
|
|
145
|
+
return {
|
|
146
|
+
...out,
|
|
147
|
+
error: '--all is not a flag. The default already runs everything (MCP + agent). ' +
|
|
148
|
+
'Use --mcp-only to run just the MCP server.',
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if (arg === '--help' || arg === '-h') {
|
|
152
|
+
return { ...out, error: '__help__' };
|
|
153
|
+
}
|
|
154
|
+
return { ...out, error: `unknown option "${arg ?? ''}"` };
|
|
155
|
+
}
|
|
156
|
+
if (out.devAllowAll && out.publicDemo) {
|
|
157
|
+
return {
|
|
158
|
+
...out,
|
|
159
|
+
error: '--dev-allow-all and --public-demo are mutually exclusive. ' +
|
|
160
|
+
'--dev-allow-all is for local-only development (no rate limit, ' +
|
|
161
|
+
'"DEV ALLOW-ALL" warning); --public-demo is for an operator-paid ' +
|
|
162
|
+
'public demo (rate-limited, "PUBLIC DEMO" copy). Pick one.',
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
if (out.multiTenant && (out.devAllowAll || out.publicDemo)) {
|
|
166
|
+
return {
|
|
167
|
+
...out,
|
|
168
|
+
error: '--multi-tenant is incompatible with --dev-allow-all and --public-demo. ' +
|
|
169
|
+
'Multi-tenant requires real per-user identities (kind:"user" / kind:"app"); ' +
|
|
170
|
+
'the any-bearer modes collapse every caller to kind:"builder" which the ' +
|
|
171
|
+
'multi-tenant gate rejects. Drop the other flag and pair with real bearers.',
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
if (out.publicAutoTunnel && out.publicBaseUrl !== undefined) {
|
|
175
|
+
return {
|
|
176
|
+
...out,
|
|
177
|
+
error: '--public and --public-base-url are mutually exclusive. ' +
|
|
178
|
+
'--public auto-spawns a cloudflared quick-tunnel and sets the URL; ' +
|
|
179
|
+
'--public-base-url pins an explicit URL. Pick one.',
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
if (out.publicAutoTunnel && out.devAllowAll && !out.iKnowItsPublic) {
|
|
183
|
+
return {
|
|
184
|
+
...out,
|
|
185
|
+
error: '--public + --dev-allow-all exposes any-bearer auth to the open ' +
|
|
186
|
+
'internet via cloudflared. Add --i-know-its-public to acknowledge ' +
|
|
187
|
+
'this, or pair real bearers and drop --dev-allow-all.',
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
return out;
|
|
191
|
+
}
|
|
192
|
+
export function describeServeBanner(input) {
|
|
193
|
+
const httpUrl = `http://${input.host}:${input.port}`;
|
|
194
|
+
const lines = [
|
|
195
|
+
'',
|
|
196
|
+
` ${input.serverName} v${input.serverVersion} — community edition · open self-hosted personal-mode`,
|
|
197
|
+
'',
|
|
198
|
+
// Operator-facing URL goes first — it's the "open a browser here"
|
|
199
|
+
// signal that closes the first-run story. Landing page lives at `/`
|
|
200
|
+
// by default when `ggui serve` is the entry point (see
|
|
201
|
+
// `cli.ts::buildMcpServerBackend`).
|
|
202
|
+
` open → ${httpUrl}/`,
|
|
203
|
+
` mcp → ${httpUrl}/mcp`,
|
|
204
|
+
` health → ${httpUrl}/ggui/health`,
|
|
205
|
+
` tools → ${input.toolCount} registered`,
|
|
206
|
+
` agent → ${describeAgentStatus(input.agent)}`,
|
|
207
|
+
...(input.embeddingModel
|
|
208
|
+
? [` rag → ${input.embeddingModel} (cached at ~/.ggui/models/)`]
|
|
209
|
+
: []),
|
|
210
|
+
];
|
|
211
|
+
if (input.publicBaseUrl) {
|
|
212
|
+
lines.push(` public → ${input.publicBaseUrl}/`);
|
|
213
|
+
}
|
|
214
|
+
if (input.pairCode && !input.devAllowAll && !input.publicDemo) {
|
|
215
|
+
// Pair code is meaningless under any-bearer-auth modes (every
|
|
216
|
+
// bearer already authenticates); skip it to keep the banner
|
|
217
|
+
// truthful. Visual + machine-readable pairing affordance. The
|
|
218
|
+
// machine-readable `PAIR_CODE <code>` line is emitted separately
|
|
219
|
+
// by `runServe` before the banner; this line is operator-facing.
|
|
220
|
+
lines.push(` pair code → ${input.pairCode} (valid ~10m)`);
|
|
221
|
+
}
|
|
222
|
+
if (input.adminToken) {
|
|
223
|
+
// Admin token gates the console `/keys` plane (list / mint /
|
|
224
|
+
// revoke). Operator pastes it into the `/admin-login` page on
|
|
225
|
+
// first visit. Printed in the same column as the pair-code so
|
|
226
|
+
// both bearers are obvious in the boot banner.
|
|
227
|
+
lines.push(` admin token → ${input.adminToken} (console /keys gate — paste at /admin-login)`);
|
|
228
|
+
}
|
|
229
|
+
if (input.noLlmKey) {
|
|
230
|
+
// First-run nudge — without an LLM key, ggui_push falls back to
|
|
231
|
+
// codeReady:false and the iframe never bootstraps. Surface the
|
|
232
|
+
// /settings URL alongside admin-token so the operator can paste a
|
|
233
|
+
// key without restarting (the file store is hot-read on every
|
|
234
|
+
// generation; setting through /settings takes effect immediately).
|
|
235
|
+
const settingsUrl = input.publicBaseUrl
|
|
236
|
+
? `${input.publicBaseUrl}/settings`
|
|
237
|
+
: `${httpUrl}/settings`;
|
|
238
|
+
lines.push(` ⚠ no LLM key configured. paste one at ${settingsUrl}`, ` (admin-gated — use the admin token above) or export`, ` ANTHROPIC_API_KEY / OPENAI_API_KEY / GOOGLE_API_KEY /`, ` OPENROUTER_API_KEY before re-running.`);
|
|
239
|
+
}
|
|
240
|
+
lines.push('');
|
|
241
|
+
if (input.devAllowAll) {
|
|
242
|
+
// Loud warning — `--dev-allow-all` accepts any bearer (including
|
|
243
|
+
// the no-bearer probe MCP custom connectors send) as builder.
|
|
244
|
+
// Operators should never expose this surface to the open internet
|
|
245
|
+
// outside an ephemeral tunnel for connector smoke testing.
|
|
246
|
+
lines.push(` ⚠ DEV ALLOW-ALL: every bearer authenticates as builder.`, ` For local dev / tunnel smoke ONLY. Never expose to the open`, ` internet. Drop --dev-allow-all to switch back to strict auth.`);
|
|
247
|
+
}
|
|
248
|
+
else if (input.publicDemo) {
|
|
249
|
+
// Distinct from --dev-allow-all: public-demo is a deliberate
|
|
250
|
+
// operator posture, not an escape hatch. Surfaces (a) cost
|
|
251
|
+
// attribution (the operator's BYOK funds every visitor's
|
|
252
|
+
// generations) and (b) the rate-limit ceiling so the operator
|
|
253
|
+
// remembers what protection is in place if the demo blows up.
|
|
254
|
+
lines.push(` ⚠ PUBLIC DEMO: every bearer authenticates as builder.`, ` End-user generations bill the operator's LLM key in`, ` ~/.ggui/credentials.json. Per-IP rate limit on ggui_push`, ` bounds abuse. Drop --public-demo for strict auth.`);
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
lines.push(` auth: strict — only pair-minted bearer tokens authenticate /mcp.`, ` Pair via the code above (landing page or \`POST /pair\`).`, ` Override the adapter via GguiServer({ auth }) in code for`, ` custom deployments (OIDC, Cognito, etc.).`);
|
|
258
|
+
if (input.multiTenant) {
|
|
259
|
+
lines.push(``, ` multi-tenant: each authenticated user manages their OWN LLM`, ` keys at /settings — the gate uses the request's bearer`, ` (Authorization: Bearer their-paired-token), NOT the`, ` admin token. Per-user scope is auto-derived from the`, ` identity (userId / appId).`, ` Note: ggui_push generation still resolves BYOK at the`, ` operator scope (env + credentials-file 'global'). Per-`, ` user generation-time BYOK is a follow-up — for now,`, ` multi-tenant lets each user MANAGE their key plane`, ` without granting them the operator's admin token.`);
|
|
260
|
+
}
|
|
261
|
+
if (input.oauth) {
|
|
262
|
+
lines.push(``, ` oauth: enabled — MCP custom-connector hosts (claude.ai etc.)`, ` can discover via /.well-known/oauth-* and complete the`, ` auth dance against /oauth/{authorize,token,register}.`, ` Paste the paired bearer on the consent page.`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
lines.push('', ` Ctrl-C to stop.`, '');
|
|
266
|
+
return lines;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Banner cell for the `agent` line. Pure — tests pin the copy.
|
|
270
|
+
*/
|
|
271
|
+
export function describeAgentStatus(status) {
|
|
272
|
+
if (status.kind === 'running') {
|
|
273
|
+
const runner = status.language === 'ts' ? 'node --import=tsx' : 'node';
|
|
274
|
+
return `${status.entry} (${runner}) — running`;
|
|
275
|
+
}
|
|
276
|
+
switch (status.reason) {
|
|
277
|
+
case '--mcp-only':
|
|
278
|
+
return `disabled (--mcp-only)`;
|
|
279
|
+
case 'no ggui.json':
|
|
280
|
+
return `disabled (no ggui.json)`;
|
|
281
|
+
case 'ggui.json has no agent.entry':
|
|
282
|
+
return `disabled (ggui.json has no agent.entry)`;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Boot the backend, start the agent (if any), print the banner, then
|
|
287
|
+
* block until the shutdown signal fires. On signal, stops both
|
|
288
|
+
* components gracefully and resolves.
|
|
289
|
+
*
|
|
290
|
+
* Agent-crash policy (§10.2a lock): a crash AFTER boot surfaces as
|
|
291
|
+
* an `onEvent({type:'status', status:'crashed'})` delivery — the
|
|
292
|
+
* serve loop keeps MCP running and waits for shutdown. No auto-
|
|
293
|
+
* restart in v1. Operators compose restart policies via their own
|
|
294
|
+
* supervisor.
|
|
295
|
+
*
|
|
296
|
+
* MCP failure surfaces as a thrown error from `backendFactory()` /
|
|
297
|
+
* `backend.listen()` and propagates to `cli.ts` (exit 1).
|
|
298
|
+
*
|
|
299
|
+
* Pure enough to unit-test: the HTTP binding, signal wiring, and
|
|
300
|
+
* banner render are all either injected or pinned by
|
|
301
|
+
* {@link describeServeBanner} / {@link describeAgentStatus}.
|
|
302
|
+
*/
|
|
303
|
+
export async function runServe(opts) {
|
|
304
|
+
const backend = opts.backendFactory();
|
|
305
|
+
const boundPort = await backend.listen(opts.flags.port, opts.flags.host);
|
|
306
|
+
// Start the agent AFTER the MCP server is listening. Rationale:
|
|
307
|
+
// most agents call MCP as soon as they boot; binding first means
|
|
308
|
+
// the agent's first connect always succeeds. If the agent's
|
|
309
|
+
// start() throws, we've still bound MCP and can surface the agent
|
|
310
|
+
// error cleanly without leaving an orphan HTTP server.
|
|
311
|
+
let agentHandle = null;
|
|
312
|
+
let unsubscribeAgent = null;
|
|
313
|
+
if (opts.agent) {
|
|
314
|
+
const supervision = opts.agent;
|
|
315
|
+
try {
|
|
316
|
+
agentHandle = await supervision.adapter.start(supervision.startInput);
|
|
317
|
+
if (supervision.onEvent) {
|
|
318
|
+
unsubscribeAgent = agentHandle.subscribe(supervision.onEvent);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
catch (err) {
|
|
322
|
+
// Start-time failure is a permanent config error per the
|
|
323
|
+
// AgentRuntimeAdapter contract. Clean up the bound server so
|
|
324
|
+
// we don't leak a socket.
|
|
325
|
+
await backend.close();
|
|
326
|
+
throw err;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
// Pre-mint an initial pairing code so a fresh operator's first-run
|
|
330
|
+
// flow has a concrete starting point. Without this, `/admin/pair/
|
|
331
|
+
// init` is gated by a builder bearer the operator doesn't have yet
|
|
332
|
+
// under strict auth — a chicken-and-egg that only devAllowAll hid.
|
|
333
|
+
// `activeInit()` on the landing page + the `PAIR_CODE` boot beacon
|
|
334
|
+
// below both read from the resulting pending code. Absent pairing
|
|
335
|
+
// (future embedding host opts out) → skip the mint + beacon + the
|
|
336
|
+
// banner line, leaving the operator on whatever bearer path their
|
|
337
|
+
// custom adapter provides.
|
|
338
|
+
//
|
|
339
|
+
// Skipped under `--dev-allow-all` — every bearer authenticates as
|
|
340
|
+
// builder there, so a pair code would be cargo-cult dressing on a
|
|
341
|
+
// permissive auth surface and harnesses parsing `PAIR_CODE` would
|
|
342
|
+
// get a value that's not actually load-bearing.
|
|
343
|
+
let initialPair;
|
|
344
|
+
if (backend.pairingService && !opts.flags.devAllowAll && !opts.flags.publicDemo) {
|
|
345
|
+
initialPair = await backend.pairingService.initPairing();
|
|
346
|
+
}
|
|
347
|
+
// Machine-readable ready beacon. Stable single-line format for
|
|
348
|
+
// test harnesses + supervisors; goes out BEFORE the pretty banner
|
|
349
|
+
// so consumers can gate on it without grepping multi-line output.
|
|
350
|
+
// Format: `READY <baseUrl>\n`.
|
|
351
|
+
opts.stdout.write(`READY http://${opts.flags.host}:${boundPort}\n`);
|
|
352
|
+
if (initialPair) {
|
|
353
|
+
// Paired beacon for harnesses — same stable-line contract as READY.
|
|
354
|
+
// Format: `PAIR_CODE <code>\n`. Expiry is implicit via the ~10m
|
|
355
|
+
// TTL the `InMemoryPairingService` ships with; harnesses that
|
|
356
|
+
// need it can read `GET /ggui/console/info#pairing.pending`.
|
|
357
|
+
opts.stdout.write(`PAIR_CODE ${initialPair.code}\n`);
|
|
358
|
+
}
|
|
359
|
+
if (backend.adminToken) {
|
|
360
|
+
// Machine-readable admin-token beacon — same stable-line contract
|
|
361
|
+
// as READY + PAIR_CODE. Harnesses pre-populate the console admin
|
|
362
|
+
// cookie by reading this off stdout.
|
|
363
|
+
opts.stdout.write(`ADMIN_TOKEN ${backend.adminToken}\n`);
|
|
364
|
+
}
|
|
365
|
+
const bannerLines = describeServeBanner({
|
|
366
|
+
port: boundPort,
|
|
367
|
+
host: opts.flags.host,
|
|
368
|
+
toolCount: backend.toolCount,
|
|
369
|
+
serverName: backend.serverName,
|
|
370
|
+
serverVersion: backend.serverVersion,
|
|
371
|
+
agent: opts.agentStatus,
|
|
372
|
+
devAllowAll: opts.flags.devAllowAll,
|
|
373
|
+
publicDemo: opts.flags.publicDemo,
|
|
374
|
+
multiTenant: opts.flags.multiTenant,
|
|
375
|
+
oauth: opts.flags.oauth,
|
|
376
|
+
...(opts.flags.publicBaseUrl !== undefined
|
|
377
|
+
? { publicBaseUrl: opts.flags.publicBaseUrl }
|
|
378
|
+
: {}),
|
|
379
|
+
...(initialPair
|
|
380
|
+
? {
|
|
381
|
+
pairCode: initialPair.code,
|
|
382
|
+
pairCodeExpiresAt: initialPair.codeExpiresAt,
|
|
383
|
+
}
|
|
384
|
+
: {}),
|
|
385
|
+
...(backend.adminToken ? { adminToken: backend.adminToken } : {}),
|
|
386
|
+
...(opts.noLlmKey ? { noLlmKey: true } : {}),
|
|
387
|
+
...(backend.embeddingModel ? { embeddingModel: backend.embeddingModel } : {}),
|
|
388
|
+
});
|
|
389
|
+
opts.stdout.write(`${bannerLines.join('\n')}\n`);
|
|
390
|
+
// Auto-open the operator's browser at the local URL. The server's
|
|
391
|
+
// `landingRedirect` then routes first-run operators (no LLM creds)
|
|
392
|
+
// to the admin onboarding flow at `/admin-login?next=/admin/llm-keys`,
|
|
393
|
+
// so the URL bar lands on the right page without the CLI needing
|
|
394
|
+
// to know which target to pick. Skipped for: `--no-open`, non-TTY
|
|
395
|
+
// (CI / supervised / piped), `--mcp-only` (no UI surface to open).
|
|
396
|
+
// Errors are swallowed — the banner already shows the URL so the
|
|
397
|
+
// operator can copy/paste if launchBrowser fails (xdg-open missing,
|
|
398
|
+
// etc.).
|
|
399
|
+
const shouldAutoOpenServe = !opts.flags.noOpen
|
|
400
|
+
&& !opts.flags.mcpOnly
|
|
401
|
+
&& opts.stdout === process.stdout
|
|
402
|
+
&& process.stdout.isTTY === true;
|
|
403
|
+
if (shouldAutoOpenServe) {
|
|
404
|
+
const localUrl = `http://${opts.flags.host}:${boundPort}`;
|
|
405
|
+
const result = launchBrowser(localUrl);
|
|
406
|
+
opts.stdout.write(result.ok
|
|
407
|
+
? `\n opening browser → ${localUrl}\n`
|
|
408
|
+
: `\n (couldn't auto-open browser: ${result.error})\n`);
|
|
409
|
+
}
|
|
410
|
+
const stopAll = async () => {
|
|
411
|
+
// Stop the agent BEFORE the server so the agent doesn't try
|
|
412
|
+
// one last call into an already-torn-down MCP surface. Swallow
|
|
413
|
+
// stop errors — the log has already delivered the crash signal
|
|
414
|
+
// if there was one, and the server close is the priority.
|
|
415
|
+
if (unsubscribeAgent)
|
|
416
|
+
unsubscribeAgent();
|
|
417
|
+
unsubscribeAgent = null;
|
|
418
|
+
if (agentHandle) {
|
|
419
|
+
try {
|
|
420
|
+
await agentHandle.stop();
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
/* best-effort */
|
|
424
|
+
}
|
|
425
|
+
agentHandle = null;
|
|
426
|
+
}
|
|
427
|
+
await backend.close();
|
|
428
|
+
};
|
|
429
|
+
if (opts.shutdownSignal.aborted) {
|
|
430
|
+
await stopAll();
|
|
431
|
+
return 0;
|
|
432
|
+
}
|
|
433
|
+
await new Promise((resolve) => {
|
|
434
|
+
opts.shutdownSignal.addEventListener('abort', () => {
|
|
435
|
+
resolve();
|
|
436
|
+
}, { once: true });
|
|
437
|
+
});
|
|
438
|
+
await stopAll();
|
|
439
|
+
return 0;
|
|
440
|
+
}
|
|
441
|
+
export const SERVE_HELP = `ggui serve — run the open self-hosted personal-mode app
|
|
442
|
+
|
|
443
|
+
Boots the @ggui-ai/mcp-server binding with persistent defaults AND
|
|
444
|
+
(by default) supervises your agent alongside it. The current MCP
|
|
445
|
+
handler surface is the blueprint-read family (search / list_featured
|
|
446
|
+
/ render); more tools come online as @ggui-ai/mcp-server-handlers
|
|
447
|
+
grows.
|
|
448
|
+
|
|
449
|
+
First-run bundle (all on by default):
|
|
450
|
+
- Landing page at / — operator opens a browser and sees
|
|
451
|
+
server identity + pair-code card.
|
|
452
|
+
- Session viewer at /r/<code> — same-origin viewer for sessions
|
|
453
|
+
minted by ggui_push; same-origin
|
|
454
|
+
HTTP-only cookie authenticates the
|
|
455
|
+
live-channel /ws upgrade.
|
|
456
|
+
- POST /pair + POST /admin/pair/init — pairing endpoint for MCP
|
|
457
|
+
hosts and third-party clients.
|
|
458
|
+
- Live-channel /ws — live session plane for MCP Apps
|
|
459
|
+
iframes and the console.
|
|
460
|
+
|
|
461
|
+
Embedding hosts that want a different shape (no landing page, no
|
|
462
|
+
pairing, programmatic control) should compose createGguiServer()
|
|
463
|
+
directly in code rather than invoking \`ggui serve\`.
|
|
464
|
+
|
|
465
|
+
Usage:
|
|
466
|
+
ggui serve [options]
|
|
467
|
+
|
|
468
|
+
Options:
|
|
469
|
+
--port <n> Bind port (default: ${DEFAULT_SERVE_PORT}, 0 = OS-assigned).
|
|
470
|
+
--host <addr> Bind host (default: ${DEFAULT_SERVE_HOST}).
|
|
471
|
+
--mcp-only Run just the MCP server; do not supervise an agent.
|
|
472
|
+
--dev-allow-all Accept any non-empty bearer (incl. no bearer) as
|
|
473
|
+
builder. Local-dev / tunnel smoke ONLY — never
|
|
474
|
+
expose to the open internet.
|
|
475
|
+
--public-demo Public-facing demo posture: same any-bearer auth
|
|
476
|
+
as --dev-allow-all, plus per-IP rate limit on
|
|
477
|
+
ggui_push (30 generations / 10 min) and a
|
|
478
|
+
"PUBLIC DEMO — operator pays" banner. Mutually
|
|
479
|
+
exclusive with --dev-allow-all.
|
|
480
|
+
--multi-tenant Multi-tenant posture: switches the /settings LLM-
|
|
481
|
+
keys gate from admin-token to auth-adapter so
|
|
482
|
+
each authenticated end-user manages their OWN
|
|
483
|
+
key plane (scope = userId/appId). kind:'builder'
|
|
484
|
+
identities are rejected at the gate. Strict-auth
|
|
485
|
+
only; mutually exclusive with --dev-allow-all
|
|
486
|
+
and --public-demo. (Generation-time BYOK still
|
|
487
|
+
resolves at the operator 'global' scope — a
|
|
488
|
+
follow-up will thread per-user BYOK through
|
|
489
|
+
ggui_push.)
|
|
490
|
+
--public-base-url <u> Override the public base URL used to compose
|
|
491
|
+
iframe-runtime + shortCode URLs. Set this to a
|
|
492
|
+
tunnel URL (https://<random>.trycloudflare.com)
|
|
493
|
+
when testing against a remote MCP host so URLs
|
|
494
|
+
resolve from the host's perspective. Without
|
|
495
|
+
this, URLs derive from --host:--port and only
|
|
496
|
+
work from the same machine.
|
|
497
|
+
--oauth Mount OAuth 2.1 + PKCE + Dynamic Client
|
|
498
|
+
Registration routes (.well-known/oauth-* +
|
|
499
|
+
/oauth/{authorize,token,register}). Required
|
|
500
|
+
for MCP custom-connector hosts (claude.ai,
|
|
501
|
+
ChatGPT) whose Add Connector form has no field
|
|
502
|
+
for a pre-shared bearer; OAuth lets them do
|
|
503
|
+
a discovery → DCR → consent dance ending in
|
|
504
|
+
the same paired bearer the strict-auth flow
|
|
505
|
+
already mints. Pure-bearer clients (Claude
|
|
506
|
+
Desktop with bearer in config) work without it.
|
|
507
|
+
--admin-token <t> Pin the admin bearer that gates the console
|
|
508
|
+
/keys plane (list / mint / revoke pairings +
|
|
509
|
+
/admin-login route). Without this, the server
|
|
510
|
+
mints a fresh ggui_admin_* token per boot and
|
|
511
|
+
prints it on the banner. Pin a stable value
|
|
512
|
+
when you want the bearer to survive restarts
|
|
513
|
+
(e.g. paste once into a password manager).
|
|
514
|
+
--ephemeral Disable cross-restart persistence. Without
|
|
515
|
+
this flag, the HMAC secrets that sign MCP
|
|
516
|
+
Apps bootstrap tokens and signed render URLs
|
|
517
|
+
are read or minted under .ggui/persistent/
|
|
518
|
+
(project-local if a ggui.json was resolved,
|
|
519
|
+
else ~/.ggui/persistent/) so cached tokens
|
|
520
|
+
survive a server restart. Pass this flag to
|
|
521
|
+
keep restarts ephemeral (tests / CI / nuclear-
|
|
522
|
+
revoke). Override the dir via GGUI_PERSISTENT_DIR.
|
|
523
|
+
--help, -h Show this help.
|
|
524
|
+
|
|
525
|
+
Agent runtime:
|
|
526
|
+
The default boots your agent alongside MCP. The agent's entry file
|
|
527
|
+
comes from ggui.json:
|
|
528
|
+
|
|
529
|
+
{ "agent": { "entry": "./agent.ts" } }
|
|
530
|
+
|
|
531
|
+
Supported extensions: .js / .mjs / .cjs (node), .ts / .tsx / .mts
|
|
532
|
+
(node --import=tsx). No ggui.json, or no agent.entry, falls back
|
|
533
|
+
to MCP-only with a warning. Malformed ggui.json or a bad entry
|
|
534
|
+
is a hard error.
|
|
535
|
+
|
|
536
|
+
Agent crashes after startup are logged; the MCP server keeps
|
|
537
|
+
running. No auto-restart — compose that with your own supervisor.
|
|
538
|
+
|
|
539
|
+
Persistent storage (default-on; opt-out via --ephemeral):
|
|
540
|
+
By default \`ggui serve\` writes a small bundle under .ggui/persistent/
|
|
541
|
+
(project-local if a ggui.json was resolved, else ~/.ggui/persistent/)
|
|
542
|
+
so cached MCP Apps tokens, signed render URLs, shortCodes, sessions,
|
|
543
|
+
vectors, and paired bearers survive a restart — claude.ai chat-history
|
|
544
|
+
revisits keep working.
|
|
545
|
+
|
|
546
|
+
Bundle layout:
|
|
547
|
+
|
|
548
|
+
.ggui/persistent/
|
|
549
|
+
├── bootstrap-secret.hex (HMAC, 0600)
|
|
550
|
+
├── render-signer-secret.hex (HMAC, 0600)
|
|
551
|
+
├── short-codes.sqlite (/r/<code> + /api/bootstrap/<code> resolution)
|
|
552
|
+
├── sessions.sqlite (SessionStore — stack items + event history)
|
|
553
|
+
├── vectors.sqlite (RAG corpus)
|
|
554
|
+
└── keys.json (paired bearers)
|
|
555
|
+
|
|
556
|
+
Override the dir with \`GGUI_PERSISTENT_DIR\`. Pass \`--ephemeral\` to
|
|
557
|
+
skip the bundle entirely (tests, CI loops, nuclear-revoke).
|
|
558
|
+
|
|
559
|
+
Explicit ggui.json#storage declarations always win — declare
|
|
560
|
+
\`{ "sessions": { "driver": "memory" } }\` to opt sessions back to
|
|
561
|
+
in-memory while keeping other surfaces persistent. Custom sqlite
|
|
562
|
+
paths via ggui.json#storage are honored verbatim:
|
|
563
|
+
|
|
564
|
+
{
|
|
565
|
+
"storage": {
|
|
566
|
+
"sessions": { "driver": "sqlite", "path": "./ggui-sessions.sqlite" },
|
|
567
|
+
"vectors": { "driver": "sqlite", "path": "./ggui-vectors.sqlite" },
|
|
568
|
+
"threads": { "driver": "sqlite", "path": "./ggui-threads.sqlite" }
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
Threads: the persistent-chat route family (/threads) is opt-in.
|
|
573
|
+
Absent \`storage.threads\` leaves the routes unmounted — pairs
|
|
574
|
+
with Portal's self-hosted origins only when declared. Driver
|
|
575
|
+
semantics:
|
|
576
|
+
- \`driver: "sqlite"\` — durable. Threads survive restart.
|
|
577
|
+
/ggui/health reports \`threads.durability: "durable"\`;
|
|
578
|
+
Portal hides its non-durable caveat.
|
|
579
|
+
- \`driver: "memory"\` — routes mount but data is lost on
|
|
580
|
+
restart. /ggui/health reports \`threads.durability:
|
|
581
|
+
"ephemeral"\`; Portal shows its caveat.
|
|
582
|
+
|
|
583
|
+
Paths resolve relative to the ggui.json directory. The sqlite
|
|
584
|
+
driver needs \`better-sqlite3\` installed in your project.
|
|
585
|
+
|
|
586
|
+
Recommended setups:
|
|
587
|
+
|
|
588
|
+
Local-only smoke (no tunnel, no remote MCP host):
|
|
589
|
+
ggui serve
|
|
590
|
+
|
|
591
|
+
claude.ai custom connector (over a public tunnel):
|
|
592
|
+
1. Tunnel: cloudflared tunnel --url http://localhost:6781
|
|
593
|
+
2. ggui serve --oauth \\
|
|
594
|
+
--public-base-url https://<tunnel>.trycloudflare.com
|
|
595
|
+
3. Note PAIR_CODE from boot. Visit the landing page (the public URL
|
|
596
|
+
above), complete pairing, save the bearer.
|
|
597
|
+
4. claude.ai → Settings → Connectors → Add custom connector.
|
|
598
|
+
URL: https://<tunnel>.trycloudflare.com/mcp
|
|
599
|
+
Client ID / Secret: leave empty (server uses Dynamic Client
|
|
600
|
+
Registration; no pre-shared secret needed).
|
|
601
|
+
5. Click Connect → claude.ai redirects you to a paste-key page.
|
|
602
|
+
Paste the bearer from step 3. Done.
|
|
603
|
+
|
|
604
|
+
Quick local-dev WITHOUT auth (NEVER over a public tunnel):
|
|
605
|
+
ggui serve --dev-allow-all
|
|
606
|
+
|
|
607
|
+
Current limits:
|
|
608
|
+
- Strict-auth only: /mcp rejects any bearer that isn't pair-minted.
|
|
609
|
+
The CLI pre-mints one pairing code on boot (printed as
|
|
610
|
+
\`PAIR_CODE <code>\` and shown in the banner + on the landing
|
|
611
|
+
page). Complete \`POST /pair {code, deviceName}\` to receive a
|
|
612
|
+
bearer you can use with MCP. Embed @ggui-ai/mcp-server in your
|
|
613
|
+
own entrypoint to inject a different AuthAdapter (OIDC, Cognito,
|
|
614
|
+
etc.) for production.
|
|
615
|
+
- Single-tenant: every request scopes to one \`builder\` app id.
|
|
616
|
+
`;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { GguiJsonLoadError, type GguiJsonV1 } from '@ggui-ai/project-config/node';
|
|
2
|
+
import type { AgentStatus, ServeAgentSupervision } from './serve-command.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolved state of the agent component at `ggui serve` boot time.
|
|
5
|
+
*
|
|
6
|
+
* - `status.kind === 'running'` → `supervision` populated; the
|
|
7
|
+
* runner supervises the agent.
|
|
8
|
+
* - `status.kind === 'disabled'` → `supervision` absent; the
|
|
9
|
+
* runner boots MCP-only with the banner surfacing the reason.
|
|
10
|
+
*
|
|
11
|
+
* `warnings` carries non-fatal messages the CLI prints BEFORE the
|
|
12
|
+
* banner (stderr). Hard errors (malformed ggui.json, bad entry
|
|
13
|
+
* extension) are thrown and caught by the CLI wrapper (exit 1).
|
|
14
|
+
*/
|
|
15
|
+
export interface ResolvedAgentPlan {
|
|
16
|
+
readonly status: AgentStatus;
|
|
17
|
+
readonly supervision?: ServeAgentSupervision;
|
|
18
|
+
readonly warnings: readonly string[];
|
|
19
|
+
/**
|
|
20
|
+
* Fully-parsed `ggui.json` when one was found (regardless of whether
|
|
21
|
+
* `agent.entry` is present). `null` when no manifest was found on the
|
|
22
|
+
* ancestor walk. CLI callers use this to resolve the storage block
|
|
23
|
+
* into concrete adapters via `resolveStorageFromConfig`.
|
|
24
|
+
*/
|
|
25
|
+
readonly manifest: GguiJsonV1 | null;
|
|
26
|
+
/**
|
|
27
|
+
* Absolute path to the manifest's parent directory — the canonical
|
|
28
|
+
* root for resolving relative paths declared in the manifest
|
|
29
|
+
* (`storage.sessions.path`, `agent.entry`, future additive fields).
|
|
30
|
+
* `null` when no manifest was found.
|
|
31
|
+
*/
|
|
32
|
+
readonly projectRoot: string | null;
|
|
33
|
+
}
|
|
34
|
+
export interface ResolveAgentPlanOptions {
|
|
35
|
+
/** `true` = operator opted out of agent supervision. */
|
|
36
|
+
readonly mcpOnly: boolean;
|
|
37
|
+
/** Starting directory for the ancestor walk to `ggui.json`. */
|
|
38
|
+
readonly cwd: string;
|
|
39
|
+
/**
|
|
40
|
+
* Event sink for the agent supervision. Tests pass a collector;
|
|
41
|
+
* production passes a function that writes `[agent …]` lines
|
|
42
|
+
* to `process.stderr`. Absent = silent.
|
|
43
|
+
*/
|
|
44
|
+
readonly onAgentEvent?: ServeAgentSupervision['onEvent'];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Apply the §10.2a matrix. Throws `GguiJsonLoadError` for cases 3
|
|
48
|
+
* (malformed JSON, schema failure) and a generic `Error` for case
|
|
49
|
+
* 4 (unsupported agent.entry extension / missing file hint).
|
|
50
|
+
*
|
|
51
|
+
* Returns a `ResolvedAgentPlan` for cases 1, 2, 5, and 6:
|
|
52
|
+
*
|
|
53
|
+
* 1. `--mcp-only` → disabled, silent
|
|
54
|
+
* 2. no ggui.json found → disabled, warn
|
|
55
|
+
* 5. ggui.json present, no agent.entry → disabled, warn
|
|
56
|
+
* 6. valid agent.entry → running, no warn
|
|
57
|
+
*/
|
|
58
|
+
export declare function resolveAgentPlan(opts: ResolveAgentPlanOptions): ResolvedAgentPlan;
|
|
59
|
+
/**
|
|
60
|
+
* Re-export for tests that want to assert on malformed-config
|
|
61
|
+
* handling without importing project-config transitively.
|
|
62
|
+
*/
|
|
63
|
+
export { GguiJsonLoadError };
|
|
64
|
+
//# sourceMappingURL=serve-fallback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve-fallback.d.ts","sourceRoot":"","sources":["../src/serve-fallback.ts"],"names":[],"mappings":"AAaA,OAAO,EAEL,iBAAiB,EAEjB,KAAK,UAAU,EAChB,MAAM,8BAA8B,CAAC;AAKtC,OAAO,KAAK,EACV,WAAW,EACX,qBAAqB,EACtB,MAAM,oBAAoB,CAAC;AAE5B;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,WAAW,CAAC,EAAE,qBAAqB,CAAC;IAC7C,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,UAAU,GAAG,IAAI,CAAC;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED,MAAM,WAAW,uBAAuB;IACtC,wDAAwD;IACxD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,qBAAqB,CAAC,SAAS,CAAC,CAAC;CAC1D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,uBAAuB,GAC5B,iBAAiB,CA+EnB;AAqBD;;;GAGG;AACH,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
|