linkshell-cli 0.9.0 → 0.10.1
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/README.md +11 -6
- package/dist/cli/src/commands/gateway.d.ts +2 -0
- package/dist/cli/src/commands/gateway.js +29 -0
- package/dist/cli/src/commands/gateway.js.map +1 -0
- package/dist/cli/src/commands/host.d.ts +2 -0
- package/dist/cli/src/commands/host.js +12 -2
- package/dist/cli/src/commands/host.js.map +1 -1
- package/dist/cli/src/commands/setup.js +4 -2
- package/dist/cli/src/commands/setup.js.map +1 -1
- package/dist/cli/src/index.js +12 -334
- package/dist/cli/src/index.js.map +1 -1
- package/dist/cli/src/utils/daemon.d.ts +0 -7
- package/dist/cli/src/utils/daemon.js +1 -18
- package/dist/cli/src/utils/daemon.js.map +1 -1
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/dist/gateway/src/accounts.d.ts +6 -0
- package/dist/gateway/src/accounts.js +32 -0
- package/dist/gateway/src/accounts.js.map +1 -0
- package/dist/gateway/src/index.d.ts +4 -0
- package/dist/gateway/src/index.js +5 -0
- package/dist/gateway/src/index.js.map +1 -0
- package/dist/gateway/src/main.d.ts +1 -0
- package/dist/gateway/src/main.js +57 -0
- package/dist/gateway/src/main.js.map +1 -0
- package/dist/gateway/src/rate-limit.d.ts +22 -0
- package/dist/gateway/src/rate-limit.js +66 -0
- package/dist/gateway/src/rate-limit.js.map +1 -0
- package/dist/gateway/src/relay.d.ts +84 -0
- package/dist/gateway/src/relay.js +434 -0
- package/dist/gateway/src/relay.js.map +1 -0
- package/dist/gateway/src/serve.d.ts +34 -0
- package/dist/gateway/src/serve.js +102 -0
- package/dist/gateway/src/serve.js.map +1 -0
- package/dist/gateway/src/store.d.ts +35 -0
- package/dist/gateway/src/store.js +109 -0
- package/dist/gateway/src/store.js.map +1 -0
- package/dist/gateway/src/subscription.d.ts +18 -0
- package/dist/gateway/src/subscription.js +46 -0
- package/dist/gateway/src/subscription.js.map +1 -0
- package/dist/host/src/drivers/claude/transcript.d.ts +23 -23
- package/dist/host/src/drivers/codex/app-server.d.ts +31 -6
- package/dist/host/src/drivers/codex/app-server.js +99 -24
- package/dist/host/src/drivers/codex/app-server.js.map +1 -1
- package/dist/host/src/drivers/codex/desktop-ipc.d.ts +8 -0
- package/dist/host/src/drivers/codex/desktop-ipc.js +124 -0
- package/dist/host/src/drivers/codex/desktop-ipc.js.map +1 -0
- package/dist/host/src/drivers/codex/driver.d.ts +111 -2
- package/dist/host/src/drivers/codex/driver.js +629 -63
- package/dist/host/src/drivers/codex/driver.js.map +1 -1
- package/dist/host/src/drivers/codex/mapper.d.ts +21 -0
- package/dist/host/src/drivers/codex/mapper.js +45 -6
- package/dist/host/src/drivers/codex/mapper.js.map +1 -1
- package/dist/host/src/drivers/types.d.ts +6 -0
- package/dist/host/src/fs.d.ts +3 -3
- package/dist/host/src/hub.js +8 -1
- package/dist/host/src/hub.js.map +1 -1
- package/dist/host/src/input.d.ts +4 -4
- package/dist/host/src/terminals.d.ts +1 -1
- package/dist/wire/src/model.d.ts +6 -6
- package/dist/wire/src/relay.d.ts +8 -8
- package/dist/wire/src/rpc.d.ts +818 -818
- package/dist/wire/src/updates.d.ts +18 -18
- package/package.json +5 -14
- package/src/commands/gateway.ts +31 -0
- package/src/commands/host.ts +13 -2
- package/src/commands/setup.ts +4 -2
- package/src/index.ts +14 -429
- package/src/utils/daemon.ts +2 -27
- package/dist/cli/src/config.d.ts +0 -14
- package/dist/cli/src/config.js +0 -23
- package/dist/cli/src/config.js.map +0 -1
- package/dist/cli/src/machine-id.d.ts +0 -6
- package/dist/cli/src/machine-id.js +0 -50
- package/dist/cli/src/machine-id.js.map +0 -1
- package/dist/cli/src/providers.d.ts +0 -13
- package/dist/cli/src/providers.js +0 -77
- package/dist/cli/src/providers.js.map +0 -1
- package/dist/cli/src/runtime/acp/acp-client.d.ts +0 -128
- package/dist/cli/src/runtime/acp/acp-client.js +0 -430
- package/dist/cli/src/runtime/acp/acp-client.js.map +0 -1
- package/dist/cli/src/runtime/acp/agent-process-discovery.d.ts +0 -48
- package/dist/cli/src/runtime/acp/agent-process-discovery.js +0 -278
- package/dist/cli/src/runtime/acp/agent-process-discovery.js.map +0 -1
- package/dist/cli/src/runtime/acp/agent-session.d.ts +0 -62
- package/dist/cli/src/runtime/acp/agent-session.js +0 -1081
- package/dist/cli/src/runtime/acp/agent-session.js.map +0 -1
- package/dist/cli/src/runtime/acp/agent-workspace.d.ts +0 -311
- package/dist/cli/src/runtime/acp/agent-workspace.js +0 -6083
- package/dist/cli/src/runtime/acp/agent-workspace.js.map +0 -1
- package/dist/cli/src/runtime/acp/claude-sdk-client.d.ts +0 -121
- package/dist/cli/src/runtime/acp/claude-sdk-client.js +0 -1147
- package/dist/cli/src/runtime/acp/claude-sdk-client.js.map +0 -1
- package/dist/cli/src/runtime/acp/claude-stream-json-client.d.ts +0 -53
- package/dist/cli/src/runtime/acp/claude-stream-json-client.js +0 -552
- package/dist/cli/src/runtime/acp/claude-stream-json-client.js.map +0 -1
- package/dist/cli/src/runtime/acp/json-rpc.d.ts +0 -22
- package/dist/cli/src/runtime/acp/json-rpc.js +0 -160
- package/dist/cli/src/runtime/acp/json-rpc.js.map +0 -1
- package/dist/cli/src/runtime/acp/provider-resolver.d.ts +0 -16
- package/dist/cli/src/runtime/acp/provider-resolver.js +0 -132
- package/dist/cli/src/runtime/acp/provider-resolver.js.map +0 -1
- package/dist/cli/src/runtime/bridge-session.d.ts +0 -97
- package/dist/cli/src/runtime/bridge-session.js +0 -1364
- package/dist/cli/src/runtime/bridge-session.js.map +0 -1
- package/dist/cli/src/runtime/hook-command.d.ts +0 -9
- package/dist/cli/src/runtime/hook-command.js +0 -80
- package/dist/cli/src/runtime/hook-command.js.map +0 -1
- package/dist/cli/src/runtime/screen-fallback.d.ts +0 -22
- package/dist/cli/src/runtime/screen-fallback.js +0 -202
- package/dist/cli/src/runtime/screen-fallback.js.map +0 -1
- package/dist/cli/src/runtime/screen-share.d.ts +0 -34
- package/dist/cli/src/runtime/screen-share.js +0 -376
- package/dist/cli/src/runtime/screen-share.js.map +0 -1
- package/dist/cli/src/runtime/scrollback.d.ts +0 -11
- package/dist/cli/src/runtime/scrollback.js +0 -33
- package/dist/cli/src/runtime/scrollback.js.map +0 -1
- package/dist/cli/src/runtime/usage-report.d.ts +0 -34
- package/dist/cli/src/runtime/usage-report.js +0 -332
- package/dist/cli/src/runtime/usage-report.js.map +0 -1
- package/dist/cli/src/utils/keep-awake.d.ts +0 -6
- package/dist/cli/src/utils/keep-awake.js +0 -48
- package/dist/cli/src/utils/keep-awake.js.map +0 -1
- package/dist/cli/src/utils/lan-ip.d.ts +0 -9
- package/dist/cli/src/utils/lan-ip.js +0 -61
- package/dist/cli/src/utils/lan-ip.js.map +0 -1
- package/dist/host/src/input-helper.d.ts +0 -1
- package/dist/host/src/input-helper.js +0 -564
- package/dist/host/src/input-helper.js.map +0 -1
- package/dist/shared-protocol/src/index.d.ts +0 -17029
- package/dist/shared-protocol/src/index.js +0 -1064
- package/dist/shared-protocol/src/index.js.map +0 -1
- package/scripts/copy-web.mjs +0 -34
- package/src/config.ts +0 -36
- package/src/machine-id.ts +0 -61
- package/src/providers.ts +0 -97
- package/src/runtime/acp/acp-client.ts +0 -521
- package/src/runtime/acp/agent-process-discovery.ts +0 -308
- package/src/runtime/acp/agent-session.ts +0 -1186
- package/src/runtime/acp/agent-workspace.ts +0 -6590
- package/src/runtime/acp/claude-sdk-client.ts +0 -1221
- package/src/runtime/acp/claude-stream-json-client.ts +0 -633
- package/src/runtime/acp/json-rpc.ts +0 -184
- package/src/runtime/acp/provider-resolver.ts +0 -154
- package/src/runtime/bridge-session.ts +0 -1549
- package/src/runtime/hook-command.ts +0 -73
- package/src/runtime/screen-fallback.ts +0 -235
- package/src/runtime/screen-share.ts +0 -426
- package/src/runtime/scrollback.ts +0 -43
- package/src/runtime/usage-report.ts +0 -342
- package/src/utils/keep-awake.ts +0 -61
- package/src/utils/lan-ip.ts +0 -61
- package/web/assets/FileBrowser-wOAfY-xj.js +0 -2
- package/web/assets/PortPreview-Cbvv7yzA.js +0 -2
- package/web/assets/TerminalPanel-CTSklkct.js +0 -1
- package/web/assets/UsageDashboard-CJ1AsNmd.js +0 -1
- package/web/assets/index-BsnDaeCt.css +0 -33
- package/web/assets/index-q85psYZ5.js +0 -36
- package/web/assets/vendor-highlight-NXWAAyIv.js +0 -5
- package/web/assets/vendor-react-M7xG3qiw.js +0 -49
- package/web/assets/vendor-xterm-D15THcI4.js +0 -9
- package/web/index.html +0 -37
- package/web/logos/claude.png +0 -0
- package/web/logos/codex.png +0 -0
- package/web/logos/copilot.png +0 -0
- package/web/logos/gemini.png +0 -0
- package/web/logos/linkshell-1024.png +0 -0
- package/web/logos/linkshell-splash.png +0 -0
- package/web/logos/linkshell.png +0 -0
package/src/index.ts
CHANGED
|
@@ -1,51 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
-
import { BridgeSession } from "./runtime/bridge-session.js";
|
|
4
|
-
import { resolveProviderConfig } from "./providers.js";
|
|
5
|
-
import { loadConfig } from "./config.js";
|
|
6
3
|
import { runDoctor } from "./commands/doctor.js";
|
|
7
4
|
import { runSetup } from "./commands/setup.js";
|
|
8
5
|
import { runUpgrade } from "./commands/upgrade.js";
|
|
9
6
|
import { runLogin } from "./commands/login.js";
|
|
10
7
|
import { runLogout } from "./commands/logout.js";
|
|
11
|
-
import { getLanIp } from "./utils/lan-ip.js";
|
|
12
|
-
import { shouldKeepAwake } from "./utils/keep-awake.js";
|
|
13
|
-
|
|
14
8
|
import { createRequire } from "node:module";
|
|
15
|
-
import { fileURLToPath } from "node:url";
|
|
16
|
-
import { dirname, resolve } from "node:path";
|
|
17
|
-
import { existsSync, statSync } from "node:fs";
|
|
18
9
|
|
|
19
10
|
const require = createRequire(import.meta.url);
|
|
20
11
|
const pkg = require("../../../package.json") as { version: string };
|
|
21
12
|
|
|
22
|
-
// Locate the bundled web-dashboard SPA and point the (embedded) gateway's
|
|
23
|
-
// static server at it via WEB_DIST — BEFORE any gateway module is imported,
|
|
24
|
-
// because static-web.ts freezes WEB_DIST at module-load time. This is what lets
|
|
25
|
-
// the in-app WebView render for LAN / self-hosted users whose CLI runs its own
|
|
26
|
-
// embedded gateway (the cloud image bundles web separately). A user-set
|
|
27
|
-
// WEB_DIST always wins. Probed across published-package, monorepo-compiled, and
|
|
28
|
-
// dev (tsx) layouts; the first candidate containing index.html is used.
|
|
29
|
-
if (!process.env.WEB_DIST) {
|
|
30
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
31
|
-
const candidates = [
|
|
32
|
-
resolve(here, "../../../web"), // published npm package root, and packages/cli/web (compiled)
|
|
33
|
-
resolve(here, "../../../apps/web-dashboard/dist"), // dev: tsx src/index.ts in the monorepo
|
|
34
|
-
];
|
|
35
|
-
for (const dir of candidates) {
|
|
36
|
-
try {
|
|
37
|
-
if (existsSync(resolve(dir, "index.html")) && statSync(dir).isDirectory()) {
|
|
38
|
-
process.env.WEB_DIST = dir;
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
} catch {
|
|
42
|
-
// ignore and try the next candidate
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const config = loadConfig();
|
|
49
13
|
const program = new Command();
|
|
50
14
|
|
|
51
15
|
program
|
|
@@ -55,14 +19,14 @@ program
|
|
|
55
19
|
)
|
|
56
20
|
.version(pkg.version);
|
|
57
21
|
|
|
58
|
-
// ── host
|
|
22
|
+
// ── host ────────────────────────────────────────────────────────────
|
|
59
23
|
|
|
60
24
|
const hostCmd = program
|
|
61
25
|
.command("host")
|
|
62
|
-
.description("Run the LinkShell host that owns agent sessions
|
|
26
|
+
.description("Run the LinkShell host that owns agent sessions")
|
|
63
27
|
.option("--daemon", "Run in background (detached)")
|
|
64
28
|
.option("--dev-port <port>", "Also serve the host API on 127.0.0.1:<port> (local development clients only)")
|
|
65
|
-
.option("--gateway <url>", "Reach this computer through a
|
|
29
|
+
.option("--gateway <url>", "Reach this computer through a gateway (saved; 'off' to disable, 'default' for the official one when logged in)")
|
|
66
30
|
.option("--_foreground-host", undefined) // internal
|
|
67
31
|
.action(async (options) => {
|
|
68
32
|
const { runHostForeground, ensureHostRunning, readHostConfig, writeHostConfig, assertHostRuntime, silenceSqliteWarning, withRunningHost, refreshGateway, describeGateway } =
|
|
@@ -204,322 +168,35 @@ for (const [agent, description] of [
|
|
|
204
168
|
});
|
|
205
169
|
}
|
|
206
170
|
|
|
207
|
-
// ── start ───────────────────────────────────────────────────────────
|
|
208
|
-
|
|
209
|
-
program
|
|
210
|
-
.command("start")
|
|
211
|
-
.description("Start a v1 bridge session (for the v1 app; with built-in or remote gateway)")
|
|
212
|
-
.option(
|
|
213
|
-
"--gateway <url>",
|
|
214
|
-
"Gateway websocket URL (omit to start built-in gateway)",
|
|
215
|
-
config.gateway ?? undefined,
|
|
216
|
-
)
|
|
217
|
-
.option(
|
|
218
|
-
"--pairing-gateway <url-or-host>",
|
|
219
|
-
"Public HTTP gateway used in QR/deep link output",
|
|
220
|
-
config.pairingGateway,
|
|
221
|
-
)
|
|
222
|
-
.option("--port <port>", "Port for built-in gateway", "8787")
|
|
223
|
-
.option("--session-id <id>", "Session identifier (auto-created if omitted)")
|
|
224
|
-
.option(
|
|
225
|
-
"--provider <provider>",
|
|
226
|
-
"(deprecated — always custom; launch your CLI manually inside the shell)",
|
|
227
|
-
"custom",
|
|
228
|
-
)
|
|
229
|
-
.option(
|
|
230
|
-
"--command <command>",
|
|
231
|
-
"Shell or CLI to spawn in the PTY (default: $SHELL)",
|
|
232
|
-
config.command,
|
|
233
|
-
)
|
|
234
|
-
.option(
|
|
235
|
-
"--client-name <name>",
|
|
236
|
-
"Display name for this CLI",
|
|
237
|
-
config.clientName ?? "local-cli",
|
|
238
|
-
)
|
|
239
|
-
.option(
|
|
240
|
-
"--hostname <name>",
|
|
241
|
-
"Override hostname sent to gateway",
|
|
242
|
-
config.hostname,
|
|
243
|
-
)
|
|
244
|
-
.option(
|
|
245
|
-
"--cols <cols>",
|
|
246
|
-
"Initial terminal columns",
|
|
247
|
-
String(config.cols ?? 120),
|
|
248
|
-
)
|
|
249
|
-
.option("--rows <rows>", "Initial terminal rows", String(config.rows ?? 36))
|
|
250
|
-
.option("--screen", "Enable screen sharing capability")
|
|
251
|
-
.option("--no-keep-awake", "Disable macOS keep-awake while bridge is running")
|
|
252
|
-
.option("--agent-ui", "Enable ACP Agent Workspace channel", true)
|
|
253
|
-
.option("--no-agent-ui", "Disable ACP Agent Workspace channel")
|
|
254
|
-
.option(
|
|
255
|
-
"--agent-provider <provider>",
|
|
256
|
-
"Agent GUI provider: codex | claude | custom",
|
|
257
|
-
)
|
|
258
|
-
.option(
|
|
259
|
-
"--agent-command <command>",
|
|
260
|
-
"ACP agent command (required for claude/custom, optional for codex)",
|
|
261
|
-
)
|
|
262
|
-
.option("--daemon", "Run in background (detached)")
|
|
263
|
-
.option("--verbose", "Enable verbose logging")
|
|
264
|
-
.option("--_foreground-bridge", undefined) // internal
|
|
265
|
-
.allowUnknownOption(true)
|
|
266
|
-
.allowExcessArguments(true)
|
|
267
|
-
.action(async (options, command) => {
|
|
268
|
-
const daemon = await import("./utils/daemon.js");
|
|
269
|
-
const keepAwake = shouldKeepAwake(options.keepAwake);
|
|
270
|
-
|
|
271
|
-
// Daemon mode: spawn detached child and exit
|
|
272
|
-
if (options.daemon && !options._foregroundBridge) {
|
|
273
|
-
const existingPid = daemon.readPid("bridge");
|
|
274
|
-
if (existingPid) {
|
|
275
|
-
process.stderr.write(` Bridge already running (PID ${existingPid})\n`);
|
|
276
|
-
process.stderr.write(` Run: linkshell stop\n\n`);
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Rebuild args for the child, replacing --daemon with --_foreground-bridge
|
|
281
|
-
const childArgs = ["start", "--_foreground-bridge"];
|
|
282
|
-
if (options.gateway) childArgs.push("--gateway", options.gateway);
|
|
283
|
-
if (options.pairingGateway)
|
|
284
|
-
childArgs.push("--pairing-gateway", options.pairingGateway);
|
|
285
|
-
childArgs.push("--port", String(options.port));
|
|
286
|
-
childArgs.push("--provider", options.provider);
|
|
287
|
-
if (options.command) childArgs.push("--command", options.command);
|
|
288
|
-
childArgs.push("--client-name", options.clientName);
|
|
289
|
-
if (options.hostname) childArgs.push("--hostname", options.hostname);
|
|
290
|
-
childArgs.push("--cols", String(options.cols));
|
|
291
|
-
childArgs.push("--rows", String(options.rows));
|
|
292
|
-
if (options.verbose) childArgs.push("--verbose");
|
|
293
|
-
if (options.screen) childArgs.push("--screen");
|
|
294
|
-
if (!keepAwake) childArgs.push("--no-keep-awake");
|
|
295
|
-
if (options.agentUi) childArgs.push("--agent-ui");
|
|
296
|
-
if (options.agentProvider)
|
|
297
|
-
childArgs.push("--agent-provider", options.agentProvider);
|
|
298
|
-
if (options.agentCommand)
|
|
299
|
-
childArgs.push("--agent-command", options.agentCommand);
|
|
300
|
-
if (options.sessionId) childArgs.push("--session-id", options.sessionId);
|
|
301
|
-
// Pass through extra args
|
|
302
|
-
const extra = command.args.filter((v: string) => v !== "--");
|
|
303
|
-
if (extra.length) childArgs.push("--", ...extra);
|
|
304
|
-
|
|
305
|
-
const pid = daemon.spawnDaemon("bridge", childArgs);
|
|
306
|
-
daemon.saveMetadata("bridge", { keepAwake, startedAt: Date.now() });
|
|
307
|
-
process.stderr.write(`\n LinkShell bridge started in background\n`);
|
|
308
|
-
process.stderr.write(` PID: ${pid}\n`);
|
|
309
|
-
process.stderr.write(` Provider: ${options.provider}\n`);
|
|
310
|
-
process.stderr.write(
|
|
311
|
-
` Keep awake: ${keepAwake ? "enabled (use --no-keep-awake to disable)" : "disabled"}\n`,
|
|
312
|
-
);
|
|
313
|
-
process.stderr.write(` Log: ${daemon.getLogFile("bridge")}\n\n`);
|
|
314
|
-
process.stderr.write(` Stop: linkshell stop\n`);
|
|
315
|
-
process.stderr.write(` Status: linkshell status\n`);
|
|
316
|
-
process.stderr.write(
|
|
317
|
-
` Logs: tail -f ${daemon.getLogFile("bridge")}\n\n`,
|
|
318
|
-
);
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Foreground mode
|
|
323
|
-
const passthroughArgs = command.args.filter(
|
|
324
|
-
(value: string) => value !== "--",
|
|
325
|
-
);
|
|
326
|
-
const providerConfig = resolveProviderConfig({
|
|
327
|
-
provider: options.provider,
|
|
328
|
-
command: options.command,
|
|
329
|
-
args: passthroughArgs,
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
let gatewayUrl = options.gateway as string | undefined;
|
|
333
|
-
let gatewayHttpUrl: string;
|
|
334
|
-
let pairingGateway = options.pairingGateway as string | undefined;
|
|
335
|
-
let embeddedGatewayHandle: { close: () => Promise<void> } | undefined;
|
|
336
|
-
|
|
337
|
-
if (!gatewayUrl) {
|
|
338
|
-
const { startEmbeddedGateway } =
|
|
339
|
-
await import("@linkshell/gateway/embedded");
|
|
340
|
-
const port = Number(options.port);
|
|
341
|
-
const gw = await startEmbeddedGateway({
|
|
342
|
-
port,
|
|
343
|
-
logLevel: options.verbose ? "debug" : "warn",
|
|
344
|
-
silent: false,
|
|
345
|
-
});
|
|
346
|
-
embeddedGatewayHandle = gw;
|
|
347
|
-
gatewayUrl = gw.wsUrl;
|
|
348
|
-
gatewayHttpUrl = gw.httpUrl;
|
|
349
|
-
|
|
350
|
-
const lanIp = getLanIp();
|
|
351
|
-
if (!pairingGateway && lanIp !== "127.0.0.1") {
|
|
352
|
-
pairingGateway = `http://${lanIp}:${gw.port}`;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
process.stderr.write(`\n Built-in gateway started on port ${gw.port}\n`);
|
|
356
|
-
if (pairingGateway) {
|
|
357
|
-
process.stderr.write(` LAN address: ${pairingGateway}\n`);
|
|
358
|
-
}
|
|
359
|
-
process.stderr.write("\n");
|
|
360
|
-
} else {
|
|
361
|
-
gatewayHttpUrl = gatewayUrl
|
|
362
|
-
.replace(/\/ws\/?$/, "")
|
|
363
|
-
.replace(/^wss:/, "https:")
|
|
364
|
-
.replace(/^ws:/, "http:");
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// Save PID for status/stop
|
|
368
|
-
daemon.savePid("bridge", process.pid);
|
|
369
|
-
daemon.saveMetadata("bridge", { keepAwake, startedAt: Date.now() });
|
|
370
|
-
|
|
371
|
-
// Load auth token if logged in
|
|
372
|
-
let authToken: string | undefined;
|
|
373
|
-
try {
|
|
374
|
-
const { getValidToken } = await import("./auth.js");
|
|
375
|
-
authToken = (await getValidToken()) ?? undefined;
|
|
376
|
-
} catch {}
|
|
377
|
-
|
|
378
|
-
const session = new BridgeSession({
|
|
379
|
-
gatewayUrl,
|
|
380
|
-
gatewayHttpUrl,
|
|
381
|
-
pairingGateway,
|
|
382
|
-
sessionId: options.sessionId,
|
|
383
|
-
cols: Number(options.cols),
|
|
384
|
-
rows: Number(options.rows),
|
|
385
|
-
clientName: options.clientName,
|
|
386
|
-
hostname: options.hostname,
|
|
387
|
-
verbose: Boolean(options.verbose),
|
|
388
|
-
screen: Boolean(options.screen),
|
|
389
|
-
providerConfig,
|
|
390
|
-
authToken,
|
|
391
|
-
keepAwake,
|
|
392
|
-
agentUi: Boolean(options.agentUi),
|
|
393
|
-
agentProvider: options.agentProvider,
|
|
394
|
-
agentCommand: options.agentCommand,
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
const cleanup = async () => {
|
|
398
|
-
session.stop(0);
|
|
399
|
-
daemon.removePid("bridge");
|
|
400
|
-
if (embeddedGatewayHandle) await embeddedGatewayHandle.close();
|
|
401
|
-
};
|
|
402
|
-
|
|
403
|
-
// When all PTYs exit naturally (not via a signal), the session closes its
|
|
404
|
-
// own socket but the embedded gateway loop and stale PID file would linger.
|
|
405
|
-
// Run the same process-level cleanup and then exit.
|
|
406
|
-
session.setOnAllTerminalsExited(async () => {
|
|
407
|
-
daemon.removePid("bridge");
|
|
408
|
-
if (embeddedGatewayHandle) await embeddedGatewayHandle.close();
|
|
409
|
-
process.exit(process.exitCode ?? 0);
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
process.on("SIGINT", () => {
|
|
413
|
-
cleanup().then(() => process.exit(0));
|
|
414
|
-
});
|
|
415
|
-
process.on("SIGTERM", () => {
|
|
416
|
-
cleanup().then(() => process.exit(0));
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
// Global crash handlers: never die silently. In daemon mode keep the bridge
|
|
420
|
-
// alive (it has its own gateway reconnect logic); stderr is redirected to the
|
|
421
|
-
// daemon log file so the trace is recoverable.
|
|
422
|
-
const isDaemonChild = Boolean(options._foregroundBridge);
|
|
423
|
-
process.on("uncaughtException", (err) => {
|
|
424
|
-
process.stderr.write(
|
|
425
|
-
`[bridge] uncaught exception: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`,
|
|
426
|
-
);
|
|
427
|
-
if (!isDaemonChild) {
|
|
428
|
-
cleanup().then(() => process.exit(1));
|
|
429
|
-
}
|
|
430
|
-
});
|
|
431
|
-
process.on("unhandledRejection", (reason) => {
|
|
432
|
-
process.stderr.write(
|
|
433
|
-
`[bridge] unhandled rejection: ${reason instanceof Error ? reason.stack ?? reason.message : String(reason)}\n`,
|
|
434
|
-
);
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
await session.start();
|
|
438
|
-
});
|
|
439
|
-
|
|
440
171
|
// ── gateway ─────────────────────────────────────────────────────────
|
|
441
172
|
|
|
442
173
|
const gatewayCmd = program
|
|
443
174
|
.command("gateway")
|
|
444
|
-
.description("
|
|
175
|
+
.description("Run a gateway of your own: it relays encrypted frames between your computers and phones, and brokers pairing")
|
|
445
176
|
.option("--port <port>", "Listen port", "8787")
|
|
446
|
-
.option(
|
|
447
|
-
"--log-level <level>",
|
|
448
|
-
"Log level: debug | info | warn | error",
|
|
449
|
-
"info",
|
|
450
|
-
)
|
|
451
177
|
.option("--daemon", "Run in background (detached)")
|
|
452
178
|
.option("--_foreground-gw", undefined) // internal
|
|
453
179
|
.action(async (options) => {
|
|
454
|
-
const daemon = await import("./utils/daemon.js");
|
|
455
|
-
|
|
456
180
|
if (options.daemon && !options._foregroundGw) {
|
|
181
|
+
const daemon = await import("./utils/daemon.js");
|
|
457
182
|
const existingPid = daemon.readPid("gateway");
|
|
458
183
|
if (existingPid) {
|
|
459
|
-
process.stderr.write(
|
|
460
|
-
` Gateway already running (PID ${existingPid})\n`,
|
|
461
|
-
);
|
|
184
|
+
process.stderr.write(` Gateway already running (PID ${existingPid})\n`);
|
|
462
185
|
process.stderr.write(` Run: linkshell gateway stop\n\n`);
|
|
463
186
|
return;
|
|
464
187
|
}
|
|
465
|
-
const pid = daemon.spawnDaemon("gateway", [
|
|
466
|
-
"gateway",
|
|
467
|
-
"--_foreground-gw",
|
|
468
|
-
"--port",
|
|
469
|
-
String(options.port),
|
|
470
|
-
"--log-level",
|
|
471
|
-
options.logLevel,
|
|
472
|
-
]);
|
|
188
|
+
const pid = daemon.spawnDaemon("gateway", ["gateway", "--_foreground-gw", "--port", String(options.port)]);
|
|
473
189
|
process.stderr.write(`\n LinkShell Gateway started in background\n`);
|
|
474
190
|
process.stderr.write(` PID: ${pid}\n`);
|
|
475
191
|
process.stderr.write(` Port: ${options.port}\n`);
|
|
476
192
|
process.stderr.write(` Log: ${daemon.getLogFile("gateway")}\n\n`);
|
|
477
193
|
process.stderr.write(` Stop: linkshell gateway stop\n`);
|
|
478
194
|
process.stderr.write(` Status: linkshell gateway status\n`);
|
|
479
|
-
process.stderr.write(
|
|
480
|
-
` Logs: tail -f ${daemon.getLogFile("gateway")}\n\n`,
|
|
481
|
-
);
|
|
195
|
+
process.stderr.write(` Logs: tail -f ${daemon.getLogFile("gateway")}\n\n`);
|
|
482
196
|
return;
|
|
483
197
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
const { startEmbeddedGateway } =
|
|
487
|
-
await import("@linkshell/gateway/embedded");
|
|
488
|
-
const port = Number(options.port);
|
|
489
|
-
const { join } = await import("node:path");
|
|
490
|
-
(await import("./commands/host.js")).silenceSqliteWarning();
|
|
491
|
-
const gw = await startEmbeddedGateway({
|
|
492
|
-
port,
|
|
493
|
-
logLevel: options.logLevel,
|
|
494
|
-
silent: false,
|
|
495
|
-
// v2 pairings live here; keep this file to keep phones paired.
|
|
496
|
-
relayDataPath: join(daemon.linkshellDir(), "relay.db"),
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
daemon.savePid("gateway", process.pid);
|
|
500
|
-
|
|
501
|
-
process.stderr.write(`\n LinkShell Gateway ${pkg.version}\n`);
|
|
502
|
-
process.stderr.write(` Listening on http://0.0.0.0:${gw.port}\n`);
|
|
503
|
-
process.stderr.write(` PID: ${process.pid}\n`);
|
|
504
|
-
process.stderr.write(` Log level: ${options.logLevel}\n\n`);
|
|
505
|
-
process.stderr.write(` Computers (v2): linkshell host --gateway ws://this-server:${gw.port}\n`);
|
|
506
|
-
process.stderr.write(
|
|
507
|
-
` v1 clients: ws://this-server:${gw.port}/ws\n`,
|
|
508
|
-
);
|
|
509
|
-
process.stderr.write(
|
|
510
|
-
` Health check: curl http://your-server:${gw.port}/healthz\n\n`,
|
|
511
|
-
);
|
|
512
|
-
|
|
513
|
-
const shutdown = async () => {
|
|
514
|
-
process.stderr.write("[gateway] shutting down...\n");
|
|
515
|
-
daemon.removePid("gateway");
|
|
516
|
-
await gw.close();
|
|
517
|
-
process.exit(0);
|
|
518
|
-
};
|
|
519
|
-
process.on("SIGINT", shutdown);
|
|
520
|
-
process.on("SIGTERM", shutdown);
|
|
521
|
-
|
|
522
|
-
await new Promise(() => {});
|
|
198
|
+
const { runGatewayForeground } = await import("./commands/gateway.js");
|
|
199
|
+
await runGatewayForeground(pkg.version, Number(options.port));
|
|
523
200
|
});
|
|
524
201
|
|
|
525
202
|
gatewayCmd
|
|
@@ -562,7 +239,7 @@ program
|
|
|
562
239
|
await runScreenSetup({ check: options.check === true });
|
|
563
240
|
});
|
|
564
241
|
|
|
565
|
-
// ── stop (
|
|
242
|
+
// ── stop (the host and the gateway server) ──────────────────────────
|
|
566
243
|
|
|
567
244
|
program
|
|
568
245
|
.command("stop")
|
|
@@ -570,10 +247,11 @@ program
|
|
|
570
247
|
.action(async () => {
|
|
571
248
|
const { stopDaemon } = await import("./utils/daemon.js");
|
|
572
249
|
const hostStopped = stopDaemon("host");
|
|
250
|
+
// A bridge left running by a 1.x install: nothing else here can end it.
|
|
573
251
|
const bridgeStopped = stopDaemon("bridge");
|
|
574
252
|
const gatewayStopped = stopDaemon("gateway");
|
|
575
253
|
if (hostStopped) process.stderr.write(" Host stopped.\n");
|
|
576
|
-
if (bridgeStopped) process.stderr.write("
|
|
254
|
+
if (bridgeStopped) process.stderr.write(" 1.x bridge stopped.\n");
|
|
577
255
|
if (gatewayStopped) process.stderr.write(" Gateway server stopped.\n");
|
|
578
256
|
if (!hostStopped && !bridgeStopped && !gatewayStopped) {
|
|
579
257
|
process.stderr.write(" No running processes found.\n");
|
|
@@ -586,18 +264,13 @@ program
|
|
|
586
264
|
.command("status")
|
|
587
265
|
.description("Show the host, its gateway connection, and anything else LinkShell runs here")
|
|
588
266
|
.action(async () => {
|
|
589
|
-
const { readPid, getLogFile
|
|
267
|
+
const { readPid, getLogFile } = await import("./utils/daemon.js");
|
|
590
268
|
const { printHostStatus, assertHostRuntime } = await import("./commands/host.js");
|
|
591
269
|
assertHostRuntime();
|
|
592
270
|
process.stdout.write("\n");
|
|
593
271
|
await printHostStatus();
|
|
594
272
|
const gatewayPid = readPid("gateway");
|
|
595
273
|
if (gatewayPid) process.stdout.write(` Gateway server: running (PID ${gatewayPid}), log ${getLogFile("gateway")}\n`);
|
|
596
|
-
const bridgePid = readPid("bridge");
|
|
597
|
-
if (bridgePid) {
|
|
598
|
-
const keepAwake = readMetadata("bridge")?.keepAwake ? ", keeps this computer awake" : "";
|
|
599
|
-
process.stdout.write(` v1 bridge: running (PID ${bridgePid}${keepAwake}), log ${getLogFile("bridge")}\n`);
|
|
600
|
-
}
|
|
601
274
|
process.stdout.write("\n");
|
|
602
275
|
});
|
|
603
276
|
|
|
@@ -645,94 +318,6 @@ program
|
|
|
645
318
|
await runLogout();
|
|
646
319
|
});
|
|
647
320
|
|
|
648
|
-
program
|
|
649
|
-
.command("list")
|
|
650
|
-
.description("List your v1 sessions on official gateways")
|
|
651
|
-
.action(async () => {
|
|
652
|
-
const { getValidToken, loadAuth, SUPABASE_URL, SUPABASE_ANON_KEY } =
|
|
653
|
-
await import("./auth.js");
|
|
654
|
-
const token = await getValidToken();
|
|
655
|
-
if (!token) {
|
|
656
|
-
process.stderr.write(
|
|
657
|
-
"\n Not logged in. Run: linkshell login\n\n",
|
|
658
|
-
);
|
|
659
|
-
return;
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
const auth = loadAuth();
|
|
663
|
-
process.stderr.write(`\n Logged in as ${auth?.email || auth?.userId || "unknown"}\n\n`);
|
|
664
|
-
|
|
665
|
-
// Fetch official gateways
|
|
666
|
-
let gateways: { url: string; name: string; region: string | null }[] = [];
|
|
667
|
-
try {
|
|
668
|
-
const res = await fetch(
|
|
669
|
-
`${SUPABASE_URL}/rest/v1/linkshell_official_gateways?enabled=eq.true&select=url,name,region`,
|
|
670
|
-
{
|
|
671
|
-
headers: {
|
|
672
|
-
Authorization: `Bearer ${token}`,
|
|
673
|
-
apikey: SUPABASE_ANON_KEY,
|
|
674
|
-
},
|
|
675
|
-
signal: AbortSignal.timeout(10_000),
|
|
676
|
-
},
|
|
677
|
-
);
|
|
678
|
-
if (res.ok) {
|
|
679
|
-
gateways = (await res.json()) as typeof gateways;
|
|
680
|
-
}
|
|
681
|
-
} catch {}
|
|
682
|
-
|
|
683
|
-
if (gateways.length === 0) {
|
|
684
|
-
process.stderr.write(" No official gateways available.\n\n");
|
|
685
|
-
return;
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
process.stderr.write(" Official Gateways:\n\n");
|
|
689
|
-
|
|
690
|
-
for (const gw of gateways) {
|
|
691
|
-
const label = gw.region ? `${gw.name} (${gw.region})` : gw.name;
|
|
692
|
-
// Fetch user's sessions on this gateway
|
|
693
|
-
let sessions: {
|
|
694
|
-
id: string;
|
|
695
|
-
provider: string | null;
|
|
696
|
-
projectName: string | null;
|
|
697
|
-
hasHost: boolean;
|
|
698
|
-
lastActivity: number;
|
|
699
|
-
}[] = [];
|
|
700
|
-
try {
|
|
701
|
-
const res = await fetch(`${gw.url}/sessions/mine`, {
|
|
702
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
703
|
-
signal: AbortSignal.timeout(5_000),
|
|
704
|
-
});
|
|
705
|
-
if (res.ok) {
|
|
706
|
-
const body = (await res.json()) as { sessions: typeof sessions };
|
|
707
|
-
sessions = body.sessions;
|
|
708
|
-
}
|
|
709
|
-
} catch {}
|
|
710
|
-
|
|
711
|
-
process.stderr.write(` \x1b[32m✓\x1b[0m ${label}\n`);
|
|
712
|
-
const wsUrl = gw.url.replace(/\/$/, "").replace(/^https:/, "wss:").replace(/^http:/, "ws:") + "/ws";
|
|
713
|
-
process.stderr.write(` ${wsUrl}\n`);
|
|
714
|
-
|
|
715
|
-
if (sessions.length === 0) {
|
|
716
|
-
process.stderr.write(" (no active sessions)\n\n");
|
|
717
|
-
} else {
|
|
718
|
-
for (const s of sessions) {
|
|
719
|
-
const ago = Math.round((Date.now() - s.lastActivity) / 60_000);
|
|
720
|
-
const agoStr = ago < 1 ? "just now" : `${ago}m ago`;
|
|
721
|
-
const info = [s.provider, s.projectName].filter(Boolean).join(" · ");
|
|
722
|
-
const hostIcon = s.hasHost ? "\x1b[32m●\x1b[0m" : "\x1b[31m●\x1b[0m";
|
|
723
|
-
process.stderr.write(
|
|
724
|
-
` └ ${hostIcon} ${s.id.slice(0, 8)} — ${info || "unknown"} · ${agoStr}\n`,
|
|
725
|
-
);
|
|
726
|
-
}
|
|
727
|
-
process.stderr.write("\n");
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
process.stderr.write(
|
|
732
|
-
" Connect: linkshell start --gateway <url>\n\n",
|
|
733
|
-
);
|
|
734
|
-
});
|
|
735
|
-
|
|
736
321
|
// `linkshell` by itself, on a computer where nothing has been set up yet: the setup, rather than a page of help.
|
|
737
322
|
program.action(async () => {
|
|
738
323
|
const { setupDone } = await import("./commands/setup.js");
|
package/src/utils/daemon.ts
CHANGED
|
@@ -5,13 +5,9 @@ import { spawn } from "node:child_process";
|
|
|
5
5
|
|
|
6
6
|
const LINKSHELL_DIR = process.env.LINKSHELL_HOME || join(homedir(), ".linkshell");
|
|
7
7
|
|
|
8
|
+
// "bridge" is the 1.x daemon: nothing starts one any more, but `linkshell stop` still ends one left running.
|
|
8
9
|
type ServiceName = "gateway" | "bridge" | "host";
|
|
9
10
|
|
|
10
|
-
export interface ServiceMetadata {
|
|
11
|
-
keepAwake?: boolean;
|
|
12
|
-
startedAt?: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
11
|
function pidFile(service: ServiceName): string {
|
|
16
12
|
return join(LINKSHELL_DIR, `${service}.pid`);
|
|
17
13
|
}
|
|
@@ -20,6 +16,7 @@ function logFile(service: ServiceName): string {
|
|
|
20
16
|
return join(LINKSHELL_DIR, `${service}.log`);
|
|
21
17
|
}
|
|
22
18
|
|
|
19
|
+
/** 1.x kept a `<service>.json` beside its pid file; removed with it. */
|
|
23
20
|
function metadataFile(service: ServiceName): string {
|
|
24
21
|
return join(LINKSHELL_DIR, `${service}.json`);
|
|
25
22
|
}
|
|
@@ -61,28 +58,6 @@ export function getLogFile(service: ServiceName): string {
|
|
|
61
58
|
return logFile(service);
|
|
62
59
|
}
|
|
63
60
|
|
|
64
|
-
export function getPidFile(service: ServiceName): string {
|
|
65
|
-
return pidFile(service);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function saveMetadata(
|
|
69
|
-
service: ServiceName,
|
|
70
|
-
metadata: ServiceMetadata,
|
|
71
|
-
): void {
|
|
72
|
-
mkdirSync(LINKSHELL_DIR, { recursive: true });
|
|
73
|
-
writeFileSync(metadataFile(service), JSON.stringify(metadata), "utf8");
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function readMetadata(service: ServiceName): ServiceMetadata | null {
|
|
77
|
-
try {
|
|
78
|
-
const file = metadataFile(service);
|
|
79
|
-
if (!existsSync(file)) return null;
|
|
80
|
-
return JSON.parse(readFileSync(file, "utf8")) as ServiceMetadata;
|
|
81
|
-
} catch {
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
61
|
export function spawnDaemon(service: ServiceName, args: string[]): number {
|
|
87
62
|
mkdirSync(LINKSHELL_DIR, { recursive: true });
|
|
88
63
|
const log = logFile(service);
|
package/dist/cli/src/config.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { ProviderName } from "./providers.js";
|
|
2
|
-
export interface LinkShellConfig {
|
|
3
|
-
gateway?: string;
|
|
4
|
-
pairingGateway?: string;
|
|
5
|
-
provider?: ProviderName;
|
|
6
|
-
command?: string;
|
|
7
|
-
clientName?: string;
|
|
8
|
-
hostname?: string;
|
|
9
|
-
cols?: number;
|
|
10
|
-
rows?: number;
|
|
11
|
-
}
|
|
12
|
-
export declare function loadConfig(): LinkShellConfig;
|
|
13
|
-
export declare function saveConfig(config: LinkShellConfig): void;
|
|
14
|
-
export declare function getConfigPath(): string;
|
package/dist/cli/src/config.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
const CONFIG_DIR = process.env.LINKSHELL_HOME || join(homedir(), ".linkshell");
|
|
5
|
-
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
6
|
-
export function loadConfig() {
|
|
7
|
-
try {
|
|
8
|
-
if (!existsSync(CONFIG_FILE))
|
|
9
|
-
return {};
|
|
10
|
-
return JSON.parse(readFileSync(CONFIG_FILE, "utf8"));
|
|
11
|
-
}
|
|
12
|
-
catch {
|
|
13
|
-
return {};
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
export function saveConfig(config) {
|
|
17
|
-
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
18
|
-
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
19
|
-
}
|
|
20
|
-
export function getConfigPath() {
|
|
21
|
-
return CONFIG_FILE;
|
|
22
|
-
}
|
|
23
|
-
//# sourceMappingURL=config.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAclC,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,CAAC,CAAC;AAC/E,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAEpD,MAAM,UAAU,UAAU;IACxB,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;YAAE,OAAO,EAAE,CAAC;QACxC,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAoB,CAAC;IAC1E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAuB;IAChD,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;AAC7E,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,WAAW,CAAC;AACrB,CAAC"}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { homedir, hostname } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
const LINKSHELL_DIR = ".linkshell";
|
|
6
|
-
const MACHINE_FILE = "machine.json";
|
|
7
|
-
function machineFilePath(homeDir = homedir()) {
|
|
8
|
-
return join(homeDir, LINKSHELL_DIR, MACHINE_FILE);
|
|
9
|
-
}
|
|
10
|
-
function parseMachineIdentity(raw) {
|
|
11
|
-
try {
|
|
12
|
-
const parsed = JSON.parse(raw);
|
|
13
|
-
if (typeof parsed.machineId === "string" &&
|
|
14
|
-
parsed.machineId.trim() &&
|
|
15
|
-
typeof parsed.hostname === "string" &&
|
|
16
|
-
parsed.hostname.trim() &&
|
|
17
|
-
typeof parsed.createdAt === "string" &&
|
|
18
|
-
parsed.createdAt.trim()) {
|
|
19
|
-
return {
|
|
20
|
-
machineId: parsed.machineId,
|
|
21
|
-
hostname: parsed.hostname,
|
|
22
|
-
createdAt: parsed.createdAt,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
// Ignore malformed machine files and recreate below.
|
|
28
|
-
}
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
export function loadOrCreateMachineIdentity(homeDir = homedir()) {
|
|
32
|
-
const filePath = machineFilePath(homeDir);
|
|
33
|
-
if (existsSync(filePath)) {
|
|
34
|
-
const existing = parseMachineIdentity(readFileSync(filePath, "utf8"));
|
|
35
|
-
if (existing)
|
|
36
|
-
return existing;
|
|
37
|
-
}
|
|
38
|
-
const identity = {
|
|
39
|
-
machineId: randomUUID(),
|
|
40
|
-
hostname: hostname(),
|
|
41
|
-
createdAt: new Date().toISOString(),
|
|
42
|
-
};
|
|
43
|
-
mkdirSync(join(homeDir, LINKSHELL_DIR), { recursive: true, mode: 0o700 });
|
|
44
|
-
writeFileSync(filePath, `${JSON.stringify(identity, null, 2)}\n`, {
|
|
45
|
-
encoding: "utf8",
|
|
46
|
-
mode: 0o600,
|
|
47
|
-
});
|
|
48
|
-
return identity;
|
|
49
|
-
}
|
|
50
|
-
//# sourceMappingURL=machine-id.js.map
|