synthos 0.10.0 → 0.11.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 (312) hide show
  1. package/README.md +5 -5
  2. package/default-pages/elevenlabs_effects_studio/chat-history.json +1 -0
  3. package/default-pages/elevenlabs_effects_studio/page.html +1345 -1363
  4. package/default-pages/elevenlabs_effects_studio/page.json +13 -11
  5. package/default-pages/elevenlabs_voice_studio/chat-history.json +1 -0
  6. package/default-pages/elevenlabs_voice_studio/page.html +782 -801
  7. package/default-pages/elevenlabs_voice_studio/page.json +13 -11
  8. package/default-pages/json_tools/chat-history.json +1 -0
  9. package/default-pages/json_tools/page.html +70 -90
  10. package/default-pages/json_tools/page.json +12 -10
  11. package/default-pages/my_notes/chat-history.json +1 -0
  12. package/default-pages/my_notes/page.html +115 -131
  13. package/default-pages/my_notes/page.json +14 -12
  14. package/default-pages/neon_asteroids/chat-history.json +1 -0
  15. package/default-pages/neon_asteroids/page.html +1777 -1803
  16. package/default-pages/neon_asteroids/page.json +14 -12
  17. package/default-pages/oregon_trail/chat-history.json +1 -0
  18. package/default-pages/oregon_trail/page.html +290 -307
  19. package/default-pages/oregon_trail/page.json +14 -12
  20. package/default-pages/solar_explorer/chat-history.json +1 -0
  21. package/default-pages/solar_explorer/page.html +1929 -1951
  22. package/default-pages/solar_explorer/page.json +14 -12
  23. package/default-pages/solar_tutorial/chat-history.json +1 -0
  24. package/default-pages/solar_tutorial/page.html +464 -478
  25. package/default-pages/solar_tutorial/page.json +12 -10
  26. package/default-pages/us_map/chat-history.json +1 -0
  27. package/default-pages/us_map/page.html +170 -193
  28. package/default-pages/us_map/page.json +14 -12
  29. package/default-pages/us_map/page.light.png +0 -0
  30. package/default-pages/us_map_1850/chat-history.json +1 -0
  31. package/default-pages/us_map_1850/page.html +302 -326
  32. package/default-pages/us_map_1850/page.json +14 -12
  33. package/default-pages/western_cities_1850/chat-history.json +1 -0
  34. package/default-pages/western_cities_1850/page.html +503 -527
  35. package/default-pages/western_cities_1850/page.json +14 -12
  36. package/default-themes/aurora-dawn.v3.css +15 -14
  37. package/default-themes/aurora-dusk.v3.css +26 -26
  38. package/default-themes/cosmos-dawn.v3.css +15 -14
  39. package/default-themes/cosmos-dusk.v3.css +26 -26
  40. package/default-themes/elemental-dawn.v3.css +200 -0
  41. package/default-themes/nebula-dawn.v3.css +15 -14
  42. package/default-themes/nebula-dusk.v3.css +24 -24
  43. package/default-themes/solar-flare-dawn.v3.css +15 -14
  44. package/default-themes/solar-flare-dusk.v3.css +26 -26
  45. package/dist/builders/anthropic.d.ts +26 -2
  46. package/dist/builders/anthropic.d.ts.map +1 -1
  47. package/dist/builders/anthropic.js +132 -31
  48. package/dist/builders/anthropic.js.map +1 -1
  49. package/dist/builders/claudecode.d.ts +13 -0
  50. package/dist/builders/claudecode.d.ts.map +1 -0
  51. package/dist/builders/claudecode.js +253 -0
  52. package/dist/builders/claudecode.js.map +1 -0
  53. package/dist/builders/index.d.ts +2 -1
  54. package/dist/builders/index.d.ts.map +1 -1
  55. package/dist/builders/index.js +8 -1
  56. package/dist/builders/index.js.map +1 -1
  57. package/dist/builders/openai.js +2 -1
  58. package/dist/builders/openai.js.map +1 -1
  59. package/dist/builders/types.d.ts +31 -7
  60. package/dist/builders/types.d.ts.map +1 -1
  61. package/dist/builders/types.js +60 -28
  62. package/dist/builders/types.js.map +1 -1
  63. package/dist/connectors/types.d.ts +8 -0
  64. package/dist/connectors/types.d.ts.map +1 -1
  65. package/dist/init.d.ts.map +1 -1
  66. package/dist/init.js +13 -6
  67. package/dist/init.js.map +1 -1
  68. package/dist/migrations.d.ts.map +1 -1
  69. package/dist/migrations.js +161 -14
  70. package/dist/migrations.js.map +1 -1
  71. package/dist/models/anthropic.d.ts +1 -0
  72. package/dist/models/anthropic.d.ts.map +1 -1
  73. package/dist/models/anthropic.js +129 -29
  74. package/dist/models/anthropic.js.map +1 -1
  75. package/dist/models/chainOfThought.d.ts.map +1 -1
  76. package/dist/models/chainOfThought.js +32 -19
  77. package/dist/models/chainOfThought.js.map +1 -1
  78. package/dist/models/index.d.ts +2 -2
  79. package/dist/models/index.d.ts.map +1 -1
  80. package/dist/models/index.js +2 -1
  81. package/dist/models/index.js.map +1 -1
  82. package/dist/models/providers.d.ts +1 -0
  83. package/dist/models/providers.d.ts.map +1 -1
  84. package/dist/models/providers.js +12 -4
  85. package/dist/models/providers.js.map +1 -1
  86. package/dist/models/types.d.ts +15 -1
  87. package/dist/models/types.d.ts.map +1 -1
  88. package/dist/models/types.js.map +1 -1
  89. package/dist/pages.d.ts +57 -8
  90. package/dist/pages.d.ts.map +1 -1
  91. package/dist/pages.js +258 -45
  92. package/dist/pages.js.map +1 -1
  93. package/dist/service/createCompletePrompt.d.ts.map +1 -1
  94. package/dist/service/createCompletePrompt.js +5 -0
  95. package/dist/service/createCompletePrompt.js.map +1 -1
  96. package/dist/service/mediaCache.d.ts +36 -0
  97. package/dist/service/mediaCache.d.ts.map +1 -0
  98. package/dist/service/mediaCache.js +182 -0
  99. package/dist/service/mediaCache.js.map +1 -0
  100. package/dist/service/pageValidator.d.ts +25 -0
  101. package/dist/service/pageValidator.d.ts.map +1 -0
  102. package/dist/service/pageValidator.js +315 -0
  103. package/dist/service/pageValidator.js.map +1 -0
  104. package/dist/service/server.d.ts.map +1 -1
  105. package/dist/service/server.js +4 -0
  106. package/dist/service/server.js.map +1 -1
  107. package/dist/service/sharedTableSchema.d.ts +73 -0
  108. package/dist/service/sharedTableSchema.d.ts.map +1 -0
  109. package/dist/service/sharedTableSchema.js +206 -0
  110. package/dist/service/sharedTableSchema.js.map +1 -0
  111. package/dist/service/transformPage.d.ts +49 -11
  112. package/dist/service/transformPage.d.ts.map +1 -1
  113. package/dist/service/transformPage.js +354 -241
  114. package/dist/service/transformPage.js.map +1 -1
  115. package/dist/service/useApiRoutes.d.ts.map +1 -1
  116. package/dist/service/useApiRoutes.js +288 -34
  117. package/dist/service/useApiRoutes.js.map +1 -1
  118. package/dist/service/useConnectorRoutes.d.ts.map +1 -1
  119. package/dist/service/useConnectorRoutes.js +170 -32
  120. package/dist/service/useConnectorRoutes.js.map +1 -1
  121. package/dist/service/useDataRoutes.d.ts.map +1 -1
  122. package/dist/service/useDataRoutes.js +59 -2
  123. package/dist/service/useDataRoutes.js.map +1 -1
  124. package/dist/service/useExtractRoutes.d.ts +4 -0
  125. package/dist/service/useExtractRoutes.d.ts.map +1 -0
  126. package/dist/service/useExtractRoutes.js +304 -0
  127. package/dist/service/useExtractRoutes.js.map +1 -0
  128. package/dist/service/usePageRoutes.d.ts +17 -0
  129. package/dist/service/usePageRoutes.d.ts.map +1 -1
  130. package/dist/service/usePageRoutes.js +1385 -483
  131. package/dist/service/usePageRoutes.js.map +1 -1
  132. package/dist/service/useSharedDataRoutes.d.ts.map +1 -1
  133. package/dist/service/useSharedDataRoutes.js +54 -2
  134. package/dist/service/useSharedDataRoutes.js.map +1 -1
  135. package/dist/settings.d.ts +27 -0
  136. package/dist/settings.d.ts.map +1 -1
  137. package/dist/settings.js +40 -1
  138. package/dist/settings.js.map +1 -1
  139. package/dist/themes.d.ts +0 -5
  140. package/dist/themes.d.ts.map +1 -1
  141. package/dist/themes.js +3 -95
  142. package/dist/themes.js.map +1 -1
  143. package/migration-rules/v2-to-v3.md +277 -119
  144. package/package.json +5 -1
  145. package/{default-pages/application → required-pages/_shell}/page.html +56 -42
  146. package/required-pages/_shell/page.json +14 -0
  147. package/required-pages/_starters/page.html +534 -0
  148. package/required-pages/_starters/page.json +12 -0
  149. package/required-pages/builder/page.html +353 -43
  150. package/required-pages/builder/page.json +12 -10
  151. package/required-pages/pages/page.html +697 -924
  152. package/required-pages/pages/page.json +12 -10
  153. package/required-pages/settings/page.html +1879 -1753
  154. package/required-pages/settings/page.json +12 -10
  155. package/required-pages/synthos_apis/page.html +834 -845
  156. package/required-pages/synthos_apis/page.json +12 -10
  157. package/required-pages/synthos_scripts/page.html +74 -88
  158. package/required-pages/synthos_scripts/page.json +12 -10
  159. package/scripts/append-instructions.py +90 -0
  160. package/scripts/audit-instructions.py +76 -0
  161. package/scripts/cleanup-shell-markup.mjs +112 -0
  162. package/service-connectors/buffer/connector.json +46 -0
  163. package/service-connectors/canva/connector.json +67 -0
  164. package/service-connectors/elevenlabs/connector.json +1 -1
  165. package/src/builders/anthropic.ts +155 -30
  166. package/src/builders/claudecode.ts +310 -0
  167. package/src/builders/index.ts +7 -1
  168. package/src/builders/openai.ts +2 -1
  169. package/src/builders/types.ts +93 -32
  170. package/src/connectors/types.ts +8 -0
  171. package/src/init.ts +13 -7
  172. package/src/migrations.ts +187 -16
  173. package/src/models/anthropic.ts +140 -30
  174. package/src/models/chainOfThought.ts +33 -18
  175. package/src/models/index.ts +2 -2
  176. package/src/models/providers.ts +12 -3
  177. package/src/models/types.ts +21 -1
  178. package/src/pages.ts +271 -35
  179. package/src/service/createCompletePrompt.ts +6 -0
  180. package/src/service/mediaCache.ts +206 -0
  181. package/src/service/pageValidator.ts +337 -0
  182. package/src/service/server.ts +4 -0
  183. package/src/service/sharedTableSchema.ts +236 -0
  184. package/src/service/transformPage.ts +370 -260
  185. package/src/service/useApiRoutes.ts +282 -32
  186. package/src/service/useConnectorRoutes.ts +189 -34
  187. package/src/service/useDataRoutes.ts +198 -116
  188. package/src/service/useExtractRoutes.ts +331 -0
  189. package/src/service/usePageRoutes.ts +1411 -394
  190. package/src/service/useSharedDataRoutes.ts +184 -109
  191. package/src/settings.ts +65 -0
  192. package/src/themes.ts +78 -180
  193. package/starters/blank_starter/chat-history.json +1 -0
  194. package/starters/blank_starter/page.dark.png +0 -0
  195. package/starters/blank_starter/page.html +47 -0
  196. package/starters/blank_starter/page.json +13 -0
  197. package/starters/blank_starter/page.light.png +0 -0
  198. package/starters/calculator_starter/chat-history.json +1 -0
  199. package/starters/calculator_starter/page.dark.png +0 -0
  200. package/starters/calculator_starter/page.html +232 -0
  201. package/starters/calculator_starter/page.json +13 -0
  202. package/starters/calculator_starter/page.light.png +0 -0
  203. package/starters/calendar_starter/chat-history.json +1 -0
  204. package/starters/calendar_starter/page.dark.png +0 -0
  205. package/starters/calendar_starter/page.html +495 -0
  206. package/starters/calendar_starter/page.json +13 -0
  207. package/starters/calendar_starter/page.light.png +0 -0
  208. package/starters/chat_starter/chat-history.json +1 -0
  209. package/starters/chat_starter/page.dark.png +0 -0
  210. package/starters/chat_starter/page.html +351 -0
  211. package/starters/chat_starter/page.json +13 -0
  212. package/starters/chat_starter/page.light.png +0 -0
  213. package/starters/checklist_starter/chat-history.json +1 -0
  214. package/starters/checklist_starter/page.dark.png +0 -0
  215. package/starters/checklist_starter/page.html +437 -0
  216. package/starters/checklist_starter/page.json +13 -0
  217. package/starters/checklist_starter/page.light.png +0 -0
  218. package/starters/dashboard_starter/chat-history.json +1 -0
  219. package/starters/dashboard_starter/page.dark.png +0 -0
  220. package/starters/dashboard_starter/page.html +195 -0
  221. package/starters/dashboard_starter/page.json +13 -0
  222. package/starters/dashboard_starter/page.light.png +0 -0
  223. package/starters/form_starter/chat-history.json +1 -0
  224. package/starters/form_starter/page.dark.png +0 -0
  225. package/starters/form_starter/page.html +313 -0
  226. package/starters/form_starter/page.json +13 -0
  227. package/starters/form_starter/page.light.png +0 -0
  228. package/starters/gallery_starter/chat-history.json +1 -0
  229. package/starters/gallery_starter/page.dark.png +0 -0
  230. package/starters/gallery_starter/page.html +418 -0
  231. package/starters/gallery_starter/page.json +13 -0
  232. package/starters/gallery_starter/page.light.png +0 -0
  233. package/starters/generator_starter/chat-history.json +1 -0
  234. package/starters/generator_starter/page.dark.png +0 -0
  235. package/starters/generator_starter/page.html +261 -0
  236. package/starters/generator_starter/page.json +13 -0
  237. package/starters/generator_starter/page.light.png +0 -0
  238. package/starters/index.html +538 -0
  239. package/starters/kanban_starter/chat-history.json +1 -0
  240. package/starters/kanban_starter/page.dark.png +0 -0
  241. package/starters/kanban_starter/page.html +432 -0
  242. package/starters/kanban_starter/page.json +13 -0
  243. package/starters/kanban_starter/page.light.png +0 -0
  244. package/starters/presentation_builder/chat-history.json +1 -0
  245. package/starters/presentation_builder/page.dark.png +0 -0
  246. package/starters/presentation_builder/page.html +970 -0
  247. package/starters/presentation_builder/page.json +15 -0
  248. package/starters/presentation_builder/page.light.png +0 -0
  249. package/starters/presentation_builder/presentation_voice/voice_config.json +9 -0
  250. package/starters/pulse_starter/chat-history.json +1 -0
  251. package/starters/pulse_starter/page.dark.png +0 -0
  252. package/starters/pulse_starter/page.html +698 -0
  253. package/starters/pulse_starter/page.json +13 -0
  254. package/starters/pulse_starter/page.light.png +0 -0
  255. package/starters/quiz_starter/chat-history.json +1 -0
  256. package/starters/quiz_starter/page.dark.png +0 -0
  257. package/starters/quiz_starter/page.html +292 -0
  258. package/starters/quiz_starter/page.json +13 -0
  259. package/starters/quiz_starter/page.light.png +0 -0
  260. package/starters/reference_starter/chat-history.json +1 -0
  261. package/starters/reference_starter/page.dark.png +0 -0
  262. package/starters/reference_starter/page.html +250 -0
  263. package/starters/reference_starter/page.json +13 -0
  264. package/starters/reference_starter/page.light.png +0 -0
  265. package/starters/retro_game_starter/chat-history.json +1 -0
  266. package/starters/retro_game_starter/page.dark.png +0 -0
  267. package/{default-pages → starters}/retro_game_starter/page.html +1281 -1308
  268. package/starters/retro_game_starter/page.json +15 -0
  269. package/starters/retro_game_starter/page.light.png +0 -0
  270. package/starters/roster_starter/chat-history.json +1 -0
  271. package/starters/roster_starter/page.dark.png +0 -0
  272. package/starters/roster_starter/page.html +600 -0
  273. package/starters/roster_starter/page.json +13 -0
  274. package/starters/roster_starter/page.light.png +0 -0
  275. package/starters/server.js +182 -0
  276. package/starters/start.cmd +1 -0
  277. package/starters/timeline_starter/chat-history.json +1 -0
  278. package/starters/timeline_starter/page.dark.png +0 -0
  279. package/starters/timeline_starter/page.html +446 -0
  280. package/starters/timeline_starter/page.json +13 -0
  281. package/starters/timeline_starter/page.light.png +0 -0
  282. package/starters/tutorial_starter/chat-history.json +1 -0
  283. package/starters/tutorial_starter/page.dark.png +0 -0
  284. package/starters/tutorial_starter/page.html +283 -0
  285. package/starters/tutorial_starter/page.json +13 -0
  286. package/starters/tutorial_starter/page.light.png +0 -0
  287. package/static-files/agent.v3.js +122 -0
  288. package/static-files/connector.v3.js +48 -0
  289. package/static-files/extract.v3.js +188 -0
  290. package/static-files/helpers.v3.js +50 -6
  291. package/static-files/page-bridge.js +114 -0
  292. package/static-files/page.v3.js +1292 -1290
  293. package/static-files/script.v3.js +32 -0
  294. package/static-files/server.v3.js +89 -0
  295. package/static-files/shell-bridge.v3.js +174 -0
  296. package/static-files/shell-modals.v3.js +521 -0
  297. package/static-files/{shell.css → shell.v3.css} +271 -22
  298. package/static-files/shell.v3.js +1865 -0
  299. package/static-files/storage.v3.js +176 -0
  300. package/tests/anthropic.spec.ts +42 -7
  301. package/tests/builders.spec.ts +72 -4
  302. package/tests/pageValidator.spec.ts +548 -0
  303. package/tests/profiles.spec.ts +122 -0
  304. package/tests/providers.spec.ts +1 -1
  305. package/tests/sharedTableSchema.spec.ts +242 -0
  306. package/tests/transformPage.spec.ts +62 -81
  307. package/default-pages/application/page.json +0 -10
  308. package/default-pages/retro_game_starter/page.json +0 -12
  309. package/default-pages/sidebar_page/page.html +0 -51
  310. package/default-pages/sidebar_page/page.json +0 -10
  311. package/default-pages/two-panel_page/page.html +0 -68
  312. package/default-pages/two-panel_page/page.json +0 -10
