batter-pixso-mcp 0.3.0 → 0.5.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,50 +1,69 @@
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
- };
11
-
12
- // src/config.ts
13
- function log(...args) {
14
- console.error("[batter-pixso-mcp]", ...args);
15
- }
16
- var config, VERSION;
17
- var init_config = __esm({
18
- "src/config.ts"() {
19
- "use strict";
20
- config = {
21
- /** 插件通信 HTTP 端口,仅监听 127.0.0.1 */
22
- httpPort: Number(process.env.HTTP_PORT ?? 3210),
23
- /** hub 空闲自毁阈值(无插件、无 agent 请求),默认 30 分钟 */
24
- hubIdleMs: Number(process.env.HUB_IDLE_MS ?? 30 * 60 * 1e3),
25
- /** 产物图片保留时长,默认 7 天 */
26
- assetTtlMs: Number(process.env.ASSET_TTL_DAYS ?? 7) * 24 * 60 * 60 * 1e3
27
- };
28
- VERSION = "0.3.0";
29
- }
30
- });
31
2
 
32
- // src/assets.ts
3
+ // src/plugin/assets.ts
33
4
  import { createHash } from "node:crypto";
34
5
  import { mkdir, readdir, stat, unlink, writeFile } from "node:fs/promises";
6
+ import { join as join2 } from "node:path";
7
+
8
+ // src/config.ts
35
9
  import { tmpdir } from "node:os";
36
10
  import { join } from "node:path";
