@ggui-ai/cli 0.1.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +96 -0
- package/dist/agent-resolution.d.ts +41 -0
- package/dist/agent-resolution.d.ts.map +1 -0
- package/dist/agent-resolution.js +80 -0
- package/dist/api-client.d.ts +59 -0
- package/dist/api-client.d.ts.map +1 -0
- package/dist/api-client.js +145 -0
- package/dist/auth-keys.d.ts +10 -0
- package/dist/auth-keys.d.ts.map +1 -0
- package/dist/auth-keys.js +382 -0
- package/dist/auth-login.d.ts +11 -0
- package/dist/auth-login.d.ts.map +1 -0
- package/dist/auth-login.js +231 -0
- package/dist/auth-logout.d.ts +3 -0
- package/dist/auth-logout.d.ts.map +1 -0
- package/dist/auth-logout.js +32 -0
- package/dist/auth-store.d.ts +67 -0
- package/dist/auth-store.d.ts.map +1 -0
- package/dist/auth-store.js +150 -0
- package/dist/auth-whoami.d.ts +3 -0
- package/dist/auth-whoami.d.ts.map +1 -0
- package/dist/auth-whoami.js +52 -0
- package/dist/blueprint-command.d.ts +3 -0
- package/dist/blueprint-command.d.ts.map +1 -0
- package/dist/blueprint-command.js +203 -0
- package/dist/blueprint-create.d.ts +71 -0
- package/dist/blueprint-create.d.ts.map +1 -0
- package/dist/blueprint-create.js +377 -0
- package/dist/byok-resolver.d.ts +141 -0
- package/dist/byok-resolver.d.ts.map +1 -0
- package/dist/byok-resolver.js +89 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1066 -0
- package/dist/dev-command.d.ts +169 -0
- package/dist/dev-command.d.ts.map +1 -0
- package/dist/dev-command.js +222 -0
- package/dist/gadget-command.d.ts +3 -0
- package/dist/gadget-command.d.ts.map +1 -0
- package/dist/gadget-command.js +181 -0
- package/dist/gadget-create.d.ts +106 -0
- package/dist/gadget-create.d.ts.map +1 -0
- package/dist/gadget-create.js +492 -0
- package/dist/generation-probe.d.ts +135 -0
- package/dist/generation-probe.d.ts.map +1 -0
- package/dist/generation-probe.js +132 -0
- package/dist/internal/artifact-install.d.ts +150 -0
- package/dist/internal/artifact-install.d.ts.map +1 -0
- package/dist/internal/artifact-install.js +1019 -0
- package/dist/internal/artifact-publish.d.ts +204 -0
- package/dist/internal/artifact-publish.d.ts.map +1 -0
- package/dist/internal/artifact-publish.js +1141 -0
- package/dist/internal/artifact-search.d.ts +155 -0
- package/dist/internal/artifact-search.d.ts.map +1 -0
- package/dist/internal/artifact-search.js +539 -0
- package/dist/internal/artifact-uninstall.d.ts +30 -0
- package/dist/internal/artifact-uninstall.d.ts.map +1 -0
- package/dist/internal/artifact-uninstall.js +205 -0
- package/dist/internal/auth-cache.d.ts +40 -0
- package/dist/internal/auth-cache.d.ts.map +1 -0
- package/dist/internal/auth-cache.js +133 -0
- package/dist/internal/auth-strategy.d.ts +89 -0
- package/dist/internal/auth-strategy.d.ts.map +1 -0
- package/dist/internal/auth-strategy.js +132 -0
- package/dist/internal/ggui-json.d.ts +53 -0
- package/dist/internal/ggui-json.d.ts.map +1 -0
- package/dist/internal/ggui-json.js +92 -0
- package/dist/internal/key-store.d.ts +43 -0
- package/dist/internal/key-store.d.ts.map +1 -0
- package/dist/internal/key-store.js +128 -0
- package/dist/internal/oidc-token.d.ts +52 -0
- package/dist/internal/oidc-token.d.ts.map +1 -0
- package/dist/internal/oidc-token.js +351 -0
- package/dist/internal/register-author-key.d.ts +44 -0
- package/dist/internal/register-author-key.d.ts.map +1 -0
- package/dist/internal/register-author-key.js +212 -0
- package/dist/local-keys.d.ts +47 -0
- package/dist/local-keys.d.ts.map +1 -0
- package/dist/local-keys.js +115 -0
- package/dist/mcp-backend.d.ts +356 -0
- package/dist/mcp-backend.d.ts.map +1 -0
- package/dist/mcp-backend.js +668 -0
- package/dist/paths.d.ts +77 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +119 -0
- package/dist/persistent-secrets.d.ts +28 -0
- package/dist/persistent-secrets.d.ts.map +1 -0
- package/dist/persistent-secrets.js +94 -0
- package/dist/persistent-short-code-index.d.ts +14 -0
- package/dist/persistent-short-code-index.d.ts.map +1 -0
- package/dist/persistent-short-code-index.js +39 -0
- package/dist/persistent-stores.d.ts +12 -0
- package/dist/persistent-stores.d.ts.map +1 -0
- package/dist/persistent-stores.js +42 -0
- package/dist/serve-command.d.ts +500 -0
- package/dist/serve-command.d.ts.map +1 -0
- package/dist/serve-command.js +616 -0
- package/dist/serve-fallback.d.ts +64 -0
- package/dist/serve-fallback.d.ts.map +1 -0
- package/dist/serve-fallback.js +122 -0
- package/dist/spawn-cloudflared.d.ts +61 -0
- package/dist/spawn-cloudflared.d.ts.map +1 -0
- package/dist/spawn-cloudflared.js +167 -0
- package/dist/theme-command.d.ts +4 -0
- package/dist/theme-command.d.ts.map +1 -0
- package/dist/theme-command.js +154 -0
- package/dist/theme-file-uploader.d.ts +8 -0
- package/dist/theme-file-uploader.d.ts.map +1 -0
- package/dist/theme-file-uploader.js +58 -0
- package/dist/theme-writer.d.ts +8 -0
- package/dist/theme-writer.d.ts.map +1 -0
- package/dist/theme-writer.js +103 -0
- package/dist/tunnel-provider.d.ts +273 -0
- package/dist/tunnel-provider.d.ts.map +1 -0
- package/dist/tunnel-provider.js +142 -0
- package/package.json +81 -0
|
@@ -0,0 +1,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
|
+
}
|