linkshell-cli 0.6.2 → 0.6.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.
Files changed (93) hide show
  1. package/README.md +47 -132
  2. package/dist/cli/src/auth.js +1 -1
  3. package/dist/cli/src/auth.js.map +1 -1
  4. package/dist/cli/src/commands/doctor.d.ts +1 -1
  5. package/dist/cli/src/commands/doctor.js +76 -36
  6. package/dist/cli/src/commands/doctor.js.map +1 -1
  7. package/dist/cli/src/commands/host.d.ts +14 -0
  8. package/dist/cli/src/commands/host.js +94 -15
  9. package/dist/cli/src/commands/host.js.map +1 -1
  10. package/dist/cli/src/commands/logout.d.ts +1 -1
  11. package/dist/cli/src/commands/logout.js +10 -2
  12. package/dist/cli/src/commands/logout.js.map +1 -1
  13. package/dist/cli/src/commands/upgrade.js +11 -0
  14. package/dist/cli/src/commands/upgrade.js.map +1 -1
  15. package/dist/cli/src/config.js +1 -1
  16. package/dist/cli/src/config.js.map +1 -1
  17. package/dist/cli/src/index.js +94 -114
  18. package/dist/cli/src/index.js.map +1 -1
  19. package/dist/cli/tsconfig.tsbuildinfo +1 -1
  20. package/dist/host/src/carry.d.ts +15 -0
  21. package/dist/host/src/carry.js +83 -0
  22. package/dist/host/src/carry.js.map +1 -0
  23. package/dist/host/src/drivers/acp/driver.d.ts +13 -3
  24. package/dist/host/src/drivers/acp/driver.js +43 -8
  25. package/dist/host/src/drivers/acp/driver.js.map +1 -1
  26. package/dist/host/src/drivers/acp/mapper.d.ts +5 -1
  27. package/dist/host/src/drivers/acp/mapper.js +53 -5
  28. package/dist/host/src/drivers/acp/mapper.js.map +1 -1
  29. package/dist/host/src/drivers/claude/driver.d.ts +53 -8
  30. package/dist/host/src/drivers/claude/driver.js +357 -40
  31. package/dist/host/src/drivers/claude/driver.js.map +1 -1
  32. package/dist/host/src/drivers/claude/holders.d.ts +9 -0
  33. package/dist/host/src/drivers/claude/holders.js +61 -0
  34. package/dist/host/src/drivers/claude/holders.js.map +1 -0
  35. package/dist/host/src/drivers/claude/transcript.d.ts +100 -62
  36. package/dist/host/src/drivers/claude/transcript.js +246 -26
  37. package/dist/host/src/drivers/claude/transcript.js.map +1 -1
  38. package/dist/host/src/drivers/codex/commands.d.ts +27 -0
  39. package/dist/host/src/drivers/codex/commands.js +23 -0
  40. package/dist/host/src/drivers/codex/commands.js.map +1 -0
  41. package/dist/host/src/drivers/codex/driver.d.ts +15 -1
  42. package/dist/host/src/drivers/codex/driver.js +110 -1
  43. package/dist/host/src/drivers/codex/driver.js.map +1 -1
  44. package/dist/host/src/drivers/codex/mapper.d.ts +4 -0
  45. package/dist/host/src/drivers/codex/mapper.js +29 -5
  46. package/dist/host/src/drivers/codex/mapper.js.map +1 -1
  47. package/dist/host/src/drivers/types.d.ts +34 -3
  48. package/dist/host/src/fs.d.ts +26 -4
  49. package/dist/host/src/fs.js +71 -17
  50. package/dist/host/src/fs.js.map +1 -1
  51. package/dist/host/src/gateway.d.ts +3 -0
  52. package/dist/host/src/gateway.js +9 -0
  53. package/dist/host/src/gateway.js.map +1 -1
  54. package/dist/host/src/host.d.ts +7 -3
  55. package/dist/host/src/host.js +42 -18
  56. package/dist/host/src/host.js.map +1 -1
  57. package/dist/host/src/hub.d.ts +91 -15
  58. package/dist/host/src/hub.js +556 -33
  59. package/dist/host/src/hub.js.map +1 -1
  60. package/dist/host/src/rpc/server.d.ts +4 -2
  61. package/dist/host/src/rpc/server.js +51 -6
  62. package/dist/host/src/rpc/server.js.map +1 -1
  63. package/dist/host/src/screen.d.ts +11 -0
  64. package/dist/host/src/screen.js +57 -3
  65. package/dist/host/src/screen.js.map +1 -1
  66. package/dist/host/src/slim.d.ts +17 -0
  67. package/dist/host/src/slim.js +66 -0
  68. package/dist/host/src/slim.js.map +1 -0
  69. package/dist/host/src/store.d.ts +75 -3
  70. package/dist/host/src/store.js +216 -16
  71. package/dist/host/src/store.js.map +1 -1
  72. package/dist/host/src/terminals.d.ts +4 -4
  73. package/dist/host/src/worktrees.d.ts +36 -0
  74. package/dist/host/src/worktrees.js +96 -0
  75. package/dist/host/src/worktrees.js.map +1 -0
  76. package/dist/wire/src/model.d.ts +301 -153
  77. package/dist/wire/src/model.js +55 -0
  78. package/dist/wire/src/model.js.map +1 -1
  79. package/dist/wire/src/relay-client.d.ts +2 -0
  80. package/dist/wire/src/relay-client.js +12 -0
  81. package/dist/wire/src/relay-client.js.map +1 -1
  82. package/dist/wire/src/rpc.d.ts +6740 -1022
  83. package/dist/wire/src/rpc.js +130 -7
  84. package/dist/wire/src/rpc.js.map +1 -1
  85. package/dist/wire/src/updates.d.ts +498 -498
  86. package/package.json +5 -5
  87. package/src/auth.ts +1 -1
  88. package/src/commands/doctor.ts +76 -34
  89. package/src/commands/host.ts +98 -14
  90. package/src/commands/logout.ts +10 -2
  91. package/src/commands/upgrade.ts +11 -0
  92. package/src/config.ts +1 -1
  93. package/src/index.ts +93 -131
