linkshell-cli 0.10.4 → 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.
- package/dist/cli/src/commands/doctor.js +1 -1
- package/dist/cli/src/commands/doctor.js.map +1 -1
- package/dist/cli/src/commands/gateway.js +7 -1
- package/dist/cli/src/commands/gateway.js.map +1 -1
- package/dist/cli/src/commands/host.js +2 -2
- package/dist/cli/src/commands/host.js.map +1 -1
- package/dist/cli/src/commands/login.js +4 -1
- package/dist/cli/src/commands/login.js.map +1 -1
- package/dist/cli/src/commands/pair.d.ts +5 -0
- package/dist/cli/src/commands/pair.js +22 -12
- package/dist/cli/src/commands/pair.js.map +1 -1
- package/dist/cli/src/utils/daemon.d.ts +5 -0
- package/dist/cli/src/utils/daemon.js +16 -1
- package/dist/cli/src/utils/daemon.js.map +1 -1
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/dist/gateway/src/main.js +1 -1
- package/dist/gateway/src/main.js.map +1 -1
- package/dist/gateway/src/rate-limit.d.ts +16 -4
- package/dist/gateway/src/rate-limit.js +52 -12
- package/dist/gateway/src/rate-limit.js.map +1 -1
- package/dist/gateway/src/serve.js +15 -1
- package/dist/gateway/src/serve.js.map +1 -1
- package/dist/gateway/src/subscription.js +1 -1
- package/dist/gateway/src/subscription.js.map +1 -1
- package/dist/host/src/drivers/acp/driver.js +1 -1
- package/dist/host/src/drivers/acp/driver.js.map +1 -1
- package/dist/host/src/drivers/codex/driver.js +1 -1
- package/dist/host/src/drivers/codex/driver.js.map +1 -1
- package/dist/host/src/input.js +0 -2
- package/dist/host/src/input.js.map +1 -1
- package/dist/host/src/rpc/server.d.ts +11 -0
- package/dist/host/src/rpc/server.js +37 -3
- package/dist/host/src/rpc/server.js.map +1 -1
- package/package.json +3 -3
- package/src/commands/doctor.ts +1 -0
- package/src/commands/gateway.ts +7 -1
- package/src/commands/host.ts +2 -2
- package/src/commands/login.ts +4 -1
- package/src/commands/pair.ts +23 -12
- package/src/utils/daemon.ts +16 -1
package/src/commands/host.ts
CHANGED
|
@@ -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://
|
|
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
|
|
358
|
+
let yielded: boolean;
|
|
359
359
|
let showingRemote = false;
|
|
360
360
|
client.on("desktop.yield", ({ sessionId: target }) => {
|
|
361
361
|
if (target !== sessionId) return;
|
package/src/commands/login.ts
CHANGED
|
@@ -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);
|
package/src/commands/pair.ts
CHANGED
|
@@ -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
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
+
}
|
package/src/utils/daemon.ts
CHANGED
|
@@ -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
|
|