linkshell-cli 0.10.3 → 0.10.5

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 (44) hide show
  1. package/dist/cli/src/commands/doctor.js +1 -1
  2. package/dist/cli/src/commands/doctor.js.map +1 -1
  3. package/dist/cli/src/commands/gateway.js +7 -1
  4. package/dist/cli/src/commands/gateway.js.map +1 -1
  5. package/dist/cli/src/commands/host.js +2 -2
  6. package/dist/cli/src/commands/host.js.map +1 -1
  7. package/dist/cli/src/commands/login.js +4 -1
  8. package/dist/cli/src/commands/login.js.map +1 -1
  9. package/dist/cli/src/commands/pair.d.ts +5 -0
  10. package/dist/cli/src/commands/pair.js +22 -12
  11. package/dist/cli/src/commands/pair.js.map +1 -1
  12. package/dist/cli/src/utils/daemon.d.ts +5 -0
  13. package/dist/cli/src/utils/daemon.js +16 -1
  14. package/dist/cli/src/utils/daemon.js.map +1 -1
  15. package/dist/cli/tsconfig.tsbuildinfo +1 -1
  16. package/dist/gateway/src/main.js +1 -1
  17. package/dist/gateway/src/main.js.map +1 -1
  18. package/dist/gateway/src/rate-limit.d.ts +16 -4
  19. package/dist/gateway/src/rate-limit.js +52 -12
  20. package/dist/gateway/src/rate-limit.js.map +1 -1
  21. package/dist/gateway/src/serve.js +15 -1
  22. package/dist/gateway/src/serve.js.map +1 -1
  23. package/dist/gateway/src/subscription.js +1 -1
  24. package/dist/gateway/src/subscription.js.map +1 -1
  25. package/dist/host/src/drivers/acp/driver.js +1 -1
  26. package/dist/host/src/drivers/acp/driver.js.map +1 -1
  27. package/dist/host/src/drivers/codex/desktop-ipc.d.ts +7 -0
  28. package/dist/host/src/drivers/codex/desktop-ipc.js +11 -1
  29. package/dist/host/src/drivers/codex/desktop-ipc.js.map +1 -1
  30. package/dist/host/src/drivers/codex/driver.d.ts +9 -0
  31. package/dist/host/src/drivers/codex/driver.js +43 -7
  32. package/dist/host/src/drivers/codex/driver.js.map +1 -1
  33. package/dist/host/src/input.js +0 -2
  34. package/dist/host/src/input.js.map +1 -1
  35. package/dist/host/src/rpc/server.d.ts +11 -0
  36. package/dist/host/src/rpc/server.js +37 -3
  37. package/dist/host/src/rpc/server.js.map +1 -1
  38. package/package.json +3 -3
  39. package/src/commands/doctor.ts +1 -0
  40. package/src/commands/gateway.ts +7 -1
  41. package/src/commands/host.ts +2 -2
  42. package/src/commands/login.ts +4 -1
  43. package/src/commands/pair.ts +23 -12
  44. package/src/utils/daemon.ts +16 -1
@@ -210,7 +210,7 @@ export async function joinGatewayAfterLogin(plan: "pro" | "free"): Promise<void>
210
210
  if (!chosen && plan !== "pro") {
211
211
  // A running host now gets the gateway's own answer, instead of an old login's.
212
212
  await refreshGateway(0);
213
- out("The official gateway needs a Pro subscription: https://itool.tech");
213
+ out("The official gateway needs a Pro subscription: https://liutianjie.github.io/LinkShell/pricing/");
214
214
  out("Or use your own gateway: linkshell host --gateway <url>, then linkshell pair\n");
215
215
  return;
216
216
  }
@@ -355,7 +355,7 @@ export async function runAgentShim(agent: string, args: string[]): Promise<void>
355
355
 
356
356
  const sessionId = launch.sessionId;
357
357
  let child: import("node:child_process").ChildProcess | undefined;
358
- let yielded = false;
358
+ let yielded: boolean;
359
359
  let showingRemote = false;
