@zhihand/mcp 0.26.2 → 0.26.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/bin/zhihand +131 -46
- package/dist/core/command.d.ts +0 -2
- package/dist/core/command.js +3 -1
- package/dist/daemon/dispatcher.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/tools/schemas.d.ts +0 -1
- package/dist/tools/schemas.js +1 -1
- package/package.json +1 -1
package/bin/zhihand
CHANGED
|
@@ -291,6 +291,7 @@ switch (command) {
|
|
|
291
291
|
const { createControlCommand, enqueueCommand } = await import("../dist/core/command.js");
|
|
292
292
|
const { waitForCommandAck } = await import("../dist/core/sse.js");
|
|
293
293
|
const { fetchScreenshotBinary } = await import("../dist/core/screenshot.js");
|
|
294
|
+
const { fetchDeviceProfile, getStaticContext, isDeviceProfileLoaded, formatDeviceStatus } = await import("../dist/core/device.js");
|
|
294
295
|
|
|
295
296
|
let testConfig;
|
|
296
297
|
try {
|
|
@@ -305,68 +306,152 @@ switch (command) {
|
|
|
305
306
|
console.log(` Device: ${testConfig.credentialId}`);
|
|
306
307
|
console.log(` Endpoint: ${testConfig.controlPlaneEndpoint}\n`);
|
|
307
308
|
|
|
308
|
-
const steps = [
|
|
309
|
-
{ label: "1. Screenshot", type: "screenshot" },
|
|
310
|
-
{ label: "2. Click center", type: "hid", params: { action: "click", xRatio: 0.5, yRatio: 0.5 } },
|
|
311
|
-
{ label: "3. Swipe up", type: "hid", params: { action: "swipe", startXRatio: 0.5, startYRatio: 0.7, endXRatio: 0.5, endYRatio: 0.3, durationMs: 300 } },
|
|
312
|
-
{ label: "4. Swipe down", type: "hid", params: { action: "swipe", startXRatio: 0.5, startYRatio: 0.3, endXRatio: 0.5, endYRatio: 0.7, durationMs: 300 } },
|
|
313
|
-
{ label: "5. Press Home", type: "hid", params: { action: "home" } },
|
|
314
|
-
{ label: "6. Open WeChat", type: "hid", params: { action: "open_app", appPackage: "com.tencent.mm" } },
|
|
315
|
-
{ label: "7. Press Back", type: "hid", params: { action: "back" } },
|
|
316
|
-
{ label: "8. Screenshot", type: "screenshot" },
|
|
317
|
-
];
|
|
318
|
-
|
|
319
309
|
let passed = 0;
|
|
320
310
|
let failed = 0;
|
|
311
|
+
let totalSteps = 0;
|
|
321
312
|
|
|
322
|
-
|
|
323
|
-
|
|
313
|
+
// ── Helper: run a single HID step ──
|
|
314
|
+
async function runHidStep(label, params) {
|
|
315
|
+
totalSteps++;
|
|
316
|
+
process.stdout.write(` ${label}... `);
|
|
324
317
|
const t0 = Date.now();
|
|
325
318
|
try {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
console.log(`⏱️ Timeout (${Date.now() - t0}ms)`);
|
|
338
|
-
failed++;
|
|
339
|
-
}
|
|
319
|
+
const cmd = createControlCommand(params);
|
|
320
|
+
const queued = await enqueueCommand(testConfig, cmd);
|
|
321
|
+
const ack = await waitForCommandAck(testConfig, { commandId: queued.id, timeoutMs: 10_000 });
|
|
322
|
+
const ms = Date.now() - t0;
|
|
323
|
+
if (ack.acked) {
|
|
324
|
+
const ackStatus = ack.command?.ack_status ?? "ok";
|
|
325
|
+
const detail = ackStatus !== "ok" ? ` [${ackStatus}]` : "";
|
|
326
|
+
const resultInfo = ack.command?.ack_result ? ` ${JSON.stringify(ack.command.ack_result)}` : "";
|
|
327
|
+
console.log(`✅ (${ms}ms)${detail}${resultInfo}`);
|
|
328
|
+
passed++;
|
|
329
|
+
return ack;
|
|
340
330
|
} else {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
331
|
+
console.log(`⏱️ Timeout (${ms}ms)`);
|
|
332
|
+
failed++;
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
} catch (err) {
|
|
336
|
+
console.log(`❌ ${err.message} (${Date.now() - t0}ms)`);
|
|
337
|
+
failed++;
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ── Helper: run a screenshot step ──
|
|
343
|
+
async function runScreenshotStep(label) {
|
|
344
|
+
totalSteps++;
|
|
345
|
+
process.stdout.write(` ${label}... `);
|
|
346
|
+
const t0 = Date.now();
|
|
347
|
+
try {
|
|
348
|
+
const cmd = createControlCommand({ action: "screenshot" });
|
|
349
|
+
const queued = await enqueueCommand(testConfig, cmd);
|
|
350
|
+
const ack = await waitForCommandAck(testConfig, { commandId: queued.id, timeoutMs: 10_000 });
|
|
351
|
+
if (ack.acked) {
|
|
352
|
+
const buf = await fetchScreenshotBinary(testConfig);
|
|
344
353
|
const ms = Date.now() - t0;
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
passed++;
|
|
351
|
-
} else {
|
|
352
|
-
console.log(`⏱️ Timeout (${ms}ms)`);
|
|
353
|
-
failed++;
|
|
354
|
-
}
|
|
354
|
+
console.log(`✅ ${(buf.length / 1024).toFixed(0)}KB (${ms}ms)`);
|
|
355
|
+
passed++;
|
|
356
|
+
} else {
|
|
357
|
+
console.log(`⏱️ Timeout (${Date.now() - t0}ms)`);
|
|
358
|
+
failed++;
|
|
355
359
|
}
|
|
356
360
|
} catch (err) {
|
|
361
|
+
console.log(`❌ ${err.message} (${Date.now() - t0}ms)`);
|
|
362
|
+
failed++;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const pause = () => new Promise((r) => setTimeout(r, 1500));
|
|
367
|
+
|
|
368
|
+
// ── Phase 1: Device Profile ──────────────────────────────
|
|
369
|
+
console.log(" ── Phase 1: Device Info ──");
|
|
370
|
+
totalSteps++;
|
|
371
|
+
process.stdout.write(" 1. Fetch device profile... ");
|
|
372
|
+
{
|
|
373
|
+
const t0 = Date.now();
|
|
374
|
+
try {
|
|
375
|
+
await fetchDeviceProfile(testConfig);
|
|
357
376
|
const ms = Date.now() - t0;
|
|
358
|
-
|
|
377
|
+
if (isDeviceProfileLoaded()) {
|
|
378
|
+
const s = getStaticContext();
|
|
379
|
+
console.log(`✅ ${s.platform} ${s.model}, ${s.osVersion}, ${s.screenWidthPx}x${s.screenHeightPx} (${ms}ms)`);
|
|
380
|
+
passed++;
|
|
381
|
+
} else {
|
|
382
|
+
console.log(`⚠️ Loaded but empty (${ms}ms)`);
|
|
383
|
+
failed++;
|
|
384
|
+
}
|
|
385
|
+
} catch (err) {
|
|
386
|
+
console.log(`❌ ${err.message} (${Date.now() - t0}ms)`);
|
|
387
|
+
failed++;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
totalSteps++;
|
|
392
|
+
process.stdout.write(" 2. Device status fields... ");
|
|
393
|
+
{
|
|
394
|
+
try {
|
|
395
|
+
const status = formatDeviceStatus();
|
|
396
|
+
const ignoredDefaults = new Set(["unknown", "0x0", "-1% (unknown)", "0"]);
|
|
397
|
+
const fields = Object.keys(status).filter((k) => {
|
|
398
|
+
const v = status[k];
|
|
399
|
+
if (v === null || v === undefined) return false;
|
|
400
|
+
if (ignoredDefaults.has(String(v))) return false;
|
|
401
|
+
return true;
|
|
402
|
+
});
|
|
403
|
+
console.log(`✅ ${fields.length} fields (${fields.join(", ")})`);
|
|
404
|
+
passed++;
|
|
405
|
+
} catch (err) {
|
|
406
|
+
console.log(`❌ ${err.message}`);
|
|
359
407
|
failed++;
|
|
360
408
|
}
|
|
361
|
-
// Pause between commands: let phone process + user can observe
|
|
362
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
363
409
|
}
|
|
364
410
|
|
|
365
|
-
|
|
411
|
+
await pause();
|
|
412
|
+
|
|
413
|
+
// ── Phase 2: Screenshot + Basic HID ──────────────────────
|
|
414
|
+
console.log(" ── Phase 2: Screenshot + HID ──");
|
|
415
|
+
await runScreenshotStep("3. Screenshot");
|
|
416
|
+
await pause();
|
|
417
|
+
await runHidStep("4. Click center", { action: "click", xRatio: 0.5, yRatio: 0.5 });
|
|
418
|
+
await pause();
|
|
419
|
+
await runHidStep("5. Swipe up", { action: "swipe", startXRatio: 0.5, startYRatio: 0.7, endXRatio: 0.5, endYRatio: 0.3, durationMs: 300 });
|
|
420
|
+
await pause();
|
|
421
|
+
await runHidStep("6. Swipe down", { action: "swipe", startXRatio: 0.5, startYRatio: 0.3, endXRatio: 0.5, endYRatio: 0.7, durationMs: 300 });
|
|
422
|
+
await pause();
|
|
423
|
+
await runHidStep("7. Scroll down", { action: "scroll", xRatio: 0.5, yRatio: 0.5, direction: "down", amount: 3 });
|
|
424
|
+
await pause();
|
|
425
|
+
await runHidStep("8. Scroll up", { action: "scroll", xRatio: 0.5, yRatio: 0.5, direction: "up", amount: 3 });
|
|
426
|
+
await pause();
|
|
427
|
+
|
|
428
|
+
// ── Phase 3: App + Navigation ────────────────────────────
|
|
429
|
+
console.log(" ── Phase 3: App + Navigation ──");
|
|
430
|
+
await runHidStep("9. Press Home", { action: "home" });
|
|
431
|
+
await pause();
|
|
432
|
+
{
|
|
433
|
+
const platform = isDeviceProfileLoaded() ? getStaticContext().platform : "android";
|
|
434
|
+
const openParams = platform === "ios"
|
|
435
|
+
? { action: "open_app", bundleId: "com.tencent.xin" }
|
|
436
|
+
: { action: "open_app", appPackage: "com.tencent.mm" };
|
|
437
|
+
await runHidStep(`10. Open WeChat (${platform})`, openParams);
|
|
438
|
+
}
|
|
439
|
+
await pause();
|
|
440
|
+
await runHidStep("11. Press Back", { action: "back" });
|
|
441
|
+
await pause();
|
|
442
|
+
|
|
443
|
+
// ── Phase 4: Clipboard Set ─────────────────────────────
|
|
444
|
+
// Note: App only supports clipboard set, not get
|
|
445
|
+
console.log(" ── Phase 4: Clipboard ──");
|
|
446
|
+
const clipboardTestText = `zhihand_test_${Date.now()}`;
|
|
447
|
+
await runHidStep("12. Clipboard set", { action: "clipboard", text: clipboardTestText });
|
|
448
|
+
|
|
449
|
+
// ── Summary ──────────────────────────────────────────────
|
|
450
|
+
console.log(`\n Result: ${passed}/${totalSteps} passed`);
|
|
366
451
|
if (failed === 0) {
|
|
367
|
-
console.log(" ✅ Device is
|
|
452
|
+
console.log(" ✅ All tests passed! Device is fully responsive.");
|
|
368
453
|
} else {
|
|
369
|
-
console.log(
|
|
454
|
+
console.log(` ⚠️ ${failed} test(s) failed. Check phone connectivity.`);
|
|
370
455
|
}
|
|
371
456
|
process.exit(failed > 0 ? 1 : 0);
|
|
372
457
|
}
|
package/dist/core/command.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { ZhiHandConfig } from "./config.ts";
|
|
2
2
|
export type ScrollDirection = "up" | "down" | "left" | "right";
|
|
3
|
-
export type ClipboardAction = "get" | "set";
|
|
4
3
|
export interface ControlParams {
|
|
5
4
|
action: string;
|
|
6
5
|
xRatio?: number;
|
|
@@ -9,7 +8,6 @@ export interface ControlParams {
|
|
|
9
8
|
direction?: ScrollDirection;
|
|
10
9
|
amount?: number;
|
|
11
10
|
keys?: string;
|
|
12
|
-
clipboardAction?: ClipboardAction;
|
|
13
11
|
durationMs?: number;
|
|
14
12
|
startXRatio?: number;
|
|
15
13
|
startYRatio?: number;
|
package/dist/core/command.js
CHANGED
|
@@ -49,9 +49,11 @@ export function createControlCommand(params) {
|
|
|
49
49
|
case "enter":
|
|
50
50
|
return { type: "receive_enter", payload: {} };
|
|
51
51
|
case "clipboard":
|
|
52
|
+
// App only supports set — payload: { clipboard: "text" }
|
|
53
|
+
// No get support on device side; clipboardAction is ignored
|
|
52
54
|
return {
|
|
53
55
|
type: "receive_clipboard",
|
|
54
|
-
payload: {
|
|
56
|
+
payload: { clipboard: params.text ?? "" },
|
|
55
57
|
};
|
|
56
58
|
case "open_app": {
|
|
57
59
|
const appPayload = {};
|
|
@@ -397,7 +397,7 @@ Control the phone. Requires "action" parameter. All coordinates use normalized r
|
|
|
397
397
|
- home: Press Home button (no params)
|
|
398
398
|
- enter: Press Enter key (no params)
|
|
399
399
|
${openAppDoc}
|
|
400
|
-
- clipboard:
|
|
400
|
+
- clipboard: Set clipboard text. Params: text (the content to copy)
|
|
401
401
|
- screenshot: Capture screen via control (same as zhihand_screenshot)
|
|
402
402
|
- wait: Wait before next action. Params: durationMs (default 1000)
|
|
403
403
|
|
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.26.
|
|
2
|
+
export declare const PACKAGE_VERSION = "0.26.4";
|
|
3
3
|
export declare function createServer(deviceName?: string): McpServer;
|
|
4
4
|
export declare function startStdioServer(deviceName?: string): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { executeControl } from "./tools/control.js";
|
|
|
6
6
|
import { handleScreenshot } from "./tools/screenshot.js";
|
|
7
7
|
import { handlePair } from "./tools/pair.js";
|
|
8
8
|
import { getStaticContext, getDynamicContext, fetchDeviceProfile, buildControlToolDescription, buildScreenshotToolDescription, formatDeviceStatus, } from "./core/device.js";
|
|
9
|
-
export const PACKAGE_VERSION = "0.26.
|
|
9
|
+
export const PACKAGE_VERSION = "0.26.4";
|
|
10
10
|
export function createServer(deviceName) {
|
|
11
11
|
const server = new McpServer({
|
|
12
12
|
name: "zhihand",
|
package/dist/tools/schemas.d.ts
CHANGED
|
@@ -7,7 +7,6 @@ export declare const controlSchema: {
|
|
|
7
7
|
direction: z.ZodOptional<z.ZodEnum<["up", "down", "left", "right"]>>;
|
|
8
8
|
amount: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
|
|
9
9
|
keys: z.ZodOptional<z.ZodString>;
|
|
10
|
-
clipboardAction: z.ZodOptional<z.ZodEnum<["get", "set"]>>;
|
|
11
10
|
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
12
11
|
startXRatio: z.ZodOptional<z.ZodNumber>;
|
|
13
12
|
startYRatio: z.ZodOptional<z.ZodNumber>;
|
package/dist/tools/schemas.js
CHANGED
|
@@ -13,7 +13,7 @@ export const controlSchema = {
|
|
|
13
13
|
direction: z.enum(["up", "down", "left", "right"]).optional().describe("Scroll direction"),
|
|
14
14
|
amount: z.number().int().positive().default(3).optional().describe("Scroll steps (default 3)"),
|
|
15
15
|
keys: z.string().optional().describe("Key combo string, e.g. 'ctrl+c', 'alt+tab'"),
|
|
16
|
-
clipboardAction
|
|
16
|
+
// clipboardAction removed — app only supports set (text via "text" param)
|
|
17
17
|
durationMs: z.number().int().positive().max(10000).optional().describe("Duration in ms: wait (default 1000), longclick (default 800), swipe (default 300). Max 10000"),
|
|
18
18
|
startXRatio: z.number().min(0).max(1).optional().describe("Swipe start X [0,1]"),
|
|
19
19
|
startYRatio: z.number().min(0).max(1).optional().describe("Swipe start Y [0,1]"),
|