@@ -1,109 +1,184 @@
1
- import { Application, Response } from 'express';
2
- import { SynthOSConfig } from "../init";
3
- import path from "path";
4
- import { v4 } from "uuid";
5
-
6
- export function useSharedDataRoutes(config: SynthOSConfig, app: Application): void {
7
- app.get('/api/shared/data/:table', (req, res) => handleList(config, req.params.table, req.query, res));
8
- app.get('/api/shared/data/:table/:id', (req, res) => handleGet(config, req.params.table, req.params.id, res));
9
- app.post('/api/shared/data/:table', (req, res) => handleUpsert(config, req.params.table, req.body, res));
10
- app.delete('/api/shared/data/:table/:id', (req, res) => handleDelete(config, req.params.table, req.params.id, res));
11
- }
12
-
13
- // ---------------------------------------------------------------------------
14
- // Route handlers
15
- // ---------------------------------------------------------------------------
16
-
17
- async function handleList(config: SynthOSConfig, table: string, query: Record<string, any>, res: Response): Promise<void> {
18
- const sp = config.storageProvider;
19
- const folder = sharedTableFolder(config, table);
20
- if (!(await sp.checkIfExists(folder))) {
21
- res.status(404).json({ error: 'table_not_found', table });
22
- return;
23
- }
24
-
25
- const ids = (await sp.listFiles(folder)).filter(f => f.endsWith('.json')).map(f => f.replace('.json', ''));
26
-
27
- const rows: Record<string, any>[] = [];
28
- for (const id of ids) {
29
- const file = recordFile(folder, id);
30
- try {
31
- const row = JSON.parse(await sp.loadFile(file));
32
- row.id = id;
33
- rows.push(row);
34
- } catch (err: unknown) {
35
- console.error(err);
36
- }
37
- }
38
-
39
- // Paginate when limit is provided
40
- const limitParam = typeof query.limit === 'string' ? parseInt(query.limit, 10) : NaN;
41
- if (!isNaN(limitParam) && limitParam > 0) {
42
- const offset = Math.max(0, typeof query.offset === 'string' ? parseInt(query.offset, 10) || 0 : 0);
43
- const items = rows.slice(offset, offset + limitParam);
44
- res.json({ items, total: rows.length, offset, limit: limitParam, hasMore: offset + limitParam < rows.length });
45
- } else {
46
- res.json(rows);
47
- }
48
- }
49
-
50
- async function handleGet(config: SynthOSConfig, table: string, id: string, res: Response): Promise<void> {
51
- const sp = config.storageProvider;
52
- const folder = sharedTableFolder(config, table);
53
- if (!(await sp.checkIfExists(folder))) {
54
- res.status(404).json({ error: 'table_not_found', table });
55
- return;
56
- }
57
-
58
- const file = recordFile(folder, id);
59
- try {
60
- const row = JSON.parse(await sp.loadFile(file));
61
- row.id = id;
62
- res.json(row);
63
- } catch (err: unknown) {
64
- res.json({});
65
- }
66
- }
67
-
68
- async function handleUpsert(config: SynthOSConfig, table: string, body: any, res: Response): Promise<void> {
69
- const sp = config.storageProvider;
70
- const id = body.id ?? v4();
71
- const folder = sharedTableFolder(config, table);
72
- const file = recordFile(folder, id);
73
- try {
74
- const row = { ...body, id };
75
- await sp.ensureFolderExists(folder);
76
- await sp.saveFile(file, JSON.stringify(row, null, 4));
77
- res.json(row);
78
- } catch (err: unknown) {
79
- console.error(err);
80
- res.status(500).send((err as Error).message);
81
- }
82
- }
83
-
84
- async function handleDelete(config: SynthOSConfig, table: string, id: string, res: Response): Promise<void> {
85
- const sp = config.storageProvider;
86
- const folder = sharedTableFolder(config, table);
87
- const file = recordFile(folder, id);
88
- try {
89
- if (await sp.checkIfExists(file)) {
90
- await sp.deleteFile(file);
91
- }
92
- res.json({ success: true });
93
- } catch (err: unknown) {
94
- console.error(err);
95
- res.status(500).send((err as Error).message);
96
- }
97
- }
98
-
99
- // ---------------------------------------------------------------------------
100
- // Helpers
101
- // ---------------------------------------------------------------------------
102
-
103
- function sharedTableFolder(config: SynthOSConfig, table: string): string {
104
- return path.join(config.pagesFolder, 'shared', table);
105
- }
106
-
107
- function recordFile(folder: string, id: string): string {
108
- return path.join(folder, `${id}.json`);
109
- }
1
+ import { Application, Response } from 'express';
2
+ import { SynthOSConfig } from "../init";
3
+ import path from "path";
4
+ import { v4 } from "uuid";
5
+ import {
6
+ deleteSchema,
7
+ isValidSchemaPayload,
8
+ listTables,
9
+ loadSchema,
10
+ mergeSchema,
11
+ newSchemaWrapper,
12
+ saveSchema,
13
+ updateSchemaWrapper,
14
+ MergeMode,
15
+ } from "./sharedTableSchema";
16
+
17
+ export function useSharedDataRoutes(config: SynthOSConfig, app: Application): void {
18
+ // Schema sidecar + table-list endpoints are registered FIRST so the
19
+ // literal `_schema` and `_tables` segments don't get captured by the
20
+ // generic `:table` / `:table/:id` patterns below.
21
+ app.get('/api/shared/data/_tables', (req, res) => handleListTables(config, res));
22
+ app.get('/api/shared/data/:table/_schema', (req, res) => handleGetSchema(config, req.params.table, res));
23
+ app.put('/api/shared/data/:table/_schema', (req, res) => handlePutSchema(config, req.params.table, req.query, req.body, res));
24
+ app.delete('/api/shared/data/:table/_schema', (req, res) => handleDeleteSchema(config, req.params.table, res));
25
+
26
+ app.get('/api/shared/data/:table', (req, res) => handleList(config, req.params.table, req.query, res));
27
+ app.get('/api/shared/data/:table/:id', (req, res) => handleGet(config, req.params.table, req.params.id, res));
28
+ app.post('/api/shared/data/:table', (req, res) => handleUpsert(config, req.params.table, req.body, res));
29
+ app.delete('/api/shared/data/:table/:id', (req, res) => handleDelete(config, req.params.table, req.params.id, res));
30
+ }
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Route handlers — records
34
+ // ---------------------------------------------------------------------------
35
+
36
+ async function handleList(config: SynthOSConfig, table: string, query: Record<string, any>, res: Response): Promise<void> {
37
+ const sp = config.storageProvider;
38
+ const folder = sharedTableFolder(config, table);
39
+ if (!(await sp.checkIfExists(folder))) {
40
+ res.status(404).json({ error: 'table_not_found', table });
41
+ return;
42
+ }
43
+
44
+ const ids = (await sp.listFiles(folder)).filter(f => f.endsWith('.json')).map(f => f.replace('.json', ''));
45
+
46
+ const rows: Record<string, any>[] = [];
47
+ for (const id of ids) {
48
+ const file = recordFile(folder, id);
49
+ try {
50
+ const row = JSON.parse(await sp.loadFile(file));
51
+ row.id = id;
52
+ rows.push(row);
53
+ } catch (err: unknown) {
54
+ console.error(err);
55
+ }
56
+ }
57
+
58
+ // Paginate when limit is provided
59
+ const limitParam = typeof query.limit === 'string' ? parseInt(query.limit, 10) : NaN;
60
+ if (!isNaN(limitParam) && limitParam > 0) {
61
+ const offset = Math.max(0, typeof query.offset === 'string' ? parseInt(query.offset, 10) || 0 : 0);
62
+ const items = rows.slice(offset, offset + limitParam);
63
+ res.json({ items, total: rows.length, offset, limit: limitParam, hasMore: offset + limitParam < rows.length });
64
+ } else {
65
+ res.json(rows);
66
+ }
67
+ }
68
+
69
+ async function handleGet(config: SynthOSConfig, table: string, id: string, res: Response): Promise<void> {
70
+ const sp = config.storageProvider;
71
+ const folder = sharedTableFolder(config, table);
72
+ if (!(await sp.checkIfExists(folder))) {
73
+ res.status(404).json({ error: 'table_not_found', table });
74
+ return;
75
+ }
76
+
77
+ const file = recordFile(folder, id);
78
+ try {
79
+ const row = JSON.parse(await sp.loadFile(file));
80
+ row.id = id;
81
+ res.json(row);
82
+ } catch (err: unknown) {
83
+ res.json({});
84
+ }
85
+ }
86
+
87
+ async function handleUpsert(config: SynthOSConfig, table: string, body: any, res: Response): Promise<void> {
88
+ const sp = config.storageProvider;
89
+ const id = body.id ?? v4();
90
+ const folder = sharedTableFolder(config, table);
91
+ const file = recordFile(folder, id);
92
+ try {
93
+ const row = { ...body, id };
94
+ await sp.ensureFolderExists(folder);
95
+ await sp.saveFile(file, JSON.stringify(row, null, 4));
96
+ res.json(row);
97
+ } catch (err: unknown) {
98
+ console.error(err);
99
+ res.status(500).send((err as Error).message);
100
+ }
101
+ }
102
+
103
+ async function handleDelete(config: SynthOSConfig, table: string, id: string, res: Response): Promise<void> {
104
+ const sp = config.storageProvider;
105
+ const folder = sharedTableFolder(config, table);
106
+ const file = recordFile(folder, id);
107
+ try {
108
+ if (await sp.checkIfExists(file)) {
109
+ await sp.deleteFile(file);
110
+ }
111
+ res.json({ success: true });
112
+ } catch (err: unknown) {
113
+ console.error(err);
114
+ res.status(500).send((err as Error).message);
115
+ }
116
+ }
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // Route handlers — schema sidecar
120
+ // ---------------------------------------------------------------------------
121
+
122
+ async function handleGetSchema(config: SynthOSConfig, table: string, res: Response): Promise<void> {
123
+ const wrapper = await loadSchema(config, sharedNamespace(config), table);
124
+ if (!wrapper) {
125
+ res.status(404).json({ error: 'schema_not_found', table });
126
+ return;
127
+ }
128
+ res.json(wrapper);
129
+ }
130
+
131
+ async function handlePutSchema(
132
+ config: SynthOSConfig,
133
+ table: string,
134
+ query: Record<string, any>,
135
+ body: any,
136
+ res: Response,
137
+ ): Promise<void> {
138
+ const incoming = body && typeof body === 'object' && body.schema ? body.schema : body;
139
+ if (!isValidSchemaPayload(incoming)) {
140
+ res.status(400).json({ error: 'invalid_schema', message: 'Body must be a JSON Schema object (or { schema: ... } wrapper).' });
141
+ return;
142
+ }
143
+ const merge: MergeMode = query.merge === 'replace' ? 'replace' : 'additive';
144
+ const definedBy = typeof body?.definedBy === 'string' ? body.definedBy : undefined;
145
+ const namespace = sharedNamespace(config);
146
+ const existing = await loadSchema(config, namespace, table);
147
+ const { merged, conflicts } = mergeSchema(existing?.schema, incoming, merge);
148
+ if (conflicts.length > 0) {
149
+ res.status(409).json({ error: 'schema_conflict', conflicts });
150
+ return;
151
+ }
152
+ const now = new Date().toISOString();
153
+ const wrapper = existing
154
+ ? updateSchemaWrapper(existing, merged, now, definedBy)
155
+ : newSchemaWrapper(merged, now, definedBy);
156
+ await saveSchema(config, namespace, table, wrapper);
157
+ res.json(wrapper);
158
+ }
159
+
160
+ async function handleDeleteSchema(config: SynthOSConfig, table: string, res: Response): Promise<void> {
161
+ await deleteSchema(config, sharedNamespace(config), table);
162
+ res.status(204).end();
163
+ }
164
+
165
+ async function handleListTables(config: SynthOSConfig, res: Response): Promise<void> {
166
+ const tables = await listTables(config, sharedNamespace(config));
167
+ res.json({ tables });
168
+ }
169
+
170
+ // ---------------------------------------------------------------------------
171
+ // Helpers
172
+ // ---------------------------------------------------------------------------
173
+
174
+ function sharedNamespace(config: SynthOSConfig): string {
175
+ return path.join(config.pagesFolder, 'shared');
176
+ }
177
+
178
+ function sharedTableFolder(config: SynthOSConfig, table: string): string {
179
+ return path.join(sharedNamespace(config), table);
180
+ }
181
+
182
+ function recordFile(folder: string, id: string): string {
183
+ return path.join(folder, `${id}.json`);
184
+ }
package/src/settings.ts CHANGED
@@ -31,6 +31,28 @@ export interface SettingsV1 {
31
31
  /**
32
32
  * V2 settings shape with versioned models array.
33
33
  */
34
+ export interface CacheSettings {
35
+ enabled: boolean;
36
+ }
37
+
38
+ export interface PersonalInfo {
39
+ name?: string;
40
+ address?: string;
41
+ details?: string;
42
+ }
43
+
44
+ export interface BusinessInfo {
45
+ name?: string;
46
+ locations?: string;
47
+ hours?: string;
48
+ details?: string;
49
+ }
50
+
51
+ export interface UserProfile {
52
+ personal?: PersonalInfo;
53
+ business?: BusinessInfo;
54
+ }
55
+
34
56
  export interface SettingsV2 {
35
57
  version: 2;
36
58
  theme: string;
@@ -40,6 +62,8 @@ export interface SettingsV2 {
40
62
  connectors?: ServicesConfig;
41
63
  agents?: AgentConfig[];
42
64
  toolbarPosition?: 'left' | 'right';
65
+ cache?: CacheSettings;
66
+ profile?: UserProfile;
43
67
  }
44
68
 
45
69
  export const DefaultSettings: SettingsV2 = {
@@ -181,3 +205,44 @@ export async function saveSettings(config: SynthOSConfig, settings: Partial<Sett
181
205
  _settings.version = 2;
182
206
  await sp.saveFile(path.join(config.pagesFolder, 'settings.json'), JSON.stringify(_settings, null, 4));
183
207
  }
208
+
209
+ /**
210
+ * Render the user profile as markdown for injection into the builder's
211
+ * <USER_PROFILE> context section. Each field is optional; sections with no
212
+ * filled fields are omitted entirely. Returns an empty string when nothing
213
+ * would be rendered.
214
+ */
215
+ export function renderUserProfile(profile: UserProfile | undefined): string {
216
+ if (!profile) return '';
217
+
218
+ const blocks: string[] = [];
219
+
220
+ const personalFields: Array<{ label: string; value?: string }> = [
221
+ { label: 'Name', value: profile.personal?.name },
222
+ { label: 'Address', value: profile.personal?.address },
223
+ { label: 'Details', value: profile.personal?.details },
224
+ ];
225
+ const personalBody = renderProfileSection(personalFields);
226
+ if (personalBody) blocks.push(`## Personal Information\n\n${personalBody}`);
227
+
228
+ const businessFields: Array<{ label: string; value?: string }> = [
229
+ { label: 'Name', value: profile.business?.name },
230
+ { label: 'Locations', value: profile.business?.locations },
231
+ { label: 'Hours', value: profile.business?.hours },
232
+ { label: 'Details', value: profile.business?.details },
233
+ ];
234
+ const businessBody = renderProfileSection(businessFields);
235
+ if (businessBody) blocks.push(`## Business Information\n\n${businessBody}`);
236
+
237
+ return blocks.join('\n\n');
238
+ }
239
+
240
+ function renderProfileSection(fields: Array<{ label: string; value?: string }>): string {
241
+ const parts: string[] = [];
242
+ for (const { label, value } of fields) {
243
+ if (value && value.trim().length > 0) {
244
+ parts.push(`**${label}:**\n${value.trim()}`);
245
+ }
246
+ }
247
+ return parts.join('\n\n');
248
+ }
package/src/themes.ts CHANGED
@@ -1,180 +1,78 @@
1
- import path from 'path';
2
- import { checkIfExists, findFileInFolders, listFiles, listFilesFromFolders, loadFile } from './files';
3
- import { SynthOSConfig } from './init';
4
-
5
- export const THEME_VERSION = 2;
6
-
7
- export interface ThemeInfo {
8
- mode: 'light' | 'dark';
9
- colors: Record<string, string>;
10
- }
11
-
12
- function userThemesFolder(config: SynthOSConfig): string {
13
- return path.join(config.pagesFolder, 'themes');
14
- }
15
-
16
- /**
17
- * Extract the base theme name and version from a CSS filename.
18
- * e.g. "nebula-dusk.v2.css" → { name: "nebula-dusk", version: 2 }
19
- * "nebula-dusk.css" → { name: "nebula-dusk", version: 1 }
20
- */
21
- export function parseThemeFilename(filename: string): { name: string; version: number } | undefined {
22
- if (!filename.endsWith('.css')) return undefined;
23
- const versionedMatch = filename.match(/^(.+)\.v(\d+)\.css$/);
24
- if (versionedMatch) {
25
- return { name: versionedMatch[1], version: parseInt(versionedMatch[2], 10) };
26
- }
27
- return { name: filename.replace(/\.css$/, ''), version: 1 };
28
- }
29
-
30
- /**
31
- * Find the CSS file for a theme by name in a user folder (via storageProvider).
32
- * Prefers the highest-versioned file (e.g. name.v2.css over name.css).
33
- */
34
- async function findUserThemeCssFile(config: SynthOSConfig, folder: string, name: string): Promise<{ path: string; version: number } | undefined> {
35
- const sp = config.storageProvider;
36
- if (!await sp.checkIfExists(folder)) return undefined;
37
- const files = await sp.listFiles(folder);
38
- let best: { path: string; version: number } | undefined;
39
- for (const f of files) {
40
- const parsed = parseThemeFilename(f);
41
- if (parsed && parsed.name === name) {
42
- if (!best || parsed.version > best.version) {
43
- best = { path: path.join(folder, f), version: parsed.version };
44
- }
45
- }
46
- }
47
- return best;
48
- }
49
-
50
- /**
51
- * Find the CSS file for a theme by name in a local-fs folder (package defaults).
52
- */
53
- async function findDefaultThemeCssFile(folder: string, name: string): Promise<{ path: string; version: number } | undefined> {
54
- if (!await checkIfExists(folder)) return undefined;
55
- const files = await listFiles(folder);
56
- let best: { path: string; version: number } | undefined;
57
- for (const f of files) {
58
- const parsed = parseThemeFilename(f);
59
- if (parsed && parsed.name === name) {
60
- if (!best || parsed.version > best.version) {
61
- best = { path: path.join(folder, f), version: parsed.version };
62
- }
63
- }
64
- }
65
- return best;
66
- }
67
-
68
- export async function loadThemeVersion(name: string, config: SynthOSConfig): Promise<number> {
69
- const local = await findUserThemeCssFile(config, userThemesFolder(config), name);
70
- if (local) return local.version;
71
- for (const folder of config.defaultThemesFolders) {
72
- const def = await findDefaultThemeCssFile(folder, name);
73
- if (def) return def.version;
74
- }
75
- return 1;
76
- }
77
-
78
- export async function loadThemeInfo(name: string, config: SynthOSConfig): Promise<ThemeInfo | undefined> {
79
- const sp = config.storageProvider;
80
-
81
- // Check user's local themes first (user storage)
82
- const localPath = path.join(userThemesFolder(config), `${name}.json`);
83
- if (await sp.checkIfExists(localPath)) {
84
- const raw = await sp.loadFile(localPath);
85
- return raw ? JSON.parse(raw) : undefined;
86
- }
87
-
88
- // Fall back to package defaults (local fs)
89
- const defaultPath = await findFileInFolders(config.defaultThemesFolders, `${name}.json`);
90
- if (defaultPath) {
91
- const raw = await loadFile(defaultPath);
92
- return raw ? JSON.parse(raw) : undefined;
93
- }
94
-
95
- return undefined;
96
- }
97
-
98
- export async function loadTheme(name: string, config: SynthOSConfig): Promise<string | undefined> {
99
- const sp = config.storageProvider;
100
-
101
- // Check user's local themes first (user storage)
102
- const local = await findUserThemeCssFile(config, userThemesFolder(config), name);
103
- if (local) {
104
- return await sp.loadFile(local.path);
105
- }
106
-
107
- // Search all default theme folders (local fs)
108
- for (const folder of config.defaultThemesFolders) {
109
- const def = await findDefaultThemeCssFile(folder, name);
110
- if (def) {
111
- return await loadFile(def.path);
112
- }
113
- }
114
-
115
- return undefined;
116
- }
117
-
118
- export async function listThemes(config: SynthOSConfig): Promise<string[]> {
119
- const sp = config.storageProvider;
120
- const names = new Set<string>();
121
-
122
- // Collect from user's local themes folder (user storage)
123
- const localFolder = userThemesFolder(config);
124
- if (await sp.checkIfExists(localFolder)) {
125
- const files = await sp.listFiles(localFolder);
126
- for (const f of files) {
127
- const parsed = parseThemeFilename(f);
128
- if (parsed) names.add(parsed.name);
129
- }
130
- }
131
-
132
- // Collect from all default theme folders (local fs)
133
- const defaultFiles = await listFilesFromFolders(config.defaultThemesFolders);
134
- for (const f of defaultFiles) {
135
- const parsed = parseThemeFilename(f);
136
- if (parsed) names.add(parsed.name);
137
- }
138
-
139
- return Array.from(names).sort();
140
- }
141
-
142
- /**
143
- * Compare local theme versions against defaults and return themes that need upgrading.
144
- */
145
- export async function getOutdatedThemes(config: SynthOSConfig): Promise<string[]> {
146
- const sp = config.storageProvider;
147
- const localFolder = userThemesFolder(config);
148
- if (!await sp.checkIfExists(localFolder)) return [];
149
-
150
- const defaultFiles = await listFilesFromFolders(config.defaultThemesFolders);
151
- const localFiles = await sp.listFiles(localFolder);
152
-
153
- // Build maps: theme name → highest version
154
- const defaultVersions = new Map<string, number>();
155
- for (const f of defaultFiles) {
156
- const parsed = parseThemeFilename(f);
157
- if (parsed) {
158
- const cur = defaultVersions.get(parsed.name) ?? 0;
159
- if (parsed.version > cur) defaultVersions.set(parsed.name, parsed.version);
160
- }
161
- }
162
-
163
- const localVersions = new Map<string, number>();
164
- for (const f of localFiles) {
165
- const parsed = parseThemeFilename(f);
166
- if (parsed) {
167
- const cur = localVersions.get(parsed.name) ?? 0;
168
- if (parsed.version > cur) localVersions.set(parsed.name, parsed.version);
169
- }
170
- }
171
-
172
- const outdated: string[] = [];
173
- for (const [name, defVer] of defaultVersions) {
174
- const localVer = localVersions.get(name) ?? 0;
175
- if (localVer < defVer) {
176
- outdated.push(name);
177
- }
178
- }
179
- return outdated;
180
- }
1
+ import { checkIfExists, findFileInFolders, listFiles, listFilesFromFolders, loadFile } from './files';
2
+ import path from 'path';
3
+ import { SynthOSConfig } from './init';
4
+
5
+ export interface ThemeInfo {
6
+ mode: 'light' | 'dark';
7
+ colors: Record<string, string>;
8
+ }
9
+
10
+ /**
11
+ * Extract the base theme name and version from a CSS filename.
12
+ * e.g. "nebula-dusk.v2.css" → { name: "nebula-dusk", version: 2 }
13
+ * "nebula-dusk.css" { name: "nebula-dusk", version: 1 }
14
+ */
15
+ export function parseThemeFilename(filename: string): { name: string; version: number } | undefined {
16
+ if (!filename.endsWith('.css')) return undefined;
17
+ const versionedMatch = filename.match(/^(.+)\.v(\d+)\.css$/);
18
+ if (versionedMatch) {
19
+ return { name: versionedMatch[1], version: parseInt(versionedMatch[2], 10) };
20
+ }
21
+ return { name: filename.replace(/\.css$/, ''), version: 1 };
22
+ }
23
+
24
+ /**
25
+ * Find the CSS file for a theme by name in a local-fs folder (package defaults).
26
+ * Prefers the highest-versioned file (e.g. name.v2.css over name.css).
27
+ */
28
+ async function findDefaultThemeCssFile(folder: string, name: string): Promise<{ path: string; version: number } | undefined> {
29
+ if (!await checkIfExists(folder)) return undefined;
30
+ const files = await listFiles(folder);
31
+ let best: { path: string; version: number } | undefined;
32
+ for (const f of files) {
33
+ const parsed = parseThemeFilename(f);
34
+ if (parsed && parsed.name === name) {
35
+ if (!best || parsed.version > best.version) {
36
+ best = { path: path.join(folder, f), version: parsed.version };
37
+ }
38
+ }
39
+ }
40
+ return best;
41
+ }
42
+
43
+ export async function loadThemeVersion(name: string, config: SynthOSConfig): Promise<number> {
44
+ for (const folder of config.defaultThemesFolders) {
45
+ const def = await findDefaultThemeCssFile(folder, name);
46
+ if (def) return def.version;
47
+ }
48
+ return 1;
49
+ }
50
+
51
+ export async function loadThemeInfo(name: string, config: SynthOSConfig): Promise<ThemeInfo | undefined> {
52
+ const defaultPath = await findFileInFolders(config.defaultThemesFolders, `${name}.json`);
53
+ if (defaultPath) {
54
+ const raw = await loadFile(defaultPath);
55
+ return raw ? JSON.parse(raw) : undefined;
56
+ }
57
+ return undefined;
58
+ }
59
+
60
+ export async function loadTheme(name: string, config: SynthOSConfig): Promise<string | undefined> {
61
+ for (const folder of config.defaultThemesFolders) {
62
+ const def = await findDefaultThemeCssFile(folder, name);
63
+ if (def) {
64
+ return await loadFile(def.path);
65
+ }
66
+ }
67
+ return undefined;
68
+ }
69
+
70
+ export async function listThemes(config: SynthOSConfig): Promise<string[]> {
71
+ const names = new Set<string>();
72
+ const defaultFiles = await listFilesFromFolders(config.defaultThemesFolders);
73
+ for (const f of defaultFiles) {
74
+ const parsed = parseThemeFilename(f);
75
+ if (parsed) names.add(parsed.name);
76
+ }
77
+ return Array.from(names).sort();
78
+ }
@@ -0,0 +1 @@
1
+ [{"role":"assistant","content":"Welcome to the Blank starter — an empty page ready to be shaped into anything. Tell me what you'd like to build and I'll lay out the structure, styles, and behavior from scratch."}]