synthos 0.8.0 → 0.10.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 (368) 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 +1803 -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} +16 -30
  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} +15 -12
  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 +1 -1
  100. package/dist/connectors/index.d.ts.map +1 -1
  101. package/dist/connectors/index.js +3 -2
  102. package/dist/connectors/index.js.map +1 -1
  103. package/dist/connectors/registry.d.ts +2 -1
  104. package/dist/connectors/registry.d.ts.map +1 -1
  105. package/dist/connectors/registry.js +31 -8
  106. package/dist/connectors/registry.js.map +1 -1
  107. package/dist/customizer/Customizer.d.ts +62 -0
  108. package/dist/customizer/Customizer.d.ts.map +1 -0
  109. package/dist/customizer/Customizer.js +134 -0
  110. package/dist/customizer/Customizer.js.map +1 -0
  111. package/dist/customizer/index.d.ts +4 -0
  112. package/dist/customizer/index.d.ts.map +1 -0
  113. package/dist/customizer/index.js +9 -0
  114. package/dist/customizer/index.js.map +1 -0
  115. package/dist/files.d.ts +16 -0
  116. package/dist/files.d.ts.map +1 -1
  117. package/dist/files.js +60 -1
  118. package/dist/files.js.map +1 -1
  119. package/dist/index.d.ts +2 -0
  120. package/dist/index.d.ts.map +1 -1
  121. package/dist/index.js +2 -0
  122. package/dist/index.js.map +1 -1
  123. package/dist/init.d.ts +12 -6
  124. package/dist/init.d.ts.map +1 -1
  125. package/dist/init.js +150 -133
  126. package/dist/init.js.map +1 -1
  127. package/dist/migrations.d.ts.map +1 -1
  128. package/dist/migrations.js +23 -10
  129. package/dist/migrations.js.map +1 -1
  130. package/dist/models/anthropic.d.ts +4 -2
  131. package/dist/models/anthropic.d.ts.map +1 -1
  132. package/dist/models/anthropic.js +33 -6
  133. package/dist/models/anthropic.js.map +1 -1
  134. package/dist/models/fireworksai.d.ts.map +1 -1
  135. package/dist/models/fireworksai.js +9 -1
  136. package/dist/models/fireworksai.js.map +1 -1
  137. package/dist/models/index.d.ts +1 -1
  138. package/dist/models/index.d.ts.map +1 -1
  139. package/dist/models/index.js +2 -1
  140. package/dist/models/index.js.map +1 -1
  141. package/dist/models/openai.d.ts +1 -1
  142. package/dist/models/openai.d.ts.map +1 -1
  143. package/dist/models/openai.js +24 -3
  144. package/dist/models/openai.js.map +1 -1
  145. package/dist/models/types.d.ts +20 -1
  146. package/dist/models/types.d.ts.map +1 -1
  147. package/dist/models/types.js +6 -1
  148. package/dist/models/types.js.map +1 -1
  149. package/dist/pages.d.ts +34 -10
  150. package/dist/pages.d.ts.map +1 -1
  151. package/dist/pages.js +229 -79
  152. package/dist/pages.js.map +1 -1
  153. package/dist/service/createCompletePrompt.d.ts +2 -1
  154. package/dist/service/createCompletePrompt.d.ts.map +1 -1
  155. package/dist/service/createCompletePrompt.js +2 -2
  156. package/dist/service/createCompletePrompt.js.map +1 -1
  157. package/dist/service/requiresSettings.d.ts +2 -1
  158. package/dist/service/requiresSettings.d.ts.map +1 -1
  159. package/dist/service/requiresSettings.js +3 -3
  160. package/dist/service/requiresSettings.js.map +1 -1
  161. package/dist/service/server.d.ts +2 -1
  162. package/dist/service/server.d.ts.map +1 -1
  163. package/dist/service/server.js +37 -8
  164. package/dist/service/server.js.map +1 -1
  165. package/dist/service/transformPage.d.ts +47 -20
  166. package/dist/service/transformPage.d.ts.map +1 -1
  167. package/dist/service/transformPage.js +514 -293
  168. package/dist/service/transformPage.js.map +1 -1
  169. package/dist/service/useAgentRoutes.d.ts +2 -1
  170. package/dist/service/useAgentRoutes.d.ts.map +1 -1
  171. package/dist/service/useAgentRoutes.js +17 -14
  172. package/dist/service/useAgentRoutes.js.map +1 -1
  173. package/dist/service/useApiRoutes.d.ts +2 -1
  174. package/dist/service/useApiRoutes.d.ts.map +1 -1
  175. package/dist/service/useApiRoutes.js +287 -172
  176. package/dist/service/useApiRoutes.js.map +1 -1
  177. package/dist/service/useConnectorRoutes.js +17 -17
  178. package/dist/service/useConnectorRoutes.js.map +1 -1
  179. package/dist/service/useDataRoutes.d.ts.map +1 -1
  180. package/dist/service/useDataRoutes.js +13 -10
  181. package/dist/service/useDataRoutes.js.map +1 -1
  182. package/dist/service/useFileRoutes.d.ts +4 -0
  183. package/dist/service/useFileRoutes.d.ts.map +1 -0
  184. package/dist/service/useFileRoutes.js +122 -0
  185. package/dist/service/useFileRoutes.js.map +1 -0
  186. package/dist/service/usePageRoutes.d.ts +2 -1
  187. package/dist/service/usePageRoutes.d.ts.map +1 -1
  188. package/dist/service/usePageRoutes.js +671 -74
  189. package/dist/service/usePageRoutes.js.map +1 -1
  190. package/dist/service/useSharedDataRoutes.d.ts +4 -0
  191. package/dist/service/useSharedDataRoutes.d.ts.map +1 -0
  192. package/dist/service/useSharedDataRoutes.js +107 -0
  193. package/dist/service/useSharedDataRoutes.js.map +1 -0
  194. package/dist/service/useSharedFileRoutes.d.ts +4 -0
  195. package/dist/service/useSharedFileRoutes.d.ts.map +1 -0
  196. package/dist/service/useSharedFileRoutes.js +121 -0
  197. package/dist/service/useSharedFileRoutes.js.map +1 -0
  198. package/dist/settings.d.ts +5 -3
  199. package/dist/settings.d.ts.map +1 -1
  200. package/dist/settings.js +12 -10
  201. package/dist/settings.js.map +1 -1
  202. package/dist/storage/FsStorageProvider.d.ts +25 -0
  203. package/dist/storage/FsStorageProvider.d.ts.map +1 -0
  204. package/dist/storage/FsStorageProvider.js +103 -0
  205. package/dist/storage/FsStorageProvider.js.map +1 -0
  206. package/dist/storage/StorageProvider.d.ts +31 -0
  207. package/dist/storage/StorageProvider.d.ts.map +1 -0
  208. package/dist/storage/StorageProvider.js +3 -0
  209. package/dist/storage/StorageProvider.js.map +1 -0
  210. package/dist/storage/index.d.ts +3 -0
  211. package/dist/storage/index.d.ts.map +1 -0
  212. package/dist/storage/index.js +6 -0
  213. package/dist/storage/index.js.map +1 -0
  214. package/dist/synthos-cli.d.ts.map +1 -1
  215. package/dist/synthos-cli.js +4 -3
  216. package/dist/synthos-cli.js.map +1 -1
  217. package/dist/themes.d.ts +1 -0
  218. package/dist/themes.d.ts.map +1 -1
  219. package/dist/themes.js +65 -28
  220. package/dist/themes.js.map +1 -1
  221. package/migration-rules/v1-to-v2.md +193 -0
  222. package/migration-rules/v2-to-v3.md +481 -0
  223. package/package.json +11 -10
  224. package/required-pages/builder/page.html +43 -0
  225. package/required-pages/builder/page.json +10 -0
  226. package/required-pages/{pages.html → pages/page.html} +238 -233
  227. package/required-pages/pages/page.json +10 -0
  228. package/required-pages/{settings.html → settings/page.html} +389 -275
  229. package/required-pages/settings/page.json +10 -0
  230. package/required-pages/synthos_apis/page.html +846 -0
  231. package/required-pages/synthos_apis/page.json +10 -0
  232. package/required-pages/{synthos_scripts.html → synthos_scripts/page.html} +13 -11
  233. package/required-pages/synthos_scripts/page.json +10 -0
  234. package/src/agents/index.ts +1 -1
  235. package/src/agents/openclaw/gatewayManager.ts +22 -11
  236. package/src/agents/openclaw/openclawProvider.ts +2 -4
  237. package/src/agents/openclaw/sshTunnelManager.ts +19 -11
  238. package/src/builders/anthropic.ts +283 -0
  239. package/src/builders/fireworksai.ts +59 -0
  240. package/src/builders/index.ts +33 -0
  241. package/src/builders/openai.ts +89 -0
  242. package/src/builders/types.ts +261 -0
  243. package/src/connectors/index.ts +1 -1
  244. package/src/connectors/registry.ts +28 -8
  245. package/src/customizer/Customizer.ts +163 -0
  246. package/src/customizer/index.ts +5 -0
  247. package/src/files.ts +57 -0
  248. package/src/index.ts +3 -1
  249. package/src/init.ts +195 -145
  250. package/src/migrations.ts +30 -10
  251. package/src/models/anthropic.ts +40 -10
  252. package/src/models/fireworksai.ts +9 -2
  253. package/src/models/index.ts +1 -1
  254. package/src/models/openai.ts +26 -6
  255. package/src/models/types.ts +31 -1
  256. package/src/pages.ts +230 -77
  257. package/src/service/createCompletePrompt.ts +3 -2
  258. package/src/service/requiresSettings.ts +4 -3
  259. package/src/service/server.ts +36 -9
  260. package/src/service/transformPage.ts +557 -326
  261. package/src/service/useAgentRoutes.ts +19 -14
  262. package/src/service/useApiRoutes.ts +208 -84
  263. package/src/service/useConnectorRoutes.ts +18 -18
  264. package/src/service/useDataRoutes.ts +13 -10
  265. package/src/service/useFileRoutes.ts +128 -0
  266. package/src/service/usePageRoutes.ts +730 -81
  267. package/src/service/useSharedDataRoutes.ts +109 -0
  268. package/src/service/useSharedFileRoutes.ts +127 -0
  269. package/src/settings.ts +14 -10
  270. package/src/storage/FsStorageProvider.ts +87 -0
  271. package/src/storage/StorageProvider.ts +34 -0
  272. package/src/storage/index.ts +2 -0
  273. package/src/synthos-cli.ts +4 -3
  274. package/src/themes.ts +64 -27
  275. package/static-files/favicon.svg +12 -0
  276. package/static-files/fluentlm-instructions.llmd +868 -0
  277. package/static-files/fluentlm-instructions.md +1595 -0
  278. package/static-files/fluentlm.css +4844 -0
  279. package/static-files/fluentlm.js +3602 -0
  280. package/static-files/fluentlm.min.css +1 -0
  281. package/static-files/fluentlm.min.js +1 -0
  282. package/{page-scripts/helpers-v2.js → static-files/helpers.v3.js} +82 -0
  283. package/static-files/page.v3.js +1290 -0
  284. package/static-files/recommended-frameworks.llmd +81 -0
  285. package/static-files/recommended-frameworks.md +137 -0
  286. package/static-files/retro-game.js +877 -0
  287. package/static-files/shell.css +797 -0
  288. package/static-files/theme-dark.css +169 -0
  289. package/static-files/theme-light.css +169 -0
  290. package/tests/builders.spec.ts +139 -0
  291. package/tests/pages.spec.ts +54 -84
  292. package/tests/transformPage.spec.ts +299 -360
  293. package/default-pages/application.html +0 -40
  294. package/default-pages/application.json +0 -1
  295. package/default-pages/json_tools.json +0 -1
  296. package/default-pages/my_notes.html +0 -33
  297. package/default-pages/neon_asteroids.html +0 -77
  298. package/default-pages/sidebar_page.json +0 -1
  299. package/default-pages/solar_tutorial.json +0 -1
  300. package/default-pages/two-panel_page.json +0 -1
  301. package/dist/service/useGatewayRoutes.d.ts +0 -4
  302. package/dist/service/useGatewayRoutes.d.ts.map +0 -1
  303. package/dist/service/useGatewayRoutes.js +0 -168
  304. package/dist/service/useGatewayRoutes.js.map +0 -1
  305. package/page-scripts/page-v2.js +0 -656
  306. package/required-pages/builder.html +0 -48
  307. package/required-pages/builder.json +0 -1
  308. package/required-pages/pages.json +0 -1
  309. package/required-pages/settings.json +0 -1
  310. package/required-pages/synthos_apis.html +0 -327
  311. package/required-pages/synthos_apis.json +0 -1
  312. package/required-pages/synthos_scripts.json +0 -1
  313. package/src/connectors/airtable/connector.json +0 -27
  314. package/src/connectors/alpha-vantage/connector.json +0 -26
  315. package/src/connectors/brave-search/connector.json +0 -26
  316. package/src/connectors/cloudinary/connector.json +0 -27
  317. package/src/connectors/deepl/connector.json +0 -28
  318. package/src/connectors/elevenlabs/connector.json +0 -30
  319. package/src/connectors/giphy/connector.json +0 -27
  320. package/src/connectors/github/connector.json +0 -29
  321. package/src/connectors/huggingface/connector.json +0 -27
  322. package/src/connectors/imgur/connector.json +0 -29
  323. package/src/connectors/instagram/connector.json +0 -43
  324. package/src/connectors/jira/connector.json +0 -28
  325. package/src/connectors/mapbox/connector.json +0 -26
  326. package/src/connectors/nasa/connector.json +0 -27
  327. package/src/connectors/newsapi/connector.json +0 -27
  328. package/src/connectors/notion/connector.json +0 -28
  329. package/src/connectors/open-exchange-rates/connector.json +0 -27
  330. package/src/connectors/openweathermap/connector.json +0 -26
  331. package/src/connectors/pexels/connector.json +0 -27
  332. package/src/connectors/resend/connector.json +0 -29
  333. package/src/connectors/rss2json/connector.json +0 -27
  334. package/src/connectors/sendgrid/connector.json +0 -27
  335. package/src/connectors/spoonacular/connector.json +0 -28
  336. package/src/connectors/stability-ai/connector.json +0 -27
  337. package/src/connectors/twilio/connector.json +0 -28
  338. package/src/connectors/unsplash/connector.json +0 -27
  339. package/src/connectors/wolfram-alpha/connector.json +0 -26
  340. package/src/connectors/youtube-data/connector.json +0 -30
  341. /package/{dist/connectors → service-connectors}/airtable/connector.json +0 -0
  342. /package/{dist/connectors → service-connectors}/alpha-vantage/connector.json +0 -0
  343. /package/{dist/connectors → service-connectors}/brave-search/connector.json +0 -0
  344. /package/{dist/connectors → service-connectors}/cloudinary/connector.json +0 -0
  345. /package/{dist/connectors → service-connectors}/deepl/connector.json +0 -0
  346. /package/{dist/connectors → service-connectors}/elevenlabs/connector.json +0 -0
  347. /package/{dist/connectors → service-connectors}/giphy/connector.json +0 -0
  348. /package/{dist/connectors → service-connectors}/github/connector.json +0 -0
  349. /package/{dist/connectors → service-connectors}/huggingface/connector.json +0 -0
  350. /package/{dist/connectors → service-connectors}/imgur/connector.json +0 -0
  351. /package/{dist/connectors → service-connectors}/instagram/connector.json +0 -0
  352. /package/{dist/connectors → service-connectors}/jira/connector.json +0 -0
  353. /package/{dist/connectors → service-connectors}/mapbox/connector.json +0 -0
  354. /package/{dist/connectors → service-connectors}/nasa/connector.json +0 -0
  355. /package/{dist/connectors → service-connectors}/newsapi/connector.json +0 -0
  356. /package/{dist/connectors → service-connectors}/notion/connector.json +0 -0
  357. /package/{dist/connectors → service-connectors}/open-exchange-rates/connector.json +0 -0
  358. /package/{dist/connectors → service-connectors}/openweathermap/connector.json +0 -0
  359. /package/{dist/connectors → service-connectors}/pexels/connector.json +0 -0
  360. /package/{dist/connectors → service-connectors}/resend/connector.json +0 -0
  361. /package/{dist/connectors → service-connectors}/rss2json/connector.json +0 -0
  362. /package/{dist/connectors → service-connectors}/sendgrid/connector.json +0 -0
  363. /package/{dist/connectors → service-connectors}/spoonacular/connector.json +0 -0
  364. /package/{dist/connectors → service-connectors}/stability-ai/connector.json +0 -0
  365. /package/{dist/connectors → service-connectors}/twilio/connector.json +0 -0
  366. /package/{dist/connectors → service-connectors}/unsplash/connector.json +0 -0
  367. /package/{dist/connectors → service-connectors}/wolfram-alpha/connector.json +0 -0
  368. /package/{dist/connectors → service-connectors}/youtube-data/connector.json +0 -0