package/src/index.ts CHANGED
@@ -65,8 +65,10 @@ const hostCmd = program
65
65
  .option("--gateway <url>", "Reach this computer through a v2 gateway (saved; 'off' to disable, 'default' for the official one when logged in)")
66
66
  .option("--_foreground-host", undefined) // internal
67
67
  .action(async (options) => {
68
- const { runHostForeground, ensureHostRunning, readHostConfig, writeHostConfig, assertHostRuntime } = await import("./commands/host.js");
68
+ const { runHostForeground, ensureHostRunning, readHostConfig, writeHostConfig, assertHostRuntime, silenceSqliteWarning, withRunningHost, refreshGateway, describeGateway } =
69
+ await import("./commands/host.js");
69
70
  assertHostRuntime();
71
+ silenceSqliteWarning();
70
72
  if (options.devPort) process.env.LINKSHELL_DEV_PORT = String(options.devPort);
71
73
  if (options.gateway) {
72
74
  const { defaultHome } = await import("@linkshell/host");
@@ -74,15 +76,30 @@ const hostCmd = program
74
76
  // "off" is remembered, so being logged in doesn't turn the official gateway back on.
75
77
  writeHostConfig(home, { ...readHostConfig(home), gateway: options.gateway === "default" ? undefined : options.gateway });
76
78
  }
79
+ const running = options._foregroundHost ? undefined : await withRunningHost((client) => client.call("machine.info", {}));
77
80
  if (options.daemon && !options._foregroundHost) {
78
81
  const socket = await ensureHostRunning();
79
82
  const daemon = await import("./utils/daemon.js");
80
83
  process.stderr.write(`\n LinkShell host running (${socket})\n`);
84
+ if (running && running.hostVersion !== pkg.version) {
85
+ process.stderr.write(` It is still ${running.hostVersion}; this CLI is ${pkg.version}. To update it (stops running agent turns):\n`);
86
+ process.stderr.write(` linkshell host stop && linkshell host --daemon\n`);
87
+ }
88
+ // A gateway given to a host that was already running applies now.
89
+ if (options.gateway && running) {
90
+ const gateway = await refreshGateway(10_000);
91
+ if (gateway) process.stderr.write(` Gateway: ${describeGateway(gateway)}\n`);
92
+ }
81
93
  process.stderr.write(` Log: ${daemon.getLogFile("host")}\n`);
82
94
  process.stderr.write(` Status: linkshell host status\n`);
83
95
  process.stderr.write(` Stop: linkshell host stop\n\n`);
84
96
  return;
85
97
  }
98
+ if (running && options.gateway) {
99
+ const gateway = await refreshGateway(10_000);
100
+ if (gateway) process.stderr.write(`\n Gateway: ${describeGateway(gateway)}\n\n`);
101
+ return;
102
+ }
86
103
  await runHostForeground(pkg.version);
87
104
  });