37
- function assetDir() {
38
- return ASSETS_DIR;
11
+ var VERSION = true ? "0.5.0" : "0.0.0-dev";
12
+ var HUB_NAME = "batter-pixso-mcp";
13
+ var ASSETS_DIR = join(tmpdir(), "batter-pixso-assets");
14
+ var config = {
15
+ /** 插件通信 HTTP 端口,仅监听 127.0.0.1。与插件 UI 的 DEFAULT_PORT 互为默认对端,改默认需同步 */
16
+ httpPort: Number(process.env.HTTP_PORT ?? 3210),
17
+ /** hub 空闲自毁阈值(无插件、无 agent 请求),默认 30 分钟 */
18
+ hubIdleMs: Number(process.env.HUB_IDLE_MS ?? 30 * 60 * 1e3),
19
+ /** 产物图片保留时长,默认 7 天 */
20
+ assetTtlMs: Number(process.env.ASSET_TTL_DAYS ?? 7) * 24 * 60 * 60 * 1e3
21
+ };
22
+ function hubBaseUrl() {
23
+ return `http://127.0.0.1:${config.httpPort}`;
39
24
  }
25
+ function log(...args) {
26
+ console.error("[batter-pixso-mcp]", ...args);
27
+ }
28
+ function withResolvers() {
29
+ let resolve;
30
+ let reject;
31
+ const promise = new Promise((res, rej) => {
32
+ resolve = res;
33
+ reject = rej;
34
+ });
35
+ return { promise, resolve, reject };
36
+ }
37
+
38
+ // src/plugin/assets.ts
39
+ var EXT_BY_FORMAT = {
40
+ PNG: ".png",
41
+ SVG: ".svg",
42
+ JPG: ".jpg",
43
+ JPEG: ".jpg",
44
+ WEBP: ".webp",
45
+ PDF: ".pdf"
46
+ // export_image 尊重设计师导出标记,标记可能指定 PDF
47
+ };
48
+ var dirReady = null;
40
49
  async function saveAsset(bytes, format) {
41
- await mkdir(ASSETS_DIR, { recursive: true });
50
+ dirReady ??= mkdir(ASSETS_DIR, { recursive: true });
51
+ await dirReady;
42
52
  const ext = EXT_BY_FORMAT[format.toUpperCase()] ?? ".bin";
43
53
  const name = `${createHash("sha1").update(bytes).digest("hex").slice(0, 16)}${ext}`;
44
- const path = join(ASSETS_DIR, name);
45
- await writeFile(path, bytes);
46
- log(`asset saved: ${path} (${bytes.length} bytes)`);
47
- return { name, path };
54
+ await writeFile(join2(ASSETS_DIR, name), bytes);
55
+ log(`asset saved: ${name} (${bytes.length} bytes)`);
56
+ return name;
57
+ }
58
+ function substituteAssets(result, assets) {
59
+ if (!assets.length) return result;
60
+ const nameByKey = new Map(assets.map((a) => [a.key, a.name]));
61
+ return JSON.parse(
62
+ JSON.stringify(
63
+ result,
64
+ (_k, v) => typeof v === "string" ? nameByKey.get(v) ?? v : v
65
+ )
66
+ );
48
67
  }
49
68
  async function cleanOldAssets(ttlMs) {
50
69
  let removed = 0;
@@ -57,7 +76,7 @@ async function cleanOldAssets(ttlMs) {
57
76
  }
58
77
  for (const name of names) {
59
78
  try {
60
- const p = join(ASSETS_DIR, name);
79
+ const p = join2(ASSETS_DIR, name);
61
80
  const s = await stat(p);
62
81
  if (s.isFile() && s.mtimeMs < cutoff) {
63
82
  await unlink(p);
@@ -69,111 +88,125 @@ async function cleanOldAssets(ttlMs) {
69
88
  if (removed > 0) log(`assets cleanup: removed ${removed} expired file(s)`);
70
89
  return removed;
71
90
  }
72
- var ASSETS_DIR, EXT_BY_FORMAT;
73
- var init_assets = __esm({
74
- "src/assets.ts"() {
75
- "use strict";
76
- init_config();
77
- ASSETS_DIR = join(tmpdir(), "batter-pixso-assets");
78
- EXT_BY_FORMAT = {
79
- PNG: ".png",
80
- SVG: ".svg",
81
- JPG: ".jpg",
82
- JPEG: ".jpg",
83
- WEBP: ".webp",
84
- PDF: ".pdf"
85
- // export_image 尊重设计师导出标记,标记可能指定 PDF
86
- };
87
- }
88
- });
89
91
 
90
- // src/core/sessions.ts
92
+ // src/plugin/sessions.ts
91
93
  import { randomUUID } from "node:crypto";
92
- var ONLINE_WINDOW_MS, SessionRegistry, sessions;
93
- var init_sessions = __esm({
94
- "src/core/sessions.ts"() {
95
- "use strict";
96
- init_config();
97
- ONLINE_WINDOW_MS = 45e3;
98
- SessionRegistry = class {
99
- sessions = /* @__PURE__ */ new Map();
100
- lastActiveId = null;
101
- isOnline(s) {
102
- return s.connected || Date.now() - s.lastSeen < ONLINE_WINDOW_MS;
103
- }
104
- register(meta = {}) {
105
- const session = { id: randomUUID(), meta, lastSeen: Date.now(), connected: false };
106
- this.sessions.set(session.id, session);
107
- this.lastActiveId = session.id;
108
- log(`session registered: ${session.id} (${meta.fileName ?? "unknown file"})`);
109
- return this.toInfo(session);
110
- }
111
- touch(id) {
112
- const s = this.sessions.get(id);
113
- if (!s) return null;
114
- s.lastSeen = Date.now();
115
- this.lastActiveId = id;
116
- return s;
117
- }
118
- setConnected(id, connected) {
119
- const s = this.touch(id);
120
- if (!s) return false;
121
- s.connected = connected;
122
- return true;
123
- }
124
- updateContext(id, meta) {
125
- const s = this.touch(id);
126
- if (!s) return false;
127
- s.meta = { ...s.meta, ...meta };
128
- log(
129
- `context updated [${id.slice(0, 8)}]: ${s.meta.fileName ?? "?"} / ${s.meta.pageName ?? "?"} / ${s.meta.selection?.length ?? 0} selected`
130
- );
131
- return true;
132
- }
133
- list() {
134
- return [...this.sessions.values()].map((s) => this.toInfo(s));
135
- }
136
- anyOnline() {
137
- return [...this.sessions.values()].some((s) => this.isOnline(s));
94
+ var ONLINE_WINDOW_MS = 45e3;
95
+ function unknownTargetError(target) {
96
+ return new Error(`\u627E\u4E0D\u5230\u76EE\u6807\u63D2\u4EF6\u4F1A\u8BDD\uFF1A${target}\u3002\u53EF\u7528 list_sessions \u67E5\u770B\u5F53\u524D\u8FDE\u63A5\u7684\u4F1A\u8BDD`);
97
+ }
98
+ var SessionRegistry = class {
99
+ sessions = /* @__PURE__ */ new Map();
100
+ lastActiveId = null;
101
+ /** 会话亲和:fileKey → 上一次路由使用的 sessionId(仍在线时优先,避免同文件多实例间漂移) */
102
+ lastUsedByFileKey = /* @__PURE__ */ new Map();
103
+ isOnline(s) {
104
+ return s.connected || Date.now() - s.lastSeen < ONLINE_WINDOW_MS;
105
+ }
106
+ /** fileKey 寻址候选:该文件的在线会话(谁先 pong 由 ping 竞速决定,此处不排序) */
107
+ candidatesForFileKey(fileKey) {
108
+ return [...this.sessions.values()].filter((s) => s.meta.fileKey === fileKey && this.isOnline(s));
109
+ }
110
+ /** 亲和查询:上一次为该 fileKey 服务的 sessionId */
111
+ affinityIdOf(fileKey) {
112
+ return this.lastUsedByFileKey.get(fileKey);
113
+ }
114
+ /** 亲和钉住:fileKey 寻址探活成功后记录(下次优先) */
115
+ pinAffinity(fileKey, sessionId) {
116
+ this.lastUsedByFileKey.set(fileKey, sessionId);
117
+ }
118
+ /** 剔除会话(幽灵剔除/ping 判死共用):清理亲和引用;存活但繁忙被误剔的插件会在 SSE 断开或上报 404 后自重注册 */
119
+ evict(id) {
120
+ if (this.sessions.delete(id)) {
121
+ for (const [k, v] of this.lastUsedByFileKey) {
122
+ if (v === id) this.lastUsedByFileKey.delete(k);
138
123
  }
139
- /** 路由:显式 sessionId > fileKey 匹配 > 最近活跃在线会话 */
140
- resolve(target) {
141
- if (target) {
142
- const byId = this.sessions.get(target);
143
- if (byId) return this.assertOnline(byId);
144
- const byFile = [...this.sessions.values()].find((s) => s.meta.fileKey === target);
145
- if (byFile) return this.assertOnline(byFile);
146
- throw new Error(`\u627E\u4E0D\u5230\u76EE\u6807\u63D2\u4EF6\u4F1A\u8BDD\uFF1A${target}\u3002\u53EF\u7528 list_sessions \u67E5\u770B\u5F53\u524D\u8FDE\u63A5\u7684\u4F1A\u8BDD`);
147
- }
148
- if (this.lastActiveId) {
149
- const s = this.sessions.get(this.lastActiveId);
150
- if (s && this.isOnline(s)) return s;
124
+ log(`session evicted: ${id.slice(0, 8)}`);
125
+ }
126
+ }
127
+ register(meta = {}) {
128
+ if (meta.fileKey) {
129
+ for (const s of [...this.sessions.values()]) {
130
+ if (s.meta.fileKey === meta.fileKey && !this.isOnline(s)) {
131
+ this.evict(s.id);
151
132
  }
152
- const fallback = [...this.sessions.values()].find((s) => this.isOnline(s));
153
- if (fallback) return fallback;
154
- throw new Error("Pixso \u63D2\u4EF6\u672A\u8FDE\u63A5\uFF1A\u8BF7\u5728 Pixso \u5BA2\u6237\u7AEF\u4E2D\u8FD0\u884C Batter Pixso Bridge \u63D2\u4EF6");
155
- }
156
- assertOnline(s) {
157
- if (!this.isOnline(s)) throw new Error(`\u76EE\u6807\u63D2\u4EF6\u5DF2\u79BB\u7EBF\uFF1A${s.meta.fileName ?? s.id}`);
158
- return s;
159
- }
160
- toInfo(s) {
161
- return {
162
- sessionId: s.id,
163
- fileKey: s.meta.fileKey,
164
- fileName: s.meta.fileName,
165
- pageName: s.meta.pageName,
166
- selectionCount: s.meta.selection?.length,
167
- online: this.isOnline(s),
168
- lastSeen: s.lastSeen
169
- };
170
133
  }
134
+ }
135
+ const session = { id: randomUUID(), meta, lastSeen: Date.now(), connected: false };
136
+ this.sessions.set(session.id, session);
137
+ this.lastActiveId = session.id;
138
+ log(`session registered: ${session.id} (${meta.fileName ?? "unknown file"})`);
139
+ return this.toInfo(session);
140
+ }
141
+ /** 心跳/连接状态用:只刷 lastSeen,不影响 lastActiveId("最近活跃"= 用户行为,心跳不算) */
142
+ touchSeen(id) {
143
+ const s = this.sessions.get(id);
144
+ if (!s) return null;
145
+ s.lastSeen = Date.now();
146
+ return s;
147
+ }
148
+ /** 用户行为(选区上报/命令服务/注册):刷 lastSeen 并标记最近活跃 */
149
+ touch(id) {
150
+ const s = this.touchSeen(id);
151
+ if (!s) return null;
152
+ this.lastActiveId = id;
153
+ return s;
154
+ }
155
+ setConnected(id, connected) {
156
+ const s = this.touchSeen(id);
157
+ if (!s) return false;
158
+ s.connected = connected;
159
+ return true;
160
+ }
161
+ updateContext(id, meta) {
162
+ const s = this.touch(id);
163
+ if (!s) return false;
164
+ s.meta = { ...s.meta, ...meta };
165
+ log(
166
+ `context updated [${id.slice(0, 8)}]: ${s.meta.fileName ?? "?"} / ${s.meta.pageName ?? "?"} / ${s.meta.selection?.length ?? 0} selected`
167
+ );
168
+ return true;
169
+ }
170
+ list() {
171
+ return [...this.sessions.values()].map((s) => this.toInfo(s));
172
+ }
173
+ /** 纯查找(不判活):工具层寻址用——离线会话也可取到 fileKey 用于候选扩展与故障转移 */
174
+ get(id) {
175
+ return this.sessions.get(id);
176
+ }
177
+ /** 缺省路由:最近活跃在线会话 > 任意在线会话(显式 target 由工具层 get/candidatesForFileKey + ping 完成) */
178
+ resolve() {
179
+ if (this.lastActiveId) {
180
+ const s = this.sessions.get(this.lastActiveId);
181
+ if (s && this.isOnline(s)) return s;
182
+ }
183
+ const fallback = [...this.sessions.values()].find((s) => this.isOnline(s));
184
+ if (fallback) return fallback;
185
+ throw new Error("Pixso \u63D2\u4EF6\u672A\u8FDE\u63A5\uFF1A\u8BF7\u5728 Pixso \u5BA2\u6237\u7AEF\u4E2D\u8FD0\u884C Batter Pixso Bridge \u63D2\u4EF6");
186
+ }
187
+ toInfo(s) {
188
+ return {
189
+ sessionId: s.id,
190
+ fileKey: s.meta.fileKey,
191
+ fileName: s.meta.fileName,
192
+ pageName: s.meta.pageName,
193
+ selectionCount: s.meta.selection?.length,
194
+ online: this.isOnline(s),
195
+ lastSeen: s.lastSeen
171
196
  };
172
- sessions = new SessionRegistry();
173
197
  }
174
- });
198
+ };
199
+ var sessions = new SessionRegistry();
200
+
201
+ // src/http.ts
202
+ import { createServer } from "node:http";
203
+
204
+ // src/agent/http.ts
205
+ import { randomUUID as randomUUID2 } from "node:crypto";
206
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
207
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
175
208
 
176
- // src/routes/http-utils.ts
209
+ // src/http-body.ts
177
210
  function sendJson(res, status, body) {
178
211
  res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
179
212
  res.end(JSON.stringify(body));
@@ -183,769 +216,149 @@ async function readBody(req) {
183
216
  for await (const chunk of req) chunks.push(chunk);
184
217
  return Buffer.concat(chunks);
185
218
  }
186
- var init_http_utils = __esm({
187
- "src/routes/http-utils.ts"() {
188
- "use strict";
219
+ var JsonBodyError = class extends Error {
220
+ };
221
+ async function readJsonBody(req) {
222
+ const buf = await readBody(req);
223
+ try {
224
+ return buf.length ? JSON.parse(buf.toString("utf8")) : {};
225
+ } catch {
226
+ throw new JsonBodyError("invalid json");
189
227
  }
190
- });
191
-
192
- // src/install/dist-resolver.ts
193
- import { existsSync } from "node:fs";
194
- import { readFile } from "node:fs/promises";
195
- import { join as join2 } from "node:path";
196
- import { fileURLToPath } from "node:url";
197
- function resolvePluginDir() {
198
- const candidates = [
199
- fileURLToPath(new URL("./plugin/", import.meta.url)),
200
- // bundle: dist/index.js → dist/plugin/
201
- fileURLToPath(new URL("../../plugin/dist/", import.meta.url)),
202
- // bundle 开发: dist/index.js → packages/plugin/dist/
203
- fileURLToPath(new URL("../../../plugin/dist/", import.meta.url))
204
- // tsx 开发: src/install/ → packages/plugin/dist/
205
- ];
206
- return candidates.find((p) => existsSync(join2(p, "manifest.json"))) ?? null;
207
228
  }
208
- async function pluginManifest() {
209
- if (!PLUGIN_DIR) return null;
210
- const raw = JSON.parse(
211
- await readFile(join2(PLUGIN_DIR, "manifest.json"), "utf8")
212
- );
213
- raw.networkAccess = {
214
- allowedDomains: [`http://localhost:${config.httpPort}`, `http://127.0.0.1:${config.httpPort}`]
215
- };
216
- return Buffer.from(JSON.stringify(raw, null, 2));
229
+ var segsCache = /* @__PURE__ */ new Map();
230
+ function segsOf(template) {
231
+ let segs = segsCache.get(template);
232
+ if (!segs) {
233
+ segs = template.split("/");
234
+ segsCache.set(template, segs);
235
+ }
236
+ return segs;
217
237
  }
218
- async function readPluginFile(name) {
219
- if (!PLUGIN_DIR) return null;
220
- try {
221
- return await readFile(join2(PLUGIN_DIR, name));
222
- } catch {
223
- return null;
238
+ function matchPath(template, pathname) {
239
+ const tplSegs = segsOf(template);
240
+ const pathSegs = segsOf(pathname);
241
+ if (tplSegs.length !== pathSegs.length) return null;
242
+ const params = {};
243
+ for (let i = 0; i < tplSegs.length; i++) {
244
+ const t = tplSegs[i];
245
+ if (t.startsWith(":")) {
246
+ params[t.slice(1)] = decodeURIComponent(pathSegs[i]);
247
+ } else if (t !== pathSegs[i]) {
248
+ return null;
249
+ }
224
250
  }
251
+ return params;
225
252
  }
226
- var PLUGIN_DIR;
227
- var init_dist_resolver = __esm({
228
- "src/install/dist-resolver.ts"() {
229
- "use strict";
230
- init_config();
231
- PLUGIN_DIR = resolvePluginDir();
253
+ async function dispatchRoute(routes, req, res, url) {
254
+ for (const route of routes) {
255
+ if (route.method !== req.method) continue;
256
+ const params = matchPath(route.path, url.pathname);
257
+ if (!params) continue;
258
+ try {
259
+ await route.handler({ req, res, url, params });
260
+ } catch (err) {
261
+ if (err instanceof JsonBodyError) {
262
+ if (!res.headersSent) sendJson(res, 400, { error: "invalid json" });
263
+ } else {
264
+ console.error("[batter-pixso-mcp] route handler error:", err);
265
+ if (!res.headersSent) sendJson(res, 500, { error: "internal error" });
266
+ }
267
+ }
268
+ return true;
232
269
  }
233
- });
270
+ return false;
271
+ }
234
272
 
235
- // src/install/templates/bookmarklet.txt
236
- var bookmarklet_default;
237
- var init_bookmarklet = __esm({
238
- "src/install/templates/bookmarklet.txt"() {
239
- 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';
240
- }
241
- });
273
+ // src/agent/mcp.ts
274
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
275
+ import {
276
+ CallToolRequestSchema,
277
+ GetPromptRequestSchema,
278
+ ListPromptsRequestSchema,
279
+ ListResourceTemplatesRequestSchema,
280
+ ListToolsRequestSchema
281
+ } from "@modelcontextprotocol/sdk/types.js";
242
282
 
243
- // src/install/templates/install-page.html
244
- var install_page_default;
245
- var init_install_page = __esm({
246
- "src/install/templates/install-page.html"() {
247
- install_page_default = `<!DOCTYPE html>
248
- <html lang="zh-CN">
249
- <head>
250
- <meta charset="utf-8">
251
- <meta name="viewport" content="width=device-width, initial-scale=1">
252
- <title>Batter Pixso Bridge \xB7 \u5B89\u88C5</title>
253
- <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Crect width='24' height='24' rx='6' fill='%23575BF2'/%3E%3Cpath d='M13 4 7.5 13.5H11L10 20l6.5-9.5H12.5Z' fill='%23fff'/%3E%3C/svg%3E">
254
- <link rel="preconnect" href="https://fonts.googleapis.com">
255
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
256
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
257
- <style>
258
- :root {
259
- --ink: #1e1e1e;
260
- --sub: #6b6b6b;
261
- --faint: #a3a3a3;
262
- --line: #e8e8ea;
263
- --panel: #f7f7f8;
264
- --iris: #575bf2;
265
- --iris-strong: #4346e0;
266
- --iris-soft: #eef0fe;
267
- --ok: #14ae5c;
268
- --err: #f24822;
269
- --radius: 6px;
270
- --mono: ui-monospace, "SF Mono", Menlo, Consolas, "Cascadia Mono", monospace;
271
- }
272
- * { box-sizing: border-box; margin: 0; padding: 0; }
273
- html { -webkit-text-size-adjust: 100%; }
274
- body {
275
- background: #fff;
276
- color: var(--ink);
277
- font-family: Inter, -apple-system, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
278
- font-feature-settings: "tnum" 1, "cv05" 1;
279
- line-height: 1.5;
280
- min-height: 100vh;
281
- }
282
- ::selection { background: var(--iris-soft); }
283
- .page {
284
- max-width: 680px;
285
- margin: 0 auto;
286
- padding: clamp(48px, 10vh, 96px) 24px 64px;
287
- animation: enter 420ms cubic-bezier(0.2, 0.6, 0.2, 1) both;
288
- }
289
- @keyframes enter {
290
- from { opacity: 0; transform: translateY(8px); }
291
- to { opacity: 1; transform: none; }
292
- }
293
- a { color: var(--iris); text-decoration: none; }
294
- a:focus-visible, button:focus-visible {
295
- outline: 2px solid var(--iris);
296
- outline-offset: 2px;
297
- border-radius: 2px;
298
- }
283
+ // src/agent/guides.ts
284
+ var INTRO = `# batter-pixso-mcp \u4F7F\u7528\u6307\u5357
299
285
 
300
- /* \u2500\u2500 \u9876\u680F\uFF1A\u54C1\u724C + \u670D\u52A1\u72B6\u6001 \u2500\u2500 */
301
- .top {
302
- display: flex;
303
- align-items: center;
304
- justify-content: space-between;
305
- gap: 16px;
306
- margin-bottom: clamp(40px, 7vh, 72px);
307
- }
308
- .brand {
309
- display: flex;
310
- align-items: center;
311
- gap: 10px;
312
- font-size: 13px;
313
- font-weight: 600;
314
- letter-spacing: -0.01em;
315
- }
316
- .brand svg { display: block; }
317
- .brand .ver {
318
- font-family: var(--mono);
319
- font-weight: 400;
320
- font-size: 11px;
321
- color: var(--faint);
322
- margin-left: 2px;
323
- }
324
- .status {
325
- display: inline-flex;
326
- align-items: center;
327
- gap: 7px;
328
- padding: 5px 10px;
329
- border-radius: 999px;
330
- background: var(--panel);
331
- border: 1px solid var(--line);
332
- font-size: 11px;
333
- color: var(--sub);
334
- white-space: nowrap;
335
- }
336
- .status .dot {
337
- width: 6px;
338
- height: 6px;
339
- border-radius: 50%;
340
- background: var(--faint);
341
- flex-shrink: 0;
342
- animation: pulse 2.2s ease-in-out infinite;
343
- }
344
- @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
345
- .status.ok { color: #0d7a3e; }
346
- .status.ok .dot { background: var(--ok); }
347
- .status.err { color: var(--err); }
348
- .status.err .dot { background: var(--err); animation: none; }
286
+ ## \u5DE5\u5177\u603B\u89C8
349
287
 
350
- /* \u2500\u2500 \u4E3B\u6807\u9898\u533A \u2500\u2500 */
351
- h1 {
352
- font-size: clamp(28px, 5vw, 38px);
353
- font-weight: 650;
354
- letter-spacing: -0.025em;
355
- line-height: 1.2;
356
- margin-bottom: 12px;
357
- }
358
- .sub {
359
- font-size: 14px;
360
- color: var(--sub);
361
- max-width: 46em;
362
- line-height: 1.7;
363
- margin-bottom: clamp(40px, 7vh, 64px);
364
- }
288
+ | \u5DE5\u5177 | \u7528\u9014 |
289
+ | --- | --- |
290
+ | \`get_selected\` | \u8BFB\u753B\u5E03\u5F53\u524D\u9009\u4E2D\u56FE\u5C42\uFF08guid\u3001\u540D\u79F0\u3001\u7C7B\u578B\u3001\u5C3A\u5BF8\uFF09 |
291
+ | \`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 |
292
+ | \`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 |
293
+ | \`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 |
294
+ | \`export_image\` | \u6309 guid \u5BFC\u51FA\u56FE\u7247\u7D20\u6750\uFF08\u5207\u56FE\uFF09\uFF1A\u8282\u70B9\u5E26\u5BFC\u51FA\u6807\u8BB0\u65F6\u6309\u8BBE\u8BA1\u5E08\u8BBE\u7F6E\u9010\u4EFD\u5BFC\u51FA\uFF0C\u65E0\u6807\u8BB0\u6309\u53C2\u6570\u515C\u5E95 |
295
+ | \`list_sessions\` | \u5217\u51FA\u5168\u90E8\u63D2\u4EF6\u4F1A\u8BDD\uFF08\u591A\u6587\u4EF6\u573A\u666F\u8DEF\u7531\u7528\uFF09 |
365
296
 
366
- /* \u2500\u2500 \u6B65\u9AA4\u5217\u8868\uFF1A\u53D1\u4E1D\u7EBF\u5206\u9694 \u2500\u2500 */
367
- .steps { border-top: 1px solid var(--line); }
368
- .step {
369
- display: grid;
370
- grid-template-columns: 44px 1fr;
371
- gap: 4px 16px;
372
- padding: 26px 0 28px;
373
- border-bottom: 1px solid var(--line);
374
- }
375
- .num {
376
- grid-row: 1 / span 2;
377
- font-family: var(--mono);
378
- font-size: 12px;
379
- color: var(--faint);
380
- padding-top: 3px;
381
- font-variant-numeric: tabular-nums;
382
- }
383
- .step-head {
384
- display: flex;
385
- align-items: center;
386
- gap: 10px;
387
- flex-wrap: wrap;
388
- }
389
- .step-title {
390
- font-size: 14px;
391
- font-weight: 600;
392
- letter-spacing: -0.01em;
393
- }
394
- .tag {
395
- font-size: 11px;
396
- font-weight: 500;
397
- color: var(--iris);
398
- background: var(--iris-soft);
399
- padding: 2px 8px;
400
- border-radius: 999px;
401
- }
402
- .step-body { grid-column: 2; }
403
- .note {
404
- font-size: 12.5px;
405
- color: var(--sub);
406
- line-height: 1.75;
407
- max-width: 60em;
408
- }
409
- .note strong { color: var(--ink); font-weight: 600; }
410
- code {
411
- font-family: var(--mono);
412
- background: var(--panel);
413
- border: 1px solid var(--line);
414
- color: var(--ink);
415
- padding: 0.5px 5px;
416
- border-radius: 4px;
417
- font-size: 11px;
418
- }
419
- .actions { display: flex; align-items: center; gap: 12px; margin: 14px 0 10px; }
297
+ ## \u94FE\u63A5\u5373\u8F93\u5165\uFF08\u9AD8\u9891\u573A\u666F\uFF09
420
298
 
421
- /* \u4E3B\u6309\u94AE\uFF1A\u53EF\u62D6\u62FD\u7684\u4E66\u7B7E */
422
- .bm {
423
- display: inline-flex;
424
- align-items: center;
425
- gap: 8px;
426
- padding: 9px 18px;
427
- background: var(--iris);
428
- color: #fff;
429
- border-radius: var(--radius);
430
- font-size: 13px;
431
- font-weight: 600;
432
- letter-spacing: -0.01em;
433
- cursor: grab;
434
- box-shadow: 0 1px 2px rgba(30, 30, 30, 0.08);
435
- transition: background 140ms ease, transform 140ms ease;
436
- user-select: none;
437
- }
438
- .bm:hover { background: var(--iris-strong); }
439
- .bm:active { cursor: grabbing; transform: scale(0.985); }
440
- .drag-hint {
441
- font-size: 12px;
442
- color: var(--faint);
443
- }
299
+ \u7528\u6237\u63D0\u4F9B Pixso \u94FE\u63A5\u65F6\uFF08\u5B98\u65B9 \`pixso.cn\` \u6216\u79C1\u6709\u5316\u90E8\u7F72\uFF0C\u4EFB\u610F\u57DF\u540D\uFF09\uFF0C\u94FE\u63A5\u672C\u8EAB\u5C31\u662F\u4EFB\u52A1\u8F93\u5165\u2014\u2014
300
+ \u63D0\u53D6 \`item-id\` \u67E5\u8BE2\u53C2\u6570 \u2192 URL \u89E3\u7801 \u2192 \u5F97\u5230\u8282\u70B9 guid \u76F4\u63A5\u8C03\u7528\uFF0C\u65E0\u9700\u5148\u95EE\u7528\u6237\u9009\u4E2D\u4EC0\u4E48\u3002
301
+ \u89C4\u5219\u5168\u6587\u89C1 \`batter://guides/design-to-code\` \u786C\u6027\u89C4\u5219\u7B2C 2 \u6761\u3002
444
302
 
445
- /* \u6B21\u7EA7\u6309\u94AE */
446
- .btn {
447
- display: inline-flex;
448
- align-items: center;
449
- gap: 7px;
450
- padding: 8px 14px;
451
- border: 1px solid var(--line);
452
- border-radius: var(--radius);
453
- background: #fff;
454
- color: var(--ink);
455
- font-size: 13px;
456
- font-weight: 500;
457
- letter-spacing: -0.01em;
458
- text-decoration: none;
459
- transition: border-color 140ms ease, background 140ms ease;
460
- }
461
- .btn:hover { border-color: #d2d2d6; background: var(--panel); }
462
- .btn svg { display: block; color: var(--sub); }
303
+ ## \u591A\u6587\u4EF6\u573A\u666F
463
304
 
464
- /* MCP \u914D\u7F6E\u4EE3\u7801\u5757 */
465
- .cfg {
466
- position: relative;
467
- margin: 14px 0 10px;
468
- }
469
- pre {
470
- background: var(--panel);
471
- border: 1px solid var(--line);
472
- border-radius: 8px;
473
- padding: 14px 16px;
474
- font-family: var(--mono);
475
- font-size: 12px;
476
- line-height: 1.7;
477
- color: #3c3c3c;
478
- overflow-x: auto;
479
- }
480
- .copy {
481
- position: absolute;
482
- top: 8px;
483
- right: 8px;
484
- padding: 4px 10px;
485
- font-family: inherit;
486
- font-size: 11px;
487
- font-weight: 500;
488
- color: var(--sub);
489
- background: #fff;
490
- border: 1px solid var(--line);
491
- border-radius: var(--radius);
492
- cursor: pointer;
493
- transition: color 140ms ease, border-color 140ms ease;
494
- }
495
- .copy:hover { color: var(--ink); border-color: #d2d2d6; }
496
- .copy.done { color: var(--ok); border-color: var(--ok); }
305
+ \u6BCF\u4E2A\u8FD0\u884C\u63D2\u4EF6\u7684\u8BBE\u8BA1\u6587\u4EF6\u662F\u4E00\u4E2A\u4F1A\u8BDD\u3002\u591A\u4E2A\u6587\u4EF6\u540C\u65F6\u6253\u5F00\u65F6\uFF1A
306
+ \u5148\u7528 \`list_sessions\` \u786E\u8BA4\u76EE\u6807\u6587\u4EF6\uFF0C\u518D\u628A\u8FD4\u56DE\u7684 \`sessionId\` \u6216 \`fileKey\`
307
+ \u4F5C\u4E3A\u5176\u4ED6\u5DE5\u5177\u7684 \`target\` \u53C2\u6570\u4F20\u5165\uFF1B\u4E0D\u4F20 \`target\` \u65F6\u8DEF\u7531\u5230\u6700\u8FD1\u6D3B\u8DC3\u7684\u6587\u4EF6\u3002
497
308
 
498
- .footer {
499
- margin-top: 40px;
500
- font-size: 11px;
501
- color: var(--faint);
502
- font-family: var(--mono);
503
- display: flex;
504
- gap: 8px;
505
- flex-wrap: wrap;
506
- }
309
+ **\u4F1A\u8BDD\u89E3\u6790\u4E0E\u4FE1\u5C01**\uFF1A\u63D2\u4EF6\u7C7B\u5DE5\u5177\u7684\u8FD4\u56DE\u7EDF\u4E00\u4E3A \`{sessionId, data}\`\u2014\u2014\`sessionId\` \u662F\u672C\u6B21\u5B9E\u9645\u670D\u52A1\u7684\u4F1A\u8BDD
310
+ \uFF08\u6BCF\u6B21\u8C03\u7528\u7ECF ping/pong \u63A2\u6D3B\u91CD\u65B0\u786E\u8BA4\uFF0C\u4E0D\u662F\u53EF\u957F\u671F\u9489\u4F4F\u7684\u56FA\u5B9A\u6807\u8BC6\uFF09\u3002\u63A8\u8350 \`target\` \u7528 \`fileKey\`\uFF1A
311
+ \u6BCF\u6B21\u8C03\u7528\u6309 fileKey \u91CD\u65B0\u5BFB\u5740\u2014\u2014\u540C fileKey \u591A\u4F1A\u8BDD\u65F6\u4F18\u5148\u4E0A\u4E00\u6B21\u4F7F\u7528\u7684\uFF08\u4EB2\u548C\uFF09\uFF0C\u6587\u4EF6\u5173\u95ED\u91CD\u5F00\u3001\u63D2\u4EF6\u91CD\u8DD1\u540E
312
+ \u81EA\u52A8\u89E3\u6790\u5230\u5F53\u524D\u5B58\u6D3B\u5B9E\u4F8B\uFF0Cagent \u65E0\u9700\u5173\u5FC3 sessionId \u7684\u5931\u6548\u3002
507
313
 
508
- @media (max-width: 560px) {
509
- .step { grid-template-columns: 32px 1fr; gap: 4px 10px; }
510
- .top { align-items: flex-start; flex-direction: column; gap: 12px; }
511
- }
512
- @media (prefers-reduced-motion: reduce) {
513
- .page { animation: none; }
514
- .status .dot { animation: none; }
515
- .bm, .btn, .copy { transition: none; }
516
- }
517
- </style>
518
- </head>
519
- <body>
520
- <div class="page">
521
- <header class="top">
522
- <div class="brand">
523
- <svg width="22" height="22" viewBox="0 0 24 24" aria-hidden="true"><rect width="24" height="24" rx="6" fill="#575BF2"/><path d="M13 4 7.5 13.5H11L10 20l6.5-9.5H12.5Z" fill="#fff"/></svg>
524
- Batter Pixso Bridge
525
- <span class="ver">v{{ version }}</span>
526
- </div>
527
- <div class="status" id="status">
528
- <span class="dot"></span><span id="status-text">\u6B63\u5728\u8FDE\u63A5\u670D\u52A1\u2026</span>
529
- </div>
530
- </header>
314
+ \u8BE6\u7EC6\u5DE5\u4F5C\u6D41\u89C1\u8D44\u6E90 \`batter://guides/design-to-code\`\u3002
315
+ `;
316
+ var DESIGN_TO_CODE = `# \u8BBE\u8BA1\u8F6C\u4EE3\u7801\u5DE5\u4F5C\u6D41
531
317
 
532
- <h1>\u628A\u8BBE\u8BA1\u7A3F\u4EA4\u7ED9 AI</h1>
533
- <p class="sub">\u4E00\u6B21\u5B89\u88C5\uFF0C\u8BA9 Claude\u3001Cursor \u7B49 AI \u7F16\u7A0B\u52A9\u624B\u76F4\u63A5\u8BFB\u53D6 Pixso / MasterGo \u753B\u5E03\u7684\u7ED3\u6784\u3001\u6837\u5F0F\u4E0E\u622A\u56FE\u3002\u6309\u4E0B\u9762\u56DB\u6B65\u5B8C\u6210\u8BBE\u7F6E\u3002</p>
318
+ ## \u3010\u786C\u6027\u89C4\u5219 \xB7 \u8FDD\u53CD\u5373\u8FD4\u5DE5\u3011
534
319
 
535
- <div class="steps">
536
- <div class="step">
537
- <span class="num">01</span>
538
- <div class="step-head">
539
- <span class="step-title">\u6DFB\u52A0\u4E66\u7B7E</span>
540
- <span class="tag">\u7F51\u9875\u7248 \xB7 \u63A8\u8350</span>
541
- </div>
542
- <div class="step-body">
543
- <div class="actions">
544
- <a class="bm" href="{{ href }}">Batter Bridge</a>
545
- <span class="drag-hint">\u2190 \u628A\u8FD9\u4E2A\u6309\u94AE\u62D6\u5230\u6D4F\u89C8\u5668\u4E66\u7B7E\u680F</span>
546
- </div>
547
- <p class="note">
548
- <strong>\u53EA\u9700\u62D6\u62FD\u4E00\u6B21\u3002</strong>\u4EE5\u540E\u6253\u5F00 Pixso / MasterGo \u8BBE\u8BA1\u6587\u4EF6\u65F6\uFF0C\u70B9\u4E00\u4E0B\u4E66\u7B7E\u680F\u91CC\u7684\u300CBatter Bridge\u300D\uFF0C\u63D2\u4EF6\u5C31\u4F1A\u6CE8\u5165\u5F53\u524D\u9875\u9762\u3002
549
- \u4E66\u7B7E\u6307\u5411\u672C\u673A\u670D\u52A1 <code>{{ base }}</code>\uFF0C\u670D\u52A1\u91CD\u542F\u540E\u4F9D\u7136\u6709\u6548\u3002\u770B\u4E0D\u5230\u4E66\u7B7E\u680F\uFF1F\u6309 <code>Ctrl + Shift + B</code> \u6253\u5F00\u3002
550
- </p>
551
- </div>
552
- </div>
320
+ 1. **\u8D44\u6E90\u662F\u672C\u673A\u6587\u4EF6\uFF0C\u6309\u9700\u53D6\u7528**\uFF1ADSL \u4E0E\u622A\u56FE\u7ED3\u679C\u4E2D\u7684 \`img\` \u5B57\u6BB5\u662F\u4EA7\u7269**\u6587\u4EF6\u540D**
321
+ \uFF08\u5982 \`"ab12cd34.svg"\`\uFF09\uFF0C\u5B8C\u6574\u8DEF\u5F84 = \u4EA7\u7269\u76EE\u5F55 + \u6587\u4EF6\u540D\uFF08\u672C\u673A\u4EA7\u7269\u76EE\u5F55\uFF1A\`${ASSETS_DIR}\`\uFF09\u3002\u4EA7\u7269\u4E0E\u670D\u52A1\u540C\u673A\uFF0C\u76F4\u63A5\u8BFB\u6587\u4EF6\u7CFB\u7EDF\uFF1A
322
+ - **\u622A\u56FE\u7B49\u89C6\u89C9\u53C2\u7167**\uFF1A\u7528 Read \u5DE5\u5177\u76F4\u63A5\u8BFB\u8BE5\u8DEF\u5F84\u67E5\u770B\uFF0C\u65E0\u9700\u590D\u5236\u3002
323
+ - **\u8981\u5199\u8FDB\u4EA4\u4ED8\u4EE3\u7801\u7684\u56FE\u6807/\u56FE\u7247**\uFF1A**\u5FC5\u987B**\u5148\u590D\u5236\u5230\u9879\u76EE\u8D44\u6E90\u76EE\u5F55
324
+ \uFF08PowerShell \`Copy-Item\` / \u7C7B Unix \`cp\`\uFF09\uFF0C\u518D\u4EE5\u672C\u5730\u76F8\u5BF9\u8DEF\u5F84\u5F15\u7528
325
+ \uFF08\`<img src>\`\u3001CSS \`background-image\` \u7B49\uFF09\u3002\u4EA7\u7269\u6587\u4EF6\u540D\u662F\u5185\u5BB9 hash\uFF0C\u6CA1\u6709\u8BED\u4E49\u2014\u2014
326
+ **\u590D\u5236\u65F6\u6309\u8282\u70B9\u7528\u9014\u81EA\u884C\u547D\u540D\u8BED\u4E49\u5316\u6587\u4EF6\u540D**\uFF08\u5982 \`icon-arrow.svg\`\u3001\`hero-banner.png\`\uFF1B
327
+ \`export_image\` \u8FD4\u56DE\u7684 \`suffix\` \u5982 \`@2x\` \u5EFA\u8BAE\u4FDD\u7559\uFF09\uFF0C\u4E25\u7981\u628A hash \u6587\u4EF6\u540D\u5E26\u8FDB\u4EA4\u4ED8\u4EE3\u7801\u3002
328
+ \u4E25\u7981\u76F4\u63A5\u5F15\u7528\u4EA7\u7269\u76EE\u5F55\u7684\u7EDD\u5BF9\u8DEF\u5F84\uFF08\u4EA7\u7269\u9ED8\u8BA4 7 \u5929\u540E\u81EA\u52A8\u6E05\u626B\uFF0C\u5F15\u7528\u4F1A\u5931\u6548\uFF09\uFF1B
329
+ \u4E25\u7981\u79FB\u52A8\u6216\u5220\u9664\u4EA7\u7269\u76EE\u5F55\u4E0B\u7684\u6587\u4EF6\uFF08\u591A\u4F1A\u8BDD\u5171\u4EAB\uFF0C\u6E05\u626B\u7531\u670D\u52A1\u7EDF\u4E00\u8D1F\u8D23\uFF09\u3002
330
+ 2. **Pixso \u94FE\u63A5\u5FC5\u987B\u63D0\u53D6 \`item-id\` \u5F53 guid**\uFF1A\u4EFB\u610F\u57DF\u540D\u90FD\u6709\u6548\u2014\u2014\u5B98\u65B9 \`pixso.cn\` \u6216\u79C1\u6709\u5316\u90E8\u7F72\u5982
331
+ \`https://pixso.ky-tech.com.cn/app/design/<\u6587\u4EF6>?item-id=1%3A2\`\uFF08\u4E5F\u53EF\u80FD\u662F \`/app/editor/\` \u8DEF\u5F84\uFF09\u3002
332
+ \u4ECE \`item-id\` \u67E5\u8BE2\u53C2\u6570\u63D0\u53D6 guid \u5E76 URL \u89E3\u7801\uFF08\`1%3A2\` \u2192 \`"1:2"\`\uFF09\u540E\u4F5C\u4E3A \`guid\` \u4F20\u5165\u3002
333
+ \u4E25\u7981\u628A\u6574\u4E2A URL \u5F53 guid \u4F20\uFF1B\u65E0\u8BBA\u57DF\u540D\u662F\u5426\u8BA4\u8BC6\uFF0C\u90FD\u6309\u6B64\u89C4\u5219\u5904\u7406\u3002
553
334
 
554
- <div class="step">
555
- <span class="num">02</span>
556
- <div class="step-head">
557
- <span class="step-title">\u5B89\u88C5\u5230 Pixso \u5BA2\u6237\u7AEF</span>
558
- </div>
559
- <div class="step-body">
560
- <div class="actions">
561
- <a class="btn" href="/plugin.zip" download="batter-pixso-bridge.zip">
562
- <svg width="12" height="12" viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12"/><path d="m7 11 5 5 5-5"/><path d="M4 21h16"/></svg>
563
- \u4E0B\u8F7D\u63D2\u4EF6\u5305\uFF08.zip\uFF09
564
- </a>
565
- </div>
566
- <p class="note">\u89E3\u538B\u540E\uFF0C\u5728\u5BA2\u6237\u7AEF\u7684\u8BBE\u8BA1\u6587\u4EF6\u4E2D\uFF1A\u53F3\u952E \u2192 \u63D2\u4EF6 \u2192 \u5F00\u53D1\u63D2\u4EF6 \u2192 \u5BFC\u5165 <code>manifest.json</code>\u3002</p>
567
- </div>
568
- </div>
335
+ ## \u63A8\u8350\u6B65\u9AA4
569
336
 
570
- <div class="step">
571
- <span class="num">03</span>
572
- <div class="step-head">
573
- <span class="step-title">\u914D\u7F6E AI \u5BA2\u6237\u7AEF</span>
574
- </div>
575
- <div class="step-body">
576
- <div class="cfg">
577
- <pre id="cfg">{
578
- "mcpServers": {
579
- "batter-pixso": {
580
- "command": "npx",
581
- "args": ["-y", "batter-pixso-mcp"],
582
- "env": { "HTTP_PORT": "{{ httpPort }}" }
583
- }
584
- }
585
- }</pre>
586
- <button class="copy" id="copy" type="button">\u590D\u5236</button>
587
- </div>
588
- <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>
589
- </div>
590
- </div>
337
+ 1. **\u786E\u8BA4\u76EE\u6807\u8282\u70B9**
338
+ - \u7528\u6237\u7ED9\u4E86 Pixso \u94FE\u63A5\uFF1A\u6309\u4E0A\u65B9\u786C\u6027\u89C4\u5219\u7B2C 2 \u6761\u63D0\u53D6 guid\uFF0C\u76F4\u63A5\u7EE7\u7EED\u3002
339
+ - \u7528\u6237\u660E\u786E\u8BF4\u4E86"\u8FD9\u4E2A/\u6211\u9009\u4E2D\u7684"\uFF1A\u8C03 \`get_selected\` \u62FF guid\uFF0C\u76F4\u63A5\u7EE7\u7EED\u3002
340
+ - \u5176\u4F59\u4F9D\u8D56\u5F53\u524D\u9009\u533A\u7684\u573A\u666F\uFF1A**\u4E25\u7981\u9690\u5F0F\u4F7F\u7528\u9009\u533A guid**\u2014\u2014\u8C03 \`get_selected\` \u540E
341
+ \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\u518D\u7EE7\u7EED\u3002
342
+ \`get_node_dsl\` / \`get_screenshot\` \u7684 guid \u7F3A\u7701\u56DE\u9000\u540C\u6837\u9002\u7528\u672C\u89C4\u5219\u3002
343
+ 2. **\u53D6\u7ED3\u6784**\uFF1A\u63D2\u4EF6\u7C7B\u5DE5\u5177\u8FD4\u56DE\u7EDF\u4E00\u4E3A \`{sessionId, data}\` \u4FE1\u5C01\u2014\u2014DSL \u5728 \`data\` \u5B57\u6BB5\u91CC\u3002
344
+ \`get_node_dsl(guid)\` \u62FF\u7CBE\u7B80 DSL\u3002
345
+ **\u9996\u6B21\u63A5\u89E6\u4E00\u4E2A\u8BBE\u8BA1\u7A3F/\u9875\u9762**\uFF08\u8FD8\u6CA1\u6709\u76EE\u6807\u8282\u70B9\u7684\u4E0A\u4E0B\u6587\u65F6\uFF09\uFF1A\u5148 \`get_metadata(guid)\`
346
+ \u62FF\u5168\u6811\u9AA8\u67B6\uFF08\u4EC5 id/name/type/kids\uFF0C\u65E0\u6837\u5F0F/\u5750\u6807/\u8D44\u6E90\uFF0C\u4F53\u79EF\u6781\u5C0F\uFF09+ \`get_screenshot(guid)\` \u62FF\u89C6\u89C9\u5168\u8C8C\uFF0C
347
+ \u636E\u6B64\u5B9A\u4F4D\u76EE\u6807\u8282\u70B9\uFF0C\u518D\u5BF9\u76EE\u6807\u6B63\u5E38\u53D6 DSL\u3002\u9AA8\u67B6\u4E2D ICON/IMAGE \u53F6\u5B50\u65E0 \`img\`\u2014\u2014\u9700\u8D44\u6E90\u65F6\u6309\u5176 \`id\` \u4E0B\u94BB\u5168\u91CF DSL\u3002
348
+ 3. **\u53D6\u89C6\u89C9\u53C2\u7167**\uFF1A\`get_screenshot(guid)\` \u62FF\u622A\u56FE\u6587\u4EF6\u540D\uFF0C\u6309\u786C\u6027\u89C4\u5219\u7B2C 1 \u6761\u62FC\u8DEF\u5F84\u540E\u7528 Read \u76F4\u8BFB\uFF0C\u4E0E DSL \u5BF9\u7167\u7406\u89E3\u89C6\u89C9\u5C42\u7EA7\u3002
349
+ 4. **\u751F\u6210\u4EE3\u7801**\uFF1A\u6309\u4E0B\u6587 DSL \u8BED\u4E49\u6620\u5C04\u5230\u76EE\u6807\u6846\u67B6\u3002
350
+ 5. **\u6821\u9A8C**\uFF1A\u751F\u6210\u540E\u518D\u53D6\u4E00\u6B21\u622A\u56FE\u5BF9\u6BD4\uFF0C\u6709\u504F\u5DEE\u5C31\u4FEE\u6B63\u540E\u518D\u6BD4\u4E00\u8F6E\u3002
591
351
 
592
- <div class="step">
593
- <span class="num">04</span>
594
- <div class="step-head">
595
- <span class="step-title">\u5F00\u59CB\u4F7F\u7528</span>
596
- </div>
597
- <div class="step-body">
598
- <p class="note" style="margin-top:10px">
599
- \u63D2\u4EF6\u8FD0\u884C\u540E\uFF0C\u5728\u753B\u5E03\u4E2D\u9009\u4E2D\u56FE\u5C42\uFF0C\u5BF9 AI \u8BF4\u300C\u5B9E\u73B0\u8FD9\u4E2A\u8BBE\u8BA1\u300D\u3002
600
- AI \u4F1A\u81EA\u52A8\u8C03\u7528 <code>get_node_dsl</code>\u3001<code>get_screenshot</code> \u7B49\u5DE5\u5177\u8BFB\u53D6\u8BBE\u8BA1\u5E76\u751F\u6210\u4EE3\u7801\u3002
601
- </p>
602
- </div>
603
- </div>
604
- </div>
352
+ ## DSL \u5B57\u6BB5\u8BED\u4E49\uFF08\u7D27\u51D1\u683C\u5F0F\uFF09
605
353
 
606
- <div class="footer">
607
- <span>batter-pixso-mcp</span>
608
- <span>\xB7</span>
609
- <span>v{{ version }}</span>
610
- <span>\xB7</span>
611
- <span>{{ base }}</span>
612
- </div>
613
- </div>
614
- <script>
615
- fetch("/health").then(function (r) { return r.json(); }).then(function (h) {
616
- var el = document.getElementById("status");
617
- el.className = "status ok";
618
- document.getElementById("status-text").textContent =
619
- "\u670D\u52A1\u8FD0\u884C\u4E2D \xB7 \u63D2\u4EF6 " + h.pluginsOnline + " \u5728\u7EBF";
620
- }).catch(function () {
621
- var el = document.getElementById("status");
622
- el.className = "status err";
623
- document.getElementById("status-text").textContent = "\u670D\u52A1\u672A\u8FDE\u63A5";
624
- });
625
-
626
- var copyBtn = document.getElementById("copy");
627
- copyBtn.addEventListener("click", function () {
628
- var text = document.getElementById("cfg").textContent;
629
- function done() {
630
- copyBtn.textContent = "\u5DF2\u590D\u5236";
631
- copyBtn.className = "copy done";
632
- setTimeout(function () {
633
- copyBtn.textContent = "\u590D\u5236";
634
- copyBtn.className = "copy";
635
- }, 1600);
636
- }
637
- if (navigator.clipboard && navigator.clipboard.writeText) {
638
- navigator.clipboard.writeText(text).then(done, done);
639
- } else {
640
- var ta = document.createElement("textarea");
641
- ta.value = text;
642
- document.body.appendChild(ta);
643
- ta.select();
644
- try { document.execCommand("copy"); } catch (e) {}
645
- document.body.removeChild(ta);
646
- done();
647
- }
648
- });
649
- </script>
650
- </body>
651
- </html>
652
- `;
653
- }
654
- });
655
-
656
- // src/install/template.ts
657
- function escapeHtml(s) {
658
- return s.replace(/[&<>"']/g, (c) => HTML_ESCAPES[c]);
659
- }
660
- function escapeJsString(s) {
661
- return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\r/g, "\\r").replace(/\n/g, "\\n");
662
- }
663
- function renderTemplate(tpl, vars, escape = escapeHtml) {
664
- const lookup = (key) => {
665
- const v = vars[key];
666
- if (v === void 0) throw new Error(`\u6A21\u677F\u53D8\u91CF\u7F3A\u5931\uFF1A{{${key}}}`);
667
- return String(v);
668
- };
669
- return tpl.replace(/\{\{\{\s*(\w+)\s*\}\}\}/g, (_, key) => lookup(key)).replace(/\{\{\s*(\w+)\s*\}\}/g, (_, key) => escape(lookup(key)));
670
- }
671
- function loadTemplate(name) {
672
- return TEMPLATES[name];
673
- }
674
- var HTML_ESCAPES, TEMPLATES;
675
- var init_template = __esm({
676
- "src/install/template.ts"() {
677
- "use strict";
678
- init_bookmarklet();
679
- init_install_page();
680
- HTML_ESCAPES = {
681
- "&": "&amp;",
682
- "<": "&lt;",
683
- ">": "&gt;",
684
- '"': "&quot;",
685
- "'": "&#39;"
686
- };
687
- TEMPLATES = {
688
- "bookmarklet.txt": bookmarklet_default,
689
- "install-page.html": install_page_default
690
- };
691
- }
692
- });
693
-
694
- // src/install/page.ts
695
- function bookmarkletHref(base) {
696
- const source = renderTemplate(loadTemplate("bookmarklet.txt"), { base }, escapeJsString);
697
- return `javascript:${encodeURIComponent(source)}`;
698
- }
699
- function renderInstallPage(base, httpPort) {
700
- return renderTemplate(loadTemplate("install-page.html"), {
701
- version: VERSION,
702
- href: bookmarkletHref(base),
703
- base,
704
- httpPort
705
- });
706
- }
707
- var init_page = __esm({
708
- "src/install/page.ts"() {
709
- "use strict";
710
- init_config();
711
- init_template();
712
- }
713
- });
714
-
715
- // src/install/zip.ts
716
- import { Buffer as Buffer2 } from "node:buffer";
717
- function crc32(buf) {
718
- let crc = 4294967295;
719
- for (const byte of buf) crc = CRC_TABLE[(crc ^ byte) & 255] ^ crc >>> 8;
720
- return (crc ^ 4294967295) >>> 0;
721
- }
722
- function buildZip(entries) {
723
- const chunks = [];
724
- const central = [];
725
- let offset = 0;
726
- for (const { name, data } of entries) {
727
- const nameBuf = Buffer2.from(name, "utf8");
728
- const crc = crc32(data);
729
- const local = Buffer2.alloc(30);
730
- local.writeUInt32LE(67324752, 0);
731
- local.writeUInt16LE(20, 4);
732
- local.writeUInt16LE(2048, 6);
733
- local.writeUInt16LE(0, 8);
734
- local.writeUInt16LE(0, 10);
735
- local.writeUInt16LE(33, 12);
736
- local.writeUInt32LE(crc, 14);
737
- local.writeUInt32LE(data.length, 18);
738
- local.writeUInt32LE(data.length, 22);
739
- local.writeUInt16LE(nameBuf.length, 26);
740
- local.writeUInt16LE(0, 28);
741
- chunks.push(local, nameBuf, data);
742
- const cen = Buffer2.alloc(46);
743
- cen.writeUInt32LE(33639248, 0);
744
- cen.writeUInt16LE(20, 4);
745
- cen.writeUInt16LE(20, 6);
746
- cen.writeUInt16LE(2048, 8);
747
- cen.writeUInt16LE(0, 10);
748
- cen.writeUInt16LE(0, 12);
749
- cen.writeUInt16LE(33, 14);
750
- cen.writeUInt32LE(crc, 16);
751
- cen.writeUInt32LE(data.length, 20);
752
- cen.writeUInt32LE(data.length, 24);
753
- cen.writeUInt16LE(nameBuf.length, 28);
754
- cen.writeUInt16LE(0, 30);
755
- cen.writeUInt16LE(0, 32);
756
- cen.writeUInt16LE(0, 34);
757
- cen.writeUInt16LE(0, 36);
758
- cen.writeUInt32LE(0, 38);
759
- cen.writeUInt32LE(offset, 42);
760
- central.push(Buffer2.concat([cen, nameBuf]));
761
- offset += 30 + nameBuf.length + data.length;
762
- }
763
- const centralBuf = Buffer2.concat(central);
764
- const eocd = Buffer2.alloc(22);
765
- eocd.writeUInt32LE(101010256, 0);
766
- eocd.writeUInt16LE(entries.length, 8);
767
- eocd.writeUInt16LE(entries.length, 10);
768
- eocd.writeUInt32LE(centralBuf.length, 12);
769
- eocd.writeUInt32LE(offset, 16);
770
- return Buffer2.concat([...chunks, centralBuf, eocd]);
771
- }
772
- var CRC_TABLE;
773
- var init_zip = __esm({
774
- "src/install/zip.ts"() {
775
- "use strict";
776
- CRC_TABLE = (() => {
777
- const table = new Uint32Array(256);
778
- for (let n = 0; n < 256; n++) {
779
- let c = n;
780
- for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
781
- table[n] = c >>> 0;
782
- }
783
- return table;
784
- })();
785
- }
786
- });
787
-
788
- // src/routes/install.ts
789
- async function handleInstall(req, res, url) {
790
- if (req.method !== "GET") return false;
791
- const path = url.pathname;
792
- if (path === "/") {
793
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
794
- res.end(renderInstallPage(`http://127.0.0.1:${config.httpPort}/`, config.httpPort));
795
- return true;
796
- }
797
- if (path === "/manifest.json") {
798
- const buf = await pluginManifest().catch(() => null);
799
- if (!buf) {
800
- sendJson(res, 404, { error: NO_DIST });
801
- return true;
802
- }
803
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" });
804
- res.end(buf);
805
- return true;
806
- }
807
- if (path === "/code.js" || path === "/ui.html") {
808
- const buf = await readPluginFile(path.slice(1));
809
- if (!buf) {
810
- sendJson(res, 404, { error: PLUGIN_DIR ? "not found" : NO_DIST });
811
- return true;
812
- }
813
- res.writeHead(200, {
814
- "Content-Type": path.endsWith(".js") ? "text/javascript; charset=utf-8" : "text/html; charset=utf-8",
815
- "Cache-Control": "no-store"
816
- });
817
- res.end(buf);
818
- return true;
819
- }
820
- if (path === "/plugin.zip") {
821
- if (!PLUGIN_DIR) {
822
- sendJson(res, 404, { error: NO_DIST });
823
- return true;
824
- }
825
- try {
826
- const [manifest, code, ui] = await Promise.all([
827
- pluginManifest(),
828
- readPluginFile("code.js"),
829
- readPluginFile("ui.html")
830
- ]);
831
- const zip = buildZip([
832
- { name: "manifest.json", data: manifest },
833
- { name: "code.js", data: code },
834
- { name: "ui.html", data: ui }
835
- ]);
836
- res.writeHead(200, {
837
- "Content-Type": "application/zip",
838
- "Content-Disposition": 'attachment; filename="batter-pixso-bridge.zip"'
839
- });
840
- res.end(zip);
841
- } catch {
842
- sendJson(res, 500, { error: "zip build failed" });
843
- }
844
- return true;
845
- }
846
- return false;
847
- }
848
- var NO_DIST;
849
- var init_install = __esm({
850
- "src/routes/install.ts"() {
851
- "use strict";
852
- init_config();
853
- init_dist_resolver();
854
- init_page();
855
- init_zip();
856
- init_http_utils();
857
- NO_DIST = "plugin dist not found\uFF08\u8FD0\u884C npm run build \u751F\u6210\uFF09";
858
- }
859
- });
860
-
861
- // src/guides.ts
862
- var INTRO, DESIGN_TO_CODE, GUIDES;
863
- var init_guides = __esm({
864
- "src/guides.ts"() {
865
- "use strict";
866
- INTRO = `# batter-pixso-mcp \u4F7F\u7528\u6307\u5357
867
-
868
- \u672C\u670D\u52A1\u628A Pixso \u8BBE\u8BA1\u7A3F\u80FD\u529B\u4EE5 MCP \u5DE5\u5177\u66B4\u9732\u7ED9 AI\u3002\u6570\u636E\u94FE\u8DEF\uFF1A
869
-
870
- \`\`\`
871
- AI \u5BA2\u6237\u7AEF \u2500\u2500MCP(stdio/HTTP)\u2500\u2500\u25B6 batter-pixso-mcp \u2500\u2500HTTP \u957F\u8F6E\u8BE2\u2500\u2500\u25B6 Pixso \u63D2\u4EF6(\u8BBE\u8BA1\u6587\u4EF6\u5185)
872
- \`\`\`
873
-
874
- ## \u524D\u63D0
875
-
876
- 1. batter-pixso-mcp \u670D\u52A1\u5DF2\u542F\u52A8\uFF08agent \u7684 MCP \u914D\u7F6E\u4F1A\u81EA\u52A8\u62C9\u8D77\uFF09\u3002
877
- 2. Pixso \u5BA2\u6237\u7AEF\u4E2D\u5DF2\u5BFC\u5165\u5E76\u8FD0\u884C **Batter Pixso Bridge** \u63D2\u4EF6\uFF0C\u63D2\u4EF6 UI \u663E\u793A\u300C\u5DF2\u8FDE\u63A5\u672C\u5730\u670D\u52A1\u300D\u3002
878
- 3. \u76EE\u6807\u8BBE\u8BA1\u6587\u4EF6\u5904\u4E8E\u6253\u5F00\u72B6\u6001\u3002
879
-
880
- ## \u5DE5\u5177\u603B\u89C8
881
-
882
- | \u5DE5\u5177 | \u7528\u9014 |
883
- | --- | --- |
884
- | \`get_selected\` | \u8BFB\u753B\u5E03\u5F53\u524D\u9009\u4E2D\u56FE\u5C42\uFF08guid\u3001\u540D\u79F0\u3001\u7C7B\u578B\u3001\u5C3A\u5BF8\uFF09 |
885
- | \`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 |
886
- | \`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 |
887
- | \`export_image\` | \u6309 guid \u5BFC\u51FA\u56FE\u7247\u7D20\u6750\uFF08\u5207\u56FE\uFF09\uFF1A\u8282\u70B9\u5E26\u5BFC\u51FA\u6807\u8BB0\u65F6\u6309\u8BBE\u8BA1\u5E08\u8BBE\u7F6E\u9010\u4EFD\u5BFC\u51FA\uFF0C\u65E0\u6807\u8BB0\u6309\u53C2\u6570\u515C\u5E95 |
888
- | \`list_sessions\` | \u5217\u51FA\u5168\u90E8\u63D2\u4EF6\u4F1A\u8BDD\uFF08\u591A\u6587\u4EF6\u573A\u666F\u8DEF\u7531\u7528\uFF09 |
889
-
890
- ## \u94FE\u63A5\u5373\u8F93\u5165\uFF08\u9AD8\u9891\u573A\u666F\uFF09
891
-
892
- \u7528\u6237\u63D0\u4F9B Pixso \u94FE\u63A5\u65F6\uFF08\u5B98\u65B9 \`pixso.cn\` \u6216\u79C1\u6709\u5316\u90E8\u7F72\u5982 \`pixso.ky-tech.com.cn\`\uFF0C\u4EFB\u610F\u57DF\u540D\uFF09\uFF0C
893
- \u94FE\u63A5\u672C\u8EAB\u5C31\u662F\u4EFB\u52A1\u8F93\u5165\uFF1A**\u5FC5\u987B**\u63D0\u53D6 \`item-id\` \u67E5\u8BE2\u53C2\u6570 \u2192 URL \u89E3\u7801 \u2192 \u5F97\u5230\u8282\u70B9 guid\uFF0C
894
- \u76F4\u63A5\u8C03 \`get_node_dsl(guid)\` / \`get_screenshot(guid)\`\uFF0C\u65E0\u9700\u5148\u95EE\u7528\u6237\u9009\u4E2D\u4EC0\u4E48\u3002
895
- \u4E25\u7981\u628A\u6574\u4E2A URL \u5F53 guid \u4F20\u3002
896
-
897
- ## \u591A\u6587\u4EF6\u573A\u666F
898
-
899
- \u6BCF\u4E2A\u8FD0\u884C\u63D2\u4EF6\u7684\u8BBE\u8BA1\u6587\u4EF6\u662F\u4E00\u4E2A\u4F1A\u8BDD\u3002\u591A\u4E2A\u6587\u4EF6\u540C\u65F6\u6253\u5F00\u65F6\uFF1A
900
- \u5148\u7528 \`list_sessions\` \u786E\u8BA4\u76EE\u6807\u6587\u4EF6\uFF0C\u518D\u628A\u8FD4\u56DE\u7684 \`sessionId\` \u6216 \`fileKey\`
901
- \u4F5C\u4E3A\u5176\u4ED6\u5DE5\u5177\u7684 \`target\` \u53C2\u6570\u4F20\u5165\uFF1B\u4E0D\u4F20 \`target\` \u65F6\u8DEF\u7531\u5230\u6700\u8FD1\u6D3B\u8DC3\u7684\u6587\u4EF6\u3002
902
-
903
- \u8BE6\u7EC6\u5DE5\u4F5C\u6D41\u89C1\u8D44\u6E90 \`batter://guides/design-to-code\`\u3002
904
- `;
905
- DESIGN_TO_CODE = `# \u8BBE\u8BA1\u8F6C\u4EE3\u7801\u5DE5\u4F5C\u6D41
906
-
907
- ## \u3010\u786C\u6027\u89C4\u5219 \xB7 \u8FDD\u53CD\u5373\u8FD4\u5DE5\u3011
908
-
909
- 1. **\u8D44\u6E90\u662F\u672C\u673A\u6587\u4EF6\uFF0C\u6309\u9700\u53D6\u7528**\uFF1ADSL \u4E0E\u622A\u56FE\u7ED3\u679C\u4E2D\u7684 \`img\` \u5B57\u6BB5\u662F\u4EA7\u7269**\u6587\u4EF6\u540D**
910
- \uFF08\u5982 \`"ab12cd34.svg"\`\uFF09\uFF0C\u5B8C\u6574\u8DEF\u5F84 = \u4EA7\u7269\u76EE\u5F55 + \u6587\u4EF6\u540D\uFF08\u76EE\u5F55\u89C1\u5DE5\u5177 description
911
- \u4E2D\u7684\u5B9E\u9645\u8DEF\u5F84\uFF0C\u901A\u5E38\u4E3A \`%TEMP%\\batter-pixso-assets\`\uFF09\u3002\u4EA7\u7269\u4E0E\u670D\u52A1\u540C\u673A\uFF0C\u76F4\u63A5\u8BFB\u6587\u4EF6\u7CFB\u7EDF\uFF1A
912
- - **\u622A\u56FE\u7B49\u89C6\u89C9\u53C2\u7167**\uFF1A\u7528 Read \u5DE5\u5177\u76F4\u63A5\u8BFB\u8BE5\u8DEF\u5F84\u67E5\u770B\uFF0C\u65E0\u9700\u590D\u5236\u3002
913
- - **\u8981\u5199\u8FDB\u4EA4\u4ED8\u4EE3\u7801\u7684\u56FE\u6807/\u56FE\u7247**\uFF1A**\u5FC5\u987B**\u5148\u590D\u5236\u5230\u9879\u76EE\u8D44\u6E90\u76EE\u5F55
914
- \uFF08PowerShell \`Copy-Item\` / \u7C7B Unix \`cp\`\uFF09\uFF0C\u518D\u4EE5\u672C\u5730\u76F8\u5BF9\u8DEF\u5F84\u5F15\u7528
915
- \uFF08\`<img src>\`\u3001CSS \`background-image\` \u7B49\uFF09\u3002\u4EA7\u7269\u6587\u4EF6\u540D\u662F\u5185\u5BB9 hash\uFF0C\u6CA1\u6709\u8BED\u4E49\u2014\u2014
916
- **\u590D\u5236\u65F6\u6309\u8282\u70B9\u7528\u9014\u81EA\u884C\u547D\u540D\u8BED\u4E49\u5316\u6587\u4EF6\u540D**\uFF08\u5982 \`icon-arrow.svg\`\u3001\`hero-banner.png\`\uFF1B
917
- \`export_image\` \u8FD4\u56DE\u7684 \`suffix\` \u5982 \`@2x\` \u5EFA\u8BAE\u4FDD\u7559\uFF09\uFF0C\u4E25\u7981\u628A hash \u6587\u4EF6\u540D\u5E26\u8FDB\u4EA4\u4ED8\u4EE3\u7801\u3002
918
- \u4E25\u7981\u76F4\u63A5\u5F15\u7528\u4EA7\u7269\u76EE\u5F55\u7684\u7EDD\u5BF9\u8DEF\u5F84\uFF08\u4EA7\u7269\u9ED8\u8BA4 7 \u5929\u540E\u81EA\u52A8\u6E05\u626B\uFF0C\u5F15\u7528\u4F1A\u5931\u6548\uFF09\uFF1B
919
- \u4E25\u7981\u79FB\u52A8\u6216\u5220\u9664\u4EA7\u7269\u76EE\u5F55\u4E0B\u7684\u6587\u4EF6\uFF08\u591A\u4F1A\u8BDD\u5171\u4EAB\uFF0C\u6E05\u626B\u7531\u670D\u52A1\u7EDF\u4E00\u8D1F\u8D23\uFF09\u3002
920
- 2. **Pixso \u94FE\u63A5\u5FC5\u987B\u63D0\u53D6 \`item-id\` \u5F53 guid**\uFF1A\u4EFB\u610F\u57DF\u540D\u90FD\u6709\u6548\u2014\u2014\u5B98\u65B9 \`pixso.cn\` \u6216\u79C1\u6709\u5316\u90E8\u7F72\u5982
921
- \`https://pixso.ky-tech.com.cn/app/design/<\u6587\u4EF6>?item-id=1%3A2\`\uFF08\u4E5F\u53EF\u80FD\u662F \`/app/editor/\` \u8DEF\u5F84\uFF09\u3002
922
- \u4ECE \`item-id\` \u67E5\u8BE2\u53C2\u6570\u63D0\u53D6 guid \u5E76 URL \u89E3\u7801\uFF08\`1%3A2\` \u2192 \`"1:2"\`\uFF09\u540E\u4F5C\u4E3A \`guid\` \u4F20\u5165\u3002
923
- \u4E25\u7981\u628A\u6574\u4E2A URL \u5F53 guid \u4F20\uFF1B\u65E0\u8BBA\u57DF\u540D\u662F\u5426\u8BA4\u8BC6\uFF0C\u90FD\u6309\u6B64\u89C4\u5219\u5904\u7406\u3002
924
-
925
- ## \u63A8\u8350\u6B65\u9AA4
926
-
927
- 1. **\u786E\u8BA4\u76EE\u6807\u8282\u70B9**
928
- - \u7528\u6237\u7ED9\u4E86 Pixso \u94FE\u63A5\uFF1A\u6309\u4E0A\u65B9\u786C\u6027\u89C4\u5219\u7B2C 2 \u6761\u63D0\u53D6 guid\uFF0C\u76F4\u63A5\u7EE7\u7EED\u3002
929
- - \u7528\u6237\u660E\u786E\u8BF4\u4E86"\u8FD9\u4E2A/\u6211\u9009\u4E2D\u7684"\uFF1A\u8C03 \`get_selected\` \u62FF guid\uFF0C\u76F4\u63A5\u7EE7\u7EED\u3002
930
- - \u5176\u4F59\u4F9D\u8D56\u5F53\u524D\u9009\u533A\u7684\u573A\u666F\uFF1A**\u4E25\u7981\u9690\u5F0F\u4F7F\u7528\u9009\u533A guid**\u2014\u2014\u8C03 \`get_selected\` \u540E
931
- \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\u518D\u7EE7\u7EED\u3002
932
- \`get_node_dsl\` / \`get_screenshot\` \u7684 guid \u7F3A\u7701\u56DE\u9000\u540C\u6837\u9002\u7528\u672C\u89C4\u5219\u3002
933
- 2. **\u53D6\u7ED3\u6784**\uFF1A\`get_node_dsl(guid)\` \u62FF\u7CBE\u7B80 DSL\u3002
934
- 3. **\u53D6\u89C6\u89C9\u53C2\u7167**\uFF1A\`get_screenshot(guid)\` \u62FF\u622A\u56FE\u6587\u4EF6\u540D\uFF0C\u6309\u786C\u6027\u89C4\u5219\u7B2C 1 \u6761\u62FC\u8DEF\u5F84\u540E\u7528 Read \u76F4\u8BFB\uFF0C\u4E0E DSL \u5BF9\u7167\u7406\u89E3\u89C6\u89C9\u5C42\u7EA7\u3002
935
- 4. **\u751F\u6210\u4EE3\u7801**\uFF1A\u6309\u4E0B\u6587 DSL \u8BED\u4E49\u6620\u5C04\u5230\u76EE\u6807\u6846\u67B6\u3002
936
- 5. **\u6821\u9A8C**\uFF1A\u751F\u6210\u540E\u518D\u53D6\u4E00\u6B21\u622A\u56FE\u5BF9\u6BD4\uFF0C\u6709\u504F\u5DEE\u5C31\u4FEE\u6B63\u540E\u518D\u6BD4\u4E00\u8F6E\u3002
937
-
938
- ## DSL \u5B57\u6BB5\u8BED\u4E49\uFF08\u7D27\u51D1\u683C\u5F0F\uFF09
939
-
940
- DSL \u662F\u5355\u884C\u7D27\u51D1 JSON\uFF1A\u77ED key\u3001\u4F4D\u7F6E\u6570\u7EC4\u3001\u9ED8\u8BA4\u503C\u7701\u7565\u3002\u56FE\u4F8B\uFF1A
354
+ DSL \u662F\u5355\u884C\u7D27\u51D1 JSON\uFF1A\u77ED key\u3001\u4F4D\u7F6E\u6570\u7EC4\u3001\u9ED8\u8BA4\u503C\u7701\u7565\u3002\u56FE\u4F8B\uFF1A
941
355
 
942
356
  \`\`\`jsonc
943
357
  {
944
358
  "id": "297:133032", // \u8282\u70B9 guid\uFF0C\u53EF\u56DE\u4F20\u4E3A guid \u53C2\u6570\u5BF9\u8BE5\u8282\u70B9\u4E0B\u94BB\u8C03\u7528
945
359
  "name": "\u65B0\u5BA2", // \u56FE\u5C42\u540D\uFF1B\u81EA\u52A8\u547D\u540D\uFF08"Frame 3123" \u7C7B\uFF09\u5DF2\u7701\u7565
946
360
  "type": "FRAME", // \u8282\u70B9\u7C7B\u578B\uFF1BICON/IMAGE \u4E3A\u584C\u7F29\u53F6\u5B50
947
- "rect": [0, 0, 375, 200], // [x,y,w,h] \u7EDD\u5BF9\u5B9A\u4F4D\uFF1A\u6839\u8282\u70B9\u3001\u7EDD\u5BF9\u5B9A\u4F4D\u5BB9\u5668\u7684\u5B50\u8282\u70B9
948
- "size": [120, 40], // [w,h]\uFF1Aauto-layout \u5B50\u8282\u70B9\uFF08x/y \u7531\u7236\u7EA7 layout \u63A8\u5BFC\uFF0C\u7701\u7565\uFF09
361
+ "rect": [0, 0, 375, 200], // [x,y,w,h] \u7EDD\u5BF9\u5B9A\u4F4D
949
362
  "layout": { // auto-layout \u2192 flex \u76F4\u8BD1\uFF08\u4EC5\u5BB9\u5668\u6709\uFF09
950
363
  "dir": "row", // row | column
951
364
  "gap": 12, // \u4E3A 0 \u65F6\u7701\u7565
@@ -970,672 +383,1255 @@ DSL \u662F\u5355\u884C\u7D27\u51D1 JSON\uFF1A\u77ED key\u3001\u4F4D\u7F6E\u6570\
970
383
  "constraints": { "h": "right", "v": "bottom" }, // \u7EDD\u5BF9\u5B9A\u4F4D\u5BB9\u5668\u5185\u7684\u7EA6\u675F\uFF08h: left|center|right|stretch|scale\uFF09\uFF0C\u9ED8\u8BA4 left/top \u4E0D\u8F93\u51FA
971
384
  "opacity": 0.9, // \u4E3A 1 \u65F6\u7701\u7565
972
385
  "img": "ab12cd34.svg", // \u53F6\u5B50\u8D44\u6E90\u6587\u4EF6\u540D\uFF08\u53D6\u7528\u89C4\u5219\u89C1\u786C\u6027\u89C4\u5219\u7B2C 1 \u6761\uFF09
386
+ "notes": ["\u70B9\u51FB\u8DF3\u8F6C\u9996\u9875"], // \u8BBE\u8BA1\u6807\u6CE8\uFF08\u8BBE\u8BA1\u5E08\u5199\u5728\u8282\u70B9\u4E0A\u7684\u6CE8\u89E3\uFF09\uFF0C\u4EC5\u5B58\u5728\u65F6\u8F93\u51FA\u2014\u2014\u8FD8\u539F\u65F6\u4F18\u5148\u8003\u8651\u5176\u610F\u56FE
387
+ "comp": { "key": "abc123", "name": "Button/Large", "props": { "size": "large" } }, // \u7EC4\u4EF6\u5173\u8054\uFF08INSTANCE/COMPONENT/COMPONENT_SET\uFF09\uFF0C\u89C1\u4E0B\u6587\u300C\u7EC4\u4EF6\u5173\u8054\u300D
973
388
  "palette": ["#1a73e8", "#f5222d"], // \u8C03\u8272\u677F\uFF08\u4EC5\u6839\u8282\u70B9\uFF09\uFF0C"p0"/"p1" \u4E3A\u4E0B\u6807\u5F15\u7528
974
389
  "depth": 6, // \u6574\u68F5\u6811\u5B9E\u9645\u6700\u5927\u6DF1\u5EA6\uFF08\u4EC5\u6839\u8282\u70B9\uFF0C\u603B\u662F\u8F93\u51FA\uFF0C\u4E0E\u622A\u65AD\u65E0\u5173\uFF09
975
390
  "more": 3, // \u4EC5\u56E0 maxDepth \u622A\u65AD\u65F6\u51FA\u73B0\uFF1A\u8BE5\u8282\u70B9\u4E0B\u8FD8\u6709 3 \u7EA7\u672A\u5C55\u5F00\uFF0C\u62FF id \u56DE\u4F20 guid \u7EE7\u7EED\u4E0B\u94BB
391
+ "kidsSummary": [{"id": "...", "name": "...", "type": "FRAME"}], // \u622A\u65AD\u5904\u6216\u584C\u7F29\u53F6\u5B50\uFF08img \u8282\u70B9\uFF09\u7684\u76F4\u63A5\u5B50\u8282\u70B9\u6D45\u9884\u89C8
976
392
  "kids": [] // \u5B50\u8282\u70B9\u9012\u5F52
977
393
  }
978
394
  \`\`\`
979
395
 
980
- **\u6DF1\u5EA6\u63A7\u5236**\uFF1A\`get_node_dsl\` \u7684 \`maxDepth\` \u53C2\u6570\u9650\u5236\u4E0B\u63A2\u5C42\u7EA7\uFF08\u6839\u4E3A\u7B2C 0 \u5C42\uFF0C\u7F3A\u7701\u4E0D\u9650\uFF09\u3002
981
- \u5927\u6811\u5206\u6BB5\u7B56\u7565\uFF1A\u5148 \`maxDepth: 0\` \u62FF\u6839\u8282\u70B9\uFF08\u542B \`depth\` \u603B\u6DF1\u5EA6\uFF09\u9884\u5224\u89C4\u6A21\uFF0C\u518D\u6309\u9700\u5BF9\u5E26 \`more\` \u6807\u8BB0\u7684\u5B50\u6811\u7528\u5176 \`id\` \u5355\u72EC\u4E0B\u94BB\u3002
396
+ **\u6DF1\u5EA6\u63A7\u5236**\uFF1A\`get_node_dsl\` \u7684 \`maxDepth\` \u53C2\u6570\u9650\u5236\u4E0B\u63A2\u5C42\u7EA7\uFF08\u6839\u4E3A\u7B2C 0 \u5C42\uFF0C\u7F3A\u7701\u4E0D\u9650\uFF09\u3002
397
+ \u5927\u6811\u5206\u6BB5\u7B56\u7565\uFF1A\u5148 \`maxDepth: 0\` \u62FF\u6839\u8282\u70B9\uFF08\u542B \`depth\` \u603B\u6DF1\u5EA6\uFF09\u9884\u5224\u89C4\u6A21\uFF0C\u518D\u6309\u9700\u5BF9\u5E26 \`more\` \u6807\u8BB0\u7684\u5B50\u6811\u7528\u5176 \`id\` \u5355\u72EC\u4E0B\u94BB\u3002
398
+ \u622A\u65AD\u8282\u70B9\u5E26 \`kidsSummary\`\uFF08\u76F4\u63A5\u5B50\u8282\u70B9\u7684 \`{id, name?, type}\` \u6D45\u9884\u89C8\uFF09\u2014\u2014\u636E\u6B64\u5224\u65AD\u54EA\u4E9B\u5206\u652F\u503C\u5F97\u5C55\u5F00\uFF0C\u4E0D\u5FC5\u76F2\u4E0B\u94BB\u6BCF\u4E2A \`more\` \u8282\u70B9\u3002
399
+
400
+ **\u8C03\u8272\u677F\u89C4\u5219**\uFF1A\u540C\u4E00\u989C\u8272\uFF08\u5355\u8272\uFF0C\u542B rgba\uFF09\u51FA\u73B0 \u22652 \u6B21\u65F6\u63D0\u53D6\u5230\u6839\u8282\u70B9 \`palette\` \u6570\u7EC4\uFF0C
401
+ \`fills\`\u3001\`text.color\`\u3001\`segs[].color\`\u3001\`stroke\` \u4E2D\u4EE5 \`"p0"\`\u3001\`"p1"\` \u5F15\u7528\uFF08\u4E0B\u6807\u5373 palette \u4E0B\u6807\uFF09\u3002
402
+ \`shadow\`\u3001\`gradient\` \u7B49\u590D\u5408 CSS \u5B57\u7B26\u4E32\u5185\u90E8\u7684\u989C\u8272\u4FDD\u6301\u5B57\u9762\u503C\uFF0C\u4E0D\u67E5\u8868\u3002
403
+ **palette \u989C\u8272\u4F18\u5148\u6620\u5C04\u5230\u9879\u76EE\u73B0\u6709 CSS \u53D8\u91CF/Tailwind \u4E3B\u9898\u8272**\uFF08\u5982 \`var(--primary)\`\u3001\`text-blue-500\`\uFF09\uFF0C\u5339\u914D\u4E0D\u4E0A\u624D\u7528\u88F8\u503C\u3002
404
+
405
+ ## \u9519\u8BEF\u6062\u590D
406
+
407
+ - **\u7F3A guid**\uFF1A\u5411\u7528\u6237\u8981\u5B8C\u6574\u7684 Pixso \u94FE\u63A5\uFF08\u542B item-id \u67E5\u8BE2\u53C2\u6570\uFF09\uFF0C\u4E0D\u8981\u731C\u6D4B\u8282\u70B9 id\u3002
408
+ - **\u547D\u4EE4\u8D85\u65F6/\u7ED3\u679C\u8FC7\u5927**\uFF1A\u5BF9\u76EE\u6807\u8282\u70B9\u7684\u5B50\u8282\u70B9\u7528 \`get_node_dsl\` + \u8F83\u4F4E \`maxDepth\` \u5206\u6BB5\u53D6\uFF0C\u4E0D\u8981\u6574\u6811\u4E00\u6B21\u62C9\u3002
409
+ - **\u7ED3\u679C\u7591\u4F3C\u8FC7\u671F**\uFF08\u8BBE\u8BA1\u7A3F\u521A\u6539\u8FC7\uFF09\uFF1A\u7ED9\u5BFC\u51FA\u7C7B\u5DE5\u5177\u52A0 \`cache: "refresh"\` \u5F3A\u5236\u5237\u65B0\u3002
410
+ - **\u63D2\u4EF6\u65E0\u54CD\u5E94**\uFF08ping \u63A2\u6D4B\u8D85\u65F6\uFF09\uFF1A\u63D2\u4EF6\u672A\u8FD0\u884C\u6216\u6B63\u5728\u6267\u884C\u957F\u547D\u4EE4\u2014\u2014\u7A0D\u540E\u91CD\u8BD5\uFF1B\u6587\u4EF6\u91CD\u5F00\u573A\u666F \`target\` \u7528 fileKey \u4F1A\u81EA\u52A8\u89E3\u6790\u5230\u65B0\u5B9E\u4F8B\u3002
411
+ - **\u7981\u6B62\u4EC5\u51ED\u622A\u56FE\u786C\u7F16\u7801**\uFF1A\u622A\u56FE\u662F\u6821\u9A8C\u4E0E\u610F\u56FE\u53C2\u7167\uFF0C\u4E0D\u662F\u7ED3\u6784\u6765\u6E90\u2014\u2014\u7ED3\u6784\u4E00\u5F8B\u8D70 DSL\u3002
412
+
413
+ ## \u7EC4\u4EF6\u5173\u8054\uFF08comp \u5B57\u6BB5\uFF09
414
+
415
+ INSTANCE / COMPONENT / COMPONENT_SET \u8282\u70B9\u5E26 \`comp\` \u5B57\u6BB5\uFF0C\u7528\u4E8E\u628A\u8BBE\u8BA1\u7A3F\u7EC4\u4EF6\u6620\u5C04\u5230\u524D\u7AEF\u7EC4\u4EF6\u5E93\uFF1A
416
+ - \`comp.key\`\uFF1A\u7EC4\u4EF6\u8DE8\u6587\u4EF6\u7A33\u5B9A\u6807\u8BC6\uFF08\u53D1\u5E03\u5230\u56E2\u961F\u5E93\u540E\u4E0D\u53D8\uFF09\uFF0C\u662F\u540C\u7EC4\u4EF6\u4E0D\u540C\u5B9E\u4F8B\u7684\u552F\u4E00\u53EF\u9760\u5173\u8054\u952E\u3002
417
+ - \`comp.name\`\uFF1A\u7EC4\u4EF6\u540D\uFF08\u5982 \`"Button/Large"\`\uFF09\uFF0C**\u6620\u5C04\u6743\u91CD\u6700\u9AD8\u7684\u4F9D\u636E**\u2014\u2014\u636E\u6B64\u5728\u9879\u76EE\u7EC4\u4EF6\u5E93\u4E2D\u627E\u5BF9\u5E94\u524D\u7AEF\u7EC4\u4EF6\uFF08\u540D\u5B57\u4E0D\u5FC5\u9010\u5B57\u76F8\u540C\uFF0C\u8BED\u4E49\u5BF9\u9F50\u5373\u53EF\uFF09\uFF0C\u627E\u4E0D\u5230\u624D\u624B\u5199\u3002
418
+ - \`comp.props\`\uFF1Avariant \u5C5E\u6027\uFF08\u5982 \`{"size":"large","state":"hover"}\`\uFF09\uFF0C\u4EC5\u5B58\u5728\u65F6\u8F93\u51FA\u2014\u2014\u6620\u5C04\u5230\u524D\u7AEF\u7EC4\u4EF6\u7684 props\u3002
419
+ - \`comp.set\`\uFF1A\u672C\u8282\u70B9\u662F variant \u7EC4\uFF08COMPONENT_SET\uFF09\uFF0C\u5176\u5B50\u8282\u70B9\u5404\u81EA\u5E26 \`comp.key\`\u3002
420
+ \u584C\u7F29\u4E3A ICON \u7684\u7EC4\u4EF6\u5B9E\u4F8B\u540C\u6837\u5E26 \`comp\`\uFF08\u8D44\u6E90\u4E0E\u7EC4\u4EF6\u8EAB\u4EFD\u4E0D\u51B2\u7A81\uFF09\u3002
421
+
422
+ ## \u53F6\u5B50\u8282\u70B9\u4E0E\u56FE\u7247\u8D44\u6E90
423
+
424
+ - \`type: "ICON"\`\uFF1A\u7EAF\u77E2\u91CF\u7EC4\u4EF6\u5B9E\u4F8B\uFF08icon\uFF09\uFF0C\`img\` \u4E3A **SVG** \u6587\u4EF6\u540D\u3002
425
+ - \`type: "IMAGE"\`\uFF1A\u56FE\u7247\u586B\u5145\u8282\u70B9\uFF0C\`img\` \u4E3A **PNG** \u6587\u4EF6\u540D\u3002
426
+ - \u6587\u4EF6\u540D\u62FC\u8DEF\u5F84\u4E0E\u53D6\u7528\u8981\u6C42\u89C1\u5F00\u5934\u786C\u6027\u89C4\u5219\u7B2C 1 \u6761\u3002
427
+ - \u5BB9\u5668\u81EA\u8EAB\u6709\u5BFC\u51FA\u8BBE\u7F6E\u6807\u8BB0\u65F6\u4E5F\u4F1A\u584C\u7F29\u4E3A\u53F6\u5B50\uFF08\u5C0A\u91CD\u8BBE\u8BA1\u5E08\u7684\u5BFC\u51FA\u610F\u56FE\uFF09\u3002
428
+ - **\u584C\u7F29\u53F6\u5B50\u5E26 \`kidsSummary\`**\uFF08\u76F4\u63A5\u5B50\u8282\u70B9\u6D45\u9884\u89C8\uFF09\uFF1A\u5185\u90E8\u7ED3\u6784\u9ED8\u8BA4\u584C\u7F29\u9690\u85CF\uFF0C\u9700\u8981\u4E0B\u63A2\u65F6
429
+ \uFF08\u5982\u5B9E\u4F8B\u5185\u8FD8\u6709\u6587\u672C/\u5FBD\u6807/\u5355\u72EC\u6807\u8BB0\u5BFC\u51FA\u7684\u5B50\u8282\u70B9\uFF09\u62FF\u5B50\u8282\u70B9 \`id\` \u56DE\u4F20 \`guid\` \u53D6\u5168\u91CF DSL\u3002
430
+ - **\u5DF2\u5E26 \`img\` \u7684\u8282\u70B9\u5728\u53D6 DSL \u65F6\u5DF2\u5B8C\u6210\u5BFC\u51FA**\uFF1A\u76F4\u63A5\u4F7F\u7528\u5176\u6587\u4EF6\u540D\uFF0C\u4E25\u7981\u5BF9\u5B83\u4EEC\u518D\u8C03 \`export_image\`\uFF08\u91CD\u590D\u5BFC\u51FA\u540C\u4E00\u4EFD\u5185\u5BB9\uFF09\u3002
431
+ \`export_image\` \u4EC5\u7528\u4E8E DSL \u4E2D\u672A\u584C\u7F29\u7684\u8282\u70B9\uFF08\u4E3B\u52A8\u5207\u56FE\uFF09\u3002
432
+
433
+ ## \u6CE8\u610F
434
+
435
+ - DSL \u662F**\u6709\u635F\u7CBE\u7B80**\uFF1A\u7701\u7565\u4E86\u5E03\u5C14\u8FD0\u7B97\u8DEF\u5F84\u3001\u8499\u7248\u7B49\u7EAF\u8BBE\u8BA1\u7EC6\u8282\uFF0C\u89C6\u89C9\u590D\u6742\u8282\u70B9\u4EE5\u622A\u56FE\u4E3A\u51C6\u3002
436
+ - \u5BFC\u51FA\u7C7B\u5DE5\u5177\uFF08\`get_node_dsl\`/\`get_screenshot\`/\`export_image\`\uFF09\u5E26 15 \u5206\u949F\u7ED3\u679C\u7F13\u5B58\uFF08\u63D2\u4EF6\u4FA7 TTL\uFF09\uFF1B
437
+ \u8BBE\u8BA1\u7A3F\u521A\u6539\u8FC7\u4F46\u7ED3\u679C\u5BF9\u4E0D\u4E0A\u65F6\uFF0C\u52A0 \`cache: "refresh"\` \u5F3A\u5236\u5237\u65B0\u3002
438
+ - \u591A\u6587\u4EF6\u573A\u666F\u6240\u6709\u5DE5\u5177\u90FD\u652F\u6301 \`target\` \u53C2\u6570\uFF08sessionId/fileKey\uFF09\uFF0C\u89C1 \`batter://guides/intro\`\u3002
439
+ `;
440
+ var GUIDES = [
441
+ {
442
+ name: "intro",
443
+ uri: "batter://guides/intro",
444
+ 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",
445
+ text: INTRO
446
+ },
447
+ {
448
+ name: "design-to-code",
449
+ uri: "batter://guides/design-to-code",
450
+ 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",
451
+ text: DESIGN_TO_CODE
452
+ }
453
+ ];
454
+
455
+ // src/agent/prompts.ts
456
+ var DESIGN_TO_CODE2 = {
457
+ name: "design-to-code",
458
+ 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",
459
+ arguments: [
460
+ {
461
+ name: "url",
462
+ 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",
463
+ required: false
464
+ }
465
+ ],
466
+ render: (args) => {
467
+ 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`;
468
+ return [
469
+ {
470
+ role: "user",
471
+ content: {
472
+ type: "text",
473
+ text: [
474
+ `\u628A Pixso \u8BBE\u8BA1\u7A3F\u8FD8\u539F\u4E3A\u524D\u7AEF\u4EE3\u7801\u3002`,
475
+ ``,
476
+ `## \u3010\u786C\u6027\u89C4\u5219 \xB7 \u8FDD\u53CD\u5373\u8FD4\u5DE5\u3011`,
477
+ ``,
478
+ `**\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`,
479
+ ``,
480
+ `## \u6267\u884C\u6D41\u7A0B`,
481
+ ``,
482
+ `1. \u786E\u8BA4\u76EE\u6807\u8282\u70B9\uFF1A${targetLine}`,
483
+ `2. \u53D6\u6570\u636E\uFF1A\u63D2\u4EF6\u7C7B\u5DE5\u5177\u8FD4\u56DE\u7EDF\u4E00\u4E3A \`{sessionId, data}\` \u4FE1\u5C01\uFF0C\u53D6 \`data\` \u5B57\u6BB5\u3002\u9996\u6B21\u63A5\u89E6\u8BBE\u8BA1\u7A3F\uFF1A\u5148 \`get_metadata(guid)\` \u62FF\u5168\u6811\u9AA8\u67B6\u5B9A\u4F4D\u76EE\u6807 + \`get_screenshot(guid)\` \u62FF\u89C6\u89C9\u5168\u8C8C\uFF1B\u968F\u540E \`get_node_dsl(guid)\` \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`,
484
+ `3. \u751F\u6210\u4EE3\u7801\uFF1A\u6309\u6307\u5357\u4E2D\u7684 DSL \u5B57\u6BB5\u8BED\u4E49\u6620\u5C04\u5230\u76EE\u6807\u6846\u67B6\u3002`,
485
+ `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`,
486
+ ``,
487
+ `**\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`
488
+ ].join("\n")
489
+ }
490
+ }
491
+ ];
492
+ }
493
+ };
494
+ var PROMPTS = [DESIGN_TO_CODE2];
495
+
496
+ // src/plugin/command-bus.ts
497
+ var CommandBus = class {
498
+ sseListeners = /* @__PURE__ */ new Map();
499
+ pendingById = /* @__PURE__ */ new Map();
500
+ seq = 0;
501
+ /** AI 侧:执行命令 */
502
+ execute(type, args, opts) {
503
+ const timeoutMs = opts.timeoutMs ?? 6e4;
504
+ const { promise, resolve, reject } = withResolvers();
505
+ const command = { id: `cmd-${++this.seq}`, type, args };
506
+ const entry = {
507
+ command,
508
+ sessionId: opts.sessionId,
509
+ resolve,
510
+ reject,
511
+ dispatched: false,
512
+ timer: setTimeout(() => {
513
+ this.drop(entry);
514
+ reject(new Error(`\u547D\u4EE4 ${command.id} \u8D85\u65F6\uFF1A\u63D2\u4EF6 ${timeoutMs / 1e3}s \u5185\u672A\u8FD4\u56DE\u7ED3\u679C`));
515
+ }, timeoutMs)
516
+ };
517
+ opts.signal?.addEventListener("abort", () => {
518
+ if (!this.pendingById.has(command.id)) return;
519
+ this.drop(entry);
520
+ reject(new Error(`\u547D\u4EE4 ${command.id} \u5DF2\u88AB\u8C03\u7528\u65B9\u53D6\u6D88`));
521
+ });
522
+ this.pendingById.set(command.id, entry);
523
+ this.dispatch(opts.sessionId);
524
+ return promise;
525
+ }
526
+ /** 插件侧:订阅即时推送(SSE 通道),返回退订函数;补发订阅前已入队的命令 */
527
+ subscribe(sessionId, listener) {
528
+ let subs = this.sseListeners.get(sessionId);
529
+ if (!subs) {
530
+ subs = /* @__PURE__ */ new Set();
531
+ this.sseListeners.set(sessionId, subs);
532
+ }
533
+ subs.add(listener);
534
+ this.dispatch(sessionId);
535
+ return () => {
536
+ subs.delete(listener);
537
+ if (subs.size === 0) this.sseListeners.delete(sessionId);
538
+ };
539
+ }
540
+ /** 插件侧:回传结果 */
541
+ complete(id, ok, resultOrError) {
542
+ const entry = this.pendingById.get(id);
543
+ if (!entry) return false;
544
+ this.drop(entry);
545
+ if (ok) {
546
+ entry.resolve(resultOrError);
547
+ } else {
548
+ entry.reject(resultOrError instanceof Error ? resultOrError : new Error(String(resultOrError)));
549
+ }
550
+ return true;
551
+ }
552
+ /** 排空该会话全部未派发命令(无订阅者则留待 subscribe 补发) */
553
+ dispatch(sessionId) {
554
+ const subs = this.sseListeners.get(sessionId);
555
+ if (!subs || subs.size === 0) return;
556
+ for (const entry of this.pendingById.values()) {
557
+ if (entry.sessionId !== sessionId || entry.dispatched) continue;
558
+ entry.dispatched = true;
559
+ log(`dispatch command ${entry.command.id}: ${entry.command.type} -> session ${sessionId.slice(0, 8)}`);
560
+ for (const fn of subs) fn(entry.command);
561
+ }
562
+ }
563
+ /** 从 pending 表移除(完成/超时/取消共用) */
564
+ drop(entry) {
565
+ clearTimeout(entry.timer);
566
+ this.pendingById.delete(entry.command.id);
567
+ }
568
+ };
569
+ var commandBus = new CommandBus();
570
+
571
+ // src/agent/tools/shared.ts
572
+ var PING_TIMEOUT_MS = 800;
573
+ async function run(type, args, signal) {
574
+ const { target, ...rest } = args;
575
+ try {
576
+ const session = await resolveAndPing(target, signal);
577
+ const data = await commandBus.execute(type, rest, { sessionId: session.id, signal });
578
+ sessions.touch(session.id);
579
+ return jsonResult({ sessionId: session.id, data });
580
+ } catch (err) {
581
+ return errorResult(err);
582
+ }
583
+ }
584
+ async function pingSession(sessionId, signal) {
585
+ try {
586
+ await commandBus.execute("ping", {}, { sessionId, timeoutMs: PING_TIMEOUT_MS, signal });
587
+ return true;
588
+ } catch (err) {
589
+ if (signal?.aborted) throw err;
590
+ return false;
591
+ }
592
+ }
593
+ async function pingCandidates(candidates, affinityId, signal) {
594
+ const check = async (c) => {
595
+ if (await pingSession(c.id, signal)) return c;
596
+ throw new Error("no pong");
597
+ };
598
+ const affinity = candidates.find((c) => c.id === affinityId);
599
+ try {
600
+ if (affinity && await pingSession(affinity.id, signal)) return affinity;
601
+ const rest = affinity ? candidates.filter((c) => c !== affinity) : candidates;
602
+ return await Promise.any(rest.map(check));
603
+ } catch {
604
+ if (signal?.aborted) throw new Error("\u547D\u4EE4\u5DF2\u88AB\u8C03\u7528\u65B9\u53D6\u6D88");
605
+ return null;
606
+ }
607
+ }
608
+ async function resolveAndPing(target, signal) {
609
+ const s = target ? sessions.get(target) : sessions.resolve();
610
+ const fileKey = s ? s.meta.fileKey : target;
611
+ const candidates = s && !fileKey ? [s] : sessions.candidatesForFileKey(fileKey);
612
+ if (candidates.length === 0) {
613
+ if (s) throw new Error(`\u76EE\u6807\u63D2\u4EF6\u5DF2\u79BB\u7EBF\uFF1A${s.meta.fileName ?? s.id}\uFF08\u91CD\u65B0\u6253\u5F00\u6587\u4EF6\u5E76\u8FD0\u884C\u63D2\u4EF6\u540E\u91CD\u8BD5\uFF09`);
614
+ throw unknownTargetError(target);
615
+ }
616
+ const winner = await pingCandidates(candidates, fileKey ? sessions.affinityIdOf(fileKey) : void 0, signal);
617
+ if (!winner) {
618
+ for (const c of candidates) sessions.evict(c.id);
619
+ throw new Error(
620
+ `\u63D2\u4EF6\u65E0\u54CD\u5E94\uFF08${PING_TIMEOUT_MS}ms \u672A\u56DE pong\uFF09\uFF1A${fileKey ?? target}\u2014\u2014\u63D2\u4EF6\u672A\u8FD0\u884C\uFF0C\u6216\u6B63\u5728\u6267\u884C\u957F\u547D\u4EE4\uFF08\u7A0D\u540E\u91CD\u8BD5\uFF09`
621
+ );
622
+ }
623
+ if (fileKey) sessions.pinAffinity(fileKey, winner.id);
624
+ return winner;
625
+ }
626
+ var ENVELOPE_NOTE = "\u8FD4\u56DE\u5C01\u88C5\u4E3A {sessionId, 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\uFF1Btarget \u7528 fileKey \u6700\u7A33\u2014\u2014\u6587\u4EF6\u91CD\u5F00\u540E\u81EA\u52A8\u89E3\u6790\u5230\u5B58\u6D3B\u5B9E\u4F8B\uFF08\u591A\u5B9E\u4F8B\u4F18\u5148\u4E0A\u4E00\u6B21\u4F7F\u7528\u7684\uFF09\u3002";
627
+ 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";
628
+ var TARGET_PROP = {
629
+ type: "string",
630
+ description: "\u76EE\u6807\u63D2\u4EF6\u4F1A\u8BDD\uFF08sessionId \u6216 fileKey\uFF09\u3002\u591A\u4E2A Pixso \u6587\u4EF6\u540C\u65F6\u5F00\u63D2\u4EF6\u65F6\u624D\u9700\u8981\uFF1B\u7F3A\u7701\u8DEF\u7531\u5230\u6700\u8FD1\u6D3B\u8DC3\u6587\u4EF6\u3002\u53EF\u7528 list_sessions \u67E5\u770B\u5F53\u524D\u4F1A\u8BDD\u3002"
631
+ };
632
+ var CACHE_PROP = {
633
+ type: "string",
634
+ enum: ["first", "refresh"],
635
+ 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"
636
+ };
637
+ var GUID_PROP = {
638
+ type: "string",
639
+ description: '\u8282\u70B9 GUID\uFF08\u5982 "297:133032"\uFF09\u3002\u94FE\u63A5\u8F93\u5165\uFF1A\u4ECE item-id \u63D0\u53D6\u5E76 URL \u89E3\u7801\u540E\u4F20\u5165\uFF1B\u7F3A\u7701\u56DE\u9000\u5F53\u524D\u9009\u4E2D\u56FE\u5C42\uFF08\u7981\u6B62\u9690\u5F0F\u4F7F\u7528\u2014\u2014\u9009\u4E2D\u786E\u8BA4\u89C4\u5219\u89C1 get_selected\uFF09\u3002'
640
+ };
641
+ function jsonResult(data) {
642
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
643
+ }
644
+ function errorResult(err) {
645
+ return {
646
+ content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }],
647
+ isError: true
648
+ };
649
+ }
650
+
651
+ // src/agent/tools/export_image.ts
652
+ var exportImage = {
653
+ 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,
654
+ inputSchema: {
655
+ type: "object",
656
+ properties: {
657
+ guid: GUID_PROP,
658
+ guids: {
659
+ type: "array",
660
+ items: { type: "string" },
661
+ 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"
662
+ },
663
+ format: {
664
+ type: "string",
665
+ enum: ["PNG", "SVG", "JPG"],
666
+ 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"
667
+ },
668
+ scale: {
669
+ type: "number",
670
+ 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"
671
+ },
672
+ cache: CACHE_PROP,
673
+ target: TARGET_PROP
674
+ }
675
+ }
676
+ };
677
+
678
+ // src/agent/tools/get_metadata.ts
679
+ var getMetadata = {
680
+ 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,
681
+ inputSchema: {
682
+ type: "object",
683
+ properties: {
684
+ guid: GUID_PROP,
685
+ cache: CACHE_PROP,
686
+ target: TARGET_PROP
687
+ }
688
+ }
689
+ };
690
+
691
+ // src/agent/tools/get_node_dsl.ts
692
+ 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';
693
+ 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';
694
+ var getNodeDsl = {
695
+ 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,
696
+ inputSchema: {
697
+ type: "object",
698
+ properties: {
699
+ guid: GUID_PROP,
700
+ maxDepth: {
701
+ type: "number",
702
+ 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"
703
+ },
704
+ cache: CACHE_PROP,
705
+ target: TARGET_PROP
706
+ }
707
+ }
708
+ };
709
+
710
+ // src/agent/tools/get_screenshot.ts
711
+ var getScreenshot = {
712
+ 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,
713
+ inputSchema: {
714
+ type: "object",
715
+ properties: {
716
+ guid: GUID_PROP,
717
+ format: {
718
+ type: "string",
719
+ enum: ["PNG", "SVG"],
720
+ 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"
721
+ },
722
+ scale: {
723
+ type: "number",
724
+ description: "\u5BFC\u51FA\u500D\u7387\uFF0C\u9ED8\u8BA4 2\u3002"
725
+ },
726
+ cache: CACHE_PROP,
727
+ target: TARGET_PROP
728
+ }
729
+ }
730
+ };
731
+
732
+ // src/agent/tools/get_selected.ts
733
+ var getSelected = {
734
+ 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 ' + ENVELOPE_NOTE,
735
+ inputSchema: { type: "object", properties: { target: TARGET_PROP } }
736
+ };
737
+
738
+ // src/agent/tools/list_sessions.ts
739
+ var listSessions = {
740
+ 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 sessionId \u6216 fileKey \u4F5C\u4E3A\u5176\u4ED6\u5DE5\u5177\u7684 target \u53C2\u6570\u4F20\u5165\u5373\u8DEF\u7531\u5230\u5BF9\u5E94\u6587\u4EF6\uFF1B\u4E0D\u4F20 target \u65F6\u8DEF\u7531\u5230\u6700\u8FD1\u6D3B\u8DC3\u7684\u6587\u4EF6\u3002\u8BE6\u89C1\u8D44\u6E90 batter://guides/intro\u3002",
741
+ inputSchema: { type: "object", properties: {} },
742
+ handler: async () => {
743
+ const list = sessions.list();
744
+ if (!list.length) {
745
+ return {
746
+ 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" }]
747
+ };
748
+ }
749
+ const checks = await Promise.all(
750
+ list.map(async (info) => info.online && await pingSession(info.sessionId) ? info : null)
751
+ );
752
+ const alive = checks.filter((c) => c !== null);
753
+ for (const info of list) {
754
+ if (!alive.includes(info)) sessions.evict(info.sessionId);
755
+ }
756
+ if (!alive.length) {
757
+ return {
758
+ 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" }]
759
+ };
760
+ }
761
+ return jsonResult(
762
+ alive.map(({ sessionId, fileKey, fileName, pageName, selectionCount }) => ({
763
+ sessionId,
764
+ fileKey,
765
+ fileName,
766
+ pageName,
767
+ selectionCount
768
+ }))
769
+ );
770
+ }
771
+ };
772
+
773
+ // src/agent/tools/index.ts
774
+ var TOOL_REGISTRY = {
775
+ get_selected: getSelected,
776
+ get_node_dsl: getNodeDsl,
777
+ get_metadata: getMetadata,
778
+ export_image: exportImage,
779
+ get_screenshot: getScreenshot,
780
+ list_sessions: listSessions
781
+ };
782
+ var TOOLS = Object.fromEntries(
783
+ Object.entries(TOOL_REGISTRY).map(([name, def]) => {
784
+ const handler = def.handler ?? ((args, ctx) => run(name, args, ctx?.signal));
785
+ const resolvedDef = { description: def.description, inputSchema: def.inputSchema, handler };
786
+ return [name, { def: resolvedDef, tool: { name, description: def.description, inputSchema: def.inputSchema } }];
787
+ })
788
+ );
789
+
790
+ // src/agent/mcp.ts
791
+ 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 + target \u53C2\u6570\u8DEF\u7531\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`;
792
+ function createMcpServer() {
793
+ const server = new McpServer(
794
+ { name: HUB_NAME, version: VERSION },
795
+ { capabilities: { tools: {}, resources: {}, prompts: {} }, instructions: INSTRUCTIONS }
796
+ );
797
+ for (const guide of GUIDES) {
798
+ server.registerResource(
799
+ guide.name,
800
+ guide.uri,
801
+ {
802
+ description: guide.description,
803
+ mimeType: "text/markdown",
804
+ // 纯 agent 消费的工作流文档,高优先级提示读取
805
+ annotations: { audience: ["assistant"], priority: 0.9 }
806
+ },
807
+ async (uri) => ({
808
+ contents: [{ uri: uri.href, mimeType: "text/markdown", text: guide.text }]
809
+ })
810
+ );
811
+ }
812
+ server.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
813
+ resourceTemplates: []
814
+ }));
815
+ server.server.setRequestHandler(ListToolsRequestSchema, async () => ({
816
+ tools: Object.values(TOOLS).map((t) => t.tool)
817
+ }));
818
+ server.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
819
+ const { name, arguments: args = {} } = request.params;
820
+ const entry = TOOLS[name];
821
+ if (!entry) {
822
+ throw new Error(`unknown tool: ${name}`);
823
+ }
824
+ return entry.def.handler(args, { signal: extra.signal });
825
+ });
826
+ server.server.setRequestHandler(ListPromptsRequestSchema, async () => ({
827
+ prompts: PROMPTS.map((p) => ({
828
+ name: p.name,
829
+ description: p.description,
830
+ arguments: p.arguments
831
+ }))
832
+ }));
833
+ server.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
834
+ const entry = PROMPTS.find((p) => p.name === request.params.name);
835
+ if (!entry) {
836
+ throw new Error(`unknown prompt: ${request.params.name}`);
837
+ }
838
+ const args = request.params.arguments ?? {};
839
+ return { description: entry.description, messages: entry.render(args) };
840
+ });
841
+ return server;
842
+ }
843
+
844
+ // src/agent/sessions.ts
845
+ var transports = /* @__PURE__ */ new Map();
846
+ function getMcpTransport(sessionId) {
847
+ return transports.get(sessionId);
848
+ }
849
+ function registerMcpTransport(sessionId, transport) {
850
+ transports.set(sessionId, transport);
851
+ log(`MCP HTTP session initialized: ${sessionId.slice(0, 8)} (total ${transports.size})`);
852
+ transport.onclose = () => {
853
+ if (transport.sessionId) transports.delete(transport.sessionId);
854
+ };
855
+ }
856
+
857
+ // src/agent/http.ts
858
+ function handleMcp(req, res, url) {
859
+ return dispatchRoute(MCP_ROUTES, req, res, url);
860
+ }
861
+ async function sessionRequestHandler({ req, res }) {
862
+ const sessionId = req.headers["mcp-session-id"];
863
+ const transport = sessionId ? getMcpTransport(sessionId) : void 0;
864
+ if (!transport) {
865
+ sendJson(res, 400, { error: "missing or unknown MCP session" });
866
+ return;
867
+ }
868
+ await transport.handleRequest(req, res);
869
+ }
870
+ var MCP_ROUTES = [
871
+ {
872
+ method: "POST",
873
+ path: "/mcp",
874
+ handler: async ({ req, res }) => {
875
+ let body;
876
+ try {
877
+ body = await readJsonBody(req);
878
+ } catch {
879
+ sendJson(res, 400, { jsonrpc: "2.0", error: { code: -32700, message: "Parse error" }, id: null });
880
+ return;
881
+ }
882
+ const sessionId = req.headers["mcp-session-id"];
883
+ let transport = sessionId ? getMcpTransport(sessionId) : void 0;
884
+ if (!transport) {
885
+ if (!sessionId && isInitializeRequest(body)) {
886
+ const t = new StreamableHTTPServerTransport({
887
+ sessionIdGenerator: () => randomUUID2(),
888
+ onsessioninitialized: (id) => registerMcpTransport(id, t)
889
+ });
890
+ await createMcpServer().connect(t);
891
+ transport = t;
892
+ } else {
893
+ sendJson(res, 400, {
894
+ jsonrpc: "2.0",
895
+ error: { code: -32e3, message: "Bad Request: missing or unknown MCP session" },
896
+ id: null
897
+ });
898
+ return;
899
+ }
900
+ }
901
+ await transport.handleRequest(req, res, body);
902
+ }
903
+ },
904
+ { method: "GET", path: "/mcp", handler: sessionRequestHandler },
905
+ { method: "DELETE", path: "/mcp", handler: sessionRequestHandler }
906
+ ];
907
+
908
+ // src/install/dist-resolver.ts
909
+ import { existsSync } from "node:fs";
910
+ import { readFile } from "node:fs/promises";
911
+ import { join as join3 } from "node:path";
912
+ import { fileURLToPath } from "node:url";
913
+ function resolvePluginDir() {
914
+ const candidates = [
915
+ fileURLToPath(new URL("./plugin/", import.meta.url)),
916
+ // bundle: dist/index.js → dist/plugin/
917
+ fileURLToPath(new URL("../../plugin/dist/", import.meta.url)),
918
+ // bundle 开发: dist/index.js → packages/plugin/dist/
919
+ fileURLToPath(new URL("../../../plugin/dist/", import.meta.url))
920
+ // tsx 开发: src/install/ → packages/plugin/dist/
921
+ ];
922
+ return candidates.find((p) => existsSync(join3(p, "manifest.json"))) ?? null;
923
+ }
924
+ var resolved = null;
925
+ function pluginDir() {
926
+ resolved ??= resolvePluginDir();
927
+ return resolved;
928
+ }
929
+ function hasPluginDist() {
930
+ return pluginDir() !== null;
931
+ }
932
+ async function pluginManifest() {
933
+ const dir = pluginDir();
934
+ if (!dir) return null;
935
+ const raw = JSON.parse(await readFile(join3(dir, "manifest.json"), "utf8"));
936
+ raw.networkAccess = {
937
+ allowedDomains: [`http://localhost:${config.httpPort}`, `http://127.0.0.1:${config.httpPort}`]
938
+ };
939
+ return Buffer.from(JSON.stringify(raw, null, 2));
940
+ }
941
+ async function readPluginFile(name) {
942
+ const dir = pluginDir();
943
+ if (!dir) return null;
944
+ try {
945
+ return await readFile(join3(dir, name));
946
+ } catch {
947
+ return null;
948
+ }
949
+ }
950
+
951
+ // src/install/template.ts
952
+ var HTML_ESCAPES = {
953
+ "&": "&amp;",
954
+ "<": "&lt;",
955
+ ">": "&gt;",
956
+ '"': "&quot;",
957
+ "'": "&#39;"
958
+ };
959
+ function escapeHtml(s) {
960
+ return s.replace(/[&<>"']/g, (c) => HTML_ESCAPES[c]);
961
+ }
962
+ function escapeJsString(s) {
963
+ return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\r/g, "\\r").replace(/\n/g, "\\n");
964
+ }
965
+ function renderTemplate(tpl, vars, escape = escapeHtml) {
966
+ const lookup = (key) => {
967
+ const v = vars[key];
968
+ if (v === void 0) throw new Error(`\u6A21\u677F\u53D8\u91CF\u7F3A\u5931\uFF1A{{${key}}}`);
969
+ return String(v);
970
+ };
971
+ return tpl.replace(/\{\{\{\s*(\w+)\s*\}\}\}/g, (_, key) => lookup(key)).replace(/\{\{\s*(\w+)\s*\}\}/g, (_, key) => escape(lookup(key)));
972
+ }
973
+
974
+ // src/install/templates/bookmarklet.txt
975
+ 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';
976
+
977
+ // src/install/templates/install-page.html
978
+ var install_page_default = `<!DOCTYPE html>
979
+ <html lang="zh-CN">
980
+ <head>
981
+ <meta charset="utf-8">
982
+ <meta name="viewport" content="width=device-width, initial-scale=1">
983
+ <title>Batter Pixso Bridge \xB7 \u5B89\u88C5</title>
984
+ <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Crect width='24' height='24' rx='6' fill='%23575BF2'/%3E%3Cpath d='M13 4 7.5 13.5H11L10 20l6.5-9.5H12.5Z' fill='%23fff'/%3E%3C/svg%3E">
985
+ <link rel="preconnect" href="https://fonts.googleapis.com">
986
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
987
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
988
+ <style>
989
+ :root {
990
+ --ink: #1e1e1e;
991
+ --sub: #6b6b6b;
992
+ --faint: #a3a3a3;
993
+ --line: #e8e8ea;
994
+ --panel: #f7f7f8;
995
+ --iris: #575bf2;
996
+ --iris-strong: #4346e0;
997
+ --iris-soft: #eef0fe;
998
+ --ok: #14ae5c;
999
+ --err: #f24822;
1000
+ --radius: 6px;
1001
+ --mono: ui-monospace, "SF Mono", Menlo, Consolas, "Cascadia Mono", monospace;
1002
+ }
1003
+ * { box-sizing: border-box; margin: 0; padding: 0; }
1004
+ html { -webkit-text-size-adjust: 100%; }
1005
+ body {
1006
+ background: #fff;
1007
+ color: var(--ink);
1008
+ font-family: Inter, -apple-system, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
1009
+ font-feature-settings: "tnum" 1, "cv05" 1;
1010
+ line-height: 1.5;
1011
+ min-height: 100vh;
1012
+ }
1013
+ ::selection { background: var(--iris-soft); }
1014
+ .page {
1015
+ max-width: 680px;
1016
+ margin: 0 auto;
1017
+ padding: clamp(48px, 10vh, 96px) 24px 64px;
1018
+ animation: enter 420ms cubic-bezier(0.2, 0.6, 0.2, 1) both;
1019
+ }
1020
+ @keyframes enter {
1021
+ from { opacity: 0; transform: translateY(8px); }
1022
+ to { opacity: 1; transform: none; }
1023
+ }
1024
+ a { color: var(--iris); text-decoration: none; }
1025
+ a:focus-visible, button:focus-visible {
1026
+ outline: 2px solid var(--iris);
1027
+ outline-offset: 2px;
1028
+ border-radius: 2px;
1029
+ }
1030
+
1031
+ /* \u2500\u2500 \u9876\u680F\uFF1A\u54C1\u724C + \u670D\u52A1\u72B6\u6001 \u2500\u2500 */
1032
+ .top {
1033
+ display: flex;
1034
+ align-items: center;
1035
+ justify-content: space-between;
1036
+ gap: 16px;
1037
+ margin-bottom: clamp(40px, 7vh, 72px);
1038
+ }
1039
+ .brand {
1040
+ display: flex;
1041
+ align-items: center;
1042
+ gap: 10px;
1043
+ font-size: 13px;
1044
+ font-weight: 600;
1045
+ letter-spacing: -0.01em;
1046
+ }
1047
+ .brand svg { display: block; }
1048
+ .brand .ver {
1049
+ font-family: var(--mono);
1050
+ font-weight: 400;
1051
+ font-size: 11px;
1052
+ color: var(--faint);
1053
+ margin-left: 2px;
1054
+ }
1055
+ .status {
1056
+ display: inline-flex;
1057
+ align-items: center;
1058
+ gap: 7px;
1059
+ padding: 5px 10px;
1060
+ border-radius: 999px;
1061
+ background: var(--panel);
1062
+ border: 1px solid var(--line);
1063
+ font-size: 11px;
1064
+ color: var(--sub);
1065
+ white-space: nowrap;
1066
+ }
1067
+ .status .dot {
1068
+ width: 6px;
1069
+ height: 6px;
1070
+ border-radius: 50%;
1071
+ background: var(--faint);
1072
+ flex-shrink: 0;
1073
+ animation: pulse 2.2s ease-in-out infinite;
1074
+ }
1075
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
1076
+ .status.ok { color: #0d7a3e; }
1077
+ .status.ok .dot { background: var(--ok); }
1078
+ .status.err { color: var(--err); }
1079
+ .status.err .dot { background: var(--err); animation: none; }
1080
+
1081
+ /* \u2500\u2500 \u4E3B\u6807\u9898\u533A \u2500\u2500 */
1082
+ h1 {
1083
+ font-size: clamp(28px, 5vw, 38px);
1084
+ font-weight: 650;
1085
+ letter-spacing: -0.025em;
1086
+ line-height: 1.2;
1087
+ margin-bottom: 12px;
1088
+ }
1089
+ .sub {
1090
+ font-size: 14px;
1091
+ color: var(--sub);
1092
+ max-width: 46em;
1093
+ line-height: 1.7;
1094
+ margin-bottom: clamp(40px, 7vh, 64px);
1095
+ }
1096
+
1097
+ /* \u2500\u2500 \u6B65\u9AA4\u5217\u8868\uFF1A\u53D1\u4E1D\u7EBF\u5206\u9694 \u2500\u2500 */
1098
+ .steps { border-top: 1px solid var(--line); }
1099
+ .step {
1100
+ display: grid;
1101
+ grid-template-columns: 44px 1fr;
1102
+ gap: 4px 16px;
1103
+ padding: 26px 0 28px;
1104
+ border-bottom: 1px solid var(--line);
1105
+ }
1106
+ .num {
1107
+ grid-row: 1 / span 2;
1108
+ font-family: var(--mono);
1109
+ font-size: 12px;
1110
+ color: var(--faint);
1111
+ padding-top: 3px;
1112
+ font-variant-numeric: tabular-nums;
1113
+ }
1114
+ .step-head {
1115
+ display: flex;
1116
+ align-items: center;
1117
+ gap: 10px;
1118
+ flex-wrap: wrap;
1119
+ }
1120
+ .step-title {
1121
+ font-size: 14px;
1122
+ font-weight: 600;
1123
+ letter-spacing: -0.01em;
1124
+ }
1125
+ .tag {
1126
+ font-size: 11px;
1127
+ font-weight: 500;
1128
+ color: var(--iris);
1129
+ background: var(--iris-soft);
1130
+ padding: 2px 8px;
1131
+ border-radius: 999px;
1132
+ }
1133
+ .step-body { grid-column: 2; }
1134
+ .note {
1135
+ font-size: 12.5px;
1136
+ color: var(--sub);
1137
+ line-height: 1.75;
1138
+ max-width: 60em;
1139
+ }
1140
+ .note strong { color: var(--ink); font-weight: 600; }
1141
+ code {
1142
+ font-family: var(--mono);
1143
+ background: var(--panel);
1144
+ border: 1px solid var(--line);
1145
+ color: var(--ink);
1146
+ padding: 0.5px 5px;
1147
+ border-radius: 4px;
1148
+ font-size: 11px;
1149
+ }
1150
+ .actions { display: flex; align-items: center; gap: 12px; margin: 14px 0 10px; }
1151
+
1152
+ /* \u4E3B\u6309\u94AE\uFF1A\u53EF\u62D6\u62FD\u7684\u4E66\u7B7E */
1153
+ .bm {
1154
+ display: inline-flex;
1155
+ align-items: center;
1156
+ gap: 8px;
1157
+ padding: 9px 18px;
1158
+ background: var(--iris);
1159
+ color: #fff;
1160
+ border-radius: var(--radius);
1161
+ font-size: 13px;
1162
+ font-weight: 600;
1163
+ letter-spacing: -0.01em;
1164
+ cursor: grab;
1165
+ box-shadow: 0 1px 2px rgba(30, 30, 30, 0.08);
1166
+ transition: background 140ms ease, transform 140ms ease;
1167
+ user-select: none;
1168
+ }
1169
+ .bm:hover { background: var(--iris-strong); }
1170
+ .bm:active { cursor: grabbing; transform: scale(0.985); }
1171
+ .drag-hint {
1172
+ font-size: 12px;
1173
+ color: var(--faint);
1174
+ }
1175
+
1176
+ /* \u6B21\u7EA7\u6309\u94AE */
1177
+ .btn {
1178
+ display: inline-flex;
1179
+ align-items: center;
1180
+ gap: 7px;
1181
+ padding: 8px 14px;
1182
+ border: 1px solid var(--line);
1183
+ border-radius: var(--radius);
1184
+ background: #fff;
1185
+ color: var(--ink);
1186
+ font-size: 13px;
1187
+ font-weight: 500;
1188
+ letter-spacing: -0.01em;
1189
+ text-decoration: none;
1190
+ transition: border-color 140ms ease, background 140ms ease;
1191
+ }
1192
+ .btn:hover { border-color: #d2d2d6; background: var(--panel); }
1193
+ .btn svg { display: block; color: var(--sub); }
1194
+
1195
+ /* MCP \u914D\u7F6E\u4EE3\u7801\u5757 */
1196
+ .cfg {
1197
+ position: relative;
1198
+ margin: 14px 0 10px;
1199
+ }
1200
+ pre {
1201
+ background: var(--panel);
1202
+ border: 1px solid var(--line);
1203
+ border-radius: 8px;
1204
+ padding: 14px 16px;
1205
+ font-family: var(--mono);
1206
+ font-size: 12px;
1207
+ line-height: 1.7;
1208
+ color: #3c3c3c;
1209
+ overflow-x: auto;
1210
+ }
1211
+ .copy {
1212
+ position: absolute;
1213
+ top: 8px;
1214
+ right: 8px;
1215
+ padding: 4px 10px;
1216
+ font-family: inherit;
1217
+ font-size: 11px;
1218
+ font-weight: 500;
1219
+ color: var(--sub);
1220
+ background: #fff;
1221
+ border: 1px solid var(--line);
1222
+ border-radius: var(--radius);
1223
+ cursor: pointer;
1224
+ transition: color 140ms ease, border-color 140ms ease;
1225
+ }
1226
+ .copy:hover { color: var(--ink); border-color: #d2d2d6; }
1227
+ .copy.done { color: var(--ok); border-color: var(--ok); }
982
1228
 
983
- **\u8C03\u8272\u677F\u89C4\u5219**\uFF1A\u540C\u4E00\u989C\u8272\uFF08\u5355\u8272\uFF0C\u542B rgba\uFF09\u51FA\u73B0 \u22652 \u6B21\u65F6\u63D0\u53D6\u5230\u6839\u8282\u70B9 \`palette\` \u6570\u7EC4\uFF0C
984
- \`fills\`\u3001\`text.color\`\u3001\`segs[].color\`\u3001\`stroke\` \u4E2D\u4EE5 \`"p0"\`\u3001\`"p1"\` \u5F15\u7528\uFF08\u4E0B\u6807\u5373 palette \u4E0B\u6807\uFF09\u3002
985
- \`shadow\`\u3001\`gradient\` \u7B49\u590D\u5408 CSS \u5B57\u7B26\u4E32\u5185\u90E8\u7684\u989C\u8272\u4FDD\u6301\u5B57\u9762\u503C\uFF0C\u4E0D\u67E5\u8868\u3002
1229
+ .footer {
1230
+ margin-top: 40px;
1231
+ font-size: 11px;
1232
+ color: var(--faint);
1233
+ font-family: var(--mono);
1234
+ display: flex;
1235
+ gap: 8px;
1236
+ flex-wrap: wrap;
1237
+ }
986
1238
 
987
- ## \u53F6\u5B50\u8282\u70B9\u4E0E\u56FE\u7247\u8D44\u6E90
1239
+ @media (max-width: 560px) {
1240
+ .step { grid-template-columns: 32px 1fr; gap: 4px 10px; }
1241
+ .top { align-items: flex-start; flex-direction: column; gap: 12px; }
1242
+ }
1243
+ @media (prefers-reduced-motion: reduce) {
1244
+ .page { animation: none; }
1245
+ .status .dot { animation: none; }
1246
+ .bm, .btn, .copy { transition: none; }
1247
+ }
1248
+ </style>
1249
+ </head>
1250
+ <body>
1251
+ <div class="page">
1252
+ <header class="top">
1253
+ <div class="brand">
1254
+ <svg width="22" height="22" viewBox="0 0 24 24" aria-hidden="true"><rect width="24" height="24" rx="6" fill="#575BF2"/><path d="M13 4 7.5 13.5H11L10 20l6.5-9.5H12.5Z" fill="#fff"/></svg>
1255
+ Batter Pixso Bridge
1256
+ <span class="ver">v{{ version }}</span>
1257
+ </div>
1258
+ <div class="status" id="status">
1259
+ <span class="dot"></span><span id="status-text">\u6B63\u5728\u8FDE\u63A5\u670D\u52A1\u2026</span>
1260
+ </div>
1261
+ </header>
988
1262
 
989
- - \`type: "ICON"\`\uFF1A\u7EAF\u77E2\u91CF\u5C0F\u8282\u70B9\uFF08icon\uFF09\uFF0C\`img\` \u4E3A **SVG** \u6587\u4EF6\u540D\u3002
990
- - \`type: "IMAGE"\`\uFF1A\u56FE\u7247\u586B\u5145\u8282\u70B9\uFF0C\`img\` \u4E3A **PNG** \u6587\u4EF6\u540D\u3002
991
- - \u6587\u4EF6\u540D\u62FC\u8DEF\u5F84\u4E0E\u53D6\u7528\u8981\u6C42\u89C1\u5F00\u5934\u786C\u6027\u89C4\u5219\u7B2C 1 \u6761\u3002
992
- - \u5BB9\u5668\u81EA\u8EAB\u6709\u5BFC\u51FA\u8BBE\u7F6E\u6807\u8BB0\u65F6\u4E5F\u4F1A\u584C\u7F29\u4E3A\u53F6\u5B50\uFF08\u5C0A\u91CD\u8BBE\u8BA1\u5E08\u7684\u5BFC\u51FA\u610F\u56FE\uFF09\u3002
993
- - **\u5DF2\u5E26 \`img\` \u7684\u8282\u70B9\u5728\u53D6 DSL \u65F6\u5DF2\u5B8C\u6210\u5BFC\u51FA**\uFF1A\u76F4\u63A5\u4F7F\u7528\u5176\u6587\u4EF6\u540D\uFF0C\u4E25\u7981\u5BF9\u5B83\u4EEC\u518D\u8C03 \`export_image\`\uFF08\u91CD\u590D\u5BFC\u51FA\u540C\u4E00\u4EFD\u5185\u5BB9\uFF09\u3002
994
- \`export_image\` \u4EC5\u7528\u4E8E DSL \u4E2D\u672A\u584C\u7F29\u7684\u8282\u70B9\uFF08\u4E3B\u52A8\u5207\u56FE\uFF09\u3002
1263
+ <h1>\u628A\u8BBE\u8BA1\u7A3F\u4EA4\u7ED9 AI</h1>
1264
+ <p class="sub">\u4E00\u6B21\u5B89\u88C5\uFF0C\u8BA9 Claude\u3001Cursor \u7B49 AI \u7F16\u7A0B\u52A9\u624B\u76F4\u63A5\u8BFB\u53D6 Pixso / MasterGo \u753B\u5E03\u7684\u7ED3\u6784\u3001\u6837\u5F0F\u4E0E\u622A\u56FE\u3002\u6309\u4E0B\u9762\u56DB\u6B65\u5B8C\u6210\u8BBE\u7F6E\u3002</p>
995
1265
 
996
- ## \u6CE8\u610F
1266
+ <div class="steps">
1267
+ <div class="step">
1268
+ <span class="num">01</span>
1269
+ <div class="step-head">
1270
+ <span class="step-title">\u6DFB\u52A0\u4E66\u7B7E</span>
1271
+ <span class="tag">\u7F51\u9875\u7248 \xB7 \u63A8\u8350</span>
1272
+ </div>
1273
+ <div class="step-body">
1274
+ <div class="actions">
1275
+ <a class="bm" href="{{ href }}">Batter Bridge</a>
1276
+ <span class="drag-hint">\u2190 \u628A\u8FD9\u4E2A\u6309\u94AE\u62D6\u5230\u6D4F\u89C8\u5668\u4E66\u7B7E\u680F</span>
1277
+ </div>
1278
+ <p class="note">
1279
+ <strong>\u53EA\u9700\u62D6\u62FD\u4E00\u6B21\u3002</strong>\u4EE5\u540E\u6253\u5F00 Pixso / MasterGo \u8BBE\u8BA1\u6587\u4EF6\u65F6\uFF0C\u70B9\u4E00\u4E0B\u4E66\u7B7E\u680F\u91CC\u7684\u300CBatter Bridge\u300D\uFF0C\u63D2\u4EF6\u5C31\u4F1A\u6CE8\u5165\u5F53\u524D\u9875\u9762\u3002
1280
+ \u4E66\u7B7E\u6307\u5411\u672C\u673A\u670D\u52A1 <code>{{ base }}</code>\uFF0C\u670D\u52A1\u91CD\u542F\u540E\u4F9D\u7136\u6709\u6548\u3002\u770B\u4E0D\u5230\u4E66\u7B7E\u680F\uFF1F\u6309 <code>Ctrl + Shift + B</code> \u6253\u5F00\u3002
1281
+ </p>
1282
+ </div>
1283
+ </div>
997
1284
 
998
- - DSL \u662F**\u6709\u635F\u7CBE\u7B80**\uFF1A\u7701\u7565\u4E86\u5E03\u5C14\u8FD0\u7B97\u8DEF\u5F84\u3001\u8499\u7248\u7B49\u7EAF\u8BBE\u8BA1\u7EC6\u8282\uFF0C\u89C6\u89C9\u590D\u6742\u8282\u70B9\u4EE5\u622A\u56FE\u4E3A\u51C6\u3002
999
- - \u5BFC\u51FA\u7C7B\u5DE5\u5177\uFF08\`get_node_dsl\`/\`get_screenshot\`/\`export_image\`\uFF09\u5E26 15 \u5206\u949F\u7ED3\u679C\u7F13\u5B58\uFF08\u63D2\u4EF6\u4FA7 TTL\uFF09\uFF1B
1000
- \u8BBE\u8BA1\u7A3F\u521A\u6539\u8FC7\u4F46\u7ED3\u679C\u5BF9\u4E0D\u4E0A\u65F6\uFF0C\u52A0 \`cache: "refresh"\` \u5F3A\u5236\u5237\u65B0\u3002
1001
- - \u591A\u6587\u4EF6\u573A\u666F\u6240\u6709\u5DE5\u5177\u90FD\u652F\u6301 \`target\` \u53C2\u6570\uFF08sessionId/fileKey\uFF09\uFF0C\u89C1 \`batter://guides/intro\`\u3002
1002
- `;
1003
- GUIDES = [
1004
- {
1005
- name: "intro",
1006
- uri: "batter://guides/intro",
1007
- description: "batter-pixso-mcp \u6982\u89C8\uFF1A\u67B6\u6784\u524D\u63D0\u3001\u5DE5\u5177\u6E05\u5355\u3001\u591A\u6587\u4EF6\u4F1A\u8BDD\u8DEF\u7531\u3002\u4F7F\u7528\u524D\u5FC5\u8BFB\u3002",
1008
- text: INTRO
1009
- },
1010
- {
1011
- name: "design-to-code",
1012
- uri: "batter://guides/design-to-code",
1013
- 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",
1014
- text: DESIGN_TO_CODE
1015
- }
1016
- ];
1017
- }
1018
- });
1285
+ <div class="step">
1286
+ <span class="num">02</span>
1287
+ <div class="step-head">
1288
+ <span class="step-title">\u5B89\u88C5\u5230 Pixso \u5BA2\u6237\u7AEF</span>
1289
+ </div>
1290
+ <div class="step-body">
1291
+ <div class="actions">
1292
+ <a class="btn" href="/plugin.zip" download="batter-pixso-bridge.zip">
1293
+ <svg width="12" height="12" viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12"/><path d="m7 11 5 5 5-5"/><path d="M4 21h16"/></svg>
1294
+ \u4E0B\u8F7D\u63D2\u4EF6\u5305\uFF08.zip\uFF09
1295
+ </a>
1296
+ </div>
1297
+ <p class="note">\u89E3\u538B\u540E\uFF0C\u5728\u5BA2\u6237\u7AEF\u7684\u8BBE\u8BA1\u6587\u4EF6\u4E2D\uFF1A\u53F3\u952E \u2192 \u63D2\u4EF6 \u2192 \u5F00\u53D1\u63D2\u4EF6 \u2192 \u5BFC\u5165 <code>manifest.json</code>\u3002</p>
1298
+ </div>
1299
+ </div>
1019
1300
 
1020
- // src/prompts.ts
1021
- var ASSET_HARD_RULE, DESIGN_TO_CODE2, PROMPTS;
1022
- var init_prompts = __esm({
1023
- "src/prompts.ts"() {
1024
- "use strict";
1025
- ASSET_HARD_RULE = `**\u4E25\u7981\u628A\u4EA7\u7269\u76EE\u5F55\u8DEF\u5F84\u5199\u8FDB\u4EA4\u4ED8\u4EE3\u7801**\uFF1ADSL \u8FD4\u56DE\u7684 \`img\` \u662F\u4EA7\u7269\u6587\u4EF6\u540D\uFF08\u5B8C\u6574\u8DEF\u5F84 = \u4EA7\u7269\u76EE\u5F55 + \u6587\u4EF6\u540D\uFF0C\u76EE\u5F55\u89C1\u5DE5\u5177 description\uFF09\u3002\u622A\u56FE\u7B49\u89C6\u89C9\u53C2\u7167\u7528 Read \u5DE5\u5177\u76F4\u8BFB\u5B8C\u6574\u8DEF\u5F84\u5373\u53EF\uFF1B\u8981\u5199\u8FDB\u4EA4\u4ED8\u4EE3\u7801\u7684\u56FE\u6807/\u56FE\u7247\u5FC5\u987B\u5148\u590D\u5236\u5230\u9879\u76EE\u8D44\u6E90\u76EE\u5F55\uFF08PowerShell \`Copy-Item\` / \u7C7B Unix \`cp\`\uFF09\uFF0C\u590D\u5236\u65F6\u6309\u8282\u70B9\u7528\u9014\u81EA\u884C\u547D\u540D\u8BED\u4E49\u5316\u6587\u4EF6\u540D\uFF08\u5982 \`icon-arrow.svg\`\uFF0C\u4E0D\u7528 hash \u540D\uFF09\uFF0C\u518D\u7528\u672C\u5730\u76F8\u5BF9\u8DEF\u5F84\u5F15\u7528\uFF08\`<img src>\`\u3001\`background-image\` \u7B49\uFF09\u3002\u4EA7\u7269\u9ED8\u8BA4 7 \u5929\u540E\u81EA\u52A8\u6E05\u626B\uFF1B\u4E25\u7981\u79FB\u52A8\u6216\u5220\u9664\u4EA7\u7269\u76EE\u5F55\u4E0B\u7684\u6587\u4EF6\u3002`;
1026
- DESIGN_TO_CODE2 = {
1027
- name: "design-to-code",
1028
- 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",
1029
- arguments: [
1030
- {
1031
- name: "url",
1032
- 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",
1033
- required: false
1034
- }
1035
- ],
1036
- render: (args) => {
1037
- 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`;
1038
- return [
1039
- {
1040
- role: "user",
1041
- content: {
1042
- type: "text",
1043
- text: [
1044
- `\u628A Pixso \u8BBE\u8BA1\u7A3F\u8FD8\u539F\u4E3A\u524D\u7AEF\u4EE3\u7801\u3002\u52A8\u624B\u524D\u5148\u8BB0\u4F4F\u4E0B\u9762\u7684\u786C\u6027\u89C4\u5219\uFF08\u8FDD\u53CD\u5373\u8FD4\u5DE5\uFF09\uFF1A`,
1045
- ``,
1046
- `## \u3010\u786C\u6027\u89C4\u5219\u3011`,
1047
- ``,
1048
- `1. ${ASSET_HARD_RULE}`,
1049
- `2. **\u5199\u4EE3\u7801\u524D\u5FC5\u987B\u5148\u8BFB**\u8D44\u6E90 \`batter://guides/design-to-code\`\uFF0C\u4E86\u89E3 DSL \u5B57\u6BB5\u8BED\u4E49\u3001\u8D44\u6E90\u5904\u7406\u7EC6\u8282\u4E0E\u591A\u6587\u4EF6\u8DEF\u7531\uFF08\`target\` \u53C2\u6570\uFF09\uFF1B\u603B\u89C8\u89C1 \`batter://guides/intro\`\u3002`,
1050
- ``,
1051
- `## \u6267\u884C\u6D41\u7A0B`,
1052
- ``,
1053
- `1. \u786E\u8BA4\u76EE\u6807\u8282\u70B9\uFF1A${targetLine}`,
1054
- `2. \u53D6\u6570\u636E\uFF1A\u8C03 \`get_node_dsl(guid)\` \u62FF\u7ED3\u6784\u4E0E\u6837\u5F0F DSL\uFF08\u7ED3\u6784\u5F88\u5927\u65F6\u5148\u7528 \`maxDepth: 0\` \u770B\u6839\u8282\u70B9 \`depth\` \u9884\u5224\u89C4\u6A21\uFF0C\u518D\u5206\u6BB5\u4E0B\u63A2\u2014\u2014\u622A\u65AD\u5904\u8282\u70B9\u5E26 \`more\` \u6807\u8BB0\uFF0C\u62FF\u5176 \`id\` \u56DE\u4F20 \`guid\` \u7EE7\u7EED\u4E0B\u94BB\uFF09\uFF1B\u8C03 \`get_screenshot(guid)\` \u62FF\u89C6\u89C9\u53C2\u7167\u622A\u56FE\uFF08\u8FD4\u56DE\u6587\u4EF6\u540D\uFF0CRead \u76F4\u8BFB\u672C\u5730\u8DEF\u5F84\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`,
1055
- `3. \u751F\u6210\u4EE3\u7801\uFF1Aauto-layout \u76F4\u8BD1 flex\uFF1B\u989C\u8272/\u5706\u89D2/\u9634\u5F71\u7528 DSL \u7684 CSS \u503C\uFF1B\u56FE\u7247\u4E00\u5F8B\u7528\u5DF2\u590D\u5236\u5230\u9879\u76EE\u7684\u672C\u5730\u8DEF\u5F84\u3002`,
1056
- `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`,
1057
- ``,
1058
- `**\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\u3002`
1059
- ].join("\n")
1060
- }
1061
- }
1062
- ];
1063
- }
1064
- };
1065
- PROMPTS = [DESIGN_TO_CODE2];
1301
+ <div class="step">
1302
+ <span class="num">03</span>
1303
+ <div class="step-head">
1304
+ <span class="step-title">\u914D\u7F6E AI \u5BA2\u6237\u7AEF</span>
1305
+ </div>
1306
+ <div class="step-body">
1307
+ <div class="cfg">
1308
+ <pre id="cfg">{
1309
+ "mcpServers": {
1310
+ "batter-pixso": {
1311
+ "command": "npx",
1312
+ "args": ["-y", "batter-pixso-mcp"],
1313
+ "env": { "HTTP_PORT": "{{ httpPort }}" }
1314
+ }
1066
1315
  }
1067
- });
1316
+ }</pre>
1317
+ <button class="copy" id="copy" type="button">\u590D\u5236</button>
1318
+ </div>
1319
+ <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>
1320
+ </div>
1321
+ </div>
1068
1322
 
1069
- // src/tools/builtin.ts
1070
- var BUILTIN_TOOLS;
1071
- var init_builtin = __esm({
1072
- "src/tools/builtin.ts"() {
1073
- "use strict";
1074
- init_sessions();
1075
- BUILTIN_TOOLS = [
1076
- {
1077
- name: "list_sessions",
1078
- description: "\u3010\u7528\u9014\u3011\u5217\u51FA\u5F53\u524D\u8FDE\u63A5\u5230\u672C\u670D\u52A1\u7684\u6240\u6709 Pixso \u63D2\u4EF6\u4F1A\u8BDD\uFF08sessionId\u3001\u6587\u4EF6\u3001\u9875\u9762\u3001\u5728\u7EBF\u72B6\u6001\uFF09\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\u8FD0\u884C\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 sessionId \u6216 fileKey \u4F5C\u4E3A get_selected / get_node_dsl / get_screenshot / export_image \u7684 target \u53C2\u6570\u4F20\u5165\u5373\u8DEF\u7531\u5230\u5BF9\u5E94\u6587\u4EF6\uFF1B\u4E0D\u4F20 target \u65F6\u8DEF\u7531\u5230\u6700\u8FD1\u6D3B\u8DC3\u7684\u6587\u4EF6\u3002\u591A\u6587\u4EF6\u8DEF\u7531\u8BE6\u89C1\u8D44\u6E90 batter://guides/intro\u3002",
1079
- inputSchema: { type: "object", properties: {} },
1080
- handler: async () => {
1081
- const list = sessions.list();
1082
- const text = list.length ? JSON.stringify(list) : "\u5F53\u524D\u6CA1\u6709\u63D2\u4EF6\u8FDE\u63A5\uFF1A\u8BF7\u5728 Pixso \u5BA2\u6237\u7AEF\u4E2D\u8FD0\u884C Batter Pixso Bridge \u63D2\u4EF6\u3002";
1083
- return { content: [{ type: "text", text }] };
1084
- }
1085
- }
1086
- ];
1087
- }
1088
- });
1323
+ <div class="step">
1324
+ <span class="num">04</span>
1325
+ <div class="step-head">
1326
+ <span class="step-title">\u5F00\u59CB\u4F7F\u7528</span>
1327
+ </div>
1328
+ <div class="step-body">
1329
+ <p class="note" style="margin-top:10px">
1330
+ \u63D2\u4EF6\u8FD0\u884C\u540E\uFF0C\u5728\u753B\u5E03\u4E2D\u9009\u4E2D\u56FE\u5C42\uFF0C\u5BF9 AI \u8BF4\u300C\u5B9E\u73B0\u8FD9\u4E2A\u8BBE\u8BA1\u300D\u3002
1331
+ AI \u4F1A\u81EA\u52A8\u8C03\u7528 <code>get_node_dsl</code>\u3001<code>get_screenshot</code> \u7B49\u5DE5\u5177\u8BFB\u53D6\u8BBE\u8BA1\u5E76\u751F\u6210\u4EE3\u7801\u3002
1332
+ </p>
1333
+ </div>
1334
+ </div>
1335
+ </div>
1089
1336
 
1090
- // src/core/commands.ts
1091
- var CommandBus, commandBus;
1092
- var init_commands = __esm({
1093
- "src/core/commands.ts"() {
1094
- "use strict";
1095
- init_config();
1096
- init_sessions();
1097
- CommandBus = class {
1098
- queues = /* @__PURE__ */ new Map();
1099
- pollWaiters = /* @__PURE__ */ new Map();
1100
- sseListeners = /* @__PURE__ */ new Map();
1101
- pendingById = /* @__PURE__ */ new Map();
1102
- seq = 0;
1103
- /** AI 侧:执行命令 */
1104
- execute(type, args, opts = {}) {
1105
- const session = sessions.resolve(opts.target);
1106
- const timeoutMs = opts.timeoutMs ?? 6e4;
1107
- const { promise, resolve, reject } = Promise.withResolvers();
1108
- const command = { id: `cmd-${++this.seq}`, type, args };
1109
- const entry = {
1110
- command,
1111
- sessionId: session.id,
1112
- resolve,
1113
- reject,
1114
- dispatched: false,
1115
- timer: setTimeout(() => {
1116
- this.drop(entry);
1117
- reject(new Error(`\u547D\u4EE4 ${command.id} \u8D85\u65F6\uFF1A\u63D2\u4EF6 ${timeoutMs / 1e3}s \u5185\u672A\u8FD4\u56DE\u7ED3\u679C`));
1118
- }, timeoutMs)
1119
- };
1120
- opts.signal?.addEventListener("abort", () => {
1121
- if (!this.pendingById.has(command.id)) return;
1122
- this.drop(entry);
1123
- reject(new Error(`\u547D\u4EE4 ${command.id} \u5DF2\u88AB\u8C03\u7528\u65B9\u53D6\u6D88`));
1124
- });
1125
- this.pendingById.set(command.id, entry);
1126
- this.queueOf(session.id).push(entry);
1127
- this.dispatch(session.id);
1128
- return promise;
1129
- }
1130
- /** 插件侧(长轮询兜底通道):取命令,超时返回 null */
1131
- async take(sessionId, timeoutMs = 25e3) {
1132
- const entry = this.nextUndispatched(sessionId);
1133
- if (entry) {
1134
- entry.dispatched = true;
1135
- log(`dispatch command ${entry.command.id}: ${entry.command.type} -> session ${sessionId.slice(0, 8)} (poll)`);
1136
- return entry.command;
1137
- }
1138
- const { promise, resolve } = Promise.withResolvers();
1139
- const waiters = this.waitersOf(sessionId);
1140
- waiters.push(resolve);
1141
- setTimeout(() => {
1142
- const i = waiters.indexOf(resolve);
1143
- if (i >= 0) {
1144
- waiters.splice(i, 1);
1145
- resolve(null);
1146
- }
1147
- }, timeoutMs);
1148
- return promise;
1149
- }
1150
- /** 插件侧(SSE 主通道):订阅即时推送,返回退订函数 */
1151
- subscribe(sessionId, listener) {
1152
- let subs = this.sseListeners.get(sessionId);
1153
- if (!subs) {
1154
- subs = /* @__PURE__ */ new Set();
1155
- this.sseListeners.set(sessionId, subs);
1156
- }
1157
- subs.add(listener);
1158
- this.dispatch(sessionId);
1159
- return () => {
1160
- subs.delete(listener);
1161
- if (subs.size === 0) this.sseListeners.delete(sessionId);
1162
- };
1163
- }
1164
- /** 插件侧:回传结果 */
1165
- complete(id, ok, resultOrError) {
1166
- const entry = this.pendingById.get(id);
1167
- if (!entry) return false;
1168
- this.drop(entry);
1169
- if (ok) {
1170
- entry.resolve(resultOrError);
1171
- } else {
1172
- entry.reject(resultOrError instanceof Error ? resultOrError : new Error(String(resultOrError)));
1173
- }
1174
- return true;
1175
- }
1176
- dispatch(sessionId) {
1177
- const entry = this.nextUndispatched(sessionId);
1178
- if (!entry) return;
1179
- const subs = this.sseListeners.get(sessionId);
1180
- if (subs && subs.size > 0) {
1181
- entry.dispatched = true;
1182
- log(`dispatch command ${entry.command.id}: ${entry.command.type} -> session ${sessionId.slice(0, 8)} (sse)`);
1183
- for (const fn of subs) fn(entry.command);
1184
- return;
1185
- }
1186
- const waiter = this.waitersOf(sessionId).shift();
1187
- if (!waiter) return;
1188
- entry.dispatched = true;
1189
- log(`dispatch command ${entry.command.id}: ${entry.command.type} -> session ${sessionId.slice(0, 8)} (poll)`);
1190
- waiter(entry.command);
1191
- }
1192
- nextUndispatched(sessionId) {
1193
- return this.queueOf(sessionId).find((e) => !e.dispatched);
1194
- }
1195
- queueOf(sessionId) {
1196
- let q = this.queues.get(sessionId);
1197
- if (!q) {
1198
- q = [];
1199
- this.queues.set(sessionId, q);
1200
- }
1201
- return q;
1202
- }
1203
- waitersOf(sessionId) {
1204
- let w = this.pollWaiters.get(sessionId);
1205
- if (!w) {
1206
- w = [];
1207
- this.pollWaiters.set(sessionId, w);
1208
- }
1209
- return w;
1210
- }
1211
- /** 从队列与 pending 表移除(完成/超时/取消共用) */
1212
- drop(entry) {
1213
- clearTimeout(entry.timer);
1214
- this.pendingById.delete(entry.command.id);
1215
- const q = this.queues.get(entry.sessionId);
1216
- if (q) this.queues.set(entry.sessionId, q.filter((e) => e !== entry));
1217
- }
1218
- };
1219
- commandBus = new CommandBus();
1220
- }
1337
+ <div class="footer">
1338
+ <span>batter-pixso-mcp</span>
1339
+ <span>\xB7</span>
1340
+ <span>v{{ version }}</span>
1341
+ <span>\xB7</span>
1342
+ <span>{{ base }}</span>
1343
+ </div>
1344
+ </div>
1345
+ <script>
1346
+ fetch("/health").then(function (r) { return r.json(); }).then(function (h) {
1347
+ var el = document.getElementById("status");
1348
+ el.className = "status ok";
1349
+ document.getElementById("status-text").textContent =
1350
+ "\u670D\u52A1\u8FD0\u884C\u4E2D \xB7 \u63D2\u4EF6 " + h.pluginsOnline + " \u5728\u7EBF";
1351
+ }).catch(function () {
1352
+ var el = document.getElementById("status");
1353
+ el.className = "status err";
1354
+ document.getElementById("status-text").textContent = "\u670D\u52A1\u672A\u8FDE\u63A5";
1221
1355
  });
1222
1356
 
1223
- // src/tools/plugin.ts
1224
- function jsonResult(data) {
1225
- return { content: [{ type: "text", text: JSON.stringify(data) }] };
1226
- }
1227
- function errorResult(err) {
1228
- return {
1229
- content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }],
1230
- isError: true
1231
- };
1232
- }
1233
- async function run(type, args, signal) {
1234
- const { target, ...rest } = args;
1235
- try {
1236
- return jsonResult(
1237
- await commandBus.execute(type, rest, { target, signal })
1238
- );
1239
- } catch (err) {
1240
- return errorResult(err);
1357
+ var copyBtn = document.getElementById("copy");
1358
+ copyBtn.addEventListener("click", function () {
1359
+ var text = document.getElementById("cfg").textContent;
1360
+ function done() {
1361
+ copyBtn.textContent = "\u5DF2\u590D\u5236";
1362
+ copyBtn.className = "copy done";
1363
+ setTimeout(function () {
1364
+ copyBtn.textContent = "\u590D\u5236";
1365
+ copyBtn.className = "copy";
1366
+ }, 1600);
1241
1367
  }
1242
- }
1243
- var URL_RULE, URL_RULE_PTR, SELECT_RULE_PTR, ASSET_RULE, DSL_LEGEND, TARGET_PROP, CACHE_PROP, GUID_PROP, PLUGIN_TOOLS;
1244
- var init_plugin = __esm({
1245
- "src/tools/plugin.ts"() {
1246
- "use strict";
1247
- init_assets();
1248
- init_commands();
1249
- URL_RULE = '\u2460 \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';
1250
- URL_RULE_PTR = "\u2460 \u94FE\u63A5\u8F93\u5165\uFF1A\u5FC5\u987B\u4ECE item-id \u63D0\u53D6 guid \u5E76 URL \u89E3\u7801\u540E\u4F20\u5165\uFF0C\u4E25\u7981\u4F20\u6574\u4E2A URL\uFF08\u89C4\u5219\u5168\u6587\u89C1 get_node_dsl\uFF09\u3002";
1251
- SELECT_RULE_PTR = '\u2461 \u9009\u4E2D\u56DE\u9000\uFF1A\u672A\u4F20 guid \u65F6\u56DE\u9000\u5F53\u524D\u9009\u4E2D\u56FE\u5C42\uFF0C\u4F46\u4E25\u7981\u9690\u5F0F\u4F7F\u7528\u2014\u2014\u9664\u975E\u7528\u6237\u660E\u786E\u8BF4\u4E86"\u6211\u9009\u4E2D\u7684/\u5F53\u524D\u9009\u4E2D\u7684"\uFF0C\u5FC5\u987B\u5148\u8C03 get_selected \u628A\u9009\u4E2D\u56FE\u5C42\u540D\u79F0\u62A5\u7ED9\u7528\u6237\u786E\u8BA4\uFF08\u89C4\u5219\u5168\u6587\u89C1 get_selected\uFF09\u3002';
1252
- ASSET_RULE = `\u2462 \u8D44\u6E90\uFF1Aimg \u662F\u4EA7\u7269\u6587\u4EF6\u540D\uFF08\u5185\u5BB9 hash \u547D\u540D\uFF0C\u5982 "6a4d1730d46bae01.svg"\uFF09\uFF0C\u5B8C\u6574\u672C\u5730\u8DEF\u5F84 = ${assetDir()}\\<\u6587\u4EF6\u540D>\uFF08\u672C\u673A\u6587\u4EF6\uFF0C\u76F4\u63A5\u8BFB\u6587\u4EF6\u7CFB\u7EDF\uFF09\u3002\u622A\u56FE\u7B49\u89C6\u89C9\u53C2\u7167\u7528 Read \u5DE5\u5177\u76F4\u8BFB\u8BE5\u8DEF\u5F84\uFF0C\u65E0\u9700\u590D\u5236\uFF1B\u8981\u5199\u8FDB\u4EA4\u4ED8\u4EE3\u7801\u7684\u56FE\u6807/\u56FE\u7247\u5FC5\u987B\u5148\u590D\u5236\u5230\u9879\u76EE\u8D44\u6E90\u76EE\u5F55\uFF08PowerShell: Copy-Item "<\u5B8C\u6574\u8DEF\u5F84>" "<\u9879\u76EE\u76EE\u5F55>"\uFF1B\u7C7B Unix: cp "<\u5B8C\u6574\u8DEF\u5F84>" "<\u9879\u76EE\u76EE\u5F55>"\uFF09\uFF0C\u518D\u4EE5\u672C\u5730\u76F8\u5BF9\u8DEF\u5F84\u5F15\u7528\u3002\u590D\u5236\u65F6\u5FC5\u987B\u6309\u8282\u70B9\u7528\u9014\u81EA\u884C\u547D\u540D\u8BED\u4E49\u5316\u76EE\u6807\u6587\u4EF6\u540D\uFF08\u5982 icon-arrow.svg\u3001hero-banner.png\uFF1Bexport_image \u8FD4\u56DE\u7684 suffix \u5EFA\u8BAE\u4FDD\u7559\uFF09\uFF0C\u4E25\u7981\u628A hash \u6587\u4EF6\u540D\u5E26\u8FDB\u4EA4\u4ED8\u4EE3\u7801\u3002\u4E25\u7981\u76F4\u63A5\u5F15\u7528\u4EA7\u7269\u76EE\u5F55\u7684\u7EDD\u5BF9\u8DEF\u5F84\uFF08\u4EA7\u7269\u9ED8\u8BA4 7 \u5929\u540E\u81EA\u52A8\u6E05\u626B\uFF0C\u5F15\u7528\u4F1A\u5931\u6548\uFF09\uFF0C\u4E25\u7981\u79FB\u52A8\u6216\u5220\u9664\u4EA7\u7269\u76EE\u5F55\u4E0B\u7684\u6587\u4EF6\uFF08\u591A\u4F1A\u8BDD\u5171\u4EAB\uFF0C\u6E05\u626B\u7531\u670D\u52A1\u7EDF\u4E00\u8D1F\u8D23\uFF09\u3002`;
1253
- DSL_LEGEND = '\u5B57\u6BB5\u56FE\u4F8B\uFF1Aid=\u8282\u70B9 guid\uFF08\u53EF\u56DE\u4F20\u4E3A guid \u53C2\u6570\u5BF9\u5B50\u8282\u70B9\u4E0B\u94BB\u8C03\u7528\uFF09\uFF1Bname=\u56FE\u5C42\u540D\uFF08\u81EA\u52A8\u547D\u540D\u5982 "Frame 3123" \u5DF2\u7701\u7565\uFF09\uFF1Btype=\u8282\u70B9\u7C7B\u578B\uFF0CICON/IMAGE \u4E3A\u584C\u7F29\u53F6\u5B50\uFF1Brect=[x,y,w,h] \u7EDD\u5BF9\u5B9A\u4F4D\uFF08\u6839\u8282\u70B9\u4E0E\u7EDD\u5BF9\u5B9A\u4F4D\u5BB9\u5668\u7684\u5B50\u8282\u70B9\uFF09\uFF1Bsize=[w,h]\uFF08auto-layout \u5B50\u8282\u70B9\uFF0Cx/y \u7531\u7236\u7EA7 layout \u63A8\u5BFC\u6545\u7701\u7565\uFF09\uFF1Blayout={dir:row|column, gap, pad:[\u4E0A,\u53F3,\u4E0B,\u5DE6], align:[\u4E3B\u8F74,\u4EA4\u53C9\u8F74](start|center|end|space-between|baseline), sizing:[\u4E3B\u8F74,\u4EA4\u53C9\u8F74](hug|fixed|fill)}\uFF1Bconstraints={h,v} \u7EDD\u5BF9\u5B9A\u4F4D\u5B50\u8282\u70B9\u7EA6\u675F\uFF08\u9ED8\u8BA4 left/top \u7701\u7565\uFF09\uFF1Btext={content, size=\u5B57\u53F7, font=\u5B57\u65CF, weight=\u5B57\u91CD, lh=\u884C\u9AD8, align=\u6C34\u5E73\u5BF9\u9F50, color, deco=underline|line-through, ls=\u5B57\u8DDD px, link, segs=\u6DF7\u6392\u5206\u6BB5}\uFF08segs \u6BCF\u6BB5\u53EA\u5E26\u4E0E base \u4E0D\u540C\u7684\u5B57\u6BB5\uFF0C\u6570\u7EC4\u987A\u5E8F\u5373\u5B57\u7B26\u504F\u79FB\uFF09\uFF1Bimg=\u8D44\u6E90\u6587\u4EF6\u540D\uFF1Bkids=\u5B50\u8282\u70B9\u6570\u7EC4\uFF1Bpalette=\u8C03\u8272\u677F\uFF08\u4EC5\u6839\u8282\u70B9\uFF09\u3002depth=\u6574\u68F5\u6811\u5B9E\u9645\u6700\u5927\u6DF1\u5EA6\uFF08\u4EC5\u6839\u8282\u70B9\uFF0C\u603B\u662F\u8F93\u51FA\uFF0C\u4E0E\u622A\u65AD\u65E0\u5173\uFF09\uFF1Bmore=\u8BE5\u8282\u70B9\u4E0B\u672A\u5C55\u5F00\u7684\u5269\u4F59\u5C42\u7EA7\u6570\uFF08\u4EC5\u56E0 maxDepth \u622A\u65AD\u65F6\u51FA\u73B0\uFF0C\u62FF id \u56DE\u4F20 guid \u53EF\u7EE7\u7EED\u4E0B\u94BB\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\u8C03\u8272\u677F\uFF1A\u51FA\u73B0\u22652 \u6B21\u7684\u5355\u8272\u63D0\u53D6\u5230\u6839\u8282\u70B9 palette \u6570\u7EC4\uFF0Cfills/text.color/stroke \u4E2D\u7684 "p0""p1" \u4E3A palette \u4E0B\u6807\u5F15\u7528\uFF1Bshadow/gradient \u7B49\u590D\u5408 CSS \u4E32\u5185\u7684\u989C\u8272\u4FDD\u6301\u5B57\u9762\u503C\uFF0C\u4E0D\u67E5\u8868\u3002';
1254
- TARGET_PROP = {
1255
- type: "string",
1256
- description: "\u76EE\u6807\u63D2\u4EF6\u4F1A\u8BDD\uFF08sessionId \u6216 fileKey\uFF09\u3002\u4EC5\u5F53\u591A\u4E2A Pixso \u6587\u4EF6\u540C\u65F6\u8FD0\u884C\u63D2\u4EF6\u65F6\u9700\u8981\u6307\u5B9A\uFF1B\u7F3A\u7701\u8DEF\u7531\u5230\u6700\u8FD1\u6D3B\u8DC3\u7684\u6587\u4EF6\u3002\u53EF\u7528 list_sessions \u67E5\u770B\u5F53\u524D\u4F1A\u8BDD\u3002"
1257
- };
1258
- CACHE_PROP = {
1259
- type: "string",
1260
- enum: ["first", "refresh"],
1261
- 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\u7F13\u5B58\uFF09\uFF1Brefresh=\u4E0D\u8BFB\u7F13\u5B58\u3001\u5F3A\u5236\u91CD\u65B0\u6784\u5EFA/\u5BFC\u51FA\uFF0C\u7ED3\u679C\u4ECD\u5199\u5165\u7F13\u5B58\u3002\u8BBE\u8BA1\u7A3F\u521A\u4FEE\u6539\u8FC7\u4E14\u7ED3\u679C\u5BF9\u4E0D\u4E0A\u65F6\u7528 refresh\u3002"
1262
- };
1263
- GUID_PROP = {
1264
- type: "string",
1265
- description: '\u8282\u70B9 GUID\uFF08\u5982 "297:133032"\uFF09\u3002\u94FE\u63A5\u8F93\u5165\uFF1A\u4ECE item-id \u67E5\u8BE2\u53C2\u6570\u63D0\u53D6\u5E76 URL \u89E3\u7801\u540E\u4F20\u5165\uFF1B\u7F3A\u7701\u56DE\u9000\u5F53\u524D\u9009\u4E2D\u56FE\u5C42\uFF0C\u4F46\u7981\u6B62\u9690\u5F0F\u4F7F\u7528\u8BE5\u56DE\u9000\uFF08\u9009\u4E2D\u786E\u8BA4\u89C4\u5219\u89C1\u5DE5\u5177 description\uFF09\u3002'
1266
- };
1267
- PLUGIN_TOOLS = [
1268
- {
1269
- name: "get_selected",
1270
- description: '\u3010\u7528\u9014\u3011\u83B7\u53D6 Pixso \u753B\u5E03\u5F53\u524D\u9009\u4E2D\u7684\u56FE\u5C42\u5217\u8868\uFF08guid\u3001\u540D\u79F0\u3001\u7C7B\u578B\u3001\u5C3A\u5BF8\uFF09\uFF0C\u5B9E\u65F6\u8BFB\u53D6\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011\u7528\u6237\u8BF4"\u8FD9\u4E2A/\u5F53\u524D\u9009\u4E2D\u7684"\u56FE\u5C42\u3001\u7EC4\u4EF6\u6216\u5143\u7D20\u3002\u7528\u6237\u5DF2\u63D0\u4F9B Pixso \u94FE\u63A5\u65F6\u4E0D\u8981\u8C03\u672C\u5DE5\u5177\u2014\u2014\u76F4\u63A5\u4ECE\u94FE\u63A5\u63D0\u53D6 guid\uFF08\u89C4\u5219\u89C1 get_node_dsl\uFF09\u3002\u591A\u6587\u4EF6\u8DEF\u7531\u7528 list_sessions\uFF0C\u4E0D\u7528\u672C\u5DE5\u5177\u3002\u3010\u8FD4\u56DE\u3011\u9009\u4E2D\u56FE\u5C42\u5217\u8868 JSON\u3002\u3010\u786C\u6027\u89C4\u5219\u3011\u4E25\u7981\u9690\u5F0F\u4F7F\u7528\u672C\u5DE5\u5177\u8FD4\u56DE\u7684 guid\uFF1A\u4EC5\u5F53\u7528\u6237\u660E\u786E\u8BF4\u4E86"\u6211\u9009\u4E2D\u7684/\u5F53\u524D\u9009\u4E2D\u7684"\uFF0C\u62FF\u5230 guid \u624D\u53EF\u76F4\u63A5\u7EE7\u7EED\uFF1B\u5176\u4F59\u573A\u666F\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\u628A guid \u4F20\u7ED9\u5176\u4ED6\u5DE5\u5177\u2014\u2014\u907F\u514D\u9009\u533A\u7406\u89E3\u9519\u800C\u767D\u505A\u3002',
1271
- inputSchema: { type: "object", properties: { target: TARGET_PROP } },
1272
- handler: (args, ctx) => run("get_selected", args, ctx?.signal)
1273
- },
1274
- {
1275
- name: "get_node_dsl",
1276
- 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\uFF0C\u4F18\u5148\u7528\u672C\u5DE5\u5177\u7406\u89E3\u8BBE\u8BA1\u7ED3\u6784\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011\u7528\u6237\u8981\u6C42"\u6837\u5F0F\u8FD8\u539F/\u8BBE\u8BA1\u8FD8\u539F/UI \u8FD8\u539F/\u8BBE\u8BA1\u8F6C\u4EE3\u7801/\u6839\u636E\u8BBE\u8BA1\u7A3F\u6216\u8BBE\u8BA1\u56FE\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\u7ED3\u6784\u590D\u6742\u60F3\u5148\u770B\u6574\u4F53\u6846\u67B6\u65F6\uFF1AmaxDepth \u4F20 0 \u53EA\u62FF\u6839\u8282\u70B9\uFF08depth \u5B57\u6BB5=\u6574\u6811\u5B9E\u9645\u6DF1\u5EA6\uFF09\u9884\u5224\u89C4\u6A21\uFF0C\u518D\u5BF9\u5E26 more \u6807\u8BB0\u7684\u5B50\u6811\u5206\u6BB5\u4E0B\u94BB\u3002\u3010\u8FD4\u56DE\u3011\u5355\u884C\u7D27\u51D1 JSON\u3002' + DSL_LEGEND + "\u53F6\u5B50\u584C\u7F29\u89C4\u5219\uFF1Aicon/\u7EAF\u77E2\u91CF\u5C0F\u8282\u70B9\uFF08\u226464px \u65E0\u6587\u672C\uFF09\u5BFC\u51FA SVG\u3001\u56FE\u7247\u586B\u5145\u8282\u70B9\u5BFC\u51FA PNG\u3001\u8BBE\u8BA1\u5E08\u6807\u8BB0\u6309\u5176\u8BBE\u7F6E\u5BFC\u51FA\u3002\u5DF2\u5E26 img \u7684\u8282\u70B9\u5373\u5B8C\u6210\u5BFC\u51FA\uFF0C\u76F4\u63A5\u4F7F\u7528\u5176\u6587\u4EF6\u540D\uFF0C\u65E0\u9700\u518D\u8C03 export_image\u3002DSL \u662F\u6709\u635F\u7CBE\u7B80\uFF08\u7701\u7565\u8499\u7248\u3001\u5E03\u5C14\u8FD0\u7B97\u8DEF\u5F84\u7B49\uFF09\uFF0C\u89C6\u89C9\u590D\u6742\u8282\u70B9\u914D\u5408 get_screenshot \u5BF9\u7167\u3002\u5B57\u6BB5\u8BED\u4E49\u8BE6\u89C1\u8D44\u6E90 batter://guides/design-to-code\u3002\u3010\u786C\u6027\u89C4\u5219\u3011" + URL_RULE + SELECT_RULE_PTR + ASSET_RULE,
1277
- inputSchema: {
1278
- type: "object",
1279
- properties: {
1280
- guid: GUID_PROP,
1281
- maxDepth: {
1282
- type: "number",
1283
- 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\u7B2C N \u5C42\u8282\u70B9\u82E5\u4ECD\u6709\u5B50\u8282\u70B9\u5219\u5E26 more \u5B57\u6BB5\uFF08\u5269\u4F59\u672A\u5C55\u5F00\u5C42\u7EA7\u6570\uFF09\uFF0C\u62FF\u5176 id \u56DE\u4F20 guid \u53EF\u7EE7\u7EED\u4E0B\u94BB\u3002\u4F20 0 \u53EA\u8FD4\u56DE\u6839\u8282\u70B9\uFF1A\u914D\u5408\u6839\u8282\u70B9 depth \u5B57\u6BB5\uFF08\u6574\u6811\u5B9E\u9645\u6DF1\u5EA6\uFF09\u5EC9\u4EF7\u9884\u5224\u89C4\u6A21\uFF0C\u518D\u51B3\u5B9A\u4E0B\u94BB\u7B56\u7565\u3002"
1284
- },
1285
- cache: CACHE_PROP,
1286
- target: TARGET_PROP
1287
- }
1288
- },
1289
- handler: (args, ctx) => run("get_node_dsl", args, ctx?.signal)
1290
- },
1291
- {
1292
- name: "export_image",
1293
- description: '\u3010\u7528\u9014\u3011\u6309 guid \u5BFC\u51FA\u56FE\u7247\u7D20\u6750\uFF08\u5207\u56FE\uFF09\uFF0C\u4EA7\u7269\u4F9B\u4EA4\u4ED8\u4EE3\u7801\u4F7F\u7528\u3002\u6BCF\u6B21\u53EA\u5904\u7406\u4E00\u4E2A\u8282\u70B9\uFF1B\u591A\u8282\u70B9\u573A\u666F\u9010\u4E2A\u8C03\u7528\u3002\u4EC5\u7528\u4E8E DSL \u4E2D\u672A\u584C\u7F29\u7684\u8282\u70B9\u2014\u2014get_node_dsl \u8FD4\u56DE\u91CC\u5DF2\u5E26 img \u5B57\u6BB5\u7684\u8282\u70B9\uFF08ICON/IMAGE \u53F6\u5B50\uFF09\u5728\u53D6 DSL \u65F6\u5DF2\u5B8C\u6210\u5BFC\u51FA\uFF0C\u76F4\u63A5\u4F7F\u7528\u5176 img \u6587\u4EF6\u540D\u5373\u53EF\uFF0C\u4E25\u7981\u5BF9\u8FD9\u7C7B\u8282\u70B9\u91CD\u590D\u8C03\u7528\u672C\u5DE5\u5177\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011\u7528\u6237\u8981"\u5207\u56FE/\u5BFC\u51FA\u56FE\u6807/\u5BFC\u51FA\u56FE\u7247\u7D20\u6750/\u628A\u67D0\u4E2A\u8282\u70B9\u5B58\u6210\u56FE\u7247"\uFF0C\u6216\u8BBE\u8BA1\u8FD8\u539F\u4E2D\u9700\u8981\u63D0\u53D6\u56FE\u7247\u8D44\u6E90\u3002\u8981\u89C6\u89C9\u53C2\u7167/\u8FD8\u539F\u5EA6\u6821\u9A8C\u7528 get_screenshot\u3002\u3010\u8FD4\u56DE\u3011{width, height, exports: [{img, format, scale?, suffix?}]}\uFF1A\u8282\u70B9\u5E26\u8BBE\u8BA1\u5E08\u5BFC\u51FA\u8BBE\u7F6E\uFF08exportSettings\uFF09\u65F6\u6309\u5176\u8BBE\u7F6E\u9010\u4EFD\u5BFC\u51FA\uFF08\u683C\u5F0F/\u500D\u7387/\u540E\u7F00\u5168\u90E8\u751F\u6548\uFF0Cformat/scale \u53C2\u6570\u65E0\u6548\uFF09\uFF1B\u65E0\u6807\u8BB0\u8282\u70B9\u6309\u53C2\u6570\u5BFC\u51FA\u5355\u4EFD\uFF08\u9ED8\u8BA4 PNG\u30012x\uFF09\u3002img \u4E3A\u4EA7\u7269\u6587\u4EF6\u540D\uFF1Bsuffix \u4E3A\u8BBE\u8BA1\u5E08\u8BBE\u7F6E\u7684\u6587\u4EF6\u540D\u540E\u7F00\uFF08\u5982 "@2x"\uFF09\uFF0C\u590D\u5236\u8FDB\u9879\u76EE\u65F6\u5EFA\u8BAE\u4FDD\u7559\u547D\u540D\u3002\u3010\u786C\u6027\u89C4\u5219\u3011' + URL_RULE_PTR + SELECT_RULE_PTR + ASSET_RULE,
1294
- inputSchema: {
1295
- type: "object",
1296
- properties: {
1297
- guid: GUID_PROP,
1298
- format: {
1299
- type: "string",
1300
- enum: ["PNG", "SVG", "JPG"],
1301
- 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"
1302
- },
1303
- scale: {
1304
- type: "number",
1305
- 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"
1306
- },
1307
- cache: CACHE_PROP,
1308
- target: TARGET_PROP
1309
- }
1310
- },
1311
- handler: (args, ctx) => run("export_image", args, ctx?.signal)
1312
- },
1313
- {
1314
- name: "get_screenshot",
1315
- description: '\u3010\u7528\u9014\u3011\u5BFC\u51FA\u5355\u4E2A\u8282\u70B9\u7684\u622A\u56FE\uFF0C\u7528\u4E8E\u83B7\u53D6\u8BBE\u8BA1\u7684\u89C6\u89C9\u53C2\u7167\u3002\u6BCF\u6B21\u53EA\u5904\u7406\u4E00\u4E2A\u8282\u70B9\uFF1B\u591A\u8282\u70B9\u573A\u666F\u9010\u4E2A\u8C03\u7528\u3002\u8981\u5207\u56FE/\u5BFC\u51FA\u4EA4\u4ED8\u7D20\u6750\u7528 export_image\u3002\u3010\u547D\u4E2D\u4FE1\u53F7\u3011\u7528\u6237\u8BF4"\u770B\u770B\u8FD9\u4E2A\u8BBE\u8BA1\u7A3F/\u8BBE\u8BA1\u56FE\u957F\u4EC0\u4E48\u6837""\u7ED9\u6211\u622A\u56FE"\uFF0C\u6216\u8FD8\u539F\u7C7B\u4EFB\u52A1\u9700\u8981\u89C6\u89C9\u53C2\u7167\u3001\u8FD8\u539F\u5EA6\u6821\u9A8C\u3001\u6837\u5F0F\u5BF9\u6BD4\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' + URL_RULE_PTR + SELECT_RULE_PTR + ASSET_RULE,
1316
- inputSchema: {
1317
- type: "object",
1318
- properties: {
1319
- guid: GUID_PROP,
1320
- format: {
1321
- type: "string",
1322
- enum: ["PNG", "SVG", "JPG", "WEBP"],
1323
- 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"
1324
- },
1325
- scale: {
1326
- type: "number",
1327
- description: "\u5BFC\u51FA\u500D\u7387\uFF0C\u9ED8\u8BA4 2\u3002"
1328
- },
1329
- cache: CACHE_PROP,
1330
- target: TARGET_PROP
1331
- }
1332
- },
1333
- handler: (args, ctx) => run("get_screenshot", args, ctx?.signal)
1334
- }
1335
- ];
1368
+ if (navigator.clipboard && navigator.clipboard.writeText) {
1369
+ navigator.clipboard.writeText(text).then(done, done);
1370
+ } else {
1371
+ var ta = document.createElement("textarea");
1372
+ ta.value = text;
1373
+ document.body.appendChild(ta);
1374
+ ta.select();
1375
+ try { document.execCommand("copy"); } catch (e) {}
1376
+ document.body.removeChild(ta);
1377
+ done();
1336
1378
  }
1337
1379
  });
1380
+ </script>
1381
+ </body>
1382
+ </html>
1383
+ `;
1338
1384
 
1339
- // src/tools/registry.ts
1340
- function resolveTools() {
1341
- const seenNames = /* @__PURE__ */ new Set();
1342
- const resolved = [];
1343
- for (const def of TOOL_REGISTRY) {
1344
- if (seenNames.has(def.name)) continue;
1345
- seenNames.add(def.name);
1346
- resolved.push({
1347
- def,
1348
- tool: {
1349
- name: def.name,
1350
- description: def.description,
1351
- inputSchema: def.inputSchema
1352
- }
1353
- });
1354
- }
1355
- return resolved;
1385
+ // src/install/page.ts
1386
+ function bookmarkletHref(base) {
1387
+ const source = renderTemplate(bookmarklet_default, { base }, escapeJsString);
1388
+ return `javascript:${encodeURIComponent(source)}`;
1356
1389
  }
1357
- var TOOL_REGISTRY;
1358
- var init_registry = __esm({
1359
- "src/tools/registry.ts"() {
1360
- "use strict";
1361
- init_builtin();
1362
- init_plugin();
1363
- TOOL_REGISTRY = [
1364
- ...PLUGIN_TOOLS,
1365
- ...BUILTIN_TOOLS
1366
- ];
1367
- }
1368
- });
1369
-
1370
- // src/mcp-local.ts
1371
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1372
- import {
1373
- CallToolRequestSchema,
1374
- GetPromptRequestSchema,
1375
- ListPromptsRequestSchema,
1376
- ListResourceTemplatesRequestSchema,
1377
- ListToolsRequestSchema
1378
- } from "@modelcontextprotocol/sdk/types.js";
1379
- function createMcpServer() {
1380
- const server = new McpServer(
1381
- { name: "batter-pixso-mcp", version: VERSION },
1382
- { capabilities: { tools: {}, resources: {}, prompts: {} }, instructions: INSTRUCTIONS }
1383
- );
1384
- for (const guide of GUIDES) {
1385
- server.registerResource(
1386
- guide.name,
1387
- guide.uri,
1388
- {
1389
- description: guide.description,
1390
- mimeType: "text/markdown",
1391
- // 纯 agent 消费的工作流文档,高优先级提示读取
1392
- annotations: { audience: ["assistant"], priority: 0.9 }
1393
- },
1394
- async (uri) => ({
1395
- contents: [{ uri: uri.href, mimeType: "text/markdown", text: guide.text }]
1396
- })
1397
- );
1398
- }
1399
- server.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
1400
- resourceTemplates: []
1401
- }));
1402
- const tools = resolveTools();
1403
- const byName = {};
1404
- for (const t of tools) {
1405
- byName[t.def.name] = t;
1406
- }
1407
- server.server.setRequestHandler(ListToolsRequestSchema, async () => ({
1408
- tools: tools.map((t) => t.tool)
1409
- }));
1410
- server.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
1411
- const { name, arguments: args = {} } = request.params;
1412
- const entry = byName[name];
1413
- if (!entry) {
1414
- throw new Error(`unknown tool: ${name}`);
1415
- }
1416
- let finalArgs = args;
1417
- if (entry.def.beforeCall) {
1418
- finalArgs = entry.def.beforeCall(finalArgs);
1419
- }
1420
- let result = await entry.def.handler(finalArgs, { signal: extra.signal });
1421
- if (entry.def.afterCall) {
1422
- result = entry.def.afterCall(result);
1423
- }
1424
- return result;
1425
- });
1426
- server.server.setRequestHandler(ListPromptsRequestSchema, async () => ({
1427
- prompts: PROMPTS.map((p) => ({
1428
- name: p.name,
1429
- description: p.description,
1430
- arguments: p.arguments
1431
- }))
1432
- }));
1433
- server.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
1434
- const entry = PROMPTS.find((p) => p.name === request.params.name);
1435
- if (!entry) {
1436
- throw new Error(`unknown prompt: ${request.params.name}`);
1437
- }
1438
- const args = request.params.arguments ?? {};
1439
- return { description: entry.description, messages: entry.render(args) };
1390
+ function renderInstallPage() {
1391
+ const base = `${hubBaseUrl()}/`;
1392
+ return renderTemplate(install_page_default, {
1393
+ version: VERSION,
1394
+ href: bookmarkletHref(base),
1395
+ base,
1396
+ httpPort: config.httpPort
1440
1397
  });
1441
- return server;
1442
1398
  }
1443
- var INSTRUCTIONS;
1444
- var init_mcp_local = __esm({
1445
- "src/mcp-local.ts"() {
1446
- "use strict";
1447
- init_config();
1448
- init_guides();
1449
- init_prompts();
1450
- init_registry();
1451
- INSTRUCTIONS = `Pixso \u8BBE\u8BA1\u7A3F\u8F6C\u4EE3\u7801\u670D\u52A1\u3002\u6309\u7528\u6237\u8F93\u5165\u8DEF\u7531\uFF1A\u2460 \u7ED9\u51FA Pixso \u94FE\u63A5\uFF08\u4EFB\u610F\u57DF\u540D\uFF0C\u542B\u79C1\u6709\u5316\u90E8\u7F72\uFF09\u2192 \u4ECE item-id \u63D0\u53D6 guid\uFF08URL \u89E3\u7801\uFF09\uFF0C\u76F4\u63A5\u8C03 get_node_dsl / get_screenshot / export_image\uFF1B\u2461 \u8BF4"\u6211\u9009\u4E2D\u7684/\u5F53\u524D\u9009\u4E2D\u7684"\u2192 get_selected \u62FF guid \u7EE7\u7EED\uFF1B\u5176\u4ED6\u4F9D\u8D56\u9009\u533A\u7684\u573A\u666F\u5148 get_selected \u518D\u5411\u7528\u6237\u62A5\u540D\u79F0\u786E\u8BA4\uFF1B\u2462 "\u6837\u5F0F\u8FD8\u539F/\u8BBE\u8BA1\u8FD8\u539F/UI \u8FD8\u539F/\u8BBE\u8BA1\u8F6C\u4EE3\u7801/\u6839\u636E\u8BBE\u8BA1\u7A3F\u5199\u9875\u9762\u6216\u7EC4\u4EF6"\u2192 get_node_dsl \u53D6\u7ED3\u6784\u6837\u5F0F + get_screenshot \u53D6\u89C6\u89C9\u53C2\u7167\uFF1B\u2463 "\u5207\u56FE/\u5BFC\u51FA\u56FE\u6807\u6216\u56FE\u7247\u7D20\u6750"\u2192 export_image\uFF08\u5C0A\u91CD\u8BBE\u8BA1\u5E08\u5BFC\u51FA\u6807\u8BB0\uFF1BDSL \u91CC\u5DF2\u5E26 img \u7684\u8282\u70B9\u76F4\u63A5\u7528\u5176\u6587\u4EF6\u540D\uFF0C\u4E0D\u8981\u91CD\u590D\u5BFC\u51FA\uFF09\uFF1B\u2464 \u7ED3\u6784\u590D\u6742/\u5148\u770B\u6574\u4F53\u6846\u67B6 \u2192 get_node_dsl \u7528 maxDepth \u5206\u6BB5\u4E0B\u63A2\uFF08\u622A\u65AD\u5904 more \u6807\u8BB0\uFF0C\u6309 id \u4E0B\u94BB\uFF09\uFF1B\u2465 \u751F\u6210\u540E\u5BF9\u6BD4\u8FD8\u539F\u5EA6 \u2192 get_screenshot \u6821\u9A8C\u5FAA\u73AF\uFF1B\u2466 \u591A\u4E2A Pixso \u6587\u4EF6\u540C\u65F6\u6253\u5F00 \u2192 list_sessions + target \u53C2\u6570\u8DEF\u7531\u3002\u8D44\u6E90\u53D6\u7528\u4E0E\u5B57\u6BB5\u8BED\u4E49\u7B49\u786C\u6027\u89C4\u5219\u89C1\u8D44\u6E90 batter://guides/intro \u4E0E batter://guides/design-to-code\uFF1B\u8BBE\u8BA1\u7A3F\u521A\u6539\u8FC7\u4F46\u7ED3\u679C\u5BF9\u4E0D\u4E0A\u65F6\uFF0C\u7ED9\u5BFC\u51FA\u7C7B\u5DE5\u5177\u52A0 cache:"refresh" \u5F3A\u5236\u5237\u65B0\u3002`;
1399
+
1400
+ // src/install/zip.ts
1401
+ import { Buffer as Buffer2 } from "node:buffer";
1402
+ var CRC_TABLE = (() => {
1403
+ const table = new Uint32Array(256);
1404
+ for (let n = 0; n < 256; n++) {
1405
+ let c = n;
1406
+ for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
1407
+ table[n] = c >>> 0;
1408
+ }
1409
+ return table;
1410
+ })();
1411
+ function crc32(buf) {
1412
+ let crc = 4294967295;
1413
+ for (const byte of buf) crc = CRC_TABLE[(crc ^ byte) & 255] ^ crc >>> 8;
1414
+ return (crc ^ 4294967295) >>> 0;
1415
+ }
1416
+ function buildZip(entries) {
1417
+ const chunks = [];
1418
+ const central = [];
1419
+ let offset = 0;
1420
+ for (const { name, data } of entries) {
1421
+ const nameBuf = Buffer2.from(name, "utf8");
1422
+ const crc = crc32(data);
1423
+ const local = Buffer2.alloc(30);
1424
+ local.writeUInt32LE(67324752, 0);
1425
+ local.writeUInt16LE(20, 4);
1426
+ local.writeUInt16LE(2048, 6);
1427
+ local.writeUInt16LE(0, 8);
1428
+ local.writeUInt16LE(0, 10);
1429
+ local.writeUInt16LE(33, 12);
1430
+ local.writeUInt32LE(crc, 14);
1431
+ local.writeUInt32LE(data.length, 18);
1432
+ local.writeUInt32LE(data.length, 22);
1433
+ local.writeUInt16LE(nameBuf.length, 26);
1434
+ local.writeUInt16LE(0, 28);
1435
+ chunks.push(local, nameBuf, data);
1436
+ const cen = Buffer2.alloc(46);
1437
+ cen.writeUInt32LE(33639248, 0);
1438
+ cen.writeUInt16LE(20, 4);
1439
+ cen.writeUInt16LE(20, 6);
1440
+ cen.writeUInt16LE(2048, 8);
1441
+ cen.writeUInt16LE(0, 10);
1442
+ cen.writeUInt16LE(0, 12);
1443
+ cen.writeUInt16LE(33, 14);
1444
+ cen.writeUInt32LE(crc, 16);
1445
+ cen.writeUInt32LE(data.length, 20);
1446
+ cen.writeUInt32LE(data.length, 24);
1447
+ cen.writeUInt16LE(nameBuf.length, 28);
1448
+ cen.writeUInt16LE(0, 30);
1449
+ cen.writeUInt16LE(0, 32);
1450
+ cen.writeUInt16LE(0, 34);
1451
+ cen.writeUInt16LE(0, 36);
1452
+ cen.writeUInt32LE(0, 38);
1453
+ cen.writeUInt32LE(offset, 42);
1454
+ central.push(Buffer2.concat([cen, nameBuf]));
1455
+ offset += 30 + nameBuf.length + data.length;
1452
1456
  }
1453
- });
1457
+ const centralBuf = Buffer2.concat(central);
1458
+ const eocd = Buffer2.alloc(22);
1459
+ eocd.writeUInt32LE(101010256, 0);
1460
+ eocd.writeUInt16LE(entries.length, 8);
1461
+ eocd.writeUInt16LE(entries.length, 10);
1462
+ eocd.writeUInt32LE(centralBuf.length, 12);
1463
+ eocd.writeUInt32LE(offset, 16);
1464
+ return Buffer2.concat([...chunks, centralBuf, eocd]);
1465
+ }
1454
1466
 
1455
- // src/routes/mcp-http.ts
1456
- import { randomUUID as randomUUID2 } from "node:crypto";
1457
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
1458
- import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
1459
- async function handleMcp(req, res, url) {
1460
- if (url.pathname !== "/mcp") return false;
1461
- const sessionId = req.headers["mcp-session-id"];
1462
- if (req.method === "POST") {
1463
- const raw = await readBody(req);
1464
- let body;
1465
- try {
1466
- body = raw.length ? JSON.parse(raw.toString("utf8")) : void 0;
1467
- } catch {
1468
- sendJson(res, 400, { jsonrpc: "2.0", error: { code: -32700, message: "Parse error" }, id: null });
1469
- return true;
1467
+ // src/install/api.ts
1468
+ var NO_DIST = "plugin dist not found\uFF08\u8FD0\u884C npm run build \u751F\u6210\uFF09";
1469
+ function handleInstall(req, res, url) {
1470
+ return dispatchRoute(INSTALL_ROUTES, req, res, url);
1471
+ }
1472
+ function sendBinary(res, buf, contentType) {
1473
+ res.writeHead(200, { "Content-Type": contentType, "Cache-Control": "no-store" });
1474
+ res.end(buf);
1475
+ }
1476
+ var PLUGIN_FILE_ROUTES = [
1477
+ { path: "/code.js", file: "code.js", type: "text/javascript; charset=utf-8" },
1478
+ { path: "/ui.html", file: "ui.html", type: "text/html; charset=utf-8" }
1479
+ ].map(({ path, file, type }) => ({
1480
+ method: "GET",
1481
+ path,
1482
+ handler: async ({ res }) => {
1483
+ const buf = await readPluginFile(file);
1484
+ if (!buf) {
1485
+ sendJson(res, 404, { error: hasPluginDist() ? "not found" : NO_DIST });
1486
+ return;
1470
1487
  }
1471
- let transport2 = sessionId ? mcpTransports.get(sessionId) : void 0;
1472
- if (!transport2) {
1473
- if (!sessionId && isInitializeRequest(body)) {
1474
- const t = new StreamableHTTPServerTransport({
1475
- sessionIdGenerator: () => randomUUID2(),
1476
- onsessioninitialized: (id) => {
1477
- mcpTransports.set(id, t);
1478
- log(`MCP HTTP session initialized: ${id.slice(0, 8)} (total ${mcpTransports.size})`);
1479
- }
1480
- });
1481
- t.onclose = () => {
1482
- if (t.sessionId) mcpTransports.delete(t.sessionId);
1483
- };
1484
- await createMcpServer().connect(t);
1485
- transport2 = t;
1486
- } else {
1487
- sendJson(res, 400, {
1488
- jsonrpc: "2.0",
1489
- error: { code: -32e3, message: "Bad Request: missing or unknown MCP session" },
1490
- id: null
1488
+ sendBinary(res, buf, type);
1489
+ }
1490
+ }));
1491
+ var INSTALL_ROUTES = [
1492
+ {
1493
+ method: "GET",
1494
+ path: "/",
1495
+ handler: ({ res }) => {
1496
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1497
+ res.end(renderInstallPage());
1498
+ }
1499
+ },
1500
+ {
1501
+ method: "GET",
1502
+ path: "/manifest.json",
1503
+ handler: async ({ res }) => {
1504
+ const buf = await pluginManifest();
1505
+ if (!buf) {
1506
+ sendJson(res, 404, { error: NO_DIST });
1507
+ return;
1508
+ }
1509
+ sendBinary(res, buf, "application/json; charset=utf-8");
1510
+ }
1511
+ },
1512
+ ...PLUGIN_FILE_ROUTES,
1513
+ {
1514
+ method: "GET",
1515
+ path: "/plugin.zip",
1516
+ handler: async ({ res }) => {
1517
+ if (!hasPluginDist()) {
1518
+ sendJson(res, 404, { error: NO_DIST });
1519
+ return;
1520
+ }
1521
+ try {
1522
+ const [manifest, code, ui] = await Promise.all([
1523
+ pluginManifest(),
1524
+ readPluginFile("code.js"),
1525
+ readPluginFile("ui.html")
1526
+ ]);
1527
+ const zip = buildZip([
1528
+ { name: "manifest.json", data: manifest },
1529
+ { name: "code.js", data: code },
1530
+ { name: "ui.html", data: ui }
1531
+ ]);
1532
+ res.writeHead(200, {
1533
+ "Content-Type": "application/zip",
1534
+ "Content-Disposition": 'attachment; filename="batter-pixso-bridge.zip"'
1491
1535
  });
1492
- return true;
1536
+ res.end(zip);
1537
+ } catch {
1538
+ sendJson(res, 500, { error: "zip build failed" });
1493
1539
  }
1494
1540
  }
1495
- await transport2.handleRequest(req, res, body);
1496
- return true;
1497
1541
  }
1498
- const transport = sessionId ? mcpTransports.get(sessionId) : void 0;
1499
- if (!transport) {
1500
- sendJson(res, 400, { error: "missing or unknown MCP session" });
1501
- return true;
1502
- }
1503
- await transport.handleRequest(req, res);
1504
- return true;
1505
- }
1506
- var mcpTransports;
1507
- var init_mcp_http = __esm({
1508
- "src/routes/mcp-http.ts"() {
1509
- "use strict";
1510
- init_config();
1511
- init_mcp_local();
1512
- init_http_utils();
1513
- mcpTransports = /* @__PURE__ */ new Map();
1514
- }
1515
- });
1542
+ ];
1516
1543
 
1517
- // src/routes/plugin-api.ts
1518
- async function handlePluginApi(req, res, url) {
1519
- const path = url.pathname;
1520
- const session = url.searchParams.get("session");
1521
- if (req.method === "POST" && path === "/sessions/register") {
1522
- try {
1523
- const buf = await readBody(req);
1524
- const meta = buf.length ? JSON.parse(buf.toString("utf8")) : {};
1525
- sendJson(res, 201, sessions.register(meta));
1526
- } catch {
1527
- sendJson(res, 400, { error: "invalid json" });
1528
- }
1529
- return true;
1530
- }
1531
- if (req.method === "POST" && path === "/context") {
1532
- if (!session) {
1533
- sendJson(res, 400, { error: "missing session" });
1534
- return true;
1544
+ // src/plugin/api.ts
1545
+ function handlePluginApi(req, res, url) {
1546
+ return dispatchRoute(PLUGIN_ROUTES, req, res, url);
1547
+ }
1548
+ var PLUGIN_ROUTES = [
1549
+ {
1550
+ method: "POST",
1551
+ path: "/sessions/register",
1552
+ handler: async ({ req, res }) => {
1553
+ sendJson(res, 201, sessions.register(await readJsonBody(req)));
1535
1554
  }
1536
- try {
1537
- const buf = await readBody(req);
1538
- const meta = buf.length ? JSON.parse(buf.toString("utf8")) : {};
1555
+ },
1556
+ {
1557
+ method: "POST",
1558
+ path: "/context",
1559
+ handler: async ({ req, res, url }) => {
1560
+ const session = url.searchParams.get("session");
1561
+ if (!session) {
1562
+ sendJson(res, 400, { error: "missing session" });
1563
+ return;
1564
+ }
1565
+ const meta = await readJsonBody(req);
1539
1566
  if (!sessions.updateContext(session, meta)) {
1540
1567
  sendJson(res, 404, { error: "unknown_session" });
1541
- return true;
1568
+ return;
1542
1569
  }
1543
1570
  sendJson(res, 200, { ok: true });
1544
- } catch {
1545
- sendJson(res, 400, { error: "invalid json" });
1546
1571
  }
1547
- return true;
1548
- }
1549
- if (req.method === "GET" && path === "/commands/stream") {
1550
- if (!session || !sessions.setConnected(session, true)) {
1551
- sendJson(res, 404, { error: "unknown_session" });
1552
- return true;
1553
- }
1554
- res.writeHead(200, {
1555
- "Content-Type": "text/event-stream; charset=utf-8",
1556
- "Cache-Control": "no-store",
1557
- Connection: "keep-alive"
1558
- });
1559
- res.write(": connected\n\n");
1560
- const unsubscribe = commandBus.subscribe(session, (command) => {
1561
- res.write(`data: ${JSON.stringify({ command })}
1572
+ },
1573
+ {
1574
+ method: "GET",
1575
+ path: "/commands/stream",
1576
+ handler: ({ req, res, url }) => {
1577
+ const session = url.searchParams.get("session");
1578
+ if (!session || !sessions.setConnected(session, true)) {
1579
+ sendJson(res, 404, { error: "unknown_session" });
1580
+ return;
1581
+ }
1582
+ res.writeHead(200, {
1583
+ "Content-Type": "text/event-stream; charset=utf-8",
1584
+ "Cache-Control": "no-store",
1585
+ Connection: "keep-alive"
1586
+ });
1587
+ res.write(": connected\n\n");
1588
+ const unsubscribe = commandBus.subscribe(session, (command) => {
1589
+ res.write(`data: ${JSON.stringify({ command })}
1562
1590
 
1563
1591
  `);
1564
- });
1565
- const heartbeat = setInterval(() => {
1566
- sessions.touch(session);
1567
- res.write(": ping\n\n");
1568
- }, 15e3);
1569
- req.on("close", () => {
1570
- clearInterval(heartbeat);
1571
- unsubscribe();
1572
- sessions.setConnected(session, false);
1573
- log(`SSE channel closed: session ${session.slice(0, 8)}`);
1574
- });
1575
- return true;
1576
- }
1577
- if (req.method === "GET" && path === "/commands/next") {
1578
- if (!session) {
1579
- sendJson(res, 400, { error: "missing session" });
1580
- return true;
1581
- }
1582
- if (!sessions.touch(session)) {
1583
- sendJson(res, 404, { error: "unknown_session" });
1584
- return true;
1585
- }
1586
- try {
1587
- const command = await commandBus.take(session);
1588
- if (command === null) {
1589
- res.writeHead(204);
1590
- res.end();
1591
- return true;
1592
- }
1593
- sendJson(res, 200, { command });
1594
- } catch {
1595
- sendJson(res, 500, { error: "internal error" });
1592
+ });
1593
+ const heartbeat = setInterval(() => {
1594
+ sessions.touchSeen(session);
1595
+ res.write(": ping\n\n");
1596
+ }, 15e3);
1597
+ req.on("close", () => {
1598
+ clearInterval(heartbeat);
1599
+ unsubscribe();
1600
+ sessions.setConnected(session, false);
1601
+ log(`SSE channel closed: session ${session.slice(0, 8)}`);
1602
+ });
1596
1603
  }
1597
- return true;
1598
- }
1599
- const resultMatch = path.match(/^\/commands\/([\w-]+)\/result$/);
1600
- if (req.method === "POST" && resultMatch) {
1601
- try {
1602
- const buf = await readBody(req);
1603
- const payload = JSON.parse(buf.toString("utf8"));
1604
- const found = commandBus.complete(resultMatch[1], payload.ok, payload.ok ? payload.result : payload.error);
1604
+ },
1605
+ {
1606
+ method: "POST",
1607
+ path: "/commands/:id/result",
1608
+ handler: async ({ req, res, params }) => {
1609
+ const payload = await readJsonBody(req);
1610
+ const result = payload.ok && payload.assets?.length ? substituteAssets(payload.result, payload.assets) : payload.result;
1611
+ const found = commandBus.complete(params["id"], payload.ok, payload.ok ? result : payload.error);
1605
1612
  sendJson(res, found ? 200 : 404, found ? { ok: true } : { error: "unknown command" });
1606
- } catch {
1607
- sendJson(res, 400, { error: "invalid json" });
1608
1613
  }
1609
- return true;
1610
- }
1611
- if (req.method === "POST" && path === "/assets") {
1612
- try {
1613
- const buf = await readBody(req);
1614
- const saved = await saveAsset(buf, url.searchParams.get("format") ?? "PNG");
1615
- sendJson(res, 201, { name: saved.name });
1616
- } catch {
1617
- sendJson(res, 500, { error: "asset save failed" });
1614
+ },
1615
+ {
1616
+ method: "POST",
1617
+ path: "/assets",
1618
+ handler: async ({ req, res, url }) => {
1619
+ try {
1620
+ const name = await saveAsset(await readBody(req), url.searchParams.get("format") ?? "PNG");
1621
+ sendJson(res, 201, { name });
1622
+ } catch {
1623
+ sendJson(res, 500, { error: "asset save failed" });
1624
+ }
1618
1625
  }
1619
- return true;
1620
- }
1621
- return false;
1622
- }
1623
- var init_plugin_api = __esm({
1624
- "src/routes/plugin-api.ts"() {
1625
- "use strict";
1626
- init_assets();
1627
- init_commands();
1628
- init_sessions();
1629
- init_config();
1630
- init_http_utils();
1631
1626
  }
1632
- });
1627
+ ];
1633
1628
 
1634
- // src/server.ts
1635
- import { createServer } from "node:http";
1629
+ // src/http.ts
1630
+ var lastActivity = Date.now();
1636
1631
  function getLastActivity() {
1637
1632
  return lastActivity;
1638
1633
  }
1634
+ var ROUTES = [handleInstall, handleMcp, handlePluginApi];
1639
1635
  function startHttpServer() {
1640
1636
  const server = createServer((req, res) => {
1641
1637
  lastActivity = Date.now();
@@ -1653,23 +1649,19 @@ function startHttpServer() {
1653
1649
  const all = sessions.list();
1654
1650
  sendJson(res, 200, {
1655
1651
  ok: true,
1656
- name: "batter-pixso-mcp",
1652
+ name: HUB_NAME,
1657
1653
  pid: process.pid,
1658
1654
  version: VERSION,
1659
- sessions: all.length,
1660
1655
  pluginsOnline: all.filter((s) => s.online).length
1661
1656
  });
1662
1657
  return;
1663
1658
  }
1664
- (async () => {
1659
+ void (async () => {
1665
1660
  for (const handler of ROUTES) {
1666
1661
  if (await handler(req, res, url)) return;
1667
1662
  }
1668
1663
  sendJson(res, 404, { error: "not found" });
1669
- })().catch((err) => {
1670
- log("route error:", err);
1671
- if (!res.headersSent) sendJson(res, 500, { error: "internal error" });
1672
- });
1664
+ })();
1673
1665
  });
1674
1666
  return new Promise((resolve, reject) => {
1675
1667
  server.once("error", (err) => {
@@ -1685,22 +1677,8 @@ function startHttpServer() {
1685
1677
  });
1686
1678
  });
1687
1679
  }
1688
- var lastActivity, ROUTES;
1689
- var init_server = __esm({
1690
- "src/server.ts"() {
1691
- "use strict";
1692
- init_config();
1693
- init_sessions();
1694
- init_http_utils();
1695
- init_install();
1696
- init_mcp_http();
1697
- init_plugin_api();
1698
- lastActivity = Date.now();
1699
- ROUTES = [handleInstall, handleMcp, handlePluginApi];
1700
- }
1701
- });
1702
1680
 
1703
- // src/lifecycle/hub-mode.ts
1681
+ // src/process/hub.ts
1704
1682
  async function runHub() {
1705
1683
  const server = await startHttpServer();
1706
1684
  if (!server) {
@@ -1708,46 +1686,46 @@ async function runHub() {
1708
1686
  process.exit(0);
1709
1687
  }
1710
1688
  log(`hub ready (idle TTL ${config.hubIdleMs / 6e4}min)`);
1689
+ void cleanOldAssets(config.assetTtlMs);
1690
+ let tick = 0;
1711
1691
  const watchdog = setInterval(() => {
1712
1692
  const idle = Date.now() - getLastActivity();
1713
- if (idle > config.hubIdleMs) {
1714
- log(`\u7A7A\u95F2 ${Math.round(idle / 6e4)} \u5206\u949F\uFF0Chub \u81EA\u52A8\u9000\u51FA`);
1693
+ const anyPluginOnline = sessions.list().some((s) => s.online);
1694
+ if (idle > config.hubIdleMs && !anyPluginOnline) {
1695
+ log(`\u7A7A\u95F2 ${Math.round(idle / 6e4)} \u5206\u949F\u4E14\u65E0\u5728\u7EBF\u63D2\u4EF6\uFF0Chub \u81EA\u52A8\u9000\u51FA`);
1715
1696
  process.exit(0);
1716
1697
  }
1717
- void cleanOldAssets(config.assetTtlMs);
1698
+ if (++tick % 60 === 0) void cleanOldAssets(config.assetTtlMs);
1718
1699
  }, 6e4);
1719
1700
  watchdog.unref();
1720
1701
  }
1721
- var init_hub_mode = __esm({
1722
- "src/lifecycle/hub-mode.ts"() {
1723
- "use strict";
1724
- init_assets();
1725
- init_config();
1726
- init_server();
1727
- }
1728
- });
1729
1702
 
1730
- // src/lifecycle/hub-process.ts
1703
+ // src/process/stdio-proxy.ts
1704
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1705
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
1706
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
1707
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1708
+ import { ResultSchema } from "@modelcontextprotocol/sdk/types.js";
1709
+
1710
+ // src/process/hub-process.ts
1731
1711
  import { spawn } from "node:child_process";
1732
- import { openSync } from "node:fs";
1712
+ import { closeSync, openSync } from "node:fs";
1733
1713
  import { tmpdir as tmpdir2 } from "node:os";
1734
- import { join as join3 } from "node:path";
1714
+ import { join as join4 } from "node:path";
1735
1715
  import { fileURLToPath as fileURLToPath2 } from "node:url";
1736
- function hubBaseUrl() {
1737
- return `http://127.0.0.1:${config.httpPort}`;
1738
- }
1739
1716
  async function probeHub() {
1740
1717
  try {
1741
1718
  const res = await fetch(`${hubBaseUrl()}/health`, { signal: AbortSignal.timeout(1500) });
1742
1719
  if (!res.ok) return "foreign";
1743
1720
  const body = await res.json();
1744
- if (body.ok === true && body.name === "batter-pixso-mcp") return "alive";
1721
+ if (body.ok === true && body.name === HUB_NAME) return "alive";
1745
1722
  if (body.ok === true) return "legacy";
1746
1723
  return "foreign";
1747
1724
  } catch {
1748
1725
  return "down";
1749
1726
  }
1750
1727
  }
1728
+ var PORT_FIX_HINT = `\u7528 netstat -ano | findstr :${config.httpPort} \u67E5\u5230 PID \u540E taskkill /PID <pid> /F \u7ED3\u675F\uFF0C\u518D\u91CD\u8BD5`;
1751
1729
  function hubStateError(state) {
1752
1730
  if (state === "legacy") {
1753
1731
  return new Error(
@@ -1756,6 +1734,7 @@ function hubStateError(state) {
1756
1734
  }
1757
1735
  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`);
1758
1736
  }
1737
+ var HUB_LOG = join4(tmpdir2(), "batter-pixso-mcp-hub.log");
1759
1738
  function spawnHub() {
1760
1739
  const out = openSync(HUB_LOG, "a");
1761
1740
  const entry = process.argv[1] ?? fileURLToPath2(import.meta.url);
@@ -1770,6 +1749,7 @@ function spawnHub() {
1770
1749
  env: { ...process.env }
1771
1750
  });
1772
1751
  child.unref();
1752
+ closeSync(out);
1773
1753
  log(`hub spawned (pid ${child.pid}), log: ${HUB_LOG}`);
1774
1754
  }
1775
1755
  async function waitForHub(timeoutMs = 15e3) {
@@ -1782,22 +1762,8 @@ async function waitForHub(timeoutMs = 15e3) {
1782
1762
  await new Promise((r) => setTimeout(r, 200));
1783
1763
  }
1784
1764
  }
1785
- var PORT_FIX_HINT, HUB_LOG;
1786
- var init_hub_process = __esm({
1787
- "src/lifecycle/hub-process.ts"() {
1788
- "use strict";
1789
- init_config();
1790
- PORT_FIX_HINT = `\u7528 netstat -ano | findstr :${config.httpPort} \u67E5\u5230 PID \u540E taskkill /PID <pid> /F \u7ED3\u675F\uFF0C\u518D\u91CD\u8BD5`;
1791
- HUB_LOG = join3(tmpdir2(), "batter-pixso-mcp-hub.log");
1792
- }
1793
- });
1794
1765
 
1795
- // src/stdio-proxy.ts
1796
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1797
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
1798
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
1799
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1800
- import { ResultSchema } from "@modelcontextprotocol/sdk/types.js";
1766
+ // src/process/stdio-proxy.ts
1801
1767
  async function startStdioProxy() {
1802
1768
  const mcpUrl = `${hubBaseUrl()}/mcp`;
1803
1769
  let upstream = null;
@@ -1810,13 +1776,7 @@ async function startStdioProxy() {
1810
1776
  return client;
1811
1777
  }
1812
1778
  async function ensureUpstream() {
1813
- if (upstream) {
1814
- try {
1815
- await upstream.ping();
1816
- return upstream;
1817
- } catch {
1818
- }
1819
- }
1779
+ if (upstream) return upstream;
1820
1780
  const state = await probeHub();
1821
1781
  if (state === "down") spawnHub();
1822
1782
  if (state === "legacy" || state === "foreign") throw hubStateError(state);
@@ -1830,8 +1790,12 @@ async function startStdioProxy() {
1830
1790
  try {
1831
1791
  return await fn(client);
1832
1792
  } catch (err) {
1833
- if (await probeHub() !== "down") throw err;
1834
- log("hub \u5931\u8054\uFF0C\u91CD\u5EFA\u540E\u91CD\u8BD5\u2026");
1793
+ const alive = await client.ping().then(
1794
+ () => true,
1795
+ () => false
1796
+ );
1797
+ if (alive) throw err;
1798
+ log("hub \u4F1A\u8BDD\u5931\u8054\uFF0C\u91CD\u5EFA\u540E\u91CD\u8BD5\u2026");
1835
1799
  upstream = null;
1836
1800
  const rebuilt = await ensureUpstream();
1837
1801
  return await fn(rebuilt);
@@ -1839,7 +1803,7 @@ async function startStdioProxy() {
1839
1803
  }
1840
1804
  const upstreamClient = await ensureUpstream();
1841
1805
  const server = new Server(
1842
- { name: "batter-pixso-mcp", version: VERSION },
1806
+ { name: HUB_NAME, version: VERSION },
1843
1807
  {
1844
1808
  capabilities: upstreamClient.getServerCapabilities() ?? {}
1845
1809
  }
@@ -1852,19 +1816,8 @@ async function startStdioProxy() {
1852
1816
  await server.connect(new StdioServerTransport());
1853
1817
  log("proxy ready on stdio");
1854
1818
  }
1855
- var init_stdio_proxy = __esm({
1856
- "src/stdio-proxy.ts"() {
1857
- "use strict";
1858
- init_config();
1859
- init_hub_process();
1860
- }
1861
- });
1862
1819
 
1863
- // src/main.ts
1864
- var main_exports = {};
1865
- __export(main_exports, {
1866
- main: () => main
1867
- });
1820
+ // src/process/main.ts
1868
1821
  async function main() {
1869
1822
  if (process.argv.includes("--hub")) {
1870
1823
  await runHub();
@@ -1872,26 +1825,9 @@ async function main() {
1872
1825
  }
1873
1826
  await startStdioProxy();
1874
1827
  }
1875
- var init_main = __esm({
1876
- "src/main.ts"() {
1877
- "use strict";
1878
- init_hub_mode();
1879
- init_stdio_proxy();
1880
- }
1881
- });
1882
1828
 
1883
- // src/index.ts
1884
- var major = Number(process.versions.node.split(".")[0]);
1885
- if (major < 18) {
1886
- console.error(
1887
- `[batter-pixso-mcp] \u9700\u8981 Node \u2265 18\uFF08\u5F53\u524D ${process.version}\uFF09\u3002
1888
- \u7CFB\u7EDF Node \u8FC7\u65E7\u65F6\u53EF\u7528 Volta \u6307\u5B9A\u8FD0\u884C\u65F6\uFF1A
1889
- MCP \u914D\u7F6E: { "command": "volta", "args": ["run", "--node", "24", "npx", "-y", "batter-pixso-mcp"] }`
1890
- );
1891
- process.exit(1);
1892
- }
1893
- var { main: main2 } = await Promise.resolve().then(() => (init_main(), main_exports));
1894
- main2().catch((err) => {
1829
+ // src/process/index.ts
1830
+ main().catch((err) => {
1895
1831
  console.error("[batter-pixso-mcp] fatal:", err);
1896
1832
  process.exit(1);
1897
1833
  });