linkshell-cli 0.6.2 → 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 +14 -0
- package/dist/cli/src/commands/host.js +94 -15
- 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 +94 -114
- package/dist/cli/src/index.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 +13 -3
- package/dist/host/src/drivers/acp/driver.js +43 -8
- 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 +53 -5
- package/dist/host/src/drivers/acp/mapper.js.map +1 -1
- package/dist/host/src/drivers/claude/driver.d.ts +53 -8
- package/dist/host/src/drivers/claude/driver.js +357 -40
- 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 +100 -62
- package/dist/host/src/drivers/claude/transcript.js +246 -26
- 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 +15 -1
- package/dist/host/src/drivers/codex/driver.js +110 -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 +34 -3
- 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 +9 -0
- package/dist/host/src/gateway.js.map +1 -1
- package/dist/host/src/host.d.ts +7 -3
- package/dist/host/src/host.js +42 -18
- package/dist/host/src/host.js.map +1 -1
- package/dist/host/src/hub.d.ts +91 -15
- package/dist/host/src/hub.js +556 -33
- package/dist/host/src/hub.js.map +1 -1
- package/dist/host/src/rpc/server.d.ts +4 -2
- package/dist/host/src/rpc/server.js +51 -6
- 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/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 +75 -3
- package/dist/host/src/store.js +216 -16
- package/dist/host/src/store.js.map +1 -1
- package/dist/host/src/terminals.d.ts +4 -4
- 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 +301 -153
- package/dist/wire/src/model.js +55 -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 +6740 -1022
- package/dist/wire/src/rpc.js +130 -7
- 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 +98 -14
- package/src/commands/logout.ts +10 -2
- package/src/commands/upgrade.ts +11 -0
- package/src/config.ts +1 -1
- 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 } =
|
|
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 (
|
|
487
|
-
if (
|
|
488
|
-
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) {
|
|
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
|
|
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
|
-
|
|
501
|
-
);
|
|
502
|
-
|
|
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
|
-
|
|
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
|
-
|
|
509
|
-
process.
|
|
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.
|
|
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
|
|
530
|
-
.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")
|
|
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("
|
|
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
|
-
|
|
557
|
-
|
|
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
|