gesso-vite-plugin 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +4 -0
- package/dist/gesso-channels.mjs +530 -0
- package/dist/gesso-channels.mjs.map +1 -0
- package/dist/index.d.ts +161 -1
- package/dist/index.js +761 -7
- package/dist/index.js.map +1 -1
- package/package.json +11 -2
package/dist/index.js
CHANGED
|
@@ -1,3 +1,154 @@
|
|
|
1
|
+
import { defaultClientConditions } from "vite";
|
|
2
|
+
//#region src/agent.ts
|
|
3
|
+
/**
|
|
4
|
+
* The dev server's half of the agent bridge: MCP at `/__gesso/mcp`.
|
|
5
|
+
*
|
|
6
|
+
* An agent such as Claude Code connects to an MCP server by URL, and
|
|
7
|
+
* the dev server already has one. But the channels it should offer live
|
|
8
|
+
* in the page's workers, and a server cannot reach a worker. The page
|
|
9
|
+
* can, and it holds a socket to the server already, Vite's HMR
|
|
10
|
+
* connection. So this relays: each JSON-RPC message an agent posts goes
|
|
11
|
+
* down that socket to the page, `connectDevAgent` in `gesso-framework/agent`
|
|
12
|
+
* answers it against the render worker, and the answer comes back up
|
|
13
|
+
* and out as the HTTP response.
|
|
14
|
+
*
|
|
15
|
+
* MCP is spoken in the page rather than here, so this file imports
|
|
16
|
+
* nothing from the framework and parses nothing but enough JSON to
|
|
17
|
+
* know a request from a notification.
|
|
18
|
+
*
|
|
19
|
+
* Several tabs may be open. The newest to announce itself answers,
|
|
20
|
+
* which is the one a person most likely means, and a tab that closes
|
|
21
|
+
* hands back to the one before it.
|
|
22
|
+
*/
|
|
23
|
+
/** The path an agent is pointed at. */
|
|
24
|
+
const AGENT_PATH = "/__gesso/mcp";
|
|
25
|
+
/** The HMR events, as `connectDevAgent` names them. */
|
|
26
|
+
const EVENTS = {
|
|
27
|
+
ready: "gesso:agent:ready",
|
|
28
|
+
request: "gesso:agent:request",
|
|
29
|
+
response: "gesso:agent:response"
|
|
30
|
+
};
|
|
31
|
+
function createAgentBridge(socket, options = {}) {
|
|
32
|
+
const timeoutMs = options.timeoutMs ?? 13e4;
|
|
33
|
+
/** Pages that announced themselves, newest last. */
|
|
34
|
+
const pages = [];
|
|
35
|
+
const pending = /* @__PURE__ */ new Map();
|
|
36
|
+
let next = 1;
|
|
37
|
+
socket.on(EVENTS.ready, (_data, client) => {
|
|
38
|
+
const at = pages.indexOf(client);
|
|
39
|
+
if (at >= 0) pages.splice(at, 1);
|
|
40
|
+
pages.push(client);
|
|
41
|
+
});
|
|
42
|
+
socket.on("vite:ws:disconnect", (_data, client) => {
|
|
43
|
+
const at = pages.indexOf(client);
|
|
44
|
+
if (at >= 0) pages.splice(at, 1);
|
|
45
|
+
});
|
|
46
|
+
socket.on(EVENTS.response, (data) => {
|
|
47
|
+
const resolve = pending.get(data.id);
|
|
48
|
+
if (resolve !== void 0) {
|
|
49
|
+
pending.delete(data.id);
|
|
50
|
+
resolve(data.response);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
const relay = (message) => {
|
|
54
|
+
const page = pages[pages.length - 1];
|
|
55
|
+
const id = next++;
|
|
56
|
+
return new Promise((resolve) => {
|
|
57
|
+
const timer = setTimeout(() => {
|
|
58
|
+
pending.delete(id);
|
|
59
|
+
resolve(rpcError(message, -32001, `The page did not answer within ${Math.round(timeoutMs / 1e3)} seconds.`));
|
|
60
|
+
}, timeoutMs);
|
|
61
|
+
pending.set(id, (response) => {
|
|
62
|
+
clearTimeout(timer);
|
|
63
|
+
resolve(response);
|
|
64
|
+
});
|
|
65
|
+
page.send(EVENTS.request, {
|
|
66
|
+
id,
|
|
67
|
+
message
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
get connected() {
|
|
73
|
+
return pages.length > 0;
|
|
74
|
+
},
|
|
75
|
+
middleware(request, response, next) {
|
|
76
|
+
if (request.url?.split("?")[0] !== "/__gesso/mcp") {
|
|
77
|
+
next();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (request.headers.origin !== void 0) {
|
|
81
|
+
send(response, 403, "text/plain", "The agent endpoint does not take requests from browser pages.");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (request.method !== "POST") {
|
|
85
|
+
response.setHeader("allow", "POST");
|
|
86
|
+
send(response, 405, "text/plain", "POST JSON-RPC messages here.");
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
let body = "";
|
|
90
|
+
request.on("data", (chunk) => {
|
|
91
|
+
body += chunk.toString();
|
|
92
|
+
});
|
|
93
|
+
request.on("end", () => {
|
|
94
|
+
let message;
|
|
95
|
+
try {
|
|
96
|
+
message = JSON.parse(body);
|
|
97
|
+
} catch {
|
|
98
|
+
send(response, 400, "application/json", JSON.stringify(rpcError(null, -32700, "The body is not JSON.")));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const notification = typeof message === "object" && message !== null && !("id" in message);
|
|
102
|
+
if (pages.length === 0) {
|
|
103
|
+
if (notification) {
|
|
104
|
+
send(response, 202);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
send(response, 200, "application/json", JSON.stringify(rpcError(message, -32e3, "No page is running the application. Open it in a browser from this dev server; the agent bridge runs in the page, because that is where the channels are.")));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
relay(message).then((answer) => {
|
|
111
|
+
if (answer === null || answer === void 0) send(response, 202);
|
|
112
|
+
else send(response, 200, "application/json", JSON.stringify(answer));
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function send(response, status, type, body) {
|
|
119
|
+
response.statusCode = status;
|
|
120
|
+
if (type !== void 0) response.setHeader("content-type", type);
|
|
121
|
+
response.end(body);
|
|
122
|
+
}
|
|
123
|
+
function rpcError(message, code, text) {
|
|
124
|
+
const id = message?.id;
|
|
125
|
+
return {
|
|
126
|
+
jsonrpc: "2.0",
|
|
127
|
+
id: typeof id === "string" || typeof id === "number" ? id : null,
|
|
128
|
+
error: {
|
|
129
|
+
code,
|
|
130
|
+
message: text
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* What the shell module gets in a dev server: the app, handed to the
|
|
136
|
+
* page's half of the bridge once `gesso-framework/agent` has loaded.
|
|
137
|
+
* Loaded on demand, so the page pays for it only in development.
|
|
138
|
+
*/
|
|
139
|
+
const SHELL_BRIDGE = `function __gessoAgent(app) {
|
|
140
|
+
if (typeof app.useWebMcp === 'function') {
|
|
141
|
+
// A single-thread builder: on by default here, as \`webmcp: true\`
|
|
142
|
+
// is for createApp, and a later useWebMcp(false) still decides.
|
|
143
|
+
app.useWebMcp(true);
|
|
144
|
+
}
|
|
145
|
+
if (import.meta.hot) {
|
|
146
|
+
void import('gesso-framework/agent').then(({ connectDevAgent }) => connectDevAgent(app, import.meta.hot));
|
|
147
|
+
}
|
|
148
|
+
return app;
|
|
149
|
+
}
|
|
150
|
+
`;
|
|
151
|
+
//#endregion
|
|
1
152
|
//#region src/source.ts
|
|
2
153
|
const IDENTIFIER = /[A-Za-z0-9_$]/;
|
|
3
154
|
/**
|
|
@@ -233,6 +384,470 @@ function importedNames(clause) {
|
|
|
233
384
|
return names;
|
|
234
385
|
}
|
|
235
386
|
//#endregion
|
|
387
|
+
//#region src/contracts.ts
|
|
388
|
+
/** The functions that declare a channel. */
|
|
389
|
+
const DECLARATIONS = ["channel", "defineChannel"];
|
|
390
|
+
/**
|
|
391
|
+
* Whether a module declares a channel, without asking the checker.
|
|
392
|
+
*
|
|
393
|
+
* The checker is a process round trip per question, and every module
|
|
394
|
+
* the application has passes through `transform`, so this is the gate:
|
|
395
|
+
* a call to `channel` or `defineChannel` imported from
|
|
396
|
+
* `gesso-framework`. A module that re-exports a token someone else
|
|
397
|
+
* declared is not a contract, and the module that declared it is the
|
|
398
|
+
* one described.
|
|
399
|
+
*/
|
|
400
|
+
function declaresChannel(code) {
|
|
401
|
+
if (!code.includes("gesso-framework")) return false;
|
|
402
|
+
const blank = blankLiterals(code);
|
|
403
|
+
const imports = importSources(code, blank);
|
|
404
|
+
return DECLARATIONS.some((name) => imports.get(name) === "gesso-framework" && (findCalls(code, name, blank).length > 0 || new RegExp(`(?<![\\w$.])${name}\\s*<`).test(blank)));
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* The code appended to a contract module.
|
|
408
|
+
*
|
|
409
|
+
* An import at the bottom is still hoisted, and appending rather than
|
|
410
|
+
* rewriting leaves every line the author wrote where they wrote it, so
|
|
411
|
+
* the module's source map needs nothing.
|
|
412
|
+
*/
|
|
413
|
+
function describeCalls(channels) {
|
|
414
|
+
const lines = ["import { describeChannel as __gessoDescribeChannel } from 'gesso-framework';"];
|
|
415
|
+
for (const [name, schema] of channels) lines.push(`__gessoDescribeChannel(${name}, ${JSON.stringify(schema)});`);
|
|
416
|
+
return `\n${lines.join("\n")}\n`;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* A module that describes channels when it is imported, for a bundle
|
|
420
|
+
* this plugin never sees.
|
|
421
|
+
*
|
|
422
|
+
* The plugin describes a contract by appending to it as Vite bundles
|
|
423
|
+
* it. An Electrobun main process is bundled by Electrobun's own build,
|
|
424
|
+
* which takes no plugins, so there the description is written ahead of
|
|
425
|
+
* time instead: `gesso-channels` reads the contracts with the same
|
|
426
|
+
* checker and writes this module, and the main process imports it once.
|
|
427
|
+
* A token is imported from its contract, so the schema lands on the
|
|
428
|
+
* same object the application serves.
|
|
429
|
+
*
|
|
430
|
+
* `outFile` and every `file` are absolute POSIX paths.
|
|
431
|
+
*/
|
|
432
|
+
function describedModule(contracts, outFile) {
|
|
433
|
+
const lines = [
|
|
434
|
+
"// Written by gesso-channels from the channel contracts it names below.",
|
|
435
|
+
"// Run it again after changing a contract rather than editing this file.",
|
|
436
|
+
"import { describeChannel } from 'gesso-framework';"
|
|
437
|
+
];
|
|
438
|
+
const from = outFile.slice(0, outFile.lastIndexOf("/"));
|
|
439
|
+
for (const contract of contracts) if (contract.channels.size > 0) {
|
|
440
|
+
const names = [...contract.channels.keys()].join(", ");
|
|
441
|
+
lines.push(`import { ${names} } from '${importPath(from, contract.file)}';`);
|
|
442
|
+
}
|
|
443
|
+
for (const contract of contracts) for (const [name, schema] of contract.channels) lines.push("", `describeChannel(${name}, ${JSON.stringify(schema, null, 2)});`);
|
|
444
|
+
return `${lines.join("\n")}\n`;
|
|
445
|
+
}
|
|
446
|
+
/** `./Counter` from `/app/src/shared` to `/app/src/shared/Counter.ts`, without the extension. */
|
|
447
|
+
function importPath(fromDir, file) {
|
|
448
|
+
const from = fromDir.split("/").filter(Boolean);
|
|
449
|
+
const to = file.replace(/\.[cm]?[jt]sx?$/, "").split("/").filter(Boolean);
|
|
450
|
+
let shared = 0;
|
|
451
|
+
while (shared < from.length && shared < to.length - 1 && from[shared] === to[shared]) shared++;
|
|
452
|
+
const up = from.length - shared;
|
|
453
|
+
const rest = to.slice(shared).join("/");
|
|
454
|
+
return up === 0 ? `./${rest}` : `${"../".repeat(up)}${rest}`;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* The checker, held open across reads.
|
|
458
|
+
*
|
|
459
|
+
* Spawning it and loading a project costs about a hundred
|
|
460
|
+
* milliseconds; a question after that costs well under one. So one
|
|
461
|
+
* reader lives as long as the dev server or the build, is told which
|
|
462
|
+
* files changed between reads, and is closed at the end, because the
|
|
463
|
+
* process it holds would otherwise keep the build from exiting.
|
|
464
|
+
*/
|
|
465
|
+
var ContractReader = class {
|
|
466
|
+
api = null;
|
|
467
|
+
changed = /* @__PURE__ */ new Set();
|
|
468
|
+
/** Each contract's tsconfig, found once. */
|
|
469
|
+
configs = /* @__PURE__ */ new Map();
|
|
470
|
+
/**
|
|
471
|
+
* @param fs Callbacks the checker reads through before the disk, for
|
|
472
|
+
* a spec that keeps its project in memory.
|
|
473
|
+
*/
|
|
474
|
+
ts;
|
|
475
|
+
cwd;
|
|
476
|
+
fs;
|
|
477
|
+
constructor(ts, cwd, fs) {
|
|
478
|
+
this.ts = ts;
|
|
479
|
+
this.cwd = cwd;
|
|
480
|
+
this.fs = fs;
|
|
481
|
+
}
|
|
482
|
+
/** A file changed on disk since the last read. */
|
|
483
|
+
invalidate(file) {
|
|
484
|
+
this.changed.add(file);
|
|
485
|
+
}
|
|
486
|
+
/** Every channel `file` exports, described. */
|
|
487
|
+
read(file) {
|
|
488
|
+
const api = this.api ??= new this.ts.API({
|
|
489
|
+
cwd: this.cwd,
|
|
490
|
+
...this.fs === void 0 ? {} : { fs: this.fs }
|
|
491
|
+
});
|
|
492
|
+
this.changed.add(file);
|
|
493
|
+
let snapshot = api.updateSnapshot({ fileChanges: { changed: [...this.changed] } });
|
|
494
|
+
this.changed.clear();
|
|
495
|
+
let config = this.configs.get(file);
|
|
496
|
+
if (config === void 0) {
|
|
497
|
+
config = api.updateSnapshot({ openFiles: [file] }).getDefaultProjectForFile(file)?.configFileName;
|
|
498
|
+
snapshot = api.updateSnapshot({
|
|
499
|
+
...config === void 0 ? {} : { openProjects: [config] },
|
|
500
|
+
closeFiles: [file]
|
|
501
|
+
});
|
|
502
|
+
if (config !== void 0) this.configs.set(file, config);
|
|
503
|
+
}
|
|
504
|
+
const project = config === void 0 ? void 0 : snapshot.getProject(config);
|
|
505
|
+
const source = project?.program.getSourceFile(file);
|
|
506
|
+
if (project === void 0 || source === void 0) return {
|
|
507
|
+
channels: /* @__PURE__ */ new Map(),
|
|
508
|
+
warnings: [`TypeScript has no project that includes ${file}.`]
|
|
509
|
+
};
|
|
510
|
+
const checker = project.checker;
|
|
511
|
+
const module = checker.getSymbolAtLocation(source);
|
|
512
|
+
const describer = new Describer(this.ts, checker);
|
|
513
|
+
const channels = /* @__PURE__ */ new Map();
|
|
514
|
+
for (const exported of module === void 0 ? [] : checker.getExportsOfModule(module)) {
|
|
515
|
+
const type = checker.getTypeOfSymbol(exported);
|
|
516
|
+
if (type === void 0 || type.getSymbol()?.name !== "ChannelToken") continue;
|
|
517
|
+
const [view, commands] = checker.getTypeArguments(type);
|
|
518
|
+
channels.set(exported.name, describer.channel(exported, view, commands));
|
|
519
|
+
}
|
|
520
|
+
return {
|
|
521
|
+
channels,
|
|
522
|
+
warnings: describer.warnings
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
close() {
|
|
526
|
+
this.api?.close();
|
|
527
|
+
this.api = null;
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
/**
|
|
531
|
+
* Library types that are objects to the checker and not plain data to
|
|
532
|
+
* a channel. They compare by reference, so `provide` rejects them on
|
|
533
|
+
* their first value; describing their methods as properties would be
|
|
534
|
+
* describing something that never arrives.
|
|
535
|
+
*/
|
|
536
|
+
const NOT_PLAIN = /* @__PURE__ */ new Set([
|
|
537
|
+
"Date",
|
|
538
|
+
"Map",
|
|
539
|
+
"ReadonlyMap",
|
|
540
|
+
"Set",
|
|
541
|
+
"ReadonlySet",
|
|
542
|
+
"WeakMap",
|
|
543
|
+
"WeakSet",
|
|
544
|
+
"Promise",
|
|
545
|
+
"RegExp",
|
|
546
|
+
"Error",
|
|
547
|
+
"ArrayBuffer",
|
|
548
|
+
"SharedArrayBuffer",
|
|
549
|
+
"DataView",
|
|
550
|
+
"Blob",
|
|
551
|
+
"File",
|
|
552
|
+
"URL",
|
|
553
|
+
"Int8Array",
|
|
554
|
+
"Uint8Array",
|
|
555
|
+
"Uint8ClampedArray",
|
|
556
|
+
"Int16Array",
|
|
557
|
+
"Uint16Array",
|
|
558
|
+
"Int32Array",
|
|
559
|
+
"Uint32Array",
|
|
560
|
+
"Float32Array",
|
|
561
|
+
"Float64Array",
|
|
562
|
+
"BigInt64Array",
|
|
563
|
+
"BigUint64Array"
|
|
564
|
+
]);
|
|
565
|
+
/**
|
|
566
|
+
* Bytes, which a command may carry and a view key may not.
|
|
567
|
+
*
|
|
568
|
+
* A command's argument is structured-cloned onto the owning thread, and
|
|
569
|
+
* a typed array or an `ArrayBuffer` clones as itself: a file a person
|
|
570
|
+
* dropped crosses as its bytes, with no encoding pass on the render
|
|
571
|
+
* thread. A view key is diffed, and a differ that compares bytes by
|
|
572
|
+
* reference reports a change on every emission, so there they stay
|
|
573
|
+
* unplain. In a command's schema they are a base64 string, which is
|
|
574
|
+
* what an agent can write, tagged with the type the command expects so
|
|
575
|
+
* the agent surface can turn the string back into it.
|
|
576
|
+
*/
|
|
577
|
+
const BINARY = /* @__PURE__ */ new Set([
|
|
578
|
+
"ArrayBuffer",
|
|
579
|
+
"Int8Array",
|
|
580
|
+
"Uint8Array",
|
|
581
|
+
"Uint8ClampedArray",
|
|
582
|
+
"Int16Array",
|
|
583
|
+
"Uint16Array",
|
|
584
|
+
"Int32Array",
|
|
585
|
+
"Uint32Array",
|
|
586
|
+
"Float32Array",
|
|
587
|
+
"Float64Array",
|
|
588
|
+
"BigInt64Array",
|
|
589
|
+
"BigUint64Array"
|
|
590
|
+
]);
|
|
591
|
+
/** Turns checker types into JSON Schema for one module's channels. */
|
|
592
|
+
var Describer = class {
|
|
593
|
+
warnings = [];
|
|
594
|
+
flags;
|
|
595
|
+
/** Describing a command's parameters, where bytes are allowed; see `BINARY`. */
|
|
596
|
+
inCommand = false;
|
|
597
|
+
/** Types being described right now, to notice a type that contains itself. */
|
|
598
|
+
inProgress = /* @__PURE__ */ new Set();
|
|
599
|
+
/** Types found to contain themselves, which are described once in `$defs`. */
|
|
600
|
+
recursive = /* @__PURE__ */ new Set();
|
|
601
|
+
/** `$defs` for the schema being built. Reset per root, so each schema stands alone. */
|
|
602
|
+
defs = /* @__PURE__ */ new Map();
|
|
603
|
+
defNames = /* @__PURE__ */ new Map();
|
|
604
|
+
ts;
|
|
605
|
+
checker;
|
|
606
|
+
constructor(ts, checker) {
|
|
607
|
+
this.ts = ts;
|
|
608
|
+
this.checker = checker;
|
|
609
|
+
this.flags = ts.TypeFlags;
|
|
610
|
+
}
|
|
611
|
+
channel(token, view, commands) {
|
|
612
|
+
const out = {
|
|
613
|
+
view: this.root(view, `${token.name}.view`),
|
|
614
|
+
commands: {}
|
|
615
|
+
};
|
|
616
|
+
const description = this.checker.getDocumentationCommentOfSymbol(token);
|
|
617
|
+
if (description !== "") out.description = description;
|
|
618
|
+
if (commands !== void 0) for (const command of this.checker.getPropertiesOfType(commands)) {
|
|
619
|
+
const described = this.command(command, `${token.name}.commands.${command.name}`);
|
|
620
|
+
if (described !== null) out.commands[command.name] = described;
|
|
621
|
+
}
|
|
622
|
+
return out;
|
|
623
|
+
}
|
|
624
|
+
/** A schema that stands alone: its own `$defs`, if anything in it recurs. */
|
|
625
|
+
root(type, path) {
|
|
626
|
+
this.defs = /* @__PURE__ */ new Map();
|
|
627
|
+
const schema = this.describe(type, path);
|
|
628
|
+
if (this.defs.size > 0) return {
|
|
629
|
+
...schema,
|
|
630
|
+
$defs: Object.fromEntries(this.defs)
|
|
631
|
+
};
|
|
632
|
+
return schema;
|
|
633
|
+
}
|
|
634
|
+
command(symbol, path) {
|
|
635
|
+
const type = this.checker.getTypeOfSymbol(symbol);
|
|
636
|
+
const signatures = type === void 0 ? [] : this.checker.getSignaturesOfType(type, this.ts.SignatureKind.Call);
|
|
637
|
+
if (signatures.length === 0) {
|
|
638
|
+
this.warnings.push(`${path} is not a function, so it is not a command an agent can send.`);
|
|
639
|
+
return null;
|
|
640
|
+
}
|
|
641
|
+
if (signatures.length > 1) this.warnings.push(`${path} is overloaded; only its first signature is described.`);
|
|
642
|
+
const signature = signatures[0];
|
|
643
|
+
const tags = this.checker.getJsDocTagsOfSymbol(symbol);
|
|
644
|
+
const parameters = signature.getParameters();
|
|
645
|
+
this.defs = /* @__PURE__ */ new Map();
|
|
646
|
+
const properties = {};
|
|
647
|
+
const required = [];
|
|
648
|
+
this.inCommand = true;
|
|
649
|
+
parameters.forEach((parameter, index) => {
|
|
650
|
+
const parameterType = this.checker.getTypeOfSymbol(parameter);
|
|
651
|
+
const optional = parameterType !== void 0 && this.includesUndefined(parameterType);
|
|
652
|
+
const schema = parameterType === void 0 ? {} : this.describe(parameterType, `${path}(${parameter.name})`);
|
|
653
|
+
const description = this.parameterDescription(parameter, tags);
|
|
654
|
+
properties[parameter.name] = description === "" ? schema : {
|
|
655
|
+
...schema,
|
|
656
|
+
description
|
|
657
|
+
};
|
|
658
|
+
const rest = signature.hasRestParameter && index === parameters.length - 1;
|
|
659
|
+
if (!optional && !rest) required.push(parameter.name);
|
|
660
|
+
});
|
|
661
|
+
this.inCommand = false;
|
|
662
|
+
const input = {
|
|
663
|
+
type: "object",
|
|
664
|
+
properties,
|
|
665
|
+
additionalProperties: false
|
|
666
|
+
};
|
|
667
|
+
if (required.length > 0) input.required = required;
|
|
668
|
+
if (this.defs.size > 0) input.$defs = Object.fromEntries(this.defs);
|
|
669
|
+
const out = {
|
|
670
|
+
parameters: parameters.map((parameter) => parameter.name),
|
|
671
|
+
input
|
|
672
|
+
};
|
|
673
|
+
const description = this.checker.getDocumentationCommentOfSymbol(symbol);
|
|
674
|
+
if (description !== "") out.description = description;
|
|
675
|
+
if (signature.hasRestParameter) out.rest = true;
|
|
676
|
+
for (const annotation of [
|
|
677
|
+
"destructive",
|
|
678
|
+
"idempotent",
|
|
679
|
+
"confirm",
|
|
680
|
+
"hidden"
|
|
681
|
+
]) if (tags.some((tag) => tag.name === annotation)) out[annotation] = true;
|
|
682
|
+
return out;
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* A parameter's own comment, or failing that its `@param` line.
|
|
686
|
+
*
|
|
687
|
+
* The checker attaches `@param` text to the parameter of a method
|
|
688
|
+
* signature. A command written as a property, `move: (from: number)
|
|
689
|
+
* => void`, keeps its comment on the property, so the tag is read
|
|
690
|
+
* from there instead.
|
|
691
|
+
*/
|
|
692
|
+
parameterDescription(parameter, tags) {
|
|
693
|
+
const own = this.checker.getDocumentationCommentOfSymbol(parameter);
|
|
694
|
+
if (own !== "") return own;
|
|
695
|
+
for (const tag of tags) {
|
|
696
|
+
const match = tag.name === "param" ? /^\s*(\S+)\s*(?:-\s*)?([\s\S]*)$/.exec(tag.text ?? "") : null;
|
|
697
|
+
if (match !== null && match[1] === parameter.name) return match[2].trim();
|
|
698
|
+
}
|
|
699
|
+
return "";
|
|
700
|
+
}
|
|
701
|
+
includesUndefined(type) {
|
|
702
|
+
const { Undefined, Void } = this.flags;
|
|
703
|
+
if (type.isUnionType()) return (type.getTypes() ?? []).some((member) => (member.flags & (Undefined | Void)) !== 0);
|
|
704
|
+
return (type.flags & (Undefined | Void)) !== 0;
|
|
705
|
+
}
|
|
706
|
+
describe(type, path) {
|
|
707
|
+
const f = this.flags;
|
|
708
|
+
const flags = type.flags;
|
|
709
|
+
if (flags & (f.Any | f.Unknown)) return {};
|
|
710
|
+
if (flags & f.Never) {
|
|
711
|
+
this.warnings.push(`${path} is never, which describes no value. An empty array literal is never[] unless it says what it holds: write \`[] as Row[]\`.`);
|
|
712
|
+
return { not: {} };
|
|
713
|
+
}
|
|
714
|
+
if (flags & (f.String | f.TemplateLiteral | f.StringMapping)) return { type: "string" };
|
|
715
|
+
if (flags & f.Number) return { type: "number" };
|
|
716
|
+
if (flags & f.Boolean) return { type: "boolean" };
|
|
717
|
+
if (flags & f.Null) return { type: "null" };
|
|
718
|
+
if (flags & (f.Undefined | f.Void)) return {};
|
|
719
|
+
if (flags & f.BooleanLiteral) return { const: type.intrinsicName === "true" };
|
|
720
|
+
if (flags & (f.StringLiteral | f.NumberLiteral)) return { const: type.value };
|
|
721
|
+
if (flags & (f.BigInt | f.BigIntLiteral | f.ESSymbol | f.UniqueESSymbol)) return this.unplain(path, flags & f.BigIntLike ? "a bigint" : "a symbol");
|
|
722
|
+
if (type.isUnionType()) return this.union(type, path);
|
|
723
|
+
if (flags & f.TypeParameter) return {};
|
|
724
|
+
if (flags & (f.Object | f.Intersection)) {
|
|
725
|
+
if (this.checker.isArrayType(type)) {
|
|
726
|
+
const [element] = this.checker.getTypeArguments(type);
|
|
727
|
+
return {
|
|
728
|
+
type: "array",
|
|
729
|
+
items: element === void 0 ? {} : this.describe(element, `${path}[]`)
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
if (this.checker.isTupleType(type)) return this.tuple(type, path);
|
|
733
|
+
return this.named(type, path);
|
|
734
|
+
}
|
|
735
|
+
this.warnings.push(`${path} has a type this reader does not describe, so it is left open.`);
|
|
736
|
+
return {};
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* A union, with `undefined` taken out (optionality is said by
|
|
740
|
+
* `required`), `true | false` folded back into `boolean`, and a union
|
|
741
|
+
* of nothing but literals written as an `enum`.
|
|
742
|
+
*/
|
|
743
|
+
union(type, path) {
|
|
744
|
+
const f = this.flags;
|
|
745
|
+
let members = (type.getTypes() ?? []).filter((member) => (member.flags & (f.Undefined | f.Void)) === 0);
|
|
746
|
+
const booleans = members.filter((member) => member.flags & f.BooleanLiteral);
|
|
747
|
+
const schemas = [];
|
|
748
|
+
if (booleans.length === 2) {
|
|
749
|
+
members = members.filter((member) => !(member.flags & f.BooleanLiteral));
|
|
750
|
+
schemas.push({ type: "boolean" });
|
|
751
|
+
}
|
|
752
|
+
if (members.length > 0 && schemas.length === 0 && members.every((member) => member.flags & (f.StringLiteral | f.NumberLiteral))) return { enum: members.map((member) => member.value) };
|
|
753
|
+
members.sort((a, b) => Number((a.flags & f.Null) !== 0) - Number((b.flags & f.Null) !== 0));
|
|
754
|
+
schemas.push(...members.map((member) => this.describe(member, path)));
|
|
755
|
+
return schemas.length === 1 ? schemas[0] : { anyOf: schemas };
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* An object type, described once however often it appears, and by
|
|
759
|
+
* reference when it contains itself.
|
|
760
|
+
*/
|
|
761
|
+
named(type, path) {
|
|
762
|
+
const id = type.id;
|
|
763
|
+
if (this.inProgress.has(id)) {
|
|
764
|
+
this.recursive.add(id);
|
|
765
|
+
return { $ref: `#/$defs/${this.defName(type)}` };
|
|
766
|
+
}
|
|
767
|
+
this.inProgress.add(id);
|
|
768
|
+
const schema = this.object(type, path);
|
|
769
|
+
this.inProgress.delete(id);
|
|
770
|
+
if (this.recursive.has(id)) {
|
|
771
|
+
const name = this.defName(type);
|
|
772
|
+
this.defs.set(name, schema);
|
|
773
|
+
return { $ref: `#/$defs/${name}` };
|
|
774
|
+
}
|
|
775
|
+
return schema;
|
|
776
|
+
}
|
|
777
|
+
defName(type) {
|
|
778
|
+
let name = this.defNames.get(type.id);
|
|
779
|
+
if (name === void 0) {
|
|
780
|
+
const base = (type.getAliasSymbol() ?? type.getSymbol())?.name ?? "Type";
|
|
781
|
+
name = new Set(this.defNames.values()).has(base) ? `${base}${type.id}` : base;
|
|
782
|
+
this.defNames.set(type.id, name);
|
|
783
|
+
}
|
|
784
|
+
return name;
|
|
785
|
+
}
|
|
786
|
+
object(type, path) {
|
|
787
|
+
const checker = this.checker;
|
|
788
|
+
const symbolName = type.getSymbol()?.name;
|
|
789
|
+
if (symbolName !== void 0 && this.inCommand && BINARY.has(symbolName)) return {
|
|
790
|
+
type: "string",
|
|
791
|
+
contentEncoding: "base64",
|
|
792
|
+
"x-gesso-binary": symbolName
|
|
793
|
+
};
|
|
794
|
+
if (symbolName !== void 0 && NOT_PLAIN.has(symbolName)) return this.unplain(path, `a ${symbolName}`);
|
|
795
|
+
if (type.objectFlags & this.ts.ObjectFlags.Class) return this.unplain(path, `an instance of ${symbolName ?? "a class"}`);
|
|
796
|
+
if (checker.getSignaturesOfType(type, this.ts.SignatureKind.Call).length > 0) return this.unplain(path, "a function");
|
|
797
|
+
const properties = {};
|
|
798
|
+
const required = [];
|
|
799
|
+
for (const property of checker.getPropertiesOfType(type)) {
|
|
800
|
+
const propertyType = checker.getTypeOfSymbol(property);
|
|
801
|
+
const at = `${path}.${property.name}`;
|
|
802
|
+
if (propertyType === void 0) {
|
|
803
|
+
properties[property.name] = {};
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
const schema = this.describe(propertyType, at);
|
|
807
|
+
const description = checker.getDocumentationCommentOfSymbol(property);
|
|
808
|
+
properties[property.name] = description === "" ? schema : {
|
|
809
|
+
...schema,
|
|
810
|
+
description
|
|
811
|
+
};
|
|
812
|
+
if (!((property.flags & this.ts.SymbolFlags.Optional) !== 0 || this.includesUndefined(propertyType))) required.push(property.name);
|
|
813
|
+
}
|
|
814
|
+
const schema = {
|
|
815
|
+
type: "object",
|
|
816
|
+
properties
|
|
817
|
+
};
|
|
818
|
+
if (required.length > 0) schema.required = required;
|
|
819
|
+
const index = checker.getIndexInfosOfType(type).find((info) => info.keyType.flags & this.flags.String);
|
|
820
|
+
if (index !== void 0) schema.additionalProperties = this.describe(index.valueType, `${path}[key]`);
|
|
821
|
+
return schema;
|
|
822
|
+
}
|
|
823
|
+
tuple(type, path) {
|
|
824
|
+
const elements = this.checker.getTypeArguments(type);
|
|
825
|
+
const elementFlags = type.getTarget().elementFlags ?? [];
|
|
826
|
+
const { Required, Rest } = this.ts.ElementFlags;
|
|
827
|
+
const prefixItems = [];
|
|
828
|
+
let items = false;
|
|
829
|
+
let minItems = 0;
|
|
830
|
+
elements.forEach((element, index) => {
|
|
831
|
+
const schema = this.describe(element, `${path}[${index}]`);
|
|
832
|
+
if (elementFlags[index] & Rest) items = schema;
|
|
833
|
+
else {
|
|
834
|
+
prefixItems.push(schema);
|
|
835
|
+
if (elementFlags[index] & Required) minItems = index + 1;
|
|
836
|
+
}
|
|
837
|
+
});
|
|
838
|
+
return {
|
|
839
|
+
type: "array",
|
|
840
|
+
prefixItems,
|
|
841
|
+
items,
|
|
842
|
+
minItems
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
unplain(path, what) {
|
|
846
|
+
this.warnings.push(`${path} is ${what}, which is not plain data and cannot cross a channel.`);
|
|
847
|
+
return {};
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
//#endregion
|
|
236
851
|
//#region src/render.ts
|
|
237
852
|
const MARKER$1 = "/* gesso-vite-plugin: hot replacement */";
|
|
238
853
|
/**
|
|
@@ -368,10 +983,12 @@ function findShellCall(code, blank = blankLiterals(code)) {
|
|
|
368
983
|
function transformShell(code, options) {
|
|
369
984
|
const shell = findShellCall(code, blankLiterals(code));
|
|
370
985
|
if (shell === null) return null;
|
|
371
|
-
if (options.entries === null && !options.overlay) return null;
|
|
986
|
+
if (options.entries === null && !options.overlay && options.agent !== true) return null;
|
|
372
987
|
const { call } = shell;
|
|
373
988
|
const args = code.slice(call.argsStart, call.argsEnd).trim();
|
|
374
|
-
|
|
989
|
+
const created = `${code.slice(call.start, call.argsStart)}__gessoOptions(${args === "" ? "{}" : args})${code.slice(call.argsEnd, call.end)}`;
|
|
990
|
+
const wrapped = options.agent === true ? `__gessoAgent(${created})` : created;
|
|
991
|
+
return `${`${code.slice(0, call.start)}${wrapped}${code.slice(call.end)}`}\n${prelude(options)}${options.agent === true ? SHELL_BRIDGE : ""}`;
|
|
375
992
|
}
|
|
376
993
|
/**
|
|
377
994
|
* The appended half: the worker construction, and in development the
|
|
@@ -394,6 +1011,7 @@ function prelude(options) {
|
|
|
394
1011
|
...entries === null ? [] : [` renderWorker: () => new Worker(new URL(${JSON.stringify(entries.renderWorker)}, import.meta.url), { type: 'module', name: options.workerName }),`],
|
|
395
1012
|
...entries?.appLogicWorker == null ? [] : [` appLogicWorker: () => new Worker(new URL(${JSON.stringify(entries.appLogicWorker)}, import.meta.url), { type: 'module', name: options.workerName }),`],
|
|
396
1013
|
...options.overlay ? [" onError: __gessoReportError,"] : [],
|
|
1014
|
+
...options.agent === true ? [" webmcp: true,"] : [],
|
|
397
1015
|
" ...options",
|
|
398
1016
|
" };",
|
|
399
1017
|
"}"
|
|
@@ -452,9 +1070,42 @@ addEventListener('unhandledrejection', event => {
|
|
|
452
1070
|
__gessoReportError(reason?.message ?? String(reason), reason?.stack, 'window');
|
|
453
1071
|
});
|
|
454
1072
|
`;
|
|
1073
|
+
/**
|
|
1074
|
+
* The single-thread shell in a dev server: `createSyncApp(Root)`
|
|
1075
|
+
* wrapped so the agent bridge gets the builder, which answers an agent
|
|
1076
|
+
* port from the page itself. There are no workers to write and no
|
|
1077
|
+
* overlay to wire here, so this is all the plugin does to such a shell.
|
|
1078
|
+
*/
|
|
1079
|
+
function transformSyncShell(code) {
|
|
1080
|
+
if (code.includes(MARKER)) return null;
|
|
1081
|
+
const blank = blankLiterals(code);
|
|
1082
|
+
if (importSources(code, blank).get("createSyncApp") !== "gesso-framework") return null;
|
|
1083
|
+
const call = findCall(code, "createSyncApp", blank);
|
|
1084
|
+
if (call === null) return null;
|
|
1085
|
+
const wrapped = `__gessoAgent(${code.slice(call.start, call.end)})`;
|
|
1086
|
+
return `${code.slice(0, call.start)}${wrapped}${code.slice(call.end)}\n${MARKER}\n${SHELL_BRIDGE}`;
|
|
1087
|
+
}
|
|
455
1088
|
//#endregion
|
|
456
1089
|
//#region src/index.ts
|
|
457
1090
|
/**
|
|
1091
|
+
* The export conditions a Gesso application resolves its dependencies
|
|
1092
|
+
* with: `worker` ahead of Vite's defaults.
|
|
1093
|
+
*
|
|
1094
|
+
* A Gesso application's code runs in workers, and Vite resolves every
|
|
1095
|
+
* dependency with the `browser` condition, which is the right call for
|
|
1096
|
+
* a page and the wrong one for a worker: a package's browser build may
|
|
1097
|
+
* reach for `document`. `decode-named-character-reference`, which every
|
|
1098
|
+
* markdown parser built on micromark imports, does exactly that, and a
|
|
1099
|
+
* render worker that imported one died on start with "document is not
|
|
1100
|
+
* defined". The same package publishes a `worker` build, as packages
|
|
1101
|
+
* with a DOM-only browser build tend to. A package's own export order
|
|
1102
|
+
* decides between the two, and those list `worker` first.
|
|
1103
|
+
*
|
|
1104
|
+
* The main thread resolves the same way. A worker build runs in a page
|
|
1105
|
+
* as well, and the shell imports little beyond Gesso itself.
|
|
1106
|
+
*/
|
|
1107
|
+
const workerConditions = ["worker", ...defaultClientConditions];
|
|
1108
|
+
/**
|
|
458
1109
|
* Where a render worker entry is looked for, beside the shell.
|
|
459
1110
|
*
|
|
460
1111
|
* Ordered so that a name that says what the file is beats the generic
|
|
@@ -486,14 +1137,99 @@ function gesso(options = {}) {
|
|
|
486
1137
|
let renderWorkerId = null;
|
|
487
1138
|
/** Files already reported as reloading, so a save is not a stream of warnings. */
|
|
488
1139
|
const reported = /* @__PURE__ */ new Set();
|
|
1140
|
+
/** The checker, started by the first contract, or null once it is known to be unavailable. */
|
|
1141
|
+
let contracts;
|
|
1142
|
+
let root = "";
|
|
1143
|
+
const readerFor = (warn) => contracts ??= import("typescript/unstable/sync").then((ts) => new ContractReader(ts, root), () => {
|
|
1144
|
+
warn("Channels are not described, because TypeScript 7 is not installed. Add `typescript` (7 or later) to devDependencies, or pass `channelSchemas: false` to stop this message.");
|
|
1145
|
+
return null;
|
|
1146
|
+
});
|
|
1147
|
+
const closeContracts = async () => {
|
|
1148
|
+
(await contracts)?.close();
|
|
1149
|
+
contracts = void 0;
|
|
1150
|
+
};
|
|
1151
|
+
/** The module with its channels described, or null when it declares none. */
|
|
1152
|
+
const describeContract = async (code, id, warn) => {
|
|
1153
|
+
if (options.channelSchemas === false || !SOURCE.test(id) || id.includes("/node_modules/") || !declaresChannel(code)) return null;
|
|
1154
|
+
const reader = await readerFor(warn);
|
|
1155
|
+
if (reader === null) return null;
|
|
1156
|
+
const reading = reader.read(id.replace(/\?.*$/, ""));
|
|
1157
|
+
reading.warnings.forEach(warn);
|
|
1158
|
+
return reading.channels.size > 0 ? code + describeCalls(reading.channels) : null;
|
|
1159
|
+
};
|
|
1160
|
+
/**
|
|
1161
|
+
* The same description, for a worker's bundle.
|
|
1162
|
+
*
|
|
1163
|
+
* A build bundles each worker with `worker.plugins` rather than with
|
|
1164
|
+
* the plugins of the page, and a contract is imported by workers far
|
|
1165
|
+
* more often than by the page, so without this a build would describe
|
|
1166
|
+
* almost nothing. A dev server runs every module through the main
|
|
1167
|
+
* plugins and does not need it. Each worker bundle closes the checker
|
|
1168
|
+
* when it is done, because nothing guarantees another bundle follows
|
|
1169
|
+
* to close it; the next contract starts it again.
|
|
1170
|
+
*/
|
|
1171
|
+
const workerContracts = {
|
|
1172
|
+
name: "gesso:channel-schemas",
|
|
1173
|
+
enforce: "pre",
|
|
1174
|
+
async transform(code, id) {
|
|
1175
|
+
const described = await describeContract(code, id, (message) => this.warn(message));
|
|
1176
|
+
return described === null ? null : {
|
|
1177
|
+
code: described,
|
|
1178
|
+
map: null
|
|
1179
|
+
};
|
|
1180
|
+
},
|
|
1181
|
+
async closeBundle() {
|
|
1182
|
+
await closeContracts();
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
489
1185
|
return {
|
|
490
1186
|
name: "gesso",
|
|
491
1187
|
enforce: "pre",
|
|
1188
|
+
config(config) {
|
|
1189
|
+
return {
|
|
1190
|
+
...options.workerConditions === false ? {} : { resolve: { conditions: [...workerConditions] } },
|
|
1191
|
+
worker: {
|
|
1192
|
+
...config.worker?.format === void 0 ? { format: "es" } : {},
|
|
1193
|
+
...options.channelSchemas === false ? {} : { plugins: () => [workerContracts] }
|
|
1194
|
+
}
|
|
1195
|
+
};
|
|
1196
|
+
},
|
|
492
1197
|
configResolved(config) {
|
|
493
1198
|
serving = config.command === "serve";
|
|
1199
|
+
root = config.root;
|
|
494
1200
|
},
|
|
495
|
-
|
|
1201
|
+
/**
|
|
1202
|
+
* The agent endpoint, and a line saying where it is once the server
|
|
1203
|
+
* is listening, because an endpoint nobody knows the address of is
|
|
1204
|
+
* one nobody connects to.
|
|
1205
|
+
*/
|
|
1206
|
+
configureServer(server) {
|
|
1207
|
+
if (options.agent === false) return;
|
|
1208
|
+
const bridge = createAgentBridge(server.ws);
|
|
1209
|
+
server.middlewares.use((request, response, next) => bridge.middleware(request, response, next));
|
|
1210
|
+
server.httpServer?.once("listening", () => {
|
|
1211
|
+
const base = server.resolvedUrls?.local[0];
|
|
1212
|
+
if (base !== void 0) {
|
|
1213
|
+
const url = new URL(AGENT_PATH, base).href;
|
|
1214
|
+
server.config.logger.info(` ➜ Agents: ${url}\n claude mcp add --transport http ${agentName(server.config.root)} ${url}`);
|
|
1215
|
+
}
|
|
1216
|
+
});
|
|
1217
|
+
},
|
|
1218
|
+
watchChange(id) {
|
|
1219
|
+
contracts?.then((reader) => reader?.invalidate(id));
|
|
1220
|
+
},
|
|
1221
|
+
/**
|
|
1222
|
+
* The checker is a child process, and a build that left it running
|
|
1223
|
+
* would never exit. A dev server calls this when it closes, so the
|
|
1224
|
+
* one hook covers both.
|
|
1225
|
+
*/
|
|
1226
|
+
async closeBundle() {
|
|
1227
|
+
await closeContracts();
|
|
1228
|
+
},
|
|
1229
|
+
async transform(source, id) {
|
|
496
1230
|
if (!SOURCE.test(id) || id.includes("/node_modules/")) return null;
|
|
1231
|
+
const described = await describeContract(source, id, (message) => this.warn(message));
|
|
1232
|
+
const code = described ?? source;
|
|
497
1233
|
if (serving && options.hmr !== false) {
|
|
498
1234
|
const wiring = transformRenderWorker(code);
|
|
499
1235
|
if (wiring.skipped !== null) this.warn(`${wiring.skipped} Saving it will reload the page rather than replace the tree.`);
|
|
@@ -506,13 +1242,27 @@ function gesso(options = {}) {
|
|
|
506
1242
|
}
|
|
507
1243
|
}
|
|
508
1244
|
const call = findShellCall(code);
|
|
509
|
-
if (call === null)
|
|
1245
|
+
if (call === null) {
|
|
1246
|
+
const sync = serving && options.agent !== false ? transformSyncShell(code) : null;
|
|
1247
|
+
if (sync !== null) return {
|
|
1248
|
+
code: sync,
|
|
1249
|
+
map: null
|
|
1250
|
+
};
|
|
1251
|
+
return described === null ? null : {
|
|
1252
|
+
code,
|
|
1253
|
+
map: null
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
510
1256
|
shellId = id;
|
|
511
1257
|
const shell = transformShell(code, {
|
|
512
1258
|
entries: call.needsWorkers ? await resolveEntries(this, id, options) : null,
|
|
513
|
-
overlay: serving && options.overlay !== false
|
|
1259
|
+
overlay: serving && options.overlay !== false,
|
|
1260
|
+
agent: serving && options.agent !== false
|
|
514
1261
|
});
|
|
515
|
-
return shell === null ? null : {
|
|
1262
|
+
return shell === null ? described === null ? null : {
|
|
1263
|
+
code,
|
|
1264
|
+
map: null
|
|
1265
|
+
} : {
|
|
516
1266
|
code: shell,
|
|
517
1267
|
map: null
|
|
518
1268
|
};
|
|
@@ -539,6 +1289,10 @@ function gesso(options = {}) {
|
|
|
539
1289
|
}
|
|
540
1290
|
};
|
|
541
1291
|
}
|
|
1292
|
+
/** A name for the MCP server: the project's directory, which is what a person calls the app. */
|
|
1293
|
+
function agentName(root) {
|
|
1294
|
+
return (root.split("/").filter(Boolean).pop() ?? "gesso").replace(/[^A-Za-z0-9_-]/g, "-");
|
|
1295
|
+
}
|
|
542
1296
|
/** Every module that transitively imports this one, plus this one. */
|
|
543
1297
|
function importerClosure(module) {
|
|
544
1298
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -579,6 +1333,6 @@ async function firstResolved(context, importer, names) {
|
|
|
579
1333
|
return null;
|
|
580
1334
|
}
|
|
581
1335
|
//#endregion
|
|
582
|
-
export { blankLiterals, gesso as default, gesso, findCall, findCalls, findShellCall, firstArgumentName, importSources, transformRenderWorker, transformShell };
|
|
1336
|
+
export { ContractReader, blankLiterals, gesso as default, gesso, describedModule, findCall, findCalls, findShellCall, firstArgumentName, importSources, transformRenderWorker, transformShell, transformSyncShell, workerConditions };
|
|
583
1337
|
|
|
584
1338
|
//# sourceMappingURL=index.js.map
|