skydive-cli 0.5.0-beta.31 → 0.5.0-beta.33
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 +20 -0
- package/dist/js/api-DQCaztBg.mjs +315 -0
- package/dist/js/{billing-blocked-Dgu5-oDy.mjs → billing-blocked-SE6tySLd.mjs} +1 -1
- package/dist/js/bin.mjs +214 -50
- package/dist/js/{boot-iaVSnd7h.mjs → boot-C5HJfwRS.mjs} +17 -14
- package/dist/js/chunk-BbwQpWto.mjs +33 -0
- package/dist/js/{client-DbqRBquD.mjs → client-d6K9qm6B.mjs} +70 -2
- package/dist/js/client-yz3zrpAS.mjs +5 -0
- package/dist/js/{daemon-BTyg329O.mjs → daemon-BPGZufS1.mjs} +4 -2
- package/dist/js/daemon-WQ8QYrFt.mjs +7 -0
- package/dist/js/daemon-client-DTfIza0O.mjs +8 -0
- package/dist/js/{daemon-client--A_yMKq6.mjs → daemon-client-MpAGVkqP.mjs} +1 -1
- package/dist/js/dist-CRtjM7ba.mjs +1750 -0
- package/dist/js/{forward-DL6DYqyc.mjs → forward-zwB55Bls.mjs} +25 -4
- package/dist/js/{profiler-BVbYs_Yg.mjs → install-BPUAm8bS.mjs} +274 -210
- package/dist/js/localhost-cert-Bn-UBUmj.mjs +67 -0
- package/dist/js/{print-DACzRUwQ.mjs → print-B9djx8GU.mjs} +3 -3
- package/dist/js/{print-C_TnQSPg.mjs → print-Clgq46GU.mjs} +3 -3
- package/dist/js/{print-share-BH9gvCls.mjs → print-share-BBy3OTno.mjs} +6 -2
- package/dist/js/{raw-pty-DY4KelZW.mjs → raw-pty-B9Bue8gg.mjs} +1 -1
- package/dist/js/raw-pty-g3kATVQe.mjs +5 -0
- package/dist/js/{rest-CgfbKXst.mjs → rest-C6E4mS2A.mjs} +2 -2
- package/dist/js/rest-CaWUSrnH.mjs +6 -0
- package/dist/js/tls-cert-CLgSQALB.mjs +4 -0
- package/dist/js/tls-cert-CV-pwxVN.mjs +67 -0
- package/package.json +1 -1
- package/dist/js/api-DG5W6iwx.mjs +0 -131
- package/dist/js/client-hH2PJL8y.mjs +0 -5
- package/dist/js/daemon-SPQgtsbW.mjs +0 -6
- package/dist/js/daemon-client-C7emKKlj.mjs +0 -7
- package/dist/js/raw-pty-DmdUf4_w.mjs +0 -5
- package/dist/js/rest-VV5nc-Mn.mjs +0 -6
- /package/dist/js/{billing-blocked-2wju4gC_.mjs → billing-blocked-D3l5kJlX.mjs} +0 -0
- /package/dist/js/{client-c4c5MmgN.mjs → client-BTQ1fwzM.mjs} +0 -0
- /package/dist/js/{http-error-DzyrsLAZ.mjs → http-error-UHH3mVBF.mjs} +0 -0
- /package/dist/js/{output-DYzzdXYV.mjs → output-wY0VQDea.mjs} +0 -0
package/README.md
CHANGED
|
@@ -483,6 +483,25 @@ The remaining keys (`apiKey`, `apiUrl`, `sessionToken`, `appUrl`, `themeDark`,
|
|
|
483
483
|
`themeLight`, …) are credentials or flow-managed state — leave them to the
|
|
484
484
|
CLI.
|
|
485
485
|
|
|
486
|
+
## Diagnostic logs
|
|
487
|
+
|
|
488
|
+
The CLI records local diagnostics by default. It keeps the latest 20 sessions
|
|
489
|
+
beside the CLI config. Nothing uploads automatically.
|
|
490
|
+
|
|
491
|
+
```sh
|
|
492
|
+
skydive logs # list recent sessions
|
|
493
|
+
skydive logs path # print the latest session directory
|
|
494
|
+
skydive logs export # archive the latest session for sharing
|
|
495
|
+
skydive logs export <session> # archive one session from `logs list`
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
Automatic logs include request timing, TUI state changes, render performance,
|
|
499
|
+
and process metadata. Command values and URL query values are removed.
|
|
500
|
+
|
|
501
|
+
Set `SKYDIVE_PROFILE=<path>` to write a full profile to a chosen directory.
|
|
502
|
+
Full profiles also include CPU data. Set `SKYDIVE_PROFILE=0` to disable all
|
|
503
|
+
session diagnostics.
|
|
504
|
+
|
|
486
505
|
## Environment variables
|
|
487
506
|
|
|
488
507
|
| Variable | Effect |
|
|
@@ -493,6 +512,7 @@ CLI.
|
|
|
493
512
|
| `SKYDIVE_APP_URL` | Chat app/session origin (overrides stored value) |
|
|
494
513
|
| `SKYDIVE_THEME` | Chat colorscheme id (flag `--theme` overrides) |
|
|
495
514
|
| `SKYDIVE_CONFIG_NAME` | On-disk config profile name (default `skydive`) |
|
|
515
|
+
| `SKYDIVE_PROFILE` | Full profile path; `0` disables diagnostics |
|
|
496
516
|
|
|
497
517
|
## Testing the TUI
|
|
498
518
|
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { t as HttpError } from "./http-error-UHH3mVBF.mjs";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import http from "node:http";
|
|
5
|
+
import http2 from "node:http2";
|
|
6
|
+
|
|
7
|
+
//#region ../portal-daemon/src/tls-forward.ts
|
|
8
|
+
const TLS_PORT_OFFSET = 1e4;
|
|
9
|
+
const STRIP_HEADERS = new Set([
|
|
10
|
+
"connection",
|
|
11
|
+
"keep-alive",
|
|
12
|
+
"proxy-authenticate",
|
|
13
|
+
"proxy-authorization",
|
|
14
|
+
"proxy-connection",
|
|
15
|
+
"te",
|
|
16
|
+
"trailer",
|
|
17
|
+
"transfer-encoding",
|
|
18
|
+
"upgrade",
|
|
19
|
+
"http2-settings"
|
|
20
|
+
]);
|
|
21
|
+
/**
|
|
22
|
+
* The opt-in TLS+h2 origin for a forwarded/exposed port (ANY-6872, additive
|
|
23
|
+
* next to the plain listener — never a replacement). Browsers only negotiate
|
|
24
|
+
* h2 over TLS, and h2's stream multiplexing is what lifts the
|
|
25
|
+
* 6-connections-per-origin cap that makes unbundled dev servers crawl through
|
|
26
|
+
* the tunnel; each h2 stream is converted here to an h1 request over a tunnel
|
|
27
|
+
* from `acquireTunnel`. Used by BOTH tunnel surfaces: the CLI's
|
|
28
|
+
* `skydive portal forward` and the desktop daemon's `platform portal expose`.
|
|
29
|
+
*
|
|
30
|
+
* The dev server keeps seeing `Host: localhost:<port>` — the browser's
|
|
31
|
+
* authority is a public name it would reject (Vite 403s unknown hosts), and
|
|
32
|
+
* "looks exactly like localhost" is the product contract. The original
|
|
33
|
+
* authority travels in x-forwarded-host / x-forwarded-proto for apps that
|
|
34
|
+
* want it.
|
|
35
|
+
*/
|
|
36
|
+
async function startTlsForward({ cert, localPort, targetPort, acquireTunnel, log }) {
|
|
37
|
+
const connectionShims = {
|
|
38
|
+
setNoDelay: () => {},
|
|
39
|
+
setKeepAlive: () => {},
|
|
40
|
+
setTimeout: () => {},
|
|
41
|
+
ref: () => {},
|
|
42
|
+
unref: () => {}
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* node's http client drives the tunnel: the agent's keep-alive free list
|
|
46
|
+
* gives request→request socket reuse on top of the pool's pre-paid dials,
|
|
47
|
+
* and a tunnel the daemon/dev-server side reaps while idle is evicted from
|
|
48
|
+
* the free list by its 'close' — the next request just adopts a fresh one.
|
|
49
|
+
* maxSockets mirrors the pool cap: every socket here holds an edge WS open,
|
|
50
|
+
* and ANY-6873 is the honest fix for wanting more concurrency than that.
|
|
51
|
+
*
|
|
52
|
+
* The base signature allows synchronous implementations to return the
|
|
53
|
+
* socket; this one is asynchronous, so it always uses the callback and
|
|
54
|
+
* returns undefined (the documented async contract). The agent core always
|
|
55
|
+
* passes the callback — the error branch below guards the contract anyway
|
|
56
|
+
* rather than assuming it.
|
|
57
|
+
*/
|
|
58
|
+
class TunnelAgent extends http.Agent {
|
|
59
|
+
createConnection(_options, callback) {
|
|
60
|
+
if (!callback) throw new Error("TunnelAgent.createConnection requires the async callback");
|
|
61
|
+
(async () => {
|
|
62
|
+
let tunnel;
|
|
63
|
+
try {
|
|
64
|
+
tunnel = await acquireTunnel();
|
|
65
|
+
} catch (error) {
|
|
66
|
+
callback(error instanceof Error ? error : new Error(String(error)));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
Object.assign(tunnel.stream, connectionShims);
|
|
70
|
+
tunnel.stream.resume();
|
|
71
|
+
callback(null, tunnel.stream);
|
|
72
|
+
})();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const agent = new TunnelAgent({
|
|
76
|
+
keepAlive: true,
|
|
77
|
+
maxSockets: 16
|
|
78
|
+
});
|
|
79
|
+
const server = http2.createSecureServer({
|
|
80
|
+
key: cert.key,
|
|
81
|
+
cert: cert.cert,
|
|
82
|
+
allowHTTP1: true
|
|
83
|
+
});
|
|
84
|
+
const liveSessions = /* @__PURE__ */ new Set();
|
|
85
|
+
server.on("session", (session) => {
|
|
86
|
+
liveSessions.add(session);
|
|
87
|
+
session.once("close", () => liveSessions.delete(session));
|
|
88
|
+
});
|
|
89
|
+
server.on("secureConnection", (socket) => {
|
|
90
|
+
liveSessions.add(socket);
|
|
91
|
+
socket.once("close", () => liveSessions.delete(socket));
|
|
92
|
+
});
|
|
93
|
+
server.on("request", (req, res) => {
|
|
94
|
+
const headers = {};
|
|
95
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
96
|
+
if (name.startsWith(":") || STRIP_HEADERS.has(name)) continue;
|
|
97
|
+
if (value !== void 0) headers[name] = value;
|
|
98
|
+
}
|
|
99
|
+
headers.host = `localhost:${targetPort}`;
|
|
100
|
+
headers["x-forwarded-proto"] = "https";
|
|
101
|
+
headers["x-forwarded-host"] = cert.hostname;
|
|
102
|
+
const upstream = http.request({
|
|
103
|
+
host: "localhost",
|
|
104
|
+
port: targetPort,
|
|
105
|
+
method: req.method,
|
|
106
|
+
path: req.url,
|
|
107
|
+
headers,
|
|
108
|
+
agent
|
|
109
|
+
}, (upstreamRes) => {
|
|
110
|
+
const resHeaders = {};
|
|
111
|
+
for (const [name, value] of Object.entries(upstreamRes.headers)) {
|
|
112
|
+
if (STRIP_HEADERS.has(name)) continue;
|
|
113
|
+
if (value !== void 0) resHeaders[name] = value;
|
|
114
|
+
}
|
|
115
|
+
res.writeHead(upstreamRes.statusCode ?? 502, resHeaders);
|
|
116
|
+
upstreamRes.pipe(res);
|
|
117
|
+
upstreamRes.on("error", () => res.destroy());
|
|
118
|
+
});
|
|
119
|
+
upstream.on("error", (err) => {
|
|
120
|
+
log(`forward(tls): upstream request failed: ${err.message}`);
|
|
121
|
+
if (!res.headersSent) {
|
|
122
|
+
res.writeHead(502, { "content-type": "text/plain" });
|
|
123
|
+
res.end("portal forward: tunnel request failed");
|
|
124
|
+
} else res.destroy();
|
|
125
|
+
});
|
|
126
|
+
req.pipe(upstream);
|
|
127
|
+
req.on("error", () => upstream.destroy());
|
|
128
|
+
});
|
|
129
|
+
server.on("upgrade", (req, socket, head) => {
|
|
130
|
+
socket.pause();
|
|
131
|
+
(async () => {
|
|
132
|
+
let tunnel;
|
|
133
|
+
try {
|
|
134
|
+
tunnel = await acquireTunnel();
|
|
135
|
+
} catch (err) {
|
|
136
|
+
log(`forward(tls): upgrade tunnel failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
137
|
+
socket.destroy();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const lines = [`${req.method} ${req.url} HTTP/1.1`];
|
|
141
|
+
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
142
|
+
const name = req.rawHeaders[i];
|
|
143
|
+
const value = name?.toLowerCase() === "host" ? `localhost:${targetPort}` : req.rawHeaders[i + 1];
|
|
144
|
+
lines.push(`${name}: ${value}`);
|
|
145
|
+
}
|
|
146
|
+
tunnel.stream.write(lines.join("\r\n") + "\r\n\r\n");
|
|
147
|
+
if (head.length > 0) tunnel.stream.write(head);
|
|
148
|
+
tunnel.stream.on("error", () => socket.destroy());
|
|
149
|
+
tunnel.stream.on("close", () => socket.destroy());
|
|
150
|
+
socket.on("error", () => tunnel.stream.destroy());
|
|
151
|
+
socket.on("close", () => tunnel.stream.destroy());
|
|
152
|
+
socket.pipe(tunnel.stream).pipe(socket);
|
|
153
|
+
socket.resume();
|
|
154
|
+
})();
|
|
155
|
+
});
|
|
156
|
+
server.on("tlsClientError", (err, tlsSocket) => {
|
|
157
|
+
tlsSocket.destroy();
|
|
158
|
+
log(`forward(tls): handshake failed: ${err.message}`);
|
|
159
|
+
});
|
|
160
|
+
const preferredPort = localPort + TLS_PORT_OFFSET <= 65535 ? localPort + TLS_PORT_OFFSET : 0;
|
|
161
|
+
return {
|
|
162
|
+
port: await new Promise((resolve, reject) => {
|
|
163
|
+
const onError = (err) => {
|
|
164
|
+
if (err.code === "EADDRINUSE" && preferredPort !== 0) {
|
|
165
|
+
server.listen(0, "127.0.0.1");
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
server.removeListener("error", onError);
|
|
169
|
+
reject(err);
|
|
170
|
+
};
|
|
171
|
+
server.on("error", onError);
|
|
172
|
+
server.on("listening", () => {
|
|
173
|
+
server.removeListener("error", onError);
|
|
174
|
+
const addr = server.address();
|
|
175
|
+
resolve(addr && typeof addr === "object" ? addr.port : preferredPort);
|
|
176
|
+
});
|
|
177
|
+
server.listen(preferredPort, "127.0.0.1");
|
|
178
|
+
}),
|
|
179
|
+
hostname: cert.hostname,
|
|
180
|
+
close: () => new Promise((resolve) => {
|
|
181
|
+
agent.destroy();
|
|
182
|
+
server.close(() => resolve());
|
|
183
|
+
for (const live of liveSessions) live.destroy();
|
|
184
|
+
})
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region ../portal-daemon/src/api.ts
|
|
190
|
+
/**
|
|
191
|
+
* The portal's session-authed REST surface, shared by `PortalClient` (the
|
|
192
|
+
* TUI/`portal open` connection) and the `skydive portal` management
|
|
193
|
+
* commands, so the endpoint contracts and response schemas live in exactly
|
|
194
|
+
* one place.
|
|
195
|
+
*/
|
|
196
|
+
const deviceSchema = z.object({
|
|
197
|
+
id: z.string(),
|
|
198
|
+
machineName: z.string(),
|
|
199
|
+
friendlyName: z.string(),
|
|
200
|
+
connected: z.boolean(),
|
|
201
|
+
lastSeen: z.string().nullable(),
|
|
202
|
+
grantedAgentIds: z.array(z.string())
|
|
203
|
+
});
|
|
204
|
+
const devicesResponseSchema = z.object({
|
|
205
|
+
devices: z.array(deviceSchema),
|
|
206
|
+
agents: z.array(z.object({
|
|
207
|
+
id: z.string(),
|
|
208
|
+
name: z.string()
|
|
209
|
+
}))
|
|
210
|
+
});
|
|
211
|
+
const deviceTokenSchema = z.object({ token: z.string().min(1) });
|
|
212
|
+
async function portalFetch(auth, path, init) {
|
|
213
|
+
const res = await fetch(`${auth.appUrl}${path}`, {
|
|
214
|
+
method: init.method,
|
|
215
|
+
headers: {
|
|
216
|
+
authorization: `Bearer ${auth.sessionToken}`,
|
|
217
|
+
accept: "application/json",
|
|
218
|
+
...init.body ? { "content-type": "application/json" } : {}
|
|
219
|
+
},
|
|
220
|
+
...init.body ? { body: init.body } : {}
|
|
221
|
+
});
|
|
222
|
+
if (!res.ok) {
|
|
223
|
+
const body = await res.text().catch(() => "");
|
|
224
|
+
throw new HttpError(res.status, body);
|
|
225
|
+
}
|
|
226
|
+
return res.json();
|
|
227
|
+
}
|
|
228
|
+
async function fetchPortalDevices(auth) {
|
|
229
|
+
const json = await portalFetch(auth, "/api/v1/portal/devices", { method: "GET" });
|
|
230
|
+
return devicesResponseSchema.parse(json);
|
|
231
|
+
}
|
|
232
|
+
const registerResponseSchema = z.object({ device: z.object({ id: z.string() }) });
|
|
233
|
+
/**
|
|
234
|
+
* Register this machine's device row without connecting. Connecting registers
|
|
235
|
+
* as a side effect; this covers granting an agent on a machine that has never
|
|
236
|
+
* shared yet (the grant references the device row).
|
|
237
|
+
*/
|
|
238
|
+
async function registerPortalDevice(auth, { machineName, friendlyName }) {
|
|
239
|
+
const json = await portalFetch(auth, "/api/v1/portal/devices", {
|
|
240
|
+
method: "POST",
|
|
241
|
+
body: JSON.stringify({
|
|
242
|
+
machineName,
|
|
243
|
+
friendlyName
|
|
244
|
+
})
|
|
245
|
+
});
|
|
246
|
+
return registerResponseSchema.parse(json).device;
|
|
247
|
+
}
|
|
248
|
+
/** Short-lived token the machine presents when dialing the portal WebSocket. */
|
|
249
|
+
async function mintPortalDeviceToken(auth) {
|
|
250
|
+
const json = await portalFetch(auth, "/api/v1/portal/device-token", { method: "POST" });
|
|
251
|
+
return deviceTokenSchema.parse(json).token;
|
|
252
|
+
}
|
|
253
|
+
const forwardTargetSchema = z.object({
|
|
254
|
+
daemonOrigin: z.string().min(1),
|
|
255
|
+
token: z.string().min(1),
|
|
256
|
+
expiresInSeconds: z.number()
|
|
257
|
+
});
|
|
258
|
+
/**
|
|
259
|
+
* Everything `portal forward` needs to dial an agent's sandbox daemon through
|
|
260
|
+
* the agent-webserver edge Worker: the daemon's stable public origin and a
|
|
261
|
+
* daemon auth token (canUse-gated server-side).
|
|
262
|
+
*/
|
|
263
|
+
async function fetchForwardTarget(auth, agentId) {
|
|
264
|
+
const json = await portalFetch(auth, "/api/v1/portal/forward-target", {
|
|
265
|
+
method: "POST",
|
|
266
|
+
body: JSON.stringify({ agentId })
|
|
267
|
+
});
|
|
268
|
+
return forwardTargetSchema.parse(json);
|
|
269
|
+
}
|
|
270
|
+
async function grantPortalAccess(auth, { deviceId, agentId, conversationId }) {
|
|
271
|
+
await portalFetch(auth, `/api/v1/portal/devices/${encodeURIComponent(deviceId)}/grants`, {
|
|
272
|
+
method: "POST",
|
|
273
|
+
body: JSON.stringify({
|
|
274
|
+
agentId,
|
|
275
|
+
conversationId
|
|
276
|
+
})
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
async function revokePortalAccess(auth, { deviceId, agentId }) {
|
|
280
|
+
await portalFetch(auth, `/api/v1/portal/devices/${encodeURIComponent(deviceId)}/grants/${encodeURIComponent(agentId)}`, { method: "DELETE" });
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* The device row for a given machine identity. Matching is by `machineName`
|
|
284
|
+
* equality — the stable handle the machine registers under, not the display
|
|
285
|
+
* label.
|
|
286
|
+
*/
|
|
287
|
+
function findThisDevice(devices, machineName) {
|
|
288
|
+
return devices.find((device) => device.machineName === machineName) ?? null;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* One-time grant migration onto the merged device. Earlier CLI builds
|
|
292
|
+
* registered a separate `<machineName>-cli` device, so a user's existing
|
|
293
|
+
* approvals hang off that row; the merged device would start with zero grants
|
|
294
|
+
* and every already-authorized agent would ask again. Copy any grant the
|
|
295
|
+
* merged device is missing (the grant endpoint upserts, so re-runs are
|
|
296
|
+
* no-ops). The legacy row is left in place — an old CLI build may still
|
|
297
|
+
* connect under it. Returns how many grants were copied.
|
|
298
|
+
*/
|
|
299
|
+
async function unifyLegacyCliGrants(auth, machineName) {
|
|
300
|
+
const { devices } = await fetchPortalDevices(auth);
|
|
301
|
+
const merged = findThisDevice(devices, machineName);
|
|
302
|
+
const legacy = findThisDevice(devices, `${machineName}-cli`);
|
|
303
|
+
if (!merged || !legacy) return 0;
|
|
304
|
+
const have = new Set(merged.grantedAgentIds);
|
|
305
|
+
const missing = legacy.grantedAgentIds.filter((id) => !have.has(id));
|
|
306
|
+
for (const agentId of missing) await grantPortalAccess(auth, {
|
|
307
|
+
deviceId: merged.id,
|
|
308
|
+
agentId,
|
|
309
|
+
conversationId: null
|
|
310
|
+
});
|
|
311
|
+
return missing.length;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
//#endregion
|
|
315
|
+
export { mintPortalDeviceToken as a, unifyLegacyCliGrants as c, grantPortalAccess as i, startTlsForward as l, fetchPortalDevices as n, registerPortalDevice as o, findThisDevice as r, revokePortalAccess as s, fetchForwardTarget as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as billingBlockedOutcomeSchema, i as billingBlockedOutcomeFromSendResponse, n as BillingBlockedError, r as billingBlockedJsonResult, t as BILLING_BLOCKED_EXIT_CODE } from "./billing-blocked-
|
|
2
|
+
import { a as billingBlockedOutcomeSchema, i as billingBlockedOutcomeFromSendResponse, n as BillingBlockedError, r as billingBlockedJsonResult, t as BILLING_BLOCKED_EXIT_CODE } from "./billing-blocked-D3l5kJlX.mjs";
|
|
3
3
|
|
|
4
4
|
export { BILLING_BLOCKED_EXIT_CODE, BillingBlockedError, billingBlockedJsonResult };
|