linkshell-cli 0.6.2 → 0.6.4
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 +57 -3
- 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/README.md
CHANGED
|
@@ -1,165 +1,80 @@
|
|
|
1
1
|
# linkshell-cli
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12
|
+
npm i -g linkshell-cli
|
|
13
|
+
linkshell host --daemon
|
|
15
14
|
```
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
1. 启动内置 Gateway(端口 8787)
|
|
19
|
-
2. 检测局域网 IP
|
|
20
|
-
3. 创建配对并打印 QR 码
|
|
21
|
-
4. 手机扫码即连
|
|
16
|
+
Then connect your phone one of two ways:
|
|
22
17
|
|
|
23
|
-
|
|
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
|
|
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
|
-
##
|
|
78
|
-
|
|
79
|
-
```bash
|
|
80
|
-
# 后台启动独立 Gateway
|
|
81
|
-
linkshell gateway --daemon --port 8787
|
|
33
|
+
## Commands
|
|
82
34
|
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
54
|
+
linkshell gateway --port 8787 --daemon
|
|
128
55
|
```
|
|
129
56
|
|
|
130
|
-
|
|
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
|
-
##
|
|
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
|
-
├──
|
|
149
|
-
├──
|
|
150
|
-
├──
|
|
151
|
-
├──
|
|
152
|
-
|
|
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
|
-
|
|
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
|
|
package/dist/cli/src/auth.js
CHANGED
|
@@ -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 {
|
package/dist/cli/src/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/auth.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;AAElC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,CAAC,CAAC;
|
|
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 {
|
|
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
|
|
29
|
-
return { name
|
|
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
|
|
33
|
+
return { name, ok: false, detail: `${url}: ${e instanceof Error ? e.message : "unreachable"}` };
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
70
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,
|
|
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
|
|
48
|
+
export function hostRuntimeOk() {
|
|
48
49
|
const [major = 0, minor = 0] = process.versions.node.split(".").map(Number);
|
|
49
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
142
|
-
|
|
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 {
|