@treenity/mods 3.0.1

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 (486) hide show
  1. package/board/board.test.ts +212 -0
  2. package/board/client.ts +1 -0
  3. package/board/seed.ts +26 -0
  4. package/board/server.ts +5 -0
  5. package/board/types.ts +87 -0
  6. package/board/view.tsx +574 -0
  7. package/brahman/CLAUDE.md +18 -0
  8. package/brahman/brahman.test.ts +855 -0
  9. package/brahman/client.ts +2 -0
  10. package/brahman/helpers.ts +374 -0
  11. package/brahman/server.ts +2 -0
  12. package/brahman/service.ts +328 -0
  13. package/brahman/types.ts +727 -0
  14. package/brahman/view.tsx +73 -0
  15. package/brahman/views/action-cards.tsx +615 -0
  16. package/brahman/views/bot-view.tsx +76 -0
  17. package/brahman/views/chat-editor.tsx +782 -0
  18. package/brahman/views/chat-preview.tsx +266 -0
  19. package/brahman/views/menu-editor.tsx +451 -0
  20. package/brahman/views/page-layout.tsx +285 -0
  21. package/brahman/views/tstring-input.tsx +84 -0
  22. package/cafe/CLAUDE.md +7 -0
  23. package/cafe/seed.ts +8 -0
  24. package/cafe/server.ts +2 -0
  25. package/cafe/types.ts +32 -0
  26. package/canary/seed.ts +8 -0
  27. package/canary/server.ts +3 -0
  28. package/canary/service.ts +101 -0
  29. package/canary/types.ts +16 -0
  30. package/dist/board/client.d.ts +2 -0
  31. package/dist/board/client.d.ts.map +1 -0
  32. package/dist/board/client.js +2 -0
  33. package/dist/board/client.js.map +1 -0
  34. package/dist/board/seed.d.ts +2 -0
  35. package/dist/board/seed.d.ts.map +1 -0
  36. package/dist/board/seed.js +23 -0
  37. package/dist/board/seed.js.map +1 -0
  38. package/dist/board/server.d.ts +3 -0
  39. package/dist/board/server.d.ts.map +1 -0
  40. package/dist/board/server.js +5 -0
  41. package/dist/board/server.js.map +1 -0
  42. package/dist/board/types.d.ts +45 -0
  43. package/dist/board/types.d.ts.map +1 -0
  44. package/dist/board/types.js +82 -0
  45. package/dist/board/types.js.map +1 -0
  46. package/dist/board/view.d.ts +2 -0
  47. package/dist/board/view.d.ts.map +1 -0
  48. package/dist/board/view.js +254 -0
  49. package/dist/board/view.js.map +1 -0
  50. package/dist/brahman/client.d.ts +3 -0
  51. package/dist/brahman/client.d.ts.map +1 -0
  52. package/dist/brahman/client.js +3 -0
  53. package/dist/brahman/client.js.map +1 -0
  54. package/dist/brahman/helpers.d.ts +51 -0
  55. package/dist/brahman/helpers.d.ts.map +1 -0
  56. package/dist/brahman/helpers.js +321 -0
  57. package/dist/brahman/helpers.js.map +1 -0
  58. package/dist/brahman/server.d.ts +3 -0
  59. package/dist/brahman/server.d.ts.map +1 -0
  60. package/dist/brahman/server.js +3 -0
  61. package/dist/brahman/server.js.map +1 -0
  62. package/dist/brahman/service.d.ts +2 -0
  63. package/dist/brahman/service.d.ts.map +1 -0
  64. package/dist/brahman/service.js +310 -0
  65. package/dist/brahman/service.js.map +1 -0
  66. package/dist/brahman/types.d.ts +335 -0
  67. package/dist/brahman/types.d.ts.map +1 -0
  68. package/dist/brahman/types.js +633 -0
  69. package/dist/brahman/types.js.map +1 -0
  70. package/dist/brahman/view.d.ts +2 -0
  71. package/dist/brahman/view.d.ts.map +1 -0
  72. package/dist/brahman/view.js +47 -0
  73. package/dist/brahman/view.js.map +1 -0
  74. package/dist/brahman/views/action-cards.d.ts +60 -0
  75. package/dist/brahman/views/action-cards.d.ts.map +1 -0
  76. package/dist/brahman/views/action-cards.js +283 -0
  77. package/dist/brahman/views/action-cards.js.map +1 -0
  78. package/dist/brahman/views/bot-view.d.ts +5 -0
  79. package/dist/brahman/views/bot-view.d.ts.map +1 -0
  80. package/dist/brahman/views/bot-view.js +14 -0
  81. package/dist/brahman/views/bot-view.js.map +1 -0
  82. package/dist/brahman/views/chat-editor.d.ts +5 -0
  83. package/dist/brahman/views/chat-editor.d.ts.map +1 -0
  84. package/dist/brahman/views/chat-editor.js +306 -0
  85. package/dist/brahman/views/chat-editor.js.map +1 -0
  86. package/dist/brahman/views/chat-preview.d.ts +5 -0
  87. package/dist/brahman/views/chat-preview.d.ts.map +1 -0
  88. package/dist/brahman/views/chat-preview.js +145 -0
  89. package/dist/brahman/views/chat-preview.js.map +1 -0
  90. package/dist/brahman/views/menu-editor.d.ts +11 -0
  91. package/dist/brahman/views/menu-editor.d.ts.map +1 -0
  92. package/dist/brahman/views/menu-editor.js +171 -0
  93. package/dist/brahman/views/menu-editor.js.map +1 -0
  94. package/dist/brahman/views/page-layout.d.ts +5 -0
  95. package/dist/brahman/views/page-layout.d.ts.map +1 -0
  96. package/dist/brahman/views/page-layout.js +114 -0
  97. package/dist/brahman/views/page-layout.js.map +1 -0
  98. package/dist/brahman/views/tstring-input.d.ts +15 -0
  99. package/dist/brahman/views/tstring-input.d.ts.map +1 -0
  100. package/dist/brahman/views/tstring-input.js +23 -0
  101. package/dist/brahman/views/tstring-input.js.map +1 -0
  102. package/dist/cafe/seed.d.ts +2 -0
  103. package/dist/cafe/seed.d.ts.map +1 -0
  104. package/dist/cafe/seed.js +7 -0
  105. package/dist/cafe/seed.js.map +1 -0
  106. package/dist/cafe/server.d.ts +3 -0
  107. package/dist/cafe/server.d.ts.map +1 -0
  108. package/dist/cafe/server.js +3 -0
  109. package/dist/cafe/server.js.map +1 -0
  110. package/dist/cafe/types.d.ts +2 -0
  111. package/dist/cafe/types.d.ts.map +1 -0
  112. package/dist/cafe/types.js +31 -0
  113. package/dist/cafe/types.js.map +1 -0
  114. package/dist/canary/seed.d.ts +2 -0
  115. package/dist/canary/seed.d.ts.map +1 -0
  116. package/dist/canary/seed.js +7 -0
  117. package/dist/canary/seed.js.map +1 -0
  118. package/dist/canary/server.d.ts +4 -0
  119. package/dist/canary/server.d.ts.map +1 -0
  120. package/dist/canary/server.js +4 -0
  121. package/dist/canary/server.js.map +1 -0
  122. package/dist/canary/service.d.ts +2 -0
  123. package/dist/canary/service.d.ts.map +1 -0
  124. package/dist/canary/service.js +101 -0
  125. package/dist/canary/service.js.map +1 -0
  126. package/dist/canary/types.d.ts +9 -0
  127. package/dist/canary/types.d.ts.map +1 -0
  128. package/dist/canary/types.js +13 -0
  129. package/dist/canary/types.js.map +1 -0
  130. package/dist/doc/client.d.ts +3 -0
  131. package/dist/doc/client.d.ts.map +1 -0
  132. package/dist/doc/client.js +5 -0
  133. package/dist/doc/client.js.map +1 -0
  134. package/dist/doc/fs-codec.d.ts +2 -0
  135. package/dist/doc/fs-codec.d.ts.map +1 -0
  136. package/dist/doc/fs-codec.js +44 -0
  137. package/dist/doc/fs-codec.js.map +1 -0
  138. package/dist/doc/markdown.d.ts +13 -0
  139. package/dist/doc/markdown.d.ts.map +1 -0
  140. package/dist/doc/markdown.js +250 -0
  141. package/dist/doc/markdown.js.map +1 -0
  142. package/dist/doc/prefab.d.ts +2 -0
  143. package/dist/doc/prefab.d.ts.map +1 -0
  144. package/dist/doc/prefab.js +23 -0
  145. package/dist/doc/prefab.js.map +1 -0
  146. package/dist/doc/renderers.d.ts +2 -0
  147. package/dist/doc/renderers.d.ts.map +1 -0
  148. package/dist/doc/renderers.js +94 -0
  149. package/dist/doc/renderers.js.map +1 -0
  150. package/dist/doc/seed.d.ts +2 -0
  151. package/dist/doc/seed.d.ts.map +1 -0
  152. package/dist/doc/seed.js +9 -0
  153. package/dist/doc/seed.js.map +1 -0
  154. package/dist/doc/server.d.ts +6 -0
  155. package/dist/doc/server.d.ts.map +1 -0
  156. package/dist/doc/server.js +6 -0
  157. package/dist/doc/server.js.map +1 -0
  158. package/dist/doc/slash-command.d.ts +3 -0
  159. package/dist/doc/slash-command.d.ts.map +1 -0
  160. package/dist/doc/slash-command.js +123 -0
  161. package/dist/doc/slash-command.js.map +1 -0
  162. package/dist/doc/slash-menu.d.ts +15 -0
  163. package/dist/doc/slash-menu.d.ts.map +1 -0
  164. package/dist/doc/slash-menu.js +54 -0
  165. package/dist/doc/slash-menu.js.map +1 -0
  166. package/dist/doc/text.d.ts +2 -0
  167. package/dist/doc/text.d.ts.map +1 -0
  168. package/dist/doc/text.js +22 -0
  169. package/dist/doc/text.js.map +1 -0
  170. package/dist/doc/toolbar.d.ts +5 -0
  171. package/dist/doc/toolbar.d.ts.map +1 -0
  172. package/dist/doc/toolbar.js +15 -0
  173. package/dist/doc/toolbar.js.map +1 -0
  174. package/dist/doc/treenity-block-view.d.ts +2 -0
  175. package/dist/doc/treenity-block-view.d.ts.map +1 -0
  176. package/dist/doc/treenity-block-view.js +37 -0
  177. package/dist/doc/treenity-block-view.js.map +1 -0
  178. package/dist/doc/treenity-block.d.ts +3 -0
  179. package/dist/doc/treenity-block.d.ts.map +1 -0
  180. package/dist/doc/treenity-block.js +27 -0
  181. package/dist/doc/treenity-block.js.map +1 -0
  182. package/dist/doc/types.d.ts +2 -0
  183. package/dist/doc/types.d.ts.map +1 -0
  184. package/dist/doc/types.js +10 -0
  185. package/dist/doc/types.js.map +1 -0
  186. package/dist/launcher/client.d.ts +5 -0
  187. package/dist/launcher/client.d.ts.map +1 -0
  188. package/dist/launcher/client.js +5 -0
  189. package/dist/launcher/client.js.map +1 -0
  190. package/dist/launcher/icons.d.ts +2 -0
  191. package/dist/launcher/icons.d.ts.map +1 -0
  192. package/dist/launcher/icons.js +57 -0
  193. package/dist/launcher/icons.js.map +1 -0
  194. package/dist/launcher/seed.d.ts +2 -0
  195. package/dist/launcher/seed.d.ts.map +1 -0
  196. package/dist/launcher/seed.js +36 -0
  197. package/dist/launcher/seed.js.map +1 -0
  198. package/dist/launcher/server.d.ts +3 -0
  199. package/dist/launcher/server.d.ts.map +1 -0
  200. package/dist/launcher/server.js +3 -0
  201. package/dist/launcher/server.js.map +1 -0
  202. package/dist/launcher/types.d.ts +21 -0
  203. package/dist/launcher/types.d.ts.map +1 -0
  204. package/dist/launcher/types.js +47 -0
  205. package/dist/launcher/types.js.map +1 -0
  206. package/dist/launcher/view.d.ts +2 -0
  207. package/dist/launcher/view.d.ts.map +1 -0
  208. package/dist/launcher/view.js +187 -0
  209. package/dist/launcher/view.js.map +1 -0
  210. package/dist/launcher/widgets.d.ts +2 -0
  211. package/dist/launcher/widgets.d.ts.map +1 -0
  212. package/dist/launcher/widgets.js +73 -0
  213. package/dist/launcher/widgets.js.map +1 -0
  214. package/dist/mindmap/branch.d.ts +17 -0
  215. package/dist/mindmap/branch.d.ts.map +1 -0
  216. package/dist/mindmap/branch.js +47 -0
  217. package/dist/mindmap/branch.js.map +1 -0
  218. package/dist/mindmap/client.d.ts +3 -0
  219. package/dist/mindmap/client.d.ts.map +1 -0
  220. package/dist/mindmap/client.js +3 -0
  221. package/dist/mindmap/client.js.map +1 -0
  222. package/dist/mindmap/radial-tree.d.ts +14 -0
  223. package/dist/mindmap/radial-tree.d.ts.map +1 -0
  224. package/dist/mindmap/radial-tree.js +184 -0
  225. package/dist/mindmap/radial-tree.js.map +1 -0
  226. package/dist/mindmap/sidebar.d.ts +8 -0
  227. package/dist/mindmap/sidebar.d.ts.map +1 -0
  228. package/dist/mindmap/sidebar.js +24 -0
  229. package/dist/mindmap/sidebar.js.map +1 -0
  230. package/dist/mindmap/types.d.ts +8 -0
  231. package/dist/mindmap/types.d.ts.map +1 -0
  232. package/dist/mindmap/types.js +10 -0
  233. package/dist/mindmap/types.js.map +1 -0
  234. package/dist/mindmap/use-tree-data.d.ts +14 -0
  235. package/dist/mindmap/use-tree-data.d.ts.map +1 -0
  236. package/dist/mindmap/use-tree-data.js +95 -0
  237. package/dist/mindmap/use-tree-data.js.map +1 -0
  238. package/dist/mindmap/view.d.ts +3 -0
  239. package/dist/mindmap/view.d.ts.map +1 -0
  240. package/dist/mindmap/view.js +141 -0
  241. package/dist/mindmap/view.js.map +1 -0
  242. package/dist/sensor-demo/client.d.ts +2 -0
  243. package/dist/sensor-demo/client.d.ts.map +1 -0
  244. package/dist/sensor-demo/client.js +2 -0
  245. package/dist/sensor-demo/client.js.map +1 -0
  246. package/dist/sensor-demo/server.d.ts +2 -0
  247. package/dist/sensor-demo/server.d.ts.map +1 -0
  248. package/dist/sensor-demo/server.js +2 -0
  249. package/dist/sensor-demo/server.js.map +1 -0
  250. package/dist/sensor-demo/service.d.ts +2 -0
  251. package/dist/sensor-demo/service.d.ts.map +1 -0
  252. package/dist/sensor-demo/service.js +27 -0
  253. package/dist/sensor-demo/service.js.map +1 -0
  254. package/dist/sensor-demo/types.d.ts +15 -0
  255. package/dist/sensor-demo/types.d.ts.map +1 -0
  256. package/dist/sensor-demo/types.js +18 -0
  257. package/dist/sensor-demo/types.js.map +1 -0
  258. package/dist/sensor-demo/view.d.ts +2 -0
  259. package/dist/sensor-demo/view.d.ts.map +1 -0
  260. package/dist/sensor-demo/view.js +33 -0
  261. package/dist/sensor-demo/view.js.map +1 -0
  262. package/dist/sensor-generator/action.d.ts +2 -0
  263. package/dist/sensor-generator/action.d.ts.map +1 -0
  264. package/dist/sensor-generator/action.js +23 -0
  265. package/dist/sensor-generator/action.js.map +1 -0
  266. package/dist/sensor-generator/client.d.ts +2 -0
  267. package/dist/sensor-generator/client.d.ts.map +1 -0
  268. package/dist/sensor-generator/client.js +2 -0
  269. package/dist/sensor-generator/client.js.map +1 -0
  270. package/dist/sensor-generator/server.d.ts +2 -0
  271. package/dist/sensor-generator/server.d.ts.map +1 -0
  272. package/dist/sensor-generator/server.js +2 -0
  273. package/dist/sensor-generator/server.js.map +1 -0
  274. package/dist/sensor-generator/view.d.ts +2 -0
  275. package/dist/sensor-generator/view.d.ts.map +1 -0
  276. package/dist/sensor-generator/view.js +64 -0
  277. package/dist/sensor-generator/view.js.map +1 -0
  278. package/dist/sim/client.d.ts +2 -0
  279. package/dist/sim/client.d.ts.map +1 -0
  280. package/dist/sim/client.js +2 -0
  281. package/dist/sim/client.js.map +1 -0
  282. package/dist/sim/seed.d.ts +2 -0
  283. package/dist/sim/seed.d.ts.map +1 -0
  284. package/dist/sim/seed.js +50 -0
  285. package/dist/sim/seed.js.map +1 -0
  286. package/dist/sim/server.d.ts +3 -0
  287. package/dist/sim/server.d.ts.map +1 -0
  288. package/dist/sim/server.js +3 -0
  289. package/dist/sim/server.js.map +1 -0
  290. package/dist/sim/service.d.ts +4 -0
  291. package/dist/sim/service.d.ts.map +1 -0
  292. package/dist/sim/service.js +528 -0
  293. package/dist/sim/service.js.map +1 -0
  294. package/dist/sim/types.d.ts +63 -0
  295. package/dist/sim/types.d.ts.map +1 -0
  296. package/dist/sim/types.js +57 -0
  297. package/dist/sim/types.js.map +1 -0
  298. package/dist/sim/view.d.ts +2 -0
  299. package/dist/sim/view.d.ts.map +1 -0
  300. package/dist/sim/view.js +205 -0
  301. package/dist/sim/view.js.map +1 -0
  302. package/dist/table/client.d.ts +4 -0
  303. package/dist/table/client.d.ts.map +1 -0
  304. package/dist/table/client.js +4 -0
  305. package/dist/table/client.js.map +1 -0
  306. package/dist/table/edit.d.ts +2 -0
  307. package/dist/table/edit.d.ts.map +1 -0
  308. package/dist/table/edit.js +115 -0
  309. package/dist/table/edit.js.map +1 -0
  310. package/dist/table/server.d.ts +2 -0
  311. package/dist/table/server.d.ts.map +1 -0
  312. package/dist/table/server.js +2 -0
  313. package/dist/table/server.js.map +1 -0
  314. package/dist/table/types.d.ts +18 -0
  315. package/dist/table/types.d.ts.map +1 -0
  316. package/dist/table/types.js +13 -0
  317. package/dist/table/types.js.map +1 -0
  318. package/dist/table/use-debounced-sync.d.ts +8 -0
  319. package/dist/table/use-debounced-sync.d.ts.map +1 -0
  320. package/dist/table/use-debounced-sync.js +56 -0
  321. package/dist/table/use-debounced-sync.js.map +1 -0
  322. package/dist/table/view.d.ts +2 -0
  323. package/dist/table/view.d.ts.map +1 -0
  324. package/dist/table/view.js +199 -0
  325. package/dist/table/view.js.map +1 -0
  326. package/dist/tasks/server.d.ts +2 -0
  327. package/dist/tasks/server.d.ts.map +1 -0
  328. package/dist/tasks/server.js +2 -0
  329. package/dist/tasks/server.js.map +1 -0
  330. package/dist/tasks/types.d.ts +22 -0
  331. package/dist/tasks/types.d.ts.map +1 -0
  332. package/dist/tasks/types.js +34 -0
  333. package/dist/tasks/types.js.map +1 -0
  334. package/dist/three/client.d.ts +2 -0
  335. package/dist/three/client.d.ts.map +1 -0
  336. package/dist/three/client.js +2 -0
  337. package/dist/three/client.js.map +1 -0
  338. package/dist/three/seed.d.ts +2 -0
  339. package/dist/three/seed.d.ts.map +1 -0
  340. package/dist/three/seed.js +45 -0
  341. package/dist/three/seed.js.map +1 -0
  342. package/dist/three/server.d.ts +3 -0
  343. package/dist/three/server.d.ts.map +1 -0
  344. package/dist/three/server.js +3 -0
  345. package/dist/three/server.js.map +1 -0
  346. package/dist/three/types.d.ts +178 -0
  347. package/dist/three/types.d.ts.map +1 -0
  348. package/dist/three/types.js +209 -0
  349. package/dist/three/types.js.map +1 -0
  350. package/dist/three/view.d.ts +2 -0
  351. package/dist/three/view.d.ts.map +1 -0
  352. package/dist/three/view.js +307 -0
  353. package/dist/three/view.js.map +1 -0
  354. package/dist/todo/client.d.ts +3 -0
  355. package/dist/todo/client.d.ts.map +1 -0
  356. package/dist/todo/client.js +3 -0
  357. package/dist/todo/client.js.map +1 -0
  358. package/dist/todo/seed.d.ts +2 -0
  359. package/dist/todo/seed.d.ts.map +1 -0
  360. package/dist/todo/seed.js +8 -0
  361. package/dist/todo/seed.js.map +1 -0
  362. package/dist/todo/server.d.ts +3 -0
  363. package/dist/todo/server.d.ts.map +1 -0
  364. package/dist/todo/server.js +3 -0
  365. package/dist/todo/server.js.map +1 -0
  366. package/dist/todo/types.d.ts +15 -0
  367. package/dist/todo/types.d.ts.map +1 -0
  368. package/dist/todo/types.js +29 -0
  369. package/dist/todo/types.js.map +1 -0
  370. package/dist/todo/view.d.ts +2 -0
  371. package/dist/todo/view.d.ts.map +1 -0
  372. package/dist/todo/view.js +27 -0
  373. package/dist/todo/view.js.map +1 -0
  374. package/dist/whisper/client.d.ts +2 -0
  375. package/dist/whisper/client.d.ts.map +1 -0
  376. package/dist/whisper/client.js +2 -0
  377. package/dist/whisper/client.js.map +1 -0
  378. package/dist/whisper/inbox.d.ts +2 -0
  379. package/dist/whisper/inbox.d.ts.map +1 -0
  380. package/dist/whisper/inbox.js +50 -0
  381. package/dist/whisper/inbox.js.map +1 -0
  382. package/dist/whisper/route.d.ts +10 -0
  383. package/dist/whisper/route.d.ts.map +1 -0
  384. package/dist/whisper/route.js +154 -0
  385. package/dist/whisper/route.js.map +1 -0
  386. package/dist/whisper/seed.d.ts +2 -0
  387. package/dist/whisper/seed.d.ts.map +1 -0
  388. package/dist/whisper/seed.js +14 -0
  389. package/dist/whisper/seed.js.map +1 -0
  390. package/dist/whisper/server.d.ts +5 -0
  391. package/dist/whisper/server.d.ts.map +1 -0
  392. package/dist/whisper/server.js +5 -0
  393. package/dist/whisper/server.js.map +1 -0
  394. package/dist/whisper/service.d.ts +2 -0
  395. package/dist/whisper/service.d.ts.map +1 -0
  396. package/dist/whisper/service.js +26 -0
  397. package/dist/whisper/service.js.map +1 -0
  398. package/dist/whisper/types.d.ts +38 -0
  399. package/dist/whisper/types.d.ts.map +1 -0
  400. package/dist/whisper/types.js +45 -0
  401. package/dist/whisper/types.js.map +1 -0
  402. package/dist/whisper/view.d.ts +2 -0
  403. package/dist/whisper/view.d.ts.map +1 -0
  404. package/dist/whisper/view.js +40 -0
  405. package/dist/whisper/view.js.map +1 -0
  406. package/doc/CLAUDE.md +49 -0
  407. package/doc/client.ts +5 -0
  408. package/doc/editor.css +283 -0
  409. package/doc/fs-codec.test.ts +119 -0
  410. package/doc/fs-codec.ts +50 -0
  411. package/doc/markdown.test.ts +152 -0
  412. package/doc/markdown.ts +284 -0
  413. package/doc/prefab.ts +26 -0
  414. package/doc/renderers.tsx +126 -0
  415. package/doc/seed.ts +10 -0
  416. package/doc/server.ts +5 -0
  417. package/doc/slash-command.ts +136 -0
  418. package/doc/slash-menu.tsx +91 -0
  419. package/doc/text.ts +20 -0
  420. package/doc/toolbar.tsx +86 -0
  421. package/doc/treenity-block-view.tsx +116 -0
  422. package/doc/treenity-block.ts +31 -0
  423. package/doc/types.ts +10 -0
  424. package/launcher/client.ts +4 -0
  425. package/launcher/icons.tsx +234 -0
  426. package/launcher/launcher.css +64 -0
  427. package/launcher/seed.ts +41 -0
  428. package/launcher/server.ts +2 -0
  429. package/launcher/types.ts +53 -0
  430. package/launcher/view.tsx +401 -0
  431. package/launcher/widgets.tsx +171 -0
  432. package/mindmap/branch.tsx +163 -0
  433. package/mindmap/client.ts +2 -0
  434. package/mindmap/mindmap.css +243 -0
  435. package/mindmap/sidebar.tsx +127 -0
  436. package/mindmap/types.ts +10 -0
  437. package/mindmap/view.tsx +247 -0
  438. package/package.json +75 -0
  439. package/sensor-demo/CLAUDE.md +3 -0
  440. package/sensor-demo/client.ts +1 -0
  441. package/sensor-demo/server.ts +1 -0
  442. package/sensor-demo/service.ts +27 -0
  443. package/sensor-demo/types.ts +20 -0
  444. package/sensor-demo/view.tsx +64 -0
  445. package/sensor-generator/CLAUDE.md +3 -0
  446. package/sensor-generator/action.ts +28 -0
  447. package/sensor-generator/client.ts +1 -0
  448. package/sensor-generator/server.ts +1 -0
  449. package/sensor-generator/view.tsx +107 -0
  450. package/sim/CLAUDE.md +16 -0
  451. package/sim/client.ts +1 -0
  452. package/sim/seed.ts +55 -0
  453. package/sim/server.ts +2 -0
  454. package/sim/service.ts +594 -0
  455. package/sim/sim.test.ts +282 -0
  456. package/sim/types.ts +87 -0
  457. package/sim/view.tsx +446 -0
  458. package/table/client.ts +3 -0
  459. package/table/edit.tsx +241 -0
  460. package/table/server.ts +1 -0
  461. package/table/types.ts +22 -0
  462. package/table/use-debounced-sync.ts +67 -0
  463. package/table/view.tsx +339 -0
  464. package/tasks/CLAUDE.md +6 -0
  465. package/tasks/server.ts +1 -0
  466. package/tasks/types.ts +36 -0
  467. package/three/CLAUDE.md +6 -0
  468. package/three/client.ts +1 -0
  469. package/three/seed.ts +54 -0
  470. package/three/server.ts +2 -0
  471. package/three/types.ts +238 -0
  472. package/three/view.tsx +453 -0
  473. package/todo/client.ts +2 -0
  474. package/todo/seed.ts +9 -0
  475. package/todo/server.ts +2 -0
  476. package/todo/types.ts +32 -0
  477. package/todo/view.tsx +67 -0
  478. package/whisper/CLAUDE.md +16 -0
  479. package/whisper/client.ts +1 -0
  480. package/whisper/inbox.ts +54 -0
  481. package/whisper/route.ts +182 -0
  482. package/whisper/seed.ts +15 -0
  483. package/whisper/server.ts +4 -0
  484. package/whisper/service.ts +29 -0
  485. package/whisper/types.ts +51 -0
  486. package/whisper/view.tsx +81 -0
