theokit 0.37.0 → 0.39.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.
Files changed (77) hide show
  1. package/dist/{actions-virtual-module-6HQK5GHV.js → actions-virtual-module-RNPMHJOM.js} +3 -3
  2. package/dist/{actions-virtual-module-HYCFRMC3.js → actions-virtual-module-YGKSEIOJ.js} +4 -3
  3. package/dist/{actions-virtual-module-HYCFRMC3.js.map → actions-virtual-module-YGKSEIOJ.js.map} +1 -1
  4. package/dist/{agent-PSJ77NJA.js → agent-NDHBKRAV.js} +3 -3
  5. package/dist/{agents-typed-client-MAAILRI6.js → agents-typed-client-HSZKZY2P.js} +23 -5
  6. package/dist/agents-typed-client-HSZKZY2P.js.map +1 -0
  7. package/dist/{agents-typed-client-5DCGDWFZ.js → agents-typed-client-WFK7M5A7.js} +23 -5
  8. package/dist/agents-typed-client-WFK7M5A7.js.map +1 -0
  9. package/dist/{app-typed-client-K3PXIZDZ.js → app-typed-client-FQPUOL6A.js} +113 -24
  10. package/dist/app-typed-client-FQPUOL6A.js.map +1 -0
  11. package/dist/{app-typed-client-X2A6NMI2.js → app-typed-client-YSLQU5HT.js} +114 -24
  12. package/dist/app-typed-client-YSLQU5HT.js.map +1 -0
  13. package/dist/{build-4HOXGMZV.js → build-PF65J7N6.js} +6 -6
  14. package/dist/{chunk-CEHXC5NI.js → chunk-32GOXYA7.js} +161 -142
  15. package/dist/chunk-32GOXYA7.js.map +1 -0
  16. package/dist/{chunk-J5PK5FRN.js → chunk-BEQQ6WCK.js} +19 -227
  17. package/dist/chunk-BEQQ6WCK.js.map +1 -0
  18. package/dist/{chunk-EZWFDHTI.js → chunk-CL3HJTWQ.js} +5 -5
  19. package/dist/{chunk-AXF3KYY5.js → chunk-ELRVWUOZ.js} +97 -41
  20. package/dist/chunk-ELRVWUOZ.js.map +1 -0
  21. package/dist/{chunk-4FE56YQP.js → chunk-GW5H4KB4.js} +2 -2
  22. package/dist/{chunk-PMBYKLLN.js → chunk-HEDLJJDF.js} +232 -1
  23. package/dist/chunk-HEDLJJDF.js.map +1 -0
  24. package/dist/{chunk-GNUXRXHI.js → chunk-HQQADLLD.js} +110 -7
  25. package/dist/chunk-HQQADLLD.js.map +1 -0
  26. package/dist/{chunk-XQPBWMC2.js → chunk-I4BTOOZ2.js} +123 -352
  27. package/dist/chunk-I4BTOOZ2.js.map +1 -0
  28. package/dist/chunk-KLFCJQXU.js +425 -0
  29. package/dist/chunk-KLFCJQXU.js.map +1 -0
  30. package/dist/{chunk-2WTL2GZO.js → chunk-QWBXUZAX.js} +2 -1
  31. package/dist/cli/index.js +5 -5
  32. package/dist/client/core.d.ts +12 -1
  33. package/dist/client/core.js +1 -1
  34. package/dist/client/index.d.ts +53 -8
  35. package/dist/client/index.js +29 -7
  36. package/dist/client/index.js.map +1 -1
  37. package/dist/controller-swc-transform-URELVCBM.js +27 -0
  38. package/dist/controller-swc-transform-URELVCBM.js.map +1 -0
  39. package/dist/controller-swc-transform-XV3ETSE3.js +28 -0
  40. package/dist/controller-swc-transform-XV3ETSE3.js.map +1 -0
  41. package/dist/{dev-GA572OS7.js → dev-YP2QH576.js} +9 -9
  42. package/dist/index.js +4 -4
  43. package/dist/{internal-api-N6JGTRMN.js → internal-api-MRTUTAM7.js} +12 -3
  44. package/dist/{internal-api-TV4CFQY2.js → internal-api-ORXPQTEI.js} +13 -3
  45. package/dist/{mcp-Z3SZULLS.js → mcp-FWJE2QXV.js} +3 -3
  46. package/dist/{registry-3NB7KOUI.js → registry-FKPH7XEL.js} +1 -1
  47. package/dist/server/http/index.js +1 -1
  48. package/dist/server/index.js +2 -2
  49. package/dist/{start-SWWCB36Q.js → start-MK3MNSFB.js} +8 -8
  50. package/dist/vite-plugin/index.js +4 -4
  51. package/dist/{vite-plugin-OY5ESFTR.js → vite-plugin-42TH3C64.js} +6 -6
  52. package/package.json +2 -2
  53. package/dist/agents-typed-client-5DCGDWFZ.js.map +0 -1
  54. package/dist/agents-typed-client-MAAILRI6.js.map +0 -1
  55. package/dist/app-typed-client-K3PXIZDZ.js.map +0 -1
  56. package/dist/app-typed-client-X2A6NMI2.js.map +0 -1
  57. package/dist/chunk-AXF3KYY5.js.map +0 -1
  58. package/dist/chunk-CEHXC5NI.js.map +0 -1
  59. package/dist/chunk-GNUXRXHI.js.map +0 -1
  60. package/dist/chunk-J5PK5FRN.js.map +0 -1
  61. package/dist/chunk-PMBYKLLN.js.map +0 -1
  62. package/dist/chunk-VB2WWBIT.js +0 -1
  63. package/dist/chunk-XQPBWMC2.js.map +0 -1
  64. package/dist/vite-plugin-OY5ESFTR.js.map +0 -1
  65. /package/dist/{actions-virtual-module-6HQK5GHV.js.map → actions-virtual-module-RNPMHJOM.js.map} +0 -0
  66. /package/dist/{agent-PSJ77NJA.js.map → agent-NDHBKRAV.js.map} +0 -0
  67. /package/dist/{build-4HOXGMZV.js.map → build-PF65J7N6.js.map} +0 -0
  68. /package/dist/{chunk-EZWFDHTI.js.map → chunk-CL3HJTWQ.js.map} +0 -0
  69. /package/dist/{chunk-4FE56YQP.js.map → chunk-GW5H4KB4.js.map} +0 -0
  70. /package/dist/{chunk-2WTL2GZO.js.map → chunk-QWBXUZAX.js.map} +0 -0
  71. /package/dist/{dev-GA572OS7.js.map → dev-YP2QH576.js.map} +0 -0
  72. /package/dist/{chunk-VB2WWBIT.js.map → internal-api-MRTUTAM7.js.map} +0 -0
  73. /package/dist/{internal-api-N6JGTRMN.js.map → internal-api-ORXPQTEI.js.map} +0 -0
  74. /package/dist/{mcp-Z3SZULLS.js.map → mcp-FWJE2QXV.js.map} +0 -0
  75. /package/dist/{registry-3NB7KOUI.js.map → registry-FKPH7XEL.js.map} +0 -0
  76. /package/dist/{start-SWWCB36Q.js.map → start-MK3MNSFB.js.map} +0 -0
  77. /package/dist/{internal-api-TV4CFQY2.js.map → vite-plugin-42TH3C64.js.map} +0 -0
@@ -1,5 +1,5 @@
1
- import "./chunk-VB2WWBIT.js";
2
- import "./chunk-2WTL2GZO.js";
1
+ import "./chunk-KLFCJQXU.js";
2
+ import "./chunk-QWBXUZAX.js";
3
3
  import "./chunk-GY5Q27BJ.js";
4
4
  import "./chunk-5QW7IQQU.js";
5
5
  import "./chunk-WFNLNIJX.js";
@@ -221,4 +221,4 @@ export const actions = manifest
221
221
  export {
222
222
  actionsVirtualModule
223
223
  };
224
- //# sourceMappingURL=actions-virtual-module-6HQK5GHV.js.map
224
+ //# sourceMappingURL=actions-virtual-module-RNPMHJOM.js.map
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import "tsx/esm";
3
- import "./chunk-GNUXRXHI.js";
4
- import "./chunk-PMBYKLLN.js";
3
+ import "./chunk-HQQADLLD.js";
4
+ import "./chunk-HEDLJJDF.js";
5
+ import "./chunk-M6NMS5LR.js";
5
6
  import "./chunk-NQRNDPZJ.js";
6
7
  import "./chunk-L4GCEMNW.js";
