@desplega.ai/qa-use 2.14.1 → 2.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/lib/env/index.d.ts +13 -0
- package/dist/lib/env/index.d.ts.map +1 -1
- package/dist/lib/env/index.js +35 -0
- package/dist/lib/env/index.js.map +1 -1
- package/dist/lib/env/localhost.d.ts +22 -0
- package/dist/lib/env/localhost.d.ts.map +1 -0
- package/dist/lib/env/localhost.js +49 -0
- package/dist/lib/env/localhost.js.map +1 -0
- package/dist/lib/env/paths.d.ts +27 -0
- package/dist/lib/env/paths.d.ts.map +1 -0
- package/dist/lib/env/paths.js +42 -0
- package/dist/lib/env/paths.js.map +1 -0
- package/dist/lib/env/sessions.d.ts +55 -0
- package/dist/lib/env/sessions.d.ts.map +1 -0
- package/dist/lib/env/sessions.js +128 -0
- package/dist/lib/env/sessions.js.map +1 -0
- package/dist/lib/tunnel/errors.d.ts +61 -0
- package/dist/lib/tunnel/errors.d.ts.map +1 -0
- package/dist/lib/tunnel/errors.js +152 -0
- package/dist/lib/tunnel/errors.js.map +1 -0
- package/dist/lib/tunnel/index.d.ts.map +1 -1
- package/dist/lib/tunnel/index.js +26 -11
- package/dist/lib/tunnel/index.js.map +1 -1
- package/dist/lib/tunnel/registry.d.ts +182 -0
- package/dist/lib/tunnel/registry.d.ts.map +1 -0
- package/dist/lib/tunnel/registry.js +561 -0
- package/dist/lib/tunnel/registry.js.map +1 -0
- package/dist/package.json +1 -1
- package/dist/src/cli/commands/browser/_detached.d.ts +27 -0
- package/dist/src/cli/commands/browser/_detached.d.ts.map +1 -0
- package/dist/src/cli/commands/browser/_detached.js +422 -0
- package/dist/src/cli/commands/browser/_detached.js.map +1 -0
- package/dist/src/cli/commands/browser/close.d.ts +7 -0
- package/dist/src/cli/commands/browser/close.d.ts.map +1 -1
- package/dist/src/cli/commands/browser/close.js +101 -5
- package/dist/src/cli/commands/browser/close.js.map +1 -1
- package/dist/src/cli/commands/browser/create.d.ts +7 -0
- package/dist/src/cli/commands/browser/create.d.ts.map +1 -1
- package/dist/src/cli/commands/browser/create.js +233 -25
- package/dist/src/cli/commands/browser/create.js.map +1 -1
- package/dist/src/cli/commands/browser/index.d.ts.map +1 -1
- package/dist/src/cli/commands/browser/index.js +3 -0
- package/dist/src/cli/commands/browser/index.js.map +1 -1
- package/dist/src/cli/commands/browser/run.d.ts.map +1 -1
- package/dist/src/cli/commands/browser/run.js +13 -6
- package/dist/src/cli/commands/browser/run.js.map +1 -1
- package/dist/src/cli/commands/browser/status.d.ts +4 -0
- package/dist/src/cli/commands/browser/status.d.ts.map +1 -1
- package/dist/src/cli/commands/browser/status.js +85 -3
- package/dist/src/cli/commands/browser/status.js.map +1 -1
- package/dist/src/cli/commands/doctor.d.ts +45 -0
- package/dist/src/cli/commands/doctor.d.ts.map +1 -0
- package/dist/src/cli/commands/doctor.js +267 -0
- package/dist/src/cli/commands/doctor.js.map +1 -0
- package/dist/src/cli/commands/test/run.d.ts.map +1 -1
- package/dist/src/cli/commands/test/run.js +29 -18
- package/dist/src/cli/commands/test/run.js.map +1 -1
- package/dist/src/cli/commands/tunnel/close.d.ts +18 -0
- package/dist/src/cli/commands/tunnel/close.d.ts.map +1 -0
- package/dist/src/cli/commands/tunnel/close.js +154 -0
- package/dist/src/cli/commands/tunnel/close.js.map +1 -0
- package/dist/src/cli/commands/tunnel/index.d.ts +6 -0
- package/dist/src/cli/commands/tunnel/index.d.ts.map +1 -0
- package/dist/src/cli/commands/tunnel/index.js +17 -0
- package/dist/src/cli/commands/tunnel/index.js.map +1 -0
- package/dist/src/cli/commands/tunnel/ls.d.ts +10 -0
- package/dist/src/cli/commands/tunnel/ls.d.ts.map +1 -0
- package/dist/src/cli/commands/tunnel/ls.js +89 -0
- package/dist/src/cli/commands/tunnel/ls.js.map +1 -0
- package/dist/src/cli/commands/tunnel/start.d.ts +15 -0
- package/dist/src/cli/commands/tunnel/start.d.ts.map +1 -0
- package/dist/src/cli/commands/tunnel/start.js +65 -0
- package/dist/src/cli/commands/tunnel/start.js.map +1 -0
- package/dist/src/cli/commands/tunnel/status.d.ts +8 -0
- package/dist/src/cli/commands/tunnel/status.d.ts.map +1 -0
- package/dist/src/cli/commands/tunnel/status.js +58 -0
- package/dist/src/cli/commands/tunnel/status.js.map +1 -0
- package/dist/src/cli/generated/docs-content.d.ts +1 -1
- package/dist/src/cli/generated/docs-content.d.ts.map +1 -1
- package/dist/src/cli/generated/docs-content.js +157 -100
- package/dist/src/cli/generated/docs-content.js.map +1 -1
- package/dist/src/cli/index.js +8 -0
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/cli/lib/browser.d.ts +25 -9
- package/dist/src/cli/lib/browser.d.ts.map +1 -1
- package/dist/src/cli/lib/browser.js +73 -42
- package/dist/src/cli/lib/browser.js.map +1 -1
- package/dist/src/cli/lib/cli-entry.d.ts +40 -0
- package/dist/src/cli/lib/cli-entry.d.ts.map +1 -0
- package/dist/src/cli/lib/cli-entry.js +65 -0
- package/dist/src/cli/lib/cli-entry.js.map +1 -0
- package/dist/src/cli/lib/startup-sweep.d.ts +45 -0
- package/dist/src/cli/lib/startup-sweep.d.ts.map +1 -0
- package/dist/src/cli/lib/startup-sweep.js +246 -0
- package/dist/src/cli/lib/startup-sweep.js.map +1 -0
- package/dist/src/cli/lib/tunnel-banner.d.ts +33 -0
- package/dist/src/cli/lib/tunnel-banner.d.ts.map +1 -0
- package/dist/src/cli/lib/tunnel-banner.js +55 -0
- package/dist/src/cli/lib/tunnel-banner.js.map +1 -0
- package/dist/src/cli/lib/tunnel-error-hint.d.ts +20 -0
- package/dist/src/cli/lib/tunnel-error-hint.d.ts.map +1 -0
- package/dist/src/cli/lib/tunnel-error-hint.js +48 -0
- package/dist/src/cli/lib/tunnel-error-hint.js.map +1 -0
- package/dist/src/cli/lib/tunnel-option.d.ts +27 -0
- package/dist/src/cli/lib/tunnel-option.d.ts.map +1 -0
- package/dist/src/cli/lib/tunnel-option.js +77 -0
- package/dist/src/cli/lib/tunnel-option.js.map +1 -0
- package/dist/src/cli/lib/tunnel-resolve.d.ts +42 -0
- package/dist/src/cli/lib/tunnel-resolve.d.ts.map +1 -0
- package/dist/src/cli/lib/tunnel-resolve.js +72 -0
- package/dist/src/cli/lib/tunnel-resolve.js.map +1 -0
- package/lib/env/index.ts +51 -0
- package/lib/env/localhost.test.ts +63 -0
- package/lib/env/localhost.ts +51 -0
- package/lib/env/paths.ts +46 -0
- package/lib/env/sessions.test.ts +109 -0
- package/lib/env/sessions.ts +155 -0
- package/lib/tunnel/errors.test.ts +105 -0
- package/lib/tunnel/errors.ts +169 -0
- package/lib/tunnel/index.ts +26 -11
- package/lib/tunnel/registry.test.ts +420 -0
- package/lib/tunnel/registry.ts +646 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -93,9 +93,32 @@ qa-use browser run # Interactive REPL mode
|
|
|
93
93
|
| `qa-use browser screenshot <path>` | Save screenshot |
|
|
94
94
|
| `qa-use browser run` | Interactive REPL mode |
|
|
95
95
|
| `qa-use browser close` | Close browser session |
|
|
96
|
+
| `qa-use browser status --list` | List active sessions across processes |
|
|
96
97
|
|
|
97
98
|
Run `qa-use browser --help` for the full list of 29 browser commands.
|
|
98
99
|
|
|
100
|
+
### Tunnel & Maintenance Commands
|
|
101
|
+
|
|
102
|
+
`browser create` auto-tunnels when your target is localhost and the API is remote. For standalone tunnels (or cross-process registry inspection) use `qa-use tunnel`:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
qa-use tunnel start http://localhost:3000 --hold # Hold a public tunnel
|
|
106
|
+
qa-use tunnel ls # List active tunnels
|
|
107
|
+
qa-use tunnel status <target|hash> # Detail for one entry
|
|
108
|
+
qa-use tunnel close <target|hash> # Force-release a tunnel
|
|
109
|
+
qa-use doctor # Reap stale sessions/tunnels
|
|
110
|
+
qa-use doctor --dry-run # Preview what would be reaped
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
| Command | Description |
|
|
114
|
+
|---------|-------------|
|
|
115
|
+
| `qa-use tunnel ls` | List active tunnels in the registry |
|
|
116
|
+
| `qa-use tunnel status <target>` | Show detail for a single tunnel |
|
|
117
|
+
| `qa-use tunnel close <target>` | Force-release a tunnel (kills detached holder) |
|
|
118
|
+
| `qa-use doctor` | Reap stale sessions/tunnels (dead PIDs) |
|
|
119
|
+
|
|
120
|
+
> **Note:** `qa-use tunnel *` is the CLI-side cross-process tunnel registry, distinct from `qa-use mcp tunnel` which is the MCP-mode persistent tunnel wrapper (see below).
|
|
121
|
+
|
|
99
122
|
### API Commands
|
|
100
123
|
|
|
101
124
|
Dynamic API access powered by live OpenAPI (`/api/v1/openapi.json`) with local cache fallback.
|
package/dist/lib/env/index.d.ts
CHANGED
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
* 2. Config file (~/.qa-use.json) top-level fields (api_key, api_url, etc.)
|
|
7
7
|
* 3. Config file (~/.qa-use.json) env block ({ "env": { "VAR_NAME": "value" } })
|
|
8
8
|
*/
|
|
9
|
+
export { getPortFromUrl, isLocalhostUrl } from './localhost.js';
|
|
10
|
+
/**
|
|
11
|
+
* Tunnel mode stored in ~/.qa-use.json.
|
|
12
|
+
* Kept as a string literal here (instead of importing `TunnelMode` from
|
|
13
|
+
* `src/cli/lib/tunnel-option.ts`) to avoid pulling CLI code into the
|
|
14
|
+
* shared env loader.
|
|
15
|
+
*/
|
|
16
|
+
export type QaUseTunnelMode = 'auto' | 'on' | 'off';
|
|
9
17
|
export type EnvSource = 'env' | 'config' | 'none';
|
|
10
18
|
export interface EnvResult {
|
|
11
19
|
value: string | undefined;
|
|
@@ -48,6 +56,11 @@ export declare function logConfigSources(): void;
|
|
|
48
56
|
* Priority: config file headers, then QA_USE_HEADERS env var overrides.
|
|
49
57
|
*/
|
|
50
58
|
export declare function getCustomHeaders(): Record<string, string> | null;
|
|
59
|
+
export declare function getTunnelModeFromConfig(): QaUseTunnelMode | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Reset the tunnel-warning latch (for tests).
|
|
62
|
+
*/
|
|
63
|
+
export declare function clearTunnelWarningLatch(): void;
|
|
51
64
|
/**
|
|
52
65
|
* Get agent session ID from environment if available.
|
|
53
66
|
* Used for auto-linking browser sessions and test runs to agent sessions.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../lib/env/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../lib/env/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,IAAI,GAAG,KAAK,CAAC;AA6DpD,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AAElD,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,MAAM,EAAE,SAAS,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAwBxD;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEvD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAGvC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C;AAgBD;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CA6BvC;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAwBhE;AAYD,wBAAgB,uBAAuB,IAAI,eAAe,GAAG,SAAS,CAmBrE;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,GAAG,SAAS,CAEtD"}
|
package/dist/lib/env/index.js
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
import { existsSync, readFileSync } from 'node:fs';
|
|
10
10
|
import { homedir } from 'node:os';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
|
+
export { getPortFromUrl, isLocalhostUrl } from './localhost.js';
|
|
13
|
+
const VALID_TUNNEL_MODES = ['auto', 'on', 'off'];
|
|
12
14
|
/**
|
|
13
15
|
* Mapping from env var names to top-level config field names.
|
|
14
16
|
* Allows getEnvWithSource to check direct fields before the env block.
|
|
@@ -173,6 +175,39 @@ export function getCustomHeaders() {
|
|
|
173
175
|
}
|
|
174
176
|
return Object.keys(headers).length > 0 ? headers : null;
|
|
175
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Read the `tunnel` key from `~/.qa-use.json`.
|
|
180
|
+
*
|
|
181
|
+
* Returns one of `'auto' | 'on' | 'off'`, or `undefined` if unset.
|
|
182
|
+
* On an invalid value, logs a one-line stderr warning and returns `undefined`
|
|
183
|
+
* (caller will fall back to the default, typically `'auto'`).
|
|
184
|
+
*
|
|
185
|
+
* Phase 1: config-only (no env-var override layer for tunnel mode).
|
|
186
|
+
*/
|
|
187
|
+
let tunnelWarningLogged = false;
|
|
188
|
+
export function getTunnelModeFromConfig() {
|
|
189
|
+
const config = loadConfig();
|
|
190
|
+
if (!config)
|
|
191
|
+
return undefined;
|
|
192
|
+
const raw = config.tunnel;
|
|
193
|
+
if (raw === undefined || raw === null)
|
|
194
|
+
return undefined;
|
|
195
|
+
if (typeof raw === 'string' && VALID_TUNNEL_MODES.includes(raw)) {
|
|
196
|
+
return raw;
|
|
197
|
+
}
|
|
198
|
+
if (!tunnelWarningLogged) {
|
|
199
|
+
console.error(`qa-use: invalid "tunnel" value in ~/.qa-use.json: ${JSON.stringify(raw)}. ` +
|
|
200
|
+
`Expected one of: ${VALID_TUNNEL_MODES.join(', ')}. Falling back to "auto".`);
|
|
201
|
+
tunnelWarningLogged = true;
|
|
202
|
+
}
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Reset the tunnel-warning latch (for tests).
|
|
207
|
+
*/
|
|
208
|
+
export function clearTunnelWarningLatch() {
|
|
209
|
+
tunnelWarningLogged = false;
|
|
210
|
+
}
|
|
176
211
|
/**
|
|
177
212
|
* Get agent session ID from environment if available.
|
|
178
213
|
* Used for auto-linking browser sessions and test runs to agent sessions.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/env/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/env/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical localhost URL helpers.
|
|
3
|
+
*
|
|
4
|
+
* These live in `lib/env/` so they are usable from both the CLI layer and
|
|
5
|
+
* library code (tunnel, browser) without forcing a dependency on `src/cli`.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Check if a URL points to localhost.
|
|
9
|
+
*
|
|
10
|
+
* Matches:
|
|
11
|
+
* - `localhost`
|
|
12
|
+
* - `127.0.0.1`
|
|
13
|
+
* - `::1`
|
|
14
|
+
* - `*.localhost` (e.g., `foo.localhost`)
|
|
15
|
+
* - `0.0.0.0`
|
|
16
|
+
*/
|
|
17
|
+
export declare function isLocalhostUrl(url: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Get the port from a URL, defaulting to 443 for https and 80 otherwise.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getPortFromUrl(url: string): number;
|
|
22
|
+
//# sourceMappingURL=localhost.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"localhost.d.ts","sourceRoot":"","sources":["../../../lib/env/localhost.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAgBnD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAUlD"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical localhost URL helpers.
|
|
3
|
+
*
|
|
4
|
+
* These live in `lib/env/` so they are usable from both the CLI layer and
|
|
5
|
+
* library code (tunnel, browser) without forcing a dependency on `src/cli`.
|
|
6
|
+
*/
|
|
7
|
+
import { URL } from 'node:url';
|
|
8
|
+
/**
|
|
9
|
+
* Check if a URL points to localhost.
|
|
10
|
+
*
|
|
11
|
+
* Matches:
|
|
12
|
+
* - `localhost`
|
|
13
|
+
* - `127.0.0.1`
|
|
14
|
+
* - `::1`
|
|
15
|
+
* - `*.localhost` (e.g., `foo.localhost`)
|
|
16
|
+
* - `0.0.0.0`
|
|
17
|
+
*/
|
|
18
|
+
export function isLocalhostUrl(url) {
|
|
19
|
+
try {
|
|
20
|
+
const parsed = new URL(url);
|
|
21
|
+
// Node/Bun's URL parser returns IPv6 hosts wrapped in brackets
|
|
22
|
+
// (e.g. "[::1]"). Strip them for comparison.
|
|
23
|
+
const host = parsed.hostname.replace(/^\[|\]$/g, '');
|
|
24
|
+
return (host === 'localhost' ||
|
|
25
|
+
host === '127.0.0.1' ||
|
|
26
|
+
host === '::1' ||
|
|
27
|
+
host === '0.0.0.0' ||
|
|
28
|
+
host.endsWith('.localhost'));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get the port from a URL, defaulting to 443 for https and 80 otherwise.
|
|
36
|
+
*/
|
|
37
|
+
export function getPortFromUrl(url) {
|
|
38
|
+
try {
|
|
39
|
+
const parsed = new URL(url);
|
|
40
|
+
if (parsed.port) {
|
|
41
|
+
return parseInt(parsed.port, 10);
|
|
42
|
+
}
|
|
43
|
+
return parsed.protocol === 'https:' ? 443 : 80;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return 80;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=localhost.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"localhost.js","sourceRoot":"","sources":["../../../lib/env/localhost.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/B;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,+DAA+D;QAC/D,6CAA6C;QAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QACrD,OAAO,CACL,IAAI,KAAK,WAAW;YACpB,IAAI,KAAK,WAAW;YACpB,IAAI,KAAK,KAAK;YACd,IAAI,KAAK,SAAS;YAClB,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,CAC5B,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YAChB,OAAO,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACnC,CAAC;QACD,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem paths used by qa-use for CLI-side state.
|
|
3
|
+
*
|
|
4
|
+
* All paths are relative to `os.homedir()` and created lazily on first
|
|
5
|
+
* write. Reading these directories does not create them — that's important
|
|
6
|
+
* for commands like `tunnel ls` that report an empty state cleanly when
|
|
7
|
+
* nothing has ever been written.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Base dir for all qa-use CLI-side state. Defaults to `~/.qa-use` but may
|
|
11
|
+
* be overridden via `QA_USE_HOME` (test-friendly).
|
|
12
|
+
*/
|
|
13
|
+
export declare function qaUseDir(): string;
|
|
14
|
+
/**
|
|
15
|
+
* Where persisted tunnel registry entries live. Each active tunnel is a
|
|
16
|
+
* single JSON file named `<sha256(target)[0..10]>.json`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function tunnelsDir(): string;
|
|
19
|
+
/**
|
|
20
|
+
* Where detached browser-session PID files live (used by Phase 4 onward).
|
|
21
|
+
*/
|
|
22
|
+
export declare function sessionsDir(): string;
|
|
23
|
+
/**
|
|
24
|
+
* Ensure a directory exists (recursively). No-op if it already does.
|
|
25
|
+
*/
|
|
26
|
+
export declare function ensureDir(dir: string): void;
|
|
27
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../../lib/env/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH;;;GAGG;AACH,wBAAgB,QAAQ,IAAI,MAAM,CAMjC;AAED;;;GAGG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAE3C"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem paths used by qa-use for CLI-side state.
|
|
3
|
+
*
|
|
4
|
+
* All paths are relative to `os.homedir()` and created lazily on first
|
|
5
|
+
* write. Reading these directories does not create them — that's important
|
|
6
|
+
* for commands like `tunnel ls` that report an empty state cleanly when
|
|
7
|
+
* nothing has ever been written.
|
|
8
|
+
*/
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
/**
|
|
13
|
+
* Base dir for all qa-use CLI-side state. Defaults to `~/.qa-use` but may
|
|
14
|
+
* be overridden via `QA_USE_HOME` (test-friendly).
|
|
15
|
+
*/
|
|
16
|
+
export function qaUseDir() {
|
|
17
|
+
const override = process.env.QA_USE_HOME;
|
|
18
|
+
if (override && override.length > 0) {
|
|
19
|
+
return override;
|
|
20
|
+
}
|
|
21
|
+
return path.join(os.homedir(), '.qa-use');
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Where persisted tunnel registry entries live. Each active tunnel is a
|
|
25
|
+
* single JSON file named `<sha256(target)[0..10]>.json`.
|
|
26
|
+
*/
|
|
27
|
+
export function tunnelsDir() {
|
|
28
|
+
return path.join(qaUseDir(), 'tunnels');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Where detached browser-session PID files live (used by Phase 4 onward).
|
|
32
|
+
*/
|
|
33
|
+
export function sessionsDir() {
|
|
34
|
+
return path.join(qaUseDir(), 'sessions');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Ensure a directory exists (recursively). No-op if it already does.
|
|
38
|
+
*/
|
|
39
|
+
export function ensureDir(dir) {
|
|
40
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../../lib/env/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;;;GAGG;AACH,MAAM,UAAU,QAAQ;IACtB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IACzC,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpC,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;AAC5C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,SAAS,CAAC,CAAC;AAC1C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW;IACzB,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,UAAU,CAAC,CAAC;AAC3C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detached browser-session PID files.
|
|
3
|
+
*
|
|
4
|
+
* Each detached `browser create` writes a JSON file under
|
|
5
|
+
* `~/.qa-use/sessions/<session-id>.json` describing the running child
|
|
6
|
+
* process. The file schema is documented in `DetachedSessionRecord`.
|
|
7
|
+
*
|
|
8
|
+
* Readers (tunnel close, browser status, doctor) cross-reference the
|
|
9
|
+
* `pid` field via `process.kill(pid, 0)` to detect stale entries.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* On-disk schema for a detached browser session.
|
|
13
|
+
*
|
|
14
|
+
* The file is created by the detached child (`__browser-detach`) shortly
|
|
15
|
+
* after it starts, and removed on clean exit. Partial writes are
|
|
16
|
+
* avoided by atomic rename (write `.tmp` + `rename`).
|
|
17
|
+
*/
|
|
18
|
+
export interface DetachedSessionRecord {
|
|
19
|
+
/** Backend session id (also the filename base). */
|
|
20
|
+
id: string;
|
|
21
|
+
/** PID of the detached child process. */
|
|
22
|
+
pid: number;
|
|
23
|
+
/** Tunnel target (canonical origin of the browser WS URL). */
|
|
24
|
+
target: string;
|
|
25
|
+
/** Public URL for the tunnel (from registry handle). `null` when no tunnel. */
|
|
26
|
+
publicUrl: string | null;
|
|
27
|
+
/** ISO timestamp of child startup. */
|
|
28
|
+
startedAt: string;
|
|
29
|
+
/** Epoch ms at which the backend session TTL expires. */
|
|
30
|
+
ttlExpiresAt: number;
|
|
31
|
+
/**
|
|
32
|
+
* True when the registry handle was an attach (another process owns
|
|
33
|
+
* the in-process `TunnelManager`). Informational — `browser close`
|
|
34
|
+
* prints this in diagnostics.
|
|
35
|
+
*/
|
|
36
|
+
crossProcessTunnel?: boolean;
|
|
37
|
+
/** Optional: subdomain used for the tunnel. */
|
|
38
|
+
subdomain?: string;
|
|
39
|
+
/** Optional: viewport for display/debug. */
|
|
40
|
+
viewport?: string;
|
|
41
|
+
/** Optional: headless flag for display/debug. */
|
|
42
|
+
headless?: boolean;
|
|
43
|
+
}
|
|
44
|
+
export declare function sessionFilePath(sessionId: string): string;
|
|
45
|
+
export declare function writeSessionRecord(record: DetachedSessionRecord): void;
|
|
46
|
+
export declare function readSessionRecord(sessionId: string): DetachedSessionRecord | null;
|
|
47
|
+
export declare function removeSessionRecord(sessionId: string): void;
|
|
48
|
+
/**
|
|
49
|
+
* List all persisted detached-session records. Does NOT reconcile
|
|
50
|
+
* against PIDs — callers decide how to handle stale entries.
|
|
51
|
+
*/
|
|
52
|
+
export declare function listSessionRecords(): DetachedSessionRecord[];
|
|
53
|
+
/** Cheap liveness check — `kill(pid, 0)` returns true if the pid exists. */
|
|
54
|
+
export declare function isPidAlive(pid: number): boolean;
|
|
55
|
+
//# sourceMappingURL=sessions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../../lib/env/sessions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,mDAAmD;IACnD,EAAE,EAAE,MAAM,CAAC;IACX,yCAAyC;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,8DAA8D;IAC9D,MAAM,EAAE,MAAM,CAAC;IACf,+EAA+E;IAC/E,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,qBAAqB,GAAG,IAAI,CAOtE;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,qBAAqB,GAAG,IAAI,CASjF;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CA0C3D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,qBAAqB,EAAE,CAqB5D;AAED,4EAA4E;AAC5E,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAS/C"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detached browser-session PID files.
|
|
3
|
+
*
|
|
4
|
+
* Each detached `browser create` writes a JSON file under
|
|
5
|
+
* `~/.qa-use/sessions/<session-id>.json` describing the running child
|
|
6
|
+
* process. The file schema is documented in `DetachedSessionRecord`.
|
|
7
|
+
*
|
|
8
|
+
* Readers (tunnel close, browser status, doctor) cross-reference the
|
|
9
|
+
* `pid` field via `process.kill(pid, 0)` to detect stale entries.
|
|
10
|
+
*/
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { ensureDir, sessionsDir } from './paths.js';
|
|
14
|
+
export function sessionFilePath(sessionId) {
|
|
15
|
+
return path.join(sessionsDir(), `${sessionId}.json`);
|
|
16
|
+
}
|
|
17
|
+
export function writeSessionRecord(record) {
|
|
18
|
+
const dir = sessionsDir();
|
|
19
|
+
ensureDir(dir);
|
|
20
|
+
const finalPath = sessionFilePath(record.id);
|
|
21
|
+
const tmp = `${finalPath}.tmp-${process.pid}-${Date.now()}`;
|
|
22
|
+
fs.writeFileSync(tmp, JSON.stringify(record, null, 2));
|
|
23
|
+
fs.renameSync(tmp, finalPath);
|
|
24
|
+
}
|
|
25
|
+
export function readSessionRecord(sessionId) {
|
|
26
|
+
try {
|
|
27
|
+
const raw = fs.readFileSync(sessionFilePath(sessionId), 'utf8');
|
|
28
|
+
const parsed = JSON.parse(raw);
|
|
29
|
+
if (typeof parsed.id !== 'string' || typeof parsed.pid !== 'number')
|
|
30
|
+
return null;
|
|
31
|
+
return parsed;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function removeSessionRecord(sessionId) {
|
|
38
|
+
try {
|
|
39
|
+
fs.unlinkSync(sessionFilePath(sessionId));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
// Fast path missed — fall through to dir-scan fallback only on ENOENT.
|
|
44
|
+
// Any other error (EPERM, EACCES, EBUSY, etc.) is propagated by
|
|
45
|
+
// simply returning: callers treat removal as best-effort and surfacing
|
|
46
|
+
// an error here would change the public contract.
|
|
47
|
+
if (err.code !== 'ENOENT') {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Fallback: filename may have drifted from the internal `id` (corrupted
|
|
52
|
+
// state, manual edits, legacy data). Scan the dir and unlink the file
|
|
53
|
+
// whose parsed content has a matching `id`.
|
|
54
|
+
const dir = sessionsDir();
|
|
55
|
+
let files;
|
|
56
|
+
try {
|
|
57
|
+
files = fs.readdirSync(dir);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
for (const name of files) {
|
|
63
|
+
if (!name.endsWith('.json') || name.endsWith('.tmp'))
|
|
64
|
+
continue;
|
|
65
|
+
const fullPath = path.join(dir, name);
|
|
66
|
+
try {
|
|
67
|
+
const raw = fs.readFileSync(fullPath, 'utf8');
|
|
68
|
+
const parsed = JSON.parse(raw);
|
|
69
|
+
if (parsed.id === sessionId) {
|
|
70
|
+
try {
|
|
71
|
+
fs.unlinkSync(fullPath);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
/* already gone */
|
|
75
|
+
}
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
/* skip unreadable */
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* List all persisted detached-session records. Does NOT reconcile
|
|
86
|
+
* against PIDs — callers decide how to handle stale entries.
|
|
87
|
+
*/
|
|
88
|
+
export function listSessionRecords() {
|
|
89
|
+
const dir = sessionsDir();
|
|
90
|
+
let files;
|
|
91
|
+
try {
|
|
92
|
+
files = fs.readdirSync(dir);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
const out = [];
|
|
98
|
+
for (const name of files) {
|
|
99
|
+
if (!name.endsWith('.json') || name.endsWith('.tmp'))
|
|
100
|
+
continue;
|
|
101
|
+
try {
|
|
102
|
+
const raw = fs.readFileSync(path.join(dir, name), 'utf8');
|
|
103
|
+
const parsed = JSON.parse(raw);
|
|
104
|
+
if (typeof parsed.id !== 'string' || typeof parsed.pid !== 'number')
|
|
105
|
+
continue;
|
|
106
|
+
out.push(parsed);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
/* skip unreadable */
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
/** Cheap liveness check — `kill(pid, 0)` returns true if the pid exists. */
|
|
115
|
+
export function isPidAlive(pid) {
|
|
116
|
+
if (!pid || pid <= 0)
|
|
117
|
+
return false;
|
|
118
|
+
try {
|
|
119
|
+
process.kill(pid, 0);
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
if (err.code === 'EPERM')
|
|
124
|
+
return true;
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=sessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../../lib/env/sessions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured tunnel error classes.
|
|
3
|
+
*
|
|
4
|
+
* The CLI uses these to render triage-hint error messages on tunnel
|
|
5
|
+
* failure. `classifyTunnelFailure` inspects a thrown error from the
|
|
6
|
+
* underlying `@desplega.ai/localtunnel` provider and returns the most
|
|
7
|
+
* specific subclass we can identify.
|
|
8
|
+
*
|
|
9
|
+
* Zero retries live in this layer — classification is strictly about
|
|
10
|
+
* picking the right error shape to hand back up the stack.
|
|
11
|
+
*/
|
|
12
|
+
export interface TunnelErrorContext {
|
|
13
|
+
/** Human-readable target the user was trying to tunnel (URL or host:port). */
|
|
14
|
+
target?: string;
|
|
15
|
+
/** Identifier of the tunnel provider (kept open for future providers). */
|
|
16
|
+
provider?: string;
|
|
17
|
+
/** Original underlying error, preserved for logs / debugging. */
|
|
18
|
+
cause?: unknown;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Base class for all tunnel-layer failures surfaced to the CLI.
|
|
22
|
+
*
|
|
23
|
+
* Do not throw `TunnelError` directly — throw one of the subclasses
|
|
24
|
+
* below so the CLI can pick the right hint.
|
|
25
|
+
*/
|
|
26
|
+
export declare class TunnelError extends Error {
|
|
27
|
+
readonly target?: string;
|
|
28
|
+
readonly provider?: string;
|
|
29
|
+
readonly cause?: unknown;
|
|
30
|
+
constructor(message: string, context?: TunnelErrorContext);
|
|
31
|
+
}
|
|
32
|
+
/** Network / connectivity failure (DNS, timeout, ECONNREFUSED, etc.). */
|
|
33
|
+
export declare class TunnelNetworkError extends TunnelError {
|
|
34
|
+
constructor(message: string, context?: TunnelErrorContext);
|
|
35
|
+
}
|
|
36
|
+
/** Auth failure (bad/expired API key, 401/403 from provider). */
|
|
37
|
+
export declare class TunnelAuthError extends TunnelError {
|
|
38
|
+
constructor(message: string, context?: TunnelErrorContext);
|
|
39
|
+
}
|
|
40
|
+
/** Quota / rate-limit / subdomain-clash failure. */
|
|
41
|
+
export declare class TunnelQuotaError extends TunnelError {
|
|
42
|
+
constructor(message: string, context?: TunnelErrorContext);
|
|
43
|
+
}
|
|
44
|
+
/** Fallback when no classification matched. */
|
|
45
|
+
export declare class TunnelUnknownError extends TunnelError {
|
|
46
|
+
constructor(message: string, context?: TunnelErrorContext);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Inspect a thrown tunnel error and return the most specific
|
|
50
|
+
* `TunnelError` subclass we can identify.
|
|
51
|
+
*
|
|
52
|
+
* Heuristics:
|
|
53
|
+
* - Node / undici error codes (`ECONNREFUSED`, `ETIMEDOUT`, …) →
|
|
54
|
+
* `TunnelNetworkError`
|
|
55
|
+
* - HTTP 401/403 → `TunnelAuthError`
|
|
56
|
+
* - HTTP 429 or messages about quota / rate-limit / subdomain in use →
|
|
57
|
+
* `TunnelQuotaError`
|
|
58
|
+
* - Everything else → `TunnelUnknownError`
|
|
59
|
+
*/
|
|
60
|
+
export declare function classifyTunnelFailure(err: unknown, context?: TunnelErrorContext): TunnelError;
|
|
61
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../lib/tunnel/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,kBAAkB;IACjC,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;GAKG;AACH,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;gBAEb,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB;CAO9D;AAED,yEAAyE;AACzE,qBAAa,kBAAmB,SAAQ,WAAW;gBACrC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB;CAI9D;AAED,iEAAiE;AACjE,qBAAa,eAAgB,SAAQ,WAAW;gBAClC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB;CAI9D;AAED,oDAAoD;AACpD,qBAAa,gBAAiB,SAAQ,WAAW;gBACnC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB;CAI9D;AAED,+CAA+C;AAC/C,qBAAa,kBAAmB,SAAQ,WAAW;gBACrC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB;CAI9D;AAmDD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,GAAE,kBAAuB,GAAG,WAAW,CAkCjG"}
|