@evident-ai/cli 0.1.4 → 0.1.6
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/dist/index.js +592 -39
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -179,7 +179,11 @@ var SERVICE_NAME = "evident-cli";
|
|
|
179
179
|
var ACCOUNT_NAME = "default";
|
|
180
180
|
async function getKeytar() {
|
|
181
181
|
try {
|
|
182
|
-
|
|
182
|
+
const keytar = await import("keytar");
|
|
183
|
+
if (typeof keytar.setPassword !== "function") {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
return keytar;
|
|
183
187
|
} catch {
|
|
184
188
|
return null;
|
|
185
189
|
}
|
|
@@ -432,22 +436,184 @@ async function whoami() {
|
|
|
432
436
|
import WebSocket from "ws";
|
|
433
437
|
import chalk4 from "chalk";
|
|
434
438
|
import ora2 from "ora";
|
|
439
|
+
|
|
440
|
+
// src/lib/telemetry.ts
|
|
441
|
+
var CLI_VERSION = process.env.npm_package_version || "unknown";
|
|
442
|
+
var eventBuffer = [];
|
|
443
|
+
var flushTimeout = null;
|
|
444
|
+
var isShuttingDown = false;
|
|
445
|
+
var FLUSH_INTERVAL_MS = 5e3;
|
|
446
|
+
var MAX_BUFFER_SIZE = 50;
|
|
447
|
+
var FLUSH_TIMEOUT_MS = 3e3;
|
|
448
|
+
function logEvent(eventType, options = {}) {
|
|
449
|
+
const event = {
|
|
450
|
+
event_type: eventType,
|
|
451
|
+
severity: options.severity || "info",
|
|
452
|
+
message: options.message,
|
|
453
|
+
metadata: options.metadata,
|
|
454
|
+
sandbox_id: options.sandboxId,
|
|
455
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
456
|
+
};
|
|
457
|
+
eventBuffer.push(event);
|
|
458
|
+
if (options.severity === "error" || eventBuffer.length >= MAX_BUFFER_SIZE) {
|
|
459
|
+
void flushEvents();
|
|
460
|
+
} else if (!flushTimeout && !isShuttingDown) {
|
|
461
|
+
flushTimeout = setTimeout(() => {
|
|
462
|
+
flushTimeout = null;
|
|
463
|
+
void flushEvents();
|
|
464
|
+
}, FLUSH_INTERVAL_MS);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
var telemetry = {
|
|
468
|
+
debug: (eventType, message, metadata, sandboxId) => logEvent(eventType, { severity: "debug", message, metadata, sandboxId }),
|
|
469
|
+
info: (eventType, message, metadata, sandboxId) => logEvent(eventType, { severity: "info", message, metadata, sandboxId }),
|
|
470
|
+
warn: (eventType, message, metadata, sandboxId) => logEvent(eventType, { severity: "warning", message, metadata, sandboxId }),
|
|
471
|
+
error: (eventType, message, metadata, sandboxId) => logEvent(eventType, { severity: "error", message, metadata, sandboxId })
|
|
472
|
+
};
|
|
473
|
+
async function flushEvents() {
|
|
474
|
+
if (eventBuffer.length === 0) return;
|
|
475
|
+
const events = eventBuffer;
|
|
476
|
+
eventBuffer = [];
|
|
477
|
+
if (flushTimeout) {
|
|
478
|
+
clearTimeout(flushTimeout);
|
|
479
|
+
flushTimeout = null;
|
|
480
|
+
}
|
|
481
|
+
try {
|
|
482
|
+
const credentials2 = await getToken();
|
|
483
|
+
if (!credentials2) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
const apiUrl = getApiUrlConfig();
|
|
487
|
+
const controller = new AbortController();
|
|
488
|
+
const timeout = setTimeout(() => controller.abort(), FLUSH_TIMEOUT_MS);
|
|
489
|
+
try {
|
|
490
|
+
const response = await fetch(`${apiUrl}/telemetry/events`, {
|
|
491
|
+
method: "POST",
|
|
492
|
+
headers: {
|
|
493
|
+
"Content-Type": "application/json",
|
|
494
|
+
Authorization: `Bearer ${credentials2.token}`
|
|
495
|
+
},
|
|
496
|
+
body: JSON.stringify({
|
|
497
|
+
events,
|
|
498
|
+
client_type: "cli",
|
|
499
|
+
client_version: CLI_VERSION
|
|
500
|
+
}),
|
|
501
|
+
signal: controller.signal
|
|
502
|
+
});
|
|
503
|
+
if (!response.ok) {
|
|
504
|
+
console.error(`Telemetry flush failed: ${response.status}`);
|
|
505
|
+
}
|
|
506
|
+
} finally {
|
|
507
|
+
clearTimeout(timeout);
|
|
508
|
+
}
|
|
509
|
+
} catch (error2) {
|
|
510
|
+
if (process.env.DEBUG) {
|
|
511
|
+
console.error("Telemetry error:", error2);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
async function shutdownTelemetry() {
|
|
516
|
+
isShuttingDown = true;
|
|
517
|
+
if (flushTimeout) {
|
|
518
|
+
clearTimeout(flushTimeout);
|
|
519
|
+
flushTimeout = null;
|
|
520
|
+
}
|
|
521
|
+
await flushEvents();
|
|
522
|
+
}
|
|
523
|
+
var EventTypes = {
|
|
524
|
+
// Tunnel lifecycle
|
|
525
|
+
TUNNEL_STARTING: "tunnel.starting",
|
|
526
|
+
TUNNEL_CONNECTED: "tunnel.connected",
|
|
527
|
+
TUNNEL_DISCONNECTED: "tunnel.disconnected",
|
|
528
|
+
TUNNEL_RECONNECTING: "tunnel.reconnecting",
|
|
529
|
+
TUNNEL_ERROR: "tunnel.error",
|
|
530
|
+
// OpenCode communication
|
|
531
|
+
OPENCODE_HEALTH_CHECK: "opencode.health_check",
|
|
532
|
+
OPENCODE_HEALTH_OK: "opencode.health_ok",
|
|
533
|
+
OPENCODE_HEALTH_FAILED: "opencode.health_failed",
|
|
534
|
+
OPENCODE_REQUEST_RECEIVED: "opencode.request_received",
|
|
535
|
+
OPENCODE_REQUEST_FORWARDED: "opencode.request_forwarded",
|
|
536
|
+
OPENCODE_RESPONSE_SENT: "opencode.response_sent",
|
|
537
|
+
OPENCODE_UNREACHABLE: "opencode.unreachable",
|
|
538
|
+
OPENCODE_ERROR: "opencode.error",
|
|
539
|
+
// Authentication
|
|
540
|
+
AUTH_LOGIN_STARTED: "auth.login_started",
|
|
541
|
+
AUTH_LOGIN_SUCCESS: "auth.login_success",
|
|
542
|
+
AUTH_LOGIN_FAILED: "auth.login_failed",
|
|
543
|
+
AUTH_LOGOUT: "auth.logout",
|
|
544
|
+
// CLI lifecycle
|
|
545
|
+
CLI_STARTED: "cli.started",
|
|
546
|
+
CLI_COMMAND: "cli.command",
|
|
547
|
+
CLI_ERROR: "cli.error"
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
// src/commands/tunnel.ts
|
|
435
551
|
var MAX_RECONNECT_DELAY = 3e4;
|
|
436
552
|
var BASE_RECONNECT_DELAY = 500;
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
const
|
|
440
|
-
|
|
553
|
+
var MAX_ACTIVITY_LOG_ENTRIES = 10;
|
|
554
|
+
function logActivity(state, entry) {
|
|
555
|
+
const fullEntry = {
|
|
556
|
+
...entry,
|
|
557
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
558
|
+
};
|
|
559
|
+
state.activityLog.push(fullEntry);
|
|
560
|
+
if (state.activityLog.length > MAX_ACTIVITY_LOG_ENTRIES) {
|
|
561
|
+
state.activityLog.shift();
|
|
562
|
+
}
|
|
563
|
+
state.lastActivity = fullEntry.timestamp;
|
|
564
|
+
}
|
|
565
|
+
function formatActivityEntry(entry, _verbose) {
|
|
566
|
+
const time = entry.timestamp.toLocaleTimeString("en-US", {
|
|
567
|
+
hour12: false,
|
|
568
|
+
hour: "2-digit",
|
|
569
|
+
minute: "2-digit",
|
|
570
|
+
second: "2-digit"
|
|
571
|
+
});
|
|
572
|
+
switch (entry.type) {
|
|
573
|
+
case "request": {
|
|
574
|
+
const duration = entry.durationMs ? ` (${entry.durationMs}ms)` : "";
|
|
575
|
+
const status = entry.status ? ` \u2192 ${colorizeStatus(entry.status)}` : " ...";
|
|
576
|
+
return ` ${chalk4.dim(`[${time}]`)} ${chalk4.cyan("\u2190")} ${entry.method} ${entry.path}${status}${duration}`;
|
|
577
|
+
}
|
|
578
|
+
case "response": {
|
|
579
|
+
const duration = entry.durationMs ? ` (${entry.durationMs}ms)` : "";
|
|
580
|
+
return ` ${chalk4.dim(`[${time}]`)} ${chalk4.green("\u2192")} ${entry.method} ${entry.path} ${colorizeStatus(entry.status)}${duration}`;
|
|
581
|
+
}
|
|
582
|
+
case "error": {
|
|
583
|
+
const errorMsg = entry.error || "Unknown error";
|
|
584
|
+
const path = entry.path ? ` ${entry.method} ${entry.path}` : "";
|
|
585
|
+
return ` ${chalk4.dim(`[${time}]`)} ${chalk4.red("\u2717")}${path} - ${chalk4.red(errorMsg)}`;
|
|
586
|
+
}
|
|
587
|
+
case "info": {
|
|
588
|
+
return ` ${chalk4.dim(`[${time}]`)} ${chalk4.blue("\u25CF")} ${entry.message}`;
|
|
589
|
+
}
|
|
590
|
+
default:
|
|
591
|
+
return ` ${chalk4.dim(`[${time}]`)} ${entry.message || "Unknown"}`;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
function colorizeStatus(status) {
|
|
595
|
+
if (status >= 200 && status < 300) {
|
|
596
|
+
return chalk4.green(status.toString());
|
|
597
|
+
} else if (status >= 300 && status < 400) {
|
|
598
|
+
return chalk4.yellow(status.toString());
|
|
599
|
+
} else if (status >= 400 && status < 500) {
|
|
600
|
+
return chalk4.red(status.toString());
|
|
601
|
+
} else if (status >= 500) {
|
|
602
|
+
return chalk4.bgRed.white(` ${status} `);
|
|
603
|
+
}
|
|
604
|
+
return status.toString();
|
|
441
605
|
}
|
|
442
606
|
function displayStatus(state) {
|
|
443
607
|
console.clear();
|
|
444
608
|
console.log(chalk4.bold("Evident Tunnel"));
|
|
445
|
-
console.log(chalk4.dim("\u2500".repeat(
|
|
609
|
+
console.log(chalk4.dim("\u2500".repeat(60)));
|
|
446
610
|
blank();
|
|
447
611
|
if (state.connected) {
|
|
448
|
-
console.log(` ${chalk4.green("\u25CF")} Status: ${chalk4.green("
|
|
612
|
+
console.log(` ${chalk4.green("\u25CF")} Status: ${chalk4.green("Connected")}`);
|
|
449
613
|
console.log(` Sandbox: ${state.sandboxId ?? "Unknown"}`);
|
|
450
|
-
|
|
614
|
+
if (state.sandboxName) {
|
|
615
|
+
console.log(` Name: ${state.sandboxName}`);
|
|
616
|
+
}
|
|
451
617
|
} else {
|
|
452
618
|
console.log(` ${chalk4.yellow("\u25CB")} Status: ${chalk4.yellow("Reconnecting...")}`);
|
|
453
619
|
if (state.reconnectAttempt > 0) {
|
|
@@ -455,10 +621,89 @@ function displayStatus(state) {
|
|
|
455
621
|
}
|
|
456
622
|
}
|
|
457
623
|
blank();
|
|
458
|
-
|
|
624
|
+
if (state.activityLog.length > 0) {
|
|
625
|
+
console.log(chalk4.bold(" Activity:"));
|
|
626
|
+
for (const entry of state.activityLog) {
|
|
627
|
+
console.log(formatActivityEntry(entry, state.verbose));
|
|
628
|
+
}
|
|
629
|
+
} else {
|
|
630
|
+
console.log(chalk4.dim(" No activity yet. Waiting for requests..."));
|
|
631
|
+
}
|
|
632
|
+
blank();
|
|
633
|
+
console.log(chalk4.dim("\u2500".repeat(60)));
|
|
634
|
+
if (state.verbose) {
|
|
635
|
+
console.log(chalk4.dim(" Verbose mode: ON (request/response bodies will be logged)"));
|
|
636
|
+
}
|
|
637
|
+
console.log(chalk4.dim(" Press Ctrl+C to disconnect"));
|
|
638
|
+
}
|
|
639
|
+
function displayError(_state, error2, details) {
|
|
640
|
+
blank();
|
|
641
|
+
console.log(chalk4.bgRed.white.bold(" ERROR "));
|
|
642
|
+
console.log(chalk4.red(` ${error2}`));
|
|
643
|
+
if (details) {
|
|
644
|
+
console.log(chalk4.dim(` ${details}`));
|
|
645
|
+
}
|
|
646
|
+
blank();
|
|
647
|
+
}
|
|
648
|
+
async function validateSandbox(token, sandboxId) {
|
|
649
|
+
const apiUrl = getApiUrlConfig();
|
|
650
|
+
try {
|
|
651
|
+
const response = await fetch(`${apiUrl}/sandboxes/${sandboxId}`, {
|
|
652
|
+
headers: {
|
|
653
|
+
Authorization: `Bearer ${token}`
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
if (response.status === 404) {
|
|
657
|
+
return { valid: false, error: "Sandbox not found" };
|
|
658
|
+
}
|
|
659
|
+
if (response.status === 401) {
|
|
660
|
+
return { valid: false, error: "Authentication failed. Please run `evident login` again." };
|
|
661
|
+
}
|
|
662
|
+
if (!response.ok) {
|
|
663
|
+
return { valid: false, error: `API error: ${response.status}` };
|
|
664
|
+
}
|
|
665
|
+
const sandbox = await response.json();
|
|
666
|
+
if (sandbox.sandbox_type !== "remote") {
|
|
667
|
+
return {
|
|
668
|
+
valid: false,
|
|
669
|
+
error: `Sandbox is type '${sandbox.sandbox_type}', must be 'remote' for tunnel connection`
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
return { valid: true, name: sandbox.name };
|
|
673
|
+
} catch (error2) {
|
|
674
|
+
const message = error2 instanceof Error ? error2.message : "Unknown error";
|
|
675
|
+
return { valid: false, error: `Failed to validate sandbox: ${message}` };
|
|
676
|
+
}
|
|
459
677
|
}
|
|
460
|
-
async function forwardToOpenCode(port, request) {
|
|
678
|
+
async function forwardToOpenCode(port, request, requestId, state) {
|
|
461
679
|
const url = `http://localhost:${port}${request.path}`;
|
|
680
|
+
const startTime = Date.now();
|
|
681
|
+
state.pendingRequests.set(requestId, {
|
|
682
|
+
startTime,
|
|
683
|
+
method: request.method,
|
|
684
|
+
path: request.path
|
|
685
|
+
});
|
|
686
|
+
logActivity(state, {
|
|
687
|
+
type: "request",
|
|
688
|
+
method: request.method,
|
|
689
|
+
path: request.path,
|
|
690
|
+
requestId
|
|
691
|
+
});
|
|
692
|
+
displayStatus(state);
|
|
693
|
+
if (state.verbose && request.body) {
|
|
694
|
+
console.log(chalk4.dim(` Request body: ${JSON.stringify(request.body, null, 2)}`));
|
|
695
|
+
}
|
|
696
|
+
telemetry.debug(
|
|
697
|
+
EventTypes.OPENCODE_REQUEST_FORWARDED,
|
|
698
|
+
`Forwarding ${request.method} ${request.path}`,
|
|
699
|
+
{
|
|
700
|
+
method: request.method,
|
|
701
|
+
path: request.path,
|
|
702
|
+
port,
|
|
703
|
+
requestId
|
|
704
|
+
},
|
|
705
|
+
state.sandboxId ?? void 0
|
|
706
|
+
);
|
|
462
707
|
try {
|
|
463
708
|
const response = await fetch(url, {
|
|
464
709
|
method: request.method,
|
|
@@ -475,21 +720,97 @@ async function forwardToOpenCode(port, request) {
|
|
|
475
720
|
} else {
|
|
476
721
|
body = await response.text();
|
|
477
722
|
}
|
|
723
|
+
const durationMs = Date.now() - startTime;
|
|
724
|
+
state.pendingRequests.delete(requestId);
|
|
725
|
+
const lastEntry = state.activityLog[state.activityLog.length - 1];
|
|
726
|
+
if (lastEntry && lastEntry.requestId === requestId) {
|
|
727
|
+
lastEntry.type = "response";
|
|
728
|
+
lastEntry.status = response.status;
|
|
729
|
+
lastEntry.durationMs = durationMs;
|
|
730
|
+
} else {
|
|
731
|
+
logActivity(state, {
|
|
732
|
+
type: "response",
|
|
733
|
+
method: request.method,
|
|
734
|
+
path: request.path,
|
|
735
|
+
status: response.status,
|
|
736
|
+
durationMs,
|
|
737
|
+
requestId
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
displayStatus(state);
|
|
741
|
+
if (state.verbose && body) {
|
|
742
|
+
const bodyStr = typeof body === "string" ? body : JSON.stringify(body, null, 2);
|
|
743
|
+
const truncated = bodyStr.length > 500 ? bodyStr.substring(0, 500) + "..." : bodyStr;
|
|
744
|
+
console.log(chalk4.dim(` Response body: ${truncated}`));
|
|
745
|
+
}
|
|
746
|
+
telemetry.debug(
|
|
747
|
+
EventTypes.OPENCODE_RESPONSE_SENT,
|
|
748
|
+
`Response ${response.status}`,
|
|
749
|
+
{
|
|
750
|
+
status: response.status,
|
|
751
|
+
path: request.path,
|
|
752
|
+
durationMs,
|
|
753
|
+
requestId
|
|
754
|
+
},
|
|
755
|
+
state.sandboxId ?? void 0
|
|
756
|
+
);
|
|
478
757
|
return {
|
|
479
758
|
status: response.status,
|
|
480
759
|
body
|
|
481
760
|
};
|
|
482
761
|
} catch (error2) {
|
|
483
762
|
const message = error2 instanceof Error ? error2.message : "Unknown error";
|
|
763
|
+
const durationMs = Date.now() - startTime;
|
|
764
|
+
state.pendingRequests.delete(requestId);
|
|
765
|
+
logActivity(state, {
|
|
766
|
+
type: "error",
|
|
767
|
+
method: request.method,
|
|
768
|
+
path: request.path,
|
|
769
|
+
error: `OpenCode unreachable: ${message}`,
|
|
770
|
+
durationMs,
|
|
771
|
+
requestId
|
|
772
|
+
});
|
|
773
|
+
displayStatus(state);
|
|
774
|
+
telemetry.error(
|
|
775
|
+
EventTypes.OPENCODE_UNREACHABLE,
|
|
776
|
+
`Failed to connect to OpenCode: ${message}`,
|
|
777
|
+
{
|
|
778
|
+
port,
|
|
779
|
+
path: request.path,
|
|
780
|
+
error: message,
|
|
781
|
+
requestId
|
|
782
|
+
},
|
|
783
|
+
state.sandboxId ?? void 0
|
|
784
|
+
);
|
|
484
785
|
return {
|
|
485
786
|
status: 502,
|
|
486
787
|
body: { error: "Failed to connect to OpenCode", message }
|
|
487
788
|
};
|
|
488
789
|
}
|
|
489
790
|
}
|
|
791
|
+
function getReconnectDelay(attempt) {
|
|
792
|
+
const exponentialDelay = BASE_RECONNECT_DELAY * Math.pow(2, attempt);
|
|
793
|
+
const jitter = Math.random() * 1e3;
|
|
794
|
+
return Math.min(exponentialDelay + jitter, MAX_RECONNECT_DELAY);
|
|
795
|
+
}
|
|
490
796
|
async function connect(token, sandboxId, port, state) {
|
|
491
797
|
const tunnelUrl = getTunnelUrlConfig();
|
|
492
|
-
const url =
|
|
798
|
+
const url = `${tunnelUrl}/tunnel/${sandboxId}/connect`;
|
|
799
|
+
logActivity(state, {
|
|
800
|
+
type: "info",
|
|
801
|
+
message: "Connecting to tunnel relay..."
|
|
802
|
+
});
|
|
803
|
+
displayStatus(state);
|
|
804
|
+
telemetry.info(
|
|
805
|
+
EventTypes.TUNNEL_STARTING,
|
|
806
|
+
`Connecting to ${url}`,
|
|
807
|
+
{
|
|
808
|
+
sandboxId,
|
|
809
|
+
port,
|
|
810
|
+
tunnelUrl
|
|
811
|
+
},
|
|
812
|
+
sandboxId
|
|
813
|
+
);
|
|
493
814
|
return new Promise((resolve, reject) => {
|
|
494
815
|
const ws = new WebSocket(url, {
|
|
495
816
|
headers: {
|
|
@@ -499,20 +820,47 @@ async function connect(token, sandboxId, port, state) {
|
|
|
499
820
|
ws.on("open", () => {
|
|
500
821
|
state.connected = true;
|
|
501
822
|
state.reconnectAttempt = 0;
|
|
502
|
-
state
|
|
823
|
+
logActivity(state, {
|
|
824
|
+
type: "info",
|
|
825
|
+
message: "WebSocket connection established"
|
|
826
|
+
});
|
|
503
827
|
displayStatus(state);
|
|
504
828
|
});
|
|
505
829
|
ws.on("message", async (data) => {
|
|
506
830
|
try {
|
|
507
831
|
const message = JSON.parse(data.toString());
|
|
508
|
-
state.lastActivity = /* @__PURE__ */ new Date();
|
|
509
832
|
switch (message.type) {
|
|
510
833
|
case "connected":
|
|
511
|
-
state.sandboxId = message.sandbox_id ??
|
|
834
|
+
state.sandboxId = message.sandbox_id ?? sandboxId;
|
|
835
|
+
logActivity(state, {
|
|
836
|
+
type: "info",
|
|
837
|
+
message: `Tunnel connected (sandbox: ${state.sandboxId})`
|
|
838
|
+
});
|
|
839
|
+
telemetry.info(
|
|
840
|
+
EventTypes.TUNNEL_CONNECTED,
|
|
841
|
+
`Tunnel connected`,
|
|
842
|
+
{
|
|
843
|
+
sandboxId: message.sandbox_id
|
|
844
|
+
},
|
|
845
|
+
message.sandbox_id
|
|
846
|
+
);
|
|
512
847
|
displayStatus(state);
|
|
513
848
|
break;
|
|
514
849
|
case "error":
|
|
515
|
-
|
|
850
|
+
logActivity(state, {
|
|
851
|
+
type: "error",
|
|
852
|
+
error: message.message || "Unknown tunnel error"
|
|
853
|
+
});
|
|
854
|
+
telemetry.error(
|
|
855
|
+
EventTypes.TUNNEL_ERROR,
|
|
856
|
+
`Tunnel error: ${message.message}`,
|
|
857
|
+
{
|
|
858
|
+
code: message.code,
|
|
859
|
+
message: message.message
|
|
860
|
+
},
|
|
861
|
+
state.sandboxId ?? void 0
|
|
862
|
+
);
|
|
863
|
+
displayStatus(state);
|
|
516
864
|
if (message.code === "unauthorized") {
|
|
517
865
|
ws.close();
|
|
518
866
|
reject(new Error("Unauthorized"));
|
|
@@ -523,7 +871,17 @@ async function connect(token, sandboxId, port, state) {
|
|
|
523
871
|
break;
|
|
524
872
|
case "request":
|
|
525
873
|
if (message.id && message.payload) {
|
|
526
|
-
|
|
874
|
+
telemetry.debug(
|
|
875
|
+
EventTypes.OPENCODE_REQUEST_RECEIVED,
|
|
876
|
+
`Request: ${message.payload.method} ${message.payload.path}`,
|
|
877
|
+
{
|
|
878
|
+
requestId: message.id,
|
|
879
|
+
method: message.payload.method,
|
|
880
|
+
path: message.payload.path
|
|
881
|
+
},
|
|
882
|
+
state.sandboxId ?? void 0
|
|
883
|
+
);
|
|
884
|
+
const response = await forwardToOpenCode(port, message.payload, message.id, state);
|
|
527
885
|
ws.send(
|
|
528
886
|
JSON.stringify({
|
|
529
887
|
type: "response",
|
|
@@ -531,73 +889,267 @@ async function connect(token, sandboxId, port, state) {
|
|
|
531
889
|
payload: response
|
|
532
890
|
})
|
|
533
891
|
);
|
|
534
|
-
displayStatus(state);
|
|
535
892
|
}
|
|
536
893
|
break;
|
|
537
894
|
}
|
|
538
895
|
} catch (error2) {
|
|
539
|
-
|
|
896
|
+
const errorMessage = error2 instanceof Error ? error2.message : "Unknown error";
|
|
897
|
+
logActivity(state, {
|
|
898
|
+
type: "error",
|
|
899
|
+
error: `Failed to handle message: ${errorMessage}`
|
|
900
|
+
});
|
|
901
|
+
telemetry.error(
|
|
902
|
+
EventTypes.TUNNEL_ERROR,
|
|
903
|
+
`Failed to handle message: ${errorMessage}`,
|
|
904
|
+
{
|
|
905
|
+
error: errorMessage
|
|
906
|
+
},
|
|
907
|
+
state.sandboxId ?? void 0
|
|
908
|
+
);
|
|
909
|
+
displayStatus(state);
|
|
540
910
|
}
|
|
541
911
|
});
|
|
542
|
-
ws.on("close", () => {
|
|
912
|
+
ws.on("close", (code, reason) => {
|
|
543
913
|
state.connected = false;
|
|
914
|
+
const reasonStr = reason.toString() || "No reason provided";
|
|
915
|
+
logActivity(state, {
|
|
916
|
+
type: "info",
|
|
917
|
+
message: `Disconnected (code: ${code}, reason: ${reasonStr})`
|
|
918
|
+
});
|
|
919
|
+
telemetry.info(
|
|
920
|
+
EventTypes.TUNNEL_DISCONNECTED,
|
|
921
|
+
"Tunnel disconnected",
|
|
922
|
+
{
|
|
923
|
+
sandboxId: state.sandboxId,
|
|
924
|
+
code,
|
|
925
|
+
reason: reasonStr
|
|
926
|
+
},
|
|
927
|
+
state.sandboxId ?? void 0
|
|
928
|
+
);
|
|
544
929
|
displayStatus(state);
|
|
545
930
|
resolve();
|
|
546
931
|
});
|
|
547
932
|
ws.on("error", (error2) => {
|
|
548
933
|
state.connected = false;
|
|
934
|
+
logActivity(state, {
|
|
935
|
+
type: "error",
|
|
936
|
+
error: `Connection error: ${error2.message}`
|
|
937
|
+
});
|
|
938
|
+
telemetry.error(
|
|
939
|
+
EventTypes.TUNNEL_ERROR,
|
|
940
|
+
`Connection error: ${error2.message}`,
|
|
941
|
+
{
|
|
942
|
+
error: error2.message
|
|
943
|
+
},
|
|
944
|
+
state.sandboxId ?? void 0
|
|
945
|
+
);
|
|
549
946
|
displayStatus(state);
|
|
550
|
-
console.error(chalk4.dim(`Connection error: ${error2.message}`));
|
|
551
947
|
});
|
|
552
|
-
const cleanup = () => {
|
|
948
|
+
const cleanup = async () => {
|
|
949
|
+
process.removeAllListeners("SIGINT");
|
|
950
|
+
process.removeAllListeners("SIGTERM");
|
|
951
|
+
logActivity(state, {
|
|
952
|
+
type: "info",
|
|
953
|
+
message: "Shutting down..."
|
|
954
|
+
});
|
|
955
|
+
displayStatus(state);
|
|
956
|
+
telemetry.info(
|
|
957
|
+
EventTypes.TUNNEL_DISCONNECTED,
|
|
958
|
+
"Tunnel stopped by user",
|
|
959
|
+
{
|
|
960
|
+
sandboxId: state.sandboxId
|
|
961
|
+
},
|
|
962
|
+
state.sandboxId ?? void 0
|
|
963
|
+
);
|
|
964
|
+
await shutdownTelemetry();
|
|
553
965
|
ws.close();
|
|
554
966
|
process.exit(0);
|
|
555
967
|
};
|
|
556
|
-
process.
|
|
557
|
-
process.
|
|
968
|
+
process.removeAllListeners("SIGINT");
|
|
969
|
+
process.removeAllListeners("SIGTERM");
|
|
970
|
+
process.once("SIGINT", () => void cleanup());
|
|
971
|
+
process.once("SIGTERM", () => void cleanup());
|
|
558
972
|
});
|
|
559
973
|
}
|
|
560
974
|
async function tunnel(options) {
|
|
975
|
+
const verbose = options.verbose ?? false;
|
|
561
976
|
const credentials2 = await getToken();
|
|
562
977
|
if (!credentials2) {
|
|
563
|
-
|
|
978
|
+
telemetry.error(EventTypes.CLI_ERROR, "Not logged in", { command: "tunnel" });
|
|
979
|
+
printError("Not logged in. Run `evident login` first.");
|
|
564
980
|
process.exit(1);
|
|
565
981
|
}
|
|
566
982
|
const port = options.port ?? 4096;
|
|
567
983
|
const sandboxId = options.sandbox;
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
} catch {
|
|
576
|
-
spinner.warn(`Could not connect to OpenCode on port ${port}`);
|
|
577
|
-
printWarning("Make sure OpenCode is running before starting the tunnel.");
|
|
984
|
+
if (!sandboxId) {
|
|
985
|
+
printError("--sandbox <id> is required");
|
|
986
|
+
blank();
|
|
987
|
+
console.log(chalk4.dim("To find your sandbox ID:"));
|
|
988
|
+
console.log(chalk4.dim(" 1. Create a remote sandbox in the Evident web UI"));
|
|
989
|
+
console.log(chalk4.dim(" 2. Copy the sandbox ID from the URL or settings"));
|
|
990
|
+
console.log(chalk4.dim(" 3. Run: evident tunnel --sandbox <id>"));
|
|
578
991
|
blank();
|
|
992
|
+
telemetry.error(EventTypes.CLI_ERROR, "Missing sandbox ID", { command: "tunnel" });
|
|
993
|
+
process.exit(1);
|
|
579
994
|
}
|
|
580
995
|
const state = {
|
|
581
996
|
connected: false,
|
|
582
|
-
sandboxId
|
|
997
|
+
sandboxId,
|
|
998
|
+
sandboxName: null,
|
|
583
999
|
reconnectAttempt: 0,
|
|
584
|
-
lastActivity: /* @__PURE__ */ new Date()
|
|
1000
|
+
lastActivity: /* @__PURE__ */ new Date(),
|
|
1001
|
+
activityLog: [],
|
|
1002
|
+
pendingRequests: /* @__PURE__ */ new Map(),
|
|
1003
|
+
verbose
|
|
585
1004
|
};
|
|
1005
|
+
telemetry.info(
|
|
1006
|
+
EventTypes.CLI_COMMAND,
|
|
1007
|
+
"Starting tunnel command",
|
|
1008
|
+
{
|
|
1009
|
+
command: "tunnel",
|
|
1010
|
+
port,
|
|
1011
|
+
sandboxId,
|
|
1012
|
+
verbose
|
|
1013
|
+
},
|
|
1014
|
+
sandboxId
|
|
1015
|
+
);
|
|
1016
|
+
logActivity(state, {
|
|
1017
|
+
type: "info",
|
|
1018
|
+
message: `Starting tunnel (port: ${port}, verbose: ${verbose})`
|
|
1019
|
+
});
|
|
1020
|
+
logActivity(state, {
|
|
1021
|
+
type: "info",
|
|
1022
|
+
message: "Validating sandbox..."
|
|
1023
|
+
});
|
|
1024
|
+
const validateSpinner = ora2("Validating sandbox...").start();
|
|
1025
|
+
const validation = await validateSandbox(credentials2.token, sandboxId);
|
|
1026
|
+
if (!validation.valid) {
|
|
1027
|
+
validateSpinner.fail(`Sandbox validation failed: ${validation.error}`);
|
|
1028
|
+
logActivity(state, {
|
|
1029
|
+
type: "error",
|
|
1030
|
+
error: `Sandbox validation failed: ${validation.error}`
|
|
1031
|
+
});
|
|
1032
|
+
telemetry.error(EventTypes.CLI_ERROR, `Sandbox validation failed: ${validation.error}`, {
|
|
1033
|
+
command: "tunnel",
|
|
1034
|
+
sandboxId
|
|
1035
|
+
});
|
|
1036
|
+
displayStatus(state);
|
|
1037
|
+
process.exit(1);
|
|
1038
|
+
}
|
|
1039
|
+
state.sandboxName = validation.name ?? null;
|
|
1040
|
+
validateSpinner.succeed(`Sandbox: ${validation.name || sandboxId}`);
|
|
1041
|
+
logActivity(state, {
|
|
1042
|
+
type: "info",
|
|
1043
|
+
message: `Sandbox validated: ${validation.name || sandboxId}`
|
|
1044
|
+
});
|
|
1045
|
+
logActivity(state, {
|
|
1046
|
+
type: "info",
|
|
1047
|
+
message: `Checking OpenCode on port ${port}...`
|
|
1048
|
+
});
|
|
1049
|
+
const opencodeSpinner = ora2("Checking OpenCode connection...").start();
|
|
1050
|
+
try {
|
|
1051
|
+
telemetry.debug(
|
|
1052
|
+
EventTypes.OPENCODE_HEALTH_CHECK,
|
|
1053
|
+
`Checking OpenCode on port ${port}`,
|
|
1054
|
+
{ port },
|
|
1055
|
+
sandboxId
|
|
1056
|
+
);
|
|
1057
|
+
const response = await fetch(`http://localhost:${port}/health`);
|
|
1058
|
+
if (!response.ok) {
|
|
1059
|
+
throw new Error(`Health check returned ${response.status}`);
|
|
1060
|
+
}
|
|
1061
|
+
const healthData = await response.json().catch(() => ({}));
|
|
1062
|
+
const version = healthData.version ? ` (v${healthData.version})` : "";
|
|
1063
|
+
telemetry.info(
|
|
1064
|
+
EventTypes.OPENCODE_HEALTH_OK,
|
|
1065
|
+
`OpenCode healthy on port ${port}`,
|
|
1066
|
+
{
|
|
1067
|
+
port,
|
|
1068
|
+
healthData
|
|
1069
|
+
},
|
|
1070
|
+
sandboxId
|
|
1071
|
+
);
|
|
1072
|
+
opencodeSpinner.succeed(`OpenCode running on port ${port}${version}`);
|
|
1073
|
+
logActivity(state, {
|
|
1074
|
+
type: "info",
|
|
1075
|
+
message: `OpenCode running on port ${port}${version}`
|
|
1076
|
+
});
|
|
1077
|
+
} catch (error2) {
|
|
1078
|
+
const errorMessage = error2 instanceof Error ? error2.message : "Unknown error";
|
|
1079
|
+
telemetry.warn(
|
|
1080
|
+
EventTypes.OPENCODE_HEALTH_FAILED,
|
|
1081
|
+
`Could not connect to OpenCode: ${errorMessage}`,
|
|
1082
|
+
{
|
|
1083
|
+
port,
|
|
1084
|
+
error: errorMessage
|
|
1085
|
+
},
|
|
1086
|
+
sandboxId
|
|
1087
|
+
);
|
|
1088
|
+
opencodeSpinner.warn(`Could not connect to OpenCode on port ${port}`);
|
|
1089
|
+
logActivity(state, {
|
|
1090
|
+
type: "error",
|
|
1091
|
+
error: `OpenCode not reachable on port ${port}: ${errorMessage}`
|
|
1092
|
+
});
|
|
1093
|
+
printWarning("Make sure OpenCode is running before starting the tunnel:");
|
|
1094
|
+
console.log(chalk4.dim(` opencode serve --port ${port}`));
|
|
1095
|
+
blank();
|
|
1096
|
+
}
|
|
586
1097
|
while (true) {
|
|
587
1098
|
try {
|
|
588
1099
|
await connect(credentials2.token, sandboxId, port, state);
|
|
589
1100
|
state.reconnectAttempt++;
|
|
590
1101
|
const delay = getReconnectDelay(state.reconnectAttempt);
|
|
1102
|
+
logActivity(state, {
|
|
1103
|
+
type: "info",
|
|
1104
|
+
message: `Reconnecting in ${Math.round(delay / 1e3)}s (attempt ${state.reconnectAttempt})...`
|
|
1105
|
+
});
|
|
1106
|
+
telemetry.info(
|
|
1107
|
+
EventTypes.TUNNEL_RECONNECTING,
|
|
1108
|
+
`Reconnecting (attempt ${state.reconnectAttempt})`,
|
|
1109
|
+
{
|
|
1110
|
+
attempt: state.reconnectAttempt,
|
|
1111
|
+
delayMs: delay
|
|
1112
|
+
},
|
|
1113
|
+
state.sandboxId ?? void 0
|
|
1114
|
+
);
|
|
591
1115
|
displayStatus(state);
|
|
592
1116
|
await sleep(delay);
|
|
593
1117
|
} catch (error2) {
|
|
594
1118
|
const message = error2 instanceof Error ? error2.message : "Unknown error";
|
|
595
1119
|
if (message === "Unauthorized") {
|
|
596
|
-
|
|
1120
|
+
telemetry.error(
|
|
1121
|
+
EventTypes.CLI_ERROR,
|
|
1122
|
+
"Authentication failed",
|
|
1123
|
+
{
|
|
1124
|
+
command: "tunnel",
|
|
1125
|
+
error: message
|
|
1126
|
+
},
|
|
1127
|
+
state.sandboxId ?? void 0
|
|
1128
|
+
);
|
|
1129
|
+
await shutdownTelemetry();
|
|
1130
|
+
displayError(state, "Authentication failed", "Please run `evident login` again.");
|
|
597
1131
|
process.exit(1);
|
|
598
1132
|
}
|
|
1133
|
+
logActivity(state, {
|
|
1134
|
+
type: "error",
|
|
1135
|
+
error: message
|
|
1136
|
+
});
|
|
1137
|
+
telemetry.error(
|
|
1138
|
+
EventTypes.TUNNEL_ERROR,
|
|
1139
|
+
`Tunnel error: ${message}`,
|
|
1140
|
+
{
|
|
1141
|
+
error: message,
|
|
1142
|
+
attempt: state.reconnectAttempt
|
|
1143
|
+
},
|
|
1144
|
+
state.sandboxId ?? void 0
|
|
1145
|
+
);
|
|
599
1146
|
state.reconnectAttempt++;
|
|
600
1147
|
const delay = getReconnectDelay(state.reconnectAttempt);
|
|
1148
|
+
logActivity(state, {
|
|
1149
|
+
type: "info",
|
|
1150
|
+
message: `Reconnecting in ${Math.round(delay / 1e3)}s (attempt ${state.reconnectAttempt})...`
|
|
1151
|
+
});
|
|
1152
|
+
displayStatus(state);
|
|
601
1153
|
await sleep(delay);
|
|
602
1154
|
}
|
|
603
1155
|
}
|
|
@@ -614,10 +1166,11 @@ program.name("evident").description("Run OpenCode locally and connect it to Evid
|
|
|
614
1166
|
program.command("login").description("Authenticate with Evident").option("--token", "Use token-based authentication (for CI/CD)").option("--no-browser", "Do not open the browser automatically").action(login);
|
|
615
1167
|
program.command("logout").description("Remove stored credentials").action(logout);
|
|
616
1168
|
program.command("whoami").description("Show the currently logged in user").action(whoami);
|
|
617
|
-
program.command("tunnel").description("Establish a tunnel to Evident for Local Mode").
|
|
1169
|
+
program.command("tunnel").description("Establish a tunnel to Evident for Local Mode").requiredOption("-s, --sandbox <id>", "Sandbox ID to connect to (required)").option("-p, --port <port>", "OpenCode port (default: 4096)", "4096").option("-v, --verbose", "Show detailed request/response information").action((options) => {
|
|
618
1170
|
tunnel({
|
|
619
1171
|
sandbox: options.sandbox,
|
|
620
|
-
port: parseInt(options.port, 10)
|
|
1172
|
+
port: parseInt(options.port, 10),
|
|
1173
|
+
verbose: options.verbose
|
|
621
1174
|
});
|
|
622
1175
|
});
|
|
623
1176
|
program.parse();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/commands/login.ts","../src/lib/config.ts","../src/lib/api.ts","../src/lib/keychain.ts","../src/utils/ui.ts","../src/commands/logout.ts","../src/commands/whoami.ts","../src/commands/tunnel.ts"],"sourcesContent":["/**\n * Evident CLI\n *\n * Run OpenCode locally and connect it to the Evident platform.\n */\n\nimport { Command } from 'commander';\nimport { login } from './commands/login.js';\nimport { logout } from './commands/logout.js';\nimport { whoami } from './commands/whoami.js';\nimport { tunnel } from './commands/tunnel.js';\nimport { setEnvironment, type Environment } from './lib/config.js';\n\nconst program = new Command();\n\nprogram\n .name('evident')\n .description('Run OpenCode locally and connect it to Evident')\n .version('0.1.0')\n .option('-e, --env <environment>', 'Environment to use (local, dev, production)', 'production')\n .hook('preAction', (thisCommand) => {\n const env = thisCommand.opts().env as Environment;\n if (env) {\n setEnvironment(env);\n }\n });\n\n// Login command\nprogram\n .command('login')\n .description('Authenticate with Evident')\n .option('--token', 'Use token-based authentication (for CI/CD)')\n .option('--no-browser', 'Do not open the browser automatically')\n .action(login);\n\n// Logout command\nprogram.command('logout').description('Remove stored credentials').action(logout);\n\n// Whoami command\nprogram.command('whoami').description('Show the currently logged in user').action(whoami);\n\n// Tunnel command\nprogram\n .command('tunnel')\n .description('Establish a tunnel to Evident for Local Mode')\n .option('-s, --sandbox <id>', 'Sandbox ID to connect to')\n .option('-p, --port <port>', 'OpenCode port (default: 4096)', '4096')\n .action((options: { sandbox?: string; port: string }) => {\n tunnel({\n sandbox: options.sandbox,\n port: parseInt(options.port, 10),\n });\n });\n\n// Parse arguments\nprogram.parse();\n","/**\n * Login Command\n *\n * Authenticates the user using OAuth Device Flow.\n * See ADR-0018 for details.\n */\n\nimport open from 'open';\nimport ora from 'ora';\nimport chalk from 'chalk';\nimport { api } from '../lib/api.js';\nimport { storeToken } from '../lib/keychain.js';\nimport { printSuccess, printError, blank, waitForEnter, sleep } from '../utils/ui.js';\n\ninterface DeviceAuthResponse {\n device_code: string;\n user_code: string;\n verification_uri: string;\n expires_in: number;\n interval: number;\n}\n\ninterface TokenPollResponse {\n status: 'pending' | 'complete' | 'expired';\n access_token?: string;\n expires_at?: string;\n user?: {\n id: string;\n email: string;\n };\n}\n\ninterface LoginOptions {\n token?: boolean;\n noBrowser?: boolean;\n}\n\n/**\n * Start device flow authentication\n */\nasync function deviceFlowLogin(options: LoginOptions): Promise<void> {\n // Step 1: Request device code\n let deviceAuth: DeviceAuthResponse;\n try {\n deviceAuth = await api.post<DeviceAuthResponse>('/auth/device');\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError(`Failed to start authentication: ${message}`);\n process.exit(1);\n }\n\n const { device_code, user_code, verification_uri, interval } = deviceAuth;\n\n // Step 2: Display instructions\n blank();\n console.log(chalk.bold('To authenticate, visit:'));\n console.log();\n console.log(` ${chalk.cyan(verification_uri)}`);\n console.log();\n console.log(chalk.bold('And enter this code:'));\n console.log();\n console.log(` ${chalk.yellow.bold(user_code)}`);\n blank();\n\n // Step 3: Open browser (unless --no-browser)\n if (!options.noBrowser) {\n await waitForEnter('Press Enter to open the browser...');\n try {\n await open(verification_uri);\n } catch {\n console.log(chalk.dim('Could not open browser. Please visit the URL manually.'));\n }\n }\n\n // Step 4: Poll for completion\n const spinner = ora('Waiting for authentication...').start();\n\n const pollIntervalMs = (interval || 5) * 1000;\n const maxAttempts = 60; // 5 minutes max at 5s intervals\n let attempts = 0;\n\n while (attempts < maxAttempts) {\n await sleep(pollIntervalMs);\n attempts++;\n\n try {\n const result = await api.post<TokenPollResponse>('/auth/device/token', {\n device_code,\n });\n\n if (result.status === 'complete' && result.access_token && result.user) {\n // Success! Store the token\n await storeToken({\n token: result.access_token,\n user: result.user,\n expiresAt: result.expires_at,\n });\n\n spinner.stop();\n blank();\n printSuccess(`Logged in as ${chalk.bold(result.user.email)}`);\n return;\n }\n\n if (result.status === 'expired') {\n spinner.stop();\n blank();\n printError('Authentication expired. Please try again.');\n process.exit(1);\n }\n\n // Still pending, continue polling\n } catch (error) {\n // Network error, continue polling\n const message = error instanceof Error ? error.message : 'Unknown error';\n spinner.text = `Waiting for authentication... (${message})`;\n }\n }\n\n spinner.stop();\n blank();\n printError('Authentication timed out. Please try again.');\n process.exit(1);\n}\n\n/**\n * Token-based login (for CI/CD)\n */\nasync function tokenLogin(): Promise<void> {\n console.log('Token login mode.');\n console.log('Visit your Evident dashboard to generate a CLI token.');\n blank();\n\n // Read token from stdin\n process.stdout.write('Paste token: ');\n\n const token = await new Promise<string>((resolve) => {\n let data = '';\n process.stdin.setEncoding('utf8');\n process.stdin.on('data', (chunk) => {\n data += chunk;\n });\n process.stdin.on('end', () => {\n resolve(data.trim());\n });\n // For TTY, read a single line\n if (process.stdin.isTTY) {\n process.stdin.once('data', (chunk) => {\n process.stdin.pause();\n resolve(chunk.toString().trim());\n });\n process.stdin.resume();\n }\n });\n\n if (!token) {\n printError('No token provided.');\n process.exit(1);\n }\n\n // Validate the token\n const spinner = ora('Validating token...').start();\n\n try {\n interface ValidateResponse {\n user: { id: string; email: string };\n expires_at?: string;\n }\n\n const result = await api.post<ValidateResponse>('/auth/token/validate', { token });\n\n await storeToken({\n token,\n user: result.user,\n expiresAt: result.expires_at,\n });\n\n spinner.stop();\n printSuccess(`Logged in as ${chalk.bold(result.user.email)}`);\n } catch (error) {\n spinner.stop();\n const message = error instanceof Error ? error.message : 'Invalid token';\n printError(`Authentication failed: ${message}`);\n process.exit(1);\n }\n}\n\n/**\n * Login command handler\n */\nexport async function login(options: LoginOptions): Promise<void> {\n if (options.token) {\n await tokenLogin();\n } else {\n await deviceFlowLogin(options);\n }\n}\n","/**\n * CLI Configuration\n *\n * Manages configuration values and file-based credential storage.\n * Supports multiple environments: local, dev, production\n */\n\nimport Conf from 'conf';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\n// Supported environments\nexport type Environment = 'local' | 'dev' | 'production';\n\n// Configuration schema\ninterface ConfigSchema {\n apiUrl: string;\n tunnelUrl: string;\n}\n\n// Credentials schema (stored separately with stricter permissions)\ninterface CredentialsSchema {\n token?: string;\n user?: {\n id: string;\n email: string;\n };\n expiresAt?: string;\n}\n\n// Environment presets\n// Domain format: {service}.{env}.evident.run (with aliases for production)\n// API URLs include /v1 prefix as all endpoints are versioned\nconst environmentPresets: Record<Environment, ConfigSchema> = {\n local: {\n apiUrl: 'http://localhost:3000/v1',\n tunnelUrl: 'ws://localhost:8787',\n },\n dev: {\n apiUrl: 'https://api.dev.evident.run/v1',\n tunnelUrl: 'wss://tunnel.dev.evident.run',\n },\n production: {\n // Production URLs also have aliases: api.evident.run, tunnel.evident.run\n apiUrl: 'https://api.production.evident.run/v1',\n tunnelUrl: 'wss://tunnel.production.evident.run',\n },\n};\n\n// Default to production\nconst defaults: ConfigSchema = environmentPresets.production;\n\n// Current environment (can be set via --env flag or EVIDENT_ENV)\nlet currentEnvironment: Environment = 'production';\n\n/**\n * Set the current environment\n */\nexport function setEnvironment(env: Environment): void {\n currentEnvironment = env;\n}\n\n/**\n * Get the current environment\n */\nexport function getEnvironment(): Environment {\n // Environment variable takes precedence\n const envVar = process.env.EVIDENT_ENV as Environment | undefined;\n if (envVar && environmentPresets[envVar]) {\n return envVar;\n }\n return currentEnvironment;\n}\n\n/**\n * Get configuration for current environment\n */\nfunction getEnvConfig(): ConfigSchema {\n return environmentPresets[getEnvironment()];\n}\n\n// Environment overrides (env vars take highest precedence)\nfunction getApiUrl(): string {\n return process.env.EVIDENT_API_URL ?? getEnvConfig().apiUrl;\n}\n\nfunction getTunnelUrl(): string {\n return process.env.EVIDENT_TUNNEL_URL ?? getEnvConfig().tunnelUrl;\n}\n\n// Configuration store\nconst config = new Conf<ConfigSchema>({\n projectName: 'evident',\n projectSuffix: '',\n defaults,\n});\n\n// Credentials store (separate file with restricted access)\nconst credentials = new Conf<CredentialsSchema>({\n projectName: 'evident',\n projectSuffix: '',\n configName: 'credentials',\n defaults: {},\n});\n\n/**\n * Get the configuration directory path\n */\nexport function getConfigDir(): string {\n // XDG_CONFIG_HOME on Linux, ~/.config on others\n const xdgConfig = process.env.XDG_CONFIG_HOME;\n if (xdgConfig) {\n return join(xdgConfig, 'evident');\n }\n return join(homedir(), '.config', 'evident');\n}\n\n/**\n * Get the API URL\n */\nexport function getApiUrlConfig(): string {\n return getApiUrl();\n}\n\n/**\n * Get the tunnel WebSocket URL\n */\nexport function getTunnelUrlConfig(): string {\n return getTunnelUrl();\n}\n\n/**\n * Get stored credentials\n */\nexport function getCredentials(): CredentialsSchema {\n return {\n token: credentials.get('token'),\n user: credentials.get('user'),\n expiresAt: credentials.get('expiresAt'),\n };\n}\n\n/**\n * Store credentials\n */\nexport function setCredentials(creds: CredentialsSchema): void {\n if (creds.token) credentials.set('token', creds.token);\n if (creds.user) credentials.set('user', creds.user);\n if (creds.expiresAt) credentials.set('expiresAt', creds.expiresAt);\n}\n\n/**\n * Clear stored credentials\n */\nexport function clearCredentials(): void {\n credentials.clear();\n}\n\n/**\n * Check if we have valid credentials\n */\nexport function hasValidCredentials(): boolean {\n const creds = getCredentials();\n\n if (!creds.token) {\n return false;\n }\n\n if (creds.expiresAt) {\n const expiresAt = new Date(creds.expiresAt);\n if (expiresAt < new Date()) {\n return false;\n }\n }\n\n return true;\n}\n\nexport { config, credentials };\n","/**\n * API Client\n *\n * Handles HTTP requests to the Evident backend API.\n */\n\nimport { getApiUrlConfig, getCredentials } from './config.js';\n\ninterface ApiRequestOptions {\n method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n body?: unknown;\n headers?: Record<string, string>;\n authenticated?: boolean;\n}\n\ninterface ApiError {\n message: string;\n statusCode: number;\n error?: string;\n}\n\nexport class ApiClient {\n private baseUrl: string;\n\n constructor(baseUrl?: string) {\n this.baseUrl = baseUrl ?? getApiUrlConfig();\n }\n\n /**\n * Make an API request\n */\n async request<T>(path: string, options: ApiRequestOptions = {}): Promise<T> {\n const { method = 'GET', body, headers = {}, authenticated = false } = options;\n\n const url = `${this.baseUrl}${path}`;\n\n const requestHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...headers,\n };\n\n // Add authentication header if requested\n if (authenticated) {\n const creds = getCredentials();\n if (!creds.token) {\n throw new Error('Not authenticated. Run the `login` command first.');\n }\n requestHeaders['Authorization'] = `Bearer ${creds.token}`;\n }\n\n const response = await fetch(url, {\n method,\n headers: requestHeaders,\n body: body ? JSON.stringify(body) : undefined,\n });\n\n // Handle errors\n if (!response.ok) {\n let errorData: ApiError;\n try {\n errorData = (await response.json()) as ApiError;\n } catch {\n errorData = {\n message: response.statusText,\n statusCode: response.status,\n };\n }\n\n const error = new Error(errorData.message) as Error & { statusCode: number };\n error.statusCode = response.status;\n throw error;\n }\n\n // Handle empty responses\n const contentType = response.headers.get('Content-Type');\n if (!contentType?.includes('application/json')) {\n return {} as T;\n }\n\n return response.json() as Promise<T>;\n }\n\n /**\n * GET request\n */\n async get<T>(path: string, options: Omit<ApiRequestOptions, 'method' | 'body'> = {}): Promise<T> {\n return this.request<T>(path, { ...options, method: 'GET' });\n }\n\n /**\n * POST request\n */\n async post<T>(\n path: string,\n body?: unknown,\n options: Omit<ApiRequestOptions, 'method'> = {},\n ): Promise<T> {\n return this.request<T>(path, { ...options, method: 'POST', body });\n }\n\n /**\n * PUT request\n */\n async put<T>(\n path: string,\n body?: unknown,\n options: Omit<ApiRequestOptions, 'method'> = {},\n ): Promise<T> {\n return this.request<T>(path, { ...options, method: 'PUT', body });\n }\n\n /**\n * DELETE request\n */\n async delete<T>(\n path: string,\n options: Omit<ApiRequestOptions, 'method' | 'body'> = {},\n ): Promise<T> {\n return this.request<T>(path, { ...options, method: 'DELETE' });\n }\n}\n\n// Lazy API client instance - created on first use after env is set\nlet _api: ApiClient | null = null;\nexport const api = {\n get<T>(path: string, options?: Parameters<ApiClient['get']>[1]) {\n if (!_api) _api = new ApiClient();\n return _api.get<T>(path, options);\n },\n post<T>(path: string, body?: unknown, options?: Parameters<ApiClient['post']>[2]) {\n if (!_api) _api = new ApiClient();\n return _api.post<T>(path, body, options);\n },\n put<T>(path: string, body?: unknown, options?: Parameters<ApiClient['put']>[2]) {\n if (!_api) _api = new ApiClient();\n return _api.put<T>(path, body, options);\n },\n delete<T>(path: string, options?: Parameters<ApiClient['delete']>[1]) {\n if (!_api) _api = new ApiClient();\n return _api.delete<T>(path, options);\n },\n};\n","/**\n * Keychain Storage\n *\n * Provides secure credential storage using the system keychain.\n * Falls back to file-based storage if keychain is unavailable.\n */\n\nimport { getCredentials, setCredentials, clearCredentials } from './config.js';\n\nconst SERVICE_NAME = 'evident-cli';\nconst ACCOUNT_NAME = 'default';\n\n// Dynamic import for keytar (optional dependency)\nasync function getKeytar(): Promise<typeof import('keytar') | null> {\n try {\n return await import('keytar');\n } catch {\n // keytar not available (e.g., missing native dependencies)\n return null;\n }\n}\n\nexport interface StoredCredentials {\n token: string;\n user: {\n id: string;\n email: string;\n };\n expiresAt?: string;\n}\n\n/**\n * Store credentials in the system keychain\n */\nexport async function storeToken(credentials: StoredCredentials): Promise<void> {\n const keytar = await getKeytar();\n\n if (keytar) {\n // Store in system keychain\n await keytar.setPassword(SERVICE_NAME, ACCOUNT_NAME, JSON.stringify(credentials));\n } else {\n // Fallback to file-based storage\n setCredentials({\n token: credentials.token,\n user: credentials.user,\n expiresAt: credentials.expiresAt,\n });\n }\n}\n\n/**\n * Retrieve credentials from the system keychain\n */\nexport async function getToken(): Promise<StoredCredentials | null> {\n const keytar = await getKeytar();\n\n if (keytar) {\n // Try system keychain first\n const stored = await keytar.getPassword(SERVICE_NAME, ACCOUNT_NAME);\n if (stored) {\n try {\n return JSON.parse(stored) as StoredCredentials;\n } catch {\n // Invalid JSON, clear it\n await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);\n return null;\n }\n }\n }\n\n // Fallback to file-based storage\n const creds = getCredentials();\n if (creds.token && creds.user) {\n return {\n token: creds.token,\n user: creds.user,\n expiresAt: creds.expiresAt,\n };\n }\n\n return null;\n}\n\n/**\n * Delete credentials from the system keychain\n */\nexport async function deleteToken(): Promise<void> {\n const keytar = await getKeytar();\n\n if (keytar) {\n await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);\n }\n\n // Always clear file-based storage too\n clearCredentials();\n}\n\n/**\n * Check if credentials are valid (not expired)\n */\nexport async function hasValidToken(): Promise<boolean> {\n const credentials = await getToken();\n\n if (!credentials) {\n return false;\n }\n\n if (credentials.expiresAt) {\n const expiresAt = new Date(credentials.expiresAt);\n if (expiresAt < new Date()) {\n return false;\n }\n }\n\n return true;\n}\n","/**\n * CLI UI Utilities\n *\n * Common formatting and display functions.\n */\n\nimport chalk from 'chalk';\n\n/**\n * Format success message\n */\nexport function success(message: string): string {\n return `${chalk.green('✓')} ${message}`;\n}\n\n/**\n * Format error message\n */\nexport function error(message: string): string {\n return `${chalk.red('✗')} ${message}`;\n}\n\n/**\n * Format warning message\n */\nexport function warning(message: string): string {\n return `${chalk.yellow('!')} ${message}`;\n}\n\n/**\n * Format info message\n */\nexport function info(message: string): string {\n return `${chalk.blue('i')} ${message}`;\n}\n\n/**\n * Print success message\n */\nexport function printSuccess(message: string): void {\n console.log(success(message));\n}\n\n/**\n * Print error message\n */\nexport function printError(message: string): void {\n console.error(error(message));\n}\n\n/**\n * Print warning message\n */\nexport function printWarning(message: string): void {\n console.log(warning(message));\n}\n\n/**\n * Print info message\n */\nexport function printInfo(message: string): void {\n console.log(info(message));\n}\n\n/**\n * Format a key-value pair for display\n */\nexport function keyValue(key: string, value: string): string {\n return `${chalk.dim(key + ':')} ${value}`;\n}\n\n/**\n * Print a blank line\n */\nexport function blank(): void {\n console.log();\n}\n\n/**\n * Wait for user to press Enter\n */\nexport function waitForEnter(prompt = 'Press Enter to continue...'): Promise<void> {\n return new Promise((resolve) => {\n process.stdout.write(chalk.dim(prompt));\n\n const handler = (): void => {\n process.stdin.removeListener('data', handler);\n process.stdin.setRawMode?.(false);\n process.stdin.pause();\n console.log();\n resolve();\n };\n\n if (process.stdin.isTTY) {\n process.stdin.setRawMode?.(true);\n }\n process.stdin.resume();\n process.stdin.once('data', handler);\n });\n}\n\n/**\n * Sleep for a given number of milliseconds\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * Logout Command\n *\n * Removes stored credentials.\n */\n\nimport { deleteToken, getToken } from '../lib/keychain.js';\nimport { printSuccess, printWarning } from '../utils/ui.js';\n\n/**\n * Logout command handler\n */\nexport async function logout(): Promise<void> {\n const credentials = await getToken();\n\n if (!credentials) {\n printWarning('You are not logged in.');\n return;\n }\n\n await deleteToken();\n printSuccess('Logged out successfully.');\n}\n","/**\n * Whoami Command\n *\n * Displays the currently logged in user.\n */\n\nimport chalk from 'chalk';\nimport { getToken } from '../lib/keychain.js';\nimport { printError, keyValue, blank } from '../utils/ui.js';\n\n/**\n * Whoami command handler\n */\nexport async function whoami(): Promise<void> {\n const credentials = await getToken();\n\n if (!credentials) {\n printError('Not logged in. Run the `login` command to authenticate.');\n process.exit(1);\n }\n\n blank();\n console.log(keyValue('User', chalk.bold(credentials.user.email)));\n console.log(keyValue('User ID', credentials.user.id));\n\n if (credentials.expiresAt) {\n const expiresAt = new Date(credentials.expiresAt);\n const now = new Date();\n\n if (expiresAt < now) {\n console.log(keyValue('Status', chalk.red('Token expired')));\n } else {\n const daysRemaining = Math.ceil(\n (expiresAt.getTime() - now.getTime()) / (1000 * 60 * 60 * 24),\n );\n console.log(keyValue('Expires', `${daysRemaining} days`));\n }\n }\n\n blank();\n}\n","/**\n * Tunnel Command\n *\n * Establishes a WebSocket tunnel to Evident for Local Mode.\n * See ADR-0020 for architecture details.\n */\n\nimport WebSocket from 'ws';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { getToken } from '../lib/keychain.js';\nimport { getTunnelUrlConfig } from '../lib/config.js';\nimport { printError, printWarning, blank, sleep } from '../utils/ui.js';\n\ninterface TunnelOptions {\n sandbox?: string;\n port?: number;\n}\n\ninterface RelayMessage {\n type: 'connected' | 'error' | 'ping' | 'request';\n sandbox_id?: string;\n code?: string;\n message?: string;\n id?: string;\n payload?: {\n method: string;\n path: string;\n headers?: Record<string, string>;\n body?: unknown;\n };\n}\n\ninterface TunnelState {\n connected: boolean;\n sandboxId: string | null;\n reconnectAttempt: number;\n lastActivity: Date;\n}\n\nconst MAX_RECONNECT_DELAY = 30000; // 30 seconds\nconst BASE_RECONNECT_DELAY = 500; // 0.5 seconds\n\n/**\n * Calculate reconnect delay with exponential backoff and jitter\n */\nfunction getReconnectDelay(attempt: number): number {\n const exponentialDelay = BASE_RECONNECT_DELAY * Math.pow(2, attempt);\n const jitter = Math.random() * 1000;\n return Math.min(exponentialDelay + jitter, MAX_RECONNECT_DELAY);\n}\n\n/**\n * Display tunnel status\n */\nfunction displayStatus(state: TunnelState): void {\n console.clear();\n console.log(chalk.bold('Evident Tunnel'));\n console.log(chalk.dim('─'.repeat(50)));\n blank();\n\n if (state.connected) {\n console.log(` ${chalk.green('●')} Status: ${chalk.green('Online')}`);\n console.log(` Sandbox: ${state.sandboxId ?? 'Unknown'}`);\n console.log(` Last activity: ${state.lastActivity.toLocaleTimeString()}`);\n } else {\n console.log(` ${chalk.yellow('○')} Status: ${chalk.yellow('Reconnecting...')}`);\n if (state.reconnectAttempt > 0) {\n console.log(` Attempt: ${state.reconnectAttempt}`);\n }\n }\n\n blank();\n console.log(chalk.dim('Press Ctrl+C to disconnect'));\n}\n\n/**\n * Forward request to local OpenCode instance\n */\nasync function forwardToOpenCode(\n port: number,\n request: { method: string; path: string; headers?: Record<string, string>; body?: unknown },\n): Promise<{ status: number; headers?: Record<string, string>; body?: unknown }> {\n const url = `http://localhost:${port}${request.path}`;\n\n try {\n const response = await fetch(url, {\n method: request.method,\n headers: {\n 'Content-Type': 'application/json',\n ...request.headers,\n },\n body: request.body ? JSON.stringify(request.body) : undefined,\n });\n\n let body: unknown;\n const contentType = response.headers.get('Content-Type');\n if (contentType?.includes('application/json')) {\n body = await response.json();\n } else {\n body = await response.text();\n }\n\n return {\n status: response.status,\n body,\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n return {\n status: 502,\n body: { error: 'Failed to connect to OpenCode', message },\n };\n }\n}\n\n/**\n * Establish tunnel connection\n */\nasync function connect(\n token: string,\n sandboxId: string | undefined,\n port: number,\n state: TunnelState,\n): Promise<void> {\n const tunnelUrl = getTunnelUrlConfig();\n const url = sandboxId\n ? `${tunnelUrl}/tunnel/${sandboxId}/connect`\n : `${tunnelUrl}/tunnel/default/connect`;\n\n return new Promise((resolve, reject) => {\n const ws = new WebSocket(url, {\n headers: {\n Authorization: `Bearer ${token}`,\n },\n });\n\n ws.on('open', () => {\n state.connected = true;\n state.reconnectAttempt = 0;\n state.lastActivity = new Date();\n displayStatus(state);\n });\n\n ws.on('message', async (data: WebSocket.RawData) => {\n try {\n const message: RelayMessage = JSON.parse(data.toString());\n state.lastActivity = new Date();\n\n switch (message.type) {\n case 'connected':\n state.sandboxId = message.sandbox_id ?? null;\n displayStatus(state);\n break;\n\n case 'error':\n printError(`Tunnel error: ${message.message}`);\n if (message.code === 'unauthorized') {\n ws.close();\n reject(new Error('Unauthorized'));\n }\n break;\n\n case 'ping':\n ws.send(JSON.stringify({ type: 'pong' }));\n break;\n\n case 'request':\n if (message.id && message.payload) {\n const response = await forwardToOpenCode(port, message.payload);\n ws.send(\n JSON.stringify({\n type: 'response',\n id: message.id,\n payload: response,\n }),\n );\n displayStatus(state);\n }\n break;\n }\n } catch (error) {\n console.error('Failed to handle message:', error);\n }\n });\n\n ws.on('close', () => {\n state.connected = false;\n displayStatus(state);\n resolve();\n });\n\n ws.on('error', (error: Error) => {\n state.connected = false;\n displayStatus(state);\n // Don't reject on connection errors, let it reconnect\n console.error(chalk.dim(`Connection error: ${error.message}`));\n });\n\n // Handle Ctrl+C\n const cleanup = (): void => {\n ws.close();\n process.exit(0);\n };\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n });\n}\n\n/**\n * Tunnel command handler\n */\nexport async function tunnel(options: TunnelOptions): Promise<void> {\n // Get credentials\n const credentials = await getToken();\n if (!credentials) {\n printError('Not logged in. Run the `login` command first.');\n process.exit(1);\n }\n\n const port = options.port ?? 4096;\n const sandboxId = options.sandbox;\n\n // Check if OpenCode is running\n const spinner = ora('Checking OpenCode connection...').start();\n\n try {\n const response = await fetch(`http://localhost:${port}/health`);\n if (!response.ok) {\n throw new Error('Health check failed');\n }\n spinner.succeed(`OpenCode detected on port ${port}`);\n } catch {\n spinner.warn(`Could not connect to OpenCode on port ${port}`);\n printWarning('Make sure OpenCode is running before starting the tunnel.');\n blank();\n }\n\n // Connection state\n const state: TunnelState = {\n connected: false,\n sandboxId: null,\n reconnectAttempt: 0,\n lastActivity: new Date(),\n };\n\n // Reconnection loop\n // eslint-disable-next-line no-constant-condition\n while (true) {\n try {\n await connect(credentials.token, sandboxId, port, state);\n\n // Connection closed, attempt reconnect\n state.reconnectAttempt++;\n const delay = getReconnectDelay(state.reconnectAttempt);\n\n displayStatus(state);\n await sleep(delay);\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n\n if (message === 'Unauthorized') {\n printError('Authentication failed. Please run the `login` command again.');\n process.exit(1);\n }\n\n state.reconnectAttempt++;\n const delay = getReconnectDelay(state.reconnectAttempt);\n await sleep(delay);\n }\n }\n}\n"],"mappings":"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","names":["chalk","error","credentials","error","chalk","credentials","chalk","credentials","chalk","chalk","ora","chalk","error","credentials","ora"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/commands/login.ts","../src/lib/config.ts","../src/lib/api.ts","../src/lib/keychain.ts","../src/utils/ui.ts","../src/commands/logout.ts","../src/commands/whoami.ts","../src/commands/tunnel.ts","../src/lib/telemetry.ts"],"sourcesContent":["/**\n * Evident CLI\n *\n * Run OpenCode locally and connect it to the Evident platform.\n */\n\nimport { Command } from 'commander';\nimport { login } from './commands/login.js';\nimport { logout } from './commands/logout.js';\nimport { whoami } from './commands/whoami.js';\nimport { tunnel } from './commands/tunnel.js';\nimport { setEnvironment, type Environment } from './lib/config.js';\n\nconst program = new Command();\n\nprogram\n .name('evident')\n .description('Run OpenCode locally and connect it to Evident')\n .version('0.1.0')\n .option('-e, --env <environment>', 'Environment to use (local, dev, production)', 'production')\n .hook('preAction', (thisCommand) => {\n const env = thisCommand.opts().env as Environment;\n if (env) {\n setEnvironment(env);\n }\n });\n\n// Login command\nprogram\n .command('login')\n .description('Authenticate with Evident')\n .option('--token', 'Use token-based authentication (for CI/CD)')\n .option('--no-browser', 'Do not open the browser automatically')\n .action(login);\n\n// Logout command\nprogram.command('logout').description('Remove stored credentials').action(logout);\n\n// Whoami command\nprogram.command('whoami').description('Show the currently logged in user').action(whoami);\n\n// Tunnel command\nprogram\n .command('tunnel')\n .description('Establish a tunnel to Evident for Local Mode')\n .requiredOption('-s, --sandbox <id>', 'Sandbox ID to connect to (required)')\n .option('-p, --port <port>', 'OpenCode port (default: 4096)', '4096')\n .option('-v, --verbose', 'Show detailed request/response information')\n .action((options: { sandbox: string; port: string; verbose?: boolean }) => {\n tunnel({\n sandbox: options.sandbox,\n port: parseInt(options.port, 10),\n verbose: options.verbose,\n });\n });\n\n// Parse arguments\nprogram.parse();\n","/**\n * Login Command\n *\n * Authenticates the user using OAuth Device Flow.\n * See ADR-0018 for details.\n */\n\nimport open from 'open';\nimport ora from 'ora';\nimport chalk from 'chalk';\nimport { api } from '../lib/api.js';\nimport { storeToken } from '../lib/keychain.js';\nimport { printSuccess, printError, blank, waitForEnter, sleep } from '../utils/ui.js';\n\ninterface DeviceAuthResponse {\n device_code: string;\n user_code: string;\n verification_uri: string;\n expires_in: number;\n interval: number;\n}\n\ninterface TokenPollResponse {\n status: 'pending' | 'complete' | 'expired';\n access_token?: string;\n expires_at?: string;\n user?: {\n id: string;\n email: string;\n };\n}\n\ninterface LoginOptions {\n token?: boolean;\n noBrowser?: boolean;\n}\n\n/**\n * Start device flow authentication\n */\nasync function deviceFlowLogin(options: LoginOptions): Promise<void> {\n // Step 1: Request device code\n let deviceAuth: DeviceAuthResponse;\n try {\n deviceAuth = await api.post<DeviceAuthResponse>('/auth/device');\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError(`Failed to start authentication: ${message}`);\n process.exit(1);\n }\n\n const { device_code, user_code, verification_uri, interval } = deviceAuth;\n\n // Step 2: Display instructions\n blank();\n console.log(chalk.bold('To authenticate, visit:'));\n console.log();\n console.log(` ${chalk.cyan(verification_uri)}`);\n console.log();\n console.log(chalk.bold('And enter this code:'));\n console.log();\n console.log(` ${chalk.yellow.bold(user_code)}`);\n blank();\n\n // Step 3: Open browser (unless --no-browser)\n if (!options.noBrowser) {\n await waitForEnter('Press Enter to open the browser...');\n try {\n await open(verification_uri);\n } catch {\n console.log(chalk.dim('Could not open browser. Please visit the URL manually.'));\n }\n }\n\n // Step 4: Poll for completion\n const spinner = ora('Waiting for authentication...').start();\n\n const pollIntervalMs = (interval || 5) * 1000;\n const maxAttempts = 60; // 5 minutes max at 5s intervals\n let attempts = 0;\n\n while (attempts < maxAttempts) {\n await sleep(pollIntervalMs);\n attempts++;\n\n try {\n const result = await api.post<TokenPollResponse>('/auth/device/token', {\n device_code,\n });\n\n if (result.status === 'complete' && result.access_token && result.user) {\n // Success! Store the token\n await storeToken({\n token: result.access_token,\n user: result.user,\n expiresAt: result.expires_at,\n });\n\n spinner.stop();\n blank();\n printSuccess(`Logged in as ${chalk.bold(result.user.email)}`);\n return;\n }\n\n if (result.status === 'expired') {\n spinner.stop();\n blank();\n printError('Authentication expired. Please try again.');\n process.exit(1);\n }\n\n // Still pending, continue polling\n } catch (error) {\n // Network error, continue polling\n const message = error instanceof Error ? error.message : 'Unknown error';\n spinner.text = `Waiting for authentication... (${message})`;\n }\n }\n\n spinner.stop();\n blank();\n printError('Authentication timed out. Please try again.');\n process.exit(1);\n}\n\n/**\n * Token-based login (for CI/CD)\n */\nasync function tokenLogin(): Promise<void> {\n console.log('Token login mode.');\n console.log('Visit your Evident dashboard to generate a CLI token.');\n blank();\n\n // Read token from stdin\n process.stdout.write('Paste token: ');\n\n const token = await new Promise<string>((resolve) => {\n let data = '';\n process.stdin.setEncoding('utf8');\n process.stdin.on('data', (chunk) => {\n data += chunk;\n });\n process.stdin.on('end', () => {\n resolve(data.trim());\n });\n // For TTY, read a single line\n if (process.stdin.isTTY) {\n process.stdin.once('data', (chunk) => {\n process.stdin.pause();\n resolve(chunk.toString().trim());\n });\n process.stdin.resume();\n }\n });\n\n if (!token) {\n printError('No token provided.');\n process.exit(1);\n }\n\n // Validate the token\n const spinner = ora('Validating token...').start();\n\n try {\n interface ValidateResponse {\n user: { id: string; email: string };\n expires_at?: string;\n }\n\n const result = await api.post<ValidateResponse>('/auth/token/validate', { token });\n\n await storeToken({\n token,\n user: result.user,\n expiresAt: result.expires_at,\n });\n\n spinner.stop();\n printSuccess(`Logged in as ${chalk.bold(result.user.email)}`);\n } catch (error) {\n spinner.stop();\n const message = error instanceof Error ? error.message : 'Invalid token';\n printError(`Authentication failed: ${message}`);\n process.exit(1);\n }\n}\n\n/**\n * Login command handler\n */\nexport async function login(options: LoginOptions): Promise<void> {\n if (options.token) {\n await tokenLogin();\n } else {\n await deviceFlowLogin(options);\n }\n}\n","/**\n * CLI Configuration\n *\n * Manages configuration values and file-based credential storage.\n * Supports multiple environments: local, dev, production\n */\n\nimport Conf from 'conf';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\n// Supported environments\nexport type Environment = 'local' | 'dev' | 'production';\n\n// Configuration schema\ninterface ConfigSchema {\n apiUrl: string;\n tunnelUrl: string;\n}\n\n// Credentials schema (stored separately with stricter permissions)\ninterface CredentialsSchema {\n token?: string;\n user?: {\n id: string;\n email: string;\n };\n expiresAt?: string;\n}\n\n// Environment presets\n// Domain format: {service}.{env}.evident.run (with aliases for production)\n// API URLs include /v1 prefix as all endpoints are versioned\nconst environmentPresets: Record<Environment, ConfigSchema> = {\n local: {\n apiUrl: 'http://localhost:3000/v1',\n tunnelUrl: 'ws://localhost:8787',\n },\n dev: {\n apiUrl: 'https://api.dev.evident.run/v1',\n tunnelUrl: 'wss://tunnel.dev.evident.run',\n },\n production: {\n // Production URLs also have aliases: api.evident.run, tunnel.evident.run\n apiUrl: 'https://api.production.evident.run/v1',\n tunnelUrl: 'wss://tunnel.production.evident.run',\n },\n};\n\n// Default to production\nconst defaults: ConfigSchema = environmentPresets.production;\n\n// Current environment (can be set via --env flag or EVIDENT_ENV)\nlet currentEnvironment: Environment = 'production';\n\n/**\n * Set the current environment\n */\nexport function setEnvironment(env: Environment): void {\n currentEnvironment = env;\n}\n\n/**\n * Get the current environment\n */\nexport function getEnvironment(): Environment {\n // Environment variable takes precedence\n const envVar = process.env.EVIDENT_ENV as Environment | undefined;\n if (envVar && environmentPresets[envVar]) {\n return envVar;\n }\n return currentEnvironment;\n}\n\n/**\n * Get configuration for current environment\n */\nfunction getEnvConfig(): ConfigSchema {\n return environmentPresets[getEnvironment()];\n}\n\n// Environment overrides (env vars take highest precedence)\nfunction getApiUrl(): string {\n return process.env.EVIDENT_API_URL ?? getEnvConfig().apiUrl;\n}\n\nfunction getTunnelUrl(): string {\n return process.env.EVIDENT_TUNNEL_URL ?? getEnvConfig().tunnelUrl;\n}\n\n// Configuration store\nconst config = new Conf<ConfigSchema>({\n projectName: 'evident',\n projectSuffix: '',\n defaults,\n});\n\n// Credentials store (separate file with restricted access)\nconst credentials = new Conf<CredentialsSchema>({\n projectName: 'evident',\n projectSuffix: '',\n configName: 'credentials',\n defaults: {},\n});\n\n/**\n * Get the configuration directory path\n */\nexport function getConfigDir(): string {\n // XDG_CONFIG_HOME on Linux, ~/.config on others\n const xdgConfig = process.env.XDG_CONFIG_HOME;\n if (xdgConfig) {\n return join(xdgConfig, 'evident');\n }\n return join(homedir(), '.config', 'evident');\n}\n\n/**\n * Get the API URL\n */\nexport function getApiUrlConfig(): string {\n return getApiUrl();\n}\n\n/**\n * Get the tunnel WebSocket URL\n */\nexport function getTunnelUrlConfig(): string {\n return getTunnelUrl();\n}\n\n/**\n * Get stored credentials\n */\nexport function getCredentials(): CredentialsSchema {\n return {\n token: credentials.get('token'),\n user: credentials.get('user'),\n expiresAt: credentials.get('expiresAt'),\n };\n}\n\n/**\n * Store credentials\n */\nexport function setCredentials(creds: CredentialsSchema): void {\n if (creds.token) credentials.set('token', creds.token);\n if (creds.user) credentials.set('user', creds.user);\n if (creds.expiresAt) credentials.set('expiresAt', creds.expiresAt);\n}\n\n/**\n * Clear stored credentials\n */\nexport function clearCredentials(): void {\n credentials.clear();\n}\n\n/**\n * Check if we have valid credentials\n */\nexport function hasValidCredentials(): boolean {\n const creds = getCredentials();\n\n if (!creds.token) {\n return false;\n }\n\n if (creds.expiresAt) {\n const expiresAt = new Date(creds.expiresAt);\n if (expiresAt < new Date()) {\n return false;\n }\n }\n\n return true;\n}\n\nexport { config, credentials };\n","/**\n * API Client\n *\n * Handles HTTP requests to the Evident backend API.\n */\n\nimport { getApiUrlConfig, getCredentials } from './config.js';\n\ninterface ApiRequestOptions {\n method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n body?: unknown;\n headers?: Record<string, string>;\n authenticated?: boolean;\n}\n\ninterface ApiError {\n message: string;\n statusCode: number;\n error?: string;\n}\n\nexport class ApiClient {\n private baseUrl: string;\n\n constructor(baseUrl?: string) {\n this.baseUrl = baseUrl ?? getApiUrlConfig();\n }\n\n /**\n * Make an API request\n */\n async request<T>(path: string, options: ApiRequestOptions = {}): Promise<T> {\n const { method = 'GET', body, headers = {}, authenticated = false } = options;\n\n const url = `${this.baseUrl}${path}`;\n\n const requestHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...headers,\n };\n\n // Add authentication header if requested\n if (authenticated) {\n const creds = getCredentials();\n if (!creds.token) {\n throw new Error('Not authenticated. Run the `login` command first.');\n }\n requestHeaders['Authorization'] = `Bearer ${creds.token}`;\n }\n\n const response = await fetch(url, {\n method,\n headers: requestHeaders,\n body: body ? JSON.stringify(body) : undefined,\n });\n\n // Handle errors\n if (!response.ok) {\n let errorData: ApiError;\n try {\n errorData = (await response.json()) as ApiError;\n } catch {\n errorData = {\n message: response.statusText,\n statusCode: response.status,\n };\n }\n\n const error = new Error(errorData.message) as Error & { statusCode: number };\n error.statusCode = response.status;\n throw error;\n }\n\n // Handle empty responses\n const contentType = response.headers.get('Content-Type');\n if (!contentType?.includes('application/json')) {\n return {} as T;\n }\n\n return response.json() as Promise<T>;\n }\n\n /**\n * GET request\n */\n async get<T>(path: string, options: Omit<ApiRequestOptions, 'method' | 'body'> = {}): Promise<T> {\n return this.request<T>(path, { ...options, method: 'GET' });\n }\n\n /**\n * POST request\n */\n async post<T>(\n path: string,\n body?: unknown,\n options: Omit<ApiRequestOptions, 'method'> = {},\n ): Promise<T> {\n return this.request<T>(path, { ...options, method: 'POST', body });\n }\n\n /**\n * PUT request\n */\n async put<T>(\n path: string,\n body?: unknown,\n options: Omit<ApiRequestOptions, 'method'> = {},\n ): Promise<T> {\n return this.request<T>(path, { ...options, method: 'PUT', body });\n }\n\n /**\n * DELETE request\n */\n async delete<T>(\n path: string,\n options: Omit<ApiRequestOptions, 'method' | 'body'> = {},\n ): Promise<T> {\n return this.request<T>(path, { ...options, method: 'DELETE' });\n }\n}\n\n// Lazy API client instance - created on first use after env is set\nlet _api: ApiClient | null = null;\nexport const api = {\n get<T>(path: string, options?: Parameters<ApiClient['get']>[1]) {\n if (!_api) _api = new ApiClient();\n return _api.get<T>(path, options);\n },\n post<T>(path: string, body?: unknown, options?: Parameters<ApiClient['post']>[2]) {\n if (!_api) _api = new ApiClient();\n return _api.post<T>(path, body, options);\n },\n put<T>(path: string, body?: unknown, options?: Parameters<ApiClient['put']>[2]) {\n if (!_api) _api = new ApiClient();\n return _api.put<T>(path, body, options);\n },\n delete<T>(path: string, options?: Parameters<ApiClient['delete']>[1]) {\n if (!_api) _api = new ApiClient();\n return _api.delete<T>(path, options);\n },\n};\n","/**\n * Keychain Storage\n *\n * Provides secure credential storage using the system keychain.\n * Falls back to file-based storage if keychain is unavailable.\n */\n\nimport { getCredentials, setCredentials, clearCredentials } from './config.js';\n\nconst SERVICE_NAME = 'evident-cli';\nconst ACCOUNT_NAME = 'default';\n\n// Dynamic import for keytar (optional dependency)\nasync function getKeytar(): Promise<typeof import('keytar') | null> {\n try {\n const keytar = await import('keytar');\n // Verify keytar is actually functional (has the expected methods)\n if (typeof keytar.setPassword !== 'function') {\n return null;\n }\n return keytar;\n } catch {\n // keytar not available (e.g., missing native dependencies)\n return null;\n }\n}\n\nexport interface StoredCredentials {\n token: string;\n user: {\n id: string;\n email: string;\n };\n expiresAt?: string;\n}\n\n/**\n * Store credentials in the system keychain\n */\nexport async function storeToken(credentials: StoredCredentials): Promise<void> {\n const keytar = await getKeytar();\n\n if (keytar) {\n // Store in system keychain\n await keytar.setPassword(SERVICE_NAME, ACCOUNT_NAME, JSON.stringify(credentials));\n } else {\n // Fallback to file-based storage\n setCredentials({\n token: credentials.token,\n user: credentials.user,\n expiresAt: credentials.expiresAt,\n });\n }\n}\n\n/**\n * Retrieve credentials from the system keychain\n */\nexport async function getToken(): Promise<StoredCredentials | null> {\n const keytar = await getKeytar();\n\n if (keytar) {\n // Try system keychain first\n const stored = await keytar.getPassword(SERVICE_NAME, ACCOUNT_NAME);\n if (stored) {\n try {\n return JSON.parse(stored) as StoredCredentials;\n } catch {\n // Invalid JSON, clear it\n await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);\n return null;\n }\n }\n }\n\n // Fallback to file-based storage\n const creds = getCredentials();\n if (creds.token && creds.user) {\n return {\n token: creds.token,\n user: creds.user,\n expiresAt: creds.expiresAt,\n };\n }\n\n return null;\n}\n\n/**\n * Delete credentials from the system keychain\n */\nexport async function deleteToken(): Promise<void> {\n const keytar = await getKeytar();\n\n if (keytar) {\n await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);\n }\n\n // Always clear file-based storage too\n clearCredentials();\n}\n\n/**\n * Check if credentials are valid (not expired)\n */\nexport async function hasValidToken(): Promise<boolean> {\n const credentials = await getToken();\n\n if (!credentials) {\n return false;\n }\n\n if (credentials.expiresAt) {\n const expiresAt = new Date(credentials.expiresAt);\n if (expiresAt < new Date()) {\n return false;\n }\n }\n\n return true;\n}\n","/**\n * CLI UI Utilities\n *\n * Common formatting and display functions.\n */\n\nimport chalk from 'chalk';\n\n/**\n * Format success message\n */\nexport function success(message: string): string {\n return `${chalk.green('✓')} ${message}`;\n}\n\n/**\n * Format error message\n */\nexport function error(message: string): string {\n return `${chalk.red('✗')} ${message}`;\n}\n\n/**\n * Format warning message\n */\nexport function warning(message: string): string {\n return `${chalk.yellow('!')} ${message}`;\n}\n\n/**\n * Format info message\n */\nexport function info(message: string): string {\n return `${chalk.blue('i')} ${message}`;\n}\n\n/**\n * Print success message\n */\nexport function printSuccess(message: string): void {\n console.log(success(message));\n}\n\n/**\n * Print error message\n */\nexport function printError(message: string): void {\n console.error(error(message));\n}\n\n/**\n * Print warning message\n */\nexport function printWarning(message: string): void {\n console.log(warning(message));\n}\n\n/**\n * Print info message\n */\nexport function printInfo(message: string): void {\n console.log(info(message));\n}\n\n/**\n * Format a key-value pair for display\n */\nexport function keyValue(key: string, value: string): string {\n return `${chalk.dim(key + ':')} ${value}`;\n}\n\n/**\n * Print a blank line\n */\nexport function blank(): void {\n console.log();\n}\n\n/**\n * Wait for user to press Enter\n */\nexport function waitForEnter(prompt = 'Press Enter to continue...'): Promise<void> {\n return new Promise((resolve) => {\n process.stdout.write(chalk.dim(prompt));\n\n const handler = (): void => {\n process.stdin.removeListener('data', handler);\n process.stdin.setRawMode?.(false);\n process.stdin.pause();\n console.log();\n resolve();\n };\n\n if (process.stdin.isTTY) {\n process.stdin.setRawMode?.(true);\n }\n process.stdin.resume();\n process.stdin.once('data', handler);\n });\n}\n\n/**\n * Sleep for a given number of milliseconds\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * Logout Command\n *\n * Removes stored credentials.\n */\n\nimport { deleteToken, getToken } from '../lib/keychain.js';\nimport { printSuccess, printWarning } from '../utils/ui.js';\n\n/**\n * Logout command handler\n */\nexport async function logout(): Promise<void> {\n const credentials = await getToken();\n\n if (!credentials) {\n printWarning('You are not logged in.');\n return;\n }\n\n await deleteToken();\n printSuccess('Logged out successfully.');\n}\n","/**\n * Whoami Command\n *\n * Displays the currently logged in user.\n */\n\nimport chalk from 'chalk';\nimport { getToken } from '../lib/keychain.js';\nimport { printError, keyValue, blank } from '../utils/ui.js';\n\n/**\n * Whoami command handler\n */\nexport async function whoami(): Promise<void> {\n const credentials = await getToken();\n\n if (!credentials) {\n printError('Not logged in. Run the `login` command to authenticate.');\n process.exit(1);\n }\n\n blank();\n console.log(keyValue('User', chalk.bold(credentials.user.email)));\n console.log(keyValue('User ID', credentials.user.id));\n\n if (credentials.expiresAt) {\n const expiresAt = new Date(credentials.expiresAt);\n const now = new Date();\n\n if (expiresAt < now) {\n console.log(keyValue('Status', chalk.red('Token expired')));\n } else {\n const daysRemaining = Math.ceil(\n (expiresAt.getTime() - now.getTime()) / (1000 * 60 * 60 * 24),\n );\n console.log(keyValue('Expires', `${daysRemaining} days`));\n }\n }\n\n blank();\n}\n","/**\n * Tunnel Command\n *\n * Establishes a WebSocket tunnel to Evident for Local Mode.\n * See ADR-0020 for architecture details.\n *\n * Features:\n * - Real-time activity logging\n * - Verbose mode for debugging\n * - Clear error display\n */\n\nimport WebSocket from 'ws';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { getToken } from '../lib/keychain.js';\nimport { getTunnelUrlConfig, getApiUrlConfig } from '../lib/config.js';\nimport { printError, printWarning, blank, sleep } from '../utils/ui.js';\nimport { telemetry, EventTypes, shutdownTelemetry } from '../lib/telemetry.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\ninterface TunnelOptions {\n sandbox?: string;\n port?: number;\n verbose?: boolean;\n}\n\ninterface RelayMessage {\n type: 'connected' | 'error' | 'ping' | 'request';\n sandbox_id?: string;\n code?: string;\n message?: string;\n id?: string;\n payload?: {\n method: string;\n path: string;\n headers?: Record<string, string>;\n body?: unknown;\n };\n}\n\ninterface ActivityLogEntry {\n timestamp: Date;\n type: 'request' | 'response' | 'error' | 'info';\n method?: string;\n path?: string;\n status?: number;\n durationMs?: number;\n error?: string;\n message?: string;\n requestId?: string;\n}\n\ninterface TunnelState {\n connected: boolean;\n sandboxId: string | null;\n sandboxName: string | null;\n reconnectAttempt: number;\n lastActivity: Date;\n activityLog: ActivityLogEntry[];\n pendingRequests: Map<string, { startTime: number; method: string; path: string }>;\n verbose: boolean;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst MAX_RECONNECT_DELAY = 30000; // 30 seconds\nconst BASE_RECONNECT_DELAY = 500; // 0.5 seconds\nconst MAX_ACTIVITY_LOG_ENTRIES = 10;\n\n// ============================================================================\n// Activity Logging\n// ============================================================================\n\n/**\n * Add an entry to the activity log\n */\nfunction logActivity(state: TunnelState, entry: Omit<ActivityLogEntry, 'timestamp'>): void {\n const fullEntry: ActivityLogEntry = {\n ...entry,\n timestamp: new Date(),\n };\n\n state.activityLog.push(fullEntry);\n\n // Keep only the last N entries\n if (state.activityLog.length > MAX_ACTIVITY_LOG_ENTRIES) {\n state.activityLog.shift();\n }\n\n state.lastActivity = fullEntry.timestamp;\n}\n\n/**\n * Format a single activity log entry for display\n */\nfunction formatActivityEntry(entry: ActivityLogEntry, _verbose: boolean): string {\n const time = entry.timestamp.toLocaleTimeString('en-US', {\n hour12: false,\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n });\n\n switch (entry.type) {\n case 'request': {\n const duration = entry.durationMs ? ` (${entry.durationMs}ms)` : '';\n const status = entry.status ? ` → ${colorizeStatus(entry.status)}` : ' ...';\n return ` ${chalk.dim(`[${time}]`)} ${chalk.cyan('←')} ${entry.method} ${entry.path}${status}${duration}`;\n }\n\n case 'response': {\n const duration = entry.durationMs ? ` (${entry.durationMs}ms)` : '';\n return ` ${chalk.dim(`[${time}]`)} ${chalk.green('→')} ${entry.method} ${entry.path} ${colorizeStatus(entry.status!)}${duration}`;\n }\n\n case 'error': {\n const errorMsg = entry.error || 'Unknown error';\n const path = entry.path ? ` ${entry.method} ${entry.path}` : '';\n return ` ${chalk.dim(`[${time}]`)} ${chalk.red('✗')}${path} - ${chalk.red(errorMsg)}`;\n }\n\n case 'info': {\n return ` ${chalk.dim(`[${time}]`)} ${chalk.blue('●')} ${entry.message}`;\n }\n\n default:\n return ` ${chalk.dim(`[${time}]`)} ${entry.message || 'Unknown'}`;\n }\n}\n\n/**\n * Colorize HTTP status code\n */\nfunction colorizeStatus(status: number): string {\n if (status >= 200 && status < 300) {\n return chalk.green(status.toString());\n } else if (status >= 300 && status < 400) {\n return chalk.yellow(status.toString());\n } else if (status >= 400 && status < 500) {\n return chalk.red(status.toString());\n } else if (status >= 500) {\n return chalk.bgRed.white(` ${status} `);\n }\n return status.toString();\n}\n\n// ============================================================================\n// Display\n// ============================================================================\n\n/**\n * Display tunnel status with activity log\n */\nfunction displayStatus(state: TunnelState): void {\n console.clear();\n\n // Header\n console.log(chalk.bold('Evident Tunnel'));\n console.log(chalk.dim('─'.repeat(60)));\n blank();\n\n // Connection status\n if (state.connected) {\n console.log(` ${chalk.green('●')} Status: ${chalk.green('Connected')}`);\n console.log(` Sandbox: ${state.sandboxId ?? 'Unknown'}`);\n if (state.sandboxName) {\n console.log(` Name: ${state.sandboxName}`);\n }\n } else {\n console.log(` ${chalk.yellow('○')} Status: ${chalk.yellow('Reconnecting...')}`);\n if (state.reconnectAttempt > 0) {\n console.log(` Attempt: ${state.reconnectAttempt}`);\n }\n }\n\n blank();\n\n // Activity log\n if (state.activityLog.length > 0) {\n console.log(chalk.bold(' Activity:'));\n for (const entry of state.activityLog) {\n console.log(formatActivityEntry(entry, state.verbose));\n }\n } else {\n console.log(chalk.dim(' No activity yet. Waiting for requests...'));\n }\n\n blank();\n console.log(chalk.dim('─'.repeat(60)));\n\n // Verbose mode indicator\n if (state.verbose) {\n console.log(chalk.dim(' Verbose mode: ON (request/response bodies will be logged)'));\n }\n\n // Footer\n console.log(chalk.dim(' Press Ctrl+C to disconnect'));\n}\n\n/**\n * Display error prominently (doesn't clear screen)\n */\nfunction displayError(_state: TunnelState, error: string, details?: string): void {\n blank();\n console.log(chalk.bgRed.white.bold(' ERROR '));\n console.log(chalk.red(` ${error}`));\n if (details) {\n console.log(chalk.dim(` ${details}`));\n }\n blank();\n}\n\n// ============================================================================\n// Sandbox Validation\n// ============================================================================\n\n/**\n * Validate sandbox exists and is of type 'remote'\n */\nasync function validateSandbox(\n token: string,\n sandboxId: string,\n): Promise<{ valid: boolean; name?: string; error?: string }> {\n const apiUrl = getApiUrlConfig();\n\n try {\n const response = await fetch(`${apiUrl}/sandboxes/${sandboxId}`, {\n headers: {\n Authorization: `Bearer ${token}`,\n },\n });\n\n if (response.status === 404) {\n return { valid: false, error: 'Sandbox not found' };\n }\n\n if (response.status === 401) {\n return { valid: false, error: 'Authentication failed. Please run `evident login` again.' };\n }\n\n if (!response.ok) {\n return { valid: false, error: `API error: ${response.status}` };\n }\n\n const sandbox = (await response.json()) as {\n id: string;\n name: string;\n sandbox_type: 'e2b' | 'remote';\n status: string;\n };\n\n if (sandbox.sandbox_type !== 'remote') {\n return {\n valid: false,\n error: `Sandbox is type '${sandbox.sandbox_type}', must be 'remote' for tunnel connection`,\n };\n }\n\n return { valid: true, name: sandbox.name };\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n return { valid: false, error: `Failed to validate sandbox: ${message}` };\n }\n}\n\n// ============================================================================\n// Request Forwarding\n// ============================================================================\n\n/**\n * Forward request to local OpenCode instance\n */\nasync function forwardToOpenCode(\n port: number,\n request: { method: string; path: string; headers?: Record<string, string>; body?: unknown },\n requestId: string,\n state: TunnelState,\n): Promise<{ status: number; headers?: Record<string, string>; body?: unknown }> {\n const url = `http://localhost:${port}${request.path}`;\n const startTime = Date.now();\n\n // Track pending request\n state.pendingRequests.set(requestId, {\n startTime,\n method: request.method,\n path: request.path,\n });\n\n // Log incoming request\n logActivity(state, {\n type: 'request',\n method: request.method,\n path: request.path,\n requestId,\n });\n displayStatus(state);\n\n // Verbose: log request body\n if (state.verbose && request.body) {\n console.log(chalk.dim(` Request body: ${JSON.stringify(request.body, null, 2)}`));\n }\n\n telemetry.debug(\n EventTypes.OPENCODE_REQUEST_FORWARDED,\n `Forwarding ${request.method} ${request.path}`,\n {\n method: request.method,\n path: request.path,\n port,\n requestId,\n },\n state.sandboxId ?? undefined,\n );\n\n try {\n const response = await fetch(url, {\n method: request.method,\n headers: {\n 'Content-Type': 'application/json',\n ...request.headers,\n },\n body: request.body ? JSON.stringify(request.body) : undefined,\n });\n\n let body: unknown;\n const contentType = response.headers.get('Content-Type');\n if (contentType?.includes('application/json')) {\n body = await response.json();\n } else {\n body = await response.text();\n }\n\n const durationMs = Date.now() - startTime;\n\n // Remove from pending and update activity log\n state.pendingRequests.delete(requestId);\n\n // Update the last activity entry with the response\n const lastEntry = state.activityLog[state.activityLog.length - 1];\n if (lastEntry && lastEntry.requestId === requestId) {\n lastEntry.type = 'response';\n lastEntry.status = response.status;\n lastEntry.durationMs = durationMs;\n } else {\n // Add new entry if somehow the request entry is gone\n logActivity(state, {\n type: 'response',\n method: request.method,\n path: request.path,\n status: response.status,\n durationMs,\n requestId,\n });\n }\n\n displayStatus(state);\n\n // Verbose: log response body\n if (state.verbose && body) {\n const bodyStr = typeof body === 'string' ? body : JSON.stringify(body, null, 2);\n const truncated = bodyStr.length > 500 ? bodyStr.substring(0, 500) + '...' : bodyStr;\n console.log(chalk.dim(` Response body: ${truncated}`));\n }\n\n telemetry.debug(\n EventTypes.OPENCODE_RESPONSE_SENT,\n `Response ${response.status}`,\n {\n status: response.status,\n path: request.path,\n durationMs,\n requestId,\n },\n state.sandboxId ?? undefined,\n );\n\n return {\n status: response.status,\n body,\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n const durationMs = Date.now() - startTime;\n\n // Remove from pending\n state.pendingRequests.delete(requestId);\n\n // Log error\n logActivity(state, {\n type: 'error',\n method: request.method,\n path: request.path,\n error: `OpenCode unreachable: ${message}`,\n durationMs,\n requestId,\n });\n\n displayStatus(state);\n\n telemetry.error(\n EventTypes.OPENCODE_UNREACHABLE,\n `Failed to connect to OpenCode: ${message}`,\n {\n port,\n path: request.path,\n error: message,\n requestId,\n },\n state.sandboxId ?? undefined,\n );\n\n return {\n status: 502,\n body: { error: 'Failed to connect to OpenCode', message },\n };\n }\n}\n\n// ============================================================================\n// Reconnection\n// ============================================================================\n\n/**\n * Calculate reconnect delay with exponential backoff and jitter\n */\nfunction getReconnectDelay(attempt: number): number {\n const exponentialDelay = BASE_RECONNECT_DELAY * Math.pow(2, attempt);\n const jitter = Math.random() * 1000;\n return Math.min(exponentialDelay + jitter, MAX_RECONNECT_DELAY);\n}\n\n// ============================================================================\n// WebSocket Connection\n// ============================================================================\n\n/**\n * Establish tunnel connection\n */\nasync function connect(\n token: string,\n sandboxId: string,\n port: number,\n state: TunnelState,\n): Promise<void> {\n const tunnelUrl = getTunnelUrlConfig();\n const url = `${tunnelUrl}/tunnel/${sandboxId}/connect`;\n\n logActivity(state, {\n type: 'info',\n message: 'Connecting to tunnel relay...',\n });\n displayStatus(state);\n\n telemetry.info(\n EventTypes.TUNNEL_STARTING,\n `Connecting to ${url}`,\n {\n sandboxId,\n port,\n tunnelUrl,\n },\n sandboxId,\n );\n\n return new Promise((resolve, reject) => {\n const ws = new WebSocket(url, {\n headers: {\n Authorization: `Bearer ${token}`,\n },\n });\n\n ws.on('open', () => {\n state.connected = true;\n state.reconnectAttempt = 0;\n logActivity(state, {\n type: 'info',\n message: 'WebSocket connection established',\n });\n displayStatus(state);\n });\n\n ws.on('message', async (data: WebSocket.RawData) => {\n try {\n const message: RelayMessage = JSON.parse(data.toString());\n\n switch (message.type) {\n case 'connected':\n state.sandboxId = message.sandbox_id ?? sandboxId;\n logActivity(state, {\n type: 'info',\n message: `Tunnel connected (sandbox: ${state.sandboxId})`,\n });\n telemetry.info(\n EventTypes.TUNNEL_CONNECTED,\n `Tunnel connected`,\n {\n sandboxId: message.sandbox_id,\n },\n message.sandbox_id,\n );\n displayStatus(state);\n break;\n\n case 'error':\n logActivity(state, {\n type: 'error',\n error: message.message || 'Unknown tunnel error',\n });\n telemetry.error(\n EventTypes.TUNNEL_ERROR,\n `Tunnel error: ${message.message}`,\n {\n code: message.code,\n message: message.message,\n },\n state.sandboxId ?? undefined,\n );\n displayStatus(state);\n\n if (message.code === 'unauthorized') {\n ws.close();\n reject(new Error('Unauthorized'));\n }\n break;\n\n case 'ping':\n ws.send(JSON.stringify({ type: 'pong' }));\n break;\n\n case 'request':\n if (message.id && message.payload) {\n telemetry.debug(\n EventTypes.OPENCODE_REQUEST_RECEIVED,\n `Request: ${message.payload.method} ${message.payload.path}`,\n {\n requestId: message.id,\n method: message.payload.method,\n path: message.payload.path,\n },\n state.sandboxId ?? undefined,\n );\n\n const response = await forwardToOpenCode(port, message.payload, message.id, state);\n\n ws.send(\n JSON.stringify({\n type: 'response',\n id: message.id,\n payload: response,\n }),\n );\n }\n break;\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n logActivity(state, {\n type: 'error',\n error: `Failed to handle message: ${errorMessage}`,\n });\n telemetry.error(\n EventTypes.TUNNEL_ERROR,\n `Failed to handle message: ${errorMessage}`,\n {\n error: errorMessage,\n },\n state.sandboxId ?? undefined,\n );\n displayStatus(state);\n }\n });\n\n ws.on('close', (code: number, reason: Buffer) => {\n state.connected = false;\n const reasonStr = reason.toString() || 'No reason provided';\n logActivity(state, {\n type: 'info',\n message: `Disconnected (code: ${code}, reason: ${reasonStr})`,\n });\n telemetry.info(\n EventTypes.TUNNEL_DISCONNECTED,\n 'Tunnel disconnected',\n {\n sandboxId: state.sandboxId,\n code,\n reason: reasonStr,\n },\n state.sandboxId ?? undefined,\n );\n displayStatus(state);\n resolve();\n });\n\n ws.on('error', (error: Error) => {\n state.connected = false;\n logActivity(state, {\n type: 'error',\n error: `Connection error: ${error.message}`,\n });\n telemetry.error(\n EventTypes.TUNNEL_ERROR,\n `Connection error: ${error.message}`,\n {\n error: error.message,\n },\n state.sandboxId ?? undefined,\n );\n displayStatus(state);\n // Don't reject on connection errors, let it reconnect\n });\n\n // Handle Ctrl+C - use once to avoid duplicate handlers on reconnect\n const cleanup = async (): Promise<void> => {\n // Remove handlers to prevent duplicate calls\n process.removeAllListeners('SIGINT');\n process.removeAllListeners('SIGTERM');\n\n logActivity(state, {\n type: 'info',\n message: 'Shutting down...',\n });\n displayStatus(state);\n telemetry.info(\n EventTypes.TUNNEL_DISCONNECTED,\n 'Tunnel stopped by user',\n {\n sandboxId: state.sandboxId,\n },\n state.sandboxId ?? undefined,\n );\n await shutdownTelemetry();\n ws.close();\n process.exit(0);\n };\n\n // Remove old handlers and add new ones\n process.removeAllListeners('SIGINT');\n process.removeAllListeners('SIGTERM');\n process.once('SIGINT', () => void cleanup());\n process.once('SIGTERM', () => void cleanup());\n });\n}\n\n// ============================================================================\n// Main Command Handler\n// ============================================================================\n\n/**\n * Tunnel command handler\n */\nexport async function tunnel(options: TunnelOptions): Promise<void> {\n const verbose = options.verbose ?? false;\n\n // Get credentials\n const credentials = await getToken();\n if (!credentials) {\n telemetry.error(EventTypes.CLI_ERROR, 'Not logged in', { command: 'tunnel' });\n printError('Not logged in. Run `evident login` first.');\n process.exit(1);\n }\n\n const port = options.port ?? 4096;\n const sandboxId = options.sandbox;\n\n // Sandbox ID is required\n if (!sandboxId) {\n printError('--sandbox <id> is required');\n blank();\n console.log(chalk.dim('To find your sandbox ID:'));\n console.log(chalk.dim(' 1. Create a remote sandbox in the Evident web UI'));\n console.log(chalk.dim(' 2. Copy the sandbox ID from the URL or settings'));\n console.log(chalk.dim(' 3. Run: evident tunnel --sandbox <id>'));\n blank();\n telemetry.error(EventTypes.CLI_ERROR, 'Missing sandbox ID', { command: 'tunnel' });\n process.exit(1);\n }\n\n // Initialize state early so we can log to it\n const state: TunnelState = {\n connected: false,\n sandboxId: sandboxId,\n sandboxName: null,\n reconnectAttempt: 0,\n lastActivity: new Date(),\n activityLog: [],\n pendingRequests: new Map(),\n verbose,\n };\n\n telemetry.info(\n EventTypes.CLI_COMMAND,\n 'Starting tunnel command',\n {\n command: 'tunnel',\n port,\n sandboxId,\n verbose,\n },\n sandboxId,\n );\n\n // Log tunnel start\n logActivity(state, {\n type: 'info',\n message: `Starting tunnel (port: ${port}, verbose: ${verbose})`,\n });\n\n // Validate sandbox\n logActivity(state, {\n type: 'info',\n message: 'Validating sandbox...',\n });\n const validateSpinner = ora('Validating sandbox...').start();\n const validation = await validateSandbox(credentials.token, sandboxId);\n\n if (!validation.valid) {\n validateSpinner.fail(`Sandbox validation failed: ${validation.error}`);\n logActivity(state, {\n type: 'error',\n error: `Sandbox validation failed: ${validation.error}`,\n });\n telemetry.error(EventTypes.CLI_ERROR, `Sandbox validation failed: ${validation.error}`, {\n command: 'tunnel',\n sandboxId,\n });\n // Show the activity log before exiting\n displayStatus(state);\n process.exit(1);\n }\n\n state.sandboxName = validation.name ?? null;\n validateSpinner.succeed(`Sandbox: ${validation.name || sandboxId}`);\n logActivity(state, {\n type: 'info',\n message: `Sandbox validated: ${validation.name || sandboxId}`,\n });\n\n // Check if OpenCode is running\n logActivity(state, {\n type: 'info',\n message: `Checking OpenCode on port ${port}...`,\n });\n const opencodeSpinner = ora('Checking OpenCode connection...').start();\n\n try {\n telemetry.debug(\n EventTypes.OPENCODE_HEALTH_CHECK,\n `Checking OpenCode on port ${port}`,\n { port },\n sandboxId,\n );\n const response = await fetch(`http://localhost:${port}/health`);\n if (!response.ok) {\n throw new Error(`Health check returned ${response.status}`);\n }\n const healthData = (await response.json().catch(() => ({}))) as { version?: string };\n const version = healthData.version ? ` (v${healthData.version})` : '';\n telemetry.info(\n EventTypes.OPENCODE_HEALTH_OK,\n `OpenCode healthy on port ${port}`,\n {\n port,\n healthData,\n },\n sandboxId,\n );\n opencodeSpinner.succeed(`OpenCode running on port ${port}${version}`);\n logActivity(state, {\n type: 'info',\n message: `OpenCode running on port ${port}${version}`,\n });\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n telemetry.warn(\n EventTypes.OPENCODE_HEALTH_FAILED,\n `Could not connect to OpenCode: ${errorMessage}`,\n {\n port,\n error: errorMessage,\n },\n sandboxId,\n );\n opencodeSpinner.warn(`Could not connect to OpenCode on port ${port}`);\n logActivity(state, {\n type: 'error',\n error: `OpenCode not reachable on port ${port}: ${errorMessage}`,\n });\n printWarning('Make sure OpenCode is running before starting the tunnel:');\n console.log(chalk.dim(` opencode serve --port ${port}`));\n blank();\n }\n\n // Reconnection loop\n // eslint-disable-next-line no-constant-condition\n while (true) {\n try {\n await connect(credentials.token, sandboxId, port, state);\n\n // Connection closed, attempt reconnect\n state.reconnectAttempt++;\n const delay = getReconnectDelay(state.reconnectAttempt);\n\n logActivity(state, {\n type: 'info',\n message: `Reconnecting in ${Math.round(delay / 1000)}s (attempt ${state.reconnectAttempt})...`,\n });\n\n telemetry.info(\n EventTypes.TUNNEL_RECONNECTING,\n `Reconnecting (attempt ${state.reconnectAttempt})`,\n {\n attempt: state.reconnectAttempt,\n delayMs: delay,\n },\n state.sandboxId ?? undefined,\n );\n\n displayStatus(state);\n await sleep(delay);\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n\n if (message === 'Unauthorized') {\n telemetry.error(\n EventTypes.CLI_ERROR,\n 'Authentication failed',\n {\n command: 'tunnel',\n error: message,\n },\n state.sandboxId ?? undefined,\n );\n await shutdownTelemetry();\n displayError(state, 'Authentication failed', 'Please run `evident login` again.');\n process.exit(1);\n }\n\n logActivity(state, {\n type: 'error',\n error: message,\n });\n\n telemetry.error(\n EventTypes.TUNNEL_ERROR,\n `Tunnel error: ${message}`,\n {\n error: message,\n attempt: state.reconnectAttempt,\n },\n state.sandboxId ?? undefined,\n );\n\n state.reconnectAttempt++;\n const delay = getReconnectDelay(state.reconnectAttempt);\n\n logActivity(state, {\n type: 'info',\n message: `Reconnecting in ${Math.round(delay / 1000)}s (attempt ${state.reconnectAttempt})...`,\n });\n\n displayStatus(state);\n await sleep(delay);\n }\n }\n}\n","/**\n * CLI Telemetry Client\n *\n * Captures and reports events to the Evident API for debugging and observability.\n * Events are batched and sent periodically to minimize network overhead.\n */\n\nimport { getApiUrlConfig } from './config.js';\nimport { getToken } from './keychain.js';\n\n// Get version from package.json at build time\nconst CLI_VERSION = process.env.npm_package_version || 'unknown';\n\nexport type EventSeverity = 'debug' | 'info' | 'warning' | 'error';\n\nexport interface TelemetryEvent {\n event_type: string;\n severity?: EventSeverity;\n message?: string;\n metadata?: Record<string, unknown>;\n sandbox_id?: string;\n timestamp?: string;\n}\n\n// Event buffer for batching\nlet eventBuffer: TelemetryEvent[] = [];\nlet flushTimeout: NodeJS.Timeout | null = null;\nlet isShuttingDown = false;\n\n// Configuration\nconst FLUSH_INTERVAL_MS = 5000; // Flush every 5 seconds\nconst MAX_BUFFER_SIZE = 50; // Flush when buffer reaches this size\nconst FLUSH_TIMEOUT_MS = 3000; // Timeout for flush requests\n\n/**\n * Log a telemetry event\n * Events are buffered and sent in batches\n */\nexport function logEvent(\n eventType: string,\n options: {\n severity?: EventSeverity;\n message?: string;\n metadata?: Record<string, unknown>;\n sandboxId?: string;\n } = {},\n): void {\n const event: TelemetryEvent = {\n event_type: eventType,\n severity: options.severity || 'info',\n message: options.message,\n metadata: options.metadata,\n sandbox_id: options.sandboxId,\n timestamp: new Date().toISOString(),\n };\n\n eventBuffer.push(event);\n\n // Flush immediately for errors or if buffer is full\n if (options.severity === 'error' || eventBuffer.length >= MAX_BUFFER_SIZE) {\n void flushEvents();\n } else if (!flushTimeout && !isShuttingDown) {\n // Schedule a flush\n flushTimeout = setTimeout(() => {\n flushTimeout = null;\n void flushEvents();\n }, FLUSH_INTERVAL_MS);\n }\n}\n\n/**\n * Convenience methods for different severity levels\n */\nexport const telemetry = {\n debug: (\n eventType: string,\n message?: string,\n metadata?: Record<string, unknown>,\n sandboxId?: string,\n ) => logEvent(eventType, { severity: 'debug', message, metadata, sandboxId }),\n\n info: (\n eventType: string,\n message?: string,\n metadata?: Record<string, unknown>,\n sandboxId?: string,\n ) => logEvent(eventType, { severity: 'info', message, metadata, sandboxId }),\n\n warn: (\n eventType: string,\n message?: string,\n metadata?: Record<string, unknown>,\n sandboxId?: string,\n ) => logEvent(eventType, { severity: 'warning', message, metadata, sandboxId }),\n\n error: (\n eventType: string,\n message?: string,\n metadata?: Record<string, unknown>,\n sandboxId?: string,\n ) => logEvent(eventType, { severity: 'error', message, metadata, sandboxId }),\n};\n\n/**\n * Flush buffered events to the API\n */\nexport async function flushEvents(): Promise<void> {\n if (eventBuffer.length === 0) return;\n\n // Take current buffer and reset\n const events = eventBuffer;\n eventBuffer = [];\n\n // Clear any pending flush timeout\n if (flushTimeout) {\n clearTimeout(flushTimeout);\n flushTimeout = null;\n }\n\n try {\n const credentials = await getToken();\n if (!credentials) {\n // Not logged in, can't send telemetry\n return;\n }\n\n const apiUrl = getApiUrlConfig();\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), FLUSH_TIMEOUT_MS);\n\n try {\n const response = await fetch(`${apiUrl}/telemetry/events`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${credentials.token}`,\n },\n body: JSON.stringify({\n events,\n client_type: 'cli',\n client_version: CLI_VERSION,\n }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n // Log failure but don't throw - telemetry shouldn't break the CLI\n console.error(`Telemetry flush failed: ${response.status}`);\n }\n } finally {\n clearTimeout(timeout);\n }\n } catch (error) {\n // Silently ignore telemetry errors - don't disrupt the user\n if (process.env.DEBUG) {\n console.error('Telemetry error:', error);\n }\n }\n}\n\n/**\n * Shutdown telemetry - flush remaining events\n * Call this before the process exits\n */\nexport async function shutdownTelemetry(): Promise<void> {\n isShuttingDown = true;\n\n if (flushTimeout) {\n clearTimeout(flushTimeout);\n flushTimeout = null;\n }\n\n await flushEvents();\n}\n\n// Predefined event types for consistency\nexport const EventTypes = {\n // Tunnel lifecycle\n TUNNEL_STARTING: 'tunnel.starting',\n TUNNEL_CONNECTED: 'tunnel.connected',\n TUNNEL_DISCONNECTED: 'tunnel.disconnected',\n TUNNEL_RECONNECTING: 'tunnel.reconnecting',\n TUNNEL_ERROR: 'tunnel.error',\n\n // OpenCode communication\n OPENCODE_HEALTH_CHECK: 'opencode.health_check',\n OPENCODE_HEALTH_OK: 'opencode.health_ok',\n OPENCODE_HEALTH_FAILED: 'opencode.health_failed',\n OPENCODE_REQUEST_RECEIVED: 'opencode.request_received',\n OPENCODE_REQUEST_FORWARDED: 'opencode.request_forwarded',\n OPENCODE_RESPONSE_SENT: 'opencode.response_sent',\n OPENCODE_UNREACHABLE: 'opencode.unreachable',\n OPENCODE_ERROR: 'opencode.error',\n\n // Authentication\n AUTH_LOGIN_STARTED: 'auth.login_started',\n AUTH_LOGIN_SUCCESS: 'auth.login_success',\n AUTH_LOGIN_FAILED: 'auth.login_failed',\n AUTH_LOGOUT: 'auth.logout',\n\n // CLI lifecycle\n CLI_STARTED: 'cli.started',\n CLI_COMMAND: 'cli.command',\n CLI_ERROR: 'cli.error',\n} as const;\n"],"mappings":"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","names":["chalk","error","credentials","error","chalk","credentials","chalk","credentials","chalk","chalk","ora","credentials","error","chalk","error","credentials","ora"]}
|