@zhihand/mcp 0.26.2 → 0.26.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/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,176 @@ 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
- for (const step of steps) {
323
- process.stdout.write(` ${step.label}... `);
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
- if (step.type === "screenshot") {
327
- // Screenshot: send receive_screenshot command, then fetch binary
328
- const cmd = createControlCommand({ action: "screenshot" });
329
- const queued = await enqueueCommand(testConfig, cmd);
330
- const ack = await waitForCommandAck(testConfig, { commandId: queued.id, timeoutMs: 10_000 });
331
- if (ack.acked) {
332
- const buf = await fetchScreenshotBinary(testConfig);
333
- const ms = Date.now() - t0;
334
- console.log(`✅ ${(buf.length / 1024).toFixed(0)}KB (${ms}ms)`);
335
- passed++;
336
- } else {
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
- const cmd = createControlCommand(step.params);
342
- const queued = await enqueueCommand(testConfig, cmd);
343
- const ack = await waitForCommandAck(testConfig, { commandId: queued.id, timeoutMs: 10_000 });
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
- if (ack.acked) {
346
- const ackStatus = ack.command?.ack_status ?? "ok";
347
- const detail = ackStatus !== "ok" ? ` [${ackStatus}]` : "";
348
- const resultInfo = ack.command?.ack_result ? ` ${JSON.stringify(ack.command.ack_result)}` : "";
349
- console.log(`✅ (${ms}ms)${detail}${resultInfo}`);
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
- console.log(`❌ ${err.message} (${ms}ms)`);
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)`);
359
387
  failed++;
360
388
  }
361
- // Pause between commands: let phone process + user can observe
362
- await new Promise((r) => setTimeout(r, 2000));
363
389
  }
364
390
 
365
- console.log(`\n Result: ${passed}/${steps.length} passed`);
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}`);
407
+ failed++;
408
+ }
409
+ }
410
+
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 Roundtrip ─────────────────────────
444
+ console.log(" ── Phase 4: Clipboard Roundtrip ──");
445
+ const clipboardTestText = `zhihand_test_${Date.now()}`;
446
+
447
+ const setAck = await runHidStep("12. Clipboard set", { action: "clipboard", clipboardAction: "set", text: clipboardTestText });
448
+ await pause();
449
+
450
+ const getAck = await runHidStep("13. Clipboard get", { action: "clipboard", clipboardAction: "get" });
451
+ await pause();
452
+
453
+ // Verify roundtrip
454
+ totalSteps++;
455
+ process.stdout.write(" 14. Clipboard roundtrip verify... ");
456
+ if (setAck && getAck) {
457
+ const returned = getAck.command?.ack_result?.text ?? getAck.command?.ack_result?.clipboard ?? null;
458
+ if (returned === clipboardTestText) {
459
+ console.log(`✅ Match: "${clipboardTestText}"`);
460
+ passed++;
461
+ } else if (returned) {
462
+ console.log(`⚠️ Mismatch: sent "${clipboardTestText}", got "${returned}"`);
463
+ failed++;
464
+ } else {
465
+ console.log(`⚠️ No text in ack_result (keys: ${JSON.stringify(Object.keys(getAck.command?.ack_result ?? {}))})`);
466
+ failed++;
467
+ }
468
+ } else {
469
+ console.log(`⏭️ Skipped (clipboard set/get failed)`);
470
+ failed++;
471
+ }
472
+
473
+ // ── Summary ──────────────────────────────────────────────
474
+ console.log(`\n Result: ${passed}/${totalSteps} passed`);
366
475
  if (failed === 0) {
367
- console.log(" ✅ Device is responding to all commands!");
476
+ console.log(" ✅ All tests passed! Device is fully responsive.");
368
477
  } else {
369
- console.log(" ⚠️ Some commands failed. Check phone connectivity.");
478
+ console.log(` ⚠️ ${failed} test(s) failed. Check phone connectivity.`);
370
479
  }
371
480
  process.exit(failed > 0 ? 1 : 0);
372
481
  }
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";
2
+ export declare const PACKAGE_VERSION = "0.26.3";
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.2";
9
+ export const PACKAGE_VERSION = "0.26.3";
10
10
  export function createServer(deviceName) {
11
11
  const server = new McpServer({
12
12
  name: "zhihand",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhihand/mcp",
3
- "version": "0.26.2",
3
+ "version": "0.26.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "ZhiHand MCP Server — phone control tools for Claude Code, Codex, Gemini CLI, and OpenClaw",