batter-pixso-mcp 0.6.0 → 0.7.0-beta.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/dist/index.js CHANGED
@@ -1,233 +1,296 @@
1
1
  #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
2
11
 
3
12
  // src/config.ts
4
13
  import { tmpdir } from "node:os";
5
14
  import { join } from "node:path";
6
- var VERSION = true ? "0.6.0" : "0.0.0-dev";
7
- var HUB_NAME = "batter-pixso-mcp";
8
- var ASSETS_DIR = join(tmpdir(), "batter-pixso-assets");
9
- var config = {
10
- /** 插件通信 HTTP 端口,仅监听 127.0.0.1。与插件 UI 的 DEFAULT_PORT 互为默认对端,改默认需同步 */
11
- httpPort: Number(process.env.HTTP_PORT ?? 3210),
12
- // hub 常驻(不自毁):升级由 proxy 版本比对自动换血,手动停止用 --stop
13
- /** MCP HTTP 会话空闲 TTL:超时清扫防泄漏(proxy 对失效会话有自动重建兜底),默认 30 分钟 */
14
- mcpSessionTtlMs: Number(process.env.MCP_SESSION_TTL_MS ?? 30 * 60 * 1e3),
15
- /** 产物图片保留时长,默认 7 天 */
16
- assetTtlMs: Number(process.env.ASSET_TTL_DAYS ?? 7) * 24 * 60 * 60 * 1e3
17
- };
18
- function hubBaseUrl() {
15
+ function sleep(ms) {
16
+ const { promise, resolve } = Promise.withResolvers();
17
+ setTimeout(resolve, ms);
18
+ return promise;
19
+ }
20
+ function serviceBaseUrl() {
19
21
  return `http://127.0.0.1:${config.httpPort}`;
20
22
  }
23
+ function mcpUrl() {
24
+ return `${serviceBaseUrl()}/mcp`;
25
+ }
26
+ function claudeAddCommand() {
27
+ return `claude mcp add --transport http batter-pixso ${mcpUrl()}`;
28
+ }
21
29
  function log(...args) {
22
30
  console.error("[batter-pixso-mcp]", ...args);
23
31
  }
24
- function withResolvers() {
25
- let resolve;
26
- let reject;
27
- const promise = new Promise((res, rej) => {
28
- resolve = res;
29
- reject = rej;
30
- });
31
- return { promise, resolve, reject };
32
- }
32
+ var VERSION, DEFAULT_PORT, SERVICE_NAME, ASSETS_DIR, config, PING_TIMEOUT_MS, SESSION_ONLINE_WINDOW_MS, COMMAND_TIMEOUT_MS, SSE_HEARTBEAT_MS, MCP_SESSION_SWEEP_MS, PROBE_TIMEOUT_MS, ASSET_SWEEP_INTERVAL_MS;
33
+ var init_config = __esm({
34
+ "src/config.ts"() {
35
+ "use strict";
36
+ VERSION = true ? "0.7.0-beta.0" : "0.0.0-dev";
37
+ DEFAULT_PORT = true ? 3210 : 3210;
38
+ SERVICE_NAME = "batter-pixso-mcp";
39
+ ASSETS_DIR = join(tmpdir(), "batter-pixso-assets");
40
+ config = {
41
+ /** 插件通信 HTTP 端口,仅监听 127.0.0.1。默认值见上方 DEFAULT_PORT(HTTP_PORT 环境变量优先) */
42
+ httpPort: Number(process.env.HTTP_PORT ?? DEFAULT_PORT),
43
+ /** MCP HTTP 会话空闲 TTL:超时清扫防泄漏(客户端重连重新 initialize 即可恢复),默认 30 分钟 */
44
+ mcpSessionTtlMs: Number(process.env.MCP_SESSION_TTL_MS ?? 30 * 60 * 1e3),
45
+ /** 产物图片保留时长,默认 7 天 */
46
+ assetTtlMs: Number(process.env.ASSET_TTL_DAYS ?? 7) * 24 * 60 * 60 * 1e3
47
+ };
48
+ PING_TIMEOUT_MS = 800;
49
+ SESSION_ONLINE_WINDOW_MS = 45e3;
50
+ COMMAND_TIMEOUT_MS = 6e4;
51
+ SSE_HEARTBEAT_MS = 15e3;
52
+ MCP_SESSION_SWEEP_MS = 6e4;
53
+ PROBE_TIMEOUT_MS = 1500;
54
+ ASSET_SWEEP_INTERVAL_MS = 60 * 6e4;
55
+ }
56
+ });
33
57
 
34
- // src/plugin/assets.ts
35
- import { createHash } from "node:crypto";
36
- import { mkdir, readdir, stat, unlink, writeFile } from "node:fs/promises";
37
- import { join as join2 } from "node:path";
38
- var EXT_BY_FORMAT = {
39
- PNG: ".png",
40
- SVG: ".svg",
41
- JPG: ".jpg",
42
- JPEG: ".jpg",
43
- WEBP: ".webp",
44
- PDF: ".pdf"
45
- // export_image 尊重设计师导出标记,标记可能指定 PDF
46
- };
47
- var dirReady = null;
48
- async function saveAsset(bytes, format) {
49
- dirReady ??= mkdir(ASSETS_DIR, { recursive: true });
50
- await dirReady;
51
- const ext = EXT_BY_FORMAT[format.toUpperCase()] ?? ".bin";
52
- const name = `${createHash("sha1").update(bytes).digest("hex").slice(0, 16)}${ext}`;
53
- await writeFile(join2(ASSETS_DIR, name), bytes);
54
- log(`asset saved: ${name} (${bytes.length} bytes)`);
55
- return name;
56
- }
57
- function substituteAssets(result, assets) {
58
- if (!assets.length) return result;
59
- const nameByKey = new Map(assets.map((a) => [a.key, a.name]));
60
- return JSON.parse(
61
- JSON.stringify(
62
- result,
63
- (_k, v) => typeof v === "string" ? nameByKey.get(v) ?? v : v
64
- )
65
- );
66
- }
67
- async function cleanOldAssets(ttlMs) {
68
- let removed = 0;
69
- const cutoff = Date.now() - ttlMs;
70
- let names;
58
+ // src/cli/service.ts
59
+ var service_exports = {};
60
+ __export(service_exports, {
61
+ PORT_FIX_HINT: () => PORT_FIX_HINT,
62
+ probeService: () => probeService,
63
+ serviceStateError: () => serviceStateError,
64
+ stopRunningService: () => stopRunningService,
65
+ stopService: () => stopService
66
+ });
67
+ async function probeService() {
71
68
  try {
72
- names = await readdir(ASSETS_DIR);
73
- } catch {
74
- return 0;
75
- }
76
- for (const name of names) {
69
+ const res = await fetch(`${serviceBaseUrl()}/health`, { signal: AbortSignal.timeout(PROBE_TIMEOUT_MS) });
70
+ if (!res.ok) return { state: "foreign" };
71
+ let body;
77
72
  try {
78
- const p = join2(ASSETS_DIR, name);
79
- const s = await stat(p);
80
- if (s.isFile() && s.mtimeMs < cutoff) {
81
- await unlink(p);
82
- removed++;
83
- }
73
+ body = await res.json();
84
74
  } catch {
75
+ return { state: "foreign" };
85
76
  }
86
- }
87
- if (removed > 0) log(`assets cleanup: removed ${removed} expired file(s)`);
88
- return removed;
89
- }
90
-
91
- // src/process/hub-process.ts
92
- import { spawn } from "node:child_process";
93
- import { closeSync, openSync, readFileSync, rmSync, writeFileSync } from "node:fs";
94
- import { tmpdir as tmpdir2 } from "node:os";
95
- import { join as join3 } from "node:path";
96
- import { fileURLToPath } from "node:url";
97
- async function probeHub() {
98
- try {
99
- const res = await fetch(`${hubBaseUrl()}/health`, { signal: AbortSignal.timeout(1500) });
100
- if (!res.ok) return { state: "foreign" };
101
- const body = await res.json();
102
- if (body.ok === true && body.name === HUB_NAME) return { state: "alive", health: body };
77
+ if (body.ok === true && body.name === SERVICE_NAME) return { state: "alive", health: body };
103
78
  return { state: "foreign" };
104
79
  } catch (err) {
105
80
  const hung = err instanceof Error && err.name === "TimeoutError";
106
81
  return { state: hung ? "hung" : "down" };
107
82
  }
108
83
  }
109
- var PORT_FIX_HINT = `\u7528 netstat -ano | findstr :${config.httpPort} \u67E5\u5230 PID \u540E taskkill /PID <pid> /F \u7ED3\u675F\uFF0C\u518D\u91CD\u8BD5`;
110
- function hubStateError(state) {
84
+ function serviceStateError(state) {
111
85
  if (state === "hung") {
112
- return new Error(`\u7AEF\u53E3 ${config.httpPort} \u4E0A\u7684\u670D\u52A1\u65E0\u54CD\u5E94\uFF08\u53EF\u80FD\u6302\u6B7B\uFF09\u4E14\u65E0\u6CD5\u5B9A\u4F4D pid\u3002${PORT_FIX_HINT}`);
86
+ return new Error(`\u7AEF\u53E3 ${config.httpPort} \u4E0A\u7684\u670D\u52A1\u65E0\u54CD\u5E94\uFF08\u53EF\u80FD\u6302\u6B7B\uFF09\u3002${PORT_FIX_HINT}`);
113
87
  }
114
88
  return new Error(`\u7AEF\u53E3 ${config.httpPort} \u88AB\u5176\u4ED6\u7A0B\u5E8F\u5360\u7528\u3002${PORT_FIX_HINT}\uFF0C\u6216\u4FEE\u6539 HTTP_PORT \u6362\u7AEF\u53E3`);
115
89
  }
116
- var HUB_LOG = join3(tmpdir2(), "batter-pixso-mcp-hub.log");
117
- var PID_FILE = join3(tmpdir2(), "batter-pixso-mcp-hub.pid");
118
- function writePidFile(pid) {
90
+ function killServiceByPid(pid) {
119
91
  try {
120
- writeFileSync(PID_FILE, String(pid), "utf8");
121
- process.on("exit", () => {
122
- try {
123
- rmSync(PID_FILE);
124
- } catch {
125
- }
126
- });
92
+ process.kill(pid);
93
+ return true;
127
94
  } catch {
95
+ return false;
96
+ }
97
+ }
98
+ async function stopRunningService() {
99
+ const { state, health } = await probeService();
100
+ if (state !== "alive") return state;
101
+ return killServiceByPid(health.pid) ? "stopped" : "failed";
102
+ }
103
+ async function stopService() {
104
+ const result = await stopRunningService();
105
+ if (result === "down") {
106
+ log(`\u670D\u52A1\u672A\u5728\u8FD0\u884C\uFF08\u7AEF\u53E3 ${config.httpPort} \u65E0\u670D\u52A1\uFF09`);
107
+ } else if (result === "foreign") {
108
+ log(`\u7AEF\u53E3 ${config.httpPort} \u4E0A\u4E0D\u662F batter-pixso-mcp\uFF0C\u672A\u5904\u7406`);
109
+ } else if (result === "stopped") {
110
+ log("\u670D\u52A1\u5DF2\u505C\u6B62");
111
+ } else {
112
+ log(`\u505C\u6B62\u5931\u8D25${result === "hung" ? "\uFF08\u670D\u52A1\u65E0\u54CD\u5E94\uFF0C\u53EF\u80FD\u6302\u6B7B\uFF09" : ""}\u3002${PORT_FIX_HINT}`);
113
+ }
114
+ }
115
+ var PORT_FIX_HINT;
116
+ var init_service = __esm({
117
+ "src/cli/service.ts"() {
118
+ "use strict";
119
+ init_config();
120
+ PORT_FIX_HINT = `\u7528 netstat -ano | findstr :${config.httpPort} \u67E5\u5230 PID \u540E taskkill /PID <pid> /F \u7ED3\u675F\uFF0C\u518D\u91CD\u8BD5`;
128
121
  }
122
+ });
123
+
124
+ // src/install/dist-resolver.ts
125
+ import { existsSync } from "node:fs";
126
+ import { readFile } from "node:fs/promises";
127
+ import { join as join2 } from "node:path";
128
+ import { fileURLToPath } from "node:url";
129
+ function resolvePluginDir() {
130
+ const candidates = [
131
+ fileURLToPath(new URL("./plugin/", import.meta.url)),
132
+ // bundle: dist/index.js → dist/plugin/
133
+ fileURLToPath(new URL("../../plugin/dist/", import.meta.url)),
134
+ // bundle 开发: dist/index.js → packages/plugin/dist/
135
+ fileURLToPath(new URL("../../../plugin/dist/", import.meta.url))
136
+ // tsx 开发: src/install/ → packages/plugin/dist/
137
+ ];
138
+ return candidates.find((p) => existsSync(join2(p, "manifest.json"))) ?? null;
139
+ }
140
+ function pluginDistDir() {
141
+ resolved ??= resolvePluginDir();
142
+ return resolved;
143
+ }
144
+ function hasPluginDist() {
145
+ return pluginDistDir() !== null;
146
+ }
147
+ async function pluginManifest() {
148
+ const dir = pluginDistDir();
149
+ if (!dir) return null;
150
+ const raw = JSON.parse(await readFile(join2(dir, "manifest.json"), "utf8"));
151
+ raw.networkAccess = {
152
+ allowedDomains: [`http://localhost:${config.httpPort}`, `http://127.0.0.1:${config.httpPort}`]
153
+ };
154
+ return Buffer.from(JSON.stringify(raw, null, 2));
129
155
  }
130
- function readPidFile() {
156
+ async function readPluginFile(name) {
157
+ const dir = pluginDistDir();
158
+ if (!dir) return null;
131
159
  try {
132
- const pid = Number(readFileSync(PID_FILE, "utf8").trim());
133
- return Number.isInteger(pid) && pid > 0 ? pid : null;
160
+ return await readFile(join2(dir, name));
134
161
  } catch {
135
162
  return null;
136
163
  }
137
164
  }
138
- function removePidFile() {
139
- try {
140
- rmSync(PID_FILE);
141
- } catch {
165
+ var resolved;
166
+ var init_dist_resolver = __esm({
167
+ "src/install/dist-resolver.ts"() {
168
+ "use strict";
169
+ init_config();
170
+ resolved = null;
142
171
  }
172
+ });
173
+
174
+ // src/cli/setup.ts
175
+ var setup_exports = {};
176
+ __export(setup_exports, {
177
+ setupAutostart: () => setupAutostart,
178
+ uninstallAutostart: () => uninstallAutostart
179
+ });
180
+ import { spawnSync } from "node:child_process";
181
+ import { existsSync as existsSync2, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
182
+ import { copyFile, cp } from "node:fs/promises";
183
+ import { dirname, join as join3 } from "node:path";
184
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
185
+ function serviceCmd() {
186
+ return [
187
+ "@echo off",
188
+ `set HTTP_PORT=${config.httpPort}`,
189
+ `"%~dp0node.exe" "%~dp0index.js" >> "%TEMP%\\${SERVICE_LOG_NAME}" 2>&1`
190
+ ].join("\r\n");
143
191
  }
144
- function killHubByPid(pid) {
145
- try {
146
- process.kill(pid);
147
- return true;
148
- } catch {
149
- return false;
192
+ async function withTransientLockRetry(what, action) {
193
+ for (let i = 0; ; i++) {
194
+ try {
195
+ await action();
196
+ return;
197
+ } catch (err) {
198
+ const code = err.code ?? "";
199
+ if (!TRANSIENT_LOCK_ERRORS.has(code) || i >= 4) {
200
+ throw new Error(
201
+ `\u62F7\u8D1D\u5931\u8D25 ${what}\uFF08${code || err}\uFF09\uFF1A\u6587\u4EF6\u88AB\u5360\u7528\u2014\u2014\u82E5\u81EA\u542F\u5B9E\u4F8B\u6B63\u5728\u8FD0\u884C\uFF0C\u8BF7\u5148 --stop\uFF1B\u5426\u5219\u591A\u4E3A\u6740\u6BD2\u8F6F\u4EF6\u626B\u63CF\u9501\u5B9A\uFF0C\u7A0D\u5019\u91CD\u8BD5`,
202
+ { cause: err }
203
+ );
204
+ }
205
+ await sleep(500);
206
+ }
150
207
  }
151
208
  }
152
- function killHubByPidFile() {
153
- const pid = readPidFile();
154
- if (!pid) return false;
155
- if (!killHubByPid(pid)) return false;
156
- removePidFile();
157
- return true;
209
+ async function copyWithRetry(src, dest) {
210
+ await withTransientLockRetry(dest, () => copyFile(src, dest));
158
211
  }
159
- function spawnHub() {
160
- const out = openSync(HUB_LOG, "a");
161
- const entry = process.argv[1] ?? fileURLToPath(import.meta.url);
162
- const SKIP_FLAGS = /* @__PURE__ */ new Set(["--eval", "-e", "--print", "-p", "--watch", "--watch-preserve-output"]);
163
- const SKIP_PREFIXES = [...SKIP_FLAGS].map((f) => f + "=");
164
- const execArgv = process.execArgv.filter(
165
- (a, i, arr) => !SKIP_FLAGS.has(a) && !SKIP_PREFIXES.some((p) => a.startsWith(p)) && !(i > 0 && SKIP_FLAGS.has(arr[i - 1]))
166
- );
167
- const child = spawn(process.execPath, [...execArgv, entry, "--hub"], {
168
- detached: true,
169
- stdio: ["ignore", out, out],
170
- windowsHide: true,
171
- env: { ...process.env }
172
- });
173
- child.on("error", (err) => log("hub spawn \u5931\u8D25:", err.message));
174
- child.unref();
175
- closeSync(out);
176
- log(`hub spawned (pid ${child.pid}), log: ${HUB_LOG}`);
212
+ async function materializeAutostart() {
213
+ if (!DIR) throw new Error("\u65E0\u6CD5\u5B9A\u4F4D\u7269\u5316\u76EE\u5F55\uFF1A\u73AF\u5883\u53D8\u91CF LOCALAPPDATA \u672A\u8BBE\u7F6E");
214
+ const bundle = fileURLToPath2(import.meta.url);
215
+ if (!bundle.endsWith(".js")) {
216
+ throw new Error("--setup \u4EC5\u652F\u6301\u53D1\u5E03\u5305\u5F62\u6001\uFF08dist bundle\uFF09\uFF1Btsx \u5F00\u53D1\u6001\u8BF7\u5148 npm run build \u540E\u7528 dist \u5165\u53E3\u6267\u884C");
217
+ }
218
+ const standalone = join3(dirname(bundle), "..", "standalone", "index.js");
219
+ if (!existsSync2(standalone)) {
220
+ throw new Error("\u672A\u627E\u5230 standalone bundle\uFF08standalone/index.js\uFF09\uFF1B\u4ED3\u5E93\u5185\u8BF7\u5148\u6267\u884C npm run pack:standalone -w batter-pixso-mcp");
221
+ }
222
+ const fresh = autostartVersion() === VERSION && existsSync2(join3(DIR, "node.exe")) && existsSync2(join3(DIR, "index.js")) && existsSync2(join3(DIR, "plugin", "manifest.json"));
223
+ mkdirSync(DIR, { recursive: true });
224
+ if (!fresh) {
225
+ const plugin = pluginDistDir();
226
+ await Promise.all([
227
+ copyWithRetry(process.execPath, join3(DIR, "node.exe")),
228
+ copyWithRetry(standalone, join3(DIR, "index.js")),
229
+ ...plugin ? [withTransientLockRetry(join3(DIR, "plugin"), () => cp(plugin, join3(DIR, "plugin"), { recursive: true }))] : []
230
+ ]);
231
+ }
232
+ writeFileSync(join3(DIR, "service.cmd"), serviceCmd(), "utf8");
233
+ writeFileSync(join3(DIR, "run.vbs"), RUN_VBS, "utf8");
234
+ writeFileSync(join3(DIR, "version.txt"), VERSION, "utf8");
177
235
  }
178
- async function waitForHub(timeoutMs = 15e3) {
179
- const deadline = Date.now() + timeoutMs;
180
- for (; ; ) {
181
- const { state } = await probeHub();
182
- if (state === "alive") return;
183
- if (state !== "down") throw hubStateError(state);
184
- if (Date.now() > deadline) throw new Error(`\u7B49\u5F85 hub \u5C31\u7EEA\u8D85\u65F6\uFF08${timeoutMs / 1e3}s\uFF09`);
185
- await new Promise((r) => setTimeout(r, 200));
236
+ function autostartVersion() {
237
+ if (!DIR) return null;
238
+ try {
239
+ return readFileSync(join3(DIR, "version.txt"), "utf8").trim() || null;
240
+ } catch {
241
+ return null;
186
242
  }
187
243
  }
188
- async function waitForHubDown(timeoutMs = 5e3) {
189
- const deadline = Date.now() + timeoutMs;
190
- for (; ; ) {
191
- const { state } = await probeHub();
192
- if (state === "down") return;
193
- if (Date.now() > deadline) throw new Error(`\u7B49\u5F85 hub \u9000\u51FA\u8D85\u65F6\uFF08${timeoutMs / 1e3}s\uFF09`);
194
- await new Promise((r) => setTimeout(r, 100));
244
+ async function setupAutostart() {
245
+ if (process.platform !== "win32") {
246
+ throw new Error("--setup \u5F53\u524D\u4EC5\u652F\u6301 Windows\uFF1Bmac/Linux \u8BF7\u7528\u7CFB\u7EDF\u5F00\u673A\u542F\u52A8\u673A\u5236\u914D\u7F6E npx batter-pixso-mcp");
247
+ }
248
+ await materializeAutostart();
249
+ const target = `wscript.exe "${join3(DIR, "run.vbs")}"`;
250
+ const r = spawnSync("reg", ["add", RUN_KEY, "/v", RUN_VALUE, "/t", "REG_SZ", "/d", target, "/f"]);
251
+ if (r.status !== 0) {
252
+ throw new Error(`\u6CE8\u518C\u5F00\u673A\u81EA\u542F\u5931\u8D25\uFF1A${r.stderr?.toString().trim() || `reg exit ${r.status}`}`);
195
253
  }
254
+ log(`\u5F00\u673A\u81EA\u542F\u5DF2\u6CE8\u518C\uFF1A\u767B\u5F55\u540E\u9759\u9ED8\u542F\u52A8\u670D\u52A1\uFF08\u526F\u672C\u5728 ${DIR}\uFF0C\u4E0E nvm/npm \u72B6\u6001\u65E0\u5173\uFF09`);
196
255
  }
197
- async function stopHub() {
198
- const { state, health } = await probeHub();
199
- if (state === "down") {
200
- log(`hub \u672A\u8FD0\u884C\uFF08\u7AEF\u53E3 ${config.httpPort} \u65E0\u670D\u52A1\uFF09`);
201
- return;
256
+ async function uninstallAutostart() {
257
+ if (process.platform === "win32") {
258
+ spawnSync("reg", ["delete", RUN_KEY, "/v", RUN_VALUE, "/f"]);
259
+ log("\u5DF2\u79FB\u9664\u5F00\u673A\u81EA\u542F\uFF08\u82E5\u66FE\u6CE8\u518C\uFF09");
202
260
  }
203
- if (state === "foreign") {
204
- log(`\u7AEF\u53E3 ${config.httpPort} \u4E0A\u4E0D\u662F batter-pixso-mcp\uFF0C\u672A\u5904\u7406`);
205
- return;
261
+ const result = await stopRunningService();
262
+ if (result === "failed" || result === "hung") {
263
+ log(`\u670D\u52A1\u505C\u6B62\u5931\u8D25\uFF0C\u7269\u5316\u76EE\u5F55\u53EF\u80FD\u5220\u4E0D\u51C0\u3002${PORT_FIX_HINT}`);
206
264
  }
207
- if (state === "hung") {
208
- if (killHubByPidFile()) {
209
- log("hub \u65E0\u54CD\u5E94\uFF08\u50F5\u5C38\u8FDB\u7A0B\uFF09\uFF0C\u5DF2\u6309 pid \u6587\u4EF6\u5F3A\u6740\u3002\u4E0B\u6B21 agent \u8BF7\u6C42\u4F1A\u81EA\u52A8\u62C9\u8D77\u65B0\u7248\u672C");
210
- return;
265
+ if (DIR && existsSync2(DIR)) {
266
+ try {
267
+ rmSync(DIR, { recursive: true, force: true, maxRetries: 4, retryDelay: 500 });
268
+ } catch {
211
269
  }
212
- log(`\u7AEF\u53E3 ${config.httpPort} \u4E0A\u7684\u670D\u52A1\u65E0\u54CD\u5E94\u4E14\u65E0\u6CD5\u5B9A\u4F4D pid\u3002${PORT_FIX_HINT}`);
213
- return;
214
- }
215
- const pid = health.pid;
216
- if (killHubByPid(pid)) {
217
- removePidFile();
218
- log(`hub \u5DF2\u505C\u6B62\uFF08pid ${pid}\uFF09\u3002\u4E0B\u6B21 agent \u8BF7\u6C42\u4F1A\u81EA\u52A8\u62C9\u8D77\u65B0\u7248\u672C`);
219
- } else {
220
- log(`\u505C\u6B62 pid ${pid} \u5931\u8D25\u3002${PORT_FIX_HINT}`);
270
+ log(existsSync2(DIR) ? `\u76EE\u5F55\u5220\u9664\u5931\u8D25\uFF0C\u53EF\u624B\u52A8\u5220\u9664\uFF1A${DIR}` : `\u5DF2\u5220\u9664\u7269\u5316\u76EE\u5F55 ${DIR}`);
221
271
  }
272
+ log("\u5378\u8F7D\u5B8C\u6210\uFF08npm \u5168\u5C40/npx \u7F13\u5B58\u4E2D\u7684\u5305\u5982\u9700\u6E05\u7406\u8BF7\u81EA\u884C npm uninstall\uFF09");
222
273
  }
223
-
224
- // src/http.ts
225
- import { createServer } from "node:http";
226
-
227
- // src/agent/http.ts
228
- import { randomUUID as randomUUID2 } from "node:crypto";
229
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
230
- import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
274
+ var DIR, RUN_KEY, RUN_VALUE, SERVICE_LOG_NAME, RUN_VBS, TRANSIENT_LOCK_ERRORS;
275
+ var init_setup = __esm({
276
+ "src/cli/setup.ts"() {
277
+ "use strict";
278
+ init_config();
279
+ init_dist_resolver();
280
+ init_service();
281
+ DIR = process.env.LOCALAPPDATA ? join3(process.env.LOCALAPPDATA, "batter-pixso-mcp") : null;
282
+ RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
283
+ RUN_VALUE = "BatterPixsoMcp";
284
+ SERVICE_LOG_NAME = "batter-pixso-mcp-service.log";
285
+ RUN_VBS = [
286
+ "Set fso = CreateObject(" + JSON.stringify("Scripting.FileSystemObject") + ")",
287
+ "dir = fso.GetParentFolderName(WScript.ScriptFullName)",
288
+ // 路径由脚本自身位置推导,不写死绝对路径(用户名含中文/空格时 VBS 字面量会翻车)
289
+ 'CreateObject("Wscript.Shell").Run """" & dir & "\\service.cmd""", 0, False'
290
+ ].join("\r\n");
291
+ TRANSIENT_LOCK_ERRORS = /* @__PURE__ */ new Set(["EPERM", "EBUSY"]);
292
+ }
293
+ });
231
294
 
232
295
  // src/http-body.ts
233
296
  function sendJson(res, status, body) {
@@ -239,8 +302,6 @@ async function readBody(req) {
239
302
  for await (const chunk of req) chunks.push(chunk);
240
303
  return Buffer.concat(chunks);
241
304
  }
242
- var JsonBodyError = class extends Error {
243
- };
244
305
  async function readJsonBody(req) {
245
306
  const buf = await readBody(req);
246
307
  try {
@@ -249,7 +310,6 @@ async function readJsonBody(req) {
249
310
  throw new JsonBodyError("invalid json");
250
311
  }
251
312
  }
252
- var segsCache = /* @__PURE__ */ new Map();
253
313
  function segsOf(template) {
254
314
  let segs = segsCache.get(template);
255
315
  if (!segs) {
@@ -284,7 +344,7 @@ async function dispatchRoute(routes, req, res, url) {
284
344
  if (err instanceof JsonBodyError) {
285
345
  if (!res.headersSent) sendJson(res, 400, { error: "invalid json" });
286
346
  } else {
287
- console.error("[batter-pixso-mcp] route handler error:", err);
347
+ log("route handler error:", err);
288
348
  if (!res.headersSent) sendJson(res, 500, { error: "internal error" });
289
349
  }
290
350
  }
@@ -292,25 +352,30 @@ async function dispatchRoute(routes, req, res, url) {
292
352
  }
293
353
  return false;
294
354
  }
295
-
296
- // src/agent/mcp.ts
297
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
298
- import {
299
- CallToolRequestSchema,
300
- GetPromptRequestSchema,
301
- ListPromptsRequestSchema,
302
- ListResourceTemplatesRequestSchema,
303
- ListToolsRequestSchema
304
- } from "@modelcontextprotocol/sdk/types.js";
355
+ var JsonBodyError, segsCache;
356
+ var init_http_body = __esm({
357
+ "src/http-body.ts"() {
358
+ "use strict";
359
+ init_config();
360
+ JsonBodyError = class extends Error {
361
+ };
362
+ segsCache = /* @__PURE__ */ new Map();
363
+ }
364
+ });
305
365
 
306
366
  // src/agent/guides.ts
307
- var INTRO = `# batter-pixso-mcp \u4F7F\u7528\u6307\u5357
367
+ var INTRO, DESIGN_TO_CODE, GUIDES;
368
+ var init_guides = __esm({
369
+ "src/agent/guides.ts"() {
370
+ "use strict";
371
+ init_config();
372
+ INTRO = `# batter-pixso-mcp \u4F7F\u7528\u6307\u5357
308
373
 
309
374
  ## \u5DE5\u5177\u603B\u89C8
310
375
 
311
376
  | \u5DE5\u5177 | \u7528\u9014 |
312
377
  | --- | --- |
313
- | \`get_selected\` | \u8BFB\u753B\u5E03\u5F53\u524D\u9009\u4E2D\u56FE\u5C42\uFF08guid\u3001\u540D\u79F0\u3001\u7C7B\u578B\u3001\u5C3A\u5BF8\uFF09 |
378
+ | \`get_selected\` | \u8BFB\u753B\u5E03\u5F53\u524D\u9009\u4E2D\u56FE\u5C42\uFF08guid\u3001\u540D\u79F0\u3001\u7C7B\u578B\uFF09 |
314
379
  | \`get_metadata\` | \u5168\u6811\u7ED3\u6784\u9AA8\u67B6\uFF08\u4EC5 id/name/type/kids\uFF0C\u65E0\u6837\u5F0F/\u5750\u6807/\u8D44\u6E90\uFF09\uFF0C\u9996\u6B21\u5B9A\u4F4D\u7528 |
315
380
  | \`get_node_dsl\` | \u751F\u6210\u8282\u70B9\u7684\u5F00\u53D1\u5411\u7CBE\u7B80 DSL\uFF08\u7ED3\u6784 + \u6837\u5F0F + \u8D44\u6E90\u6587\u4EF6\u540D\uFF09\uFF0C\u8BBE\u8BA1\u8F6C\u4EE3\u7801\u7684\u6838\u5FC3\u5165\u53E3 |
316
381
  | \`get_screenshot\` | \u5BFC\u51FA\u8282\u70B9\u622A\u56FE\uFF0C\u8FD4\u56DE\u56FE\u7247\u6587\u4EF6\u540D\uFF08\u672C\u673A\u8DEF\u5F84\u76F4\u8BFB\uFF09\uFF0C\u7528\u4E8E\u89C6\u89C9\u53C2\u7167/\u6821\u9A8C |
@@ -336,7 +401,7 @@ var INTRO = `# batter-pixso-mcp \u4F7F\u7528\u6307\u5357
336
401
 
337
402
  \u8BE6\u7EC6\u5DE5\u4F5C\u6D41\u89C1\u8D44\u6E90 \`batter://guides/design-to-code\`\u3002
338
403
  `;
339
- var DESIGN_TO_CODE = `# \u8BBE\u8BA1\u8F6C\u4EE3\u7801\u5DE5\u4F5C\u6D41
404
+ DESIGN_TO_CODE = `# \u8BBE\u8BA1\u8F6C\u4EE3\u7801\u5DE5\u4F5C\u6D41
340
405
 
341
406
  ## \u3010\u786C\u6027\u89C4\u5219 \xB7 \u8FDD\u53CD\u5373\u8FD4\u5DE5\u3011
342
407
 
@@ -460,252 +525,272 @@ INSTANCE / COMPONENT / COMPONENT_SET \u8282\u70B9\u5E26 \`comp\` \u5B57\u6BB5\uF
460
525
  \u8BBE\u8BA1\u7A3F\u521A\u6539\u8FC7\u4F46\u7ED3\u679C\u5BF9\u4E0D\u4E0A\u65F6\uFF0C\u52A0 \`cache: "refresh"\` \u5F3A\u5236\u5237\u65B0\u3002
461
526
  - \u591A\u6587\u4EF6\u573A\u666F\u8282\u70B9\u7C7B\u5DE5\u5177\u7528 \`fileKey\`/\`sessionId\` \u53C2\u6570\u8DEF\u7531\uFF08\u4E8C\u9009\u4E00\u5FC5\u4F20\uFF09\uFF0C\u89C1 \`batter://guides/intro\`\u3002
462
527
  `;
463
- var GUIDES = [
464
- {
465
- name: "intro",
466
- uri: "batter://guides/intro",
467
- description: "batter-pixso-mcp \u6982\u89C8\uFF1A\u5DE5\u5177\u6E05\u5355\u3001\u94FE\u63A5\u63D0\u53D6 item-id\u3001\u591A\u6587\u4EF6\u4F1A\u8BDD\u8DEF\u7531\u3002",
468
- text: INTRO
469
- },
470
- {
471
- name: "design-to-code",
472
- uri: "batter://guides/design-to-code",
473
- description: "\u8BBE\u8BA1\u8F6C\u4EE3\u7801\uFF08\u6837\u5F0F\u8FD8\u539F/\u8BBE\u8BA1\u8FD8\u539F\uFF09\u5DE5\u4F5C\u6D41\uFF1A\u786C\u6027\u89C4\u5219\uFF08\u8D44\u6E90\u6309\u672C\u673A\u8DEF\u5F84\u53D6\u7528\u3001\u94FE\u63A5\u63D0\u53D6 item-id\uFF09\u3001DSL \u5B57\u6BB5\u8BED\u4E49\u3001\u89C6\u89C9\u6821\u9A8C\u3002",
474
- text: DESIGN_TO_CODE
528
+ GUIDES = [
529
+ {
530
+ name: "intro",
531
+ uri: "batter://guides/intro",
532
+ description: "batter-pixso-mcp \u6982\u89C8\uFF1A\u5DE5\u5177\u6E05\u5355\u3001\u94FE\u63A5\u63D0\u53D6 item-id\u3001\u591A\u6587\u4EF6\u4F1A\u8BDD\u8DEF\u7531\u3002",
533
+ text: INTRO
534
+ },
535
+ {
536
+ name: "design-to-code",
537
+ uri: "batter://guides/design-to-code",
538
+ description: "\u8BBE\u8BA1\u8F6C\u4EE3\u7801\uFF08\u6837\u5F0F\u8FD8\u539F/\u8BBE\u8BA1\u8FD8\u539F\uFF09\u5DE5\u4F5C\u6D41\uFF1A\u786C\u6027\u89C4\u5219\uFF08\u8D44\u6E90\u6309\u672C\u673A\u8DEF\u5F84\u53D6\u7528\u3001\u94FE\u63A5\u63D0\u53D6 item-id\uFF09\u3001DSL \u5B57\u6BB5\u8BED\u4E49\u3001\u89C6\u89C9\u6821\u9A8C\u3002",
539
+ text: DESIGN_TO_CODE
540
+ }
541
+ ];
475
542
  }
476
- ];
543
+ });
477
544
 
478
545
  // src/agent/prompts.ts
479
- var DESIGN_TO_CODE2 = {
480
- name: "design-to-code",
481
- description: "\u8BBE\u8BA1\u7A3F\u8F6C\u524D\u7AEF\u4EE3\u7801\uFF08\u6837\u5F0F\u8FD8\u539F/\u8BBE\u8BA1\u8FD8\u539F/UI \u8FD8\u539F\uFF09\uFF1A\u4ECE Pixso \u94FE\u63A5\u6216\u5F53\u524D\u9009\u533A\u53D6 DSL + \u622A\u56FE \u2192 \u751F\u6210 \u2192 \u622A\u56FE\u6821\u9A8C\u3002",
482
- arguments: [
483
- {
484
- name: "url",
485
- description: "Pixso \u94FE\u63A5\uFF08\u542B item-id \u67E5\u8BE2\u53C2\u6570\uFF0C\u4EFB\u610F\u57DF\u540D\uFF1A\u5B98\u65B9 pixso.cn \u6216\u79C1\u6709\u5316\u90E8\u7F72\uFF09\u3002\u7559\u7A7A\u5219\u4F7F\u7528\u753B\u5E03\u5F53\u524D\u9009\u4E2D\u56FE\u5C42\u3002",
486
- required: false
487
- }
488
- ],
489
- render: (args) => {
490
- const targetLine = args.url?.trim() ? `\u76EE\u6807\u8282\u70B9\u6765\u81EA\u94FE\u63A5\uFF1A${args.url.trim()}\u3002\u5FC5\u987B\u4ECE\u67E5\u8BE2\u53C2\u6570 \`item-id\` \u63D0\u53D6\u8282\u70B9 guid \u5E76 URL \u89E3\u7801\uFF08\`1%3A2\` \u2192 \`"1:2"\`\uFF09\u540E\u4F5C\u4E3A guid \u4F20\u5165\uFF1B\u4E25\u7981\u628A\u6574\u4E2A URL \u5F53 guid \u4F20\u3002\u65E0\u8BBA\u57DF\u540D\u662F\u5426\u8BA4\u8BC6\uFF0C\u90FD\u6309\u6B64\u89C4\u5219\u5904\u7406\u3002` : `\u76EE\u6807\u8282\u70B9\u4E3A\u5F53\u524D\u9009\u533A\uFF1A\u8C03 \`get_selected\` \u62FF\u9009\u4E2D\u56FE\u5C42\u3002\u7981\u6B62\u9690\u5F0F\u4F7F\u7528\u5176 guid\u2014\u2014\u9664\u975E\u7528\u6237\u660E\u786E\u8BF4\u4E86"\u6211\u9009\u4E2D\u7684/\u5F53\u524D\u9009\u4E2D\u7684"\uFF0C\u5148\u628A\u9009\u4E2D\u56FE\u5C42\u540D\u79F0\u62A5\u7ED9\u7528\u6237\u786E\u8BA4\uFF08"\u5F53\u524D\u9009\u4E2D\u7684\u662F\u300CXX\u300D\uFF0C\u786E\u8BA4\u5904\u7406\u5B83\u5417\uFF1F"\uFF09\uFF0C\u786E\u8BA4\u540E\u518D\u7EE7\u7EED\u3002`;
491
- return [
492
- {
493
- role: "user",
494
- content: {
495
- type: "text",
496
- text: [
497
- `\u628A Pixso \u8BBE\u8BA1\u7A3F\u8FD8\u539F\u4E3A\u524D\u7AEF\u4EE3\u7801\u3002`,
498
- ``,
499
- `## \u3010\u786C\u6027\u89C4\u5219 \xB7 \u8FDD\u53CD\u5373\u8FD4\u5DE5\u3011`,
500
- ``,
501
- `**\u5199\u4EE3\u7801\u524D\u5FC5\u987B\u5148\u8BFB**\u8D44\u6E90 \`batter://guides/design-to-code\` \u5E76\u9075\u5B88\u5176\u5168\u90E8\u786C\u6027\u89C4\u5219\uFF08\u8D44\u6E90\u53D6\u7528/\u7EC4\u4EF6\u6620\u5C04/\u9519\u8BEF\u6062\u590D/\u7981\u6B62\u622A\u56FE\u786C\u7F16\u7801\uFF09\uFF1B\u603B\u89C8\u89C1 \`batter://guides/intro\`\u3002`,
502
- ``,
503
- `## \u6267\u884C\u6D41\u7A0B`,
504
- ``,
505
- `1. \u786E\u8BA4\u76EE\u6807\u8282\u70B9\uFF1A${targetLine}`,
506
- `2. \u53D6\u6570\u636E\uFF1A\u63D2\u4EF6\u7C7B\u5DE5\u5177\u8FD4\u56DE\u7EDF\u4E00\u4E3A \`{sessionId, fileKey, data}\` \u4FE1\u5C01\uFF0C\u53D6 \`data\` \u5B57\u6BB5\u3002\u8282\u70B9\u7C7B\u5DE5\u5177\u7684 guid \u5FC5\u4F20\uFF0CfileKey/sessionId \u5FC5\u4F20\u5176\u4E00\uFF08\u4F18\u5148 fileKey\uFF1B\u9009\u533A\u573A\u666F\u7528 \`get_selected\` \u8FD4\u56DE\u7684 fileKey\uFF09\u3002\u9996\u6B21\u63A5\u89E6\u8BBE\u8BA1\u7A3F\uFF1A\u5148 \`get_metadata(guid, fileKey)\` \u62FF\u5168\u6811\u9AA8\u67B6\u5B9A\u4F4D\u76EE\u6807 + \`get_screenshot(guid, fileKey)\` \u62FF\u89C6\u89C9\u5168\u8C8C\uFF1B\u968F\u540E \`get_node_dsl(guid, fileKey)\` \u62FF\u5168\u91CF DSL\uFF08\u7ED3\u6784\u5F88\u5927\u65F6\u7528 \`maxDepth\` \u5206\u6BB5\u4E0B\u63A2\u2014\u2014\u622A\u65AD\u5904\u8282\u70B9\u5E26 \`more\` \u6807\u8BB0\u4E0E \`kidsSummary\` \u6D45\u9884\u89C8\uFF0C\u62FF\u5176 \`id\` \u56DE\u4F20 \`guid\` \u4E0B\u94BB\uFF09\u3002DSL \u4E2D\u5DF2\u5E26 \`img\` \u7684\u8282\u70B9\uFF08ICON/IMAGE \u53F6\u5B50\uFF09\u5DF2\u5B8C\u6210\u5BFC\u51FA\uFF0C\u76F4\u63A5\u7528\u5176\u6587\u4EF6\u540D\uFF0C\u4E0D\u8981\u518D\u8C03 \`export_image\`\u3002`,
507
- `3. \u751F\u6210\u4EE3\u7801\uFF1A\u6309\u6307\u5357\u4E2D\u7684 DSL \u5B57\u6BB5\u8BED\u4E49\u6620\u5C04\u5230\u76EE\u6807\u6846\u67B6\u3002`,
508
- `4. \u81EA\u68C0\uFF1A\u518D\u6B21\u8C03 \`get_screenshot(guid)\`\uFF0C\u4E0E\u4EE3\u7801\u6E32\u67D3\u7ED3\u679C\u5BF9\u6BD4\uFF0C\u6709\u504F\u5DEE\u5C31\u4FEE\u6B63\u540E\u518D\u6BD4\u4E00\u8F6E\uFF08\u5BFC\u51FA\u7C7B\u5DE5\u5177\u5E26 15 \u5206\u949F\u7F13\u5B58\uFF1B\u7ED3\u679C\u7591\u4F3C\u8FC7\u671F\u65F6\u52A0 \`cache: "refresh"\` \u5F3A\u5236\u5237\u65B0\uFF09\u3002`,
509
- ``,
510
- `**\u6700\u540E\u91CD\u7533**\uFF1A\u4EA4\u4ED8\u4EE3\u7801\u4E2D\u4E0D\u5141\u8BB8\u51FA\u73B0\u4EA7\u7269\u76EE\u5F55\u7684\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u5168\u90E8\u56FE\u7247\u5FC5\u987B\u662F\u5DF2\u590D\u5236\u5230\u9879\u76EE\u7684\u672C\u5730\u76F8\u5BF9\u8DEF\u5F84\uFF08\u7EC6\u5219\u89C1\u6307\u5357\u786C\u6027\u89C4\u5219\u7B2C 1 \u6761\uFF09\u3002`
511
- ].join("\n")
546
+ var DESIGN_TO_CODE2, PROMPTS;
547
+ var init_prompts = __esm({
548
+ "src/agent/prompts.ts"() {
549
+ "use strict";
550
+ DESIGN_TO_CODE2 = {
551
+ name: "design-to-code",
552
+ description: "\u8BBE\u8BA1\u7A3F\u8F6C\u524D\u7AEF\u4EE3\u7801\uFF08\u6837\u5F0F\u8FD8\u539F/\u8BBE\u8BA1\u8FD8\u539F/UI \u8FD8\u539F\uFF09\uFF1A\u4ECE Pixso \u94FE\u63A5\u6216\u5F53\u524D\u9009\u533A\u53D6 DSL + \u622A\u56FE \u2192 \u751F\u6210 \u2192 \u622A\u56FE\u6821\u9A8C\u3002",
553
+ arguments: [
554
+ {
555
+ name: "url",
556
+ description: "Pixso \u94FE\u63A5\uFF08\u542B item-id \u67E5\u8BE2\u53C2\u6570\uFF0C\u4EFB\u610F\u57DF\u540D\uFF1A\u5B98\u65B9 pixso.cn \u6216\u79C1\u6709\u5316\u90E8\u7F72\uFF09\u3002\u7559\u7A7A\u5219\u4F7F\u7528\u753B\u5E03\u5F53\u524D\u9009\u4E2D\u56FE\u5C42\u3002",
557
+ required: false
512
558
  }
559
+ ],
560
+ render: (args) => {
561
+ const targetLine = args.url?.trim() ? `\u76EE\u6807\u8282\u70B9\u6765\u81EA\u94FE\u63A5\uFF1A${args.url.trim()}\u3002\u5FC5\u987B\u4ECE\u67E5\u8BE2\u53C2\u6570 \`item-id\` \u63D0\u53D6\u8282\u70B9 guid \u5E76 URL \u89E3\u7801\uFF08\`1%3A2\` \u2192 \`"1:2"\`\uFF09\u540E\u4F5C\u4E3A guid \u4F20\u5165\uFF1B\u4E25\u7981\u628A\u6574\u4E2A URL \u5F53 guid \u4F20\u3002\u65E0\u8BBA\u57DF\u540D\u662F\u5426\u8BA4\u8BC6\uFF0C\u90FD\u6309\u6B64\u89C4\u5219\u5904\u7406\u3002` : `\u76EE\u6807\u8282\u70B9\u4E3A\u5F53\u524D\u9009\u533A\uFF1A\u8C03 \`get_selected\` \u62FF\u9009\u4E2D\u56FE\u5C42\u3002\u7981\u6B62\u9690\u5F0F\u4F7F\u7528\u5176 guid\u2014\u2014\u9664\u975E\u7528\u6237\u660E\u786E\u8BF4\u4E86"\u6211\u9009\u4E2D\u7684/\u5F53\u524D\u9009\u4E2D\u7684"\uFF0C\u5148\u628A\u9009\u4E2D\u56FE\u5C42\u540D\u79F0\u62A5\u7ED9\u7528\u6237\u786E\u8BA4\uFF08"\u5F53\u524D\u9009\u4E2D\u7684\u662F\u300CXX\u300D\uFF0C\u786E\u8BA4\u5904\u7406\u5B83\u5417\uFF1F"\uFF09\uFF0C\u786E\u8BA4\u540E\u518D\u7EE7\u7EED\u3002`;
562
+ return [
563
+ {
564
+ role: "user",
565
+ content: {
566
+ type: "text",
567
+ text: [
568
+ `\u628A Pixso \u8BBE\u8BA1\u7A3F\u8FD8\u539F\u4E3A\u524D\u7AEF\u4EE3\u7801\u3002`,
569
+ ``,
570
+ `## \u3010\u786C\u6027\u89C4\u5219 \xB7 \u8FDD\u53CD\u5373\u8FD4\u5DE5\u3011`,
571
+ ``,
572
+ `**\u5199\u4EE3\u7801\u524D\u5FC5\u987B\u5148\u8BFB**\u8D44\u6E90 \`batter://guides/design-to-code\` \u5E76\u9075\u5B88\u5176\u5168\u90E8\u786C\u6027\u89C4\u5219\uFF08\u8D44\u6E90\u53D6\u7528/\u7EC4\u4EF6\u6620\u5C04/\u9519\u8BEF\u6062\u590D/\u7981\u6B62\u622A\u56FE\u786C\u7F16\u7801\uFF09\uFF1B\u603B\u89C8\u89C1 \`batter://guides/intro\`\u3002`,
573
+ ``,
574
+ `## \u6267\u884C\u6D41\u7A0B`,
575
+ ``,
576
+ `1. \u786E\u8BA4\u76EE\u6807\u8282\u70B9\uFF1A${targetLine}`,
577
+ `2. \u53D6\u6570\u636E\uFF1A\u63D2\u4EF6\u7C7B\u5DE5\u5177\u8FD4\u56DE\u7EDF\u4E00\u4E3A \`{sessionId, fileKey, data}\` \u4FE1\u5C01\uFF0C\u53D6 \`data\` \u5B57\u6BB5\u3002\u8282\u70B9\u7C7B\u5DE5\u5177\u7684 guid \u5FC5\u4F20\uFF0CfileKey/sessionId \u5FC5\u4F20\u5176\u4E00\uFF08\u4F18\u5148 fileKey\uFF1B\u9009\u533A\u573A\u666F\u7528 \`get_selected\` \u8FD4\u56DE\u7684 fileKey\uFF09\u3002\u9996\u6B21\u63A5\u89E6\u8BBE\u8BA1\u7A3F\uFF1A\u5148 \`get_metadata(guid, fileKey)\` \u62FF\u5168\u6811\u9AA8\u67B6\u5B9A\u4F4D\u76EE\u6807 + \`get_screenshot(guid, fileKey)\` \u62FF\u89C6\u89C9\u5168\u8C8C\uFF1B\u968F\u540E \`get_node_dsl(guid, fileKey)\` \u62FF\u5168\u91CF DSL\uFF08\u7ED3\u6784\u5F88\u5927\u65F6\u7528 \`maxDepth\` \u5206\u6BB5\u4E0B\u63A2\u2014\u2014\u622A\u65AD\u5904\u8282\u70B9\u5E26 \`more\` \u6807\u8BB0\u4E0E \`kidsSummary\` \u6D45\u9884\u89C8\uFF0C\u62FF\u5176 \`id\` \u56DE\u4F20 \`guid\` \u4E0B\u94BB\uFF09\u3002DSL \u4E2D\u5DF2\u5E26 \`img\` \u7684\u8282\u70B9\uFF08ICON/IMAGE \u53F6\u5B50\uFF09\u5DF2\u5B8C\u6210\u5BFC\u51FA\uFF0C\u76F4\u63A5\u7528\u5176\u6587\u4EF6\u540D\uFF0C\u4E0D\u8981\u518D\u8C03 \`export_image\`\u3002`,
578
+ `3. \u751F\u6210\u4EE3\u7801\uFF1A\u6309\u6307\u5357\u4E2D\u7684 DSL \u5B57\u6BB5\u8BED\u4E49\u6620\u5C04\u5230\u76EE\u6807\u6846\u67B6\u3002`,
579
+ `4. \u81EA\u68C0\uFF1A\u518D\u6B21\u8C03 \`get_screenshot(guid)\`\uFF0C\u4E0E\u4EE3\u7801\u6E32\u67D3\u7ED3\u679C\u5BF9\u6BD4\uFF0C\u6709\u504F\u5DEE\u5C31\u4FEE\u6B63\u540E\u518D\u6BD4\u4E00\u8F6E\uFF08\u5BFC\u51FA\u7C7B\u5DE5\u5177\u5E26 15 \u5206\u949F\u7F13\u5B58\uFF1B\u7ED3\u679C\u7591\u4F3C\u8FC7\u671F\u65F6\u52A0 \`cache: "refresh"\` \u5F3A\u5236\u5237\u65B0\uFF09\u3002`,
580
+ ``,
581
+ `**\u6700\u540E\u91CD\u7533**\uFF1A\u4EA4\u4ED8\u4EE3\u7801\u4E2D\u4E0D\u5141\u8BB8\u51FA\u73B0\u4EA7\u7269\u76EE\u5F55\u7684\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u5168\u90E8\u56FE\u7247\u5FC5\u987B\u662F\u5DF2\u590D\u5236\u5230\u9879\u76EE\u7684\u672C\u5730\u76F8\u5BF9\u8DEF\u5F84\uFF08\u7EC6\u5219\u89C1\u6307\u5357\u786C\u6027\u89C4\u5219\u7B2C 1 \u6761\uFF09\u3002`
582
+ ].join("\n")
583
+ }
584
+ }
585
+ ];
513
586
  }
514
- ];
587
+ };
588
+ PROMPTS = [DESIGN_TO_CODE2];
515
589
  }
516
- };
517
- var PROMPTS = [DESIGN_TO_CODE2];
590
+ });
518
591
 
519
592
  // src/plugin/command-bus.ts
520
- var CommandBus = class {
521
- sseListeners = /* @__PURE__ */ new Map();
522
- pendingById = /* @__PURE__ */ new Map();
523
- seq = 0;
524
- /** AI 侧:执行命令 */
525
- execute(type, args, opts) {
526
- const timeoutMs = opts.timeoutMs ?? 6e4;
527
- const { promise, resolve, reject } = withResolvers();
528
- const command = { id: `cmd-${++this.seq}`, type, args };
529
- const entry = {
530
- command,
531
- sessionId: opts.sessionId,
532
- resolve,
533
- reject,
534
- dispatched: false,
535
- timer: setTimeout(() => {
593
+ var CommandBus, commandBus;
594
+ var init_command_bus = __esm({
595
+ "src/plugin/command-bus.ts"() {
596
+ "use strict";
597
+ init_config();
598
+ CommandBus = class {
599
+ sseListeners = /* @__PURE__ */ new Map();
600
+ pendingById = /* @__PURE__ */ new Map();
601
+ seq = 0;
602
+ /** AI 侧:执行命令 */
603
+ execute(type, args, opts) {
604
+ const timeoutMs = opts.timeoutMs ?? COMMAND_TIMEOUT_MS;
605
+ const { promise, resolve, reject } = Promise.withResolvers();
606
+ const command = { id: `cmd-${++this.seq}`, type, args };
607
+ const entry = {
608
+ command,
609
+ sessionId: opts.sessionId,
610
+ resolve,
611
+ reject,
612
+ dispatched: false,
613
+ timer: setTimeout(() => {
614
+ this.drop(entry);
615
+ reject(new Error(`\u547D\u4EE4 ${command.id} \u8D85\u65F6\uFF1A\u63D2\u4EF6 ${timeoutMs / 1e3}s \u5185\u672A\u8FD4\u56DE\u7ED3\u679C`));
616
+ }, timeoutMs)
617
+ };
618
+ opts.signal?.addEventListener("abort", () => {
619
+ if (!this.pendingById.has(command.id)) return;
620
+ this.drop(entry);
621
+ reject(new Error(`\u547D\u4EE4 ${command.id} \u5DF2\u88AB\u8C03\u7528\u65B9\u53D6\u6D88`));
622
+ });
623
+ this.pendingById.set(command.id, entry);
624
+ this.dispatch(opts.sessionId);
625
+ return promise;
626
+ }
627
+ /** 插件侧:订阅即时推送(SSE 通道),返回退订函数;补发订阅前已入队的命令 */
628
+ subscribe(sessionId, listener) {
629
+ let subs = this.sseListeners.get(sessionId);
630
+ if (!subs) {
631
+ subs = /* @__PURE__ */ new Set();
632
+ this.sseListeners.set(sessionId, subs);
633
+ }
634
+ subs.add(listener);
635
+ this.dispatch(sessionId);
636
+ return () => {
637
+ subs.delete(listener);
638
+ if (subs.size === 0) this.sseListeners.delete(sessionId);
639
+ };
640
+ }
641
+ /** 插件侧:回传结果 */
642
+ complete(id, ok, resultOrError) {
643
+ const entry = this.pendingById.get(id);
644
+ if (!entry) return false;
536
645
  this.drop(entry);
537
- reject(new Error(`\u547D\u4EE4 ${command.id} \u8D85\u65F6\uFF1A\u63D2\u4EF6 ${timeoutMs / 1e3}s \u5185\u672A\u8FD4\u56DE\u7ED3\u679C`));
538
- }, timeoutMs)
539
- };
540
- opts.signal?.addEventListener("abort", () => {
541
- if (!this.pendingById.has(command.id)) return;
542
- this.drop(entry);
543
- reject(new Error(`\u547D\u4EE4 ${command.id} \u5DF2\u88AB\u8C03\u7528\u65B9\u53D6\u6D88`));
544
- });
545
- this.pendingById.set(command.id, entry);
546
- this.dispatch(opts.sessionId);
547
- return promise;
548
- }
549
- /** 插件侧:订阅即时推送(SSE 通道),返回退订函数;补发订阅前已入队的命令 */
550
- subscribe(sessionId, listener) {
551
- let subs = this.sseListeners.get(sessionId);
552
- if (!subs) {
553
- subs = /* @__PURE__ */ new Set();
554
- this.sseListeners.set(sessionId, subs);
555
- }
556
- subs.add(listener);
557
- this.dispatch(sessionId);
558
- return () => {
559
- subs.delete(listener);
560
- if (subs.size === 0) this.sseListeners.delete(sessionId);
646
+ if (ok) {
647
+ entry.resolve(resultOrError);
648
+ } else {
649
+ entry.reject(resultOrError instanceof Error ? resultOrError : new Error(String(resultOrError)));
650
+ }
651
+ return true;
652
+ }
653
+ /** 排空该会话全部未派发命令(无订阅者则留待 subscribe 补发) */
654
+ dispatch(sessionId) {
655
+ const subs = this.sseListeners.get(sessionId);
656
+ if (!subs || subs.size === 0) return;
657
+ for (const entry of this.pendingById.values()) {
658
+ if (entry.sessionId !== sessionId || entry.dispatched) continue;
659
+ entry.dispatched = true;
660
+ log(`dispatch command ${entry.command.id}: ${entry.command.type} -> session ${sessionId.slice(0, 8)}`);
661
+ for (const fn of subs) fn(entry.command);
662
+ }
663
+ }
664
+ /** 从 pending 表移除(完成/超时/取消共用) */
665
+ drop(entry) {
666
+ clearTimeout(entry.timer);
667
+ this.pendingById.delete(entry.command.id);
668
+ }
561
669
  };
670
+ commandBus = new CommandBus();
562
671
  }
563
- /** 插件侧:回传结果 */
564
- complete(id, ok, resultOrError) {
565
- const entry = this.pendingById.get(id);
566
- if (!entry) return false;
567
- this.drop(entry);
568
- if (ok) {
569
- entry.resolve(resultOrError);
570
- } else {
571
- entry.reject(resultOrError instanceof Error ? resultOrError : new Error(String(resultOrError)));
572
- }
573
- return true;
574
- }
575
- /** 排空该会话全部未派发命令(无订阅者则留待 subscribe 补发) */
576
- dispatch(sessionId) {
577
- const subs = this.sseListeners.get(sessionId);
578
- if (!subs || subs.size === 0) return;
579
- for (const entry of this.pendingById.values()) {
580
- if (entry.sessionId !== sessionId || entry.dispatched) continue;
581
- entry.dispatched = true;
582
- log(`dispatch command ${entry.command.id}: ${entry.command.type} -> session ${sessionId.slice(0, 8)}`);
583
- for (const fn of subs) fn(entry.command);
584
- }
585
- }
586
- /** 从 pending 表移除(完成/超时/取消共用) */
587
- drop(entry) {
588
- clearTimeout(entry.timer);
589
- this.pendingById.delete(entry.command.id);
590
- }
591
- };
592
- var commandBus = new CommandBus();
672
+ });
593
673
 
594
674
  // src/plugin/sessions.ts
595
675
  import { randomUUID } from "node:crypto";
596
- var ONLINE_WINDOW_MS = 45e3;
597
676
  function unknownRouteError(route) {
598
677
  if (route.fileKey) {
599
678
  return new Error(`\u627E\u4E0D\u5230 fileKey \u4E3A ${route.fileKey} \u7684\u5B58\u6D3B\u63D2\u4EF6\u4F1A\u8BDD\u3002\u53EF\u7528 list_sessions \u67E5\u770B\u5F53\u524D\u8FDE\u63A5\u7684\u4F1A\u8BDD`);
600
679
  }
601
680
  return new Error(`\u627E\u4E0D\u5230 sessionId \u4E3A ${route.sessionId} \u7684\u5B58\u6D3B\u63D2\u4EF6\u4F1A\u8BDD\uFF08\u53EF\u80FD\u5DF2\u88AB\u5254\u9664\uFF09\u3002\u53EF\u7528 list_sessions \u67E5\u770B\u5F53\u524D\u8FDE\u63A5\u7684\u4F1A\u8BDD`);
602
681
  }
603
- var SessionRegistry = class {
604
- sessions = /* @__PURE__ */ new Map();
605
- lastActiveId = null;
606
- /** 会话亲和:fileKey → 上一次路由使用的 sessionId(仍在线时优先,避免同文件多实例间漂移) */
607
- lastUsedByFileKey = /* @__PURE__ */ new Map();
608
- isOnline(s) {
609
- return s.connected || Date.now() - s.lastSeen < ONLINE_WINDOW_MS;
610
- }
611
- /** fileKey 寻址候选:该文件的在线会话(谁先 pong 由 ping 竞速决定,此处不排序) */
612
- candidatesForFileKey(fileKey) {
613
- return [...this.sessions.values()].filter((s) => s.meta.fileKey === fileKey && this.isOnline(s));
614
- }
615
- /** 亲和查询:上一次为该 fileKey 服务的 sessionId */
616
- affinityIdOf(fileKey) {
617
- return this.lastUsedByFileKey.get(fileKey);
618
- }
619
- /** 亲和钉住:fileKey 寻址探活成功后记录(下次优先) */
620
- pinAffinity(fileKey, sessionId) {
621
- this.lastUsedByFileKey.set(fileKey, sessionId);
622
- }
623
- /** 剔除会话(幽灵剔除/ping 判死共用):清理亲和引用;存活但繁忙被误剔的插件会在 SSE 断开或上报 404 后自重注册;
624
- * 失效 sessionId 的 failover 不靠墓碑——工具结果信封带 fileKey,agent 手里的稳定句柄一直是 fileKey */
625
- evict(id) {
626
- if (this.sessions.delete(id)) {
627
- for (const [k, v] of this.lastUsedByFileKey) {
628
- if (v === id) this.lastUsedByFileKey.delete(k);
682
+ var SessionRegistry, sessions;
683
+ var init_sessions = __esm({
684
+ "src/plugin/sessions.ts"() {
685
+ "use strict";
686
+ init_config();
687
+ SessionRegistry = class {
688
+ sessions = /* @__PURE__ */ new Map();
689
+ lastActiveId = null;
690
+ /** 会话亲和:fileKey → 上一次路由使用的 sessionId(仍在线时优先,避免同文件多实例间漂移) */
691
+ lastUsedByFileKey = /* @__PURE__ */ new Map();
692
+ isOnline(s) {
693
+ return s.connected || Date.now() - s.lastSeen < SESSION_ONLINE_WINDOW_MS;
629
694
  }
630
- log(`session evicted: ${id.slice(0, 8)}`);
631
- }
632
- }
633
- register(meta = {}) {
634
- if (meta.fileKey) {
635
- for (const s of [...this.sessions.values()]) {
636
- if (s.meta.fileKey === meta.fileKey && !this.isOnline(s)) {
637
- this.evict(s.id);
695
+ /** fileKey 寻址候选:该文件的在线会话(谁先 pong 由 ping 竞速决定,此处不排序) */
696
+ candidatesForFileKey(fileKey) {
697
+ return [...this.sessions.values()].filter((s) => s.meta.fileKey === fileKey && this.isOnline(s));
698
+ }
699
+ /** 亲和查询:上一次为该 fileKey 服务的 sessionId */
700
+ affinityIdOf(fileKey) {
701
+ return this.lastUsedByFileKey.get(fileKey);
702
+ }
703
+ /** 亲和钉住:fileKey 寻址探活成功后记录(下次优先) */
704
+ pinAffinity(fileKey, sessionId) {
705
+ this.lastUsedByFileKey.set(fileKey, sessionId);
706
+ }
707
+ /** 剔除会话(幽灵剔除/ping 判死共用):清理亲和引用;存活但繁忙被误剔的插件会在 SSE 断开或上报 404 后自重注册;
708
+ * 失效 sessionId 的 failover 不靠墓碑——工具结果信封带 fileKey,agent 手里的稳定句柄一直是 fileKey */
709
+ evict(id) {
710
+ if (this.sessions.delete(id)) {
711
+ for (const [k, v] of this.lastUsedByFileKey) {
712
+ if (v === id) this.lastUsedByFileKey.delete(k);
713
+ }
714
+ log(`session evicted: ${id.slice(0, 8)}`);
638
715
  }
639
716
  }
640
- }
641
- const session = { id: randomUUID(), meta, lastSeen: Date.now(), connected: false };
642
- this.sessions.set(session.id, session);
643
- this.lastActiveId = session.id;
644
- log(`session registered: ${session.id} (${meta.fileName ?? "unknown file"})`);
645
- return this.toInfo(session);
646
- }
647
- /** 心跳/连接状态用:只刷 lastSeen,不影响 lastActiveId("最近活跃"= 用户行为,心跳不算) */
648
- touchSeen(id) {
649
- const s = this.sessions.get(id);
650
- if (!s) return null;
651
- s.lastSeen = Date.now();
652
- return s;
653
- }
654
- /** 用户行为(选区上报/命令服务/注册):刷 lastSeen 并标记最近活跃 */
655
- touch(id) {
656
- const s = this.touchSeen(id);
657
- if (!s) return null;
658
- this.lastActiveId = id;
659
- return s;
660
- }
661
- setConnected(id, connected) {
662
- const s = this.touchSeen(id);
663
- if (!s) return false;
664
- s.connected = connected;
665
- return true;
666
- }
667
- updateContext(id, meta) {
668
- const s = this.touch(id);
669
- if (!s) return false;
670
- s.meta = { ...s.meta, ...meta };
671
- log(
672
- `context updated [${id.slice(0, 8)}]: ${s.meta.fileName ?? "?"} / ${s.meta.pageName ?? "?"} / ${s.meta.selection?.length ?? 0} selected`
673
- );
674
- return true;
675
- }
676
- list() {
677
- return [...this.sessions.values()].map((s) => this.toInfo(s));
678
- }
679
- /** 纯查找(不判活):工具层寻址用——离线会话也可取到 fileKey 用于候选扩展与故障转移 */
680
- get(id) {
681
- return this.sessions.get(id);
682
- }
683
- /** 缺省路由:最近活跃在线会话 > 任意在线会话(显式 fileKey/sessionId 由工具层 get/candidatesForFileKey + ping 完成) */
684
- resolve() {
685
- if (this.lastActiveId) {
686
- const s = this.sessions.get(this.lastActiveId);
687
- if (s && this.isOnline(s)) return s;
688
- }
689
- const fallback = [...this.sessions.values()].find((s) => this.isOnline(s));
690
- if (fallback) return fallback;
691
- throw new Error("Pixso \u63D2\u4EF6\u672A\u8FDE\u63A5\uFF1A\u8BF7\u5728 Pixso \u5BA2\u6237\u7AEF\u4E2D\u8FD0\u884C Batter Pixso Bridge \u63D2\u4EF6");
692
- }
693
- toInfo(s) {
694
- return {
695
- sessionId: s.id,
696
- fileKey: s.meta.fileKey,
697
- fileName: s.meta.fileName,
698
- pageName: s.meta.pageName,
699
- selectionCount: s.meta.selection?.length,
700
- online: this.isOnline(s),
701
- lastSeen: s.lastSeen
717
+ register(meta = {}) {
718
+ if (meta.fileKey) {
719
+ for (const s of [...this.sessions.values()]) {
720
+ if (s.meta.fileKey === meta.fileKey && !this.isOnline(s)) {
721
+ this.evict(s.id);
722
+ }
723
+ }
724
+ }
725
+ const session = { id: randomUUID(), meta, lastSeen: Date.now(), connected: false };
726
+ this.sessions.set(session.id, session);
727
+ this.lastActiveId = session.id;
728
+ log(`session registered: ${session.id} (${meta.fileName ?? "unknown file"})`);
729
+ return this.toInfo(session);
730
+ }
731
+ /** 心跳/连接状态用:只刷 lastSeen,不影响 lastActiveId("最近活跃"= 用户行为,心跳不算) */
732
+ touchSeen(id) {
733
+ const s = this.sessions.get(id);
734
+ if (!s) return null;
735
+ s.lastSeen = Date.now();
736
+ return s;
737
+ }
738
+ /** 用户行为(选区上报/命令服务/注册):刷 lastSeen 并标记最近活跃 */
739
+ touch(id) {
740
+ const s = this.touchSeen(id);
741
+ if (!s) return null;
742
+ this.lastActiveId = id;
743
+ return s;
744
+ }
745
+ setConnected(id, connected) {
746
+ const s = this.touchSeen(id);
747
+ if (!s) return false;
748
+ s.connected = connected;
749
+ return true;
750
+ }
751
+ updateContext(id, meta) {
752
+ const s = this.touch(id);
753
+ if (!s) return false;
754
+ s.meta = { ...s.meta, ...meta };
755
+ log(
756
+ `context updated [${id.slice(0, 8)}]: ${s.meta.fileName ?? "?"} / ${s.meta.pageName ?? "?"} / ${s.meta.selection?.length ?? 0} selected`
757
+ );
758
+ return true;
759
+ }
760
+ list() {
761
+ return [...this.sessions.values()].map((s) => this.toInfo(s));
762
+ }
763
+ /** 纯查找(不判活):工具层寻址用——离线会话也可取到 fileKey 用于候选扩展与故障转移 */
764
+ get(id) {
765
+ return this.sessions.get(id);
766
+ }
767
+ /** 缺省路由:最近活跃在线会话 > 任意在线会话(显式 fileKey/sessionId 由工具层 get/candidatesForFileKey + ping 完成) */
768
+ resolve() {
769
+ if (this.lastActiveId) {
770
+ const s = this.sessions.get(this.lastActiveId);
771
+ if (s && this.isOnline(s)) return s;
772
+ }
773
+ const fallback = [...this.sessions.values()].find((s) => this.isOnline(s));
774
+ if (fallback) return fallback;
775
+ throw new Error("Pixso \u63D2\u4EF6\u672A\u8FDE\u63A5\uFF1A\u8BF7\u5728 Pixso \u5BA2\u6237\u7AEF\u4E2D\u8FD0\u884C Batter Pixso Bridge \u63D2\u4EF6");
776
+ }
777
+ toInfo(s) {
778
+ return {
779
+ sessionId: s.id,
780
+ fileKey: s.meta.fileKey,
781
+ fileName: s.meta.fileName,
782
+ pageName: s.meta.pageName,
783
+ selectionCount: s.meta.selection?.length,
784
+ online: this.isOnline(s),
785
+ lastSeen: s.lastSeen
786
+ };
787
+ }
702
788
  };
789
+ sessions = new SessionRegistry();
703
790
  }
704
- };
705
- var sessions = new SessionRegistry();
791
+ });
706
792
 
707
793
  // src/agent/tools/shared.ts
708
- var PING_TIMEOUT_MS = 800;
709
794
  async function run(type, args, signal) {
710
795
  const { fileKey, sessionId, ...rest } = args;
711
796
  try {
@@ -763,28 +848,6 @@ async function resolveAndPing(route, signal) {
763
848
  if (fileKey) sessions.pinAffinity(fileKey, winner.id);
764
849
  return winner;
765
850
  }
766
- var ENVELOPE_NOTE = "\u8FD4\u56DE\u5C01\u88C5\u4E3A {sessionId, fileKey, data}\uFF08\u9519\u8BEF\u4E3A isError \u6587\u672C\uFF0C\u4E0D\u542B\u4FE1\u5C01\uFF09\uFF1AsessionId \u662F ping/pong \u786E\u8BA4\u7684\u5B9E\u9645\u670D\u52A1\u4F1A\u8BDD\uFF0CfileKey \u662F\u8DE8\u91CD\u5F00\u7A33\u5B9A\u7684\u53E5\u67C4\u2014\u2014\u540E\u7EED\u8C03\u7528\u7528 fileKey \u5BFB\u5740\u3002";
767
- var GUIDES_PTR = "\u5B8C\u6574\u89C4\u5219\uFF08\u8D44\u6E90\u53D6\u7528/\u7EC4\u4EF6\u6620\u5C04/\u9519\u8BEF\u6062\u590D/\u7981\u6B62\u622A\u56FE\u786C\u7F16\u7801\uFF09\u89C1\u8D44\u6E90 batter://guides/design-to-code\u3002";
768
- var FILE_KEY_PROP = {
769
- type: "string",
770
- minLength: 1,
771
- description: "\u76EE\u6807\u8BBE\u8BA1\u6587\u4EF6\u7684 fileKey\uFF08\u8DE8\u91CD\u5F00\u7A33\u5B9A\uFF0C\u4F18\u5148\uFF09\u3002\u4E0E sessionId \u4E8C\u9009\u4E00\uFF1B\u7528 list_sessions \u67E5\u770B\u5F53\u524D\u4F1A\u8BDD\u3002"
772
- };
773
- var SESSION_ID_PROP = {
774
- type: "string",
775
- minLength: 1,
776
- description: "\u76EE\u6807\u63D2\u4EF6\u4F1A\u8BDD\u7684 sessionId\uFF08ping/pong \u786E\u8BA4\u7684\u5B58\u6D3B\u5B9E\u4F8B\uFF09\u3002\u4E0E fileKey \u4E8C\u9009\u4E00\u3002"
777
- };
778
- var CACHE_PROP = {
779
- type: "string",
780
- enum: ["first", "refresh"],
781
- description: "\u7F13\u5B58\u7B56\u7565\uFF1Afirst=\u7F13\u5B58\u4F18\u5148\uFF08\u9ED8\u8BA4\uFF0C15 \u5206\u949F\u5185\u76F8\u540C\u8C03\u7528\u76F4\u63A5\u8FD4\u56DE\uFF09\uFF1Brefresh=\u5F3A\u5236\u91CD\u65B0\u6784\u5EFA/\u5BFC\u51FA\uFF08\u4ECD\u5199\u5165\u7F13\u5B58\uFF09\u3002\u8BBE\u8BA1\u7A3F\u521A\u6539\u8FC7\u4E14\u7ED3\u679C\u5BF9\u4E0D\u4E0A\u65F6\u7528 refresh\u3002"
782
- };
783
- var GUID_PROP = {
784
- type: "string",
785
- minLength: 1,
786
- description: '\u8282\u70B9 GUID\uFF08\u5982 "297:133032"\uFF09\uFF0C\u5FC5\u4F20\u3002\u94FE\u63A5\u8F93\u5165\uFF1A\u4ECE item-id \u63D0\u53D6\u5E76 URL \u89E3\u7801\u540E\u4F20\u5165\uFF1B\u8981\u64CD\u4F5C\u5F53\u524D\u9009\u4E2D\u56FE\u5C42\u65F6\uFF0C\u5148\u7528 get_selected \u53D6\u5176 guid \u518D\u4F20\u5165\u3002'
787
- };
788
851
  function jsonResult(data) {
789
852
  return { content: [{ type: "text", text: JSON.stringify(data) }] };
790
853
  }
@@ -794,138 +857,204 @@ function errorResult(err) {
794
857
  isError: true
795
858
  };
796
859
  }
860
+ var ENVELOPE_NOTE, GUIDES_PTR, FILE_KEY_PROP, SESSION_ID_PROP, CACHE_PROP, GUID_PROP;
861
+ var init_shared = __esm({
862
+ "src/agent/tools/shared.ts"() {
863
+ "use strict";
864
+ init_command_bus();
865
+ init_sessions();
866
+ init_config();
867
+ ENVELOPE_NOTE = "\u8FD4\u56DE\u5C01\u88C5\u4E3A {sessionId, fileKey, data}\uFF08\u9519\u8BEF\u4E3A isError \u6587\u672C\uFF0C\u4E0D\u542B\u4FE1\u5C01\uFF09\uFF1AsessionId \u662F ping/pong \u786E\u8BA4\u7684\u5B9E\u9645\u670D\u52A1\u4F1A\u8BDD\uFF0CfileKey \u662F\u8DE8\u91CD\u5F00\u7A33\u5B9A\u7684\u53E5\u67C4\u2014\u2014\u540E\u7EED\u8C03\u7528\u7528 fileKey \u5BFB\u5740\u3002";
868
+ GUIDES_PTR = "\u5B8C\u6574\u89C4\u5219\uFF08\u8D44\u6E90\u53D6\u7528/\u7EC4\u4EF6\u6620\u5C04/\u9519\u8BEF\u6062\u590D/\u7981\u6B62\u622A\u56FE\u786C\u7F16\u7801\uFF09\u89C1\u8D44\u6E90 batter://guides/design-to-code\u3002";
869
+ FILE_KEY_PROP = {
870
+ type: "string",
871
+ minLength: 1,
872
+ description: "\u76EE\u6807\u8BBE\u8BA1\u6587\u4EF6\u7684 fileKey\uFF08\u8DE8\u91CD\u5F00\u7A33\u5B9A\uFF0C\u4F18\u5148\uFF09\u3002\u4E0E sessionId \u4E8C\u9009\u4E00\uFF1B\u7528 list_sessions \u67E5\u770B\u5F53\u524D\u4F1A\u8BDD\u3002"
873
+ };
874
+ SESSION_ID_PROP = {
875
+ type: "string",
876
+ minLength: 1,
877
+ description: "\u76EE\u6807\u63D2\u4EF6\u4F1A\u8BDD\u7684 sessionId\uFF08ping/pong \u786E\u8BA4\u7684\u5B58\u6D3B\u5B9E\u4F8B\uFF09\u3002\u4E0E fileKey \u4E8C\u9009\u4E00\u3002"
878
+ };
879
+ CACHE_PROP = {
880
+ type: "string",
881
+ enum: ["first", "refresh"],
882
+ description: "\u7F13\u5B58\u7B56\u7565\uFF1Afirst=\u7F13\u5B58\u4F18\u5148\uFF08\u9ED8\u8BA4\uFF0C15 \u5206\u949F\u5185\u76F8\u540C\u8C03\u7528\u76F4\u63A5\u8FD4\u56DE\uFF09\uFF1Brefresh=\u5F3A\u5236\u91CD\u65B0\u6784\u5EFA/\u5BFC\u51FA\uFF08\u4ECD\u5199\u5165\u7F13\u5B58\uFF09\u3002\u8BBE\u8BA1\u7A3F\u521A\u6539\u8FC7\u4E14\u7ED3\u679C\u5BF9\u4E0D\u4E0A\u65F6\u7528 refresh\u3002"
883
+ };
884
+ GUID_PROP = {
885
+ type: "string",
886
+ minLength: 1,
887
+ description: '\u8282\u70B9 GUID\uFF08\u5982 "297:133032"\uFF09\uFF0C\u5FC5\u4F20\u3002\u94FE\u63A5\u8F93\u5165\uFF1A\u4ECE item-id \u63D0\u53D6\u5E76 URL \u89E3\u7801\u540E\u4F20\u5165\uFF1B\u8981\u64CD\u4F5C\u5F53\u524D\u9009\u4E2D\u56FE\u5C42\u65F6\uFF0C\u5148\u7528 get_selected \u53D6\u5176 guid \u518D\u4F20\u5165\u3002'
888
+ };
889
+ }
890
+ });
797
891
 
798
892
  // src/agent/tools/export_image.ts
799
- var exportImage = {
800
- description: '\u3010\u7528\u9014\u3011\u5BFC\u51FA\u8282\u70B9\u7684\u56FE\u7247\u7D20\u6750\uFF08\u5207\u56FE\uFF09\uFF0C\u4F9B\u4EA4\u4ED8\u4EE3\u7801\u4F7F\u7528\u3002\u5355\u8282\u70B9\u4F20 guid\uFF1B\u591A\u8282\u70B9\u4F20 guids \u6570\u7EC4\u6279\u91CF\u3002DSL \u4E2D\u5DF2\u5E26 img \u7684\u8282\u70B9\uFF08ICON/IMAGE \u53F6\u5B50\uFF09\u5728\u53D6 DSL \u65F6\u5DF2\u5BFC\u51FA\uFF0C\u76F4\u63A5\u7528\u5176\u6587\u4EF6\u540D\uFF0C\u4E25\u7981\u5BF9\u672C\u7C7B\u8282\u70B9\u91CD\u590D\u8C03\u7528\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011"\u5207\u56FE/\u5BFC\u51FA\u56FE\u6807/\u5BFC\u51FA\u56FE\u7247\u7D20\u6750/\u628A\u67D0\u4E2A\u8282\u70B9\u5B58\u6210\u56FE\u7247"\u3002\u8981\u89C6\u89C9\u53C2\u7167\u7528 get_screenshot\u3002\u3010\u8FD4\u56DE\u3011\u5355\u8282\u70B9 {width, height, exports: [{img, format, scale?, suffix?}]}\uFF1B\u6279\u91CF {items: [{guid, width, height, exports}]}\u3002\u8282\u70B9\u5E26\u8BBE\u8BA1\u5E08\u5BFC\u51FA\u8BBE\u7F6E\u65F6\u6309\u5176\u8BBE\u7F6E\u9010\u4EFD\u5BFC\u51FA\uFF08format/scale \u53C2\u6570\u5931\u6548\uFF09\uFF1B\u65E0\u6807\u8BB0\u6309\u53C2\u6570\u5BFC\u51FA\u5355\u4EFD\uFF08\u9ED8\u8BA4 PNG\u30012x\uFF09\u3002suffix\uFF08\u5982 "@2x"\uFF09\u590D\u5236\u8FDB\u9879\u76EE\u65F6\u4FDD\u7559\u547D\u540D\u3002\u3010\u786C\u6027\u89C4\u5219\u3011' + GUIDES_PTR + " " + ENVELOPE_NOTE,
801
- // 两组互斥各取其一:guid|guids(单节点|批量)× fileKey|sessionId(路由)
802
- requires: [
803
- ["guid", "guids"],
804
- ["fileKey", "sessionId"]
805
- ],
806
- properties: {
807
- guid: GUID_PROP,
808
- guids: {
809
- type: "array",
810
- items: { type: "string" },
811
- description: "\u6279\u91CF\u5BFC\u51FA\uFF1A\u8282\u70B9 GUID \u6570\u7EC4\uFF08\u4E0E guid \u4E8C\u9009\u4E00\uFF09\u3002\u591A\u8282\u70B9\u573A\u666F\u4F18\u5148\u7528\u6279\u91CF\uFF0C\u907F\u514D\u9010\u4E2A\u8C03\u7528\u3002"
812
- },
813
- format: {
814
- type: "string",
815
- enum: ["PNG", "SVG", "JPG"],
816
- description: "\u65E0\u5BFC\u51FA\u6807\u8BB0\u65F6\u7684\u515C\u5E95\u683C\u5F0F\uFF0C\u9ED8\u8BA4 PNG\u3002\u8282\u70B9\u5E26\u5BFC\u51FA\u6807\u8BB0\u65F6\u6B64\u53C2\u6570\u65E0\u6548\u3002"
817
- },
818
- scale: {
819
- type: "number",
820
- description: "\u65E0\u5BFC\u51FA\u6807\u8BB0\u65F6\u7684\u515C\u5E95\u500D\u7387\uFF0C\u9ED8\u8BA4 2\u3002\u8282\u70B9\u5E26\u5BFC\u51FA\u6807\u8BB0\u65F6\u6B64\u53C2\u6570\u65E0\u6548\u3002"
821
- },
822
- cache: CACHE_PROP,
823
- fileKey: FILE_KEY_PROP,
824
- sessionId: SESSION_ID_PROP
893
+ var exportImage;
894
+ var init_export_image = __esm({
895
+ "src/agent/tools/export_image.ts"() {
896
+ "use strict";
897
+ init_shared();
898
+ exportImage = {
899
+ description: '\u3010\u7528\u9014\u3011\u5BFC\u51FA\u8282\u70B9\u7684\u56FE\u7247\u7D20\u6750\uFF08\u5207\u56FE\uFF09\uFF0C\u4F9B\u4EA4\u4ED8\u4EE3\u7801\u4F7F\u7528\u3002\u5355\u8282\u70B9\u4F20 guid\uFF1B\u591A\u8282\u70B9\u4F20 guids \u6570\u7EC4\u6279\u91CF\u3002DSL \u4E2D\u5DF2\u5E26 img \u7684\u8282\u70B9\uFF08ICON/IMAGE \u53F6\u5B50\uFF09\u5728\u53D6 DSL \u65F6\u5DF2\u5BFC\u51FA\uFF0C\u76F4\u63A5\u7528\u5176\u6587\u4EF6\u540D\uFF0C\u4E25\u7981\u5BF9\u672C\u7C7B\u8282\u70B9\u91CD\u590D\u8C03\u7528\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011"\u5207\u56FE/\u5BFC\u51FA\u56FE\u6807/\u5BFC\u51FA\u56FE\u7247\u7D20\u6750/\u628A\u67D0\u4E2A\u8282\u70B9\u5B58\u6210\u56FE\u7247"\u3002\u8981\u89C6\u89C9\u53C2\u7167\u7528 get_screenshot\u3002\u3010\u8FD4\u56DE\u3011\u5355\u8282\u70B9 {width, height, exports: [{img, format, scale?, suffix?}]}\uFF1B\u6279\u91CF {items: [{guid, width, height, exports}]}\u3002\u8282\u70B9\u5E26\u8BBE\u8BA1\u5E08\u5BFC\u51FA\u8BBE\u7F6E\u65F6\u6309\u5176\u8BBE\u7F6E\u9010\u4EFD\u5BFC\u51FA\uFF08format/scale \u53C2\u6570\u5931\u6548\uFF09\uFF1B\u65E0\u6807\u8BB0\u6309\u53C2\u6570\u5BFC\u51FA\u5355\u4EFD\uFF08\u9ED8\u8BA4 PNG\u30012x\uFF09\u3002suffix\uFF08\u5982 "@2x"\uFF09\u590D\u5236\u8FDB\u9879\u76EE\u65F6\u4FDD\u7559\u547D\u540D\u3002\u3010\u786C\u6027\u89C4\u5219\u3011' + GUIDES_PTR + " " + ENVELOPE_NOTE,
900
+ // 两组互斥各取其一:guid|guids(单节点|批量)× fileKey|sessionId(路由)
901
+ requires: [
902
+ ["guid", "guids"],
903
+ ["fileKey", "sessionId"]
904
+ ],
905
+ properties: {
906
+ guid: GUID_PROP,
907
+ guids: {
908
+ type: "array",
909
+ items: { type: "string" },
910
+ description: "\u6279\u91CF\u5BFC\u51FA\uFF1A\u8282\u70B9 GUID \u6570\u7EC4\uFF08\u4E0E guid \u4E8C\u9009\u4E00\uFF09\u3002\u591A\u8282\u70B9\u573A\u666F\u4F18\u5148\u7528\u6279\u91CF\uFF0C\u907F\u514D\u9010\u4E2A\u8C03\u7528\u3002"
911
+ },
912
+ format: {
913
+ type: "string",
914
+ enum: ["PNG", "SVG", "JPG"],
915
+ description: "\u65E0\u5BFC\u51FA\u6807\u8BB0\u65F6\u7684\u515C\u5E95\u683C\u5F0F\uFF0C\u9ED8\u8BA4 PNG\u3002\u8282\u70B9\u5E26\u5BFC\u51FA\u6807\u8BB0\u65F6\u6B64\u53C2\u6570\u65E0\u6548\u3002"
916
+ },
917
+ scale: {
918
+ type: "number",
919
+ description: "\u65E0\u5BFC\u51FA\u6807\u8BB0\u65F6\u7684\u515C\u5E95\u500D\u7387\uFF0C\u9ED8\u8BA4 2\u3002\u8282\u70B9\u5E26\u5BFC\u51FA\u6807\u8BB0\u65F6\u6B64\u53C2\u6570\u65E0\u6548\u3002"
920
+ },
921
+ cache: CACHE_PROP,
922
+ fileKey: FILE_KEY_PROP,
923
+ sessionId: SESSION_ID_PROP
924
+ }
925
+ };
825
926
  }
826
- };
927
+ });
827
928
 
828
929
  // src/agent/tools/get_metadata.ts
829
- var getMetadata = {
830
- description: '\u3010\u7528\u9014\u3011\u5168\u6811\u7ED3\u6784\u9AA8\u67B6\uFF08\u4EC5 id/name/type/kids\uFF0C\u65E0\u6837\u5F0F/\u5750\u6807/\u8D44\u6E90\uFF0C\u4F53\u79EF\u6781\u5C0F\uFF09\u3002\u9996\u6B21\u63A5\u89E6\u8BBE\u8BA1\u7A3F\u65F6\uFF1A\u672C\u5DE5\u5177 + get_screenshot \u5B9A\u4F4D\u76EE\u6807\u8282\u70B9\uFF0C\u518D\u5BF9\u76EE\u6807\u8C03 get_node_dsl \u53D6\u5168\u91CF\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011\u9996\u6B21\u63A5\u89E6\u8BBE\u8BA1\u7A3F\uFF1B"\u9875\u9762\u91CC\u6709\u6CA1\u6709 XX"\uFF1B\u6309\u540D\u5B57\u627E\u8282\u70B9\uFF1B\u53EA\u8981\u7ED3\u6784\u6982\u89C8\u4E0D\u8981\u6837\u5F0F\u3002\u3010\u8FD4\u56DE\u3011\u7D27\u51D1 JSON\uFF1A{id, name?, type, kids: \u9012\u5F52}\uFF1BICON/IMAGE \u53F6\u5B50\u65E0 img\uFF08\u9700\u8D44\u6E90\u65F6\u6309 id \u8C03 get_node_dsl\uFF09\u3002\u3010\u786C\u6027\u89C4\u5219\u3011' + GUIDES_PTR + " " + ENVELOPE_NOTE,
831
- requires: ["guid", ["fileKey", "sessionId"]],
832
- properties: {
833
- guid: GUID_PROP,
834
- cache: CACHE_PROP,
835
- fileKey: FILE_KEY_PROP,
836
- sessionId: SESSION_ID_PROP
930
+ var getMetadata;
931
+ var init_get_metadata = __esm({
932
+ "src/agent/tools/get_metadata.ts"() {
933
+ "use strict";
934
+ init_shared();
935
+ getMetadata = {
936
+ description: '\u3010\u7528\u9014\u3011\u5168\u6811\u7ED3\u6784\u9AA8\u67B6\uFF08\u4EC5 id/name/type/kids\uFF0C\u65E0\u6837\u5F0F/\u5750\u6807/\u8D44\u6E90\uFF0C\u4F53\u79EF\u6781\u5C0F\uFF09\u3002\u9996\u6B21\u63A5\u89E6\u8BBE\u8BA1\u7A3F\u65F6\uFF1A\u672C\u5DE5\u5177 + get_screenshot \u5B9A\u4F4D\u76EE\u6807\u8282\u70B9\uFF0C\u518D\u5BF9\u76EE\u6807\u8C03 get_node_dsl \u53D6\u5168\u91CF\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011\u9996\u6B21\u63A5\u89E6\u8BBE\u8BA1\u7A3F\uFF1B"\u9875\u9762\u91CC\u6709\u6CA1\u6709 XX"\uFF1B\u6309\u540D\u5B57\u627E\u8282\u70B9\uFF1B\u53EA\u8981\u7ED3\u6784\u6982\u89C8\u4E0D\u8981\u6837\u5F0F\u3002\u3010\u8FD4\u56DE\u3011\u7D27\u51D1 JSON\uFF1A{id, name?, type, kids: \u9012\u5F52}\uFF1BICON/IMAGE \u53F6\u5B50\u65E0 img\uFF08\u9700\u8D44\u6E90\u65F6\u6309 id \u8C03 get_node_dsl\uFF09\u3002\u3010\u786C\u6027\u89C4\u5219\u3011' + GUIDES_PTR + " " + ENVELOPE_NOTE,
937
+ requires: ["guid", ["fileKey", "sessionId"]],
938
+ properties: {
939
+ guid: GUID_PROP,
940
+ cache: CACHE_PROP,
941
+ fileKey: FILE_KEY_PROP,
942
+ sessionId: SESSION_ID_PROP
943
+ }
944
+ };
837
945
  }
838
- };
946
+ });
839
947
 
840
948
  // src/agent/tools/get_node_dsl.ts
841
- var URL_RULE = '1. \u94FE\u63A5\u8F93\u5165\uFF1A\u5FC5\u987B\u4ECE item-id \u67E5\u8BE2\u53C2\u6570\u63D0\u53D6\u8282\u70B9 guid \u5E76 URL \u89E3\u7801\uFF081%3A2 \u2192 "1:2"\uFF09\uFF0C\u628A\u89E3\u7801\u503C\u4F5C\u4E3A guid \u4F20\u5165\uFF1B\u4E25\u7981\u628A\u6574\u4E2A URL \u5F53 guid \u4F20\u3002\u4EFB\u610F\u57DF\u540D\u90FD\u6709\u6548\uFF1A\u5B98\u65B9 pixso.cn \u6216\u79C1\u6709\u5316\u90E8\u7F72\uFF08\u5982 pixso.ky-tech.com.cn\uFF09\uFF0C\u683C\u5F0F\u5F62\u5982 https://<\u57DF\u540D>/app/design/<\u6587\u4EF6>?item-id=1%3A2\uFF08\u4E5F\u53EF\u80FD\u662F /app/editor/ \u8DEF\u5F84\uFF09\u3002';
842
- var DSL_LEGEND = '\u5B57\u6BB5\u56FE\u4F8B\uFF1Aid=\u8282\u70B9 guid\uFF08\u53EF\u56DE\u4F20 guid \u4E0B\u94BB\uFF09\uFF1Bname=\u56FE\u5C42\u540D\uFF08\u81EA\u52A8\u547D\u540D\u5DF2\u7701\u7565\uFF09\uFF1Btype=\u8282\u70B9\u7C7B\u578B\uFF08ICON/IMAGE \u4E3A\u584C\u7F29\u53F6\u5B50\uFF09\uFF1Brect=[x,y,w,h] \u7EDD\u5BF9\u5B9A\u4F4D\uFF1Blayout={dir:row|column, gap, pad:[\u4E0A,\u53F3,\u4E0B,\u5DE6], align:[\u4E3B\u8F74,\u4EA4\u53C9\u8F74], sizing:[hug|fixed|fill]\xD72}\uFF1Bconstraints={h,v}\uFF08\u9ED8\u8BA4 left/top \u7701\u7565\uFF09\uFF1Btext={content, size, font, weight, lh, align, color, deco, ls, link, segs=\u6DF7\u6392\u5206\u6BB5\uFF08\u4EC5\u5DEE\u5F02\u5B57\u6BB5\uFF0C\u987A\u5E8F\u5373\u5B57\u7B26\u504F\u79FB\uFF09}\uFF1Bimg=\u8D44\u6E90\u6587\u4EF6\u540D\uFF1Bkids=\u5B50\u8282\u70B9\u9012\u5F52\uFF1Bpalette=\u8C03\u8272\u677F\uFF08\u4EC5\u6839\u8282\u70B9\uFF09\uFF1A\u22652 \u6B21\u7684\u5355\u8272\u63D0\u53D6\u4E3A\u6570\u7EC4\uFF0Cfills/text.color/stroke \u4E2D "p0""p1" \u4E3A\u4E0B\u6807\u5F15\u7528\uFF08\u590D\u5408 CSS \u4E32\u5185\u989C\u8272\u4E0D\u67E5\u8868\uFF09\uFF1Bnotes=\u8BBE\u8BA1\u6807\u6CE8\u6587\u672C\uFF08\u4EC5\u5B58\u5728\u65F6\uFF09\uFF1Bcomp=\u7EC4\u4EF6\u5173\u8054\uFF08INSTANCE/COMPONENT/COMPONENT_SET\uFF09\uFF1Akey=\u8DE8\u6587\u4EF6\u7A33\u5B9A\u6807\u8BC6\uFF0Cname=\u7EC4\u4EF6\u540D\uFF08\u6620\u5C04\u6743\u91CD\u6700\u9AD8\u7684\u4F9D\u636E\u2014\u2014\u5728\u9879\u76EE\u7EC4\u4EF6\u5E93\u627E\u5BF9\u5E94\uFF0C\u4E0D\u9010\u5B57\u76F4\u7528\uFF09\uFF0Cprops=variant \u5C5E\u6027\uFF0Cset=variant \u7EC4\uFF1Bdepth=\u6574\u6811\u5B9E\u9645\u6DF1\u5EA6\uFF08\u4EC5\u6839\u8282\u70B9\uFF0C\u603B\u662F\u8F93\u51FA\uFF09\uFF1Bmore=\u622A\u65AD\u5904\u5269\u4F59\u5C42\u7EA7\u6570\uFF08\u4EC5 maxDepth \u622A\u65AD\u65F6\uFF0C\u62FF id \u56DE\u4F20 guid \u4E0B\u94BB\uFF09\uFF1BkidsSummary=\u622A\u65AD\u5904\u6216\u584C\u7F29\u53F6\u5B50\uFF08img \u8282\u70B9\uFF09\u7684\u76F4\u63A5\u5B50\u8282\u70B9\u6D45\u9884\u89C8 [{id, name?, type}]\uFF08\u8F85\u52A9\u9009\u62E9\u4E0B\u94BB\u5206\u652F\uFF09\u3002\u7701\u7565\u89C4\u5219\uFF1A\u9ED8\u8BA4\u503C\u4E0D\u8F93\u51FA\uFF08text.align=left\u3001lh=normal\u3001weight=Regular\u3001gap=0\u3001pad \u5168 0\uFF09\u3002';
843
- var getNodeDsl = {
844
- description: '\u3010\u7528\u9014\u3011\u751F\u6210\u8282\u70B9\u7684\u5F00\u53D1\u5411\u7D27\u51D1 DSL\uFF08\u7ED3\u6784+\u6837\u5F0F+\u8D44\u6E90\u6587\u4EF6\u540D\uFF09\uFF0C\u8BBE\u8BA1\u8F6C\u4EE3\u7801\u7684\u6838\u5FC3\u5165\u53E3\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011"\u6837\u5F0F\u8FD8\u539F/\u8BBE\u8BA1\u8FD8\u539F/UI \u8FD8\u539F/\u8BBE\u8BA1\u8F6C\u4EE3\u7801/\u6839\u636E\u8BBE\u8BA1\u7A3F\u5199\u9875\u9762\u3001\u7EC4\u4EF6"\uFF0C\u6216\u7ED9\u51FA Pixso \u94FE\u63A5\uFF08\u4EFB\u610F\u57DF\u540D\uFF09\u65F6\uFF0C\u9ED8\u8BA4\u4ECE\u672C\u5DE5\u5177\u5F00\u59CB\u3002\u9996\u6B21\u63A5\u89E6\u8BBE\u8BA1\u7A3F\uFF1A\u5148 get_metadata \u9AA8\u67B6 + get_screenshot \u89C6\u89C9\uFF0C\u5B9A\u4F4D\u76EE\u6807\u540E\u518D\u7528\u672C\u5DE5\u5177\u3002\u7ED3\u6784\u590D\u6742\uFF1AmaxDepth \u4F20 0 \u9884\u5224\u89C4\u6A21\uFF08\u6839\u8282\u70B9 depth=\u6574\u6811\u6DF1\u5EA6\uFF09\uFF0C\u518D\u5BF9\u5E26 more \u6807\u8BB0\u7684\u5B50\u6811\u6309 id \u4E0B\u94BB\uFF08kidsSummary \u8F85\u52A9\u9009\u5206\u652F\uFF09\u3002\u3010\u8FD4\u56DE\u3011\u5355\u884C\u7D27\u51D1 JSON\u3002' + DSL_LEGEND + "\u584C\u7F29\u53F6\u5B50\uFF1A\u7EAF\u77E2\u91CF\u7EC4\u4EF6\u5B9E\u4F8B\u2192SVG\u3001\u56FE\u7247\u586B\u5145\u2192PNG\u3001\u8BBE\u8BA1\u5E08\u6807\u8BB0\u6309\u5176\u8BBE\u7F6E\uFF1B\u5DF2\u5E26 img \u7684\u8282\u70B9\u5DF2\u5B8C\u6210\u5BFC\u51FA\uFF0C\u76F4\u63A5\u7528\u5176\u6587\u4EF6\u540D\uFF0C\u52FF\u518D\u8C03 export_image\u3002DSL \u6709\u635F\u7CBE\u7B80\uFF08\u7701\u7565\u8499\u7248/\u5E03\u5C14\u8DEF\u5F84\u7B49\uFF09\uFF0C\u89C6\u89C9\u590D\u6742\u8282\u70B9\u914D\u5408 get_screenshot \u5BF9\u7167\u3002\u3010\u786C\u6027\u89C4\u5219\u3011" + URL_RULE + " " + GUIDES_PTR + " " + ENVELOPE_NOTE,
845
- requires: ["guid", ["fileKey", "sessionId"]],
846
- properties: {
847
- guid: GUID_PROP,
848
- maxDepth: {
849
- type: "number",
850
- description: "\u4E0B\u63A2\u5B50\u8282\u70B9\u7684\u6700\u5927\u6DF1\u5EA6\uFF08\u6839\u8282\u70B9\u4E3A\u7B2C 0 \u5C42\uFF09\uFF0C\u7F3A\u7701\u4E0D\u9650\u3002\u622A\u65AD\u8282\u70B9\u5E26 more\uFF08\u5269\u4F59\u5C42\u7EA7\u6570\uFF09+ kidsSummary\uFF08\u5B50\u8282\u70B9\u6D45\u9884\u89C8\uFF09\uFF0C\u62FF\u5176 id \u56DE\u4F20 guid \u7EE7\u7EED\u4E0B\u94BB\uFF1B\u4F20 0 \u53EA\u8FD4\u56DE\u6839\u8282\u70B9\uFF08\u914D\u5408 depth \u5B57\u6BB5\u9884\u5224\u89C4\u6A21\uFF09\u3002"
851
- },
852
- cache: CACHE_PROP,
853
- fileKey: FILE_KEY_PROP,
854
- sessionId: SESSION_ID_PROP
949
+ var URL_RULE, DSL_LEGEND, getNodeDsl;
950
+ var init_get_node_dsl = __esm({
951
+ "src/agent/tools/get_node_dsl.ts"() {
952
+ "use strict";
953
+ init_shared();
954
+ URL_RULE = '1. \u94FE\u63A5\u8F93\u5165\uFF1A\u5FC5\u987B\u4ECE item-id \u67E5\u8BE2\u53C2\u6570\u63D0\u53D6\u8282\u70B9 guid \u5E76 URL \u89E3\u7801\uFF081%3A2 \u2192 "1:2"\uFF09\uFF0C\u628A\u89E3\u7801\u503C\u4F5C\u4E3A guid \u4F20\u5165\uFF1B\u4E25\u7981\u628A\u6574\u4E2A URL \u5F53 guid \u4F20\u3002\u4EFB\u610F\u57DF\u540D\u90FD\u6709\u6548\uFF1A\u5B98\u65B9 pixso.cn \u6216\u79C1\u6709\u5316\u90E8\u7F72\uFF08\u5982 pixso.ky-tech.com.cn\uFF09\uFF0C\u683C\u5F0F\u5F62\u5982 https://<\u57DF\u540D>/app/design/<\u6587\u4EF6>?item-id=1%3A2\uFF08\u4E5F\u53EF\u80FD\u662F /app/editor/ \u8DEF\u5F84\uFF09\u3002';
955
+ DSL_LEGEND = '\u5B57\u6BB5\u56FE\u4F8B\uFF1Aid=\u8282\u70B9 guid\uFF08\u53EF\u56DE\u4F20 guid \u4E0B\u94BB\uFF09\uFF1Bname=\u56FE\u5C42\u540D\uFF08\u81EA\u52A8\u547D\u540D\u5DF2\u7701\u7565\uFF09\uFF1Btype=\u8282\u70B9\u7C7B\u578B\uFF08ICON/IMAGE \u4E3A\u584C\u7F29\u53F6\u5B50\uFF09\uFF1Brect=[x,y,w,h] \u7EDD\u5BF9\u5B9A\u4F4D\uFF1Blayout={dir:row|column, gap, pad:[\u4E0A,\u53F3,\u4E0B,\u5DE6], align:[\u4E3B\u8F74,\u4EA4\u53C9\u8F74], sizing:[hug|fixed|fill]\xD72}\uFF1Bconstraints={h,v}\uFF08\u9ED8\u8BA4 left/top \u7701\u7565\uFF09\uFF1Btext={content, size, font, weight, lh, align, color, deco, ls, link, segs=\u6DF7\u6392\u5206\u6BB5\uFF08\u4EC5\u5DEE\u5F02\u5B57\u6BB5\uFF0C\u987A\u5E8F\u5373\u5B57\u7B26\u504F\u79FB\uFF09}\uFF1Bimg=\u8D44\u6E90\u6587\u4EF6\u540D\uFF1Bkids=\u5B50\u8282\u70B9\u9012\u5F52\uFF1Bpalette=\u8C03\u8272\u677F\uFF08\u4EC5\u6839\u8282\u70B9\uFF09\uFF1A\u22652 \u6B21\u7684\u5355\u8272\u63D0\u53D6\u4E3A\u6570\u7EC4\uFF0Cfills/text.color/stroke \u4E2D "p0""p1" \u4E3A\u4E0B\u6807\u5F15\u7528\uFF08\u590D\u5408 CSS \u4E32\u5185\u989C\u8272\u4E0D\u67E5\u8868\uFF09\uFF1Bnotes=\u8BBE\u8BA1\u6807\u6CE8\u6587\u672C\uFF08\u4EC5\u5B58\u5728\u65F6\uFF09\uFF1Bcomp=\u7EC4\u4EF6\u5173\u8054\uFF08INSTANCE/COMPONENT/COMPONENT_SET\uFF09\uFF1Akey=\u8DE8\u6587\u4EF6\u7A33\u5B9A\u6807\u8BC6\uFF0Cname=\u7EC4\u4EF6\u540D\uFF08\u6620\u5C04\u6743\u91CD\u6700\u9AD8\u7684\u4F9D\u636E\u2014\u2014\u5728\u9879\u76EE\u7EC4\u4EF6\u5E93\u627E\u5BF9\u5E94\uFF0C\u4E0D\u9010\u5B57\u76F4\u7528\uFF09\uFF0Cprops=variant \u5C5E\u6027\uFF0Cset=variant \u7EC4\uFF1Bdepth=\u6574\u6811\u5B9E\u9645\u6DF1\u5EA6\uFF08\u4EC5\u6839\u8282\u70B9\uFF0C\u603B\u662F\u8F93\u51FA\uFF09\uFF1Bmore=\u622A\u65AD\u5904\u5269\u4F59\u5C42\u7EA7\u6570\uFF08\u4EC5 maxDepth \u622A\u65AD\u65F6\uFF0C\u62FF id \u56DE\u4F20 guid \u4E0B\u94BB\uFF09\uFF1BkidsSummary=\u622A\u65AD\u5904\u6216\u584C\u7F29\u53F6\u5B50\uFF08img \u8282\u70B9\uFF09\u7684\u76F4\u63A5\u5B50\u8282\u70B9\u6D45\u9884\u89C8 [{id, name?, type}]\uFF08\u8F85\u52A9\u9009\u62E9\u4E0B\u94BB\u5206\u652F\uFF09\u3002\u7701\u7565\u89C4\u5219\uFF1A\u9ED8\u8BA4\u503C\u4E0D\u8F93\u51FA\uFF08text.align=left\u3001lh=normal\u3001weight=Regular\u3001gap=0\u3001pad \u5168 0\uFF09\u3002';
956
+ getNodeDsl = {
957
+ description: '\u3010\u7528\u9014\u3011\u751F\u6210\u8282\u70B9\u7684\u5F00\u53D1\u5411\u7D27\u51D1 DSL\uFF08\u7ED3\u6784+\u6837\u5F0F+\u8D44\u6E90\u6587\u4EF6\u540D\uFF09\uFF0C\u8BBE\u8BA1\u8F6C\u4EE3\u7801\u7684\u6838\u5FC3\u5165\u53E3\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011"\u6837\u5F0F\u8FD8\u539F/\u8BBE\u8BA1\u8FD8\u539F/UI \u8FD8\u539F/\u8BBE\u8BA1\u8F6C\u4EE3\u7801/\u6839\u636E\u8BBE\u8BA1\u7A3F\u5199\u9875\u9762\u3001\u7EC4\u4EF6"\uFF0C\u6216\u7ED9\u51FA Pixso \u94FE\u63A5\uFF08\u4EFB\u610F\u57DF\u540D\uFF09\u65F6\uFF0C\u9ED8\u8BA4\u4ECE\u672C\u5DE5\u5177\u5F00\u59CB\u3002\u9996\u6B21\u63A5\u89E6\u8BBE\u8BA1\u7A3F\uFF1A\u5148 get_metadata \u9AA8\u67B6 + get_screenshot \u89C6\u89C9\uFF0C\u5B9A\u4F4D\u76EE\u6807\u540E\u518D\u7528\u672C\u5DE5\u5177\u3002\u7ED3\u6784\u590D\u6742\uFF1AmaxDepth \u4F20 0 \u9884\u5224\u89C4\u6A21\uFF08\u6839\u8282\u70B9 depth=\u6574\u6811\u6DF1\u5EA6\uFF09\uFF0C\u518D\u5BF9\u5E26 more \u6807\u8BB0\u7684\u5B50\u6811\u6309 id \u4E0B\u94BB\uFF08kidsSummary \u8F85\u52A9\u9009\u5206\u652F\uFF09\u3002\u3010\u8FD4\u56DE\u3011\u5355\u884C\u7D27\u51D1 JSON\u3002' + DSL_LEGEND + "\u584C\u7F29\u53F6\u5B50\uFF1A\u7EAF\u77E2\u91CF\u7EC4\u4EF6\u5B9E\u4F8B\u2192SVG\u3001\u56FE\u7247\u586B\u5145\u2192PNG\u3001\u8BBE\u8BA1\u5E08\u6807\u8BB0\u6309\u5176\u8BBE\u7F6E\uFF1B\u5DF2\u5E26 img \u7684\u8282\u70B9\u5DF2\u5B8C\u6210\u5BFC\u51FA\uFF0C\u76F4\u63A5\u7528\u5176\u6587\u4EF6\u540D\uFF0C\u52FF\u518D\u8C03 export_image\u3002DSL \u6709\u635F\u7CBE\u7B80\uFF08\u7701\u7565\u8499\u7248/\u5E03\u5C14\u8DEF\u5F84\u7B49\uFF09\uFF0C\u89C6\u89C9\u590D\u6742\u8282\u70B9\u914D\u5408 get_screenshot \u5BF9\u7167\u3002\u3010\u786C\u6027\u89C4\u5219\u3011" + URL_RULE + " " + GUIDES_PTR + " " + ENVELOPE_NOTE,
958
+ requires: ["guid", ["fileKey", "sessionId"]],
959
+ properties: {
960
+ guid: GUID_PROP,
961
+ maxDepth: {
962
+ type: "number",
963
+ description: "\u4E0B\u63A2\u5B50\u8282\u70B9\u7684\u6700\u5927\u6DF1\u5EA6\uFF08\u6839\u8282\u70B9\u4E3A\u7B2C 0 \u5C42\uFF09\uFF0C\u7F3A\u7701\u4E0D\u9650\u3002\u622A\u65AD\u8282\u70B9\u5E26 more\uFF08\u5269\u4F59\u5C42\u7EA7\u6570\uFF09+ kidsSummary\uFF08\u5B50\u8282\u70B9\u6D45\u9884\u89C8\uFF09\uFF0C\u62FF\u5176 id \u56DE\u4F20 guid \u7EE7\u7EED\u4E0B\u94BB\uFF1B\u4F20 0 \u53EA\u8FD4\u56DE\u6839\u8282\u70B9\uFF08\u914D\u5408 depth \u5B57\u6BB5\u9884\u5224\u89C4\u6A21\uFF09\u3002"
964
+ },
965
+ cache: CACHE_PROP,
966
+ fileKey: FILE_KEY_PROP,
967
+ sessionId: SESSION_ID_PROP
968
+ }
969
+ };
855
970
  }
856
- };
971
+ });
857
972
 
858
973
  // src/agent/tools/get_screenshot.ts
859
- var getScreenshot = {
860
- description: '\u3010\u7528\u9014\u3011\u5BFC\u51FA\u5355\u4E2A\u8282\u70B9\u7684\u622A\u56FE\uFF0C\u4F5C\u89C6\u89C9\u53C2\u7167\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011"\u770B\u770B\u8FD9\u4E2A\u8BBE\u8BA1\u7A3F\u957F\u4EC0\u4E48\u6837"/"\u7ED9\u6211\u622A\u56FE"\uFF1B\u8FD8\u539F\u4EFB\u52A1\u9700\u8981\u89C6\u89C9\u53C2\u7167\u6216\u8FD8\u539F\u5EA6\u6821\u9A8C\u3002\u8981\u5207\u56FE\u7D20\u6750\u7528 export_image\u3002\u751F\u6210\u4EE3\u7801\u540E\u53EF\u518D\u6B21\u8C03\u7528\u672C\u5DE5\u5177\u5BF9\u6BD4\u8FD8\u539F\u5EA6\uFF1B\u5EFA\u8BAE\u4E0E get_node_dsl \u914D\u5408\uFF1ADSL \u5B9A\u7ED3\u6784\uFF0C\u622A\u56FE\u6821\u9A8C\u89C6\u89C9\u3002\u3010\u8FD4\u56DE\u3011{img: \u4EA7\u7269\u6587\u4EF6\u540D, width, height, format}\u3002\u3010\u786C\u6027\u89C4\u5219\u3011' + GUIDES_PTR + " " + ENVELOPE_NOTE,
861
- requires: ["guid", ["fileKey", "sessionId"]],
862
- properties: {
863
- guid: GUID_PROP,
864
- format: {
865
- type: "string",
866
- enum: ["PNG", "SVG"],
867
- description: "\u5BFC\u51FA\u683C\u5F0F\uFF0C\u9ED8\u8BA4 PNG\u3002\u622A\u56FE\u7528\u9014\u7528 PNG\uFF1B\u7EAF\u56FE\u5F62\u60F3\u62FF\u77E2\u91CF\u518D\u9009 SVG\u3002"
868
- },
869
- scale: {
870
- type: "number",
871
- description: "\u5BFC\u51FA\u500D\u7387\uFF0C\u9ED8\u8BA4 2\u3002"
872
- },
873
- cache: CACHE_PROP,
874
- fileKey: FILE_KEY_PROP,
875
- sessionId: SESSION_ID_PROP
974
+ var getScreenshot;
975
+ var init_get_screenshot = __esm({
976
+ "src/agent/tools/get_screenshot.ts"() {
977
+ "use strict";
978
+ init_shared();
979
+ getScreenshot = {
980
+ description: '\u3010\u7528\u9014\u3011\u5BFC\u51FA\u5355\u4E2A\u8282\u70B9\u7684\u622A\u56FE\uFF0C\u4F5C\u89C6\u89C9\u53C2\u7167\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011"\u770B\u770B\u8FD9\u4E2A\u8BBE\u8BA1\u7A3F\u957F\u4EC0\u4E48\u6837"/"\u7ED9\u6211\u622A\u56FE"\uFF1B\u8FD8\u539F\u4EFB\u52A1\u9700\u8981\u89C6\u89C9\u53C2\u7167\u6216\u8FD8\u539F\u5EA6\u6821\u9A8C\u3002\u8981\u5207\u56FE\u7D20\u6750\u7528 export_image\u3002\u751F\u6210\u4EE3\u7801\u540E\u53EF\u518D\u6B21\u8C03\u7528\u672C\u5DE5\u5177\u5BF9\u6BD4\u8FD8\u539F\u5EA6\uFF1B\u5EFA\u8BAE\u4E0E get_node_dsl \u914D\u5408\uFF1ADSL \u5B9A\u7ED3\u6784\uFF0C\u622A\u56FE\u6821\u9A8C\u89C6\u89C9\u3002\u3010\u8FD4\u56DE\u3011{img: \u4EA7\u7269\u6587\u4EF6\u540D, width, height, format}\u3002\u3010\u786C\u6027\u89C4\u5219\u3011' + GUIDES_PTR + " " + ENVELOPE_NOTE,
981
+ requires: ["guid", ["fileKey", "sessionId"]],
982
+ properties: {
983
+ guid: GUID_PROP,
984
+ format: {
985
+ type: "string",
986
+ enum: ["PNG", "SVG"],
987
+ description: "\u5BFC\u51FA\u683C\u5F0F\uFF0C\u9ED8\u8BA4 PNG\u3002\u622A\u56FE\u7528\u9014\u7528 PNG\uFF1B\u7EAF\u56FE\u5F62\u60F3\u62FF\u77E2\u91CF\u518D\u9009 SVG\u3002"
988
+ },
989
+ scale: {
990
+ type: "number",
991
+ description: "\u5BFC\u51FA\u500D\u7387\uFF0C\u9ED8\u8BA4 2\u3002"
992
+ },
993
+ cache: CACHE_PROP,
994
+ fileKey: FILE_KEY_PROP,
995
+ sessionId: SESSION_ID_PROP
996
+ }
997
+ };
876
998
  }
877
- };
999
+ });
878
1000
 
879
1001
  // src/agent/tools/get_selected.ts
880
- var getSelected = {
881
- description: '\u3010\u7528\u9014\u3011\u5B9E\u65F6\u8BFB\u53D6 Pixso \u753B\u5E03\u5F53\u524D\u9009\u4E2D\u7684\u56FE\u5C42\uFF08guid\u3001\u540D\u79F0\u3001\u7C7B\u578B\uFF09\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011\u7528\u6237\u8BF4"\u8FD9\u4E2A/\u5F53\u524D\u9009\u4E2D\u7684"\u56FE\u5C42\u3001\u7EC4\u4EF6\u3002\u5DF2\u7ED9 Pixso \u94FE\u63A5\u65F6\u4E0D\u8981\u7528\u672C\u5DE5\u5177\u2014\u2014\u76F4\u63A5\u4ECE\u94FE\u63A5\u63D0\u53D6 guid\uFF08\u89C1 get_node_dsl\uFF09\u3002\u3010\u8FD4\u56DE\u3011{fileKey, fileName, pageName, selection: [{id, name, type}]}\u3002\u3010\u786C\u6027\u89C4\u5219\u3011\u4E25\u7981\u9690\u5F0F\u4F7F\u7528\u8FD4\u56DE\u7684 guid\uFF1A\u9664\u975E\u7528\u6237\u660E\u786E\u8BF4\u4E86"\u6211\u9009\u4E2D\u7684/\u5F53\u524D\u9009\u4E2D\u7684"\uFF0C\u5FC5\u987B\u5148\u628A\u9009\u4E2D\u56FE\u5C42\u540D\u79F0\u62A5\u7ED9\u7528\u6237\u786E\u8BA4\uFF08"\u5F53\u524D\u9009\u4E2D\u7684\u662F\u300CXX\u300D\uFF0C\u786E\u8BA4\u5904\u7406\u5B83\u5417\uFF1F"\uFF09\uFF0C\u786E\u8BA4\u540E\u624D\u80FD\u4F20\u7ED9\u5176\u4ED6\u5DE5\u5177\u3002\u672C\u5DE5\u5177\u7684 fileKey/sessionId \u53EF\u7F3A\u7701\uFF08\u8DEF\u7531\u5230\u6700\u8FD1\u6D3B\u8DC3\u6587\u4EF6\uFF09\uFF1B\u5176\u4ED6\u5DE5\u5177\u4E24\u8005\u5FC5\u4F20\u5176\u4E00\u3002 ' + ENVELOPE_NOTE,
882
- properties: { fileKey: FILE_KEY_PROP, sessionId: SESSION_ID_PROP }
883
- };
1002
+ var getSelected;
1003
+ var init_get_selected = __esm({
1004
+ "src/agent/tools/get_selected.ts"() {
1005
+ "use strict";
1006
+ init_shared();
1007
+ getSelected = {
1008
+ description: '\u3010\u7528\u9014\u3011\u5B9E\u65F6\u8BFB\u53D6 Pixso \u753B\u5E03\u5F53\u524D\u9009\u4E2D\u7684\u56FE\u5C42\uFF08guid\u3001\u540D\u79F0\u3001\u7C7B\u578B\uFF09\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011\u7528\u6237\u8BF4"\u8FD9\u4E2A/\u5F53\u524D\u9009\u4E2D\u7684"\u56FE\u5C42\u3001\u7EC4\u4EF6\u3002\u5DF2\u7ED9 Pixso \u94FE\u63A5\u65F6\u4E0D\u8981\u7528\u672C\u5DE5\u5177\u2014\u2014\u76F4\u63A5\u4ECE\u94FE\u63A5\u63D0\u53D6 guid\uFF08\u89C1 get_node_dsl\uFF09\u3002\u3010\u8FD4\u56DE\u3011{fileKey, fileName, pageName, selection: [{id, name, type}]}\u3002\u3010\u786C\u6027\u89C4\u5219\u3011\u4E25\u7981\u9690\u5F0F\u4F7F\u7528\u8FD4\u56DE\u7684 guid\uFF1A\u9664\u975E\u7528\u6237\u660E\u786E\u8BF4\u4E86"\u6211\u9009\u4E2D\u7684/\u5F53\u524D\u9009\u4E2D\u7684"\uFF0C\u5FC5\u987B\u5148\u628A\u9009\u4E2D\u56FE\u5C42\u540D\u79F0\u62A5\u7ED9\u7528\u6237\u786E\u8BA4\uFF08"\u5F53\u524D\u9009\u4E2D\u7684\u662F\u300CXX\u300D\uFF0C\u786E\u8BA4\u5904\u7406\u5B83\u5417\uFF1F"\uFF09\uFF0C\u786E\u8BA4\u540E\u624D\u80FD\u4F20\u7ED9\u5176\u4ED6\u5DE5\u5177\u3002\u672C\u5DE5\u5177\u7684 fileKey/sessionId \u53EF\u7F3A\u7701\uFF08\u8DEF\u7531\u5230\u6700\u8FD1\u6D3B\u8DC3\u6587\u4EF6\uFF09\uFF1B\u5176\u4ED6\u5DE5\u5177\u4E24\u8005\u5FC5\u4F20\u5176\u4E00\u3002 ' + ENVELOPE_NOTE,
1009
+ properties: { fileKey: FILE_KEY_PROP, sessionId: SESSION_ID_PROP }
1010
+ };
1011
+ }
1012
+ });
884
1013
 
885
1014
  // src/agent/tools/list_sessions.ts
886
- var listSessions = {
887
- description: "\u3010\u7528\u9014\u3011\u5217\u51FA\u5B58\u6D3B\u7684\u8BBE\u8BA1\u7A3F\u63D2\u4EF6\u4F1A\u8BDD\uFF08sessionId\u3001\u6587\u4EF6\u3001\u9875\u9762\u3001\u9009\u4E2D\u6570\uFF09\u2014\u2014\u6BCF\u6B21\u8C03\u7528\u7ECF ping/pong \u5B9E\u6D4B\uFF0C\u65E0\u54CD\u5E94\u7684\u4E0D\u51FA\u73B0\u3002\u6BCF\u4E2A\u8FD0\u884C\u63D2\u4EF6\u7684\u8BBE\u8BA1\u6587\u4EF6\u5BF9\u5E94\u4E00\u4E2A\u4F1A\u8BDD\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011\u591A\u4E2A Pixso \u6587\u4EF6\u540C\u65F6\u5F00\u63D2\u4EF6\u65F6\uFF0C\u5148\u7528\u672C\u5DE5\u5177\u786E\u8BA4\u76EE\u6807\u6587\u4EF6\uFF1B\u53EA\u6709\u4E00\u4E2A\u4F1A\u8BDD\u65F6\u65E0\u9700\u5173\u6CE8\u672C\u5DE5\u5177\u3002\u3010\u8FD4\u56DE\u3011\u4F1A\u8BDD\u5217\u8868 JSON\u3002\u628A fileKey\uFF08\u8DE8\u91CD\u5F00\u7A33\u5B9A\uFF0C\u4F18\u5148\uFF09\u6216 sessionId \u4F5C\u4E3A\u5176\u4ED6\u5DE5\u5177\u7684\u540C\u540D\u53C2\u6570\u4F20\u5165\u5373\u8DEF\u7531\u5230\u5BF9\u5E94\u6587\u4EF6\uFF08\u8282\u70B9\u7C7B\u5DE5\u5177\u4E8C\u8005\u5FC5\u4F20\u5176\u4E00\uFF1B\u4EC5 get_selected \u4E0E\u672C\u5DE5\u5177\u53EF\u7F3A\u7701\uFF09\u3002\u8BE6\u89C1\u8D44\u6E90 batter://guides/intro\u3002",
888
- properties: {},
889
- handler: async () => {
890
- const list = sessions.list();
891
- if (!list.length) {
892
- return {
893
- content: [{ type: "text", text: "\u5F53\u524D\u6CA1\u6709\u63D2\u4EF6\u8FDE\u63A5\uFF1A\u8BF7\u5728 Pixso \u5BA2\u6237\u7AEF\u4E2D\u8FD0\u884C Batter Pixso Bridge \u63D2\u4EF6\u3002" }]
894
- };
895
- }
896
- const checks = await Promise.all(
897
- list.map(async (info) => info.online && await pingSession(info.sessionId) ? info : null)
898
- );
899
- const alive = checks.filter((c) => c !== null);
900
- for (const info of list) {
901
- if (!alive.includes(info)) sessions.evict(info.sessionId);
902
- }
903
- if (!alive.length) {
904
- return {
905
- content: [{ type: "text", text: "\u5F53\u524D\u6CA1\u6709\u5B58\u6D3B\u7684\u63D2\u4EF6\u8FDE\u63A5\uFF08\u5DF2\u6E05\u7406\u65E0\u54CD\u5E94\u4F1A\u8BDD\uFF09\uFF1A\u8BF7\u5728 Pixso \u5BA2\u6237\u7AEF\u4E2D\u8FD0\u884C Batter Pixso Bridge \u63D2\u4EF6\u3002" }]
906
- };
907
- }
908
- return jsonResult(
909
- alive.map(({ sessionId, fileKey, fileName, pageName, selectionCount }) => ({
910
- sessionId,
911
- fileKey,
912
- fileName,
913
- pageName,
914
- selectionCount
915
- }))
916
- );
1015
+ var listSessions;
1016
+ var init_list_sessions = __esm({
1017
+ "src/agent/tools/list_sessions.ts"() {
1018
+ "use strict";
1019
+ init_sessions();
1020
+ init_shared();
1021
+ listSessions = {
1022
+ description: "\u3010\u7528\u9014\u3011\u5217\u51FA\u5B58\u6D3B\u7684\u8BBE\u8BA1\u7A3F\u63D2\u4EF6\u4F1A\u8BDD\uFF08sessionId\u3001\u6587\u4EF6\u3001\u9875\u9762\u3001\u9009\u4E2D\u6570\uFF09\u2014\u2014\u6BCF\u6B21\u8C03\u7528\u7ECF ping/pong \u5B9E\u6D4B\uFF0C\u65E0\u54CD\u5E94\u7684\u4E0D\u51FA\u73B0\u3002\u6BCF\u4E2A\u8FD0\u884C\u63D2\u4EF6\u7684\u8BBE\u8BA1\u6587\u4EF6\u5BF9\u5E94\u4E00\u4E2A\u4F1A\u8BDD\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011\u591A\u4E2A Pixso \u6587\u4EF6\u540C\u65F6\u5F00\u63D2\u4EF6\u65F6\uFF0C\u5148\u7528\u672C\u5DE5\u5177\u786E\u8BA4\u76EE\u6807\u6587\u4EF6\uFF1B\u53EA\u6709\u4E00\u4E2A\u4F1A\u8BDD\u65F6\u65E0\u9700\u5173\u6CE8\u672C\u5DE5\u5177\u3002\u3010\u8FD4\u56DE\u3011\u4F1A\u8BDD\u5217\u8868 JSON\u3002\u628A fileKey\uFF08\u8DE8\u91CD\u5F00\u7A33\u5B9A\uFF0C\u4F18\u5148\uFF09\u6216 sessionId \u4F5C\u4E3A\u5176\u4ED6\u5DE5\u5177\u7684\u540C\u540D\u53C2\u6570\u4F20\u5165\u5373\u8DEF\u7531\u5230\u5BF9\u5E94\u6587\u4EF6\uFF08\u8282\u70B9\u7C7B\u5DE5\u5177\u4E8C\u8005\u5FC5\u4F20\u5176\u4E00\uFF1B\u4EC5 get_selected \u4E0E\u672C\u5DE5\u5177\u53EF\u7F3A\u7701\uFF09\u3002\u8BE6\u89C1\u8D44\u6E90 batter://guides/intro\u3002",
1023
+ properties: {},
1024
+ handler: async () => {
1025
+ const list = sessions.list();
1026
+ if (!list.length) {
1027
+ return {
1028
+ content: [{ type: "text", text: "\u5F53\u524D\u6CA1\u6709\u63D2\u4EF6\u8FDE\u63A5\uFF1A\u8BF7\u5728 Pixso \u5BA2\u6237\u7AEF\u4E2D\u8FD0\u884C Batter Pixso Bridge \u63D2\u4EF6\u3002" }]
1029
+ };
1030
+ }
1031
+ const checks = await Promise.all(
1032
+ list.map(async (info) => info.online && await pingSession(info.sessionId) ? info : null)
1033
+ );
1034
+ const alive = checks.filter((c) => c !== null);
1035
+ for (const info of list) {
1036
+ if (!alive.includes(info)) sessions.evict(info.sessionId);
1037
+ }
1038
+ if (!alive.length) {
1039
+ return {
1040
+ content: [{ type: "text", text: "\u5F53\u524D\u6CA1\u6709\u5B58\u6D3B\u7684\u63D2\u4EF6\u8FDE\u63A5\uFF08\u5DF2\u6E05\u7406\u65E0\u54CD\u5E94\u4F1A\u8BDD\uFF09\uFF1A\u8BF7\u5728 Pixso \u5BA2\u6237\u7AEF\u4E2D\u8FD0\u884C Batter Pixso Bridge \u63D2\u4EF6\u3002" }]
1041
+ };
1042
+ }
1043
+ return jsonResult(
1044
+ alive.map(({ sessionId, fileKey, fileName, pageName, selectionCount }) => ({
1045
+ sessionId,
1046
+ fileKey,
1047
+ fileName,
1048
+ pageName,
1049
+ selectionCount
1050
+ }))
1051
+ );
1052
+ }
1053
+ };
917
1054
  }
918
- };
1055
+ });
919
1056
 
920
1057
  // src/agent/tools/index.ts
921
- var TOOL_REGISTRY = {
922
- get_selected: getSelected,
923
- get_node_dsl: getNodeDsl,
924
- get_metadata: getMetadata,
925
- export_image: exportImage,
926
- get_screenshot: getScreenshot,
927
- list_sessions: listSessions
928
- };
929
1058
  function buildInputSchema(requires, properties) {
930
1059
  const singles = requires.filter((r) => typeof r === "string");
931
1060
  const xorGroups = requires.filter((r) => Array.isArray(r));
@@ -951,26 +1080,54 @@ function validateArgs(requires, args) {
951
1080
  }
952
1081
  return null;
953
1082
  }
954
- var TOOLS = Object.fromEntries(
955
- Object.entries(TOOL_REGISTRY).map(([name, def]) => {
956
- const requires = def.requires ?? [];
957
- const inputSchema = buildInputSchema(requires, def.properties);
958
- const base = def.handler ?? ((args, ctx) => run(name, args, ctx?.signal));
959
- const handler = (args, ctx) => {
960
- const err = requires.length ? validateArgs(requires, args) : null;
961
- if (err) return Promise.resolve(errorResult(err));
962
- return base(args, ctx);
1083
+ var TOOL_REGISTRY, TOOLS;
1084
+ var init_tools = __esm({
1085
+ "src/agent/tools/index.ts"() {
1086
+ "use strict";
1087
+ init_export_image();
1088
+ init_get_metadata();
1089
+ init_get_node_dsl();
1090
+ init_get_screenshot();
1091
+ init_get_selected();
1092
+ init_list_sessions();
1093
+ init_shared();
1094
+ TOOL_REGISTRY = {
1095
+ get_selected: getSelected,
1096
+ get_node_dsl: getNodeDsl,
1097
+ get_metadata: getMetadata,
1098
+ export_image: exportImage,
1099
+ get_screenshot: getScreenshot,
1100
+ list_sessions: listSessions
963
1101
  };
964
- const resolvedDef = { description: def.description, inputSchema, handler };
965
- return [name, { def: resolvedDef, tool: { name, description: def.description, inputSchema } }];
966
- })
967
- );
1102
+ TOOLS = Object.fromEntries(
1103
+ Object.entries(TOOL_REGISTRY).map(([name, def]) => {
1104
+ const requires = def.requires ?? [];
1105
+ const inputSchema = buildInputSchema(requires, def.properties);
1106
+ const base = def.handler ?? ((args, ctx) => run(name, args, ctx?.signal));
1107
+ const handler = (args, ctx) => {
1108
+ const err = requires.length ? validateArgs(requires, args) : null;
1109
+ if (err) return Promise.resolve(errorResult(err));
1110
+ return base(args, ctx);
1111
+ };
1112
+ const resolvedDef = { description: def.description, inputSchema, handler };
1113
+ return [name, { def: resolvedDef, tool: { name, description: def.description, inputSchema } }];
1114
+ })
1115
+ );
1116
+ }
1117
+ });
968
1118
 
969
1119
  // src/agent/mcp.ts
970
- var INSTRUCTIONS = `Pixso \u8BBE\u8BA1\u7A3F\u8F6C\u4EE3\u7801\u670D\u52A1\u3002\u6309\u7528\u6237\u8F93\u5165\u8DEF\u7531\uFF1A1. \u7ED9\u51FA Pixso \u94FE\u63A5\uFF08\u4EFB\u610F\u57DF\u540D\uFF09\u2192 \u4ECE item-id \u63D0\u53D6 guid\uFF08URL \u89E3\u7801\uFF09\uFF0C\u76F4\u63A5\u8C03 get_node_dsl / get_screenshot / export_image\uFF1B2. \u8BF4"\u6211\u9009\u4E2D\u7684/\u5F53\u524D\u9009\u4E2D\u7684"\u2192 get_selected \u62FF guid\uFF1B\u5176\u4ED6\u4F9D\u8D56\u9009\u533A\u7684\u573A\u666F\u5148 get_selected \u518D\u5411\u7528\u6237\u62A5\u540D\u79F0\u786E\u8BA4\uFF1B3. "\u6837\u5F0F\u8FD8\u539F/\u8BBE\u8BA1\u8FD8\u539F/UI \u8FD8\u539F/\u8BBE\u8BA1\u8F6C\u4EE3\u7801"\u2192 get_node_dsl \u53D6\u7ED3\u6784 + get_screenshot \u53D6\u89C6\u89C9\u53C2\u7167\uFF1B4. "\u5207\u56FE/\u5BFC\u51FA\u56FE\u6807\u6216\u56FE\u7247\u7D20\u6750"\u2192 export_image\uFF08DSL \u91CC\u5DF2\u5E26 img \u7684\u8282\u70B9\u76F4\u63A5\u7528\u5176\u6587\u4EF6\u540D\uFF0C\u4E0D\u8981\u91CD\u590D\u5BFC\u51FA\uFF09\uFF1B5. \u9996\u6B21\u63A5\u89E6\u8BBE\u8BA1\u7A3F/\u53EA\u8981\u7ED3\u6784\u6982\u89C8 \u2192 get_metadata \u62FF\u5168\u6811\u9AA8\u67B6\uFF08\u65E0\u6837\u5F0F\u65E0\u8D44\u6E90\uFF0C\u4F53\u79EF\u6781\u5C0F\uFF09\u5B9A\u4F4D\u76EE\u6807\u8282\u70B9\uFF1B\u7ED3\u6784\u590D\u6742 \u2192 get_node_dsl \u7528 maxDepth \u5206\u6BB5\u4E0B\u63A2\uFF08\u622A\u65AD\u5904 more \u6807\u8BB0 + kidsSummary \u6D45\u9884\u89C8\uFF0C\u6309 id \u4E0B\u94BB\uFF09\uFF1B6. \u751F\u6210\u540E\u5BF9\u6BD4\u8FD8\u539F\u5EA6 \u2192 get_screenshot \u6821\u9A8C\u5FAA\u73AF\uFF1B7. \u591A\u4E2A Pixso \u6587\u4EF6\u540C\u65F6\u6253\u5F00 \u2192 list_sessions + fileKey/sessionId \u53C2\u6570\u8DEF\u7531\uFF08\u8282\u70B9\u7C7B\u5DE5\u5177\u4E8C\u8005\u5FC5\u4F20\u5176\u4E00\uFF09\u3002\u786C\u6027\u89C4\u5219\uFF08\u8D44\u6E90\u53D6\u7528/\u7EC4\u4EF6\u6620\u5C04/\u9519\u8BEF\u6062\u590D/\u7981\u6B62\u622A\u56FE\u786C\u7F16\u7801\uFF09\u89C1\u8D44\u6E90 batter://guides/design-to-code\u2014\u2014\u5199\u4EE3\u7801\u524D\u5FC5\u8BFB\u3002`;
1120
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1121
+ import {
1122
+ CallToolRequestSchema,
1123
+ GetPromptRequestSchema,
1124
+ ListPromptsRequestSchema,
1125
+ ListResourceTemplatesRequestSchema,
1126
+ ListToolsRequestSchema
1127
+ } from "@modelcontextprotocol/sdk/types.js";
971
1128
  function createMcpServer() {
972
1129
  const server = new McpServer(
973
- { name: HUB_NAME, version: VERSION },
1130
+ { name: SERVICE_NAME, version: VERSION },
974
1131
  { capabilities: { tools: {}, resources: {}, prompts: {} }, instructions: INSTRUCTIONS }
975
1132
  );
976
1133
  for (const guide of GUIDES) {
@@ -1019,9 +1176,19 @@ function createMcpServer() {
1019
1176
  });
1020
1177
  return server;
1021
1178
  }
1179
+ var INSTRUCTIONS;
1180
+ var init_mcp = __esm({
1181
+ "src/agent/mcp.ts"() {
1182
+ "use strict";
1183
+ init_config();
1184
+ init_guides();
1185
+ init_prompts();
1186
+ init_tools();
1187
+ INSTRUCTIONS = `Pixso \u8BBE\u8BA1\u7A3F\u8F6C\u4EE3\u7801\u670D\u52A1\u3002\u6309\u7528\u6237\u8F93\u5165\u8DEF\u7531\uFF1A1. \u7ED9\u51FA Pixso \u94FE\u63A5\uFF08\u4EFB\u610F\u57DF\u540D\uFF09\u2192 \u4ECE item-id \u63D0\u53D6 guid\uFF08URL \u89E3\u7801\uFF09\uFF0C\u76F4\u63A5\u8C03 get_node_dsl / get_screenshot / export_image\uFF1B2. \u8BF4"\u6211\u9009\u4E2D\u7684/\u5F53\u524D\u9009\u4E2D\u7684"\u2192 get_selected \u62FF guid\uFF1B\u5176\u4ED6\u4F9D\u8D56\u9009\u533A\u7684\u573A\u666F\u5148 get_selected \u518D\u5411\u7528\u6237\u62A5\u540D\u79F0\u786E\u8BA4\uFF1B3. "\u6837\u5F0F\u8FD8\u539F/\u8BBE\u8BA1\u8FD8\u539F/UI \u8FD8\u539F/\u8BBE\u8BA1\u8F6C\u4EE3\u7801"\u2192 get_node_dsl \u53D6\u7ED3\u6784 + get_screenshot \u53D6\u89C6\u89C9\u53C2\u7167\uFF1B4. "\u5207\u56FE/\u5BFC\u51FA\u56FE\u6807\u6216\u56FE\u7247\u7D20\u6750"\u2192 export_image\uFF08DSL \u91CC\u5DF2\u5E26 img \u7684\u8282\u70B9\u76F4\u63A5\u7528\u5176\u6587\u4EF6\u540D\uFF0C\u4E0D\u8981\u91CD\u590D\u5BFC\u51FA\uFF09\uFF1B5. \u9996\u6B21\u63A5\u89E6\u8BBE\u8BA1\u7A3F/\u53EA\u8981\u7ED3\u6784\u6982\u89C8 \u2192 get_metadata \u62FF\u5168\u6811\u9AA8\u67B6\uFF08\u65E0\u6837\u5F0F\u65E0\u8D44\u6E90\uFF0C\u4F53\u79EF\u6781\u5C0F\uFF09\u5B9A\u4F4D\u76EE\u6807\u8282\u70B9\uFF1B\u7ED3\u6784\u590D\u6742 \u2192 get_node_dsl \u7528 maxDepth \u5206\u6BB5\u4E0B\u63A2\uFF08\u622A\u65AD\u5904 more \u6807\u8BB0 + kidsSummary \u6D45\u9884\u89C8\uFF0C\u6309 id \u4E0B\u94BB\uFF09\uFF1B6. \u751F\u6210\u540E\u5BF9\u6BD4\u8FD8\u539F\u5EA6 \u2192 get_screenshot \u6821\u9A8C\u5FAA\u73AF\uFF1B7. \u591A\u4E2A Pixso \u6587\u4EF6\u540C\u65F6\u6253\u5F00 \u2192 list_sessions + fileKey/sessionId \u53C2\u6570\u8DEF\u7531\uFF08\u8282\u70B9\u7C7B\u5DE5\u5177\u4E8C\u8005\u5FC5\u4F20\u5176\u4E00\uFF09\u3002\u786C\u6027\u89C4\u5219\uFF08\u8D44\u6E90\u53D6\u7528/\u7EC4\u4EF6\u6620\u5C04/\u9519\u8BEF\u6062\u590D/\u7981\u6B62\u622A\u56FE\u786C\u7F16\u7801\uFF09\u89C1\u8D44\u6E90 batter://guides/design-to-code\u2014\u2014\u5199\u4EE3\u7801\u524D\u5FC5\u8BFB\u3002`;
1188
+ }
1189
+ });
1022
1190
 
1023
1191
  // src/agent/sessions.ts
1024
- var transports = /* @__PURE__ */ new Map();
1025
1192
  function getMcpTransport(sessionId) {
1026
1193
  const entry = transports.get(sessionId);
1027
1194
  if (entry) entry.lastSeen = Date.now();
@@ -1034,19 +1201,30 @@ function registerMcpTransport(sessionId, transport) {
1034
1201
  if (transport.sessionId) transports.delete(transport.sessionId);
1035
1202
  };
1036
1203
  }
1037
- var sweeper = setInterval(() => {
1038
- const now = Date.now();
1039
- for (const [id, entry] of transports) {
1040
- if (now - entry.lastSeen <= config.mcpSessionTtlMs) continue;
1041
- transports.delete(id);
1042
- void entry.transport.close().catch(() => {
1043
- });
1044
- log(`MCP HTTP session expired: ${id.slice(0, 8)} (total ${transports.size})`);
1204
+ var transports, sweeper;
1205
+ var init_sessions2 = __esm({
1206
+ "src/agent/sessions.ts"() {
1207
+ "use strict";
1208
+ init_config();
1209
+ transports = /* @__PURE__ */ new Map();
1210
+ sweeper = setInterval(() => {
1211
+ const now = Date.now();
1212
+ for (const [id, entry] of transports) {
1213
+ if (now - entry.lastSeen <= config.mcpSessionTtlMs) continue;
1214
+ transports.delete(id);
1215
+ void entry.transport.close().catch(() => {
1216
+ });
1217
+ log(`MCP HTTP session expired: ${id.slice(0, 8)} (total ${transports.size})`);
1218
+ }
1219
+ }, MCP_SESSION_SWEEP_MS);
1220
+ sweeper.unref();
1045
1221
  }
1046
- }, 6e4);
1047
- sweeper.unref();
1222
+ });
1048
1223
 
1049
1224
  // src/agent/http.ts
1225
+ import { randomUUID as randomUUID2 } from "node:crypto";
1226
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
1227
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
1050
1228
  function handleMcp(req, res, url) {
1051
1229
  return dispatchRoute(MCP_ROUTES, req, res, url);
1052
1230
  }
@@ -1059,95 +1237,54 @@ async function sessionRequestHandler({ req, res }) {
1059
1237
  }
1060
1238
  await transport.handleRequest(req, res);
1061
1239
  }
1062
- var MCP_ROUTES = [
1063
- {
1064
- method: "POST",
1065
- path: "/mcp",
1066
- handler: async ({ req, res }) => {
1067
- let body;
1068
- try {
1069
- body = await readJsonBody(req);
1070
- } catch {
1071
- sendJson(res, 400, { jsonrpc: "2.0", error: { code: -32700, message: "Parse error" }, id: null });
1072
- return;
1073
- }
1074
- const sessionId = req.headers["mcp-session-id"];
1075
- let transport = sessionId ? getMcpTransport(sessionId) : void 0;
1076
- if (!transport) {
1077
- if (!sessionId && isInitializeRequest(body)) {
1078
- const t = new StreamableHTTPServerTransport({
1079
- sessionIdGenerator: () => randomUUID2(),
1080
- onsessioninitialized: (id) => registerMcpTransport(id, t)
1081
- });
1082
- await createMcpServer().connect(t);
1083
- transport = t;
1084
- } else {
1085
- sendJson(res, 400, {
1086
- jsonrpc: "2.0",
1087
- error: { code: -32e3, message: "Bad Request: missing or unknown MCP session" },
1088
- id: null
1089
- });
1090
- return;
1240
+ var MCP_ROUTES;
1241
+ var init_http = __esm({
1242
+ "src/agent/http.ts"() {
1243
+ "use strict";
1244
+ init_http_body();
1245
+ init_mcp();
1246
+ init_sessions2();
1247
+ MCP_ROUTES = [
1248
+ {
1249
+ method: "POST",
1250
+ path: "/mcp",
1251
+ handler: async ({ req, res }) => {
1252
+ let body;
1253
+ try {
1254
+ body = await readJsonBody(req);
1255
+ } catch {
1256
+ sendJson(res, 400, { jsonrpc: "2.0", error: { code: -32700, message: "Parse error" }, id: null });
1257
+ return;
1258
+ }
1259
+ const sessionId = req.headers["mcp-session-id"];
1260
+ let transport = sessionId ? getMcpTransport(sessionId) : void 0;
1261
+ if (!transport) {
1262
+ if (!sessionId && isInitializeRequest(body)) {
1263
+ const t = new StreamableHTTPServerTransport({
1264
+ sessionIdGenerator: () => randomUUID2(),
1265
+ onsessioninitialized: (id) => registerMcpTransport(id, t)
1266
+ });
1267
+ await createMcpServer().connect(t);
1268
+ transport = t;
1269
+ } else {
1270
+ sendJson(res, 400, {
1271
+ jsonrpc: "2.0",
1272
+ error: { code: -32e3, message: "Bad Request: missing or unknown MCP session" },
1273
+ id: null
1274
+ });
1275
+ return;
1276
+ }
1277
+ }
1278
+ await transport.handleRequest(req, res, body);
1091
1279
  }
1092
- }
1093
- await transport.handleRequest(req, res, body);
1094
- }
1095
- },
1096
- { method: "GET", path: "/mcp", handler: sessionRequestHandler },
1097
- { method: "DELETE", path: "/mcp", handler: sessionRequestHandler }
1098
- ];
1099
-
1100
- // src/install/dist-resolver.ts
1101
- import { existsSync } from "node:fs";
1102
- import { readFile } from "node:fs/promises";
1103
- import { join as join4 } from "node:path";
1104
- import { fileURLToPath as fileURLToPath2 } from "node:url";
1105
- function resolvePluginDir() {
1106
- const candidates = [
1107
- fileURLToPath2(new URL("./plugin/", import.meta.url)),
1108
- // bundle: dist/index.js → dist/plugin/
1109
- fileURLToPath2(new URL("../../plugin/dist/", import.meta.url)),
1110
- // bundle 开发: dist/index.js → packages/plugin/dist/
1111
- fileURLToPath2(new URL("../../../plugin/dist/", import.meta.url))
1112
- // tsx 开发: src/install/ → packages/plugin/dist/
1113
- ];
1114
- return candidates.find((p) => existsSync(join4(p, "manifest.json"))) ?? null;
1115
- }
1116
- var resolved = null;
1117
- function pluginDir() {
1118
- resolved ??= resolvePluginDir();
1119
- return resolved;
1120
- }
1121
- function hasPluginDist() {
1122
- return pluginDir() !== null;
1123
- }
1124
- async function pluginManifest() {
1125
- const dir = pluginDir();
1126
- if (!dir) return null;
1127
- const raw = JSON.parse(await readFile(join4(dir, "manifest.json"), "utf8"));
1128
- raw.networkAccess = {
1129
- allowedDomains: [`http://localhost:${config.httpPort}`, `http://127.0.0.1:${config.httpPort}`]
1130
- };
1131
- return Buffer.from(JSON.stringify(raw, null, 2));
1132
- }
1133
- async function readPluginFile(name) {
1134
- const dir = pluginDir();
1135
- if (!dir) return null;
1136
- try {
1137
- return await readFile(join4(dir, name));
1138
- } catch {
1139
- return null;
1280
+ },
1281
+ { method: "GET", path: "/mcp", handler: sessionRequestHandler },
1282
+ { method: "DELETE", path: "/mcp", handler: sessionRequestHandler }
1283
+ ];
1140
1284
  }
1141
- }
1285
+ });
1142
1286
 
1143
1287
  // src/install/template.ts
1144
- var HTML_ESCAPES = {
1145
- "&": "&amp;",
1146
- "<": "&lt;",
1147
- ">": "&gt;",
1148
- '"': "&quot;",
1149
- "'": "&#39;"
1150
- };
1151
1288
  function escapeHtml(s) {
1152
1289
  return s.replace(/[&<>"']/g, (c) => HTML_ESCAPES[c]);
1153
1290
  }
@@ -1162,12 +1299,33 @@ function renderTemplate(tpl, vars, escape = escapeHtml) {
1162
1299
  };
1163
1300
  return tpl.replace(/\{\{\{\s*(\w+)\s*\}\}\}/g, (_, key) => lookup(key)).replace(/\{\{\s*(\w+)\s*\}\}/g, (_, key) => escape(lookup(key)));
1164
1301
  }
1302
+ var HTML_ESCAPES;
1303
+ var init_template = __esm({
1304
+ "src/install/template.ts"() {
1305
+ "use strict";
1306
+ HTML_ESCAPES = {
1307
+ "&": "&amp;",
1308
+ "<": "&lt;",
1309
+ ">": "&gt;",
1310
+ '"': "&quot;",
1311
+ "'": "&#39;"
1312
+ };
1313
+ }
1314
+ });
1165
1315
 
1166
1316
  // src/install/templates/bookmarklet.txt
1167
- var bookmarklet_default = '// Batter Bridge \u4E66\u7B7E\u6CE8\u5165\u811A\u672C\n// \u5728 Pixso / MasterGo \u7F51\u9875\u7248\u8BBE\u8BA1\u6587\u4EF6\u9875\u9762\u70B9\u51FB\u540E\u6267\u884C\uFF1A\n// \u4ECE\u672C\u670D\u52A1\u62C9\u53D6 manifest + code\uFF0C\u901A\u8FC7\u5BBF\u4E3B\u6CE8\u5165 API \u8FD0\u884C\u63D2\u4EF6\u3002\n// \u670D\u52A1\u4E0D\u53EF\u8FBE\u65F6\u56DE\u9000 localStorage \u7F13\u5B58\uFF08\u4E0A\u6B21\u6210\u529F\u62C9\u53D6\u7684\u63D2\u4EF6\u5305\uFF09\u3002\n// \u6E32\u67D3\u65F6 {{ base }} \u66FF\u6362\u4E3A\u670D\u52A1\u5730\u5740\uFF08JS \u5B57\u7B26\u4E32\u8F6C\u4E49\uFF09\uFF0C\u6574\u4F53 encodeURIComponent \u540E\u4F5C\u4E3A\u4E66\u7B7E href\u3002\n(function () {\n var SERVER_BASE = "{{ base }}";\n var CACHE_KEY = "batter-bridge-cache";\n\n /** \u7528\u5BBF\u4E3B\u9875\u9762\u7684\u6CE8\u5165 API \u542F\u52A8\u63D2\u4EF6 */\n function startPlugin(codeText, manifestText) {\n try {\n var manifest = JSON.parse(manifestText);\n if (window._FicAppObj && window._FicAppObj.runTestPlugin) {\n // MasterGo \u5BBF\u4E3B\n window._FicAppObj.runTestPlugin(manifest, codeText, "", null, { startup: "DEBUG" });\n return;\n }\n if (window.runNewPlugin) {\n // Pixso \u5BBF\u4E3B\n window.runNewPlugin(codeText, null, null, { manifest: manifest, plugin: manifest });\n return;\n }\n alert("\u8BF7\u5728 Pixso / MasterGo \u8BBE\u8BA1\u6587\u4EF6\u9875\u9762\u70B9\u51FB\u6B64\u4E66\u7B7E");\n } catch (err) {\n alert("\u63D2\u4EF6\u542F\u52A8\u5931\u8D25\uFF1A" + ((err && err.message) || err));\n }\n }\n\n /** \u5C1D\u8BD5\u7528\u7F13\u5B58\u7684\u63D2\u4EF6\u5305\u542F\u52A8\uFF1B\u65E0\u7F13\u5B58\u8FD4\u56DE false */\n function startFromCache() {\n try {\n var cached = JSON.parse(localStorage.getItem(CACHE_KEY) || "null");\n if (cached && cached.manifest && cached.code) {\n startPlugin(cached.code, cached.manifest);\n return true;\n }\n } catch (err) {\n // \u7F13\u5B58\u635F\u574F\uFF1A\u89C6\u4E3A\u65E0\u7F13\u5B58\n }\n return false;\n }\n\n function saveCache(manifestText, codeText) {\n try {\n localStorage.setItem(CACHE_KEY, JSON.stringify({ manifest: manifestText, code: codeText }));\n } catch (err) {\n // \u5B58\u50A8\u8D85\u9650\u7B49\uFF1A\u7F13\u5B58\u53EA\u662F\u79BB\u7EBF\u515C\u5E95\uFF0C\u5931\u8D25\u53EF\u5FFD\u7565\n }\n }\n\n function fetchText(path) {\n return fetch(SERVER_BASE + path + "?t=" + Date.now(), { cache: "no-store" }).then(\n function (res) {\n if (!res.ok) throw new Error("HTTP " + res.status);\n return res.text();\n },\n );\n }\n\n Promise.all([fetchText("manifest.json"), fetchText("code.js")])\n .then(function (texts) {\n var manifestText = texts[0];\n var codeText = texts[1];\n saveCache(manifestText, codeText);\n startPlugin(codeText, manifestText);\n })\n .catch(function () {\n if (!startFromCache()) {\n alert("\u65E0\u6CD5\u8FDE\u63A5 batter-pixso-mcp \u670D\u52A1\uFF08" + SERVER_BASE + "\uFF09\uFF0C\u8BF7\u786E\u8BA4\u670D\u52A1\u5DF2\u542F\u52A8");\n }\n });\n})();\n';
1317
+ var bookmarklet_default;
1318
+ var init_bookmarklet = __esm({
1319
+ "src/install/templates/bookmarklet.txt"() {
1320
+ bookmarklet_default = '// Batter Bridge \u4E66\u7B7E\u6CE8\u5165\u811A\u672C\n// \u5728 Pixso / MasterGo \u7F51\u9875\u7248\u8BBE\u8BA1\u6587\u4EF6\u9875\u9762\u70B9\u51FB\u540E\u6267\u884C\uFF1A\n// \u4ECE\u672C\u670D\u52A1\u62C9\u53D6 manifest + code\uFF0C\u901A\u8FC7\u5BBF\u4E3B\u6CE8\u5165 API \u8FD0\u884C\u63D2\u4EF6\u3002\n// \u670D\u52A1\u4E0D\u53EF\u8FBE\u65F6\u56DE\u9000 localStorage \u7F13\u5B58\uFF08\u4E0A\u6B21\u6210\u529F\u62C9\u53D6\u7684\u63D2\u4EF6\u5305\uFF09\u3002\n// \u6E32\u67D3\u65F6 {{ base }} \u66FF\u6362\u4E3A\u670D\u52A1\u5730\u5740\uFF08JS \u5B57\u7B26\u4E32\u8F6C\u4E49\uFF09\uFF0C\u6574\u4F53 encodeURIComponent \u540E\u4F5C\u4E3A\u4E66\u7B7E href\u3002\n(function () {\n var SERVER_BASE = "{{ base }}";\n var CACHE_KEY = "batter-bridge-cache";\n\n /** \u7528\u5BBF\u4E3B\u9875\u9762\u7684\u6CE8\u5165 API \u542F\u52A8\u63D2\u4EF6 */\n function startPlugin(codeText, manifestText) {\n try {\n var manifest = JSON.parse(manifestText);\n if (window._FicAppObj && window._FicAppObj.runTestPlugin) {\n // MasterGo \u5BBF\u4E3B\n window._FicAppObj.runTestPlugin(manifest, codeText, "", null, { startup: "DEBUG" });\n return;\n }\n if (window.runNewPlugin) {\n // Pixso \u5BBF\u4E3B\n window.runNewPlugin(codeText, null, null, { manifest: manifest, plugin: manifest });\n return;\n }\n alert("\u8BF7\u5728 Pixso / MasterGo \u8BBE\u8BA1\u6587\u4EF6\u9875\u9762\u70B9\u51FB\u6B64\u4E66\u7B7E");\n } catch (err) {\n alert("\u63D2\u4EF6\u542F\u52A8\u5931\u8D25\uFF1A" + ((err && err.message) || err));\n }\n }\n\n /** \u5C1D\u8BD5\u7528\u7F13\u5B58\u7684\u63D2\u4EF6\u5305\u542F\u52A8\uFF1B\u65E0\u7F13\u5B58\u8FD4\u56DE false */\n function startFromCache() {\n try {\n var cached = JSON.parse(localStorage.getItem(CACHE_KEY) || "null");\n if (cached && cached.manifest && cached.code) {\n startPlugin(cached.code, cached.manifest);\n return true;\n }\n } catch (err) {\n // \u7F13\u5B58\u635F\u574F\uFF1A\u89C6\u4E3A\u65E0\u7F13\u5B58\n }\n return false;\n }\n\n function saveCache(manifestText, codeText) {\n try {\n localStorage.setItem(CACHE_KEY, JSON.stringify({ manifest: manifestText, code: codeText }));\n } catch (err) {\n // \u5B58\u50A8\u8D85\u9650\u7B49\uFF1A\u7F13\u5B58\u53EA\u662F\u79BB\u7EBF\u515C\u5E95\uFF0C\u5931\u8D25\u53EF\u5FFD\u7565\n }\n }\n\n function fetchText(path) {\n return fetch(SERVER_BASE + path + "?t=" + Date.now(), { cache: "no-store" }).then(\n function (res) {\n if (!res.ok) throw new Error("HTTP " + res.status);\n return res.text();\n },\n );\n }\n\n Promise.all([fetchText("manifest.json"), fetchText("code.js")])\n .then(function (texts) {\n var manifestText = texts[0];\n var codeText = texts[1];\n saveCache(manifestText, codeText);\n startPlugin(codeText, manifestText);\n })\n .catch(function () {\n if (!startFromCache()) {\n alert("\u65E0\u6CD5\u8FDE\u63A5 batter-pixso-mcp \u670D\u52A1\uFF08" + SERVER_BASE + "\uFF09\uFF0C\u8BF7\u786E\u8BA4\u670D\u52A1\u5DF2\u542F\u52A8");\n }\n });\n})();\n';
1321
+ }
1322
+ });
1168
1323
 
1169
1324
  // src/install/templates/install-page.html
1170
- var install_page_default = `<!DOCTYPE html>
1325
+ var install_page_default;
1326
+ var init_install_page = __esm({
1327
+ "src/install/templates/install-page.html"() {
1328
+ install_page_default = `<!DOCTYPE html>
1171
1329
  <html lang="zh-CN">
1172
1330
  <head>
1173
1331
  <meta charset="utf-8">
@@ -1500,15 +1658,17 @@ var install_page_default = `<!DOCTYPE html>
1500
1658
  <pre id="cfg">{
1501
1659
  "mcpServers": {
1502
1660
  "batter-pixso": {
1503
- "command": "npx",
1504
- "args": ["-y", "batter-pixso-mcp"],
1505
- "env": { "HTTP_PORT": "{{ httpPort }}" }
1661
+ "url": "{{ mcpUrl }}"
1506
1662
  }
1507
1663
  }
1508
1664
  }</pre>
1509
1665
  <button class="copy" id="copy" type="button">\u590D\u5236</button>
1510
1666
  </div>
1511
- <p class="note">\u7C98\u8D34\u5230 Cursor / Claude Code \u7684 MCP \u914D\u7F6E\u6587\u4EF6\u3002\u7CFB\u7EDF Node \u4F4E\u4E8E 18 \u65F6\u6539\u7528 volta \u5305\u88F9\u542F\u52A8\uFF0C\u8BE6\u89C1 README\u3002</p>
1667
+ <p class="note">
1668
+ \u7C98\u8D34\u5230 Cursor / Cherry Studio \u7B49\u7684 MCP \u914D\u7F6E\u6587\u4EF6\uFF1BClaude Code \u6267\u884C
1669
+ <code>{{ claudeAddCommand }}</code>\u3002
1670
+ \u670D\u52A1\u672A\u542F\u52A8\u65F6\u5148\u5728\u7EC8\u7AEF\u6267\u884C <code>npx batter-pixso-mcp</code>\uFF08Windows \u53EF\u7528 <code>--setup</code> \u6CE8\u518C\u5F00\u673A\u81EA\u542F\uFF09\u3002
1671
+ </p>
1512
1672
  </div>
1513
1673
  </div>
1514
1674
 
@@ -1560,6 +1720,7 @@ copyBtn.addEventListener("click", function () {
1560
1720
  if (navigator.clipboard && navigator.clipboard.writeText) {
1561
1721
  navigator.clipboard.writeText(text).then(done, done);
1562
1722
  } else {
1723
+ // \u515C\u5E95\uFF1A\u65E0 clipboard API \u7684\u73AF\u5883\uFF08\u53CC\u6838\u6D4F\u89C8\u5668\u517C\u5BB9\u6A21\u5F0F\u7B49\uFF09
1563
1724
  var ta = document.createElement("textarea");
1564
1725
  ta.value = text;
1565
1726
  document.body.appendChild(ta);
@@ -1573,6 +1734,8 @@ copyBtn.addEventListener("click", function () {
1573
1734
  </body>
1574
1735
  </html>
1575
1736
  `;
1737
+ }
1738
+ });
1576
1739
 
1577
1740
  // src/install/page.ts
1578
1741
  function bookmarkletHref(base) {
@@ -1580,26 +1743,27 @@ function bookmarkletHref(base) {
1580
1743
  return `javascript:${encodeURIComponent(source)}`;
1581
1744
  }
1582
1745
  function renderInstallPage() {
1583
- const base = `${hubBaseUrl()}/`;
1746
+ const base = `${serviceBaseUrl()}/`;
1584
1747
  return renderTemplate(install_page_default, {
1585
1748
  version: VERSION,
1586
1749
  href: bookmarkletHref(base),
1587
1750
  base,
1588
- httpPort: config.httpPort
1751
+ mcpUrl: mcpUrl(),
1752
+ claudeAddCommand: claudeAddCommand()
1589
1753
  });
1590
1754
  }
1755
+ var init_page = __esm({
1756
+ "src/install/page.ts"() {
1757
+ "use strict";
1758
+ init_config();
1759
+ init_template();
1760
+ init_bookmarklet();
1761
+ init_install_page();
1762
+ }
1763
+ });
1591
1764
 
1592
1765
  // src/install/zip.ts
1593
1766
  import { Buffer as Buffer2 } from "node:buffer";
1594
- var CRC_TABLE = (() => {
1595
- const table = new Uint32Array(256);
1596
- for (let n = 0; n < 256; n++) {
1597
- let c = n;
1598
- for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
1599
- table[n] = c >>> 0;
1600
- }
1601
- return table;
1602
- })();
1603
1767
  function crc32(buf) {
1604
1768
  let crc = 4294967295;
1605
1769
  for (const byte of buf) crc = CRC_TABLE[(crc ^ byte) & 255] ^ crc >>> 8;
@@ -1655,9 +1819,23 @@ function buildZip(entries) {
1655
1819
  eocd.writeUInt32LE(offset, 16);
1656
1820
  return Buffer2.concat([...chunks, centralBuf, eocd]);
1657
1821
  }
1822
+ var CRC_TABLE;
1823
+ var init_zip = __esm({
1824
+ "src/install/zip.ts"() {
1825
+ "use strict";
1826
+ CRC_TABLE = (() => {
1827
+ const table = new Uint32Array(256);
1828
+ for (let n = 0; n < 256; n++) {
1829
+ let c = n;
1830
+ for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
1831
+ table[n] = c >>> 0;
1832
+ }
1833
+ return table;
1834
+ })();
1835
+ }
1836
+ });
1658
1837
 
1659
1838
  // src/install/api.ts
1660
- var NO_DIST = "plugin dist not found\uFF08\u8FD0\u884C npm run build \u751F\u6210\uFF09";
1661
1839
  function handleInstall(req, res, url) {
1662
1840
  return dispatchRoute(INSTALL_ROUTES, req, res, url);
1663
1841
  }
@@ -1665,169 +1843,269 @@ function sendBinary(res, buf, contentType) {
1665
1843
  res.writeHead(200, { "Content-Type": contentType, "Cache-Control": "no-store" });
1666
1844
  res.end(buf);
1667
1845
  }
1668
- var PLUGIN_FILE_ROUTES = [
1669
- { path: "/code.js", file: "code.js", type: "text/javascript; charset=utf-8" },
1670
- { path: "/ui.html", file: "ui.html", type: "text/html; charset=utf-8" }
1671
- ].map(({ path, file, type }) => ({
1672
- method: "GET",
1673
- path,
1674
- handler: async ({ res }) => {
1675
- const buf = await readPluginFile(file);
1676
- if (!buf) {
1677
- sendJson(res, 404, { error: hasPluginDist() ? "not found" : NO_DIST });
1678
- return;
1679
- }
1680
- sendBinary(res, buf, type);
1681
- }
1682
- }));
1683
- var INSTALL_ROUTES = [
1684
- {
1685
- method: "GET",
1686
- path: "/",
1687
- handler: ({ res }) => {
1688
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1689
- res.end(renderInstallPage());
1690
- }
1691
- },
1692
- {
1693
- method: "GET",
1694
- path: "/manifest.json",
1695
- handler: async ({ res }) => {
1696
- const buf = await pluginManifest();
1697
- if (!buf) {
1698
- sendJson(res, 404, { error: NO_DIST });
1699
- return;
1846
+ var NO_DIST, PLUGIN_FILE_ROUTES, INSTALL_ROUTES;
1847
+ var init_api = __esm({
1848
+ "src/install/api.ts"() {
1849
+ "use strict";
1850
+ init_http_body();
1851
+ init_dist_resolver();
1852
+ init_page();
1853
+ init_zip();
1854
+ NO_DIST = "plugin dist not found\uFF08\u8FD0\u884C npm run build \u751F\u6210\uFF09";
1855
+ PLUGIN_FILE_ROUTES = [
1856
+ { path: "/code.js", file: "code.js", type: "text/javascript; charset=utf-8" },
1857
+ { path: "/ui.html", file: "ui.html", type: "text/html; charset=utf-8" }
1858
+ ].map(({ path, file, type }) => ({
1859
+ method: "GET",
1860
+ path,
1861
+ handler: async ({ res }) => {
1862
+ const buf = await readPluginFile(file);
1863
+ if (!buf) {
1864
+ sendJson(res, 404, { error: hasPluginDist() ? "not found" : NO_DIST });
1865
+ return;
1866
+ }
1867
+ sendBinary(res, buf, type);
1700
1868
  }
1701
- sendBinary(res, buf, "application/json; charset=utf-8");
1702
- }
1703
- },
1704
- ...PLUGIN_FILE_ROUTES,
1705
- {
1706
- method: "GET",
1707
- path: "/plugin.zip",
1708
- handler: async ({ res }) => {
1709
- if (!hasPluginDist()) {
1710
- sendJson(res, 404, { error: NO_DIST });
1711
- return;
1869
+ }));
1870
+ INSTALL_ROUTES = [
1871
+ {
1872
+ method: "GET",
1873
+ path: "/",
1874
+ handler: ({ res }) => {
1875
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1876
+ res.end(renderInstallPage());
1877
+ }
1878
+ },
1879
+ {
1880
+ method: "GET",
1881
+ path: "/manifest.json",
1882
+ handler: async ({ res }) => {
1883
+ const buf = await pluginManifest();
1884
+ if (!buf) {
1885
+ sendJson(res, 404, { error: NO_DIST });
1886
+ return;
1887
+ }
1888
+ sendBinary(res, buf, "application/json; charset=utf-8");
1889
+ }
1890
+ },
1891
+ ...PLUGIN_FILE_ROUTES,
1892
+ {
1893
+ method: "GET",
1894
+ path: "/plugin.zip",
1895
+ handler: async ({ res }) => {
1896
+ if (!hasPluginDist()) {
1897
+ sendJson(res, 404, { error: NO_DIST });
1898
+ return;
1899
+ }
1900
+ try {
1901
+ const [manifest, code, ui] = await Promise.all([
1902
+ pluginManifest(),
1903
+ readPluginFile("code.js"),
1904
+ readPluginFile("ui.html")
1905
+ ]);
1906
+ const zip = buildZip([
1907
+ { name: "manifest.json", data: manifest },
1908
+ { name: "code.js", data: code },
1909
+ { name: "ui.html", data: ui }
1910
+ ]);
1911
+ res.writeHead(200, {
1912
+ "Content-Type": "application/zip",
1913
+ "Content-Disposition": 'attachment; filename="batter-pixso-bridge.zip"'
1914
+ });
1915
+ res.end(zip);
1916
+ } catch {
1917
+ sendJson(res, 500, { error: "zip build failed" });
1918
+ }
1919
+ }
1712
1920
  }
1921
+ ];
1922
+ }
1923
+ });
1924
+
1925
+ // src/plugin/assets.ts
1926
+ import { createHash } from "node:crypto";
1927
+ import { mkdir, readdir, stat, unlink, writeFile } from "node:fs/promises";
1928
+ import { join as join4 } from "node:path";
1929
+ async function saveAsset(bytes, format) {
1930
+ dirReady ??= mkdir(ASSETS_DIR, { recursive: true });
1931
+ await dirReady;
1932
+ const ext = EXT_BY_FORMAT[format.toUpperCase()] ?? ".bin";
1933
+ const name = `${createHash("sha1").update(bytes).digest("hex").slice(0, 16)}${ext}`;
1934
+ await writeFile(join4(ASSETS_DIR, name), bytes);
1935
+ log(`asset saved: ${name} (${bytes.length} bytes)`);
1936
+ return name;
1937
+ }
1938
+ function substituteAssets(result, assets) {
1939
+ if (!assets.length) return result;
1940
+ const nameByKey = new Map(assets.map((a) => [a.key, a.name]));
1941
+ return JSON.parse(
1942
+ JSON.stringify(
1943
+ result,
1944
+ (_k, v) => typeof v === "string" ? nameByKey.get(v) ?? v : v
1945
+ )
1946
+ );
1947
+ }
1948
+ async function cleanOldAssets(ttlMs) {
1949
+ let removed = 0;
1950
+ const cutoff = Date.now() - ttlMs;
1951
+ let names;
1952
+ try {
1953
+ names = await readdir(ASSETS_DIR);
1954
+ } catch {
1955
+ return 0;
1956
+ }
1957
+ const expired = await Promise.all(
1958
+ names.map(async (name) => {
1713
1959
  try {
1714
- const [manifest, code, ui] = await Promise.all([
1715
- pluginManifest(),
1716
- readPluginFile("code.js"),
1717
- readPluginFile("ui.html")
1718
- ]);
1719
- const zip = buildZip([
1720
- { name: "manifest.json", data: manifest },
1721
- { name: "code.js", data: code },
1722
- { name: "ui.html", data: ui }
1723
- ]);
1724
- res.writeHead(200, {
1725
- "Content-Type": "application/zip",
1726
- "Content-Disposition": 'attachment; filename="batter-pixso-bridge.zip"'
1727
- });
1728
- res.end(zip);
1960
+ const p = join4(ASSETS_DIR, name);
1961
+ const s = await stat(p);
1962
+ return s.isFile() && s.mtimeMs < cutoff ? p : null;
1729
1963
  } catch {
1730
- sendJson(res, 500, { error: "zip build failed" });
1964
+ return null;
1731
1965
  }
1732
- }
1966
+ })
1967
+ );
1968
+ await Promise.all(
1969
+ expired.filter((p) => p !== null).map(async (p) => {
1970
+ try {
1971
+ await unlink(p);
1972
+ removed++;
1973
+ } catch {
1974
+ }
1975
+ })
1976
+ );
1977
+ if (removed > 0) log(`assets cleanup: removed ${removed} expired file(s)`);
1978
+ return removed;
1979
+ }
1980
+ function scheduleAssetsSweep(ttlMs) {
1981
+ void cleanOldAssets(ttlMs);
1982
+ const sweeper2 = setInterval(() => void cleanOldAssets(ttlMs), ASSET_SWEEP_INTERVAL_MS);
1983
+ sweeper2.unref();
1984
+ }
1985
+ var EXT_BY_FORMAT, dirReady;
1986
+ var init_assets = __esm({
1987
+ "src/plugin/assets.ts"() {
1988
+ "use strict";
1989
+ init_config();
1990
+ EXT_BY_FORMAT = {
1991
+ PNG: ".png",
1992
+ SVG: ".svg",
1993
+ JPG: ".jpg",
1994
+ JPEG: ".jpg",
1995
+ WEBP: ".webp",
1996
+ PDF: ".pdf"
1997
+ // export_image 尊重设计师导出标记,标记可能指定 PDF
1998
+ };
1999
+ dirReady = null;
1733
2000
  }
1734
- ];
2001
+ });
1735
2002
 
1736
2003
  // src/plugin/api.ts
1737
2004
  function handlePluginApi(req, res, url) {
1738
2005
  return dispatchRoute(PLUGIN_ROUTES, req, res, url);
1739
2006
  }
1740
- var PLUGIN_ROUTES = [
1741
- {
1742
- method: "POST",
1743
- path: "/sessions/register",
1744
- handler: async ({ req, res }) => {
1745
- sendJson(res, 201, sessions.register(await readJsonBody(req)));
1746
- }
1747
- },
1748
- {
1749
- method: "POST",
1750
- path: "/context",
1751
- handler: async ({ req, res, url }) => {
1752
- const session = url.searchParams.get("session");
1753
- if (!session) {
1754
- sendJson(res, 400, { error: "missing session" });
1755
- return;
1756
- }
1757
- const meta = await readJsonBody(req);
1758
- if (!sessions.updateContext(session, meta)) {
1759
- sendJson(res, 404, { error: "unknown_session" });
1760
- return;
1761
- }
1762
- sendJson(res, 200, { ok: true });
1763
- }
1764
- },
1765
- {
1766
- method: "GET",
1767
- path: "/commands/stream",
1768
- handler: ({ req, res, url }) => {
1769
- const session = url.searchParams.get("session");
1770
- if (!session || !sessions.setConnected(session, true)) {
1771
- sendJson(res, 404, { error: "unknown_session" });
1772
- return;
1773
- }
1774
- res.writeHead(200, {
1775
- "Content-Type": "text/event-stream; charset=utf-8",
1776
- "Cache-Control": "no-store",
1777
- Connection: "keep-alive"
1778
- });
1779
- res.write(": connected\n\n");
1780
- const unsubscribe = commandBus.subscribe(session, (command) => {
1781
- res.write(`data: ${JSON.stringify({ command })}
2007
+ var PLUGIN_ROUTES;
2008
+ var init_api2 = __esm({
2009
+ "src/plugin/api.ts"() {
2010
+ "use strict";
2011
+ init_config();
2012
+ init_http_body();
2013
+ init_assets();
2014
+ init_command_bus();
2015
+ init_sessions();
2016
+ PLUGIN_ROUTES = [
2017
+ {
2018
+ method: "POST",
2019
+ path: "/sessions/register",
2020
+ handler: async ({ req, res }) => {
2021
+ sendJson(res, 201, sessions.register(await readJsonBody(req)));
2022
+ }
2023
+ },
2024
+ {
2025
+ method: "POST",
2026
+ path: "/context",
2027
+ handler: async ({ req, res, url }) => {
2028
+ const session = url.searchParams.get("session");
2029
+ if (!session) {
2030
+ sendJson(res, 400, { error: "missing session" });
2031
+ return;
2032
+ }
2033
+ const meta = await readJsonBody(req);
2034
+ if (!sessions.updateContext(session, meta)) {
2035
+ sendJson(res, 404, { error: "unknown_session" });
2036
+ return;
2037
+ }
2038
+ sendJson(res, 200, { ok: true });
2039
+ }
2040
+ },
2041
+ {
2042
+ method: "GET",
2043
+ path: "/commands/stream",
2044
+ handler: ({ req, res, url }) => {
2045
+ const session = url.searchParams.get("session");
2046
+ if (!session || !sessions.setConnected(session, true)) {
2047
+ sendJson(res, 404, { error: "unknown_session" });
2048
+ return;
2049
+ }
2050
+ res.writeHead(200, {
2051
+ "Content-Type": "text/event-stream; charset=utf-8",
2052
+ "Cache-Control": "no-store",
2053
+ Connection: "keep-alive"
2054
+ });
2055
+ res.write(": connected\n\n");
2056
+ const unsubscribe = commandBus.subscribe(session, (command) => {
2057
+ res.write(`data: ${JSON.stringify({ command })}
1782
2058
 
1783
2059
  `);
1784
- });
1785
- const teardown = () => {
1786
- clearInterval(heartbeat);
1787
- unsubscribe();
1788
- sessions.setConnected(session, false);
1789
- };
1790
- const heartbeat = setInterval(() => {
1791
- if (!res.write(": ping\n\n")) {
1792
- teardown();
1793
- res.end();
1794
- log(`SSE ghost reaped: session ${session.slice(0, 8)}`);
1795
- return;
2060
+ });
2061
+ const teardown = () => {
2062
+ clearInterval(heartbeat);
2063
+ unsubscribe();
2064
+ sessions.setConnected(session, false);
2065
+ };
2066
+ const heartbeat = setInterval(() => {
2067
+ if (!res.write(": ping\n\n")) {
2068
+ teardown();
2069
+ res.end();
2070
+ log(`SSE ghost reaped: session ${session.slice(0, 8)}`);
2071
+ return;
2072
+ }
2073
+ sessions.touchSeen(session);
2074
+ }, SSE_HEARTBEAT_MS);
2075
+ req.on("close", () => {
2076
+ teardown();
2077
+ log(`SSE channel closed: session ${session.slice(0, 8)}`);
2078
+ });
2079
+ }
2080
+ },
2081
+ {
2082
+ method: "POST",
2083
+ path: "/commands/:id/result",
2084
+ handler: async ({ req, res, params }) => {
2085
+ const payload = await readJsonBody(req);
2086
+ const result = payload.ok && payload.assets?.length ? substituteAssets(payload.result, payload.assets) : payload.result;
2087
+ const found = commandBus.complete(params["id"], payload.ok, payload.ok ? result : payload.error);
2088
+ sendJson(res, found ? 200 : 404, found ? { ok: true } : { error: "unknown command" });
2089
+ }
2090
+ },
2091
+ {
2092
+ method: "POST",
2093
+ path: "/assets",
2094
+ handler: async ({ req, res, url }) => {
2095
+ try {
2096
+ const name = await saveAsset(await readBody(req), url.searchParams.get("format") ?? "PNG");
2097
+ sendJson(res, 201, { name });
2098
+ } catch {
2099
+ sendJson(res, 500, { error: "asset save failed" });
2100
+ }
1796
2101
  }
1797
- sessions.touchSeen(session);
1798
- }, 15e3);
1799
- req.on("close", () => {
1800
- teardown();
1801
- log(`SSE channel closed: session ${session.slice(0, 8)}`);
1802
- });
1803
- }
1804
- },
1805
- {
1806
- method: "POST",
1807
- path: "/commands/:id/result",
1808
- handler: async ({ req, res, params }) => {
1809
- const payload = await readJsonBody(req);
1810
- const result = payload.ok && payload.assets?.length ? substituteAssets(payload.result, payload.assets) : payload.result;
1811
- const found = commandBus.complete(params["id"], payload.ok, payload.ok ? result : payload.error);
1812
- sendJson(res, found ? 200 : 404, found ? { ok: true } : { error: "unknown command" });
1813
- }
1814
- },
1815
- {
1816
- method: "POST",
1817
- path: "/assets",
1818
- handler: async ({ req, res, url }) => {
1819
- try {
1820
- const name = await saveAsset(await readBody(req), url.searchParams.get("format") ?? "PNG");
1821
- sendJson(res, 201, { name });
1822
- } catch {
1823
- sendJson(res, 500, { error: "asset save failed" });
1824
2102
  }
1825
- }
2103
+ ];
1826
2104
  }
1827
- ];
2105
+ });
1828
2106
 
1829
2107
  // src/http.ts
1830
- var ROUTES = [handleInstall, handleMcp, handlePluginApi];
2108
+ import { createServer } from "node:http";
1831
2109
  function startHttpServer() {
1832
2110
  const server = createServer((req, res) => {
1833
2111
  res.setHeader("Access-Control-Allow-Origin", "*");
@@ -1844,7 +2122,7 @@ function startHttpServer() {
1844
2122
  const all = sessions.list();
1845
2123
  sendJson(res, 200, {
1846
2124
  ok: true,
1847
- name: HUB_NAME,
2125
+ name: SERVICE_NAME,
1848
2126
  pid: process.pid,
1849
2127
  version: VERSION,
1850
2128
  pluginsOnline: all.filter((s) => s.online).length
@@ -1868,128 +2146,141 @@ function startHttpServer() {
1868
2146
  });
1869
2147
  server.listen(config.httpPort, "127.0.0.1", () => {
1870
2148
  log(`plugin HTTP + MCP server on http://127.0.0.1:${config.httpPort} (MCP endpoint: /mcp, pid ${process.pid})`);
2149
+ scheduleAssetsSweep(config.assetTtlMs);
1871
2150
  resolve(server);
1872
2151
  });
1873
2152
  });
1874
2153
  }
2154
+ var ROUTES;
2155
+ var init_http2 = __esm({
2156
+ "src/http.ts"() {
2157
+ "use strict";
2158
+ init_http();
2159
+ init_config();
2160
+ init_http_body();
2161
+ init_api();
2162
+ init_api2();
2163
+ init_assets();
2164
+ init_sessions();
2165
+ ROUTES = [handleInstall, handleMcp, handlePluginApi];
2166
+ }
2167
+ });
1875
2168
 
1876
- // src/process/hub.ts
1877
- async function runHub() {
2169
+ // src/cli/serve.ts
2170
+ var serve_exports = {};
2171
+ __export(serve_exports, {
2172
+ serve: () => serve
2173
+ });
2174
+ async function serve() {
2175
+ const { state, health } = await probeService();
2176
+ if (state === "alive" && health?.version === VERSION) {
2177
+ printUsage();
2178
+ return;
2179
+ }
2180
+ if (state === "alive") {
2181
+ throw new Error(
2182
+ `\u670D\u52A1\u5DF2\u5728\u8FD0\u884C\u4E14\u7248\u672C\u4E0D\u7B26\uFF08\u8FD0\u884C ${health?.version} / \u5F53\u524D ${VERSION}\uFF09\u2014\u2014\u5148\u6267\u884C --stop \u505C\u6B62\u65E7\u7248\u672C\u518D\u542F\u52A8`
2183
+ );
2184
+ }
2185
+ if (state === "hung" || state === "foreign") {
2186
+ throw serviceStateError(state);
2187
+ }
1878
2188
  const server = await startHttpServer();
1879
2189
  if (!server) {
1880
- log(`\u7AEF\u53E3 ${config.httpPort} \u5DF2\u6709 hub \u5728\u8FD0\u884C\uFF0C\u672C\u8FDB\u7A0B\u9000\u51FA`);
1881
- process.exit(0);
2190
+ throw new Error(
2191
+ `\u7AEF\u53E3 ${config.httpPort} \u5728\u542F\u52A8\u77AC\u95F4\u88AB\u5360\u7528\uFF08\u53EF\u80FD\u662F\u767B\u5F55\u81EA\u542F\u5B9E\u4F8B\u62A2\u5148\u542F\u52A8\u2014\u2014\u91CD\u8DD1\u672C\u547D\u4EE4\u5373\u53EF\u81EA\u52A8\u8BC6\u522B\uFF1B\u82E5\u6301\u7EED\u88AB\u5360\uFF0C${PORT_FIX_HINT}\uFF09`
2192
+ );
1882
2193
  }
1883
- writePidFile(process.pid);
1884
- log("hub ready\uFF08\u5E38\u9A7B\uFF0C\u4E0D\u81EA\u6BC1\uFF1B\u7248\u672C\u4E0D\u7B26\u65F6\u7531 proxy \u81EA\u52A8\u6362\u8840\uFF0C\u6216 --stop \u624B\u52A8\u505C\u6B62\uFF09");
1885
- void cleanOldAssets(config.assetTtlMs);
1886
- const sweeper2 = setInterval(() => void cleanOldAssets(config.assetTtlMs), 60 * 6e4);
1887
- sweeper2.unref();
2194
+ printUsage();
2195
+ log("\u524D\u53F0\u8FD0\u884C\u4E2D\uFF1A\u6B64\u7A97\u53E3/\u8FDB\u7A0B\u9700\u4FDD\u6301\uFF08Ctrl+C \u505C\u6B62\u670D\u52A1\uFF09\uFF1B\u514D\u7A97\u53E3\u7528 --setup \u6CE8\u518C\u5F00\u673A\u81EA\u542F");
1888
2196
  }
1889
-
1890
- // src/process/stdio-proxy.ts
1891
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1892
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
1893
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
1894
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1895
- import { ResultSchema } from "@modelcontextprotocol/sdk/types.js";
1896
- async function startStdioProxy() {
1897
- const mcpUrl = `${hubBaseUrl()}/mcp`;
1898
- let upstream = null;
1899
- async function connectUpstream() {
1900
- const client = new Client(
1901
- { name: "batter-pixso-mcp-proxy", version: VERSION },
1902
- { capabilities: {} }
1903
- );
1904
- await client.connect(new StreamableHTTPClientTransport(new URL(mcpUrl)));
1905
- return client;
1906
- }
1907
- async function ensureUpstream() {
1908
- if (upstream) return upstream;
1909
- const { state, health } = await probeHub();
1910
- if (state === "alive" && health?.version !== VERSION) {
1911
- log(`hub \u7248\u672C\u4E0D\u7B26\uFF08\u8FD0\u884C ${health?.version} / \u5F53\u524D ${VERSION}\uFF09\uFF0C\u81EA\u52A8\u91CD\u542F\u2026`);
1912
- if (!killHubByPid(health.pid)) await stopHub();
1913
- await waitForHubDown();
1914
- spawnHub();
1915
- } else if (state === "hung") {
1916
- log("hub \u65E0\u54CD\u5E94\uFF08\u50F5\u5C38\u8FDB\u7A0B\uFF09\uFF0C\u5F3A\u6740\u540E\u91CD\u5EFA\u2026");
1917
- if (!killHubByPidFile()) throw hubStateError("hung");
1918
- await waitForHubDown(1e4);
1919
- spawnHub();
1920
- } else if (state === "down") {
1921
- spawnHub();
1922
- } else if (state === "foreign") {
1923
- throw hubStateError(state);
1924
- }
1925
- await waitForHub(3e4);
1926
- upstream = await connectUpstream();
1927
- log(`proxy connected to ${mcpUrl}`);
1928
- return upstream;
1929
- }
1930
- let rebuilding = null;
1931
- function rebuildUpstream() {
1932
- rebuilding ??= (async () => {
1933
- const stale = upstream;
1934
- upstream = null;
1935
- if (stale) await stale.close().catch(() => {
1936
- });
1937
- return ensureUpstream();
1938
- })().finally(() => {
1939
- rebuilding = null;
1940
- });
1941
- return rebuilding;
2197
+ function printUsage() {
2198
+ const url = mcpUrl();
2199
+ console.log(
2200
+ [
2201
+ `batter-pixso-mcp v${VERSION} \u2014 \u670D\u52A1\u8FD0\u884C\u4E2D\uFF1A${serviceBaseUrl()}`,
2202
+ ``,
2203
+ `MCP \u5BA2\u6237\u7AEF\u914D\u7F6E\uFF08URL \u76F4\u8FDE\uFF0C\u4E00\u4E2A\u5730\u5740\u5168\u5BA2\u6237\u7AEF\u901A\u7528\uFF09\uFF1A`,
2204
+ ``,
2205
+ ` Claude Code\uFF08\u4E00\u884C\u547D\u4EE4\uFF09\uFF1A`,
2206
+ ` ${claudeAddCommand()}`,
2207
+ ``,
2208
+ ` \u901A\u7528 JSON\uFF08Cursor / Cherry Studio \u7B49\uFF09\uFF1A`,
2209
+ ` { "mcpServers": { "batter-pixso": { "url": "${url}" } } }`,
2210
+ ``,
2211
+ `\u63D2\u4EF6\u5B89\u88C5\uFF1A\u6D4F\u89C8\u5668\u6253\u5F00 ${serviceBaseUrl()}/ \u6309\u5F15\u5BFC\u5B89\u88C5`,
2212
+ `\u5F00\u673A\u81EA\u542F\uFF1Anpx batter-pixso-mcp --setup\uFF08Windows\uFF09`,
2213
+ `\u505C\u6B62\u670D\u52A1\uFF1Anpx batter-pixso-mcp --stop`
2214
+ ].join("\n")
2215
+ );
2216
+ }
2217
+ var init_serve = __esm({
2218
+ "src/cli/serve.ts"() {
2219
+ "use strict";
2220
+ init_config();
2221
+ init_http2();
2222
+ init_service();
1942
2223
  }
1943
- async function callWithRecovery(fn) {
1944
- const client = await ensureUpstream();
1945
- try {
1946
- return await fn(client);
1947
- } catch (err) {
1948
- const alive = await client.ping().then(
1949
- () => true,
1950
- () => false
1951
- );
1952
- if (alive) throw err;
1953
- log("hub \u4F1A\u8BDD\u5931\u8054\uFF0C\u91CD\u5EFA\u540E\u91CD\u8BD5\u2026");
1954
- const rebuilt = await rebuildUpstream();
1955
- return await fn(rebuilt);
2224
+ });
2225
+
2226
+ // src/cli/index.ts
2227
+ init_config();
2228
+
2229
+ // src/cli/commands.ts
2230
+ init_config();
2231
+ var COMMANDS = [
2232
+ {
2233
+ flags: ["--stop"],
2234
+ summary: "\u505C\u6B62\u6B63\u5728\u8FD0\u884C\u7684\u670D\u52A1",
2235
+ run: async () => (await Promise.resolve().then(() => (init_service(), service_exports))).stopService()
2236
+ },
2237
+ {
2238
+ flags: ["--version", "-v"],
2239
+ summary: "\u6253\u5370\u7248\u672C\u53F7",
2240
+ run: async () => log(VERSION)
2241
+ },
2242
+ {
2243
+ flags: ["--uninstall"],
2244
+ summary: "\u79FB\u9664\u767B\u5F55\u81EA\u542F + \u505C\u6B62\u670D\u52A1 + \u5220\u9664\u7269\u5316\u76EE\u5F55",
2245
+ run: async () => (await Promise.resolve().then(() => (init_setup(), setup_exports))).uninstallAutostart()
2246
+ },
2247
+ {
2248
+ flags: ["--setup"],
2249
+ summary: "\uFF08Windows\uFF09\u7269\u5316\u81EA\u5305\u542B\u526F\u672C\u5230 %LOCALAPPDATA% \u5E76\u6CE8\u518C\u767B\u5F55\u81EA\u542F\uFF0C\u968F\u540E\u524D\u53F0\u542F\u52A8\u670D\u52A1",
2250
+ run: async () => {
2251
+ await (await Promise.resolve().then(() => (init_setup(), setup_exports))).setupAutostart();
2252
+ await (await Promise.resolve().then(() => (init_serve(), serve_exports))).serve();
1956
2253
  }
2254
+ },
2255
+ {
2256
+ flags: ["--help", "-h"],
2257
+ summary: "\u6253\u5370\u672C\u5E2E\u52A9",
2258
+ run: async () => printHelp()
1957
2259
  }
1958
- const upstreamClient = await ensureUpstream();
1959
- const server = new Server(
1960
- { name: HUB_NAME, version: VERSION },
1961
- {
1962
- capabilities: upstreamClient.getServerCapabilities() ?? {}
1963
- }
2260
+ ];
2261
+ function printHelp() {
2262
+ console.log(
2263
+ [
2264
+ `batter-pixso-mcp v${VERSION}`,
2265
+ ``,
2266
+ `\u7528\u6CD5\uFF1Abatter-pixso-mcp [\u547D\u4EE4]\uFF08\u4E0D\u5E26\u547D\u4EE4 = \u524D\u53F0\u542F\u52A8\u670D\u52A1\uFF09`,
2267
+ ``,
2268
+ ...COMMANDS.map((c) => ` ${c.flags.join(", ").padEnd(18)}${c.summary}`)
2269
+ ].join("\n")
1964
2270
  );
1965
- server.fallbackRequestHandler = (req, extra) => callWithRecovery((c) => c.request(req, ResultSchema, { signal: extra.signal }));
1966
- server.onclose = () => {
1967
- log("agent disconnected, proxy exiting");
1968
- process.exit(0);
1969
- };
1970
- await server.connect(new StdioServerTransport());
1971
- log("proxy ready on stdio");
1972
2271
  }
1973
-
1974
- // src/process/main.ts
1975
- async function main() {
1976
- if (process.argv.includes("--hub")) {
1977
- await runHub();
1978
- return;
1979
- }
1980
- if (process.argv.includes("--stop")) {
1981
- await stopHub();
1982
- return;
1983
- }
1984
- if (process.argv.includes("--version") || process.argv.includes("-v")) {
1985
- log(VERSION);
2272
+ async function dispatch(argv) {
2273
+ const hit = COMMANDS.find((c) => c.flags.some((f) => argv.includes(f)));
2274
+ if (hit) {
2275
+ await hit.run();
1986
2276
  return;
1987
2277
  }
1988
- await startStdioProxy();
2278
+ const { serve: serve2 } = await Promise.resolve().then(() => (init_serve(), serve_exports));
2279
+ await serve2();
1989
2280
  }
1990
2281
 
1991
- // src/process/index.ts
1992
- main().catch((err) => {
1993
- console.error("[batter-pixso-mcp] fatal:", err);
2282
+ // src/cli/index.ts
2283
+ dispatch(process.argv).catch((err) => {
2284
+ log("fatal:", err);
1994
2285
  process.exit(1);
1995
2286
  });