@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,169 @@
1
+ /**
2
+ * `ggui dev` argument parsing + runtime resolution + browser auto-open.
3
+ *
4
+ * Extracted from `cli.ts` so the pure logic (flag parsing, agent
5
+ * entry → command mapping, hub URL resolution, auto-open decision)
6
+ * is unit-testable without importing the bin's top-level `main()`
7
+ * side effect. The bin itself stays tiny.
8
+ *
9
+ * Browser auto-open lives here (the CLI layer) intentionally —
10
+ * `@ggui-ai/dev-stack` keeps owning the server/runtime stack; only
11
+ * the `ggui` binary decides "a human is driving this, open a tab."
12
+ * Other hosts (the future `guuey dev` shim, a hub spawned from a
13
+ * tunnel, a test harness) pick their own launch policy.
14
+ */
15
+ import { type ChildProcess, type SpawnOptions } from 'node:child_process';
16
+ import { buildAgentRuntime, resolveAgentCommand, type AgentCommandResolution } from './agent-resolution.js';
17
+ import type { TunnelContext, TunnelProvider, TunnelSession, TunnelSessionReady, TunnelSessionUnavailable } from './tunnel-provider.js';
18
+ export interface ParsedDevFlags {
19
+ port?: number;
20
+ host?: string;
21
+ noServe: boolean;
22
+ noOpen: boolean;
23
+ /**
24
+ * Opt into managed-mode. When true, the CLI orchestration calls
25
+ * the configured `TunnelProvider` after the local dev stack is
26
+ * listening. The local loop always runs unchanged — `--tunnel`
27
+ * only adds the managed layer above it.
28
+ */
29
+ tunnel: boolean;
30
+ agent?: string;
31
+ error?: string;
32
+ }
33
+ /**
34
+ * Parse the raw `ggui dev` argv tail. Returns a discriminated
35
+ * shape — `error` field is non-undefined when the input was
36
+ * malformed; callers render it and bail with exit code 1.
37
+ */
38
+ export declare function parseDevFlags(args: readonly string[]): ParsedDevFlags;
39
+ export { resolveAgentCommand, buildAgentRuntime };
40
+ export type { AgentCommandResolution };
41
+ /**
42
+ * Build the URL the local hub is reachable at. Normalises
43
+ * wildcard bind addresses (`0.0.0.0` / `::`) to `127.0.0.1` so
44
+ * the link is actually clickable — the dev's browser can't dial
45
+ * `0.0.0.0`. Loopback host binds pass through unchanged.
46
+ */
47
+ export declare function resolveHubUrl(host: string, port: number): string;
48
+ /** Inputs that drive the auto-open decision. Pure data so tests
49
+ * exercise every branch without touching real env / stdio. */
50
+ export interface AutoOpenContext {
51
+ /** `true` when the command is serving a real HTTP port. The
52
+ * `--no-serve` path never opens anything. */
53
+ readonly serving: boolean;
54
+ /** `true` when the user passed `--no-open` — the explicit
55
+ * escape hatch. Highest-precedence skip reason. */
56
+ readonly noOpen: boolean;
57
+ /** Whether the CLI's stdout is attached to an interactive TTY.
58
+ * Pipelines and detached contexts set this to `false`. */
59
+ readonly isTty: boolean;
60
+ /** Process env — read here so tests can substitute. */
61
+ readonly env: Readonly<Record<string, string | undefined>>;
62
+ }
63
+ /**
64
+ * Auto-open policy — `shouldAutoOpen` returns a discriminated
65
+ * decision. The `reason` field is both a test hook and a human
66
+ * diagnostic the CLI prints when it decides NOT to open. Never
67
+ * returns an error; the only shape is `{ open: boolean; reason }`.
68
+ *
69
+ * Rules (first-match wins):
70
+ *
71
+ * 1. `noOpen=true` → explicit `--no-open`.
72
+ * 2. `serving=false` → no server to open.
73
+ * 3. `CI=<truthy>` → CI runner; headless by convention.
74
+ * 4. `BROWSER=none` → explicit opt-out (create-react-app / expo
75
+ * pattern).
76
+ * 5. `isTty=false` → piped / daemonised; probably scripted.
77
+ * 6. otherwise → open.
78
+ */
79
+ export declare function shouldAutoOpen(ctx: AutoOpenContext): {
80
+ open: boolean;
81
+ reason: string;
82
+ };
83
+ /**
84
+ * Browser launcher — best-effort, cross-platform, dep-free.
85
+ *
86
+ * - Honours `$BROWSER` when set (and not `'none'`), so users who
87
+ * explicitly pick a browser get their choice.
88
+ * - Otherwise delegates to the platform's standard opener:
89
+ * darwin → `open <url>`
90
+ * win32 → `cmd /c start "" <url>` (empty title avoids the
91
+ * first quoted arg being
92
+ * consumed as the window
93
+ * title — a Windows gotcha)
94
+ * other → `xdg-open <url>`
95
+ * - Child is spawned detached with stdio ignored so our parent
96
+ * process is free to exit / stay alive independently; `unref()`
97
+ * ensures the event loop doesn't wait on it.
98
+ * - Async spawn errors (ENOENT when `xdg-open` isn't installed on
99
+ * a minimal Linux image) are caught on the `error` event — we
100
+ * don't surface them because by the time they fire the caller
101
+ * has already printed the URL and returned; they're purely
102
+ * cosmetic.
103
+ *
104
+ * Return value semantics:
105
+ * `ok: true` — spawn was dispatched without a synchronous throw.
106
+ * (Actual browser launch may still fail async.)
107
+ * `ok: false` — synchronous spawn throw (invalid command, etc.).
108
+ * Caller should treat this as "no browser opened"
109
+ * and hint at the URL.
110
+ */
111
+ export interface LaunchBrowserOptions {
112
+ readonly env?: Readonly<Record<string, string | undefined>>;
113
+ readonly platform?: NodeJS.Platform;
114
+ /** Seam for tests — receives `(cmd, args)` and MUST return a
115
+ * `ChildProcess`-shaped object with `.on()` + `.unref()`. */
116
+ readonly spawner?: Spawner;
117
+ }
118
+ type Spawner = (command: string, args: readonly string[], options: SpawnOptions) => ChildProcess;
119
+ export type LaunchBrowserResult = {
120
+ ok: true;
121
+ command: string;
122
+ args: readonly string[];
123
+ } | {
124
+ ok: false;
125
+ error: string;
126
+ };
127
+ export declare function launchBrowser(url: string, options?: LaunchBrowserOptions): LaunchBrowserResult;
128
+ /**
129
+ * Orchestrate a `TunnelProvider.open(ctx)` call.
130
+ *
131
+ * Why this exists as a narrow helper:
132
+ *
133
+ * - Keeps `cli.ts` free of provider-result branching — the bin
134
+ * just prints the result line and wires `close()` to shutdown.
135
+ * - Collapses any thrown error into an `unavailable` session so
136
+ * `cli.ts` has a single shape to render. Per contract, a
137
+ * well-behaved provider never throws for missing
138
+ * configuration; but we defend against a buggy provider to
139
+ * preserve the "local dev must never break" invariant.
140
+ * - Ignores the `ctx` shape knowledge: callers pass a prepared
141
+ * `TunnelContext`, and this function stays pure (no env,
142
+ * stdio, or network access).
143
+ */
144
+ export declare function openTunnel(provider: TunnelProvider, ctx: TunnelContext): Promise<TunnelSession>;
145
+ /**
146
+ * Describe a `TunnelSession` as lines ready for the CLI banner.
147
+ * Pure function — no stdio access. Tests pin the exact copy so
148
+ * banner drift is caught in review.
149
+ *
150
+ * Shape:
151
+ *
152
+ * ready → `[' tunnel → https://…']`
153
+ * unavailable → `[' tunnel skipped: <reason>', ' hint: <hint>']`
154
+ * (hint line is omitted when `hint` is undefined)
155
+ */
156
+ export declare function describeTunnelSession(session: TunnelSession): string[];
157
+ /**
158
+ * Narrow alias surfaces so `cli.ts` can import a single type name
159
+ * when handling a ready session. Kept here (next to `openTunnel`)
160
+ * so the CLI doesn't have to reach into the seam module.
161
+ */
162
+ export type { TunnelProvider, TunnelSession, TunnelSessionReady, TunnelSessionUnavailable, TunnelContext };
163
+ /** Pure command resolver — split out so tests can pin platform
164
+ * branches without actually spawning. */
165
+ export declare function resolveLaunchCommand(url: string, platform: NodeJS.Platform, env: Readonly<Record<string, string | undefined>>): {
166
+ command: string;
167
+ args: readonly string[];
168
+ };
169
+ //# sourceMappingURL=dev-command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-command.d.ts","sourceRoot":"","sources":["../src/dev-command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,222 @@
1
+ /**
2
+ * `ggui dev` argument parsing + runtime resolution + browser auto-open.
3
+ *
4
+ * Extracted from `cli.ts` so the pure logic (flag parsing, agent
5
+ * entry → command mapping, hub URL resolution, auto-open decision)
6
+ * is unit-testable without importing the bin's top-level `main()`
7
+ * side effect. The bin itself stays tiny.
8
+ *
9
+ * Browser auto-open lives here (the CLI layer) intentionally —
10
+ * `@ggui-ai/dev-stack` keeps owning the server/runtime stack; only
11
+ * the `ggui` binary decides "a human is driving this, open a tab."
12
+ * Other hosts (the future `guuey dev` shim, a hub spawned from a
13
+ * tunnel, a test harness) pick their own launch policy.
14
+ */
15
+ import { spawn as nodeSpawn, } from 'node:child_process';
16
+ import { buildAgentRuntime, resolveAgentCommand, } from './agent-resolution.js';
17
+ /**
18
+ * Parse the raw `ggui dev` argv tail. Returns a discriminated
19
+ * shape — `error` field is non-undefined when the input was
20
+ * malformed; callers render it and bail with exit code 1.
21
+ */
22
+ export function parseDevFlags(args) {
23
+ const out = { noServe: false, noOpen: false, tunnel: false };
24
+ for (let i = 0; i < args.length; i++) {
25
+ const arg = args[i];
26
+ if (arg === '--no-serve') {
27
+ out.noServe = true;
28
+ continue;
29
+ }
30
+ if (arg === '--no-open') {
31
+ out.noOpen = true;
32
+ continue;
33
+ }
34
+ if (arg === '--tunnel') {
35
+ out.tunnel = true;
36
+ continue;
37
+ }
38
+ if (arg === '--port') {
39
+ const v = args[++i];
40
+ if (v === undefined)
41
+ return { ...out, error: '--port requires a value' };
42
+ const n = Number(v);
43
+ if (!Number.isInteger(n) || n < 0 || n > 65535) {
44
+ return { ...out, error: `--port must be an integer in [0, 65535], got "${v}"` };
45
+ }
46
+ out.port = n;
47
+ continue;
48
+ }
49
+ if (arg === '--host') {
50
+ const v = args[++i];
51
+ if (v === undefined)
52
+ return { ...out, error: '--host requires a value' };
53
+ out.host = v;
54
+ continue;
55
+ }
56
+ if (arg === '--agent') {
57
+ const v = args[++i];
58
+ if (v === undefined || v.length === 0) {
59
+ return { ...out, error: '--agent requires an entry file path' };
60
+ }
61
+ out.agent = v;
62
+ continue;
63
+ }
64
+ return { ...out, error: `unknown flag "${arg ?? ''}"` };
65
+ }
66
+ return out;
67
+ }
68
+ // Agent-entry resolution + adapter construction live in
69
+ // `./agent-resolution.ts`. They are re-exported here so call sites
70
+ // that reach for these from `dev-command.js` keep working — and
71
+ // because `--agent` is conceptually a dev-command concern, even
72
+ // though the same helpers also back `ggui serve`.
73
+ export { resolveAgentCommand, buildAgentRuntime };
74
+ /**
75
+ * Build the URL the local hub is reachable at. Normalises
76
+ * wildcard bind addresses (`0.0.0.0` / `::`) to `127.0.0.1` so
77
+ * the link is actually clickable — the dev's browser can't dial
78
+ * `0.0.0.0`. Loopback host binds pass through unchanged.
79
+ */
80
+ export function resolveHubUrl(host, port) {
81
+ const safeHost = host === '0.0.0.0' || host === '::' || host === '' ? '127.0.0.1' : host;
82
+ return `http://${safeHost}:${port}/hub`;
83
+ }
84
+ /**
85
+ * Auto-open policy — `shouldAutoOpen` returns a discriminated
86
+ * decision. The `reason` field is both a test hook and a human
87
+ * diagnostic the CLI prints when it decides NOT to open. Never
88
+ * returns an error; the only shape is `{ open: boolean; reason }`.
89
+ *
90
+ * Rules (first-match wins):
91
+ *
92
+ * 1. `noOpen=true` → explicit `--no-open`.
93
+ * 2. `serving=false` → no server to open.
94
+ * 3. `CI=<truthy>` → CI runner; headless by convention.
95
+ * 4. `BROWSER=none` → explicit opt-out (create-react-app / expo
96
+ * pattern).
97
+ * 5. `isTty=false` → piped / daemonised; probably scripted.
98
+ * 6. otherwise → open.
99
+ */
100
+ export function shouldAutoOpen(ctx) {
101
+ if (ctx.noOpen)
102
+ return { open: false, reason: '--no-open set' };
103
+ if (!ctx.serving)
104
+ return { open: false, reason: '--no-serve set' };
105
+ const ci = ctx.env.CI;
106
+ if (ci && ci !== '' && ci !== '0' && ci.toLowerCase() !== 'false') {
107
+ return { open: false, reason: `CI env detected (CI=${ci})` };
108
+ }
109
+ if (ctx.env.BROWSER === 'none') {
110
+ return { open: false, reason: 'BROWSER=none' };
111
+ }
112
+ if (!ctx.isTty) {
113
+ return { open: false, reason: 'stdout is not a TTY' };
114
+ }
115
+ return { open: true, reason: 'interactive dev session' };
116
+ }
117
+ export function launchBrowser(url, options = {}) {
118
+ const env = options.env ?? process.env;
119
+ const platform = options.platform ?? process.platform;
120
+ const spawn = options.spawner ?? nodeSpawn;
121
+ const resolved = resolveLaunchCommand(url, platform, env);
122
+ try {
123
+ const child = spawn(resolved.command, resolved.args, {
124
+ stdio: 'ignore',
125
+ detached: true,
126
+ windowsHide: true,
127
+ });
128
+ // Async ENOENT / permission errors arrive here; swallow so they
129
+ // don't crash the CLI's unhandled-rejection path. The URL is
130
+ // already on screen.
131
+ child.on('error', () => {
132
+ /* best-effort; user still has the URL */
133
+ });
134
+ child.unref();
135
+ return { ok: true, command: resolved.command, args: resolved.args };
136
+ }
137
+ catch (err) {
138
+ return {
139
+ ok: false,
140
+ error: err instanceof Error ? err.message : String(err),
141
+ };
142
+ }
143
+ }
144
+ /**
145
+ * Orchestrate a `TunnelProvider.open(ctx)` call.
146
+ *
147
+ * Why this exists as a narrow helper:
148
+ *
149
+ * - Keeps `cli.ts` free of provider-result branching — the bin
150
+ * just prints the result line and wires `close()` to shutdown.
151
+ * - Collapses any thrown error into an `unavailable` session so
152
+ * `cli.ts` has a single shape to render. Per contract, a
153
+ * well-behaved provider never throws for missing
154
+ * configuration; but we defend against a buggy provider to
155
+ * preserve the "local dev must never break" invariant.
156
+ * - Ignores the `ctx` shape knowledge: callers pass a prepared
157
+ * `TunnelContext`, and this function stays pure (no env,
158
+ * stdio, or network access).
159
+ */
160
+ export async function openTunnel(provider, ctx) {
161
+ try {
162
+ const session = await provider.open(ctx);
163
+ // Defensive check — the seam's contract says `open` returns a
164
+ // discriminated session, but a misbehaving provider could
165
+ // return `undefined` / a malformed object. Collapse to
166
+ // `unavailable` rather than letting `cli.ts` crash trying to
167
+ // read `.status` on a non-object.
168
+ if (session &&
169
+ typeof session === 'object' &&
170
+ 'status' in session &&
171
+ (session.status === 'ready' || session.status === 'unavailable')) {
172
+ return session;
173
+ }
174
+ return {
175
+ status: 'unavailable',
176
+ reason: `tunnel provider "${provider.name}" returned an invalid session shape`,
177
+ };
178
+ }
179
+ catch (err) {
180
+ return {
181
+ status: 'unavailable',
182
+ reason: `tunnel provider "${provider.name}" threw: ${err instanceof Error ? err.message : String(err)}`,
183
+ };
184
+ }
185
+ }
186
+ /**
187
+ * Describe a `TunnelSession` as lines ready for the CLI banner.
188
+ * Pure function — no stdio access. Tests pin the exact copy so
189
+ * banner drift is caught in review.
190
+ *
191
+ * Shape:
192
+ *
193
+ * ready → `[' tunnel → https://…']`
194
+ * unavailable → `[' tunnel skipped: <reason>', ' hint: <hint>']`
195
+ * (hint line is omitted when `hint` is undefined)
196
+ */
197
+ export function describeTunnelSession(session) {
198
+ if (session.status === 'ready') {
199
+ return [` tunnel → ${session.remoteUrl}`];
200
+ }
201
+ const lines = [` tunnel skipped: ${session.reason}`];
202
+ if (session.hint) {
203
+ lines.push(` hint: ${session.hint}`);
204
+ }
205
+ return lines;
206
+ }
207
+ /** Pure command resolver — split out so tests can pin platform
208
+ * branches without actually spawning. */
209
+ export function resolveLaunchCommand(url, platform, env) {
210
+ const custom = env.BROWSER;
211
+ if (custom && custom !== 'none') {
212
+ return { command: custom, args: [url] };
213
+ }
214
+ if (platform === 'darwin')
215
+ return { command: 'open', args: [url] };
216
+ if (platform === 'win32') {
217
+ // Empty string after 'start' is the window title — without it
218
+ // Windows treats the URL as the title and drops the real arg.
219
+ return { command: 'cmd', args: ['/c', 'start', '', url] };
220
+ }
221
+ return { command: 'xdg-open', args: [url] };
222
+ }
@@ -0,0 +1,3 @@
1
+ export declare const GADGET_HELP = "ggui gadget \u2014 author + manage marketplace gadgets\n\nUsage:\n ggui gadget <subcommand> [options]\n\nSubcommands:\n create Scaffold a new gadget repo.\n publish Build, sign, and push the gadget in CWD to a marketplace registry.\n install Fetch, verify, and register a gadget in ggui.json.\n search Search the marketplace registry for gadgets.\n\nRun `ggui gadget <subcommand> --help` for subcommand-specific options.\n";
2
+ export declare function runGadgetCommand(args: readonly string[]): Promise<number>;
3
+ //# sourceMappingURL=gadget-command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gadget-command.d.ts","sourceRoot":"","sources":["../src/gadget-command.ts"],"names":[],"mappings":"AAwCA,eAAO,MAAM,WAAW,scAYvB,CAAC;AAEF,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,SAAS,MAAM,EAAE,GACtB,OAAO,CAAC,MAAM,CAAC,CAoBjB"}
@@ -0,0 +1,181 @@
1
+ /**
2
+ * `ggui gadget <create|publish|install|search>` — top-level router for
3
+ * the marketplace author + consumer surface.
4
+ *
5
+ * A thin sibling of `blueprint-command.ts`. Both commands dispatch into
6
+ * the shared `internal/artifact-*` modules with their own `kind`
7
+ * discriminator; per-kind state (scaffolders, help text) lives in
8
+ * sibling files.
9
+ *
10
+ * - `create` — scaffold a new gadget repo (hook + bundle stub).
11
+ * - `publish` — build, sign, and push to a registry.
12
+ * Manifest-kind mismatch surfaces a redirect.
13
+ * - `install` — fetch, verify, append to ggui.json.
14
+ * - `search` — query the registry, hard-locked to `kind=gadget`.
15
+ */
16
+ import { GADGET_CREATE_HELP, parseGadgetCreateFlags, runGadgetCreate, } from './gadget-create.js';
17
+ import { buildPublishHelp, parseArtifactPublishFlags, runArtifactPublish, } from './internal/artifact-publish.js';
18
+ import { buildInstallHelp, parseArtifactInstallFlags, runArtifactInstall, } from './internal/artifact-install.js';
19
+ import { buildSearchHelp, parseArtifactSearchFlags, runArtifactSearch, } from './internal/artifact-search.js';
20
+ const KIND = 'gadget';
21
+ export const GADGET_HELP = `ggui gadget — author + manage marketplace gadgets
22
+
23
+ Usage:
24
+ ggui gadget <subcommand> [options]
25
+
26
+ Subcommands:
27
+ create Scaffold a new gadget repo.
28
+ publish Build, sign, and push the gadget in CWD to a marketplace registry.
29
+ install Fetch, verify, and register a gadget in ggui.json.
30
+ search Search the marketplace registry for gadgets.
31
+
32
+ Run \`ggui gadget <subcommand> --help\` for subcommand-specific options.
33
+ `;
34
+ export async function runGadgetCommand(args) {
35
+ const [sub, ...rest] = args;
36
+ if (!sub || sub === '--help' || sub === '-h') {
37
+ process.stdout.write(GADGET_HELP);
38
+ return sub ? 0 : 2;
39
+ }
40
+ switch (sub) {
41
+ case 'create':
42
+ return runCreate(rest);
43
+ case 'publish':
44
+ return runPublish(rest);
45
+ case 'install':
46
+ return runInstall(rest);
47
+ case 'search':
48
+ return runSearch(rest);
49
+ default:
50
+ process.stderr.write(`ggui gadget: unknown subcommand "${sub}"\n\n`);
51
+ process.stderr.write(GADGET_HELP);
52
+ return 2;
53
+ }
54
+ }
55
+ /* -------------------------------------------------------------------------- */
56
+ /* create */
57
+ /* -------------------------------------------------------------------------- */
58
+ async function runCreate(args) {
59
+ const parsed = parseGadgetCreateFlags(args);
60
+ if (parsed.error === '__help__') {
61
+ process.stdout.write(GADGET_CREATE_HELP);
62
+ return 0;
63
+ }
64
+ if (parsed.error) {
65
+ process.stderr.write(`ggui gadget create: ${parsed.error}\n\n`);
66
+ process.stderr.write(GADGET_CREATE_HELP);
67
+ return 2;
68
+ }
69
+ if (!parsed.flags) {
70
+ process.stderr.write(`ggui gadget create: failed to parse flags\n`);
71
+ return 2;
72
+ }
73
+ const result = await runGadgetCreate(parsed.flags, { cwd: process.cwd() });
74
+ return renderCreateResult(result);
75
+ }
76
+ function renderCreateResult(result) {
77
+ if (!result.ok) {
78
+ process.stderr.write(`ggui gadget create: ${result.message}\n`);
79
+ return 1;
80
+ }
81
+ process.stdout.write(`\n`);
82
+ process.stdout.write(`Scaffolded gadget at ${result.targetDir}\n\n`);
83
+ process.stdout.write(`Files written:\n`);
84
+ for (const f of result.files) {
85
+ process.stdout.write(` - ${f}\n`);
86
+ }
87
+ process.stdout.write(`\n`);
88
+ process.stdout.write(`Hook export: ${result.hook}\n`);
89
+ process.stdout.write(`Identifier: ${result.manifest.scope}/${result.manifest.name}@${result.manifest.version}\n`);
90
+ process.stdout.write(`\nNext steps:\n`);
91
+ process.stdout.write(` cd ${result.targetDir}\n`);
92
+ process.stdout.write(` pnpm install\n`);
93
+ process.stdout.write(` # implement the hook in src/index.ts, then:\n`);
94
+ process.stdout.write(` ggui gadget publish\n\n`);
95
+ return 0;
96
+ }
97
+ /* -------------------------------------------------------------------------- */
98
+ /* publish */
99
+ /* -------------------------------------------------------------------------- */
100
+ async function runPublish(args) {
101
+ const flags = parseArtifactPublishFlags(args);
102
+ if (flags.help) {
103
+ process.stdout.write(buildPublishHelp(KIND));
104
+ return 0;
105
+ }
106
+ if (flags.error) {
107
+ process.stderr.write(`ggui gadget publish: ${flags.error}\n`);
108
+ return 2;
109
+ }
110
+ const result = await runArtifactPublish({
111
+ kind: KIND,
112
+ ...(flags.registry !== undefined ? { registry: flags.registry } : {}),
113
+ dryRun: flags.dryRun,
114
+ ...(flags.key !== undefined ? { key: flags.key } : {}),
115
+ ...(flags.auth !== undefined ? { auth: flags.auth } : {}),
116
+ });
117
+ return result.exitCode;
118
+ }
119
+ /* -------------------------------------------------------------------------- */
120
+ /* install */
121
+ /* -------------------------------------------------------------------------- */
122
+ async function runInstall(args) {
123
+ const parsed = parseArtifactInstallFlags(KIND, args);
124
+ if ('error' in parsed) {
125
+ if (parsed.error === '__help__') {
126
+ process.stdout.write(buildInstallHelp(KIND));
127
+ return 0;
128
+ }
129
+ process.stderr.write(`ggui gadget install: ${parsed.error}\n\n`);
130
+ process.stderr.write(buildInstallHelp(KIND));
131
+ return 2;
132
+ }
133
+ return runArtifactInstall(parsed, {
134
+ cwd: process.cwd(),
135
+ env: process.env,
136
+ fetch: globalThis.fetch.bind(globalThis),
137
+ });
138
+ }
139
+ /* -------------------------------------------------------------------------- */
140
+ /* search */
141
+ /* -------------------------------------------------------------------------- */
142
+ async function runSearch(args) {
143
+ const parsed = parseArtifactSearchFlags(KIND, args);
144
+ if (parsed.error === '__help__') {
145
+ process.stdout.write(buildSearchHelp(KIND));
146
+ return 0;
147
+ }
148
+ if (parsed.error) {
149
+ process.stderr.write(`ggui gadget search: ${parsed.error}\n\n`);
150
+ process.stderr.write(buildSearchHelp(KIND));
151
+ return 2;
152
+ }
153
+ if (!parsed.flags) {
154
+ process.stderr.write(`ggui gadget search: failed to parse flags\n`);
155
+ return 2;
156
+ }
157
+ const result = await runArtifactSearch(parsed.flags, {
158
+ cwd: process.cwd(),
159
+ env: { GGUI_REGISTRY: process.env['GGUI_REGISTRY'] },
160
+ fetch: globalThis.fetch.bind(globalThis),
161
+ });
162
+ return renderSearchResult(result, parsed.flags.json === true);
163
+ }
164
+ function renderSearchResult(result, json) {
165
+ if (!result.ok) {
166
+ process.stderr.write(`ggui gadget search: ${result.message}\n`);
167
+ return 1;
168
+ }
169
+ if (json) {
170
+ process.stdout.write(`${result.json}\n`);
171
+ return 0;
172
+ }
173
+ if (result.response.results.length === 0) {
174
+ process.stderr.write('No gadgets found matching the supplied criteria.\n');
175
+ return 0;
176
+ }
177
+ for (const line of result.lines) {
178
+ process.stdout.write(`${line}\n`);
179
+ }
180
+ return 0;
181
+ }