@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.
Files changed (117) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +96 -0
  3. package/dist/agent-resolution.d.ts +41 -0
  4. package/dist/agent-resolution.d.ts.map +1 -0
  5. package/dist/agent-resolution.js +80 -0
  6. package/dist/api-client.d.ts +59 -0
  7. package/dist/api-client.d.ts.map +1 -0
  8. package/dist/api-client.js +145 -0
  9. package/dist/auth-keys.d.ts +10 -0
  10. package/dist/auth-keys.d.ts.map +1 -0
  11. package/dist/auth-keys.js +382 -0
  12. package/dist/auth-login.d.ts +11 -0
  13. package/dist/auth-login.d.ts.map +1 -0
  14. package/dist/auth-login.js +231 -0
  15. package/dist/auth-logout.d.ts +3 -0
  16. package/dist/auth-logout.d.ts.map +1 -0
  17. package/dist/auth-logout.js +32 -0
  18. package/dist/auth-store.d.ts +67 -0
  19. package/dist/auth-store.d.ts.map +1 -0
  20. package/dist/auth-store.js +150 -0
  21. package/dist/auth-whoami.d.ts +3 -0
  22. package/dist/auth-whoami.d.ts.map +1 -0
  23. package/dist/auth-whoami.js +52 -0
  24. package/dist/blueprint-command.d.ts +3 -0
  25. package/dist/blueprint-command.d.ts.map +1 -0
  26. package/dist/blueprint-command.js +203 -0
  27. package/dist/blueprint-create.d.ts +71 -0
  28. package/dist/blueprint-create.d.ts.map +1 -0
  29. package/dist/blueprint-create.js +377 -0
  30. package/dist/byok-resolver.d.ts +141 -0
  31. package/dist/byok-resolver.d.ts.map +1 -0
  32. package/dist/byok-resolver.js +89 -0
  33. package/dist/cli.d.ts +3 -0
  34. package/dist/cli.d.ts.map +1 -0
  35. package/dist/cli.js +1066 -0
  36. package/dist/dev-command.d.ts +169 -0
  37. package/dist/dev-command.d.ts.map +1 -0
  38. package/dist/dev-command.js +222 -0
  39. package/dist/gadget-command.d.ts +3 -0
  40. package/dist/gadget-command.d.ts.map +1 -0
  41. package/dist/gadget-command.js +181 -0
  42. package/dist/gadget-create.d.ts +106 -0
  43. package/dist/gadget-create.d.ts.map +1 -0
  44. package/dist/gadget-create.js +492 -0
  45. package/dist/generation-probe.d.ts +135 -0
  46. package/dist/generation-probe.d.ts.map +1 -0
  47. package/dist/generation-probe.js +132 -0
  48. package/dist/internal/artifact-install.d.ts +150 -0
  49. package/dist/internal/artifact-install.d.ts.map +1 -0
  50. package/dist/internal/artifact-install.js +1019 -0
  51. package/dist/internal/artifact-publish.d.ts +204 -0
  52. package/dist/internal/artifact-publish.d.ts.map +1 -0
  53. package/dist/internal/artifact-publish.js +1141 -0
  54. package/dist/internal/artifact-search.d.ts +155 -0
  55. package/dist/internal/artifact-search.d.ts.map +1 -0
  56. package/dist/internal/artifact-search.js +539 -0
  57. package/dist/internal/artifact-uninstall.d.ts +30 -0
  58. package/dist/internal/artifact-uninstall.d.ts.map +1 -0
  59. package/dist/internal/artifact-uninstall.js +205 -0
  60. package/dist/internal/auth-cache.d.ts +40 -0
  61. package/dist/internal/auth-cache.d.ts.map +1 -0
  62. package/dist/internal/auth-cache.js +133 -0
  63. package/dist/internal/auth-strategy.d.ts +89 -0
  64. package/dist/internal/auth-strategy.d.ts.map +1 -0
  65. package/dist/internal/auth-strategy.js +132 -0
  66. package/dist/internal/ggui-json.d.ts +53 -0
  67. package/dist/internal/ggui-json.d.ts.map +1 -0
  68. package/dist/internal/ggui-json.js +92 -0
  69. package/dist/internal/key-store.d.ts +43 -0
  70. package/dist/internal/key-store.d.ts.map +1 -0
  71. package/dist/internal/key-store.js +128 -0
  72. package/dist/internal/oidc-token.d.ts +52 -0
  73. package/dist/internal/oidc-token.d.ts.map +1 -0
  74. package/dist/internal/oidc-token.js +351 -0
  75. package/dist/internal/register-author-key.d.ts +44 -0
  76. package/dist/internal/register-author-key.d.ts.map +1 -0
  77. package/dist/internal/register-author-key.js +212 -0
  78. package/dist/local-keys.d.ts +47 -0
  79. package/dist/local-keys.d.ts.map +1 -0
  80. package/dist/local-keys.js +115 -0
  81. package/dist/mcp-backend.d.ts +356 -0
  82. package/dist/mcp-backend.d.ts.map +1 -0
  83. package/dist/mcp-backend.js +668 -0
  84. package/dist/paths.d.ts +77 -0
  85. package/dist/paths.d.ts.map +1 -0
  86. package/dist/paths.js +119 -0
  87. package/dist/persistent-secrets.d.ts +28 -0
  88. package/dist/persistent-secrets.d.ts.map +1 -0
  89. package/dist/persistent-secrets.js +94 -0
  90. package/dist/persistent-short-code-index.d.ts +14 -0
  91. package/dist/persistent-short-code-index.d.ts.map +1 -0
  92. package/dist/persistent-short-code-index.js +39 -0
  93. package/dist/persistent-stores.d.ts +12 -0
  94. package/dist/persistent-stores.d.ts.map +1 -0
  95. package/dist/persistent-stores.js +42 -0
  96. package/dist/serve-command.d.ts +500 -0
  97. package/dist/serve-command.d.ts.map +1 -0
  98. package/dist/serve-command.js +616 -0
  99. package/dist/serve-fallback.d.ts +64 -0
  100. package/dist/serve-fallback.d.ts.map +1 -0
  101. package/dist/serve-fallback.js +122 -0
  102. package/dist/spawn-cloudflared.d.ts +61 -0
  103. package/dist/spawn-cloudflared.d.ts.map +1 -0
  104. package/dist/spawn-cloudflared.js +167 -0
  105. package/dist/theme-command.d.ts +4 -0
  106. package/dist/theme-command.d.ts.map +1 -0
  107. package/dist/theme-command.js +154 -0
  108. package/dist/theme-file-uploader.d.ts +8 -0
  109. package/dist/theme-file-uploader.d.ts.map +1 -0
  110. package/dist/theme-file-uploader.js +58 -0
  111. package/dist/theme-writer.d.ts +8 -0
  112. package/dist/theme-writer.d.ts.map +1 -0
  113. package/dist/theme-writer.js +103 -0
  114. package/dist/tunnel-provider.d.ts +273 -0
  115. package/dist/tunnel-provider.d.ts.map +1 -0
  116. package/dist/tunnel-provider.js +142 -0
  117. package/package.json +81 -0
