@guuey/config 0.1.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/dist/agent.js ADDED
@@ -0,0 +1,426 @@
1
+ /**
2
+ * `guuey.json#agent` — the agent section.
3
+ *
4
+ * The agent section describes the deployable agent: framework + model
5
+ * + system prompt + MCP host config + platform-feature opt-ins + deploy
6
+ * config. Read by `@guuey/host` at pod boot to construct the framework
7
+ * adapter; read by `@guuey/cli` to validate before submitting a deploy.
8
+ *
9
+ * Lives inside `guuey.json` post-2026-05-25 platform-architecture merge
10
+ * (slice 7.2). Previously a separate `agent.json` file. See
11
+ * `docs/plans/2026-05-25-platform-architecture.md` §14.2 for the
12
+ * field-by-field migration.
13
+ *
14
+ * **Minimal valid section** (all other fields default):
15
+ *
16
+ * ```jsonc
17
+ * {
18
+ * "framework": "claude-agent-sdk",
19
+ * "model": "claude-sonnet-5",
20
+ * "systemPrompt": { "file": "prompts/system.md" }
21
+ * }
22
+ * ```
23
+ *
24
+ * Defaults applied by the pod runtime when fields are absent:
25
+ * - `framework` → `'claude-agent-sdk'`
26
+ * - `mcpServers` → `{ ggui: { url: 'https://mcp.ggui.ai' } }` (platform default; declaring `mcpServers` REPLACES this — not merged)
27
+ * - `model` → framework-chosen default (Claude SDK → `claude-sonnet-5`)
28
+ * - `systemPrompt`→ `GUUEY_DEFAULT_SYSTEM_PROMPT` from `./system-prompt`
29
+ * - `auth` → `'anonymous'`
30
+ * - `memory` → `'thread'`
31
+ * - `storage` → `['user', 'app']`
32
+ * - `endpoint` → `{ kind: 'invoke', streaming: true }`
33
+ * - `deploy` → `{ size: 'xs', region: 'us-east-1' }`
34
+ *
35
+ * **Rules for extending:**
36
+ *
37
+ * 1. **Additive only within `schema: '1'` (top-level).** New optional fields on existing
38
+ * objects are safe. Breaking changes bump the file-level `schema` to `'2'`.
39
+ * 2. **Framework-neutral by default.** Fields meaningful to only one adapter (e.g. Claude's
40
+ * `permissions`, OpenAI's `tools.functions`) belong on a `framework`-scoped sub-block.
41
+ */
42
+ import { z } from 'zod';
43
+ import { AGENT_SIZES } from './hosting.js';
44
+ /**
45
+ * Supported framework adapters. The pod runtime selects the matching
46
+ * `@guuey/framework-*` adapter at boot. `vanilla` skips the framework
47
+ * layer entirely — the agent loop is the bare Anthropic Messages API
48
+ * call with manual MCP tool wiring. Useful for benchmarking and for
49
+ * adapters not yet built.
50
+ */
51
+ export const AGENT_FRAMEWORKS = [
52
+ 'claude-agent-sdk',
53
+ 'openai-agents-sdk',
54
+ 'google-adk',
55
+ 'vanilla',
56
+ ];
57
+ /**
58
+ * Static header map — forwarded on every request. Values may use `${env.NAME}`
59
+ * placeholders; the pod's env-substitution pass fills them at call time.
60
+ * Secrets MUST be referenced via `${env.NAME}` and declared in `agent.secrets`.
61
+ */
62
+ const HeadersSchema = z.record(z.string().min(1), z.string());
63
+ /**
64
+ * `kind: 'colocated'` — MCP server runs as a stdio child **inside the agent
65
+ * pod** (co-locate = same gVisor sandbox). COGS: ~$0 (rides the agent pod).
66
+ * Nocode runtime spawns `command [args…]` at boot.
67
+ */
68
+ const ColocatedMcp = z.strictObject({
69
+ kind: z.literal('colocated'),
70
+ /** Executable name or path. Required. */
71
+ command: z.string().min(1),
72
+ /** Argv beyond `command`. */
73
+ args: z.array(z.string()).optional(),
74
+ /** Static headers (less common for stdio, but supported for symmetry). */
75
+ headers: HeadersSchema.optional(),
76
+ });
77
+ /**
78
+ * `kind: 'hosted'` — a workspace-owned registry MCP running on guuey's
79
+ * `mcp-servers.guuey.com` fleet (Starter+). At least one of `server` or `source`
80
+ * must be set:
81
+ *
82
+ * - `server: '<id>'` — reuse an existing registry MCP by id.
83
+ * - `source: './path'` — build-or-reuse by workspace-unique name; the
84
+ * deploy-controller resolves to a `server` id and writes it back — WITHOUT
85
+ * removing `source`, so both are legitimately present after a `guuey deploy`
86
+ * (`server` wins at resolve time; `source` remains the build recipe).
87
+ */
88
+ const HostedMcp = z
89
+ .strictObject({
90
+ kind: z.literal('hosted'),
91
+ /** Existing registry MCP id. May coexist with `source` post-deploy write-back. */
92
+ server: z.string().min(1).optional(),
93
+ /** Source directory relative to `guuey.json`. May coexist with `server` post-deploy write-back. */
94
+ source: z.string().min(1).optional(),
95
+ /** Local dev-loop port (`guuey dev`) this MCP is served on for name→localhost URL resolution. */
96
+ devPort: z.number().int().min(1).max(65535).optional(),
97
+ })
98
+ .refine((v) => v.server != null || v.source != null, {
99
+ message: 'hosted MCP needs `server`and/or`source`',
100
+ });
101
+ /**
102
+ * `kind: 'proxied'` — a 3rd-party SaaS MCP reached through the mcp-proxy
103
+ * credential broker (Case C). Schema is present now; runtime support lands v2.
104
+ */
105
+ const ProxiedMcp = z.strictObject({
106
+ kind: z.literal('proxied'),
107
+ /** mcp-proxy connection id (from `guuey connections add`). */
108
+ connection: z.string().min(1),
109
+ });
110
+ /**
111
+ * `kind: 'external'` — builder-hosted MCP at an arbitrary URL.
112
+ *
113
+ * - `transport` defaults to `'http'` (StreamableHTTP).
114
+ * - `federate: true` makes guuey mint a per-invoke JWT with `aud = url` that
115
+ * the builder's MCP validates against the guuey JWKS. Omit for plain URL +
116
+ * optional static `headers`.
117
+ */
118
+ const ExternalMcp = z.strictObject({
119
+ kind: z.literal('external'),
120
+ /** Full HTTP/SSE base URL. */
121
+ url: z.url(),
122
+ /** Transport protocol. Defaults to `'http'` (StreamableHTTP). */
123
+ transport: z.enum(['http', 'sse']).optional(),
124
+ /**
125
+ * Mint a per-invoke `aud = url` JWT and inject it as `Authorization: Bearer`.
126
+ * The builder's MCP verifies it against the guuey JWKS (T6b).
127
+ */
128
+ federate: z.boolean().optional(),
129
+ /** Static headers forwarded on every request. Values may use `${env.NAME}` placeholders. */
130
+ headers: HeadersSchema.optional(),
131
+ /** Local dev-loop port (`guuey dev`) this MCP is served on for name→localhost URL resolution. */
132
+ devPort: z.number().int().min(1).max(65535).optional(),
133
+ });
134
+ /**
135
+ * A single MCP server entry inside `agent.mcpServers`.
136
+ *
137
+ * Discriminated union on `kind` — one slot per hosting mode:
138
+ * - `colocated` — stdio child inside the agent pod
139
+ * - `hosted` — guuey-hosted registry MCP (Starter+)
140
+ * - `proxied` — 3rd-party SaaS via mcp-proxy credential broker (v2)
141
+ * - `external` — builder-hosted, reached by URL (plain or federated)
142
+ */
143
+ const McpServerSchema = z.discriminatedUnion('kind', [
144
+ ColocatedMcp,
145
+ HostedMcp,
146
+ ProxiedMcp,
147
+ ExternalMcp,
148
+ ]);
149
+ /**
150
+ * Tool-gate block — allowlist applied first (intersect with what the MCP
151
+ * server advertises), then denylist subtracts. Tool names are MCP-namespaced
152
+ * (`"<server>.<tool>"`). Bare names match across all connected servers.
153
+ */
154
+ const ToolGatesSchema = z.strictObject({
155
+ allowlist: z.array(z.string().min(1)).optional(),
156
+ denylist: z.array(z.string().min(1)).optional(),
157
+ });
158
+ /**
159
+ * Runtime knobs the pod applies when constructing the framework adapter.
160
+ * All optional with framework-chosen defaults.
161
+ *
162
+ * - `maxTurns` — cap on agent loop turns per user message. Stops runaway
163
+ * loops on misbehaving prompts. Default: framework default
164
+ * (Claude SDK = 25).
165
+ * - `temperature` — model sampling temperature passthrough.
166
+ */
167
+ const RuntimeConfigSchema = z.strictObject({
168
+ maxTurns: z.number().int().min(1).max(200).optional(),
169
+ temperature: z.number().min(0).max(2).optional(),
170
+ });
171
+ /**
172
+ * Claude Agent SDK-specific knobs. Lives on a `framework` discriminator so
173
+ * other adapters don't accidentally read fields they don't understand.
174
+ */
175
+ const ClaudePermissionsSchema = z.strictObject({
176
+ mode: z.enum(['default', 'acceptEdits', 'bypassPermissions']).optional(),
177
+ });
178
+ const ClaudeFrameworkConfigSchema = z.strictObject({
179
+ permissions: ClaudePermissionsSchema.optional(),
180
+ });
181
+ /**
182
+ * System prompt — string inline OR `{ file: 'prompts/system.md' }`.
183
+ * File references are resolved relative to `guuey.json` by the loader,
184
+ * which inlines the file contents into the snapshot before deploy upload.
185
+ */
186
+ const SystemPromptSchema = z.union([
187
+ z.string().min(1),
188
+ z.strictObject({ file: z.string().min(1) }),
189
+ ]);
190
+ /**
191
+ * Bedrock-style invocation endpoint config.
192
+ *
193
+ * `kind: 'invoke'` exposes `POST /agent/invoke` with multi-modal input and
194
+ * SSE response per `docs/plans/2026-05-25-platform-architecture.md` §6.
195
+ * Reserved for future endpoint kinds (`'connect'` for WebSocket bidirectional).
196
+ */
197
+ const EndpointConfigSchema = z.strictObject({
198
+ kind: z.literal('invoke').optional(),
199
+ streaming: z.boolean().optional(),
200
+ });
201
+ /**
202
+ * Deploy config — pod size + region.
203
+ *
204
+ * Lives inside the `agent` section (was top-level on the pre-merge `guuey.json`).
205
+ * Mirror shape on `guuey.mcp.json#mcpServer.deploy` (future) — same field set,
206
+ * same semantics, just attached to a different artifact.
207
+ *
208
+ * Latent fields like `tier`, `maxPods`, `idleTimeoutMinutes` exist on the
209
+ * AgentDeployment DDB model but are platform-managed (Reserved per design
210
+ * doc §14.3) — not exposed in user-facing config.
211
+ */
212
+ const DeploySchema = z.strictObject({
213
+ /** Agent pod size. Canonical list lives in `./hosting.ts#AGENT_SIZES`. */
214
+ size: z.enum(AGENT_SIZES).optional(),
215
+ /** AWS region (e.g. `"us-east-1"`). Free-form; control plane enforces the live allow-list. */
216
+ region: z.string().min(1).optional(),
217
+ });
218
+ /**
219
+ * Auth posture for end-user invocations.
220
+ *
221
+ * - `'anonymous'` (default) — guest cookie minted on first invoke; persistent thread.
222
+ * - `'required'` — end-user must present a valid Cognito JWT; anonymous rejected.
223
+ * - `'optional'` — accept both; identity context reflects which.
224
+ */
225
+ const AuthSchema = z.enum(['anonymous', 'required', 'optional']);
226
+ /**
227
+ * Memory model. `'thread'` = automatic conversation history (DDB).
228
+ * Semantic / vector memory deferred to a later schema version.
229
+ */
230
+ const MemorySchema = z.enum(['thread', 'none']);
231
+ /**
232
+ * VFS scopes to mount into the pod. Empty array = no VFS (still uses thread + state).
233
+ */
234
+ const StorageScopeSchema = z.array(z.enum(['user', 'app']));
235
+ /**
236
+ * The agent section — composes runtime + platform features + deploy.
237
+ *
238
+ * Exported as a zod object so the top-level `GuueyJsonV1` schema (in
239
+ * `./schema.ts`) can nest it. Static type via {@link GuueyAgent}.
240
+ */
241
+ export const AgentSectionV1 = z.strictObject({
242
+ // ── Deploy routing ──
243
+ /**
244
+ * Routing declaration for `guuey deploy`:
245
+ *
246
+ * - `'code'` — a worker-entry project: the CLI runs the package build
247
+ * (`corepack pnpm build` → `guuey.worker.js`), packs the project root,
248
+ * and the platform builds the runtime image from its own base image
249
+ * (code-mode `AgentDeployment`). Stamped by `@guuey/create-agentic-app`
250
+ * scaffolds so they route explicitly.
251
+ * - `'declarative'` — no source to build; the CLI POSTs the guuey.json
252
+ * snapshot directly (nocode `AgentDeployment`, stock runtime pod).
253
+ * - absent — the platform infers: declarative when the project has no
254
+ * Dockerfile (a root Dockerfile keeps the legacy user-image code path).
255
+ */
256
+ mode: z.enum(['code', 'declarative']).optional(),
257
+ // ── Framework + runtime ──
258
+ framework: z.enum(AGENT_FRAMEWORKS).optional(),
259
+ model: z.string().min(1).optional(),
260
+ /**
261
+ * Managed-LLM provider selector — only meaningful for
262
+ * `framework: 'openai-agents-sdk'`, where OpenAI and OpenRouter share the
263
+ * identical OpenAI wire and the Router must pick the upstream + platform key
264
+ * at invoke time. `'openrouter'` routes managed traffic to OpenRouter;
265
+ * absent or `'openai'` uses native OpenAI. Ignored for other frameworks
266
+ * (claude → Anthropic, google-adk → Gemini are framework-determined).
267
+ */
268
+ modelProvider: z.enum(['openai', 'openrouter']).optional(),
269
+ systemPrompt: SystemPromptSchema.optional(),
270
+ /**
271
+ * MCP servers the agent may call. **Replaces** the platform default
272
+ * (`{ ggui: { kind: 'external', url: 'https://mcp.ggui.ai', transport: 'http' } }`)
273
+ * when present — not merged. Omit the block to inherit the default; include
274
+ * `ggui` explicitly to keep it alongside other servers.
275
+ *
276
+ * Each entry is a discriminated union on `kind`:
277
+ * - `'colocated'` — stdio child inside the agent pod
278
+ * - `'hosted'` — guuey-hosted registry MCP (Starter+)
279
+ * - `'proxied'` — 3rd-party SaaS via mcp-proxy (v2)
280
+ * - `'external'` — builder-hosted URL (plain or federated)
281
+ */
282
+ mcpServers: z.record(z.string().min(1), McpServerSchema).optional(),
283
+ tools: ToolGatesSchema.optional(),
284
+ runtime: RuntimeConfigSchema.optional(),
285
+ /** Claude Agent SDK-specific knobs. Only read when `framework: 'claude-agent-sdk'`. */
286
+ claude: ClaudeFrameworkConfigSchema.optional(),
287
+ // ── Platform features (opt-in, sensible defaults) ──
288
+ auth: AuthSchema.optional(),
289
+ memory: MemorySchema.optional(),
290
+ storage: StorageScopeSchema.optional(),
291
+ // ── Env + secrets ──
292
+ /** Literal non-sensitive env vars baked into the pod at boot. */
293
+ env: z.record(z.string().min(1), z.string()).optional(),
294
+ /**
295
+ * Names (not values) of secrets the pod needs. Values are set via
296
+ * `guuey secrets set NAME=...`, stored KMS-encrypted in DDB. Deploy-controller
297
+ * resolves to values and injects as env vars at pod boot.
298
+ */
299
+ secrets: z.array(z.string().min(1)).optional(),
300
+ // ── Invocation endpoint ──
301
+ endpoint: EndpointConfigSchema.optional(),
302
+ // ── Deploy ──
303
+ deploy: DeploySchema.optional(),
304
+ });
305
+ // ── No-literal-secrets validation (deploy-time contract enforcement) ──────────
306
+ //
307
+ // The schema (McpServerSchema JSDoc) requires secrets in `mcpServers[].headers`
308
+ // be referenced via `${env.NAME}` (declared in `agent.secrets`), never literal-
309
+ // inlined — otherwise the secret rides into the pod's `NOCODE_CONFIG_JSON` env
310
+ // var as plaintext (which the B6.3 secretKeyRef hardening cannot protect, since
311
+ // it's embedded in the config JSON, not a discrete env var). Nothing enforced
312
+ // this at deploy time; `validateNoLiteralSecrets` does.
313
+ /**
314
+ * Header names that carry credentials. A value here that is a bare literal (no
315
+ * `${env.NAME}` reference) is almost certainly a baked credential. Lowercased
316
+ * for case-insensitive matching. Deliberately focused on unambiguous auth
317
+ * headers — generic-shaped secrets in ANY header are caught separately by
318
+ * {@link SECRET_SHAPE_PATTERNS} (so we don't false-positive on, e.g., `Cookie`).
319
+ */
320
+ const SENSITIVE_HEADER_NAMES = new Set([
321
+ 'authorization',
322
+ 'proxy-authorization',
323
+ 'x-api-key',
324
+ 'x-auth-token',
325
+ 'x-authorization',
326
+ 'api-key',
327
+ 'api_key',
328
+ 'apikey',
329
+ ]);
330
+ /**
331
+ * Secret-shaped literal patterns — NAMED prefixes only, deliberately NOT
332
+ * generic entropy/length heuristics (those false-positive on legit long IDs).
333
+ * Applied to the header value AFTER stripping `${env.NAME}` references, so a
334
+ * ref-based value like `Bearer ${env.TOKEN}` never trips them.
335
+ */
336
+ const SECRET_SHAPE_PATTERNS = [
337
+ /sk-ant-/, // Anthropic
338
+ /\bsk-[A-Za-z0-9]{20,}/, // OpenAI-style sk- keys
339
+ /\bsk_(live|test)_[A-Za-z0-9]{16,}/, // Stripe secret keys
340
+ /\bAKIA[0-9A-Z]{16}\b/, // AWS access key id
341
+ /\bASIA[0-9A-Z]{16}\b/, // AWS temp access key id
342
+ /\bghp_[A-Za-z0-9]{20,}/, // GitHub PAT
343
+ /\bgho_[A-Za-z0-9]{20,}/, // GitHub OAuth
344
+ /\bgithub_pat_[A-Za-z0-9_]{20,}/, // GitHub fine-grained PAT
345
+ /\bxox[baprs]-[0-9A-Za-z-]{10,}/, // Slack
346
+ /\bglpat-[A-Za-z0-9_-]{16,}/, // GitLab PAT
347
+ /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/, // JWT (3 b64url segments)
348
+ ];
349
+ /**
350
+ * Is a header name credential-bearing? The explicit set plus low-false-positive
351
+ * NAME signals (a custom `X-Auth-*` / `*-secret` / `*-password` header is almost
352
+ * certainly a credential). Deliberately NOT bare `key`/`token` (benign uses:
353
+ * `Idempotency-Key`, `X-Request-Token`). Fully-opaque secrets in an
354
+ * arbitrarily-named header still slip layer 2 — that's undetectable without
355
+ * false-positives, so it stays best-effort (a lint, not a guarantee).
356
+ */
357
+ function isSensitiveHeaderName(name) {
358
+ const n = name.toLowerCase();
359
+ if (SENSITIVE_HEADER_NAMES.has(n))
360
+ return true;
361
+ if (/(^|[-_])auth(orization)?([-_]|$)/.test(n))
362
+ return true; // x-auth-*, *-auth-token
363
+ if (/(^|[-_])api[-_]?key([-_]|$)/.test(n))
364
+ return true; // x-api-key variants
365
+ return /(secret|credential|password|passwd)/.test(n);
366
+ }
367
+ /** Non-secret auth scheme words that may legitimately stand before an `${env.NAME}` ref. */
368
+ const AUTH_SCHEME_WORDS = /\b(bearer|basic|token|digest|negotiate)\b/gi;
369
+ const ENV_REF_GLOBAL = /\$\{env\.[A-Za-z_][A-Za-z0-9_]*\}/g;
370
+ const HAS_ENV_REF = /\$\{env\.[A-Za-z_][A-Za-z0-9_]*\}/;
371
+ /**
372
+ * Validate that no `mcpServers[*].headers` value carries a LITERAL secret.
373
+ * Returns a list of human-readable violation messages (empty = clean).
374
+ *
375
+ * Two layers:
376
+ * 1. Strip `${env.NAME}` refs from each value, then match the literal
377
+ * remainder against {@link SECRET_SHAPE_PATTERNS} → a baked secret in ANY
378
+ * header (e.g. `Authorization: Bearer sk-ant-...`).
379
+ * 2. For {@link SENSITIVE_HEADER_NAMES}, a value with NO `${env.NAME}` ref and
380
+ * a non-trivial literal (after removing scheme words) → a baked credential
381
+ * (e.g. `X-API-Key: abc123`, `Authorization: Basic <base64>`).
382
+ *
383
+ * Legit ref-based values (`Authorization: Bearer ${env.TOKEN}`,
384
+ * `X-API-Key: ${env.KEY}`) and non-secret literals (`Content-Type`) pass.
385
+ */
386
+ export function validateNoLiteralSecrets(agent) {
387
+ const violations = [];
388
+ const servers = agent?.mcpServers;
389
+ if (!servers)
390
+ return violations;
391
+ for (const [serverName, server] of Object.entries(servers)) {
392
+ // Only `colocated` and `external` union arms carry a `headers` field.
393
+ const headers = 'headers' in server ? server.headers : undefined;
394
+ if (!headers)
395
+ continue;
396
+ for (const [headerName, rawValue] of Object.entries(headers)) {
397
+ const value = String(rawValue);
398
+ const literalRemainder = value.replace(ENV_REF_GLOBAL, '');
399
+ // (1) secret-shaped literal anywhere in the non-ref text.
400
+ if (SECRET_SHAPE_PATTERNS.some((re) => re.test(literalRemainder))) {
401
+ violations.push(`mcpServers.${serverName}.headers.${headerName}: contains a literal secret — reference it as \${env.NAME} and declare the name in agent.secrets`);
402
+ continue;
403
+ }
404
+ // (2) sensitive header with a fully-literal (no-ref) credential value.
405
+ if (isSensitiveHeaderName(headerName) && !HAS_ENV_REF.test(value)) {
406
+ const bare = literalRemainder.replace(AUTH_SCHEME_WORDS, '').trim();
407
+ if (bare.length > 0) {
408
+ violations.push(`mcpServers.${serverName}.headers.${headerName}: sensitive header must reference a secret as \${env.NAME} (declared in agent.secrets), not a literal value`);
409
+ }
410
+ }
411
+ }
412
+ }
413
+ return violations;
414
+ }
415
+ /**
416
+ * Platform default MCP server map. Applied by the pod when `agent.mcpServers`
417
+ * is absent. Exposed here so non-pod consumers (CLI dry-run, lints) can show
418
+ * the effective shape without duplicating the literal.
419
+ *
420
+ * The ggui server is `kind: 'external'` — it is builder-declared when present
421
+ * or injected by the platform at runtime. Federation still detects it by host
422
+ * (via `isGguiUrl`) regardless of which key it's declared under.
423
+ */
424
+ export const DEFAULT_AGENT_MCP_SERVERS = {
425
+ ggui: { kind: 'external', url: 'https://mcp.ggui.ai', transport: 'http' },
426
+ };
package/dist/app.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * `guuey.json#app` — the App Store / Portal listing section.
3
+ *
4
+ * Describes how the deployable surfaces to end-users in Portal Discover
5
+ * and (optionally) at a custom domain. Read by the platform on first
6
+ * deploy to upsert an `AgentListing` row.
7
+ *
8
+ * Lives at the same level as `agent` and `ggui` sections — the artifact
9
+ * (agent or mcp-server) is what's deployable; this section is presentation.
10
+ */
11
+ import { z } from 'zod';
12
+ /**
13
+ * The app section schema. All fields optional in v1 — a project may carry
14
+ * the bare minimum at first and grow the listing as it publishes.
15
+ */
16
+ export declare const AppSectionV1: z.ZodObject<{
17
+ slug: z.ZodOptional<z.ZodString>;
18
+ name: z.ZodOptional<z.ZodString>;
19
+ description: z.ZodOptional<z.ZodString>;
20
+ iconUrl: z.ZodOptional<z.ZodURL>;
21
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
22
+ customDomain: z.ZodOptional<z.ZodString>;
23
+ }, z.core.$strict>;
24
+ /** Static TypeScript type for the app section. */
25
+ export type GuueyApp = z.infer<typeof AppSectionV1>;
26
+ //# sourceMappingURL=app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA0CxB;;;GAGG;AACH,eAAO,MAAM,YAAY;;;;;;;kBAOvB,CAAC;AAEH,kDAAkD;AAClD,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC"}
package/dist/app.js ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * `guuey.json#app` — the App Store / Portal listing section.
3
+ *
4
+ * Describes how the deployable surfaces to end-users in Portal Discover
5
+ * and (optionally) at a custom domain. Read by the platform on first
6
+ * deploy to upsert an `AgentListing` row.
7
+ *
8
+ * Lives at the same level as `agent` and `ggui` sections — the artifact
9
+ * (agent or mcp-server) is what's deployable; this section is presentation.
10
+ */
11
+ import { z } from 'zod';
12
+ /**
13
+ * Slug used for the public URL and App Store listing. Forms part of the
14
+ * agent's reachable hostname: `<slug>.agents.<env>.guuey.com`.
15
+ *
16
+ * Slug uniqueness is enforced platform-side via the `SlugClaim` model.
17
+ * Matches `[a-z0-9][a-z0-9-]{1,62}` — lowercase, hyphens, no leading dash.
18
+ */
19
+ const SlugSchema = z
20
+ .string()
21
+ .min(2)
22
+ .max(63)
23
+ .regex(/^[a-z0-9][a-z0-9-]*[a-z0-9]$/, 'must be lowercase alphanumeric with optional internal hyphens');
24
+ /**
25
+ * Tag for App Store discovery. Free-form short strings; no curated taxonomy
26
+ * in α — Portal renders them as plain text chips for now.
27
+ */
28
+ const TagSchema = z.string().min(1).max(32);
29
+ /**
30
+ * Custom domain for the agent (e.g. `chef.example.com`). Optional.
31
+ *
32
+ * α requires explicit lifecycle: `guuey domain add <fqdn>` → user configures
33
+ * CNAME at registrar → `guuey domain verify <fqdn>` → platform requests
34
+ * per-domain ACM cert + attaches Ingress rule. Just setting this field does
35
+ * NOT auto-provision — the CLI warns when set without an attached domain
36
+ * record (see design doc §10.3).
37
+ */
38
+ const CustomDomainSchema = z
39
+ .string()
40
+ .min(4)
41
+ .max(253)
42
+ .regex(/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/i, 'must be a valid fully-qualified domain name');
43
+ /**
44
+ * The app section schema. All fields optional in v1 — a project may carry
45
+ * the bare minimum at first and grow the listing as it publishes.
46
+ */
47
+ export const AppSectionV1 = z.strictObject({
48
+ slug: SlugSchema.optional(),
49
+ name: z.string().min(1).max(120).optional(),
50
+ description: z.string().min(1).max(500).optional(),
51
+ iconUrl: z.url().optional(),
52
+ tags: z.array(TagSchema).max(10).optional(),
53
+ customDomain: CustomDomainSchema.optional(),
54
+ });
package/dist/ggui.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * `guuey.json#ggui` — optional cross-protocol integration section.
3
+ *
4
+ * Reserved for projects that use the ggui protocol (mcp.ggui.ai) for
5
+ * rendering. The actual ggui-app identity (slug, gadgets, publicEnv,
6
+ * etc.) lives in a separate `ggui.json` file owned by the ggui
7
+ * ecosystem (`@ggui-ai/project-config`). This section provides the
8
+ * reference from `guuey.json` to that sibling file.
9
+ *
10
+ * Two reference forms:
11
+ *
12
+ * - **File path** — `{ configFile: './ggui.json' }` resolves the sibling
13
+ * file at deploy time. Recommended for most projects.
14
+ * - **Inline** — `{ inline: { ...ggui-json-fields } }` for projects that
15
+ * prefer a single config file. Schema validation defers to the ggui
16
+ * ecosystem (we only type the wrapper).
17
+ *
18
+ * Cross-ecosystem boundary: guuey-side does NOT validate the ggui section's
19
+ * contents. The platform is MCP-server-agnostic — agents can use
20
+ * `mcp.ggui.ai` and get rendering for free, or use a different MCP server
21
+ * and skip this section entirely.
22
+ */
23
+ import { z } from 'zod';
24
+ /**
25
+ * The ggui integration section schema. Mutually exclusive forms:
26
+ * `configFile` reference OR `inline` object. Both optional — projects
27
+ * that don't use ggui rendering omit the whole `ggui` block.
28
+ */
29
+ export declare const GguiSectionV1: z.ZodObject<{
30
+ appId: z.ZodOptional<z.ZodString>;
31
+ configFile: z.ZodOptional<z.ZodString>;
32
+ inline: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
33
+ }, z.core.$strict>;
34
+ /** Static TypeScript type for the ggui integration section. */
35
+ export type GuueyGguiSection = z.infer<typeof GguiSectionV1>;
36
+ //# sourceMappingURL=ggui.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ggui.d.ts","sourceRoot":"","sources":["../src/ggui.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;GAIG;AACH,eAAO,MAAM,aAAa;;;;kBAyBvB,CAAC;AAEJ,+DAA+D;AAC/D,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC"}
package/dist/ggui.js ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * `guuey.json#ggui` — optional cross-protocol integration section.
3
+ *
4
+ * Reserved for projects that use the ggui protocol (mcp.ggui.ai) for
5
+ * rendering. The actual ggui-app identity (slug, gadgets, publicEnv,
6
+ * etc.) lives in a separate `ggui.json` file owned by the ggui
7
+ * ecosystem (`@ggui-ai/project-config`). This section provides the
8
+ * reference from `guuey.json` to that sibling file.
9
+ *
10
+ * Two reference forms:
11
+ *
12
+ * - **File path** — `{ configFile: './ggui.json' }` resolves the sibling
13
+ * file at deploy time. Recommended for most projects.
14
+ * - **Inline** — `{ inline: { ...ggui-json-fields } }` for projects that
15
+ * prefer a single config file. Schema validation defers to the ggui
16
+ * ecosystem (we only type the wrapper).
17
+ *
18
+ * Cross-ecosystem boundary: guuey-side does NOT validate the ggui section's
19
+ * contents. The platform is MCP-server-agnostic — agents can use
20
+ * `mcp.ggui.ai` and get rendering for free, or use a different MCP server
21
+ * and skip this section entirely.
22
+ */
23
+ import { z } from 'zod';
24
+ /**
25
+ * The ggui integration section schema. Mutually exclusive forms:
26
+ * `configFile` reference OR `inline` object. Both optional — projects
27
+ * that don't use ggui rendering omit the whole `ggui` block.
28
+ */
29
+ export const GguiSectionV1 = z
30
+ .strictObject({
31
+ /**
32
+ * The bound ggui app id — the federation `aud` target. ggui provisions
33
+ * the `GguiApp` and hands the builder this fixed id (federation contract
34
+ * §4 / `docs/plans/2026-06-14-ggui-guuey-identity-federation.md`). The
35
+ * pod addresses `mcp.ggui.ai/apps/<appId>` and mints tokens with
36
+ * `aud = https://mcp.ggui.ai/apps/<appId>`. Omit to use `mcp.ggui.ai`
37
+ * without per-app federation (no minted token).
38
+ */
39
+ appId: z
40
+ .string()
41
+ .regex(/^[A-Za-z0-9_-]{1,64}$/, 'ggui.appId must be a ggui app id ([A-Za-z0-9_-], ≤64)')
42
+ .optional(),
43
+ configFile: z.string().min(1).optional(),
44
+ /**
45
+ * Inline ggui config (opaque to this package). The actual schema
46
+ * lives in `@ggui-ai/project-config`; we type it as a record here
47
+ * to avoid coupling the OSS guuey config package to the ggui SDK.
48
+ */
49
+ inline: z.record(z.string(), z.unknown()).optional(),
50
+ })
51
+ .refine((val) => !(val.configFile && val.inline), { message: '`ggui` cannot specify both `configFile` and `inline` — pick one' });