batter-pixso-mcp 0.2.1 → 0.4.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.2.1";
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.4.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,1457 +216,1419 @@ 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;
297
+ ## \u94FE\u63A5\u5373\u8F93\u5165\uFF08\u9AD8\u9891\u573A\u666F\uFF09
298
+
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
302
+
303
+ ## \u591A\u6587\u4EF6\u573A\u666F
304
+
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
308
+
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
313
+
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
317
+
318
+ ## \u3010\u786C\u6027\u89C4\u5219 \xB7 \u8FDD\u53CD\u5373\u8FD4\u5DE5\u3011
319
+
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\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
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
334
+
335
+ ## \u63A8\u8350\u6B65\u9AA4
336
+
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
351
+
352
+ ## DSL \u5B57\u6BB5\u8BED\u4E49\uFF08\u7D27\u51D1\u683C\u5F0F\uFF09
353
+
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
355
+
356
+ \`\`\`jsonc
357
+ {
358
+ "id": "297:133032", // \u8282\u70B9 guid\uFF0C\u53EF\u56DE\u4F20\u4E3A guid \u53C2\u6570\u5BF9\u8BE5\u8282\u70B9\u4E0B\u94BB\u8C03\u7528
359
+ "name": "\u65B0\u5BA2", // \u56FE\u5C42\u540D\uFF1B\u81EA\u52A8\u547D\u540D\uFF08"Frame 3123" \u7C7B\uFF09\u5DF2\u7701\u7565
360
+ "type": "FRAME", // \u8282\u70B9\u7C7B\u578B\uFF1BICON/IMAGE \u4E3A\u584C\u7F29\u53F6\u5B50
361
+ "rect": [0, 0, 375, 200], // [x,y,w,h] \u7EDD\u5BF9\u5B9A\u4F4D
362
+ "layout": { // auto-layout \u2192 flex \u76F4\u8BD1\uFF08\u4EC5\u5BB9\u5668\u6709\uFF09
363
+ "dir": "row", // row | column
364
+ "gap": 12, // \u4E3A 0 \u65F6\u7701\u7565
365
+ "pad": [16, 16, 16, 16], // [\u4E0A,\u53F3,\u4E0B,\u5DE6]\uFF0C\u5168 0 \u65F6\u7701\u7565
366
+ "align": ["start", "center"], // [\u4E3B\u8F74,\u4EA4\u53C9\u8F74]\uFF1Astart|center|end|space-between|baseline
367
+ "sizing": ["hug", "fixed"] // [\u4E3B\u8F74,\u4EA4\u53C9\u8F74]\uFF1Ahug|fixed|fill
368
+ },
369
+ "text": { // \u4EC5\u6587\u672C\u8282\u70B9\uFF1B\u9ED8\u8BA4\u503C\u7701\u7565\uFF1Aalign=left\u3001lh=normal\u3001weight=Regular
370
+ "content": "...", "size": 14, "font": "PingFang SC",
371
+ "weight": "Medium", "lh": "22px", "align": "center",
372
+ "deco": "underline", "ls": "0.5px", "link": "https://...", // \u6309\u9700
373
+ "segs": [ // \u4EC5\u6DF7\u6392\u6587\u672C\uFF1Abase \u4E3A\u6700\u957F\u6BB5\u6837\u5F0F\uFF0C\u6BCF\u6BB5\u53EA\u5E26\u5DEE\u5F02\u5B57\u6BB5\uFF1B
374
+ { "text": "\u7406\u8D54\u4E2D\u5FC3", "weight": "Bold", "color": "p1" } // \u6570\u7EC4\u987A\u5E8F\u5373\u5B57\u7B26\u504F\u79FB\uFF08start/end \u7701\u7565\uFF09
375
+ ]
376
+ },
377
+ "fills": ["p0", "linear-gradient(90deg, #fff 0%, #000 100%)"], // CSS \u989C\u8272/\u6E10\u53D8
378
+ "stroke": "1px solid #e5e5e5", // \u989C\u8272\u90E8\u5206\u4E5F\u53EF\u80FD\u662F "p0" \u8C03\u8272\u677F\u5F15\u7528
379
+ "radius": 8, // \u6216 [\u4E0A\u5DE6, \u4E0A\u53F3, \u4E0B\u53F3, \u4E0B\u5DE6]
380
+ "shadow": "0px 2px 8px rgba(0,0,0,0.1), inset 0px 1px 0px #fff", // CSS box-shadow\uFF0C\u591A\u9634\u5F71\u9017\u53F7\u62FC\u63A5
381
+ "blur": "4px", // \u56FE\u5C42\u6A21\u7CCA \u2192 CSS filter: blur()
382
+ "bgBlur": "8px", // \u80CC\u666F\u6A21\u7CCA \u2192 CSS backdrop-filter
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
384
+ "opacity": 0.9, // \u4E3A 1 \u65F6\u7701\u7565
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
388
+ "palette": ["#1a73e8", "#f5222d"], // \u8C03\u8272\u677F\uFF08\u4EC5\u6839\u8282\u70B9\uFF09\uFF0C"p0"/"p1" \u4E3A\u4E0B\u6807\u5F15\u7528
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
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
+ "kids": [] // \u5B50\u8282\u70B9\u9012\u5F52
392
+ }
393
+ \`\`\`
394
+
395
+ **\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
396
+ \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
397
+ \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
398
+
399
+ **\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
400
+ \`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
401
+ \`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
402
+ **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
403
+
404
+ ## \u9519\u8BEF\u6062\u590D
405
+
406
+ - **\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
407
+ - **\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
408
+ - **\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
409
+ - **\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
410
+ - **\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
411
+
412
+ ## \u7EC4\u4EF6\u5173\u8054\uFF08comp \u5B57\u6BB5\uFF09
413
+
414
+ 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
415
+ - \`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
416
+ - \`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
417
+ - \`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
418
+ - \`comp.set\`\uFF1A\u672C\u8282\u70B9\u662F variant \u7EC4\uFF08COMPONENT_SET\uFF09\uFF0C\u5176\u5B50\u8282\u70B9\u5404\u81EA\u5E26 \`comp.key\`\u3002
419
+ \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
420
+
421
+ ## \u53F6\u5B50\u8282\u70B9\u4E0E\u56FE\u7247\u8D44\u6E90
422
+
423
+ - \`type: "ICON"\`\uFF1A\u7EAF\u77E2\u91CF\u7EC4\u4EF6\u5B9E\u4F8B\uFF08icon\uFF09\uFF0C\`img\` \u4E3A **SVG** \u6587\u4EF6\u540D\u3002
424
+ - \`type: "IMAGE"\`\uFF1A\u56FE\u7247\u586B\u5145\u8282\u70B9\uFF0C\`img\` \u4E3A **PNG** \u6587\u4EF6\u540D\u3002
425
+ - \u6587\u4EF6\u540D\u62FC\u8DEF\u5F84\u4E0E\u53D6\u7528\u8981\u6C42\u89C1\u5F00\u5934\u786C\u6027\u89C4\u5219\u7B2C 1 \u6761\u3002
426
+ - \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
427
+ - **\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
428
+ \`export_image\` \u4EC5\u7528\u4E8E DSL \u4E2D\u672A\u584C\u7F29\u7684\u8282\u70B9\uFF08\u4E3B\u52A8\u5207\u56FE\uFF09\u3002
429
+
430
+ ## \u6CE8\u610F
431
+
432
+ - 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
433
+ - \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
434
+ \u8BBE\u8BA1\u7A3F\u521A\u6539\u8FC7\u4F46\u7ED3\u679C\u5BF9\u4E0D\u4E0A\u65F6\uFF0C\u52A0 \`cache: "refresh"\` \u5F3A\u5236\u5237\u65B0\u3002
435
+ - \u591A\u6587\u4EF6\u573A\u666F\u6240\u6709\u5DE5\u5177\u90FD\u652F\u6301 \`target\` \u53C2\u6570\uFF08sessionId/fileKey\uFF09\uFF0C\u89C1 \`batter://guides/intro\`\u3002
436
+ `;
437
+ var GUIDES = [
438
+ {
439
+ name: "intro",
440
+ uri: "batter://guides/intro",
441
+ 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",
442
+ text: INTRO
443
+ },
444
+ {
445
+ name: "design-to-code",
446
+ uri: "batter://guides/design-to-code",
447
+ 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",
448
+ text: DESIGN_TO_CODE
449
+ }
450
+ ];
451
+
452
+ // src/agent/prompts.ts
453
+ var DESIGN_TO_CODE2 = {
454
+ name: "design-to-code",
455
+ 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",
456
+ arguments: [
457
+ {
458
+ name: "url",
459
+ 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",
460
+ required: false
461
+ }
462
+ ],
463
+ render: (args) => {
464
+ 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`;
465
+ return [
466
+ {
467
+ role: "user",
468
+ content: {
469
+ type: "text",
470
+ text: [
471
+ `\u628A Pixso \u8BBE\u8BA1\u7A3F\u8FD8\u539F\u4E3A\u524D\u7AEF\u4EE3\u7801\u3002`,
472
+ ``,
473
+ `## \u3010\u786C\u6027\u89C4\u5219 \xB7 \u8FDD\u53CD\u5373\u8FD4\u5DE5\u3011`,
474
+ ``,
475
+ `**\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`,
476
+ ``,
477
+ `## \u6267\u884C\u6D41\u7A0B`,
478
+ ``,
479
+ `1. \u786E\u8BA4\u76EE\u6807\u8282\u70B9\uFF1A${targetLine}`,
480
+ `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`,
481
+ `3. \u751F\u6210\u4EE3\u7801\uFF1A\u6309\u6307\u5357\u4E2D\u7684 DSL \u5B57\u6BB5\u8BED\u4E49\u6620\u5C04\u5230\u76EE\u6807\u6846\u67B6\u3002`,
482
+ `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`,
483
+ ``,
484
+ `**\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`
485
+ ].join("\n")
486
+ }
487
+ }
488
+ ];
393
489
  }
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;
490
+ };
491
+ var PROMPTS = [DESIGN_TO_CODE2];
492
+
493
+ // src/plugin/command-bus.ts
494
+ var CommandBus = class {
495
+ sseListeners = /* @__PURE__ */ new Map();
496
+ pendingById = /* @__PURE__ */ new Map();
497
+ seq = 0;
498
+ /** AI 侧:执行命令 */
499
+ execute(type, args, opts) {
500
+ const timeoutMs = opts.timeoutMs ?? 6e4;
501
+ const { promise, resolve, reject } = withResolvers();
502
+ const command = { id: `cmd-${++this.seq}`, type, args };
503
+ const entry = {
504
+ command,
505
+ sessionId: opts.sessionId,
506
+ resolve,
507
+ reject,
508
+ dispatched: false,
509
+ timer: setTimeout(() => {
510
+ this.drop(entry);
511
+ reject(new Error(`\u547D\u4EE4 ${command.id} \u8D85\u65F6\uFF1A\u63D2\u4EF6 ${timeoutMs / 1e3}s \u5185\u672A\u8FD4\u56DE\u7ED3\u679C`));
512
+ }, timeoutMs)
513
+ };
514
+ opts.signal?.addEventListener("abort", () => {
515
+ if (!this.pendingById.has(command.id)) return;
516
+ this.drop(entry);
517
+ reject(new Error(`\u547D\u4EE4 ${command.id} \u5DF2\u88AB\u8C03\u7528\u65B9\u53D6\u6D88`));
518
+ });
519
+ this.pendingById.set(command.id, entry);
520
+ this.dispatch(opts.sessionId);
521
+ return promise;
522
+ }
523
+ /** 插件侧:订阅即时推送(SSE 通道),返回退订函数;补发订阅前已入队的命令 */
524
+ subscribe(sessionId, listener) {
525
+ let subs = this.sseListeners.get(sessionId);
526
+ if (!subs) {
527
+ subs = /* @__PURE__ */ new Set();
528
+ this.sseListeners.set(sessionId, subs);
529
+ }
530
+ subs.add(listener);
531
+ this.dispatch(sessionId);
532
+ return () => {
533
+ subs.delete(listener);
534
+ if (subs.size === 0) this.sseListeners.delete(sessionId);
535
+ };
401
536
  }
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;
537
+ /** 插件侧:回传结果 */
538
+ complete(id, ok, resultOrError) {
539
+ const entry = this.pendingById.get(id);
540
+ if (!entry) return false;
541
+ this.drop(entry);
542
+ if (ok) {
543
+ entry.resolve(resultOrError);
544
+ } else {
545
+ entry.reject(resultOrError instanceof Error ? resultOrError : new Error(String(resultOrError)));
546
+ }
547
+ return true;
408
548
  }
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;
549
+ /** 排空该会话全部未派发命令(无订阅者则留待 subscribe 补发) */
550
+ dispatch(sessionId) {
551
+ const subs = this.sseListeners.get(sessionId);
552
+ if (!subs || subs.size === 0) return;
553
+ for (const entry of this.pendingById.values()) {
554
+ if (entry.sessionId !== sessionId || entry.dispatched) continue;
555
+ entry.dispatched = true;
556
+ log(`dispatch command ${entry.command.id}: ${entry.command.type} -> session ${sessionId.slice(0, 8)}`);
557
+ for (const fn of subs) fn(entry.command);
558
+ }
418
559
  }
419
- .actions { display: flex; align-items: center; gap: 12px; margin: 14px 0 10px; }
560
+ /** 从 pending 表移除(完成/超时/取消共用) */
561
+ drop(entry) {
562
+ clearTimeout(entry.timer);
563
+ this.pendingById.delete(entry.command.id);
564
+ }
565
+ };
566
+ var commandBus = new CommandBus();
420
567
 
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;
568
+ // src/agent/tools/shared.ts
569
+ var PING_TIMEOUT_MS = 800;
570
+ async function run(type, args, signal) {
571
+ const { target, ...rest } = args;
572
+ try {
573
+ const session = await resolveAndPing(target, signal);
574
+ const data = await commandBus.execute(type, rest, { sessionId: session.id, signal });
575
+ sessions.touch(session.id);
576
+ return jsonResult({ sessionId: session.id, data });
577
+ } catch (err) {
578
+ return errorResult(err);
437
579
  }
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);
580
+ }
581
+ async function pingSession(sessionId, signal) {
582
+ try {
583
+ await commandBus.execute("ping", {}, { sessionId, timeoutMs: PING_TIMEOUT_MS, signal });
584
+ return true;
585
+ } catch (err) {
586
+ if (signal?.aborted) throw err;
587
+ return false;
443
588
  }
444
-
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;
589
+ }
590
+ async function pingCandidates(candidates, affinityId, signal) {
591
+ const check = async (c) => {
592
+ if (await pingSession(c.id, signal)) return c;
593
+ throw new Error("no pong");
594
+ };
595
+ const affinity = candidates.find((c) => c.id === affinityId);
596
+ try {
597
+ if (affinity && await pingSession(affinity.id, signal)) return affinity;
598
+ const rest = affinity ? candidates.filter((c) => c !== affinity) : candidates;
599
+ return await Promise.any(rest.map(check));
600
+ } catch {
601
+ if (signal?.aborted) throw new Error("\u547D\u4EE4\u5DF2\u88AB\u8C03\u7528\u65B9\u53D6\u6D88");
602
+ return null;
460
603
  }
461
- .btn:hover { border-color: #d2d2d6; background: var(--panel); }
462
- .btn svg { display: block; color: var(--sub); }
604
+ }
605
+ async function resolveAndPing(target, signal) {
606
+ const s = target ? sessions.get(target) : sessions.resolve();
607
+ const fileKey = s ? s.meta.fileKey : target;
608
+ const candidates = s && !fileKey ? [s] : sessions.candidatesForFileKey(fileKey);
609
+ if (candidates.length === 0) {
610
+ 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`);
611
+ throw unknownTargetError(target);
612
+ }
613
+ const winner = await pingCandidates(candidates, fileKey ? sessions.affinityIdOf(fileKey) : void 0, signal);
614
+ if (!winner) {
615
+ for (const c of candidates) sessions.evict(c.id);
616
+ throw new Error(
617
+ `\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`
618
+ );
619
+ }
620
+ if (fileKey) sessions.pinAffinity(fileKey, winner.id);
621
+ return winner;
622
+ }
623
+ 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";
624
+ 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";
625
+ var TARGET_PROP = {
626
+ type: "string",
627
+ 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"
628
+ };
629
+ var CACHE_PROP = {
630
+ type: "string",
631
+ enum: ["first", "refresh"],
632
+ 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"
633
+ };
634
+ var GUID_PROP = {
635
+ type: "string",
636
+ 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'
637
+ };
638
+ function jsonResult(data) {
639
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
640
+ }
641
+ function errorResult(err) {
642
+ return {
643
+ content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }],
644
+ isError: true
645
+ };
646
+ }
463
647
 
464
- /* MCP \u914D\u7F6E\u4EE3\u7801\u5757 */
465
- .cfg {
466
- position: relative;
467
- margin: 14px 0 10px;
648
+ // src/agent/tools/export_image.ts
649
+ var exportImage = {
650
+ 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,
651
+ inputSchema: {
652
+ type: "object",
653
+ properties: {
654
+ guid: GUID_PROP,
655
+ guids: {
656
+ type: "array",
657
+ items: { type: "string" },
658
+ 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"
659
+ },
660
+ format: {
661
+ type: "string",
662
+ enum: ["PNG", "SVG", "JPG"],
663
+ 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"
664
+ },
665
+ scale: {
666
+ type: "number",
667
+ 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"
668
+ },
669
+ cache: CACHE_PROP,
670
+ target: TARGET_PROP
671
+ }
468
672
  }
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;
673
+ };
674
+
675
+ // src/agent/tools/get_metadata.ts
676
+ var getMetadata = {
677
+ 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,
678
+ inputSchema: {
679
+ type: "object",
680
+ properties: {
681
+ guid: GUID_PROP,
682
+ cache: CACHE_PROP,
683
+ target: TARGET_PROP
684
+ }
479
685
  }
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;
686
+ };
687
+
688
+ // src/agent/tools/get_node_dsl.ts
689
+ 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';
690
+ 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\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';
691
+ var getNodeDsl = {
692
+ 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,
693
+ inputSchema: {
694
+ type: "object",
695
+ properties: {
696
+ guid: GUID_PROP,
697
+ maxDepth: {
698
+ type: "number",
699
+ 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"
700
+ },
701
+ cache: CACHE_PROP,
702
+ target: TARGET_PROP
703
+ }
494
704
  }
495
- .copy:hover { color: var(--ink); border-color: #d2d2d6; }
496
- .copy.done { color: var(--ok); border-color: var(--ok); }
705
+ };
497
706
 
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
- }
507
-
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; }
707
+ // src/agent/tools/get_screenshot.ts
708
+ var getScreenshot = {
709
+ 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,
710
+ inputSchema: {
711
+ type: "object",
712
+ properties: {
713
+ guid: GUID_PROP,
714
+ format: {
715
+ type: "string",
716
+ enum: ["PNG", "SVG"],
717
+ 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"
718
+ },
719
+ scale: {
720
+ type: "number",
721
+ description: "\u5BFC\u51FA\u500D\u7387\uFF0C\u9ED8\u8BA4 2\u3002"
722
+ },
723
+ cache: CACHE_PROP,
724
+ target: TARGET_PROP
725
+ }
516
726
  }
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>
531
-
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>
534
-
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>
727
+ };
553
728
 
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>
729
+ // src/agent/tools/get_selected.ts
730
+ var getSelected = {
731
+ 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,
732
+ inputSchema: { type: "object", properties: { target: TARGET_PROP } }
733
+ };
569
734
 
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 }}" }
735
+ // src/agent/tools/list_sessions.ts
736
+ var listSessions = {
737
+ 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",
738
+ inputSchema: { type: "object", properties: {} },
739
+ handler: async () => {
740
+ const list = sessions.list();
741
+ if (!list.length) {
742
+ return {
743
+ 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" }]
744
+ };
745
+ }
746
+ const checks = await Promise.all(
747
+ list.map(async (info) => info.online && await pingSession(info.sessionId) ? info : null)
748
+ );
749
+ const alive = checks.filter((c) => c !== null);
750
+ for (const info of list) {
751
+ if (!alive.includes(info)) sessions.evict(info.sessionId);
752
+ }
753
+ if (!alive.length) {
754
+ return {
755
+ 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" }]
756
+ };
583
757
  }
758
+ return jsonResult(
759
+ alive.map(({ sessionId, fileKey, fileName, pageName, selectionCount }) => ({
760
+ sessionId,
761
+ fileKey,
762
+ fileName,
763
+ pageName,
764
+ selectionCount
765
+ }))
766
+ );
584
767
  }
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>
591
-
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>
605
-
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
- });
768
+ };
625
769
 
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
- `;
770
+ // src/agent/tools/index.ts
771
+ var TOOL_REGISTRY = {
772
+ get_selected: getSelected,
773
+ get_node_dsl: getNodeDsl,
774
+ get_metadata: getMetadata,
775
+ export_image: exportImage,
776
+ get_screenshot: getScreenshot,
777
+ list_sessions: listSessions
778
+ };
779
+ var TOOLS = Object.fromEntries(
780
+ Object.entries(TOOL_REGISTRY).map(([name, def]) => {
781
+ const handler = def.handler ?? ((args, ctx) => run(name, args, ctx?.signal));
782
+ const resolvedDef = { description: def.description, inputSchema: def.inputSchema, handler };
783
+ return [name, { def: resolvedDef, tool: { name, description: def.description, inputSchema: def.inputSchema } }];
784
+ })
785
+ );
786
+
787
+ // src/agent/mcp.ts
788
+ 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`;
789
+ function createMcpServer() {
790
+ const server = new McpServer(
791
+ { name: HUB_NAME, version: VERSION },
792
+ { capabilities: { tools: {}, resources: {}, prompts: {} }, instructions: INSTRUCTIONS }
793
+ );
794
+ for (const guide of GUIDES) {
795
+ server.registerResource(
796
+ guide.name,
797
+ guide.uri,
798
+ {
799
+ description: guide.description,
800
+ mimeType: "text/markdown",
801
+ // 纯 agent 消费的工作流文档,高优先级提示读取
802
+ annotations: { audience: ["assistant"], priority: 0.9 }
803
+ },
804
+ async (uri) => ({
805
+ contents: [{ uri: uri.href, mimeType: "text/markdown", text: guide.text }]
806
+ })
807
+ );
653
808
  }
654
- });
655
-
656
- // src/install/template.ts
657
- function escapeHtml(s) {
658
- return s.replace(/[&<>"']/g, (c) => HTML_ESCAPES[c]);
809
+ server.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
810
+ resourceTemplates: []
811
+ }));
812
+ server.server.setRequestHandler(ListToolsRequestSchema, async () => ({
813
+ tools: Object.values(TOOLS).map((t) => t.tool)
814
+ }));
815
+ server.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
816
+ const { name, arguments: args = {} } = request.params;
817
+ const entry = TOOLS[name];
818
+ if (!entry) {
819
+ throw new Error(`unknown tool: ${name}`);
820
+ }
821
+ return entry.def.handler(args, { signal: extra.signal });
822
+ });
823
+ server.server.setRequestHandler(ListPromptsRequestSchema, async () => ({
824
+ prompts: PROMPTS.map((p) => ({
825
+ name: p.name,
826
+ description: p.description,
827
+ arguments: p.arguments
828
+ }))
829
+ }));
830
+ server.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
831
+ const entry = PROMPTS.find((p) => p.name === request.params.name);
832
+ if (!entry) {
833
+ throw new Error(`unknown prompt: ${request.params.name}`);
834
+ }
835
+ const args = request.params.arguments ?? {};
836
+ return { description: entry.description, messages: entry.render(args) };
837
+ });
838
+ return server;
659
839
  }
660
- function escapeJsString(s) {
661
- return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\r/g, "\\r").replace(/\n/g, "\\n");
840
+
841
+ // src/agent/sessions.ts
842
+ var transports = /* @__PURE__ */ new Map();
843
+ function getMcpTransport(sessionId) {
844
+ return transports.get(sessionId);
662
845
  }
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);
846
+ function registerMcpTransport(sessionId, transport) {
847
+ transports.set(sessionId, transport);
848
+ log(`MCP HTTP session initialized: ${sessionId.slice(0, 8)} (total ${transports.size})`);
849
+ transport.onclose = () => {
850
+ if (transport.sessionId) transports.delete(transport.sessionId);
668
851
  };
669
- return tpl.replace(/\{\{\{\s*(\w+)\s*\}\}\}/g, (_, key) => lookup(key)).replace(/\{\{\s*(\w+)\s*\}\}/g, (_, key) => escape(lookup(key)));
670
852
  }
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
853
 
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
- });
854
+ // src/agent/http.ts
855
+ function handleMcp(req, res, url) {
856
+ return dispatchRoute(MCP_ROUTES, req, res, url);
706
857
  }
707
- var init_page = __esm({
708
- "src/install/page.ts"() {
709
- "use strict";
710
- init_config();
711
- init_template();
858
+ async function sessionRequestHandler({ req, res }) {
859
+ const sessionId = req.headers["mcp-session-id"];
860
+ const transport = sessionId ? getMcpTransport(sessionId) : void 0;
861
+ if (!transport) {
862
+ sendJson(res, 400, { error: "missing or unknown MCP session" });
863
+ return;
712
864
  }
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;
865
+ await transport.handleRequest(req, res);
721
866
  }
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;
867
+ var MCP_ROUTES = [
868
+ {
869
+ method: "POST",
870
+ path: "/mcp",
871
+ handler: async ({ req, res }) => {
872
+ let body;
873
+ try {
874
+ body = await readJsonBody(req);
875
+ } catch {
876
+ sendJson(res, 400, { jsonrpc: "2.0", error: { code: -32700, message: "Parse error" }, id: null });
877
+ return;
782
878
  }
783
- return table;
784
- })();
879
+ const sessionId = req.headers["mcp-session-id"];
880
+ let transport = sessionId ? getMcpTransport(sessionId) : void 0;
881
+ if (!transport) {
882
+ if (!sessionId && isInitializeRequest(body)) {
883
+ const t = new StreamableHTTPServerTransport({
884
+ sessionIdGenerator: () => randomUUID2(),
885
+ onsessioninitialized: (id) => registerMcpTransport(id, t)
886
+ });
887
+ await createMcpServer().connect(t);
888
+ transport = t;
889
+ } else {
890
+ sendJson(res, 400, {
891
+ jsonrpc: "2.0",
892
+ error: { code: -32e3, message: "Bad Request: missing or unknown MCP session" },
893
+ id: null
894
+ });
895
+ return;
896
+ }
897
+ }
898
+ await transport.handleRequest(req, res, body);
899
+ }
900
+ },
901
+ { method: "GET", path: "/mcp", handler: sessionRequestHandler },
902
+ { method: "DELETE", path: "/mcp", handler: sessionRequestHandler }
903
+ ];
904
+
905
+ // src/install/dist-resolver.ts
906
+ import { existsSync } from "node:fs";
907
+ import { readFile } from "node:fs/promises";
908
+ import { join as join3 } from "node:path";
909
+ import { fileURLToPath } from "node:url";
910
+ function resolvePluginDir() {
911
+ const candidates = [
912
+ fileURLToPath(new URL("./plugin/", import.meta.url)),
913
+ // bundle: dist/index.js → dist/plugin/
914
+ fileURLToPath(new URL("../../plugin/dist/", import.meta.url)),
915
+ // bundle 开发: dist/index.js → packages/plugin/dist/
916
+ fileURLToPath(new URL("../../../plugin/dist/", import.meta.url))
917
+ // tsx 开发: src/install/ → packages/plugin/dist/
918
+ ];
919
+ return candidates.find((p) => existsSync(join3(p, "manifest.json"))) ?? null;
920
+ }
921
+ var resolved = null;
922
+ function pluginDir() {
923
+ resolved ??= resolvePluginDir();
924
+ return resolved;
925
+ }
926
+ function hasPluginDist() {
927
+ return pluginDir() !== null;
928
+ }
929
+ async function pluginManifest() {
930
+ const dir = pluginDir();
931
+ if (!dir) return null;
932
+ const raw = JSON.parse(await readFile(join3(dir, "manifest.json"), "utf8"));
933
+ raw.networkAccess = {
934
+ allowedDomains: [`http://localhost:${config.httpPort}`, `http://127.0.0.1:${config.httpPort}`]
935
+ };
936
+ return Buffer.from(JSON.stringify(raw, null, 2));
937
+ }
938
+ async function readPluginFile(name) {
939
+ const dir = pluginDir();
940
+ if (!dir) return null;
941
+ try {
942
+ return await readFile(join3(dir, name));
943
+ } catch {
944
+ return null;
785
945
  }
786
- });
946
+ }
787
947
 
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;
948
+ // src/install/template.ts
949
+ var HTML_ESCAPES = {
950
+ "&": "&amp;",
951
+ "<": "&lt;",
952
+ ">": "&gt;",
953
+ '"': "&quot;",
954
+ "'": "&#39;"
955
+ };
956
+ function escapeHtml(s) {
957
+ return s.replace(/[&<>"']/g, (c) => HTML_ESCAPES[c]);
958
+ }
959
+ function escapeJsString(s) {
960
+ return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\r/g, "\\r").replace(/\n/g, "\\n");
961
+ }
962
+ function renderTemplate(tpl, vars, escape = escapeHtml) {
963
+ const lookup = (key) => {
964
+ const v = vars[key];
965
+ if (v === void 0) throw new Error(`\u6A21\u677F\u53D8\u91CF\u7F3A\u5931\uFF1A{{${key}}}`);
966
+ return String(v);
967
+ };
968
+ return tpl.replace(/\{\{\{\s*(\w+)\s*\}\}\}/g, (_, key) => lookup(key)).replace(/\{\{\s*(\w+)\s*\}\}/g, (_, key) => escape(lookup(key)));
969
+ }
970
+
971
+ // src/install/templates/bookmarklet.txt
972
+ 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';
973
+
974
+ // src/install/templates/install-page.html
975
+ var install_page_default = `<!DOCTYPE html>
976
+ <html lang="zh-CN">
977
+ <head>
978
+ <meta charset="utf-8">
979
+ <meta name="viewport" content="width=device-width, initial-scale=1">
980
+ <title>Batter Pixso Bridge \xB7 \u5B89\u88C5</title>
981
+ <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">
982
+ <link rel="preconnect" href="https://fonts.googleapis.com">
983
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
984
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
985
+ <style>
986
+ :root {
987
+ --ink: #1e1e1e;
988
+ --sub: #6b6b6b;
989
+ --faint: #a3a3a3;
990
+ --line: #e8e8ea;
991
+ --panel: #f7f7f8;
992
+ --iris: #575bf2;
993
+ --iris-strong: #4346e0;
994
+ --iris-soft: #eef0fe;
995
+ --ok: #14ae5c;
996
+ --err: #f24822;
997
+ --radius: 6px;
998
+ --mono: ui-monospace, "SF Mono", Menlo, Consolas, "Cascadia Mono", monospace;
796
999
  }
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;
1000
+ * { box-sizing: border-box; margin: 0; padding: 0; }
1001
+ html { -webkit-text-size-adjust: 100%; }
1002
+ body {
1003
+ background: #fff;
1004
+ color: var(--ink);
1005
+ font-family: Inter, -apple-system, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
1006
+ font-feature-settings: "tnum" 1, "cv05" 1;
1007
+ line-height: 1.5;
1008
+ min-height: 100vh;
806
1009
  }
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;
1010
+ ::selection { background: var(--iris-soft); }
1011
+ .page {
1012
+ max-width: 680px;
1013
+ margin: 0 auto;
1014
+ padding: clamp(48px, 10vh, 96px) 24px 64px;
1015
+ animation: enter 420ms cubic-bezier(0.2, 0.6, 0.2, 1) both;
819
1016
  }
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;
1017
+ @keyframes enter {
1018
+ from { opacity: 0; transform: translateY(8px); }
1019
+ to { opacity: 1; transform: none; }
845
1020
  }
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";
1021
+ a { color: var(--iris); text-decoration: none; }
1022
+ a:focus-visible, button:focus-visible {
1023
+ outline: 2px solid var(--iris);
1024
+ outline-offset: 2px;
1025
+ border-radius: 2px;
858
1026
  }
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
1027
 
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\u4E25\u7981\u76F4\u63A5\u5F15\u7528\u4EA7\u7269\u76EE\u5F55\u7684\u7EDD\u5BF9\u8DEF\u5F84
916
- \uFF08\u4EA7\u7269\u9ED8\u8BA4 7 \u5929\u540E\u81EA\u52A8\u6E05\u626B\uFF0C\u5F15\u7528\u4F1A\u5931\u6548\uFF09\uFF1B\u4E25\u7981\u79FB\u52A8\u6216\u5220\u9664\u4EA7\u7269\u76EE\u5F55\u4E0B\u7684\u6587\u4EF6
917
- \uFF08\u591A\u4F1A\u8BDD\u5171\u4EAB\uFF0C\u6E05\u626B\u7531\u670D\u52A1\u7EDF\u4E00\u8D1F\u8D23\uFF09\u3002
918
- 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
919
- \`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
920
- \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
921
- \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
922
-
923
- ## \u63A8\u8350\u6B65\u9AA4
1028
+ /* \u2500\u2500 \u9876\u680F\uFF1A\u54C1\u724C + \u670D\u52A1\u72B6\u6001 \u2500\u2500 */
1029
+ .top {
1030
+ display: flex;
1031
+ align-items: center;
1032
+ justify-content: space-between;
1033
+ gap: 16px;
1034
+ margin-bottom: clamp(40px, 7vh, 72px);
1035
+ }
1036
+ .brand {
1037
+ display: flex;
1038
+ align-items: center;
1039
+ gap: 10px;
1040
+ font-size: 13px;
1041
+ font-weight: 600;
1042
+ letter-spacing: -0.01em;
1043
+ }
1044
+ .brand svg { display: block; }
1045
+ .brand .ver {
1046
+ font-family: var(--mono);
1047
+ font-weight: 400;
1048
+ font-size: 11px;
1049
+ color: var(--faint);
1050
+ margin-left: 2px;
1051
+ }
1052
+ .status {
1053
+ display: inline-flex;
1054
+ align-items: center;
1055
+ gap: 7px;
1056
+ padding: 5px 10px;
1057
+ border-radius: 999px;
1058
+ background: var(--panel);
1059
+ border: 1px solid var(--line);
1060
+ font-size: 11px;
1061
+ color: var(--sub);
1062
+ white-space: nowrap;
1063
+ }
1064
+ .status .dot {
1065
+ width: 6px;
1066
+ height: 6px;
1067
+ border-radius: 50%;
1068
+ background: var(--faint);
1069
+ flex-shrink: 0;
1070
+ animation: pulse 2.2s ease-in-out infinite;
1071
+ }
1072
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
1073
+ .status.ok { color: #0d7a3e; }
1074
+ .status.ok .dot { background: var(--ok); }
1075
+ .status.err { color: var(--err); }
1076
+ .status.err .dot { background: var(--err); animation: none; }
924
1077
 
925
- 1. **\u786E\u8BA4\u76EE\u6807\u8282\u70B9**
926
- - \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
927
- - \u7528\u6237\u660E\u786E\u8BF4\u4E86"\u8FD9\u4E2A/\u6211\u9009\u4E2D\u7684"\uFF1A\u8C03 \`get_selected\` \u62FF guid\uFF0C\u76F4\u63A5\u7EE7\u7EED\u3002
928
- - \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
929
- \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
930
- \`get_node_dsl\` / \`get_screenshot\` \u7684 guid \u7F3A\u7701\u56DE\u9000\u540C\u6837\u9002\u7528\u672C\u89C4\u5219\u3002
931
- 2. **\u53D6\u7ED3\u6784**\uFF1A\`get_node_dsl(guid)\` \u62FF\u7CBE\u7B80 DSL\u3002
932
- 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
933
- 4. **\u751F\u6210\u4EE3\u7801**\uFF1A\u6309\u4E0B\u6587 DSL \u8BED\u4E49\u6620\u5C04\u5230\u76EE\u6807\u6846\u67B6\u3002
934
- 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
1078
+ /* \u2500\u2500 \u4E3B\u6807\u9898\u533A \u2500\u2500 */
1079
+ h1 {
1080
+ font-size: clamp(28px, 5vw, 38px);
1081
+ font-weight: 650;
1082
+ letter-spacing: -0.025em;
1083
+ line-height: 1.2;
1084
+ margin-bottom: 12px;
1085
+ }
1086
+ .sub {
1087
+ font-size: 14px;
1088
+ color: var(--sub);
1089
+ max-width: 46em;
1090
+ line-height: 1.7;
1091
+ margin-bottom: clamp(40px, 7vh, 64px);
1092
+ }
935
1093
 
936
- ## DSL \u5B57\u6BB5\u8BED\u4E49\uFF08\u7D27\u51D1\u683C\u5F0F\uFF09
1094
+ /* \u2500\u2500 \u6B65\u9AA4\u5217\u8868\uFF1A\u53D1\u4E1D\u7EBF\u5206\u9694 \u2500\u2500 */
1095
+ .steps { border-top: 1px solid var(--line); }
1096
+ .step {
1097
+ display: grid;
1098
+ grid-template-columns: 44px 1fr;
1099
+ gap: 4px 16px;
1100
+ padding: 26px 0 28px;
1101
+ border-bottom: 1px solid var(--line);
1102
+ }
1103
+ .num {
1104
+ grid-row: 1 / span 2;
1105
+ font-family: var(--mono);
1106
+ font-size: 12px;
1107
+ color: var(--faint);
1108
+ padding-top: 3px;
1109
+ font-variant-numeric: tabular-nums;
1110
+ }
1111
+ .step-head {
1112
+ display: flex;
1113
+ align-items: center;
1114
+ gap: 10px;
1115
+ flex-wrap: wrap;
1116
+ }
1117
+ .step-title {
1118
+ font-size: 14px;
1119
+ font-weight: 600;
1120
+ letter-spacing: -0.01em;
1121
+ }
1122
+ .tag {
1123
+ font-size: 11px;
1124
+ font-weight: 500;
1125
+ color: var(--iris);
1126
+ background: var(--iris-soft);
1127
+ padding: 2px 8px;
1128
+ border-radius: 999px;
1129
+ }
1130
+ .step-body { grid-column: 2; }
1131
+ .note {
1132
+ font-size: 12.5px;
1133
+ color: var(--sub);
1134
+ line-height: 1.75;
1135
+ max-width: 60em;
1136
+ }
1137
+ .note strong { color: var(--ink); font-weight: 600; }
1138
+ code {
1139
+ font-family: var(--mono);
1140
+ background: var(--panel);
1141
+ border: 1px solid var(--line);
1142
+ color: var(--ink);
1143
+ padding: 0.5px 5px;
1144
+ border-radius: 4px;
1145
+ font-size: 11px;
1146
+ }
1147
+ .actions { display: flex; align-items: center; gap: 12px; margin: 14px 0 10px; }
937
1148
 
938
- 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
1149
+ /* \u4E3B\u6309\u94AE\uFF1A\u53EF\u62D6\u62FD\u7684\u4E66\u7B7E */
1150
+ .bm {
1151
+ display: inline-flex;
1152
+ align-items: center;
1153
+ gap: 8px;
1154
+ padding: 9px 18px;
1155
+ background: var(--iris);
1156
+ color: #fff;
1157
+ border-radius: var(--radius);
1158
+ font-size: 13px;
1159
+ font-weight: 600;
1160
+ letter-spacing: -0.01em;
1161
+ cursor: grab;
1162
+ box-shadow: 0 1px 2px rgba(30, 30, 30, 0.08);
1163
+ transition: background 140ms ease, transform 140ms ease;
1164
+ user-select: none;
1165
+ }
1166
+ .bm:hover { background: var(--iris-strong); }
1167
+ .bm:active { cursor: grabbing; transform: scale(0.985); }
1168
+ .drag-hint {
1169
+ font-size: 12px;
1170
+ color: var(--faint);
1171
+ }
939
1172
 
940
- \`\`\`jsonc
941
- {
942
- "id": "297:133032", // \u8282\u70B9 guid\uFF0C\u53EF\u56DE\u4F20\u4E3A guid \u53C2\u6570\u5BF9\u8BE5\u8282\u70B9\u4E0B\u94BB\u8C03\u7528
943
- "name": "\u65B0\u5BA2", // \u56FE\u5C42\u540D\uFF1B\u81EA\u52A8\u547D\u540D\uFF08"Frame 3123" \u7C7B\uFF09\u5DF2\u7701\u7565
944
- "type": "FRAME", // \u8282\u70B9\u7C7B\u578B\uFF1BICON/IMAGE \u4E3A\u584C\u7F29\u53F6\u5B50
945
- "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
946
- "size": [120, 40], // [w,h]\uFF1Aauto-layout \u5B50\u8282\u70B9\uFF08x/y \u7531\u7236\u7EA7 layout \u63A8\u5BFC\uFF0C\u7701\u7565\uFF09
947
- "layout": { // auto-layout \u2192 flex \u76F4\u8BD1\uFF08\u4EC5\u5BB9\u5668\u6709\uFF09
948
- "dir": "row", // row | column
949
- "gap": 12, // \u4E3A 0 \u65F6\u7701\u7565
950
- "pad": [16, 16, 16, 16], // [\u4E0A,\u53F3,\u4E0B,\u5DE6]\uFF0C\u5168 0 \u65F6\u7701\u7565
951
- "align": ["start", "center"], // [\u4E3B\u8F74,\u4EA4\u53C9\u8F74]\uFF1Astart|center|end|space-between|baseline
952
- "sizing": ["hug", "fixed"] // [\u4E3B\u8F74,\u4EA4\u53C9\u8F74]\uFF1Ahug|fixed|fill
953
- },
954
- "text": { // \u4EC5\u6587\u672C\u8282\u70B9\uFF1B\u9ED8\u8BA4\u503C\u7701\u7565\uFF1Aalign=left\u3001lh=normal\u3001weight=Regular
955
- "content": "...", "size": 14, "font": "PingFang SC",
956
- "weight": "Medium", "lh": "22px", "align": "center",
957
- "deco": "underline", "ls": "0.5px", "link": "https://...", // \u6309\u9700
958
- "segs": [ // \u4EC5\u6DF7\u6392\u6587\u672C\uFF1Abase \u4E3A\u6700\u957F\u6BB5\u6837\u5F0F\uFF0C\u6BCF\u6BB5\u53EA\u5E26\u5DEE\u5F02\u5B57\u6BB5\uFF1B
959
- { "text": "\u7406\u8D54\u4E2D\u5FC3", "weight": "Bold", "color": "p1" } // \u6570\u7EC4\u987A\u5E8F\u5373\u5B57\u7B26\u504F\u79FB\uFF08start/end \u7701\u7565\uFF09
960
- ]
961
- },
962
- "fills": ["p0", "linear-gradient(90deg, #fff 0%, #000 100%)"], // CSS \u989C\u8272/\u6E10\u53D8
963
- "stroke": "1px solid #e5e5e5", // \u989C\u8272\u90E8\u5206\u4E5F\u53EF\u80FD\u662F "p0" \u8C03\u8272\u677F\u5F15\u7528
964
- "radius": 8, // \u6216 [\u4E0A\u5DE6, \u4E0A\u53F3, \u4E0B\u53F3, \u4E0B\u5DE6]
965
- "shadow": "0px 2px 8px rgba(0,0,0,0.1), inset 0px 1px 0px #fff", // CSS box-shadow\uFF0C\u591A\u9634\u5F71\u9017\u53F7\u62FC\u63A5
966
- "blur": "4px", // \u56FE\u5C42\u6A21\u7CCA \u2192 CSS filter: blur()
967
- "bgBlur": "8px", // \u80CC\u666F\u6A21\u7CCA \u2192 CSS backdrop-filter
968
- "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
969
- "opacity": 0.9, // \u4E3A 1 \u65F6\u7701\u7565
970
- "img": "ab12cd34.svg", // \u53F6\u5B50\u8D44\u6E90\u6587\u4EF6\u540D\uFF08\u53D6\u7528\u89C4\u5219\u89C1\u786C\u6027\u89C4\u5219\u7B2C 1 \u6761\uFF09
971
- "palette": ["#1a73e8", "#f5222d"], // \u8C03\u8272\u677F\uFF08\u4EC5\u6839\u8282\u70B9\uFF09\uFF0C"p0"/"p1" \u4E3A\u4E0B\u6807\u5F15\u7528
972
- "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
973
- "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
974
- "kids": [] // \u5B50\u8282\u70B9\u9012\u5F52
975
- }
976
- \`\`\`
1173
+ /* \u6B21\u7EA7\u6309\u94AE */
1174
+ .btn {
1175
+ display: inline-flex;
1176
+ align-items: center;
1177
+ gap: 7px;
1178
+ padding: 8px 14px;
1179
+ border: 1px solid var(--line);
1180
+ border-radius: var(--radius);
1181
+ background: #fff;
1182
+ color: var(--ink);
1183
+ font-size: 13px;
1184
+ font-weight: 500;
1185
+ letter-spacing: -0.01em;
1186
+ text-decoration: none;
1187
+ transition: border-color 140ms ease, background 140ms ease;
1188
+ }
1189
+ .btn:hover { border-color: #d2d2d6; background: var(--panel); }
1190
+ .btn svg { display: block; color: var(--sub); }
977
1191
 
978
- **\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
979
- \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
1192
+ /* MCP \u914D\u7F6E\u4EE3\u7801\u5757 */
1193
+ .cfg {
1194
+ position: relative;
1195
+ margin: 14px 0 10px;
1196
+ }
1197
+ pre {
1198
+ background: var(--panel);
1199
+ border: 1px solid var(--line);
1200
+ border-radius: 8px;
1201
+ padding: 14px 16px;
1202
+ font-family: var(--mono);
1203
+ font-size: 12px;
1204
+ line-height: 1.7;
1205
+ color: #3c3c3c;
1206
+ overflow-x: auto;
1207
+ }
1208
+ .copy {
1209
+ position: absolute;
1210
+ top: 8px;
1211
+ right: 8px;
1212
+ padding: 4px 10px;
1213
+ font-family: inherit;
1214
+ font-size: 11px;
1215
+ font-weight: 500;
1216
+ color: var(--sub);
1217
+ background: #fff;
1218
+ border: 1px solid var(--line);
1219
+ border-radius: var(--radius);
1220
+ cursor: pointer;
1221
+ transition: color 140ms ease, border-color 140ms ease;
1222
+ }
1223
+ .copy:hover { color: var(--ink); border-color: #d2d2d6; }
1224
+ .copy.done { color: var(--ok); border-color: var(--ok); }
980
1225
 
981
- **\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
982
- \`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
983
- \`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
1226
+ .footer {
1227
+ margin-top: 40px;
1228
+ font-size: 11px;
1229
+ color: var(--faint);
1230
+ font-family: var(--mono);
1231
+ display: flex;
1232
+ gap: 8px;
1233
+ flex-wrap: wrap;
1234
+ }
984
1235
 
985
- ## \u53F6\u5B50\u8282\u70B9\u4E0E\u56FE\u7247\u8D44\u6E90
1236
+ @media (max-width: 560px) {
1237
+ .step { grid-template-columns: 32px 1fr; gap: 4px 10px; }
1238
+ .top { align-items: flex-start; flex-direction: column; gap: 12px; }
1239
+ }
1240
+ @media (prefers-reduced-motion: reduce) {
1241
+ .page { animation: none; }
1242
+ .status .dot { animation: none; }
1243
+ .bm, .btn, .copy { transition: none; }
1244
+ }
1245
+ </style>
1246
+ </head>
1247
+ <body>
1248
+ <div class="page">
1249
+ <header class="top">
1250
+ <div class="brand">
1251
+ <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>
1252
+ Batter Pixso Bridge
1253
+ <span class="ver">v{{ version }}</span>
1254
+ </div>
1255
+ <div class="status" id="status">
1256
+ <span class="dot"></span><span id="status-text">\u6B63\u5728\u8FDE\u63A5\u670D\u52A1\u2026</span>
1257
+ </div>
1258
+ </header>
986
1259
 
987
- - \`type: "ICON"\`\uFF1A\u7EAF\u77E2\u91CF\u5C0F\u8282\u70B9\uFF08icon\uFF09\uFF0C\`img\` \u4E3A **SVG** \u6587\u4EF6\u540D\u3002
988
- - \`type: "IMAGE"\`\uFF1A\u56FE\u7247\u586B\u5145\u8282\u70B9\uFF0C\`img\` \u4E3A **PNG** \u6587\u4EF6\u540D\u3002
989
- - \u6587\u4EF6\u540D\u62FC\u8DEF\u5F84\u4E0E\u53D6\u7528\u8981\u6C42\u89C1\u5F00\u5934\u786C\u6027\u89C4\u5219\u7B2C 1 \u6761\u3002
990
- - \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
991
- - **\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
992
- \`export_image\` \u4EC5\u7528\u4E8E DSL \u4E2D\u672A\u584C\u7F29\u7684\u8282\u70B9\uFF08\u4E3B\u52A8\u5207\u56FE\uFF09\u3002
1260
+ <h1>\u628A\u8BBE\u8BA1\u7A3F\u4EA4\u7ED9 AI</h1>
1261
+ <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>
993
1262
 
994
- ## \u6CE8\u610F
1263
+ <div class="steps">
1264
+ <div class="step">
1265
+ <span class="num">01</span>
1266
+ <div class="step-head">
1267
+ <span class="step-title">\u6DFB\u52A0\u4E66\u7B7E</span>
1268
+ <span class="tag">\u7F51\u9875\u7248 \xB7 \u63A8\u8350</span>
1269
+ </div>
1270
+ <div class="step-body">
1271
+ <div class="actions">
1272
+ <a class="bm" href="{{ href }}">Batter Bridge</a>
1273
+ <span class="drag-hint">\u2190 \u628A\u8FD9\u4E2A\u6309\u94AE\u62D6\u5230\u6D4F\u89C8\u5668\u4E66\u7B7E\u680F</span>
1274
+ </div>
1275
+ <p class="note">
1276
+ <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
1277
+ \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
1278
+ </p>
1279
+ </div>
1280
+ </div>
995
1281
 
996
- - 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
997
- - \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
998
- \u8BBE\u8BA1\u7A3F\u521A\u6539\u8FC7\u4F46\u7ED3\u679C\u5BF9\u4E0D\u4E0A\u65F6\uFF0C\u52A0 \`cache: "refresh"\` \u5F3A\u5236\u5237\u65B0\u3002
999
- - \u591A\u6587\u4EF6\u573A\u666F\u6240\u6709\u5DE5\u5177\u90FD\u652F\u6301 \`target\` \u53C2\u6570\uFF08sessionId/fileKey\uFF09\uFF0C\u89C1 \`batter://guides/intro\`\u3002
1000
- `;
1001
- GUIDES = [
1002
- {
1003
- name: "intro",
1004
- uri: "batter://guides/intro",
1005
- 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",
1006
- text: INTRO
1007
- },
1008
- {
1009
- name: "design-to-code",
1010
- uri: "batter://guides/design-to-code",
1011
- 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",
1012
- text: DESIGN_TO_CODE
1013
- }
1014
- ];
1015
- }
1016
- });
1282
+ <div class="step">
1283
+ <span class="num">02</span>
1284
+ <div class="step-head">
1285
+ <span class="step-title">\u5B89\u88C5\u5230 Pixso \u5BA2\u6237\u7AEF</span>
1286
+ </div>
1287
+ <div class="step-body">
1288
+ <div class="actions">
1289
+ <a class="btn" href="/plugin.zip" download="batter-pixso-bridge.zip">
1290
+ <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>
1291
+ \u4E0B\u8F7D\u63D2\u4EF6\u5305\uFF08.zip\uFF09
1292
+ </a>
1293
+ </div>
1294
+ <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>
1295
+ </div>
1296
+ </div>
1017
1297
 