88
105
 
@@ -110,6 +127,57 @@ program
110
127
  await runPair();
111
128
  });
112
129
 
130
+ const devicesCmd = program
131
+ .command("devices")
132
+ .description("List the phones paired with this computer")
133
+ .action(async () => {
134
+ const { withRunningHost, assertHostRuntime } = await import("./commands/host.js");
135
+ assertHostRuntime();
136
+ const gateway = await withRunningHost((client) => client.call("gateway.status", {}));
137
+ if (!gateway) {
138
+ process.stderr.write(" LinkShell host is not running. Start it with: linkshell host --daemon\n");
139
+ process.exitCode = 1;
140
+ return;
141
+ }
142
+ if (gateway.devices.length === 0) {
143
+ process.stdout.write(" No paired devices. (Devices signed in to your account don't need pairing.)\n");
144
+ return;
145
+ }
146
+ for (const device of gateway.devices) {
147
+ const paired = new Date(device.pairedAt).toLocaleDateString();
148
+ process.stdout.write(` ${device.online ? "●" : "○"} ${device.name} paired ${paired} ${device.id.slice(0, 8)}\n`);
149
+ }
150
+ process.stdout.write("\n Remove one with: linkshell devices remove <name or id>\n");
151
+ });
152
+
153
+ devicesCmd
154
+ .command("remove <device>")
155
+ .description("Unpair a phone: it can no longer reach this computer")
156
+ .action(async (wanted: string) => {
157
+ const { withRunningHost, assertHostRuntime } = await import("./commands/host.js");
158
+ assertHostRuntime();
159
+ const result = await withRunningHost(async (client) => {
160
+ const { devices } = await client.call("gateway.status", {});
161
+ const matches = devices.filter((device) => device.id.startsWith(wanted) || device.name.toLowerCase() === wanted.toLowerCase());
162
+ if (matches.length !== 1) return { matches };
163
+ await client.call("devices.revoke", { deviceId: matches[0]!.id });
164
+ return { removed: matches[0]! };
165
+ });
166
+ if (!result) {
167
+ process.stderr.write(" LinkShell host is not running. Start it with: linkshell host --daemon\n");
168
+ process.exitCode = 1;
169
+ } else if (result.removed) {
170
+ process.stdout.write(` Unpaired ${result.removed.name}.\n`);
171
+ } else {
172
+ process.stderr.write(
173
+ result.matches.length === 0
174
+ ? ` No paired device named "${wanted}". See: linkshell devices\n`
175
+ : ` "${wanted}" matches ${result.matches.length} devices; use the id shown by: linkshell devices\n`,
176
+ );
177
+ process.exitCode = 1;
178
+ }
179
+ });
180
+
113
181
  // `linkshell codex …` / `linkshell claude …` start the agent's own TUI attached
114
182
  // to the host, so the session is live here and on every paired device.
115
183
  // All arguments go to the agent.
@@ -481,11 +549,13 @@ program
481
549
  .description("Stop all running LinkShell processes")
