@zhihand/mcp 0.15.0 → 0.16.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 +82 -25
- package/bin/zhihand +122 -33
- package/dist/cli/mcp-config.d.ts +1 -1
- package/dist/cli/mcp-config.js +12 -6
- package/dist/daemon/dispatcher.d.ts +13 -0
- package/dist/daemon/dispatcher.js +562 -0
- package/dist/daemon/heartbeat.d.ts +5 -0
- package/dist/daemon/heartbeat.js +65 -0
- package/dist/daemon/index.d.ts +6 -0
- package/dist/daemon/index.js +324 -0
- package/dist/daemon/prompt-listener.d.ts +32 -0
- package/dist/daemon/prompt-listener.js +152 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,22 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
ZhiHand MCP Server — let AI agents see and control your phone.
|
|
4
4
|
|
|
5
|
-
Version: `0.
|
|
5
|
+
Version: `0.16.0`
|
|
6
6
|
|
|
7
7
|
## What is this?
|
|
8
8
|
|
|
9
|
-
`@zhihand/mcp` is the core integration layer for ZhiHand. It
|
|
9
|
+
`@zhihand/mcp` is the core integration layer for ZhiHand. It runs as a **persistent daemon** that exposes phone control tools to any compatible AI agent via [MCP (Model Context Protocol)](https://modelcontextprotocol.io/), including:
|
|
10
10
|
|
|
11
11
|
- **Claude Code**
|
|
12
12
|
- **Codex CLI**
|
|
13
13
|
- **Gemini CLI**
|
|
14
14
|
- **OpenClaw**
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
The daemon is a single persistent process that bundles three subsystems:
|
|
17
|
+
|
|
18
|
+
| Subsystem | Purpose |
|
|
19
|
+
|---|---|
|
|
20
|
+
| **MCP Server** | HTTP Streamable transport on `localhost:18686/mcp` — serves tool calls to AI agents |
|
|
21
|
+
| **Relay** | Brain heartbeat (30s), prompt listener (phone-initiated tasks), CLI dispatch |
|
|
22
|
+
| **Config API** | IPC endpoint for `zhihand gemini/claude/codex` backend switching |
|
|
23
|
+
|
|
24
|
+
Legacy entry points (backward compatible):
|
|
17
25
|
|
|
18
26
|
| Entry | Purpose |
|
|
19
27
|
|---|---|
|
|
20
|
-
| `zhihand serve` | MCP Server (stdio) —
|
|
28
|
+
| `zhihand serve` | MCP Server (stdio mode) — legacy, still works for direct CLI integration |
|
|
21
29
|
| `zhihand.openclaw` | OpenClaw Plugin entry — thin wrapper calling the same core |
|
|
22
30
|
|
|
23
31
|
## Requirements
|
|
@@ -53,10 +61,20 @@ This runs the full interactive setup:
|
|
|
53
61
|
4. Saves credentials to `~/.zhihand/credentials.json`
|
|
54
62
|
5. Detects installed CLI tools (Claude Code, Codex, Gemini CLI, OpenClaw)
|
|
55
63
|
6. Auto-selects the best available tool and configures MCP automatically
|
|
64
|
+
7. Starts the daemon (MCP Server + Relay + Config API)
|
|
56
65
|
|
|
57
66
|
No manual MCP configuration needed — `zhihand setup` handles everything.
|
|
58
67
|
|
|
59
|
-
### 2. Start
|
|
68
|
+
### 2. Start the daemon
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
zhihand start # Start daemon in foreground
|
|
72
|
+
zhihand start -d # Start daemon in background (detached)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The daemon runs the MCP Server on `localhost:18686/mcp` (HTTP Streamable transport), maintains a brain heartbeat every 30 seconds (keeps the phone Brain indicator green), and listens for phone-initiated prompts.
|
|
76
|
+
|
|
77
|
+
### 3. Start using it
|
|
60
78
|
|
|
61
79
|
Once configured, your AI agent can use ZhiHand tools directly. For example, in Claude Code:
|
|
62
80
|
|
|
@@ -70,18 +88,36 @@ Once configured, your AI agent can use ZhiHand tools directly. For example, in C
|
|
|
70
88
|
## CLI Commands
|
|
71
89
|
|
|
72
90
|
```
|
|
73
|
-
zhihand
|
|
74
|
-
zhihand
|
|
91
|
+
zhihand setup Interactive setup: pair + detect tools + auto-select + configure MCP + start daemon
|
|
92
|
+
zhihand start Start daemon (MCP Server + Relay + Config API)
|
|
93
|
+
zhihand start -d Start daemon in background (detached)
|
|
94
|
+
zhihand stop Stop the running daemon
|
|
95
|
+
zhihand status Show daemon status, pairing info, device, and active backend
|
|
96
|
+
|
|
75
97
|
zhihand pair Pair with a phone (QR code in terminal)
|
|
76
|
-
zhihand status Show pairing status, device info, and active backend
|
|
77
98
|
zhihand detect List detected CLI tools and their login status
|
|
99
|
+
zhihand serve Start MCP Server (stdio mode, backward compatible)
|
|
78
100
|
zhihand --help Show help
|
|
79
101
|
|
|
80
|
-
zhihand claude Switch backend to Claude Code (auto-configures MCP)
|
|
81
|
-
zhihand codex Switch backend to Codex CLI (auto-configures MCP)
|
|
82
|
-
zhihand gemini Switch backend to Gemini CLI (auto-configures MCP)
|
|
102
|
+
zhihand claude Switch backend to Claude Code (sends IPC to daemon, auto-configures MCP)
|
|
103
|
+
zhihand codex Switch backend to Codex CLI (sends IPC to daemon, auto-configures MCP)
|
|
104
|
+
zhihand gemini Switch backend to Gemini CLI (sends IPC to daemon, auto-configures MCP)
|
|
83
105
|
```
|
|
84
106
|
|
|
107
|
+
### Daemon Lifecycle
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
zhihand start # Start daemon in foreground
|
|
111
|
+
zhihand start -d # Start daemon in background
|
|
112
|
+
zhihand stop # Stop the daemon
|
|
113
|
+
zhihand status # Check if daemon is running, show device & backend info
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The daemon is a single persistent process that runs:
|
|
117
|
+
- **MCP Server** on `localhost:18686/mcp` (HTTP Streamable transport)
|
|
118
|
+
- **Relay**: brain heartbeat every 30s (keeps phone Brain indicator green), prompt listener (phone-initiated tasks dispatched to CLI), CLI dispatch
|
|
119
|
+
- **Config API**: IPC endpoint for backend switching
|
|
120
|
+
|
|
85
121
|
### Switching Backends
|
|
86
122
|
|
|
87
123
|
Use `zhihand claude`, `zhihand codex`, or `zhihand gemini` to switch the active backend:
|
|
@@ -93,6 +129,7 @@ zhihand codex # Switch to Codex CLI
|
|
|
93
129
|
```
|
|
94
130
|
|
|
95
131
|
When you switch:
|
|
132
|
+
- The command sends an **IPC message to the running daemon**
|
|
96
133
|
- MCP config is **automatically added** to the new backend
|
|
97
134
|
- MCP config is **automatically removed** from the previous backend
|
|
98
135
|
- If the tool is not installed, an error is shown
|
|
@@ -154,26 +191,40 @@ Pair with a phone device. Returns a QR code and pairing URL.
|
|
|
154
191
|
## How It Works
|
|
155
192
|
|
|
156
193
|
```
|
|
157
|
-
AI Agent ←
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
194
|
+
AI Agent ←HTTP Streamable→ Daemon (localhost:18686/mcp)
|
|
195
|
+
│
|
|
196
|
+
├── MCP Server ──→ ZhiHand Server ──→ Mobile App
|
|
197
|
+
│ (tool calls: control, screenshot, pair)
|
|
198
|
+
│
|
|
199
|
+
├── Relay
|
|
200
|
+
│ ├── Brain heartbeat (30s) ──→ Server
|
|
201
|
+
│ ├── Prompt listener (SSE) ←── Server ←── Phone
|
|
202
|
+
│ └── CLI dispatch ──→ spawn claude/codex/gemini
|
|
203
|
+
│
|
|
204
|
+
└── Config API
|
|
205
|
+
└── IPC from zhihand claude/codex/gemini
|
|
169
206
|
```
|
|
170
207
|
|
|
208
|
+
### Agent-initiated flow (tool calls)
|
|
209
|
+
|
|
171
210
|
1. AI agent calls a tool (e.g. `zhihand_control` with `action: "click"`)
|
|
172
211
|
2. MCP Server translates to a device command and enqueues it via the ZhiHand API
|
|
173
212
|
3. Mobile app picks up the command, executes it, and sends an ACK
|
|
174
213
|
4. MCP Server receives the ACK (via SSE or polling fallback)
|
|
175
214
|
5. MCP Server fetches a fresh screenshot and returns it to the AI agent
|
|
176
215
|
|
|
216
|
+
### Phone-initiated flow (prompt relay)
|
|
217
|
+
|
|
218
|
+
1. User speaks or types a prompt on the phone
|
|
219
|
+
2. Phone sends prompt to ZhiHand Server
|
|
220
|
+
3. Daemon receives prompt via SSE
|
|
221
|
+
4. Daemon spawns the active CLI tool (e.g. `claude`, `codex`, `gemini`) with the prompt
|
|
222
|
+
5. CLI tool executes, result is sent back to the phone
|
|
223
|
+
|
|
224
|
+
### Brain heartbeat
|
|
225
|
+
|
|
226
|
+
The daemon sends a heartbeat to the ZhiHand Server every 30 seconds. This keeps the **Brain indicator green** on the phone, showing the user that an AI backend is connected and ready.
|
|
227
|
+
|
|
177
228
|
Screenshots are transferred as raw JPEG binary and only base64-encoded at the LLM API boundary, minimizing bandwidth.
|
|
178
229
|
|
|
179
230
|
## Credential Storage
|
|
@@ -184,6 +235,7 @@ Pairing credentials are stored at:
|
|
|
184
235
|
~/.zhihand/
|
|
185
236
|
├── credentials.json # Device credentials (credentialId, controllerToken, endpoint)
|
|
186
237
|
├── backend.json # Active backend selection (claudecode/codex/gemini)
|
|
238
|
+
├── daemon.pid # Daemon PID file (for zhihand stop)
|
|
187
239
|
└── state.json # Current pairing session state
|
|
188
240
|
```
|
|
189
241
|
|
|
@@ -209,10 +261,10 @@ You can manage multiple devices. The `credentials.json` file stores a `default`
|
|
|
209
261
|
```
|
|
210
262
|
packages/mcp/
|
|
211
263
|
├── bin/
|
|
212
|
-
│ ├── zhihand # Main CLI entry (
|
|
264
|
+
│ ├── zhihand # Main CLI entry (start/stop/status/setup/serve/pair/detect)
|
|
213
265
|
│ └── zhihand.openclaw # OpenClaw plugin entry
|
|
214
266
|
├── src/
|
|
215
|
-
│ ├── index.ts # MCP Server (stdio transport)
|
|
267
|
+
│ ├── index.ts # MCP Server (stdio transport, legacy)
|
|
216
268
|
│ ├── openclaw.adapter.ts # OpenClaw Plugin adapter (thin wrapper)
|
|
217
269
|
│ ├── core/
|
|
218
270
|
│ │ ├── config.ts # Credential & config management (~/.zhihand/)
|
|
@@ -220,6 +272,11 @@ packages/mcp/
|
|
|
220
272
|
│ │ ├── screenshot.ts # Binary screenshot fetch (JPEG)
|
|
221
273
|
│ │ ├── sse.ts # SSE client + hybrid ACK (SSE push + polling fallback)
|
|
222
274
|
│ │ └── pair.ts # Plugin registration + device pairing flow
|
|
275
|
+
│ ├── daemon/
|
|
276
|
+
│ │ ├── index.ts # Daemon entry: HTTP server + MCP + Relay + Config API
|
|
277
|
+
│ │ ├── heartbeat.ts # Brain heartbeat loop (30s interval, 5s retry)
|
|
278
|
+
│ │ ├── prompt-listener.ts # SSE + polling prompt listener with dedup
|
|
279
|
+
│ │ └── dispatcher.ts # Async CLI dispatch (spawn + timeout + two-stage kill)
|
|
223
280
|
│ ├── tools/
|
|
224
281
|
│ │ ├── schemas.ts # Zod parameter schemas
|
|
225
282
|
│ │ ├── control.ts # zhihand_control handler
|
package/bin/zhihand
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import { parseArgs } from "node:util";
|
|
5
5
|
import { startStdioServer } from "../dist/index.js";
|
|
6
|
+
import { startDaemon, stopDaemon, isAlreadyRunning } from "../dist/daemon/index.js";
|
|
6
7
|
import { detectCLITools, formatDetectedTools } from "../dist/cli/detect.js";
|
|
7
8
|
import { detectAndSetupOpenClaw } from "../dist/cli/openclaw.js";
|
|
8
9
|
import { loadDefaultCredential, loadBackendConfig, saveBackendConfig } from "../dist/core/config.js";
|
|
@@ -22,9 +23,9 @@ const { positionals, values } = parseArgs({
|
|
|
22
23
|
strict: false,
|
|
23
24
|
options: {
|
|
24
25
|
device: { type: "string" },
|
|
25
|
-
model: { type: "string" },
|
|
26
|
-
http: { type: "boolean", default: false },
|
|
27
26
|
help: { type: "boolean", short: "h", default: false },
|
|
27
|
+
detach: { type: "boolean", short: "d", default: false },
|
|
28
|
+
port: { type: "string" },
|
|
28
29
|
},
|
|
29
30
|
});
|
|
30
31
|
|
|
@@ -32,28 +33,35 @@ const command = positionals[0] ?? "serve";
|
|
|
32
33
|
|
|
33
34
|
if (values.help) {
|
|
34
35
|
console.log(`
|
|
35
|
-
zhihand — MCP Server for phone control
|
|
36
|
+
zhihand — MCP Server and Relay for phone control
|
|
36
37
|
|
|
37
38
|
Usage:
|
|
38
|
-
zhihand
|
|
39
|
-
zhihand
|
|
40
|
-
zhihand
|
|
41
|
-
zhihand status Show
|
|
42
|
-
zhihand detect Detect available CLI tools
|
|
43
|
-
zhihand setup Interactive setup: pair + auto-configure
|
|
39
|
+
zhihand start Start daemon (MCP Server + Relay, foreground)
|
|
40
|
+
zhihand start -d Start daemon in background (detach)
|
|
41
|
+
zhihand stop Stop daemon
|
|
42
|
+
zhihand status Show status (pairing, backend, brain)
|
|
44
43
|
|
|
44
|
+
zhihand gemini Switch backend to Gemini CLI
|
|
45
45
|
zhihand claude Switch backend to Claude Code
|
|
46
46
|
zhihand codex Switch backend to Codex CLI
|
|
47
|
-
|
|
47
|
+
|
|
48
|
+
zhihand setup Interactive setup: pair + configure + start
|
|
49
|
+
zhihand pair Pair with a phone device
|
|
50
|
+
zhihand detect Detect available CLI tools
|
|
51
|
+
|
|
52
|
+
zhihand serve Start MCP Server (stdio mode, backward compat)
|
|
48
53
|
|
|
49
54
|
Options:
|
|
50
55
|
--device <name> Use a specific paired device
|
|
56
|
+
--port <port> Override daemon port (default: 18686)
|
|
57
|
+
-d, --detach Run daemon in background
|
|
51
58
|
-h, --help Show this help
|
|
52
59
|
`);
|
|
53
60
|
process.exit(0);
|
|
54
61
|
}
|
|
55
62
|
|
|
56
|
-
//
|
|
63
|
+
// ── Backend switch commands: claude, codex, gemini ─────────
|
|
64
|
+
|
|
57
65
|
if (Object.prototype.hasOwnProperty.call(CLI_TOOL_MAP, command)) {
|
|
58
66
|
const backendName = CLI_TOOL_MAP[command];
|
|
59
67
|
const tools = await detectCLITools();
|
|
@@ -69,6 +77,8 @@ if (Object.prototype.hasOwnProperty.call(CLI_TOOL_MAP, command)) {
|
|
|
69
77
|
console.error(`Warning: ${command} is installed but not logged in.`);
|
|
70
78
|
}
|
|
71
79
|
|
|
80
|
+
// Check if daemon is running — if so, notify it via HTTP
|
|
81
|
+
const daemonPid = isAlreadyRunning();
|
|
72
82
|
const config = loadBackendConfig();
|
|
73
83
|
const previous = config.activeBackend;
|
|
74
84
|
|
|
@@ -78,20 +88,82 @@ if (Object.prototype.hasOwnProperty.call(CLI_TOOL_MAP, command)) {
|
|
|
78
88
|
}
|
|
79
89
|
|
|
80
90
|
console.log(`Switching backend to ${displayName(backendName)}...`);
|
|
91
|
+
|
|
92
|
+
// Configure MCP (HTTP transport)
|
|
81
93
|
const { configured, removed } = configureMCP(backendName, previous);
|
|
82
94
|
|
|
83
95
|
if (configured) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
96
|
+
// Notify daemon if running
|
|
97
|
+
if (daemonPid) {
|
|
98
|
+
try {
|
|
99
|
+
const port = parseInt(process.env.ZHIHAND_PORT ?? "", 10) || 18686;
|
|
100
|
+
const res = await fetch(`http://127.0.0.1:${port}/internal/backend`, {
|
|
101
|
+
method: "POST",
|
|
102
|
+
headers: { "Content-Type": "application/json" },
|
|
103
|
+
body: JSON.stringify({ backend: backendName }),
|
|
104
|
+
signal: AbortSignal.timeout(5000),
|
|
105
|
+
});
|
|
106
|
+
if (res.ok) {
|
|
107
|
+
console.log(`\nDaemon notified. Backend switched to ${displayName(backendName)}.`);
|
|
108
|
+
}
|
|
109
|
+
} catch {
|
|
110
|
+
// Daemon not responding, just save config
|
|
111
|
+
saveBackendConfig({ activeBackend: backendName });
|
|
112
|
+
console.log(`\nBackend config saved. Daemon not responding — restart with 'zhihand start'.`);
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
saveBackendConfig({ activeBackend: backendName });
|
|
116
|
+
console.log(`\nBackend switched to ${displayName(backendName)}.`);
|
|
117
|
+
console.log(`Start the daemon to receive prompts: zhihand start`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (previous && removed) {
|
|
121
|
+
console.log(`Previous backend: ${displayName(previous)} (MCP config removed).`);
|
|
88
122
|
}
|
|
89
123
|
}
|
|
90
124
|
process.exit(0);
|
|
91
125
|
}
|
|
92
126
|
|
|
127
|
+
// ── Other commands ─────────────────────────────────────────
|
|
128
|
+
|
|
93
129
|
switch (command) {
|
|
130
|
+
case "start":
|
|
131
|
+
case "relay": {
|
|
132
|
+
if (values.detach) {
|
|
133
|
+
const { spawn: spawnChild } = await import("node:child_process");
|
|
134
|
+
const args = [process.argv[1], "start"];
|
|
135
|
+
if (values.port) args.push("--port", values.port);
|
|
136
|
+
if (values.device) args.push("--device", values.device);
|
|
137
|
+
|
|
138
|
+
const child = spawnChild(process.execPath, args, {
|
|
139
|
+
detached: true,
|
|
140
|
+
stdio: "ignore",
|
|
141
|
+
env: { ...process.env },
|
|
142
|
+
});
|
|
143
|
+
child.unref();
|
|
144
|
+
console.log(`Daemon starting in background (PID ${child.pid}).`);
|
|
145
|
+
process.exit(0);
|
|
146
|
+
}
|
|
147
|
+
const port = values.port ? parseInt(values.port, 10) : undefined;
|
|
148
|
+
await startDaemon({
|
|
149
|
+
port,
|
|
150
|
+
deviceName: values.device ?? process.env.ZHIHAND_DEVICE,
|
|
151
|
+
});
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
case "stop": {
|
|
156
|
+
const stopped = stopDaemon();
|
|
157
|
+
if (stopped) {
|
|
158
|
+
console.log("Daemon stopped.");
|
|
159
|
+
} else {
|
|
160
|
+
console.log("No daemon is running.");
|
|
161
|
+
}
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
|
|
94
165
|
case "serve": {
|
|
166
|
+
// Backward compatible: stdio MCP server (for old configs)
|
|
95
167
|
await startStdioServer(values.device ?? process.env.ZHIHAND_DEVICE);
|
|
96
168
|
break;
|
|
97
169
|
}
|
|
@@ -106,18 +178,31 @@ switch (command) {
|
|
|
106
178
|
case "status": {
|
|
107
179
|
const cred = loadDefaultCredential();
|
|
108
180
|
const backend = loadBackendConfig();
|
|
181
|
+
const daemonPid = isAlreadyRunning();
|
|
182
|
+
|
|
109
183
|
if (cred) {
|
|
110
184
|
console.log(`Paired device: ${cred.deviceName}`);
|
|
111
185
|
console.log(`Credential ID: ${cred.credentialId}`);
|
|
112
186
|
console.log(`Endpoint: ${cred.endpoint}`);
|
|
113
|
-
console.log(`Paired at: ${cred.pairedAt}`);
|
|
114
187
|
} else {
|
|
115
|
-
console.log("No paired device. Run: zhihand
|
|
188
|
+
console.log("No paired device. Run: zhihand setup");
|
|
116
189
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
190
|
+
console.log(`Active backend: ${backend.activeBackend ? displayName(backend.activeBackend) : "(none)"}`);
|
|
191
|
+
console.log(`Daemon: ${daemonPid ? `running (PID ${daemonPid})` : "not running"}`);
|
|
192
|
+
|
|
193
|
+
// If daemon running, get live status
|
|
194
|
+
if (daemonPid) {
|
|
195
|
+
try {
|
|
196
|
+
const port = parseInt(process.env.ZHIHAND_PORT ?? "", 10) || 18686;
|
|
197
|
+
const res = await fetch(`http://127.0.0.1:${port}/internal/status`, {
|
|
198
|
+
signal: AbortSignal.timeout(3000),
|
|
199
|
+
});
|
|
200
|
+
if (res.ok) {
|
|
201
|
+
const status = await res.json();
|
|
202
|
+
console.log(`Processing: ${status.processing ? "yes" : "idle"}`);
|
|
203
|
+
console.log(`Queue: ${status.queueLength} prompt(s)`);
|
|
204
|
+
}
|
|
205
|
+
} catch { /* daemon not responding */ }
|
|
121
206
|
}
|
|
122
207
|
break;
|
|
123
208
|
}
|
|
@@ -129,7 +214,7 @@ switch (command) {
|
|
|
129
214
|
}
|
|
130
215
|
|
|
131
216
|
case "setup": {
|
|
132
|
-
// 1.
|
|
217
|
+
// 1. Pair
|
|
133
218
|
let cred = loadDefaultCredential();
|
|
134
219
|
if (!cred) {
|
|
135
220
|
console.log("No paired device found. Starting pairing...\n");
|
|
@@ -142,30 +227,34 @@ switch (command) {
|
|
|
142
227
|
console.log(`\nPaired: ${cred.deviceName} (${cred.credentialId})\n`);
|
|
143
228
|
}
|
|
144
229
|
|
|
145
|
-
// 2. Detect
|
|
230
|
+
// 2. Detect tools
|
|
146
231
|
const tools = await detectCLITools();
|
|
147
232
|
console.log(formatDetectedTools(tools));
|
|
148
233
|
|
|
149
234
|
if (tools.length === 0) {
|
|
150
|
-
console.log("\nNo CLI tools detected. Install one of: Claude Code, Codex CLI, Gemini CLI
|
|
235
|
+
console.log("\nNo CLI tools detected. Install one of: Claude Code, Codex CLI, Gemini CLI.");
|
|
151
236
|
break;
|
|
152
237
|
}
|
|
153
238
|
|
|
154
|
-
// 3. Auto-select best tool
|
|
239
|
+
// 3. Auto-select best tool and configure MCP (HTTP transport)
|
|
155
240
|
const best = tools.find((t) => t.loggedIn) ?? tools[0];
|
|
156
241
|
const config = loadBackendConfig();
|
|
157
242
|
|
|
158
243
|
console.log(`\nAuto-selecting backend: ${displayName(best.name)}...`);
|
|
159
|
-
|
|
160
|
-
if (
|
|
161
|
-
|
|
162
|
-
} else {
|
|
163
|
-
configureMCP(best.name, config.activeBackend);
|
|
244
|
+
// Ensure MCP URL uses correct port
|
|
245
|
+
if (values.port) {
|
|
246
|
+
process.env.ZHIHAND_PORT = values.port;
|
|
164
247
|
}
|
|
165
|
-
|
|
248
|
+
configureMCP(best.name, config.activeBackend);
|
|
166
249
|
saveBackendConfig({ activeBackend: best.name });
|
|
167
|
-
|
|
168
|
-
|
|
250
|
+
|
|
251
|
+
// 4. Start daemon
|
|
252
|
+
console.log(`\nStarting daemon...\n`);
|
|
253
|
+
const port = values.port ? parseInt(values.port, 10) : undefined;
|
|
254
|
+
await startDaemon({
|
|
255
|
+
port,
|
|
256
|
+
deviceName: values.device ?? process.env.ZHIHAND_DEVICE,
|
|
257
|
+
});
|
|
169
258
|
break;
|
|
170
259
|
}
|
|
171
260
|
|
package/dist/cli/mcp-config.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { BackendName } from "../core/config.ts";
|
|
2
2
|
/**
|
|
3
|
-
* Configure MCP for the selected backend and remove from others.
|
|
3
|
+
* Configure MCP (HTTP transport) for the selected backend and remove from others.
|
|
4
4
|
*/
|
|
5
5
|
export declare function configureMCP(backend: BackendName, previousBackend: BackendName | null): {
|
|
6
6
|
configured: boolean;
|
package/dist/cli/mcp-config.js
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
|
+
const DEFAULT_PORT = 18686;
|
|
3
|
+
function mcpUrl() {
|
|
4
|
+
const port = parseInt(process.env.ZHIHAND_PORT ?? "", 10) || DEFAULT_PORT;
|
|
5
|
+
return `http://localhost:${port}/mcp`;
|
|
6
|
+
}
|
|
2
7
|
const MCP_COMMANDS = {
|
|
3
8
|
claudecode: {
|
|
4
|
-
add:
|
|
9
|
+
add: () => `claude mcp add --transport http zhihand ${mcpUrl()}`,
|
|
5
10
|
remove: "claude mcp remove zhihand",
|
|
6
11
|
},
|
|
7
12
|
codex: {
|
|
8
|
-
add:
|
|
13
|
+
add: () => `codex mcp add zhihand --url ${mcpUrl()}`,
|
|
9
14
|
remove: "codex mcp remove zhihand",
|
|
10
15
|
},
|
|
11
16
|
gemini: {
|
|
12
|
-
add:
|
|
17
|
+
add: () => `gemini mcp add --transport http --scope user zhihand ${mcpUrl()}`,
|
|
13
18
|
remove: "gemini mcp remove --scope user zhihand",
|
|
14
19
|
},
|
|
15
20
|
};
|
|
@@ -29,7 +34,7 @@ function tryRun(cmd) {
|
|
|
29
34
|
}
|
|
30
35
|
}
|
|
31
36
|
/**
|
|
32
|
-
* Configure MCP for the selected backend and remove from others.
|
|
37
|
+
* Configure MCP (HTTP transport) for the selected backend and remove from others.
|
|
33
38
|
*/
|
|
34
39
|
export function configureMCP(backend, previousBackend) {
|
|
35
40
|
let removed = false;
|
|
@@ -49,9 +54,10 @@ export function configureMCP(backend, previousBackend) {
|
|
|
49
54
|
}
|
|
50
55
|
else {
|
|
51
56
|
const cmds = MCP_COMMANDS[backend];
|
|
52
|
-
|
|
57
|
+
const addCmd = cmds.add();
|
|
58
|
+
console.log(` Configuring MCP for ${DISPLAY_NAMES[backend]} (HTTP transport)...`);
|
|
53
59
|
try {
|
|
54
|
-
execSync(
|
|
60
|
+
execSync(addCmd, { stdio: "inherit", timeout: 10_000 });
|
|
55
61
|
configured = true;
|
|
56
62
|
}
|
|
57
63
|
catch (err) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ZhiHandConfig, BackendName } from "../core/config.ts";
|
|
2
|
+
export interface DispatchResult {
|
|
3
|
+
text: string;
|
|
4
|
+
success: boolean;
|
|
5
|
+
durationMs: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Kill the active child process. Returns a promise that resolves
|
|
9
|
+
* when the child has exited (or immediately if no child).
|
|
10
|
+
*/
|
|
11
|
+
export declare function killActiveChild(): Promise<void>;
|
|
12
|
+
export declare function dispatchToCLI(backend: Exclude<BackendName, "openclaw">, prompt: string, log: (msg: string) => void, model?: string): Promise<DispatchResult>;
|
|
13
|
+
export declare function postReply(config: ZhiHandConfig, promptId: string, text: string): Promise<boolean>;
|