@zhihand/mcp 0.29.0 → 0.32.0
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 +448 -212
- package/dist/core/command.d.ts +5 -5
- package/dist/core/command.js +6 -8
- package/dist/core/config.d.ts +48 -21
- package/dist/core/config.js +178 -42
- package/dist/core/device.d.ts +28 -19
- package/dist/core/device.js +168 -145
- package/dist/core/logger.d.ts +17 -0
- package/dist/core/logger.js +32 -0
- package/dist/core/pair.d.ts +39 -31
- package/dist/core/pair.js +205 -77
- package/dist/core/registry.d.ts +60 -0
- package/dist/core/registry.js +415 -0
- package/dist/core/screenshot.d.ts +3 -3
- package/dist/core/screenshot.js +3 -2
- package/dist/core/sse.d.ts +40 -18
- package/dist/core/sse.js +122 -62
- package/dist/core/ws.d.ts +92 -0
- package/dist/core/ws.js +327 -0
- package/dist/daemon/dispatcher.d.ts +3 -1
- package/dist/daemon/dispatcher.js +4 -3
- package/dist/daemon/heartbeat.d.ts +4 -4
- package/dist/daemon/heartbeat.js +1 -1
- package/dist/daemon/index.js +10 -8
- package/dist/daemon/prompt-listener.d.ts +8 -7
- package/dist/daemon/prompt-listener.js +59 -99
- package/dist/index.d.ts +3 -3
- package/dist/index.js +104 -40
- package/dist/openclaw.adapter.js +10 -2
- package/dist/tools/control.d.ts +10 -3
- package/dist/tools/control.js +18 -24
- package/dist/tools/pair.d.ts +1 -1
- package/dist/tools/pair.js +22 -28
- package/dist/tools/resolve.d.ts +7 -0
- package/dist/tools/resolve.js +22 -0
- package/dist/tools/schemas.d.ts +9 -1
- package/dist/tools/schemas.js +10 -8
- package/dist/tools/screenshot.d.ts +3 -2
- package/dist/tools/screenshot.js +2 -2
- package/dist/tools/system.d.ts +3 -5
- package/dist/tools/system.js +19 -6
- package/package.json +3 -1
package/dist/core/command.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ZhiHandRuntimeConfig } from "./config.ts";
|
|
2
2
|
export type ScrollDirection = "up" | "down" | "left" | "right";
|
|
3
3
|
export interface ControlParams {
|
|
4
4
|
action: string;
|
|
@@ -36,12 +36,12 @@ export interface WaitForCommandAckResult {
|
|
|
36
36
|
acked: boolean;
|
|
37
37
|
command?: QueuedCommandRecord;
|
|
38
38
|
}
|
|
39
|
-
export declare function createControlCommand(params: ControlParams): QueuedControlCommand;
|
|
39
|
+
export declare function createControlCommand(params: ControlParams, platform?: string): QueuedControlCommand;
|
|
40
40
|
export interface SystemParams {
|
|
41
41
|
action: string;
|
|
42
42
|
text?: string;
|
|
43
43
|
}
|
|
44
|
-
export declare function createSystemCommand(params: SystemParams): QueuedControlCommand;
|
|
45
|
-
export declare function enqueueCommand(config:
|
|
46
|
-
export declare function getCommand(config:
|
|
44
|
+
export declare function createSystemCommand(params: SystemParams, platform?: string): QueuedControlCommand;
|
|
45
|
+
export declare function enqueueCommand(config: ZhiHandRuntimeConfig, command: QueuedControlCommand): Promise<QueuedCommandRecord>;
|
|
46
|
+
export declare function getCommand(config: ZhiHandRuntimeConfig, commandId: string): Promise<QueuedCommandRecord>;
|
|
47
47
|
export declare function formatAckSummary(action: string, result: WaitForCommandAckResult): string;
|
package/dist/core/command.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { log } from "./logger.js";
|
|
2
|
+
const dbg = (msg) => log.debug(msg);
|
|
3
3
|
let messageCounter = 0;
|
|
4
4
|
function nextMessageId() {
|
|
5
5
|
messageCounter = (messageCounter + 1) % 1000;
|
|
6
6
|
return (Date.now() * 1000) + messageCounter;
|
|
7
7
|
}
|
|
8
|
-
export function createControlCommand(params) {
|
|
8
|
+
export function createControlCommand(params, platform = "unknown") {
|
|
9
9
|
switch (params.action) {
|
|
10
10
|
case "click":
|
|
11
11
|
return { type: "receive_click", payload: { x: params.xRatio, y: params.yRatio } };
|
|
@@ -57,7 +57,6 @@ export function createControlCommand(params) {
|
|
|
57
57
|
};
|
|
58
58
|
case "open_app": {
|
|
59
59
|
const appPayload = {};
|
|
60
|
-
const platform = isDeviceProfileLoaded() ? getStaticContext().platform : "unknown";
|
|
61
60
|
// Only send platform-appropriate fields — Android strict JSON rejects unknown keys
|
|
62
61
|
if (platform === "android") {
|
|
63
62
|
// Android: only app_package
|
|
@@ -95,8 +94,7 @@ export function createControlCommand(params) {
|
|
|
95
94
|
}
|
|
96
95
|
const IOS_ONLY_ACTIONS = new Set(["siri", "control_center"]);
|
|
97
96
|
const ANDROID_ONLY_ACTIONS = new Set(["open_browser", "shortcut_help"]);
|
|
98
|
-
export function createSystemCommand(params) {
|
|
99
|
-
const platform = isDeviceProfileLoaded() ? getStaticContext().platform : "unknown";
|
|
97
|
+
export function createSystemCommand(params, platform = "unknown") {
|
|
100
98
|
// Platform validation — block mismatched platform-specific actions
|
|
101
99
|
if (platform === "android" && IOS_ONLY_ACTIONS.has(params.action)) {
|
|
102
100
|
throw new Error(`Action '${params.action}' is not supported on Android.`);
|
|
@@ -160,7 +158,7 @@ export async function enqueueCommand(config, command) {
|
|
|
160
158
|
method: "POST",
|
|
161
159
|
headers: {
|
|
162
160
|
"Content-Type": "application/json",
|
|
163
|
-
"
|
|
161
|
+
"Authorization": `Bearer ${config.controllerToken}`,
|
|
164
162
|
},
|
|
165
163
|
body: JSON.stringify(body),
|
|
166
164
|
});
|
|
@@ -176,7 +174,7 @@ export async function getCommand(config, commandId) {
|
|
|
176
174
|
const url = `${config.controlPlaneEndpoint}/v1/credentials/${encodeURIComponent(config.credentialId)}/commands/${encodeURIComponent(commandId)}`;
|
|
177
175
|
dbg(`[cmd] GET ${url}`);
|
|
178
176
|
const response = await fetch(url, {
|
|
179
|
-
headers: { "
|
|
177
|
+
headers: { "Authorization": `Bearer ${config.controllerToken}` },
|
|
180
178
|
});
|
|
181
179
|
if (!response.ok) {
|
|
182
180
|
dbg(`[cmd] Get failed: ${response.status}`);
|
package/dist/core/config.d.ts
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
export type DevicePlatform = "ios" | "android" | "unknown";
|
|
2
|
+
export interface DeviceRecord {
|
|
3
|
+
credential_id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
platform: DevicePlatform;
|
|
6
|
+
paired_at: string;
|
|
7
|
+
last_seen_at: string;
|
|
8
|
+
}
|
|
9
|
+
export interface UserRecord {
|
|
10
|
+
user_id: string;
|
|
11
|
+
controller_token: string;
|
|
12
|
+
label: string;
|
|
13
|
+
created_at: string;
|
|
14
|
+
devices: DeviceRecord[];
|
|
7
15
|
}
|
|
8
|
-
export interface
|
|
9
|
-
|
|
10
|
-
|
|
16
|
+
export interface ZhihandConfigV3 {
|
|
17
|
+
schema_version: 3;
|
|
18
|
+
users: Record<string, UserRecord>;
|
|
11
19
|
}
|
|
12
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Runtime config passed to HTTP callers (command/sse/device endpoints).
|
|
22
|
+
* Derived from a UserRecord + DeviceRecord pair.
|
|
23
|
+
*/
|
|
24
|
+
export interface ZhiHandRuntimeConfig {
|
|
13
25
|
controlPlaneEndpoint: string;
|
|
14
26
|
credentialId: string;
|
|
15
27
|
controllerToken: string;
|
|
@@ -21,20 +33,35 @@ export interface BackendConfig {
|
|
|
21
33
|
activeBackend: BackendName | null;
|
|
22
34
|
model?: string | null;
|
|
23
35
|
}
|
|
24
|
-
/**
|
|
25
|
-
* Default model aliases per backend.
|
|
26
|
-
* These are generic aliases that the respective CLIs resolve to the latest version:
|
|
27
|
-
* - Gemini CLI: "flash" → latest flash model (e.g. gemini-2.5-flash)
|
|
28
|
-
* - Claude Code: "sonnet" → latest sonnet (e.g. claude-sonnet-4-20250514)
|
|
29
|
-
* - Codex CLI: requires full model name, no alias support
|
|
30
|
-
*/
|
|
31
36
|
export declare const DEFAULT_MODELS: Record<Exclude<BackendName, "openclaw">, string>;
|
|
32
37
|
export declare function resolveZhiHandDir(): string;
|
|
33
38
|
export declare function ensureZhiHandDir(): void;
|
|
34
|
-
export declare function
|
|
35
|
-
export declare function
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
export declare function getConfigPath(): string;
|
|
40
|
+
export declare function loadConfig(): ZhihandConfigV3;
|
|
41
|
+
/**
|
|
42
|
+
* Atomically write config: write to .tmp, then rename. Prevents corruption
|
|
43
|
+
* when the daemon and CLI write concurrently (Gemini code review v0.31).
|
|
44
|
+
*/
|
|
45
|
+
export declare function saveConfig(cfg: ZhihandConfigV3): void;
|
|
46
|
+
export declare function addUser(user: UserRecord): void;
|
|
47
|
+
export declare function removeUser(userId: string): void;
|
|
48
|
+
export declare function addDeviceToUser(userId: string, device: DeviceRecord): void;
|
|
49
|
+
export declare function removeDeviceFromUser(userId: string, credentialId: string): void;
|
|
50
|
+
export declare function updateDeviceLabel(userId: string, credentialId: string, label: string): void;
|
|
51
|
+
export declare function updateControllerToken(userId: string, newToken: string): void;
|
|
52
|
+
export declare function updateDeviceLastSeen(userId: string, credentialId: string, iso: string): void;
|
|
53
|
+
export declare function getUserRecord(userId: string): UserRecord | null;
|
|
54
|
+
export declare function findDeviceOwner(credentialId: string): {
|
|
55
|
+
user: UserRecord;
|
|
56
|
+
device: DeviceRecord;
|
|
57
|
+
} | null;
|
|
58
|
+
export declare function listUsers(): UserRecord[];
|
|
59
|
+
export declare function resolveDefaultEndpoint(): string;
|
|
60
|
+
/**
|
|
61
|
+
* Resolve a runtime config for HTTP calls. Find which user owns the
|
|
62
|
+
* credential and use the user's controller_token.
|
|
63
|
+
*/
|
|
64
|
+
export declare function resolveConfig(credentialId?: string): ZhiHandRuntimeConfig;
|
|
38
65
|
export declare function loadState<T = unknown>(): T | null;
|
|
39
66
|
export declare function saveState(state: unknown): void;
|
|
40
67
|
export declare function loadBackendConfig(): BackendConfig;
|
package/dist/core/config.js
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import os from "node:os";
|
|
4
|
-
/**
|
|
5
|
-
* Default model aliases per backend.
|
|
6
|
-
* These are generic aliases that the respective CLIs resolve to the latest version:
|
|
7
|
-
* - Gemini CLI: "flash" → latest flash model (e.g. gemini-2.5-flash)
|
|
8
|
-
* - Claude Code: "sonnet" → latest sonnet (e.g. claude-sonnet-4-20250514)
|
|
9
|
-
* - Codex CLI: requires full model name, no alias support
|
|
10
|
-
*/
|
|
11
4
|
export const DEFAULT_MODELS = {
|
|
12
|
-
gemini: "flash",
|
|
13
|
-
claudecode: "sonnet",
|
|
14
|
-
codex: "gpt-5.4-mini",
|
|
5
|
+
gemini: "flash",
|
|
6
|
+
claudecode: "sonnet",
|
|
7
|
+
codex: "gpt-5.4-mini",
|
|
15
8
|
};
|
|
9
|
+
// ── Paths ──────────────────────────────────────────────────
|
|
16
10
|
const ZHIHAND_DIR = path.join(os.homedir(), ".zhihand");
|
|
17
|
-
const
|
|
11
|
+
const CONFIG_PATH = path.join(ZHIHAND_DIR, "config.json");
|
|
18
12
|
const STATE_PATH = path.join(ZHIHAND_DIR, "state.json");
|
|
19
13
|
const BACKEND_PATH = path.join(ZHIHAND_DIR, "backend.json");
|
|
20
14
|
export function resolveZhiHandDir() {
|
|
@@ -23,47 +17,189 @@ export function resolveZhiHandDir() {
|
|
|
23
17
|
export function ensureZhiHandDir() {
|
|
24
18
|
fs.mkdirSync(ZHIHAND_DIR, { recursive: true, mode: 0o700 });
|
|
25
19
|
}
|
|
26
|
-
export function
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
export function getConfigPath() {
|
|
21
|
+
return CONFIG_PATH;
|
|
22
|
+
}
|
|
23
|
+
// ── v3 config I/O ──────────────────────────────────────────
|
|
24
|
+
let legacyWarningPrinted = false;
|
|
25
|
+
function emptyConfig() {
|
|
26
|
+
return { schema_version: 3, users: {} };
|
|
27
|
+
}
|
|
28
|
+
export function loadConfig() {
|
|
29
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
30
|
+
// Check for v2 or legacy credentials
|
|
31
|
+
const legacyCredentials = path.join(ZHIHAND_DIR, "credentials.json");
|
|
32
|
+
if (!legacyWarningPrinted && (fs.existsSync(legacyCredentials) || checkForV2Config())) {
|
|
33
|
+
legacyWarningPrinted = true;
|
|
34
|
+
process.stderr.write("[zhihand] old config detected (v2 or legacy) — run 'zhihand pair' to re-pair on v0.31 schema\n");
|
|
35
|
+
}
|
|
36
|
+
return emptyConfig();
|
|
37
|
+
}
|
|
29
38
|
try {
|
|
30
|
-
|
|
39
|
+
const raw = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"));
|
|
40
|
+
if (raw && raw.schema_version === 3) {
|
|
41
|
+
return {
|
|
42
|
+
schema_version: 3,
|
|
43
|
+
users: raw.users ?? {},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// Old schema version detected
|
|
47
|
+
if (!legacyWarningPrinted) {
|
|
48
|
+
legacyWarningPrinted = true;
|
|
49
|
+
process.stderr.write("[zhihand] old config detected (schema v" + (raw.schema_version ?? "?") + ") — run 'zhihand pair' to re-pair on v0.31 schema\n");
|
|
50
|
+
}
|
|
31
51
|
}
|
|
32
52
|
catch {
|
|
33
|
-
|
|
53
|
+
// fall through
|
|
34
54
|
}
|
|
55
|
+
return emptyConfig();
|
|
35
56
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
57
|
+
function checkForV2Config() {
|
|
58
|
+
if (!fs.existsSync(CONFIG_PATH))
|
|
59
|
+
return false;
|
|
60
|
+
try {
|
|
61
|
+
const raw = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"));
|
|
62
|
+
return raw && raw.schema_version === 2;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
41
67
|
}
|
|
42
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Atomically write config: write to .tmp, then rename. Prevents corruption
|
|
70
|
+
* when the daemon and CLI write concurrently (Gemini code review v0.31).
|
|
71
|
+
*/
|
|
72
|
+
export function saveConfig(cfg) {
|
|
43
73
|
ensureZhiHandDir();
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
74
|
+
const tmpPath = CONFIG_PATH + ".tmp";
|
|
75
|
+
fs.writeFileSync(tmpPath, JSON.stringify(cfg, null, 2), { mode: 0o600 });
|
|
76
|
+
fs.renameSync(tmpPath, CONFIG_PATH);
|
|
77
|
+
}
|
|
78
|
+
// ── User helpers ──────────────────────────────────────────
|
|
79
|
+
export function addUser(user) {
|
|
80
|
+
const cfg = loadConfig();
|
|
81
|
+
cfg.users[user.user_id] = user;
|
|
82
|
+
saveConfig(cfg);
|
|
83
|
+
}
|
|
84
|
+
export function removeUser(userId) {
|
|
85
|
+
const cfg = loadConfig();
|
|
86
|
+
delete cfg.users[userId];
|
|
87
|
+
saveConfig(cfg);
|
|
88
|
+
}
|
|
89
|
+
export function addDeviceToUser(userId, device) {
|
|
90
|
+
const cfg = loadConfig();
|
|
91
|
+
const user = cfg.users[userId];
|
|
92
|
+
if (!user)
|
|
93
|
+
throw new Error(`User '${userId}' not found in config`);
|
|
94
|
+
// Replace if exists, else append
|
|
95
|
+
const idx = user.devices.findIndex((d) => d.credential_id === device.credential_id);
|
|
96
|
+
if (idx >= 0) {
|
|
97
|
+
user.devices[idx] = device;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
user.devices.push(device);
|
|
101
|
+
}
|
|
102
|
+
saveConfig(cfg);
|
|
103
|
+
}
|
|
104
|
+
export function removeDeviceFromUser(userId, credentialId) {
|
|
105
|
+
const cfg = loadConfig();
|
|
106
|
+
const user = cfg.users[userId];
|
|
107
|
+
if (!user)
|
|
108
|
+
return;
|
|
109
|
+
user.devices = user.devices.filter((d) => d.credential_id !== credentialId);
|
|
110
|
+
saveConfig(cfg);
|
|
111
|
+
}
|
|
112
|
+
export function updateDeviceLabel(userId, credentialId, label) {
|
|
113
|
+
const cfg = loadConfig();
|
|
114
|
+
const user = cfg.users[userId];
|
|
115
|
+
if (!user)
|
|
116
|
+
throw new Error(`User '${userId}' not found`);
|
|
117
|
+
const dev = user.devices.find((d) => d.credential_id === credentialId);
|
|
118
|
+
if (!dev)
|
|
119
|
+
throw new Error(`Device '${credentialId}' not found under user '${userId}'`);
|
|
120
|
+
dev.label = label;
|
|
121
|
+
saveConfig(cfg);
|
|
122
|
+
}
|
|
123
|
+
export function updateControllerToken(userId, newToken) {
|
|
124
|
+
const cfg = loadConfig();
|
|
125
|
+
const user = cfg.users[userId];
|
|
126
|
+
if (!user)
|
|
127
|
+
throw new Error(`User '${userId}' not found`);
|
|
128
|
+
user.controller_token = newToken;
|
|
129
|
+
saveConfig(cfg);
|
|
130
|
+
}
|
|
131
|
+
export function updateDeviceLastSeen(userId, credentialId, iso) {
|
|
132
|
+
const cfg = loadConfig();
|
|
133
|
+
const user = cfg.users[userId];
|
|
134
|
+
if (!user)
|
|
135
|
+
return;
|
|
136
|
+
const dev = user.devices.find((d) => d.credential_id === credentialId);
|
|
137
|
+
if (!dev)
|
|
138
|
+
return;
|
|
139
|
+
dev.last_seen_at = iso;
|
|
140
|
+
saveConfig(cfg);
|
|
141
|
+
}
|
|
142
|
+
export function getUserRecord(userId) {
|
|
143
|
+
const cfg = loadConfig();
|
|
144
|
+
return cfg.users[userId] ?? null;
|
|
145
|
+
}
|
|
146
|
+
export function findDeviceOwner(credentialId) {
|
|
147
|
+
const cfg = loadConfig();
|
|
148
|
+
for (const user of Object.values(cfg.users)) {
|
|
149
|
+
const device = user.devices.find((d) => d.credential_id === credentialId);
|
|
150
|
+
if (device)
|
|
151
|
+
return { user, device };
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
export function listUsers() {
|
|
156
|
+
const cfg = loadConfig();
|
|
157
|
+
return Object.values(cfg.users);
|
|
158
|
+
}
|
|
159
|
+
// ── Runtime config resolution ─────────────────────────────
|
|
160
|
+
export function resolveDefaultEndpoint() {
|
|
161
|
+
return process.env.ZHIHAND_ENDPOINT ?? "https://api.zhihand.com";
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Resolve a runtime config for HTTP calls. Find which user owns the
|
|
165
|
+
* credential and use the user's controller_token.
|
|
166
|
+
*/
|
|
167
|
+
export function resolveConfig(credentialId) {
|
|
168
|
+
const cfg = loadConfig();
|
|
169
|
+
const endpoint = resolveDefaultEndpoint();
|
|
170
|
+
if (credentialId) {
|
|
171
|
+
const owner = findDeviceOwner(credentialId);
|
|
172
|
+
if (!owner) {
|
|
173
|
+
const known = Object.values(cfg.users)
|
|
174
|
+
.flatMap((u) => u.devices.map((d) => d.credential_id))
|
|
175
|
+
.join(", ") || "(none)";
|
|
176
|
+
throw new Error(`Device '${credentialId}' not found. Known: ${known}`);
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
controlPlaneEndpoint: endpoint,
|
|
180
|
+
credentialId,
|
|
181
|
+
controllerToken: owner.user.controller_token,
|
|
182
|
+
timeoutMs: 10_000,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
// No explicit credential — pick first device of first user
|
|
186
|
+
const users = Object.values(cfg.users);
|
|
187
|
+
if (users.length === 0) {
|
|
188
|
+
throw new Error("No users configured — run zhihand pair");
|
|
54
189
|
}
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
190
|
+
for (const user of users) {
|
|
191
|
+
if (user.devices.length > 0) {
|
|
192
|
+
return {
|
|
193
|
+
controlPlaneEndpoint: endpoint,
|
|
194
|
+
credentialId: user.devices[0].credential_id,
|
|
195
|
+
controllerToken: user.controller_token,
|
|
196
|
+
timeoutMs: 10_000,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
59
199
|
}
|
|
60
|
-
|
|
61
|
-
controlPlaneEndpoint: cred.endpoint,
|
|
62
|
-
credentialId: cred.credentialId,
|
|
63
|
-
controllerToken: cred.controllerToken,
|
|
64
|
-
timeoutMs: 10_000,
|
|
65
|
-
};
|
|
200
|
+
throw new Error("No devices configured — run zhihand pair");
|
|
66
201
|
}
|
|
202
|
+
// ── State / backend ───────────────────────────────────────
|
|
67
203
|
export function loadState() {
|
|
68
204
|
if (!fs.existsSync(STATE_PATH))
|
|
69
205
|
return null;
|
package/dist/core/device.d.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Device
|
|
2
|
+
* Device profile extraction & formatting — stateless.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* through the zhihand_status tool and device://profile resource.
|
|
4
|
+
* Per-device state (profile, raw attributes, timestamps) lives in the
|
|
5
|
+
* device registry (see ./registry.ts). This module exposes pure helpers
|
|
6
|
+
* to extract, classify, and format device data so the same logic can be
|
|
7
|
+
* applied to any number of devices.
|
|
9
8
|
*/
|
|
10
|
-
import type {
|
|
9
|
+
import type { ZhiHandRuntimeConfig } from "./config.ts";
|
|
11
10
|
export interface StaticContext {
|
|
12
11
|
platform: string;
|
|
13
12
|
model: string;
|
|
@@ -35,11 +34,8 @@ export interface DynamicContext {
|
|
|
35
34
|
thermalState?: string;
|
|
36
35
|
fontScale: number;
|
|
37
36
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
export declare function getRawAttributes(): Record<string, unknown>;
|
|
41
|
-
export declare function getProfileAgeMs(): number;
|
|
42
|
-
export declare function isDeviceProfileLoaded(): boolean;
|
|
37
|
+
declare const DEFAULT_STATIC: StaticContext;
|
|
38
|
+
declare const DEFAULT_DYNAMIC: DynamicContext;
|
|
43
39
|
export interface Capability {
|
|
44
40
|
ready: boolean;
|
|
45
41
|
reason: string;
|
|
@@ -53,12 +49,25 @@ export interface Capabilities {
|
|
|
53
49
|
stale: boolean;
|
|
54
50
|
};
|
|
55
51
|
}
|
|
56
|
-
export declare function
|
|
52
|
+
export declare function computeCapabilities(rawAttributes: Record<string, unknown>, profileReceivedAtMs: number): Capabilities;
|
|
57
53
|
export declare function extractStatic(profile: Record<string, unknown>): StaticContext;
|
|
58
54
|
export declare function extractDynamic(profile: Record<string, unknown>): DynamicContext;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
export declare function
|
|
64
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Fetch and normalize the device profile from the control plane once.
|
|
57
|
+
* Returns null on failure (HTTP or network).
|
|
58
|
+
*/
|
|
59
|
+
export declare function fetchDeviceProfileOnce(config: ZhiHandRuntimeConfig): Promise<{
|
|
60
|
+
rawAttrs: Record<string, unknown>;
|
|
61
|
+
receivedAtMs: number;
|
|
62
|
+
} | null>;
|
|
63
|
+
/**
|
|
64
|
+
* Normalize an SSE device_profile.updated payload into rawAttrs shape.
|
|
65
|
+
*/
|
|
66
|
+
export declare function normalizeProfilePayload(raw: Record<string, unknown>): Record<string, unknown>;
|
|
67
|
+
export declare function pickAllowlistedRawAttributes(rawAttributes: Record<string, unknown>): Record<string, unknown>;
|
|
68
|
+
export { DEFAULT_STATIC, DEFAULT_DYNAMIC };
|
|
69
|
+
import type { DeviceState } from "./registry.ts";
|
|
70
|
+
export declare function buildControlToolDescription(state: DeviceState | null, onlineStates?: DeviceState[], multiUser?: boolean): string;
|
|
71
|
+
export declare function buildSystemToolDescription(state: DeviceState | null, onlineStates?: DeviceState[], multiUser?: boolean): string;
|
|
72
|
+
export declare function buildScreenshotToolDescription(state: DeviceState | null, onlineStates?: DeviceState[], multiUser?: boolean): string;
|
|
73
|
+
export declare function formatDeviceStatus(state: DeviceState): Record<string, unknown>;
|