@waniwani/kit 0.1.1 → 0.1.4
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/README.md +121 -22
- package/cli/codegen.mjs +36 -40
- package/cli/env.mjs +37 -0
- package/cli/framework.mjs +0 -1
- package/cli/index.mjs +31 -190
- package/cli/init.mjs +582 -0
- package/cli/log.mjs +5 -4
- package/cli/scan.mjs +42 -14
- package/cli/validate.mjs +81 -2
- package/dist/index.d.ts +40 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +7 -4
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +76 -49
- package/dist/server.js.map +1 -1
- package/package.json +7 -6
- package/src/index.ts +45 -7
- package/src/server.ts +79 -56
- package/cli/account.mjs +0 -264
- package/cli/tunnel.mjs +0 -140
package/cli/validate.mjs
CHANGED
|
@@ -8,9 +8,19 @@
|
|
|
8
8
|
|
|
9
9
|
import { readFileSync } from "node:fs";
|
|
10
10
|
import { relative } from "node:path";
|
|
11
|
+
import { loadAppEnv } from "./env.mjs";
|
|
11
12
|
|
|
12
13
|
const NAME_RE = /^[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?$/;
|
|
13
14
|
|
|
15
|
+
/**
|
|
16
|
+
* What may appear in an endpoint path segment. Deliberately narrower than what
|
|
17
|
+
* a filesystem allows: the segment becomes a URL path, and a file called
|
|
18
|
+
* `Book Call.ts` would be served at a URL nobody would guess.
|
|
19
|
+
*/
|
|
20
|
+
const SEGMENT_RE = /^[a-zA-Z0-9._-]+$/;
|
|
21
|
+
|
|
22
|
+
const HTTP_METHODS = new Set(["get", "post", "put", "patch", "delete", "head", "options"]);
|
|
23
|
+
|
|
14
24
|
class Report {
|
|
15
25
|
constructor(root) {
|
|
16
26
|
this.root = root;
|
|
@@ -47,11 +57,11 @@ function checkStructure(app, report) {
|
|
|
47
57
|
);
|
|
48
58
|
}
|
|
49
59
|
|
|
50
|
-
if (app.tools.length + app.widgets.length + app.flows.length
|
|
60
|
+
if (app.tools.length + app.widgets.length + app.flows.length === 0) {
|
|
51
61
|
report.error(
|
|
52
62
|
".",
|
|
53
63
|
"this app exposes nothing",
|
|
54
|
-
"add a tool (tools/<name>.ts), a widget (widgets/<name>/), a flow (flows/<name>.ts)
|
|
64
|
+
"add a tool (tools/<name>.ts), a widget (widgets/<name>/), or a flow (flows/<name>.ts)",
|
|
55
65
|
);
|
|
56
66
|
}
|
|
57
67
|
|
|
@@ -113,6 +123,49 @@ function checkStructure(app, report) {
|
|
|
113
123
|
seen.set(entry.name, entry);
|
|
114
124
|
}
|
|
115
125
|
|
|
126
|
+
// An endpoint's file position is its URL, so a segment that cannot appear in
|
|
127
|
+
// a URL is a file served somewhere unguessable, and two files resolving to
|
|
128
|
+
// one path means the second mount is dead — Express answers from the first.
|
|
129
|
+
const paths = new Map();
|
|
130
|
+
// The generator names one import per endpoint, camel-cased from the path, so
|
|
131
|
+
// two paths that camel-case alike (`api/cal-slots.ts`, `api/cal/slots.ts`)
|
|
132
|
+
// would emit the same identifier twice and fail in generated code the author
|
|
133
|
+
// cannot open.
|
|
134
|
+
const identifiers = new Map();
|
|
135
|
+
for (const endpoint of app.endpoints) {
|
|
136
|
+
const where = rel(root, endpoint.file);
|
|
137
|
+
|
|
138
|
+
const identifier = endpoint.segments.join("-").replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
|
|
139
|
+
const clash = identifiers.get(identifier);
|
|
140
|
+
if (clash) {
|
|
141
|
+
report.error(
|
|
142
|
+
where,
|
|
143
|
+
`this path generates the same import name as ${clash}`,
|
|
144
|
+
"rename one of the two — the generator derives an identifier from the path",
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
identifiers.set(identifier, where);
|
|
148
|
+
|
|
149
|
+
for (const segment of endpoint.segments) {
|
|
150
|
+
if (SEGMENT_RE.test(segment)) continue;
|
|
151
|
+
report.error(
|
|
152
|
+
where,
|
|
153
|
+
`"${segment}" cannot be part of a URL path`,
|
|
154
|
+
"use letters, digits, dashes, dots and underscores — the file's position is the endpoint's path",
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const previous = paths.get(endpoint.path);
|
|
159
|
+
if (previous) {
|
|
160
|
+
report.error(
|
|
161
|
+
where,
|
|
162
|
+
`${endpoint.path} is already served by ${previous}`,
|
|
163
|
+
"two files resolve to one path — Express answers from the first, so this one never runs",
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
paths.set(endpoint.path, where);
|
|
167
|
+
}
|
|
168
|
+
|
|
116
169
|
// Flows point at widgets by name. Catching a typo here beats catching it
|
|
117
170
|
// when a user is halfway through a conversation.
|
|
118
171
|
const widgetNames = new Set(app.widgets.map((w) => w.name));
|
|
@@ -188,6 +241,27 @@ async function checkModules(app, report) {
|
|
|
188
241
|
}
|
|
189
242
|
}
|
|
190
243
|
|
|
244
|
+
for (const endpoint of app.endpoints) {
|
|
245
|
+
const where = rel(root, endpoint.file);
|
|
246
|
+
const def = await load(endpoint.file, where, report);
|
|
247
|
+
if (!def) continue;
|
|
248
|
+
if (typeof def.handler !== "function") {
|
|
249
|
+
report.error(
|
|
250
|
+
where,
|
|
251
|
+
"endpoint is missing handler()",
|
|
252
|
+
"export default defineEndpoint({ handler: (req, res) => { ... } })",
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
for (const method of def.method ? [def.method].flat() : []) {
|
|
256
|
+
if (HTTP_METHODS.has(method)) continue;
|
|
257
|
+
report.error(
|
|
258
|
+
where,
|
|
259
|
+
`"${method}" is not an HTTP method`,
|
|
260
|
+
`one of: ${[...HTTP_METHODS].join(", ")}`,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
191
265
|
for (const flow of app.flows) {
|
|
192
266
|
const where = rel(root, flow.file);
|
|
193
267
|
const def = await load(flow.file, where, report);
|
|
@@ -238,6 +312,11 @@ async function load(file, where, report) {
|
|
|
238
312
|
}
|
|
239
313
|
|
|
240
314
|
export async function validateApp(app) {
|
|
315
|
+
// The check imports every server-safe module for real, and a module that
|
|
316
|
+
// builds a client at import time reads the environment while doing it. An app
|
|
317
|
+
// that runs fine would otherwise fail its own build check over a variable
|
|
318
|
+
// sitting in the file next to it.
|
|
319
|
+
loadAppEnv(app.root);
|
|
241
320
|
const report = new Report(app.root);
|
|
242
321
|
checkStructure(app, report);
|
|
243
322
|
// Importing broken modules produces noise on top of structural errors.
|
package/dist/index.d.ts
CHANGED
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
* widgets/<name>/widget.ts export default defineWidget({ ... })
|
|
13
13
|
* widgets/<name>/ui.tsx export default function Component() { ... }
|
|
14
14
|
* flows/<name>.ts export default createFlow({ ... }).compile()
|
|
15
|
-
*
|
|
15
|
+
* api/<path>.ts export default defineEndpoint({ ... })
|
|
16
16
|
*/
|
|
17
|
+
import type { RequestHandler } from "express";
|
|
17
18
|
import type { z } from "zod";
|
|
18
19
|
/** A Zod object shape — `{ name: z.string() }`, not `z.object({ ... })`. */
|
|
19
20
|
export type Shape = z.ZodRawShape;
|
|
@@ -104,10 +105,43 @@ export type WidgetDefinition<S extends Shape = Shape> = {
|
|
|
104
105
|
load?: (input: Infer<S>) => Infer<S> | Promise<Infer<S>>;
|
|
105
106
|
};
|
|
106
107
|
export declare function defineWidget<S extends Shape>(def: WidgetDefinition<S>): WidgetDefinition<S>;
|
|
107
|
-
/**
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
/**
|
|
109
|
+
* HTTP methods an endpoint can be restricted to. `undefined` accepts every
|
|
110
|
+
* method, which is what an Express `use()` mount does.
|
|
111
|
+
*/
|
|
112
|
+
export type HttpMethod = "get" | "post" | "put" | "patch" | "delete" | "head" | "options";
|
|
113
|
+
/**
|
|
114
|
+
* A plain HTTP endpoint served by the same server as the MCP tools.
|
|
115
|
+
*
|
|
116
|
+
* This exists for the browser, not for the model. A widget runs in a
|
|
117
|
+
* cross-origin iframe and can `fetch()` its own server at
|
|
118
|
+
* `window.skybridge.serverUrl` — for a booking, a price lookup, a webhook
|
|
119
|
+
* receiver — and the model never sees the call. Anything the *model* should be
|
|
120
|
+
* able to reach belongs in `tools/`, not here.
|
|
121
|
+
*
|
|
122
|
+
* The path comes from the file's location, `/api` prefix included:
|
|
123
|
+
* `api/cal/slots.ts` is served at `/api/cal/slots`.
|
|
124
|
+
*/
|
|
125
|
+
export type EndpointDefinition = {
|
|
126
|
+
/**
|
|
127
|
+
* Restrict the endpoint to these methods, answering anything else with 405.
|
|
128
|
+
* Defaults to accepting every method.
|
|
129
|
+
*/
|
|
130
|
+
method?: HttpMethod | HttpMethod[];
|
|
131
|
+
/**
|
|
132
|
+
* Answer cross-origin requests, preflight included. On by default: a widget
|
|
133
|
+
* is served from a different origin than the server it calls, so an endpoint
|
|
134
|
+
* without CORS is one the widget cannot reach.
|
|
135
|
+
*/
|
|
136
|
+
cors?: boolean;
|
|
137
|
+
/**
|
|
138
|
+
* Parse a JSON request body into `req.body`. On by default — the framework
|
|
139
|
+
* installs no body parser of its own, so an endpoint without this reads
|
|
140
|
+
* `req.body` as `undefined`.
|
|
141
|
+
*/
|
|
142
|
+
json?: boolean;
|
|
143
|
+
/** An Express handler. Throwing is safe: the runtime answers 500 and logs. */
|
|
144
|
+
handler: RequestHandler;
|
|
112
145
|
};
|
|
146
|
+
export declare function defineEndpoint(def: EndpointDefinition): EndpointDefinition;
|
|
113
147
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* widgets/<name>/widget.ts export default defineWidget({ ... })
|
|
13
13
|
* widgets/<name>/ui.tsx export default function Component() { ... }
|
|
14
14
|
* flows/<name>.ts export default createFlow({ ... }).compile()
|
|
15
|
-
*
|
|
15
|
+
* api/<path>.ts export default defineEndpoint({ ... })
|
|
16
16
|
*/
|
|
17
17
|
export function defineApp(config) {
|
|
18
18
|
return config;
|
|
@@ -23,4 +23,7 @@ export function defineTool(def) {
|
|
|
23
23
|
export function defineWidget(def) {
|
|
24
24
|
return def;
|
|
25
25
|
}
|
|
26
|
+
export function defineEndpoint(def) {
|
|
27
|
+
return def;
|
|
28
|
+
}
|
|
26
29
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AA2CH,MAAM,UAAU,SAAS,CAAC,MAAiB;IAC1C,OAAO,MAAM,CAAC;AACf,CAAC;AAwBD,MAAM,UAAU,UAAU,CACzB,GAAyB;IAEzB,OAAO,GAAG,CAAC;AACZ,CAAC;AAuCD,MAAM,UAAU,YAAY,CAAkB,GAAwB;IACrE,OAAO,GAAG,CAAC;AACZ,CAAC;AA4CD,MAAM,UAAU,cAAc,CAAC,GAAuB;IACrD,OAAO,GAAG,CAAC;AACZ,CAAC"}
|
package/dist/server.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* hands them to `registerApp()`. Nothing else.
|
|
12
12
|
*/
|
|
13
13
|
import { McpServer } from "skybridge/server";
|
|
14
|
-
import type {
|
|
14
|
+
import type { EndpointDefinition, Shape, ToolHints, WidgetCsp } from "./index.js";
|
|
15
15
|
/**
|
|
16
16
|
* The manifest holds definitions with unrelated schemas side by side, so the
|
|
17
17
|
* handler signatures are widened here. `never` in the parameter position
|
|
@@ -50,7 +50,11 @@ export type Manifest = {
|
|
|
50
50
|
def: AnyWidgetDefinition;
|
|
51
51
|
}>;
|
|
52
52
|
flows: CompiledFlow[];
|
|
53
|
-
|
|
53
|
+
/** HTTP endpoints, each with the path its file position produced. */
|
|
54
|
+
endpoints?: Array<{
|
|
55
|
+
path: string;
|
|
56
|
+
def: EndpointDefinition;
|
|
57
|
+
}>;
|
|
54
58
|
/**
|
|
55
59
|
* Origins the template's Tailwind entry loads from, read off it at build
|
|
56
60
|
* time. Every view imports that stylesheet, so every widget needs them.
|
|
@@ -58,8 +62,7 @@ export type Manifest = {
|
|
|
58
62
|
styleDomains?: string[];
|
|
59
63
|
};
|
|
60
64
|
/**
|
|
61
|
-
* Register an app's tools, widgets
|
|
62
|
-
* built.
|
|
65
|
+
* Register an app's tools, widgets and flows onto a server the template built.
|
|
63
66
|
*
|
|
64
67
|
* The template owns construction, its own tools, `withWaniwani`, and `run()`.
|
|
65
68
|
* This adds to that server rather than replacing it, so a tool the template
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,EAAE,SAAS,EAAiB,MAAM,kBAAkB,CAAC;AAC5D,OAAO,KAAK,EAAE,kBAAkB,EAAc,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE9F;;;;GAIG;AACH,KAAK,iBAAiB,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,GAAG,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;CAC/B,CAAC;AAEF,KAAK,mBAAmB,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,KAAK,CAAC;IACZ,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,MAAM,CAAC;IAClC,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;CACjC,CAAC;AAEF,+EAA+E;AAC/E,MAAM,MAAM,YAAY,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IAEb,MAAM,EAAE,GAAG,CAAC;IAEZ,OAAO,EAAE,GAAG,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACtB,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,iBAAiB,CAAA;KAAE,CAAC,CAAC;IACvD,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,mBAAmB,CAAA;KAAE,CAAC,CAAC;IAC3D,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,qEAAqE;IACrE,SAAS,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,kBAAkB,CAAA;KAAE,CAAC,CAAC;IAC7D;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AA6IF;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,CAgG3F"}
|
package/dist/server.js
CHANGED
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
* against that seam — `src/waniwani.ts` — which imports the app's modules and
|
|
11
11
|
* hands them to `registerApp()`. Nothing else.
|
|
12
12
|
*/
|
|
13
|
+
import cors from "cors";
|
|
14
|
+
import express, {} from "express";
|
|
13
15
|
import { McpServer } from "skybridge/server";
|
|
14
|
-
import { z } from "zod";
|
|
15
16
|
/**
|
|
16
17
|
* The origins a widget may load assets from: its own, plus the ones its
|
|
17
18
|
* stylesheet needs.
|
|
@@ -74,52 +75,76 @@ function widgetError(name, error) {
|
|
|
74
75
|
],
|
|
75
76
|
};
|
|
76
77
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Answer anything outside the declared methods with 405 rather than running the
|
|
80
|
+
* handler. An endpoint is mounted with `use()`, which matches every method, so
|
|
81
|
+
* without this a `GET /api/cal/book` would reach a handler written for a POST
|
|
82
|
+
* body and fail somewhere less obvious.
|
|
83
|
+
*/
|
|
84
|
+
function methodGuard(allowed) {
|
|
85
|
+
// A preflight never carries the real method, and answering it 405 blocks the
|
|
86
|
+
// request it was asking about.
|
|
87
|
+
const pass = new Set([...allowed, "OPTIONS"]);
|
|
88
|
+
return (req, res, next) => {
|
|
89
|
+
if (pass.has(req.method))
|
|
90
|
+
return next();
|
|
91
|
+
res.setHeader("Allow", allowed.join(", "));
|
|
92
|
+
res.status(405).json({ error: `${req.method} not allowed` });
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The last link in every endpoint's chain: a 4-arity middleware, which is how
|
|
97
|
+
* Express recognises an error handler.
|
|
98
|
+
*
|
|
99
|
+
* Scoped to the endpoint's own mount path rather than the app, so it cannot
|
|
100
|
+
* catch anything the endpoint did not cause. Without it a rejected handler — or
|
|
101
|
+
* a malformed JSON body, which the parser reports the same way — reaches
|
|
102
|
+
* Express's default handler and answers an HTML error page to a `fetch()` that
|
|
103
|
+
* is waiting for JSON.
|
|
104
|
+
*/
|
|
105
|
+
function endpointErrorHandler(path) {
|
|
106
|
+
return (error, _req, res, next) => {
|
|
107
|
+
console.error(`[waniwani] endpoint "${path}" failed:`, error);
|
|
108
|
+
if (res.headersSent)
|
|
109
|
+
return next(error);
|
|
110
|
+
// `express.json()` rejects a malformed body with a 400 already on the error.
|
|
111
|
+
const status = typeof error?.status === "number"
|
|
112
|
+
? error.status
|
|
113
|
+
: 500;
|
|
114
|
+
res.status(status).json({
|
|
115
|
+
error: error instanceof Error ? error.message : "Internal server error",
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Mount an app's HTTP endpoints on the server's Express app.
|
|
121
|
+
*
|
|
122
|
+
* Everything the framework does not do for us is done here, once, rather than
|
|
123
|
+
* left to each endpoint to remember: CORS (a widget calls from another origin),
|
|
124
|
+
* a JSON body parser (the framework installs none, so `req.body` would be
|
|
125
|
+
* `undefined`), the method guard, and the error envelope.
|
|
126
|
+
*/
|
|
127
|
+
function registerEndpoints(server, endpoints) {
|
|
128
|
+
for (const { path, def } of endpoints) {
|
|
129
|
+
const methods = def.method ? [def.method].flat().map((m) => m.toUpperCase()) : undefined;
|
|
130
|
+
const chain = [];
|
|
131
|
+
// The preflight answer names the methods the guard below actually accepts.
|
|
132
|
+
// Browsers cache it, so advertising a method that then answers 405 is a
|
|
133
|
+
// contradiction the widget author has to debug twice.
|
|
134
|
+
if (def.cors !== false)
|
|
135
|
+
chain.push(cors(methods ? { methods } : undefined));
|
|
136
|
+
if (def.json !== false)
|
|
137
|
+
chain.push(express.json());
|
|
138
|
+
if (methods)
|
|
139
|
+
chain.push(methodGuard(methods));
|
|
140
|
+
chain.push(def.handler, endpointErrorHandler(path));
|
|
141
|
+
// The error handler's arity is what makes Express treat it as one, and
|
|
142
|
+
// `use()` is typed for request handlers only.
|
|
143
|
+
server.use(path, ...chain);
|
|
144
|
+
}
|
|
119
145
|
}
|
|
120
146
|
/**
|
|
121
|
-
* Register an app's tools, widgets
|
|
122
|
-
* built.
|
|
147
|
+
* Register an app's tools, widgets and flows onto a server the template built.
|
|
123
148
|
*
|
|
124
149
|
* The template owns construction, its own tools, `withWaniwani`, and `run()`.
|
|
125
150
|
* This adds to that server rather than replacing it, so a tool the template
|
|
@@ -127,7 +152,12 @@ function registerDocsTool(server, docs) {
|
|
|
127
152
|
* app's own tools sit alongside it.
|
|
128
153
|
*/
|
|
129
154
|
export async function registerApp(server, manifest) {
|
|
130
|
-
const { tools, widgets, flows,
|
|
155
|
+
const { tools, widgets, flows, endpoints = [], styleDomains = [] } = manifest;
|
|
156
|
+
// Before the tools, because Express matches in registration order and the
|
|
157
|
+
// framework mounts `/mcp` after this function returns. Nothing here can
|
|
158
|
+
// shadow it — `/api/...` and `/mcp` do not overlap — but the ordering is the
|
|
159
|
+
// reason an endpoint is reachable at all.
|
|
160
|
+
registerEndpoints(server, endpoints);
|
|
131
161
|
// Widgets: one `data` schema drives the input schema, the structured output,
|
|
132
162
|
// and the type the component receives.
|
|
133
163
|
//
|
|
@@ -201,9 +231,6 @@ export async function registerApp(server, manifest) {
|
|
|
201
231
|
for (const flow of flows) {
|
|
202
232
|
server.registerTool({ ...flow.config, name: flow.name }, flow.handler);
|
|
203
233
|
}
|
|
204
|
-
if (docs.length > 0) {
|
|
205
|
-
registerDocsTool(server, docs);
|
|
206
|
-
}
|
|
207
234
|
// `withWaniwani` is deliberately not called here. It wraps every registered
|
|
208
235
|
// handler in place, so it has to run after the last registration — which is
|
|
209
236
|
// the template's, not this function's. `src/server.ts` calls it.
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@waniwani/kit",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Build an MCP app as a folder: tools, widgets
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "Build an MCP app as a folder: tools, widgets and flows, with one CLI and one shared server runtime.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"waniwani": "cli/index.mjs"
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"//files": "`src` ships alongside `dist` on purpose: `waniwani eject` vendors the runtime as readable TypeScript, and it reads it out of the installed package.",
|
|
26
26
|
"//dependencies": "tsx, typescript and the @types packages are here rather than in devDependencies because the underlying framework shells out to tsc and tsx by bare name and resolves types from the app repo tree, while declaring none of them. An app repo owns no build config, so this package is the only thing that can put them there. nodemon is the fourth of that set and arrives on its own, as a peer of it.",
|
|
27
|
+
"//express": "express and cors back the api/ convention: the runtime mounts each endpoint with a JSON body parser and CORS, and an app's handlers are typed against express. Both are already skybridge's own dependencies at these ranges, so declaring them here adds no second copy — it stops an app from depending on a transitive hoist.",
|
|
27
28
|
"files": [
|
|
28
29
|
"dist",
|
|
29
30
|
"src",
|
|
@@ -41,12 +42,16 @@
|
|
|
41
42
|
"dependencies": {
|
|
42
43
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
43
44
|
"@tailwindcss/vite": "^4.3.3",
|
|
45
|
+
"@types/cors": "^2.8.19",
|
|
46
|
+
"@types/express": "^5.0.6",
|
|
44
47
|
"@types/node": "^24.12.0",
|
|
45
48
|
"@types/react": "^19.2.14",
|
|
46
49
|
"@types/react-dom": "^19.2.3",
|
|
47
50
|
"@vitejs/plugin-react": "^6.0.3",
|
|
48
51
|
"@waniwani/sdk": "^0.19.5",
|
|
52
|
+
"cors": "^2.8.6",
|
|
49
53
|
"dotenv": "^17.4.1",
|
|
54
|
+
"express": "^5.2.1",
|
|
50
55
|
"skybridge": "^1.3.5",
|
|
51
56
|
"tailwindcss": "^4.3.3",
|
|
52
57
|
"tsx": "^4.20.6",
|
|
@@ -56,10 +61,6 @@
|
|
|
56
61
|
"devDependencies": {
|
|
57
62
|
"@skybridge/devtools": "^1.2.7"
|
|
58
63
|
},
|
|
59
|
-
"//optionalDependencies": "cloudflared is what `waniwani tunnel` runs, and its install step fetches a platform binary. Every other command works without it, so a CI image or a container build installing with --omit=optional skips the download and still gets a working CLI.",
|
|
60
|
-
"optionalDependencies": {
|
|
61
|
-
"cloudflared": "^0.7.1"
|
|
62
|
-
},
|
|
63
64
|
"peerDependencies": {
|
|
64
65
|
"react": ">=19",
|
|
65
66
|
"react-dom": ">=19",
|
package/src/index.ts
CHANGED
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
* widgets/<name>/widget.ts export default defineWidget({ ... })
|
|
13
13
|
* widgets/<name>/ui.tsx export default function Component() { ... }
|
|
14
14
|
* flows/<name>.ts export default createFlow({ ... }).compile()
|
|
15
|
-
*
|
|
15
|
+
* api/<path>.ts export default defineEndpoint({ ... })
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import type { RequestHandler } from "express";
|
|
18
19
|
import type { z } from "zod";
|
|
19
20
|
|
|
20
21
|
/** A Zod object shape — `{ name: z.string() }`, not `z.object({ ... })`. */
|
|
@@ -128,11 +129,48 @@ export function defineWidget<S extends Shape>(def: WidgetDefinition<S>): WidgetD
|
|
|
128
129
|
return def;
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
//
|
|
132
|
+
// ------------------------------------------------------------------ endpoints
|
|
132
133
|
|
|
133
|
-
/**
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
/**
|
|
135
|
+
* HTTP methods an endpoint can be restricted to. `undefined` accepts every
|
|
136
|
+
* method, which is what an Express `use()` mount does.
|
|
137
|
+
*/
|
|
138
|
+
export type HttpMethod = "get" | "post" | "put" | "patch" | "delete" | "head" | "options";
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* A plain HTTP endpoint served by the same server as the MCP tools.
|
|
142
|
+
*
|
|
143
|
+
* This exists for the browser, not for the model. A widget runs in a
|
|
144
|
+
* cross-origin iframe and can `fetch()` its own server at
|
|
145
|
+
* `window.skybridge.serverUrl` — for a booking, a price lookup, a webhook
|
|
146
|
+
* receiver — and the model never sees the call. Anything the *model* should be
|
|
147
|
+
* able to reach belongs in `tools/`, not here.
|
|
148
|
+
*
|
|
149
|
+
* The path comes from the file's location, `/api` prefix included:
|
|
150
|
+
* `api/cal/slots.ts` is served at `/api/cal/slots`.
|
|
151
|
+
*/
|
|
152
|
+
export type EndpointDefinition = {
|
|
153
|
+
/**
|
|
154
|
+
* Restrict the endpoint to these methods, answering anything else with 405.
|
|
155
|
+
* Defaults to accepting every method.
|
|
156
|
+
*/
|
|
157
|
+
method?: HttpMethod | HttpMethod[];
|
|
158
|
+
/**
|
|
159
|
+
* Answer cross-origin requests, preflight included. On by default: a widget
|
|
160
|
+
* is served from a different origin than the server it calls, so an endpoint
|
|
161
|
+
* without CORS is one the widget cannot reach.
|
|
162
|
+
*/
|
|
163
|
+
cors?: boolean;
|
|
164
|
+
/**
|
|
165
|
+
* Parse a JSON request body into `req.body`. On by default — the framework
|
|
166
|
+
* installs no body parser of its own, so an endpoint without this reads
|
|
167
|
+
* `req.body` as `undefined`.
|
|
168
|
+
*/
|
|
169
|
+
json?: boolean;
|
|
170
|
+
/** An Express handler. Throwing is safe: the runtime answers 500 and logs. */
|
|
171
|
+
handler: RequestHandler;
|
|
138
172
|
};
|
|
173
|
+
|
|
174
|
+
export function defineEndpoint(def: EndpointDefinition): EndpointDefinition {
|
|
175
|
+
return def;
|
|
176
|
+
}
|