@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.
Files changed (42) hide show
  1. package/bin/zhihand +448 -212
  2. package/dist/core/command.d.ts +5 -5
  3. package/dist/core/command.js +6 -8
  4. package/dist/core/config.d.ts +48 -21
  5. package/dist/core/config.js +178 -42
  6. package/dist/core/device.d.ts +28 -19
  7. package/dist/core/device.js +168 -145
  8. package/dist/core/logger.d.ts +17 -0
  9. package/dist/core/logger.js +32 -0
  10. package/dist/core/pair.d.ts +39 -31
  11. package/dist/core/pair.js +205 -77
  12. package/dist/core/registry.d.ts +60 -0
  13. package/dist/core/registry.js +415 -0
  14. package/dist/core/screenshot.d.ts +3 -3
  15. package/dist/core/screenshot.js +3 -2
  16. package/dist/core/sse.d.ts +40 -18
  17. package/dist/core/sse.js +122 -62
  18. package/dist/core/ws.d.ts +92 -0
  19. package/dist/core/ws.js +327 -0
  20. package/dist/daemon/dispatcher.d.ts +3 -1
  21. package/dist/daemon/dispatcher.js +4 -3
  22. package/dist/daemon/heartbeat.d.ts +4 -4
  23. package/dist/daemon/heartbeat.js +1 -1
  24. package/dist/daemon/index.js +10 -8
  25. package/dist/daemon/prompt-listener.d.ts +8 -7
  26. package/dist/daemon/prompt-listener.js +59 -99
  27. package/dist/index.d.ts +3 -3
  28. package/dist/index.js +104 -40
  29. package/dist/openclaw.adapter.js +10 -2
  30. package/dist/tools/control.d.ts +10 -3
  31. package/dist/tools/control.js +18 -24
  32. package/dist/tools/pair.d.ts +1 -1
  33. package/dist/tools/pair.js +22 -28
  34. package/dist/tools/resolve.d.ts +7 -0
  35. package/dist/tools/resolve.js +22 -0
  36. package/dist/tools/schemas.d.ts +9 -1
  37. package/dist/tools/schemas.js +10 -8
  38. package/dist/tools/screenshot.d.ts +3 -2
  39. package/dist/tools/screenshot.js +2 -2
  40. package/dist/tools/system.d.ts +3 -5
  41. package/dist/tools/system.js +19 -6
  42. package/package.json +3 -1
@@ -1,4 +1,4 @@
1
- import type { ZhiHandConfig } from "./config.ts";
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: ZhiHandConfig, command: QueuedControlCommand): Promise<QueuedCommandRecord>;
46
- export declare function getCommand(config: ZhiHandConfig, commandId: string): Promise<QueuedCommandRecord>;
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;
@@ -1,11 +1,11 @@
1
- import { getStaticContext, isDeviceProfileLoaded } from "./device.js";
2
- import { dbg } from "../daemon/logger.js";
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
- "x-zhihand-controller-token": config.controllerToken,
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: { "x-zhihand-controller-token": config.controllerToken },
177
+ headers: { "Authorization": `Bearer ${config.controllerToken}` },
180
178
  });
181
179
  if (!response.ok) {
182
180
  dbg(`[cmd] Get failed: ${response.status}`);
@@ -1,15 +1,27 @@
1
- export interface DeviceCredential {
2
- credentialId: string;
3
- controllerToken: string;
4
- endpoint: string;
5
- deviceName?: string;
6
- pairedAt?: string;
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 CredentialStore {
9
- default: string;
10
- devices: Record<string, DeviceCredential>;
16
+ export interface ZhihandConfigV3 {
17
+ schema_version: 3;
18
+ users: Record<string, UserRecord>;
11
19
  }
12
- export interface ZhiHandConfig {
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 loadCredentialStore(): CredentialStore | null;
35
- export declare function loadDefaultCredential(): DeviceCredential | null;
36
- export declare function saveCredential(name: string, cred: DeviceCredential, setDefault?: boolean): void;
37
- export declare function resolveConfig(deviceName?: string): ZhiHandConfig;
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;
@@ -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", // Gemini CLI resolves to latest flash
13
- claudecode: "sonnet", // Claude Code resolves to latest sonnet
14
- codex: "gpt-5.4-mini", // Codex default: latest GPT mini model
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 CREDENTIALS_PATH = path.join(ZHIHAND_DIR, "credentials.json");
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 loadCredentialStore() {
27
- if (!fs.existsSync(CREDENTIALS_PATH))
28
- return null;
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
- return JSON.parse(fs.readFileSync(CREDENTIALS_PATH, "utf8"));
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
- return null;
53
+ // fall through
34
54
  }
55
+ return emptyConfig();
35
56
  }
36
- export function loadDefaultCredential() {
37
- const store = loadCredentialStore();
38
- if (!store)
39
- return null;
40
- return store.devices[store.default] ?? null;
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
- export function saveCredential(name, cred, setDefault = true) {
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
- let store = loadCredentialStore() ?? { default: name, devices: {} };
45
- store.devices[name] = cred;
46
- if (setDefault)
47
- store.default = name;
48
- fs.writeFileSync(CREDENTIALS_PATH, JSON.stringify(store, null, 2), { mode: 0o600 });
49
- }
50
- export function resolveConfig(deviceName) {
51
- const store = loadCredentialStore();
52
- if (!store) {
53
- throw new Error("No ZhiHand credentials found. Run 'zhihand pair' first.");
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 name = deviceName ?? store.default;
56
- const cred = store.devices[name];
57
- if (!cred) {
58
- throw new Error(`Device '${name}' not found. Available: ${Object.keys(store.devices).join(", ")}`);
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
- return {
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;
@@ -1,13 +1,12 @@
1
1
  /**
2
- * Device Context static + dynamic device info fetched from control plane.
2
+ * Device profile extraction & formatting stateless.
3
3
  *
4
- * Static info (platform, model, screen size) is set once after pairing and
5
- * injected into MCP tool descriptions so the LLM always knows the device.
6
- *
7
- * Dynamic info (battery, network, BLE) is updated via SSE push and exposed
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 { ZhiHandConfig } from "./config.ts";
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
- export declare function getStaticContext(): StaticContext;
39
- export declare function getDynamicContext(): DynamicContext;
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 getCapabilities(): Capabilities;
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
- export declare function updateDeviceProfile(raw: Record<string, unknown>): void;
60
- export declare function fetchDeviceProfile(config: ZhiHandConfig): Promise<void>;
61
- export declare function buildControlToolDescription(): string;
62
- export declare function buildSystemToolDescription(): string;
63
- export declare function buildScreenshotToolDescription(): string;
64
- export declare function formatDeviceStatus(): Record<string, unknown>;
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>;