synthos 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (359) hide show
  1. package/README.md +1 -1
  2. package/default-pages/application/page.html +42 -0
  3. package/default-pages/application/page.json +10 -0
  4. package/default-pages/elevenlabs_effects_studio/page.html +1363 -0
  5. package/default-pages/elevenlabs_effects_studio/page.json +11 -0
  6. package/default-pages/elevenlabs_voice_studio/page.html +801 -0
  7. package/default-pages/elevenlabs_voice_studio/page.json +11 -0
  8. package/default-pages/{json_tools.html → json_tools/page.html} +13 -11
  9. package/default-pages/json_tools/page.json +10 -0
  10. package/default-pages/my_notes/notes/a1b2c3d4-e5f6-7890-abcd-ef1234567890.json +5 -0
  11. package/default-pages/my_notes/page.html +132 -0
  12. package/default-pages/{my_notes.json → my_notes/page.json} +2 -2
  13. package/default-pages/neon_asteroids/files/Ambient_Space.mp3 +0 -0
  14. package/default-pages/neon_asteroids/files/Ambient_Space2.mp3 +0 -0
  15. package/default-pages/neon_asteroids/files/Ambient_Space3.mp3 +0 -0
  16. package/default-pages/neon_asteroids/files/Asteroid_Explosion.mp3 +0 -0
  17. package/default-pages/neon_asteroids/files/Hyperspace_Jump.mp3 +0 -0
  18. package/default-pages/neon_asteroids/files/Laser_Fire.mp3 +0 -0
  19. package/default-pages/neon_asteroids/files/Menu_Navigate.mp3 +0 -0
  20. package/default-pages/neon_asteroids/files/Power_Up_Collect.mp3 +0 -0
  21. package/default-pages/neon_asteroids/files/Saucer_Alert.mp3 +0 -0
  22. package/default-pages/neon_asteroids/files/Ship_Thrust.mp3 +0 -0
  23. package/default-pages/neon_asteroids/files/effects.json +74 -0
  24. package/default-pages/neon_asteroids/page.html +1822 -0
  25. package/default-pages/{neon_asteroids.json → neon_asteroids/page.json} +3 -3
  26. package/default-pages/{oregon_trail.html → oregon_trail/page.html} +14 -12
  27. package/default-pages/{oregon_trail.json → oregon_trail/page.json} +2 -2
  28. package/default-pages/retro_game_starter/page.html +1308 -0
  29. package/default-pages/retro_game_starter/page.json +12 -0
  30. package/default-pages/{sidebar_page.html → sidebar_page/page.html} +12 -10
  31. package/default-pages/sidebar_page/page.json +10 -0
  32. package/default-pages/{solar_explorer.html → solar_explorer/page.html} +14 -11
  33. package/default-pages/{solar_explorer.json → solar_explorer/page.json} +2 -2
  34. package/default-pages/{solar_tutorial.html → solar_tutorial/page.html} +12 -10
  35. package/default-pages/solar_tutorial/page.json +10 -0
  36. package/default-pages/{two-panel_page.html → two-panel_page/page.html} +13 -11
  37. package/default-pages/two-panel_page/page.json +10 -0
  38. package/default-pages/{us_map.html → us_map/page.html} +193 -192
  39. package/default-pages/{us_map.json → us_map/page.json} +12 -12
  40. package/default-pages/{us_map_1850.html → us_map_1850/page.html} +326 -325
  41. package/default-pages/{us_map_1850.json → us_map_1850/page.json} +12 -12
  42. package/default-pages/{western_cities_1850.html → western_cities_1850/page.html} +527 -526
  43. package/default-pages/{western_cities_1850.json → western_cities_1850/page.json} +12 -12
  44. package/default-themes/aurora-dawn.json +19 -0
  45. package/default-themes/aurora-dawn.v3.css +198 -0
  46. package/default-themes/aurora-dusk.json +19 -0
  47. package/default-themes/aurora-dusk.v3.css +200 -0
  48. package/default-themes/cosmos-dawn.json +19 -0
  49. package/default-themes/cosmos-dawn.v3.css +198 -0
  50. package/default-themes/cosmos-dusk.json +19 -0
  51. package/default-themes/cosmos-dusk.v3.css +200 -0
  52. package/default-themes/high-contrast-dark.json +19 -0
  53. package/default-themes/high-contrast-dark.v3.css +200 -0
  54. package/default-themes/high-contrast-light.json +19 -0
  55. package/default-themes/high-contrast-light.v3.css +198 -0
  56. package/default-themes/nebula-dawn.v2.css +110 -0
  57. package/default-themes/nebula-dawn.v3.css +199 -0
  58. package/default-themes/nebula-dusk.v2.css +104 -0
  59. package/default-themes/nebula-dusk.v3.css +201 -0
  60. package/default-themes/solar-flare-dawn.json +19 -0
  61. package/default-themes/solar-flare-dawn.v3.css +198 -0
  62. package/default-themes/solar-flare-dusk.json +19 -0
  63. package/default-themes/solar-flare-dusk.v3.css +200 -0
  64. package/dist/agents/index.d.ts +1 -1
  65. package/dist/agents/index.d.ts.map +1 -1
  66. package/dist/agents/index.js +2 -1
  67. package/dist/agents/index.js.map +1 -1
  68. package/dist/agents/openclaw/gatewayManager.d.ts +4 -0
  69. package/dist/agents/openclaw/gatewayManager.d.ts.map +1 -1
  70. package/dist/agents/openclaw/gatewayManager.js +27 -11
  71. package/dist/agents/openclaw/gatewayManager.js.map +1 -1
  72. package/dist/agents/openclaw/openclawProvider.d.ts.map +1 -1
  73. package/dist/agents/openclaw/openclawProvider.js +2 -4
  74. package/dist/agents/openclaw/openclawProvider.js.map +1 -1
  75. package/dist/agents/openclaw/sshTunnelManager.d.ts +2 -0
  76. package/dist/agents/openclaw/sshTunnelManager.d.ts.map +1 -1
  77. package/dist/agents/openclaw/sshTunnelManager.js +31 -12
  78. package/dist/agents/openclaw/sshTunnelManager.js.map +1 -1
  79. package/dist/builders/anthropic.d.ts +31 -0
  80. package/dist/builders/anthropic.d.ts.map +1 -0
  81. package/dist/builders/anthropic.js +227 -0
  82. package/dist/builders/anthropic.js.map +1 -0
  83. package/dist/builders/fireworksai.d.ts +9 -0
  84. package/dist/builders/fireworksai.d.ts.map +1 -0
  85. package/dist/builders/fireworksai.js +57 -0
  86. package/dist/builders/fireworksai.js.map +1 -0
  87. package/dist/builders/index.d.ts +13 -0
  88. package/dist/builders/index.d.ts.map +1 -0
  89. package/dist/builders/index.js +31 -0
  90. package/dist/builders/index.js.map +1 -0
  91. package/dist/builders/openai.d.ts +8 -0
  92. package/dist/builders/openai.d.ts.map +1 -0
  93. package/dist/builders/openai.js +87 -0
  94. package/dist/builders/openai.js.map +1 -0
  95. package/dist/builders/types.d.ts +54 -0
  96. package/dist/builders/types.d.ts.map +1 -0
  97. package/dist/builders/types.js +211 -0
  98. package/dist/builders/types.js.map +1 -0
  99. package/dist/connectors/index.d.ts.map +1 -1
  100. package/dist/connectors/index.js +3 -2
  101. package/dist/connectors/index.js.map +1 -1
  102. package/dist/connectors/registry.d.ts +2 -1
  103. package/dist/connectors/registry.d.ts.map +1 -1
  104. package/dist/connectors/registry.js +31 -8
  105. package/dist/connectors/registry.js.map +1 -1
  106. package/dist/customizer/Customizer.d.ts +57 -0
  107. package/dist/customizer/Customizer.d.ts.map +1 -0
  108. package/dist/customizer/Customizer.js +124 -0
  109. package/dist/customizer/Customizer.js.map +1 -0
  110. package/dist/customizer/index.d.ts.map +1 -0
  111. package/dist/customizer/index.js +9 -0
  112. package/dist/customizer/index.js.map +1 -0
  113. package/dist/files.d.ts +16 -0
  114. package/dist/files.d.ts.map +1 -1
  115. package/dist/files.js +60 -1
  116. package/dist/files.js.map +1 -1
  117. package/dist/index.d.ts.map +1 -1
  118. package/dist/index.js +1 -0
  119. package/dist/index.js.map +1 -1
  120. package/dist/init.d.ts +10 -6
  121. package/dist/init.d.ts.map +1 -1
  122. package/dist/init.js +96 -113
  123. package/dist/init.js.map +1 -1
  124. package/dist/migrations.d.ts.map +1 -1
  125. package/dist/migrations.js +23 -10
  126. package/dist/migrations.js.map +1 -1
  127. package/dist/models/anthropic.d.ts +4 -2
  128. package/dist/models/anthropic.d.ts.map +1 -1
  129. package/dist/models/anthropic.js +33 -6
  130. package/dist/models/anthropic.js.map +1 -1
  131. package/dist/models/fireworksai.d.ts.map +1 -1
  132. package/dist/models/fireworksai.js +9 -1
  133. package/dist/models/fireworksai.js.map +1 -1
  134. package/dist/models/index.d.ts +1 -1
  135. package/dist/models/index.d.ts.map +1 -1
  136. package/dist/models/index.js +2 -1
  137. package/dist/models/index.js.map +1 -1
  138. package/dist/models/openai.d.ts +1 -1
  139. package/dist/models/openai.d.ts.map +1 -1
  140. package/dist/models/openai.js +24 -3
  141. package/dist/models/openai.js.map +1 -1
  142. package/dist/models/types.d.ts +20 -1
  143. package/dist/models/types.d.ts.map +1 -1
  144. package/dist/models/types.js +6 -1
  145. package/dist/models/types.js.map +1 -1
  146. package/dist/pages.d.ts +30 -7
  147. package/dist/pages.d.ts.map +1 -1
  148. package/dist/pages.js +177 -55
  149. package/dist/pages.js.map +1 -1
  150. package/dist/service/server.d.ts.map +1 -1
  151. package/dist/service/server.js +37 -8
  152. package/dist/service/server.js.map +1 -1
  153. package/dist/service/transformPage.d.ts +47 -20
  154. package/dist/service/transformPage.d.ts.map +1 -1
  155. package/dist/service/transformPage.js +514 -293
  156. package/dist/service/transformPage.js.map +1 -1
  157. package/dist/service/useAgentRoutes.d.ts +2 -1
  158. package/dist/service/useAgentRoutes.d.ts.map +1 -1
  159. package/dist/service/useAgentRoutes.js +5 -2
  160. package/dist/service/useAgentRoutes.js.map +1 -1
  161. package/dist/service/useApiRoutes.d.ts.map +1 -1
  162. package/dist/service/useApiRoutes.js +237 -136
  163. package/dist/service/useApiRoutes.js.map +1 -1
  164. package/dist/service/useConnectorRoutes.js +6 -6
  165. package/dist/service/useConnectorRoutes.js.map +1 -1
  166. package/dist/service/useFileRoutes.d.ts +4 -0
  167. package/dist/service/useFileRoutes.d.ts.map +1 -0
  168. package/dist/service/useFileRoutes.js +122 -0
  169. package/dist/service/useFileRoutes.js.map +1 -0
  170. package/dist/service/usePageRoutes.d.ts.map +1 -1
  171. package/dist/service/usePageRoutes.js +648 -67
  172. package/dist/service/usePageRoutes.js.map +1 -1
  173. package/dist/service/useSharedDataRoutes.d.ts +4 -0
  174. package/dist/service/useSharedDataRoutes.d.ts.map +1 -0
  175. package/dist/service/useSharedDataRoutes.js +104 -0
  176. package/dist/service/useSharedDataRoutes.js.map +1 -0
  177. package/dist/service/useSharedFileRoutes.d.ts +4 -0
  178. package/dist/service/useSharedFileRoutes.d.ts.map +1 -0
  179. package/dist/service/useSharedFileRoutes.js +121 -0
  180. package/dist/service/useSharedFileRoutes.js.map +1 -0
  181. package/dist/settings.d.ts +1 -0
  182. package/dist/settings.d.ts.map +1 -1
  183. package/dist/settings.js +1 -0
  184. package/dist/settings.js.map +1 -1
  185. package/dist/synthos-cli.d.ts.map +1 -1
  186. package/dist/synthos-cli.js +4 -3
  187. package/dist/synthos-cli.js.map +1 -1
  188. package/dist/themes.d.ts +1 -0
  189. package/dist/themes.d.ts.map +1 -1
  190. package/dist/themes.js +28 -15
  191. package/dist/themes.js.map +1 -1
  192. package/migration-rules/v1-to-v2.md +193 -0
  193. package/migration-rules/v2-to-v3.md +481 -0
  194. package/package.json +11 -10
  195. package/required-pages/builder/page.html +43 -0
  196. package/required-pages/builder/page.json +10 -0
  197. package/required-pages/{pages.html → pages/page.html} +238 -233
  198. package/required-pages/pages/page.json +10 -0
  199. package/required-pages/{settings.html → settings/page.html} +389 -275
  200. package/required-pages/settings/page.json +10 -0
  201. package/required-pages/synthos_apis/page.html +846 -0
  202. package/required-pages/synthos_apis/page.json +10 -0
  203. package/required-pages/{synthos_scripts.html → synthos_scripts/page.html} +13 -11
  204. package/required-pages/synthos_scripts/page.json +10 -0
  205. package/src/agents/index.ts +1 -1
  206. package/src/agents/openclaw/gatewayManager.ts +22 -11
  207. package/src/agents/openclaw/openclawProvider.ts +2 -4
  208. package/src/agents/openclaw/sshTunnelManager.ts +19 -11
  209. package/src/builders/anthropic.ts +283 -0
  210. package/src/builders/fireworksai.ts +59 -0
  211. package/src/builders/index.ts +33 -0
  212. package/src/builders/openai.ts +89 -0
  213. package/src/builders/types.ts +261 -0
  214. package/src/connectors/index.ts +1 -1
  215. package/src/connectors/registry.ts +28 -8
  216. package/src/customizer/Customizer.ts +151 -0
  217. package/src/customizer/index.ts +5 -0
  218. package/src/files.ts +57 -0
  219. package/src/index.ts +2 -1
  220. package/src/init.ts +137 -123
  221. package/src/migrations.ts +30 -10
  222. package/src/models/anthropic.ts +40 -10
  223. package/src/models/fireworksai.ts +9 -2
  224. package/src/models/index.ts +1 -1
  225. package/src/models/openai.ts +26 -6
  226. package/src/models/types.ts +31 -1
  227. package/src/pages.ts +176 -54
  228. package/src/service/server.ts +36 -9
  229. package/src/service/transformPage.ts +557 -326
  230. package/src/service/useAgentRoutes.ts +7 -2
  231. package/src/service/useApiRoutes.ts +150 -41
  232. package/src/service/useConnectorRoutes.ts +7 -7
  233. package/src/service/useFileRoutes.ts +127 -0
  234. package/src/service/usePageRoutes.ts +720 -73
  235. package/src/service/useSharedDataRoutes.ts +106 -0
  236. package/src/service/useSharedFileRoutes.ts +126 -0
  237. package/src/settings.ts +2 -0
  238. package/src/synthos-cli.ts +4 -3
  239. package/src/themes.ts +25 -14
  240. package/static-files/favicon.svg +12 -0
  241. package/static-files/fluentlm-instructions.llmd +868 -0
  242. package/static-files/fluentlm-instructions.md +1595 -0
  243. package/static-files/fluentlm.css +4844 -0
  244. package/static-files/fluentlm.js +3602 -0
  245. package/static-files/fluentlm.min.css +1 -0
  246. package/static-files/fluentlm.min.js +1 -0
  247. package/{page-scripts/helpers-v2.js → static-files/helpers.v3.js} +82 -0
  248. package/static-files/page.v3.js +1290 -0
  249. package/static-files/recommended-frameworks.llmd +81 -0
  250. package/static-files/recommended-frameworks.md +137 -0
  251. package/static-files/retro-game.js +877 -0
  252. package/static-files/shell.css +797 -0
  253. package/static-files/theme-dark.css +169 -0
  254. package/static-files/theme-light.css +169 -0
  255. package/tests/builders.spec.ts +139 -0
  256. package/tests/pages.spec.ts +8 -8
  257. package/tests/transformPage.spec.ts +299 -360
  258. package/default-pages/application.html +0 -40
  259. package/default-pages/application.json +0 -1
  260. package/default-pages/json_tools.json +0 -1
  261. package/default-pages/my_notes.html +0 -33
  262. package/default-pages/neon_asteroids.html +0 -77
  263. package/default-pages/sidebar_page.json +0 -1
  264. package/default-pages/solar_tutorial.json +0 -1
  265. package/default-pages/two-panel_page.json +0 -1
  266. package/dist/agents/a2a/a2aProvider.d.ts +0 -3
  267. package/dist/agents/discovery.d.ts +0 -30
  268. package/dist/agents/openclaw/openclawProvider.d.ts +0 -3
  269. package/dist/agents/types.d.ts +0 -64
  270. package/dist/connectors/index.d.ts +0 -3
  271. package/dist/connectors/types.d.ts +0 -84
  272. package/dist/index.d.ts +0 -7
  273. package/dist/migrations.d.ts +0 -12
  274. package/dist/models/chainOfThought.d.ts +0 -12
  275. package/dist/models/fireworksai.d.ts +0 -30
  276. package/dist/models/logCompletePrompt.d.ts +0 -3
  277. package/dist/models/providers.d.ts +0 -8
  278. package/dist/models/utils.d.ts +0 -6
  279. package/dist/scripts.d.ts +0 -15
  280. package/dist/service/createCompletePrompt.d.ts +0 -5
  281. package/dist/service/debugLog.d.ts +0 -11
  282. package/dist/service/generateImage.d.ts +0 -32
  283. package/dist/service/index.d.ts +0 -8
  284. package/dist/service/modelInstructions.d.ts +0 -7
  285. package/dist/service/requiresSettings.d.ts +0 -3
  286. package/dist/service/server.d.ts +0 -4
  287. package/dist/service/useApiRoutes.d.ts +0 -4
  288. package/dist/service/useConnectorRoutes.d.ts +0 -4
  289. package/dist/service/useDataRoutes.d.ts +0 -4
  290. package/dist/service/useGatewayRoutes.d.ts +0 -4
  291. package/dist/service/useGatewayRoutes.d.ts.map +0 -1
  292. package/dist/service/useGatewayRoutes.js +0 -168
  293. package/dist/service/useGatewayRoutes.js.map +0 -1
  294. package/dist/service/usePageRoutes.d.ts +0 -5
  295. package/dist/synthos-cli.d.ts +0 -2
  296. package/page-scripts/page-v2.js +0 -656
  297. package/required-pages/builder.html +0 -48
  298. package/required-pages/builder.json +0 -1
  299. package/required-pages/pages.json +0 -1
  300. package/required-pages/settings.json +0 -1
  301. package/required-pages/synthos_apis.html +0 -327
  302. package/required-pages/synthos_apis.json +0 -1
  303. package/required-pages/synthos_scripts.json +0 -1
  304. package/src/connectors/airtable/connector.json +0 -27
  305. package/src/connectors/alpha-vantage/connector.json +0 -26
  306. package/src/connectors/brave-search/connector.json +0 -26
  307. package/src/connectors/cloudinary/connector.json +0 -27
  308. package/src/connectors/deepl/connector.json +0 -28
  309. package/src/connectors/elevenlabs/connector.json +0 -30
  310. package/src/connectors/giphy/connector.json +0 -27
  311. package/src/connectors/github/connector.json +0 -29
  312. package/src/connectors/huggingface/connector.json +0 -27
  313. package/src/connectors/imgur/connector.json +0 -29
  314. package/src/connectors/instagram/connector.json +0 -43
  315. package/src/connectors/jira/connector.json +0 -28
  316. package/src/connectors/mapbox/connector.json +0 -26
  317. package/src/connectors/nasa/connector.json +0 -27
  318. package/src/connectors/newsapi/connector.json +0 -27
  319. package/src/connectors/notion/connector.json +0 -28
  320. package/src/connectors/open-exchange-rates/connector.json +0 -27
  321. package/src/connectors/openweathermap/connector.json +0 -26
  322. package/src/connectors/pexels/connector.json +0 -27
  323. package/src/connectors/resend/connector.json +0 -29
  324. package/src/connectors/rss2json/connector.json +0 -27
  325. package/src/connectors/sendgrid/connector.json +0 -27
  326. package/src/connectors/spoonacular/connector.json +0 -28
  327. package/src/connectors/stability-ai/connector.json +0 -27
  328. package/src/connectors/twilio/connector.json +0 -28
  329. package/src/connectors/unsplash/connector.json +0 -27
  330. package/src/connectors/wolfram-alpha/connector.json +0 -26
  331. package/src/connectors/youtube-data/connector.json +0 -30
  332. /package/{dist/connectors → service-connectors}/airtable/connector.json +0 -0
  333. /package/{dist/connectors → service-connectors}/alpha-vantage/connector.json +0 -0
  334. /package/{dist/connectors → service-connectors}/brave-search/connector.json +0 -0
  335. /package/{dist/connectors → service-connectors}/cloudinary/connector.json +0 -0
  336. /package/{dist/connectors → service-connectors}/deepl/connector.json +0 -0
  337. /package/{dist/connectors → service-connectors}/elevenlabs/connector.json +0 -0
  338. /package/{dist/connectors → service-connectors}/giphy/connector.json +0 -0
  339. /package/{dist/connectors → service-connectors}/github/connector.json +0 -0
  340. /package/{dist/connectors → service-connectors}/huggingface/connector.json +0 -0
  341. /package/{dist/connectors → service-connectors}/imgur/connector.json +0 -0
  342. /package/{dist/connectors → service-connectors}/instagram/connector.json +0 -0
  343. /package/{dist/connectors → service-connectors}/jira/connector.json +0 -0
  344. /package/{dist/connectors → service-connectors}/mapbox/connector.json +0 -0
  345. /package/{dist/connectors → service-connectors}/nasa/connector.json +0 -0
  346. /package/{dist/connectors → service-connectors}/newsapi/connector.json +0 -0
  347. /package/{dist/connectors → service-connectors}/notion/connector.json +0 -0
  348. /package/{dist/connectors → service-connectors}/open-exchange-rates/connector.json +0 -0
  349. /package/{dist/connectors → service-connectors}/openweathermap/connector.json +0 -0
  350. /package/{dist/connectors → service-connectors}/pexels/connector.json +0 -0
  351. /package/{dist/connectors → service-connectors}/resend/connector.json +0 -0
  352. /package/{dist/connectors → service-connectors}/rss2json/connector.json +0 -0
  353. /package/{dist/connectors → service-connectors}/sendgrid/connector.json +0 -0
  354. /package/{dist/connectors → service-connectors}/spoonacular/connector.json +0 -0
  355. /package/{dist/connectors → service-connectors}/stability-ai/connector.json +0 -0
  356. /package/{dist/connectors → service-connectors}/twilio/connector.json +0 -0
  357. /package/{dist/connectors → service-connectors}/unsplash/connector.json +0 -0
  358. /package/{dist/connectors → service-connectors}/wolfram-alpha/connector.json +0 -0
  359. /package/{dist/connectors → service-connectors}/youtube-data/connector.json +0 -0
