@pancake-apps/server 0.0.0-snapshot-20260125200133
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/chunk-72LRTGM5.js +169 -0
- package/dist/chunk-72LRTGM5.js.map +1 -0
- package/dist/chunk-CVG3DAN6.js +172 -0
- package/dist/chunk-CVG3DAN6.js.map +1 -0
- package/dist/index.cjs +1519 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +703 -0
- package/dist/index.d.ts +703 -0
- package/dist/index.js +443 -0
- package/dist/index.js.map +1 -0
- package/dist/server-B32JUAYX.js +518 -0
- package/dist/server-B32JUAYX.js.map +1 -0
- package/dist/stdio-ZTUR5PV4.js +104 -0
- package/dist/stdio-ZTUR5PV4.js.map +1 -0
- package/dist/tunnel-AGGQW6IC.js +3 -0
- package/dist/tunnel-AGGQW6IC.js.map +1 -0
- package/package.json +66 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1519 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var express = require('express');
|
|
4
|
+
var fs2 = require('fs/promises');
|
|
5
|
+
var path2 = require('path');
|
|
6
|
+
var index_js = require('@modelcontextprotocol/sdk/server/index.js');
|
|
7
|
+
var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
8
|
+
var types_js = require('@modelcontextprotocol/sdk/types.js');
|
|
9
|
+
var child_process = require('child_process');
|
|
10
|
+
var zodToJsonSchema = require('zod-to-json-schema');
|
|
11
|
+
var fsSync = require('fs');
|
|
12
|
+
var net = require('net');
|
|
13
|
+
|
|
14
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
|
+
|
|
16
|
+
function _interopNamespace(e) {
|
|
17
|
+
if (e && e.__esModule) return e;
|
|
18
|
+
var n = Object.create(null);
|
|
19
|
+
if (e) {
|
|
20
|
+
Object.keys(e).forEach(function (k) {
|
|
21
|
+
if (k !== 'default') {
|
|
22
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
23
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
get: function () { return e[k]; }
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
n.default = e;
|
|
31
|
+
return Object.freeze(n);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
var express__default = /*#__PURE__*/_interopDefault(express);
|
|
35
|
+
var fs2__namespace = /*#__PURE__*/_interopNamespace(fs2);
|
|
36
|
+
var path2__namespace = /*#__PURE__*/_interopNamespace(path2);
|
|
37
|
+
var fsSync__namespace = /*#__PURE__*/_interopNamespace(fsSync);
|
|
38
|
+
var net__namespace = /*#__PURE__*/_interopNamespace(net);
|
|
39
|
+
|
|
40
|
+
var __defProp = Object.defineProperty;
|
|
41
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
42
|
+
var __esm = (fn, res) => function __init() {
|
|
43
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
44
|
+
};
|
|
45
|
+
var __export = (target, all) => {
|
|
46
|
+
for (var name in all)
|
|
47
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// src/utils/metadata.ts
|
|
51
|
+
function mapVisibilityToMcp(visibility) {
|
|
52
|
+
switch (visibility) {
|
|
53
|
+
case "model":
|
|
54
|
+
return ["model"];
|
|
55
|
+
case "app":
|
|
56
|
+
return ["app"];
|
|
57
|
+
case "both":
|
|
58
|
+
default:
|
|
59
|
+
return ["model", "app"];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function mapVisibilityToOpenAI(visibility) {
|
|
63
|
+
switch (visibility) {
|
|
64
|
+
case "model":
|
|
65
|
+
return { "openai/visibility": "public", "openai/widgetAccessible": false };
|
|
66
|
+
case "app":
|
|
67
|
+
return { "openai/visibility": "private", "openai/widgetAccessible": true };
|
|
68
|
+
case "both":
|
|
69
|
+
default:
|
|
70
|
+
return { "openai/visibility": "public", "openai/widgetAccessible": true };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function mapVisibilityToAudience(visibility) {
|
|
74
|
+
switch (visibility) {
|
|
75
|
+
case "model":
|
|
76
|
+
return ["assistant"];
|
|
77
|
+
case "app":
|
|
78
|
+
return ["user"];
|
|
79
|
+
case "both":
|
|
80
|
+
default:
|
|
81
|
+
return ["assistant", "user"];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
var init_metadata = __esm({
|
|
85
|
+
"src/utils/metadata.ts"() {
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// src/utils/csp.ts
|
|
90
|
+
function generateMcpCSPMetadata(csp) {
|
|
91
|
+
const result = {};
|
|
92
|
+
if (csp.connectDomains && csp.connectDomains.length > 0) {
|
|
93
|
+
result.connectDomains = csp.connectDomains;
|
|
94
|
+
}
|
|
95
|
+
if (csp.resourceDomains && csp.resourceDomains.length > 0) {
|
|
96
|
+
result.resourceDomains = csp.resourceDomains;
|
|
97
|
+
}
|
|
98
|
+
if (csp.scriptDomains && csp.scriptDomains.length > 0) {
|
|
99
|
+
result.scriptDomains = csp.scriptDomains;
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
function generateOpenAICSPMetadata(csp) {
|
|
104
|
+
const result = {};
|
|
105
|
+
if (csp.connectDomains && csp.connectDomains.length > 0) {
|
|
106
|
+
result.connect_domains = csp.connectDomains;
|
|
107
|
+
}
|
|
108
|
+
if (csp.resourceDomains && csp.resourceDomains.length > 0) {
|
|
109
|
+
result.resource_domains = csp.resourceDomains;
|
|
110
|
+
}
|
|
111
|
+
if (csp.scriptDomains && csp.scriptDomains.length > 0) {
|
|
112
|
+
result.script_domains = csp.scriptDomains;
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
var init_csp = __esm({
|
|
117
|
+
"src/utils/csp.ts"() {
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// src/adapters/mcp.ts
|
|
122
|
+
var McpAdapter;
|
|
123
|
+
var init_mcp = __esm({
|
|
124
|
+
"src/adapters/mcp.ts"() {
|
|
125
|
+
init_metadata();
|
|
126
|
+
init_csp();
|
|
127
|
+
McpAdapter = class {
|
|
128
|
+
/**
|
|
129
|
+
* Build tool metadata for MCP protocol
|
|
130
|
+
*/
|
|
131
|
+
buildToolMeta(tool, uiUri) {
|
|
132
|
+
const visibility = mapVisibilityToMcp(tool.visibility);
|
|
133
|
+
const uiMeta = {
|
|
134
|
+
visibility,
|
|
135
|
+
...uiUri ? { resourceUri: uiUri } : {}
|
|
136
|
+
};
|
|
137
|
+
const annotations = {
|
|
138
|
+
audience: mapVisibilityToAudience(tool.visibility)
|
|
139
|
+
};
|
|
140
|
+
const meta = {
|
|
141
|
+
ui: uiMeta,
|
|
142
|
+
"ui/visibility": visibility,
|
|
143
|
+
...uiUri ? { "ui/resourceUri": uiUri } : {}
|
|
144
|
+
};
|
|
145
|
+
return {
|
|
146
|
+
annotations,
|
|
147
|
+
_meta: meta
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Build UI resource metadata for MCP protocol
|
|
152
|
+
*/
|
|
153
|
+
buildUIResourceMeta(uiDef) {
|
|
154
|
+
const uiMeta = {};
|
|
155
|
+
if (uiDef.csp) {
|
|
156
|
+
const cspMetadata = generateMcpCSPMetadata(uiDef.csp);
|
|
157
|
+
if (Object.keys(cspMetadata).length > 0) {
|
|
158
|
+
uiMeta.csp = cspMetadata;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (uiDef.prefersBorder !== void 0) {
|
|
162
|
+
uiMeta.prefersBorder = uiDef.prefersBorder;
|
|
163
|
+
}
|
|
164
|
+
if (uiDef.autoResize !== void 0) {
|
|
165
|
+
uiMeta.autoResize = uiDef.autoResize;
|
|
166
|
+
}
|
|
167
|
+
if (uiDef.domain) {
|
|
168
|
+
uiMeta.domain = uiDef.domain;
|
|
169
|
+
}
|
|
170
|
+
const result = {
|
|
171
|
+
mimeType: "text/html;profile=mcp-app"
|
|
172
|
+
};
|
|
173
|
+
if (Object.keys(uiMeta).length > 0) {
|
|
174
|
+
result._meta = { ui: uiMeta };
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// src/adapters/openai.ts
|
|
183
|
+
var OpenAIAdapter;
|
|
184
|
+
var init_openai = __esm({
|
|
185
|
+
"src/adapters/openai.ts"() {
|
|
186
|
+
init_metadata();
|
|
187
|
+
init_csp();
|
|
188
|
+
OpenAIAdapter = class {
|
|
189
|
+
/**
|
|
190
|
+
* Build tool metadata for OpenAI protocol
|
|
191
|
+
*/
|
|
192
|
+
buildToolMeta(tool, uiUri) {
|
|
193
|
+
const meta = {};
|
|
194
|
+
const visibilitySettings = mapVisibilityToOpenAI(tool.visibility);
|
|
195
|
+
Object.assign(meta, visibilitySettings);
|
|
196
|
+
if (uiUri) {
|
|
197
|
+
meta["openai/outputTemplate"] = uiUri;
|
|
198
|
+
}
|
|
199
|
+
const annotations = {
|
|
200
|
+
audience: mapVisibilityToAudience(tool.visibility)
|
|
201
|
+
};
|
|
202
|
+
return {
|
|
203
|
+
annotations,
|
|
204
|
+
_meta: Object.keys(meta).length > 0 ? meta : void 0
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Build UI resource metadata for OpenAI protocol
|
|
209
|
+
*/
|
|
210
|
+
buildUIResourceMeta(uiDef) {
|
|
211
|
+
const meta = {};
|
|
212
|
+
if (uiDef.csp) {
|
|
213
|
+
const cspMetadata = generateOpenAICSPMetadata(uiDef.csp);
|
|
214
|
+
if (Object.keys(cspMetadata).length > 0) {
|
|
215
|
+
meta["openai/widgetCSP"] = cspMetadata;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (uiDef.prefersBorder !== void 0) {
|
|
219
|
+
meta["openai/widgetPrefersBorder"] = uiDef.prefersBorder;
|
|
220
|
+
}
|
|
221
|
+
if (uiDef.domain) {
|
|
222
|
+
meta["openai/widgetDomain"] = uiDef.domain;
|
|
223
|
+
}
|
|
224
|
+
const result = {
|
|
225
|
+
mimeType: "text/html+skybridge"
|
|
226
|
+
};
|
|
227
|
+
if (Object.keys(meta).length > 0) {
|
|
228
|
+
result._meta = meta;
|
|
229
|
+
}
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// src/adapters/index.ts
|
|
237
|
+
function createAdapter(protocol) {
|
|
238
|
+
return protocol === "openai" ? new OpenAIAdapter() : new McpAdapter();
|
|
239
|
+
}
|
|
240
|
+
var init_adapters = __esm({
|
|
241
|
+
"src/adapters/index.ts"() {
|
|
242
|
+
init_mcp();
|
|
243
|
+
init_openai();
|
|
244
|
+
init_mcp();
|
|
245
|
+
init_openai();
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// src/utils/detect-protocol.ts
|
|
250
|
+
function detectProtocol(req, defaultProtocol = "mcp") {
|
|
251
|
+
const headers = req.headers;
|
|
252
|
+
const accept = getHeader(headers, "accept");
|
|
253
|
+
if (accept && accept.includes("text/html+skybridge")) {
|
|
254
|
+
return "openai";
|
|
255
|
+
}
|
|
256
|
+
const openaiHeader = getHeader(headers, "x-openai-client");
|
|
257
|
+
if (openaiHeader) {
|
|
258
|
+
return "openai";
|
|
259
|
+
}
|
|
260
|
+
const userAgent = getHeader(headers, "user-agent");
|
|
261
|
+
if (userAgent && (userAgent.includes("ChatGPT") || userAgent.includes("OpenAI"))) {
|
|
262
|
+
return "openai";
|
|
263
|
+
}
|
|
264
|
+
return defaultProtocol;
|
|
265
|
+
}
|
|
266
|
+
function getHeader(headers, name) {
|
|
267
|
+
const value = headers[name] || headers[name.toLowerCase()];
|
|
268
|
+
if (Array.isArray(value)) {
|
|
269
|
+
return value[0];
|
|
270
|
+
}
|
|
271
|
+
return value;
|
|
272
|
+
}
|
|
273
|
+
var init_detect_protocol = __esm({
|
|
274
|
+
"src/utils/detect-protocol.ts"() {
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// src/server/mcp.ts
|
|
279
|
+
function createMcpHandler(config) {
|
|
280
|
+
return async (req, res) => {
|
|
281
|
+
const request = req.body;
|
|
282
|
+
if (request.jsonrpc !== "2.0") {
|
|
283
|
+
res.status(400).json({
|
|
284
|
+
jsonrpc: "2.0",
|
|
285
|
+
id: request.id ?? null,
|
|
286
|
+
error: { code: -32600, message: "Invalid Request: Not JSON-RPC 2.0" }
|
|
287
|
+
});
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
const protocol = detectProtocol(req, config.defaultProtocol);
|
|
292
|
+
const adapter = createAdapter(protocol);
|
|
293
|
+
const result = await handleMethod(config, request.method, request.params ?? {}, adapter);
|
|
294
|
+
const response = {
|
|
295
|
+
jsonrpc: "2.0",
|
|
296
|
+
id: request.id,
|
|
297
|
+
result
|
|
298
|
+
};
|
|
299
|
+
res.json(response);
|
|
300
|
+
} catch (error) {
|
|
301
|
+
const response = {
|
|
302
|
+
jsonrpc: "2.0",
|
|
303
|
+
id: request.id,
|
|
304
|
+
error: {
|
|
305
|
+
code: -32603,
|
|
306
|
+
message: error instanceof Error ? error.message : "Internal error",
|
|
307
|
+
data: error instanceof Error ? { stack: error.stack } : void 0
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
res.json(response);
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
async function handleMethod(config, method, params, adapter) {
|
|
315
|
+
switch (method) {
|
|
316
|
+
case "initialize":
|
|
317
|
+
return {
|
|
318
|
+
protocolVersion: "2025-11-21",
|
|
319
|
+
capabilities: {
|
|
320
|
+
tools: {},
|
|
321
|
+
resources: {}
|
|
322
|
+
},
|
|
323
|
+
serverInfo: {
|
|
324
|
+
name: config.name,
|
|
325
|
+
version: config.version
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
case "tools/list":
|
|
329
|
+
return {
|
|
330
|
+
tools: Array.from(config.tools.values()).filter((tool) => tool.visibility !== "app").map((tool) => {
|
|
331
|
+
const uiUri = tool.ui ? getUIResourceUri(tool.name, tool.category) : void 0;
|
|
332
|
+
const meta = adapter.buildToolMeta(tool, uiUri);
|
|
333
|
+
return {
|
|
334
|
+
name: tool.name,
|
|
335
|
+
description: tool.description,
|
|
336
|
+
inputSchema: tool.inputSchema,
|
|
337
|
+
annotations: meta.annotations,
|
|
338
|
+
_meta: tool.ui ? meta._meta : void 0
|
|
339
|
+
};
|
|
340
|
+
})
|
|
341
|
+
};
|
|
342
|
+
case "tools/call": {
|
|
343
|
+
const toolName = params["name"];
|
|
344
|
+
const args = params["arguments"] ?? {};
|
|
345
|
+
const result = await config.executeTool(toolName, args);
|
|
346
|
+
return {
|
|
347
|
+
content: [
|
|
348
|
+
{
|
|
349
|
+
type: "text",
|
|
350
|
+
text: JSON.stringify(result)
|
|
351
|
+
}
|
|
352
|
+
],
|
|
353
|
+
structuredContent: result
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
case "resources/list":
|
|
357
|
+
return {
|
|
358
|
+
resources: Array.from(config.uiResources.values()).map((resource) => {
|
|
359
|
+
const meta = adapter.buildUIResourceMeta(resource.definition);
|
|
360
|
+
return {
|
|
361
|
+
uri: resource.uri,
|
|
362
|
+
name: resource.name,
|
|
363
|
+
mimeType: meta.mimeType,
|
|
364
|
+
_meta: meta._meta
|
|
365
|
+
};
|
|
366
|
+
})
|
|
367
|
+
};
|
|
368
|
+
case "resources/read": {
|
|
369
|
+
const uri = params["uri"];
|
|
370
|
+
const resource = config.uiResources.get(uri);
|
|
371
|
+
if (!resource) {
|
|
372
|
+
throw new Error(`Resource not found: ${uri}`);
|
|
373
|
+
}
|
|
374
|
+
let htmlContent;
|
|
375
|
+
const htmlPath = resource.definition.html;
|
|
376
|
+
if (htmlPath.trim().startsWith("<")) {
|
|
377
|
+
htmlContent = htmlPath;
|
|
378
|
+
} else {
|
|
379
|
+
const fsPromises = await import('fs/promises');
|
|
380
|
+
const pathModule = await import('path');
|
|
381
|
+
const fullPath = pathModule.resolve(process.cwd(), htmlPath);
|
|
382
|
+
htmlContent = await fsPromises.readFile(fullPath, "utf-8");
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
contents: [
|
|
386
|
+
{
|
|
387
|
+
uri,
|
|
388
|
+
mimeType: "text/html",
|
|
389
|
+
text: htmlContent
|
|
390
|
+
}
|
|
391
|
+
]
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
case "notifications/initialized":
|
|
395
|
+
case "ping":
|
|
396
|
+
return {};
|
|
397
|
+
default:
|
|
398
|
+
throw new Error(`Unknown method: ${method}`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
function getUIResourceUri(toolName, category) {
|
|
402
|
+
const name = toolName.replace(/^(view|action|data|tool):/, "");
|
|
403
|
+
return `pancake://ui/${category}/${name}`;
|
|
404
|
+
}
|
|
405
|
+
var init_mcp2 = __esm({
|
|
406
|
+
"src/server/mcp.ts"() {
|
|
407
|
+
init_adapters();
|
|
408
|
+
init_detect_protocol();
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// src/server/dev-proxy.ts
|
|
413
|
+
function isDevelopment() {
|
|
414
|
+
return process.env["NODE_ENV"] !== "production";
|
|
415
|
+
}
|
|
416
|
+
function createViteProxy(config) {
|
|
417
|
+
const vitePort = config.vitePort ?? 5173;
|
|
418
|
+
const viteUrl = `http://localhost:${vitePort}`;
|
|
419
|
+
const vitePaths = [
|
|
420
|
+
"/@vite/",
|
|
421
|
+
"/@react-refresh",
|
|
422
|
+
"/assets/",
|
|
423
|
+
"/src/",
|
|
424
|
+
"/__vite_ping",
|
|
425
|
+
"/@fs/",
|
|
426
|
+
"/node_modules/.vite/",
|
|
427
|
+
"/node_modules/.pnpm/"
|
|
428
|
+
// pnpm store - Vite client imports from here
|
|
429
|
+
];
|
|
430
|
+
return async (req, res, next) => {
|
|
431
|
+
const shouldProxy = vitePaths.some((p) => req.path.startsWith(p));
|
|
432
|
+
if (!shouldProxy) {
|
|
433
|
+
next();
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
try {
|
|
437
|
+
const targetUrl = `${viteUrl}${req.url}`;
|
|
438
|
+
const response = await fetch(targetUrl, {
|
|
439
|
+
method: req.method,
|
|
440
|
+
headers: {
|
|
441
|
+
...Object.fromEntries(
|
|
442
|
+
Object.entries(req.headers).filter(
|
|
443
|
+
([key]) => !["host", "connection"].includes(key.toLowerCase())
|
|
444
|
+
)
|
|
445
|
+
)
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
response.headers.forEach((value, key) => {
|
|
449
|
+
if (key.toLowerCase() !== "transfer-encoding") {
|
|
450
|
+
res.setHeader(key, value);
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
res.status(response.status);
|
|
454
|
+
const body = await response.arrayBuffer();
|
|
455
|
+
res.end(Buffer.from(body));
|
|
456
|
+
} catch (error) {
|
|
457
|
+
console.error(`[Pancake] Vite proxy failed for ${req.path}: ${error.message}`);
|
|
458
|
+
next();
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function generateHMRTemplate(viewName) {
|
|
463
|
+
return `<!DOCTYPE html>
|
|
464
|
+
<html lang="en">
|
|
465
|
+
<head>
|
|
466
|
+
<meta charset="UTF-8" />
|
|
467
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
468
|
+
<title>${viewName}</title>
|
|
469
|
+
<!-- All Vite resources go through the proxy using relative URLs -->
|
|
470
|
+
<script type="module">
|
|
471
|
+
import { injectIntoGlobalHook } from "/@react-refresh";
|
|
472
|
+
injectIntoGlobalHook(window);
|
|
473
|
+
window.$RefreshReg$ = () => {};
|
|
474
|
+
window.$RefreshSig$ = () => (type) => type;
|
|
475
|
+
window.__vite_plugin_react_preamble_installed__ = true;
|
|
476
|
+
</script>
|
|
477
|
+
<script type="module" src="/@vite/client"></script>
|
|
478
|
+
</head>
|
|
479
|
+
<body>
|
|
480
|
+
<div id="root"></div>
|
|
481
|
+
<script type="module">
|
|
482
|
+
// Initialize Pancake client before loading the view
|
|
483
|
+
import('/src/pancake-init.ts')
|
|
484
|
+
.then(async (mod) => {
|
|
485
|
+
if (mod.init) await mod.init();
|
|
486
|
+
})
|
|
487
|
+
.catch(() => {
|
|
488
|
+
// pancake-init.ts might not exist, that's ok
|
|
489
|
+
})
|
|
490
|
+
.finally(() => {
|
|
491
|
+
// Try each path and log which one fails and why
|
|
492
|
+
const paths = [
|
|
493
|
+
'/src/views/${viewName}/index.tsx',
|
|
494
|
+
'/src/views/${viewName}.tsx',
|
|
495
|
+
'/src/views/${viewName}/index.ts',
|
|
496
|
+
'/src/views/${viewName}.ts'
|
|
497
|
+
];
|
|
498
|
+
|
|
499
|
+
async function tryImport(index) {
|
|
500
|
+
if (index >= paths.length) {
|
|
501
|
+
document.body.innerHTML = '<pre style="color: red;">Failed to load view: No valid module found</pre>';
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
const path = paths[index];
|
|
505
|
+
try {
|
|
506
|
+
console.log('[Pancake] Trying to import:', path);
|
|
507
|
+
await import(path);
|
|
508
|
+
console.log('[Pancake] Successfully imported:', path);
|
|
509
|
+
} catch (err) {
|
|
510
|
+
console.error('[Pancake] Failed to import ' + path + ':', err);
|
|
511
|
+
// If this was the first path (the expected one), show the error
|
|
512
|
+
if (index === 0) {
|
|
513
|
+
document.body.innerHTML = '<pre style="color: red;">Failed to load view ' + path + ':\\n\\n' + err.message + '\\n\\n' + (err.stack || '') + '</pre>';
|
|
514
|
+
} else {
|
|
515
|
+
tryImport(index + 1);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
tryImport(0);
|
|
521
|
+
});
|
|
522
|
+
</script>
|
|
523
|
+
</body>
|
|
524
|
+
</html>`;
|
|
525
|
+
}
|
|
526
|
+
var init_dev_proxy = __esm({
|
|
527
|
+
"src/server/dev-proxy.ts"() {
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
function createUIRoutes(uiResources, devServerConfig) {
|
|
531
|
+
const router = express.Router();
|
|
532
|
+
router.get("/:category/:name", async (req, res) => {
|
|
533
|
+
const { category, name } = req.params;
|
|
534
|
+
const uri = `pancake://ui/${category}/${name}`;
|
|
535
|
+
const resource = uiResources.get(uri);
|
|
536
|
+
if (!resource) {
|
|
537
|
+
res.status(404).json({ error: `UI not found: ${category}/${name}` });
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
try {
|
|
541
|
+
const html = await getUIHtml(resource.definition, name, devServerConfig);
|
|
542
|
+
res.type("html").send(html);
|
|
543
|
+
} catch (error) {
|
|
544
|
+
res.status(500).json({
|
|
545
|
+
error: "Failed to load UI",
|
|
546
|
+
details: error instanceof Error ? error.message : String(error)
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
router.get("/:name", async (req, res) => {
|
|
551
|
+
const { name } = req.params;
|
|
552
|
+
for (const category of ["view", "action", "tool"]) {
|
|
553
|
+
const uri = `pancake://ui/${category}/${name}`;
|
|
554
|
+
const resource = uiResources.get(uri);
|
|
555
|
+
if (resource) {
|
|
556
|
+
try {
|
|
557
|
+
const html = await getUIHtml(resource.definition, name, devServerConfig);
|
|
558
|
+
res.type("html").send(html);
|
|
559
|
+
return;
|
|
560
|
+
} catch (error) {
|
|
561
|
+
res.status(500).json({
|
|
562
|
+
error: "Failed to load UI",
|
|
563
|
+
details: error instanceof Error ? error.message : String(error)
|
|
564
|
+
});
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
res.status(404).json({ error: `UI not found: ${name}` });
|
|
570
|
+
});
|
|
571
|
+
return router;
|
|
572
|
+
}
|
|
573
|
+
async function getUIHtml(ui, viewName, devServerConfig) {
|
|
574
|
+
if (isDevelopment() && devServerConfig) {
|
|
575
|
+
return generateHMRTemplate(viewName);
|
|
576
|
+
}
|
|
577
|
+
if (ui.html.trim().startsWith("<")) {
|
|
578
|
+
return ui.html;
|
|
579
|
+
}
|
|
580
|
+
const htmlPath = path2__namespace.resolve(process.cwd(), ui.html);
|
|
581
|
+
const content = await fs2__namespace.readFile(htmlPath, "utf-8");
|
|
582
|
+
return content;
|
|
583
|
+
}
|
|
584
|
+
var init_routes = __esm({
|
|
585
|
+
"src/server/routes.ts"() {
|
|
586
|
+
init_dev_proxy();
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
// src/server/stdio.ts
|
|
591
|
+
var stdio_exports = {};
|
|
592
|
+
__export(stdio_exports, {
|
|
593
|
+
startStdioServer: () => startStdioServer
|
|
594
|
+
});
|
|
595
|
+
async function startStdioServer(config) {
|
|
596
|
+
const adapter = createAdapter(config.protocol ?? "mcp");
|
|
597
|
+
const server = new index_js.Server(
|
|
598
|
+
{
|
|
599
|
+
name: config.name,
|
|
600
|
+
version: config.version
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
capabilities: {
|
|
604
|
+
tools: {},
|
|
605
|
+
resources: {}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
);
|
|
609
|
+
server.setRequestHandler(
|
|
610
|
+
types_js.ListToolsRequestSchema,
|
|
611
|
+
async () => {
|
|
612
|
+
return {
|
|
613
|
+
tools: Array.from(config.tools.values()).map((tool) => {
|
|
614
|
+
const uiUri = tool.ui ? getUIResourceUri2(tool.name, tool.category) : void 0;
|
|
615
|
+
const meta = adapter.buildToolMeta(tool, uiUri);
|
|
616
|
+
return {
|
|
617
|
+
name: tool.name,
|
|
618
|
+
description: tool.description,
|
|
619
|
+
inputSchema: tool.inputSchema,
|
|
620
|
+
annotations: meta.annotations,
|
|
621
|
+
_meta: tool.ui ? meta._meta : void 0
|
|
622
|
+
};
|
|
623
|
+
})
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
);
|
|
627
|
+
server.setRequestHandler(
|
|
628
|
+
types_js.CallToolRequestSchema,
|
|
629
|
+
async (request) => {
|
|
630
|
+
const { name, arguments: args = {} } = request.params;
|
|
631
|
+
const result = await config.executeTool(name, args);
|
|
632
|
+
return {
|
|
633
|
+
content: [
|
|
634
|
+
{
|
|
635
|
+
type: "text",
|
|
636
|
+
text: JSON.stringify(result)
|
|
637
|
+
}
|
|
638
|
+
],
|
|
639
|
+
structuredContent: result
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
);
|
|
643
|
+
server.setRequestHandler(
|
|
644
|
+
types_js.ListResourcesRequestSchema,
|
|
645
|
+
async () => {
|
|
646
|
+
return {
|
|
647
|
+
resources: Array.from(config.uiResources.values()).map((resource) => {
|
|
648
|
+
const meta = adapter.buildUIResourceMeta(resource.definition);
|
|
649
|
+
return {
|
|
650
|
+
uri: resource.uri,
|
|
651
|
+
name: resource.name,
|
|
652
|
+
mimeType: meta.mimeType,
|
|
653
|
+
_meta: meta._meta
|
|
654
|
+
};
|
|
655
|
+
})
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
);
|
|
659
|
+
server.setRequestHandler(
|
|
660
|
+
types_js.ReadResourceRequestSchema,
|
|
661
|
+
async (request) => {
|
|
662
|
+
const { uri } = request.params;
|
|
663
|
+
const resource = config.uiResources.get(uri);
|
|
664
|
+
if (!resource) {
|
|
665
|
+
throw new Error(`Resource not found: ${uri}`);
|
|
666
|
+
}
|
|
667
|
+
return {
|
|
668
|
+
contents: [
|
|
669
|
+
{
|
|
670
|
+
uri,
|
|
671
|
+
mimeType: "text/html",
|
|
672
|
+
text: resource.definition.html
|
|
673
|
+
}
|
|
674
|
+
]
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
);
|
|
678
|
+
const transport = new stdio_js.StdioServerTransport();
|
|
679
|
+
await server.connect(transport);
|
|
680
|
+
return {
|
|
681
|
+
close: async () => {
|
|
682
|
+
await server.close();
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
function getUIResourceUri2(toolName, category) {
|
|
687
|
+
const name = toolName.replace(/^(view|action|data|tool):/, "");
|
|
688
|
+
return `pancake://ui/${category}/${name}`;
|
|
689
|
+
}
|
|
690
|
+
var init_stdio = __esm({
|
|
691
|
+
"src/server/stdio.ts"() {
|
|
692
|
+
init_adapters();
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
// src/tunnel/index.ts
|
|
697
|
+
var tunnel_exports = {};
|
|
698
|
+
__export(tunnel_exports, {
|
|
699
|
+
createTunnel: () => createTunnel,
|
|
700
|
+
isTunnelProviderAvailable: () => isTunnelProviderAvailable
|
|
701
|
+
});
|
|
702
|
+
async function createTunnel(config) {
|
|
703
|
+
if (config.provider === "cloudflare") {
|
|
704
|
+
return createCloudflareTunnel(config.port);
|
|
705
|
+
} else if (config.provider === "ngrok") {
|
|
706
|
+
return createNgrokTunnel(config.port, config.authtoken);
|
|
707
|
+
} else {
|
|
708
|
+
throw new Error(`Unknown tunnel provider: ${config.provider}`);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
async function createCloudflareTunnel(port) {
|
|
712
|
+
let state = "idle";
|
|
713
|
+
let process2 = null;
|
|
714
|
+
return new Promise((resolve3, reject) => {
|
|
715
|
+
state = "starting";
|
|
716
|
+
process2 = child_process.spawn("cloudflared", ["tunnel", "--url", `http://localhost:${port}`], {
|
|
717
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
718
|
+
});
|
|
719
|
+
let resolved = false;
|
|
720
|
+
const urlPattern = /https:\/\/[a-zA-Z0-9-]+\.trycloudflare\.com/;
|
|
721
|
+
const handleOutput = (data) => {
|
|
722
|
+
const output = data.toString();
|
|
723
|
+
const match = output.match(urlPattern);
|
|
724
|
+
if (match && !resolved) {
|
|
725
|
+
resolved = true;
|
|
726
|
+
state = "active";
|
|
727
|
+
const tunnelUrl = match[0];
|
|
728
|
+
resolve3({
|
|
729
|
+
url: tunnelUrl,
|
|
730
|
+
close: async () => {
|
|
731
|
+
if (process2 && state === "active") {
|
|
732
|
+
state = "terminated";
|
|
733
|
+
process2.kill("SIGTERM");
|
|
734
|
+
process2 = null;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
process2.stdout?.on("data", handleOutput);
|
|
741
|
+
process2.stderr?.on("data", handleOutput);
|
|
742
|
+
process2.on("error", (error) => {
|
|
743
|
+
state = "terminated";
|
|
744
|
+
if (!resolved) {
|
|
745
|
+
if (error.code === "ENOENT") {
|
|
746
|
+
reject(new Error(
|
|
747
|
+
"cloudflared is not installed. Install it with:\n macOS: brew install cloudflared\n Linux: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation/"
|
|
748
|
+
));
|
|
749
|
+
} else {
|
|
750
|
+
reject(new Error(`Failed to start cloudflared: ${error.message}`));
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
process2.on("exit", (code) => {
|
|
755
|
+
state = "terminated";
|
|
756
|
+
if (!resolved) {
|
|
757
|
+
reject(new Error(`cloudflared exited with code ${code} before providing a tunnel URL`));
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
setTimeout(() => {
|
|
761
|
+
if (!resolved) {
|
|
762
|
+
state = "terminated";
|
|
763
|
+
process2?.kill("SIGTERM");
|
|
764
|
+
reject(new Error("Timed out waiting for cloudflared to provide a tunnel URL"));
|
|
765
|
+
}
|
|
766
|
+
}, 3e4);
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
async function createNgrokTunnel(port, authtoken) {
|
|
770
|
+
let state = "idle";
|
|
771
|
+
let process2 = null;
|
|
772
|
+
return new Promise((resolve3, reject) => {
|
|
773
|
+
state = "starting";
|
|
774
|
+
const args = ["http", String(port)];
|
|
775
|
+
if (authtoken) {
|
|
776
|
+
args.push("--authtoken", authtoken);
|
|
777
|
+
}
|
|
778
|
+
args.push("--log", "stdout", "--log-format", "json");
|
|
779
|
+
process2 = child_process.spawn("ngrok", args, {
|
|
780
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
781
|
+
});
|
|
782
|
+
let resolved = false;
|
|
783
|
+
const urlPattern = /https:\/\/[a-zA-Z0-9-]+\.ngrok(-free)?\.app/;
|
|
784
|
+
const handleOutput = (data) => {
|
|
785
|
+
const output = data.toString();
|
|
786
|
+
const lines = output.split("\n").filter(Boolean);
|
|
787
|
+
for (const line of lines) {
|
|
788
|
+
try {
|
|
789
|
+
const json = JSON.parse(line);
|
|
790
|
+
if (json.url && !resolved) {
|
|
791
|
+
resolved = true;
|
|
792
|
+
state = "active";
|
|
793
|
+
resolve3({
|
|
794
|
+
url: json.url,
|
|
795
|
+
close: async () => {
|
|
796
|
+
if (process2 && state === "active") {
|
|
797
|
+
state = "terminated";
|
|
798
|
+
process2.kill("SIGTERM");
|
|
799
|
+
process2 = null;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
} catch {
|
|
806
|
+
const match = line.match(urlPattern);
|
|
807
|
+
if (match && !resolved) {
|
|
808
|
+
resolved = true;
|
|
809
|
+
state = "active";
|
|
810
|
+
resolve3({
|
|
811
|
+
url: match[0],
|
|
812
|
+
close: async () => {
|
|
813
|
+
if (process2 && state === "active") {
|
|
814
|
+
state = "terminated";
|
|
815
|
+
process2.kill("SIGTERM");
|
|
816
|
+
process2 = null;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
});
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
process2.stdout?.on("data", handleOutput);
|
|
826
|
+
process2.stderr?.on("data", handleOutput);
|
|
827
|
+
process2.on("error", (error) => {
|
|
828
|
+
state = "terminated";
|
|
829
|
+
if (!resolved) {
|
|
830
|
+
if (error.code === "ENOENT") {
|
|
831
|
+
reject(new Error(
|
|
832
|
+
"ngrok is not installed. Install it with:\n macOS: brew install ngrok\n Linux/Windows: https://ngrok.com/download"
|
|
833
|
+
));
|
|
834
|
+
} else {
|
|
835
|
+
reject(new Error(`Failed to start ngrok: ${error.message}`));
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
process2.on("exit", (code) => {
|
|
840
|
+
state = "terminated";
|
|
841
|
+
if (!resolved) {
|
|
842
|
+
reject(new Error(`ngrok exited with code ${code} before providing a tunnel URL`));
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
setTimeout(() => {
|
|
846
|
+
if (!resolved) {
|
|
847
|
+
state = "terminated";
|
|
848
|
+
process2?.kill("SIGTERM");
|
|
849
|
+
reject(new Error("Timed out waiting for ngrok to provide a tunnel URL"));
|
|
850
|
+
}
|
|
851
|
+
}, 3e4);
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
async function isTunnelProviderAvailable(provider) {
|
|
855
|
+
const command = provider === "cloudflare" ? "cloudflared" : "ngrok";
|
|
856
|
+
return new Promise((resolve3) => {
|
|
857
|
+
const process2 = child_process.spawn(command, ["--version"], {
|
|
858
|
+
stdio: "ignore"
|
|
859
|
+
});
|
|
860
|
+
process2.on("error", () => resolve3(false));
|
|
861
|
+
process2.on("exit", (code) => resolve3(code === 0));
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
var init_tunnel = __esm({
|
|
865
|
+
"src/tunnel/index.ts"() {
|
|
866
|
+
}
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
// src/server/index.ts
|
|
870
|
+
var server_exports = {};
|
|
871
|
+
__export(server_exports, {
|
|
872
|
+
createServer: () => createServer
|
|
873
|
+
});
|
|
874
|
+
function generateRequestId() {
|
|
875
|
+
return `req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;
|
|
876
|
+
}
|
|
877
|
+
async function createServer(config) {
|
|
878
|
+
const app = express__default.default();
|
|
879
|
+
app.use(express__default.default.json());
|
|
880
|
+
if (config.config?.cors) {
|
|
881
|
+
const corsConfig = config.config.cors;
|
|
882
|
+
app.use((req, res, next) => {
|
|
883
|
+
const origin = corsConfig.origin;
|
|
884
|
+
if (origin === true) {
|
|
885
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
886
|
+
} else if (typeof origin === "string") {
|
|
887
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
888
|
+
} else if (Array.isArray(origin)) {
|
|
889
|
+
const requestOrigin = req.headers.origin;
|
|
890
|
+
if (requestOrigin && origin.includes(requestOrigin)) {
|
|
891
|
+
res.setHeader("Access-Control-Allow-Origin", requestOrigin);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
if (corsConfig.credentials) {
|
|
895
|
+
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
896
|
+
}
|
|
897
|
+
if (corsConfig.methods) {
|
|
898
|
+
res.setHeader("Access-Control-Allow-Methods", corsConfig.methods.join(", "));
|
|
899
|
+
}
|
|
900
|
+
if (corsConfig.allowedHeaders) {
|
|
901
|
+
res.setHeader("Access-Control-Allow-Headers", corsConfig.allowedHeaders.join(", "));
|
|
902
|
+
}
|
|
903
|
+
if (req.method === "OPTIONS") {
|
|
904
|
+
res.status(204).end();
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
next();
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
if (isDevelopment() && config.config?.devServer) {
|
|
911
|
+
app.use(createViteProxy(config.config.devServer));
|
|
912
|
+
}
|
|
913
|
+
const uiRoutes = createUIRoutes(config.uiResources, config.config?.devServer);
|
|
914
|
+
app.use("/ui", uiRoutes);
|
|
915
|
+
const executeTool = async (toolName, input, metadata = {}) => {
|
|
916
|
+
const tool = config.tools.get(toolName);
|
|
917
|
+
if (!tool) {
|
|
918
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
919
|
+
}
|
|
920
|
+
const ctx = {
|
|
921
|
+
requestId: generateRequestId(),
|
|
922
|
+
toolName,
|
|
923
|
+
metadata
|
|
924
|
+
};
|
|
925
|
+
const startTime = Date.now();
|
|
926
|
+
try {
|
|
927
|
+
const result = await config.middlewareChain.execute(
|
|
928
|
+
{ toolName, input, metadata },
|
|
929
|
+
() => tool.handler(input, ctx)
|
|
930
|
+
);
|
|
931
|
+
config.events.emit("tool:success", {
|
|
932
|
+
toolName,
|
|
933
|
+
result,
|
|
934
|
+
durationMs: Date.now() - startTime
|
|
935
|
+
});
|
|
936
|
+
return result;
|
|
937
|
+
} catch (error) {
|
|
938
|
+
config.events.emit("tool:error", {
|
|
939
|
+
toolName,
|
|
940
|
+
error,
|
|
941
|
+
durationMs: Date.now() - startTime
|
|
942
|
+
});
|
|
943
|
+
throw error;
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
const mcpRoute = config.config?.serverRoute ?? "/mcp";
|
|
947
|
+
const mcpHandler = createMcpHandler({
|
|
948
|
+
name: config.name,
|
|
949
|
+
version: config.version,
|
|
950
|
+
tools: config.tools,
|
|
951
|
+
uiResources: config.uiResources,
|
|
952
|
+
executeTool,
|
|
953
|
+
defaultProtocol: config.config?.protocol
|
|
954
|
+
});
|
|
955
|
+
app.post(mcpRoute, mcpHandler);
|
|
956
|
+
app.get("/health", (_req, res) => {
|
|
957
|
+
res.json({ status: "ok", name: config.name, version: config.version });
|
|
958
|
+
});
|
|
959
|
+
if (config.transport === "stdio") {
|
|
960
|
+
const { startStdioServer: startStdioServer2 } = await Promise.resolve().then(() => (init_stdio(), stdio_exports));
|
|
961
|
+
return startStdioServer2({
|
|
962
|
+
name: config.name,
|
|
963
|
+
version: config.version,
|
|
964
|
+
tools: config.tools,
|
|
965
|
+
uiResources: config.uiResources,
|
|
966
|
+
executeTool,
|
|
967
|
+
protocol: config.config?.protocol
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
return new Promise((resolve3) => {
|
|
971
|
+
const httpServer = app.listen(config.port, config.host, async () => {
|
|
972
|
+
const baseUrl = `http://localhost:${config.port}`;
|
|
973
|
+
const viewNames = config.viewNames ?? [];
|
|
974
|
+
const actionNames = config.actionNames ?? [];
|
|
975
|
+
const dataNames = config.dataNames ?? [];
|
|
976
|
+
let tunnel = null;
|
|
977
|
+
if (config.config?.tunnel) {
|
|
978
|
+
const provider = config.config.tunnel.provider;
|
|
979
|
+
const { isTunnelProviderAvailable: isTunnelProviderAvailable2, createTunnel: createTunnel2 } = await Promise.resolve().then(() => (init_tunnel(), tunnel_exports));
|
|
980
|
+
const isAvailable = await isTunnelProviderAvailable2(provider);
|
|
981
|
+
if (!isAvailable) {
|
|
982
|
+
console.log("");
|
|
983
|
+
console.log(` \x1B[33m\u26A0\x1B[0m Tunnel provider "${provider}" is not installed`);
|
|
984
|
+
if (provider === "cloudflare") {
|
|
985
|
+
console.log(` Install with: \x1B[1mbrew install cloudflared\x1B[0m`);
|
|
986
|
+
} else {
|
|
987
|
+
console.log(` Install with: \x1B[1mbrew install ngrok\x1B[0m`);
|
|
988
|
+
}
|
|
989
|
+
console.log(` Continuing without tunnel...`);
|
|
990
|
+
console.log("");
|
|
991
|
+
} else {
|
|
992
|
+
console.log(` \x1B[36m\u2192\x1B[0m Starting ${provider} tunnel...`);
|
|
993
|
+
try {
|
|
994
|
+
const tunnelConfig = {
|
|
995
|
+
provider,
|
|
996
|
+
port: config.port
|
|
997
|
+
};
|
|
998
|
+
if (config.config.tunnel.authtoken) {
|
|
999
|
+
tunnelConfig.authtoken = config.config.tunnel.authtoken;
|
|
1000
|
+
}
|
|
1001
|
+
tunnel = await createTunnel2(tunnelConfig);
|
|
1002
|
+
console.log(` \x1B[32m\u2713\x1B[0m Tunnel started: \x1B[1m${tunnel.url}\x1B[0m`);
|
|
1003
|
+
} catch (error) {
|
|
1004
|
+
console.log(` \x1B[31m\u2717\x1B[0m Tunnel failed: ${error.message}`);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
if (config.config?.debug) {
|
|
1009
|
+
console.log("");
|
|
1010
|
+
console.log(` \x1B[1m\x1B[32m\u2713\x1B[0m \x1B[1m${config.name}\x1B[0m is running`);
|
|
1011
|
+
console.log("");
|
|
1012
|
+
console.log(` \x1B[2m\u279C\x1B[0m \x1B[1mServer:\x1B[0m ${baseUrl}`);
|
|
1013
|
+
console.log(` \x1B[2m\u279C\x1B[0m \x1B[1mMCP:\x1B[0m ${baseUrl}${mcpRoute}`);
|
|
1014
|
+
if (viewNames.length > 0) {
|
|
1015
|
+
console.log(` \x1B[2m\u279C\x1B[0m \x1B[1mViews:\x1B[0m ${viewNames.map((v) => `${baseUrl}/ui/${v}`).join("\n ")}`);
|
|
1016
|
+
}
|
|
1017
|
+
if (tunnel) {
|
|
1018
|
+
const providerName = config.config.tunnel?.provider === "cloudflare" ? "Cloudflare" : "ngrok";
|
|
1019
|
+
console.log("");
|
|
1020
|
+
console.log(` \x1B[1m\x1B[35m\u26A1\x1B[0m \x1B[1mTunnel (${providerName}):\x1B[0m`);
|
|
1021
|
+
console.log(` \x1B[2m\u279C\x1B[0m \x1B[1mPublic:\x1B[0m ${tunnel.url}`);
|
|
1022
|
+
console.log(` \x1B[2m\u279C\x1B[0m \x1B[1mMCP:\x1B[0m ${tunnel.url}${mcpRoute}`);
|
|
1023
|
+
console.log("");
|
|
1024
|
+
console.log(` \x1B[33m\u26A0\x1B[0m \x1B[2mTunnel URL is ephemeral and will change on restart\x1B[0m`);
|
|
1025
|
+
}
|
|
1026
|
+
console.log("");
|
|
1027
|
+
console.log(` \x1B[2mRegistered:\x1B[0m`);
|
|
1028
|
+
console.log(` ${viewNames.length} view${viewNames.length !== 1 ? "s" : ""}${viewNames.length > 0 ? ` (${viewNames.join(", ")})` : ""}`);
|
|
1029
|
+
console.log(` ${actionNames.length} action${actionNames.length !== 1 ? "s" : ""}${actionNames.length > 0 ? ` (${actionNames.join(", ")})` : ""}`);
|
|
1030
|
+
console.log(` ${dataNames.length} data fetcher${dataNames.length !== 1 ? "s" : ""}${dataNames.length > 0 ? ` (${dataNames.join(", ")})` : ""}`);
|
|
1031
|
+
console.log("");
|
|
1032
|
+
if (!tunnel) {
|
|
1033
|
+
console.log(` \x1B[2mNext steps:\x1B[0m`);
|
|
1034
|
+
console.log(` \x1B[36m1.\x1B[0m Open a view in your browser: ${baseUrl}/ui/${viewNames[0] || "yourView"}`);
|
|
1035
|
+
console.log(` \x1B[36m2.\x1B[0m Connect an MCP client to: ${baseUrl}${mcpRoute}`);
|
|
1036
|
+
console.log(` \x1B[36m3.\x1B[0m Expose for AI clients: \x1B[2mngrok http ${config.port}\x1B[0m or \x1B[2mcloudflared tunnel --url http://localhost:${config.port}\x1B[0m`);
|
|
1037
|
+
console.log("");
|
|
1038
|
+
} else {
|
|
1039
|
+
console.log(` \x1B[2mNext steps:\x1B[0m`);
|
|
1040
|
+
console.log(` \x1B[36m1.\x1B[0m Open a view in your browser: ${baseUrl}/ui/${viewNames[0] || "yourView"}`);
|
|
1041
|
+
console.log(` \x1B[36m2.\x1B[0m Connect an MCP client to: ${tunnel.url}${mcpRoute}`);
|
|
1042
|
+
console.log("");
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
resolve3({
|
|
1046
|
+
close: async () => {
|
|
1047
|
+
if (tunnel) {
|
|
1048
|
+
await tunnel.close();
|
|
1049
|
+
}
|
|
1050
|
+
return new Promise((resolveClose) => {
|
|
1051
|
+
httpServer.close(() => resolveClose());
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
});
|
|
1055
|
+
});
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
var init_server = __esm({
|
|
1059
|
+
"src/server/index.ts"() {
|
|
1060
|
+
init_mcp2();
|
|
1061
|
+
init_routes();
|
|
1062
|
+
init_dev_proxy();
|
|
1063
|
+
}
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
// src/view.ts
|
|
1067
|
+
function isViewConfig(value) {
|
|
1068
|
+
return typeof value === "object" && value !== null && "description" in value && "ui" in value;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// src/action.ts
|
|
1072
|
+
function isActionConfig(value) {
|
|
1073
|
+
return typeof value === "object" && value !== null && "description" in value && "input" in value && "output" in value && "handler" in value;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// src/data.ts
|
|
1077
|
+
function isDataConfig(value) {
|
|
1078
|
+
return typeof value === "object" && value !== null && "description" in value && "input" in value && "output" in value && "handler" in value && !("ui" in value);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// src/tool.ts
|
|
1082
|
+
function isToolConfig(value) {
|
|
1083
|
+
return typeof value === "object" && value !== null && "name" in value && "description" in value && "input" in value && "output" in value && "handler" in value;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// src/normalizers.ts
|
|
1087
|
+
function toJsonSchema(schema) {
|
|
1088
|
+
if (!schema) {
|
|
1089
|
+
return { type: "object", properties: {} };
|
|
1090
|
+
}
|
|
1091
|
+
return zodToJsonSchema.zodToJsonSchema(schema);
|
|
1092
|
+
}
|
|
1093
|
+
function normalizeView(name, config) {
|
|
1094
|
+
const toolName = `view:${name}`;
|
|
1095
|
+
return {
|
|
1096
|
+
name: toolName,
|
|
1097
|
+
description: config.description,
|
|
1098
|
+
inputSchema: toJsonSchema(config.input),
|
|
1099
|
+
outputSchema: toJsonSchema(config.data),
|
|
1100
|
+
handler: async (input, ctx) => {
|
|
1101
|
+
if (config.handler) {
|
|
1102
|
+
const validated = config.input ? config.input.parse(input) : input;
|
|
1103
|
+
return config.handler(validated, {
|
|
1104
|
+
...ctx,
|
|
1105
|
+
viewName: name
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
return {};
|
|
1109
|
+
},
|
|
1110
|
+
ui: config.ui,
|
|
1111
|
+
visibility: config.visibility ?? "both",
|
|
1112
|
+
category: "view"
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
function normalizeAction(name, config) {
|
|
1116
|
+
const toolName = `action:${name}`;
|
|
1117
|
+
return {
|
|
1118
|
+
name: toolName,
|
|
1119
|
+
description: config.description,
|
|
1120
|
+
inputSchema: toJsonSchema(config.input),
|
|
1121
|
+
outputSchema: toJsonSchema(config.output),
|
|
1122
|
+
handler: async (input, ctx) => {
|
|
1123
|
+
const validated = config.input.parse(input);
|
|
1124
|
+
return config.handler(validated, {
|
|
1125
|
+
...ctx,
|
|
1126
|
+
actionName: name
|
|
1127
|
+
});
|
|
1128
|
+
},
|
|
1129
|
+
ui: config.ui,
|
|
1130
|
+
visibility: config.visibility ?? "both",
|
|
1131
|
+
category: "action"
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
function normalizeData(name, config) {
|
|
1135
|
+
const toolName = `data:${name}`;
|
|
1136
|
+
return {
|
|
1137
|
+
name: toolName,
|
|
1138
|
+
description: config.description,
|
|
1139
|
+
inputSchema: toJsonSchema(config.input),
|
|
1140
|
+
outputSchema: toJsonSchema(config.output),
|
|
1141
|
+
handler: async (input, ctx) => {
|
|
1142
|
+
const validated = config.input.parse(input);
|
|
1143
|
+
return config.handler(validated, {
|
|
1144
|
+
...ctx,
|
|
1145
|
+
dataName: name
|
|
1146
|
+
});
|
|
1147
|
+
},
|
|
1148
|
+
ui: void 0,
|
|
1149
|
+
// Data fetchers don't have UI
|
|
1150
|
+
visibility: config.visibility ?? "both",
|
|
1151
|
+
category: "data"
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
function normalizeTool(name, config) {
|
|
1155
|
+
return {
|
|
1156
|
+
name: config.name || name,
|
|
1157
|
+
description: config.description,
|
|
1158
|
+
inputSchema: toJsonSchema(config.input),
|
|
1159
|
+
outputSchema: toJsonSchema(config.output),
|
|
1160
|
+
handler: async (input, ctx) => {
|
|
1161
|
+
const validated = config.input.parse(input);
|
|
1162
|
+
return config.handler(validated, ctx);
|
|
1163
|
+
},
|
|
1164
|
+
ui: config.ui,
|
|
1165
|
+
visibility: config.visibility ?? "both",
|
|
1166
|
+
category: "tool"
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
// src/app.ts
|
|
1171
|
+
var TypedEventEmitter = class {
|
|
1172
|
+
listeners = /* @__PURE__ */ new Map();
|
|
1173
|
+
on(event, handler) {
|
|
1174
|
+
const handlers = this.listeners.get(event) ?? /* @__PURE__ */ new Set();
|
|
1175
|
+
handlers.add(handler);
|
|
1176
|
+
this.listeners.set(event, handlers);
|
|
1177
|
+
return () => handlers.delete(handler);
|
|
1178
|
+
}
|
|
1179
|
+
emit(event, payload) {
|
|
1180
|
+
const handlers = this.listeners.get(event);
|
|
1181
|
+
if (handlers) {
|
|
1182
|
+
for (const handler of handlers) {
|
|
1183
|
+
handler(payload);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
};
|
|
1188
|
+
var MiddlewareChain = class {
|
|
1189
|
+
middlewares = [];
|
|
1190
|
+
constructor(initial) {
|
|
1191
|
+
if (initial) {
|
|
1192
|
+
this.middlewares = [...initial];
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
add(middleware) {
|
|
1196
|
+
this.middlewares.push(middleware);
|
|
1197
|
+
}
|
|
1198
|
+
async execute(ctx, handler) {
|
|
1199
|
+
const state = /* @__PURE__ */ new Map();
|
|
1200
|
+
const middlewareCtx = { ...ctx, state };
|
|
1201
|
+
let index = 0;
|
|
1202
|
+
let result;
|
|
1203
|
+
const next = async () => {
|
|
1204
|
+
if (index < this.middlewares.length) {
|
|
1205
|
+
const mw = this.middlewares[index++];
|
|
1206
|
+
await mw(middlewareCtx, next);
|
|
1207
|
+
} else {
|
|
1208
|
+
result = await handler();
|
|
1209
|
+
}
|
|
1210
|
+
};
|
|
1211
|
+
await next();
|
|
1212
|
+
return result;
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
var PluginManager = class {
|
|
1216
|
+
constructor(plugins = []) {
|
|
1217
|
+
this.plugins = plugins;
|
|
1218
|
+
}
|
|
1219
|
+
async runHook(hook, context) {
|
|
1220
|
+
for (const plugin of this.plugins) {
|
|
1221
|
+
const fn = plugin[hook];
|
|
1222
|
+
if (typeof fn === "function") {
|
|
1223
|
+
await fn.call(plugin, context);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
1228
|
+
function createApp(config) {
|
|
1229
|
+
if (!config.name) {
|
|
1230
|
+
throw new Error("App name is required");
|
|
1231
|
+
}
|
|
1232
|
+
if (!config.version) {
|
|
1233
|
+
throw new Error("App version is required");
|
|
1234
|
+
}
|
|
1235
|
+
const events = new TypedEventEmitter();
|
|
1236
|
+
const middlewareChain = new MiddlewareChain(config.middleware);
|
|
1237
|
+
const pluginManager = new PluginManager(config.plugins);
|
|
1238
|
+
const tools = /* @__PURE__ */ new Map();
|
|
1239
|
+
const uiResources = /* @__PURE__ */ new Map();
|
|
1240
|
+
for (const [name, viewConfig] of Object.entries(config.views ?? {})) {
|
|
1241
|
+
const tool = normalizeView(name, viewConfig);
|
|
1242
|
+
tools.set(tool.name, tool);
|
|
1243
|
+
if (tool.ui) {
|
|
1244
|
+
const uri = `pancake://ui/view/${name}`;
|
|
1245
|
+
uiResources.set(uri, {
|
|
1246
|
+
name,
|
|
1247
|
+
uri,
|
|
1248
|
+
definition: tool.ui
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
for (const [name, actionConfig] of Object.entries(config.actions ?? {})) {
|
|
1253
|
+
const tool = normalizeAction(name, actionConfig);
|
|
1254
|
+
tools.set(tool.name, tool);
|
|
1255
|
+
if (tool.ui) {
|
|
1256
|
+
const uri = `pancake://ui/action/${name}`;
|
|
1257
|
+
uiResources.set(uri, {
|
|
1258
|
+
name,
|
|
1259
|
+
uri,
|
|
1260
|
+
definition: tool.ui
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
for (const [name, dataConfig] of Object.entries(config.data ?? {})) {
|
|
1265
|
+
const tool = normalizeData(name, dataConfig);
|
|
1266
|
+
tools.set(tool.name, tool);
|
|
1267
|
+
}
|
|
1268
|
+
for (const [name, toolConfig] of Object.entries(config.tools ?? {})) {
|
|
1269
|
+
const tool = normalizeTool(name, toolConfig);
|
|
1270
|
+
tools.set(tool.name, tool);
|
|
1271
|
+
if (tool.ui) {
|
|
1272
|
+
const uri = `pancake://ui/tool/${name}`;
|
|
1273
|
+
uiResources.set(uri, {
|
|
1274
|
+
name,
|
|
1275
|
+
uri,
|
|
1276
|
+
definition: tool.ui
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
events.emit("app:init", { config });
|
|
1281
|
+
let server = null;
|
|
1282
|
+
const app = {
|
|
1283
|
+
async start(options = {}) {
|
|
1284
|
+
const port = options.port ?? 3e3;
|
|
1285
|
+
const host = options.host ?? "0.0.0.0";
|
|
1286
|
+
const transport = options.transport ?? "http";
|
|
1287
|
+
for (const plugin of config.plugins ?? []) {
|
|
1288
|
+
await pluginManager.runHook("onInit", { app: config, plugin });
|
|
1289
|
+
}
|
|
1290
|
+
const { createServer: createServer3 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
1291
|
+
const serverConfig = {
|
|
1292
|
+
name: config.name,
|
|
1293
|
+
version: config.version,
|
|
1294
|
+
tools,
|
|
1295
|
+
uiResources,
|
|
1296
|
+
middlewareChain,
|
|
1297
|
+
events,
|
|
1298
|
+
port,
|
|
1299
|
+
host,
|
|
1300
|
+
transport,
|
|
1301
|
+
viewNames: Object.keys(config.views ?? {}),
|
|
1302
|
+
actionNames: Object.keys(config.actions ?? {}),
|
|
1303
|
+
dataNames: Object.keys(config.data ?? {})
|
|
1304
|
+
};
|
|
1305
|
+
if (config.config) {
|
|
1306
|
+
const cfg = {};
|
|
1307
|
+
if (config.config.cors !== void 0) cfg.cors = config.config.cors;
|
|
1308
|
+
if (config.config.debug !== void 0) cfg.debug = config.config.debug;
|
|
1309
|
+
if (config.config.serverRoute !== void 0) cfg.serverRoute = config.config.serverRoute;
|
|
1310
|
+
if (config.config.devServer !== void 0) cfg.devServer = config.config.devServer;
|
|
1311
|
+
if (config.config.protocol !== void 0) cfg.protocol = config.config.protocol;
|
|
1312
|
+
if (config.config.tunnel !== void 0) cfg.tunnel = config.config.tunnel;
|
|
1313
|
+
serverConfig.config = cfg;
|
|
1314
|
+
}
|
|
1315
|
+
server = await createServer3(serverConfig);
|
|
1316
|
+
for (const plugin of config.plugins ?? []) {
|
|
1317
|
+
await pluginManager.runHook("onStart", { app: config, plugin });
|
|
1318
|
+
}
|
|
1319
|
+
events.emit("app:start", { port, transport });
|
|
1320
|
+
},
|
|
1321
|
+
async stop() {
|
|
1322
|
+
if (server) {
|
|
1323
|
+
for (const plugin of config.plugins ?? []) {
|
|
1324
|
+
await pluginManager.runHook("onShutdown", { app: config, plugin });
|
|
1325
|
+
}
|
|
1326
|
+
await server.close();
|
|
1327
|
+
server = null;
|
|
1328
|
+
events.emit("app:shutdown", { graceful: true });
|
|
1329
|
+
}
|
|
1330
|
+
},
|
|
1331
|
+
handler() {
|
|
1332
|
+
return async (_req, _res, next) => {
|
|
1333
|
+
next();
|
|
1334
|
+
};
|
|
1335
|
+
},
|
|
1336
|
+
async handleRequest(_req) {
|
|
1337
|
+
return new Response("Not implemented", { status: 501 });
|
|
1338
|
+
},
|
|
1339
|
+
use(middleware) {
|
|
1340
|
+
middlewareChain.add(middleware);
|
|
1341
|
+
},
|
|
1342
|
+
on(event, handler) {
|
|
1343
|
+
return events.on(event, handler);
|
|
1344
|
+
}
|
|
1345
|
+
};
|
|
1346
|
+
return app;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// src/helpers.ts
|
|
1350
|
+
function defineView(config) {
|
|
1351
|
+
return config;
|
|
1352
|
+
}
|
|
1353
|
+
function defineAction(config) {
|
|
1354
|
+
return config;
|
|
1355
|
+
}
|
|
1356
|
+
function defineData(config) {
|
|
1357
|
+
return config;
|
|
1358
|
+
}
|
|
1359
|
+
function defineTool(config) {
|
|
1360
|
+
return config;
|
|
1361
|
+
}
|
|
1362
|
+
function discoverViews(dir) {
|
|
1363
|
+
const views = {};
|
|
1364
|
+
const absoluteDir = path2__namespace.resolve(process.cwd(), dir);
|
|
1365
|
+
try {
|
|
1366
|
+
const entries = fsSync__namespace.readdirSync(absoluteDir, { withFileTypes: true });
|
|
1367
|
+
for (const entry of entries) {
|
|
1368
|
+
if (entry.isDirectory()) {
|
|
1369
|
+
const indexHtml = path2__namespace.join(absoluteDir, entry.name, "index.html");
|
|
1370
|
+
const metadataJson = path2__namespace.join(absoluteDir, entry.name, "metadata.json");
|
|
1371
|
+
if (fileExistsSync(indexHtml) && fileExistsSync(metadataJson)) {
|
|
1372
|
+
const metadata = parseMetadataSync(metadataJson);
|
|
1373
|
+
views[entry.name] = createViewFromDiscovery(entry.name, indexHtml, metadata);
|
|
1374
|
+
}
|
|
1375
|
+
} else if (entry.name.endsWith(".html")) {
|
|
1376
|
+
const baseName = entry.name.replace(".html", "");
|
|
1377
|
+
const metadataPath = path2__namespace.join(absoluteDir, `${baseName}.json`);
|
|
1378
|
+
const htmlPath = path2__namespace.join(absoluteDir, entry.name);
|
|
1379
|
+
if (fileExistsSync(metadataPath)) {
|
|
1380
|
+
const metadata = parseMetadataSync(metadataPath);
|
|
1381
|
+
views[baseName] = createViewFromDiscovery(baseName, htmlPath, metadata);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
} catch (error) {
|
|
1386
|
+
console.warn(`[Pancake] Could not discover views from ${dir}:`, error);
|
|
1387
|
+
}
|
|
1388
|
+
return views;
|
|
1389
|
+
}
|
|
1390
|
+
async function discoverViewsAsync(dir) {
|
|
1391
|
+
const views = {};
|
|
1392
|
+
const absoluteDir = path2__namespace.resolve(process.cwd(), dir);
|
|
1393
|
+
try {
|
|
1394
|
+
const entries = await fs2__namespace.readdir(absoluteDir, { withFileTypes: true });
|
|
1395
|
+
for (const entry of entries) {
|
|
1396
|
+
if (entry.isDirectory()) {
|
|
1397
|
+
const indexHtml = path2__namespace.join(absoluteDir, entry.name, "index.html");
|
|
1398
|
+
const metadataJson = path2__namespace.join(absoluteDir, entry.name, "metadata.json");
|
|
1399
|
+
if (await fileExists(indexHtml) && await fileExists(metadataJson)) {
|
|
1400
|
+
const metadata = await parseMetadata(metadataJson);
|
|
1401
|
+
views[entry.name] = createViewFromDiscovery(entry.name, indexHtml, metadata);
|
|
1402
|
+
}
|
|
1403
|
+
} else if (entry.name.endsWith(".html")) {
|
|
1404
|
+
const baseName = entry.name.replace(".html", "");
|
|
1405
|
+
const metadataPath = path2__namespace.join(absoluteDir, `${baseName}.json`);
|
|
1406
|
+
const htmlPath = path2__namespace.join(absoluteDir, entry.name);
|
|
1407
|
+
if (await fileExists(metadataPath)) {
|
|
1408
|
+
const metadata = await parseMetadata(metadataPath);
|
|
1409
|
+
views[baseName] = createViewFromDiscovery(baseName, htmlPath, metadata);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
} catch (error) {
|
|
1414
|
+
console.warn(`[Pancake] Could not discover views from ${dir}:`, error);
|
|
1415
|
+
}
|
|
1416
|
+
return views;
|
|
1417
|
+
}
|
|
1418
|
+
async function fileExists(filePath) {
|
|
1419
|
+
try {
|
|
1420
|
+
await fs2__namespace.access(filePath);
|
|
1421
|
+
return true;
|
|
1422
|
+
} catch {
|
|
1423
|
+
return false;
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
function fileExistsSync(filePath) {
|
|
1427
|
+
try {
|
|
1428
|
+
fsSync__namespace.accessSync(filePath);
|
|
1429
|
+
return true;
|
|
1430
|
+
} catch {
|
|
1431
|
+
return false;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
async function parseMetadata(filePath) {
|
|
1435
|
+
const content = await fs2__namespace.readFile(filePath, "utf-8");
|
|
1436
|
+
return JSON.parse(content);
|
|
1437
|
+
}
|
|
1438
|
+
function parseMetadataSync(filePath) {
|
|
1439
|
+
const content = fsSync__namespace.readFileSync(filePath, "utf-8");
|
|
1440
|
+
return JSON.parse(content);
|
|
1441
|
+
}
|
|
1442
|
+
function createViewFromDiscovery(name, htmlPath, metadata) {
|
|
1443
|
+
return {
|
|
1444
|
+
description: metadata.description,
|
|
1445
|
+
// Note: input/data from JSON are JSON Schema, not Zod
|
|
1446
|
+
// We'll need to handle this specially in the normalizer
|
|
1447
|
+
visibility: metadata.visibility ?? "both",
|
|
1448
|
+
ui: {
|
|
1449
|
+
html: htmlPath,
|
|
1450
|
+
name
|
|
1451
|
+
}
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
// src/index.ts
|
|
1456
|
+
init_tunnel();
|
|
1457
|
+
function isPortAvailable(port, host = "0.0.0.0") {
|
|
1458
|
+
return new Promise((resolve3) => {
|
|
1459
|
+
const server = net__namespace.createServer();
|
|
1460
|
+
server.once("error", () => {
|
|
1461
|
+
resolve3(false);
|
|
1462
|
+
});
|
|
1463
|
+
server.once("listening", () => {
|
|
1464
|
+
server.close(() => resolve3(true));
|
|
1465
|
+
});
|
|
1466
|
+
server.listen(port, host);
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
async function getAvailablePort(options = {}) {
|
|
1470
|
+
const { startPort = 3e3, host = "0.0.0.0", maxAttempts = 100 } = options;
|
|
1471
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
1472
|
+
const port = startPort + i;
|
|
1473
|
+
if (await isPortAvailable(port, host)) {
|
|
1474
|
+
return port;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
throw new Error(`No available port found after ${maxAttempts} attempts starting from ${startPort}`);
|
|
1478
|
+
}
|
|
1479
|
+
async function getAvailablePorts(preferredPorts, host = "0.0.0.0") {
|
|
1480
|
+
const assignedPorts = /* @__PURE__ */ new Set();
|
|
1481
|
+
const results = [];
|
|
1482
|
+
for (const preferred of preferredPorts) {
|
|
1483
|
+
let port = preferred;
|
|
1484
|
+
let attempts = 0;
|
|
1485
|
+
const maxAttempts = 100;
|
|
1486
|
+
while (attempts < maxAttempts) {
|
|
1487
|
+
if (!assignedPorts.has(port) && await isPortAvailable(port, host)) {
|
|
1488
|
+
assignedPorts.add(port);
|
|
1489
|
+
results.push(port);
|
|
1490
|
+
break;
|
|
1491
|
+
}
|
|
1492
|
+
port++;
|
|
1493
|
+
attempts++;
|
|
1494
|
+
}
|
|
1495
|
+
if (attempts === maxAttempts) {
|
|
1496
|
+
throw new Error(`Could not find available port starting from ${preferred}`);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
return results;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
exports.createApp = createApp;
|
|
1503
|
+
exports.createTunnel = createTunnel;
|
|
1504
|
+
exports.defineAction = defineAction;
|
|
1505
|
+
exports.defineData = defineData;
|
|
1506
|
+
exports.defineTool = defineTool;
|
|
1507
|
+
exports.defineView = defineView;
|
|
1508
|
+
exports.discoverViews = discoverViews;
|
|
1509
|
+
exports.discoverViewsAsync = discoverViewsAsync;
|
|
1510
|
+
exports.getAvailablePort = getAvailablePort;
|
|
1511
|
+
exports.getAvailablePorts = getAvailablePorts;
|
|
1512
|
+
exports.isActionConfig = isActionConfig;
|
|
1513
|
+
exports.isDataConfig = isDataConfig;
|
|
1514
|
+
exports.isPortAvailable = isPortAvailable;
|
|
1515
|
+
exports.isToolConfig = isToolConfig;
|
|
1516
|
+
exports.isTunnelProviderAvailable = isTunnelProviderAvailable;
|
|
1517
|
+
exports.isViewConfig = isViewConfig;
|
|
1518
|
+
//# sourceMappingURL=index.cjs.map
|
|
1519
|
+
//# sourceMappingURL=index.cjs.map
|