7
8
  import {
@@ -211,4 +212,4 @@ export const actions = manifest
211
212
  export {
212
213
  actionsVirtualModule
213
214
  };
214
- //# sourceMappingURL=actions-virtual-module-HYCFRMC3.js.map
215
+ //# sourceMappingURL=actions-virtual-module-YGKSEIOJ.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/vite-plugin/actions-virtual-module.ts"],"sourcesContent":["/**\n * Vite plugin: resolves `@theo/actions` virtual module and emits a typed\n * Proxy facade that calls `theoFetch('/_actions/<name>', body)` against the\n * server-side action handlers.\n *\n * Per plan g3-server-actions-and-useaction v1.2 § Phase 3 / T3.1 + ADR D4.\n * Astro `vite-plugin-actions.ts` modular pattern. Single focused plugin\n * (not monolithic), virtual module + `configureServer` watcher.\n *\n * Module shape emitted on client environment: thin proxy that forwards\n * `actions.<name>(input)` to `POST /_actions/<name>` with JSON or\n * FormData body. On server environment: re-export of the manifest for\n * SSR / module-loader access (T1.3 sub-C consumer).\n *\n * EC-15 (DOCUMENT): HMR uses Vite default invalidation — coarse but\n * correct. Optimization (fine-grained invalidation of only consumers of\n * `@theo/actions`) deferred to G3.1 if dev-server p95 reload > 500ms.\n */\nimport { existsSync, mkdirSync, renameSync, watch as fsWatch, writeFileSync } from 'node:fs'\nimport { dirname, join, posix, resolve } from 'node:path'\n\nimport type { Plugin, ViteDevServer } from 'vite'\n\nimport { scanServerActionsEnriched, type ActionManifestEntry } from '../server/internal-api.js'\n\nconst VIRTUAL_ID = '@theo/actions'\nconst RESOLVED_ID = `\\0${VIRTUAL_ID}`\n\nexport interface ActionsVirtualModuleOptions {\n /** Path to server directory (where `actions/` lives). */\n serverDir: string\n /**\n * Optional `.theokit/` output dir for emitting `actions.d.ts`. When provided,\n * the plugin writes an ambient `declare module '@theo/actions'` so TS in\n * consumer apps can resolve the virtual module import. Mirrors the G1\n * `app-typed-client` emit pattern.\n */\n distDir?: string\n}\n\nexport function actionsVirtualModule(opts: ActionsVirtualModuleOptions): Plugin {\n const { serverDir, distDir } = opts\n\n function emitActionsDts(): void {\n if (distDir === undefined) return\n const entries = safeScan(serverDir)\n const dtsPath = posix.join(resolve(distDir).replace(/\\\\/g, '/'), 'actions.d.ts')\n const content = generateActionsDts(entries)\n try {\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time derived path\n mkdirSync(dirname(dtsPath), { recursive: true })\n const tmp = `${dtsPath}.${process.pid}.tmp`\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time derived path\n writeFileSync(tmp, content)\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time derived path\n renameSync(tmp, dtsPath)\n } catch (err) {\n console.error('[theokit:actions-virtual-module] dts emit error:', err)\n }\n }\n\n return {\n name: 'theo:actions-virtual-module',\n enforce: 'pre',\n configResolved() {\n emitActionsDts()\n },\n buildEnd() {\n emitActionsDts()\n },\n resolveId(id: string) {\n if (id === VIRTUAL_ID) return RESOLVED_ID\n return undefined\n },\n load(id: string) {\n if (id !== RESOLVED_ID) return undefined\n const entries = safeScan(serverDir)\n const ctx = this as unknown as { environment?: { name?: string } } | undefined\n const envName = ctx?.environment?.name\n const isClient = envName === 'client' || envName === undefined\n return isClient ? generateClientFacade(entries) : generateServerManifest(entries)\n },\n configureServer(server: ViteDevServer) {\n const actionsDir = join(serverDir, 'actions')\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time derived path\n if (!existsSync(actionsDir)) return\n // EC-15: coarse invalidation — invalidate ALL modules on actions file change.\n // Optimization deferred per plan ADR D4 note.\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time path\n const watcher = fsWatch(actionsDir, { recursive: true }, () => {\n emitActionsDts()\n const mod = server.moduleGraph.getModuleById(RESOLVED_ID)\n if (mod) {\n server.moduleGraph.invalidateModule(mod)\n }\n server.ws.send({ type: 'full-reload' })\n })\n server.httpServer?.once('close', () => {\n watcher.close()\n })\n },\n }\n}\n\n/**\n * Emit `.theokit/actions.d.ts` — ambient declaration of `@theo/actions` so\n * consumer TS apps can `import { actions } from '@theo/actions'` and get\n * typed completions for each scanned action name. Each entry maps name to\n * a generic `(input: unknown) => Promise<{data, error}>` invoker — the full\n * input/output inference from defineAction would require either monomorphic\n * dts-bundle or virtual type emit (deferred to a future plan).\n */\nfunction generateActionsDts(entries: readonly ActionManifestEntry[]): string {\n const lines = entries\n .map((entry) => {\n const invokerType = `(input: unknown) => Promise<{ data: unknown; error: undefined } | { data: undefined; error: { code: string; message: string; fields?: Record<string, string[]>; type?: string } }>`\n // P#4 plugin-forms typed __zodSchema (per plan p4-plugin-forms v1.1 EC-3).\n // When schemaFilePath present, emit intersection type so consumers see both\n // the callable invoker AND the __zodSchema field with the exact schema type.\n if (entry.schemaFilePath !== undefined) {\n const schemaImportPath = entry.schemaFilePath\n .replace(/\\\\/g, '/')\n .replace(/\\.(ts|tsx|js|jsx)$/, '.js')\n return ` ${JSON.stringify(entry.name)}: (${invokerType}) & { readonly __zodSchema: typeof import(${JSON.stringify(schemaImportPath)}).schema }`\n }\n return ` ${JSON.stringify(entry.name)}: ${invokerType}`\n })\n .join('\\n')\n return `// AUTO-GENERATED by @theo/actions virtual module. DO NOT EDIT MANUALLY.\ndeclare module '@theo/actions' {\n export const actions: {\n${lines}\n }\n}\n`\n}\n\nfunction safeScan(serverDir: string): ActionManifestEntry[] {\n try {\n return scanServerActionsEnriched(serverDir)\n } catch {\n // ActionScanError surfaces at build time; for HMR keep an empty manifest\n // so the dev server doesn't crash mid-edit.\n return []\n }\n}\n\n/**\n * Client-side module: emits a Proxy facade. For each action `name` in\n * manifest, `actions.<name>(input)` POSTs to `/_actions/<name>` with JSON\n * body (or FormData when input is a FormData instance) and parses the\n * `application/json+devalue` response via the runtime helper.\n */\nfunction generateClientFacade(entries: ActionManifestEntry[]): string {\n const entryLines = entries\n .map((entry) => ` ${JSON.stringify(entry.name)}: ${JSON.stringify(entry.urlPath)},`)\n .join('\\n')\n\n // P#4 plugin-forms shared-schema convention (per plan p4-plugin-forms v1.1 T1.1).\n // Emit ESM imports + ACTION_SCHEMA_MAP so the Proxy can attach __zodSchema.\n // Vite resolves absolute filesystem paths via its module graph; schema files\n // are isomorphic (zod-only, no server APIs) so they bundle into the client.\n const schemaEntries = entries.filter((e) => e.schemaFilePath !== undefined)\n const schemaImports = schemaEntries\n .map((entry, idx) => {\n // schemaEntries was filtered by `e.schemaFilePath !== undefined` above,\n // so the assertion is provably safe in this scope.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const importPath = entry.schemaFilePath!.replace(/\\\\/g, '/')\n return `import { schema as __theoSchema${idx} } from ${JSON.stringify(importPath)}`\n })\n .join('\\n')\n const schemaMapLines = schemaEntries\n .map((entry, idx) => ` ${JSON.stringify(entry.name)}: __theoSchema${idx},`)\n .join('\\n')\n\n return `// AUTO-GENERATED by @theo/actions virtual module\n/* eslint-disable */\n${schemaImports}\n\nconst ACTION_URL_MAP = {\n${entryLines}\n}\n\nconst ACTION_SCHEMA_MAP = {\n${schemaMapLines}\n}\n\n// Devtools telemetry — fires in the SAME realm as the Overlay React tree\n// (browser), so the Actions tab updates immediately on each call.\n// The Overlay component installs \\`window.__theoDevtoolsDispatcher\\` on\n// mount (dev only; prod tree-shakes the Overlay entirely). Reading the\n// global avoids a cross-package dynamic import that Vite's virtual-module\n// resolver cannot trace. Same pattern as React DevTools'\n// \\`__REACT_DEVTOOLS_GLOBAL_HOOK__\\`.\nfunction emitTelemetry(name, startedAt, input, outcome) {\n if (typeof window === 'undefined') return\n const dispatcher = window.__theoDevtoolsDispatcher\n if (!dispatcher || typeof dispatcher.onActionCall !== 'function') return\n try {\n dispatcher.onActionCall({\n id: 'act-' + String(Date.now()) + '-' + Math.random().toString(36).slice(2, 8),\n timestamp: startedAt,\n name,\n input,\n output: outcome.status === 'success' ? outcome.data : undefined,\n error: outcome.status === 'error' ? outcome.error : undefined,\n durationMs: Date.now() - startedAt,\n status: outcome.status,\n })\n } catch (_) {\n // dispatcher threw — devtools UI bug; never bubble to consumer code\n }\n}\n\nasync function callAction(name, url, input) {\n const startedAt = Date.now()\n const isForm = typeof FormData !== 'undefined' && input instanceof FormData\n const headers = { 'x-theo-action': '1' }\n const body = isForm ? input : JSON.stringify(input ?? {})\n if (!isForm) headers['content-type'] = 'application/json'\n let response\n try {\n response = await fetch(url, { method: 'POST', headers, body, credentials: 'same-origin' })\n } catch (e) {\n const err = { code: 'NETWORK_ERROR', message: e && e.message ? e.message : 'fetch failed' }\n emitTelemetry(name, startedAt, input, { status: 'error', error: err })\n return { data: undefined, error: err }\n }\n const contentType = response.headers.get('content-type') ?? ''\n let result\n if (response.status === 204) {\n result = { data: undefined, error: undefined }\n } else if (contentType.startsWith('application/json+devalue')) {\n const text = await response.text()\n const { parse } = await import('devalue')\n result = { data: parse(text, { URL: (h) => new URL(h) }), error: undefined }\n } else if (contentType.startsWith('application/json')) {\n const json = await response.json()\n result = response.ok ? { data: json, error: undefined } : { data: undefined, error: json }\n } else {\n result = { data: undefined, error: { code: 'INTERNAL_SERVER_ERROR', message: await response.text() } }\n }\n emitTelemetry(name, startedAt, input, result.error\n ? { status: 'error', error: result.error }\n : { status: 'success', data: result.data })\n return result\n}\n\n// P#4 plugin-forms cache: callable proxies must be stable per action so that\n// repeated property access does not allocate + reset the __zodSchema attach.\nconst ACTION_CALLABLE_CACHE = {}\n\nexport const actions = new Proxy({}, {\n get(target, prop) {\n if (typeof prop !== 'string') return undefined\n const url = ACTION_URL_MAP[prop]\n if (!url) return undefined\n if (!ACTION_CALLABLE_CACHE[prop]) {\n const callable = (input) => callAction(prop, url, input)\n const schema = ACTION_SCHEMA_MAP[prop]\n if (schema !== undefined) {\n Object.defineProperty(callable, '__zodSchema', {\n value: schema,\n enumerable: false,\n writable: false,\n configurable: false,\n })\n }\n ACTION_CALLABLE_CACHE[prop] = callable\n }\n return ACTION_CALLABLE_CACHE[prop]\n },\n})\n`\n}\n\n/**\n * Server-side module: re-exports the manifest entries for SSR / module-\n * loader consumption (T1.3 sub-C in action-execute.ts will route requests\n * via this manifest).\n */\nfunction generateServerManifest(entries: ActionManifestEntry[]): string {\n return `// AUTO-GENERATED by @theo/actions virtual module (server env)\n/* eslint-disable */\nexport const manifest = ${JSON.stringify(entries, null, 2)}\nexport const actions = manifest\n`\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/vite-plugin/actions-virtual-module.ts"],"sourcesContent":["/**\n * Vite plugin: resolves `@theo/actions` virtual module and emits a typed\n * Proxy facade that calls `theoFetch('/_actions/<name>', body)` against the\n * server-side action handlers.\n *\n * Per plan g3-server-actions-and-useaction v1.2 § Phase 3 / T3.1 + ADR D4.\n * Astro `vite-plugin-actions.ts` modular pattern. Single focused plugin\n * (not monolithic), virtual module + `configureServer` watcher.\n *\n * Module shape emitted on client environment: thin proxy that forwards\n * `actions.<name>(input)` to `POST /_actions/<name>` with JSON or\n * FormData body. On server environment: re-export of the manifest for\n * SSR / module-loader access (T1.3 sub-C consumer).\n *\n * EC-15 (DOCUMENT): HMR uses Vite default invalidation — coarse but\n * correct. Optimization (fine-grained invalidation of only consumers of\n * `@theo/actions`) deferred to G3.1 if dev-server p95 reload > 500ms.\n */\nimport { existsSync, mkdirSync, renameSync, watch as fsWatch, writeFileSync } from 'node:fs'\nimport { dirname, join, posix, resolve } from 'node:path'\n\nimport type { Plugin, ViteDevServer } from 'vite'\n\nimport { scanServerActionsEnriched, type ActionManifestEntry } from '../server/internal-api.js'\n\nconst VIRTUAL_ID = '@theo/actions'\nconst RESOLVED_ID = `\\0${VIRTUAL_ID}`\n\nexport interface ActionsVirtualModuleOptions {\n /** Path to server directory (where `actions/` lives). */\n serverDir: string\n /**\n * Optional `.theokit/` output dir for emitting `actions.d.ts`. When provided,\n * the plugin writes an ambient `declare module '@theo/actions'` so TS in\n * consumer apps can resolve the virtual module import. Mirrors the G1\n * `app-typed-client` emit pattern.\n */\n distDir?: string\n}\n\nexport function actionsVirtualModule(opts: ActionsVirtualModuleOptions): Plugin {\n const { serverDir, distDir } = opts\n\n function emitActionsDts(): void {\n if (distDir === undefined) return\n const entries = safeScan(serverDir)\n const dtsPath = posix.join(resolve(distDir).replace(/\\\\/g, '/'), 'actions.d.ts')\n const content = generateActionsDts(entries)\n try {\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time derived path\n mkdirSync(dirname(dtsPath), { recursive: true })\n const tmp = `${dtsPath}.${process.pid}.tmp`\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time derived path\n writeFileSync(tmp, content)\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time derived path\n renameSync(tmp, dtsPath)\n } catch (err) {\n console.error('[theokit:actions-virtual-module] dts emit error:', err)\n }\n }\n\n return {\n name: 'theo:actions-virtual-module',\n enforce: 'pre',\n configResolved() {\n emitActionsDts()\n },\n buildEnd() {\n emitActionsDts()\n },\n resolveId(id: string) {\n if (id === VIRTUAL_ID) return RESOLVED_ID\n return undefined\n },\n load(id: string) {\n if (id !== RESOLVED_ID) return undefined\n const entries = safeScan(serverDir)\n const ctx = this as unknown as { environment?: { name?: string } } | undefined\n const envName = ctx?.environment?.name\n const isClient = envName === 'client' || envName === undefined\n return isClient ? generateClientFacade(entries) : generateServerManifest(entries)\n },\n configureServer(server: ViteDevServer) {\n const actionsDir = join(serverDir, 'actions')\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time derived path\n if (!existsSync(actionsDir)) return\n // EC-15: coarse invalidation — invalidate ALL modules on actions file change.\n // Optimization deferred per plan ADR D4 note.\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time path\n const watcher = fsWatch(actionsDir, { recursive: true }, () => {\n emitActionsDts()\n const mod = server.moduleGraph.getModuleById(RESOLVED_ID)\n if (mod) {\n server.moduleGraph.invalidateModule(mod)\n }\n server.ws.send({ type: 'full-reload' })\n })\n server.httpServer?.once('close', () => {\n watcher.close()\n })\n },\n }\n}\n\n/**\n * Emit `.theokit/actions.d.ts` — ambient declaration of `@theo/actions` so\n * consumer TS apps can `import { actions } from '@theo/actions'` and get\n * typed completions for each scanned action name. Each entry maps name to\n * a generic `(input: unknown) => Promise<{data, error}>` invoker — the full\n * input/output inference from defineAction would require either monomorphic\n * dts-bundle or virtual type emit (deferred to a future plan).\n */\nfunction generateActionsDts(entries: readonly ActionManifestEntry[]): string {\n const lines = entries\n .map((entry) => {\n const invokerType = `(input: unknown) => Promise<{ data: unknown; error: undefined } | { data: undefined; error: { code: string; message: string; fields?: Record<string, string[]>; type?: string } }>`\n // P#4 plugin-forms typed __zodSchema (per plan p4-plugin-forms v1.1 EC-3).\n // When schemaFilePath present, emit intersection type so consumers see both\n // the callable invoker AND the __zodSchema field with the exact schema type.\n if (entry.schemaFilePath !== undefined) {\n const schemaImportPath = entry.schemaFilePath\n .replace(/\\\\/g, '/')\n .replace(/\\.(ts|tsx|js|jsx)$/, '.js')\n return ` ${JSON.stringify(entry.name)}: (${invokerType}) & { readonly __zodSchema: typeof import(${JSON.stringify(schemaImportPath)}).schema }`\n }\n return ` ${JSON.stringify(entry.name)}: ${invokerType}`\n })\n .join('\\n')\n return `// AUTO-GENERATED by @theo/actions virtual module. DO NOT EDIT MANUALLY.\ndeclare module '@theo/actions' {\n export const actions: {\n${lines}\n }\n}\n`\n}\n\nfunction safeScan(serverDir: string): ActionManifestEntry[] {\n try {\n return scanServerActionsEnriched(serverDir)\n } catch {\n // ActionScanError surfaces at build time; for HMR keep an empty manifest\n // so the dev server doesn't crash mid-edit.\n return []\n }\n}\n\n/**\n * Client-side module: emits a Proxy facade. For each action `name` in\n * manifest, `actions.<name>(input)` POSTs to `/_actions/<name>` with JSON\n * body (or FormData when input is a FormData instance) and parses the\n * `application/json+devalue` response via the runtime helper.\n */\nfunction generateClientFacade(entries: ActionManifestEntry[]): string {\n const entryLines = entries\n .map((entry) => ` ${JSON.stringify(entry.name)}: ${JSON.stringify(entry.urlPath)},`)\n .join('\\n')\n\n // P#4 plugin-forms shared-schema convention (per plan p4-plugin-forms v1.1 T1.1).\n // Emit ESM imports + ACTION_SCHEMA_MAP so the Proxy can attach __zodSchema.\n // Vite resolves absolute filesystem paths via its module graph; schema files\n // are isomorphic (zod-only, no server APIs) so they bundle into the client.\n const schemaEntries = entries.filter((e) => e.schemaFilePath !== undefined)\n const schemaImports = schemaEntries\n .map((entry, idx) => {\n // schemaEntries was filtered by `e.schemaFilePath !== undefined` above,\n // so the assertion is provably safe in this scope.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const importPath = entry.schemaFilePath!.replace(/\\\\/g, '/')\n return `import { schema as __theoSchema${idx} } from ${JSON.stringify(importPath)}`\n })\n .join('\\n')\n const schemaMapLines = schemaEntries\n .map((entry, idx) => ` ${JSON.stringify(entry.name)}: __theoSchema${idx},`)\n .join('\\n')\n\n return `// AUTO-GENERATED by @theo/actions virtual module\n/* eslint-disable */\n${schemaImports}\n\nconst ACTION_URL_MAP = {\n${entryLines}\n}\n\nconst ACTION_SCHEMA_MAP = {\n${schemaMapLines}\n}\n\n// Devtools telemetry — fires in the SAME realm as the Overlay React tree\n// (browser), so the Actions tab updates immediately on each call.\n// The Overlay component installs \\`window.__theoDevtoolsDispatcher\\` on\n// mount (dev only; prod tree-shakes the Overlay entirely). Reading the\n// global avoids a cross-package dynamic import that Vite's virtual-module\n// resolver cannot trace. Same pattern as React DevTools'\n// \\`__REACT_DEVTOOLS_GLOBAL_HOOK__\\`.\nfunction emitTelemetry(name, startedAt, input, outcome) {\n if (typeof window === 'undefined') return\n const dispatcher = window.__theoDevtoolsDispatcher\n if (!dispatcher || typeof dispatcher.onActionCall !== 'function') return\n try {\n dispatcher.onActionCall({\n id: 'act-' + String(Date.now()) + '-' + Math.random().toString(36).slice(2, 8),\n timestamp: startedAt,\n name,\n input,\n output: outcome.status === 'success' ? outcome.data : undefined,\n error: outcome.status === 'error' ? outcome.error : undefined,\n durationMs: Date.now() - startedAt,\n status: outcome.status,\n })\n } catch (_) {\n // dispatcher threw — devtools UI bug; never bubble to consumer code\n }\n}\n\nasync function callAction(name, url, input) {\n const startedAt = Date.now()\n const isForm = typeof FormData !== 'undefined' && input instanceof FormData\n const headers = { 'x-theo-action': '1' }\n const body = isForm ? input : JSON.stringify(input ?? {})\n if (!isForm) headers['content-type'] = 'application/json'\n let response\n try {\n response = await fetch(url, { method: 'POST', headers, body, credentials: 'same-origin' })\n } catch (e) {\n const err = { code: 'NETWORK_ERROR', message: e && e.message ? e.message : 'fetch failed' }\n emitTelemetry(name, startedAt, input, { status: 'error', error: err })\n return { data: undefined, error: err }\n }\n const contentType = response.headers.get('content-type') ?? ''\n let result\n if (response.status === 204) {\n result = { data: undefined, error: undefined }\n } else if (contentType.startsWith('application/json+devalue')) {\n const text = await response.text()\n const { parse } = await import('devalue')\n result = { data: parse(text, { URL: (h) => new URL(h) }), error: undefined }\n } else if (contentType.startsWith('application/json')) {\n const json = await response.json()\n result = response.ok ? { data: json, error: undefined } : { data: undefined, error: json }\n } else {\n result = { data: undefined, error: { code: 'INTERNAL_SERVER_ERROR', message: await response.text() } }\n }\n emitTelemetry(name, startedAt, input, result.error\n ? { status: 'error', error: result.error }\n : { status: 'success', data: result.data })\n return result\n}\n\n// P#4 plugin-forms cache: callable proxies must be stable per action so that\n// repeated property access does not allocate + reset the __zodSchema attach.\nconst ACTION_CALLABLE_CACHE = {}\n\nexport const actions = new Proxy({}, {\n get(target, prop) {\n if (typeof prop !== 'string') return undefined\n const url = ACTION_URL_MAP[prop]\n if (!url) return undefined\n if (!ACTION_CALLABLE_CACHE[prop]) {\n const callable = (input) => callAction(prop, url, input)\n const schema = ACTION_SCHEMA_MAP[prop]\n if (schema !== undefined) {\n Object.defineProperty(callable, '__zodSchema', {\n value: schema,\n enumerable: false,\n writable: false,\n configurable: false,\n })\n }\n ACTION_CALLABLE_CACHE[prop] = callable\n }\n return ACTION_CALLABLE_CACHE[prop]\n },\n})\n`\n}\n\n/**\n * Server-side module: re-exports the manifest entries for SSR / module-\n * loader consumption (T1.3 sub-C in action-execute.ts will route requests\n * via this manifest).\n */\nfunction generateServerManifest(entries: ActionManifestEntry[]): string {\n return `// AUTO-GENERATED by @theo/actions virtual module (server env)\n/* eslint-disable */\nexport const manifest = ${JSON.stringify(entries, null, 2)}\nexport const actions = manifest\n`\n}\n"],"mappings":"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","names":[]}
@@ -3,13 +3,13 @@ import "tsx/esm";
3
3
  import {
4
4
  agentCommand,
5
5
  createAgentSsrLoader
6
- } from "./chunk-EZWFDHTI.js";
7
- import "./chunk-M6NMS5LR.js";
6
+ } from "./chunk-CL3HJTWQ.js";
8
7
  import "./chunk-GDN3PXFH.js";