@@ -0,0 +1,727 @@
1
+ // Brahman — Telegram bot constructor
2
+ // Component types for bot config, pages, actions, users, sessions
3
+
4
+ import { getComponent } from '@treenity/core';
5
+ import { getCtx, registerType } from '@treenity/core/comp';
6
+ import type { BrahmanCtx } from './helpers';
7
+
8
+ // ── Shared types ──
9
+
10
+ export type TString = Record<string, string>;
11
+
12
+ /** Pending wait state — persisted in session.data.wait */
13
+ export type WaitState = {
14
+ type: 'text' | 'photo';
15
+ saveTo: string;
16
+ deleteMessages: boolean;
17
+ sentMsgId: number;
18
+ remaining: string[];
19
+ };
20
+
21
+ export type MenuType =
22
+ | 'none'
23
+ | 'inline'
24
+ | 'inline_new'
25
+ | 'inline_close'
26
+ | 'keyboard'
27
+ | 'remove'
28
+ | 'force_reply';
29
+
30
+ export const MENU_TYPES: { value: MenuType; label: string }[] = [
31
+ { value: 'none', label: 'No menu' },
32
+ { value: 'inline', label: 'Inline' },
33
+ { value: 'inline_new', label: 'Inline (new message)' },
34
+ { value: 'inline_close', label: 'Inline (edit existing)' },
35
+ { value: 'keyboard', label: 'Reply keyboard' },
36
+ { value: 'remove', label: 'Remove keyboard' },
37
+ { value: 'force_reply', label: 'Force reply' },
38
+ ];
39
+
40
+ export type ButtonAction = {
41
+ type: string;
42
+ target?: string;
43
+ [key: string]: unknown;
44
+ };
45
+
46
+ export type MenuButton = {
47
+ id: number;
48
+ title: TString;
49
+ url?: string;
50
+ tags?: string[];
51
+ action?: ButtonAction;
52
+ };
53
+
54
+ export type MenuRow = {
55
+ buttons: MenuButton[];
56
+ };
57
+
58
+ // Action type metadata for the palette
59
+ export const ACTION_TYPES = [
60
+ { type: 'brahman.action.message', label: 'Message', icon: 'MessageSquare' },
61
+ { type: 'brahman.action.question', label: 'Question', icon: 'HelpCircle' },
62
+ { type: 'brahman.action.ifelse', label: 'If / Else', icon: 'GitBranch' },
63
+ { type: 'brahman.action.page', label: 'Go to page', icon: 'FileText' },
64
+ { type: 'brahman.action.back', label: 'Back', icon: 'ArrowLeft' },
65
+ { type: 'brahman.action.tag', label: 'Set tag', icon: 'Tag' },
66
+ { type: 'brahman.action.broadcast', label: 'Broadcast', icon: 'Send' },
67
+ { type: 'brahman.action.getvalue', label: 'Get value', icon: 'Download' },
68
+ { type: 'brahman.action.setvalue', label: 'Set value', icon: 'Upload' },
69
+ { type: 'brahman.action.params', label: 'Parse params', icon: 'Settings2' },
70
+ { type: 'brahman.action.file', label: 'Send file', icon: 'File' },
71
+ { type: 'brahman.action.eval', label: 'Eval JS', icon: 'Code' },
72
+ { type: 'brahman.action.remove', label: 'Remove msg', icon: 'Trash2' },
73
+ { type: 'brahman.action.emittext', label: 'Emit text', icon: 'Repeat' },
74
+ { type: 'brahman.action.forward', label: 'Forward', icon: 'Forward' },
75
+ { type: 'brahman.action.resetsession', label: 'Reset session', icon: 'RotateCcw' },
76
+ { type: 'brahman.action.resethistory', label: 'Reset history', icon: 'History' },
77
+ { type: 'brahman.action.onerror', label: 'On error', icon: 'AlertTriangle' },
78
+ { type: 'brahman.action.keywordselect', label: 'Keyword select', icon: 'Search' },
79
+ { type: 'brahman.action.selectlang', label: 'Select language', icon: 'Globe' },
80
+ { type: 'brahman.action.call', label: 'Call action', icon: 'Zap' },
81
+ ] as const;
82
+
83
+ // ── Bot ──
84
+
85
+ /** Telegram bot instance — token, proxy, language, maintenance */
86
+ export class BotConfig {
87
+ /** @title Token @description Telegram bot token */
88
+ token = '';
89
+ /** @title Proxy @description SOCKS proxy URL */
90
+ proxy = '';
91
+ /** @title Alias @description Bot username */
92
+ alias = '';
93
+ /** @title Name @description Display name */
94
+ name = '';
95
+ /** @title Langs @description Comma-separated: ru,en,de,uz */
96
+ langs = 'ru,en';
97
+ /** @title Maintenance @description Non-empty = paused */
98
+ maintenance = '';
99
+ /** @title Running */
100
+ running = true;
101
+ }
102
+ registerType('brahman.bot', BotConfig);
103
+
104
+ // ── Page ──
105
+
106
+ /** Bot conversation page — command trigger and ordered action sequence */
107
+ export class PageConfig {
108
+ /** @title Command @description Telegram command, e.g. /start */
109
+ command = '';
110
+ /** @description Ordered child action paths */
111
+ positions: string[] = [];
112
+ }
113
+ registerType('brahman.page', PageConfig);
114
+
115
+ // ── Actions ──
116
+
117
+ /** Send message — text with optional menu and link previews */
118
+ export class MessageAction {
119
+ /** @title Text @format tstring */
120
+ text: TString = {};
121
+ /** @title Disable link previews */
122
+ disableLinks = false;
123
+ /** @title Menu type */
124
+ menuType: MenuType = 'none';
125
+ /** @title Menu rows */
126
+ rows: MenuRow[] = [];
127
+ /** @title Send to chat ID @description Leave empty for current chat */
128
+ chatId = '';
129
+ /** @title Reply to message ID @description Session field with message ID */
130
+ replyToMsgId = '';
131
+
132
+ async run(bCtx: BrahmanCtx) {
133
+ const { node } = getCtx();
134
+ const { formatText, buildReplyMarkup, format } = await import('./helpers');
135
+ const { ctx, lang } = bCtx;
136
+ const session = bCtx.session;
137
+
138
+ const text = formatText(this.text, bCtx);
139
+ const opts: Record<string, unknown> = { parse_mode: 'HTML' };
140
+ if (this.disableLinks) opts.link_preview_options = { is_disabled: true };
141
+
142
+ const markup = buildReplyMarkup(this.rows ?? [], this.menuType ?? 'none', lang, bCtx.userTags);
143
+ if (markup) Object.assign(opts, markup);
144
+
145
+ if (this.replyToMsgId && session[this.replyToMsgId]) {
146
+ opts.reply_to_message_id = Number(session[this.replyToMsgId]);
147
+ }
148
+
149
+ if (this.chatId) {
150
+ const targetChat = format(this.chatId, bCtx);
151
+ await ctx.api.sendMessage(targetChat, text, opts as any);
152
+ } else if (this.menuType === 'inline_close' && ctx.callbackQuery) {
153
+ await ctx.editMessageText(text, opts as any);
154
+ } else {
155
+ const sent = await ctx.reply(text, opts as any);
156
+ session._lastMsgId = sent.message_id;
157
+ }
158
+
159
+ if (this.menuType === 'keyboard' || this.menuType === 'inline' || this.menuType === 'inline_new') {
160
+ session._lastMenu = node.$path;
161
+ }
162
+ }
163
+ }
164
+ registerType('brahman.action.message', MessageAction);
165
+
166
+ /** Ask question — prompt for text/photo input, save answer */
167
+ export class QuestionAction {
168
+ /** @title Prompt text @format tstring */
169
+ text: TString = {};
170
+ /** @title Input type */
171
+ inputType: 'text' | 'photo' = 'text';
172
+ /** @title Save answer to */
173
+ saveTo = '';
174
+ /** @title Delete messages @description Delete question and answer after receiving */
175
+ deleteMessages = false;
176
+
177
+ async run(bCtx: BrahmanCtx) {
178
+ const { formatText } = await import('./helpers');
179
+ const { ctx } = bCtx;
180
+ const session = bCtx.session;
181
+
182
+ const text = formatText(this.text, bCtx);
183
+ const sent = await ctx.reply(text, { reply_markup: { force_reply: true }, parse_mode: 'HTML' });
184
+
185
+ if (this.saveTo) {
186
+ // Tree wait state in session — persisted, survives restarts
187
+ // remaining[] filled by executeActions() after this action returns
188
+ session.wait = {
189
+ type: this.inputType === 'photo' ? 'photo' : 'text',
190
+ saveTo: this.saveTo,
191
+ deleteMessages: this.deleteMessages,
192
+ sentMsgId: sent.message_id,
193
+ remaining: [],
194
+ } satisfies WaitState;
195
+ }
196
+ }
197
+ }
198
+ registerType('brahman.action.question', QuestionAction);
199
+
200
+ /** Conditional branch — evaluate JS expression, run if/else action */
201
+ export class IfElseAction {
202
+ /** @title Condition @format textarea @description JS expression evaluated at runtime */
203
+ condition = '';
204
+ /** @title Action if true @description Path to action node */
205
+ actionIf = '';
206
+ /** @title Action if false */
207
+ actionElse = '';
208
+ /** @title Stop after action */
209
+ stopAfterAction = false;
210
+
211
+ async run(bCtx: BrahmanCtx) {
212
+ const { format, executeAction, StopProcess } = await import('./helpers');
213
+ const { store } = bCtx;
214
+ const session = bCtx.session;
215
+
216
+ let result = false;
217
+ try {
218
+ const condition = format(this.condition, bCtx);
219
+ const evalFn = new Function('session', 'data', 'user', `return !!(${condition})`);
220
+ const userData = getComponent(bCtx.user, BrahmanUser);
221
+ result = evalFn(session, session, userData);
222
+ } catch { /* eval failed → false */ }
223
+
224
+ const target = result ? this.actionIf : this.actionElse;
225
+ if (target) {
226
+ const targetNode = await store.get(target);
227
+ if (targetNode) await executeAction(targetNode, bCtx);
228
+ }
229
+
230
+ if (this.stopAfterAction) throw new StopProcess();
231
+ }
232
+ }
233
+ registerType('brahman.action.ifelse', IfElseAction);
234
+
235
+ /** Navigate to page — redirect conversation flow */
236
+ export class PageNavAction {
237
+ /** @title Target page @description Path to page node */
238
+ targetPage = '';
239
+
240
+ async run(bCtx: BrahmanCtx) {
241
+ if (this.targetPage) {
242
+ const { format, executePage } = await import('./helpers');
243
+ const resolved = format(this.targetPage, bCtx);
244
+ await executePage(resolved, bCtx);
245
+ }
246
+ }
247
+ }
248
+ registerType('brahman.action.page', PageNavAction);
249
+
250
+ /** Go back — return to previous page in history */
251
+ export class BackAction {
252
+ async run(bCtx: BrahmanCtx) {
253
+ const { executePage } = await import('./helpers');
254
+ const history = (bCtx.session.history ?? []) as string[];
255
+ history.pop(); // remove current
256
+ const prev = history.pop();
257
+ if (prev) await executePage(prev, bCtx);
258
+ }
259
+ }
260
+ registerType('brahman.action.back', BackAction);
261
+
262
+ /** Set user tag — assign key-value metadata to user */
263
+ export class TagAction {
264
+ /** @title Tag name */
265
+ tag = '';
266
+ /** @title Value expression */
267
+ value = 'true';
268
+
269
+ async run(bCtx: BrahmanCtx) {
270
+ const { format } = await import('./helpers');
271
+ const { store } = bCtx;
272
+ const session = bCtx.session;
273
+
274
+ if (!this.tag) return;
275
+ let shouldSet = true;
276
+ if (this.value && this.value !== 'true') {
277
+ try {
278
+ const formatted = format(this.value, bCtx);
279
+ shouldSet = !!new Function('session', 'data', `return !!(${formatted})`)(session, session);
280
+ } catch { /* eval failed */ }
281
+ }
282
+
283
+ const tags = bCtx.userTags;
284
+ if (shouldSet && !tags.includes(this.tag)) {
285
+ tags.push(this.tag);
286
+ } else if (!shouldSet) {
287
+ const idx = tags.indexOf(this.tag);
288
+ if (idx >= 0) tags.splice(idx, 1);
289
+ }
290
+
291
+ const userComp = getComponent(bCtx.user, BrahmanUser);
292
+ if (userComp) {
293
+ (userComp as any).tags = tags;
294
+ await store.set(bCtx.user);
295
+ }
296
+ }
297
+ }
298
+ registerType('brahman.action.tag', TagAction);
299
+
300
+ /** Broadcast message — send action to users matching tag filter */
301
+ export class BroadcastAction {
302
+ /** @title User tags filter @format tags */
303
+ userTags: string[] = [];
304
+ /** @title Action to broadcast @description Path to action node */
305
+ action = '';
306
+
307
+ async run(bCtx: BrahmanCtx) {
308
+ const { checkTags, executeAction } = await import('./helpers');
309
+ const { store, lang } = bCtx;
310
+
311
+ if (!this.action) return;
312
+ const actionNode = await store.get(this.action);
313
+ if (!actionNode) return;
314
+
315
+ const { items: users } = await store.getChildren(`${bCtx.botPath}/users`);
316
+ const filterTags = this.userTags ?? [];
317
+
318
+ for (const userNode of users) {
319
+ const userData = getComponent(userNode, BrahmanUser);
320
+ if (!userData || userData.banned || userData.blocked) continue;
321
+ if (filterTags.length > 0 && !checkTags(userData.tags ?? [], filterTags)) continue;
322
+
323
+ try {
324
+ const userCtx = { ...bCtx, user: userNode, lang: userData.lang || lang, userTags: userData.tags ?? [] };
325
+ await executeAction(actionNode, userCtx);
326
+ } catch (err) {
327
+ const msg = err instanceof Error ? err.message : String(err);
328
+ if (msg.includes('403') || msg.includes('Forbidden')) {
329
+ (userData as any).blocked = true;
330
+ await store.set(userNode);
331
+ }
332
+ }
333
+ }
334
+ }
335
+ }
336
+ registerType('brahman.action.broadcast', BroadcastAction);
337
+
338
+ /** Read session value — extract data from user session */
339
+ export class GetValueAction {
340
+ /** @title Source path @description Dot-path in session data */
341
+ path = '';
342
+ /** @title Save to */
343
+ saveTo = '';
344
+
345
+ async run(bCtx: BrahmanCtx) {
346
+ const { format, buildTemplateData, resolveVar } = await import('./helpers');
347
+ const session = bCtx.session;
348
+
349
+ if (this.path && this.saveTo) {
350
+ const path = format(String(this.path), bCtx);
351
+ const templateData = buildTemplateData(bCtx);
352
+ const val = resolveVar(path, { ...templateData, session });
353
+ session[this.saveTo] = val;
354
+ }
355
+ }
356
+ }
357
+ registerType('brahman.action.getvalue', GetValueAction);
358
+
359
+ /** Write session value — store data in user session */
360
+ export class SetValueAction {
361
+ /** @title Value expression */
362
+ value = '';
363
+ /** @title Save to */
364
+ saveTo = '';
365
+
366
+ async run(bCtx: BrahmanCtx) {
367
+ const { format } = await import('./helpers');
368
+ const session = bCtx.session;
369
+
370
+ if (this.saveTo) {
371
+ try {
372
+ const formatted = format(this.value, bCtx);
373
+ const evalFn = new Function('session', 'data', 'user', `return (${formatted})`);
374
+ const userData = getComponent(bCtx.user, BrahmanUser);
375
+ session[this.saveTo] = evalFn(session, session, userData);
376
+ } catch {
377
+ session[this.saveTo] = this.value;
378
+ }
379
+ }
380
+ }
381
+ }
382
+ registerType('brahman.action.setvalue', SetValueAction);
383
+
384
+ /** Parse parameters — split and name input parts */
385
+ export class ParamsAction {
386
+ /** @title Base64 decode */
387
+ base64 = false;
388
+ /** @title Split delimiter */
389
+ split = ',';
390
+ /** @title Parameter names @format tags */
391
+ names: string[] = [];
392
+
393
+ async run(bCtx: BrahmanCtx) {
394
+ const { ctx } = bCtx;
395
+ const session = bCtx.session;
396
+
397
+ const text = ctx.message?.text ?? '';
398
+ const match = text.match(/^\/\S+\s+(.*)/);
399
+ const rawParam = match ? match[1] : (session.param as string ?? '');
400
+
401
+ if (rawParam) {
402
+ let raw = rawParam;
403
+ if (this.base64) {
404
+ try { raw = Buffer.from(raw, 'base64').toString('utf8'); } catch { /* ignore */ }
405
+ }
406
+ const parts = raw.split(this.split || ',');
407
+ const names = this.names ?? [];
408
+ for (let i = 0; i < names.length; i++) {
409
+ session[names[i]] = parts[i]?.trim() ?? '';
410
+ }
411
+ session.params = parts.map((s: string) => s.trim());
412
+ }
413
+ }
414
+ }
415
+ registerType('brahman.action.params', ParamsAction);
416
+
417
+ /** Send file — deliver photo/document/video/audio to chat */
418
+ export class FileAction {
419
+ /** @title File node path */
420
+ fileId = '';
421
+ /** @title Override type @description photo, document, video, audio, voice */
422
+ asType = '';
423
+
424
+ async run(bCtx: BrahmanCtx) {
425
+ const { format } = await import('./helpers');
426
+ const { ctx, store } = bCtx;
427
+
428
+ if (!this.fileId) return;
429
+ const resolvedId = format(this.fileId, bCtx);
430
+ const fileNode = await store.get(resolvedId);
431
+ const fileId = (fileNode as any)?.fileId ?? resolvedId;
432
+ const asType = this.asType || 'document';
433
+
434
+ switch (asType) {
435
+ case 'photo': await ctx.replyWithPhoto(fileId); break;
436
+ case 'video': await ctx.replyWithVideo(fileId); break;
437
+ case 'audio': await ctx.replyWithAudio(fileId); break;
438
+ case 'voice': await ctx.replyWithVoice(fileId); break;
439
+ default: await ctx.replyWithDocument(fileId); break;
440
+ }
441
+ }
442
+ }
443
+ registerType('brahman.action.file', FileAction);
444
+
445
+ // ── Eval ──
446
+
447
+ /** Execute JavaScript — run async code with context access */
448
+ export class EvalAction {
449
+ /** @title JavaScript code @format textarea @description Async function body, receives ctx object */
450
+ value = '';
451
+
452
+ async run(bCtx: BrahmanCtx) {
453
+ const { ctx, store } = bCtx;
454
+ const session = bCtx.session;
455
+
456
+ if (!this.value) return;
457
+ try {
458
+ const fn = new Function('ctx', 'session', 'data', 'user', 'store',
459
+ `return (async function() { ${this.value} }).call(null)`);
460
+ const userData = getComponent(bCtx.user, BrahmanUser);
461
+ await fn(ctx, session, session, userData, store);
462
+ } catch (err) {
463
+ console.error(`[brahman:eval]`, err);
464
+ throw err;
465
+ }
466
+ }
467
+ }
468
+ registerType('brahman.action.eval', EvalAction);
469
+
470
+ // ── Remove message ──
471
+
472
+ /** Remove message — delete bot message from chat */
473
+ export class RemoveAction {
474
+ async run(bCtx: BrahmanCtx) {
475
+ try {
476
+ await bCtx.ctx.deleteMessage();
477
+ } catch { /* msg may already be deleted or too old */ }
478
+ }
479
+ }
480
+ registerType('brahman.action.remove', RemoveAction);
481
+
482
+ // ── Emit text (synthetic text injection) ──
483
+
484
+ /** Emit synthetic text — inject text for re-processing */
485
+ export class EmitTextAction {
486
+ /** @title Text template @format textarea @description Template for text to re-process */
487
+ from = '';
488
+
489
+ async run(bCtx: BrahmanCtx) {
490
+ const { format, executePage } = await import('./helpers');
491
+ const { store } = bCtx;
492
+ const session = bCtx.session;
493
+
494
+ if (!this.from) return;
495
+ const text = format(this.from, bCtx);
496
+ if (!text) return;
497
+
498
+ if (text.startsWith('/')) {
499
+ const cmd = text.slice(1).split(/\s/)[0];
500
+ const { items: pages } = await store.getChildren(`${bCtx.botPath}/pages`);
501
+ const page = pages.find(p => {
502
+ const pc = getComponent(p, PageConfig);
503
+ return pc?.command === `/${cmd}` || pc?.command === cmd;
504
+ });
505
+ if (page) {
506
+ const paramMatch = text.match(/^\/\S+\s+(.*)/);
507
+ if (paramMatch) session.param = paramMatch[1];
508
+ await executePage(page.$path, bCtx);
509
+ }
510
+ } else {
511
+ session._emittedText = text;
512
+ }
513
+ }
514
+ }
515
+ registerType('brahman.action.emittext', EmitTextAction);
516
+
517
+ // ── Forward message ──
518
+
519
+ /** Forward message — relay message to another chat */
520
+ export class ForwardAction {
521
+ /** @title Message ID from @description Session field with msg id. Empty = current msg */
522
+ msgIdFrom = '';
523
+ /** @title Forward to @description Chat/user ID template */
524
+ toFrom = '';
525
+
526
+ async run(bCtx: BrahmanCtx) {
527
+ const { format } = await import('./helpers');
528
+ const { ctx } = bCtx;
529
+ const session = bCtx.session;
530
+
531
+ const toStr = format(this.toFrom || '', bCtx);
532
+ const to = toStr ? Number(toStr) : ctx.chat?.id;
533
+ if (!to) return;
534
+
535
+ if (this.msgIdFrom) {
536
+ const msgId = Number(session[this.msgIdFrom] ?? this.msgIdFrom);
537
+ if (msgId && ctx.chat?.id) {
538
+ await ctx.api.forwardMessage(to, ctx.chat.id, msgId);
539
+ }
540
+ } else if (ctx.message?.message_id && ctx.chat?.id) {
541
+ await ctx.api.forwardMessage(to, ctx.chat.id, ctx.message.message_id);
542
+ }
543
+ }
544
+ }
545
+ registerType('brahman.action.forward', ForwardAction);
546
+
547
+ // ── Reset session ──
548
+
549
+ /** Reset session — clear all user session data */
550
+ export class ResetSessionAction {
551
+ async run(bCtx: BrahmanCtx) {
552
+ const session = bCtx.session;
553
+ for (const key of Object.keys(session)) {
554
+ if (key === 'history') continue;
555
+ delete session[key];
556
+ }
557
+ }
558
+ }
559
+ registerType('brahman.action.resetsession', ResetSessionAction);
560
+
561
+ // ── Reset history ──
562
+
563
+ /** Reset history — clear page navigation stack */
564
+ export class ResetHistoryAction {
565
+ async run(bCtx: BrahmanCtx) {
566
+ bCtx.session.history = [];
567
+ }
568
+ }
569
+ registerType('brahman.action.resethistory', ResetHistoryAction);
570
+
571
+ // ── On error (conditional error handler) ──
572
+
573
+ /** Error handler — catch specific errors and run fallback action */
574
+ export class OnErrorAction {
575
+ /** @title Error text @description Substring to match in error message */
576
+ error = '';
577
+ /** @title Action path @description Path to action node to run */
578
+ action = '';
579
+
580
+ async run(bCtx: BrahmanCtx) {
581
+ const { executeAction } = await import('./helpers');
582
+ const { store } = bCtx;
583
+ const session = bCtx.session;
584
+
585
+ const errorInfo = session.error as { message?: string } | undefined;
586
+ if (!errorInfo?.message) return;
587
+
588
+ if (this.error && !errorInfo.message.includes(this.error)) return;
589
+
590
+ if (this.action) {
591
+ const actionNode = await store.get(this.action);
592
+ if (actionNode) await executeAction(actionNode, bCtx);
593
+ }
594
+ }
595
+ }
596
+ registerType('brahman.action.onerror', OnErrorAction);
597
+
598
+ // ── Keyword select ──
599
+
600
+ export type KeywordEntry = {
601
+ keywords: string[];
602
+ message: string;
603
+ };
604
+
605
+ /** Keyword matcher — select response based on text keywords */
606
+ export class KeywordSelectAction {
607
+ /** @title Source text @description Template for text to analyze */
608
+ textFrom = '';
609
+ /** @title Keyword entries @description Array of {keywords, message} pairs */
610
+ elements: KeywordEntry[] = [];
611
+
612
+ async run(bCtx: BrahmanCtx) {
613
+ const { format, executePage } = await import('./helpers');
614
+ const { ctx, store } = bCtx;
615
+ const session = bCtx.session;
616
+
617
+ const sourceText = this.textFrom
618
+ ? format(this.textFrom, bCtx)
619
+ : (session.text as string ?? ctx.message?.text ?? '');
620
+ const words = sourceText.toLowerCase().split(/\s+/);
621
+ const elements = this.elements ?? [];
622
+
623
+ for (const el of elements) {
624
+ const match = el.keywords.some(kw => words.includes(kw.toLowerCase()));
625
+ if (match && el.message) {
626
+ if (el.message.startsWith('/')) {
627
+ const cmd = el.message.slice(1);
628
+ const { items: pages } = await store.getChildren(`${bCtx.botPath}/pages`);
629
+ const page = pages.find(p => {
630
+ const pc = getComponent(p, PageConfig);
631
+ return pc?.command === `/${cmd}` || pc?.command === cmd;
632
+ });
633
+ if (page) await executePage(page.$path, bCtx);
634
+ } else {
635
+ await ctx.reply(el.message);
636
+ }
637
+ return;
638
+ }
639
+ }
640
+ }
641
+ }
642
+ registerType('brahman.action.keywordselect', KeywordSelectAction);
643
+
644
+ // ── Call tree action ──
645
+
646
+ /** Call tree action — execute any Treenity action from bot flow */
647
+ export class CallAction {
648
+ /** @title Node path @description Template with {session.vars} */
649
+ path = '';
650
+ /** @title Action name */
651
+ action = '';
652
+ /** @title Component type @description Optional $type for resolution */
653
+ type = '';
654
+ /** @title Component key @description Optional named component key */
655
+ key = '';
656
+ /** @title Input data @format textarea @description JSON template */
657
+ dataExpr = '{}';
658
+ /** @title Save result to @description Session key for result */
659
+ saveTo = '';
660
+
661
+ async run(bCtx: BrahmanCtx) {
662
+ const { format } = await import('./helpers');
663
+ const { executeAction } = await import('@treenity/core/server/actions');
664
+
665
+ const path = format(this.path, bCtx);
666
+ if (!path || !this.action) return;
667
+
668
+ let data: unknown;
669
+ if (this.dataExpr) {
670
+ const formatted = format(this.dataExpr, bCtx);
671
+ try { data = JSON.parse(formatted); } catch { data = formatted; }
672
+ }
673
+
674
+ const result = await executeAction(
675
+ bCtx.store, path,
676
+ this.type || undefined,
677
+ this.key || undefined,
678
+ this.action, data,
679
+ );
680
+
681
+ if (this.saveTo) bCtx.session[this.saveTo] = result;
682
+ }
683
+ }
684
+ registerType('brahman.action.call', CallAction);
685
+
686
+ // ── Select language ──
687
+
688
+ /** Language selector — let user choose interface language */
689
+ export class SelectLanguageAction {
690
+ /** @title Text @format tstring */
691
+ text: TString = {};
692
+
693
+ async run(bCtx: BrahmanCtx) {
694
+ const { formatText, buildLangKeyboard } = await import('./helpers');
695
+ const text = formatText(this.text, bCtx);
696
+ const kb = buildLangKeyboard(bCtx.botLangs);
697
+ await bCtx.ctx.reply(text || 'Select language:', { reply_markup: kb, parse_mode: 'HTML' });
698
+ }
699
+ }
700
+ registerType('brahman.action.selectlang', SelectLanguageAction);
701
+
702
+ // ── Runtime entities ──
703
+
704
+ /** Bot user profile — Telegram identity, language, tags, ban status */
705
+ export class BrahmanUser {
706
+ tid = 0;
707
+ firstName = '';
708
+ lastName = '';
709
+ username = '';
710
+ lang = 'ru';
711
+ isAdmin = false;
712
+ blocked = false;
713
+ banned = false;
714
+ /** @title User tags */
715
+ tags: string[] = [];
716
+ }
717
+ registerType('brahman.user', BrahmanUser);
718
+
719
+ /** User conversation state — session data, history, pending callbacks */
720
+ export class BrahmanSession {
721
+ tid = 0;
722
+ data: Record<string, unknown> = {};
723
+ history: string[] = [];
724
+ /** @title Pending callbacks @description messageType → action path */
725
+ callbacks: Record<string, string> = {};
726
+ }
727
+ registerType('brahman.session', BrahmanSession);