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.
Files changed (93) hide show
  1. package/README.md +47 -132
  2. package/dist/cli/src/auth.js +1 -1
  3. package/dist/cli/src/auth.js.map +1 -1
  4. package/dist/cli/src/commands/doctor.d.ts +1 -1
  5. package/dist/cli/src/commands/doctor.js +76 -36
  6. package/dist/cli/src/commands/doctor.js.map +1 -1
  7. package/dist/cli/src/commands/host.d.ts +14 -0
  8. package/dist/cli/src/commands/host.js +94 -15
  9. package/dist/cli/src/commands/host.js.map +1 -1
  10. package/dist/cli/src/commands/logout.d.ts +1 -1
  11. package/dist/cli/src/commands/logout.js +10 -2
  12. package/dist/cli/src/commands/logout.js.map +1 -1
  13. package/dist/cli/src/commands/upgrade.js +11 -0
  14. package/dist/cli/src/commands/upgrade.js.map +1 -1
  15. package/dist/cli/src/config.js +1 -1
  16. package/dist/cli/src/config.js.map +1 -1
  17. package/dist/cli/src/index.js +94 -114
  18. package/dist/cli/src/index.js.map +1 -1
  19. package/dist/cli/tsconfig.tsbuildinfo +1 -1
  20. package/dist/host/src/carry.d.ts +15 -0
  21. package/dist/host/src/carry.js +83 -0
  22. package/dist/host/src/carry.js.map +1 -0
  23. package/dist/host/src/drivers/acp/driver.d.ts +13 -3
  24. package/dist/host/src/drivers/acp/driver.js +43 -8
  25. package/dist/host/src/drivers/acp/driver.js.map +1 -1
  26. package/dist/host/src/drivers/acp/mapper.d.ts +5 -1
  27. package/dist/host/src/drivers/acp/mapper.js +53 -5
  28. package/dist/host/src/drivers/acp/mapper.js.map +1 -1
  29. package/dist/host/src/drivers/claude/driver.d.ts +53 -8
  30. package/dist/host/src/drivers/claude/driver.js +357 -40
  31. package/dist/host/src/drivers/claude/driver.js.map +1 -1
  32. package/dist/host/src/drivers/claude/holders.d.ts +9 -0
  33. package/dist/host/src/drivers/claude/holders.js +61 -0
  34. package/dist/host/src/drivers/claude/holders.js.map +1 -0
  35. package/dist/host/src/drivers/claude/transcript.d.ts +100 -62
  36. package/dist/host/src/drivers/claude/transcript.js +246 -26
  37. package/dist/host/src/drivers/claude/transcript.js.map +1 -1
  38. package/dist/host/src/drivers/codex/commands.d.ts +27 -0
  39. package/dist/host/src/drivers/codex/commands.js +23 -0
  40. package/dist/host/src/drivers/codex/commands.js.map +1 -0
  41. package/dist/host/src/drivers/codex/driver.d.ts +15 -1
  42. package/dist/host/src/drivers/codex/driver.js +110 -1
  43. package/dist/host/src/drivers/codex/driver.js.map +1 -1
  44. package/dist/host/src/drivers/codex/mapper.d.ts +4 -0
  45. package/dist/host/src/drivers/codex/mapper.js +29 -5
  46. package/dist/host/src/drivers/codex/mapper.js.map +1 -1
  47. package/dist/host/src/drivers/types.d.ts +34 -3
  48. package/dist/host/src/fs.d.ts +26 -4
  49. package/dist/host/src/fs.js +71 -17
  50. package/dist/host/src/fs.js.map +1 -1
  51. package/dist/host/src/gateway.d.ts +3 -0
  52. package/dist/host/src/gateway.js +9 -0
  53. package/dist/host/src/gateway.js.map +1 -1
  54. package/dist/host/src/host.d.ts +7 -3
  55. package/dist/host/src/host.js +42 -18
  56. package/dist/host/src/host.js.map +1 -1
  57. package/dist/host/src/hub.d.ts +91 -15
  58. package/dist/host/src/hub.js +556 -33
  59. package/dist/host/src/hub.js.map +1 -1
  60. package/dist/host/src/rpc/server.d.ts +4 -2
  61. package/dist/host/src/rpc/server.js +51 -6
  62. package/dist/host/src/rpc/server.js.map +1 -1
  63. package/dist/host/src/screen.d.ts +11 -0
  64. package/dist/host/src/screen.js +54 -2
  65. package/dist/host/src/screen.js.map +1 -1
  66. package/dist/host/src/slim.d.ts +17 -0
  67. package/dist/host/src/slim.js +66 -0
  68. package/dist/host/src/slim.js.map +1 -0
  69. package/dist/host/src/store.d.ts +75 -3
  70. package/dist/host/src/store.js +216 -16
  71. package/dist/host/src/store.js.map +1 -1
  72. package/dist/host/src/terminals.d.ts +4 -4
  73. package/dist/host/src/worktrees.d.ts +36 -0
  74. package/dist/host/src/worktrees.js +96 -0
  75. package/dist/host/src/worktrees.js.map +1 -0
  76. package/dist/wire/src/model.d.ts +301 -153
  77. package/dist/wire/src/model.js +55 -0
  78. package/dist/wire/src/model.js.map +1 -1
  79. package/dist/wire/src/relay-client.d.ts +2 -0
  80. package/dist/wire/src/relay-client.js +12 -0
  81. package/dist/wire/src/relay-client.js.map +1 -1
  82. package/dist/wire/src/rpc.d.ts +6740 -1022
  83. package/dist/wire/src/rpc.js +130 -7
  84. package/dist/wire/src/rpc.js.map +1 -1
  85. package/dist/wire/src/updates.d.ts +498 -498
  86. package/package.json +5 -5
  87. package/src/auth.ts +1 -1
  88. package/src/commands/doctor.ts +76 -34
  89. package/src/commands/host.ts +98 -14
  90. package/src/commands/logout.ts +10 -2
  91. package/src/commands/upgrade.ts +11 -0
  92. package/src/config.ts +1 -1
  93. package/src/index.ts +93 -131
