uplink-cli 0.1.2 → 0.1.4

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.
@@ -8,6 +8,11 @@ import { homedir } from "os";
8
8
  import { join } from "path";
9
9
  import { existsSync, readFileSync, writeFileSync } from "fs";
10
10
 
11
+ // Check if running from project directory (smoke tests require local scripts)
12
+ function isInProjectDir(): boolean {
13
+ return existsSync("scripts/test-comprehensive.sh") && existsSync("package.json");
14
+ }
15
+
11
16
  type MenuChoice = {
12
17
  label: string;
13
18
  action?: () => Promise<string>;
@@ -471,33 +476,36 @@ export const menuCommand = new Command("menu")
471
476
  // Only show other menu items if authentication succeeded
472
477
 
473
478
  if (isAdmin) {
474
- mainMenu.push({
475
- label: "System Status",
476
- subMenu: [
477
- {
478
- label: "View Status",
479
- action: async () => {
480
- let health = "unknown";
481
- try {
482
- const res = await fetch(`${apiBase}/health`);
483
- const json = await res.json().catch(() => ({}));
484
- health = json.status || res.statusText || "unknown";
485
- } catch {
486
- health = "unreachable";
487
- }
479
+ const systemStatusItems: MenuChoice[] = [
480
+ {
481
+ label: "View Status",
482
+ action: async () => {
483
+ let health = "unknown";
484
+ try {
485
+ const res = await fetch(`${apiBase}/health`);
486
+ const json = await res.json().catch(() => ({}));
487
+ health = json.status || res.statusText || "unknown";
488
+ } catch {
489
+ health = "unreachable";
490
+ }
488
491
 
489
- const stats = await apiRequest("GET", "/v1/admin/stats");
490
- return [
491
- `API health: ${health}`,
492
- "Tunnels:",
493
- ` Active ${stats.tunnels.active} | Inactive ${stats.tunnels.inactive} | Deleted ${stats.tunnels.deleted} | Total ${stats.tunnels.total}`,
494
- ` Created last 24h: ${stats.tunnels.createdLast24h}`,
495
- "Databases:",
496
- ` Ready ${stats.databases.ready} | Provisioning ${stats.databases.provisioning} | Failed ${stats.databases.failed} | Deleted ${stats.databases.deleted} | Total ${stats.databases.total}`,
497
- ` Created last 24h: ${stats.databases.createdLast24h}`,
498
- ].join("\n");
499
- },
492
+ const stats = await apiRequest("GET", "/v1/admin/stats");
493
+ return [
494
+ `API health: ${health}`,
495
+ "Tunnels:",
496
+ ` Active ${stats.tunnels.active} | Inactive ${stats.tunnels.inactive} | Deleted ${stats.tunnels.deleted} | Total ${stats.tunnels.total}`,
497
+ ` Created last 24h: ${stats.tunnels.createdLast24h}`,
498
+ "Databases:",
499
+ ` Ready ${stats.databases.ready} | Provisioning ${stats.databases.provisioning} | Failed ${stats.databases.failed} | Deleted ${stats.databases.deleted} | Total ${stats.databases.total}`,
500
+ ` Created last 24h: ${stats.databases.createdLast24h}`,
501
+ ].join("\n");
500
502
  },
503
+ },
504
+ ];
505
+
506
+ // Smoke tests only available when running from project directory
507
+ if (isInProjectDir()) {
508
+ systemStatusItems.push(
501
509
  {
502
510
  label: "Test: Tunnel",
503
511
  action: async () => {
@@ -525,72 +533,80 @@ export const menuCommand = new Command("menu")
525
533
  await runSmoke("test:comprehensive");
526
534
  return "test:comprehensive completed";
527
535
  },
528
- },
529
- {
530
- label: "View Connected Tunnels",
531
- action: async () => {
532
- try {
533
- const data = await apiRequest("GET", "/v1/admin/relay-status") as {
534
- connectedTunnels?: number;
535
- tunnels?: Array<{ token: string; clientIp: string; targetPort: number; connectedAt: string; connectedFor: string }>;
536
- timestamp?: string;
537
- error?: string;
538
- message?: string;
539
- };
540
-
541
- if (data.error) {
542
- return `Error: ${data.error}${data.message ? ` - ${data.message}` : ""}`;
543
- }
544
- if (!data.tunnels || data.tunnels.length === 0) {
545
- return "No tunnels currently connected to the relay.";
546
- }
547
-
548
- const lines = data.tunnels.map((t) =>
549
- `${truncate(t.token, 12).padEnd(14)} ${t.clientIp.padEnd(16)} ${String(t.targetPort).padEnd(6)} ${t.connectedFor.padEnd(10)} ${truncate(t.connectedAt, 19)}`
550
- );
536
+ }
537
+ );
538
+ }
551
539
 
552
- return [
553
- `Connected Tunnels: ${data.connectedTunnels}`,
554
- "",
555
- "Token Client IP Port Uptime Connected At",
556
- "-".repeat(75),
557
- ...lines,
558
- ].join("\n");
559
- } catch (err: any) {
560
- return `Error: Failed to get relay status - ${err.message}`;
561
- }
562
- },
563
- },
564
- {
565
- label: "View Traffic Stats",
566
- action: async () => {
567
- const data = await apiRequest("GET", "/v1/admin/traffic-stats?sync=true") as {
568
- count?: number;
569
- aliases?: Array<{ alias: string; requests: number; bytesIn: number; bytesOut: number; lastSeenAt: string | null; lastStatus: number | null }>;
540
+ systemStatusItems.push(
541
+ {
542
+ label: "View Connected Tunnels",
543
+ action: async () => {
544
+ try {
545
+ const data = await apiRequest("GET", "/v1/admin/relay-status") as {
546
+ connectedTunnels?: number;
547
+ tunnels?: Array<{ token: string; clientIp: string; targetPort: number; connectedAt: string; connectedFor: string }>;
548
+ timestamp?: string;
549
+ error?: string;
550
+ message?: string;
570
551
  };
571
552
 
572
- const aliases = data.aliases || [];
573
- if (aliases.length === 0) return "No persisted alias stats yet.";
553
+ if (data.error) {
554
+ return `Error: ${data.error}${data.message ? ` - ${data.message}` : ""}`;
555
+ }
556
+ if (!data.tunnels || data.tunnels.length === 0) {
557
+ return "No tunnels currently connected to the relay.";
558
+ }
574
559
 
575
- const top = aliases.slice(0, 25);
576
- const lines = top.map((a) => {
577
- const last = a.lastSeenAt ? truncate(a.lastSeenAt, 19) : "-";
578
- return `${truncate(a.alias, 22).padEnd(24)} ${String(a.requests || 0).padStart(8)} ${formatBytes(a.bytesIn || 0).padStart(9)} ${formatBytes(a.bytesOut || 0).padStart(9)} ${String(a.lastStatus ?? "-").padStart(3)} ${last}`;
579
- });
560
+ const lines = data.tunnels.map((t) =>
561
+ `${truncate(t.token, 12).padEnd(14)} ${t.clientIp.padEnd(16)} ${String(t.targetPort).padEnd(6)} ${t.connectedFor.padEnd(10)} ${truncate(t.connectedAt, 19)}`
562
+ );
580
563
 
581
564
  return [
582
- `Aliases tracked: ${aliases.length}`,
565
+ `Connected Tunnels: ${data.connectedTunnels}`,
583
566
  "",
584
- "Alias Requests In Out Sts Last Seen",
567
+ "Token Client IP Port Uptime Connected At",
585
568
  "-".repeat(75),
586
569
  ...lines,
587
- aliases.length > top.length ? `\nShowing top ${top.length} by requests.` : "",
588
- ]
589
- .filter(Boolean)
590
- .join("\n");
591
- },
570
+ ].join("\n");
571
+ } catch (err: any) {
572
+ return `Error: Failed to get relay status - ${err.message}`;
573
+ }
592
574
  },
593
- ],
575
+ },
576
+ {
577
+ label: "View Traffic Stats",
578
+ action: async () => {
579
+ const data = await apiRequest("GET", "/v1/admin/traffic-stats?sync=true") as {
580
+ count?: number;
581
+ aliases?: Array<{ alias: string; requests: number; bytesIn: number; bytesOut: number; lastSeenAt: string | null; lastStatus: number | null }>;
582
+ };
583
+
584
+ const aliases = data.aliases || [];
585
+ if (aliases.length === 0) return "No persisted alias stats yet.";
586
+
587
+ const top = aliases.slice(0, 25);
588
+ const lines = top.map((a) => {
589
+ const last = a.lastSeenAt ? truncate(a.lastSeenAt, 19) : "-";
590
+ return `${truncate(a.alias, 22).padEnd(24)} ${String(a.requests || 0).padStart(8)} ${formatBytes(a.bytesIn || 0).padStart(9)} ${formatBytes(a.bytesOut || 0).padStart(9)} ${String(a.lastStatus ?? "-").padStart(3)} ${last}`;
591
+ });
592
+
593
+ return [
594
+ `Aliases tracked: ${aliases.length}`,
595
+ "",
596
+ "Alias Requests In Out Sts Last Seen",
597
+ "-".repeat(75),
598
+ ...lines,
599
+ aliases.length > top.length ? `\nShowing top ${top.length} by requests.` : "",
600
+ ]
601
+ .filter(Boolean)
602
+ .join("\n");
603
+ },
604
+ }
605
+ );
606
+
607
+ mainMenu.push({
608
+ label: "System Status",
609
+ subMenu: systemStatusItems,
594
610
  });
595
611
  }
596
612
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uplink-cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Expose localhost to the internet in seconds. Interactive terminal UI, permanent custom domains, zero config. A modern ngrok alternative.",
5
5
  "keywords": [
6
6
  "tunnel",