@soederpop/luca 0.0.2

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 (358) hide show
  1. package/CLAUDE.md +71 -0
  2. package/README.md +78 -0
  3. package/bun.lock +2928 -0
  4. package/bunfig.toml +3 -0
  5. package/commands/audit-docs.ts +740 -0
  6. package/commands/build-scaffolds.ts +154 -0
  7. package/commands/generate-api-docs.ts +114 -0
  8. package/commands/update-introspection.ts +67 -0
  9. package/docs/CLI.md +335 -0
  10. package/docs/README.md +88 -0
  11. package/docs/TABLE-OF-CONTENTS.md +157 -0
  12. package/docs/apis/clients/elevenlabs.md +84 -0
  13. package/docs/apis/clients/graph.md +56 -0
  14. package/docs/apis/clients/openai.md +69 -0
  15. package/docs/apis/clients/rest.md +41 -0
  16. package/docs/apis/clients/websocket.md +107 -0
  17. package/docs/apis/features/agi/assistant.md +471 -0
  18. package/docs/apis/features/agi/assistants-manager.md +154 -0
  19. package/docs/apis/features/agi/claude-code.md +602 -0
  20. package/docs/apis/features/agi/conversation-history.md +352 -0
  21. package/docs/apis/features/agi/conversation.md +333 -0
  22. package/docs/apis/features/agi/docs-reader.md +121 -0
  23. package/docs/apis/features/agi/openai-codex.md +318 -0
  24. package/docs/apis/features/agi/openapi.md +138 -0
  25. package/docs/apis/features/agi/semantic-search.md +387 -0
  26. package/docs/apis/features/agi/skills-library.md +216 -0
  27. package/docs/apis/features/node/container-link.md +133 -0
  28. package/docs/apis/features/node/content-db.md +313 -0
  29. package/docs/apis/features/node/disk-cache.md +379 -0
  30. package/docs/apis/features/node/dns.md +651 -0
  31. package/docs/apis/features/node/docker.md +705 -0
  32. package/docs/apis/features/node/downloader.md +81 -0
  33. package/docs/apis/features/node/esbuild.md +59 -0
  34. package/docs/apis/features/node/file-manager.md +182 -0
  35. package/docs/apis/features/node/fs.md +581 -0
  36. package/docs/apis/features/node/git.md +330 -0
  37. package/docs/apis/features/node/google-auth.md +174 -0
  38. package/docs/apis/features/node/google-calendar.md +187 -0
  39. package/docs/apis/features/node/google-docs.md +151 -0
  40. package/docs/apis/features/node/google-drive.md +225 -0
  41. package/docs/apis/features/node/google-sheets.md +179 -0
  42. package/docs/apis/features/node/grep.md +290 -0
  43. package/docs/apis/features/node/helpers.md +135 -0
  44. package/docs/apis/features/node/ink.md +334 -0
  45. package/docs/apis/features/node/ipc-socket.md +260 -0
  46. package/docs/apis/features/node/json-tree.md +86 -0
  47. package/docs/apis/features/node/launcher-app-command-listener.md +145 -0
  48. package/docs/apis/features/node/networking.md +281 -0
  49. package/docs/apis/features/node/nlp.md +133 -0
  50. package/docs/apis/features/node/opener.md +97 -0
  51. package/docs/apis/features/node/os.md +118 -0
  52. package/docs/apis/features/node/package-finder.md +402 -0
  53. package/docs/apis/features/node/postgres.md +212 -0
  54. package/docs/apis/features/node/proc.md +430 -0
  55. package/docs/apis/features/node/process-manager.md +210 -0
  56. package/docs/apis/features/node/python.md +278 -0
  57. package/docs/apis/features/node/repl.md +88 -0
  58. package/docs/apis/features/node/runpod.md +673 -0
  59. package/docs/apis/features/node/secure-shell.md +169 -0
  60. package/docs/apis/features/node/semantic-search.md +401 -0
  61. package/docs/apis/features/node/sqlite.md +211 -0
  62. package/docs/apis/features/node/telegram.md +254 -0
  63. package/docs/apis/features/node/tts.md +118 -0
  64. package/docs/apis/features/node/ui.md +703 -0
  65. package/docs/apis/features/node/vault.md +64 -0
  66. package/docs/apis/features/node/vm.md +84 -0
  67. package/docs/apis/features/node/window-manager.md +337 -0
  68. package/docs/apis/features/node/yaml-tree.md +85 -0
  69. package/docs/apis/features/node/yaml.md +176 -0
  70. package/docs/apis/features/web/asset-loader.md +47 -0
  71. package/docs/apis/features/web/container-link.md +133 -0
  72. package/docs/apis/features/web/esbuild.md +59 -0
  73. package/docs/apis/features/web/helpers.md +135 -0
  74. package/docs/apis/features/web/network.md +30 -0
  75. package/docs/apis/features/web/speech.md +55 -0
  76. package/docs/apis/features/web/vault.md +64 -0
  77. package/docs/apis/features/web/vm.md +84 -0
  78. package/docs/apis/features/web/voice.md +67 -0
  79. package/docs/apis/servers/express.md +127 -0
  80. package/docs/apis/servers/mcp.md +213 -0
  81. package/docs/apis/servers/websocket.md +99 -0
  82. package/docs/documentation-audit.md +134 -0
  83. package/docs/examples/content-db.md +77 -0
  84. package/docs/examples/disk-cache.md +83 -0
  85. package/docs/examples/docker.md +101 -0
  86. package/docs/examples/downloader.md +70 -0
  87. package/docs/examples/esbuild.md +80 -0
  88. package/docs/examples/file-manager.md +82 -0
  89. package/docs/examples/fs.md +83 -0
  90. package/docs/examples/git.md +85 -0
  91. package/docs/examples/google-auth.md +88 -0
  92. package/docs/examples/google-calendar.md +94 -0
  93. package/docs/examples/google-docs.md +82 -0
  94. package/docs/examples/google-drive.md +96 -0
  95. package/docs/examples/google-sheets.md +95 -0
  96. package/docs/examples/grep.md +85 -0
  97. package/docs/examples/ink-blocks.md +75 -0
  98. package/docs/examples/ink-renderer.md +41 -0
  99. package/docs/examples/ink.md +103 -0
  100. package/docs/examples/ipc-socket.md +103 -0
  101. package/docs/examples/json-tree.md +91 -0
  102. package/docs/examples/launcher-app-command-listener.md +120 -0
  103. package/docs/examples/networking.md +58 -0
  104. package/docs/examples/nlp.md +91 -0
  105. package/docs/examples/opener.md +78 -0
  106. package/docs/examples/os.md +72 -0
  107. package/docs/examples/package-finder.md +89 -0
  108. package/docs/examples/port-exposer.md +89 -0
  109. package/docs/examples/postgres.md +91 -0
  110. package/docs/examples/proc.md +81 -0
  111. package/docs/examples/process-manager.md +79 -0
  112. package/docs/examples/python.md +91 -0
  113. package/docs/examples/repl.md +93 -0
  114. package/docs/examples/runpod.md +119 -0
  115. package/docs/examples/secure-shell.md +92 -0
  116. package/docs/examples/sqlite.md +86 -0
  117. package/docs/examples/telegram.md +77 -0
  118. package/docs/examples/tts.md +86 -0
  119. package/docs/examples/ui.md +80 -0
  120. package/docs/examples/vault.md +70 -0
  121. package/docs/examples/vm.md +86 -0
  122. package/docs/examples/window-manager.md +125 -0
  123. package/docs/examples/yaml-tree.md +93 -0
  124. package/docs/examples/yaml.md +104 -0
  125. package/docs/ideas/class-registration-refactor-possibilities.md +197 -0
  126. package/docs/ideas/container-use-api.md +9 -0
  127. package/docs/ideas/easy-auth-for-express-servers-and-luca-serve.md +0 -0
  128. package/docs/ideas/feature-stacks.md +22 -0
  129. package/docs/ideas/luca-cli-self-sufficiency-demo.md +23 -0
  130. package/docs/ideas/mcp-design.md +9 -0
  131. package/docs/ideas/web-container-debugging-feature.md +13 -0
  132. package/docs/introspection-audit.md +49 -0
  133. package/docs/introspection.md +154 -0
  134. package/docs/mcp/readme.md +162 -0
  135. package/docs/models.ts +38 -0
  136. package/docs/philosophy.md +85 -0
  137. package/docs/principles.md +7 -0
  138. package/docs/prompts/audit-codebase-for-failures-to-use-the-container.md +34 -0
  139. package/docs/prompts/mcp-test-easy-command.md +27 -0
  140. package/docs/reports/assistant-bugs.md +38 -0
  141. package/docs/reports/attach-pattern-usage.md +18 -0
  142. package/docs/reports/code-audit-results.md +391 -0
  143. package/docs/reports/introspection-audit-tasks.md +378 -0
  144. package/docs/reports/luca-mcp-improvements.md +128 -0
  145. package/docs/scaffolds/client.md +140 -0
  146. package/docs/scaffolds/command.md +106 -0
  147. package/docs/scaffolds/endpoint.md +176 -0
  148. package/docs/scaffolds/feature.md +148 -0
  149. package/docs/scaffolds/server.md +187 -0
  150. package/docs/tasks/web-container-helper-discovery.md +71 -0
  151. package/docs/todos.md +1 -0
  152. package/docs/tutorials/01-getting-started.md +106 -0
  153. package/docs/tutorials/02-container.md +210 -0
  154. package/docs/tutorials/03-scripts.md +194 -0
  155. package/docs/tutorials/04-features-overview.md +196 -0
  156. package/docs/tutorials/05-state-and-events.md +171 -0
  157. package/docs/tutorials/06-servers.md +157 -0
  158. package/docs/tutorials/07-endpoints.md +198 -0
  159. package/docs/tutorials/08-commands.md +171 -0
  160. package/docs/tutorials/09-clients.md +162 -0
  161. package/docs/tutorials/10-creating-features.md +198 -0
  162. package/docs/tutorials/11-contentbase.md +191 -0
  163. package/docs/tutorials/12-assistants.md +215 -0
  164. package/docs/tutorials/13-introspection.md +147 -0
  165. package/docs/tutorials/14-type-system.md +174 -0
  166. package/docs/tutorials/15-project-patterns.md +222 -0
  167. package/docs/tutorials/16-google-features.md +534 -0
  168. package/docs/tutorials/17-tui-blocks.md +530 -0
  169. package/docs/tutorials/18-semantic-search.md +334 -0
  170. package/index.ts +1 -0
  171. package/luca.console.ts +9 -0
  172. package/main.py +6 -0
  173. package/package.json +154 -0
  174. package/pyproject.toml +7 -0
  175. package/scripts/animations/chrome-glitch.ts +55 -0
  176. package/scripts/animations/index.ts +16 -0
  177. package/scripts/animations/neon-pulse.ts +64 -0
  178. package/scripts/animations/types.ts +6 -0
  179. package/scripts/build-web.ts +28 -0
  180. package/scripts/examples/ask-luca-expert.ts +42 -0
  181. package/scripts/examples/assistant-questions.ts +12 -0
  182. package/scripts/examples/excalidraw-expert.ts +75 -0
  183. package/scripts/examples/expert-chat.ts +0 -0
  184. package/scripts/examples/file-manager.ts +14 -0
  185. package/scripts/examples/ideas.ts +12 -0
  186. package/scripts/examples/interactive-chat.ts +20 -0
  187. package/scripts/examples/openai-tool-calls.ts +113 -0
  188. package/scripts/examples/opening-a-web-browser.ts +5 -0
  189. package/scripts/examples/telegram-bot.ts +79 -0
  190. package/scripts/examples/telegram-ink-ui.ts +302 -0
  191. package/scripts/examples/using-assistant-with-mcp.ts +560 -0
  192. package/scripts/examples/using-claude-code.ts +10 -0
  193. package/scripts/examples/using-contentdb.ts +35 -0
  194. package/scripts/examples/using-conversations.ts +35 -0
  195. package/scripts/examples/using-disk-cache.ts +10 -0
  196. package/scripts/examples/using-docker-shell.ts +75 -0
  197. package/scripts/examples/using-elevenlabs.ts +25 -0
  198. package/scripts/examples/using-google-calendar.ts +57 -0
  199. package/scripts/examples/using-google-docs.ts +74 -0
  200. package/scripts/examples/using-google-drive.ts +74 -0
  201. package/scripts/examples/using-google-sheets.ts +89 -0
  202. package/scripts/examples/using-nlp.ts +55 -0
  203. package/scripts/examples/using-ollama.ts +10 -0
  204. package/scripts/examples/using-openai-codex.ts +23 -0
  205. package/scripts/examples/using-postgres.ts +55 -0
  206. package/scripts/examples/using-runpod.ts +32 -0
  207. package/scripts/examples/using-tts.ts +40 -0
  208. package/scripts/examples/vm-loading-esm-modules.ts +16 -0
  209. package/scripts/scaffold.ts +391 -0
  210. package/scripts/scratch.ts +15 -0
  211. package/scripts/test-command-listener.ts +123 -0
  212. package/scripts/test-window-manager-lifecycle.ts +86 -0
  213. package/scripts/test-window-manager.ts +43 -0
  214. package/scripts/update-introspection-data.ts +58 -0
  215. package/src/agi/README.md +14 -0
  216. package/src/agi/container.server.ts +114 -0
  217. package/src/agi/endpoints/ask.ts +60 -0
  218. package/src/agi/endpoints/conversations/[id].ts +45 -0
  219. package/src/agi/endpoints/conversations.ts +31 -0
  220. package/src/agi/endpoints/experts.ts +37 -0
  221. package/src/agi/features/assistant.ts +767 -0
  222. package/src/agi/features/assistants-manager.ts +260 -0
  223. package/src/agi/features/claude-code.ts +1111 -0
  224. package/src/agi/features/conversation-history.ts +497 -0
  225. package/src/agi/features/conversation.ts +799 -0
  226. package/src/agi/features/openai-codex.ts +631 -0
  227. package/src/agi/features/openapi.ts +438 -0
  228. package/src/agi/features/skills-library.ts +425 -0
  229. package/src/agi/index.ts +6 -0
  230. package/src/agi/lib/token-counter.ts +122 -0
  231. package/src/browser.ts +25 -0
  232. package/src/bus.ts +100 -0
  233. package/src/cli/cli.ts +70 -0
  234. package/src/client.ts +461 -0
  235. package/src/clients/civitai/index.ts +541 -0
  236. package/src/clients/client-template.ts +41 -0
  237. package/src/clients/comfyui/index.ts +597 -0
  238. package/src/clients/elevenlabs/index.ts +291 -0
  239. package/src/clients/openai/index.ts +451 -0
  240. package/src/clients/supabase/index.ts +366 -0
  241. package/src/command.ts +164 -0
  242. package/src/commands/chat.ts +182 -0
  243. package/src/commands/console.ts +192 -0
  244. package/src/commands/describe.ts +433 -0
  245. package/src/commands/eval.ts +116 -0
  246. package/src/commands/help.ts +214 -0
  247. package/src/commands/index.ts +14 -0
  248. package/src/commands/mcp.ts +64 -0
  249. package/src/commands/prompt.ts +807 -0
  250. package/src/commands/run.ts +257 -0
  251. package/src/commands/sandbox-mcp.ts +439 -0
  252. package/src/commands/scaffold.ts +79 -0
  253. package/src/commands/serve.ts +172 -0
  254. package/src/container.ts +781 -0
  255. package/src/endpoint.ts +340 -0
  256. package/src/feature.ts +75 -0
  257. package/src/hash-object.ts +97 -0
  258. package/src/helper.ts +543 -0
  259. package/src/introspection/generated.agi.ts +23388 -0
  260. package/src/introspection/generated.node.ts +18899 -0
  261. package/src/introspection/generated.web.ts +2021 -0
  262. package/src/introspection/index.ts +256 -0
  263. package/src/introspection/scan.ts +912 -0
  264. package/src/node/container.ts +354 -0
  265. package/src/node/feature.ts +13 -0
  266. package/src/node/features/container-link.ts +558 -0
  267. package/src/node/features/content-db.ts +475 -0
  268. package/src/node/features/disk-cache.ts +382 -0
  269. package/src/node/features/dns.ts +655 -0
  270. package/src/node/features/docker.ts +912 -0
  271. package/src/node/features/downloader.ts +92 -0
  272. package/src/node/features/esbuild.ts +68 -0
  273. package/src/node/features/file-manager.ts +357 -0
  274. package/src/node/features/fs.ts +534 -0
  275. package/src/node/features/git.ts +492 -0
  276. package/src/node/features/google-auth.ts +502 -0
  277. package/src/node/features/google-calendar.ts +300 -0
  278. package/src/node/features/google-docs.ts +404 -0
  279. package/src/node/features/google-drive.ts +339 -0
  280. package/src/node/features/google-sheets.ts +279 -0
  281. package/src/node/features/grep.ts +406 -0
  282. package/src/node/features/helpers.ts +374 -0
  283. package/src/node/features/ink.ts +490 -0
  284. package/src/node/features/ipc-socket.ts +459 -0
  285. package/src/node/features/json-tree.ts +188 -0
  286. package/src/node/features/launcher-app-command-listener.ts +388 -0
  287. package/src/node/features/networking.ts +925 -0
  288. package/src/node/features/nlp.ts +211 -0
  289. package/src/node/features/opener.ts +166 -0
  290. package/src/node/features/os.ts +157 -0
  291. package/src/node/features/package-finder.ts +539 -0
  292. package/src/node/features/port-exposer.ts +342 -0
  293. package/src/node/features/postgres.ts +273 -0
  294. package/src/node/features/proc.ts +502 -0
  295. package/src/node/features/process-manager.ts +542 -0
  296. package/src/node/features/python.ts +444 -0
  297. package/src/node/features/repl.ts +194 -0
  298. package/src/node/features/runpod.ts +802 -0
  299. package/src/node/features/secure-shell.ts +248 -0
  300. package/src/node/features/semantic-search.ts +924 -0
  301. package/src/node/features/sqlite.ts +289 -0
  302. package/src/node/features/telegram.ts +342 -0
  303. package/src/node/features/tts.ts +184 -0
  304. package/src/node/features/ui.ts +857 -0
  305. package/src/node/features/vault.ts +164 -0
  306. package/src/node/features/vm.ts +312 -0
  307. package/src/node/features/window-manager.ts +804 -0
  308. package/src/node/features/yaml-tree.ts +149 -0
  309. package/src/node/features/yaml.ts +132 -0
  310. package/src/node.ts +70 -0
  311. package/src/react/index.ts +175 -0
  312. package/src/registry.ts +199 -0
  313. package/src/scaffolds/generated.ts +1613 -0
  314. package/src/scaffolds/template.ts +37 -0
  315. package/src/schemas/base.ts +255 -0
  316. package/src/server.ts +135 -0
  317. package/src/servers/express.ts +209 -0
  318. package/src/servers/mcp.ts +805 -0
  319. package/src/servers/socket.ts +120 -0
  320. package/src/state.ts +101 -0
  321. package/src/web/clients/socket.ts +82 -0
  322. package/src/web/container.ts +74 -0
  323. package/src/web/extension.ts +30 -0
  324. package/src/web/feature.ts +12 -0
  325. package/src/web/features/asset-loader.ts +64 -0
  326. package/src/web/features/container-link.ts +385 -0
  327. package/src/web/features/esbuild.ts +79 -0
  328. package/src/web/features/helpers.ts +267 -0
  329. package/src/web/features/network.ts +61 -0
  330. package/src/web/features/speech.ts +87 -0
  331. package/src/web/features/vault.ts +189 -0
  332. package/src/web/features/vm.ts +78 -0
  333. package/src/web/features/voice-recognition.ts +129 -0
  334. package/src/web/shims/isomorphic-vm.ts +149 -0
  335. package/test/bus.test.ts +134 -0
  336. package/test/clients-servers.test.ts +216 -0
  337. package/test/container-link.test.ts +274 -0
  338. package/test/features.test.ts +160 -0
  339. package/test/integration.test.ts +787 -0
  340. package/test/node-container.test.ts +121 -0
  341. package/test/rate-limit.test.ts +272 -0
  342. package/test/semantic-search.test.ts +550 -0
  343. package/test/state.test.ts +121 -0
  344. package/test-integration/assistant.test.ts +138 -0
  345. package/test-integration/assistants-manager.test.ts +123 -0
  346. package/test-integration/claude-code.test.ts +98 -0
  347. package/test-integration/conversation-history.test.ts +205 -0
  348. package/test-integration/conversation.test.ts +137 -0
  349. package/test-integration/elevenlabs.test.ts +55 -0
  350. package/test-integration/google-services.test.ts +80 -0
  351. package/test-integration/helpers.ts +89 -0
  352. package/test-integration/openai-codex.test.ts +93 -0
  353. package/test-integration/runpod.test.ts +58 -0
  354. package/test-integration/server-endpoints.test.ts +97 -0
  355. package/test-integration/skills-library.test.ts +157 -0
  356. package/test-integration/telegram.test.ts +46 -0
  357. package/tsconfig.json +58 -0
  358. package/uv.lock +8 -0