482
550
  .action(async () => {
483
551
  const { stopDaemon } = await import("./utils/daemon.js");
552
+ const hostStopped = stopDaemon("host");
484
553
  const bridgeStopped = stopDaemon("bridge");
485
554
  const gatewayStopped = stopDaemon("gateway");
486
- if (bridgeStopped) process.stderr.write(" Bridge stopped.\n");
487
- if (gatewayStopped) process.stderr.write(" Gateway stopped.\n");
488
- if (!bridgeStopped && !gatewayStopped) {
555
+ if (hostStopped) process.stderr.write(" Host stopped.\n");
556
+ if (bridgeStopped) process.stderr.write(" v1 bridge stopped.\n");
557
+ if (gatewayStopped) process.stderr.write(" Gateway server stopped.\n");
558
+ if (!hostStopped && !bridgeStopped && !gatewayStopped) {
489
559
  process.stderr.write(" No running processes found.\n");
490
560
  }
491
561
  });
@@ -494,47 +564,36 @@ program
494
564
 
495
565
  program
496
566
  .command("status")
497
- .description("Show status of all LinkShell processes")
567
+ .description("Show the host, its gateway connection, and anything else LinkShell runs here")
498
568
  .action(async () => {
499
- const { readPid, getLogFile, readMetadata } = await import(
500
- "./utils/daemon.js"
501
- );
502
- const bridgePid = readPid("bridge");
569
+ const { readPid, getLogFile, readMetadata } = await import("./utils/daemon.js");
570
+ const { printHostStatus, assertHostRuntime } = await import("./commands/host.js");
571
+ assertHostRuntime();
572
+ process.stdout.write("\n");
573
+ await printHostStatus();
503
574
  const gatewayPid = readPid("gateway");
504
- const bridgeMeta = bridgePid ? readMetadata("bridge") : null;
505
-
506
- process.stderr.write("\n");
575
+ if (gatewayPid) process.stdout.write(` Gateway server: running (PID ${gatewayPid}), log ${getLogFile("gateway")}\n`);
576
+ const bridgePid = readPid("bridge");
507
577
  if (bridgePid) {
508
- process.stderr.write(` Bridge: running (PID ${bridgePid})\n`);
509
- process.stderr.write(
510
- ` Keep awake: ${bridgeMeta?.keepAwake ? "enabled" : "disabled"}\n`,
511
- );
512
- process.stderr.write(` Log: ${getLogFile("bridge")}\n`);
513
- } else {
514
- process.stderr.write(" Bridge: not running\n");
515
- }
516
- if (gatewayPid) {
517
- process.stderr.write(` Gateway: running (PID ${gatewayPid})\n`);
518
- process.stderr.write(` Log: ${getLogFile("gateway")}\n`);
519
- } else {
520
- process.stderr.write(" Gateway: not running\n");
578
+ const keepAwake = readMetadata("bridge")?.keepAwake ? ", keeps this computer awake" : "";
579
+ process.stdout.write(` v1 bridge: running (PID ${bridgePid}${keepAwake}), log ${getLogFile("bridge")}\n`);
521
580
  }
522
- process.stderr.write("\n");
581
+ process.stdout.write("\n");
523
582
  });
524
583
 
525
584
  // ── doctor / setup ──────────────────────────────────────────────────
526
585
 
527
586
  program
528
587
  .command("doctor")
529
- .description("Check your environment and connectivity")
530
- .option("--gateway <url>", "Gateway URL to test", config.gateway)
588
+ .description("Check that this computer is ready: Node, agents, the host and its gateway")
589
+ .option("--gateway <url>", "Also check that this gateway answers")
531
590
  .action(async (options) => {
532
- await runDoctor(options.gateway);
591
+ await runDoctor(pkg.version, options.gateway);
533
592
  });
534
593
 
535
594
  program
536
595
  .command("setup")
537
- .description("Interactive setup wizard")
596
+ .description("Configure the v1 bridge (`linkshell start`)")
538
597
  .action(async () => {
539
598
  await runSetup();
540
599
  });
@@ -553,113 +612,16 @@ program
553
612
  const result = await runLogin();
554
613
  if (!result) return;
555
614
 
556
- if (result.plan !== "pro") {
557
- process.stderr.write(
558
- " Upgrade to Pro for official gateway access: https://itool.tech\n\n",
559
- );
560
- return;
561
- }
562
-
563
- // Pro user — fetch official gateways and offer to connect
564
- const { SUPABASE_URL, SUPABASE_ANON_KEY } = await import("./auth.js");
565
- let gateways: { url: string; name: string; region: string | null }[] = [];
566
- try {
567
- const res = await fetch(
568
- `${SUPABASE_URL}/rest/v1/linkshell_official_gateways?enabled=eq.true&select=url,name,region`,
569
- {
570
- headers: {
571
- Authorization: `Bearer ${result.accessToken}`,
572
- apikey: SUPABASE_ANON_KEY,
573
- },
574
- signal: AbortSignal.timeout(10_000),
575
- },
576
- );
577
- if (res.ok) {
578
- gateways = (await res.json()) as typeof gateways;
579
- }
580
- } catch {}
581
-
582
- if (gateways.length === 0) {
583
- process.stderr.write(" No official gateways available yet.\n\n");
584
- return;
585
- }
586
-
587
- process.stderr.write(" Available gateways:\n\n");
588
- for (let i = 0; i < gateways.length; i++) {
589
- const gw = gateways[i]!;
590
- const label = gw.region ? `${gw.name} (${gw.region})` : gw.name;
591
- process.stderr.write(` [${i + 1}] ${label} ${gw.url}\n`);
592
- }
593
- process.stderr.write(` [0] Skip\n\n`);
594
-
595
- const { createInterface } = await import("node:readline");
596
- const rl = createInterface({ input: process.stdin, output: process.stderr });
597
- const answer = await new Promise<string>((res) => {
598
- rl.question(" Connect to gateway [1]: ", (ans) => {
599
- rl.close();
600
- res(ans.trim());
601
- });
602
- });
603
-
604
- if (answer === "0") return;
605
-
606
- const idx = (answer === "" ? 1 : Number(answer)) - 1;
607
- if (idx < 0 || idx >= gateways.length || Number.isNaN(idx)) {
608
- process.stderr.write(" Invalid choice.\n\n");
609
- return;
610
- }
611
-
612
- const chosen = gateways[idx]!;
613
- const gwWsUrl = chosen.url.replace(/\/$/, "").replace(/^https:/, "wss:").replace(/^http:/, "ws:") + "/ws";
614
-
615
- process.stderr.write(`\n Connecting to ${chosen.name}...\n`);
616
-
617
- // Start daemon with the chosen gateway
618
- const daemon = await import("./utils/daemon.js");
619
- const keepAwake = shouldKeepAwake(undefined);
620
- const existingPid = daemon.readPid("bridge");
621
- if (existingPid) {
622
- process.stderr.write(
623
- ` Bridge already running (PID ${existingPid}). Run: linkshell stop\n\n`,
624
- );
625
- return;
626
- }
627
-
628
- const childArgs = [
629
- "start",
630
- "--_foreground-bridge",
631
- "--gateway", gwWsUrl,
632
- "--provider", "custom",
633
- "--client-name", config.clientName ?? "local-cli",
634
- "--cols", String(config.cols ?? 120),
635
- "--rows", String(config.rows ?? 36),
636
- "--screen",
637
- "--agent-ui",
638
- ];
639
- if (config.command) childArgs.push("--command", config.command);
640
- if (config.hostname) childArgs.push("--hostname", config.hostname);
641
- if (!keepAwake) childArgs.push("--no-keep-awake");
642
-
643
- const pid = daemon.spawnDaemon("bridge", childArgs);
644
- daemon.saveMetadata("bridge", { keepAwake, startedAt: Date.now() });
645
- process.stderr.write(`\n \x1b[32m✓\x1b[0m Bridge started in background (PID ${pid})\n`);
646
- process.stderr.write(` Gateway: ${chosen.name}\n`);
647
- process.stderr.write(` Provider: custom (shell)\n`);
648
- process.stderr.write(` Keep awake: ${keepAwake ? "enabled" : "disabled"}\n`);
649
- process.stderr.write(` Desktop: enabled\n`);
650
- process.stderr.write(` Agent GUI: enabled (Codex / Claude auto-detect)\n`);
651
- process.stderr.write(` Open the LinkShell app on your phone to connect.\n\n`);
652
- process.stderr.write(` Stop: linkshell stop\n`);
653
- process.stderr.write(` Status: linkshell status\n`);
654
- process.stderr.write(` Logs: tail -f ${daemon.getLogFile("bridge")}\n\n`);
615
+ const { joinGatewayAfterLogin } = await import("./commands/host.js");
616
+ await joinGatewayAfterLogin(result.plan);
655
617
  process.exit(0);
656
618
  });
657
619
 
658
620
  program
659
621
  .command("logout")
660
622
  .description("Log out of LinkShell")
661
- .action(() => {
662
- runLogout();
623
+ .action(async () => {
624
+ await runLogout();
663
625
  });
664
626
 
665
627
  program