@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.
- 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,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ggui provider-key set` — push a provider API key to a cloud ggui app.
|
|
3
|
+
*
|
|
4
|
+
* Resolves the target provider either from:
|
|
5
|
+
* - `--provider <name>` flag (explicit, validated against BYOK set)
|
|
6
|
+
* - `ggui.json#generation.model` (via `parseAnyLlmRoute` from @ggui-ai/protocol)
|
|
7
|
+
*
|
|
8
|
+
* Then reads the key from the appropriate environment variable and
|
|
9
|
+
* POSTs it to `POST /v1/apps/{appId}/provider-keys`.
|
|
10
|
+
*/
|
|
11
|
+
/* eslint-disable no-console */
|
|
12
|
+
import process from 'node:process';
|
|
13
|
+
import { parseAnyLlmRoute } from '@ggui-ai/protocol';
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
import { setAppProviderKey } from './api-client.js';
|
|
16
|
+
import { findGguiJson, readGguiJson } from './internal/ggui-json.js';
|
|
17
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
18
|
+
// BYOK provider set — bedrock is excluded (IAM-only, no plaintext key to push)
|
|
19
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
20
|
+
/** Providers that accept a user-supplied plaintext API key. */
|
|
21
|
+
export const BYOK_PROVIDERS = ['anthropic', 'openai', 'google', 'openrouter'];
|
|
22
|
+
function isByokProvider(s) {
|
|
23
|
+
return BYOK_PROVIDERS.includes(s);
|
|
24
|
+
}
|
|
25
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
26
|
+
// inferProvider
|
|
27
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
28
|
+
/**
|
|
29
|
+
* Resolve which LLM provider to push a key for.
|
|
30
|
+
*
|
|
31
|
+
* Priority:
|
|
32
|
+
* 1. `flagProvider` (explicit `--provider` flag) — validated against BYOK set.
|
|
33
|
+
* Bedrock → descriptive error. Unknown provider → error.
|
|
34
|
+
* 2. `ggui.json#generation.model` — parsed via `parseAnyLlmRoute` from
|
|
35
|
+
* `@ggui-ai/protocol`. Bedrock → error. Unparseable / absent → error.
|
|
36
|
+
*
|
|
37
|
+
* **Does NOT hand-roll a provider parser.** All model-string parsing goes
|
|
38
|
+
* through `parseAnyLlmRoute` (covers both canonical `provider:model` and
|
|
39
|
+
* LiteLLM `provider/model` forms).
|
|
40
|
+
*/
|
|
41
|
+
export function inferProvider(gguiJson, flagProvider) {
|
|
42
|
+
if (flagProvider !== undefined) {
|
|
43
|
+
if (flagProvider === 'bedrock') {
|
|
44
|
+
return { error: 'bedrock is platform/IAM-only — no BYOK key to push' };
|
|
45
|
+
}
|
|
46
|
+
if (!isByokProvider(flagProvider)) {
|
|
47
|
+
return {
|
|
48
|
+
error: `"${flagProvider}" is not a supported BYOK provider — must be one of: ${BYOK_PROVIDERS.join(', ')}`,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return flagProvider;
|
|
52
|
+
}
|
|
53
|
+
// No flag — derive from ggui.json#generation.model via parseAnyLlmRoute.
|
|
54
|
+
const schema = z.object({
|
|
55
|
+
generation: z
|
|
56
|
+
.object({
|
|
57
|
+
model: z.string().optional(),
|
|
58
|
+
})
|
|
59
|
+
.optional(),
|
|
60
|
+
});
|
|
61
|
+
const parsed = schema.safeParse(gguiJson);
|
|
62
|
+
const modelString = parsed.success ? parsed.data.generation?.model : undefined;
|
|
63
|
+
if (!modelString) {
|
|
64
|
+
return { error: 'no provider — pass --provider or set generation.model in ggui.json' };
|
|
65
|
+
}
|
|
66
|
+
const route = parseAnyLlmRoute(modelString);
|
|
67
|
+
if (!route) {
|
|
68
|
+
return { error: 'no provider — pass --provider or set generation.model in ggui.json' };
|
|
69
|
+
}
|
|
70
|
+
const provider = route.provider;
|
|
71
|
+
if (provider === 'bedrock') {
|
|
72
|
+
return { error: 'bedrock is platform/IAM-only — no BYOK key to push' };
|
|
73
|
+
}
|
|
74
|
+
if (!isByokProvider(provider)) {
|
|
75
|
+
// Defensive: covers any future LlmProvider added before BYOK_PROVIDERS is updated.
|
|
76
|
+
return { error: `"${provider}" is not a supported BYOK provider — pass --provider explicitly` };
|
|
77
|
+
}
|
|
78
|
+
return provider;
|
|
79
|
+
}
|
|
80
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
81
|
+
// Env-var helpers
|
|
82
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
83
|
+
/**
|
|
84
|
+
* Return the primary env-var name for a BYOK provider.
|
|
85
|
+
* For google the primary is `GOOGLE_API_KEY` (with `GEMINI_API_KEY` as
|
|
86
|
+
* fallback checked in `readKeyFromEnv`). Order matches `byok-resolver.ts`
|
|
87
|
+
* so that the local-dev key and the pushed key always agree.
|
|
88
|
+
*/
|
|
89
|
+
export function envVarForProvider(provider) {
|
|
90
|
+
switch (provider) {
|
|
91
|
+
case 'anthropic':
|
|
92
|
+
return 'ANTHROPIC_API_KEY';
|
|
93
|
+
case 'openai':
|
|
94
|
+
return 'OPENAI_API_KEY';
|
|
95
|
+
case 'google':
|
|
96
|
+
return 'GOOGLE_API_KEY';
|
|
97
|
+
case 'openrouter':
|
|
98
|
+
return 'OPENROUTER_API_KEY';
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Read the API key for a BYOK provider from the given env.
|
|
103
|
+
*
|
|
104
|
+
* - `anthropic` → `ANTHROPIC_API_KEY`
|
|
105
|
+
* - `openai` → `OPENAI_API_KEY`
|
|
106
|
+
* - `google` → `GOOGLE_API_KEY` (primary), then `GEMINI_API_KEY` (fallback)
|
|
107
|
+
* - `openrouter` → `OPENROUTER_API_KEY`
|
|
108
|
+
*
|
|
109
|
+
* Order matches `byok-resolver.ts` so that the key the cloud receives is
|
|
110
|
+
* always the same one local generation uses.
|
|
111
|
+
*
|
|
112
|
+
* Returns `undefined` when no non-empty value is found.
|
|
113
|
+
*/
|
|
114
|
+
export function readKeyFromEnv(provider, env = process.env) {
|
|
115
|
+
const candidates = provider === 'google'
|
|
116
|
+
? ['GOOGLE_API_KEY', 'GEMINI_API_KEY']
|
|
117
|
+
: [envVarForProvider(provider)];
|
|
118
|
+
for (const name of candidates) {
|
|
119
|
+
const val = env[name];
|
|
120
|
+
if (val && val.length > 0)
|
|
121
|
+
return val;
|
|
122
|
+
}
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
126
|
+
// Command runner
|
|
127
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
128
|
+
export const PROVIDER_KEY_HELP = `ggui provider-key — manage cloud provider API keys for a ggui app
|
|
129
|
+
|
|
130
|
+
Usage:
|
|
131
|
+
ggui provider-key set --app <appId> [--provider <name>]
|
|
132
|
+
|
|
133
|
+
Subcommands:
|
|
134
|
+
set Push a provider API key from the appropriate env var to the cloud app.
|
|
135
|
+
The provider is derived from ggui.json#generation.model unless
|
|
136
|
+
--provider is given explicitly.
|
|
137
|
+
|
|
138
|
+
Supported providers: anthropic, openai, google, openrouter.
|
|
139
|
+
(bedrock is IAM-only — no plaintext key to push.)
|
|
140
|
+
|
|
141
|
+
Reads the key from env:
|
|
142
|
+
ANTHROPIC_API_KEY (anthropic)
|
|
143
|
+
OPENAI_API_KEY (openai)
|
|
144
|
+
GOOGLE_API_KEY (google, primary); GEMINI_API_KEY (fallback)
|
|
145
|
+
OPENROUTER_API_KEY (openrouter)
|
|
146
|
+
|
|
147
|
+
Options:
|
|
148
|
+
--app <appId> Target app ID (required).
|
|
149
|
+
--provider <name> Override provider (default: derived from ggui.json).
|
|
150
|
+
--help, -h Show this help.
|
|
151
|
+
`;
|
|
152
|
+
/**
|
|
153
|
+
* Entry point for `ggui provider-key <subcommand> [options]`.
|
|
154
|
+
*
|
|
155
|
+
* @param args - argv slice after `provider-key` (e.g. `['set', '--app', 'app123']`)
|
|
156
|
+
* @param startDir - optional cwd override (primarily for tests)
|
|
157
|
+
* @returns exit code (0 = success, 1 = error)
|
|
158
|
+
*/
|
|
159
|
+
export async function runProviderKeyCommand(args, startDir = process.cwd()) {
|
|
160
|
+
if (args.includes('--help') || args.includes('-h') || args.length === 0) {
|
|
161
|
+
process.stdout.write(PROVIDER_KEY_HELP);
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
const [subcommand, ...rest] = args;
|
|
165
|
+
if (subcommand !== 'set') {
|
|
166
|
+
process.stderr.write(`ggui provider-key: unknown subcommand "${subcommand}"\n` +
|
|
167
|
+
` Run \`ggui provider-key --help\` for usage.\n`);
|
|
168
|
+
return 1;
|
|
169
|
+
}
|
|
170
|
+
// ── Parse flags ─────────────────────────────────────────────────────────
|
|
171
|
+
let appId;
|
|
172
|
+
let flagProvider;
|
|
173
|
+
for (let i = 0; i < rest.length; i++) {
|
|
174
|
+
const arg = rest[i];
|
|
175
|
+
if (arg === '--app' && i + 1 < rest.length) {
|
|
176
|
+
appId = rest[++i];
|
|
177
|
+
}
|
|
178
|
+
else if (arg === '--provider' && i + 1 < rest.length) {
|
|
179
|
+
flagProvider = rest[++i];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (!appId) {
|
|
183
|
+
process.stderr.write('ggui provider-key set: --app <appId> is required.\n');
|
|
184
|
+
return 1;
|
|
185
|
+
}
|
|
186
|
+
// ── Load ggui.json ───────────────────────────────────────────────────────
|
|
187
|
+
const gguiJsonPath = findGguiJson(startDir);
|
|
188
|
+
let gguiJson = {};
|
|
189
|
+
if (gguiJsonPath) {
|
|
190
|
+
const readResult = readGguiJson(gguiJsonPath);
|
|
191
|
+
if ('value' in readResult) {
|
|
192
|
+
gguiJson = readResult.value;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// ── Resolve provider ─────────────────────────────────────────────────────
|
|
196
|
+
const providerResult = inferProvider(gguiJson, flagProvider);
|
|
197
|
+
if (typeof providerResult === 'object' && 'error' in providerResult) {
|
|
198
|
+
process.stderr.write(`ggui provider-key set: ${providerResult.error}\n`);
|
|
199
|
+
return 1;
|
|
200
|
+
}
|
|
201
|
+
const provider = providerResult;
|
|
202
|
+
// ── Read key from env ────────────────────────────────────────────────────
|
|
203
|
+
const key = readKeyFromEnv(provider);
|
|
204
|
+
if (!key) {
|
|
205
|
+
const primaryVar = envVarForProvider(provider);
|
|
206
|
+
const hint = provider === 'google'
|
|
207
|
+
? `${primaryVar} (or GEMINI_API_KEY)`
|
|
208
|
+
: primaryVar;
|
|
209
|
+
process.stderr.write(`ggui provider-key set: no key found for provider "${provider}" — set ${hint} and retry.\n`);
|
|
210
|
+
return 1;
|
|
211
|
+
}
|
|
212
|
+
// ── Push to cloud ────────────────────────────────────────────────────────
|
|
213
|
+
try {
|
|
214
|
+
const result = await setAppProviderKey(appId, provider, key);
|
|
215
|
+
process.stdout.write(` provider key set (••••${result.lastFour}) for ${provider}\n`);
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
process.stderr.write(`ggui provider-key set: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
220
|
+
return 1;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { DataContract } from '@ggui-ai/protocol';
|
|
2
|
+
/** One entry in the push payload accepted by POST /v1/apps/{appId}/blueprints. */
|
|
3
|
+
export interface PushRecord {
|
|
4
|
+
/** `${contractHash}-${variantKey}` — deterministic, no truncation → idempotent re-push. */
|
|
5
|
+
readonly artifactId: string;
|
|
6
|
+
/** Schema version for the blueprint format (always '1'). */
|
|
7
|
+
readonly version: '1';
|
|
8
|
+
readonly manifest: {
|
|
9
|
+
readonly contract: DataContract;
|
|
10
|
+
readonly description?: string;
|
|
11
|
+
/** Generator era this blueprint was built against — feeds the import gate. */
|
|
12
|
+
readonly generatorProtocolVersion?: string;
|
|
13
|
+
/** Tool-identity catalog hash at export — feeds the import gate's re-key check. */
|
|
14
|
+
readonly toolIdentityCatalogHash?: string;
|
|
15
|
+
};
|
|
16
|
+
/** Compiled ESM JS, ready for the pod to evaluate directly (no pod-side esbuild). */
|
|
17
|
+
readonly compiledBytes: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ParsedPushFlags {
|
|
20
|
+
readonly appId?: string;
|
|
21
|
+
readonly from?: string;
|
|
22
|
+
readonly error?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function parsePushFlags(args: readonly string[]): ParsedPushFlags;
|
|
25
|
+
/**
|
|
26
|
+
* Compile a TSX source string to an ESM JS string using esbuild.
|
|
27
|
+
*
|
|
28
|
+
* The compiled output is browser-targeted ESM with the same external set
|
|
29
|
+
* the dev-stack sandbox compiler uses (`SANDBOX_EXTERNALS` — React family
|
|
30
|
+
* + `@ggui-ai/{design,wire,react}`), so stored blueprint code that imports
|
|
31
|
+
* any of those resolves against the iframe runtime's shared instances
|
|
32
|
+
* instead of getting bundled (which would risk a second React copy).
|
|
33
|
+
* esbuild writes to a temp file because the `.tsx` extension drives the
|
|
34
|
+
* right JSX loader (stdin mode wouldn't).
|
|
35
|
+
*/
|
|
36
|
+
export declare function compileTsx(code: string): Promise<string>;
|
|
37
|
+
/**
|
|
38
|
+
* Read a pool artifact from `artifactDir` and compile each blueprint's
|
|
39
|
+
* component code to ESM JS, returning push-ready records.
|
|
40
|
+
*
|
|
41
|
+
* Returns an empty array if the artifact contains no records (the caller
|
|
42
|
+
* should treat this as "nothing to push").
|
|
43
|
+
*/
|
|
44
|
+
export declare function buildBlueprintPushPayload(artifactDir: string): Promise<PushRecord[]>;
|
|
45
|
+
export declare const PUSH_HELP = "ggui push \u2014 compile + bulk-push this deployment's blueprints to a cloud app\n\nExports the local blueprint pool, compiles each blueprint's component code\nto ESM JS, and uploads them to the given ggui.ai app.\n\nUsage:\n ggui push [--app <appId>] [--from <poolDir>]\n\nOptions:\n --app <appId> Target app ID. Defaults to ggui.json#appId.\n --from <dir> Read blueprints from an existing pool artifact directory\n (created by `ggui export-pool`). When omitted, the pool\n is exported on the fly from the local vectors store.\n\nRequires authentication: run `ggui login` first.\n";
|
|
46
|
+
export declare function runPushCommand(args: readonly string[]): Promise<number>;
|
|
47
|
+
//# sourceMappingURL=push-command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push-command.d.ts","sourceRoot":"","sources":["../src/push-command.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAWtD,kFAAkF;AAClF,MAAM,WAAW,UAAU;IACzB,2FAA2F;IAC3F,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE;QACjB,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;QAChC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAC9B,8EAA8E;QAC9E,QAAQ,CAAC,wBAAwB,CAAC,EAAE,MAAM,CAAC;QAC3C,mFAAmF;QACnF,QAAQ,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAC;KAC3C,CAAC;IACF,qFAAqF;IACrF,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAMD,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,eAAe,CA+BvE;AAMD;;;;;;;;;;GAUG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA0B9D;AAMD;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAwC1F;AAMD,eAAO,MAAM,SAAS,onBAerB,CAAC;AAEF,wBAAsB,cAAc,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA4E7E"}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { writeFile, rm } from 'node:fs/promises';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { build as esbuild } from 'esbuild';
|
|
6
|
+
import { SANDBOX_EXTERNALS } from '@ggui-ai/dev-stack';
|
|
7
|
+
import { fromPortableBlueprint } from '@ggui-ai/protocol/blueprint-key';
|
|
8
|
+
import { readPoolArtifact } from './pool-artifact.js';
|
|
9
|
+
import { findGguiJson, readGguiJson } from './internal/ggui-json.js';
|
|
10
|
+
import { exportLocalPool } from './export-pool-command.js';
|
|
11
|
+
import { pushAppBlueprints } from './api-client.js';
|
|
12
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
13
|
+
// Flag parsing
|
|
14
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
15
|
+
export function parsePushFlags(args) {
|
|
16
|
+
let appId;
|
|
17
|
+
let from;
|
|
18
|
+
for (let i = 0; i < args.length; i++) {
|
|
19
|
+
const a = args[i];
|
|
20
|
+
if (a === '--app') {
|
|
21
|
+
const v = args[i + 1];
|
|
22
|
+
if (v === undefined || v.startsWith('-')) {
|
|
23
|
+
return { error: '--app requires a value' };
|
|
24
|
+
}
|
|
25
|
+
appId = v;
|
|
26
|
+
i += 1;
|
|
27
|
+
}
|
|
28
|
+
else if (a === '--from') {
|
|
29
|
+
const v = args[i + 1];
|
|
30
|
+
if (v === undefined || v.startsWith('-')) {
|
|
31
|
+
return { error: '--from requires a path' };
|
|
32
|
+
}
|
|
33
|
+
from = v;
|
|
34
|
+
i += 1;
|
|
35
|
+
}
|
|
36
|
+
else if (a === '--help' || a === '-h') {
|
|
37
|
+
return { error: '__help__' };
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
return { error: `unknown flag: ${String(a)}` };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
...(appId !== undefined ? { appId } : {}),
|
|
45
|
+
...(from !== undefined ? { from } : {}),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
49
|
+
// Compile TSX → ESM JS
|
|
50
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
51
|
+
/**
|
|
52
|
+
* Compile a TSX source string to an ESM JS string using esbuild.
|
|
53
|
+
*
|
|
54
|
+
* The compiled output is browser-targeted ESM with the same external set
|
|
55
|
+
* the dev-stack sandbox compiler uses (`SANDBOX_EXTERNALS` — React family
|
|
56
|
+
* + `@ggui-ai/{design,wire,react}`), so stored blueprint code that imports
|
|
57
|
+
* any of those resolves against the iframe runtime's shared instances
|
|
58
|
+
* instead of getting bundled (which would risk a second React copy).
|
|
59
|
+
* esbuild writes to a temp file because the `.tsx` extension drives the
|
|
60
|
+
* right JSX loader (stdin mode wouldn't).
|
|
61
|
+
*/
|
|
62
|
+
export async function compileTsx(code) {
|
|
63
|
+
const tmpFile = join(tmpdir(), `ggui-push-${randomUUID()}.tsx`);
|
|
64
|
+
try {
|
|
65
|
+
await writeFile(tmpFile, code, 'utf-8');
|
|
66
|
+
const result = await esbuild({
|
|
67
|
+
entryPoints: [tmpFile],
|
|
68
|
+
bundle: true,
|
|
69
|
+
format: 'esm',
|
|
70
|
+
target: 'es2020',
|
|
71
|
+
jsx: 'automatic',
|
|
72
|
+
jsxImportSource: 'react',
|
|
73
|
+
platform: 'browser',
|
|
74
|
+
minify: false,
|
|
75
|
+
write: false,
|
|
76
|
+
external: SANDBOX_EXTERNALS,
|
|
77
|
+
logLevel: 'silent',
|
|
78
|
+
});
|
|
79
|
+
if (result.outputFiles.length !== 1) {
|
|
80
|
+
throw new Error(`esbuild produced ${result.outputFiles.length} output files; expected 1`);
|
|
81
|
+
}
|
|
82
|
+
return result.outputFiles[0].text;
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
await rm(tmpFile, { force: true });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
89
|
+
// Payload builder
|
|
90
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
91
|
+
/**
|
|
92
|
+
* Read a pool artifact from `artifactDir` and compile each blueprint's
|
|
93
|
+
* component code to ESM JS, returning push-ready records.
|
|
94
|
+
*
|
|
95
|
+
* Returns an empty array if the artifact contains no records (the caller
|
|
96
|
+
* should treat this as "nothing to push").
|
|
97
|
+
*/
|
|
98
|
+
export async function buildBlueprintPushPayload(artifactDir) {
|
|
99
|
+
const { records } = await readPoolArtifact(artifactDir);
|
|
100
|
+
const payload = [];
|
|
101
|
+
for (const raw of records) {
|
|
102
|
+
// Artifact trust boundary — raw records validate through
|
|
103
|
+
// `fromPortableBlueprint` before anything is compiled or pushed.
|
|
104
|
+
// Pool posture: skip-with-log; one rejected record never aborts
|
|
105
|
+
// the bulk push.
|
|
106
|
+
const parsed = fromPortableBlueprint(raw);
|
|
107
|
+
if (!parsed.ok) {
|
|
108
|
+
// eslint-disable-next-line no-console -- operator-visible integrity warning
|
|
109
|
+
console.warn(`[ggui] push: skipped a blueprint record — ${parsed.reason}`);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const r = parsed.record;
|
|
113
|
+
const compiledBytes = await compileTsx(r.componentCode);
|
|
114
|
+
// Full artifactId: contractHash-variantKey (no truncation — different
|
|
115
|
+
// variantKeys on the same contractHash would collide on a prefix).
|
|
116
|
+
const artifactId = `${r.contractHash}-${r.variantKey}`;
|
|
117
|
+
payload.push({
|
|
118
|
+
artifactId,
|
|
119
|
+
version: '1',
|
|
120
|
+
manifest: {
|
|
121
|
+
contract: r.contract,
|
|
122
|
+
...(r.variance.seedPrompt !== undefined
|
|
123
|
+
? { description: r.variance.seedPrompt }
|
|
124
|
+
: {}),
|
|
125
|
+
// Carry the PortableBlueprint stamp fields so the cloud import gate
|
|
126
|
+
// can verify generator era + tool-identity catalog at load time.
|
|
127
|
+
// v2 records always carry the era stamp; the catalog hash stays
|
|
128
|
+
// optional (offline export cannot compute one).
|
|
129
|
+
generatorProtocolVersion: r.generatorProtocolVersion,
|
|
130
|
+
...(r.toolIdentityCatalogHash !== undefined
|
|
131
|
+
? { toolIdentityCatalogHash: r.toolIdentityCatalogHash }
|
|
132
|
+
: {}),
|
|
133
|
+
},
|
|
134
|
+
compiledBytes,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return payload;
|
|
138
|
+
}
|
|
139
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
140
|
+
// Command runner
|
|
141
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
142
|
+
export const PUSH_HELP = `ggui push — compile + bulk-push this deployment's blueprints to a cloud app
|
|
143
|
+
|
|
144
|
+
Exports the local blueprint pool, compiles each blueprint's component code
|
|
145
|
+
to ESM JS, and uploads them to the given ggui.ai app.
|
|
146
|
+
|
|
147
|
+
Usage:
|
|
148
|
+
ggui push [--app <appId>] [--from <poolDir>]
|
|
149
|
+
|
|
150
|
+
Options:
|
|
151
|
+
--app <appId> Target app ID. Defaults to ggui.json#appId.
|
|
152
|
+
--from <dir> Read blueprints from an existing pool artifact directory
|
|
153
|
+
(created by \`ggui export-pool\`). When omitted, the pool
|
|
154
|
+
is exported on the fly from the local vectors store.
|
|
155
|
+
|
|
156
|
+
Requires authentication: run \`ggui login\` first.
|
|
157
|
+
`;
|
|
158
|
+
export async function runPushCommand(args) {
|
|
159
|
+
const flags = parsePushFlags(args);
|
|
160
|
+
if (flags.error === '__help__') {
|
|
161
|
+
process.stdout.write(PUSH_HELP);
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
if (flags.error) {
|
|
165
|
+
process.stderr.write(`ggui push: ${flags.error}\n`);
|
|
166
|
+
return 1;
|
|
167
|
+
}
|
|
168
|
+
// Resolve appId: --app flag > ggui.json#appId
|
|
169
|
+
let appId = flags.appId;
|
|
170
|
+
if (appId === undefined) {
|
|
171
|
+
const jsonPath = findGguiJson(process.cwd());
|
|
172
|
+
if (jsonPath) {
|
|
173
|
+
const result = readGguiJson(jsonPath);
|
|
174
|
+
if ('value' in result) {
|
|
175
|
+
const val = result.value['appId'];
|
|
176
|
+
if (typeof val === 'string' && val.length > 0) {
|
|
177
|
+
appId = val;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (appId === undefined) {
|
|
183
|
+
process.stderr.write('ggui push: no app ID. Pass --app <appId> or set appId in ggui.json.\n');
|
|
184
|
+
return 2;
|
|
185
|
+
}
|
|
186
|
+
// Resolve artifact directory: --from flag or on-the-fly export.
|
|
187
|
+
// The on-the-fly export does NOT error on an empty pool (it writes an
|
|
188
|
+
// empty artifact dir); the empty case is handled gracefully below after
|
|
189
|
+
// the payload build, so `ggui push` on an empty pool exits 0 with
|
|
190
|
+
// "nothing to push." rather than failing.
|
|
191
|
+
let artifactDir;
|
|
192
|
+
if (flags.from !== undefined) {
|
|
193
|
+
artifactDir = flags.from;
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
try {
|
|
197
|
+
// exportLocalPool creates a temp dir when no outDir is given.
|
|
198
|
+
const exported = await exportLocalPool();
|
|
199
|
+
artifactDir = exported.dir;
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
process.stderr.write(`ggui push: export failed — ${err instanceof Error ? err.message : String(err)}\n`);
|
|
203
|
+
return 1;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Build the push payload (compile all TSX → ESM)
|
|
207
|
+
let records;
|
|
208
|
+
try {
|
|
209
|
+
records = await buildBlueprintPushPayload(artifactDir);
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
process.stderr.write(`ggui push: failed to build payload — ${err instanceof Error ? err.message : String(err)}\n`);
|
|
213
|
+
return 1;
|
|
214
|
+
}
|
|
215
|
+
if (records.length === 0) {
|
|
216
|
+
process.stdout.write('ggui push: nothing to push.\n');
|
|
217
|
+
return 0;
|
|
218
|
+
}
|
|
219
|
+
// Push to the cloud
|
|
220
|
+
let result;
|
|
221
|
+
try {
|
|
222
|
+
result = await pushAppBlueprints(appId, records);
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
process.stderr.write(`ggui push: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
226
|
+
return 1;
|
|
227
|
+
}
|
|
228
|
+
process.stdout.write(`Pushed ${result.pushed} blueprint(s) to app ${result.appId}.\n`);
|
|
229
|
+
return 0;
|
|
230
|
+
}
|
package/dist/serve-command.d.ts
CHANGED
|
@@ -70,20 +70,6 @@ export interface ParsedServeFlags {
|
|
|
70
70
|
* scheme with `ws`/`wss`.
|
|
71
71
|
*/
|
|
72
72
|
publicBaseUrl?: string;
|
|
73
|
-
/**
|
|
74
|
-
* When true, auto-spawn a cloudflared quick-tunnel pointed at the
|
|
75
|
-
* local listen port and treat the assigned URL as
|
|
76
|
-
* {@link publicBaseUrl}. Mutually exclusive with `--public-base-url`
|
|
77
|
-
* (operator gets EITHER explicit URL OR auto-tunnel).
|
|
78
|
-
*
|
|
79
|
-
* `--public + --dev-allow-all` combination is rejected at parse
|
|
80
|
-
* time unless `--i-know-its-public` is also set — exposing
|
|
81
|
-
* any-bearer auth to the open internet via cloudflared takes a
|
|
82
|
-
* deliberate flag.
|
|
83
|
-
*/
|
|
84
|
-
publicAutoTunnel?: boolean;
|
|
85
|
-
/** Acknowledgement flag — see {@link publicAutoTunnel}. */
|
|
86
|
-
iKnowItsPublic?: boolean;
|
|
87
73
|
/**
|
|
88
74
|
* Mount the OAuth 2.1 + PKCE + Dynamic Client Registration routes
|
|
89
75
|
* required by MCP custom-connector hosts (claude.ai, ChatGPT) whose
|
|
@@ -193,7 +179,7 @@ export interface ParsedServeFlags {
|
|
|
193
179
|
/**
|
|
194
180
|
* Disable the cross-restart persistence bundle. Without this flag,
|
|
195
181
|
* `ggui serve` reads or mints the HMAC secrets (and, in later
|
|
196
|
-
* slices,
|
|
182
|
+
* slices, GguiSessionStore + ShortCodeIndex + VectorStore + paired
|
|
197
183
|
* bearers) under `getPersistentDir(projectRoot)` so a server
|
|
198
184
|
* restart doesn't invalidate cached `_meta["ai.ggui/render"].wsToken`
|
|
199
185
|
* envelopes — claude.ai chat-history revisits keep working.
|
|
@@ -215,6 +201,19 @@ export interface ParsedServeFlags {
|
|
|
215
201
|
* Surface = `--admin-token <token>`.
|
|
216
202
|
*/
|
|
217
203
|
adminToken?: string;
|
|
204
|
+
/**
|
|
205
|
+
* Paths to read-only shared blueprint pool directories. Each entry
|
|
206
|
+
* was supplied via a `--seed-pool <dir>` flag (repeatable). At boot,
|
|
207
|
+
* each path is loaded as a `FileSystemBlueprintSource` and passed to
|
|
208
|
+
* `buildSeedPool` so its blueprints are available for exact-contract
|
|
209
|
+
* reuse behind the operator's own discovered set.
|
|
210
|
+
*
|
|
211
|
+
* Defaults to `[]` (no shared pools). Shared pools are checked
|
|
212
|
+
* AFTER the operator's own blueprints — local declarations always win.
|
|
213
|
+
*
|
|
214
|
+
* Surface = `--seed-pool <dir>` (repeatable).
|
|
215
|
+
*/
|
|
216
|
+
seedPools: string[];
|
|
218
217
|
/** Populated when parsing failed; caller renders + bails with exit code 1. */
|
|
219
218
|
error?: string;
|
|
220
219
|
}
|
|
@@ -329,7 +328,7 @@ export interface ServeBannerInputs {
|
|
|
329
328
|
/**
|
|
330
329
|
* Embedding model id surfaced for the local RAG layer. When set,
|
|
331
330
|
* the banner shows a `rag` line so operators see which model is
|
|
332
|
-
* wired (and that bge-small downloads lazily on first
|
|
331
|
+
* wired (and that bge-small downloads lazily on first render). Absent
|
|
333
332
|
* when no embedding is wired (e.g. tests, programmatic embeds that
|
|
334
333
|
* fall back to MockEmbeddingProvider).
|
|
335
334
|
*/
|
|
@@ -448,8 +447,13 @@ export interface ServeShutdownSignal {
|
|
|
448
447
|
}): void;
|
|
449
448
|
}
|
|
450
449
|
export interface RunServeOptions {
|
|
451
|
-
/**
|
|
452
|
-
|
|
450
|
+
/**
|
|
451
|
+
* Parsed flags. The `error` field must already be handled upstream.
|
|
452
|
+
* `seedPools` is omitted too — it's parse output the CLI consumes to
|
|
453
|
+
* build the shared `BlueprintPool[]` (fed straight into the backend
|
|
454
|
+
* factory), NOT a `runServe` lifecycle input.
|
|
455
|
+
*/
|
|
456
|
+
readonly flags: Readonly<Omit<ParsedServeFlags, 'error' | 'seedPools'>>;
|
|
453
457
|
/** Build the backend. Called once, after flag parsing. */
|
|
454
458
|
readonly backendFactory: ServeBackendFactory;
|
|
455
459
|
/**
|
|
@@ -496,5 +500,5 @@ export interface RunServeOptions {
|
|
|
496
500
|
* {@link describeServeBanner} / {@link describeAgentStatus}.
|
|
497
501
|
*/
|
|
498
502
|
export declare function runServe(opts: RunServeOptions): Promise<number>;
|
|
499
|
-
export declare const SERVE_HELP = "ggui serve \u2014 run the open self-hosted personal-mode app\n\nBoots the @ggui-ai/mcp-server binding with persistent defaults AND\n(by default) supervises your agent alongside it. The current MCP\nhandler surface is the blueprint-read family (search / list_featured\n/ render); more tools come online as @ggui-ai/mcp-server-handlers\ngrows.\n\nFirst-run bundle (all on by default):\n - Landing page at / \u2014 operator opens a browser and sees\n server identity + pair-code card.\n - Render viewer at /r/<code> \u2014 same-origin viewer for renders\n minted by ggui_render; same-origin\n HTTP-only cookie authenticates the\n live-channel /ws upgrade.\n - POST /pair + POST /admin/pair/init \u2014 pairing endpoint for MCP\n hosts and third-party clients.\n - Live-channel /ws \u2014 live render plane for MCP Apps\n iframes and the console.\n\nEmbedding hosts that want a different shape (no landing page, no\npairing, programmatic control) should compose createGguiServer()\ndirectly in code rather than invoking `ggui serve`.\n\nUsage:\n ggui serve [options]\n\nOptions:\n --port <n> Bind port (default: 6781, 0 = OS-assigned).\n --host <addr> Bind host (default: 127.0.0.1).\n --mcp-only Run just the MCP server; do not supervise an agent.\n --dev-allow-all Accept any non-empty bearer (incl. no bearer) as\n builder. Local-dev / tunnel smoke ONLY \u2014 never\n expose to the open internet.\n --public-demo Public-facing demo posture: same any-bearer auth\n as --dev-allow-all, plus per-IP rate limit on\n ggui_render (30 generations / 10 min) and a\n \"PUBLIC DEMO \u2014 operator pays\" banner. Mutually\n exclusive with --dev-allow-all.\n --multi-tenant Multi-tenant posture: switches the /settings LLM-\n keys gate from admin-token to auth-adapter so\n each authenticated end-user manages their OWN\n key plane (scope = userId/appId). kind:'builder'\n identities are rejected at the gate. Strict-auth\n only; mutually exclusive with --dev-allow-all\n and --public-demo. (Generation-time BYOK still\n resolves at the operator 'global' scope \u2014 a\n follow-up will thread per-user BYOK through\n ggui_render.)\n --public-base-url <u> Override the public base URL used to compose\n iframe-runtime + shortCode URLs. Set this to a\n tunnel URL (https://<random>.trycloudflare.com)\n when testing against a remote MCP host so URLs\n resolve from the host's perspective. Without\n this, URLs derive from --host:--port and only\n work from the same machine.\n --oauth Mount OAuth 2.1 + PKCE + Dynamic Client\n Registration routes (.well-known/oauth-* +\n /oauth/{authorize,token,register}). Required\n for MCP custom-connector hosts (claude.ai,\n ChatGPT) whose Add Connector form has no field\n for a pre-shared bearer; OAuth lets them do\n a discovery \u2192 DCR \u2192 consent dance ending in\n the same paired bearer the strict-auth flow\n already mints. Pure-bearer clients (Claude\n Desktop with bearer in config) work without it.\n --admin-token <t> Pin the admin bearer that gates the console\n /keys plane (list / mint / revoke pairings +\n /admin-login route). Without this, the server\n mints a fresh ggui_admin_* token per boot and\n prints it on the banner. Pin a stable value\n when you want the bearer to survive restarts\n (e.g. paste once into a password manager).\n --ephemeral Disable cross-restart persistence. Without\n this flag, the HMAC secrets that sign MCP\n Apps bootstrap tokens and signed render URLs\n are read or minted under .ggui/persistent/\n (project-local if a ggui.json was resolved,\n else ~/.ggui/persistent/) so cached tokens\n survive a server restart. Pass this flag to\n keep restarts ephemeral (tests / CI / nuclear-\n revoke). Override the dir via GGUI_PERSISTENT_DIR.\n --help, -h Show this help.\n\nAgent runtime:\n The default boots your agent alongside MCP. The agent's entry file\n comes from ggui.json:\n\n { \"agent\": { \"entry\": \"./agent.ts\" } }\n\n Supported extensions: .js / .mjs / .cjs (node), .ts / .tsx / .mts\n (node --import=tsx). No ggui.json, or no agent.entry, falls back\n to MCP-only with a warning. Malformed ggui.json or a bad entry\n is a hard error.\n\n Agent crashes after startup are logged; the MCP server keeps\n running. No auto-restart \u2014 compose that with your own supervisor.\n\nPersistent storage (default-on; opt-out via --ephemeral):\n By default `ggui serve` writes a small bundle under .ggui/persistent/\n (project-local if a ggui.json was resolved, else ~/.ggui/persistent/)\n so cached MCP Apps tokens, signed render URLs, shortCodes, renders,\n vectors, and paired bearers survive a restart \u2014 claude.ai chat-history\n revisits keep working.\n\n Bundle layout:\n\n .ggui/persistent/\n \u251C\u2500\u2500 ws-token-secret.hex (HMAC, 0600)\n \u251C\u2500\u2500 render-signer-secret.hex (HMAC, 0600)\n \u251C\u2500\u2500 short-codes.sqlite (signed render-URL resolution)\n \u251C\u2500\u2500
|
|
503
|
+
export declare const SERVE_HELP = "ggui serve \u2014 run the open self-hosted personal-mode app\n\nBoots the @ggui-ai/mcp-server binding with persistent defaults AND\n(by default) supervises your agent alongside it. The current MCP\nhandler surface is the blueprint-read family (search / list_featured\n/ render); more tools come online as @ggui-ai/mcp-server-handlers\ngrows.\n\nFirst-run bundle (all on by default):\n - Landing page at / \u2014 operator opens a browser and sees\n server identity + pair-code card.\n - Render viewer at /r/<code> \u2014 same-origin viewer for renders\n minted by ggui_render; same-origin\n HTTP-only cookie authenticates the\n live-channel /ws upgrade.\n - POST /pair + POST /admin/pair/init \u2014 pairing endpoint for MCP\n hosts and third-party clients.\n - Live-channel /ws \u2014 live render plane for MCP Apps\n iframes and the console.\n\nEmbedding hosts that want a different shape (no landing page, no\npairing, programmatic control) should compose createGguiServer()\ndirectly in code rather than invoking `ggui serve`.\n\nUsage:\n ggui serve [options]\n\nOptions:\n --port <n> Bind port (default: 6781, 0 = OS-assigned).\n --host <addr> Bind host (default: 127.0.0.1).\n --mcp-only Run just the MCP server; do not supervise an agent.\n --dev-allow-all Accept any non-empty bearer (incl. no bearer) as\n builder. Local-dev / tunnel smoke ONLY \u2014 never\n expose to the open internet.\n --public-demo Public-facing demo posture: same any-bearer auth\n as --dev-allow-all, plus per-IP rate limit on\n ggui_render (30 generations / 10 min) and a\n \"PUBLIC DEMO \u2014 operator pays\" banner. Mutually\n exclusive with --dev-allow-all.\n --multi-tenant Multi-tenant posture: switches the /settings LLM-\n keys gate from admin-token to auth-adapter so\n each authenticated end-user manages their OWN\n key plane (scope = userId/appId). kind:'builder'\n identities are rejected at the gate. Strict-auth\n only; mutually exclusive with --dev-allow-all\n and --public-demo. (Generation-time BYOK still\n resolves at the operator 'global' scope \u2014 a\n follow-up will thread per-user BYOK through\n ggui_render.)\n --public-base-url <u> Override the public base URL used to compose\n iframe-runtime + shortCode URLs. Set this to a\n tunnel URL (https://<random>.trycloudflare.com)\n when testing against a remote MCP host so URLs\n resolve from the host's perspective. Without\n this, URLs derive from --host:--port and only\n work from the same machine.\n --oauth Mount OAuth 2.1 + PKCE + Dynamic Client\n Registration routes (.well-known/oauth-* +\n /oauth/{authorize,token,register}). Required\n for MCP custom-connector hosts (claude.ai,\n ChatGPT) whose Add Connector form has no field\n for a pre-shared bearer; OAuth lets them do\n a discovery \u2192 DCR \u2192 consent dance ending in\n the same paired bearer the strict-auth flow\n already mints. Pure-bearer clients (Claude\n Desktop with bearer in config) work without it.\n --admin-token <t> Pin the admin bearer that gates the console\n /keys plane (list / mint / revoke pairings +\n /admin-login route). Without this, the server\n mints a fresh ggui_admin_* token per boot and\n prints it on the banner. Pin a stable value\n when you want the bearer to survive restarts\n (e.g. paste once into a password manager).\n --ephemeral Disable cross-restart persistence. Without\n this flag, the HMAC secrets that sign MCP\n Apps bootstrap tokens and signed render URLs\n are read or minted under .ggui/persistent/\n (project-local if a ggui.json was resolved,\n else ~/.ggui/persistent/) so cached tokens\n survive a server restart. Pass this flag to\n keep restarts ephemeral (tests / CI / nuclear-\n revoke). Override the dir via GGUI_PERSISTENT_DIR.\n --seed-pool <dir> Load a read-only shared blueprint pool from a\n directory artifact (repeatable). Blueprints in it\n are reused by exact contract match, after your own.\n --help, -h Show this help.\n\nAgent runtime:\n The default boots your agent alongside MCP. The agent's entry file\n comes from ggui.json:\n\n { \"agent\": { \"entry\": \"./agent.ts\" } }\n\n Supported extensions: .js / .mjs / .cjs (node), .ts / .tsx / .mts\n (node --import=tsx). No ggui.json, or no agent.entry, falls back\n to MCP-only with a warning. Malformed ggui.json or a bad entry\n is a hard error.\n\n Agent crashes after startup are logged; the MCP server keeps\n running. No auto-restart \u2014 compose that with your own supervisor.\n\nPersistent storage (default-on; opt-out via --ephemeral):\n By default `ggui serve` writes a small bundle under .ggui/persistent/\n (project-local if a ggui.json was resolved, else ~/.ggui/persistent/)\n so cached MCP Apps tokens, signed render URLs, shortCodes, renders,\n vectors, and paired bearers survive a restart \u2014 claude.ai chat-history\n revisits keep working.\n\n Bundle layout:\n\n .ggui/persistent/\n \u251C\u2500\u2500 ws-token-secret.hex (HMAC, 0600)\n \u251C\u2500\u2500 render-signer-secret.hex (HMAC, 0600)\n \u251C\u2500\u2500 short-codes.sqlite (signed render-URL resolution)\n \u251C\u2500\u2500 sessions.sqlite (GguiSessionStore \u2014 renders + event history)\n \u251C\u2500\u2500 vectors.sqlite (RAG corpus)\n \u2514\u2500\u2500 keys.json (paired bearers)\n\n Override the dir with `GGUI_PERSISTENT_DIR`. Pass `--ephemeral` to\n skip the bundle entirely (tests, CI loops, nuclear-revoke).\n\n Explicit ggui.json#storage declarations always win \u2014 declare\n `{ \"renders\": { \"driver\": \"memory\" } }` to opt renders back to\n in-memory while keeping other surfaces persistent. Custom sqlite\n paths via ggui.json#storage are honored verbatim:\n\n {\n \"storage\": {\n \"renders\": { \"driver\": \"sqlite\", \"path\": \"./ggui-sessions.sqlite\" },\n \"vectors\": { \"driver\": \"sqlite\", \"path\": \"./ggui-vectors.sqlite\" },\n \"threads\": { \"driver\": \"sqlite\", \"path\": \"./ggui-threads.sqlite\" }\n }\n }\n\n Threads: the persistent-chat route family (/threads) is opt-in.\n Absent `storage.threads` leaves the routes unmounted \u2014 pairs\n with Portal's self-hosted origins only when declared. Driver\n semantics:\n - `driver: \"sqlite\"` \u2014 durable. Threads survive restart.\n /ggui/health reports `threads.durability: \"durable\"`;\n Portal hides its non-durable caveat.\n - `driver: \"memory\"` \u2014 routes mount but data is lost on\n restart. /ggui/health reports `threads.durability:\n \"ephemeral\"`; Portal shows its caveat.\n\n Paths resolve relative to the ggui.json directory. The sqlite\n driver needs `better-sqlite3` installed in your project.\n\nRecommended setups:\n\n Local-only smoke (no tunnel, no remote MCP host):\n ggui serve\n\n claude.ai custom connector (over a public tunnel):\n 1. Tunnel: cloudflared tunnel --url http://localhost:6781\n 2. ggui serve --oauth \\\n --public-base-url https://<tunnel>.trycloudflare.com\n 3. Note PAIR_CODE from boot. Visit the landing page (the public URL\n above), complete pairing, save the bearer.\n 4. claude.ai \u2192 Settings \u2192 Connectors \u2192 Add custom connector.\n URL: https://<tunnel>.trycloudflare.com/mcp\n Client ID / Secret: leave empty (server uses Dynamic Client\n Registration; no pre-shared secret needed).\n 5. Click Connect \u2192 claude.ai redirects you to a paste-key page.\n Paste the bearer from step 3. Done.\n\n Quick local-dev WITHOUT auth (NEVER over a public tunnel):\n ggui serve --dev-allow-all\n\nCurrent limits:\n - Strict-auth only: /mcp rejects any bearer that isn't pair-minted.\n The CLI pre-mints one pairing code on boot (printed as\n `PAIR_CODE <code>` and shown in the banner + on the landing\n page). Complete `POST /pair {code, deviceName}` to receive a\n bearer you can use with MCP. Embed @ggui-ai/mcp-server in your\n own entrypoint to inject a different AuthAdapter (OIDC, Cognito,\n etc.) for production.\n - Single-tenant: every request scopes to one `builder` app id.\n";
|
|
500
504
|
//# sourceMappingURL=serve-command.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve-command.d.ts","sourceRoot":"","sources":["../src/serve-command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,KAAK,EACV,mBAAmB,EACnB,iBAAiB,EAEjB,sBAAsB,EACvB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAG1D;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAEvC,6DAA6D;AAC7D,eAAO,MAAM,kBAAkB,cAAc,CAAC;AAE9C,MAAM,WAAW,gBAAgB;IAC/B,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;;;;;;;;;OAUG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB;;;;;;;;;;;OAWG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB
|
|
1
|
+
{"version":3,"file":"serve-command.d.ts","sourceRoot":"","sources":["../src/serve-command.ts"],"names":[],"mappings":"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"}
|