@pancake-apps/server 0.0.1

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.cjs ADDED
@@ -0,0 +1,985 @@
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 zodToJsonSchema = require('zod-to-json-schema');
10
+ var fsSync = require('fs');
11
+
12
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
+
14
+ function _interopNamespace(e) {
15
+ if (e && e.__esModule) return e;
16
+ var n = Object.create(null);
17
+ if (e) {
18
+ Object.keys(e).forEach(function (k) {
19
+ if (k !== 'default') {
20
+ var d = Object.getOwnPropertyDescriptor(e, k);
21
+ Object.defineProperty(n, k, d.get ? d : {
22
+ enumerable: true,
23
+ get: function () { return e[k]; }
24
+ });
25
+ }
26
+ });
27
+ }
28
+ n.default = e;
29
+ return Object.freeze(n);
30
+ }
31
+
32
+ var express__default = /*#__PURE__*/_interopDefault(express);
33
+ var fs2__namespace = /*#__PURE__*/_interopNamespace(fs2);
34
+ var path2__namespace = /*#__PURE__*/_interopNamespace(path2);
35
+ var fsSync__namespace = /*#__PURE__*/_interopNamespace(fsSync);
36
+
37
+ var __defProp = Object.defineProperty;
38
+ var __getOwnPropNames = Object.getOwnPropertyNames;
39
+ var __esm = (fn, res) => function __init() {
40
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
41
+ };
42
+ var __export = (target, all) => {
43
+ for (var name in all)
44
+ __defProp(target, name, { get: all[name], enumerable: true });
45
+ };
46
+
47
+ // src/server/mcp.ts
48
+ function visibilityToAudience(visibility) {
49
+ switch (visibility) {
50
+ case "model":
51
+ return ["assistant"];
52
+ case "app":
53
+ return ["user"];
54
+ case "both":
55
+ default:
56
+ return ["assistant", "user"];
57
+ }
58
+ }
59
+ function createMcpHandler(config) {
60
+ return async (req, res) => {
61
+ const request = req.body;
62
+ if (request.jsonrpc !== "2.0") {
63
+ res.status(400).json({
64
+ jsonrpc: "2.0",
65
+ id: request.id ?? null,
66
+ error: { code: -32600, message: "Invalid Request: Not JSON-RPC 2.0" }
67
+ });
68
+ return;
69
+ }
70
+ try {
71
+ const result = await handleMethod(config, request.method, request.params ?? {});
72
+ const response = {
73
+ jsonrpc: "2.0",
74
+ id: request.id,
75
+ result
76
+ };
77
+ res.json(response);
78
+ } catch (error) {
79
+ const response = {
80
+ jsonrpc: "2.0",
81
+ id: request.id,
82
+ error: {
83
+ code: -32603,
84
+ message: error instanceof Error ? error.message : "Internal error",
85
+ data: error instanceof Error ? { stack: error.stack } : void 0
86
+ }
87
+ };
88
+ res.json(response);
89
+ }
90
+ };
91
+ }
92
+ async function handleMethod(config, method, params) {
93
+ switch (method) {
94
+ case "initialize":
95
+ return {
96
+ protocolVersion: "2024-11-05",
97
+ capabilities: {
98
+ tools: {},
99
+ resources: {}
100
+ },
101
+ serverInfo: {
102
+ name: config.name,
103
+ version: config.version
104
+ }
105
+ };
106
+ case "tools/list":
107
+ return {
108
+ tools: Array.from(config.tools.values()).filter((tool) => tool.visibility !== "app").map((tool) => ({
109
+ name: tool.name,
110
+ description: tool.description,
111
+ inputSchema: tool.inputSchema,
112
+ annotations: {
113
+ audience: visibilityToAudience(tool.visibility)
114
+ },
115
+ _meta: tool.ui ? {
116
+ ui: {
117
+ visibility: visibilityToAudience(tool.visibility),
118
+ resourceUri: getUIResourceUri(tool.name, tool.category)
119
+ }
120
+ } : void 0
121
+ }))
122
+ };
123
+ case "tools/call": {
124
+ const toolName = params["name"];
125
+ const args = params["arguments"] ?? {};
126
+ const result = await config.executeTool(toolName, args);
127
+ return {
128
+ content: [
129
+ {
130
+ type: "text",
131
+ text: JSON.stringify(result)
132
+ }
133
+ ],
134
+ structuredContent: result
135
+ };
136
+ }
137
+ case "resources/list":
138
+ return {
139
+ resources: Array.from(config.uiResources.values()).map((resource) => ({
140
+ uri: resource.uri,
141
+ name: resource.name,
142
+ mimeType: "text/html;profile=mcp-app",
143
+ _meta: {
144
+ ui: {
145
+ prefersBorder: resource.definition.prefersBorder,
146
+ autoResize: resource.definition.autoResize,
147
+ csp: resource.definition.csp
148
+ }
149
+ }
150
+ }))
151
+ };
152
+ case "resources/read": {
153
+ const uri = params["uri"];
154
+ const resource = config.uiResources.get(uri);
155
+ if (!resource) {
156
+ throw new Error(`Resource not found: ${uri}`);
157
+ }
158
+ let htmlContent;
159
+ const htmlPath = resource.definition.html;
160
+ if (htmlPath.trim().startsWith("<")) {
161
+ htmlContent = htmlPath;
162
+ } else {
163
+ const fsPromises = await import('fs/promises');
164
+ const pathModule = await import('path');
165
+ const fullPath = pathModule.resolve(process.cwd(), htmlPath);
166
+ htmlContent = await fsPromises.readFile(fullPath, "utf-8");
167
+ }
168
+ return {
169
+ contents: [
170
+ {
171
+ uri,
172
+ mimeType: "text/html",
173
+ text: htmlContent
174
+ }
175
+ ]
176
+ };
177
+ }
178
+ case "notifications/initialized":
179
+ case "ping":
180
+ return {};
181
+ default:
182
+ throw new Error(`Unknown method: ${method}`);
183
+ }
184
+ }
185
+ function getUIResourceUri(toolName, category) {
186
+ const name = toolName.replace(/^(view|action|data|tool):/, "");
187
+ return `pancake://ui/${category}/${name}`;
188
+ }
189
+ var init_mcp = __esm({
190
+ "src/server/mcp.ts"() {
191
+ }
192
+ });
193
+
194
+ // src/server/dev-proxy.ts
195
+ function isDevelopment() {
196
+ return process.env["NODE_ENV"] !== "production";
197
+ }
198
+ function createViteProxy(config) {
199
+ const vitePort = config.vitePort ?? 5173;
200
+ const viteUrl = `http://localhost:${vitePort}`;
201
+ const vitePaths = [
202
+ "/@vite/",
203
+ "/@react-refresh",
204
+ "/assets/",
205
+ "/src/",
206
+ "/__vite_ping",
207
+ "/@fs/",
208
+ "/node_modules/.vite/"
209
+ ];
210
+ return async (req, res, next) => {
211
+ const shouldProxy = vitePaths.some((p) => req.path.startsWith(p));
212
+ if (!shouldProxy) {
213
+ next();
214
+ return;
215
+ }
216
+ try {
217
+ const targetUrl = `${viteUrl}${req.url}`;
218
+ const response = await fetch(targetUrl, {
219
+ method: req.method,
220
+ headers: {
221
+ ...Object.fromEntries(
222
+ Object.entries(req.headers).filter(
223
+ ([key]) => !["host", "connection"].includes(key.toLowerCase())
224
+ )
225
+ )
226
+ }
227
+ });
228
+ response.headers.forEach((value, key) => {
229
+ res.setHeader(key, value);
230
+ });
231
+ res.status(response.status);
232
+ if (response.body) {
233
+ const reader = response.body.getReader();
234
+ const pump = async () => {
235
+ const { done, value } = await reader.read();
236
+ if (done) {
237
+ res.end();
238
+ return;
239
+ }
240
+ res.write(Buffer.from(value));
241
+ return pump();
242
+ };
243
+ await pump();
244
+ } else {
245
+ res.end();
246
+ }
247
+ } catch (error) {
248
+ if (isDevelopment()) {
249
+ console.warn(`[Pancake] Vite proxy failed for ${req.path}:`, error);
250
+ }
251
+ next();
252
+ }
253
+ };
254
+ }
255
+ function generateHMRTemplate(serverUrl, viewName) {
256
+ return `<!DOCTYPE html>
257
+ <html lang="en">
258
+ <head>
259
+ <meta charset="UTF-8" />
260
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
261
+ <title>${viewName}</title>
262
+ <base href="${serverUrl}/" />
263
+ <script type="module">
264
+ import { injectIntoGlobalHook } from "${serverUrl}/@react-refresh";
265
+ injectIntoGlobalHook(window);
266
+ window.$RefreshReg$ = () => {};
267
+ window.$RefreshSig$ = () => (type) => type;
268
+ window.__vite_plugin_react_preamble_installed__ = true;
269
+ </script>
270
+ <script type="module" src="${serverUrl}/@vite/client"></script>
271
+ </head>
272
+ <body>
273
+ <div id="root"></div>
274
+ <script type="module">
275
+ // Initialize Pancake client before loading the view
276
+ import('${serverUrl}/src/pancake-init.ts')
277
+ .then(async (mod) => {
278
+ if (mod.init) await mod.init();
279
+ })
280
+ .catch(() => {
281
+ // pancake-init.ts might not exist, that's ok
282
+ })
283
+ .finally(() => {
284
+ import('${serverUrl}/src/views/${viewName}/index.tsx')
285
+ .catch(() => import('${serverUrl}/src/views/${viewName}.tsx'))
286
+ .catch(() => import('${serverUrl}/src/views/${viewName}/index.ts'))
287
+ .catch(() => import('${serverUrl}/src/views/${viewName}.ts'))
288
+ .catch((err) => {
289
+ console.error('Failed to load view:', err);
290
+ document.body.innerHTML = '<pre style="color: red;">Failed to load view: ' + err.message + '</pre>';
291
+ });
292
+ });
293
+ </script>
294
+ </body>
295
+ </html>`;
296
+ }
297
+ var init_dev_proxy = __esm({
298
+ "src/server/dev-proxy.ts"() {
299
+ }
300
+ });
301
+ function createUIRoutes(uiResources, devServerConfig) {
302
+ const router = express.Router();
303
+ router.get("/:category/:name", async (req, res) => {
304
+ const { category, name } = req.params;
305
+ const uri = `pancake://ui/${category}/${name}`;
306
+ const resource = uiResources.get(uri);
307
+ if (!resource) {
308
+ res.status(404).json({ error: `UI not found: ${category}/${name}` });
309
+ return;
310
+ }
311
+ try {
312
+ const html = await getUIHtml(resource.definition, name, devServerConfig);
313
+ res.type("html").send(html);
314
+ } catch (error) {
315
+ res.status(500).json({
316
+ error: "Failed to load UI",
317
+ details: error instanceof Error ? error.message : String(error)
318
+ });
319
+ }
320
+ });
321
+ router.get("/:name", async (req, res) => {
322
+ const { name } = req.params;
323
+ for (const category of ["view", "action", "tool"]) {
324
+ const uri = `pancake://ui/${category}/${name}`;
325
+ const resource = uiResources.get(uri);
326
+ if (resource) {
327
+ try {
328
+ const html = await getUIHtml(resource.definition, name, devServerConfig);
329
+ res.type("html").send(html);
330
+ return;
331
+ } catch (error) {
332
+ res.status(500).json({
333
+ error: "Failed to load UI",
334
+ details: error instanceof Error ? error.message : String(error)
335
+ });
336
+ return;
337
+ }
338
+ }
339
+ }
340
+ res.status(404).json({ error: `UI not found: ${name}` });
341
+ });
342
+ return router;
343
+ }
344
+ async function getUIHtml(ui, viewName, devServerConfig) {
345
+ if (isDevelopment() && devServerConfig) {
346
+ const vitePort = devServerConfig.vitePort ?? 5173;
347
+ return generateHMRTemplate(`http://localhost:${vitePort}`, viewName);
348
+ }
349
+ if (ui.html.trim().startsWith("<")) {
350
+ return ui.html;
351
+ }
352
+ const htmlPath = path2__namespace.resolve(process.cwd(), ui.html);
353
+ const content = await fs2__namespace.readFile(htmlPath, "utf-8");
354
+ return content;
355
+ }
356
+ var init_routes = __esm({
357
+ "src/server/routes.ts"() {
358
+ init_dev_proxy();
359
+ }
360
+ });
361
+
362
+ // src/server/stdio.ts
363
+ var stdio_exports = {};
364
+ __export(stdio_exports, {
365
+ startStdioServer: () => startStdioServer
366
+ });
367
+ function visibilityToAudience2(visibility) {
368
+ switch (visibility) {
369
+ case "model":
370
+ return ["assistant"];
371
+ case "app":
372
+ return ["user"];
373
+ case "both":
374
+ default:
375
+ return ["assistant", "user"];
376
+ }
377
+ }
378
+ async function startStdioServer(config) {
379
+ const server = new index_js.Server(
380
+ {
381
+ name: config.name,
382
+ version: config.version
383
+ },
384
+ {
385
+ capabilities: {
386
+ tools: {},
387
+ resources: {}
388
+ }
389
+ }
390
+ );
391
+ server.setRequestHandler(
392
+ types_js.ListToolsRequestSchema,
393
+ async () => {
394
+ return {
395
+ tools: Array.from(config.tools.values()).map((tool) => ({
396
+ name: tool.name,
397
+ description: tool.description,
398
+ inputSchema: tool.inputSchema,
399
+ annotations: {
400
+ audience: visibilityToAudience2(tool.visibility)
401
+ },
402
+ _meta: tool.ui ? {
403
+ ui: {
404
+ visibility: visibilityToAudience2(tool.visibility),
405
+ resourceUri: getUIResourceUri2(tool.name, tool.category)
406
+ }
407
+ } : void 0
408
+ }))
409
+ };
410
+ }
411
+ );
412
+ server.setRequestHandler(
413
+ types_js.CallToolRequestSchema,
414
+ async (request) => {
415
+ const { name, arguments: args = {} } = request.params;
416
+ const result = await config.executeTool(name, args);
417
+ return {
418
+ content: [
419
+ {
420
+ type: "text",
421
+ text: JSON.stringify(result)
422
+ }
423
+ ],
424
+ structuredContent: result
425
+ };
426
+ }
427
+ );
428
+ server.setRequestHandler(
429
+ types_js.ListResourcesRequestSchema,
430
+ async () => {
431
+ return {
432
+ resources: Array.from(config.uiResources.values()).map((resource) => ({
433
+ uri: resource.uri,
434
+ name: resource.name,
435
+ mimeType: "text/html;profile=mcp-app",
436
+ _meta: {
437
+ ui: {
438
+ prefersBorder: resource.definition.prefersBorder,
439
+ autoResize: resource.definition.autoResize,
440
+ csp: resource.definition.csp
441
+ }
442
+ }
443
+ }))
444
+ };
445
+ }
446
+ );
447
+ server.setRequestHandler(
448
+ types_js.ReadResourceRequestSchema,
449
+ async (request) => {
450
+ const { uri } = request.params;
451
+ const resource = config.uiResources.get(uri);
452
+ if (!resource) {
453
+ throw new Error(`Resource not found: ${uri}`);
454
+ }
455
+ return {
456
+ contents: [
457
+ {
458
+ uri,
459
+ mimeType: "text/html",
460
+ text: resource.definition.html
461
+ }
462
+ ]
463
+ };
464
+ }
465
+ );
466
+ const transport = new stdio_js.StdioServerTransport();
467
+ await server.connect(transport);
468
+ return {
469
+ close: async () => {
470
+ await server.close();
471
+ }
472
+ };
473
+ }
474
+ function getUIResourceUri2(toolName, category) {
475
+ const name = toolName.replace(/^(view|action|data|tool):/, "");
476
+ return `pancake://ui/${category}/${name}`;
477
+ }
478
+ var init_stdio = __esm({
479
+ "src/server/stdio.ts"() {
480
+ }
481
+ });
482
+
483
+ // src/server/index.ts
484
+ var server_exports = {};
485
+ __export(server_exports, {
486
+ createServer: () => createServer
487
+ });
488
+ function generateRequestId() {
489
+ return `req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;
490
+ }
491
+ async function createServer(config) {
492
+ const app = express__default.default();
493
+ app.use(express__default.default.json());
494
+ if (config.config?.cors) {
495
+ const corsConfig = config.config.cors;
496
+ app.use((req, res, next) => {
497
+ const origin = corsConfig.origin;
498
+ if (origin === true) {
499
+ res.setHeader("Access-Control-Allow-Origin", "*");
500
+ } else if (typeof origin === "string") {
501
+ res.setHeader("Access-Control-Allow-Origin", origin);
502
+ } else if (Array.isArray(origin)) {
503
+ const requestOrigin = req.headers.origin;
504
+ if (requestOrigin && origin.includes(requestOrigin)) {
505
+ res.setHeader("Access-Control-Allow-Origin", requestOrigin);
506
+ }
507
+ }
508
+ if (corsConfig.credentials) {
509
+ res.setHeader("Access-Control-Allow-Credentials", "true");
510
+ }
511
+ if (corsConfig.methods) {
512
+ res.setHeader("Access-Control-Allow-Methods", corsConfig.methods.join(", "));
513
+ }
514
+ if (corsConfig.allowedHeaders) {
515
+ res.setHeader("Access-Control-Allow-Headers", corsConfig.allowedHeaders.join(", "));
516
+ }
517
+ if (req.method === "OPTIONS") {
518
+ res.status(204).end();
519
+ return;
520
+ }
521
+ next();
522
+ });
523
+ }
524
+ if (isDevelopment() && config.config?.devServer) {
525
+ const viteProxy = createViteProxy(config.config.devServer);
526
+ app.use(viteProxy);
527
+ }
528
+ const uiRoutes = createUIRoutes(config.uiResources, config.config?.devServer);
529
+ app.use("/ui", uiRoutes);
530
+ const executeTool = async (toolName, input, metadata = {}) => {
531
+ const tool = config.tools.get(toolName);
532
+ if (!tool) {
533
+ throw new Error(`Unknown tool: ${toolName}`);
534
+ }
535
+ const ctx = {
536
+ requestId: generateRequestId(),
537
+ toolName,
538
+ metadata
539
+ };
540
+ const startTime = Date.now();
541
+ try {
542
+ const result = await config.middlewareChain.execute(
543
+ { toolName, input, metadata },
544
+ () => tool.handler(input, ctx)
545
+ );
546
+ config.events.emit("tool:success", {
547
+ toolName,
548
+ result,
549
+ durationMs: Date.now() - startTime
550
+ });
551
+ return result;
552
+ } catch (error) {
553
+ config.events.emit("tool:error", {
554
+ toolName,
555
+ error,
556
+ durationMs: Date.now() - startTime
557
+ });
558
+ throw error;
559
+ }
560
+ };
561
+ const mcpRoute = config.config?.serverRoute ?? "/mcp";
562
+ const mcpHandler = createMcpHandler({
563
+ name: config.name,
564
+ version: config.version,
565
+ tools: config.tools,
566
+ uiResources: config.uiResources,
567
+ executeTool
568
+ });
569
+ app.post(mcpRoute, mcpHandler);
570
+ app.get("/health", (_req, res) => {
571
+ res.json({ status: "ok", name: config.name, version: config.version });
572
+ });
573
+ if (config.transport === "stdio") {
574
+ const { startStdioServer: startStdioServer2 } = await Promise.resolve().then(() => (init_stdio(), stdio_exports));
575
+ return startStdioServer2({
576
+ name: config.name,
577
+ version: config.version,
578
+ tools: config.tools,
579
+ uiResources: config.uiResources,
580
+ executeTool
581
+ });
582
+ }
583
+ return new Promise((resolve3) => {
584
+ const httpServer = app.listen(config.port, config.host, () => {
585
+ if (config.config?.debug) {
586
+ console.log(`Pancake server "${config.name}" running on http://${config.host}:${config.port}`);
587
+ console.log(` MCP endpoint: ${mcpRoute}`);
588
+ console.log(` UI endpoint: /ui/:viewName`);
589
+ console.log(` Tools registered: ${config.tools.size}`);
590
+ }
591
+ resolve3({
592
+ close: () => new Promise((resolveClose) => {
593
+ httpServer.close(() => resolveClose());
594
+ })
595
+ });
596
+ });
597
+ });
598
+ }
599
+ var init_server = __esm({
600
+ "src/server/index.ts"() {
601
+ init_mcp();
602
+ init_routes();
603
+ init_dev_proxy();
604
+ }
605
+ });
606
+
607
+ // src/view.ts
608
+ function isViewConfig(value) {
609
+ return typeof value === "object" && value !== null && "description" in value && "ui" in value;
610
+ }
611
+
612
+ // src/action.ts
613
+ function isActionConfig(value) {
614
+ return typeof value === "object" && value !== null && "description" in value && "input" in value && "output" in value && "handler" in value;
615
+ }
616
+
617
+ // src/data.ts
618
+ function isDataConfig(value) {
619
+ return typeof value === "object" && value !== null && "description" in value && "input" in value && "output" in value && "handler" in value && !("ui" in value);
620
+ }
621
+
622
+ // src/tool.ts
623
+ function isToolConfig(value) {
624
+ return typeof value === "object" && value !== null && "name" in value && "description" in value && "input" in value && "output" in value && "handler" in value;
625
+ }
626
+
627
+ // src/normalizers.ts
628
+ function toJsonSchema(schema) {
629
+ if (!schema) {
630
+ return { type: "object", properties: {} };
631
+ }
632
+ return zodToJsonSchema.zodToJsonSchema(schema);
633
+ }
634
+ function normalizeView(name, config) {
635
+ const toolName = `view:${name}`;
636
+ return {
637
+ name: toolName,
638
+ description: config.description,
639
+ inputSchema: toJsonSchema(config.input),
640
+ outputSchema: toJsonSchema(config.data),
641
+ handler: async (input, ctx) => {
642
+ if (config.handler) {
643
+ const validated = config.input ? config.input.parse(input) : input;
644
+ return config.handler(validated, {
645
+ ...ctx,
646
+ viewName: name
647
+ });
648
+ }
649
+ return {};
650
+ },
651
+ ui: config.ui,
652
+ visibility: config.visibility ?? "both",
653
+ category: "view"
654
+ };
655
+ }
656
+ function normalizeAction(name, config) {
657
+ const toolName = `action:${name}`;
658
+ return {
659
+ name: toolName,
660
+ description: config.description,
661
+ inputSchema: toJsonSchema(config.input),
662
+ outputSchema: toJsonSchema(config.output),
663
+ handler: async (input, ctx) => {
664
+ const validated = config.input.parse(input);
665
+ return config.handler(validated, {
666
+ ...ctx,
667
+ actionName: name
668
+ });
669
+ },
670
+ ui: config.ui,
671
+ visibility: config.visibility ?? "both",
672
+ category: "action"
673
+ };
674
+ }
675
+ function normalizeData(name, config) {
676
+ const toolName = `data:${name}`;
677
+ return {
678
+ name: toolName,
679
+ description: config.description,
680
+ inputSchema: toJsonSchema(config.input),
681
+ outputSchema: toJsonSchema(config.output),
682
+ handler: async (input, ctx) => {
683
+ const validated = config.input.parse(input);
684
+ return config.handler(validated, {
685
+ ...ctx,
686
+ dataName: name
687
+ });
688
+ },
689
+ ui: void 0,
690
+ // Data fetchers don't have UI
691
+ visibility: config.visibility ?? "both",
692
+ category: "data"
693
+ };
694
+ }
695
+ function normalizeTool(name, config) {
696
+ return {
697
+ name: config.name || name,
698
+ description: config.description,
699
+ inputSchema: toJsonSchema(config.input),
700
+ outputSchema: toJsonSchema(config.output),
701
+ handler: async (input, ctx) => {
702
+ const validated = config.input.parse(input);
703
+ return config.handler(validated, ctx);
704
+ },
705
+ ui: config.ui,
706
+ visibility: config.visibility ?? "both",
707
+ category: "tool"
708
+ };
709
+ }
710
+
711
+ // src/app.ts
712
+ var TypedEventEmitter = class {
713
+ listeners = /* @__PURE__ */ new Map();
714
+ on(event, handler) {
715
+ const handlers = this.listeners.get(event) ?? /* @__PURE__ */ new Set();
716
+ handlers.add(handler);
717
+ this.listeners.set(event, handlers);
718
+ return () => handlers.delete(handler);
719
+ }
720
+ emit(event, payload) {
721
+ const handlers = this.listeners.get(event);
722
+ if (handlers) {
723
+ for (const handler of handlers) {
724
+ handler(payload);
725
+ }
726
+ }
727
+ }
728
+ };
729
+ var MiddlewareChain = class {
730
+ middlewares = [];
731
+ constructor(initial) {
732
+ if (initial) {
733
+ this.middlewares = [...initial];
734
+ }
735
+ }
736
+ add(middleware) {
737
+ this.middlewares.push(middleware);
738
+ }
739
+ async execute(ctx, handler) {
740
+ const state = /* @__PURE__ */ new Map();
741
+ const middlewareCtx = { ...ctx, state };
742
+ let index = 0;
743
+ let result;
744
+ const next = async () => {
745
+ if (index < this.middlewares.length) {
746
+ const mw = this.middlewares[index++];
747
+ await mw(middlewareCtx, next);
748
+ } else {
749
+ result = await handler();
750
+ }
751
+ };
752
+ await next();
753
+ return result;
754
+ }
755
+ };
756
+ var PluginManager = class {
757
+ constructor(plugins = []) {
758
+ this.plugins = plugins;
759
+ }
760
+ async runHook(hook, context) {
761
+ for (const plugin of this.plugins) {
762
+ const fn = plugin[hook];
763
+ if (typeof fn === "function") {
764
+ await fn.call(plugin, context);
765
+ }
766
+ }
767
+ }
768
+ };
769
+ function createApp(config) {
770
+ if (!config.name) {
771
+ throw new Error("App name is required");
772
+ }
773
+ if (!config.version) {
774
+ throw new Error("App version is required");
775
+ }
776
+ const events = new TypedEventEmitter();
777
+ const middlewareChain = new MiddlewareChain(config.middleware);
778
+ const pluginManager = new PluginManager(config.plugins);
779
+ const tools = /* @__PURE__ */ new Map();
780
+ const uiResources = /* @__PURE__ */ new Map();
781
+ for (const [name, viewConfig] of Object.entries(config.views ?? {})) {
782
+ const tool = normalizeView(name, viewConfig);
783
+ tools.set(tool.name, tool);
784
+ if (tool.ui) {
785
+ const uri = `pancake://ui/view/${name}`;
786
+ uiResources.set(uri, {
787
+ name,
788
+ uri,
789
+ definition: tool.ui
790
+ });
791
+ }
792
+ }
793
+ for (const [name, actionConfig] of Object.entries(config.actions ?? {})) {
794
+ const tool = normalizeAction(name, actionConfig);
795
+ tools.set(tool.name, tool);
796
+ if (tool.ui) {
797
+ const uri = `pancake://ui/action/${name}`;
798
+ uiResources.set(uri, {
799
+ name,
800
+ uri,
801
+ definition: tool.ui
802
+ });
803
+ }
804
+ }
805
+ for (const [name, dataConfig] of Object.entries(config.data ?? {})) {
806
+ const tool = normalizeData(name, dataConfig);
807
+ tools.set(tool.name, tool);
808
+ }
809
+ for (const [name, toolConfig] of Object.entries(config.tools ?? {})) {
810
+ const tool = normalizeTool(name, toolConfig);
811
+ tools.set(tool.name, tool);
812
+ if (tool.ui) {
813
+ const uri = `pancake://ui/tool/${name}`;
814
+ uiResources.set(uri, {
815
+ name,
816
+ uri,
817
+ definition: tool.ui
818
+ });
819
+ }
820
+ }
821
+ events.emit("app:init", { config });
822
+ let server = null;
823
+ const app = {
824
+ async start(options = {}) {
825
+ const port = options.port ?? 3e3;
826
+ const host = options.host ?? "0.0.0.0";
827
+ const transport = options.transport ?? "http";
828
+ for (const plugin of config.plugins ?? []) {
829
+ await pluginManager.runHook("onInit", { app: config, plugin });
830
+ }
831
+ const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
832
+ const serverConfig = {
833
+ name: config.name,
834
+ version: config.version,
835
+ tools,
836
+ uiResources,
837
+ middlewareChain,
838
+ events,
839
+ port,
840
+ host,
841
+ transport
842
+ };
843
+ if (config.config) {
844
+ const cfg = {};
845
+ if (config.config.cors !== void 0) cfg.cors = config.config.cors;
846
+ if (config.config.debug !== void 0) cfg.debug = config.config.debug;
847
+ if (config.config.serverRoute !== void 0) cfg.serverRoute = config.config.serverRoute;
848
+ if (config.config.devServer !== void 0) cfg.devServer = config.config.devServer;
849
+ serverConfig.config = cfg;
850
+ }
851
+ server = await createServer2(serverConfig);
852
+ for (const plugin of config.plugins ?? []) {
853
+ await pluginManager.runHook("onStart", { app: config, plugin });
854
+ }
855
+ events.emit("app:start", { port, transport });
856
+ },
857
+ async stop() {
858
+ if (server) {
859
+ for (const plugin of config.plugins ?? []) {
860
+ await pluginManager.runHook("onShutdown", { app: config, plugin });
861
+ }
862
+ await server.close();
863
+ server = null;
864
+ events.emit("app:shutdown", { graceful: true });
865
+ }
866
+ },
867
+ handler() {
868
+ return async (_req, _res, next) => {
869
+ next();
870
+ };
871
+ },
872
+ async handleRequest(_req) {
873
+ return new Response("Not implemented", { status: 501 });
874
+ },
875
+ use(middleware) {
876
+ middlewareChain.add(middleware);
877
+ },
878
+ on(event, handler) {
879
+ return events.on(event, handler);
880
+ }
881
+ };
882
+ return app;
883
+ }
884
+ function discoverViews(dir) {
885
+ const views = {};
886
+ const absoluteDir = path2__namespace.resolve(process.cwd(), dir);
887
+ try {
888
+ const entries = fsSync__namespace.readdirSync(absoluteDir, { withFileTypes: true });
889
+ for (const entry of entries) {
890
+ if (entry.isDirectory()) {
891
+ const indexHtml = path2__namespace.join(absoluteDir, entry.name, "index.html");
892
+ const metadataJson = path2__namespace.join(absoluteDir, entry.name, "metadata.json");
893
+ if (fileExistsSync(indexHtml) && fileExistsSync(metadataJson)) {
894
+ const metadata = parseMetadataSync(metadataJson);
895
+ views[entry.name] = createViewFromDiscovery(entry.name, indexHtml, metadata);
896
+ }
897
+ } else if (entry.name.endsWith(".html")) {
898
+ const baseName = entry.name.replace(".html", "");
899
+ const metadataPath = path2__namespace.join(absoluteDir, `${baseName}.json`);
900
+ const htmlPath = path2__namespace.join(absoluteDir, entry.name);
901
+ if (fileExistsSync(metadataPath)) {
902
+ const metadata = parseMetadataSync(metadataPath);
903
+ views[baseName] = createViewFromDiscovery(baseName, htmlPath, metadata);
904
+ }
905
+ }
906
+ }
907
+ } catch (error) {
908
+ console.warn(`[Pancake] Could not discover views from ${dir}:`, error);
909
+ }
910
+ return views;
911
+ }
912
+ async function discoverViewsAsync(dir) {
913
+ const views = {};
914
+ const absoluteDir = path2__namespace.resolve(process.cwd(), dir);
915
+ try {
916
+ const entries = await fs2__namespace.readdir(absoluteDir, { withFileTypes: true });
917
+ for (const entry of entries) {
918
+ if (entry.isDirectory()) {
919
+ const indexHtml = path2__namespace.join(absoluteDir, entry.name, "index.html");
920
+ const metadataJson = path2__namespace.join(absoluteDir, entry.name, "metadata.json");
921
+ if (await fileExists(indexHtml) && await fileExists(metadataJson)) {
922
+ const metadata = await parseMetadata(metadataJson);
923
+ views[entry.name] = createViewFromDiscovery(entry.name, indexHtml, metadata);
924
+ }
925
+ } else if (entry.name.endsWith(".html")) {
926
+ const baseName = entry.name.replace(".html", "");
927
+ const metadataPath = path2__namespace.join(absoluteDir, `${baseName}.json`);
928
+ const htmlPath = path2__namespace.join(absoluteDir, entry.name);
929
+ if (await fileExists(metadataPath)) {
930
+ const metadata = await parseMetadata(metadataPath);
931
+ views[baseName] = createViewFromDiscovery(baseName, htmlPath, metadata);
932
+ }
933
+ }
934
+ }
935
+ } catch (error) {
936
+ console.warn(`[Pancake] Could not discover views from ${dir}:`, error);
937
+ }
938
+ return views;
939
+ }
940
+ async function fileExists(filePath) {
941
+ try {
942
+ await fs2__namespace.access(filePath);
943
+ return true;
944
+ } catch {
945
+ return false;
946
+ }
947
+ }
948
+ function fileExistsSync(filePath) {
949
+ try {
950
+ fsSync__namespace.accessSync(filePath);
951
+ return true;
952
+ } catch {
953
+ return false;
954
+ }
955
+ }
956
+ async function parseMetadata(filePath) {
957
+ const content = await fs2__namespace.readFile(filePath, "utf-8");
958
+ return JSON.parse(content);
959
+ }
960
+ function parseMetadataSync(filePath) {
961
+ const content = fsSync__namespace.readFileSync(filePath, "utf-8");
962
+ return JSON.parse(content);
963
+ }
964
+ function createViewFromDiscovery(name, htmlPath, metadata) {
965
+ return {
966
+ description: metadata.description,
967
+ // Note: input/data from JSON are JSON Schema, not Zod
968
+ // We'll need to handle this specially in the normalizer
969
+ visibility: metadata.visibility ?? "both",
970
+ ui: {
971
+ html: htmlPath,
972
+ name
973
+ }
974
+ };
975
+ }
976
+
977
+ exports.createApp = createApp;
978
+ exports.discoverViews = discoverViews;
979
+ exports.discoverViewsAsync = discoverViewsAsync;
980
+ exports.isActionConfig = isActionConfig;
981
+ exports.isDataConfig = isDataConfig;
982
+ exports.isToolConfig = isToolConfig;
983
+ exports.isViewConfig = isViewConfig;
984
+ //# sourceMappingURL=index.cjs.map
985
+ //# sourceMappingURL=index.cjs.map