linkshell-cli 0.6.1 → 0.6.3
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 +47 -132
- package/dist/cli/src/auth.js +1 -1
- package/dist/cli/src/auth.js.map +1 -1
- package/dist/cli/src/commands/doctor.d.ts +1 -1
- package/dist/cli/src/commands/doctor.js +76 -36
- package/dist/cli/src/commands/doctor.js.map +1 -1
- package/dist/cli/src/commands/host.d.ts +15 -0
- package/dist/cli/src/commands/host.js +101 -20
- package/dist/cli/src/commands/host.js.map +1 -1
- package/dist/cli/src/commands/logout.d.ts +1 -1
- package/dist/cli/src/commands/logout.js +10 -2
- package/dist/cli/src/commands/logout.js.map +1 -1
- package/dist/cli/src/commands/upgrade.js +11 -0
- package/dist/cli/src/commands/upgrade.js.map +1 -1
- package/dist/cli/src/config.js +1 -1
- package/dist/cli/src/config.js.map +1 -1
- package/dist/cli/src/index.js +105 -120
- package/dist/cli/src/index.js.map +1 -1
- package/dist/cli/src/utils/daemon.d.ts +2 -0
- package/dist/cli/src/utils/daemon.js +4 -0
- package/dist/cli/src/utils/daemon.js.map +1 -1
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/dist/host/src/carry.d.ts +15 -0
- package/dist/host/src/carry.js +83 -0
- package/dist/host/src/carry.js.map +1 -0
- package/dist/host/src/drivers/acp/driver.d.ts +21 -3
- package/dist/host/src/drivers/acp/driver.js +99 -13
- package/dist/host/src/drivers/acp/driver.js.map +1 -1
- package/dist/host/src/drivers/acp/mapper.d.ts +5 -1
- package/dist/host/src/drivers/acp/mapper.js +58 -6
- package/dist/host/src/drivers/acp/mapper.js.map +1 -1
- package/dist/host/src/drivers/claude/driver.d.ts +76 -8
- package/dist/host/src/drivers/claude/driver.js +510 -29
- package/dist/host/src/drivers/claude/driver.js.map +1 -1
- package/dist/host/src/drivers/claude/holders.d.ts +9 -0
- package/dist/host/src/drivers/claude/holders.js +61 -0
- package/dist/host/src/drivers/claude/holders.js.map +1 -0
- package/dist/host/src/drivers/claude/transcript.d.ts +105 -61
- package/dist/host/src/drivers/claude/transcript.js +260 -22
- package/dist/host/src/drivers/claude/transcript.js.map +1 -1
- package/dist/host/src/drivers/codex/commands.d.ts +27 -0
- package/dist/host/src/drivers/codex/commands.js +23 -0
- package/dist/host/src/drivers/codex/commands.js.map +1 -0
- package/dist/host/src/drivers/codex/driver.d.ts +21 -1
- package/dist/host/src/drivers/codex/driver.js +140 -1
- package/dist/host/src/drivers/codex/driver.js.map +1 -1
- package/dist/host/src/drivers/codex/mapper.d.ts +4 -0
- package/dist/host/src/drivers/codex/mapper.js +29 -5
- package/dist/host/src/drivers/codex/mapper.js.map +1 -1
- package/dist/host/src/drivers/types.d.ts +45 -4
- package/dist/host/src/fs.d.ts +26 -4
- package/dist/host/src/fs.js +71 -17
- package/dist/host/src/fs.js.map +1 -1
- package/dist/host/src/gateway.d.ts +3 -0
- package/dist/host/src/gateway.js +10 -1
- package/dist/host/src/gateway.js.map +1 -1
- package/dist/host/src/host.d.ts +7 -3
- package/dist/host/src/host.js +53 -21
- package/dist/host/src/host.js.map +1 -1
- package/dist/host/src/hub.d.ts +98 -14
- package/dist/host/src/hub.js +639 -27
- package/dist/host/src/hub.js.map +1 -1
- package/dist/host/src/index.d.ts +1 -1
- package/dist/host/src/index.js +1 -1
- package/dist/host/src/index.js.map +1 -1
- package/dist/host/src/legacy.d.ts +7 -0
- package/dist/host/src/legacy.js +36 -0
- package/dist/host/src/legacy.js.map +1 -0
- package/dist/host/src/rpc/server.d.ts +4 -2
- package/dist/host/src/rpc/server.js +62 -5
- package/dist/host/src/rpc/server.js.map +1 -1
- package/dist/host/src/screen.d.ts +11 -0
- package/dist/host/src/screen.js +54 -2
- package/dist/host/src/screen.js.map +1 -1
- package/dist/host/src/shell-env.d.ts +6 -0
- package/dist/host/src/shell-env.js +48 -0
- package/dist/host/src/shell-env.js.map +1 -1
- package/dist/host/src/slim.d.ts +17 -0
- package/dist/host/src/slim.js +66 -0
- package/dist/host/src/slim.js.map +1 -0
- package/dist/host/src/store.d.ts +81 -3
- package/dist/host/src/store.js +249 -16
- package/dist/host/src/store.js.map +1 -1
- package/dist/host/src/terminals.d.ts +4 -4
- package/dist/host/src/terminals.js +19 -4
- package/dist/host/src/terminals.js.map +1 -1
- package/dist/host/src/worktrees.d.ts +36 -0
- package/dist/host/src/worktrees.js +96 -0
- package/dist/host/src/worktrees.js.map +1 -0
- package/dist/wire/src/model.d.ts +334 -139
- package/dist/wire/src/model.js +68 -0
- package/dist/wire/src/model.js.map +1 -1
- package/dist/wire/src/relay-client.d.ts +2 -0
- package/dist/wire/src/relay-client.js +12 -0
- package/dist/wire/src/relay-client.js.map +1 -1
- package/dist/wire/src/rpc.d.ts +7273 -890
- package/dist/wire/src/rpc.js +154 -6
- package/dist/wire/src/rpc.js.map +1 -1
- package/dist/wire/src/updates.d.ts +498 -498
- package/package.json +5 -5
- package/src/auth.ts +1 -1
- package/src/commands/doctor.ts +76 -34
- package/src/commands/host.ts +109 -19
- package/src/commands/logout.ts +10 -2
- package/src/commands/upgrade.ts +11 -0
- package/src/config.ts +1 -1
- package/src/index.ts +104 -137
- package/src/utils/daemon.ts +5 -0
package/src/index.ts
CHANGED
|
@@ -51,7 +51,7 @@ const program = new Command();
|
|
|
51
51
|
program
|
|
52
52
|
.name("linkshell")
|
|
53
53
|
.description(
|
|
54
|
-
"
|
|
54
|
+
"Your coding agents and terminals, on your phone. Run `linkshell host --daemon`, then `linkshell pair` (or `linkshell login` for the official gateway).",
|
|
55
55
|
)
|
|
56
56
|
.version(pkg.version);
|
|
57
57
|
|
|
@@ -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 } =
|
|
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.
|
|
@@ -133,7 +201,7 @@ for (const [agent, description] of [
|
|
|
133
201
|
|
|
134
202
|
program
|
|
135
203
|
.command("start")
|
|
136
|
-
.description("Start a bridge session (with built-in or remote gateway)")
|
|
204
|
+
.description("Start a v1 bridge session (for the v1 app; with built-in or remote gateway)")
|
|
137
205
|
.option(
|
|
138
206
|
"--gateway <url>",
|
|
139
207
|
"Gateway websocket URL (omit to start built-in gateway)",
|
|
@@ -411,20 +479,25 @@ const gatewayCmd = program
|
|
|
411
479
|
const { startEmbeddedGateway } =
|
|
412
480
|
await import("@linkshell/gateway/embedded");
|
|
413
481
|
const port = Number(options.port);
|
|
482
|
+
const { join } = await import("node:path");
|
|
483
|
+
(await import("./commands/host.js")).silenceSqliteWarning();
|
|
414
484
|
const gw = await startEmbeddedGateway({
|
|
415
485
|
port,
|
|
416
486
|
logLevel: options.logLevel,
|
|
417
487
|
silent: false,
|
|
488
|
+
// v2 pairings live here; keep this file to keep phones paired.
|
|
489
|
+
relayDataPath: join(daemon.linkshellDir(), "relay.db"),
|
|
418
490
|
});
|
|
419
491
|
|
|
420
492
|
daemon.savePid("gateway", process.pid);
|
|
421
493
|
|
|
422
|
-
process.stderr.write(`\n LinkShell Gateway
|
|
494
|
+
process.stderr.write(`\n LinkShell Gateway ${pkg.version}\n`);
|
|
423
495
|
process.stderr.write(` Listening on http://0.0.0.0:${gw.port}\n`);
|
|
424
496
|
process.stderr.write(` PID: ${process.pid}\n`);
|
|
425
497
|
process.stderr.write(` Log level: ${options.logLevel}\n\n`);
|
|
498
|
+
process.stderr.write(` Computers (v2): linkshell host --gateway ws://this-server:${gw.port}\n`);
|
|
426
499
|
process.stderr.write(
|
|
427
|
-
`
|
|
500
|
+
` v1 clients: ws://this-server:${gw.port}/ws\n`,
|
|
428
501
|
);
|
|
429
502
|
process.stderr.write(
|
|
430
503
|
` Health check: curl http://your-server:${gw.port}/healthz\n\n`,
|
|
@@ -476,11 +549,13 @@ program
|
|
|
476
549
|
.description("Stop all running LinkShell processes")
|
|
477
550
|
.action(async () => {
|
|
478
551
|
const { stopDaemon } = await import("./utils/daemon.js");
|
|
552
|
+
const hostStopped = stopDaemon("host");
|
|
479
553
|
const bridgeStopped = stopDaemon("bridge");
|
|
480
554
|
const gatewayStopped = stopDaemon("gateway");
|
|
481
|
-
if (
|
|
482
|
-
if (
|
|
483
|
-
if (
|
|
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) {
|
|
484
559
|
process.stderr.write(" No running processes found.\n");
|
|
485
560
|
}
|
|
486
561
|
});
|
|
@@ -489,47 +564,36 @@ program
|
|
|
489
564
|
|
|
490
565
|
program
|
|
491
566
|
.command("status")
|
|
492
|
-
.description("Show
|
|
567
|
+
.description("Show the host, its gateway connection, and anything else LinkShell runs here")
|
|
493
568
|
.action(async () => {
|
|
494
|
-
const { readPid, getLogFile, readMetadata } = await import(
|
|
495
|
-
|
|
496
|
-
);
|
|
497
|
-
|
|
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();
|
|
498
574
|
const gatewayPid = readPid("gateway");
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
process.stderr.write("\n");
|
|
575
|
+
if (gatewayPid) process.stdout.write(` Gateway server: running (PID ${gatewayPid}), log ${getLogFile("gateway")}\n`);
|
|
576
|
+
const bridgePid = readPid("bridge");
|
|
502
577
|
if (bridgePid) {
|
|
503
|
-
|
|
504
|
-
process.
|
|
505
|
-
` Keep awake: ${bridgeMeta?.keepAwake ? "enabled" : "disabled"}\n`,
|
|
506
|
-
);
|
|
507
|
-
process.stderr.write(` Log: ${getLogFile("bridge")}\n`);
|
|
508
|
-
} else {
|
|
509
|
-
process.stderr.write(" Bridge: not running\n");
|
|
510
|
-
}
|
|
511
|
-
if (gatewayPid) {
|
|
512
|
-
process.stderr.write(` Gateway: running (PID ${gatewayPid})\n`);
|
|
513
|
-
process.stderr.write(` Log: ${getLogFile("gateway")}\n`);
|
|
514
|
-
} else {
|
|
515
|
-
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`);
|
|
516
580
|
}
|
|
517
|
-
process.
|
|
581
|
+
process.stdout.write("\n");
|
|
518
582
|
});
|
|
519
583
|
|
|
520
584
|
// ── doctor / setup ──────────────────────────────────────────────────
|
|
521
585
|
|
|
522
586
|
program
|
|
523
587
|
.command("doctor")
|
|
524
|
-
.description("Check
|
|
525
|
-
.option("--gateway <url>", "
|
|
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")
|
|
526
590
|
.action(async (options) => {
|
|
527
|
-
await runDoctor(options.gateway);
|
|
591
|
+
await runDoctor(pkg.version, options.gateway);
|
|
528
592
|
});
|
|
529
593
|
|
|
530
594
|
program
|
|
531
595
|
.command("setup")
|
|
532
|
-
.description("
|
|
596
|
+
.description("Configure the v1 bridge (`linkshell start`)")
|
|
533
597
|
.action(async () => {
|
|
534
598
|
await runSetup();
|
|
535
599
|
});
|
|
@@ -543,123 +607,26 @@ program
|
|
|
543
607
|
|
|
544
608
|
program
|
|
545
609
|
.command("login")
|
|
546
|
-
.description("Log in to LinkShell (
|
|
610
|
+
.description("Log in to LinkShell (Pro: the official gateway, no pairing needed)")
|
|
547
611
|
.action(async () => {
|
|
548
612
|
const result = await runLogin();
|
|
549
613
|
if (!result) return;
|
|
550
614
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
" Upgrade to Pro for official gateway access: https://itool.tech\n\n",
|
|
554
|
-
);
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
// Pro user — fetch official gateways and offer to connect
|
|
559
|
-
const { SUPABASE_URL, SUPABASE_ANON_KEY } = await import("./auth.js");
|
|
560
|
-
let gateways: { url: string; name: string; region: string | null }[] = [];
|
|
561
|
-
try {
|
|
562
|
-
const res = await fetch(
|
|
563
|
-
`${SUPABASE_URL}/rest/v1/linkshell_official_gateways?enabled=eq.true&select=url,name,region`,
|
|
564
|
-
{
|
|
565
|
-
headers: {
|
|
566
|
-
Authorization: `Bearer ${result.accessToken}`,
|
|
567
|
-
apikey: SUPABASE_ANON_KEY,
|
|
568
|
-
},
|
|
569
|
-
signal: AbortSignal.timeout(10_000),
|
|
570
|
-
},
|
|
571
|
-
);
|
|
572
|
-
if (res.ok) {
|
|
573
|
-
gateways = (await res.json()) as typeof gateways;
|
|
574
|
-
}
|
|
575
|
-
} catch {}
|
|
576
|
-
|
|
577
|
-
if (gateways.length === 0) {
|
|
578
|
-
process.stderr.write(" No official gateways available yet.\n\n");
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
process.stderr.write(" Available gateways:\n\n");
|
|
583
|
-
for (let i = 0; i < gateways.length; i++) {
|
|
584
|
-
const gw = gateways[i]!;
|
|
585
|
-
const label = gw.region ? `${gw.name} (${gw.region})` : gw.name;
|
|
586
|
-
process.stderr.write(` [${i + 1}] ${label} ${gw.url}\n`);
|
|
587
|
-
}
|
|
588
|
-
process.stderr.write(` [0] Skip\n\n`);
|
|
589
|
-
|
|
590
|
-
const { createInterface } = await import("node:readline");
|
|
591
|
-
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
592
|
-
const answer = await new Promise<string>((res) => {
|
|
593
|
-
rl.question(" Connect to gateway [1]: ", (ans) => {
|
|
594
|
-
rl.close();
|
|
595
|
-
res(ans.trim());
|
|
596
|
-
});
|
|
597
|
-
});
|
|
598
|
-
|
|
599
|
-
if (answer === "0") return;
|
|
600
|
-
|
|
601
|
-
const idx = (answer === "" ? 1 : Number(answer)) - 1;
|
|
602
|
-
if (idx < 0 || idx >= gateways.length || Number.isNaN(idx)) {
|
|
603
|
-
process.stderr.write(" Invalid choice.\n\n");
|
|
604
|
-
return;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
const chosen = gateways[idx]!;
|
|
608
|
-
const gwWsUrl = chosen.url.replace(/\/$/, "").replace(/^https:/, "wss:").replace(/^http:/, "ws:") + "/ws";
|
|
609
|
-
|
|
610
|
-
process.stderr.write(`\n Connecting to ${chosen.name}...\n`);
|
|
611
|
-
|
|
612
|
-
// Start daemon with the chosen gateway
|
|
613
|
-
const daemon = await import("./utils/daemon.js");
|
|
614
|
-
const keepAwake = shouldKeepAwake(undefined);
|
|
615
|
-
const existingPid = daemon.readPid("bridge");
|
|
616
|
-
if (existingPid) {
|
|
617
|
-
process.stderr.write(
|
|
618
|
-
` Bridge already running (PID ${existingPid}). Run: linkshell stop\n\n`,
|
|
619
|
-
);
|
|
620
|
-
return;
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
const childArgs = [
|
|
624
|
-
"start",
|
|
625
|
-
"--_foreground-bridge",
|
|
626
|
-
"--gateway", gwWsUrl,
|
|
627
|
-
"--provider", "custom",
|
|
628
|
-
"--client-name", config.clientName ?? "local-cli",
|
|
629
|
-
"--cols", String(config.cols ?? 120),
|
|
630
|
-
"--rows", String(config.rows ?? 36),
|
|
631
|
-
"--screen",
|
|
632
|
-
"--agent-ui",
|
|
633
|
-
];
|
|
634
|
-
if (config.command) childArgs.push("--command", config.command);
|
|
635
|
-
if (config.hostname) childArgs.push("--hostname", config.hostname);
|
|
636
|
-
if (!keepAwake) childArgs.push("--no-keep-awake");
|
|
637
|
-
|
|
638
|
-
const pid = daemon.spawnDaemon("bridge", childArgs);
|
|
639
|
-
daemon.saveMetadata("bridge", { keepAwake, startedAt: Date.now() });
|
|
640
|
-
process.stderr.write(`\n \x1b[32m✓\x1b[0m Bridge started in background (PID ${pid})\n`);
|
|
641
|
-
process.stderr.write(` Gateway: ${chosen.name}\n`);
|
|
642
|
-
process.stderr.write(` Provider: custom (shell)\n`);
|
|
643
|
-
process.stderr.write(` Keep awake: ${keepAwake ? "enabled" : "disabled"}\n`);
|
|
644
|
-
process.stderr.write(` Desktop: enabled\n`);
|
|
645
|
-
process.stderr.write(` Agent GUI: enabled (Codex / Claude auto-detect)\n`);
|
|
646
|
-
process.stderr.write(` Open the LinkShell app on your phone to connect.\n\n`);
|
|
647
|
-
process.stderr.write(` Stop: linkshell stop\n`);
|
|
648
|
-
process.stderr.write(` Status: linkshell status\n`);
|
|
649
|
-
process.stderr.write(` Logs: tail -f ${daemon.getLogFile("bridge")}\n\n`);
|
|
615
|
+
const { joinGatewayAfterLogin } = await import("./commands/host.js");
|
|
616
|
+
await joinGatewayAfterLogin(result.plan);
|
|
650
617
|
process.exit(0);
|
|
651
618
|
});
|
|
652
619
|
|
|
653
620
|
program
|
|
654
621
|
.command("logout")
|
|
655
622
|
.description("Log out of LinkShell")
|
|
656
|
-
.action(() => {
|
|
657
|
-
runLogout();
|
|
623
|
+
.action(async () => {
|
|
624
|
+
await runLogout();
|
|
658
625
|
});
|
|
659
626
|
|
|
660
627
|
program
|
|
661
628
|
.command("list")
|
|
662
|
-
.description("List your sessions on official gateways")
|
|
629
|
+
.description("List your v1 sessions on official gateways")
|
|
663
630
|
.action(async () => {
|
|
664
631
|
const { getValidToken, loadAuth, SUPABASE_URL, SUPABASE_ANON_KEY } =
|
|
665
632
|
await import("./auth.js");
|
package/src/utils/daemon.ts
CHANGED
|
@@ -24,6 +24,11 @@ function metadataFile(service: ServiceName): string {
|
|
|
24
24
|
return join(LINKSHELL_DIR, `${service}.json`);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/** ~/.linkshell, or LINKSHELL_HOME. */
|
|
28
|
+
export function linkshellDir(): string {
|
|
29
|
+
return LINKSHELL_DIR;
|
|
30
|
+
}
|
|
31
|
+
|
|
27
32
|
export function savePid(service: ServiceName, pid: number): void {
|
|
28
33
|
mkdirSync(LINKSHELL_DIR, { recursive: true });
|
|
29
34
|
writeFileSync(pidFile(service), String(pid), "utf8");
|