@llui/vite-plugin 0.0.28 → 0.0.30

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 CHANGED
@@ -23,9 +23,15 @@ export default defineConfig({
23
23
  ```ts
24
24
  llui({
25
25
  mcpPort: 5200, // MCP debug server port (default: 5200, false to disable)
26
+ agent: true, // Emit LLui Agent metadata in prod builds (default: false)
26
27
  })
27
28
  ```
28
29
 
30
+ - `mcpPort` — MCP debug bridge port. Default `5200`. Set to `false` to disable.
31
+ - `agent` — Opt into emitting schemas + binding descriptors in prod builds.
32
+ Required when the app is deployed with `@llui/agent/client`. Default `false`
33
+ (metadata is dev-only to keep production bundle size minimal).
34
+
29
35
  ## What It Does
30
36
 
31
37
  The compiler runs 3 passes over every `.ts`/`.tsx` file using the TypeScript Compiler API:
@@ -0,0 +1,23 @@
1
+ export type BindingDescriptor = {
2
+ variant: string;
3
+ };
4
+ /**
5
+ * Walk the `view` arrow function of every top-level `component({...})` call
6
+ * in the source and collect every `send({type: '...'})` call site's variant
7
+ * literal. Returns them in encounter order.
8
+ *
9
+ * False positives: any call of the form `identifier({ type: 'x', ... })` —
10
+ * we don't verify the callee resolves to the destructured `send` from the
11
+ * view argument, because that level of scope tracking is beyond the budget
12
+ * of this MVP extractor. Apps that call other identifiers with similarly
13
+ * shaped literals would see those in the output. In practice, the pattern
14
+ * is uncommon enough that false positives are rare.
15
+ *
16
+ * Missing: non-literal `type` values (e.g. `send({type: nextStep})`) are
17
+ * skipped. This is the correct behavior — we can only record statically-
18
+ * known variants.
19
+ *
20
+ * @see agent spec §5.2, §12.2
21
+ */
22
+ export declare function extractBindingDescriptors(source: string): BindingDescriptor[];
23
+ //# sourceMappingURL=binding-descriptors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binding-descriptors.d.ts","sourceRoot":"","sources":["../src/binding-descriptors.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,EAAE,CA2D7E"}
@@ -0,0 +1,82 @@
1
+ import ts from 'typescript';
2
+ /**
3
+ * Walk the `view` arrow function of every top-level `component({...})` call
4
+ * in the source and collect every `send({type: '...'})` call site's variant
5
+ * literal. Returns them in encounter order.
6
+ *
7
+ * False positives: any call of the form `identifier({ type: 'x', ... })` —
8
+ * we don't verify the callee resolves to the destructured `send` from the
9
+ * view argument, because that level of scope tracking is beyond the budget
10
+ * of this MVP extractor. Apps that call other identifiers with similarly
11
+ * shaped literals would see those in the output. In practice, the pattern
12
+ * is uncommon enough that false positives are rare.
13
+ *
14
+ * Missing: non-literal `type` values (e.g. `send({type: nextStep})`) are
15
+ * skipped. This is the correct behavior — we can only record statically-
16
+ * known variants.
17
+ *
18
+ * @see agent spec §5.2, §12.2
19
+ */
20
+ export function extractBindingDescriptors(source) {
21
+ const sf = ts.createSourceFile('view.ts', source, ts.ScriptTarget.Latest, true);
22
+ const out = [];
23
+ function visitComponentConfig(config) {
24
+ for (const prop of config.properties) {
25
+ if (!ts.isPropertyAssignment(prop))
26
+ continue;
27
+ if (!prop.name || !ts.isIdentifier(prop.name) || prop.name.text !== 'view')
28
+ continue;
29
+ const viewExpr = prop.initializer;
30
+ if (!ts.isArrowFunction(viewExpr) && !ts.isFunctionExpression(viewExpr))
31
+ continue;
32
+ collectSendCalls(viewExpr.body);
33
+ }
34
+ }
35
+ function collectSendCalls(node) {
36
+ if (ts.isCallExpression(node)) {
37
+ const callee = node.expression;
38
+ const first = node.arguments[0];
39
+ if (callee && ts.isIdentifier(callee) && first && ts.isObjectLiteralExpression(first)) {
40
+ const variant = readTypeLiteral(first);
41
+ if (variant !== null) {
42
+ out.push({ variant });
43
+ }
44
+ }
45
+ }
46
+ ts.forEachChild(node, collectSendCalls);
47
+ }
48
+ function readTypeLiteral(obj) {
49
+ for (const prop of obj.properties) {
50
+ if (!ts.isPropertyAssignment(prop))
51
+ continue;
52
+ if (!prop.name)
53
+ continue;
54
+ const nameOk = (ts.isIdentifier(prop.name) && prop.name.text === 'type') ||
55
+ (ts.isStringLiteral(prop.name) && prop.name.text === 'type');
56
+ if (!nameOk)
57
+ continue;
58
+ const init = prop.initializer;
59
+ if (ts.isStringLiteral(init))
60
+ return init.text;
61
+ if (ts.isNoSubstitutionTemplateLiteral(init))
62
+ return init.text;
63
+ }
64
+ return null;
65
+ }
66
+ function visitTopLevel(node) {
67
+ if (ts.isCallExpression(node)) {
68
+ const callee = node.expression;
69
+ const calleeName = ts.isIdentifier(callee) ? callee.text : null;
70
+ if (calleeName === 'component' && node.arguments.length > 0) {
71
+ const firstArg = node.arguments[0];
72
+ if (firstArg && ts.isObjectLiteralExpression(firstArg)) {
73
+ visitComponentConfig(firstArg);
74
+ }
75
+ }
76
+ }
77
+ ts.forEachChild(node, visitTopLevel);
78
+ }
79
+ ts.forEachChild(sf, visitTopLevel);
80
+ return out;
81
+ }
82
+ //# sourceMappingURL=binding-descriptors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binding-descriptors.js","sourceRoot":"","sources":["../src/binding-descriptors.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript'\n\nexport type BindingDescriptor = {\n variant: string\n}\n\n/**\n * Walk the `view` arrow function of every top-level `component({...})` call\n * in the source and collect every `send({type: '...'})` call site's variant\n * literal. Returns them in encounter order.\n *\n * False positives: any call of the form `identifier({ type: 'x', ... })` —\n * we don't verify the callee resolves to the destructured `send` from the\n * view argument, because that level of scope tracking is beyond the budget\n * of this MVP extractor. Apps that call other identifiers with similarly\n * shaped literals would see those in the output. In practice, the pattern\n * is uncommon enough that false positives are rare.\n *\n * Missing: non-literal `type` values (e.g. `send({type: nextStep})`) are\n * skipped. This is the correct behavior — we can only record statically-\n * known variants.\n *\n * @see agent spec §5.2, §12.2\n */\nexport function extractBindingDescriptors(source: string): BindingDescriptor[] {\n const sf = ts.createSourceFile('view.ts', source, ts.ScriptTarget.Latest, true)\n const out: BindingDescriptor[] = []\n\n function visitComponentConfig(config: ts.ObjectLiteralExpression): void {\n for (const prop of config.properties) {\n if (!ts.isPropertyAssignment(prop)) continue\n if (!prop.name || !ts.isIdentifier(prop.name) || prop.name.text !== 'view') continue\n const viewExpr = prop.initializer\n if (!ts.isArrowFunction(viewExpr) && !ts.isFunctionExpression(viewExpr)) continue\n collectSendCalls(viewExpr.body)\n }\n }\n\n function collectSendCalls(node: ts.Node): void {\n if (ts.isCallExpression(node)) {\n const callee = node.expression\n const first = node.arguments[0]\n if (callee && ts.isIdentifier(callee) && first && ts.isObjectLiteralExpression(first)) {\n const variant = readTypeLiteral(first)\n if (variant !== null) {\n out.push({ variant })\n }\n }\n }\n ts.forEachChild(node, collectSendCalls)\n }\n\n function readTypeLiteral(obj: ts.ObjectLiteralExpression): string | null {\n for (const prop of obj.properties) {\n if (!ts.isPropertyAssignment(prop)) continue\n if (!prop.name) continue\n const nameOk =\n (ts.isIdentifier(prop.name) && prop.name.text === 'type') ||\n (ts.isStringLiteral(prop.name) && prop.name.text === 'type')\n if (!nameOk) continue\n const init = prop.initializer\n if (ts.isStringLiteral(init)) return init.text\n if (ts.isNoSubstitutionTemplateLiteral(init)) return init.text\n }\n return null\n }\n\n function visitTopLevel(node: ts.Node): void {\n if (ts.isCallExpression(node)) {\n const callee = node.expression\n const calleeName = ts.isIdentifier(callee) ? callee.text : null\n if (calleeName === 'component' && node.arguments.length > 0) {\n const firstArg = node.arguments[0]\n if (firstArg && ts.isObjectLiteralExpression(firstArg)) {\n visitComponentConfig(firstArg)\n }\n }\n }\n ts.forEachChild(node, visitTopLevel)\n }\n\n ts.forEachChild(sf, visitTopLevel)\n return out\n}\n"]}
@@ -0,0 +1,20 @@
1
+ export interface BindingSourceEntry {
2
+ bindingIndex: number;
3
+ file: string;
4
+ line: number;
5
+ column: number;
6
+ }
7
+ export interface CompilerCacheEntry {
8
+ preSource: string;
9
+ postSource: string;
10
+ msgMaskMap: Record<string, number>;
11
+ bindingSources: BindingSourceEntry[];
12
+ }
13
+ export declare class CompilerCache {
14
+ private readonly cache;
15
+ set(componentName: string, entry: CompilerCacheEntry): void;
16
+ get(componentName: string): CompilerCacheEntry | undefined;
17
+ has(componentName: string): boolean;
18
+ }
19
+ export declare const compilerCache: CompilerCache;
20
+ //# sourceMappingURL=compiler-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compiler-cache.d.ts","sourceRoot":"","sources":["../src/compiler-cache.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAClC,cAAc,EAAE,kBAAkB,EAAE,CAAA;CACrC;AAID,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwC;IAE9D,GAAG,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,IAAI;IAQ3D,GAAG,CAAC,aAAa,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAI1D,GAAG,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO;CAGpC;AAED,eAAO,MAAM,aAAa,eAAsB,CAAA"}
@@ -0,0 +1,20 @@
1
+ const MAX_ENTRIES = 50;
2
+ export class CompilerCache {
3
+ cache = new Map();
4
+ set(componentName, entry) {
5
+ if (this.cache.has(componentName))
6
+ this.cache.delete(componentName);
7
+ this.cache.set(componentName, entry);
8
+ if (this.cache.size > MAX_ENTRIES) {
9
+ this.cache.delete(this.cache.keys().next().value);
10
+ }
11
+ }
12
+ get(componentName) {
13
+ return this.cache.get(componentName);
14
+ }
15
+ has(componentName) {
16
+ return this.cache.has(componentName);
17
+ }
18
+ }
19
+ export const compilerCache = new CompilerCache();
20
+ //# sourceMappingURL=compiler-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compiler-cache.js","sourceRoot":"","sources":["../src/compiler-cache.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,GAAG,EAAE,CAAA;AAEtB,MAAM,OAAO,aAAa;IACP,KAAK,GAAG,IAAI,GAAG,EAA8B,CAAA;IAE9D,GAAG,CAAC,aAAqB,EAAE,KAAyB;QAClD,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC;YAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,aAAa,CAAC,CAAA;QACnE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,EAAE,KAAK,CAAC,CAAA;QACpC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,WAAW,EAAE,CAAC;YAClC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAM,CAAC,CAAA;QACpD,CAAC;IACH,CAAC;IAED,GAAG,CAAC,aAAqB;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;IACtC,CAAC;IAED,GAAG,CAAC,aAAqB;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;IACtC,CAAC;CACF;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,aAAa,EAAE,CAAA","sourcesContent":["export interface BindingSourceEntry {\n bindingIndex: number\n file: string\n line: number\n column: number\n}\n\nexport interface CompilerCacheEntry {\n preSource: string\n postSource: string\n msgMaskMap: Record<string, number>\n bindingSources: BindingSourceEntry[]\n}\n\nconst MAX_ENTRIES = 50\n\nexport class CompilerCache {\n private readonly cache = new Map<string, CompilerCacheEntry>()\n\n set(componentName: string, entry: CompilerCacheEntry): void {\n if (this.cache.has(componentName)) this.cache.delete(componentName)\n this.cache.set(componentName, entry)\n if (this.cache.size > MAX_ENTRIES) {\n this.cache.delete(this.cache.keys().next().value!)\n }\n }\n\n get(componentName: string): CompilerCacheEntry | undefined {\n return this.cache.get(componentName)\n }\n\n has(componentName: string): boolean {\n return this.cache.has(componentName)\n }\n}\n\nexport const compilerCache = new CompilerCache()\n"]}
package/dist/index.d.ts CHANGED
@@ -47,6 +47,32 @@ export interface LluiPluginOptions {
47
47
  * template-clone to `elSplit`. Off by default.
48
48
  */
49
49
  verbose?: boolean;
50
+ /**
51
+ * Enables two things together when set:
52
+ *
53
+ * 1. Emits schemas + binding descriptors in prod builds so the
54
+ * @llui/agent runtime has metadata to advertise over its WS hello
55
+ * frame (see agent spec §7.4).
56
+ * 2. Auto-mounts `@llui/agent/server`'s router at `/agent/*` and its
57
+ * WS upgrade handler at `/agent/ws` on the Vite dev server — so
58
+ * plain `vite dev` has working agent endpoints with no extra
59
+ * server.ts wiring. Requires `@llui/agent` installed; if it isn't,
60
+ * the plugin warns and skips dev mounting (prod emission still
61
+ * works from Plan 3b).
62
+ *
63
+ * Pass `true` for defaults (random signing key per dev session;
64
+ * `identityResolver` returns `'dev-user'`). Pass an object to customize.
65
+ * Default `false` — metadata is dev-only, no agent endpoints.
66
+ */
67
+ agent?: boolean | AgentPluginConfig;
50
68
  }
69
+ export type AgentPluginConfig = {
70
+ /**
71
+ * HMAC signing key for tokens. ≥32 bytes. Rotation invalidates all
72
+ * tokens. Falls back to `process.env.AGENT_SIGNING_KEY`, then to a
73
+ * per-session random key (dev-only).
74
+ */
75
+ signingKey?: string;
76
+ };
51
77
  export default function llui(options?: LluiPluginOptions): Plugin;
52
78
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAA;AAOjD,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEhE,YAAY,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAqBtD,MAAM,WAAW,iBAAiB;IAChC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IAExB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IAEvB;;;;;;;;;;OAUG;IACH,gBAAgB,CAAC,EAAE,SAAS,cAAc,EAAE,CAAA;IAE5C;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAsCD,MAAM,CAAC,OAAO,UAAU,IAAI,CAAC,OAAO,GAAE,iBAAsB,GAAG,MAAM,CA4SpE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAA;AAOjD,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEhE,YAAY,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAqBtD,MAAM,WAAW,iBAAiB;IAChC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IAExB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IAEvB;;;;;;;;;;OAUG;IACH,gBAAgB,CAAC,EAAE,SAAS,cAAc,EAAE,CAAA;IAE5C;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB;;;;;;;;;;;;;;;;OAgBG;IACH,KAAK,CAAC,EAAE,OAAO,GAAG,iBAAiB,CAAA;CACpC;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAmLD,MAAM,CAAC,OAAO,UAAU,IAAI,CAAC,OAAO,GAAE,iBAAsB,GAAG,MAAM,CA6UpE"}
package/dist/index.js CHANGED
@@ -62,6 +62,119 @@ function resolveMcpCliPath(root) {
62
62
  return null;
63
63
  }
64
64
  }
65
+ /**
66
+ * Dynamically load @llui/agent/server relative to the app root and
67
+ * construct an agent server instance. Returns null if @llui/agent isn't
68
+ * installed — the plugin degrades to "prod schema emission only" mode.
69
+ */
70
+ async function loadAgentServer(appRoot, cfg) {
71
+ let serverModule;
72
+ try {
73
+ // Walk up from the app root to find node_modules/@llui/agent. Works
74
+ // for both pnpm workspace and regular npm installs. Direct file-system
75
+ // walk avoids "exports" / "subpath './package.json'" gymnastics that
76
+ // require.resolve and Node's module resolver disagree on.
77
+ const pkgDir = findPackageDir(appRoot, '@llui/agent');
78
+ if (!pkgDir)
79
+ throw new Error('not found in any ancestor node_modules');
80
+ const pkg = JSON.parse(readFileSync(resolve(pkgDir, 'package.json'), 'utf8'));
81
+ const serverExport = pkg.exports?.['./server'];
82
+ const rel = typeof serverExport === 'string' ? serverExport : serverExport?.import;
83
+ if (!rel)
84
+ throw new Error('missing ./server export in package.json');
85
+ const modUrl = new URL(`file://${resolve(pkgDir, rel)}`).href;
86
+ serverModule = (await import(modUrl));
87
+ }
88
+ catch (e) {
89
+ console.warn('[llui] agent: true is set but `@llui/agent` could not be loaded: ' +
90
+ (e instanceof Error ? e.message : String(e)));
91
+ return null;
92
+ }
93
+ const { randomBytes } = await import('node:crypto');
94
+ const signingKey = cfg.signingKey ?? process.env['AGENT_SIGNING_KEY'] ?? randomBytes(32).toString('base64url');
95
+ return serverModule.createLluiAgentServer({
96
+ signingKey,
97
+ identityResolver: async () => 'dev-user',
98
+ });
99
+ }
100
+ /**
101
+ * Register the agent middleware + WS upgrade on the Vite dev server.
102
+ * Must be called synchronously from configureServer so registration
103
+ * happens BEFORE Vite installs its catch-all SPA fallback.
104
+ */
105
+ function registerAgentMiddleware(server, agent) {
106
+ // Connect-style middleware. Vite's middleware chain runs in order, so
107
+ // synchronous registration during configureServer places us ahead of
108
+ // Vite's catch-all fallback.
109
+ server.middlewares.use((req, res, next) => {
110
+ const url = req.url ?? '/';
111
+ if (!url.startsWith('/agent/') && url !== '/agent') {
112
+ next();
113
+ return;
114
+ }
115
+ void handleAgentRequest(req, res, agent.router).catch((e) => {
116
+ console.error('[llui] agent middleware error:', e);
117
+ next(e);
118
+ });
119
+ });
120
+ // WS upgrade: only /agent/ws goes to the agent. Vite's own HMR upgrade
121
+ // uses a different path and runs as a separate listener on the same
122
+ // event, so this filter keeps both coexisting.
123
+ server.httpServer?.on('upgrade', (req, socket, head) => {
124
+ const url = new URL(req.url ?? '/', 'http://localhost');
125
+ if (url.pathname === '/agent/ws') {
126
+ agent.wsUpgrade(req, socket, head);
127
+ }
128
+ });
129
+ console.info('[llui] agent dev endpoints active: POST /agent/mint, WS /agent/ws, LAP /agent/lap/v1/*');
130
+ }
131
+ /**
132
+ * Walk up from `start` looking for `node_modules/<pkgName>`. Returns the
133
+ * absolute path to the package directory, or null if not found.
134
+ */
135
+ function findPackageDir(start, pkgName) {
136
+ let dir = resolve(start);
137
+ while (true) {
138
+ const candidate = resolve(dir, 'node_modules', pkgName);
139
+ if (existsSync(resolve(candidate, 'package.json')))
140
+ return candidate;
141
+ const parent = dirname(dir);
142
+ if (parent === dir)
143
+ return null;
144
+ dir = parent;
145
+ }
146
+ }
147
+ /** Convert a Node http req → Web Request, call router, write the response. */
148
+ async function handleAgentRequest(req, res, router) {
149
+ const method = req.method ?? 'GET';
150
+ const url = req.url ?? '/';
151
+ const headers = {};
152
+ for (const [k, v] of Object.entries(req.headers)) {
153
+ if (v === undefined)
154
+ continue;
155
+ headers[k] = Array.isArray(v) ? v.join(', ') : v;
156
+ }
157
+ let body;
158
+ if (!['GET', 'HEAD'].includes(method)) {
159
+ const chunks = [];
160
+ for await (const chunk of req)
161
+ chunks.push(chunk);
162
+ if (chunks.length > 0)
163
+ body = new Uint8Array(Buffer.concat(chunks));
164
+ }
165
+ const origin = `http://${req.headers.host ?? 'localhost'}`;
166
+ const webReq = new Request(`${origin}${url}`, { method, headers, body });
167
+ const webRes = await router(webReq);
168
+ if (!webRes) {
169
+ res.statusCode = 404;
170
+ res.end();
171
+ return;
172
+ }
173
+ res.statusCode = webRes.status;
174
+ webRes.headers.forEach((v, k) => res.setHeader(k, v));
175
+ const buf = Buffer.from(await webRes.arrayBuffer());
176
+ res.end(buf);
177
+ }
65
178
  export default function llui(options = {}) {
66
179
  let devMode = false;
67
180
  // `mcpPort` + `mcpMode` are resolved lazily in `configResolved` so we
@@ -77,6 +190,12 @@ export default function llui(options = {}) {
77
190
  const failOnWarning = options.failOnWarning === true;
78
191
  const disabledWarnings = new Set(options.disabledWarnings ?? []);
79
192
  const verbose = options.verbose === true;
193
+ const agent = options.agent ?? false;
194
+ const agentConfig = typeof agent === 'object' ? agent : {};
195
+ // Agent server instance — loaded in configResolved (async), registered
196
+ // in configureServer (sync). Null until loaded, or if @llui/agent isn't
197
+ // installed.
198
+ let agentServer = null;
80
199
  // File-based handshake with @llui/mcp. The MCP server writes a marker
81
200
  // file when its bridge starts; we watch it and send a Vite HMR custom
82
201
  // event so the browser can call __lluiConnect() automatically — without
@@ -137,8 +256,14 @@ export default function llui(options = {}) {
137
256
  return {
138
257
  name: 'llui',
139
258
  enforce: 'pre',
140
- configResolved(config) {
259
+ async configResolved(config) {
141
260
  devMode = config.command === 'serve' || config.mode === 'development';
261
+ // Load @llui/agent here (async) so we can register middleware
262
+ // synchronously in configureServer — which must happen BEFORE Vite
263
+ // installs its catch-all SPA/fallback middleware.
264
+ if (agent && devMode) {
265
+ agentServer = await loadAgentServer(config.root, agentConfig);
266
+ }
142
267
  if (options.mcpPort === false) {
143
268
  mcpMode = 'disabled';
144
269
  mcpPort = null;
@@ -164,6 +289,12 @@ export default function llui(options = {}) {
164
289
  }
165
290
  },
166
291
  configureServer(server) {
292
+ // Agent dev endpoints — runs regardless of mcp state. Must be before
293
+ // any early-returns below. Registration is synchronous because
294
+ // agentServer was preloaded in configResolved.
295
+ if (agentServer) {
296
+ registerAgentMiddleware(server, agentServer);
297
+ }
167
298
  if (mcpPort === null) {
168
299
  // #3 diagnostic: MCP server is running but the plugin is opted
169
300
  // out. Users in this state usually don't realize the mismatch —
@@ -298,6 +429,12 @@ export default function llui(options = {}) {
298
429
  // absent.
299
430
  notifyMcpReady(server);
300
431
  });
432
+ // ── Agent dev endpoints ──────────────────────────────────────
433
+ // When `agent: true` (or `agent: {...}`), auto-mount /agent/* on
434
+ // the Vite dev server so `pnpm dev` has working agent endpoints.
435
+ // Users with a custom server.ts (SSR apps) mount createLluiAgentServer
436
+ // themselves — configureServer also fires in middleware mode, but
437
+ // there server.httpServer is null so the upgrade hook is a no-op.
301
438
  },
302
439
  transform(code, id, options) {
303
440
  if (!id.endsWith('.ts') && !id.endsWith('.tsx'))
@@ -339,7 +476,7 @@ export default function llui(options = {}) {
339
476
  }
340
477
  }
341
478
  }
342
- const result = transformLlui(code, id, devMode, mcpPort, verbose);
479
+ const result = transformLlui(code, id, devMode, Boolean(agent), mcpPort, verbose);
343
480
  if (!result)
344
481
  return undefined;
345
482
  // Apply per-statement edits via MagicString for accurate source maps.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Plugin, ViteDevServer } from 'vite'\nimport MagicString from 'magic-string'\nimport { existsSync, readFileSync, writeFileSync, watch as fsWatch, type FSWatcher } from 'node:fs'\nimport { dirname, relative, resolve } from 'node:path'\nimport { createRequire } from 'node:module'\nimport { spawn, type ChildProcess } from 'node:child_process'\nimport { transformLlui, transformUseClientSsr, hasUseClientDirective } from './transform.js'\nimport { diagnose, type DiagnosticRule } from './diagnostics.js'\n\nexport type { DiagnosticRule } from './diagnostics.js'\n\n/**\n * Locate the workspace root so we share the MCP active marker file\n * with @llui/mcp regardless of which subdirectory the dev server runs in.\n * Mirrors `findWorkspaceRoot` from @llui/mcp — duplicated to avoid a\n * vite-plugin → mcp dependency cycle. The contract must stay in sync.\n */\nfunction findWorkspaceRoot(start: string = process.cwd()): string {\n let dir = resolve(start)\n let lastPackageJson: string | null = null\n while (true) {\n if (existsSync(resolve(dir, 'pnpm-workspace.yaml'))) return dir\n if (existsSync(resolve(dir, '.git'))) return dir\n if (existsSync(resolve(dir, 'package.json'))) lastPackageJson = dir\n const parent = dirname(dir)\n if (parent === dir) return lastPackageJson ?? start\n dir = parent\n }\n}\n\nexport interface LluiPluginOptions {\n /**\n * Port for the MCP debug bridge. In dev mode, the runtime relay connects\n * to `ws://127.0.0.1:<port>` so an external `llui-mcp` server can forward\n * tool calls into the running app.\n *\n * When omitted, the plugin checks whether `@llui/mcp` is resolvable from\n * the Vite project root. If yes → defaults to `5200`. If no → stays\n * disabled. This means installing `@llui/mcp` (+ starting its server)\n * Just Works without an explicit config entry. Pass an explicit `false`\n * to opt out even when `@llui/mcp` is installed; pass a number to use\n * a non-default port. When enabled but the MCP server isn't running,\n * the plugin returns 404 from its discovery endpoint and the browser\n * silently skips the connection — no retry noise.\n */\n mcpPort?: number | false\n\n /**\n * Treat every compiler diagnostic as a build error.\n *\n * Default `false` — diagnostics are emitted via rollup's `this.warn` and\n * can be ignored. Set to `true` in CI so lint-style regressions (namespace\n * imports, bitmask overflow, spread-in-children, `.map()` on state, etc.)\n * fail the build without requiring a custom `build.rollupOptions.onwarn`\n * handler.\n */\n failOnWarning?: boolean\n\n /**\n * Silence specific diagnostic rules without disabling the whole lint\n * pass. Each message is tagged with a rule name (shown in brackets at\n * the start of every warning, e.g. `[spread-in-children]`). Listing\n * a rule here drops all diagnostics with that tag before rollup sees\n * them — so they don't fire via `this.warn` and don't fail the build\n * even when `failOnWarning` is enabled.\n *\n * The valid rule names are enumerated by the `DiagnosticRule` type\n * re-exported from this module. Unknown rule names are ignored.\n */\n disabledWarnings?: readonly DiagnosticRule[]\n\n /**\n * Emit `[llui]`-prefixed `console.info` logs for every transformed\n * component file — state-path bit assignments, mask injections, and\n * helper compile/bail counts. Useful when diagnosing why a binding\n * isn't gated the way you expect, or why a call fell back from\n * template-clone to `elSplit`. Off by default.\n */\n verbose?: boolean\n}\n\n/**\n * Does `@llui/mcp` resolve from `root`'s node_modules? Uses\n * `require.resolve` so monorepo workspaces and hoisted installs both\n * work. Catches failures silently — the only consequence is that we\n * leave `mcpPort` disabled, which is the safe default.\n */\nfunction hasMcpPackage(root: string): boolean {\n try {\n const req = createRequire(resolve(root, 'package.json'))\n req.resolve('@llui/mcp/package.json')\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Resolve the path to the llui-mcp CLI entry. Reads `bin.llui-mcp`\n * from @llui/mcp's package.json and joins it against the package\n * directory. Returns null if @llui/mcp isn't resolvable.\n */\nfunction resolveMcpCliPath(root: string): string | null {\n try {\n const req = createRequire(resolve(root, 'package.json'))\n const pkgJsonPath = req.resolve('@llui/mcp/package.json')\n const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf8')) as {\n bin?: string | Record<string, string>\n }\n const binEntry = typeof pkgJson.bin === 'string' ? pkgJson.bin : pkgJson.bin?.['llui-mcp']\n if (!binEntry) return null\n return resolve(dirname(pkgJsonPath), binEntry)\n } catch {\n return null\n }\n}\n\nexport default function llui(options: LluiPluginOptions = {}): Plugin {\n let devMode = false\n // `mcpPort` + `mcpMode` are resolved lazily in `configResolved` so we\n // can check for @llui/mcp in the consuming project's node_modules.\n // - `options.mcpPort === false` → disabled\n // - explicit number → wire-only (user manages the server)\n // - undefined + @llui/mcp found → spawn (plugin starts llui-mcp --http)\n // - undefined + no @llui/mcp → disabled\n let mcpPort: number | null = null\n let mcpMode: 'disabled' | 'wire' | 'spawn' = 'disabled'\n let mcpCliPath: string | null = null\n let mcpChild: ChildProcess | null = null\n const failOnWarning = options.failOnWarning === true\n const disabledWarnings = new Set<string>(options.disabledWarnings ?? [])\n const verbose = options.verbose === true\n\n // File-based handshake with @llui/mcp. The MCP server writes a marker\n // file when its bridge starts; we watch it and send a Vite HMR custom\n // event so the browser can call __lluiConnect() automatically — without\n // retry spam, regardless of whether MCP or Vite started first.\n const activeFilePath = resolve(findWorkspaceRoot(), 'node_modules/.cache/llui-mcp/active.json')\n let mcpWatcher: FSWatcher | null = null\n let dirWatcher: FSWatcher | null = null\n // Cached once Vite's HTTP server emits `listening`. `stampDevUrl()`\n // uses this to write the URL into the marker file — either immediately\n // (if MCP already started and wrote one) or later when the marker\n // appears via the directory watcher (MCP-starts-after-Vite path).\n let cachedDevUrl: string | null = null\n\n function readMcpMarker(): { port: number; devUrl?: string } | null {\n try {\n if (!existsSync(activeFilePath)) return null\n const data = JSON.parse(readFileSync(activeFilePath, 'utf8')) as {\n port?: number\n devUrl?: string\n }\n if (typeof data.port !== 'number') return null\n return { port: data.port, ...(data.devUrl ? { devUrl: data.devUrl } : {}) }\n } catch {\n return null\n }\n }\n\n /**\n * Idempotently write `cachedDevUrl` into the marker file. No-op if the\n * URL hasn't been captured yet (Vite hasn't emitted `listening`) or if\n * the marker file doesn't exist (MCP hasn't started yet). Covers both\n * orderings — the listening hook calls this after caching, and the\n * directory watcher calls it when the marker appears later.\n */\n function stampDevUrl(): void {\n if (cachedDevUrl === null) return\n if (!existsSync(activeFilePath)) return\n try {\n const marker = JSON.parse(readFileSync(activeFilePath, 'utf8')) as Record<string, unknown>\n if (marker.devUrl === cachedDevUrl) return\n marker.devUrl = cachedDevUrl\n writeFileSync(activeFilePath, JSON.stringify(marker))\n } catch {\n // Best-effort — failure to update the marker should not crash Vite\n }\n }\n\n function notifyMcpReady(server: ViteDevServer): void {\n const marker = readMcpMarker()\n if (marker === null) return\n server.ws.send({ type: 'custom', event: 'llui:mcp-ready', data: marker })\n }\n\n function notifyMcpOffline(server: ViteDevServer): void {\n server.ws.send({ type: 'custom', event: 'llui:mcp-offline', data: {} })\n }\n\n return {\n name: 'llui',\n enforce: 'pre',\n\n configResolved(config) {\n devMode = config.command === 'serve' || config.mode === 'development'\n if (options.mcpPort === false) {\n mcpMode = 'disabled'\n mcpPort = null\n } else if (typeof options.mcpPort === 'number') {\n mcpMode = 'wire'\n mcpPort = options.mcpPort\n } else if (hasMcpPackage(config.root)) {\n mcpCliPath = resolveMcpCliPath(config.root)\n if (mcpCliPath) {\n mcpMode = 'spawn'\n mcpPort = 5200\n } else {\n mcpMode = 'wire'\n mcpPort = 5200\n }\n } else {\n mcpMode = 'disabled'\n mcpPort = null\n }\n },\n\n configureServer(server) {\n if (mcpPort === null) {\n // #3 diagnostic: MCP server is running but the plugin is opted\n // out. Users in this state usually don't realize the mismatch —\n // loud-and-early log saves the \"why isn't my MCP attached\" hunt.\n if (existsSync(activeFilePath)) {\n console.warn(\n `[llui] @llui/mcp server is running (marker at ${activeFilePath}) ` +\n `but the Vite plugin is opted out (mcpPort: false, or @llui/mcp ` +\n `isn't a dep of this project). Add \\`llui({ mcpPort: 5200 })\\` ` +\n `to vite.config to wire them up, or remove the marker file and ` +\n `stop the MCP server if the mismatch was unintended.`,\n )\n }\n return\n }\n\n // Spawn mode: plugin launches llui-mcp as a child process so\n // `pnpm dev` handles the whole stack. Skip spawning when a marker\n // already exists — something (usually a separate llui-mcp process\n // started before Vite) is already listening. The existing wire\n // behavior takes over from there.\n if (mcpMode === 'spawn' && mcpCliPath !== null && !existsSync(activeFilePath)) {\n mcpChild = spawn(process.execPath, [mcpCliPath, '--http', String(mcpPort)], {\n stdio: ['ignore', 'pipe', 'pipe'],\n env: { ...process.env, LLUI_MCP_PORT: String(mcpPort) },\n })\n mcpChild.stdout?.on('data', (buf: Buffer) => {\n process.stdout.write(`[mcp] ${buf.toString()}`)\n })\n mcpChild.stderr?.on('data', (buf: Buffer) => {\n process.stderr.write(`[mcp] ${buf.toString()}`)\n })\n mcpChild.on('exit', (code) => {\n if (code !== 0 && code !== null) {\n console.warn(`[llui] @llui/mcp child exited with code ${code}`)\n }\n mcpChild = null\n })\n const killChild = (): void => {\n if (mcpChild && !mcpChild.killed) mcpChild.kill('SIGTERM')\n }\n server.httpServer?.on('close', killChild)\n process.once('exit', killChild)\n }\n\n // HTTP endpoint: the browser fetches this on load to discover the\n // current MCP port. Avoids the race where HMR events sent before\n // the import.meta.hot listener registers get dropped — and lets\n // the browser connect to the actual port (which may differ from\n // the compile-time default if MCP was started with LLUI_MCP_PORT).\n server.middlewares.use('/__llui_mcp_status', (_req, res) => {\n const marker = readMcpMarker()\n if (marker === null) {\n res.statusCode = 404\n res.end()\n return\n }\n res.statusCode = 200\n res.setHeader('content-type', 'application/json')\n res.end(JSON.stringify({ port: marker.port }))\n })\n\n // Watch the marker file for create/delete. fs.watch on the parent\n // directory catches both events; the file itself may not exist\n // when we start watching.\n const dir = dirname(activeFilePath)\n try {\n // Watch the parent directory for the marker file appearing/disappearing\n const watchDir = (): void => {\n if (!existsSync(dir)) return\n dirWatcher = fsWatch(dir, (_event, filename) => {\n if (filename !== 'active.json') return\n if (existsSync(activeFilePath)) {\n // Stamp BEFORE notifying so the `llui:mcp-ready` payload\n // carries the cached devUrl. This is the MCP-after-Vite\n // path: listening already fired and cached the URL; the\n // marker is only now appearing.\n stampDevUrl()\n notifyMcpReady(server)\n } else {\n notifyMcpOffline(server)\n }\n })\n }\n if (existsSync(dir)) {\n watchDir()\n } else {\n // Parent directory doesn't exist yet — poll for it briefly\n const poll = setInterval(() => {\n if (existsSync(dir)) {\n clearInterval(poll)\n watchDir()\n }\n }, 1000)\n // Clean up the poller if vite shuts down before MCP starts\n server.httpServer?.on('close', () => clearInterval(poll))\n }\n } catch {\n // fs.watch can fail on some filesystems — degrade silently\n }\n\n // Re-send the ready event when a new HMR client connects, in case\n // the page loads while MCP is already running.\n server.ws.on('connection', () => {\n if (existsSync(activeFilePath)) notifyMcpReady(server)\n })\n\n server.httpServer?.on('close', () => {\n mcpWatcher?.close()\n dirWatcher?.close()\n mcpWatcher = null\n dirWatcher = null\n })\n\n // Once Vite's HTTP server is listening, cache our dev URL and stamp\n // it into the marker file. Two orderings are possible:\n // (a) MCP started FIRST → marker exists now → stampDevUrl() writes\n // it, and we broadcast llui:mcp-ready so the browser picks up\n // the devUrl without relying on an incidental fs.watch tick\n // (which can miss on NFS/SMB).\n // (b) MCP will start LATER → marker doesn't exist yet → stamp is a\n // no-op. When MCP eventually writes the marker, the directory\n // watcher fires, calls stampDevUrl(), and notifies.\n server.httpServer?.once('listening', () => {\n const address = server.httpServer?.address()\n if (!address || typeof address !== 'object') return\n const host =\n address.address === '::' || address.address === '0.0.0.0' ? 'localhost' : address.address\n cachedDevUrl = `http://${host}:${address.port}`\n stampDevUrl()\n // Broadcast after stamping so the payload carries devUrl. Only\n // fires in case (a) — notifyMcpReady no-ops when the marker is\n // absent.\n notifyMcpReady(server)\n })\n },\n\n transform(code, id, options) {\n if (!id.endsWith('.ts') && !id.endsWith('.tsx')) return\n\n // `'use client'` directive — SSR builds replace the module with a\n // stub so top-level imports and side effects never run on the\n // server. Client builds pass through to the normal transform; the\n // directive is effectively a no-op on the client.\n if (options?.ssr && hasUseClientDirective(code)) {\n const result = transformUseClientSsr(code, id)\n if (result) {\n const cwd = process.cwd()\n const rel = relative(cwd, id)\n const display = rel.startsWith('..') ? id : rel\n for (const warning of result.warnings) {\n this.warn(`${display}: ${warning}`)\n }\n return { code: result.output, map: { mappings: '' } }\n }\n }\n\n const diagnostics = diagnose(code)\n if (diagnostics.length > 0) {\n // Prefix every diagnostic with `<file>:<line>:<col>` plus the\n // `[rule-name]` tag so consumers logging `warning.message` in a\n // custom onwarn handler see both the location and the rule they\n // could silence via `disabledWarnings`.\n const cwd = process.cwd()\n const rel = relative(cwd, id)\n const display = rel.startsWith('..') ? id : rel\n for (const d of diagnostics) {\n if (disabledWarnings.has(d.rule)) continue\n const message = `${display}:${d.line}:${d.column}: [${d.rule}] ${d.message}`\n if (failOnWarning) {\n this.error({ message, loc: { line: d.line, column: d.column, file: id } })\n } else {\n this.warn(message, { line: d.line, column: d.column })\n }\n }\n }\n\n const result = transformLlui(code, id, devMode, mcpPort, verbose)\n if (!result) return undefined\n\n // Apply per-statement edits via MagicString for accurate source maps.\n // Untouched statements keep their original positions.\n const s = new MagicString(code)\n for (const edit of result.edits) {\n if (edit.start === edit.end) {\n // Insert at position — appendRight for middle, append for end-of-file\n if (edit.start === code.length) s.append(edit.replacement)\n else s.appendRight(edit.start, edit.replacement)\n } else {\n s.overwrite(edit.start, edit.end, edit.replacement)\n }\n }\n\n return {\n code: s.toString(),\n map: s.generateMap({ source: id, includeContent: true, hires: true }),\n }\n },\n }\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Plugin, ViteDevServer } from 'vite'\nimport MagicString from 'magic-string'\nimport { existsSync, readFileSync, writeFileSync, watch as fsWatch, type FSWatcher } from 'node:fs'\nimport { dirname, relative, resolve } from 'node:path'\nimport { createRequire } from 'node:module'\nimport { spawn, type ChildProcess } from 'node:child_process'\nimport { transformLlui, transformUseClientSsr, hasUseClientDirective } from './transform.js'\nimport { diagnose, type DiagnosticRule } from './diagnostics.js'\n\nexport type { DiagnosticRule } from './diagnostics.js'\n\n/**\n * Locate the workspace root so we share the MCP active marker file\n * with @llui/mcp regardless of which subdirectory the dev server runs in.\n * Mirrors `findWorkspaceRoot` from @llui/mcp — duplicated to avoid a\n * vite-plugin → mcp dependency cycle. The contract must stay in sync.\n */\nfunction findWorkspaceRoot(start: string = process.cwd()): string {\n let dir = resolve(start)\n let lastPackageJson: string | null = null\n while (true) {\n if (existsSync(resolve(dir, 'pnpm-workspace.yaml'))) return dir\n if (existsSync(resolve(dir, '.git'))) return dir\n if (existsSync(resolve(dir, 'package.json'))) lastPackageJson = dir\n const parent = dirname(dir)\n if (parent === dir) return lastPackageJson ?? start\n dir = parent\n }\n}\n\nexport interface LluiPluginOptions {\n /**\n * Port for the MCP debug bridge. In dev mode, the runtime relay connects\n * to `ws://127.0.0.1:<port>` so an external `llui-mcp` server can forward\n * tool calls into the running app.\n *\n * When omitted, the plugin checks whether `@llui/mcp` is resolvable from\n * the Vite project root. If yes → defaults to `5200`. If no → stays\n * disabled. This means installing `@llui/mcp` (+ starting its server)\n * Just Works without an explicit config entry. Pass an explicit `false`\n * to opt out even when `@llui/mcp` is installed; pass a number to use\n * a non-default port. When enabled but the MCP server isn't running,\n * the plugin returns 404 from its discovery endpoint and the browser\n * silently skips the connection — no retry noise.\n */\n mcpPort?: number | false\n\n /**\n * Treat every compiler diagnostic as a build error.\n *\n * Default `false` — diagnostics are emitted via rollup's `this.warn` and\n * can be ignored. Set to `true` in CI so lint-style regressions (namespace\n * imports, bitmask overflow, spread-in-children, `.map()` on state, etc.)\n * fail the build without requiring a custom `build.rollupOptions.onwarn`\n * handler.\n */\n failOnWarning?: boolean\n\n /**\n * Silence specific diagnostic rules without disabling the whole lint\n * pass. Each message is tagged with a rule name (shown in brackets at\n * the start of every warning, e.g. `[spread-in-children]`). Listing\n * a rule here drops all diagnostics with that tag before rollup sees\n * them — so they don't fire via `this.warn` and don't fail the build\n * even when `failOnWarning` is enabled.\n *\n * The valid rule names are enumerated by the `DiagnosticRule` type\n * re-exported from this module. Unknown rule names are ignored.\n */\n disabledWarnings?: readonly DiagnosticRule[]\n\n /**\n * Emit `[llui]`-prefixed `console.info` logs for every transformed\n * component file — state-path bit assignments, mask injections, and\n * helper compile/bail counts. Useful when diagnosing why a binding\n * isn't gated the way you expect, or why a call fell back from\n * template-clone to `elSplit`. Off by default.\n */\n verbose?: boolean\n\n /**\n * Enables two things together when set:\n *\n * 1. Emits schemas + binding descriptors in prod builds so the\n * @llui/agent runtime has metadata to advertise over its WS hello\n * frame (see agent spec §7.4).\n * 2. Auto-mounts `@llui/agent/server`'s router at `/agent/*` and its\n * WS upgrade handler at `/agent/ws` on the Vite dev server — so\n * plain `vite dev` has working agent endpoints with no extra\n * server.ts wiring. Requires `@llui/agent` installed; if it isn't,\n * the plugin warns and skips dev mounting (prod emission still\n * works from Plan 3b).\n *\n * Pass `true` for defaults (random signing key per dev session;\n * `identityResolver` returns `'dev-user'`). Pass an object to customize.\n * Default `false` — metadata is dev-only, no agent endpoints.\n */\n agent?: boolean | AgentPluginConfig\n}\n\nexport type AgentPluginConfig = {\n /**\n * HMAC signing key for tokens. ≥32 bytes. Rotation invalidates all\n * tokens. Falls back to `process.env.AGENT_SIGNING_KEY`, then to a\n * per-session random key (dev-only).\n */\n signingKey?: string\n}\n\n/**\n * Does `@llui/mcp` resolve from `root`'s node_modules? Uses\n * `require.resolve` so monorepo workspaces and hoisted installs both\n * work. Catches failures silently — the only consequence is that we\n * leave `mcpPort` disabled, which is the safe default.\n */\nfunction hasMcpPackage(root: string): boolean {\n try {\n const req = createRequire(resolve(root, 'package.json'))\n req.resolve('@llui/mcp/package.json')\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Resolve the path to the llui-mcp CLI entry. Reads `bin.llui-mcp`\n * from @llui/mcp's package.json and joins it against the package\n * directory. Returns null if @llui/mcp isn't resolvable.\n */\nfunction resolveMcpCliPath(root: string): string | null {\n try {\n const req = createRequire(resolve(root, 'package.json'))\n const pkgJsonPath = req.resolve('@llui/mcp/package.json')\n const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf8')) as {\n bin?: string | Record<string, string>\n }\n const binEntry = typeof pkgJson.bin === 'string' ? pkgJson.bin : pkgJson.bin?.['llui-mcp']\n if (!binEntry) return null\n return resolve(dirname(pkgJsonPath), binEntry)\n } catch {\n return null\n }\n}\n\ntype AgentServerInstance = {\n router: (req: Request) => Promise<Response | null>\n wsUpgrade: (\n req: import('http').IncomingMessage,\n socket: import('stream').Duplex,\n head: Buffer,\n ) => void\n}\n\n/**\n * Dynamically load @llui/agent/server relative to the app root and\n * construct an agent server instance. Returns null if @llui/agent isn't\n * installed — the plugin degrades to \"prod schema emission only\" mode.\n */\nasync function loadAgentServer(\n appRoot: string,\n cfg: AgentPluginConfig,\n): Promise<AgentServerInstance | null> {\n let serverModule: {\n createLluiAgentServer: (opts: {\n signingKey: string\n identityResolver?: (req: Request) => Promise<string | null>\n }) => AgentServerInstance\n }\n try {\n // Walk up from the app root to find node_modules/@llui/agent. Works\n // for both pnpm workspace and regular npm installs. Direct file-system\n // walk avoids \"exports\" / \"subpath './package.json'\" gymnastics that\n // require.resolve and Node's module resolver disagree on.\n const pkgDir = findPackageDir(appRoot, '@llui/agent')\n if (!pkgDir) throw new Error('not found in any ancestor node_modules')\n const pkg = JSON.parse(readFileSync(resolve(pkgDir, 'package.json'), 'utf8')) as {\n exports?: Record<string, { import?: string } | string>\n }\n const serverExport = pkg.exports?.['./server']\n const rel = typeof serverExport === 'string' ? serverExport : serverExport?.import\n if (!rel) throw new Error('missing ./server export in package.json')\n const modUrl = new URL(`file://${resolve(pkgDir, rel)}`).href\n serverModule = (await import(modUrl)) as typeof serverModule\n } catch (e) {\n console.warn(\n '[llui] agent: true is set but `@llui/agent` could not be loaded: ' +\n (e instanceof Error ? e.message : String(e)),\n )\n return null\n }\n\n const { randomBytes } = await import('node:crypto')\n const signingKey: string =\n cfg.signingKey ?? process.env['AGENT_SIGNING_KEY'] ?? randomBytes(32).toString('base64url')\n\n return serverModule.createLluiAgentServer({\n signingKey,\n identityResolver: async () => 'dev-user',\n })\n}\n\n/**\n * Register the agent middleware + WS upgrade on the Vite dev server.\n * Must be called synchronously from configureServer so registration\n * happens BEFORE Vite installs its catch-all SPA fallback.\n */\nfunction registerAgentMiddleware(server: ViteDevServer, agent: AgentServerInstance): void {\n // Connect-style middleware. Vite's middleware chain runs in order, so\n // synchronous registration during configureServer places us ahead of\n // Vite's catch-all fallback.\n server.middlewares.use((req, res, next) => {\n const url = req.url ?? '/'\n if (!url.startsWith('/agent/') && url !== '/agent') {\n next()\n return\n }\n void handleAgentRequest(req, res, agent.router).catch((e) => {\n console.error('[llui] agent middleware error:', e)\n next(e)\n })\n })\n\n // WS upgrade: only /agent/ws goes to the agent. Vite's own HMR upgrade\n // uses a different path and runs as a separate listener on the same\n // event, so this filter keeps both coexisting.\n server.httpServer?.on('upgrade', (req, socket, head) => {\n const url = new URL(req.url ?? '/', 'http://localhost')\n if (url.pathname === '/agent/ws') {\n agent.wsUpgrade(req, socket, head)\n }\n })\n\n console.info(\n '[llui] agent dev endpoints active: POST /agent/mint, WS /agent/ws, LAP /agent/lap/v1/*',\n )\n}\n\n/**\n * Walk up from `start` looking for `node_modules/<pkgName>`. Returns the\n * absolute path to the package directory, or null if not found.\n */\nfunction findPackageDir(start: string, pkgName: string): string | null {\n let dir = resolve(start)\n while (true) {\n const candidate = resolve(dir, 'node_modules', pkgName)\n if (existsSync(resolve(candidate, 'package.json'))) return candidate\n const parent = dirname(dir)\n if (parent === dir) return null\n dir = parent\n }\n}\n\n/** Convert a Node http req → Web Request, call router, write the response. */\nasync function handleAgentRequest(\n req: import('http').IncomingMessage,\n res: import('http').ServerResponse,\n router: (req: Request) => Promise<Response | null>,\n): Promise<void> {\n const method = req.method ?? 'GET'\n const url = req.url ?? '/'\n const headers: Record<string, string> = {}\n for (const [k, v] of Object.entries(req.headers)) {\n if (v === undefined) continue\n headers[k] = Array.isArray(v) ? v.join(', ') : v\n }\n let body: BodyInit | undefined\n if (!['GET', 'HEAD'].includes(method)) {\n const chunks: Buffer[] = []\n for await (const chunk of req) chunks.push(chunk as Buffer)\n if (chunks.length > 0) body = new Uint8Array(Buffer.concat(chunks))\n }\n const origin = `http://${req.headers.host ?? 'localhost'}`\n const webReq = new Request(`${origin}${url}`, { method, headers, body })\n const webRes = await router(webReq)\n if (!webRes) {\n res.statusCode = 404\n res.end()\n return\n }\n res.statusCode = webRes.status\n webRes.headers.forEach((v, k) => res.setHeader(k, v))\n const buf = Buffer.from(await webRes.arrayBuffer())\n res.end(buf)\n}\n\nexport default function llui(options: LluiPluginOptions = {}): Plugin {\n let devMode = false\n // `mcpPort` + `mcpMode` are resolved lazily in `configResolved` so we\n // can check for @llui/mcp in the consuming project's node_modules.\n // - `options.mcpPort === false` → disabled\n // - explicit number → wire-only (user manages the server)\n // - undefined + @llui/mcp found → spawn (plugin starts llui-mcp --http)\n // - undefined + no @llui/mcp → disabled\n let mcpPort: number | null = null\n let mcpMode: 'disabled' | 'wire' | 'spawn' = 'disabled'\n let mcpCliPath: string | null = null\n let mcpChild: ChildProcess | null = null\n const failOnWarning = options.failOnWarning === true\n const disabledWarnings = new Set<string>(options.disabledWarnings ?? [])\n const verbose = options.verbose === true\n const agent = options.agent ?? false\n const agentConfig: AgentPluginConfig = typeof agent === 'object' ? agent : {}\n // Agent server instance — loaded in configResolved (async), registered\n // in configureServer (sync). Null until loaded, or if @llui/agent isn't\n // installed.\n let agentServer: {\n router: (req: Request) => Promise<Response | null>\n wsUpgrade: (\n req: import('http').IncomingMessage,\n socket: import('stream').Duplex,\n head: Buffer,\n ) => void\n } | null = null\n\n // File-based handshake with @llui/mcp. The MCP server writes a marker\n // file when its bridge starts; we watch it and send a Vite HMR custom\n // event so the browser can call __lluiConnect() automatically — without\n // retry spam, regardless of whether MCP or Vite started first.\n const activeFilePath = resolve(findWorkspaceRoot(), 'node_modules/.cache/llui-mcp/active.json')\n let mcpWatcher: FSWatcher | null = null\n let dirWatcher: FSWatcher | null = null\n // Cached once Vite's HTTP server emits `listening`. `stampDevUrl()`\n // uses this to write the URL into the marker file — either immediately\n // (if MCP already started and wrote one) or later when the marker\n // appears via the directory watcher (MCP-starts-after-Vite path).\n let cachedDevUrl: string | null = null\n\n function readMcpMarker(): { port: number; devUrl?: string } | null {\n try {\n if (!existsSync(activeFilePath)) return null\n const data = JSON.parse(readFileSync(activeFilePath, 'utf8')) as {\n port?: number\n devUrl?: string\n }\n if (typeof data.port !== 'number') return null\n return { port: data.port, ...(data.devUrl ? { devUrl: data.devUrl } : {}) }\n } catch {\n return null\n }\n }\n\n /**\n * Idempotently write `cachedDevUrl` into the marker file. No-op if the\n * URL hasn't been captured yet (Vite hasn't emitted `listening`) or if\n * the marker file doesn't exist (MCP hasn't started yet). Covers both\n * orderings — the listening hook calls this after caching, and the\n * directory watcher calls it when the marker appears later.\n */\n function stampDevUrl(): void {\n if (cachedDevUrl === null) return\n if (!existsSync(activeFilePath)) return\n try {\n const marker = JSON.parse(readFileSync(activeFilePath, 'utf8')) as Record<string, unknown>\n if (marker.devUrl === cachedDevUrl) return\n marker.devUrl = cachedDevUrl\n writeFileSync(activeFilePath, JSON.stringify(marker))\n } catch {\n // Best-effort — failure to update the marker should not crash Vite\n }\n }\n\n function notifyMcpReady(server: ViteDevServer): void {\n const marker = readMcpMarker()\n if (marker === null) return\n server.ws.send({ type: 'custom', event: 'llui:mcp-ready', data: marker })\n }\n\n function notifyMcpOffline(server: ViteDevServer): void {\n server.ws.send({ type: 'custom', event: 'llui:mcp-offline', data: {} })\n }\n\n return {\n name: 'llui',\n enforce: 'pre',\n\n async configResolved(config) {\n devMode = config.command === 'serve' || config.mode === 'development'\n // Load @llui/agent here (async) so we can register middleware\n // synchronously in configureServer — which must happen BEFORE Vite\n // installs its catch-all SPA/fallback middleware.\n if (agent && devMode) {\n agentServer = await loadAgentServer(config.root, agentConfig)\n }\n if (options.mcpPort === false) {\n mcpMode = 'disabled'\n mcpPort = null\n } else if (typeof options.mcpPort === 'number') {\n mcpMode = 'wire'\n mcpPort = options.mcpPort\n } else if (hasMcpPackage(config.root)) {\n mcpCliPath = resolveMcpCliPath(config.root)\n if (mcpCliPath) {\n mcpMode = 'spawn'\n mcpPort = 5200\n } else {\n mcpMode = 'wire'\n mcpPort = 5200\n }\n } else {\n mcpMode = 'disabled'\n mcpPort = null\n }\n },\n\n configureServer(server) {\n // Agent dev endpoints — runs regardless of mcp state. Must be before\n // any early-returns below. Registration is synchronous because\n // agentServer was preloaded in configResolved.\n if (agentServer) {\n registerAgentMiddleware(server, agentServer)\n }\n\n if (mcpPort === null) {\n // #3 diagnostic: MCP server is running but the plugin is opted\n // out. Users in this state usually don't realize the mismatch —\n // loud-and-early log saves the \"why isn't my MCP attached\" hunt.\n if (existsSync(activeFilePath)) {\n console.warn(\n `[llui] @llui/mcp server is running (marker at ${activeFilePath}) ` +\n `but the Vite plugin is opted out (mcpPort: false, or @llui/mcp ` +\n `isn't a dep of this project). Add \\`llui({ mcpPort: 5200 })\\` ` +\n `to vite.config to wire them up, or remove the marker file and ` +\n `stop the MCP server if the mismatch was unintended.`,\n )\n }\n return\n }\n\n // Spawn mode: plugin launches llui-mcp as a child process so\n // `pnpm dev` handles the whole stack. Skip spawning when a marker\n // already exists — something (usually a separate llui-mcp process\n // started before Vite) is already listening. The existing wire\n // behavior takes over from there.\n if (mcpMode === 'spawn' && mcpCliPath !== null && !existsSync(activeFilePath)) {\n mcpChild = spawn(process.execPath, [mcpCliPath, '--http', String(mcpPort)], {\n stdio: ['ignore', 'pipe', 'pipe'],\n env: { ...process.env, LLUI_MCP_PORT: String(mcpPort) },\n })\n mcpChild.stdout?.on('data', (buf: Buffer) => {\n process.stdout.write(`[mcp] ${buf.toString()}`)\n })\n mcpChild.stderr?.on('data', (buf: Buffer) => {\n process.stderr.write(`[mcp] ${buf.toString()}`)\n })\n mcpChild.on('exit', (code) => {\n if (code !== 0 && code !== null) {\n console.warn(`[llui] @llui/mcp child exited with code ${code}`)\n }\n mcpChild = null\n })\n const killChild = (): void => {\n if (mcpChild && !mcpChild.killed) mcpChild.kill('SIGTERM')\n }\n server.httpServer?.on('close', killChild)\n process.once('exit', killChild)\n }\n\n // HTTP endpoint: the browser fetches this on load to discover the\n // current MCP port. Avoids the race where HMR events sent before\n // the import.meta.hot listener registers get dropped — and lets\n // the browser connect to the actual port (which may differ from\n // the compile-time default if MCP was started with LLUI_MCP_PORT).\n server.middlewares.use('/__llui_mcp_status', (_req, res) => {\n const marker = readMcpMarker()\n if (marker === null) {\n res.statusCode = 404\n res.end()\n return\n }\n res.statusCode = 200\n res.setHeader('content-type', 'application/json')\n res.end(JSON.stringify({ port: marker.port }))\n })\n\n // Watch the marker file for create/delete. fs.watch on the parent\n // directory catches both events; the file itself may not exist\n // when we start watching.\n const dir = dirname(activeFilePath)\n try {\n // Watch the parent directory for the marker file appearing/disappearing\n const watchDir = (): void => {\n if (!existsSync(dir)) return\n dirWatcher = fsWatch(dir, (_event, filename) => {\n if (filename !== 'active.json') return\n if (existsSync(activeFilePath)) {\n // Stamp BEFORE notifying so the `llui:mcp-ready` payload\n // carries the cached devUrl. This is the MCP-after-Vite\n // path: listening already fired and cached the URL; the\n // marker is only now appearing.\n stampDevUrl()\n notifyMcpReady(server)\n } else {\n notifyMcpOffline(server)\n }\n })\n }\n if (existsSync(dir)) {\n watchDir()\n } else {\n // Parent directory doesn't exist yet — poll for it briefly\n const poll = setInterval(() => {\n if (existsSync(dir)) {\n clearInterval(poll)\n watchDir()\n }\n }, 1000)\n // Clean up the poller if vite shuts down before MCP starts\n server.httpServer?.on('close', () => clearInterval(poll))\n }\n } catch {\n // fs.watch can fail on some filesystems — degrade silently\n }\n\n // Re-send the ready event when a new HMR client connects, in case\n // the page loads while MCP is already running.\n server.ws.on('connection', () => {\n if (existsSync(activeFilePath)) notifyMcpReady(server)\n })\n\n server.httpServer?.on('close', () => {\n mcpWatcher?.close()\n dirWatcher?.close()\n mcpWatcher = null\n dirWatcher = null\n })\n\n // Once Vite's HTTP server is listening, cache our dev URL and stamp\n // it into the marker file. Two orderings are possible:\n // (a) MCP started FIRST → marker exists now → stampDevUrl() writes\n // it, and we broadcast llui:mcp-ready so the browser picks up\n // the devUrl without relying on an incidental fs.watch tick\n // (which can miss on NFS/SMB).\n // (b) MCP will start LATER → marker doesn't exist yet → stamp is a\n // no-op. When MCP eventually writes the marker, the directory\n // watcher fires, calls stampDevUrl(), and notifies.\n server.httpServer?.once('listening', () => {\n const address = server.httpServer?.address()\n if (!address || typeof address !== 'object') return\n const host =\n address.address === '::' || address.address === '0.0.0.0' ? 'localhost' : address.address\n cachedDevUrl = `http://${host}:${address.port}`\n stampDevUrl()\n // Broadcast after stamping so the payload carries devUrl. Only\n // fires in case (a) — notifyMcpReady no-ops when the marker is\n // absent.\n notifyMcpReady(server)\n })\n\n // ── Agent dev endpoints ──────────────────────────────────────\n // When `agent: true` (or `agent: {...}`), auto-mount /agent/* on\n // the Vite dev server so `pnpm dev` has working agent endpoints.\n // Users with a custom server.ts (SSR apps) mount createLluiAgentServer\n // themselves — configureServer also fires in middleware mode, but\n // there server.httpServer is null so the upgrade hook is a no-op.\n },\n\n transform(code, id, options) {\n if (!id.endsWith('.ts') && !id.endsWith('.tsx')) return\n\n // `'use client'` directive — SSR builds replace the module with a\n // stub so top-level imports and side effects never run on the\n // server. Client builds pass through to the normal transform; the\n // directive is effectively a no-op on the client.\n if (options?.ssr && hasUseClientDirective(code)) {\n const result = transformUseClientSsr(code, id)\n if (result) {\n const cwd = process.cwd()\n const rel = relative(cwd, id)\n const display = rel.startsWith('..') ? id : rel\n for (const warning of result.warnings) {\n this.warn(`${display}: ${warning}`)\n }\n return { code: result.output, map: { mappings: '' } }\n }\n }\n\n const diagnostics = diagnose(code)\n if (diagnostics.length > 0) {\n // Prefix every diagnostic with `<file>:<line>:<col>` plus the\n // `[rule-name]` tag so consumers logging `warning.message` in a\n // custom onwarn handler see both the location and the rule they\n // could silence via `disabledWarnings`.\n const cwd = process.cwd()\n const rel = relative(cwd, id)\n const display = rel.startsWith('..') ? id : rel\n for (const d of diagnostics) {\n if (disabledWarnings.has(d.rule)) continue\n const message = `${display}:${d.line}:${d.column}: [${d.rule}] ${d.message}`\n if (failOnWarning) {\n this.error({ message, loc: { line: d.line, column: d.column, file: id } })\n } else {\n this.warn(message, { line: d.line, column: d.column })\n }\n }\n }\n\n const result = transformLlui(code, id, devMode, Boolean(agent), mcpPort, verbose)\n if (!result) return undefined\n\n // Apply per-statement edits via MagicString for accurate source maps.\n // Untouched statements keep their original positions.\n const s = new MagicString(code)\n for (const edit of result.edits) {\n if (edit.start === edit.end) {\n // Insert at position — appendRight for middle, append for end-of-file\n if (edit.start === code.length) s.append(edit.replacement)\n else s.appendRight(edit.start, edit.replacement)\n } else {\n s.overwrite(edit.start, edit.end, edit.replacement)\n }\n }\n\n return {\n code: s.toString(),\n map: s.generateMap({ source: id, includeContent: true, hires: true }),\n }\n },\n }\n}\n"]}
@@ -0,0 +1,23 @@
1
+ export type MessageAnnotations = {
2
+ intent: string | null;
3
+ alwaysAffordable: boolean;
4
+ requiresConfirm: boolean;
5
+ humanOnly: boolean;
6
+ };
7
+ /**
8
+ * Walk a Msg-like discriminated-union type alias and extract JSDoc
9
+ * annotations attached to each union member. Returns null if no
10
+ * recognizable union is found so callers can skip emission cleanly.
11
+ *
12
+ * Expected JSDoc grammar (order-independent):
13
+ * @intent("human readable")
14
+ * @alwaysAffordable
15
+ * @requiresConfirm
16
+ * @humanOnly
17
+ *
18
+ * Unknown tags are ignored; malformed @intent (no quoted string) is
19
+ * treated as "no intent". The four flags are booleans; any occurrence
20
+ * of the tag sets it true.
21
+ */
22
+ export declare function extractMsgAnnotations(source: string): Record<string, MessageAnnotations> | null;
23
+ //# sourceMappingURL=msg-annotations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"msg-annotations.d.ts","sourceRoot":"","sources":["../src/msg-annotations.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,gBAAgB,EAAE,OAAO,CAAA;IACzB,eAAe,EAAE,OAAO,CAAA;IACxB,SAAS,EAAE,OAAO,CAAA;CACnB,CAAA;AASD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,IAAI,CA2B/F"}