@@ -0,0 +1,103 @@
1
+ /**
2
+ * `themeWriter` factory for `ggui serve`.
3
+ *
4
+ * The OSS server (`@ggui-ai/mcp-server`) defines a `ThemeWriter` seam
5
+ * but never touches the filesystem itself — it only knows the shape it
6
+ * wants to persist (`ThemeConfig | null`). The CLI plugs in the side
7
+ * that knows where `ggui.json` lives.
8
+ *
9
+ * Behavior:
10
+ *
11
+ * - Read the existing `ggui.json`.
12
+ * - Parse as JSON. Refuse on malformed input — we will not paper over
13
+ * a manifest the CLI can't round-trip safely.
14
+ * - Mutate exactly the `theme` field (`null` argument removes it
15
+ * entirely so the manifest falls back to the default-theme branch).
16
+ * - Write back atomically (`tmp` + `rename`) with 2-space indent,
17
+ * trailing newline. All other fields are preserved verbatim — this
18
+ * is a JSON round-trip, NOT a re-serialize-from-schema, so unknown
19
+ * fields the operator added (forward-compat schema, custom
20
+ * extensions) survive a Save.
21
+ *
22
+ * What v1 does NOT preserve:
23
+ *
24
+ * - JSON5-style comments (the schema doesn't allow them but operators
25
+ * occasionally have a fork that does).
26
+ * - Original indentation (always rewritten to 2-space).
27
+ * - Trailing-comma tolerance (JSON.parse already rejects, so the
28
+ * refusal is upstream of us).
29
+ *
30
+ * Operators who need comment-preserving edits should hand-edit the
31
+ * file; the picker is a write-through path for the common case.
32
+ *
33
+ * Validation: `ThemeConfig` was already validated upstream by
34
+ * `mountDevtoolThemeRoutes` (Zod safeParse against `ThemeConfigSchema`)
35
+ * before reaching the writer. We re-do the type-narrow shape check on
36
+ * the resulting object purely so a programmatic embedder that called
37
+ * `themeWriter` directly with a malformed value gets a 400-equivalent
38
+ * `Error` instead of writing nonsense to disk.
39
+ */
40
+ import { dirname } from 'node:path';
41
+ import { promises as fs } from 'node:fs';
42
+ import { ThemeConfigSchema, } from '@ggui-ai/project-config';
43
+ /**
44
+ * Build a {@link ThemeWriter} bound to the manifest at `manifestPath`.
45
+ * The returned function is async, idempotent for equal inputs, and
46
+ * crash-safe via a `tmp` + `rename` write.
47
+ */
48
+ export function createThemeWriter(manifestPath) {
49
+ return async (config) => {
50
+ if (config !== null) {
51
+ // Belt-and-braces re-validate. The server's transport already
52
+ // does this, but the seam is public — any consumer that reaches
53
+ // it directly should still get a clear refusal rather than a
54
+ // silent shape leak into ggui.json.
55
+ const result = ThemeConfigSchema.safeParse(config);
56
+ if (!result.success) {
57
+ throw new Error(`themeWriter: invalid ThemeConfig — ${JSON.stringify(result.error.flatten())}`);
58
+ }
59
+ }
60
+ let raw;
61
+ try {
62
+ raw = await fs.readFile(manifestPath, 'utf-8');
63
+ }
64
+ catch (err) {
65
+ // ENOENT = the manifest disappeared between server boot and
66
+ // first save. Surface clearly so the operator sees the cause
67
+ // instead of a silent data-loss-by-creation.
68
+ throw new Error(`themeWriter: failed to read ${manifestPath} — ${err instanceof Error ? err.message : String(err)}`);
69
+ }
70
+ let parsed;
71
+ try {
72
+ const json = JSON.parse(raw);
73
+ if (typeof json !== 'object' || json === null || Array.isArray(json)) {
74
+ throw new Error('manifest is not a JSON object');
75
+ }
76
+ parsed = json;
77
+ }
78
+ catch (err) {
79
+ throw new Error(`themeWriter: ${manifestPath} is not parseable JSON — ${err instanceof Error ? err.message : String(err)}`);
80
+ }
81
+ if (config === null) {
82
+ delete parsed['theme'];
83
+ }
84
+ else {
85
+ parsed['theme'] = config;
86
+ }
87
+ const next = `${JSON.stringify(parsed, null, 2)}\n`;
88
+ // Atomic write via tmp + rename — same pattern as
89
+ // `oauth-providers-store::writeFileShape`. A crash mid-write leaves
90
+ // the original `ggui.json` intact.
91
+ const tmp = `${manifestPath}.tmp`;
92
+ await fs.mkdir(dirname(manifestPath), { recursive: true });
93
+ const handle = await fs.open(tmp, 'w');
94
+ try {
95
+ await handle.writeFile(next, 'utf-8');
96
+ await handle.sync();
97
+ }
98
+ finally {
99
+ await handle.close();
100
+ }
101
+ await fs.rename(tmp, manifestPath);
102
+ };
103
+ }
@@ -0,0 +1,273 @@
1
+ /**
2
+ * `TunnelProvider` — narrow seam for managed-mode tunnel bootstrap.
3
+ *
4
+ * Design:
5
+ *
6
+ * - `@ggui-ai/dev-stack` stays local-engine only. No tunnel code
7
+ * lives inside the dev-stack barrel. The CLI is the layer that
8
+ * decides whether to wrap a running dev-stack host in a managed
9
+ * tunnel.
10
+ * - `TunnelProvider` is intentionally the minimum interface a
11
+ * provider needs to satisfy. Future providers (Cloudflare
12
+ * tunnel, Guuey-managed auth-proxy tunnel, Tailscale funnel,
13
+ * ngrok-style forwarder) plug in behind this seam without
14
+ * touching dev-stack or the core `runDev` coordination logic.
15
+ * - The current scaffold ships a **null** provider that always
16
+ * returns `{ status: 'unavailable' }`. This proves the
17
+ * architecture end-to-end without shipping a public-tunnel
18
+ * story before remote authentication is resolved.
19
+ *
20
+ * Non-negotiable invariants:
21
+ *
22
+ * 1. `open()` MUST NOT throw for missing configuration. It returns
23
+ * `{ status: 'unavailable' }` so the CLI can print a clean
24
+ * status line and keep local dev running unchanged. Only
25
+ * genuinely exceptional failures (invariant violations in the
26
+ * provider itself) are allowed to throw.
27
+ * 2. Providers do not own the decision to expose `/hub*` over the
28
+ * tunnel. `TunnelContext.authToken` is surfaced so providers
29
+ * CAN use it for bearer-authenticated upstream calls — but
30
+ * providers MUST NOT leak it into public tunnel responses.
31
+ * Remote hub exposure requires a separate auth layer
32
+ * (see plan doc §"Open: remote auth").
33
+ * 3. `close()` on a ready session MUST be idempotent + safe to
34
+ * call after the underlying `AbortSignal` has fired.
35
+ */
36
+ /**
37
+ * Snapshot of the running local dev stack handed to a tunnel
38
+ * provider. Readonly — the provider inspects and wraps, but cannot
39
+ * reconfigure the local host.
40
+ */
41
+ export interface TunnelContext {
42
+ /**
43
+ * Fully-qualified URL of the local dev-stack HTTP host, e.g.
44
+ * `http://127.0.0.1:6780`. Never a wildcard bind or a path —
45
+ * the CLI normalises before handing to the provider.
46
+ */
47
+ readonly localUrl: string;
48
+ /**
49
+ * Bearer token the dev-stack accepts for gated endpoints
50
+ * (`/uis/*`, `/events`, `/runtime/*`). `null` when the server
51
+ * was started without a security policy (tests).
52
+ *
53
+ * Providers MAY use this to make bearer-authenticated upstream
54
+ * calls against the local host (e.g. to probe `/health` or
55
+ * pre-fetch state during bootstrap). Providers MUST NOT pass
56
+ * the bearer through to public tunnel clients — the bearer is
57
+ * a per-run secret, not a user credential.
58
+ */
59
+ readonly authToken: string | null;
60
+ /**
61
+ * Project identity surfaced from `ggui.json`. Providers use
62
+ * this as a stable handle when requesting a remote URL
63
+ * ("open a tunnel for `weather-bot`"). Kept narrow on purpose;
64
+ * richer metadata can be added later without a breaking change.
65
+ */
66
+ readonly project: {
67
+ readonly slug: string;
68
+ readonly name: string;
69
+ };
70
+ /**
71
+ * Port the supervised agent runtime is bound on, when an `--agent`
72
+ * was supplied AND the CLI orchestration asked the dev-stack to
73
+ * wire a specific port (`DevOptions.runtimePort`). `null` when:
74
+ *
75
+ * - no agent runtime was supplied (`--agent` absent), OR
76
+ * - the agent runtime was supplied but the CLI did not ask for a
77
+ * specific port (e.g. the user ran `ggui dev --agent …` without
78
+ * `--tunnel`).
79
+ *
80
+ * Bridge-style tunnel providers use this to construct a
81
+ * `UrlProxy('http://127.0.0.1:<runtimePort>')` or equivalent for
82
+ * forwarding inbound platform traffic to the local agent. Other
83
+ * providers (forwarders that wrap `localUrl` end-to-end) MAY
84
+ * ignore it.
85
+ */
86
+ readonly runtimePort: number | null;
87
+ /**
88
+ * Lifetime signal. Aborted when the CLI is shutting down the
89
+ * dev stack (Ctrl-C, SIGTERM, server.close()). Providers that
90
+ * maintain long-lived connections MUST wire this signal into
91
+ * their underlying transport so the local server shutdown is
92
+ * not blocked by a dangling tunnel socket.
93
+ */
94
+ readonly signal: AbortSignal;
95
+ }
96
+ /**
97
+ * Discriminated result of `TunnelProvider.open(ctx)`.
98
+ *
99
+ * - `ready` → managed tunnel is up; remoteUrl is printable.
100
+ * - `unavailable` → provider correctly declined (missing
101
+ * configuration, expired auth, offline), and
102
+ * the CLI should fall back to local-only.
103
+ * `reason` is user-facing; `hint` is an
104
+ * optional actionable suggestion.
105
+ */
106
+ export type TunnelSession = TunnelSessionReady | TunnelSessionUnavailable;
107
+ export interface TunnelSessionReady {
108
+ readonly status: 'ready';
109
+ /** Public-facing URL the developer can share. */
110
+ readonly remoteUrl: string;
111
+ /**
112
+ * Shut down the tunnel session. Idempotent — calling twice (or
113
+ * calling after `signal` fired) is a no-op. Resolves when the
114
+ * underlying transport has released its resources.
115
+ */
116
+ close(): Promise<void>;
117
+ }
118
+ export interface TunnelSessionUnavailable {
119
+ readonly status: 'unavailable';
120
+ /**
121
+ * One-line user-facing reason. Printed in the CLI banner, so
122
+ * keep it short and concrete (e.g. `"not configured"`,
123
+ * `"login expired"`, `"network unreachable"`).
124
+ */
125
+ readonly reason: string;
126
+ /**
127
+ * Optional follow-up action — typically a command the user can
128
+ * run to unblock (`"run \`<your-cli> login\`"`). May be omitted
129
+ * when the reason is self-evident.
130
+ */
131
+ readonly hint?: string;
132
+ }
133
+ /**
134
+ * A single managed-mode tunnel implementation. Providers are
135
+ * pluggable by construction — a CLI host picks one at boot time
136
+ * (flag, env var, discovery) and hands it to the orchestrator.
137
+ */
138
+ export interface TunnelProvider {
139
+ /**
140
+ * Human-readable provider name for banner / logs. Keep short
141
+ * (`"null"`, `"cloudflare"`). Not used for any protocol dispatch
142
+ * — display only.
143
+ */
144
+ readonly name: string;
145
+ /**
146
+ * Attempt to establish a managed tunnel for the running local
147
+ * dev host. Non-throwing by contract — missing configuration is
148
+ * `{ status: 'unavailable', reason, hint }`, not an exception.
149
+ *
150
+ * Providers respecting `ctx.signal` MAY return before the
151
+ * tunnel is fully dialled if the signal fires during bootstrap.
152
+ */
153
+ open(ctx: TunnelContext): Promise<TunnelSession>;
154
+ }
155
+ /**
156
+ * Reference "null" tunnel provider.
157
+ *
158
+ * Purpose:
159
+ *
160
+ * - Proves the `TunnelProvider` seam works end-to-end (the CLI
161
+ * can construct one, wire it into `runDev` orchestration, and
162
+ * print the expected banner line).
163
+ * - Never makes a network call, never reads config files, never
164
+ * spawns a subprocess. Safe to run under any environment
165
+ * policy (CI, sandboxes, sandboxed CI, locked-down laptops).
166
+ * - Acts as the default when `ggui dev --tunnel` runs without a
167
+ * configured provider. The user sees an honest "no provider
168
+ * configured" line instead of a cryptic network error.
169
+ *
170
+ * An optional `reason` override lets tests pin a specific
171
+ * status string without monkey-patching.
172
+ */
173
+ export interface NullTunnelProviderOptions {
174
+ /**
175
+ * Override the default reason string. Useful for tests asserting
176
+ * the banner reads the expected copy, or for hosts that want to
177
+ * customise the "not configured" message without replacing the
178
+ * provider.
179
+ */
180
+ readonly reason?: string;
181
+ /** Optional hint shown alongside the reason. */
182
+ readonly hint?: string;
183
+ }
184
+ export declare function createNullTunnelProvider(options?: NullTunnelProviderOptions): TunnelProvider;
185
+ /**
186
+ * Contract a tunnel-provider module must satisfy for dynamic
187
+ * discovery. Modules export either a default factory or a named
188
+ * `createTunnelProvider` factory — both shapes are tried in that
189
+ * order. The factory takes no arguments for the first slice; future
190
+ * revisions MAY pass options (project identity, login snapshot)
191
+ * behind a backwards-compatible shim.
192
+ *
193
+ * Kept as a module shape rather than a typed `require` because the
194
+ * target module path is controlled by the user's env, not by the
195
+ * open CLI's dependency graph. The discovery wrapper below validates
196
+ * the shape at runtime and falls back cleanly on any mismatch.
197
+ */
198
+ export interface TunnelProviderModule {
199
+ /** Preferred shape. */
200
+ readonly createTunnelProvider?: () => TunnelProvider | Promise<TunnelProvider>;
201
+ /** Backwards-compatible shape — default export returning the provider. */
202
+ readonly default?: TunnelProvider | (() => TunnelProvider | Promise<TunnelProvider>);
203
+ }
204
+ /**
205
+ * Options for the provider-discovery helper. `resolve` is a test
206
+ * seam that stands in for `import()` — production callers pass no
207
+ * options and the helper uses the real dynamic import.
208
+ */
209
+ export interface DiscoverTunnelProviderOptions {
210
+ /**
211
+ * Module specifier to attempt. Default: `process.env.GGUI_TUNNEL_PROVIDER`.
212
+ * When unset / empty, no discovery is attempted and the caller
213
+ * falls back to the null provider with reason "no provider
214
+ * configured".
215
+ */
216
+ readonly moduleSpecifier?: string | null;
217
+ /**
218
+ * Test seam — receives the module specifier and returns the
219
+ * imported module (or throws to simulate a failed import).
220
+ * Production callers leave this undefined; the helper then uses
221
+ * the real dynamic `import()`.
222
+ */
223
+ readonly resolve?: (specifier: string) => Promise<unknown>;
224
+ }
225
+ /**
226
+ * Discriminated result of `discoverTunnelProvider`.
227
+ *
228
+ * - `found` → the module path resolved AND its factory produced a
229
+ * provider. The CLI uses `provider` directly.
230
+ * - `none` → no module specifier set; expected for OSS users who
231
+ * haven't installed a private provider. Caller falls
232
+ * back to the null provider with a specific reason.
233
+ * - `error` → a specifier was configured but loading / instantiating
234
+ * failed. Caller falls back to the null provider with
235
+ * the failure reason surfaced so the user can act
236
+ * (fix path, reinstall, etc.).
237
+ */
238
+ export type TunnelProviderDiscovery = {
239
+ readonly kind: 'found';
240
+ readonly provider: TunnelProvider;
241
+ readonly moduleSpecifier: string;
242
+ } | {
243
+ readonly kind: 'none';
244
+ } | {
245
+ readonly kind: 'error';
246
+ readonly moduleSpecifier: string;
247
+ readonly reason: string;
248
+ };
249
+ /**
250
+ * Attempt to load a `TunnelProvider` from an external module whose
251
+ * path is given by `process.env.GGUI_TUNNEL_PROVIDER` (or the
252
+ * `moduleSpecifier` override).
253
+ *
254
+ * Discovery mechanics:
255
+ *
256
+ * 1. If no specifier is configured → `{ kind: 'none' }`. The
257
+ * caller prints "no tunnel provider configured" and uses the
258
+ * null provider.
259
+ * 2. Dynamic-import the specifier. On any thrown error →
260
+ * `{ kind: 'error', reason }` — caller prints the reason.
261
+ * 3. Read `module.createTunnelProvider` first, then `module.default`.
262
+ * Both may be factory functions or (for `default` only) an
263
+ * already-built provider object. Anything else → `error`.
264
+ * 4. Invoke the factory (if callable). If it throws → `error`.
265
+ * 5. Validate the returned value has `.name: string` + `.open`
266
+ * callable. If not → `error`.
267
+ * 6. Success → `{ kind: 'found', provider, moduleSpecifier }`.
268
+ *
269
+ * Never throws. The null-provider fallback is the single recovery
270
+ * path across every failure mode.
271
+ */
272
+ export declare function discoverTunnelProvider(options?: DiscoverTunnelProviderOptions): Promise<TunnelProviderDiscovery>;
273
+ //# sourceMappingURL=tunnel-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunnel-provider.d.ts","sourceRoot":"","sources":["../src/tunnel-provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * `TunnelProvider` — narrow seam for managed-mode tunnel bootstrap.
3
+ *
4
+ * Design:
5
+ *
6
+ * - `@ggui-ai/dev-stack` stays local-engine only. No tunnel code
7
+ * lives inside the dev-stack barrel. The CLI is the layer that
8
+ * decides whether to wrap a running dev-stack host in a managed
9
+ * tunnel.
10
+ * - `TunnelProvider` is intentionally the minimum interface a
11
+ * provider needs to satisfy. Future providers (Cloudflare
12
+ * tunnel, Guuey-managed auth-proxy tunnel, Tailscale funnel,
13
+ * ngrok-style forwarder) plug in behind this seam without
14
+ * touching dev-stack or the core `runDev` coordination logic.
15
+ * - The current scaffold ships a **null** provider that always
16
+ * returns `{ status: 'unavailable' }`. This proves the
17
+ * architecture end-to-end without shipping a public-tunnel
18
+ * story before remote authentication is resolved.
19
+ *
20
+ * Non-negotiable invariants:
21
+ *
22
+ * 1. `open()` MUST NOT throw for missing configuration. It returns
23
+ * `{ status: 'unavailable' }` so the CLI can print a clean
24
+ * status line and keep local dev running unchanged. Only
25
+ * genuinely exceptional failures (invariant violations in the
26
+ * provider itself) are allowed to throw.
27
+ * 2. Providers do not own the decision to expose `/hub*` over the
28
+ * tunnel. `TunnelContext.authToken` is surfaced so providers
29
+ * CAN use it for bearer-authenticated upstream calls — but
30
+ * providers MUST NOT leak it into public tunnel responses.
31
+ * Remote hub exposure requires a separate auth layer
32
+ * (see plan doc §"Open: remote auth").
33
+ * 3. `close()` on a ready session MUST be idempotent + safe to
34
+ * call after the underlying `AbortSignal` has fired.
35
+ */
36
+ export function createNullTunnelProvider(options = {}) {
37
+ const reason = options.reason ?? 'no tunnel provider configured';
38
+ const hint = options.hint;
39
+ return {
40
+ name: 'null',
41
+ async open(_ctx) {
42
+ // Keep the context parameter so the interface is exercised;
43
+ // the null provider ignores every field by design.
44
+ void _ctx;
45
+ const result = hint
46
+ ? { status: 'unavailable', reason, hint }
47
+ : { status: 'unavailable', reason };
48
+ return result;
49
+ },
50
+ };
51
+ }
52
+ /**
53
+ * Attempt to load a `TunnelProvider` from an external module whose
54
+ * path is given by `process.env.GGUI_TUNNEL_PROVIDER` (or the
55
+ * `moduleSpecifier` override).
56
+ *
57
+ * Discovery mechanics:
58
+ *
59
+ * 1. If no specifier is configured → `{ kind: 'none' }`. The
60
+ * caller prints "no tunnel provider configured" and uses the
61
+ * null provider.
62
+ * 2. Dynamic-import the specifier. On any thrown error →
63
+ * `{ kind: 'error', reason }` — caller prints the reason.
64
+ * 3. Read `module.createTunnelProvider` first, then `module.default`.
65
+ * Both may be factory functions or (for `default` only) an
66
+ * already-built provider object. Anything else → `error`.
67
+ * 4. Invoke the factory (if callable). If it throws → `error`.
68
+ * 5. Validate the returned value has `.name: string` + `.open`
69
+ * callable. If not → `error`.
70
+ * 6. Success → `{ kind: 'found', provider, moduleSpecifier }`.
71
+ *
72
+ * Never throws. The null-provider fallback is the single recovery
73
+ * path across every failure mode.
74
+ */
75
+ export async function discoverTunnelProvider(options = {}) {
76
+ const specifier = options.moduleSpecifier !== undefined
77
+ ? options.moduleSpecifier
78
+ : process.env.GGUI_TUNNEL_PROVIDER ?? null;
79
+ if (!specifier || specifier.length === 0) {
80
+ return { kind: 'none' };
81
+ }
82
+ const resolve = options.resolve ?? ((s) => import(s));
83
+ let mod;
84
+ try {
85
+ mod = await resolve(specifier);
86
+ }
87
+ catch (err) {
88
+ return {
89
+ kind: 'error',
90
+ moduleSpecifier: specifier,
91
+ reason: `failed to import "${specifier}": ${err instanceof Error ? err.message : String(err)}`,
92
+ };
93
+ }
94
+ if (mod === null || typeof mod !== 'object') {
95
+ return {
96
+ kind: 'error',
97
+ moduleSpecifier: specifier,
98
+ reason: `module "${specifier}" did not export an object`,
99
+ };
100
+ }
101
+ const typed = mod;
102
+ const factory = typed.createTunnelProvider ??
103
+ (typeof typed.default === 'function' ? typed.default : null);
104
+ const preBuilt = typed.createTunnelProvider === undefined && typeof typed.default === 'object' && typed.default !== null
105
+ ? typed.default
106
+ : null;
107
+ let provider;
108
+ try {
109
+ if (preBuilt) {
110
+ provider = preBuilt;
111
+ }
112
+ else if (factory) {
113
+ const maybeProvider = await factory();
114
+ provider = maybeProvider;
115
+ }
116
+ else {
117
+ return {
118
+ kind: 'error',
119
+ moduleSpecifier: specifier,
120
+ reason: `module "${specifier}" exports neither \`createTunnelProvider\` nor a default export`,
121
+ };
122
+ }
123
+ }
124
+ catch (err) {
125
+ return {
126
+ kind: 'error',
127
+ moduleSpecifier: specifier,
128
+ reason: `provider factory in "${specifier}" threw: ${err instanceof Error ? err.message : String(err)}`,
129
+ };
130
+ }
131
+ if (!provider ||
132
+ typeof provider !== 'object' ||
133
+ typeof provider.name !== 'string' ||
134
+ typeof provider.open !== 'function') {
135
+ return {
136
+ kind: 'error',
137
+ moduleSpecifier: specifier,
138
+ reason: `module "${specifier}" returned a value that is not a TunnelProvider`,
139
+ };
140
+ }
141
+ return { kind: 'found', provider, moduleSpecifier: specifier };
142
+ }
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "@ggui-ai/cli",
3
+ "version": "0.1.0-rc.1",
4
+ "description": "The `ggui` CLI — run the ggui protocol locally: dev server, self-hosted MCP server, and gadget / blueprint authoring. End-to-end OSS and self-hosted workflows; no account required.",
5
+ "keywords": [
6
+ "ggui",
7
+ "cli",
8
+ "mcp",
9
+ "model-context-protocol",
10
+ "ai-agents",
11
+ "generative-ui",
12
+ "dev-server"
13
+ ],
14
+ "license": "Apache-2.0",
15
+ "type": "module",
16
+ "bin": {
17
+ "ggui": "./dist/cli.js"
18
+ },
19
+ "exports": {
20
+ "./tunnel-provider": {
21
+ "types": "./dist/tunnel-provider.d.ts",
22
+ "import": "./dist/tunnel-provider.js",
23
+ "default": "./dist/tunnel-provider.js"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "README.md"
29
+ ],
30
+ "dependencies": {
31
+ "@aws-sdk/client-cognito-identity-provider": "^3.600.0",
32
+ "@huggingface/transformers": "^3.0.0",
33
+ "@noble/hashes": "^1.6.0",
34
+ "esbuild": "^0.25.12",
35
+ "@ggui-ai/agent-runtime": "0.1.0-rc.1",
36
+ "@ggui-ai/artifact-manifest": "0.1.0-rc.1",
37
+ "@ggui-ai/design": "0.1.0-rc.1",
38
+ "@ggui-ai/dev-stack": "0.1.0-rc.1",
39
+ "@ggui-ai/gadget-signing": "0.1.0-rc.1",
40
+ "@ggui-ai/mcp-server": "0.1.0-rc.1",
41
+ "@ggui-ai/embedding-local": "0.1.0-rc.1",
42
+ "@ggui-ai/mcp-server-core": "0.1.0-rc.1",
43
+ "@ggui-ai/preview-a2ui": "0.1.0-rc.1",
44
+ "@ggui-ai/project-config": "0.1.0-rc.1",
45
+ "@ggui-ai/protocol": "0.1.0-rc.1",
46
+ "@ggui-ai/registry-core": "0.1.0-rc.1",
47
+ "@ggui-ai/iframe-runtime": "0.1.0-rc.1",
48
+ "@ggui-ai/mcp-server-handlers": "0.1.0-rc.1",
49
+ "@ggui-ai/ui-registry": "0.1.0-rc.1",
50
+ "@ggui-ai/ui-gen": "0.1.0-rc.1"
51
+ },
52
+ "devDependencies": {
53
+ "@types/node": "^24.0.0",
54
+ "typescript": "^5.0.0",
55
+ "vitest": "^3.0.0",
56
+ "zod": "^4.3.6"
57
+ },
58
+ "repository": {
59
+ "type": "git",
60
+ "url": "https://github.com/ggui-ai/ggui.git",
61
+ "directory": "packages/ggui-cli"
62
+ },
63
+ "homepage": "https://github.com/ggui-ai/ggui/tree/main/packages/ggui-cli",
64
+ "bugs": {
65
+ "url": "https://github.com/ggui-ai/ggui/issues"
66
+ },
67
+ "publishConfig": {
68
+ "access": "public"
69
+ },
70
+ "engines": {
71
+ "node": ">=20.0.0"
72
+ },
73
+ "author": "ggui contributors <hello@ggui.ai>",
74
+ "scripts": {
75
+ "build": "tsc -p tsconfig.build.json && chmod +x dist/cli.js",
76
+ "dev": "tsc --watch",
77
+ "typecheck": "tsc --noEmit",
78
+ "test": "vitest run --passWithNoTests",
79
+ "test:watch": "vitest"
80
+ }
81
+ }