360
360
  client.on("desktop.yield", ({ sessionId: target }) => {
361
361
  if (target !== sessionId) return;
@@ -26,6 +26,7 @@ export async function runLogin(): Promise<LoginResult | null> {
26
26
  process.stderr.write("\n LinkShell Login\n\n");
27
27
 
28
28
  return new Promise<LoginResult | null>((resolve) => {
29
+ let timeout: ReturnType<typeof setTimeout> | undefined;
29
30
  const server = http.createServer(async (req, res) => {
30
31
  const url = new URL(req.url ?? "/", "http://localhost");
31
32
 
@@ -136,6 +137,8 @@ export async function runLogin(): Promise<LoginResult | null> {
136
137
  ` \x1b[32m✓\x1b[0m Logged in as ${email || userId} (${planLabel})\n\n`,
137
138
  );
138
139
 
140
+ // Left pending, the five-minute limit would keep the command alive long after the login.
141
+ clearTimeout(timeout);
139
142
  setTimeout(() => {
140
143
  server.close();
141
144
  resolve({
@@ -182,7 +185,7 @@ export async function runLogin(): Promise<LoginResult | null> {
182
185
  } catch {}
183
186
 
184
187
  // Timeout after 5 minutes
185
- setTimeout(() => {
188
+ timeout = setTimeout(() => {
186
189
  process.stderr.write(" Login timed out.\n\n");
187
190
  server.close();
188
191
  resolve(null);
@@ -1,4 +1,5 @@
1
1
  import qrcode from "qrcode-terminal";
2
+ import type { HostClient } from "@linkshell/host";
2
3
 
3
4
  // `linkshell pair`: show a QR code (and a code to type) that lets a phone reach
4
5
  // this computer through the gateway, then wait for it to pair.
@@ -34,19 +35,29 @@ export async function runPair(): Promise<void> {
34
35
  const code = `${offer.code.slice(0, 3)} ${offer.code.slice(3)}`;
35
36
  const minutes = Math.round((offer.expiresAt - Date.now()) / 60_000);
36
37
  process.stdout.write(`\n Or enter the code: ${code} (valid ${minutes} min, gateway ${offer.gateway})\n\n Waiting for your phone…`);
37
- const device = await new Promise<{ name: string } | undefined>((resolve) => {
38
- const stop = client.on("pairing.done", ({ device }) => {
39
- stop();
40
- resolve(device);
41
- });
42
- setTimeout(() => {
43
- stop();
44
- resolve(undefined);
45
- }, offer.expiresAt - Date.now());
46
- process.on("SIGINT", () => resolve(undefined));
47
- });
48
- process.stdout.write(device ? `\r ✓ Paired with ${device.name}. It can reach this computer from anywhere now.\n\n` : "\r Pairing window closed. \n\n");
38
+ const outcome = await waitForPairing(client, offer.expiresAt);
39
+ process.stdout.write(typeof outcome === "object" ? `\r ✓ Paired with ${outcome.name}. It can reach this computer from anywhere now.\n\n` : "\r Pairing window closed. \n\n");
40
+ // What a shell expects of a command ended by Ctrl-C.
41
+ if (outcome === "interrupted") process.exitCode = 130;
49
42
  } finally {
50
43
  client.close();
51
44
  }
52
45
  }
46
+
47
+ /** Waits for a phone to pair: the device, or why the wait ended without one. */
48
+ export function waitForPairing(client: Pick<HostClient, "on">, expiresAt: number): Promise<{ name: string } | "expired" | "interrupted"> {
49
+ return new Promise((resolve) => {
50
+ // However the wait ends, the timer and the listeners end with it: a pending timer keeps the process alive until
51
+ // the window closes, and a SIGINT listener left behind swallows every later Ctrl-C.
52
+ const finish = (outcome: { name: string } | "expired" | "interrupted") => {
53
+ stop();
54
+ clearTimeout(timer);
55
+ process.off("SIGINT", interrupt);
56
+ resolve(outcome);
57
+ };
58
+ const stop = client.on("pairing.done", ({ device }) => finish(device));
59
+ const timer = setTimeout(() => finish("expired"), expiresAt - Date.now());
60
+ const interrupt = () => finish("interrupted");
61
+ process.on("SIGINT", interrupt);
62
+ });
63
+ }
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync, openSync } from "node:fs";
1
+ import { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync, openSync, renameSync, statSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { homedir } from "node:os";
4
4
  import { spawn } from "node:child_process";
@@ -58,9 +58,24 @@ export function getLogFile(service: ServiceName): string {
58
58
  return logFile(service);
59
59
  }
60
60
 
61
+ const MAX_LOG_BYTES = 5 * 1024 * 1024;
62
+
63
+ /**
64
+ * Keeps a log from growing forever: a full one becomes `<service>.log.1`, replacing the one before it.
65
+ * Only when a daemon starts, since a running one holds the file open.
66
+ */
67
+ export function rotateLog(log: string, maxBytes = MAX_LOG_BYTES): void {
68
+ try {
69
+ if (statSync(log).size > maxBytes) renameSync(log, `${log}.1`);
70
+ } catch {
71
+ // No log yet.
72
+ }
73
+ }
74
+
61
75
  export function spawnDaemon(service: ServiceName, args: string[]): number {
62
76
  mkdirSync(LINKSHELL_DIR, { recursive: true });
63
77
  const log = logFile(service);
78
+ rotateLog(log);
64
79
  const out = openSync(log, "a");
65
80
  const err = openSync(log, "a");
66
81