@@ -5,7 +5,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.useApiRoutes = void 0;
7
7
  const path_1 = __importDefault(require("path"));
8
- const promises_1 = __importDefault(require("fs/promises"));
9
8
  const adm_zip_1 = __importDefault(require("adm-zip"));
10
9
  const pages_1 = require("../pages");
11
10
  const files_1 = require("../files");
@@ -31,10 +30,11 @@ const SERVICE_REGISTRY = [
31
30
  exclusive: 'search'
32
31
  }
33
32
  ];
34
- function useApiRoutes(config, app) {
33
+ function useApiRoutes(config, app, customizer) {
34
+ const sp = config.storageProvider;
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, config.requiredPagesFolders);
38
38
  res.json(pages);
39
39
  });
40
40
  // Import a page from a zip file
@@ -75,12 +75,12 @@ function useApiRoutes(config, app) {
75
75
  // Auto-append _1, _2, etc. on name conflicts
76
76
  let finalName = pageName;
77
77
  let suffix = 0;
78
- while (await (0, files_1.checkIfExists)(path_1.default.join(config.pagesFolder, 'pages', finalName))) {
78
+ while (await sp.checkIfExists(path_1.default.join(config.pagesFolder, 'pages', finalName))) {
79
79
  suffix++;
80
80
  finalName = `${pageName}_${suffix}`;
81
81
  }
82
82
  const targetDir = path_1.default.join(config.pagesFolder, 'pages', finalName);
83
- await (0, files_1.ensureFolderExists)(targetDir);
83
+ await sp.ensureFolderExists(targetDir);
84
84
  // Extract entries with path traversal protection
85
85
  for (const entry of entries) {
86
86
  if (entry.isDirectory)
@@ -94,12 +94,12 @@ function useApiRoutes(config, app) {
94
94
  // Path traversal — skip this entry
95
95
  continue;
96
96
  }
97
- await (0, files_1.ensureFolderExists)(path_1.default.dirname(resolvedPath));
98
- await promises_1.default.writeFile(resolvedPath, entry.getData());
97
+ await sp.ensureFolderExists(path_1.default.dirname(resolvedPath));
98
+ await sp.saveBuffer(resolvedPath, entry.getData());
99
99
  }
100
100
  // Update metadata: set createdDate and lastModified to now
101
101
  const now = new Date().toISOString();
102
- const existingMeta = await (0, pages_1.loadPageMetadata)(config.pagesFolder, finalName);
102
+ const existingMeta = await (0, pages_1.loadPageMetadata)(config, finalName);
103
103
  const metadata = {
104
104
  title: existingMeta?.title ?? '',
105
105
  categories: existingMeta?.categories ?? [],
@@ -110,7 +110,7 @@ function useApiRoutes(config, app) {
110
110
  pageVersion: existingMeta?.pageVersion ?? pages_1.PAGE_VERSION,
111
111
  mode: existingMeta?.mode ?? 'unlocked',
112
112
  };
113
- await (0, pages_1.savePageMetadata)(config.pagesFolder, finalName, metadata);
113
+ await (0, pages_1.savePageMetadata)(config, finalName, metadata);
114
114
  res.status(201).json({ name: finalName, title: metadata.title });
115
115
  }
116
116
  catch (err) {
@@ -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, 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, name, config.requiredPagesFolders);
176
176
  const metadata = {
177
177
  title: existing?.title ?? '',
178
178
  categories: existing?.categories ?? [],
@@ -199,14 +199,22 @@ function useApiRoutes(config, app) {
199
199
  // Promote required page to user folder if being unlocked/designed
200
200
  if (metadata.mode !== 'locked') {
201
201
  const userPagePath = path_1.default.join(config.pagesFolder, 'pages', name, 'page.html');
202
- if (!(await (0, files_1.checkIfExists)(userPagePath))) {
203
- const html = await (0, pages_1.loadPageState)(config.requiredPagesFolder, name, false);
202
+ if (!(await sp.checkIfExists(userPagePath))) {
203
+ // Read from required pages (package content, always local fs)
204
+ let html;
205
+ for (const folder of config.requiredPagesFolders) {
206
+ const candidate = path_1.default.join(folder, name, 'page.html');
207
+ if (await (0, files_1.checkIfExists)(candidate)) {
208
+ html = await (0, files_1.loadFile)(candidate);
209
+ break;
210
+ }
211
+ }
204
212
  if (html) {
205
- await (0, pages_1.savePageState)(config.pagesFolder, name, html);
213
+ await (0, pages_1.savePageState)(config, name, html);
206
214
  }
207
215
  }
208
216
  }
209
- await (0, pages_1.savePageMetadata)(config.pagesFolder, name, metadata);
217
+ await (0, pages_1.savePageMetadata)(config, name, metadata);
210
218
  res.json(metadata);
211
219
  }
212
220
  catch (err) {
@@ -224,7 +232,7 @@ function useApiRoutes(config, app) {
224
232
  return;
225
233
  }
226
234
  // Load existing metadata (user override → fallback .json → defaults)
227
- let metadata = await (0, pages_1.loadPageMetadata)(config.pagesFolder, name, config.requiredPagesFolder);
235
+ let metadata = await (0, pages_1.loadPageMetadata)(config, name, config.requiredPagesFolders);
228
236
  if (!metadata) {
229
237
  metadata = {
230
238
  title: '',
@@ -238,7 +246,7 @@ function useApiRoutes(config, app) {
238
246
  };
239
247
  }
240
248
  metadata.pinned = pinned;
241
- await (0, pages_1.savePageMetadata)(config.pagesFolder, name, metadata);
249
+ await (0, pages_1.savePageMetadata)(config, name, metadata);
242
250
  res.json(metadata);
243
251
  }
244
252
  catch (err) {
@@ -251,19 +259,19 @@ function useApiRoutes(config, app) {
251
259
  try {
252
260
  const { name } = req.params;
253
261
  // Cannot delete required pages
254
- if (pages_1.REQUIRED_PAGES.includes(name)) {
262
+ if (config.requiredPages.includes(name)) {
255
263
  res.status(400).json({ error: `Cannot delete required page "${name}"` });
256
264
  return;
257
265
  }
258
266
  // Check if page exists (folder-based or legacy flat file)
259
267
  const folderPath = path_1.default.join(config.pagesFolder, 'pages', name, 'page.html');
260
268
  const flatPath = path_1.default.join(config.pagesFolder, `${name}.html`);
261
- const exists = await (0, files_1.checkIfExists)(folderPath) || await (0, files_1.checkIfExists)(flatPath);
269
+ const exists = await sp.checkIfExists(folderPath) || await sp.checkIfExists(flatPath);
262
270
  if (!exists) {
263
271
  res.status(404).json({ error: `Page "${name}" not found` });
264
272
  return;
265
273
  }
266
- await (0, pages_1.deletePage)(config.pagesFolder, name);
274
+ await (0, pages_1.deletePage)(config, name);
267
275
  res.json({ deleted: true });
268
276
  }
269
277
  catch (err) {
@@ -271,11 +279,61 @@ function useApiRoutes(config, app) {
271
279
  res.status(500).send(err.message);
272
280
  }
273
281
  });
282
+ // Discover what a page contains (tables + files)
283
+ app.get('/api/pages/:name/contents', async (req, res) => {
284
+ try {
285
+ const { name } = req.params;
286
+ // Resolve page folder: user pages first, then required pages
287
+ let pageFolder;
288
+ const userFolder = path_1.default.join(config.pagesFolder, 'pages', name);
289
+ if (await sp.checkIfExists(path_1.default.join(userFolder, 'page.html'))) {
290
+ pageFolder = userFolder;
291
+ }
292
+ else {
293
+ for (const folder of config.requiredPagesFolders) {
294
+ const candidate = path_1.default.join(folder, name);
295
+ if (await (0, files_1.checkIfExists)(path_1.default.join(candidate, 'page.html'))) {
296
+ pageFolder = candidate;
297
+ break;
298
+ }
299
+ }
300
+ }
301
+ if (!pageFolder) {
302
+ res.status(404).json({ error: `Page "${name}" not found` });
303
+ return;
304
+ }
305
+ // List subdirectories, filtering out non-table entries
306
+ const EXCLUDED = new Set(['files']);
307
+ // Use provider for user folder, local fs for required pages
308
+ const isUserFolder = pageFolder === userFolder;
309
+ const subdirs = isUserFolder
310
+ ? await sp.listFolders(pageFolder)
311
+ : await (0, files_1.listFolders)(pageFolder);
312
+ const tables = subdirs.filter(d => !EXCLUDED.has(d));
313
+ // Check if files/ exists and has entries
314
+ const filesDir = path_1.default.join(pageFolder, 'files');
315
+ let hasFiles = false;
316
+ const filesDirExists = isUserFolder
317
+ ? await sp.checkIfExists(filesDir)
318
+ : await (0, files_1.checkIfExists)(filesDir);
319
+ if (filesDirExists) {
320
+ const entries = isUserFolder
321
+ ? await sp.listFiles(filesDir)
322
+ : await (0, files_1.listFiles)(filesDir);
323
+ hasFiles = entries.length > 0;
324
+ }
325
+ res.json({ tables, hasFiles });
326
+ }
327
+ catch (err) {
328
+ console.error(err);
329
+ res.status(500).json({ error: err.message });
330
+ }
331
+ });
274
332
  // Copy a page to a new name
275
333
  app.post('/api/pages/:name/copy', async (req, res) => {
276
334
  try {
277
335
  const sourceName = req.params.name;
278
- const { name: targetName, title, categories } = req.body;
336
+ const { name: targetName, title, categories, copyTables, copyFiles } = req.body;
279
337
  // Validate target name
280
338
  if (!targetName || typeof targetName !== 'string') {
281
339
  res.status(400).json({ error: 'name is required' });
@@ -293,10 +351,17 @@ function useApiRoutes(config, app) {
293
351
  // Check source exists (user pages → required pages)
294
352
  const sourceFolderPath = path_1.default.join(config.pagesFolder, 'pages', sourceName, 'page.html');
295
353
  const sourceFlatPath = path_1.default.join(config.pagesFolder, `${sourceName}.html`);
296
- const sourceRequiredPath = path_1.default.join(config.requiredPagesFolder, `${sourceName}.html`);
297
- const sourceExists = await (0, files_1.checkIfExists)(sourceFolderPath)
298
- || await (0, files_1.checkIfExists)(sourceFlatPath)
299
- || await (0, files_1.checkIfExists)(sourceRequiredPath);
354
+ let sourceRequiredPath;
355
+ for (const folder of config.requiredPagesFolders) {
356
+ const candidate = path_1.default.join(folder, sourceName, 'page.html');
357
+ if (await (0, files_1.checkIfExists)(candidate)) {
358
+ sourceRequiredPath = candidate;
359
+ break;
360
+ }
361
+ }
362
+ const sourceExists = await sp.checkIfExists(sourceFolderPath)
363
+ || await sp.checkIfExists(sourceFlatPath)
364
+ || !!sourceRequiredPath;
300
365
  if (!sourceExists) {
301
366
  res.status(404).json({ error: `Source page "${sourceName}" not found` });
302
367
  return;
@@ -304,13 +369,16 @@ function useApiRoutes(config, app) {
304
369
  // Check target doesn't already exist
305
370
  const targetFolderPath = path_1.default.join(config.pagesFolder, 'pages', targetName, 'page.html');
306
371
  const targetFlatPath = path_1.default.join(config.pagesFolder, `${targetName}.html`);
307
- if (await (0, files_1.checkIfExists)(targetFolderPath) || await (0, files_1.checkIfExists)(targetFlatPath)) {
372
+ if (await sp.checkIfExists(targetFolderPath) || await sp.checkIfExists(targetFlatPath)) {
308
373
  res.status(409).json({ error: `Page "${targetName}" already exists` });
309
374
  return;
310
375
  }
311
- await (0, pages_1.copyPage)(config.pagesFolder, sourceName, targetName, typeof title === 'string' ? title : '', Array.isArray(categories) ? categories : [], config.requiredPagesFolder);
376
+ await (0, pages_1.copyPage)(config, sourceName, targetName, typeof title === 'string' ? title : '', Array.isArray(categories) ? categories : [], config.requiredPagesFolders, {
377
+ copyTables: copyTables === true,
378
+ copyFiles: copyFiles !== false, // default true
379
+ });
312
380
  // Return the new page metadata
313
- const metadata = await (0, pages_1.loadPageMetadata)(config.pagesFolder, targetName);
381
+ const metadata = await (0, pages_1.loadPageMetadata)(config, targetName);
314
382
  res.status(201).json({ name: targetName, ...metadata });
315
383
  }
316
384
  catch (err) {
@@ -320,7 +388,7 @@ function useApiRoutes(config, app) {
320
388
  });
321
389
  // Define a route to return settings
322
390
  app.get('/api/settings', async (req, res) => {
323
- const settings = await (0, settings_1.loadSettings)(config.pagesFolder);
391
+ const settings = await (0, settings_1.loadSettings)(config);
324
392
  const providers = createCompletePrompt_1.PROVIDERS.map(p => ({ name: p.name, builderModels: p.builderModels, chatModels: p.chatModels }));
325
393
  res.json({ ...settings, providers });
326
394
  });
@@ -339,7 +407,7 @@ function useApiRoutes(config, app) {
339
407
  }
340
408
  }
341
409
  // Save settings
342
- await (0, settings_1.saveSettings)(config.pagesFolder, settings);
410
+ await (0, settings_1.saveSettings)(config, settings);
343
411
  res.redirect('/builder');
344
412
  }
345
413
  catch (err) {
@@ -349,7 +417,7 @@ function useApiRoutes(config, app) {
349
417
  });
350
418
  // Define a route to generate an image
351
419
  app.post('/api/generate/image', async (req, res) => {
352
- await (0, requiresSettings_1.requiresSettings)(res, config.pagesFolder, async (settings) => {
420
+ await (0, requiresSettings_1.requiresSettings)(res, config, async (settings) => {
353
421
  const { prompt, shape, style } = req.body;
354
422
  const builder = (0, settings_1.getModelEntry)(settings, 'builder');
355
423
  const { configuration, imageQuality, provider } = builder;
@@ -366,9 +434,9 @@ function useApiRoutes(config, app) {
366
434
  });
367
435
  // Define a route to generate a completion using chain-of-thought
368
436
  app.post('/api/generate/completion', async (req, res) => {
369
- await (0, requiresSettings_1.requiresSettings)(res, config.pagesFolder, async (settings) => {
437
+ await (0, requiresSettings_1.requiresSettings)(res, config, async (settings) => {
370
438
  const { prompt, temperature } = req.body;
371
- const completePrompt = await (0, createCompletePrompt_1.createCompletePrompt)(config.pagesFolder, 'chat', req.body.model);
439
+ const completePrompt = await (0, createCompletePrompt_1.createCompletePrompt)(config, 'chat', req.body.model);
372
440
  const response = await (0, models_1.chainOfThought)({ question: prompt, temperature, completePrompt });
373
441
  if (response.completed) {
374
442
  res.json(response.value ?? {});
@@ -380,15 +448,17 @@ function useApiRoutes(config, app) {
380
448
  });
381
449
  });
382
450
  // 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.
451
+ if (!customizer || customizer.isEnabled('brainstorm'))
452
+ app.post('/api/brainstorm', async (req, res) => {
453
+ await (0, requiresSettings_1.requiresSettings)(res, config, async (settings) => {
454
+ const { context, messages } = req.body;
455
+ const completePrompt = await (0, createCompletePrompt_1.createCompletePrompt)(config, 'chat');
456
+ const productName = customizer?.productName ?? 'SynthOS';
457
+ const system = {
458
+ role: 'system',
459
+ content: `You are a creative brainstorming assistant for ${productName}, a tool that builds pages through conversation.
460
+ ${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.
461
+ The user is brainstorming — exploring ideas before building. Be concise, creative, and collaborative.
392
462
  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
463
  The goal is to help them generate a prompt for the builder that captures their vision, along with suggestions for next steps.
394
464
  Suggest concrete approaches when you can, not complex visions for some ellaborate app.
@@ -399,85 +469,91 @@ ${context}
399
469
 
400
470
  <INSTRUCTIONS>
401
471
  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.
472
+ 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
473
 
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.
474
+ ${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
475
 
406
476
  You MUST return your response as a JSON object with exactly these fields:
407
477
  {
408
478
  "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.",
479
+ "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
480
  "suggestions": ["Short clickable option A", "Short clickable option B", "Short clickable option C"]
411
481
  }
412
482
 
413
483
  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
484
 
415
485
  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');
486
+ };
487
+ // Format multi-turn conversation into a single prompt
488
+ const formatted = messages.map(m => `${m.role === 'user' ? 'User' : 'Assistant'}: ${m.content}`).join('\n\n');
489
+ const prompt = { role: 'user', content: formatted };
490
+ const result = await completePrompt({ prompt, system, jsonMode: true });
491
+ if (result.completed) {
492
+ let response = result.value || '';
493
+ let brainstormPrompt = '';
494
+ let suggestions = [];
495
+ // jsonMode returns an already-parsed object from agentm-core
496
+ const parsed = (typeof result.value === 'object' && result.value !== null)
497
+ ? result.value
498
+ : (() => { try {
499
+ return JSON.parse(result.value);
500
+ }
501
+ catch {
502
+ return null;
503
+ } })();
504
+ if (parsed) {
505
+ if (typeof parsed.response === 'string')
506
+ response = parsed.response;
507
+ if (typeof parsed.prompt === 'string')
508
+ brainstormPrompt = parsed.prompt;
509
+ if (Array.isArray(parsed.suggestions)) {
510
+ suggestions = parsed.suggestions.filter((s) => typeof s === 'string');
511
+ }
441
512
  }
513
+ res.json({ response, prompt: brainstormPrompt, suggestions });
442
514
  }
443
- res.json({ response, prompt: brainstormPrompt, suggestions });
444
- }
445
- else {
446
- console.error(result.error);
447
- res.status(500).send(result.error?.message);
448
- }
515
+ else {
516
+ console.error(result.error);
517
+ res.status(500).send(result.error?.message);
518
+ }
519
+ });
449
520
  });
450
- });
451
521
  // 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
- }
522
+ if (!customizer || customizer.isEnabled('scripts'))
523
+ app.post('/api/scripts/:id', async (req, res) => {
524
+ await (0, requiresSettings_1.requiresSettings)(res, config, async (settings) => {
525
+ const { id } = req.params;
526
+ const scriptId = id;
527
+ const response = await (0, scripts_1.executeScript)({ pagesFolder: config.pagesFolder, scriptId, variables: req.body });
528
+ if (response.completed) {
529
+ // Return the response as text
530
+ const value = (response.value?.output ?? (response.value?.errors ?? []).join('\n')).trim();
531
+ res.set('Content-Type', 'text/plain');
532
+ res.send(value);
533
+ }
534
+ else {
535
+ console.error(response.error);
536
+ res.status(500).send(response.error);
537
+ }
538
+ });
468
539
  });
469
- });
470
540
  // Return theme info as a self-executing JS script
471
541
  app.get('/api/theme-info.js', async (req, res) => {
472
542
  try {
473
- const settings = await (0, settings_1.loadSettings)(config.pagesFolder);
543
+ const settings = await (0, settings_1.loadSettings)(config);
474
544
  const themeName = settings.theme ?? 'nebula-dusk';
475
545
  const info = await (0, themes_1.loadThemeInfo)(themeName, config);
476
546
  if (!info) {
477
547
  res.status(404).send(`// Theme info for "${themeName}" not found`);
478
548
  return;
479
549
  }
480
- const js = `window.themeInfo=${JSON.stringify(info)};document.documentElement.classList.add(window.themeInfo.mode+"-mode");`;
550
+ const themeVersion = await (0, themes_1.loadThemeVersion)(themeName, config);
551
+ const payload = { ...info, name: themeName, version: themeVersion };
552
+ let js = `window.themeInfo=${JSON.stringify(payload)};document.documentElement.classList.add(window.themeInfo.mode+"-mode");`;
553
+ if (themeVersion >= 3) {
554
+ js += `document.documentElement.classList.add(${JSON.stringify(themeName)});`;
555
+ }
556
+ js += `document.documentElement.setAttribute("data-toolbar",${JSON.stringify(settings.toolbarPosition || 'left')});`;
481
557
  res.set('Content-Type', 'application/javascript');
482
558
  res.send(js);
483
559
  }
@@ -494,21 +570,18 @@ Return ONLY the JSON object.`
494
570
  res.status(400).send('// Missing page query parameter');
495
571
  return;
496
572
  }
497
- const metadata = await (0, pages_1.loadPageMetadata)(config.pagesFolder, page, config.requiredPagesFolder);
573
+ const metadata = await (0, pages_1.loadPageMetadata)(config, page, config.requiredPagesFolders);
498
574
  const mode = metadata?.mode ?? 'unlocked';
499
575
  const title = metadata?.title ?? '';
500
576
  const categories = metadata?.categories ?? [];
501
- const info = JSON.stringify({ name: page, mode, latestPageVersion: pages_1.PAGE_VERSION, title, categories });
577
+ const isRequiredPage = config.requiredPages.includes(page);
578
+ const productName = customizer?.productName ?? 'SynthOS';
579
+ const info = JSON.stringify({ name: page, mode, latestPageVersion: pages_1.PAGE_VERSION, title, categories, isRequiredPage, productName });
502
580
  const js = [
503
581
  `window.pageInfo=${info};`,
504
582
  `if(window.pageInfo.mode==="locked"){`,
505
583
  `document.addEventListener("DOMContentLoaded",function(){`,
506
584
  `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
585
  `});`,
513
586
  `}`,
514
587
  ].join('');
@@ -524,7 +597,7 @@ Return ONLY the JSON object.`
524
597
  // Return the current theme as CSS
525
598
  app.get('/api/theme.css', async (req, res) => {
526
599
  try {
527
- const settings = await (0, settings_1.loadSettings)(config.pagesFolder);
600
+ const settings = await (0, settings_1.loadSettings)(config);
528
601
  const themeName = settings.theme ?? 'nebula-dusk';
529
602
  const css = await (0, themes_1.loadTheme)(themeName, config);
530
603
  if (!css) {
@@ -558,8 +631,8 @@ Return ONLY the JSON object.`
558
631
  res.status(400).send('// Invalid version parameter');
559
632
  return;
560
633
  }
561
- const scriptPath = path_1.default.join(config.pageScriptsFolder, `page-v${v}.js`);
562
- if (!(await (0, files_1.checkIfExists)(scriptPath))) {
634
+ const scriptPath = await (0, files_1.findFileInFolders)(config.staticFilesFolders, `page.v${v}.js`);
635
+ if (!scriptPath) {
563
636
  res.status(404).send(`// page-v${v}.js not found`);
564
637
  return;
565
638
  }
@@ -581,8 +654,8 @@ Return ONLY the JSON object.`
581
654
  res.status(400).send('// Invalid version parameter');
582
655
  return;
583
656
  }
584
- const scriptPath = path_1.default.join(config.pageScriptsFolder, `helpers-v${v}.js`);
585
- if (!(await (0, files_1.checkIfExists)(scriptPath))) {
657
+ const scriptPath = await (0, files_1.findFileInFolders)(config.staticFilesFolders, `helpers.v${v}.js`);
658
+ if (!scriptPath) {
586
659
  res.status(404).send(`// helpers-v${v}.js not found`);
587
660
  return;
588
661
  }
@@ -606,7 +679,7 @@ Return ONLY the JSON object.`
606
679
  // Return user's configured services (API keys masked)
607
680
  app.get('/api/services', async (_req, res) => {
608
681
  try {
609
- const settings = await (0, settings_1.loadSettings)(config.pagesFolder);
682
+ const settings = await (0, settings_1.loadSettings)(config);
610
683
  const services = settings.services ?? {};
611
684
  const masked = {};
612
685
  for (const [id, cfg] of Object.entries(services)) {
@@ -626,7 +699,7 @@ Return ONLY the JSON object.`
626
699
  app.post('/api/services', async (req, res) => {
627
700
  try {
628
701
  const incoming = req.body;
629
- const settings = await (0, settings_1.loadSettings)(config.pagesFolder);
702
+ const settings = await (0, settings_1.loadSettings)(config);
630
703
  const existing = settings.services ?? {};
631
704
  // Build merged config — empty apiKey means "keep existing"
632
705
  const merged = {};
@@ -646,7 +719,7 @@ Return ONLY the JSON object.`
646
719
  }
647
720
  }
648
721
  }
649
- await (0, settings_1.saveSettings)(config.pagesFolder, { services: merged });
722
+ await (0, settings_1.saveSettings)(config, { services: merged });
650
723
  res.json({ saved: true });
651
724
  }
652
725
  catch (err) {
@@ -657,57 +730,58 @@ Return ONLY the JSON object.`
657
730
  // -----------------------------------------------------------------------
658
731
  // Web Search (Brave Search API)
659
732
  // -----------------------------------------------------------------------
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
733
+ if (!customizer || customizer.isEnabled('search'))
734
+ app.post('/api/search/web', async (req, res) => {
735
+ try {
736
+ const { query, count, country, freshness } = req.body;
737
+ if (!query || typeof query !== 'string') {
738
+ res.status(400).json({ error: 'query is required' });
739
+ return;
685
740
  }
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;
741
+ const settings = await (0, settings_1.loadSettings)(config);
742
+ const braveConfig = settings.connectors?.['brave-search'] ?? settings.services?.['brave-search'];
743
+ if (!braveConfig || !braveConfig.enabled || !braveConfig.apiKey) {
744
+ res.status(400).json({ error: 'Brave Search is not configured or not enabled. Add your API key in Settings > Services.' });
745
+ return;
746
+ }
747
+ const params = new URLSearchParams({ q: query });
748
+ if (count)
749
+ params.set('count', String(Math.min(Number(count) || 5, 20)));
750
+ if (country)
751
+ params.set('country', country);
752
+ if (freshness)
753
+ params.set('freshness', freshness);
754
+ const response = await fetch(`https://api.search.brave.com/res/v1/web/search?${params.toString()}`, {
755
+ headers: {
756
+ 'Accept': 'application/json',
757
+ 'Accept-Encoding': 'gzip',
758
+ 'X-Subscription-Token': braveConfig.apiKey
759
+ }
760
+ });
761
+ if (!response.ok) {
762
+ const text = await response.text();
763
+ res.status(response.status).json({ error: `Brave Search API error: ${text}` });
764
+ return;
765
+ }
766
+ const data = await response.json();
767
+ const results = (data.web?.results ?? []).map(r => ({
768
+ title: r.title,
769
+ url: r.url,
770
+ description: r.description
771
+ }));
772
+ res.json({ results });
773
+ }
774
+ catch (err) {
775
+ console.error(err);
776
+ res.status(500).json({ error: err.message });
691
777
  }
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
- });
778
+ });
705
779
  // Upgrade a page to the latest version
706
780
  app.post('/api/pages/:name/upgrade', async (req, res) => {
707
781
  try {
708
782
  const { name } = req.params;
709
783
  // Load current metadata
710
- const metadata = await (0, pages_1.loadPageMetadata)(config.pagesFolder, name, config.requiredPagesFolder);
784
+ const metadata = await (0, pages_1.loadPageMetadata)(config, name, config.requiredPagesFolders);
711
785
  if (!metadata) {
712
786
  res.status(404).json({ error: `Page "${name}" not found` });
713
787
  return;
@@ -724,27 +798,31 @@ Return ONLY the JSON object.`
724
798
  return;
725
799
  }
726
800
  // Run LLM-based migration
727
- const completePrompt = await (0, createCompletePrompt_1.createCompletePrompt)(config.pagesFolder, 'builder');
801
+ const completePrompt = await (0, createCompletePrompt_1.createCompletePrompt)(config, 'builder');
728
802
  const migratedHtml = await (0, migrations_1.migratePage)(html, currentVersion, pages_1.PAGE_VERSION, completePrompt);
729
803
  // Save upgraded HTML to v2 folder structure
730
- await (0, pages_1.savePageState)(config.pagesFolder, name, migratedHtml);
804
+ await (0, pages_1.savePageState)(config, name, migratedHtml);
731
805
  // Backup original page to .migrated/ before overwriting
732
806
  const migratedFolder = path_1.default.join(config.pagesFolder, '.migrated');
733
- // Handle legacy flat file (.synthos/pagename.html)
807
+ // Handle legacy flat file (<localFolder>/pagename.html)
734
808
  const flatPath = path_1.default.join(config.pagesFolder, `${name}.html`);
735
- if (await (0, files_1.checkIfExists)(flatPath)) {
736
- await (0, files_1.copyFile)(flatPath, migratedFolder);
737
- await (0, files_1.deleteFile)(flatPath);
809
+ if (await sp.checkIfExists(flatPath)) {
810
+ await sp.ensureFolderExists(migratedFolder);
811
+ const flatData = await sp.loadBuffer(flatPath);
812
+ await sp.saveBuffer(path_1.default.join(migratedFolder, `${name}.html`), flatData);
813
+ await sp.deleteFile(flatPath);
738
814
  }
739
- // Handle folder-based page (.synthos/pages/name/)
815
+ // Handle folder-based page (<localFolder>/pages/name/)
740
816
  const folderPath = path_1.default.join(config.pagesFolder, 'pages', name);
741
- if (await (0, files_1.checkIfExists)(folderPath)) {
742
- await (0, files_1.copyFolderRecursive)(folderPath, path_1.default.join(migratedFolder, name));
817
+ if (await sp.checkIfExists(folderPath)) {
818
+ await sp.copyFolderRecursive(folderPath, path_1.default.join(migratedFolder, name));
743
819
  }
820
+ // Clear stale version files (undo snapshots from the old page version)
821
+ await (0, pages_1.clearVersions)(config, name);
744
822
  // Update metadata
745
823
  metadata.pageVersion = pages_1.PAGE_VERSION;
746
824
  metadata.lastModified = new Date().toISOString();
747
- await (0, pages_1.savePageMetadata)(config.pagesFolder, name, metadata);
825
+ await (0, pages_1.savePageMetadata)(config, name, metadata);
748
826
  res.json({ upgraded: true, fromVersion: currentVersion, toVersion: pages_1.PAGE_VERSION });
749
827
  }
750
828
  catch (err) {
@@ -758,20 +836,26 @@ Return ONLY the JSON object.`
758
836
  const { name } = req.params;
759
837
  // Try user pages folder first, then required pages
760
838
  const userPageDir = path_1.default.join(config.pagesFolder, 'pages', name);
761
- const requiredPageFile = path_1.default.join(config.requiredPagesFolder, `${name}.html`);
839
+ let requiredPageDir;
840
+ for (const folder of config.requiredPagesFolders) {
841
+ if (await (0, files_1.checkIfExists)(path_1.default.join(folder, name, 'page.html'))) { // package content, local fs
842
+ requiredPageDir = path_1.default.join(folder, name);
843
+ break;
844
+ }
845
+ }
762
846
  let sourceDir = null;
763
- if (await (0, files_1.checkIfExists)(path_1.default.join(userPageDir, 'page.html'))) {
847
+ if (await sp.checkIfExists(path_1.default.join(userPageDir, 'page.html'))) {
764
848
  sourceDir = userPageDir;
765
849
  }
766
- else if (await (0, files_1.checkIfExists)(requiredPageFile)) {
850
+ else if (requiredPageDir) {
767
851
  // For required pages, create a temp-like zip with just the HTML
768
852
  const zip = new adm_zip_1.default();
769
- const html = await (0, files_1.loadFile)(requiredPageFile);
853
+ const html = await (0, files_1.loadFile)(path_1.default.join(requiredPageDir, 'page.html'));
770
854
  zip.addFile(`${name}/page.html`, Buffer.from(html, 'utf-8'));
771
855
  // 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);
856
+ const metaPath = path_1.default.join(requiredPageDir, 'page.json');
857
+ if (await (0, files_1.checkIfExists)(metaPath)) {
858
+ const meta = await (0, files_1.loadFile)(metaPath);
775
859
  zip.addFile(`${name}/page.json`, Buffer.from(meta, 'utf-8'));
776
860
  }
777
861
  const zipBuffer = zip.toBuffer();
@@ -797,6 +881,37 @@ Return ONLY the JSON object.`
797
881
  res.status(500).json({ error: err.message });
798
882
  }
799
883
  });
884
+ // Ask a question about a page (with full page HTML context)
885
+ app.post('/api/pages/:name/ask', async (req, res) => {
886
+ await (0, requiresSettings_1.requiresSettings)(res, config, async (settings) => {
887
+ const { name } = req.params;
888
+ const { question } = req.body;
889
+ if (typeof question !== 'string' || !question.trim()) {
890
+ res.status(400).json({ error: 'question is required' });
891
+ return;
892
+ }
893
+ // Load the page HTML
894
+ const html = await (0, usePageRoutes_1.loadPageWithFallback)(name, config, false);
895
+ if (!html) {
896
+ res.status(404).json({ error: `Page "${name}" not found` });
897
+ return;
898
+ }
899
+ // Create completion (uses 'chat' model, not 'builder')
900
+ const complete = await (0, createCompletePrompt_1.createCompletePrompt)(config, 'chat', req.body.model);
901
+ const system = {
902
+ role: 'system',
903
+ 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}`
904
+ };
905
+ const prompt = { role: 'user', content: question };
906
+ const result = await complete({ system, prompt });
907
+ if (result.completed) {
908
+ res.json({ answer: result.value });
909
+ }
910
+ else {
911
+ res.status(500).json({ error: result.error?.message || 'Completion failed' });
912
+ }
913
+ });
914
+ });
800
915
  }
801
916
  exports.useApiRoutes = useApiRoutes;
802
917
  //# sourceMappingURL=useApiRoutes.js.map