@drisp/cli 0.3.39

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.
@@ -0,0 +1,839 @@
1
+ // src/gateway/paths.ts
2
+ import os from "os";
3
+ import path from "path";
4
+ var SUN_PATH_MAX = 104;
5
+ function resolveGatewayPaths(env = process.env) {
6
+ const home = env["HOME"] ?? os.homedir();
7
+ const xdg = env["XDG_RUNTIME_DIR"]?.trim();
8
+ const runDir = xdg && xdg.length > 0 ? path.join(xdg, "athena") : path.join(home, ".config", "athena", "run");
9
+ const configDir = path.join(home, ".config", "athena", "gateway");
10
+ const socketPath = path.join(runDir, "gateway.sock");
11
+ const lockPath = path.join(runDir, "gateway.lock");
12
+ const tokenPath = path.join(configDir, "token");
13
+ const statePath = path.join(configDir, "state.db");
14
+ if (Buffer.byteLength(socketPath, "utf-8") > SUN_PATH_MAX) {
15
+ throw new Error(
16
+ `Gateway socket path exceeds ${SUN_PATH_MAX} bytes (sun_path limit): ${socketPath}. Set XDG_RUNTIME_DIR to a shorter path or relocate $HOME.`
17
+ );
18
+ }
19
+ return { runDir, configDir, socketPath, lockPath, tokenPath, statePath };
20
+ }
21
+ function resolveListenSpec(opts) {
22
+ if (!opts.bind) {
23
+ return { kind: "uds", socketPath: opts.paths.socketPath };
24
+ }
25
+ const parsed = parseHostPort(opts.bind);
26
+ return {
27
+ kind: "tcp",
28
+ host: parsed.host,
29
+ port: parsed.port,
30
+ insecure: opts.insecure ?? false,
31
+ ...opts.tls ? { tls: opts.tls } : {}
32
+ };
33
+ }
34
+ function parseHostPort(bind) {
35
+ const idx = bind.lastIndexOf(":");
36
+ if (idx <= 0 || idx === bind.length - 1) {
37
+ throw new Error(
38
+ `gateway: invalid --bind value ${bind}; expected host:port`
39
+ );
40
+ }
41
+ const host = bind.slice(0, idx);
42
+ const portText = bind.slice(idx + 1);
43
+ const port = Number(portText);
44
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
45
+ throw new Error(`gateway: invalid --bind port ${portText}`);
46
+ }
47
+ return { host, port };
48
+ }
49
+ function isLoopbackHost(host) {
50
+ const normalized = host.toLowerCase();
51
+ return normalized === "localhost" || normalized === "::1" || normalized === "[::1]" || normalized === "0:0:0:0:0:0:0:1" || normalized.startsWith("127.");
52
+ }
53
+
54
+ // src/infra/telemetry/client.ts
55
+ import { PostHog } from "posthog-node";
56
+ var POSTHOG_API_KEY = true ? "" : "";
57
+ var POSTHOG_HOST = "https://us.i.posthog.com";
58
+ var client = null;
59
+ var deviceId = null;
60
+ var enabled = false;
61
+ var superProperties = {};
62
+ function initTelemetry(options) {
63
+ const envDisabled = process.env["ATHENA_TELEMETRY_DISABLED"] === "1";
64
+ const hasKey = POSTHOG_API_KEY.length > 0;
65
+ enabled = hasKey && (options.telemetryEnabled ?? true) && !envDisabled;
66
+ if (!enabled) {
67
+ return;
68
+ }
69
+ deviceId = options.deviceId;
70
+ superProperties = {};
71
+ if (options.appVersion) {
72
+ superProperties["app_version"] = options.appVersion;
73
+ }
74
+ if (options.os) {
75
+ superProperties["os"] = options.os;
76
+ }
77
+ client = new PostHog(POSTHOG_API_KEY, {
78
+ host: POSTHOG_HOST,
79
+ disableGeoip: true,
80
+ flushAt: 20,
81
+ flushInterval: 3e4
82
+ });
83
+ }
84
+ function isTelemetryEnabled() {
85
+ return enabled;
86
+ }
87
+ function disableTelemetry() {
88
+ enabled = false;
89
+ deviceId = null;
90
+ superProperties = {};
91
+ if (!client) {
92
+ return Promise.resolve();
93
+ }
94
+ const currentClient = client;
95
+ client = null;
96
+ return currentClient.shutdown();
97
+ }
98
+ function capture(event, properties) {
99
+ if (!enabled || !client || !deviceId) {
100
+ return;
101
+ }
102
+ client.capture({
103
+ distinctId: deviceId,
104
+ event,
105
+ properties: { ...superProperties, ...properties }
106
+ });
107
+ }
108
+ async function shutdownTelemetry() {
109
+ if (client) {
110
+ await client.shutdown();
111
+ client = null;
112
+ }
113
+ deviceId = null;
114
+ enabled = false;
115
+ superProperties = {};
116
+ }
117
+
118
+ // src/infra/telemetry/events.ts
119
+ import os2 from "os";
120
+ function systemProps() {
121
+ return {
122
+ os: `${os2.platform()}-${os2.arch()}`,
123
+ nodeVersion: process.version
124
+ };
125
+ }
126
+ function trackAppLaunched(props) {
127
+ capture("app.launched", { ...props, ...systemProps() });
128
+ }
129
+ function trackSessionStarted(props) {
130
+ capture("session.started", props);
131
+ }
132
+ function trackSessionEnded(props) {
133
+ capture("session.ended", props);
134
+ }
135
+ function sanitizeStackTrace(stack) {
136
+ const home = os2.homedir();
137
+ return stack.replaceAll(home, "~");
138
+ }
139
+ function trackError(props) {
140
+ capture("app.error", {
141
+ errorName: props.errorName,
142
+ stackTrace: sanitizeStackTrace(props.stackTrace)
143
+ });
144
+ }
145
+ function trackTelemetryOptedOut() {
146
+ capture("telemetry.opted_out", {});
147
+ }
148
+ function classifyClaudeStartupFailure(reason) {
149
+ const normalized = reason.toLowerCase();
150
+ if (normalized.includes("enoent") || normalized.includes("not found") || normalized.includes("command not found")) {
151
+ return "binary_not_found";
152
+ }
153
+ if (normalized.includes("eacces") || normalized.includes("permission denied")) {
154
+ return "permission_denied";
155
+ }
156
+ if (normalized.includes("timed out") || normalized.includes("timeout") || normalized.includes("etimedout")) {
157
+ return "timeout";
158
+ }
159
+ if (normalized.includes("auth") || normalized.includes("login")) {
160
+ return "auth_required";
161
+ }
162
+ if (normalized.includes("connection refused") || normalized.includes("econnrefused")) {
163
+ return "connection_refused";
164
+ }
165
+ return "other";
166
+ }
167
+ function trackClaudeStartupFailed(props) {
168
+ const classifiedReason = classifyClaudeStartupFailure(props.message);
169
+ const resolvedBinary = props.failureStage === "exit_nonzero" || props.failureStage === "startup_timeout" || classifiedReason !== "binary_not_found";
170
+ capture("claude.startup_failed", {
171
+ harness: props.harness,
172
+ platform: `${os2.platform()}-${os2.arch()}`,
173
+ failure_stage: props.failureStage,
174
+ resolved_binary: resolvedBinary,
175
+ exit_code: props.exitCode,
176
+ classified_reason: classifiedReason
177
+ });
178
+ }
179
+ function trackGatewayTransportConnect(props) {
180
+ capture("gateway.transport.connect", props);
181
+ }
182
+ function trackGatewayTransportDisconnect(props) {
183
+ capture("gateway.transport.disconnect", props);
184
+ }
185
+ function trackGatewayTransportReconnect(props) {
186
+ capture("gateway.transport.reconnect", props);
187
+ }
188
+ function trackGatewayRuntimeRebind(props) {
189
+ capture("gateway.runtime.rebind", props);
190
+ }
191
+ function trackGatewayRuntimeExpired(props) {
192
+ capture("gateway.runtime.expired", props);
193
+ }
194
+
195
+ // src/infra/gatewayTrace.ts
196
+ import fs from "fs";
197
+ function writeGatewayTrace(message) {
198
+ if (process.env["ATHENA_GATEWAY_TRACE"] !== "1") return;
199
+ const line = `athena-gateway: [trace] ${message}
200
+ `;
201
+ const traceFile = process.env["ATHENA_GATEWAY_TRACE_FILE"];
202
+ if (traceFile && traceFile.length > 0) {
203
+ try {
204
+ fs.appendFileSync(traceFile, line, "utf-8");
205
+ return;
206
+ } catch {
207
+ }
208
+ }
209
+ process.stderr.write(line);
210
+ }
211
+
212
+ // src/gateway/auth.ts
213
+ import crypto from "crypto";
214
+ import fs2 from "fs";
215
+ import path2 from "path";
216
+ var TOKEN_BYTES = 32;
217
+ function loadOrCreateToken(tokenPath) {
218
+ try {
219
+ const buf = fs2.readFileSync(tokenPath);
220
+ const text = buf.toString("utf-8").trim();
221
+ if (text.length >= 16) return text;
222
+ } catch (err) {
223
+ const code = err.code;
224
+ if (code !== "ENOENT") throw err;
225
+ }
226
+ return writeNewToken(tokenPath);
227
+ }
228
+ function rotateGatewayToken(tokenPath) {
229
+ return writeNewToken(tokenPath);
230
+ }
231
+ function writeNewToken(tokenPath) {
232
+ const dir = path2.dirname(tokenPath);
233
+ fs2.mkdirSync(dir, { recursive: true, mode: 448 });
234
+ const token = crypto.randomBytes(TOKEN_BYTES).toString("base64url");
235
+ const tmpPath = `${tokenPath}.tmp-${process.pid}-${crypto.randomBytes(4).toString("hex")}`;
236
+ fs2.writeFileSync(tmpPath, token + "\n", { mode: 384 });
237
+ try {
238
+ fs2.renameSync(tmpPath, tokenPath);
239
+ } catch (err) {
240
+ try {
241
+ fs2.unlinkSync(tmpPath);
242
+ } catch {
243
+ }
244
+ throw err;
245
+ }
246
+ if (process.platform !== "win32") {
247
+ fs2.chmodSync(dir, 448);
248
+ fs2.chmodSync(tokenPath, 384);
249
+ }
250
+ return token;
251
+ }
252
+ function timingSafeTokenEqual(a, b) {
253
+ const ab = Buffer.from(a, "utf-8");
254
+ const bb = Buffer.from(b, "utf-8");
255
+ if (ab.length !== bb.length) {
256
+ const filler = Buffer.alloc(Math.max(ab.length, bb.length));
257
+ crypto.timingSafeEqual(filler, filler);
258
+ return false;
259
+ }
260
+ return crypto.timingSafeEqual(ab, bb);
261
+ }
262
+ function requireTokenForBind(spec, token) {
263
+ if (spec.kind === "uds" || isLoopbackHost(spec.host)) return;
264
+ if (!token || token.length < 16) {
265
+ throw new Error(
266
+ `gateway: refusing to bind ${spec.host}:${spec.port} without token configured`
267
+ );
268
+ }
269
+ if (spec.tls) return;
270
+ if (!spec.insecure) {
271
+ throw new Error(
272
+ `gateway: refusing to bind ${spec.host}:${spec.port} without TLS; pass --tls-cert/--tls-key, or --insecure only for trusted reverse-proxy/tunnel deployments`
273
+ );
274
+ }
275
+ }
276
+
277
+ // src/infra/config/dashboardClient.ts
278
+ import crypto2 from "crypto";
279
+ import fs3 from "fs";
280
+ import os3 from "os";
281
+ import path3 from "path";
282
+ function dashboardClientConfigPath(env = process.env) {
283
+ const home = env["HOME"] ?? os3.homedir();
284
+ return path3.join(home, ".config", "athena", "dashboard.json");
285
+ }
286
+ function normalizeDashboardUrl(input) {
287
+ let parsed;
288
+ try {
289
+ parsed = new URL(input);
290
+ } catch {
291
+ throw new Error("dashboard url must be a valid URL");
292
+ }
293
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
294
+ throw new Error("dashboard url must use http:// or https://");
295
+ }
296
+ return parsed.origin;
297
+ }
298
+ function readDashboardClientConfig(env = process.env) {
299
+ const configPath = dashboardClientConfigPath(env);
300
+ let raw;
301
+ try {
302
+ raw = fs3.readFileSync(configPath, "utf-8");
303
+ } catch (err) {
304
+ if (err.code === "ENOENT") return null;
305
+ throw err;
306
+ }
307
+ let parsed;
308
+ try {
309
+ parsed = JSON.parse(raw);
310
+ } catch (err) {
311
+ throw new Error(
312
+ `dashboard client config ${configPath} is invalid JSON: ${err instanceof Error ? err.message : String(err)}`
313
+ );
314
+ }
315
+ try {
316
+ return parseDashboardClientConfig(parsed);
317
+ } catch (err) {
318
+ throw new Error(
319
+ `dashboard client config ${configPath} is invalid: ${err instanceof Error ? err.message : String(err)}`
320
+ );
321
+ }
322
+ }
323
+ function writeDashboardClientConfig(config, env = process.env) {
324
+ const validated = parseDashboardClientConfig(config);
325
+ const configPath = dashboardClientConfigPath(env);
326
+ const dir = path3.dirname(configPath);
327
+ fs3.mkdirSync(dir, { recursive: true, mode: 448 });
328
+ const tmpPath = `${configPath}.${process.pid}.${crypto2.randomBytes(4).toString("hex")}.tmp`;
329
+ const fd = fs3.openSync(tmpPath, "w", 384);
330
+ try {
331
+ fs3.writeSync(fd, JSON.stringify(validated, null, 2) + "\n");
332
+ fs3.fsyncSync(fd);
333
+ } finally {
334
+ fs3.closeSync(fd);
335
+ }
336
+ try {
337
+ fs3.renameSync(tmpPath, configPath);
338
+ } catch (err) {
339
+ try {
340
+ fs3.unlinkSync(tmpPath);
341
+ } catch {
342
+ }
343
+ throw err;
344
+ }
345
+ if (process.platform !== "win32") {
346
+ try {
347
+ fs3.chmodSync(dir, 448);
348
+ fs3.chmodSync(configPath, 384);
349
+ } catch {
350
+ }
351
+ }
352
+ }
353
+ function removeDashboardClientConfig(env = process.env) {
354
+ const configPath = dashboardClientConfigPath(env);
355
+ try {
356
+ fs3.unlinkSync(configPath);
357
+ } catch (err) {
358
+ if (err.code !== "ENOENT") throw err;
359
+ }
360
+ }
361
+ function parseDashboardClientConfig(raw) {
362
+ if (typeof raw !== "object" || raw === null) {
363
+ throw new Error("dashboard config root must be an object");
364
+ }
365
+ const obj = raw;
366
+ const stringFields = [
367
+ "dashboardUrl",
368
+ "instanceId",
369
+ "refreshToken",
370
+ "fingerprint"
371
+ ];
372
+ for (const key of stringFields) {
373
+ const value = obj[key];
374
+ if (typeof value !== "string" || value.length === 0) {
375
+ throw new Error(`${key} must be a non-empty string`);
376
+ }
377
+ }
378
+ if (typeof obj["pairedAt"] !== "number") {
379
+ throw new Error("pairedAt must be a number");
380
+ }
381
+ if (obj["lastRefreshAt"] !== void 0 && typeof obj["lastRefreshAt"] !== "number") {
382
+ throw new Error("lastRefreshAt must be a number");
383
+ }
384
+ return {
385
+ dashboardUrl: obj["dashboardUrl"],
386
+ instanceId: obj["instanceId"],
387
+ refreshToken: obj["refreshToken"],
388
+ fingerprint: obj["fingerprint"],
389
+ pairedAt: obj["pairedAt"],
390
+ ...obj["lastRefreshAt"] !== void 0 ? { lastRefreshAt: obj["lastRefreshAt"] } : {}
391
+ };
392
+ }
393
+
394
+ // src/infra/config/dashboardAuth.ts
395
+ import fs4 from "fs";
396
+ var DEFAULT_LOCK_TIMEOUT_MS = 3e4;
397
+ var DEFAULT_LOCK_POLL_MS = 50;
398
+ var DEFAULT_STALE_LOCK_MS = 6e4;
399
+ async function refreshDashboardAccessToken(deps = {}) {
400
+ const env = deps.env ?? process.env;
401
+ const fetchImpl = deps.fetch ?? fetch;
402
+ const now = deps.now ?? (() => Date.now());
403
+ const initial = readDashboardClientConfig(env);
404
+ if (!initial) {
405
+ throw new Error(
406
+ 'dashboard not paired. Run "athena dashboard pair <token> --url <origin>" first.'
407
+ );
408
+ }
409
+ return await withLock(env, deps, async () => {
410
+ const config = readDashboardClientConfig(env);
411
+ if (!config) {
412
+ throw new Error(
413
+ 'dashboard not paired. Run "athena dashboard pair <token> --url <origin>" first.'
414
+ );
415
+ }
416
+ let response;
417
+ try {
418
+ response = await fetchImpl(
419
+ `${config.dashboardUrl}/api/instances/refresh`,
420
+ {
421
+ method: "POST",
422
+ headers: { "content-type": "application/json" },
423
+ body: JSON.stringify({
424
+ refreshToken: config.refreshToken,
425
+ fingerprint: config.fingerprint
426
+ })
427
+ }
428
+ );
429
+ } catch (err) {
430
+ throw new Error(
431
+ `dashboard refresh: failed to reach ${config.dashboardUrl}: ${err instanceof Error ? err.message : String(err)}`
432
+ );
433
+ }
434
+ if (!response.ok) {
435
+ let detail = "";
436
+ const dropBody = response.status === 401 || response.status === 403;
437
+ if (!dropBody) {
438
+ try {
439
+ detail = await response.text();
440
+ } catch {
441
+ }
442
+ }
443
+ throw new Error(
444
+ `dashboard refresh: ${config.dashboardUrl} returned ${response.status}` + (detail ? ` \u2014 ${truncate(detail, 200)}` : "")
445
+ );
446
+ }
447
+ let parsed;
448
+ try {
449
+ parsed = parseRefreshResponse(await response.json());
450
+ } catch (err) {
451
+ throw new Error(
452
+ `dashboard refresh: invalid response: ${err instanceof Error ? err.message : String(err)}`
453
+ );
454
+ }
455
+ const updated = {
456
+ ...config,
457
+ refreshToken: parsed.refreshToken,
458
+ lastRefreshAt: now()
459
+ };
460
+ writeDashboardClientConfig(updated, env);
461
+ return {
462
+ accessToken: parsed.accessToken,
463
+ instanceId: parsed.instanceId,
464
+ expiresInSec: parsed.expiresInSec
465
+ };
466
+ });
467
+ }
468
+ async function withLock(env, deps, fn) {
469
+ const configPath = dashboardClientConfigPath(env);
470
+ const lockPath = `${configPath}.lock`;
471
+ const timeoutMs = deps.lockTimeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS;
472
+ const pollMs = deps.lockPollMs ?? DEFAULT_LOCK_POLL_MS;
473
+ const staleMs = deps.staleLockMs ?? DEFAULT_STALE_LOCK_MS;
474
+ const deadline = Date.now() + timeoutMs;
475
+ let fd = null;
476
+ for (; ; ) {
477
+ try {
478
+ fd = fs4.openSync(lockPath, "wx", 384);
479
+ break;
480
+ } catch (err) {
481
+ if (err.code !== "EEXIST") throw err;
482
+ try {
483
+ const stat = fs4.statSync(lockPath);
484
+ if (Date.now() - stat.mtimeMs > staleMs) {
485
+ fs4.unlinkSync(lockPath);
486
+ continue;
487
+ }
488
+ } catch {
489
+ continue;
490
+ }
491
+ if (Date.now() >= deadline) {
492
+ throw new Error(
493
+ `dashboard refresh: timed out waiting for ${lockPath} after ${timeoutMs}ms`
494
+ );
495
+ }
496
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
497
+ }
498
+ }
499
+ try {
500
+ return await fn();
501
+ } finally {
502
+ try {
503
+ fs4.closeSync(fd);
504
+ } catch {
505
+ }
506
+ try {
507
+ fs4.unlinkSync(lockPath);
508
+ } catch {
509
+ }
510
+ }
511
+ }
512
+ function parseRefreshResponse(raw) {
513
+ if (typeof raw !== "object" || raw === null) {
514
+ throw new Error("expected object");
515
+ }
516
+ const obj = raw;
517
+ const accessToken = obj["accessToken"];
518
+ const refreshToken = obj["refreshToken"];
519
+ const instanceId = obj["instanceId"];
520
+ const expiresInSec = obj["expiresInSec"];
521
+ if (typeof accessToken !== "string" || accessToken.length === 0) {
522
+ throw new Error("missing accessToken");
523
+ }
524
+ if (typeof refreshToken !== "string" || refreshToken.length === 0) {
525
+ throw new Error("missing refreshToken");
526
+ }
527
+ if (typeof instanceId !== "string" || instanceId.length === 0) {
528
+ throw new Error("missing instanceId");
529
+ }
530
+ if (typeof expiresInSec !== "number") {
531
+ throw new Error("missing expiresInSec");
532
+ }
533
+ return { accessToken, refreshToken, instanceId, expiresInSec };
534
+ }
535
+ function truncate(text, max) {
536
+ return text.length > max ? text.slice(0, max) + "\u2026" : text;
537
+ }
538
+
539
+ // src/gateway/transport/types.ts
540
+ var TransportUnreachableError = class extends Error {
541
+ constructor(message) {
542
+ super(message);
543
+ this.name = "TransportUnreachableError";
544
+ }
545
+ };
546
+
547
+ // src/gateway/transport/trace.ts
548
+ function traceGatewayFrame(transport, peer, direction, frame) {
549
+ if (process.env["ATHENA_GATEWAY_TRACE"] !== "1") return;
550
+ writeGatewayTrace(
551
+ `${transport} ${direction} ${peer} ${JSON.stringify(redactFrame(frame))}`
552
+ );
553
+ }
554
+ function redactFrame(value) {
555
+ if (Array.isArray(value)) return value.map(redactFrame);
556
+ if (typeof value !== "object" || value === null) return value;
557
+ const out = {};
558
+ for (const [key, child] of Object.entries(value)) {
559
+ if (key === "token") {
560
+ out[key] = "<redacted>";
561
+ continue;
562
+ }
563
+ out[key] = redactFrame(child);
564
+ }
565
+ return out;
566
+ }
567
+
568
+ // src/gateway/transport/uds.ts
569
+ import fs5 from "fs";
570
+ import net from "net";
571
+ import path4 from "path";
572
+
573
+ // src/gateway/transport/framing.ts
574
+ var DEFAULT_MAX_LINE_BYTES = 1024 * 1024;
575
+ var LineReaderOverflowError = class extends Error {
576
+ constructor(limit) {
577
+ super(`NDJSON line exceeded ${limit} bytes`);
578
+ this.name = "LineReaderOverflowError";
579
+ }
580
+ };
581
+ var LineReader = class {
582
+ buffer = "";
583
+ maxBytes;
584
+ constructor(maxBytes = DEFAULT_MAX_LINE_BYTES) {
585
+ this.maxBytes = maxBytes;
586
+ }
587
+ push(chunk) {
588
+ const incoming = typeof chunk === "string" ? chunk : chunk.toString("utf-8");
589
+ if (this.buffer.length + incoming.length > this.maxBytes) {
590
+ this.buffer = "";
591
+ throw new LineReaderOverflowError(this.maxBytes);
592
+ }
593
+ this.buffer += incoming;
594
+ const lines = [];
595
+ let idx = this.buffer.indexOf("\n");
596
+ while (idx !== -1) {
597
+ let line = this.buffer.slice(0, idx);
598
+ if (line.endsWith("\r")) line = line.slice(0, -1);
599
+ if (line.length > 0) lines.push(line);
600
+ this.buffer = this.buffer.slice(idx + 1);
601
+ idx = this.buffer.indexOf("\n");
602
+ }
603
+ return lines;
604
+ }
605
+ flush() {
606
+ const remainder = this.buffer.trim();
607
+ this.buffer = "";
608
+ return remainder.length > 0 ? [remainder] : [];
609
+ }
610
+ };
611
+ function encodeLine(value) {
612
+ return JSON.stringify(value) + "\n";
613
+ }
614
+
615
+ // src/gateway/transport/uds.ts
616
+ function createUdsServerTransport(opts) {
617
+ return {
618
+ kind: "uds",
619
+ listen: (onConnection) => listenUds(opts, onConnection)
620
+ };
621
+ }
622
+ function createUdsClientTransport(opts) {
623
+ return {
624
+ kind: "uds",
625
+ connect: () => connectUds(opts)
626
+ };
627
+ }
628
+ async function listenUds(opts, onConnection) {
629
+ const logError = opts.logError ?? ((m) => process.stderr.write(m + "\n"));
630
+ await unlinkIfStale(opts.socketPath);
631
+ fs5.mkdirSync(path4.dirname(opts.socketPath), { recursive: true, mode: 448 });
632
+ const activeSockets = /* @__PURE__ */ new Set();
633
+ const server = net.createServer({ pauseOnConnect: false }, (socket) => {
634
+ activeSockets.add(socket);
635
+ socket.once("close", () => activeSockets.delete(socket));
636
+ onConnection(createSocketConnection(socket, "uds", logError));
637
+ });
638
+ await new Promise((resolve, reject) => {
639
+ const onError = (err) => reject(err);
640
+ server.once("error", onError);
641
+ server.listen(opts.socketPath, () => {
642
+ server.off("error", onError);
643
+ try {
644
+ if (process.platform !== "win32") {
645
+ fs5.chmodSync(opts.socketPath, 384);
646
+ }
647
+ } catch (err) {
648
+ logError(
649
+ `gateway: chmod 0600 on socket failed: ${err instanceof Error ? err.message : String(err)}`
650
+ );
651
+ }
652
+ resolve();
653
+ });
654
+ });
655
+ return {
656
+ close: () => new Promise((resolve) => {
657
+ for (const socket of activeSockets) {
658
+ socket.destroy();
659
+ }
660
+ activeSockets.clear();
661
+ server.close(() => {
662
+ try {
663
+ fs5.unlinkSync(opts.socketPath);
664
+ } catch {
665
+ }
666
+ resolve();
667
+ });
668
+ })
669
+ };
670
+ }
671
+ async function connectUds(opts) {
672
+ const timeoutMs = opts.timeoutMs ?? 5e3;
673
+ const socket = net.createConnection({ path: opts.socketPath });
674
+ await new Promise((resolve, reject) => {
675
+ const timer = setTimeout(() => {
676
+ socket.destroy();
677
+ reject(
678
+ new TransportUnreachableError(`connect timed out after ${timeoutMs}ms`)
679
+ );
680
+ }, timeoutMs);
681
+ socket.once("connect", () => {
682
+ clearTimeout(timer);
683
+ resolve();
684
+ });
685
+ socket.once("error", (err) => {
686
+ clearTimeout(timer);
687
+ const code = err.code;
688
+ if (code === "ENOENT" || code === "ECONNREFUSED") {
689
+ reject(
690
+ new TransportUnreachableError(
691
+ `gateway not reachable at ${opts.socketPath}: ${err.message}`
692
+ )
693
+ );
694
+ } else {
695
+ reject(err);
696
+ }
697
+ });
698
+ });
699
+ return createSocketConnection(socket, "uds");
700
+ }
701
+ async function unlinkIfStale(socketPath) {
702
+ if (!fs5.existsSync(socketPath)) return;
703
+ const alive = await new Promise((resolve) => {
704
+ const probe = net.connect(socketPath);
705
+ const timer = setTimeout(() => {
706
+ probe.destroy();
707
+ resolve(false);
708
+ }, 250);
709
+ probe.once("connect", () => {
710
+ clearTimeout(timer);
711
+ probe.end();
712
+ resolve(true);
713
+ });
714
+ probe.once("error", () => {
715
+ clearTimeout(timer);
716
+ resolve(false);
717
+ });
718
+ });
719
+ if (!alive) {
720
+ try {
721
+ fs5.unlinkSync(socketPath);
722
+ } catch {
723
+ }
724
+ }
725
+ }
726
+ function createSocketConnection(socket, peer, logError) {
727
+ const reader = new LineReader();
728
+ const frameHandlers = /* @__PURE__ */ new Set();
729
+ const closeHandlers = /* @__PURE__ */ new Set();
730
+ const errorHandlers = /* @__PURE__ */ new Set();
731
+ socket.on("data", (chunk) => {
732
+ let lines;
733
+ try {
734
+ lines = reader.push(chunk);
735
+ } catch (err) {
736
+ if (err instanceof LineReaderOverflowError) {
737
+ logError?.(`gateway: control connection overflow \u2014 closing`);
738
+ socket.destroy();
739
+ return;
740
+ }
741
+ throw err;
742
+ }
743
+ for (const line of lines) {
744
+ let parsed;
745
+ try {
746
+ parsed = JSON.parse(line);
747
+ } catch {
748
+ socket.destroy();
749
+ return;
750
+ }
751
+ traceGatewayFrame("uds", peer, "in", parsed);
752
+ for (const handler of frameHandlers) handler(parsed);
753
+ }
754
+ });
755
+ socket.on("error", (err) => {
756
+ for (const handler of errorHandlers) handler(err);
757
+ });
758
+ socket.on("close", () => {
759
+ for (const handler of closeHandlers) handler();
760
+ });
761
+ return {
762
+ kind: "uds",
763
+ peer,
764
+ send: (frame) => {
765
+ if (!socket.writable) return;
766
+ traceGatewayFrame("uds", peer, "out", frame);
767
+ socket.write(encodeLine(frame));
768
+ },
769
+ close: () => socket.destroy(),
770
+ onFrame: (cb) => {
771
+ frameHandlers.add(cb);
772
+ return () => frameHandlers.delete(cb);
773
+ },
774
+ onClose: (cb) => {
775
+ closeHandlers.add(cb);
776
+ return () => closeHandlers.delete(cb);
777
+ },
778
+ onError: (cb) => {
779
+ errorHandlers.add(cb);
780
+ return () => errorHandlers.delete(cb);
781
+ }
782
+ };
783
+ }
784
+
785
+ // src/shared/gateway-protocol/channelRequestId.ts
786
+ import { randomInt } from "crypto";
787
+ var ALPHABET = "abcdefghijkmnopqrstuvwxyz";
788
+ var CHANNEL_REQUEST_ID_LENGTH = 5;
789
+ var CHANNEL_REQUEST_ID_REGEX = /^[a-km-z]{5}$/;
790
+ function generateChannelRequestId() {
791
+ let id = "";
792
+ for (let i = 0; i < CHANNEL_REQUEST_ID_LENGTH; i++) {
793
+ id += ALPHABET[randomInt(ALPHABET.length)];
794
+ }
795
+ return id;
796
+ }
797
+ function isValidChannelRequestId(value) {
798
+ return CHANNEL_REQUEST_ID_REGEX.test(value);
799
+ }
800
+
801
+ export {
802
+ writeGatewayTrace,
803
+ TransportUnreachableError,
804
+ traceGatewayFrame,
805
+ createUdsServerTransport,
806
+ createUdsClientTransport,
807
+ resolveGatewayPaths,
808
+ resolveListenSpec,
809
+ isLoopbackHost,
810
+ CHANNEL_REQUEST_ID_REGEX,
811
+ generateChannelRequestId,
812
+ isValidChannelRequestId,
813
+ initTelemetry,
814
+ isTelemetryEnabled,
815
+ disableTelemetry,
816
+ shutdownTelemetry,
817
+ trackAppLaunched,
818
+ trackSessionStarted,
819
+ trackSessionEnded,
820
+ trackError,
821
+ trackTelemetryOptedOut,
822
+ trackClaudeStartupFailed,
823
+ trackGatewayTransportConnect,
824
+ trackGatewayTransportDisconnect,
825
+ trackGatewayTransportReconnect,
826
+ trackGatewayRuntimeRebind,
827
+ trackGatewayRuntimeExpired,
828
+ loadOrCreateToken,
829
+ rotateGatewayToken,
830
+ timingSafeTokenEqual,
831
+ requireTokenForBind,
832
+ dashboardClientConfigPath,
833
+ normalizeDashboardUrl,
834
+ readDashboardClientConfig,
835
+ writeDashboardClientConfig,
836
+ removeDashboardClientConfig,
837
+ refreshDashboardAccessToken
838
+ };
839
+ //# sourceMappingURL=chunk-3FVULBV4.js.map