@@ -31,10 +31,10 @@ const SERVICE_REGISTRY = [
31
31
  exclusive: 'search'
32
32
  }
33
33
  ];
34
- function useApiRoutes(config, app) {
34
+ function useApiRoutes(config, app, customizer) {
35
35
  // List pages
36
36
  app.get('/api/pages', async (req, res) => {
37
- const pages = await (0, pages_1.listPages)(config.pagesFolder, config.requiredPagesFolder);
37
+ const pages = await (0, pages_1.listPages)(config.pagesFolder, config.requiredPagesFolders);
38
38
  res.json(pages);
39
39
  });
40
40
  // Import a page from a zip file
@@ -122,7 +122,7 @@ function useApiRoutes(config, app) {
122
122
  app.get('/api/pages/:name', async (req, res) => {
123
123
  try {
124
124
  const { name } = req.params;
125
- const metadata = await (0, pages_1.loadPageMetadata)(config.pagesFolder, name, config.requiredPagesFolder);
125
+ const metadata = await (0, pages_1.loadPageMetadata)(config.pagesFolder, name, config.requiredPagesFolders);
126
126
  if (metadata) {
127
127
  res.json(metadata);
128
128
  }
@@ -172,7 +172,7 @@ function useApiRoutes(config, app) {
172
172
  return;
173
173
  }
174
174
  // Load existing metadata (or defaults)
175
- const existing = await (0, pages_1.loadPageMetadata)(config.pagesFolder, name, config.requiredPagesFolder);
175
+ const existing = await (0, pages_1.loadPageMetadata)(config.pagesFolder, name, config.requiredPagesFolders);
176
176
  const metadata = {
177
177
  title: existing?.title ?? '',
178
178
  categories: existing?.categories ?? [],
@@ -200,7 +200,12 @@ function useApiRoutes(config, app) {
200
200
  if (metadata.mode !== 'locked') {
201
201
  const userPagePath = path_1.default.join(config.pagesFolder, 'pages', name, 'page.html');
202
202
  if (!(await (0, files_1.checkIfExists)(userPagePath))) {
203
- const html = await (0, pages_1.loadPageState)(config.requiredPagesFolder, name, false);
203
+ let html;
204
+ for (const folder of config.requiredPagesFolders) {
205
+ html = await (0, pages_1.loadPageState)(folder, name);
206
+ if (html)
207
+ break;
208
+ }
204
209
  if (html) {
205
210
  await (0, pages_1.savePageState)(config.pagesFolder, name, html);
206
211
  }
@@ -224,7 +229,7 @@ function useApiRoutes(config, app) {
224
229
  return;
225
230
  }
226
231
  // Load existing metadata (user override → fallback .json → defaults)
227
- let metadata = await (0, pages_1.loadPageMetadata)(config.pagesFolder, name, config.requiredPagesFolder);
232
+ let metadata = await (0, pages_1.loadPageMetadata)(config.pagesFolder, name, config.requiredPagesFolders);
228
233
  if (!metadata) {
229
234
  metadata = {
230
235
  title: '',
@@ -251,7 +256,7 @@ function useApiRoutes(config, app) {
251
256
  try {
252
257
  const { name } = req.params;
253
258
  // Cannot delete required pages
254
- if (pages_1.REQUIRED_PAGES.includes(name)) {
259
+ if (config.requiredPages.includes(name)) {
255
260
  res.status(400).json({ error: `Cannot delete required page "${name}"` });
256
261
  return;
257
262
  }
@@ -271,11 +276,52 @@ function useApiRoutes(config, app) {
271
276
  res.status(500).send(err.message);
272
277
  }
273
278
  });
279
+ // Discover what a page contains (tables + files)
280
+ app.get('/api/pages/:name/contents', async (req, res) => {
281
+ try {
282
+ const { name } = req.params;
283
+ // Resolve page folder: user pages first, then required pages
284
+ let pageFolder;
285
+ const userFolder = path_1.default.join(config.pagesFolder, 'pages', name);
286
+ if (await (0, files_1.checkIfExists)(path_1.default.join(userFolder, 'page.html'))) {
287
+ pageFolder = userFolder;
288
+ }
289
+ else {
290
+ for (const folder of config.requiredPagesFolders) {
291
+ const candidate = path_1.default.join(folder, name);
292
+ if (await (0, files_1.checkIfExists)(path_1.default.join(candidate, 'page.html'))) {
293
+ pageFolder = candidate;
294
+ break;
295
+ }
296
+ }
297
+ }
298
+ if (!pageFolder) {
299
+ res.status(404).json({ error: `Page "${name}" not found` });
300
+ return;
301
+ }
302
+ // List subdirectories, filtering out non-table entries
303
+ const EXCLUDED = new Set(['files']);
304
+ const subdirs = await (0, files_1.listFolders)(pageFolder);
305
+ const tables = subdirs.filter(d => !EXCLUDED.has(d));
306
+ // Check if files/ exists and has entries
307
+ const filesDir = path_1.default.join(pageFolder, 'files');
308
+ let hasFiles = false;
309
+ if (await (0, files_1.checkIfExists)(filesDir)) {
310
+ const entries = await promises_1.default.readdir(filesDir);
311
+ hasFiles = entries.length > 0;
312
+ }
313
+ res.json({ tables, hasFiles });
314
+ }
315
+ catch (err) {
316
+ console.error(err);
317
+ res.status(500).json({ error: err.message });
318
+ }
319
+ });
274
320
  // Copy a page to a new name
275
321
  app.post('/api/pages/:name/copy', async (req, res) => {
276
322
  try {
277
323
  const sourceName = req.params.name;
278
- const { name: targetName, title, categories } = req.body;
324
+ const { name: targetName, title, categories, copyTables, copyFiles } = req.body;
279
325
  // Validate target name
280
326
  if (!targetName || typeof targetName !== 'string') {
281
327
  res.status(400).json({ error: 'name is required' });
@@ -293,10 +339,17 @@ function useApiRoutes(config, app) {
293
339
  // Check source exists (user pages → required pages)
294
340
  const sourceFolderPath = path_1.default.join(config.pagesFolder, 'pages', sourceName, 'page.html');
295
341
  const sourceFlatPath = path_1.default.join(config.pagesFolder, `${sourceName}.html`);
296
- const sourceRequiredPath = path_1.default.join(config.requiredPagesFolder, `${sourceName}.html`);
342
+ let sourceRequiredPath;
343
+ for (const folder of config.requiredPagesFolders) {
344
+ const candidate = path_1.default.join(folder, sourceName, 'page.html');
345
+ if (await (0, files_1.checkIfExists)(candidate)) {
346
+ sourceRequiredPath = candidate;
347
+ break;
348
+ }
349
+ }
297
350
  const sourceExists = await (0, files_1.checkIfExists)(sourceFolderPath)
298
351
  || await (0, files_1.checkIfExists)(sourceFlatPath)
299
- || await (0, files_1.checkIfExists)(sourceRequiredPath);
352
+ || !!sourceRequiredPath;
300
353
  if (!sourceExists) {
301
354
  res.status(404).json({ error: `Source page "${sourceName}" not found` });
302
355
  return;
@@ -308,7 +361,10 @@ function useApiRoutes(config, app) {
308
361
  res.status(409).json({ error: `Page "${targetName}" already exists` });
309
362
  return;
310
363
  }
311
- await (0, pages_1.copyPage)(config.pagesFolder, sourceName, targetName, typeof title === 'string' ? title : '', Array.isArray(categories) ? categories : [], config.requiredPagesFolder);
364
+ await (0, pages_1.copyPage)(config.pagesFolder, sourceName, targetName, typeof title === 'string' ? title : '', Array.isArray(categories) ? categories : [], config.requiredPagesFolders, {
365
+ copyTables: copyTables === true,
366
+ copyFiles: copyFiles !== false, // default true
367
+ });
312
368
  // Return the new page metadata
313
369
  const metadata = await (0, pages_1.loadPageMetadata)(config.pagesFolder, targetName);
314
370
  res.status(201).json({ name: targetName, ...metadata });
@@ -380,15 +436,17 @@ function useApiRoutes(config, app) {
380
436
  });
381
437
  });
382
438
  // Brainstorm endpoint
383
- app.post('/api/brainstorm', async (req, res) => {
384
- await (0, requiresSettings_1.requiresSettings)(res, config.pagesFolder, async (settings) => {
385
- const { context, messages } = req.body;
386
- const completePrompt = await (0, createCompletePrompt_1.createCompletePrompt)(config.pagesFolder, 'chat');
387
- const system = {
388
- role: 'system',
389
- content: `You are a creative brainstorming assistant for SynthOS, a tool that builds pages through conversation.
390
- SynthOS is like a WIKI for vibe coding. Each page has a chat panel and a viewer panel. They are vibe coding what's displayed in that viewer panel. They can then save that as a page.
391
- The user is brainstorming exploring ideas before building. Be concise, creative, and collaborative.
439
+ if (!customizer || customizer.isEnabled('brainstorm'))
440
+ app.post('/api/brainstorm', async (req, res) => {
441
+ await (0, requiresSettings_1.requiresSettings)(res, config.pagesFolder, async (settings) => {
442
+ const { context, messages } = req.body;
443
+ const completePrompt = await (0, createCompletePrompt_1.createCompletePrompt)(config.pagesFolder, 'chat');
444
+ const productName = customizer?.productName ?? 'SynthOS';
445
+ const system = {
446
+ role: 'system',
447
+ content: `You are a creative brainstorming assistant for ${productName}, a tool that builds pages through conversation.
448
+ ${productName} is like a WIKI for vibe coding. Each page has a chat panel and a viewer panel. They are vibe coding what's displayed in that viewer panel. They can then save that as a page.
449
+ The user is brainstorming — exploring ideas before building. Be concise, creative, and collaborative.
392
450
  They may say that they want to build an app or page that does XYZ but they're talking about what they expect to see in the viewer panel.
393
451
  The goal is to help them generate a prompt for the builder that captures their vision, along with suggestions for next steps.
394
452
  Suggest concrete approaches when you can, not complex visions for some ellaborate app.
@@ -399,74 +457,75 @@ ${context}
399
457
 
400
458
  <INSTRUCTIONS>
401
459
  Look at the <CHAT_HISTORY> and if it's empty it's the start of a new idea. Simply greet them and ask them what they're thinking of building. Suggestions could be help me decide, etc.
402
- If you see a conversation between SynthOS and the User. Asses what they're building and ask them what they'd like help with. Maybe offer a few good next steps.
460
+ If you see a conversation between ${productName} and the User. Asses what they're building and ask them what they'd like help with. Maybe offer a few good next steps.
403
461
 
404
- SynthOS exposes table storage and chat completion api's that every page can use. If the user wants to store something or use AI, your prompt should suggest using table storage or make llm calls.
462
+ ${productName} exposes table storage and chat completion api's that every page can use. If the user wants to store something or use AI, your prompt should suggest using table storage or make llm calls.
405
463
 
406
464
  You MUST return your response as a JSON object with exactly these fields:
407
465
  {
408
466
  "response": "Your conversational reply — explanations, options, suggestions. Markdown OK.",
409
- "prompt": "A clean, actionable instruction ready to paste into SynthOS chat to build what was discussed. Update this each exchange to reflect the latest brainstorm state.",
467
+ "prompt": "A clean, actionable instruction ready to paste into ${productName} chat to build what was discussed. Update this each exchange to reflect the latest brainstorm state.",
410
468
  "suggestions": ["Short clickable option A", "Short clickable option B", "Short clickable option C"]
411
469
  }
412
470
 
413
471
  suggestions — 2-4 short phrases the user can click to continue the conversation. These are next-step options: directions to explore, questions to answer, or choices to make. Keep each under 60 characters. Always provide suggestions.
414
472
 
415
473
  Return ONLY the JSON object.`
416
- };
417
- // Format multi-turn conversation into a single prompt
418
- const formatted = messages.map(m => `${m.role === 'user' ? 'User' : 'Assistant'}: ${m.content}`).join('\n\n');
419
- const prompt = { role: 'user', content: formatted };
420
- const result = await completePrompt({ prompt, system, jsonMode: true });
421
- if (result.completed) {
422
- let response = result.value || '';
423
- let brainstormPrompt = '';
424
- let suggestions = [];
425
- // jsonMode returns an already-parsed object from agentm-core
426
- const parsed = (typeof result.value === 'object' && result.value !== null)
427
- ? result.value
428
- : (() => { try {
429
- return JSON.parse(result.value);
430
- }
431
- catch {
432
- return null;
433
- } })();
434
- if (parsed) {
435
- if (typeof parsed.response === 'string')
436
- response = parsed.response;
437
- if (typeof parsed.prompt === 'string')
438
- brainstormPrompt = parsed.prompt;
439
- if (Array.isArray(parsed.suggestions)) {
440
- suggestions = parsed.suggestions.filter((s) => typeof s === 'string');
474
+ };
475
+ // Format multi-turn conversation into a single prompt
476
+ const formatted = messages.map(m => `${m.role === 'user' ? 'User' : 'Assistant'}: ${m.content}`).join('\n\n');
477
+ const prompt = { role: 'user', content: formatted };
478
+ const result = await completePrompt({ prompt, system, jsonMode: true });
479
+ if (result.completed) {
480
+ let response = result.value || '';
481
+ let brainstormPrompt = '';
482
+ let suggestions = [];
483
+ // jsonMode returns an already-parsed object from agentm-core
484
+ const parsed = (typeof result.value === 'object' && result.value !== null)
485
+ ? result.value
486
+ : (() => { try {
487
+ return JSON.parse(result.value);
488
+ }
489
+ catch {
490
+ return null;
491
+ } })();
492
+ if (parsed) {
493
+ if (typeof parsed.response === 'string')
494
+ response = parsed.response;
495
+ if (typeof parsed.prompt === 'string')
496
+ brainstormPrompt = parsed.prompt;
497
+ if (Array.isArray(parsed.suggestions)) {
498
+ suggestions = parsed.suggestions.filter((s) => typeof s === 'string');
499
+ }
441
500
  }
501
+ res.json({ response, prompt: brainstormPrompt, suggestions });
442
502
  }
443
- res.json({ response, prompt: brainstormPrompt, suggestions });
444
- }
445
- else {
446
- console.error(result.error);
447
- res.status(500).send(result.error?.message);
448
- }
503
+ else {
504
+ console.error(result.error);
505
+ res.status(500).send(result.error?.message);
506
+ }
507
+ });
449
508
  });
450
- });
451
509
  // Define a route for running configured scripts
452
- app.post('/api/scripts/:id', async (req, res) => {
453
- await (0, requiresSettings_1.requiresSettings)(res, config.pagesFolder, async (settings) => {
454
- const { id } = req.params;
455
- const pagesFolder = config.pagesFolder;
456
- const scriptId = id;
457
- const response = await (0, scripts_1.executeScript)({ pagesFolder, scriptId, variables: req.body });
458
- if (response.completed) {
459
- // Return the response as text
460
- const value = (response.value?.output ?? (response.value?.errors ?? []).join('\n')).trim();
461
- res.set('Content-Type', 'text/plain');
462
- res.send(value);
463
- }
464
- else {
465
- console.error(response.error);
466
- res.status(500).send(response.error);
467
- }
510
+ if (!customizer || customizer.isEnabled('scripts'))
511
+ app.post('/api/scripts/:id', async (req, res) => {
512
+ await (0, requiresSettings_1.requiresSettings)(res, config.pagesFolder, async (settings) => {
513
+ const { id } = req.params;
514
+ const pagesFolder = config.pagesFolder;
515
+ const scriptId = id;
516
+ const response = await (0, scripts_1.executeScript)({ pagesFolder, scriptId, variables: req.body });
517
+ if (response.completed) {
518
+ // Return the response as text
519
+ const value = (response.value?.output ?? (response.value?.errors ?? []).join('\n')).trim();
520
+ res.set('Content-Type', 'text/plain');
521
+ res.send(value);
522
+ }
523
+ else {
524
+ console.error(response.error);
525
+ res.status(500).send(response.error);
526
+ }
527
+ });
468
528
  });
469
- });
470
529
  // Return theme info as a self-executing JS script
471
530
  app.get('/api/theme-info.js', async (req, res) => {
472
531
  try {
@@ -477,7 +536,13 @@ Return ONLY the JSON object.`
477
536
  res.status(404).send(`// Theme info for "${themeName}" not found`);
478
537
  return;
479
538
  }
480
- const js = `window.themeInfo=${JSON.stringify(info)};document.documentElement.classList.add(window.themeInfo.mode+"-mode");`;
539
+ const themeVersion = await (0, themes_1.loadThemeVersion)(themeName, config);
540
+ const payload = { ...info, name: themeName, version: themeVersion };
541
+ let js = `window.themeInfo=${JSON.stringify(payload)};document.documentElement.classList.add(window.themeInfo.mode+"-mode");`;
542
+ if (themeVersion >= 3) {
543
+ js += `document.documentElement.classList.add(${JSON.stringify(themeName)});`;
544
+ }
545
+ js += `document.documentElement.setAttribute("data-toolbar",${JSON.stringify(settings.toolbarPosition || 'left')});`;
481
546
  res.set('Content-Type', 'application/javascript');
482
547
  res.send(js);
483
548
  }
@@ -494,21 +559,17 @@ Return ONLY the JSON object.`
494
559
  res.status(400).send('// Missing page query parameter');
495
560
  return;
496
561
  }
497
- const metadata = await (0, pages_1.loadPageMetadata)(config.pagesFolder, page, config.requiredPagesFolder);
562
+ const metadata = await (0, pages_1.loadPageMetadata)(config.pagesFolder, page, config.requiredPagesFolders);
498
563
  const mode = metadata?.mode ?? 'unlocked';
499
564
  const title = metadata?.title ?? '';
500
565
  const categories = metadata?.categories ?? [];
501
- const info = JSON.stringify({ name: page, mode, latestPageVersion: pages_1.PAGE_VERSION, title, categories });
566
+ const isRequiredPage = config.requiredPages.includes(page);
567
+ const info = JSON.stringify({ name: page, mode, latestPageVersion: pages_1.PAGE_VERSION, title, categories, isRequiredPage });
502
568
  const js = [
503
569
  `window.pageInfo=${info};`,
504
570
  `if(window.pageInfo.mode==="locked"){`,
505
571
  `document.addEventListener("DOMContentLoaded",function(){`,
506
572
  `var f=document.getElementById("chatForm");if(f)f.style.display="none";`,
507
- `var s=document.getElementById("saveLink");if(s)s.textContent="Copy";`,
508
- `var r=document.getElementById("resetLink");if(r){`,
509
- `var c=r.cloneNode(true);c.textContent="Reload";`,
510
- `c.addEventListener("click",function(e){e.preventDefault();window.location.href=window.location.pathname;});`,
511
- `r.parentNode.replaceChild(c,r);}`,
512
573
  `});`,
513
574
  `}`,
514
575
  ].join('');
@@ -558,8 +619,8 @@ Return ONLY the JSON object.`
558
619
  res.status(400).send('// Invalid version parameter');
559
620
  return;
560
621
  }
561
- const scriptPath = path_1.default.join(config.pageScriptsFolder, `page-v${v}.js`);
562
- if (!(await (0, files_1.checkIfExists)(scriptPath))) {
622
+ const scriptPath = await (0, files_1.findFileInFolders)(config.staticFilesFolders, `page.v${v}.js`);
623
+ if (!scriptPath) {
563
624
  res.status(404).send(`// page-v${v}.js not found`);
564
625
  return;
565
626
  }
@@ -581,8 +642,8 @@ Return ONLY the JSON object.`
581
642
  res.status(400).send('// Invalid version parameter');
582
643
  return;
583
644
  }
584
- const scriptPath = path_1.default.join(config.pageScriptsFolder, `helpers-v${v}.js`);
585
- if (!(await (0, files_1.checkIfExists)(scriptPath))) {
645
+ const scriptPath = await (0, files_1.findFileInFolders)(config.staticFilesFolders, `helpers.v${v}.js`);
646
+ if (!scriptPath) {
586
647
  res.status(404).send(`// helpers-v${v}.js not found`);
587
648
  return;
588
649
  }
@@ -657,57 +718,58 @@ Return ONLY the JSON object.`
657
718
  // -----------------------------------------------------------------------
658
719
  // Web Search (Brave Search API)
659
720
  // -----------------------------------------------------------------------
660
- app.post('/api/search/web', async (req, res) => {
661
- try {
662
- const { query, count, country, freshness } = req.body;
663
- if (!query || typeof query !== 'string') {
664
- res.status(400).json({ error: 'query is required' });
665
- return;
666
- }
667
- const settings = await (0, settings_1.loadSettings)(config.pagesFolder);
668
- const braveConfig = settings.connectors?.['brave-search'] ?? settings.services?.['brave-search'];
669
- if (!braveConfig || !braveConfig.enabled || !braveConfig.apiKey) {
670
- res.status(400).json({ error: 'Brave Search is not configured or not enabled. Add your API key in Settings > Services.' });
671
- return;
672
- }
673
- const params = new URLSearchParams({ q: query });
674
- if (count)
675
- params.set('count', String(Math.min(Number(count) || 5, 20)));
676
- if (country)
677
- params.set('country', country);
678
- if (freshness)
679
- params.set('freshness', freshness);
680
- const response = await fetch(`https://api.search.brave.com/res/v1/web/search?${params.toString()}`, {
681
- headers: {
682
- 'Accept': 'application/json',
683
- 'Accept-Encoding': 'gzip',
684
- 'X-Subscription-Token': braveConfig.apiKey
721
+ if (!customizer || customizer.isEnabled('search'))
722
+ app.post('/api/search/web', async (req, res) => {
723
+ try {
724
+ const { query, count, country, freshness } = req.body;
725
+ if (!query || typeof query !== 'string') {
726
+ res.status(400).json({ error: 'query is required' });
727
+ return;
685
728
  }
686
- });
687
- if (!response.ok) {
688
- const text = await response.text();
689
- res.status(response.status).json({ error: `Brave Search API error: ${text}` });
690
- return;
729
+ const settings = await (0, settings_1.loadSettings)(config.pagesFolder);
730
+ const braveConfig = settings.connectors?.['brave-search'] ?? settings.services?.['brave-search'];
731
+ if (!braveConfig || !braveConfig.enabled || !braveConfig.apiKey) {
732
+ res.status(400).json({ error: 'Brave Search is not configured or not enabled. Add your API key in Settings > Services.' });
733
+ return;
734
+ }
735
+ const params = new URLSearchParams({ q: query });
736
+ if (count)
737
+ params.set('count', String(Math.min(Number(count) || 5, 20)));
738
+ if (country)
739
+ params.set('country', country);
740
+ if (freshness)
741
+ params.set('freshness', freshness);
742
+ const response = await fetch(`https://api.search.brave.com/res/v1/web/search?${params.toString()}`, {
743
+ headers: {
744
+ 'Accept': 'application/json',
745
+ 'Accept-Encoding': 'gzip',
746
+ 'X-Subscription-Token': braveConfig.apiKey
747
+ }
748
+ });
749
+ if (!response.ok) {
750
+ const text = await response.text();
751
+ res.status(response.status).json({ error: `Brave Search API error: ${text}` });
752
+ return;
753
+ }
754
+ const data = await response.json();
755
+ const results = (data.web?.results ?? []).map(r => ({
756
+ title: r.title,
757
+ url: r.url,
758
+ description: r.description
759
+ }));
760
+ res.json({ results });
761
+ }
762
+ catch (err) {
763
+ console.error(err);
764
+ res.status(500).json({ error: err.message });
691
765
  }
692
- const data = await response.json();
693
- const results = (data.web?.results ?? []).map(r => ({
694
- title: r.title,
695
- url: r.url,
696
- description: r.description
697
- }));
698
- res.json({ results });
699
- }
700
- catch (err) {
701
- console.error(err);
702
- res.status(500).json({ error: err.message });
703
- }
704
- });
766
+ });
705
767
  // Upgrade a page to the latest version
706
768
  app.post('/api/pages/:name/upgrade', async (req, res) => {
707
769
  try {
708
770
  const { name } = req.params;
709
771
  // Load current metadata
710
- const metadata = await (0, pages_1.loadPageMetadata)(config.pagesFolder, name, config.requiredPagesFolder);
772
+ const metadata = await (0, pages_1.loadPageMetadata)(config.pagesFolder, name, config.requiredPagesFolders);
711
773
  if (!metadata) {
712
774
  res.status(404).json({ error: `Page "${name}" not found` });
713
775
  return;
@@ -730,17 +792,19 @@ Return ONLY the JSON object.`
730
792
  await (0, pages_1.savePageState)(config.pagesFolder, name, migratedHtml);
731
793
  // Backup original page to .migrated/ before overwriting
732
794
  const migratedFolder = path_1.default.join(config.pagesFolder, '.migrated');
733
- // Handle legacy flat file (.synthos/pagename.html)
795
+ // Handle legacy flat file (<localFolder>/pagename.html)
734
796
  const flatPath = path_1.default.join(config.pagesFolder, `${name}.html`);
735
797
  if (await (0, files_1.checkIfExists)(flatPath)) {
736
798
  await (0, files_1.copyFile)(flatPath, migratedFolder);
737
799
  await (0, files_1.deleteFile)(flatPath);
738
800
  }
739
- // Handle folder-based page (.synthos/pages/name/)
801
+ // Handle folder-based page (<localFolder>/pages/name/)
740
802
  const folderPath = path_1.default.join(config.pagesFolder, 'pages', name);
741
803
  if (await (0, files_1.checkIfExists)(folderPath)) {
742
804
  await (0, files_1.copyFolderRecursive)(folderPath, path_1.default.join(migratedFolder, name));
743
805
  }
806
+ // Clear stale version files (undo snapshots from the old page version)
807
+ await (0, pages_1.clearVersions)(config.pagesFolder, name);
744
808
  // Update metadata
745
809
  metadata.pageVersion = pages_1.PAGE_VERSION;
746
810
  metadata.lastModified = new Date().toISOString();
@@ -758,20 +822,26 @@ Return ONLY the JSON object.`
758
822
  const { name } = req.params;
759
823
  // Try user pages folder first, then required pages
760
824
  const userPageDir = path_1.default.join(config.pagesFolder, 'pages', name);
761
- const requiredPageFile = path_1.default.join(config.requiredPagesFolder, `${name}.html`);
825
+ let requiredPageDir;
826
+ for (const folder of config.requiredPagesFolders) {
827
+ if (await (0, files_1.checkIfExists)(path_1.default.join(folder, name, 'page.html'))) {
828
+ requiredPageDir = path_1.default.join(folder, name);
829
+ break;
830
+ }
831
+ }
762
832
  let sourceDir = null;
763
833
  if (await (0, files_1.checkIfExists)(path_1.default.join(userPageDir, 'page.html'))) {
764
834
  sourceDir = userPageDir;
765
835
  }
766
- else if (await (0, files_1.checkIfExists)(requiredPageFile)) {
836
+ else if (requiredPageDir) {
767
837
  // For required pages, create a temp-like zip with just the HTML
768
838
  const zip = new adm_zip_1.default();
769
- const html = await (0, files_1.loadFile)(requiredPageFile);
839
+ const html = await (0, files_1.loadFile)(path_1.default.join(requiredPageDir, 'page.html'));
770
840
  zip.addFile(`${name}/page.html`, Buffer.from(html, 'utf-8'));
771
841
  // Include page.json if it exists
772
- const requiredMetaFile = path_1.default.join(config.requiredPagesFolder, `${name}.json`);
773
- if (await (0, files_1.checkIfExists)(requiredMetaFile)) {
774
- const meta = await (0, files_1.loadFile)(requiredMetaFile);
842
+ const metaPath = path_1.default.join(requiredPageDir, 'page.json');
843
+ if (await (0, files_1.checkIfExists)(metaPath)) {
844
+ const meta = await (0, files_1.loadFile)(metaPath);
775
845
  zip.addFile(`${name}/page.json`, Buffer.from(meta, 'utf-8'));
776
846
  }
777
847
  const zipBuffer = zip.toBuffer();
@@ -797,6 +867,37 @@ Return ONLY the JSON object.`
797
867
  res.status(500).json({ error: err.message });
798
868
  }
799
869
  });
870
+ // Ask a question about a page (with full page HTML context)
871
+ app.post('/api/pages/:name/ask', async (req, res) => {
872
+ await (0, requiresSettings_1.requiresSettings)(res, config.pagesFolder, async (settings) => {
873
+ const { name } = req.params;
874
+ const { question } = req.body;
875
+ if (typeof question !== 'string' || !question.trim()) {
876
+ res.status(400).json({ error: 'question is required' });
877
+ return;
878
+ }
879
+ // Load the page HTML
880
+ const html = await (0, usePageRoutes_1.loadPageWithFallback)(name, config, false);
881
+ if (!html) {
882
+ res.status(404).json({ error: `Page "${name}" not found` });
883
+ return;
884
+ }
885
+ // Create completion (uses 'chat' model, not 'builder')
886
+ const complete = await (0, createCompletePrompt_1.createCompletePrompt)(config.pagesFolder, 'chat', req.body.model);
887
+ const system = {
888
+ role: 'system',
889
+ content: `You are a helpful assistant. The user will ask questions about a web page. Answer based on the page content provided.\n\n<PAGE_HTML>\n${html}`
890
+ };
891
+ const prompt = { role: 'user', content: question };
892
+ const result = await complete({ system, prompt });
893
+ if (result.completed) {
894
+ res.json({ answer: result.value });
895
+ }
896
+ else {
897
+ res.status(500).json({ error: result.error?.message || 'Completion failed' });
898
+ }
899
+ });
900
+ });
800
901
  }
801
902
  exports.useApiRoutes = useApiRoutes;
802
903
  //# sourceMappingURL=useApiRoutes.js.map