@zhihand/mcp 0.23.1 โ 0.24.1
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 +2 -1
- package/bin/zhihand +82 -0
- package/dist/daemon/dispatcher.js +19 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
ZhiHand MCP Server โ let AI agents see and control your phone.
|
|
4
4
|
|
|
5
|
-
Version: `0.
|
|
5
|
+
Version: `0.24.1`
|
|
6
6
|
|
|
7
7
|
## What is this?
|
|
8
8
|
|
|
@@ -96,6 +96,7 @@ zhihand start -d Start daemon in background (logs to ~/.zhihand/daemon
|
|
|
96
96
|
zhihand stop Stop the running daemon
|
|
97
97
|
zhihand status Show daemon status, pairing info, device, backend, and model
|
|
98
98
|
|
|
99
|
+
zhihand test Test device connectivity (screenshot, click, swipe, home, back)
|
|
99
100
|
zhihand pair Pair with a phone (QR code in terminal)
|
|
100
101
|
zhihand detect List detected CLI tools and their login status
|
|
101
102
|
zhihand serve Start MCP Server (stdio mode, backward compatible)
|
package/bin/zhihand
CHANGED
|
@@ -51,6 +51,7 @@ Usage:
|
|
|
51
51
|
zhihand pair Pair with a phone device
|
|
52
52
|
zhihand detect Detect available CLI tools
|
|
53
53
|
|
|
54
|
+
zhihand test Test device connectivity (sends click + type commands)
|
|
54
55
|
zhihand serve Start MCP Server (stdio mode, backward compat)
|
|
55
56
|
|
|
56
57
|
Options:
|
|
@@ -280,6 +281,87 @@ switch (command) {
|
|
|
280
281
|
break;
|
|
281
282
|
}
|
|
282
283
|
|
|
284
|
+
case "test": {
|
|
285
|
+
const { resolveConfig: resolveTestConfig } = await import("../dist/core/config.js");
|
|
286
|
+
const { createControlCommand, enqueueCommand } = await import("../dist/core/command.js");
|
|
287
|
+
const { waitForCommandAck } = await import("../dist/core/sse.js");
|
|
288
|
+
const { fetchScreenshotBinary } = await import("../dist/core/screenshot.js");
|
|
289
|
+
|
|
290
|
+
let testConfig;
|
|
291
|
+
try {
|
|
292
|
+
testConfig = resolveTestConfig(values.device ?? process.env.ZHIHAND_DEVICE);
|
|
293
|
+
} catch (err) {
|
|
294
|
+
console.error(`Error: ${err.message}`);
|
|
295
|
+
console.error("Run 'zhihand setup' to pair a device first.");
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
console.log("๐งช ZhiHand Device Test");
|
|
300
|
+
console.log(` Device: ${testConfig.credentialId}`);
|
|
301
|
+
console.log(` Endpoint: ${testConfig.controlPlaneEndpoint}\n`);
|
|
302
|
+
|
|
303
|
+
const steps = [
|
|
304
|
+
{ label: "1. Screenshot", type: "screenshot" },
|
|
305
|
+
{ label: "2. Click center", type: "hid", params: { action: "click", xRatio: 0.5, yRatio: 0.5 } },
|
|
306
|
+
{ label: "3. Swipe up", type: "hid", params: { action: "swipe", startXRatio: 0.5, startYRatio: 0.7, endXRatio: 0.5, endYRatio: 0.3, durationMs: 300 } },
|
|
307
|
+
{ label: "4. Swipe down", type: "hid", params: { action: "swipe", startXRatio: 0.5, startYRatio: 0.3, endXRatio: 0.5, endYRatio: 0.7, durationMs: 300 } },
|
|
308
|
+
{ label: "5. Press Home", type: "hid", params: { action: "home" } },
|
|
309
|
+
{ label: "6. Press Back", type: "hid", params: { action: "back" } },
|
|
310
|
+
{ label: "7. Screenshot", type: "screenshot" },
|
|
311
|
+
];
|
|
312
|
+
|
|
313
|
+
let passed = 0;
|
|
314
|
+
let failed = 0;
|
|
315
|
+
|
|
316
|
+
for (const step of steps) {
|
|
317
|
+
process.stdout.write(` ${step.label}... `);
|
|
318
|
+
const t0 = Date.now();
|
|
319
|
+
try {
|
|
320
|
+
if (step.type === "screenshot") {
|
|
321
|
+
// Screenshot: send receive_screenshot command, then fetch binary
|
|
322
|
+
const cmd = createControlCommand({ action: "screenshot" });
|
|
323
|
+
const queued = await enqueueCommand(testConfig, cmd);
|
|
324
|
+
const ack = await waitForCommandAck(testConfig, { commandId: queued.id, timeoutMs: 10_000 });
|
|
325
|
+
if (ack.acked) {
|
|
326
|
+
const buf = await fetchScreenshotBinary(testConfig);
|
|
327
|
+
const ms = Date.now() - t0;
|
|
328
|
+
console.log(`โ
${(buf.length / 1024).toFixed(0)}KB (${ms}ms)`);
|
|
329
|
+
passed++;
|
|
330
|
+
} else {
|
|
331
|
+
console.log(`โฑ๏ธ Timeout (${Date.now() - t0}ms)`);
|
|
332
|
+
failed++;
|
|
333
|
+
}
|
|
334
|
+
} else {
|
|
335
|
+
const cmd = createControlCommand(step.params);
|
|
336
|
+
const queued = await enqueueCommand(testConfig, cmd);
|
|
337
|
+
const ack = await waitForCommandAck(testConfig, { commandId: queued.id, timeoutMs: 10_000 });
|
|
338
|
+
const ms = Date.now() - t0;
|
|
339
|
+
if (ack.acked) {
|
|
340
|
+
console.log(`โ
(${ms}ms)`);
|
|
341
|
+
passed++;
|
|
342
|
+
} else {
|
|
343
|
+
console.log(`โฑ๏ธ Timeout (${ms}ms)`);
|
|
344
|
+
failed++;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
} catch (err) {
|
|
348
|
+
const ms = Date.now() - t0;
|
|
349
|
+
console.log(`โ ${err.message} (${ms}ms)`);
|
|
350
|
+
failed++;
|
|
351
|
+
}
|
|
352
|
+
// Pause between commands: let phone process + user can observe
|
|
353
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
console.log(`\n Result: ${passed}/${steps.length} passed`);
|
|
357
|
+
if (failed === 0) {
|
|
358
|
+
console.log(" โ
Device is responding to all commands!");
|
|
359
|
+
} else {
|
|
360
|
+
console.log(" โ ๏ธ Some commands failed. Check phone connectivity.");
|
|
361
|
+
}
|
|
362
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
363
|
+
}
|
|
364
|
+
|
|
283
365
|
default:
|
|
284
366
|
console.error(`Unknown command: ${command}. Run 'zhihand --help' for usage.`);
|
|
285
367
|
process.exit(1);
|
|
@@ -558,11 +558,29 @@ async function dispatchClaudeWithHistory(prompt, startTime, log, model) {
|
|
|
558
558
|
// Write prompt to stdin, then close to signal EOF
|
|
559
559
|
child.stdin?.write(fullPrompt);
|
|
560
560
|
child.stdin?.end();
|
|
561
|
-
const
|
|
561
|
+
const raw = await collectChildOutput(child, startTime);
|
|
562
|
+
// Claude --output-format json wraps the result in a JSON envelope โ extract the actual text
|
|
563
|
+
const result = extractClaudeResult(raw);
|
|
562
564
|
recordTurn("user", prompt);
|
|
563
565
|
recordTurn("assistant", result.text);
|
|
564
566
|
return result;
|
|
565
567
|
}
|
|
568
|
+
/** Parse Claude JSON output and extract the result text. */
|
|
569
|
+
function extractClaudeResult(raw) {
|
|
570
|
+
try {
|
|
571
|
+
const parsed = JSON.parse(raw.text);
|
|
572
|
+
if (!parsed || typeof parsed !== "object")
|
|
573
|
+
return raw;
|
|
574
|
+
const resultText = typeof parsed.result === "string" ? parsed.result : raw.text;
|
|
575
|
+
const isError = parsed.is_error === true || parsed.subtype === "error";
|
|
576
|
+
// Preserve process exit failure: only succeed if both JSON and process agree
|
|
577
|
+
return { text: resultText, success: raw.success && !isError, durationMs: raw.durationMs };
|
|
578
|
+
}
|
|
579
|
+
catch {
|
|
580
|
+
// Not JSON โ return as-is
|
|
581
|
+
return raw;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
566
584
|
// โโ Codex JSONL Output Collector โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
567
585
|
function collectCodexOutput(child, startTime) {
|
|
568
586
|
return new Promise((resolve) => {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
export declare const PACKAGE_VERSION = "0.
|
|
2
|
+
export declare const PACKAGE_VERSION = "0.24.1";
|
|
3
3
|
export declare function createServer(deviceName?: string): McpServer;
|
|
4
4
|
export declare function startStdioServer(deviceName?: string): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { controlSchema, screenshotSchema, pairSchema } from "./tools/schemas.js"
|
|
|
5
5
|
import { executeControl } from "./tools/control.js";
|
|
6
6
|
import { handleScreenshot } from "./tools/screenshot.js";
|
|
7
7
|
import { handlePair } from "./tools/pair.js";
|
|
8
|
-
export const PACKAGE_VERSION = "0.
|
|
8
|
+
export const PACKAGE_VERSION = "0.24.1";
|
|
9
9
|
export function createServer(deviceName) {
|
|
10
10
|
const server = new McpServer({
|
|
11
11
|
name: "zhihand",
|