@tuongaz/seeflow 0.1.57 → 0.1.61
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/web/assets/{index-CPlccVLi.js → index-BXYHeBKM.js} +1 -1
- package/dist/web/assets/{index.es-CYTTDW0Q.js → index.es-BzG6d4Ro.js} +1 -1
- package/dist/web/assets/{jspdf.es.min-DOaPC0dc.js → jspdf.es.min-CcOxqEhi.js} +3 -3
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/src/api.ts +18 -0
- package/src/cli-manifest.ts +26 -0
- package/src/cli.ts +21 -0
- package/src/mcp.ts +38 -0
- package/src/schema-catalog.ts +120 -0
- package/src/schema.ts +108 -71
- package/src/server.ts +3 -1
package/src/cli.ts
CHANGED
|
@@ -157,6 +157,8 @@ if (argv.includes('--version') || argv.includes('-v')) {
|
|
|
157
157
|
await runConnectorsDelete();
|
|
158
158
|
} else if (sub === 'validate') {
|
|
159
159
|
await runValidate();
|
|
160
|
+
} else if (sub === 'schema') {
|
|
161
|
+
await runSchema();
|
|
160
162
|
} else if (sub === 'e2e') {
|
|
161
163
|
await runE2e();
|
|
162
164
|
} else {
|
|
@@ -196,6 +198,8 @@ Commands (work without a running studio):
|
|
|
196
198
|
connectors:patch <id> <connId> Patch a connector (--json/--file/--stdin)
|
|
197
199
|
connectors:delete <id> <connId> Delete a connector
|
|
198
200
|
validate Schema-validate a flow.json (--file <file> [--style <file>])
|
|
201
|
+
schema [<category>] Get the flow.json schema. No arg → category index;
|
|
202
|
+
category arg → full JSON Schema(s) for that category
|
|
199
203
|
|
|
200
204
|
Commands (require a running studio):
|
|
201
205
|
flows:play <id> <n> Trigger a play on node <n>
|
|
@@ -251,6 +255,7 @@ async function runHelp() {
|
|
|
251
255
|
}
|
|
252
256
|
|
|
253
257
|
async function runStart() {
|
|
258
|
+
mkdirSync(seeflowHome(), { recursive: true });
|
|
254
259
|
const config = readConfig();
|
|
255
260
|
const portArg = flagValue('port');
|
|
256
261
|
// --port wins; otherwise always fall back to the schema default (not the
|
|
@@ -800,6 +805,22 @@ async function runValidate() {
|
|
|
800
805
|
printOk(body);
|
|
801
806
|
}
|
|
802
807
|
|
|
808
|
+
async function runSchema() {
|
|
809
|
+
const category = argv[1] && !argv[1].startsWith('--') ? argv[1] : undefined;
|
|
810
|
+
const { listSchemaCategories, getSchemaCategory } = await import('./schema-catalog.ts');
|
|
811
|
+
if (!category) {
|
|
812
|
+
printOk({ categories: listSchemaCategories() });
|
|
813
|
+
}
|
|
814
|
+
const payload = getSchemaCategory(category as string);
|
|
815
|
+
if (!payload) {
|
|
816
|
+
const available = listSchemaCategories().map((c) => c.name);
|
|
817
|
+
const message = `unknown schema category: ${category}`;
|
|
818
|
+
process.stderr.write(`${JSON.stringify({ error: message, code: 'notFound', available })}\n`);
|
|
819
|
+
process.exit(3);
|
|
820
|
+
}
|
|
821
|
+
printOk({ name: category, schemas: payload.schemas, notes: payload.notes });
|
|
822
|
+
}
|
|
823
|
+
|
|
803
824
|
async function runE2e() {
|
|
804
825
|
const flowId = requireArg(1, '<flowId>');
|
|
805
826
|
const skipNodesRaw = flagValue('skip-nodes');
|
package/src/mcp.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
flowBulkNonEmpty,
|
|
21
21
|
} from './operations.ts';
|
|
22
22
|
import type { Registry } from './registry.ts';
|
|
23
|
+
import { getSchemaCategory, listSchemaCategories, schemaCategoryNames } from './schema-catalog.ts';
|
|
23
24
|
import type { FlowWatcher } from './watcher.ts';
|
|
24
25
|
|
|
25
26
|
export interface CreateMcpServerOptions {
|
|
@@ -193,6 +194,43 @@ const buildTools = (ops: Operations): McpTool[] => [
|
|
|
193
194
|
return okResult(result.data);
|
|
194
195
|
},
|
|
195
196
|
},
|
|
197
|
+
{
|
|
198
|
+
name: 'seeflow_schema',
|
|
199
|
+
description:
|
|
200
|
+
'Get the SeeFlow flow.json schema. Call with no args for a category index; ' +
|
|
201
|
+
"call with `name` for one category's full JSON Schemas. Use this to learn " +
|
|
202
|
+
'what a node, connector, action, or flow envelope looks like before authoring ' +
|
|
203
|
+
'writes. Categories: `flow`, `node`, `connector`, `action`, `style`.',
|
|
204
|
+
inputSchema: {
|
|
205
|
+
type: 'object',
|
|
206
|
+
properties: {
|
|
207
|
+
name: {
|
|
208
|
+
type: 'string',
|
|
209
|
+
description: 'Optional category name. Omit for the index.',
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
additionalProperties: false,
|
|
213
|
+
},
|
|
214
|
+
handler: async (args) => {
|
|
215
|
+
const name =
|
|
216
|
+
args && typeof args === 'object' && !Array.isArray(args)
|
|
217
|
+
? (args as { name?: unknown }).name
|
|
218
|
+
: undefined;
|
|
219
|
+
if (name === undefined || name === null || name === '') {
|
|
220
|
+
return okResult({ categories: listSchemaCategories() });
|
|
221
|
+
}
|
|
222
|
+
if (typeof name !== 'string') {
|
|
223
|
+
return errorResult('Invalid arguments: `name` must be a string when present');
|
|
224
|
+
}
|
|
225
|
+
const payload = getSchemaCategory(name);
|
|
226
|
+
if (!payload) {
|
|
227
|
+
return errorResult(
|
|
228
|
+
`unknown schema category: ${name} (available: ${schemaCategoryNames().join(', ')})`,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
return okResult({ name, schemas: payload.schemas, notes: payload.notes });
|
|
232
|
+
},
|
|
233
|
+
},
|
|
196
234
|
{
|
|
197
235
|
name: 'validate_seeflow',
|
|
198
236
|
description:
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// Single source of truth for runtime schema introspection. The CLI
|
|
2
|
+
// (`seeflow schema`), the MCP tool (`seeflow_schema`), and the REST routes
|
|
3
|
+
// (`GET /api/schema[/:name]`) all delegate here so the agent-facing surface
|
|
4
|
+
// stays in lockstep with the on-disk Zod schemas in schema.ts. Built once at
|
|
5
|
+
// module load — each call returns a fresh shallow copy so callers can't
|
|
6
|
+
// mutate the cached payload.
|
|
7
|
+
|
|
8
|
+
import type { ZodTypeAny } from 'zod';
|
|
9
|
+
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
10
|
+
import {
|
|
11
|
+
FlowDefaultConnectorSchema,
|
|
12
|
+
FlowEnvelopeSchema,
|
|
13
|
+
FlowEventConnectorSchema,
|
|
14
|
+
FlowHtmlNodeSchema,
|
|
15
|
+
FlowHttpConnectorSchema,
|
|
16
|
+
FlowIconNodeSchema,
|
|
17
|
+
FlowImageNodeSchema,
|
|
18
|
+
FlowPlayNodeSchema,
|
|
19
|
+
FlowQueueConnectorSchema,
|
|
20
|
+
FlowShapeNodeSchema,
|
|
21
|
+
FlowStateNodeSchema,
|
|
22
|
+
PlayActionSchema,
|
|
23
|
+
ResetActionSchema,
|
|
24
|
+
StatusActionSchema,
|
|
25
|
+
StatusReportSchema,
|
|
26
|
+
StyleSchema,
|
|
27
|
+
} from './schema.ts';
|
|
28
|
+
|
|
29
|
+
export interface SchemaCategory {
|
|
30
|
+
name: string;
|
|
31
|
+
description: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface SchemaPayload {
|
|
35
|
+
schemas: Record<string, unknown>;
|
|
36
|
+
notes: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Draft-07 pin matches the widest tool support; the same target string is
|
|
40
|
+
// used by the MCP `tools/list` JSON Schemas (default in zod-to-json-schema)
|
|
41
|
+
// so consumers see one consistent dialect across the whole surface.
|
|
42
|
+
const toJsonSchema = (schema: ZodTypeAny): unknown =>
|
|
43
|
+
zodToJsonSchema(schema, { $refStrategy: 'none', target: 'jsonSchema7' });
|
|
44
|
+
|
|
45
|
+
const CATEGORIES: SchemaCategory[] = [
|
|
46
|
+
{ name: 'flow', description: 'Top-level flow.json envelope.' },
|
|
47
|
+
{
|
|
48
|
+
name: 'node',
|
|
49
|
+
description:
|
|
50
|
+
'All six node variants (playNode, stateNode, shapeNode, imageNode, iconNode, htmlNode).',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'connector',
|
|
54
|
+
description: 'All four connector kinds (http, event, queue, default).',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'action',
|
|
58
|
+
description: 'playAction, statusAction, resetAction, statusReport.',
|
|
59
|
+
},
|
|
60
|
+
{ name: 'style', description: 'style.json (studio-owned).' },
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
const PAYLOADS: Record<string, SchemaPayload> = {
|
|
64
|
+
flow: {
|
|
65
|
+
schemas: { flow: toJsonSchema(FlowEnvelopeSchema) },
|
|
66
|
+
notes: ['connectors[].source and connectors[].target must reference an existing nodes[].id.'],
|
|
67
|
+
},
|
|
68
|
+
node: {
|
|
69
|
+
schemas: {
|
|
70
|
+
playNode: toJsonSchema(FlowPlayNodeSchema),
|
|
71
|
+
stateNode: toJsonSchema(FlowStateNodeSchema),
|
|
72
|
+
shapeNode: toJsonSchema(FlowShapeNodeSchema),
|
|
73
|
+
imageNode: toJsonSchema(FlowImageNodeSchema),
|
|
74
|
+
iconNode: toJsonSchema(FlowIconNodeSchema),
|
|
75
|
+
htmlNode: toJsonSchema(FlowHtmlNodeSchema),
|
|
76
|
+
},
|
|
77
|
+
notes: [
|
|
78
|
+
"imageNode.data.path must start with 'nodes/<id>/'.",
|
|
79
|
+
"scriptPath in playAction/statusAction is relative to nodes/<nodeId>/ and may not contain '..' or absolute paths.",
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
connector: {
|
|
83
|
+
schemas: {
|
|
84
|
+
http: toJsonSchema(FlowHttpConnectorSchema),
|
|
85
|
+
event: toJsonSchema(FlowEventConnectorSchema),
|
|
86
|
+
queue: toJsonSchema(FlowQueueConnectorSchema),
|
|
87
|
+
default: toJsonSchema(FlowDefaultConnectorSchema),
|
|
88
|
+
},
|
|
89
|
+
notes: [],
|
|
90
|
+
},
|
|
91
|
+
action: {
|
|
92
|
+
schemas: {
|
|
93
|
+
playAction: toJsonSchema(PlayActionSchema),
|
|
94
|
+
statusAction: toJsonSchema(StatusActionSchema),
|
|
95
|
+
resetAction: toJsonSchema(ResetActionSchema),
|
|
96
|
+
statusReport: toJsonSchema(StatusReportSchema),
|
|
97
|
+
},
|
|
98
|
+
notes: [
|
|
99
|
+
"scriptPath in playAction/statusAction is relative to nodes/<nodeId>/ and may not contain '..' or absolute paths.",
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
style: {
|
|
103
|
+
schemas: { style: toJsonSchema(StyleSchema) },
|
|
104
|
+
notes: [],
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export function listSchemaCategories(): SchemaCategory[] {
|
|
109
|
+
return CATEGORIES.map((c) => ({ ...c }));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function getSchemaCategory(name: string): SchemaPayload | null {
|
|
113
|
+
const payload = PAYLOADS[name];
|
|
114
|
+
if (!payload) return null;
|
|
115
|
+
return { schemas: { ...payload.schemas }, notes: [...payload.notes] };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function schemaCategoryNames(): string[] {
|
|
119
|
+
return CATEGORIES.map((c) => c.name);
|
|
120
|
+
}
|
package/src/schema.ts
CHANGED
|
@@ -84,7 +84,7 @@ export const PlayActionSchema = ScriptActionSchema;
|
|
|
84
84
|
// invoked from the /reset endpoint. The studio kills every live play and
|
|
85
85
|
// status script for the demo before running this script, so the running app
|
|
86
86
|
// sees a clean baseline when wiping its state.
|
|
87
|
-
const ResetActionSchema = ScriptActionSchema;
|
|
87
|
+
export const ResetActionSchema = ScriptActionSchema;
|
|
88
88
|
|
|
89
89
|
// Long-running status script. Same spawn shape as ScriptAction (interpreter +
|
|
90
90
|
// args + scriptPath) but no stdin payload and a much longer max lifetime since
|
|
@@ -524,49 +524,61 @@ const FlowNodeBaseShape = {
|
|
|
524
524
|
id: z.string().min(1),
|
|
525
525
|
};
|
|
526
526
|
|
|
527
|
+
export const FlowPlayNodeSchema = z
|
|
528
|
+
.object({
|
|
529
|
+
...FlowNodeBaseShape,
|
|
530
|
+
type: z.literal('playNode'),
|
|
531
|
+
data: FlowPlayNodeDataSchema,
|
|
532
|
+
})
|
|
533
|
+
.strict();
|
|
534
|
+
|
|
535
|
+
export const FlowStateNodeSchema = z
|
|
536
|
+
.object({
|
|
537
|
+
...FlowNodeBaseShape,
|
|
538
|
+
type: z.literal('stateNode'),
|
|
539
|
+
data: FlowStateNodeDataSchema,
|
|
540
|
+
})
|
|
541
|
+
.strict();
|
|
542
|
+
|
|
543
|
+
export const FlowShapeNodeSchema = z
|
|
544
|
+
.object({
|
|
545
|
+
...FlowNodeBaseShape,
|
|
546
|
+
type: z.literal('shapeNode'),
|
|
547
|
+
data: FlowShapeNodeDataSchema,
|
|
548
|
+
})
|
|
549
|
+
.strict();
|
|
550
|
+
|
|
551
|
+
export const FlowImageNodeSchema = z
|
|
552
|
+
.object({
|
|
553
|
+
...FlowNodeBaseShape,
|
|
554
|
+
type: z.literal('imageNode'),
|
|
555
|
+
data: FlowImageNodeDataSchema,
|
|
556
|
+
})
|
|
557
|
+
.strict();
|
|
558
|
+
|
|
559
|
+
export const FlowIconNodeSchema = z
|
|
560
|
+
.object({
|
|
561
|
+
...FlowNodeBaseShape,
|
|
562
|
+
type: z.literal('iconNode'),
|
|
563
|
+
data: FlowIconNodeDataSchema,
|
|
564
|
+
})
|
|
565
|
+
.strict();
|
|
566
|
+
|
|
567
|
+
export const FlowHtmlNodeSchema = z
|
|
568
|
+
.object({
|
|
569
|
+
...FlowNodeBaseShape,
|
|
570
|
+
type: z.literal('htmlNode'),
|
|
571
|
+
data: FlowHtmlNodeDataSchema,
|
|
572
|
+
})
|
|
573
|
+
.strict();
|
|
574
|
+
|
|
527
575
|
const FlowNodeSchema = z.discriminatedUnion('type', [
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
.strict(),
|
|
535
|
-
z
|
|
536
|
-
.object({
|
|
537
|
-
...FlowNodeBaseShape,
|
|
538
|
-
type: z.literal('stateNode'),
|
|
539
|
-
data: FlowStateNodeDataSchema,
|
|
540
|
-
})
|
|
541
|
-
.strict(),
|
|
542
|
-
z
|
|
543
|
-
.object({
|
|
544
|
-
...FlowNodeBaseShape,
|
|
545
|
-
type: z.literal('shapeNode'),
|
|
546
|
-
data: FlowShapeNodeDataSchema,
|
|
547
|
-
})
|
|
548
|
-
.strict(),
|
|
549
|
-
z
|
|
550
|
-
.object({
|
|
551
|
-
...FlowNodeBaseShape,
|
|
552
|
-
type: z.literal('imageNode'),
|
|
553
|
-
data: FlowImageNodeDataSchema,
|
|
554
|
-
})
|
|
555
|
-
.strict(),
|
|
556
|
-
z
|
|
557
|
-
.object({
|
|
558
|
-
...FlowNodeBaseShape,
|
|
559
|
-
type: z.literal('iconNode'),
|
|
560
|
-
data: FlowIconNodeDataSchema,
|
|
561
|
-
})
|
|
562
|
-
.strict(),
|
|
563
|
-
z
|
|
564
|
-
.object({
|
|
565
|
-
...FlowNodeBaseShape,
|
|
566
|
-
type: z.literal('htmlNode'),
|
|
567
|
-
data: FlowHtmlNodeDataSchema,
|
|
568
|
-
})
|
|
569
|
-
.strict(),
|
|
576
|
+
FlowPlayNodeSchema,
|
|
577
|
+
FlowStateNodeSchema,
|
|
578
|
+
FlowShapeNodeSchema,
|
|
579
|
+
FlowImageNodeSchema,
|
|
580
|
+
FlowIconNodeSchema,
|
|
581
|
+
FlowHtmlNodeSchema,
|
|
570
582
|
]);
|
|
571
583
|
|
|
572
584
|
const FlowConnectorBaseShape = {
|
|
@@ -576,35 +588,43 @@ const FlowConnectorBaseShape = {
|
|
|
576
588
|
label: z.string().optional(),
|
|
577
589
|
};
|
|
578
590
|
|
|
591
|
+
export const FlowHttpConnectorSchema = z
|
|
592
|
+
.object({
|
|
593
|
+
...FlowConnectorBaseShape,
|
|
594
|
+
kind: z.literal('http'),
|
|
595
|
+
method: HttpMethodSchema.optional(),
|
|
596
|
+
url: z.string().min(1).optional(),
|
|
597
|
+
})
|
|
598
|
+
.strict();
|
|
599
|
+
|
|
600
|
+
export const FlowEventConnectorSchema = z
|
|
601
|
+
.object({
|
|
602
|
+
...FlowConnectorBaseShape,
|
|
603
|
+
kind: z.literal('event'),
|
|
604
|
+
eventName: z.string().min(1),
|
|
605
|
+
})
|
|
606
|
+
.strict();
|
|
607
|
+
|
|
608
|
+
export const FlowQueueConnectorSchema = z
|
|
609
|
+
.object({
|
|
610
|
+
...FlowConnectorBaseShape,
|
|
611
|
+
kind: z.literal('queue'),
|
|
612
|
+
queueName: z.string().min(1),
|
|
613
|
+
})
|
|
614
|
+
.strict();
|
|
615
|
+
|
|
616
|
+
export const FlowDefaultConnectorSchema = z
|
|
617
|
+
.object({
|
|
618
|
+
...FlowConnectorBaseShape,
|
|
619
|
+
kind: z.literal('default'),
|
|
620
|
+
})
|
|
621
|
+
.strict();
|
|
622
|
+
|
|
579
623
|
const FlowConnectorSchema = z.discriminatedUnion('kind', [
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
method: HttpMethodSchema.optional(),
|
|
585
|
-
url: z.string().min(1).optional(),
|
|
586
|
-
})
|
|
587
|
-
.strict(),
|
|
588
|
-
z
|
|
589
|
-
.object({
|
|
590
|
-
...FlowConnectorBaseShape,
|
|
591
|
-
kind: z.literal('event'),
|
|
592
|
-
eventName: z.string().min(1),
|
|
593
|
-
})
|
|
594
|
-
.strict(),
|
|
595
|
-
z
|
|
596
|
-
.object({
|
|
597
|
-
...FlowConnectorBaseShape,
|
|
598
|
-
kind: z.literal('queue'),
|
|
599
|
-
queueName: z.string().min(1),
|
|
600
|
-
})
|
|
601
|
-
.strict(),
|
|
602
|
-
z
|
|
603
|
-
.object({
|
|
604
|
-
...FlowConnectorBaseShape,
|
|
605
|
-
kind: z.literal('default'),
|
|
606
|
-
})
|
|
607
|
-
.strict(),
|
|
624
|
+
FlowHttpConnectorSchema,
|
|
625
|
+
FlowEventConnectorSchema,
|
|
626
|
+
FlowQueueConnectorSchema,
|
|
627
|
+
FlowDefaultConnectorSchema,
|
|
608
628
|
]);
|
|
609
629
|
|
|
610
630
|
export const FlowSchema = z
|
|
@@ -641,6 +661,23 @@ export type Flow = z.infer<typeof FlowSchema>;
|
|
|
641
661
|
export type FlowNode = z.infer<typeof FlowNodeSchema>;
|
|
642
662
|
export type FlowConnector = z.infer<typeof FlowConnectorSchema>;
|
|
643
663
|
|
|
664
|
+
// Envelope-only flow shape for the `seeflow schema flow` surface. The full
|
|
665
|
+
// FlowSchema validates the whole graph; this companion schema describes the
|
|
666
|
+
// top-level shape without inlining every node + connector variant, so the
|
|
667
|
+
// runtime-introspectable JSON Schema stays compact. Authors drill into
|
|
668
|
+
// `seeflow schema node` / `seeflow schema connector` for the per-variant
|
|
669
|
+
// shapes. Not used for validation — only the catalog reads it.
|
|
670
|
+
export const FlowEnvelopeSchema = z
|
|
671
|
+
.object({
|
|
672
|
+
version: z.literal(2),
|
|
673
|
+
name: z.string().min(1),
|
|
674
|
+
description: z.string().optional(),
|
|
675
|
+
resetAction: ResetActionSchema.optional(),
|
|
676
|
+
nodes: z.array(z.unknown().describe('See `seeflow schema node`')),
|
|
677
|
+
connectors: z.array(z.unknown().describe('See `seeflow schema connector`')),
|
|
678
|
+
})
|
|
679
|
+
.strict();
|
|
680
|
+
|
|
644
681
|
// =============================================================================
|
|
645
682
|
// Style schema — keyed map of presentation overrides, side-table by id.
|
|
646
683
|
// What lives on disk in <project>/.seeflow/style.json (optional file).
|
package/src/server.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
1
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
2
2
|
import { resolve as resolvePath } from 'node:path';
|
|
3
3
|
import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
4
4
|
import { Hono } from 'hono';
|
|
@@ -7,6 +7,7 @@ import { type ProxyFacade, createApi } from './api.ts';
|
|
|
7
7
|
import { createDemoRouter } from './demo.ts';
|
|
8
8
|
import { type EventBus, createEventBus } from './events.ts';
|
|
9
9
|
import { createMcpServer } from './mcp.ts';
|
|
10
|
+
import { seeflowHome } from './paths.ts';
|
|
10
11
|
import { type ProcessSpawner, defaultProcessSpawner } from './process-spawner.ts';
|
|
11
12
|
import { type RegistryWatcher, createRegistryWatcher } from './registry-watcher.ts';
|
|
12
13
|
import { type Registry, createRegistry } from './registry.ts';
|
|
@@ -208,6 +209,7 @@ export interface ServeOptions extends CreateAppOptions {
|
|
|
208
209
|
export function serve(options: ServeOptions = {}) {
|
|
209
210
|
const port = options.port ?? 4321;
|
|
210
211
|
const hostname = options.hostname ?? '0.0.0.0';
|
|
212
|
+
mkdirSync(seeflowHome(), { recursive: true });
|
|
211
213
|
const app = createApp(options);
|
|
212
214
|
return Bun.serve({ port, hostname, fetch: app.fetch });
|
|
213
215
|
}
|