package/README.md CHANGED
@@ -1,165 +1,80 @@
1
1
  # linkshell-cli
2
2
 
3
- 在手机上远程查看和控制本地 Claude Code / Codex / Gemini / Copilot 终端会话,并提供 Agent Workspace 结构化对话界面。
3
+ Follow, steer and approve the coding agents running on your computer — from your phone. This is the computer side of [LinkShell](https://github.com/LiuTianjie/LinkShell): it runs Claude Code, Codex and other agents you already have installed, and keeps them reachable from the LinkShell app ([iPhone](https://apps.apple.com/cn/app/linkshell/id6761547516) · [Android](https://github.com/LiuTianjie/LinkShell/releases/latest)) over an end-to-end encrypted channel.
4
4
 
5
- ## 安装
5
+ [Website](https://liutianjie.github.io/LinkShell/) · [Docs](https://liutianjie.github.io/LinkShell/docs/) · [简体中文](https://github.com/LiuTianjie/LinkShell/blob/main/README_CN.md)
6
6
 
7
- ```bash
8
- npm install -g linkshell-cli
9
- ```
7
+ ## Install
10
8
 
11
- ## 一条命令开始
9
+ macOS or Linux, **Node.js 22.13 or newer**.
12
10
 
13
11
  ```bash
14
- linkshell start --daemon --provider claude
12
+ npm i -g linkshell-cli
13
+ linkshell host --daemon
15
14
  ```
16
15
 
17
- CLI 会在后台:
18
- 1. 启动内置 Gateway(端口 8787)
19
- 2. 检测局域网 IP
20
- 3. 创建配对并打印 QR 码
21
- 4. 手机扫码即连
16
+ Then connect your phone one of two ways:
22
17
 
23
- App 断开不影响后台进程,重新扫码即可恢复。
18
+ - **Pro — the official gateway.** `linkshell login`, then sign in to the app with the same account. The computer shows up by itself.
19
+ - **Your own gateway.** Run one (below), then:
24
20
 
25
- ## 命令一览
21
+ ```bash
22
+ linkshell host --gateway wss://gw.example.com --daemon
23
+ linkshell pair # scan the QR code in the app
24
+ ```
26
25
 
27
- | 命令 | 说明 |
28
- |------|------|
29
- | `linkshell start` | 启动桥接会话(支持 `--daemon` 后台运行) |
30
- | `linkshell stop` | 停止所有后台进程 |
31
- | `linkshell status` | 查看运行状态 |
32
- | `linkshell gateway` | 启动独立 Gateway(支持 `--daemon`) |
33
- | `linkshell gateway stop` | 停止后台 Gateway |
34
- | `linkshell gateway status` | 查看 Gateway 状态 |
35
- | `linkshell setup` | 交互式配置向导 |
36
- | `linkshell doctor` | 环境检查和连通性诊断 |
37
-
38
- ## 使用示例
26
+ Start sessions from the phone, or launch agents from your terminal so they can be handed over at any time:
39
27
 
40
28
  ```bash
41
- # 后台启动(推荐)
42
- linkshell start --daemon --provider claude
43
-
44
- # 启用远程桌面查看
45
- linkshell start --daemon --provider claude --screen
46
-
47
- # 前台启动
48
- linkshell start --provider claude
49
-
50
- # 桥接 Codex
51
- linkshell start --daemon --provider codex
52
-
53
- # 桥接 Gemini / GitHub Copilot
54
- linkshell start --daemon --provider gemini
55
- linkshell start --daemon --provider copilot
56
-
57
- # Agent Workspace(自动检测 Claude Code / Codex)
58
- linkshell start --daemon --agent-ui
59
-
60
- # 桥接任意命令
61
- linkshell start --daemon --provider custom --command bash
62
-
63
- # 指定端口
64
- linkshell start --daemon --provider claude --port 9000
65
-
66
- # 连接远程 Gateway(不启动内置 Gateway)
67
- linkshell start --daemon --gateway wss://your-server.com:8787/ws --provider claude
68
-
69
- # 查看状态和日志
70
- linkshell status
71
- tail -f ~/.linkshell/bridge.log
72
-
73
- # 停止
74
- linkshell stop
29
+ linkshell claude # Claude Code; a message from the phone takes over, any key here takes it back
30
+ linkshell codex # Codex; the terminal and the phone are live at the same time
75
31
  ```
76
32
 
77
- ## 服务器部署 Gateway
78
-
79
- ```bash
80
- # 后台启动独立 Gateway
81
- linkshell gateway --daemon --port 8787
33
+ ## Commands
82
34
 
83
- # 查看状态
84
- linkshell gateway status
35
+ | | |
36
+ | --- | --- |
37
+ | `linkshell host --daemon` | Start LinkShell in the background |
38
+ | `linkshell status` | The host, its agents, sessions and gateway connection |
39
+ | `linkshell host stop` | Stop the host (`linkshell stop` stops everything LinkShell runs here) |
40
+ | `linkshell host --gateway <url>` | Choose the gateway (`off` to disconnect, `default` for the official one); applies to a running host |
41
+ | `linkshell pair` | Pair a phone through your own gateway |
42
+ | `linkshell devices` | Paired phones; `linkshell devices remove <name>` unpairs one |
43
+ | `linkshell login` / `logout` | Pro account: join or leave the official gateway, without restarting the host |
44
+ | `linkshell claude` / `linkshell codex` | The agent's own UI, shareable with the phone (arguments pass through) |
45
+ | `linkshell gateway [--port 8787] [--daemon]` | Run a gateway |
46
+ | `linkshell doctor` | Check Node, agents, the host and its gateway |
47
+ | `linkshell upgrade` | Upgrade the CLI (restart the host afterwards to run the new version) |
85
48
 
86
- # 查看日志
87
- tail -f ~/.linkshell/gateway.log
49
+ ## Run your own gateway
88
50
 
89
- # 停止
90
- linkshell gateway stop
91
- ```
92
-
93
- ## start 选项
94
-
95
- ```
96
- --gateway <url> 远程 Gateway 地址(省略则启动内置 Gateway)
97
- --port <port> 内置 Gateway 端口(默认 8787)
98
- --pairing-gateway <url> QR 码中给手机使用的地址
99
- --provider <name> claude | codex | gemini | copilot | custom(默认 claude)
100
- --command <cmd> 自定义命令(custom provider 必填)
101
- --daemon 后台运行
102
- --session-id <id> 手动指定 session ID
103
- --client-name <name> 显示名称(默认 local-cli)
104
- --cols <n> 终端列数(默认 120)
105
- --rows <n> 终端行数(默认 36)
106
- --screen 启用远程桌面共享
107
- --no-keep-awake macOS 上允许闲置睡眠
108
- --agent-ui / --no-agent-ui 启用或关闭 Agent Workspace channel
109
- --agent-provider <name> Agent provider:codex | claude | custom
110
- --agent-command <cmd> 自定义 ACP/Agent 命令
111
- --verbose 详细日志
112
- ```
113
-
114
- ## Agent Workspace
115
-
116
- `--agent-ui` 默认开启,CLI 会检测本机是否安装了 Claude Code 与 Codex CLI,并把可用 provider、模型、权限模式、推理强度和功能能力同步给 App。手机端可以在 Agent 标签里选择 provider 和项目目录,继续历史对话,查看 tool call、命令执行、文件变更、计划、权限请求、结构化补充输入和子 Agent 活动。
117
-
118
- - Codex:默认命令为 `codex app-server --listen stdio://`
119
- - Claude:优先使用 `@anthropic-ai/claude-agent-sdk`,否则回退到 `claude --print --output-format stream-json --input-format stream-json`
120
- - Custom:需要传入 `--agent-provider custom --agent-command "<command>"`
121
-
122
- 如果 Agent provider 不可用,终端桥接仍会正常运行。
123
-
124
- ## 配置持久化
51
+ A gateway only relays encrypted frames and brokers pairing.
125
52
 
126
53
  ```bash
127
- linkshell setup
54
+ linkshell gateway --port 8787 --daemon
128
55
  ```
129
56
 
130
- 配置保存在 `~/.linkshell/config.json`。
57
+ or with Docker: `nickname4th/linkshell-gateway` (see the [self-hosting guide](https://github.com/LiuTianjie/LinkShell/blob/main/docs/deploy.md)). Put it behind HTTPS and point hosts at `wss://your-domain`.
131
58
 
132
- ## doctor 检查项
133
-
134
- ```bash
135
- linkshell doctor
136
- ```
137
-
138
- - Node.js 版本 >= 18
139
- - node-pty 原生模块
140
- - Claude / Codex / Gemini / Copilot CLI 是否安装
141
- - 配置文件状态
142
- - Gateway 连通性和延迟
143
-
144
- ## 文件位置
59
+ ## Files
145
60
 
146
61
  ```
147
62
  ~/.linkshell/
148
- ├── config.json # 配置文件
149
- ├── bridge.pid # 桥接进程 PID
150
- ├── bridge.log # 桥接进程日志
151
- ├── gateway.pid # Gateway 进程 PID
152
- └── gateway.log # Gateway 进程日志
63
+ ├── state.db sessions and their history
64
+ ├── identity.json this computer's keys
65
+ ├── paired-devices.json phones paired with it
66
+ ├── auth.json the Pro login, if any
67
+ ├── config.json the chosen gateway
68
+ ├── host.log the host's log
69
+ ├── worktrees/ git worktrees made for sessions started (or forked) into one
70
+ └── relay.db pairings, when this computer runs a gateway
153
71
  ```
154
72
 
155
- ## 代码入口
73
+ `LINKSHELL_HOME` moves this directory. LinkShell never handles your agents' accounts or API keys: agents run as you, with your login shell's environment.
74
+
75
+ ## Coming from 1.x
156
76
 
157
- 1. src/index.ts — CLI 命令定义
158
- 2. src/providers.ts — 终端 Provider 适配
159
- 3. src/runtime/bridge-session.ts — 核心会话
160
- 4. src/runtime/acp/agent-workspace.ts — Agent Workspace v2
161
- 5. src/runtime/acp/provider-resolver.ts — Agent provider 自动检测
162
- 6. src/utils/daemon.ts — 后台进程管理
77
+ 2.0 changes both the app and the computer side: upgrade both and connect again. `linkshell start`, `setup` and `list` still serve the 1.x app.
163
78
 
164
79
  ## License
165
80
 
@@ -1,7 +1,7 @@
1
1
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { homedir } from "node:os";
4
- const AUTH_DIR = join(homedir(), ".linkshell");
4
+ const AUTH_DIR = process.env.LINKSHELL_HOME || join(homedir(), ".linkshell");
5
5
  const AUTH_FILE = join(AUTH_DIR, "auth.json");
6
6
  export function loadAuth() {
7
7
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/auth.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- export declare function runDoctor(gatewayUrl?: string): Promise<void>;
1
+ export declare function runDoctor(version: string, gatewayUrl?: string): Promise<void>;
@@ -1,6 +1,7 @@
1
1
  import { execSync } from "node:child_process";
2
2
  import { createRequire } from "node:module";
3
- import { loadConfig, getConfigPath } from "../config.js";
3
+ import { loadAuth } from "../auth.js";
4
+ import { hostRuntimeOk, resolveGateway, withRunningHost } from "./host.js";
4
5
  const requireFromCli = createRequire(import.meta.url);
5
6
  function check(name, fn) {
6
7
  try {
@@ -18,31 +19,31 @@ function which(bin) {
18
19
  return undefined;
19
20
  }
20
21
  }
21
- async function checkGateway(url) {
22
+ async function checkGateway(name, url) {
22
23
  try {
23
- const httpUrl = url.replace(/\/ws\/?$/, "").replace(/^wss:/, "https:").replace(/^ws:/, "http:");
24
+ const httpUrl = url.replace(/\/(ws|v2\/connect)\/?$/, "").replace(/\/$/, "").replace(/^wss:/, "https:").replace(/^ws:/, "http:");
24
25
  const start = Date.now();
25
26
  const res = await fetch(`${httpUrl}/healthz`, { signal: AbortSignal.timeout(5000) });
26
27
  const latency = Date.now() - start;
27
28
  if (!res.ok)
28
- return { name: "Gateway", ok: false, detail: `HTTP ${res.status}` };
29
- return { name: "Gateway", ok: true, detail: `reachable (${latency}ms)` };
29
+ return { name, ok: false, detail: `${url}: HTTP ${res.status}` };
30
+ return { name, ok: true, detail: `${url} answers (${latency}ms)` };
30
31
  }
31
32
  catch (e) {
32
- return { name: "Gateway", ok: false, detail: e instanceof Error ? e.message : "unreachable" };
33
+ return { name, ok: false, detail: `${url}: ${e instanceof Error ? e.message : "unreachable"}` };
33
34
  }
34
35
  }
35
- export async function runDoctor(gatewayUrl) {
36
- const config = loadConfig();
37
- const gateway = gatewayUrl ?? config.gateway;
36
+ const AGENT_INSTALL = {
37
+ claude: "npm i -g @anthropic-ai/claude-code",
38
+ codex: "npm i -g @openai/codex",
39
+ };
40
+ export async function runDoctor(version, gatewayUrl) {
38
41
  process.stdout.write("\n LinkShell Doctor\n\n");
39
42
  const results = [];
40
43
  results.push(check("Node.js", () => {
41
- const ver = process.versions.node;
42
- const major = Number(ver.split(".")[0]);
43
- if (major < 18)
44
- throw new Error(`v${ver} (need >= 18)`);
45
- return `v${ver}`;
44
+ if (!hostRuntimeOk())
45
+ throw new Error(`v${process.versions.node} (the host needs 22.13 or newer)`);
46
+ return `v${process.versions.node}`;
46
47
  }));
47
48
  results.push(check("node-pty", () => {
48
49
  try {
@@ -57,33 +58,72 @@ export async function runDoctor(gatewayUrl) {
57
58
  "For a pnpm source checkout, approve node-pty build scripts and reinstall.");
58
59
  }
59
60
  }));
60
- // Optional CLI hints — not required, since LinkShell now bridges a plain shell.
61
- for (const [label, bin, hint] of [
62
- ["Claude CLI (optional)", "claude", "npm i -g @anthropic-ai/claude-code"],
63
- ["Codex CLI (optional)", "codex", "npm i -g @openai/codex"],
64
- ]) {
65
- const path = which(bin);
66
- if (path) {
67
- results.push({ name: label, ok: true, detail: `found (${path})` });
61
+ const host = await withRunningHost(async (client) => ({
62
+ info: await client.call("machine.info", {}),
63
+ gateway: await client.call("gateway.status", {}),
64
+ })).catch(() => undefined);
65
+ if (!host) {
66
+ results.push({ name: "Host", ok: false, detail: "not running — start it with: linkshell host --daemon" });
67
+ // Without the host, at least say whether the two main agents are on the PATH.
68
+ for (const [bin, hint] of Object.entries(AGENT_INSTALL)) {
69
+ const path = which(bin);
70
+ results.push({ name: `${bin} (optional)`, ok: true, detail: path ? `found (${path})` : `not installed — ${hint}` });
68
71
  }
69
- else {
70
- results.push({ name: label, ok: true, detail: `not installed — ${hint}` });
72
+ }
73
+ else {
74
+ results.push(host.info.hostVersion === version
75
+ ? { name: "Host", ok: true, detail: `running, ${version}` }
76
+ : {
77
+ name: "Host",
78
+ ok: false,
79
+ detail: `running ${host.info.hostVersion}, but this CLI is ${version} — update it with: linkshell host stop && linkshell host --daemon`,
80
+ });
81
+ // Agents as the host sees them: what the phone can actually start.
82
+ for (const agent of host.info.agents) {
83
+ if (!agent.installed) {
84
+ const hint = AGENT_INSTALL[agent.id];
85
+ if (hint)
86
+ results.push({ name: `${agent.label} (optional)`, ok: true, detail: `not installed — ${hint}` });
87
+ continue;
88
+ }
89
+ const signedOut = agent.auth?.state === "missing";
90
+ const problem = agent.problem ?? (signedOut ? (agent.auth?.hint ?? "not logged in") : undefined);
91
+ results.push({ name: agent.label, ok: !problem, detail: `v${agent.version ?? "?"}${problem ? ` — ${problem}` : ""}` });
71
92
  }
72
93
  }
73
- results.push(check("Config", () => {
74
- const path = getConfigPath();
75
- const cfg = loadConfig();
76
- const keys = Object.keys(cfg).filter((k) => cfg[k] !== undefined);
77
- if (keys.length === 0)
78
- return `${path} (empty — run: linkshell setup)`;
79
- return `${path} (${keys.join(", ")})`;
80
- }));
81
- if (gateway) {
82
- results.push(await checkGateway(gateway));
94
+ const auth = loadAuth();
95
+ results.push({
96
+ name: "Account",
97
+ ok: true,
98
+ detail: auth?.accessToken ? `logged in as ${auth.email || auth.userId}` : "not logged in (only the official gateway needs it: linkshell login)",
99
+ });
100
+ if (host) {
101
+ const gateway = host.gateway;
102
+ if (gateway.status === "online") {
103
+ const account = gateway.account ? `, account ${gateway.account.email ?? gateway.account.userId}` : "";
104
+ const paired = gateway.devices.length ? `, ${gateway.devices.length} paired device(s)` : "";
105
+ results.push({ name: "Gateway", ok: true, detail: `online ${gateway.url}${account}${paired}` });
106
+ }
107
+ else if (gateway.status === "off") {
108
+ results.push({
109
+ name: "Gateway",
110
+ ok: false,
111
+ detail: "off, so a phone can't reach this computer — linkshell login (official gateway), or linkshell host --gateway <url>",
112
+ });
113
+ }
114
+ else {
115
+ results.push({ name: "Gateway", ok: false, detail: `${gateway.status} ${gateway.url}${gateway.error ? ` — ${gateway.error.message}` : ""}` });
116
+ }
83
117
  }
84
- else {
85
- results.push({ name: "Gateway", ok: false, detail: "no gateway configured — run: linkshell setup" });
118
+ else if (!gatewayUrl && hostRuntimeOk()) {
119
+ const { defaultHome } = await import("@linkshell/host");
120
+ const chosen = resolveGateway(defaultHome());
121
+ results.push(chosen
122
+ ? await checkGateway("Gateway", chosen)
123
+ : { name: "Gateway", ok: false, detail: "none chosen — linkshell login (official gateway), or linkshell host --gateway <url>" });
86
124
  }
125
+ if (gatewayUrl)
126
+ results.push(await checkGateway(host ? "Gateway (--gateway)" : "Gateway", gatewayUrl));
87
127
  for (const r of results) {
88
128
  const icon = r.ok ? "\x1b[32m✓\x1b[0m" : "\x1b[31m✗\x1b[0m";
89
129
  process.stdout.write(` ${icon} ${r.name}: ${r.detail}\n`);
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import type { GatewayStatus } from "@linkshell/wire";
1
2
  export declare function silenceSqliteWarning(): void;
2
3
  interface HostConfig {
3
4
  /** v2 gateway base URL (ws:// or wss://), or "off". Unset: the official gateway once logged in. */
@@ -13,11 +14,24 @@ export declare function resolveGateway(home: string): string | undefined;
13
14
  export declare function readHostConfig(home: string): HostConfig;
14
15
  export declare function writeHostConfig(home: string, config: HostConfig): void;
15
16
  /** The host keeps its state in node:sqlite, unflagged from Node 22.13. */
17
+ export declare function hostRuntimeOk(): boolean;
16
18
  export declare function assertHostRuntime(): void;
19
+ declare function loadHost(): Promise<typeof import("@linkshell/host")>;
17
20
  /** Runs the host in this process until SIGINT/SIGTERM. */
18
21
  export declare function runHostForeground(version: string): Promise<void>;
19
22
  /** Starts the host in the background unless one already answers. */
20
23
  export declare function ensureHostRunning(): Promise<string>;
24
+ type HostClient = Awaited<ReturnType<Awaited<ReturnType<typeof loadHost>>["connectHost"]>>;
25
+ /** Runs `use` against the running host; undefined when there is none. */
26
+ export declare function withRunningHost<T>(use: (client: HostClient) => Promise<T>): Promise<T | undefined>;
27
+ /**
28
+ * Has the running host apply the current gateway choice and account, then
29
+ * waits (up to `waitMs`) for it to come online or be refused.
30
+ */
31
+ export declare function refreshGateway(waitMs: number): Promise<GatewayStatus | undefined>;
32
+ export declare function describeGateway(gateway: GatewayStatus): string;
33
+ /** After `linkshell login`: this computer joins the gateway its account is for. */
34
+ export declare function joinGatewayAfterLogin(plan: "pro" | "free"): Promise<void>;
21
35
  export declare function printHostStatus(): Promise<void>;
22
36
  /**
23
37
  * `linkshell <agent> [args…]`: starts the agent's native UI attached to the
@@ -24,8 +24,9 @@ export function resolveGateway(home) {
24
24
  const chosen = process.env.LINKSHELL_GATEWAY || readHostConfig(home).gateway;
25
25
  if (chosen === "off")
26
26
  return undefined;
27
+ // config.json is shared with the v1 bridge, whose gateway URLs end in /ws.
27
28
  if (chosen)
28
- return chosen;
29
+ return chosen.replace(/\/ws\/?$/, "");
29
30
  return isLoggedIn() ? OFFICIAL_GATEWAY : undefined;
30
31
  }
31
32
  function configPath(home) {
@@ -44,9 +45,12 @@ export function writeHostConfig(home, config) {
44
45
  writeFileSync(configPath(home), `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
45
46
  }
46
47
  /** The host keeps its state in node:sqlite, unflagged from Node 22.13. */
47
- export function assertHostRuntime() {
48
+ export function hostRuntimeOk() {
48
49
  const [major = 0, minor = 0] = process.versions.node.split(".").map(Number);
49
- if (major > 22 || (major === 22 && minor >= 13))
50
+ return major > 22 || (major === 22 && minor >= 13);
51
+ }
52
+ export function assertHostRuntime() {
53
+ if (hostRuntimeOk())
50
54
  return;
51
55
  process.stderr.write(`\n LinkShell's host needs Node.js 22.13 or newer (this is ${process.version}).\n` +
52
56
  " Upgrade Node (for example: nvm install 22, or brew upgrade node), then try again.\n\n");
@@ -70,12 +74,13 @@ export async function runHostForeground(version) {
70
74
  const devPort = process.env.LINKSHELL_DEV_PORT ? Number(process.env.LINKSHELL_DEV_PORT) : undefined;
71
75
  // Where devices reach this machine from outside the LAN. The account from
72
76
  // `linkshell login`, if any, lets that account's devices in without pairing.
73
- const gatewayUrl = resolveGateway(host.defaultHome());
77
+ // Asked again on `gateway.refresh`, so login, logout and `--gateway` apply to a running host.
78
+ const home = host.defaultHome();
74
79
  const running = await host.startHost({
75
80
  version,
76
81
  env,
77
82
  log,
78
- gateway: gatewayUrl ? { url: gatewayUrl, token: async () => (await getValidToken()) ?? undefined } : undefined,
83
+ gateway: { url: () => resolveGateway(home), token: async () => (await getValidToken()) ?? undefined },
79
84
  tcpPort: devPort,
80
85
  claudeCommand: process.env.LINKSHELL_CLAUDE_COMMAND || undefined,
81
86
  claudeAdapter: adapter ? { command: adapter, args: [] } : undefined,
@@ -113,6 +118,87 @@ export async function ensureHostRunning() {
113
118
  }
114
119
  throw new Error(`LinkShell host did not start; see ${daemon.getLogFile("host")}`);
115
120
  }
121
+ /** Runs `use` against the running host; undefined when there is none. */
122
+ export async function withRunningHost(use) {
123
+ if (!hostRuntimeOk())
124
+ return undefined;
125
+ const host = await loadHost();
126
+ const { hostSocket } = host.hostPaths();
127
+ if (!(await host.isHostRunning(hostSocket)))
128
+ return undefined;
129
+ const client = await host.connectHost(hostSocket);
130
+ try {
131
+ return await use(client);
132
+ }
133
+ finally {
134
+ client.close();
135
+ }
136
+ }
137
+ /**
138
+ * Has the running host apply the current gateway choice and account, then
139
+ * waits (up to `waitMs`) for it to come online or be refused.
140
+ */
141
+ export async function refreshGateway(waitMs) {
142
+ return withRunningHost(async (client) => {
143
+ let status;
144
+ try {
145
+ status = await client.call("gateway.refresh", {});
146
+ }
147
+ catch {
148
+ // A host from before 0.6.3 (still running after an upgrade) only reads its gateway at start.
149
+ process.stderr.write(" The running host is older than this CLI. Restart it to apply this (stops running agent turns):\n");
150
+ process.stderr.write(" linkshell host stop && linkshell host --daemon\n");
151
+ return client.call("gateway.status", {});
152
+ }
153
+ const deadline = Date.now() + waitMs;
154
+ while (status.status === "connecting" && Date.now() < deadline) {
155
+ await new Promise((resolve) => setTimeout(resolve, 200));
156
+ status = await client.call("gateway.status", {});
157
+ }
158
+ return status;
159
+ });
160
+ }
161
+ export function describeGateway(gateway) {
162
+ if (gateway.status === "off")
163
+ return "off (linkshell login for the official one, or linkshell host --gateway <url>)";
164
+ const account = gateway.account ? `, account ${gateway.account.email ?? gateway.account.userId}` : "";
165
+ const problem = gateway.error ? ` ⚠ ${gateway.error.message}` : "";
166
+ return `${gateway.status} ${gateway.url}${account}${problem}`;
167
+ }
168
+ /** After `linkshell login`: this computer joins the gateway its account is for. */
169
+ export async function joinGatewayAfterLogin(plan) {
170
+ const out = (line) => process.stderr.write(` ${line}\n`);
171
+ if (!hostRuntimeOk()) {
172
+ out(`The LinkShell host needs Node.js 22.13 or newer (this is ${process.version}); upgrade Node, then: linkshell host --daemon\n`);
173
+ return;
174
+ }
175
+ const host = await loadHost();
176
+ const chosen = process.env.LINKSHELL_GATEWAY || readHostConfig(host.defaultHome()).gateway;
177
+ if (chosen === "off") {
178
+ out("The gateway is turned off on this computer. To use the official one: linkshell host --gateway default\n");
179
+ return;
180
+ }
181
+ if (!chosen && plan !== "pro") {
182
+ // A running host now gets the gateway's own answer, instead of an old login's.
183
+ await refreshGateway(0);
184
+ out("The official gateway needs a Pro subscription: https://itool.tech");
185
+ out("Or use your own gateway: linkshell host --gateway <url>, then linkshell pair\n");
186
+ return;
187
+ }
188
+ await ensureHostRunning();
189
+ const gateway = await refreshGateway(15_000);
190
+ const where = chosen ? (gateway?.url ?? chosen) : "the official gateway";
191
+ if (gateway?.status === "online") {
192
+ out(`\x1b[32m✓\x1b[0m This computer is online on ${where}.`);
193
+ out(gateway.account
194
+ ? " Open LinkShell on your phone and sign in with the same account: it shows up there, no pairing needed.\n"
195
+ : " Pair your phone with: linkshell pair\n");
196
+ }
197
+ else {
198
+ out(`\x1b[31m✗\x1b[0m Not connected to ${where}${gateway?.error ? `: ${gateway.error.message}` : " yet"}`);
199
+ out(" Check again with: linkshell host status\n");
200
+ }
201
+ }
116
202
  export async function printHostStatus() {
117
203
  const host = await loadHost();
118
204
  const { hostSocket } = host.hostPaths();
@@ -138,16 +224,9 @@ export async function printHostStatus() {
138
224
  const active = sessions.filter((s) => s.state === "running" || s.state === "waiting");
139
225
  process.stdout.write(` Sessions: ${sessions.length} known, ${active.length} active\n`);
140
226
  const gateway = await client.call("gateway.status", {});
141
- if (gateway.status === "off") {
142
- process.stdout.write(" Gateway: off (linkshell login for the official one, or linkshell host --gateway <url>)\n");
143
- }
144
- else {
145
- const account = gateway.account ? `, account ${gateway.account.email ?? gateway.account.userId}` : "";
146
- const problem = gateway.error ? ` ⚠ ${gateway.error.message}` : "";
147
- process.stdout.write(` Gateway: ${gateway.status} ${gateway.url}${account}${problem}\n`);
148
- for (const device of gateway.devices) {
149
- process.stdout.write(` ${device.online ? "●" : "○"} ${device.name} paired ${new Date(device.pairedAt).toLocaleDateString()}\n`);
150
- }
227
+ process.stdout.write(` Gateway: ${describeGateway(gateway)}\n`);
228
+ for (const device of gateway.devices) {
229
+ process.stdout.write(` ${device.online ? "●" : "○"} ${device.name} paired ${new Date(device.pairedAt).toLocaleDateString()}\n`);
151
230
  }
152
231
  }
153
232
  finally {