@zhihand/mcp 0.33.0 → 0.33.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/bin/zhihand +22 -3
- package/dist/core/logger.d.ts +10 -5
- package/dist/core/logger.js +37 -9
- package/dist/core/ws.js +3 -0
- package/dist/daemon/index.js +12 -3
- package/dist/daemon/logger.d.ts +5 -3
- package/dist/daemon/logger.js +10 -11
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/bin/zhihand
CHANGED
|
@@ -30,7 +30,7 @@ import { fetchUserCredentials } from "../dist/core/ws.js";
|
|
|
30
30
|
import { configureMCP, displayName } from "../dist/cli/mcp-config.js";
|
|
31
31
|
|
|
32
32
|
const DEFAULT_ENDPOINT = "https://api.zhihand.com";
|
|
33
|
-
const VERSION = "0.33.
|
|
33
|
+
const VERSION = "0.33.1";
|
|
34
34
|
|
|
35
35
|
const CLI_TOOL_MAP = {
|
|
36
36
|
claude: "claudecode",
|
|
@@ -558,6 +558,9 @@ switch (command) {
|
|
|
558
558
|
const { createControlCommand, createSystemCommand } = await import("../dist/core/command.js");
|
|
559
559
|
const { fetchScreenshot, getSnapshotStaleThresholdMs } = await import("../dist/core/screenshot.js");
|
|
560
560
|
const { fetchDeviceProfileOnce, extractStatic, computeCapabilities, formatDeviceStatus } = await import("../dist/core/device.js");
|
|
561
|
+
const { setDebugEnabled: setCoreDebug, setTimestampEnabled, log: coreLog } = await import("../dist/core/logger.js");
|
|
562
|
+
if (values.debug) setCoreDebug(true);
|
|
563
|
+
setTimestampEnabled(true);
|
|
561
564
|
|
|
562
565
|
const KIND_CAPABILITY = {
|
|
563
566
|
profile: "none", status: "none", screenshot: "screen", hid: "hid", system: "none",
|
|
@@ -633,17 +636,26 @@ switch (command) {
|
|
|
633
636
|
const DAEMON_PORT = parseInt(process.env.ZHIHAND_PORT ?? "", 10) || 18686;
|
|
634
637
|
const DAEMON_BASE = `http://127.0.0.1:${DAEMON_PORT}`;
|
|
635
638
|
let daemonOk = false;
|
|
639
|
+
let daemonStatus = null;
|
|
636
640
|
try {
|
|
637
641
|
const resp = await fetch(`${DAEMON_BASE}/internal/status`, { signal: AbortSignal.timeout(2000) });
|
|
638
642
|
daemonOk = resp.ok;
|
|
643
|
+
if (resp.ok) daemonStatus = await resp.json();
|
|
639
644
|
} catch { /* daemon not reachable */ }
|
|
640
645
|
if (!daemonOk) {
|
|
641
646
|
console.error("❌ Daemon is not running. Start it first: zhihand start");
|
|
642
647
|
process.exit(1);
|
|
643
648
|
}
|
|
644
649
|
|
|
650
|
+
const testDbg = values.debug
|
|
651
|
+
? (msg) => coreLog.debug(`[test] ${msg}`)
|
|
652
|
+
: () => {};
|
|
653
|
+
|
|
645
654
|
// Execute command via daemon's /internal/exec endpoint
|
|
646
655
|
async function execViaDaemon(command, timeoutMs = 10_000) {
|
|
656
|
+
const action = command?.payload?.action ?? command?.type ?? "?";
|
|
657
|
+
testDbg(`exec action=${action} timeout=${timeoutMs}ms`);
|
|
658
|
+
const t0 = Date.now();
|
|
647
659
|
const resp = await fetch(`${DAEMON_BASE}/internal/exec`, {
|
|
648
660
|
method: "POST",
|
|
649
661
|
headers: { "Content-Type": "application/json" },
|
|
@@ -652,9 +664,12 @@ switch (command) {
|
|
|
652
664
|
});
|
|
653
665
|
if (!resp.ok) {
|
|
654
666
|
const body = await resp.text();
|
|
667
|
+
testDbg(`exec FAILED ${resp.status} ${Date.now() - t0}ms`);
|
|
655
668
|
throw new Error(`Daemon exec failed: ${resp.status} ${body}`);
|
|
656
669
|
}
|
|
657
|
-
|
|
670
|
+
const result = await resp.json();
|
|
671
|
+
testDbg(`exec done action=${action} acked=${result.acked} id=${result.id ?? "-"} ${Date.now() - t0}ms`);
|
|
672
|
+
return result;
|
|
658
673
|
}
|
|
659
674
|
|
|
660
675
|
const forceRun = values.force === true;
|
|
@@ -681,7 +696,11 @@ switch (command) {
|
|
|
681
696
|
|
|
682
697
|
console.log("🔧 ZhiHand Device Test");
|
|
683
698
|
console.log(` Device: ${testConfig.credentialId}`);
|
|
684
|
-
console.log(` Endpoint: ${testConfig.controlPlaneEndpoint}
|
|
699
|
+
console.log(` Endpoint: ${testConfig.controlPlaneEndpoint}`);
|
|
700
|
+
if (daemonStatus) {
|
|
701
|
+
console.log(` Daemon: v${daemonStatus.version} pid=${daemonStatus.pid} backend=${daemonStatus.backend ?? "none"}`);
|
|
702
|
+
}
|
|
703
|
+
console.log("");
|
|
685
704
|
|
|
686
705
|
// Pre-fetch device profile
|
|
687
706
|
let currentRawAttrs = {};
|
package/dist/core/logger.d.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unified logger — all log output goes to stderr so stdout stays clean
|
|
3
|
-
* for MCP JSON-RPC.
|
|
4
|
-
* in core/ and tools/ code.
|
|
3
|
+
* for MCP JSON-RPC.
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* (daemon/logger.ts) remains for daemon-specific verbose output.
|
|
5
|
+
* All modules (core/, daemon/, tools/) should use this logger.
|
|
6
|
+
* The daemon's dbg() in daemon/logger.ts delegates here for the debug flag.
|
|
9
7
|
*/
|
|
8
|
+
/**
|
|
9
|
+
* Redact sensitive tokens from log messages.
|
|
10
|
+
* Replaces Bearer tokens and controller_token values with <REDACTED>.
|
|
11
|
+
*/
|
|
12
|
+
export declare function redact(msg: string): string;
|
|
10
13
|
export declare const log: {
|
|
11
14
|
info: (...args: unknown[]) => void;
|
|
12
15
|
warn: (...args: unknown[]) => void;
|
|
@@ -15,3 +18,5 @@ export declare const log: {
|
|
|
15
18
|
};
|
|
16
19
|
export declare function setDebugEnabled(v: boolean): void;
|
|
17
20
|
export declare function isDebugEnabled(): boolean;
|
|
21
|
+
/** Enable timestamps in log output (for daemon / CLI long-running processes). */
|
|
22
|
+
export declare function setTimestampEnabled(v: boolean): void;
|
package/dist/core/logger.js
CHANGED
|
@@ -1,26 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unified logger — all log output goes to stderr so stdout stays clean
|
|
3
|
-
* for MCP JSON-RPC.
|
|
4
|
-
* in core/ and tools/ code.
|
|
3
|
+
* for MCP JSON-RPC.
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* (daemon/logger.ts) remains for daemon-specific verbose output.
|
|
5
|
+
* All modules (core/, daemon/, tools/) should use this logger.
|
|
6
|
+
* The daemon's dbg() in daemon/logger.ts delegates here for the debug flag.
|
|
9
7
|
*/
|
|
10
8
|
let debugEnabled = false;
|
|
9
|
+
let timestampEnabled = false;
|
|
10
|
+
// ── Token redaction ──────────────────────────────────────
|
|
11
|
+
const REDACT_PATTERNS = [
|
|
12
|
+
// Bearer tokens in headers / JSON
|
|
13
|
+
/(Bearer\s+)[^\s"',}]+/gi,
|
|
14
|
+
// controller_token in JSON / key=value
|
|
15
|
+
/(controller_token["']?\s*[:=]\s*["']?)[^\s"',}]+/gi,
|
|
16
|
+
];
|
|
17
|
+
/**
|
|
18
|
+
* Redact sensitive tokens from log messages.
|
|
19
|
+
* Replaces Bearer tokens and controller_token values with <REDACTED>.
|
|
20
|
+
*/
|
|
21
|
+
export function redact(msg) {
|
|
22
|
+
let result = msg;
|
|
23
|
+
for (const pattern of REDACT_PATTERNS) {
|
|
24
|
+
result = result.replace(pattern, "$1<REDACTED>");
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
// ── Logger ───────────────────────────────────────────────
|
|
29
|
+
function prefix(level) {
|
|
30
|
+
if (timestampEnabled) {
|
|
31
|
+
return `[${new Date().toLocaleTimeString()}] [${level}] `;
|
|
32
|
+
}
|
|
33
|
+
return `[${level.padEnd(5)}] `;
|
|
34
|
+
}
|
|
11
35
|
export const log = {
|
|
12
36
|
info: (...args) => {
|
|
13
|
-
process.stderr.write(
|
|
37
|
+
process.stderr.write(`${prefix("info")}${redact(args.map(String).join(" "))}\n`);
|
|
14
38
|
},
|
|
15
39
|
warn: (...args) => {
|
|
16
|
-
process.stderr.write(
|
|
40
|
+
process.stderr.write(`${prefix("warn")}${redact(args.map(String).join(" "))}\n`);
|
|
17
41
|
},
|
|
18
42
|
error: (...args) => {
|
|
19
|
-
process.stderr.write(
|
|
43
|
+
process.stderr.write(`${prefix("error")}${redact(args.map(String).join(" "))}\n`);
|
|
20
44
|
},
|
|
21
45
|
debug: (...args) => {
|
|
22
46
|
if (debugEnabled) {
|
|
23
|
-
process.stderr.write(
|
|
47
|
+
process.stderr.write(`${prefix("debug")}${redact(args.map(String).join(" "))}\n`);
|
|
24
48
|
}
|
|
25
49
|
},
|
|
26
50
|
};
|
|
@@ -30,3 +54,7 @@ export function setDebugEnabled(v) {
|
|
|
30
54
|
export function isDebugEnabled() {
|
|
31
55
|
return debugEnabled;
|
|
32
56
|
}
|
|
57
|
+
/** Enable timestamps in log output (for daemon / CLI long-running processes). */
|
|
58
|
+
export function setTimestampEnabled(v) {
|
|
59
|
+
timestampEnabled = v;
|
|
60
|
+
}
|
package/dist/core/ws.js
CHANGED
|
@@ -283,12 +283,15 @@ export async function fetchUserCredentials(endpoint, userId, controllerToken, on
|
|
|
283
283
|
export async function waitForCommandAck(_config, options) {
|
|
284
284
|
const timeoutMs = options.timeoutMs ?? 15_000;
|
|
285
285
|
log.debug(`[ws-cmd] Waiting for ACK: commandId=${options.commandId}, timeout=${timeoutMs}ms`);
|
|
286
|
+
const t0 = Date.now();
|
|
286
287
|
return new Promise((resolve, reject) => {
|
|
287
288
|
const timeout = setTimeout(() => {
|
|
289
|
+
log.debug(`[ws-cmd] ACK timeout: commandId=${options.commandId} after ${Date.now() - t0}ms`);
|
|
288
290
|
cleanup();
|
|
289
291
|
resolve({ acked: false });
|
|
290
292
|
}, timeoutMs);
|
|
291
293
|
const unsubscribe = subscribeToCommandAck(options.commandId, (ackedCommand) => {
|
|
294
|
+
log.debug(`[ws-cmd] ACK received: commandId=${options.commandId} status=${ackedCommand.ack_status ?? "ok"} ${Date.now() - t0}ms`);
|
|
292
295
|
cleanup();
|
|
293
296
|
resolve({ acked: true, command: ackedCommand });
|
|
294
297
|
});
|
package/dist/daemon/index.js
CHANGED
|
@@ -21,9 +21,9 @@ let activeBackend = null;
|
|
|
21
21
|
let activeModel = null; // user-selected model alias, null = use default
|
|
22
22
|
let isProcessing = false;
|
|
23
23
|
const promptQueue = [];
|
|
24
|
+
import { log as coreLog, setTimestampEnabled } from "../core/logger.js";
|
|
24
25
|
function log(msg) {
|
|
25
|
-
|
|
26
|
-
process.stdout.write(`[${ts}] ${msg}\n`);
|
|
26
|
+
coreLog.info(msg);
|
|
27
27
|
}
|
|
28
28
|
// ── Prompt Processing ──────────────────────────────────────
|
|
29
29
|
async function processPrompt(config, prompt) {
|
|
@@ -106,7 +106,6 @@ function handleInternalAPI(req, res) {
|
|
|
106
106
|
}
|
|
107
107
|
// Execute command via daemon's WS (used by zhihand test)
|
|
108
108
|
if (url === "/internal/exec" && req.method === "POST") {
|
|
109
|
-
dbg(`[api] POST /internal/exec`);
|
|
110
109
|
let body = "";
|
|
111
110
|
const MAX_BODY = 10 * 1024;
|
|
112
111
|
req.on("data", (chunk) => {
|
|
@@ -118,19 +117,28 @@ function handleInternalAPI(req, res) {
|
|
|
118
117
|
}
|
|
119
118
|
});
|
|
120
119
|
req.on("end", async () => {
|
|
120
|
+
const t0 = Date.now();
|
|
121
121
|
try {
|
|
122
122
|
const { command, credentialId, timeoutMs } = JSON.parse(body);
|
|
123
|
+
const cmdType = command.type ?? "unknown";
|
|
124
|
+
const cmdAction = command.payload?.action ?? "-";
|
|
125
|
+
dbg(`[api] POST /internal/exec cred=${credentialId} type=${cmdType} action=${cmdAction} timeout=${timeoutMs ?? 10_000}ms`);
|
|
123
126
|
const cfg = resolveConfig(credentialId);
|
|
124
127
|
const effectiveTimeout = timeoutMs ?? 10_000;
|
|
125
128
|
const queued = await enqueueCommand(cfg, command);
|
|
129
|
+
dbg(`[api] /internal/exec enqueued id=${queued.id}`);
|
|
126
130
|
const ack = await waitForCommandAck(cfg, {
|
|
127
131
|
commandId: queued.id,
|
|
128
132
|
timeoutMs: effectiveTimeout,
|
|
129
133
|
});
|
|
134
|
+
const ms = Date.now() - t0;
|
|
135
|
+
const ackStatus = ack.acked ? (ack.command?.ack_status ?? "ok") : "timeout";
|
|
136
|
+
dbg(`[api] /internal/exec done id=${queued.id} acked=${ack.acked} status=${ackStatus} ${ms}ms`);
|
|
130
137
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
131
138
|
res.end(JSON.stringify({ id: queued.id, ...ack }));
|
|
132
139
|
}
|
|
133
140
|
catch (err) {
|
|
141
|
+
dbg(`[api] /internal/exec error: ${err.message} ${Date.now() - t0}ms`);
|
|
134
142
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
135
143
|
res.end(JSON.stringify({ error: err.message }));
|
|
136
144
|
}
|
|
@@ -190,6 +198,7 @@ export function isAlreadyRunning() {
|
|
|
190
198
|
}
|
|
191
199
|
// ── Main Daemon Entry ──────���───────────────────────────────
|
|
192
200
|
export async function startDaemon(options) {
|
|
201
|
+
setTimestampEnabled(true);
|
|
193
202
|
if (options?.debug)
|
|
194
203
|
setDebugEnabled(true);
|
|
195
204
|
const port = options?.port ?? (parseInt(process.env.ZHIHAND_PORT ?? "", 10) || DEFAULT_PORT);
|
package/dist/daemon/logger.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Debug logger for ZhiHand daemon.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Delegates to core/logger.ts for the debug flag, redaction, and output.
|
|
5
|
+
* All output goes to stderr to keep stdout clean for MCP JSON-RPC.
|
|
6
|
+
*
|
|
7
|
+
* Enable with `zhihand start --debug`.
|
|
6
8
|
*/
|
|
7
9
|
export declare function setDebugEnabled(enabled: boolean): void;
|
|
8
10
|
export declare function isDebugEnabled(): boolean;
|
|
9
|
-
/** Debug log — only outputs when --debug is active. */
|
|
11
|
+
/** Debug log — only outputs when --debug is active. Writes to stderr with redaction. */
|
|
10
12
|
export declare function dbg(msg: string): void;
|
package/dist/daemon/logger.js
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Debug logger for ZhiHand daemon.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Delegates to core/logger.ts for the debug flag, redaction, and output.
|
|
5
|
+
* All output goes to stderr to keep stdout clean for MCP JSON-RPC.
|
|
6
|
+
*
|
|
7
|
+
* Enable with `zhihand start --debug`.
|
|
6
8
|
*/
|
|
7
|
-
|
|
9
|
+
import { log, setDebugEnabled as coreSetDebug, isDebugEnabled as coreIsDebug, } from "../core/logger.js";
|
|
8
10
|
export function setDebugEnabled(enabled) {
|
|
9
|
-
|
|
11
|
+
coreSetDebug(enabled);
|
|
10
12
|
}
|
|
11
13
|
export function isDebugEnabled() {
|
|
12
|
-
return
|
|
13
|
-
}
|
|
14
|
-
function ts() {
|
|
15
|
-
return new Date().toLocaleTimeString();
|
|
14
|
+
return coreIsDebug();
|
|
16
15
|
}
|
|
17
|
-
/** Debug log — only outputs when --debug is active. */
|
|
16
|
+
/** Debug log — only outputs when --debug is active. Writes to stderr with redaction. */
|
|
18
17
|
export function dbg(msg) {
|
|
19
|
-
if (!
|
|
18
|
+
if (!coreIsDebug())
|
|
20
19
|
return;
|
|
21
|
-
|
|
20
|
+
log.debug(msg);
|
|
22
21
|
}
|
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.33.
|
|
2
|
+
export declare const PACKAGE_VERSION = "0.33.1";
|
|
3
3
|
export declare function createServer(): McpServer;
|
|
4
4
|
export declare function startStdioServer(): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { handlePair } from "./tools/pair.js";
|
|
|
8
8
|
import { resolveTargetDevice } from "./tools/resolve.js";
|
|
9
9
|
import { buildControlToolDescription, buildSystemToolDescription, buildScreenshotToolDescription, formatDeviceStatus, extractDynamic, } from "./core/device.js";
|
|
10
10
|
import { registry } from "./core/registry.js";
|
|
11
|
-
export const PACKAGE_VERSION = "0.33.
|
|
11
|
+
export const PACKAGE_VERSION = "0.33.1";
|
|
12
12
|
function errorResult(message) {
|
|
13
13
|
return { content: [{ type: "text", text: message }], isError: true };
|
|
14
14
|
}
|