1018
- // src/prompts.ts
1019
- var ASSET_HARD_RULE, DESIGN_TO_CODE2, PROMPTS;
1020
- var init_prompts = __esm({
1021
- "src/prompts.ts"() {
1022
- "use strict";
1023
- 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\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`;
1024
- DESIGN_TO_CODE2 = {
1025
- name: "design-to-code",
1026
- 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",
1027
- arguments: [
1028
- {
1029
- name: "url",
1030
- 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",
1031
- required: false
1032
- }
1033
- ],
1034
- render: (args) => {
1035
- 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`;
1036
- return [
1037
- {
1038
- role: "user",
1039
- content: {
1040
- type: "text",
1041
- text: [
1042
- `\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`,
1043
- ``,
1044
- `## \u3010\u786C\u6027\u89C4\u5219\u3011`,
1045
- ``,
1046
- `1. ${ASSET_HARD_RULE}`,
1047
- `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`,
1048
- ``,
1049
- `## \u6267\u884C\u6D41\u7A0B`,
1050
- ``,
1051
- `1. \u786E\u8BA4\u76EE\u6807\u8282\u70B9\uFF1A${targetLine}`,
1052
- `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`,
1053
- `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`,
1054
- `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`,
1055
- ``,
1056
- `**\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`
1057
- ].join("\n")
1058
- }
1059
- }
1060
- ];
1061
- }
1062
- };
1063
- PROMPTS = [DESIGN_TO_CODE2];
1298
+ <div class="step">
1299
+ <span class="num">03</span>
1300
+ <div class="step-head">
1301
+ <span class="step-title">\u914D\u7F6E AI \u5BA2\u6237\u7AEF</span>
1302
+ </div>
1303
+ <div class="step-body">
1304
+ <div class="cfg">
1305
+ <pre id="cfg">{
1306
+ "mcpServers": {
1307
+ "batter-pixso": {
1308
+ "command": "npx",
1309
+ "args": ["-y", "batter-pixso-mcp"],
1310
+ "env": { "HTTP_PORT": "{{ httpPort }}" }
1311
+ }
1064
1312
  }
1065
- });
1313
+ }</pre>
1314
+ <button class="copy" id="copy" type="button">\u590D\u5236</button>
1315
+ </div>
1316
+ <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>
1317
+ </div>
1318
+ </div>
1066
1319
 