@@ -0,0 +1,781 @@
1
+ // @ts-nocheck
2
+ import { Bus } from './bus'
3
+ import { SetStateValue, State } from './state'
4
+ import { AvailableFeatures, features, Feature, FeaturesRegistry } from './feature'
5
+ import { Helper } from './helper'
6
+ import uuid from 'node-uuid'
7
+ import hashObject from './hash-object'
8
+ import { uniq, keyBy, uniqBy, groupBy, debounce, throttle, mapValues, mapKeys, pick, get, set, omit, kebabCase, camelCase, upperFirst, lowerFirst } from 'lodash-es'
9
+ import { pluralize, singularize } from 'inflect'
10
+ import { z } from 'zod'
11
+ import { ContainerStateSchema, describeZodShape } from './schemas/base'
12
+ import { getContainerBuildTimeData, type ContainerIntrospection, type RegistryIntrospection, type IntrospectionSection } from './introspection/index'
13
+
14
+ export { z }
15
+
16
+ const { v4 } = uuid
17
+
18
+ const stringUtils = { kebabCase, camelCase, upperFirst, lowerFirst, pluralize, singularize }
19
+
20
+ export type { AvailableFeatures }
21
+
22
+ // I want the InstanceType of each value of AvailableFeatures, AvailableClients, whatever
23
+ export type AvailableInstanceTypes<T> = {
24
+ [K in keyof T]: T[K] extends new (...args: any) => any ? InstanceType<T[K]> : never
25
+ }
26
+
27
+ /**
28
+ * You'll want to use module augmentation to add your own options to the ContainerArgv interface
29
+ */
30
+ export interface ContainerArgv {
31
+ _?: string[]
32
+ }
33
+
34
+ export type ContainerState = z.infer<typeof ContainerStateSchema>
35
+
36
+ export interface Plugin<T> {
37
+ attach?: (container: Container<any> & T, options?: any) => any
38
+ }
39
+
40
+ export type Extension<T> = 'string' | keyof AvailableFeatures | Plugin<T> | { attach: (container: Container<any>, options?: any) => T}
41
+
42
+ export interface ContainerContext<T extends AvailableFeatures = any> {
43
+ container: Container<T>
44
+ }
45
+
46
+ /**
47
+ * Containers are single objects that contain state, an event bus, and registries of helpers such as:
48
+ *
49
+ * - features
50
+ * - clients
51
+ * - servers
52
+ *
53
+ * A Helper represents a category of components in your program which have a common interface, e.g. all servers can be started / stopped, all features can be enabled, if supported, all clients can connect to something.
54
+ *
55
+ * A Helper can be introspected at runtime to learn about the interface of the helper. A helper has state, and emits events.
56
+ *
57
+ * You can design your own containers and load them up with the helpers you want for that environment.
58
+ */
59
+ export class Container<Features extends AvailableFeatures = AvailableFeatures, ContainerState extends ContainerState = ContainerState > {
60
+ static stateSchema = ContainerStateSchema
61
+
62
+ readonly uuid = v4()
63
+ private readonly _events = new Bus()
64
+ private readonly _state: State<ContainerState>
65
+
66
+ /**
67
+ * You can use module augmentation to define the starting interface for your container
68
+ * whether it is process.argv, process.env, or some combination thereof
69
+ */
70
+ readonly options: ContainerArgv
71
+
72
+ constructor(options: ContainerArgv) {
73
+ this.options = options
74
+ this._state = new State<ContainerState>()
75
+ this.z = z
76
+ this.state
77
+ .set('enabledFeatures', [])
78
+ .set('started', false)
79
+ .set('registries', ['features'])
80
+ .set('factories', ['feature'])
81
+
82
+ this._hide('options', '_state', '_events', 'uuid', '_plugins', 'z')
83
+
84
+ this.on('featureEnabled', (featureId: string, feature: any) => {
85
+ const featureKey = featureId.replace(/^features\./,'')
86
+ const mapKey = `${this.uuid}/${featureKey}`
87
+ featureIdToHelperCacheKeyMap.set(mapKey, feature.cacheKey)
88
+ this.state.set('enabledFeatures', uniq([
89
+ ...this.state.get('enabledFeatures')!,
90
+ featureKey
91
+ ]))
92
+ this.addContext(featureKey, feature)
93
+ })
94
+
95
+ this.state.observe(() => {
96
+ this.emit('stateChange', this.state.current)
97
+ })
98
+ }
99
+
100
+ /**
101
+ * Creates a new subcontainer instance of the same concrete Container subclass.
102
+ *
103
+ * The new instance is constructed with the same options as this container,
104
+ * shallow-merged with any overrides you provide. This preserves the runtime
105
+ * container type (e.g. NodeContainer, BrowserContainer, etc.).
106
+ *
107
+ * @param options - Options to override for the new container instance.
108
+ * @returns A new container instance of the same subclass.
109
+ */
110
+ subcontainer<This extends Container<any, any>>(
111
+ this: This,
112
+ options: ConstructorParameters<This['constructor']>[0]
113
+ ): This {
114
+ const Ctor = this.constructor as new (options: ConstructorParameters<This['constructor']>[0]) => This
115
+ const mergedOptions = {
116
+ ...(this as any).options || {},
117
+ ...(options || {}),
118
+ }
119
+ return new Ctor(mergedOptions)
120
+ }
121
+
122
+ z!: typeof z
123
+
124
+
125
+ /** The observable state object for this container instance. */
126
+ get state() {
127
+ return this._state
128
+ }
129
+
130
+ /** Returns the list of shortcut IDs for all currently enabled features. */
131
+ get enabledFeatureIds() {
132
+ return this.state.get('enabledFeatures') || []
133
+ }
134
+
135
+ /** Returns a map of enabled feature shortcut IDs to their instances. */
136
+ get enabledFeatures() : Partial<AvailableInstanceTypes<Features>> {
137
+ return Object.fromEntries(
138
+ this.enabledFeatureIds.map((featureId) => [featureId, (this as any)[featureId]])
139
+ ) as AvailableInstanceTypes<Features>
140
+ }
141
+
142
+ utils = {
143
+ hashObject: (obj: any) => hashObject(obj),
144
+ get stringUtils() { return stringUtils },
145
+ uuid: () => v4(),
146
+ lodash: {
147
+ uniq, keyBy, uniqBy, groupBy, debounce, throttle, mapValues, mapKeys, pick, get, set, omit,
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Add a value to the container's shared context, which is passed to all helper instances.
153
+ * Accepts either a key and value, or an object of key-value pairs to add.
154
+ *
155
+ * @param {K} key - The context key (or object of key-value pairs)
156
+ * @param {ContainerContext[K]} value - The context value (omit when passing an object)
157
+ */
158
+ addContext<K extends keyof ContainerContext>(key: K, value: ContainerContext[K]): this
159
+ addContext(context: Partial<ContainerContext>): this
160
+ addContext(keyOrContext: keyof ContainerContext | Partial<ContainerContext>, value?: ContainerContext[keyof ContainerContext]): this {
161
+ if (arguments.length === 1 && typeof keyOrContext === 'object' && keyOrContext !== null) {
162
+ for (const [k, v] of Object.entries(keyOrContext)) {
163
+ if (v !== undefined) {
164
+ this.addContext(k as keyof ContainerContext, v as ContainerContext[keyof ContainerContext])
165
+ }
166
+ }
167
+ return this
168
+ }
169
+ const contexts = contextMap.get(this) || new Map()
170
+ contexts.set(keyOrContext as keyof ContainerContext, value)
171
+ contextMap.set(this, contexts)
172
+ return this
173
+ }
174
+
175
+ /**
176
+ * The Container's context is an object that contains the enabled features, the container itself, and any additional context that has been added to the container.
177
+ *
178
+ * All helper instances that are created by the container will have access to the shared context.
179
+ */
180
+ get context(): ContainerContext<Features> & Partial<AvailableInstanceTypes<AvailableFeatures>> {
181
+ const contexts = contextMap.get(this) || new Map()
182
+
183
+ return {
184
+ ...this.enabledFeatures,
185
+ ...Object.fromEntries(Array.from(contexts.entries())) as ContainerContext<Features>,
186
+ container: this as Container<Features>,
187
+ }
188
+ }
189
+
190
+ /**
191
+ * The current state of the container.
192
+ *
193
+ * This is a snapshot of the container's state at the time this method is called.
194
+ *
195
+ * @returns The current state of the container.
196
+ */
197
+ get currentState() {
198
+ return this.state.current
199
+ }
200
+
201
+ /**
202
+ * Sets the state of the container.
203
+ *
204
+ * @param newState - The new state of the container.
205
+ * @returns The container instance.
206
+ */
207
+ setState(newState: SetStateValue<ContainerState>) {
208
+ this.state.setState(newState)
209
+ return this
210
+ }
211
+
212
+ get Feature() {
213
+ return Feature
214
+ }
215
+
216
+ get Helper() {
217
+ return Helper
218
+ }
219
+
220
+ get State() {
221
+ return State
222
+ }
223
+
224
+ get features(): FeaturesRegistry {
225
+ return features
226
+ }
227
+
228
+ /**
229
+ * Convenience method for creating a new event bus instance.
230
+ */
231
+ bus() {
232
+ return new Bus()
233
+ }
234
+
235
+ /**
236
+ * Convenience method for creating a new observable State object.
237
+ */
238
+ newState<T extends object = any>(initialState: T = {} as T) {
239
+ return new State<T>({ initialState })
240
+ }
241
+
242
+ /**
243
+ * Parse helper options through the helper's static options schema so defaults are materialized.
244
+ */
245
+ normalizeHelperOptions(BaseClass: any, options: any, fallbackName?: string) {
246
+ const candidate = { ...(options || {}) }
247
+
248
+ if (fallbackName && (candidate.name === undefined || candidate.name === null || candidate.name === '')) {
249
+ candidate.name = fallbackName
250
+ }
251
+
252
+ const schema = BaseClass?.optionsSchema
253
+ if (!schema || typeof schema.safeParse !== 'function') {
254
+ return candidate
255
+ }
256
+
257
+ const parsed = schema.safeParse(candidate)
258
+ if (parsed.success) {
259
+ return parsed.data
260
+ }
261
+
262
+ const target = BaseClass?.shortcut || BaseClass?.name || 'helper'
263
+ const details = parsed.error.issues
264
+ .map((issue: any) => `${issue.path?.join('.') || 'options'}: ${issue.message}`)
265
+ .join('; ')
266
+ throw new Error(`Invalid options for ${target}: ${details || parsed.error.message}`)
267
+ }
268
+
269
+ buildHelperCacheKey(type: string, id: string, options: any, omitOptionKeys: string[] = []) {
270
+ const hashableOptions = omit(options || {}, uniq(['_cacheKey', ...omitOptionKeys]))
271
+
272
+ return hashObject({
273
+ __type: type,
274
+ id,
275
+ options: hashableOptions,
276
+ uuid: this.uuid,
277
+ })
278
+ }
279
+
280
+ createHelperInstance({
281
+ cache,
282
+ type,
283
+ id,
284
+ BaseClass,
285
+ options,
286
+ fallbackName,
287
+ omitOptionKeys = [],
288
+ context,
289
+ }: {
290
+ cache: Map<string, any>
291
+ type: string
292
+ id: string
293
+ BaseClass: any
294
+ options?: any
295
+ fallbackName?: string
296
+ omitOptionKeys?: string[]
297
+ context?: any
298
+ }) {
299
+ const normalizedOptions = this.normalizeHelperOptions(BaseClass, options, fallbackName || id)
300
+ const cacheKey = this.buildHelperCacheKey(type, id, normalizedOptions, omitOptionKeys)
301
+ const cached = cache.get(cacheKey)
302
+
303
+ if (cached) {
304
+ return cached
305
+ }
306
+
307
+ const helperOptions = {
308
+ ...normalizedOptions,
309
+ _cacheKey: normalizedOptions._cacheKey || cacheKey,
310
+ }
311
+
312
+ const instance = new (BaseClass as any)(helperOptions, context || this.context)
313
+ cache.set(cacheKey, instance)
314
+ return instance
315
+ }
316
+
317
+ /**
318
+ * Creates a new instance of a feature.
319
+ *
320
+ * If you pass the same arguments, it will return the same instance as last time you created that.
321
+ *
322
+ * If you need the ability to create fresh instances, it is up to you how you define your options to support that.
323
+ *
324
+ * @param id - The id of the feature to create.
325
+ * @param options - The options to pass to the feature constructor.
326
+ * @returns The new feature instance.
327
+ */
328
+ feature<T extends keyof Features>(
329
+ id: T,
330
+ options?: ConstructorParameters<Features[T]>[0]
331
+ ): InstanceType<Features[T]> {
332
+ const BaseClass = this.features.lookup(id as string) as Features[T]
333
+
334
+ return this.createHelperInstance({
335
+ cache: helperCache,
336
+ type: 'feature',
337
+ id: String(id),
338
+ BaseClass,
339
+ options,
340
+ omitOptionKeys: ['enable'],
341
+ context: { container: this },
342
+ }) as InstanceType<Features[T]>
343
+ }
344
+
345
+ /**
346
+ * TODO:
347
+ *
348
+ * A container should be able to container.use(plugin) and that plugin should be able to define
349
+ * an asynchronous method that will be run when the container is started. Right now there's nothing
350
+ * to do with starting / stopping a container but that might be neat.
351
+ */
352
+ async start() {
353
+ this.emit('started', this as Container<Features>)
354
+ this.state.set('started', true)
355
+ return this
356
+ }
357
+
358
+ /**
359
+ * ENVIRONMENT DETECTION METHODS
360
+ *
361
+ * One of the ideas of the container is that it can detect what kind of environment it is running in and
362
+ * e.g. perhaps load different versions of features to provide the same API with different implementations.
363
+ *
364
+ */
365
+
366
+ /**
367
+ * Returns true if the container is running in a browser.
368
+ */
369
+ get isBrowser() {
370
+ return typeof window !== 'undefined' && typeof document !== 'undefined'
371
+ }
372
+
373
+ /**
374
+ * Returns true if the container is running in Bun.
375
+ */
376
+ get isBun() {
377
+ return this.isNode && typeof Bun !== 'undefined'
378
+ }
379
+
380
+ /**
381
+ * Returns true if the container is running in Node.
382
+ */
383
+ get isNode() {
384
+ return typeof process !== 'undefined' && typeof process.versions !== 'undefined' && typeof process.versions.node !== 'undefined'
385
+ }
386
+
387
+ /**
388
+ * Returns true if the container is running in Electron.
389
+ */
390
+ get isElectron() {
391
+ return typeof process !== 'undefined' && typeof process.versions !== 'undefined' && typeof process.versions.electron !== 'undefined'
392
+ }
393
+
394
+ /**
395
+ * Returns true if the container is running in development mode.
396
+ */
397
+ get isDevelopment() {
398
+ return typeof process !== 'undefined' && process.env.NODE_ENV === 'development'
399
+ }
400
+
401
+ /**
402
+ * Returns true if the container is running in production mode.
403
+ */
404
+ get isProduction() {
405
+ return typeof process !== 'undefined' && process.env.NODE_ENV === 'production'
406
+ }
407
+
408
+ /**
409
+ * Returns true if the container is running in a CI environment.
410
+ */
411
+ get isCI() {
412
+ return typeof process !== 'undefined' && process.env.CI !== undefined && String(process.env.CI).length > 0
413
+ }
414
+
415
+ /** Emit an event on the container's event bus. */
416
+ emit(event: string, ...args: any[]) {
417
+ this._events.emit(event, ...args)
418
+ return this
419
+ }
420
+
421
+ /** Subscribe to an event on the container's event bus. */
422
+ on(event: string, listener: (...args: any[]) => void) {
423
+ this._events.on(event, listener)
424
+ return this
425
+ }
426
+
427
+ /** Unsubscribe a listener from an event on the container's event bus. */
428
+ off(event: string, listener?: (...args: any[]) => void) {
429
+ this._events.off(event, listener)
430
+ return this
431
+ }
432
+
433
+ /** Subscribe to an event on the container's event bus, but only fire once. */
434
+ once(event: string, listener: (...args: any[]) => void) {
435
+ this._events.once(event, listener)
436
+ return this
437
+ }
438
+
439
+ /**
440
+ * Returns a promise that will resolve when the event is emitted
441
+ */
442
+ async waitFor(event: string) {
443
+ const resp = await this._events.waitFor(event)
444
+ return resp
445
+ }
446
+
447
+ /**
448
+ * Register a helper type (registry + factory pair) on this container.
449
+ * Called automatically by Helper.attach() methods (e.g. Client.attach, Server.attach).
450
+ *
451
+ * @param registryName - The plural name of the registry, e.g. "clients", "servers"
452
+ * @param factoryName - The singular factory method name, e.g. "client", "server"
453
+ */
454
+ registerHelperType(registryName: string, factoryName: string) {
455
+ const registries = uniq([...this.state.get('registries')!, registryName])
456
+ const factories = uniq([...this.state.get('factories')!, factoryName])
457
+ this.state.set('registries', registries)
458
+ this.state.set('factories', factories)
459
+ return this
460
+ }
461
+
462
+ /** Returns the names of all attached registries (e.g. ["features", "clients", "servers"]). */
463
+ get registryNames(): string[] {
464
+ return this.state.get('registries') || ['features']
465
+ }
466
+
467
+ /** Returns the names of all available factory methods (e.g. ["feature", "client", "server"]). */
468
+ get factoryNames(): string[] {
469
+ return this.state.get('factories') || ['feature']
470
+ }
471
+
472
+ /**
473
+ * Returns a full introspection object for this container, merging build-time AST data
474
+ * (JSDoc descriptions, methods, getters) with runtime data (registries, factories, state, environment).
475
+ *
476
+ * @returns {ContainerIntrospection} The complete introspection data
477
+ */
478
+ inspect(): ContainerIntrospection {
479
+ const className = this.constructor.name
480
+ const buildTimeData = getContainerBuildTimeData(className) || {}
481
+
482
+ // Walk up the prototype chain to merge inherited build-time data
483
+ let mergedMethods = { ...(buildTimeData.methods || {}) }
484
+ let mergedGetters = { ...(buildTimeData.getters || {}) }
485
+ let mergedEvents = { ...(buildTimeData.events || {}) }
486
+ let mergedDescription = buildTimeData.description || ''
487
+
488
+ let proto = Object.getPrototypeOf(this.constructor)
489
+ while (proto && proto.name) {
490
+ const parentData = getContainerBuildTimeData(proto.name)
491
+ if (parentData) {
492
+ mergedMethods = { ...parentData.methods, ...mergedMethods }
493
+ mergedGetters = { ...parentData.getters, ...mergedGetters }
494
+ mergedEvents = { ...parentData.events, ...mergedEvents }
495
+ if (!mergedDescription && parentData.description) {
496
+ mergedDescription = parentData.description
497
+ }
498
+ }
499
+ proto = Object.getPrototypeOf(proto)
500
+ }
501
+
502
+ // Build registry introspection from runtime data
503
+ const registryNames = this.registryNames
504
+ const registries: RegistryIntrospection[] = registryNames.map((name) => {
505
+ const registry = (this as any)[name]
506
+ return {
507
+ name,
508
+ baseClass: registry?.baseClass?.name || 'Helper',
509
+ available: registry?.available || []
510
+ }
511
+ })
512
+
513
+ // Get state description from the Zod schema
514
+ const stateSchema = (this.constructor as any).stateSchema
515
+ const stateDescription = stateSchema ? describeZodShape(stateSchema) : {}
516
+
517
+ return {
518
+ className,
519
+ uuid: this.uuid,
520
+ description: mergedDescription,
521
+ registries,
522
+ factories: this.factoryNames,
523
+ methods: mergedMethods,
524
+ getters: mergedGetters,
525
+ events: mergedEvents,
526
+ state: stateDescription,
527
+ enabledFeatures: this.enabledFeatureIds,
528
+ environment: {
529
+ isBrowser: this.isBrowser,
530
+ isNode: this.isNode,
531
+ isBun: this.isBun,
532
+ isElectron: this.isElectron,
533
+ isDevelopment: this.isDevelopment,
534
+ isProduction: this.isProduction,
535
+ isCI: this.isCI
536
+ }
537
+ }
538
+ }
539
+
540
+ /**
541
+ * Returns a human-readable markdown representation of this container's introspection data.
542
+ * Useful in REPLs, AI agent contexts, or documentation generation.
543
+ *
544
+ * The first argument can be a section name (`'methods'`, `'getters'`, etc.) to render only
545
+ * that section, or a number for the starting heading depth (backward compatible).
546
+ *
547
+ * @returns {string} Markdown-formatted introspection text
548
+ */
549
+ inspectAsText(sectionOrDepth?: IntrospectionSection | number, startHeadingDepth?: number): string {
550
+ let section: IntrospectionSection | undefined
551
+ let depth = 1
552
+
553
+ if (typeof sectionOrDepth === 'string') {
554
+ section = sectionOrDepth
555
+ depth = startHeadingDepth ?? 1
556
+ } else if (typeof sectionOrDepth === 'number') {
557
+ depth = sectionOrDepth
558
+ }
559
+
560
+ const data = this.inspect()
561
+ return presentContainerIntrospectionAsMarkdown(data, depth, section)
562
+ }
563
+
564
+ /** Alias for inspectAsText */
565
+ introspectAsText(sectionOrDepth?: IntrospectionSection | number, startHeadingDepth?: number): string {
566
+ return this.inspectAsText(sectionOrDepth, startHeadingDepth)
567
+ }
568
+
569
+ /** Alias for inspectAsJSON */
570
+ introspectAsJSON(sectionOrDepth?: IntrospectionSection | number, startHeadingDepth?: number): any {
571
+ const data = this.inspect()
572
+ return presentContainerIntrospectionAsJSON(data, depth, section)
573
+ }
574
+
575
+ /** Make a property non-enumerable, which is nice for inspecting it in the REPL */
576
+ _hide(...propNames: string[]) {
577
+ propNames.map((propName) => {
578
+ Object.defineProperty(this, propName, { enumerable: false })
579
+ })
580
+
581
+ return this
582
+ }
583
+
584
+ /** Sleep for the specified number of milliseconds. Useful for scripting and sequencing. */
585
+ async sleep(ms = 1000) {
586
+ await new Promise((res) => setTimeout(res,ms))
587
+ return this
588
+ }
589
+
590
+ _plugins: (() => void)[] = []
591
+
592
+ /**
593
+ * Apply a plugin or enable a feature by string name. Plugins must have a static attach(container) method.
594
+ *
595
+ * @param {Extension<T>} plugin - A feature name string, or a class/object with a static attach method
596
+ * @param {any} options - Options to pass to the plugin's attach method
597
+ */
598
+ use<T = {}>(plugin: Extension<T>, options: any = {}) : this & T {
599
+ const container = this
600
+
601
+ if(typeof plugin === 'string' && features.has(plugin)) {
602
+ const featureId = plugin as keyof AvailableFeatures
603
+ this.feature(featureId, {
604
+ ...options,
605
+ enable: true
606
+ })
607
+ } else if (typeof plugin === 'string' && !features.has(plugin)) {
608
+ throw new Error(`Feature ${plugin} is not available.`)
609
+ } else if ((typeof plugin === 'object' || typeof plugin === 'function') && typeof plugin?.attach === 'function') {
610
+ // This is like using a Helper or Feature subclass which declares a static attach method
611
+ plugin.attach(container as this & T, options)
612
+ }
613
+
614
+ return this as (this & T)
615
+ }
616
+ }
617
+
618
+ const helperCache = new Map()
619
+ const featureIdToHelperCacheKeyMap= new Map()
620
+ const contextMap = new WeakMap()
621
+
622
+ function presentContainerIntrospectionAsMarkdown(data: ContainerIntrospection, startHeadingDepth: number = 1, section?: IntrospectionSection): string {
623
+ const sections: string[] = []
624
+ const heading = (level: number) => '#'.repeat(Math.max(1, startHeadingDepth + level - 1))
625
+
626
+ const shouldRender = (name: IntrospectionSection | string) => !section || section === name
627
+
628
+ if (!section) {
629
+ // Header
630
+ sections.push(`${heading(1)} ${data.className}\n\n${data.description || ''}`)
631
+
632
+ // Registries section
633
+ if (data.registries && data.registries.length > 0) {
634
+ sections.push(`${heading(2)} Registries`)
635
+
636
+ for (const reg of data.registries) {
637
+ sections.push(`${heading(3)} ${reg.name} (${reg.baseClass})`)
638
+ if (reg.available.length > 0) {
639
+ sections.push(reg.available.map(a => `- \`${a}\``).join('\n'))
640
+ } else {
641
+ sections.push('_No members registered_')
642
+ }
643
+ }
644
+ }
645
+
646
+ // Factories section
647
+ if (data.factories && data.factories.length > 0) {
648
+ sections.push(`${heading(2)} Factory Methods`)
649
+ sections.push(data.factories.map(f => `- \`${f}()\``).join('\n'))
650
+ }
651
+ }
652
+
653
+ // Methods section
654
+ if (shouldRender('methods') && data.methods && Object.keys(data.methods).length > 0) {
655
+ sections.push(`${heading(2)} Methods`)
656
+
657
+ for (const [methodName, methodInfo] of Object.entries(data.methods)) {
658
+ sections.push(`${heading(3)} ${methodName}`)
659
+
660
+ if (methodInfo.description) {
661
+ sections.push(methodInfo.description)
662
+ }
663
+
664
+ if (methodInfo.parameters && Object.keys(methodInfo.parameters).length > 0) {
665
+ const tableRows = [
666
+ `**Parameters:**`,
667
+ '',
668
+ `| Name | Type | Required | Description |`,
669
+ `|------|------|----------|-------------|`,
670
+ ]
671
+
672
+ for (const [paramName, paramInfo] of Object.entries(methodInfo.parameters)) {
673
+ const isRequired = methodInfo.required?.includes(paramName) ? '✓' : ''
674
+ tableRows.push(`| \`${paramName}\` | \`${paramInfo.type || 'any'}\` | ${isRequired} | ${paramInfo.description || ''} |`)
675
+
676
+ if (paramInfo.properties && Object.keys(paramInfo.properties).length > 0) {
677
+ tableRows.push('')
678
+ tableRows.push(`\`${paramInfo.type}\` properties:`)
679
+ tableRows.push('')
680
+ tableRows.push(`| Property | Type | Description |`)
681
+ tableRows.push(`|----------|------|-------------|`)
682
+
683
+ for (const [propName, propInfo] of Object.entries(paramInfo.properties)) {
684
+ tableRows.push(`| \`${propName}\` | \`${propInfo.type || 'any'}\` | ${propInfo.description || ''} |`)
685
+ }
686
+ }
687
+ }
688
+ sections.push(tableRows.join('\n'))
689
+ }
690
+
691
+ if (methodInfo.returns) {
692
+ sections.push(`**Returns:** \`${methodInfo.returns}\``)
693
+ }
694
+
695
+ sections.push('')
696
+ }
697
+ }
698
+
699
+ // Getters section
700
+ if (shouldRender('getters') && data.getters && Object.keys(data.getters).length > 0) {
701
+ const getterTableRows = [
702
+ `${heading(2)} Getters`,
703
+ '',
704
+ `| Property | Type | Description |`,
705
+ `|----------|------|-------------|`,
706
+ ]
707
+
708
+ for (const [getterName, getterInfo] of Object.entries(data.getters)) {
709
+ getterTableRows.push(`| \`${getterName}\` | \`${getterInfo.returns || 'any'}\` | ${getterInfo.description || ''} |`)
710
+ }
711
+ sections.push(getterTableRows.join('\n'))
712
+ }
713
+
714
+ // Events section
715
+ if (shouldRender('events') && data.events && Object.keys(data.events).length > 0) {
716
+ sections.push(`${heading(2)} Events`)
717
+
718
+ for (const [eventName, eventInfo] of Object.entries(data.events)) {
719
+ sections.push(`${heading(3)} ${eventName}`)
720
+
721
+ if (eventInfo.description) {
722
+ sections.push(eventInfo.description)
723
+ }
724
+
725
+ if (eventInfo.arguments && Object.keys(eventInfo.arguments).length > 0) {
726
+ const tableRows = [
727
+ `**Event Arguments:**`,
728
+ '',
729
+ `| Name | Type | Description |`,
730
+ `|------|------|-------------|`,
731
+ ]
732
+
733
+ for (const [argName, argInfo] of Object.entries(eventInfo.arguments)) {
734
+ tableRows.push(`| \`${argName}\` | \`${argInfo.type || 'any'}\` | ${argInfo.description || ''} |`)
735
+ }
736
+ sections.push(tableRows.join('\n'))
737
+ }
738
+
739
+ sections.push('')
740
+ }
741
+ }
742
+
743
+ // State section
744
+ if (shouldRender('state') && data.state && Object.keys(data.state).length > 0) {
745
+ const stateTableRows = [
746
+ `${heading(2)} State`,
747
+ '',
748
+ `| Property | Type | Description |`,
749
+ `|----------|------|-------------|`,
750
+ ]
751
+
752
+ for (const [stateName, stateInfo] of Object.entries(data.state)) {
753
+ stateTableRows.push(`| \`${stateName}\` | \`${stateInfo.type || 'any'}\` | ${stateInfo.description || ''} |`)
754
+ }
755
+ sections.push(stateTableRows.join('\n'))
756
+ }
757
+
758
+ if (!section) {
759
+ // Enabled features section
760
+ if (data.enabledFeatures && data.enabledFeatures.length > 0) {
761
+ sections.push(`${heading(2)} Enabled Features`)
762
+ sections.push(data.enabledFeatures.map(f => `- \`${f}\``).join('\n'))
763
+ }
764
+
765
+ // Environment section
766
+ if (data.environment) {
767
+ const envTableRows = [
768
+ `${heading(2)} Environment`,
769
+ '',
770
+ `| Flag | Value |`,
771
+ `|------|-------|`,
772
+ ]
773
+ for (const [key, value] of Object.entries(data.environment)) {
774
+ envTableRows.push(`| \`${key}\` | ${value} |`)
775
+ }
776
+ sections.push(envTableRows.join('\n'))
777
+ }
778
+ }
779
+
780
+ return sections.join('\n\n')
781
+ }