8
+ import "./chunk-M6NMS5LR.js";
9
9
  import "./chunk-L4GCEMNW.js";
10
10
  import "./chunk-P37RZRFV.js";
11
11
  export {
12
12
  agentCommand,
13
13
  createAgentSsrLoader
14
14
  };
15
- //# sourceMappingURL=agent-PSJ77NJA.js.map
15
+ //# sourceMappingURL=agent-NDHBKRAV.js.map
@@ -28,6 +28,7 @@ function generateAgentsDts({
28
28
  const agents = manifest.agents ?? [];
29
29
  const imports = [];
30
30
  const entries = [];
31
+ const handles = [];
31
32
  for (const agent of agents) {
32
33
  const alias = aliasFor(agent.name);
33
34
  imports.push(
@@ -36,7 +37,13 @@ function generateAgentsDts({
36
37
  entries.push(
37
38
  ` '${agent.name}': { input: InferAgentInput<${alias}>; tools: InferAgentToolNames<${alias}> }`
38
39
  );
40
+ handles.push(
41
+ ` export const ${agent.name}: AgentHandle<InferAgentInput<${alias}>, InferAgentToolNames<${alias}>>`
42
+ );
39
43
  }
44
+ const handleBlock = handles.length > 0 ? `
45
+ ${handles.join("\n")}
46
+ ` : "";
40
47
  const body = entries.length > 0 ? `{
41
48
  ${entries.join("\n")}
42
49
  }` : `{
@@ -47,7 +54,7 @@ ${entries.join("\n")}
47
54
  return `${FILE_HEADER}
48
55
  declare module '@theo/agents' {
49
56
  import type { InferAgentInput, InferAgentToolNames } from '@theokit/agents'
50
- import type { UseAgentReturn, AgentTransport } from 'theokit/client'
57
+ import type { UseAgentReturn, AgentTransport, AgentHandle } from 'theokit/client'
51
58
  ${importBlock}
52
59
  export interface AppAgents ${body}
53
60
 
@@ -55,12 +62,19 @@ ${importBlock}
55
62
  export function useAgent<K extends keyof AppAgents>(
56
63
  name: K,
57
64
  ): UseAgentReturn<AppAgents[K]['input'], AppAgents[K]['tools']>
65
+ // M47 \u2014 bind by a typed handle: \`import { chat } from '@theo/agents'; useAgent(chat)\`. No magic
66
+ // string (the path lives on the handle) and no duplicated input type (inferred from the handle).
67
+ export function useAgent<TInput, TTools extends string>(
68
+ handle: AgentHandle<TInput, TTools>,
69
+ ): UseAgentReturn<TInput, TTools>
58
70
  // M41 (ADR-0050) \u2014 terminal/desktop (and advanced): bind by an explicit AgentTransport
59
71
  // (e.g. InProcessTransport). The SAME hook, one consumption shape across every surface.
60
72
  export function useAgent<TInput = unknown>(
61
73
  transport: AgentTransport,
62
74
  ): UseAgentReturn<TInput>
63
- }
75
+
76
+ // M47 \u2014 one client-safe handle per agent (runtime value = { path }; types are phantom).
77
+ ${handleBlock}}
64
78
  `;
65
79
  }
66
80
  var VIRTUAL_AGENTS_ID = "@theo/agents";
@@ -139,8 +153,12 @@ function agentsTypedClientPlugin(opts) {
139
153
  },
140
154
  load(id) {
141
155
  if (id === RESOLVED_AGENTS_ID) {
142
- return `export { useAgent } from 'theokit/client'
143
- `;
156
+ const agents = opts.scanManifest(opts.projectRoot).agents ?? [];
157
+ const handleLines = agents.map(
158
+ (a) => `export const ${a.name} = agentHandle('/api/agents/${a.name}')`
159
+ );
160
+ return `export { useAgent, agentHandle } from 'theokit/client'
161
+ ` + (handleLines.length > 0 ? handleLines.join("\n") + "\n" : "");
144
162
  }
145
163
  return null;
146
164
  }
@@ -150,4 +168,4 @@ export {
150
168
  agentsTypedClientPlugin,
151
169
  generateAgentsDts
152
170
  };
153
- //# sourceMappingURL=agents-typed-client-MAAILRI6.js.map
171
+ //# sourceMappingURL=agents-typed-client-HSZKZY2P.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vite-plugin/agents-typed-client.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Codegen-time fs ops: all paths derive from `opts.projectRoot` / `opts.distDir`\n * (vite plugin opts — trusted, set by the framework's CLI). Never serves HTTP input.\n */\n/**\n * M2 (theokit-ai-first) — end-to-end typed binding for the `agents/*.ts` convention.\n *\n * Sibling of `app-typed-client.ts` (kept separate so the `@theo/client` output stays\n * byte-unchanged). Two parts:\n *\n * 1. `generateAgentsDts({ manifest, dtsOutPath, projectRoot })` — pure function emitting\n * `.theokit/agents.d.ts` (the virtual `@theo/agents` module) with `AppAgents` mapping\n * each scanned agent to `InferAgentInput` of its `defineAgent({ input })` schema, plus a\n * typed `useAgent` signature — so the client binds to `/api/agents/<name>` with the right\n * request type and ZERO manual wiring (DoD line 2).\n * 2. `agentsTypedClientPlugin(opts)` — Vite plugin that emits (1), rewrites on `agents/*`\n * changes, and serves the virtual `@theo/agents` runtime (`useAgent` from `theokit/client`).\n */\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'\nimport { dirname, posix } from 'node:path'\n\nimport type { Plugin, ViteDevServer } from 'vite'\n\nimport type { TheoManifest } from '../server/internal-api.js'\n\nconst FILE_HEADER = `// AUTO-GENERATED by theokit — do not edit.\n// Source: theokit/packages/theo/src/vite-plugin/agents-typed-client.ts (M2).\n// To refresh: \\`pnpm dev\\` or \\`pnpm build\\` regenerates this on every agents/ change.\n\n/* eslint-disable */\n`\n\n/** A safe TS identifier alias for an agent (name may contain path separators). */\nfunction aliasFor(name: string): string {\n return `_agent_${name.replace(/[^\\w$]/g, '_')}`\n}\n\n/** Relative `import type` path from the .d.ts dir to the agent source (extension stripped). */\nfunction importPath(dtsOutPath: string, projectRoot: string, filePath: string): string {\n const fromDir = dirname(dtsOutPath).replace(/\\\\/g, '/')\n const abs = posix.join(projectRoot.replace(/\\\\/g, '/'), filePath.replace(/\\\\/g, '/'))\n const noExt = abs.replace(/\\.[jt]sx?$/, '')\n let rel = posix.relative(fromDir, noExt)\n if (!rel.startsWith('.')) rel = `./${rel}`\n return rel\n}\n\nexport interface GenerateAgentsDtsOptions {\n manifest: TheoManifest\n /** Absolute path to the .d.ts that will be emitted (drives the import paths). */\n dtsOutPath: string\n /** Absolute project root — agents live at `<projectRoot>/agents`. */\n projectRoot: string\n}\n\nexport function generateAgentsDts({\n manifest,\n dtsOutPath,\n projectRoot,\n}: GenerateAgentsDtsOptions): string {\n const agents = manifest.agents ?? []\n const imports: string[] = []\n const entries: string[] = []\n const handles: string[] = []\n for (const agent of agents) {\n const alias = aliasFor(agent.name)\n imports.push(\n ` import type ${alias} from '${importPath(dtsOutPath, projectRoot, agent.filePath)}'`,\n )\n entries.push(\n ` '${agent.name}': { input: InferAgentInput<${alias}>; tools: InferAgentToolNames<${alias}> }`,\n )\n // M47 — a named, client-safe handle per agent: `import { chat } from '@theo/agents'; useAgent(chat)`.\n // cmd-click on the generated const hops to the agent source via the type-only import above.\n handles.push(\n ` export const ${agent.name}: AgentHandle<InferAgentInput<${alias}>, InferAgentToolNames<${alias}>>`,\n )\n }\n const handleBlock = handles.length > 0 ? `\\n${handles.join('\\n')}\\n` : ''\n\n const body =\n entries.length > 0\n ? `{\\n${entries.join('\\n')}\\n }`\n : `{\\n // No agents detected — add \\`agents/*.ts\\` to populate this binding.\\n }`\n\n const importBlock = imports.length > 0 ? `${imports.join('\\n')}\\n` : ''\n\n return `${FILE_HEADER}\ndeclare module '@theo/agents' {\n import type { InferAgentInput, InferAgentToolNames } from '@theokit/agents'\n import type { UseAgentReturn, AgentTransport, AgentHandle } from 'theokit/client'\n${importBlock}\n export interface AppAgents ${body}\n\n // Web (and any HTTP surface): bind by agent name — \\`send\\` is typed to the agent's \\`input\\` schema.\n export function useAgent<K extends keyof AppAgents>(\n name: K,\n ): UseAgentReturn<AppAgents[K]['input'], AppAgents[K]['tools']>\n // M47 — bind by a typed handle: \\`import { chat } from '@theo/agents'; useAgent(chat)\\`. No magic\n // string (the path lives on the handle) and no duplicated input type (inferred from the handle).\n export function useAgent<TInput, TTools extends string>(\n handle: AgentHandle<TInput, TTools>,\n ): UseAgentReturn<TInput, TTools>\n // M41 (ADR-0050) — terminal/desktop (and advanced): bind by an explicit AgentTransport\n // (e.g. InProcessTransport). The SAME hook, one consumption shape across every surface.\n export function useAgent<TInput = unknown>(\n transport: AgentTransport,\n ): UseAgentReturn<TInput>\n\n // M47 — one client-safe handle per agent (runtime value = { path }; types are phantom).\n${handleBlock}}\n`\n}\n\n// ─── Vite plugin ─────────────────────────────────────────────────────────────\n\nconst VIRTUAL_AGENTS_ID = '@theo/agents'\nconst RESOLVED_AGENTS_ID = '\\0@theo/agents'\n\nexport interface AgentsTypedClientPluginOptions {\n /** Absolute project root (agents live at `<projectRoot>/<agentsDir>`). */\n projectRoot: string\n /** Agents dir name (config `agentsDir`, default \"agents\"), relative to projectRoot — the watch glob. */\n agentsDir?: string\n /** Absolute `.theokit/` output dir (where `agents.d.ts` lands). */\n distDir: string\n /** Injects the scanned agents; returns the current manifest-shaped agent list. */\n scanManifest: (projectRoot: string) => TheoManifest\n}\n\n/** Atomic write via tmp + rename; skips when content is unchanged. */\nfunction writeIfChanged(filePath: string, content: string): void {\n if (existsSync(filePath)) {\n try {\n if (readFileSync(filePath, 'utf-8') === content) return\n } catch {\n /* fall through to write */\n }\n }\n mkdirSync(dirname(filePath), { recursive: true })\n const tmp = `${filePath}.${process.pid}.tmp`\n writeFileSync(tmp, content)\n renameSync(tmp, filePath)\n}\n\nfunction emitAgentsDts(opts: AgentsTypedClientPluginOptions): string {\n const dtsOutPath = posix.join(opts.distDir.replace(/\\\\/g, '/'), 'agents.d.ts')\n const content = generateAgentsDts({\n manifest: opts.scanManifest(opts.projectRoot),\n dtsOutPath,\n projectRoot: opts.projectRoot,\n })\n writeIfChanged(dtsOutPath, content)\n return dtsOutPath\n}\n\nexport function agentsTypedClientPlugin(opts: AgentsTypedClientPluginOptions): Plugin {\n let debounce: NodeJS.Timeout | undefined\n let viteServer: ViteDevServer | undefined\n\n const scheduleEmit = (): void => {\n if (debounce) clearTimeout(debounce)\n debounce = setTimeout(() => {\n try {\n emitAgentsDts(opts)\n if (viteServer) {\n const mod = viteServer.moduleGraph.getModuleById(RESOLVED_AGENTS_ID)\n if (mod) viteServer.moduleGraph.invalidateModule(mod)\n }\n } catch (err) {\n console.error('[theokit:agents-typed-client] codegen error:', err)\n }\n }, 100)\n }\n\n return {\n name: 'theokit:agents-typed-client',\n enforce: 'post',\n configResolved() {\n try {\n emitAgentsDts(opts)\n } catch (err) {\n console.error('[theokit:agents-typed-client] initial codegen error:', err)\n }\n },\n buildEnd() {\n try {\n emitAgentsDts(opts)\n } catch (err) {\n console.error('[theokit:agents-typed-client] buildEnd codegen error:', err)\n }\n },\n configureServer(server) {\n viteServer = server\n const agentsGlob = posix.join(\n opts.projectRoot.replace(/\\\\/g, '/'),\n opts.agentsDir ?? 'agents',\n )\n const onFile = (file: string): void => {\n if (file.replace(/\\\\/g, '/').startsWith(agentsGlob)) scheduleEmit()\n }\n server.watcher.on('add', onFile)\n server.watcher.on('change', onFile)\n server.watcher.on('unlink', onFile)\n },\n resolveId(id) {\n return id === VIRTUAL_AGENTS_ID ? RESOLVED_AGENTS_ID : null\n },\n load(id) {\n if (id === RESOLVED_AGENTS_ID) {\n // M47 — the runtime module re-exports `useAgent` AND emits one handle value per agent\n // (`export const chat = agentHandle('/api/agents/chat')`), so `import { chat } from '@theo/agents'`\n // resolves at runtime. Types come from the sibling `agents.d.ts` module augmentation.\n const agents = opts.scanManifest(opts.projectRoot).agents ?? []\n const handleLines = agents.map(\n (a) => `export const ${a.name} = agentHandle('/api/agents/${a.name}')`,\n )\n return (\n `export { useAgent, agentHandle } from 'theokit/client'\\n` +\n (handleLines.length > 0 ? handleLines.join('\\n') + '\\n' : '')\n )\n }\n return null\n },\n }\n}\n"],"mappings":";;;AAkBA,SAAS,YAAY,WAAW,cAAc,YAAY,qBAAqB;AAC/E,SAAS,SAAS,aAAa;AAM/B,IAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAQpB,SAAS,SAAS,MAAsB;AACtC,SAAO,UAAU,KAAK,QAAQ,WAAW,GAAG,CAAC;AAC/C;AAGA,SAAS,WAAW,YAAoB,aAAqB,UAA0B;AACrF,QAAM,UAAU,QAAQ,UAAU,EAAE,QAAQ,OAAO,GAAG;AACtD,QAAM,MAAM,MAAM,KAAK,YAAY,QAAQ,OAAO,GAAG,GAAG,SAAS,QAAQ,OAAO,GAAG,CAAC;AACpF,QAAM,QAAQ,IAAI,QAAQ,cAAc,EAAE;AAC1C,MAAI,MAAM,MAAM,SAAS,SAAS,KAAK;AACvC,MAAI,CAAC,IAAI,WAAW,GAAG,EAAG,OAAM,KAAK,GAAG;AACxC,SAAO;AACT;AAUO,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AACF,GAAqC;AACnC,QAAM,SAAS,SAAS,UAAU,CAAC;AACnC,QAAM,UAAoB,CAAC;AAC3B,QAAM,UAAoB,CAAC;AAC3B,QAAM,UAAoB,CAAC;AAC3B,aAAW,SAAS,QAAQ;AAC1B,UAAM,QAAQ,SAAS,MAAM,IAAI;AACjC,YAAQ;AAAA,MACN,iBAAiB,KAAK,UAAU,WAAW,YAAY,aAAa,MAAM,QAAQ,CAAC;AAAA,IACrF;AACA,YAAQ;AAAA,MACN,QAAQ,MAAM,IAAI,+BAA+B,KAAK,iCAAiC,KAAK;AAAA,IAC9F;AAGA,YAAQ;AAAA,MACN,kBAAkB,MAAM,IAAI,iCAAiC,KAAK,0BAA0B,KAAK;AAAA,IACnG;AAAA,EACF;AACA,QAAM,cAAc,QAAQ,SAAS,IAAI;AAAA,EAAK,QAAQ,KAAK,IAAI,CAAC;AAAA,IAAO;AAEvE,QAAM,OACJ,QAAQ,SAAS,IACb;AAAA,EAAM,QAAQ,KAAK,IAAI,CAAC;AAAA,OACxB;AAAA;AAAA;AAEN,QAAM,cAAc,QAAQ,SAAS,IAAI,GAAG,QAAQ,KAAK,IAAI,CAAC;AAAA,IAAO;AAErE,SAAO,GAAG,WAAW;AAAA;AAAA;AAAA;AAAA,EAIrB,WAAW;AAAA,+BACkB,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBjC,WAAW;AAAA;AAEb;AAIA,IAAM,oBAAoB;AAC1B,IAAM,qBAAqB;AAc3B,SAAS,eAAe,UAAkB,SAAuB;AAC/D,MAAI,WAAW,QAAQ,GAAG;AACxB,QAAI;AACF,UAAI,aAAa,UAAU,OAAO,MAAM,QAAS;AAAA,IACnD,QAAQ;AAAA,IAER;AAAA,EACF;AACA,YAAU,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAChD,QAAM,MAAM,GAAG,QAAQ,IAAI,QAAQ,GAAG;AACtC,gBAAc,KAAK,OAAO;AAC1B,aAAW,KAAK,QAAQ;AAC1B;AAEA,SAAS,cAAc,MAA8C;AACnE,QAAM,aAAa,MAAM,KAAK,KAAK,QAAQ,QAAQ,OAAO,GAAG,GAAG,aAAa;AAC7E,QAAM,UAAU,kBAAkB;AAAA,IAChC,UAAU,KAAK,aAAa,KAAK,WAAW;AAAA,IAC5C;AAAA,IACA,aAAa,KAAK;AAAA,EACpB,CAAC;AACD,iBAAe,YAAY,OAAO;AAClC,SAAO;AACT;AAEO,SAAS,wBAAwB,MAA8C;AACpF,MAAI;AACJ,MAAI;AAEJ,QAAM,eAAe,MAAY;AAC/B,QAAI,SAAU,cAAa,QAAQ;AACnC,eAAW,WAAW,MAAM;AAC1B,UAAI;AACF,sBAAc,IAAI;AAClB,YAAI,YAAY;AACd,gBAAM,MAAM,WAAW,YAAY,cAAc,kBAAkB;AACnE,cAAI,IAAK,YAAW,YAAY,iBAAiB,GAAG;AAAA,QACtD;AAAA,MACF,SAAS,KAAK;AACZ,gBAAQ,MAAM,gDAAgD,GAAG;AAAA,MACnE;AAAA,IACF,GAAG,GAAG;AAAA,EACR;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,iBAAiB;AACf,UAAI;AACF,sBAAc,IAAI;AAAA,MACpB,SAAS,KAAK;AACZ,gBAAQ,MAAM,wDAAwD,GAAG;AAAA,MAC3E;AAAA,IACF;AAAA,IACA,WAAW;AACT,UAAI;AACF,sBAAc,IAAI;AAAA,MACpB,SAAS,KAAK;AACZ,gBAAQ,MAAM,yDAAyD,GAAG;AAAA,MAC5E;AAAA,IACF;AAAA,IACA,gBAAgB,QAAQ;AACtB,mBAAa;AACb,YAAM,aAAa,MAAM;AAAA,QACvB,KAAK,YAAY,QAAQ,OAAO,GAAG;AAAA,QACnC,KAAK,aAAa;AAAA,MACpB;AACA,YAAM,SAAS,CAAC,SAAuB;AACrC,YAAI,KAAK,QAAQ,OAAO,GAAG,EAAE,WAAW,UAAU,EAAG,cAAa;AAAA,MACpE;AACA,aAAO,QAAQ,GAAG,OAAO,MAAM;AAC/B,aAAO,QAAQ,GAAG,UAAU,MAAM;AAClC,aAAO,QAAQ,GAAG,UAAU,MAAM;AAAA,IACpC;AAAA,IACA,UAAU,IAAI;AACZ,aAAO,OAAO,oBAAoB,qBAAqB;AAAA,IACzD;AAAA,IACA,KAAK,IAAI;AACP,UAAI,OAAO,oBAAoB;AAI7B,cAAM,SAAS,KAAK,aAAa,KAAK,WAAW,EAAE,UAAU,CAAC;AAC9D,cAAM,cAAc,OAAO;AAAA,UACzB,CAAC,MAAM,gBAAgB,EAAE,IAAI,+BAA+B,EAAE,IAAI;AAAA,QACpE;AACA,eACE;AAAA,KACC,YAAY,SAAS,IAAI,YAAY,KAAK,IAAI,IAAI,OAAO;AAAA,MAE9D;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF;","names":[]}
@@ -29,6 +29,7 @@ function generateAgentsDts({
29
29
  const agents = manifest.agents ?? [];
30
30
  const imports = [];
31
31
  const entries = [];
32
+ const handles = [];
32
33
  for (const agent of agents) {
33
34
  const alias = aliasFor(agent.name);
34
35
  imports.push(
@@ -37,7 +38,13 @@ function generateAgentsDts({
37
38
  entries.push(
38
39
  ` '${agent.name}': { input: InferAgentInput<${alias}>; tools: InferAgentToolNames<${alias}> }`
39
40
  );
41
+ handles.push(
42
+ ` export const ${agent.name}: AgentHandle<InferAgentInput<${alias}>, InferAgentToolNames<${alias}>>`
43
+ );
40
44
  }
45
+ const handleBlock = handles.length > 0 ? `
46
+ ${handles.join("\n")}
47
+ ` : "";
41
48
  const body = entries.length > 0 ? `{
42
49
  ${entries.join("\n")}
43
50
  }` : `{
@@ -48,7 +55,7 @@ ${entries.join("\n")}
48
55
  return `${FILE_HEADER}
49
56
  declare module '@theo/agents' {
50
57
  import type { InferAgentInput, InferAgentToolNames } from '@theokit/agents'
51
- import type { UseAgentReturn, AgentTransport } from 'theokit/client'
58
+ import type { UseAgentReturn, AgentTransport, AgentHandle } from 'theokit/client'
52
59
  ${importBlock}
53
60
  export interface AppAgents ${body}
54
61
 
@@ -56,12 +63,19 @@ ${importBlock}
56
63
  export function useAgent<K extends keyof AppAgents>(
57
64
  name: K,
58
65
  ): UseAgentReturn<AppAgents[K]['input'], AppAgents[K]['tools']>
66
+ // M47 \u2014 bind by a typed handle: \`import { chat } from '@theo/agents'; useAgent(chat)\`. No magic
67
+ // string (the path lives on the handle) and no duplicated input type (inferred from the handle).
68
+ export function useAgent<TInput, TTools extends string>(
69
+ handle: AgentHandle<TInput, TTools>,
70
+ ): UseAgentReturn<TInput, TTools>
59
71
  // M41 (ADR-0050) \u2014 terminal/desktop (and advanced): bind by an explicit AgentTransport
60
72
  // (e.g. InProcessTransport). The SAME hook, one consumption shape across every surface.
61
73
  export function useAgent<TInput = unknown>(
62
74
  transport: AgentTransport,
63
75
  ): UseAgentReturn<TInput>
64
- }
76
+
77
+ // M47 \u2014 one client-safe handle per agent (runtime value = { path }; types are phantom).
78
+ ${handleBlock}}
65
79
  `;
66
80
  }
67
81
  var VIRTUAL_AGENTS_ID = "@theo/agents";
@@ -140,8 +154,12 @@ function agentsTypedClientPlugin(opts) {
140
154
  },
141
155
  load(id) {
142
156
  if (id === RESOLVED_AGENTS_ID) {
143
- return `export { useAgent } from 'theokit/client'
144
- `;
157
+ const agents = opts.scanManifest(opts.projectRoot).agents ?? [];
158
+ const handleLines = agents.map(
159
+ (a) => `export const ${a.name} = agentHandle('/api/agents/${a.name}')`
160
+ );
161
+ return `export { useAgent, agentHandle } from 'theokit/client'
162
+ ` + (handleLines.length > 0 ? handleLines.join("\n") + "\n" : "");
145
163
  }
146
164
  return null;
147
165
  }
@@ -151,4 +169,4 @@ export {
151
169
  agentsTypedClientPlugin,
152
170
  generateAgentsDts
153
171
  };
154
- //# sourceMappingURL=agents-typed-client-5DCGDWFZ.js.map
172
+ //# sourceMappingURL=agents-typed-client-WFK7M5A7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vite-plugin/agents-typed-client.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Codegen-time fs ops: all paths derive from `opts.projectRoot` / `opts.distDir`\n * (vite plugin opts — trusted, set by the framework's CLI). Never serves HTTP input.\n */\n/**\n * M2 (theokit-ai-first) — end-to-end typed binding for the `agents/*.ts` convention.\n *\n * Sibling of `app-typed-client.ts` (kept separate so the `@theo/client` output stays\n * byte-unchanged). Two parts:\n *\n * 1. `generateAgentsDts({ manifest, dtsOutPath, projectRoot })` — pure function emitting\n * `.theokit/agents.d.ts` (the virtual `@theo/agents` module) with `AppAgents` mapping\n * each scanned agent to `InferAgentInput` of its `defineAgent({ input })` schema, plus a\n * typed `useAgent` signature — so the client binds to `/api/agents/<name>` with the right\n * request type and ZERO manual wiring (DoD line 2).\n * 2. `agentsTypedClientPlugin(opts)` — Vite plugin that emits (1), rewrites on `agents/*`\n * changes, and serves the virtual `@theo/agents` runtime (`useAgent` from `theokit/client`).\n */\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'\nimport { dirname, posix } from 'node:path'\n\nimport type { Plugin, ViteDevServer } from 'vite'\n\nimport type { TheoManifest } from '../server/internal-api.js'\n\nconst FILE_HEADER = `// AUTO-GENERATED by theokit — do not edit.\n// Source: theokit/packages/theo/src/vite-plugin/agents-typed-client.ts (M2).\n// To refresh: \\`pnpm dev\\` or \\`pnpm build\\` regenerates this on every agents/ change.\n\n/* eslint-disable */\n`\n\n/** A safe TS identifier alias for an agent (name may contain path separators). */\nfunction aliasFor(name: string): string {\n return `_agent_${name.replace(/[^\\w$]/g, '_')}`\n}\n\n/** Relative `import type` path from the .d.ts dir to the agent source (extension stripped). */\nfunction importPath(dtsOutPath: string, projectRoot: string, filePath: string): string {\n const fromDir = dirname(dtsOutPath).replace(/\\\\/g, '/')\n const abs = posix.join(projectRoot.replace(/\\\\/g, '/'), filePath.replace(/\\\\/g, '/'))\n const noExt = abs.replace(/\\.[jt]sx?$/, '')\n let rel = posix.relative(fromDir, noExt)\n if (!rel.startsWith('.')) rel = `./${rel}`\n return rel\n}\n\nexport interface GenerateAgentsDtsOptions {\n manifest: TheoManifest\n /** Absolute path to the .d.ts that will be emitted (drives the import paths). */\n dtsOutPath: string\n /** Absolute project root — agents live at `<projectRoot>/agents`. */\n projectRoot: string\n}\n\nexport function generateAgentsDts({\n manifest,\n dtsOutPath,\n projectRoot,\n}: GenerateAgentsDtsOptions): string {\n const agents = manifest.agents ?? []\n const imports: string[] = []\n const entries: string[] = []\n const handles: string[] = []\n for (const agent of agents) {\n const alias = aliasFor(agent.name)\n imports.push(\n ` import type ${alias} from '${importPath(dtsOutPath, projectRoot, agent.filePath)}'`,\n )\n entries.push(\n ` '${agent.name}': { input: InferAgentInput<${alias}>; tools: InferAgentToolNames<${alias}> }`,\n )\n // M47 — a named, client-safe handle per agent: `import { chat } from '@theo/agents'; useAgent(chat)`.\n // cmd-click on the generated const hops to the agent source via the type-only import above.\n handles.push(\n ` export const ${agent.name}: AgentHandle<InferAgentInput<${alias}>, InferAgentToolNames<${alias}>>`,\n )\n }\n const handleBlock = handles.length > 0 ? `\\n${handles.join('\\n')}\\n` : ''\n\n const body =\n entries.length > 0\n ? `{\\n${entries.join('\\n')}\\n }`\n : `{\\n // No agents detected — add \\`agents/*.ts\\` to populate this binding.\\n }`\n\n const importBlock = imports.length > 0 ? `${imports.join('\\n')}\\n` : ''\n\n return `${FILE_HEADER}\ndeclare module '@theo/agents' {\n import type { InferAgentInput, InferAgentToolNames } from '@theokit/agents'\n import type { UseAgentReturn, AgentTransport, AgentHandle } from 'theokit/client'\n${importBlock}\n export interface AppAgents ${body}\n\n // Web (and any HTTP surface): bind by agent name — \\`send\\` is typed to the agent's \\`input\\` schema.\n export function useAgent<K extends keyof AppAgents>(\n name: K,\n ): UseAgentReturn<AppAgents[K]['input'], AppAgents[K]['tools']>\n // M47 — bind by a typed handle: \\`import { chat } from '@theo/agents'; useAgent(chat)\\`. No magic\n // string (the path lives on the handle) and no duplicated input type (inferred from the handle).\n export function useAgent<TInput, TTools extends string>(\n handle: AgentHandle<TInput, TTools>,\n ): UseAgentReturn<TInput, TTools>\n // M41 (ADR-0050) — terminal/desktop (and advanced): bind by an explicit AgentTransport\n // (e.g. InProcessTransport). The SAME hook, one consumption shape across every surface.\n export function useAgent<TInput = unknown>(\n transport: AgentTransport,\n ): UseAgentReturn<TInput>\n\n // M47 — one client-safe handle per agent (runtime value = { path }; types are phantom).\n${handleBlock}}\n`\n}\n\n// ─── Vite plugin ─────────────────────────────────────────────────────────────\n\nconst VIRTUAL_AGENTS_ID = '@theo/agents'\nconst RESOLVED_AGENTS_ID = '\\0@theo/agents'\n\nexport interface AgentsTypedClientPluginOptions {\n /** Absolute project root (agents live at `<projectRoot>/<agentsDir>`). */\n projectRoot: string\n /** Agents dir name (config `agentsDir`, default \"agents\"), relative to projectRoot — the watch glob. */\n agentsDir?: string\n /** Absolute `.theokit/` output dir (where `agents.d.ts` lands). */\n distDir: string\n /** Injects the scanned agents; returns the current manifest-shaped agent list. */\n scanManifest: (projectRoot: string) => TheoManifest\n}\n\n/** Atomic write via tmp + rename; skips when content is unchanged. */\nfunction writeIfChanged(filePath: string, content: string): void {\n if (existsSync(filePath)) {\n try {\n if (readFileSync(filePath, 'utf-8') === content) return\n } catch {\n /* fall through to write */\n }\n }\n mkdirSync(dirname(filePath), { recursive: true })\n const tmp = `${filePath}.${process.pid}.tmp`\n writeFileSync(tmp, content)\n renameSync(tmp, filePath)\n}\n\nfunction emitAgentsDts(opts: AgentsTypedClientPluginOptions): string {\n const dtsOutPath = posix.join(opts.distDir.replace(/\\\\/g, '/'), 'agents.d.ts')\n const content = generateAgentsDts({\n manifest: opts.scanManifest(opts.projectRoot),\n dtsOutPath,\n projectRoot: opts.projectRoot,\n })\n writeIfChanged(dtsOutPath, content)\n return dtsOutPath\n}\n\nexport function agentsTypedClientPlugin(opts: AgentsTypedClientPluginOptions): Plugin {\n let debounce: NodeJS.Timeout | undefined\n let viteServer: ViteDevServer | undefined\n\n const scheduleEmit = (): void => {\n if (debounce) clearTimeout(debounce)\n debounce = setTimeout(() => {\n try {\n emitAgentsDts(opts)\n if (viteServer) {\n const mod = viteServer.moduleGraph.getModuleById(RESOLVED_AGENTS_ID)\n if (mod) viteServer.moduleGraph.invalidateModule(mod)\n }\n } catch (err) {\n console.error('[theokit:agents-typed-client] codegen error:', err)\n }\n }, 100)\n }\n\n return {\n name: 'theokit:agents-typed-client',\n enforce: 'post',\n configResolved() {\n try {\n emitAgentsDts(opts)\n } catch (err) {\n console.error('[theokit:agents-typed-client] initial codegen error:', err)\n }\n },\n buildEnd() {\n try {\n emitAgentsDts(opts)\n } catch (err) {\n console.error('[theokit:agents-typed-client] buildEnd codegen error:', err)\n }\n },\n configureServer(server) {\n viteServer = server\n const agentsGlob = posix.join(\n opts.projectRoot.replace(/\\\\/g, '/'),\n opts.agentsDir ?? 'agents',\n )\n const onFile = (file: string): void => {\n if (file.replace(/\\\\/g, '/').startsWith(agentsGlob)) scheduleEmit()\n }\n server.watcher.on('add', onFile)\n server.watcher.on('change', onFile)\n server.watcher.on('unlink', onFile)\n },\n resolveId(id) {\n return id === VIRTUAL_AGENTS_ID ? RESOLVED_AGENTS_ID : null\n },\n load(id) {\n if (id === RESOLVED_AGENTS_ID) {\n // M47 — the runtime module re-exports `useAgent` AND emits one handle value per agent\n // (`export const chat = agentHandle('/api/agents/chat')`), so `import { chat } from '@theo/agents'`\n // resolves at runtime. Types come from the sibling `agents.d.ts` module augmentation.\n const agents = opts.scanManifest(opts.projectRoot).agents ?? []\n const handleLines = agents.map(\n (a) => `export const ${a.name} = agentHandle('/api/agents/${a.name}')`,\n )\n return (\n `export { useAgent, agentHandle } from 'theokit/client'\\n` +\n (handleLines.length > 0 ? handleLines.join('\\n') + '\\n' : '')\n )\n }\n return null\n },\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,5 +1,7 @@
1
- import "./chunk-VB2WWBIT.js";
2
- import "./chunk-2WTL2GZO.js";
1
+ import {
2
+ scanControllerModules
3
+ } from "./chunk-KLFCJQXU.js";
4
+ import "./chunk-QWBXUZAX.js";
3
5
  import "./chunk-GY5Q27BJ.js";
4
6
  import "./chunk-5QW7IQQU.js";
5
7
  import "./chunk-WFNLNIJX.js";
@@ -25,6 +27,29 @@ import "./chunk-DGUM43GV.js";
25
27
  // src/vite-plugin/app-typed-client.ts
26
28
  import { mkdirSync, readFileSync, renameSync, writeFileSync, existsSync } from "fs";
27
29
  import { dirname, isAbsolute, posix, relative, resolve } from "path";
30
+
31
+ // src/vite-plugin/controller-client-emit.ts
32
+ import { loadControllerWithSwc, walkControllerMetadata } from "@theokit/http";
33
+ async function collectControllerRouteData(opts) {
34
+ const loader = opts.loadModule ?? loadControllerWithSwc;
35
+ const modules = await scanControllerModules(opts.controllersDir, loader);
36
+ const out = [];
37
+ for (const { filePath, cls } of modules) {
38
+ const className = cls.name;
39
+ for (const walk of walkControllerMetadata(cls)) {
40
+ out.push({
41
+ filePath,
42
+ className,
43
+ fullPath: walk.fullPath,
44
+ verb: walk.verb,
45
+ methodName: String(walk.propertyKey)
46
+ });
47
+ }
48
+ }
49
+ return out;
50
+ }
51
+
52
+ // src/vite-plugin/app-typed-client.ts
28
53
  var FILE_HEADER = `// AUTO-GENERATED by theokit \u2014 do not edit.
29
54
  // Source: theokit/packages/theo/src/vite-plugin/app-typed-client.ts (G1 plan).
30
55
  // To refresh: \`pnpm dev\` or \`pnpm build\` regenerates this file on every route change.
@@ -32,7 +57,12 @@ var FILE_HEADER = `// AUTO-GENERATED by theokit \u2014 do not edit.
32
57
  /* eslint-disable */
33
58
  `;
34
59
  function makeNode() {
35
- return { children: /* @__PURE__ */ new Map(), methods: /* @__PURE__ */ new Map(), paramNamesAccumulated: [] };
60
+ return {
61
+ children: /* @__PURE__ */ new Map(),
62
+ methods: /* @__PURE__ */ new Map(),
63
+ controllerMethods: /* @__PURE__ */ new Map(),
64
+ paramNamesAccumulated: []
65
+ };
36
66
  }
37
67
  function normalizeSegment(raw) {
38
68
  let s = raw;
@@ -100,11 +130,38 @@ function buildOneRoute(route, ctx) {
100
130
  node.methods.set(method, alias);
101
131
  }
102
132
  }
133
+ function buildOneController(cr, classAlias, ctx) {
134
+ const verb = cr.verb.toUpperCase();
135
+ if (!HTTP_METHODS.includes(verb)) return;
136
+ const method = verb;
137
+ const segments = routePathSegments(cr.fullPath);
138
+ let node = ctx.root;
139
+ for (const seg of segments) {
140
+ const norm = normalizeSegment(seg);
141
+ let child = node.children.get(norm.identifier);
142
+ if (child === void 0) {
143
+ child = makeNode();
144
+ node.children.set(norm.identifier, child);
145
+ }
146
+ node = child;
147
+ }
148
+ node.paramNamesAccumulated = paramNamesFromRoute(cr.fullPath);
149
+ if (node.methods.has(method)) {
150
+ ctx.warnings.push(
151
+ `WARNING (#122): controller ${cr.className}.${cr.methodName} (${method} /${segments.join("/")}) collides with a file-based route at the same path; file route wins.`
152
+ );
153
+ return;
154
+ }
155
+ node.controllerMethods.set(method, { classAlias, methodName: cr.methodName });
156
+ }
103
157
  function resolveCollisions(node, pathPrefix, warnings) {
104
158
  const methodLowerNames = /* @__PURE__ */ new Set();
105
159
  for (const method of node.methods.keys()) {
106
160
  methodLowerNames.add(method.toLowerCase());
107
161
  }
162
+ for (const method of node.controllerMethods.keys()) {
163
+ methodLowerNames.add(method.toLowerCase());
164
+ }
108
165
  const renames = [];
109
166
  for (const childName of node.children.keys()) {
110
167
  if (methodLowerNames.has(childName)) {
@@ -136,6 +193,15 @@ function renderTreeNode(node, indent, _isRoot) {
136
193
  `${indent} ${lower}: (opts${optionalMark}: ${paramsField}import('theokit/client').TheoFetchOptions<typeof ${alias}>) => Promise<import('theokit/client').InferResponse<typeof ${alias}>>`
137
194
  );
138
195
  }
196
+ for (const [method, leaf] of node.controllerMethods) {
197
+ const lower = method.toLowerCase();
198
+ const paramsList = node.paramNamesAccumulated.map((p) => `${p}: string`).join("; ");
199
+ const paramsField = node.paramNamesAccumulated.length > 0 ? `{ params: { ${paramsList} } } & ` : "";
200
+ const optionalMark = node.paramNamesAccumulated.length === 0 ? "?" : "";
201
+ lines.push(
202
+ `${indent} ${lower}: (opts${optionalMark}: ${paramsField}{ body?: unknown; query?: Record<string, string> }) => Promise<Awaited<ReturnType<InstanceType<typeof ${leaf.classAlias}>['${leaf.methodName}']>>>`
203
+ );
204
+ }
139
205
  for (const [name, child] of node.children) {
140
206
  const propKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `'${name.replace(/'/g, "\\'")}'`;
141
207
  lines.push(`${indent} ${propKey}: ${renderTreeNode(child, indent + " ", false)}`);
@@ -146,7 +212,8 @@ function renderTreeNode(node, indent, _isRoot) {
146
212
  function generateClientDts({
147
213
  manifest,
148
214
  dtsOutPath,
149
- serverDir
215
+ serverDir,
216
+ controllerRoutes
150
217
  }) {
151
218
  const ctx = {
152
219
  root: makeNode(),
@@ -159,10 +226,23 @@ function generateClientDts({
159
226
  for (const route of manifest.routes) {
160
227
  buildOneRoute(route, ctx);
161
228
  }
229
+ if (controllerRoutes?.length) {
230
+ const classAliases = /* @__PURE__ */ new Map();
231
+ for (const cr of controllerRoutes) {
232
+ let alias = classAliases.get(cr.filePath);
233
+ if (alias === void 0) {
234
+ alias = `_c${classAliases.size}`;
235
+ classAliases.set(cr.filePath, alias);
236
+ const importPath = importPathFor(dtsOutPath, cr.filePath);
237
+ ctx.importLines.push(` import type { ${cr.className} as ${alias} } from '${importPath}'`);
238
+ }
239
+ buildOneController(cr, alias, ctx);
240
+ }
241
+ }
162
242
  resolveCollisions(ctx.root, "", ctx.warnings);
163
243
  const importsBlock = ctx.importLines.length > 0 ? ctx.importLines.join("\n") + "\n\n" : "\n";
164
244
  const warningsBlock = ctx.warnings.length > 0 ? " // " + ctx.warnings.join("\n // ") + "\n\n" : "";
165
- const treeBody = ctx.root.children.size === 0 && ctx.root.methods.size === 0 ? "{\n // No routes detected \u2014 add `server/routes/*.ts` to populate this client.\n }" : renderTreeNode(
245
+ const treeBody = ctx.root.children.size === 0 && ctx.root.methods.size === 0 && ctx.root.controllerMethods.size === 0 ? "{\n // No routes detected \u2014 add `server/routes/*.ts` to populate this client.\n }" : renderTreeNode(
166
246
  ctx.root,
167
247
  " ",
168
248
  /* isRoot */
@@ -177,6 +257,11 @@ ${importsBlock}${warningsBlock} export interface AppClient ${treeBody}
177
257
  }
178
258
  var VIRTUAL_APP_CLIENT_ID = "@theo/client";
179
259
  var RESOLVED_APP_CLIENT_ID = "\0@theo/client";
260
+ function shouldReemitForFile(file, routesGlob, controllersGlob) {
261
+ const normalized = file.replace(/\\/g, "/");
262
+ if (normalized.includes(".__decorated__.")) return false;
263
+ return normalized.startsWith(routesGlob) || normalized.startsWith(controllersGlob);
264
+ }
180
265
  function writeFileIfChanged(filePath, content) {
181
266
  if (existsSync(filePath)) {
182
267
  try {
@@ -191,7 +276,7 @@ function writeFileIfChanged(filePath, content) {
191
276
  renameSync(tmp, filePath);
192
277
  return { changed: true };
193
278
  }
194
- function emitClientDts(opts) {
279
+ async function emitClientDts(opts) {
195
280
  if (!existsSync(opts.serverDir)) {
196
281
  const stubPath = posix.join(opts.distDir.replace(/\\/g, "/"), "client.d.ts");
197
282
  return {
@@ -216,11 +301,14 @@ function emitClientDts(opts) {
216
301
  if (opts.extraRoutes?.length) {
217
302
  manifest.routes.push(...opts.extraRoutes);
218
303
  }
304
+ const controllersDir = resolve(opts.serverDir, "controllers");
305
+ const controllerRoutes = existsSync(controllersDir) ? await collectControllerRouteData({ controllersDir }) : [];
219
306
  const dtsOutPath = posix.join(opts.distDir.replace(/\\/g, "/"), "client.d.ts");
220
307
  const content = generateClientDts({
221
308
  manifest,
222
309
  dtsOutPath,
223
- serverDir: opts.serverDir
310
+ serverDir: opts.serverDir,
311
+ controllerRoutes
224
312
  });
225
313
  const { changed } = writeFileIfChanged(dtsOutPath, content);
226
314
  return { changed, path: dtsOutPath };
@@ -231,30 +319,32 @@ function appTypedClientPlugin(opts) {
231
319
  function scheduleEmit() {
232
320
  if (debounceTimer) clearTimeout(debounceTimer);
233
321
  debounceTimer = setTimeout(() => {
234
- try {
235
- emitClientDts(opts);
236
- if (viteServer) {
237
- const mod = viteServer.moduleGraph.getModuleById(RESOLVED_APP_CLIENT_ID);
238
- if (mod) viteServer.moduleGraph.invalidateModule(mod);
322
+ void (async () => {
323
+ try {
324
+ await emitClientDts(opts);
325
+ if (viteServer) {
326
+ const mod = viteServer.moduleGraph.getModuleById(RESOLVED_APP_CLIENT_ID);
327
+ if (mod) viteServer.moduleGraph.invalidateModule(mod);
328
+ }
329
+ } catch (err) {
330
+ console.error("[theokit:app-typed-client] codegen error:", err);
239
331
  }
240
- } catch (err) {
241
- console.error("[theokit:app-typed-client] codegen error:", err);
242
- }
332
+ })();
243
333
  }, 100);
244
334
  }
245
335
  return {
246
336
  name: "theokit:app-typed-client",
247
337
  enforce: "post",
248
- configResolved() {
338
+ async configResolved() {
249
339
  try {
250
- emitClientDts(opts);
340
+ await emitClientDts(opts);
251
341
  } catch (err) {
252
342
  console.error("[theokit:app-typed-client] initial codegen error:", err);
253
343
  }
254
344
  },
255
- buildEnd() {
345
+ async buildEnd() {
256
346
  try {
257
- emitClientDts(opts);
347
+ await emitClientDts(opts);
258
348
  } catch (err) {
259
349
  console.error("[theokit:app-typed-client] buildEnd codegen error:", err);
260
350
  }
@@ -264,9 +354,7 @@ function appTypedClientPlugin(opts) {
264
354
  const routesGlob = posix.join(opts.serverDir.replace(/\\/g, "/"), "routes");
265
355
  const controllersGlob = posix.join(opts.serverDir.replace(/\\/g, "/"), "controllers");
266
356
  const onFile = (file) => {
267
- const normalized = file.replace(/\\/g, "/");
268
- if (normalized.startsWith(routesGlob) || normalized.startsWith(controllersGlob))
269
- scheduleEmit();
357
+ if (shouldReemitForFile(file, routesGlob, controllersGlob)) scheduleEmit();
270
358
  };
271
359
  server.watcher.on("add", onFile);
272
360
  server.watcher.on("change", onFile);
@@ -294,6 +382,7 @@ export {
294
382
  VIRTUAL_APP_CLIENT_ID,
295
383
  appTypedClientPlugin,
296
384
  emitClientDts,
297
- generateClientDts
385
+ generateClientDts,
386
+ shouldReemitForFile
298
387
  };
299
- //# sourceMappingURL=app-typed-client-K3PXIZDZ.js.map
388
+ //# sourceMappingURL=app-typed-client-FQPUOL6A.js.map