1067
- // src/tools/builtin.ts
1068
- var BUILTIN_TOOLS;
1069
- var init_builtin = __esm({
1070
- "src/tools/builtin.ts"() {
1071
- "use strict";
1072
- init_sessions();
1073
- BUILTIN_TOOLS = [
1074
- {
1075
- name: "list_sessions",
1076
- 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",
1077
- inputSchema: { type: "object", properties: {} },
1078
- handler: async () => {
1079
- const list = sessions.list();
1080
- 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";
1081
- return { content: [{ type: "text", text }] };
1082
- }
1083
- }
1084
- ];
1085
- }
1086
- });
1320
+ <div class="step">
1321
+ <span class="num">04</span>
1322
+ <div class="step-head">
1323
+ <span class="step-title">\u5F00\u59CB\u4F7F\u7528</span>
1324
+ </div>
1325
+ <div class="step-body">
1326
+ <p class="note" style="margin-top:10px">
1327
+ \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
1328
+ 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
1329
+ </p>
1330
+ </div>
1331
+ </div>
1332
+ </div>
1087
1333
 
1088
- // src/core/commands.ts
1089
- var CommandBus, commandBus;
1090
- var init_commands = __esm({
1091
- "src/core/commands.ts"() {
1092
- "use strict";
1093
- init_config();
1094
- init_sessions();
1095
- CommandBus = class {
1096
- queues = /* @__PURE__ */ new Map();
1097
- pollWaiters = /* @__PURE__ */ new Map();
1098
- sseListeners = /* @__PURE__ */ new Map();
1099
- pendingById = /* @__PURE__ */ new Map();
1100
- seq = 0;
1101
- /** AI 侧:执行命令 */
1102
- execute(type, args, opts = {}) {
1103
- const session = sessions.resolve(opts.target);
1104
- const timeoutMs = opts.timeoutMs ?? 6e4;
1105
- const { promise, resolve, reject } = Promise.withResolvers();
1106
- const command = { id: `cmd-${++this.seq}`, type, args };
1107
- const entry = {
1108
- command,
1109
- sessionId: session.id,
1110
- resolve,
1111
- reject,
1112
- dispatched: false,
1113
- timer: setTimeout(() => {
1114
- this.drop(entry);
1115
- reject(new Error(`\u547D\u4EE4 ${command.id} \u8D85\u65F6\uFF1A\u63D2\u4EF6 ${timeoutMs / 1e3}s \u5185\u672A\u8FD4\u56DE\u7ED3\u679C`));
1116
- }, timeoutMs)
1117
- };
1118
- opts.signal?.addEventListener("abort", () => {
1119
- if (!this.pendingById.has(command.id)) return;
1120
- this.drop(entry);
1121
- reject(new Error(`\u547D\u4EE4 ${command.id} \u5DF2\u88AB\u8C03\u7528\u65B9\u53D6\u6D88`));
1122
- });
1123
- this.pendingById.set(command.id, entry);
1124
- this.queueOf(session.id).push(entry);
1125
- this.dispatch(session.id);
1126
- return promise;
1127
- }
1128
- /** 插件侧(长轮询兜底通道):取命令,超时返回 null */
1129
- async take(sessionId, timeoutMs = 25e3) {
1130
- const entry = this.nextUndispatched(sessionId);
1131
- if (entry) {
1132
- entry.dispatched = true;
1133
- log(`dispatch command ${entry.command.id}: ${entry.command.type} -> session ${sessionId.slice(0, 8)} (poll)`);
1134
- return entry.command;
1135
- }
1136
- const { promise, resolve } = Promise.withResolvers();
1137
- const waiters = this.waitersOf(sessionId);
1138
- waiters.push(resolve);
1139
- setTimeout(() => {
1140
- const i = waiters.indexOf(resolve);
1141
- if (i >= 0) {
1142
- waiters.splice(i, 1);
1143
- resolve(null);
1144
- }
1145
- }, timeoutMs);
1146
- return promise;
1147
- }
1148
- /** 插件侧(SSE 主通道):订阅即时推送,返回退订函数 */
1149
- subscribe(sessionId, listener) {
1150
- let subs = this.sseListeners.get(sessionId);
1151
- if (!subs) {
1152
- subs = /* @__PURE__ */ new Set();
1153
- this.sseListeners.set(sessionId, subs);
1154
- }
1155
- subs.add(listener);
1156
- this.dispatch(sessionId);
1157
- return () => {
1158
- subs.delete(listener);
1159
- if (subs.size === 0) this.sseListeners.delete(sessionId);
1160
- };
1161
- }
1162
- /** 插件侧:回传结果 */
1163
- complete(id, ok, resultOrError) {
1164
- const entry = this.pendingById.get(id);
1165
- if (!entry) return false;
1166
- this.drop(entry);
1167
- if (ok) {
1168
- entry.resolve(resultOrError);
1169
- } else {
1170
- entry.reject(resultOrError instanceof Error ? resultOrError : new Error(String(resultOrError)));
1171
- }
1172
- return true;
1173
- }
1174
- dispatch(sessionId) {
1175
- const entry = this.nextUndispatched(sessionId);
1176
- if (!entry) return;
1177
- const subs = this.sseListeners.get(sessionId);
1178
- if (subs && subs.size > 0) {
1179
- entry.dispatched = true;
1180
- log(`dispatch command ${entry.command.id}: ${entry.command.type} -> session ${sessionId.slice(0, 8)} (sse)`);
1181
- for (const fn of subs) fn(entry.command);
1182
- return;
1183
- }
1184
- const waiter = this.waitersOf(sessionId).shift();
1185
- if (!waiter) return;
1186
- entry.dispatched = true;
1187
- log(`dispatch command ${entry.command.id}: ${entry.command.type} -> session ${sessionId.slice(0, 8)} (poll)`);
1188
- waiter(entry.command);
1189
- }
1190
- nextUndispatched(sessionId) {
1191
- return this.queueOf(sessionId).find((e) => !e.dispatched);
1192
- }
1193
- queueOf(sessionId) {
1194
- let q = this.queues.get(sessionId);
1195
- if (!q) {
1196
- q = [];
1197
- this.queues.set(sessionId, q);
1198
- }
1199
- return q;
1200
- }
1201
- waitersOf(sessionId) {
1202
- let w = this.pollWaiters.get(sessionId);
1203
- if (!w) {
1204
- w = [];
1205
- this.pollWaiters.set(sessionId, w);
1206
- }
1207
- return w;
1208
- }
1209
- /** 从队列与 pending 表移除(完成/超时/取消共用) */
1210
- drop(entry) {
1211
- clearTimeout(entry.timer);
1212
- this.pendingById.delete(entry.command.id);
1213
- const q = this.queues.get(entry.sessionId);
1214
- if (q) this.queues.set(entry.sessionId, q.filter((e) => e !== entry));
1215
- }
1216
- };
1217
- commandBus = new CommandBus();
1218
- }
1334
+ <div class="footer">
1335
+ <span>batter-pixso-mcp</span>
1336
+ <span>\xB7</span>
1337
+ <span>v{{ version }}</span>
1338
+ <span>\xB7</span>
1339
+ <span>{{ base }}</span>
1340
+ </div>
1341
+ </div>
1342
+ <script>
1343
+ fetch("/health").then(function (r) { return r.json(); }).then(function (h) {
1344
+ var el = document.getElementById("status");
1345
+ el.className = "status ok";
1346
+ document.getElementById("status-text").textContent =
1347
+ "\u670D\u52A1\u8FD0\u884C\u4E2D \xB7 \u63D2\u4EF6 " + h.pluginsOnline + " \u5728\u7EBF";
1348
+ }).catch(function () {
1349
+ var el = document.getElementById("status");
1350
+ el.className = "status err";
1351
+ document.getElementById("status-text").textContent = "\u670D\u52A1\u672A\u8FDE\u63A5";
1219
1352
  });
1220
1353
 
1221
- // src/tools/plugin.ts
1222
- function jsonResult(data) {
1223
- return { content: [{ type: "text", text: JSON.stringify(data) }] };
1224
- }
1225
- function errorResult(err) {
1226
- return {
1227
- content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }],
1228
- isError: true
1229
- };
1230
- }
1231
- async function run(type, args, signal) {
1232
- const { target, ...rest } = args;
1233
- try {
1234
- return jsonResult(
1235
- await commandBus.execute(type, rest, { target, signal })
1236
- );
1237
- } catch (err) {
1238
- return errorResult(err);
1354
+ var copyBtn = document.getElementById("copy");
1355
+ copyBtn.addEventListener("click", function () {
1356
+ var text = document.getElementById("cfg").textContent;
1357
+ function done() {
1358
+ copyBtn.textContent = "\u5DF2\u590D\u5236";
1359
+ copyBtn.className = "copy done";
1360
+ setTimeout(function () {
1361
+ copyBtn.textContent = "\u590D\u5236";
1362
+ copyBtn.className = "copy";
1363
+ }, 1600);
1239
1364
  }
1240
- }
1241
- var URL_RULE, URL_RULE_PTR, SELECT_RULE_PTR, ASSET_RULE, DSL_LEGEND, TARGET_PROP, CACHE_PROP, GUID_PROP, PLUGIN_TOOLS;
1242
- var init_plugin = __esm({
1243
- "src/tools/plugin.ts"() {
1244
- "use strict";
1245
- init_assets();
1246
- init_commands();
1247
- 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';
1248
- 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";
1249
- 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';
1250
- ASSET_RULE = `\u2462 \u8D44\u6E90\uFF1Aimg \u662F\u4EA7\u7269\u6587\u4EF6\u540D\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\u2014\u2014\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`;
1251
- 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';
1252
- TARGET_PROP = {
1253
- type: "string",
1254
- 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"
1255
- };
1256
- CACHE_PROP = {
1257
- type: "string",
1258
- enum: ["first", "refresh"],
1259
- 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"
1260
- };
1261
- GUID_PROP = {
1262
- type: "string",
1263
- 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'
1264
- };
1265
- PLUGIN_TOOLS = [
1266
- {
1267
- name: "get_selected",
1268
- 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',
1269
- inputSchema: { type: "object", properties: { target: TARGET_PROP } },
1270
- handler: (args, ctx) => run("get_selected", args, ctx?.signal)
1271
- },
1272
- {
1273
- name: "get_node_dsl",
1274
- 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,
1275
- inputSchema: {
1276
- type: "object",
1277
- properties: {
1278
- guid: GUID_PROP,
1279
- maxDepth: {
1280
- type: "number",
1281
- 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"
1282
- },
1283
- cache: CACHE_PROP,
1284
- target: TARGET_PROP
1285
- }
1286
- },
1287
- handler: (args, ctx) => run("get_node_dsl", args, ctx?.signal)
1288
- },
1289
- {
1290
- name: "export_image",
1291
- 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,
1292
- inputSchema: {
1293
- type: "object",
1294
- properties: {
1295
- guid: GUID_PROP,
1296
- format: {
1297
- type: "string",
1298
- enum: ["PNG", "SVG", "JPG"],
1299
- 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"
1300
- },
1301
- scale: {
1302
- type: "number",
1303
- 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"
1304
- },
1305
- cache: CACHE_PROP,
1306
- target: TARGET_PROP
1307
- }
1308
- },
1309
- handler: (args, ctx) => run("export_image", args, ctx?.signal)
1310
- },
1311
- {
1312
- name: "get_screenshot",
1313
- 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,
1314
- inputSchema: {
1315
- type: "object",
1316
- properties: {
1317
- guid: GUID_PROP,
1318
- format: {
1319
- type: "string",
1320
- enum: ["PNG", "SVG", "JPG", "WEBP"],
1321
- 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"
1322
- },
1323
- scale: {
1324
- type: "number",
1325
- description: "\u5BFC\u51FA\u500D\u7387\uFF0C\u9ED8\u8BA4 2\u3002"
1326
- },
1327
- cache: CACHE_PROP,
1328
- target: TARGET_PROP
1329
- }
1330
- },
1331
- handler: (args, ctx) => run("get_screenshot", args, ctx?.signal)
1332
- }
1333
- ];
1365
+ if (navigator.clipboard && navigator.clipboard.writeText) {
1366
+ navigator.clipboard.writeText(text).then(done, done);
1367
+ } else {
1368
+ var ta = document.createElement("textarea");
1369
+ ta.value = text;
1370
+ document.body.appendChild(ta);
1371
+ ta.select();
1372
+ try { document.execCommand("copy"); } catch (e) {}
1373
+ document.body.removeChild(ta);
1374
+ done();
1334
1375
  }
1335
1376
  });
1377
+ </script>
1378
+ </body>
1379
+ </html>
1380
+ `;
1336
1381
 
1337
- // src/tools/registry.ts
1338
- function resolveTools() {
1339
- const seenNames = /* @__PURE__ */ new Set();
1340
- const resolved = [];
1341
- for (const def of TOOL_REGISTRY) {
1342
- if (seenNames.has(def.name)) continue;
1343
- seenNames.add(def.name);
1344
- resolved.push({
1345
- def,
1346
- tool: {
1347
- name: def.name,
1348
- description: def.description,
1349
- inputSchema: def.inputSchema
1350
- }
1351
- });
1352
- }
1353
- return resolved;
1382
+ // src/install/page.ts
1383
+ function bookmarkletHref(base) {
1384
+ const source = renderTemplate(bookmarklet_default, { base }, escapeJsString);
1385
+ return `javascript:${encodeURIComponent(source)}`;
1354
1386
  }
1355
- var TOOL_REGISTRY;
1356
- var init_registry = __esm({
1357
- "src/tools/registry.ts"() {
1358
- "use strict";
1359
- init_builtin();
1360
- init_plugin();
1361
- TOOL_REGISTRY = [
1362
- ...PLUGIN_TOOLS,
1363
- ...BUILTIN_TOOLS
1364
- ];
1365
- }
1366
- });
1367
-
1368
- // src/mcp-local.ts
1369
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1370
- import {
1371
- CallToolRequestSchema,
1372
- GetPromptRequestSchema,
1373
- ListPromptsRequestSchema,
1374
- ListResourceTemplatesRequestSchema,
1375
- ListToolsRequestSchema
1376
- } from "@modelcontextprotocol/sdk/types.js";
1377
- function createMcpServer() {
1378
- const server = new McpServer(
1379
- { name: "batter-pixso-mcp", version: VERSION },
1380
- { capabilities: { tools: {}, resources: {}, prompts: {} }, instructions: INSTRUCTIONS }
1381
- );
1382
- for (const guide of GUIDES) {
1383
- server.registerResource(
1384
- guide.name,
1385
- guide.uri,
1386
- {
1387
- description: guide.description,
1388
- mimeType: "text/markdown",
1389
- // 纯 agent 消费的工作流文档,高优先级提示读取
1390
- annotations: { audience: ["assistant"], priority: 0.9 }
1391
- },
1392
- async (uri) => ({
1393
- contents: [{ uri: uri.href, mimeType: "text/markdown", text: guide.text }]
1394
- })
1395
- );
1396
- }
1397
- server.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
1398
- resourceTemplates: []
1399
- }));
1400
- const tools = resolveTools();
1401
- const byName = {};
1402
- for (const t of tools) {
1403
- byName[t.def.name] = t;
1404
- }
1405
- server.server.setRequestHandler(ListToolsRequestSchema, async () => ({
1406
- tools: tools.map((t) => t.tool)
1407
- }));
1408
- server.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
1409
- const { name, arguments: args = {} } = request.params;
1410
- const entry = byName[name];
1411
- if (!entry) {
1412
- throw new Error(`unknown tool: ${name}`);
1413
- }
1414
- let finalArgs = args;
1415
- if (entry.def.beforeCall) {
1416
- finalArgs = entry.def.beforeCall(finalArgs);
1417
- }
1418
- let result = await entry.def.handler(finalArgs, { signal: extra.signal });
1419
- if (entry.def.afterCall) {
1420
- result = entry.def.afterCall(result);
1421
- }
1422
- return result;
1423
- });
1424
- server.server.setRequestHandler(ListPromptsRequestSchema, async () => ({
1425
- prompts: PROMPTS.map((p) => ({
1426
- name: p.name,
1427
- description: p.description,
1428
- arguments: p.arguments
1429
- }))
1430
- }));
1431
- server.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
1432
- const entry = PROMPTS.find((p) => p.name === request.params.name);
1433
- if (!entry) {
1434
- throw new Error(`unknown prompt: ${request.params.name}`);
1435
- }
1436
- const args = request.params.arguments ?? {};
1437
- return { description: entry.description, messages: entry.render(args) };
1387
+ function renderInstallPage() {
1388
+ const base = `${hubBaseUrl()}/`;
1389
+ return renderTemplate(install_page_default, {
1390
+ version: VERSION,
1391
+ href: bookmarkletHref(base),
1392
+ base,
1393
+ httpPort: config.httpPort
1438
1394
  });
1439
- return server;
1440
1395
  }
1441
- var INSTRUCTIONS;
1442
- var init_mcp_local = __esm({
1443
- "src/mcp-local.ts"() {
1444
- "use strict";
1445
- init_config();
1446
- init_guides();
1447
- init_prompts();
1448
- init_registry();
1449
- 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`;
1396
+
1397
+ // src/install/zip.ts
1398
+ import { Buffer as Buffer2 } from "node:buffer";
1399
+ var CRC_TABLE = (() => {
1400
+ const table = new Uint32Array(256);
1401
+ for (let n = 0; n < 256; n++) {
1402
+ let c = n;
1403
+ for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
1404
+ table[n] = c >>> 0;
1405
+ }
1406
+ return table;
1407
+ })();
1408
+ function crc32(buf) {
1409
+ let crc = 4294967295;
1410
+ for (const byte of buf) crc = CRC_TABLE[(crc ^ byte) & 255] ^ crc >>> 8;
1411
+ return (crc ^ 4294967295) >>> 0;
1412
+ }
1413
+ function buildZip(entries) {
1414
+ const chunks = [];
1415
+ const central = [];
1416
+ let offset = 0;
1417
+ for (const { name, data } of entries) {
1418
+ const nameBuf = Buffer2.from(name, "utf8");
1419
+ const crc = crc32(data);
1420
+ const local = Buffer2.alloc(30);
1421
+ local.writeUInt32LE(67324752, 0);
1422
+ local.writeUInt16LE(20, 4);
1423
+ local.writeUInt16LE(2048, 6);
1424
+ local.writeUInt16LE(0, 8);
1425
+ local.writeUInt16LE(0, 10);
1426
+ local.writeUInt16LE(33, 12);
1427
+ local.writeUInt32LE(crc, 14);
1428
+ local.writeUInt32LE(data.length, 18);
1429
+ local.writeUInt32LE(data.length, 22);
1430
+ local.writeUInt16LE(nameBuf.length, 26);
1431
+ local.writeUInt16LE(0, 28);
1432
+ chunks.push(local, nameBuf, data);
1433
+ const cen = Buffer2.alloc(46);
1434
+ cen.writeUInt32LE(33639248, 0);
1435
+ cen.writeUInt16LE(20, 4);
1436
+ cen.writeUInt16LE(20, 6);
1437
+ cen.writeUInt16LE(2048, 8);
1438
+ cen.writeUInt16LE(0, 10);
1439
+ cen.writeUInt16LE(0, 12);
1440
+ cen.writeUInt16LE(33, 14);
1441
+ cen.writeUInt32LE(crc, 16);
1442
+ cen.writeUInt32LE(data.length, 20);
1443
+ cen.writeUInt32LE(data.length, 24);
1444
+ cen.writeUInt16LE(nameBuf.length, 28);
1445
+ cen.writeUInt16LE(0, 30);
1446
+ cen.writeUInt16LE(0, 32);
1447
+ cen.writeUInt16LE(0, 34);
1448
+ cen.writeUInt16LE(0, 36);
1449
+ cen.writeUInt32LE(0, 38);
1450
+ cen.writeUInt32LE(offset, 42);
1451
+ central.push(Buffer2.concat([cen, nameBuf]));
1452
+ offset += 30 + nameBuf.length + data.length;
1450
1453
  }
1451
- });
1454
+ const centralBuf = Buffer2.concat(central);
1455
+ const eocd = Buffer2.alloc(22);
1456
+ eocd.writeUInt32LE(101010256, 0);
1457
+ eocd.writeUInt16LE(entries.length, 8);
1458
+ eocd.writeUInt16LE(entries.length, 10);
1459
+ eocd.writeUInt32LE(centralBuf.length, 12);
1460
+ eocd.writeUInt32LE(offset, 16);
1461
+ return Buffer2.concat([...chunks, centralBuf, eocd]);
1462
+ }
1452
1463
 
1453
- // src/routes/mcp-http.ts
1454
- import { randomUUID as randomUUID2 } from "node:crypto";
1455
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
1456
- import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
1457
- async function handleMcp(req, res, url) {
1458
- if (url.pathname !== "/mcp") return false;
1459
- const sessionId = req.headers["mcp-session-id"];
1460
- if (req.method === "POST") {
1461
- const raw = await readBody(req);
1462
- let body;
1463
- try {
1464
- body = raw.length ? JSON.parse(raw.toString("utf8")) : void 0;
1465
- } catch {
1466
- sendJson(res, 400, { jsonrpc: "2.0", error: { code: -32700, message: "Parse error" }, id: null });
1467
- return true;
1464
+ // src/install/api.ts
1465
+ var NO_DIST = "plugin dist not found\uFF08\u8FD0\u884C npm run build \u751F\u6210\uFF09";
1466
+ function handleInstall(req, res, url) {
1467
+ return dispatchRoute(INSTALL_ROUTES, req, res, url);
1468
+ }
1469
+ function sendBinary(res, buf, contentType) {
1470
+ res.writeHead(200, { "Content-Type": contentType, "Cache-Control": "no-store" });
1471
+ res.end(buf);
1472
+ }
1473
+ var PLUGIN_FILE_ROUTES = [
1474
+ { path: "/code.js", file: "code.js", type: "text/javascript; charset=utf-8" },
1475
+ { path: "/ui.html", file: "ui.html", type: "text/html; charset=utf-8" }
1476
+ ].map(({ path, file, type }) => ({
1477
+ method: "GET",
1478
+ path,
1479
+ handler: async ({ res }) => {
1480
+ const buf = await readPluginFile(file);
1481
+ if (!buf) {
1482
+ sendJson(res, 404, { error: hasPluginDist() ? "not found" : NO_DIST });
1483
+ return;
1468
1484
  }
1469
- let transport2 = sessionId ? mcpTransports.get(sessionId) : void 0;
1470
- if (!transport2) {
1471
- if (!sessionId && isInitializeRequest(body)) {
1472
- const t = new StreamableHTTPServerTransport({
1473
- sessionIdGenerator: () => randomUUID2(),
1474
- onsessioninitialized: (id) => {
1475
- mcpTransports.set(id, t);
1476
- log(`MCP HTTP session initialized: ${id.slice(0, 8)} (total ${mcpTransports.size})`);
1477
- }
1478
- });
1479
- t.onclose = () => {
1480
- if (t.sessionId) mcpTransports.delete(t.sessionId);
1481
- };
1482
- await createMcpServer().connect(t);
1483
- transport2 = t;
1484
- } else {
1485
- sendJson(res, 400, {
1486
- jsonrpc: "2.0",
1487
- error: { code: -32e3, message: "Bad Request: missing or unknown MCP session" },
1488
- id: null
1485
+ sendBinary(res, buf, type);
1486
+ }
1487
+ }));
1488
+ var INSTALL_ROUTES = [
1489
+ {
1490
+ method: "GET",
1491
+ path: "/",
1492
+ handler: ({ res }) => {
1493
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1494
+ res.end(renderInstallPage());
1495
+ }
1496
+ },
1497
+ {
1498
+ method: "GET",
1499
+ path: "/manifest.json",
1500
+ handler: async ({ res }) => {
1501
+ const buf = await pluginManifest();
1502
+ if (!buf) {
1503
+ sendJson(res, 404, { error: NO_DIST });
1504
+ return;
1505
+ }
1506
+ sendBinary(res, buf, "application/json; charset=utf-8");
1507
+ }
1508
+ },
1509
+ ...PLUGIN_FILE_ROUTES,
1510
+ {
1511
+ method: "GET",
1512
+ path: "/plugin.zip",
1513
+ handler: async ({ res }) => {
1514
+ if (!hasPluginDist()) {
1515
+ sendJson(res, 404, { error: NO_DIST });
1516
+ return;
1517
+ }
1518
+ try {
1519
+ const [manifest, code, ui] = await Promise.all([
1520
+ pluginManifest(),
1521
+ readPluginFile("code.js"),
1522
+ readPluginFile("ui.html")
1523
+ ]);
1524
+ const zip = buildZip([
1525
+ { name: "manifest.json", data: manifest },
1526
+ { name: "code.js", data: code },
1527
+ { name: "ui.html", data: ui }
1528
+ ]);
1529
+ res.writeHead(200, {
1530
+ "Content-Type": "application/zip",
1531
+ "Content-Disposition": 'attachment; filename="batter-pixso-bridge.zip"'
1489
1532
  });
1490
- return true;
1533
+ res.end(zip);
1534
+ } catch {
1535
+ sendJson(res, 500, { error: "zip build failed" });
1491
1536
  }
1492
1537
  }
1493
- await transport2.handleRequest(req, res, body);
1494
- return true;
1495
1538
  }
1496
- const transport = sessionId ? mcpTransports.get(sessionId) : void 0;
1497
- if (!transport) {
1498
- sendJson(res, 400, { error: "missing or unknown MCP session" });
1499
- return true;
1500
- }
1501
- await transport.handleRequest(req, res);
1502
- return true;
1503
- }
1504
- var mcpTransports;
1505
- var init_mcp_http = __esm({
1506
- "src/routes/mcp-http.ts"() {
1507
- "use strict";
1508
- init_config();
1509
- init_mcp_local();
1510
- init_http_utils();
1511
- mcpTransports = /* @__PURE__ */ new Map();
1512
- }
1513
- });
1539
+ ];
1514
1540
 
1515
- // src/routes/plugin-api.ts
1516
- async function handlePluginApi(req, res, url) {
1517
- const path = url.pathname;
1518
- const session = url.searchParams.get("session");
1519
- if (req.method === "POST" && path === "/sessions/register") {
1520
- try {
1521
- const buf = await readBody(req);
1522
- const meta = buf.length ? JSON.parse(buf.toString("utf8")) : {};
1523
- sendJson(res, 201, sessions.register(meta));
1524
- } catch {
1525
- sendJson(res, 400, { error: "invalid json" });
1526
- }
1527
- return true;
1528
- }
1529
- if (req.method === "POST" && path === "/context") {
1530
- if (!session) {
1531
- sendJson(res, 400, { error: "missing session" });
1532
- return true;
1541
+ // src/plugin/api.ts
1542
+ function handlePluginApi(req, res, url) {
1543
+ return dispatchRoute(PLUGIN_ROUTES, req, res, url);
1544
+ }
1545
+ var PLUGIN_ROUTES = [
1546
+ {
1547
+ method: "POST",
1548
+ path: "/sessions/register",
1549
+ handler: async ({ req, res }) => {
1550
+ sendJson(res, 201, sessions.register(await readJsonBody(req)));
1533
1551
  }
1534
- try {
1535
- const buf = await readBody(req);
1536
- const meta = buf.length ? JSON.parse(buf.toString("utf8")) : {};
1552
+ },
1553
+ {
1554
+ method: "POST",
1555
+ path: "/context",
1556
+ handler: async ({ req, res, url }) => {
1557
+ const session = url.searchParams.get("session");
1558
+ if (!session) {
1559
+ sendJson(res, 400, { error: "missing session" });
1560
+ return;
1561
+ }
1562
+ const meta = await readJsonBody(req);
1537
1563
  if (!sessions.updateContext(session, meta)) {
1538
1564
  sendJson(res, 404, { error: "unknown_session" });
1539
- return true;
1565
+ return;
1540
1566
  }
1541
1567
  sendJson(res, 200, { ok: true });
1542
- } catch {
1543
- sendJson(res, 400, { error: "invalid json" });
1544
1568
  }
1545
- return true;
1546
- }
1547
- if (req.method === "GET" && path === "/commands/stream") {
1548
- if (!session || !sessions.setConnected(session, true)) {
1549
- sendJson(res, 404, { error: "unknown_session" });
1550
- return true;
1551
- }
1552
- res.writeHead(200, {
1553
- "Content-Type": "text/event-stream; charset=utf-8",
1554
- "Cache-Control": "no-store",
1555
- Connection: "keep-alive"
1556
- });
1557
- res.write(": connected\n\n");
1558
- const unsubscribe = commandBus.subscribe(session, (command) => {
1559
- res.write(`data: ${JSON.stringify({ command })}
1569
+ },
1570
+ {
1571
+ method: "GET",
1572
+ path: "/commands/stream",
1573
+ handler: ({ req, res, url }) => {
1574
+ const session = url.searchParams.get("session");
1575
+ if (!session || !sessions.setConnected(session, true)) {
1576
+ sendJson(res, 404, { error: "unknown_session" });
1577
+ return;
1578
+ }
1579
+ res.writeHead(200, {
1580
+ "Content-Type": "text/event-stream; charset=utf-8",
1581
+ "Cache-Control": "no-store",
1582
+ Connection: "keep-alive"
1583
+ });
1584
+ res.write(": connected\n\n");
1585
+ const unsubscribe = commandBus.subscribe(session, (command) => {
1586
+ res.write(`data: ${JSON.stringify({ command })}
1560
1587
 
1561
1588
  `);
1562
- });
1563
- const heartbeat = setInterval(() => {
1564
- sessions.touch(session);
1565
- res.write(": ping\n\n");
1566
- }, 15e3);
1567
- req.on("close", () => {
1568
- clearInterval(heartbeat);
1569
- unsubscribe();
1570
- sessions.setConnected(session, false);
1571
- log(`SSE channel closed: session ${session.slice(0, 8)}`);
1572
- });
1573
- return true;
1574
- }
1575
- if (req.method === "GET" && path === "/commands/next") {
1576
- if (!session) {
1577
- sendJson(res, 400, { error: "missing session" });
1578
- return true;
1579
- }
1580
- if (!sessions.touch(session)) {
1581
- sendJson(res, 404, { error: "unknown_session" });
1582
- return true;
1583
- }
1584
- try {
1585
- const command = await commandBus.take(session);
1586
- if (command === null) {
1587
- res.writeHead(204);
1588
- res.end();
1589
- return true;
1590
- }
1591
- sendJson(res, 200, { command });
1592
- } catch {
1593
- sendJson(res, 500, { error: "internal error" });
1589
+ });
1590
+ const heartbeat = setInterval(() => {
1591
+ sessions.touchSeen(session);
1592
+ res.write(": ping\n\n");
1593
+ }, 15e3);
1594
+ req.on("close", () => {
1595
+ clearInterval(heartbeat);
1596
+ unsubscribe();
1597
+ sessions.setConnected(session, false);
1598
+ log(`SSE channel closed: session ${session.slice(0, 8)}`);
1599
+ });
1594
1600
  }
1595
- return true;
1596
- }
1597
- const resultMatch = path.match(/^\/commands\/([\w-]+)\/result$/);
1598
- if (req.method === "POST" && resultMatch) {
1599
- try {
1600
- const buf = await readBody(req);
1601
- const payload = JSON.parse(buf.toString("utf8"));
1602
- const found = commandBus.complete(resultMatch[1], payload.ok, payload.ok ? payload.result : payload.error);
1601
+ },
1602
+ {
1603
+ method: "POST",
1604
+ path: "/commands/:id/result",
1605
+ handler: async ({ req, res, params }) => {
1606
+ const payload = await readJsonBody(req);
1607
+ const result = payload.ok && payload.assets?.length ? substituteAssets(payload.result, payload.assets) : payload.result;
1608
+ const found = commandBus.complete(params["id"], payload.ok, payload.ok ? result : payload.error);
1603
1609
  sendJson(res, found ? 200 : 404, found ? { ok: true } : { error: "unknown command" });
1604
- } catch {
1605
- sendJson(res, 400, { error: "invalid json" });
1606
1610
  }
1607
- return true;
1608
- }
1609
- if (req.method === "POST" && path === "/assets") {
1610
- try {
1611
- const buf = await readBody(req);
1612
- const saved = await saveAsset(buf, url.searchParams.get("format") ?? "PNG");
1613
- sendJson(res, 201, { name: saved.name });
1614
- } catch {
1615
- sendJson(res, 500, { error: "asset save failed" });
1611
+ },
1612
+ {
1613
+ method: "POST",
1614
+ path: "/assets",
1615
+ handler: async ({ req, res, url }) => {
1616
+ try {
1617
+ const name = await saveAsset(await readBody(req), url.searchParams.get("format") ?? "PNG");
1618
+ sendJson(res, 201, { name });
1619
+ } catch {
1620
+ sendJson(res, 500, { error: "asset save failed" });
1621
+ }
1616
1622
  }
1617
- return true;
1618
- }
1619
- return false;
1620
- }
1621
- var init_plugin_api = __esm({
1622
- "src/routes/plugin-api.ts"() {
1623
- "use strict";
1624
- init_assets();
1625
- init_commands();
1626
- init_sessions();
1627
- init_config();
1628
- init_http_utils();
1629
1623
  }
1630
- });
1624
+ ];
1631
1625
 
1632
- // src/server.ts
1633
- import { createServer } from "node:http";
1626
+ // src/http.ts
1627
+ var lastActivity = Date.now();
1634
1628
  function getLastActivity() {
1635
1629
  return lastActivity;
1636
1630
  }
1631
+ var ROUTES = [handleInstall, handleMcp, handlePluginApi];
1637
1632
  function startHttpServer() {
1638
1633
  const server = createServer((req, res) => {
1639
1634
  lastActivity = Date.now();
@@ -1651,23 +1646,19 @@ function startHttpServer() {
1651
1646
  const all = sessions.list();
1652
1647
  sendJson(res, 200, {
1653
1648
  ok: true,
1654
- name: "batter-pixso-mcp",
1649
+ name: HUB_NAME,
1655
1650
  pid: process.pid,
1656
1651
  version: VERSION,
1657
- sessions: all.length,
1658
1652
  pluginsOnline: all.filter((s) => s.online).length
1659
1653
  });
1660
1654
  return;
1661
1655
  }
1662
- (async () => {
1656
+ void (async () => {
1663
1657
  for (const handler of ROUTES) {
1664
1658
  if (await handler(req, res, url)) return;
1665
1659
  }
1666
1660
  sendJson(res, 404, { error: "not found" });
1667
- })().catch((err) => {
1668
- log("route error:", err);
1669
- if (!res.headersSent) sendJson(res, 500, { error: "internal error" });
1670
- });
1661
+ })();
1671
1662
  });
1672
1663
  return new Promise((resolve, reject) => {
1673
1664
  server.once("error", (err) => {
@@ -1683,22 +1674,8 @@ function startHttpServer() {
1683
1674
  });
1684
1675
  });
1685
1676
  }
1686
- var lastActivity, ROUTES;
1687
- var init_server = __esm({
1688
- "src/server.ts"() {
1689
- "use strict";
1690
- init_config();
1691
- init_sessions();
1692
- init_http_utils();
1693
- init_install();
1694
- init_mcp_http();
1695
- init_plugin_api();
1696
- lastActivity = Date.now();
1697
- ROUTES = [handleInstall, handleMcp, handlePluginApi];
1698
- }
1699
- });
1700
1677
 
1701
- // src/lifecycle/hub-mode.ts
1678
+ // src/process/hub.ts
1702
1679
  async function runHub() {
1703
1680
  const server = await startHttpServer();
1704
1681
  if (!server) {
@@ -1706,46 +1683,46 @@ async function runHub() {
1706
1683
  process.exit(0);
1707
1684
  }
1708
1685
  log(`hub ready (idle TTL ${config.hubIdleMs / 6e4}min)`);
1686
+ void cleanOldAssets(config.assetTtlMs);
1687
+ let tick = 0;
1709
1688
  const watchdog = setInterval(() => {
1710
1689
  const idle = Date.now() - getLastActivity();
1711
- if (idle > config.hubIdleMs) {
1712
- log(`\u7A7A\u95F2 ${Math.round(idle / 6e4)} \u5206\u949F\uFF0Chub \u81EA\u52A8\u9000\u51FA`);
1690
+ const anyPluginOnline = sessions.list().some((s) => s.online);
1691
+ if (idle > config.hubIdleMs && !anyPluginOnline) {
1692
+ log(`\u7A7A\u95F2 ${Math.round(idle / 6e4)} \u5206\u949F\u4E14\u65E0\u5728\u7EBF\u63D2\u4EF6\uFF0Chub \u81EA\u52A8\u9000\u51FA`);
1713
1693
  process.exit(0);
1714
1694
  }
1715
- void cleanOldAssets(config.assetTtlMs);
1695
+ if (++tick % 60 === 0) void cleanOldAssets(config.assetTtlMs);
1716
1696
  }, 6e4);
1717
1697
  watchdog.unref();
1718
1698
  }
1719
- var init_hub_mode = __esm({
1720
- "src/lifecycle/hub-mode.ts"() {
1721
- "use strict";
1722
- init_assets();
1723
- init_config();
1724
- init_server();
1725
- }
1726
- });
1727
1699
 
1728
- // src/lifecycle/hub-process.ts
1700
+ // src/process/stdio-proxy.ts
1701
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1702
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
1703
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
1704
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1705
+ import { ResultSchema } from "@modelcontextprotocol/sdk/types.js";
1706
+
1707
+ // src/process/hub-process.ts
1729
1708
  import { spawn } from "node:child_process";
1730
- import { openSync } from "node:fs";
1709
+ import { closeSync, openSync } from "node:fs";
1731
1710
  import { tmpdir as tmpdir2 } from "node:os";
1732
- import { join as join3 } from "node:path";
1711
+ import { join as join4 } from "node:path";
1733
1712
  import { fileURLToPath as fileURLToPath2 } from "node:url";
1734
- function hubBaseUrl() {
1735
- return `http://127.0.0.1:${config.httpPort}`;
1736
- }
1737
1713
  async function probeHub() {
1738
1714
  try {
1739
1715
  const res = await fetch(`${hubBaseUrl()}/health`, { signal: AbortSignal.timeout(1500) });
1740
1716
  if (!res.ok) return "foreign";
1741
1717
  const body = await res.json();
1742
- if (body.ok === true && body.name === "batter-pixso-mcp") return "alive";
1718
+ if (body.ok === true && body.name === HUB_NAME) return "alive";
1743
1719
  if (body.ok === true) return "legacy";
1744
1720
  return "foreign";
1745
1721
  } catch {
1746
1722
  return "down";
1747
1723
  }
1748
1724
  }
1725
+ var PORT_FIX_HINT = `\u7528 netstat -ano | findstr :${config.httpPort} \u67E5\u5230 PID \u540E taskkill /PID <pid> /F \u7ED3\u675F\uFF0C\u518D\u91CD\u8BD5`;
1749
1726
  function hubStateError(state) {
1750
1727
  if (state === "legacy") {
1751
1728
  return new Error(
@@ -1754,6 +1731,7 @@ function hubStateError(state) {
1754
1731
  }
1755
1732
  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`);
1756
1733
  }
1734
+ var HUB_LOG = join4(tmpdir2(), "batter-pixso-mcp-hub.log");
1757
1735
  function spawnHub() {
1758
1736
  const out = openSync(HUB_LOG, "a");
1759
1737
  const entry = process.argv[1] ?? fileURLToPath2(import.meta.url);
@@ -1768,6 +1746,7 @@ function spawnHub() {
1768
1746
  env: { ...process.env }
1769
1747
  });
1770
1748
  child.unref();
1749
+ closeSync(out);
1771
1750
  log(`hub spawned (pid ${child.pid}), log: ${HUB_LOG}`);
1772
1751
  }
1773
1752
  async function waitForHub(timeoutMs = 15e3) {
@@ -1780,22 +1759,8 @@ async function waitForHub(timeoutMs = 15e3) {
1780
1759
  await new Promise((r) => setTimeout(r, 200));
1781
1760
  }
1782
1761
  }
1783
- var PORT_FIX_HINT, HUB_LOG;
1784
- var init_hub_process = __esm({
1785
- "src/lifecycle/hub-process.ts"() {
1786
- "use strict";
1787
- init_config();
1788
- PORT_FIX_HINT = `\u7528 netstat -ano | findstr :${config.httpPort} \u67E5\u5230 PID \u540E taskkill /PID <pid> /F \u7ED3\u675F\uFF0C\u518D\u91CD\u8BD5`;
1789
- HUB_LOG = join3(tmpdir2(), "batter-pixso-mcp-hub.log");
1790
- }
1791
- });
1792
1762
 
1793
- // src/stdio-proxy.ts
1794
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1795
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
1796
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
1797
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1798
- import { ResultSchema } from "@modelcontextprotocol/sdk/types.js";
1763
+ // src/process/stdio-proxy.ts
1799
1764
  async function startStdioProxy() {
1800
1765
  const mcpUrl = `${hubBaseUrl()}/mcp`;
1801
1766
  let upstream = null;
@@ -1808,13 +1773,7 @@ async function startStdioProxy() {
1808
1773
  return client;
1809
1774
  }
1810
1775
  async function ensureUpstream() {
1811
- if (upstream) {
1812
- try {
1813
- await upstream.ping();
1814
- return upstream;
1815
- } catch {
1816
- }
1817
- }
1776
+ if (upstream) return upstream;
1818
1777
  const state = await probeHub();
1819
1778
  if (state === "down") spawnHub();
1820
1779
  if (state === "legacy" || state === "foreign") throw hubStateError(state);
@@ -1828,8 +1787,12 @@ async function startStdioProxy() {
1828
1787
  try {
1829
1788
  return await fn(client);
1830
1789
  } catch (err) {
1831
- if (await probeHub() !== "down") throw err;
1832
- log("hub \u5931\u8054\uFF0C\u91CD\u5EFA\u540E\u91CD\u8BD5\u2026");
1790
+ const alive = await client.ping().then(
1791
+ () => true,
1792
+ () => false
1793
+ );
1794
+ if (alive) throw err;
1795
+ log("hub \u4F1A\u8BDD\u5931\u8054\uFF0C\u91CD\u5EFA\u540E\u91CD\u8BD5\u2026");
1833
1796
  upstream = null;
1834
1797
  const rebuilt = await ensureUpstream();
1835
1798
  return await fn(rebuilt);
@@ -1837,7 +1800,7 @@ async function startStdioProxy() {
1837
1800
  }
1838
1801
  const upstreamClient = await ensureUpstream();
1839
1802
  const server = new Server(
1840
- { name: "batter-pixso-mcp", version: VERSION },
1803
+ { name: HUB_NAME, version: VERSION },
1841
1804
  {
1842
1805
  capabilities: upstreamClient.getServerCapabilities() ?? {}
1843
1806
  }
@@ -1850,19 +1813,8 @@ async function startStdioProxy() {
1850
1813
  await server.connect(new StdioServerTransport());
1851
1814
  log("proxy ready on stdio");
1852
1815
  }
1853
- var init_stdio_proxy = __esm({
1854
- "src/stdio-proxy.ts"() {
1855
- "use strict";
1856
- init_config();
1857
- init_hub_process();
1858
- }
1859
- });
1860
1816
 
1861
- // src/main.ts
1862
- var main_exports = {};
1863
- __export(main_exports, {
1864
- main: () => main
1865
- });
1817
+ // src/process/main.ts
1866
1818
  async function main() {
1867
1819
  if (process.argv.includes("--hub")) {
1868
1820
  await runHub();
@@ -1870,26 +1822,9 @@ async function main() {
1870
1822
  }
1871
1823
  await startStdioProxy();
1872
1824
  }
1873
- var init_main = __esm({
1874
- "src/main.ts"() {
1875
- "use strict";
1876
- init_hub_mode();
1877
- init_stdio_proxy();
1878
- }
1879
- });
1880
1825
 
1881
- // src/index.ts
1882
- var major = Number(process.versions.node.split(".")[0]);
1883
- if (major < 18) {
1884
- console.error(
1885
- `[batter-pixso-mcp] \u9700\u8981 Node \u2265 18\uFF08\u5F53\u524D ${process.version}\uFF09\u3002
1886
- \u7CFB\u7EDF Node \u8FC7\u65E7\u65F6\u53EF\u7528 Volta \u6307\u5B9A\u8FD0\u884C\u65F6\uFF1A
1887
- MCP \u914D\u7F6E: { "command": "volta", "args": ["run", "--node", "24", "npx", "-y", "batter-pixso-mcp"] }`
1888
- );
1889
- process.exit(1);
1890
- }
1891
- var { main: main2 } = await Promise.resolve().then(() => (init_main(), main_exports));
1892
- main2().catch((err) => {
1826
+ // src/process/index.ts
1827
+ main().catch((err) => {
1893
1828
  console.error("[batter-pixso-mcp] fatal:", err);
1894
1829
  process.exit(1);
1895
1830
  });