synthos 0.7.2 → 0.8.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 (262) hide show
  1. package/README.md +215 -65
  2. package/default-pages/application.json +1 -0
  3. package/default-pages/json_tools.json +1 -1
  4. package/default-pages/oregon_trail.html +321 -0
  5. package/default-pages/oregon_trail.json +12 -0
  6. package/default-pages/sidebar_page.json +1 -0
  7. package/default-pages/solar_explorer.html +10 -18
  8. package/default-pages/solar_explorer.json +2 -2
  9. package/default-pages/two-panel_page.json +1 -0
  10. package/default-pages/us_map.html +192 -0
  11. package/default-pages/us_map.json +12 -0
  12. package/default-pages/us_map_1850.html +325 -0
  13. package/default-pages/us_map_1850.json +12 -0
  14. package/default-pages/western_cities_1850.html +526 -0
  15. package/default-pages/western_cities_1850.json +12 -0
  16. package/default-themes/{nebula-dawn.css → nebula-dawn.v2.css} +24 -0
  17. package/default-themes/{nebula-dusk.css → nebula-dusk.v2.css} +24 -0
  18. package/dist/agents/a2a/a2aProvider.d.ts +3 -0
  19. package/dist/agents/a2a/a2aProvider.d.ts.map +1 -0
  20. package/dist/agents/a2a/a2aProvider.js +126 -0
  21. package/dist/agents/a2a/a2aProvider.js.map +1 -0
  22. package/dist/agents/discovery.d.ts +30 -0
  23. package/dist/agents/discovery.d.ts.map +1 -0
  24. package/dist/agents/discovery.js +52 -0
  25. package/dist/agents/discovery.js.map +1 -0
  26. package/dist/agents/index.d.ts +7 -0
  27. package/dist/agents/index.d.ts.map +1 -0
  28. package/dist/agents/index.js +19 -0
  29. package/dist/agents/index.js.map +1 -0
  30. package/dist/agents/openclaw/gatewayManager.d.ts +113 -0
  31. package/dist/agents/openclaw/gatewayManager.d.ts.map +1 -0
  32. package/dist/agents/openclaw/gatewayManager.js +470 -0
  33. package/dist/agents/openclaw/gatewayManager.js.map +1 -0
  34. package/dist/agents/openclaw/openclawProvider.d.ts +3 -0
  35. package/dist/agents/openclaw/openclawProvider.d.ts.map +1 -0
  36. package/dist/agents/openclaw/openclawProvider.js +239 -0
  37. package/dist/agents/openclaw/openclawProvider.js.map +1 -0
  38. package/dist/agents/openclaw/sshTunnelManager.d.ts +23 -0
  39. package/dist/agents/openclaw/sshTunnelManager.d.ts.map +1 -0
  40. package/dist/agents/openclaw/sshTunnelManager.js +340 -0
  41. package/dist/agents/openclaw/sshTunnelManager.js.map +1 -0
  42. package/dist/agents/types.d.ts +64 -0
  43. package/dist/agents/types.d.ts.map +1 -0
  44. package/dist/agents/types.js +6 -0
  45. package/dist/agents/types.js.map +1 -0
  46. package/dist/connectors/airtable/connector.json +27 -0
  47. package/dist/connectors/alpha-vantage/connector.json +26 -0
  48. package/dist/connectors/brave-search/connector.json +26 -0
  49. package/dist/connectors/cloudinary/connector.json +27 -0
  50. package/dist/connectors/deepl/connector.json +28 -0
  51. package/dist/connectors/elevenlabs/connector.json +30 -0
  52. package/dist/connectors/giphy/connector.json +27 -0
  53. package/dist/connectors/github/connector.json +29 -0
  54. package/dist/connectors/huggingface/connector.json +27 -0
  55. package/dist/connectors/imgur/connector.json +29 -0
  56. package/dist/connectors/index.d.ts +1 -1
  57. package/dist/connectors/index.d.ts.map +1 -1
  58. package/dist/connectors/instagram/connector.json +43 -0
  59. package/dist/connectors/jira/connector.json +28 -0
  60. package/dist/connectors/mapbox/connector.json +26 -0
  61. package/dist/connectors/nasa/connector.json +27 -0
  62. package/dist/connectors/newsapi/connector.json +27 -0
  63. package/dist/connectors/notion/connector.json +28 -0
  64. package/dist/connectors/open-exchange-rates/connector.json +27 -0
  65. package/dist/connectors/openweathermap/connector.json +26 -0
  66. package/dist/connectors/pexels/connector.json +27 -0
  67. package/dist/connectors/registry.d.ts.map +1 -1
  68. package/dist/connectors/registry.js +42 -96
  69. package/dist/connectors/registry.js.map +1 -1
  70. package/dist/connectors/resend/connector.json +29 -0
  71. package/dist/connectors/rss2json/connector.json +27 -0
  72. package/dist/connectors/sendgrid/connector.json +27 -0
  73. package/dist/connectors/spoonacular/connector.json +28 -0
  74. package/dist/connectors/stability-ai/connector.json +27 -0
  75. package/dist/connectors/twilio/connector.json +28 -0
  76. package/dist/connectors/types.d.ts +23 -0
  77. package/dist/connectors/types.d.ts.map +1 -1
  78. package/dist/connectors/unsplash/connector.json +27 -0
  79. package/dist/connectors/wolfram-alpha/connector.json +26 -0
  80. package/dist/connectors/youtube-data/connector.json +30 -0
  81. package/dist/files.d.ts +1 -0
  82. package/dist/files.d.ts.map +1 -1
  83. package/dist/files.js +16 -1
  84. package/dist/files.js.map +1 -1
  85. package/dist/init.d.ts.map +1 -1
  86. package/dist/init.js +28 -0
  87. package/dist/init.js.map +1 -1
  88. package/dist/migrations.d.ts +3 -2
  89. package/dist/migrations.d.ts.map +1 -1
  90. package/dist/migrations.js +122 -138
  91. package/dist/migrations.js.map +1 -1
  92. package/dist/models/anthropic.d.ts +22 -0
  93. package/dist/models/anthropic.d.ts.map +1 -0
  94. package/dist/models/anthropic.js +76 -0
  95. package/dist/models/anthropic.js.map +1 -0
  96. package/dist/models/chainOfThought.d.ts +12 -0
  97. package/dist/models/chainOfThought.d.ts.map +1 -0
  98. package/dist/models/chainOfThought.js +45 -0
  99. package/dist/models/chainOfThought.js.map +1 -0
  100. package/dist/models/fireworksai.d.ts +30 -0
  101. package/dist/models/fireworksai.d.ts.map +1 -0
  102. package/dist/models/fireworksai.js +133 -0
  103. package/dist/models/fireworksai.js.map +1 -0
  104. package/dist/models/index.d.ts +7 -1
  105. package/dist/models/index.d.ts.map +1 -1
  106. package/dist/models/index.js +19 -1
  107. package/dist/models/index.js.map +1 -1
  108. package/dist/models/logCompletePrompt.d.ts +3 -0
  109. package/dist/models/logCompletePrompt.d.ts.map +1 -0
  110. package/dist/models/logCompletePrompt.js +23 -0
  111. package/dist/models/logCompletePrompt.js.map +1 -0
  112. package/dist/models/openai.d.ts +24 -0
  113. package/dist/models/openai.d.ts.map +1 -0
  114. package/dist/models/openai.js +80 -0
  115. package/dist/models/openai.js.map +1 -0
  116. package/dist/models/providers.d.ts +1 -0
  117. package/dist/models/providers.d.ts.map +1 -1
  118. package/dist/models/providers.js +12 -4
  119. package/dist/models/providers.js.map +1 -1
  120. package/dist/models/types.d.ts +34 -2
  121. package/dist/models/types.d.ts.map +1 -1
  122. package/dist/models/types.js +16 -0
  123. package/dist/models/types.js.map +1 -1
  124. package/dist/models/utils.d.ts +6 -0
  125. package/dist/models/utils.d.ts.map +1 -0
  126. package/dist/models/utils.js +21 -0
  127. package/dist/models/utils.js.map +1 -0
  128. package/dist/scripts.d.ts +2 -1
  129. package/dist/scripts.d.ts.map +1 -1
  130. package/dist/scripts.js +4 -3
  131. package/dist/scripts.js.map +1 -1
  132. package/dist/service/createCompletePrompt.d.ts +1 -1
  133. package/dist/service/createCompletePrompt.d.ts.map +1 -1
  134. package/dist/service/createCompletePrompt.js +9 -6
  135. package/dist/service/createCompletePrompt.js.map +1 -1
  136. package/dist/service/generateImage.d.ts +1 -1
  137. package/dist/service/generateImage.d.ts.map +1 -1
  138. package/dist/service/generateImage.js +3 -3
  139. package/dist/service/generateImage.js.map +1 -1
  140. package/dist/service/server.d.ts.map +1 -1
  141. package/dist/service/server.js +3 -0
  142. package/dist/service/server.js.map +1 -1
  143. package/dist/service/transformPage.d.ts +4 -2
  144. package/dist/service/transformPage.d.ts.map +1 -1
  145. package/dist/service/transformPage.js +74 -6
  146. package/dist/service/transformPage.js.map +1 -1
  147. package/dist/service/useAgentRoutes.d.ts +4 -0
  148. package/dist/service/useAgentRoutes.d.ts.map +1 -0
  149. package/dist/service/useAgentRoutes.js +389 -0
  150. package/dist/service/useAgentRoutes.js.map +1 -0
  151. package/dist/service/useApiRoutes.d.ts.map +1 -1
  152. package/dist/service/useApiRoutes.js +157 -16
  153. package/dist/service/useApiRoutes.js.map +1 -1
  154. package/dist/service/useConnectorRoutes.d.ts.map +1 -1
  155. package/dist/service/useConnectorRoutes.js +14 -3
  156. package/dist/service/useConnectorRoutes.js.map +1 -1
  157. package/dist/service/useGatewayRoutes.d.ts +4 -0
  158. package/dist/service/useGatewayRoutes.d.ts.map +1 -0
  159. package/dist/service/useGatewayRoutes.js +168 -0
  160. package/dist/service/useGatewayRoutes.js.map +1 -0
  161. package/dist/service/usePageRoutes.d.ts.map +1 -1
  162. package/dist/service/usePageRoutes.js +16 -5
  163. package/dist/service/usePageRoutes.js.map +1 -1
  164. package/dist/settings.d.ts +2 -1
  165. package/dist/settings.d.ts.map +1 -1
  166. package/dist/settings.js +4 -8
  167. package/dist/settings.js.map +1 -1
  168. package/dist/themes.d.ts +14 -0
  169. package/dist/themes.d.ts.map +1 -1
  170. package/dist/themes.js +86 -13
  171. package/dist/themes.js.map +1 -1
  172. package/package.json +8 -5
  173. package/page-scripts/helpers-v2.js +101 -0
  174. package/page-scripts/page-v2.js +47 -6
  175. package/required-pages/builder.html +1 -27
  176. package/required-pages/pages.html +745 -22
  177. package/required-pages/settings.html +819 -21
  178. package/required-pages/synthos_apis.html +56 -1
  179. package/src/agents/a2a/a2aProvider.ts +110 -0
  180. package/src/agents/discovery.ts +74 -0
  181. package/src/agents/index.ts +6 -0
  182. package/src/agents/openclaw/gatewayManager.ts +559 -0
  183. package/src/agents/openclaw/openclawProvider.ts +261 -0
  184. package/src/agents/openclaw/sshTunnelManager.ts +385 -0
  185. package/src/agents/types.ts +82 -0
  186. package/src/connectors/airtable/connector.json +27 -0
  187. package/src/connectors/alpha-vantage/connector.json +26 -0
  188. package/src/connectors/brave-search/connector.json +26 -0
  189. package/src/connectors/cloudinary/connector.json +27 -0
  190. package/src/connectors/deepl/connector.json +28 -0
  191. package/src/connectors/elevenlabs/connector.json +30 -0
  192. package/src/connectors/giphy/connector.json +27 -0
  193. package/src/connectors/github/connector.json +29 -0
  194. package/src/connectors/huggingface/connector.json +27 -0
  195. package/src/connectors/imgur/connector.json +29 -0
  196. package/src/connectors/index.ts +2 -0
  197. package/src/connectors/instagram/connector.json +43 -0
  198. package/src/connectors/jira/connector.json +28 -0
  199. package/src/connectors/mapbox/connector.json +26 -0
  200. package/src/connectors/nasa/connector.json +27 -0
  201. package/src/connectors/newsapi/connector.json +27 -0
  202. package/src/connectors/notion/connector.json +28 -0
  203. package/src/connectors/open-exchange-rates/connector.json +27 -0
  204. package/src/connectors/openweathermap/connector.json +26 -0
  205. package/src/connectors/pexels/connector.json +27 -0
  206. package/src/connectors/registry.ts +21 -97
  207. package/src/connectors/resend/connector.json +29 -0
  208. package/src/connectors/rss2json/connector.json +27 -0
  209. package/src/connectors/sendgrid/connector.json +27 -0
  210. package/src/connectors/spoonacular/connector.json +28 -0
  211. package/src/connectors/stability-ai/connector.json +27 -0
  212. package/src/connectors/twilio/connector.json +28 -0
  213. package/src/connectors/types.ts +25 -0
  214. package/src/connectors/unsplash/connector.json +27 -0
  215. package/src/connectors/wolfram-alpha/connector.json +26 -0
  216. package/src/connectors/youtube-data/connector.json +30 -0
  217. package/src/files.ts +14 -0
  218. package/src/init.ts +27 -0
  219. package/src/migrations.ts +121 -138
  220. package/src/models/anthropic.ts +89 -0
  221. package/src/models/chainOfThought.ts +56 -0
  222. package/src/models/fireworksai.ts +136 -0
  223. package/src/models/index.ts +7 -1
  224. package/src/models/logCompletePrompt.ts +25 -0
  225. package/src/models/openai.ts +90 -0
  226. package/src/models/providers.ts +12 -3
  227. package/src/models/types.ts +67 -2
  228. package/src/models/utils.ts +16 -0
  229. package/src/scripts.ts +2 -2
  230. package/src/service/createCompletePrompt.ts +3 -1
  231. package/src/service/generateImage.ts +2 -2
  232. package/src/service/server.ts +4 -0
  233. package/src/service/transformPage.ts +81 -8
  234. package/src/service/useAgentRoutes.ts +423 -0
  235. package/src/service/useApiRoutes.ts +173 -18
  236. package/src/service/useConnectorRoutes.ts +14 -3
  237. package/src/service/usePageRoutes.ts +20 -6
  238. package/src/settings.ts +6 -10
  239. package/src/themes.ts +84 -12
  240. package/tests/anthropic.spec.ts +84 -0
  241. package/tests/chainOfThought.spec.ts +108 -0
  242. package/tests/ensureScripts.spec.ts +82 -0
  243. package/tests/files.spec.ts +233 -0
  244. package/tests/fireworksai.spec.ts +92 -0
  245. package/tests/logCompletePrompt.spec.ts +74 -0
  246. package/tests/migrations.spec.ts +79 -1
  247. package/tests/openai.spec.ts +71 -0
  248. package/tests/pages.spec.ts +226 -1
  249. package/tests/providers.spec.ts +144 -0
  250. package/tests/scripts.spec.ts +209 -0
  251. package/tests/transformPage.spec.ts +517 -0
  252. package/tests/types.spec.ts +23 -0
  253. package/default-pages/app_builder.json +0 -1
  254. package/default-pages/sidebar_builder.json +0 -1
  255. package/default-pages/two-panel_builder.json +0 -1
  256. package/images/home.png +0 -0
  257. package/images/page-management.png +0 -0
  258. package/images/settings.png +0 -0
  259. package/images/synthos-square.png +0 -0
  260. /package/default-pages/{app_builder.html → application.html} +0 -0
  261. /package/default-pages/{sidebar_builder.html → sidebar_page.html} +0 -0
  262. /package/default-pages/{two-panel_builder.html → two-panel_page.html} +0 -0
package/src/migrations.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import * as cheerio from 'cheerio';
2
- import { completePrompt } from 'agentm-core';
2
+ import * as fs from 'fs/promises';
3
+ import path from 'path';
4
+ import { completePrompt } from './models';
3
5
  import { deduplicateInlineScripts } from './service/transformPage';
4
6
 
5
7
  /**
@@ -26,128 +28,57 @@ export async function migratePage(html: string, fromVersion: number, toVersion:
26
28
  return current;
27
29
  }
28
30
 
29
- /** CSS classes that belong to the shared theme and must NOT appear in page-specific <style> blocks. */
31
+ /** CSS selectors provided by the shared theme used by post-processing to strip leftovers. */
30
32
  const SHARED_CSS_SELECTORS = [
31
- '*',
32
- 'body',
33
- '.chat-panel',
34
- '.chat-header',
35
- '.chat-messages',
36
- '.chat-message',
37
- '.chat-message p',
38
- '.chat-message strong',
39
- '.chat-message pre',
40
- '.chat-message code',
41
- '.chat-message a',
42
- '.link-group',
43
- '.link-group a',
33
+ // Base
34
+ ':root', '*', 'body', 'html',
35
+ // Chat panel
36
+ '.chat-panel', '.chat-header', '.chat-messages',
37
+ '.chat-message', '.chat-message p', '.chat-message p strong', '.chat-message p code',
38
+ '.chat-message strong', '.chat-message pre', '.chat-message code', '.chat-message a',
39
+ '.link-group', '.link-group a', '.link-group a:hover',
44
40
  'form',
45
- '.chat-input',
46
- '.chat-submit',
47
- '.loading-overlay',
48
- '.spinner',
49
- '.viewer-panel',
50
- '#loadingOverlay',
51
- '.chat-submit:disabled',
52
- '.chat-input:disabled',
41
+ '.chat-input', '.chat-input:focus', '.chat-input::placeholder', '.chat-input:disabled',
42
+ '.chat-submit', '.chat-submit:hover', '.chat-submit:active', '.chat-submit:disabled',
43
+ '.chat-input-wrapper', '.chat-input-wrapper .chat-input',
44
+ // Viewer panel
45
+ '.viewer-panel', '.viewer-panel::before', '.viewer-panel.full-viewer',
46
+ // Loading
47
+ '.loading-overlay', '.spinner', '#loadingOverlay',
48
+ // Chat toggle
49
+ '.chat-toggle', '.chat-toggle:hover', '.chat-toggle-dots', '.chat-toggle-dot',
50
+ '.chat-toggle:hover .chat-toggle-dot',
51
+ 'body.chat-collapsed .chat-panel', 'body.chat-collapsed .chat-toggle',
52
+ // Modal system
53
+ '.modal-overlay', '.modal-overlay.show', '.modal-content', '.modal-header',
54
+ '.modal-body', '.modal-footer', '.modal-footer-right',
55
+ '.modal-btn', '.modal-btn-primary', '.modal-btn-primary:hover',
56
+ '.modal-btn-secondary', '.modal-btn-secondary:hover',
57
+ '.modal-btn-danger', '.modal-btn-danger:hover',
58
+ // Form elements
59
+ '.form-group', '.form-group:last-child', '.form-label',
60
+ '.form-input', '.form-input:focus', '.form-input:read-only', '.form-input::placeholder',
61
+ '.checkbox-label', '.checkbox-label input[type="checkbox"]', '.checkbox-label span',
62
+ // Brainstorm
63
+ '.brainstorm-icon-btn', '.brainstorm-icon-btn:hover',
64
+ '.brainstorm-modal .modal-content', '.brainstorm-modal .modal-header',
65
+ '.brainstorm-close-btn', '.brainstorm-close-btn:hover',
66
+ '.brainstorm-messages', '.brainstorm-message', '.brainstorm-user', '.brainstorm-assistant',
67
+ '.brainstorm-input-row', '.brainstorm-input', '.brainstorm-input:focus', '.brainstorm-input::placeholder',
68
+ '.brainstorm-send-btn', '.brainstorm-send-btn:hover', '.brainstorm-send-btn:disabled',
69
+ '.brainstorm-assistant p', '.brainstorm-assistant pre', '.brainstorm-assistant code',
70
+ '.brainstorm-build-row', '.brainstorm-build-btn', '.brainstorm-build-btn:hover',
71
+ '.brainstorm-suggestions', '.brainstorm-suggestion-chip',
72
+ '.brainstorm-suggestion-chip:hover', '.brainstorm-suggestion-chip:disabled',
73
+ '.brainstorm-thinking',
53
74
  ];
54
75
 
55
- const V1_TO_V2_SYSTEM_PROMPT = `You are a code migration tool. You convert SynthOS v1 pages to v2 format.
56
-
57
- ## Rules — What to REMOVE
58
-
59
- 1. **Shared CSS rules** — Remove CSS rules that match these selectors **exactly** (they are now in theme.css):
60
- \`*\`, \`body\`, \`.chat-panel\`, \`.chat-header\`, \`.chat-messages\`, \`.chat-message\` (and descendants like \`.chat-message p\`, \`.chat-message strong\`, \`.chat-message pre\`, \`.chat-message code\`, \`.chat-message a\`), \`.link-group\`, \`.link-group a\`, \`form\`, \`.chat-input\`, \`.chat-submit\`, \`.loading-overlay\`, \`.spinner\`, \`.viewer-panel\`, \`#loadingOverlay\`, \`.chat-submit:disabled\`, \`.chat-input:disabled\`
61
- Also remove any \`@keyframes spin\` and scrollbar pseudo-element rules (\`::-webkit-scrollbar\`, \`::-webkit-scrollbar-track\`, \`::-webkit-scrollbar-thumb\`).
62
- **Important:** Only remove rules matching these selectors exactly. Do NOT remove pseudo-element variants (\`.viewer-panel::before\`, \`.viewer-panel::after\`), pseudo-class variants (\`.chat-input:focus\`, \`.chat-submit:hover\`), or any other compound selectors that extend the shared selectors — those are page-specific styles and must be preserved. Also preserve any \`@keyframes\` referenced by page-specific rules (e.g. if a page has \`.viewer-panel::before\` using a custom animation, keep that \`@keyframes\`).
63
-
64
- 2. **Shared inline JS** — Remove these specific code blocks from \`<script>\` tags:
65
- - \`document.getElementById('chatInput').focus()\` line
66
- - \`chatForm\` submit event listener (the one with setTimeout and loading overlay)
67
- - \`saveLink\` click handler
68
- - \`resetLink\` click handler
69
- - \`window.onload\` that ONLY scrolls chatMessages (keep other onload logic!)
70
- - Chat panel toggle IIFE (references \`synthos-chat-collapsed\`)
71
- - Focus management IIFE (references \`stopImmediatePropagation\`)
72
- - \`// Basic chat functionality\` comment
73
-
74
- 3. **Empty \`<script>\` tags** — If a script block becomes empty after stripping, remove it entirely.
75
-
76
- ## Rules — What to ADD
77
-
78
- 1. In \`<head>\`, add these two lines right after the \`<title>\` tag (if not already present):
79
- \`\`\`
80
- <script src="/api/theme-info.js"></script>
81
- <link rel="stylesheet" href="/api/theme.css">
82
- \`\`\`
83
-
84
- ## Rules — What to TRANSFORM
85
-
86
- ### Data/Table API migration
87
- The data API changed from global tables to **page-scoped** tables:
88
- - Old: \`/api/data/:table\` → New: \`/api/data/:page/:table\`
89
- - Old: \`/api/data/:table/:id\` → New: \`/api/data/:page/:table/:id\`
90
- - Tables are now stored as sub-folders of each page's folder.
91
- - **Raw fetch() calls** must be updated: e.g. \`fetch('/api/data/notes')\` → \`fetch('/api/data/' + pageName + '/notes')\`
92
- - **synthos.data.\* helpers** handle this automatically — they read the current page name from \`window.pageInfo.name\`. Prefer converting raw fetch data calls to use the helpers instead:
93
- - \`fetch('/api/data/notes')\` → \`synthos.data.list('notes')\`
94
- - \`fetch('/api/data/notes/' + id)\` → \`synthos.data.get('notes', id)\`
95
- - \`fetch('/api/data/notes', { method: 'POST', ... })\` → \`synthos.data.save('notes', row)\`
96
- - \`fetch('/api/data/notes/' + id, { method: 'DELETE' })\` → \`synthos.data.remove('notes', id)\`
97
-
98
- ### Color variables
99
- Replace hardcoded Nebula Dusk colors with CSS variables in **page-specific** CSS only:
100
- | Hardcoded | CSS Variable |
101
- |-----------|-------------|
102
- | \`#667eea\` | \`var(--accent-primary)\` |
103
- | \`#764ba2\` | \`var(--accent-secondary)\` |
104
- | \`#f093fb\` | \`var(--accent-tertiary)\` |
105
- | \`#b794f6\` | \`var(--text-secondary)\` |
106
- | \`#e0e0e0\` | \`var(--text-primary)\` |
107
- | \`rgba(138, 43, 226, 0.3)\` or similar purple rgba | \`var(--border-color)\` or \`var(--accent-glow)\` (use border-color for borders, accent-glow for shadows) |
108
- | \`#1a1a2e\` | \`var(--bg-primary)\` |
109
- | \`#16213e\` | \`var(--bg-secondary)\` |
110
- | \`#0f0f23\` | \`var(--bg-tertiary)\` |
111
-
112
- For gradients using these colors (e.g. \`linear-gradient(135deg, #667eea, #764ba2)\`), replace with \`linear-gradient(135deg, var(--accent-primary), var(--accent-secondary))\`.
113
-
114
- ## Rules — What to PRESERVE (do NOT modify)
115
-
116
- - ALL viewer-panel HTML content (games, presentations, tools, etc.)
117
- - ALL page-specific JavaScript (game logic, presentation logic, keyboard handlers, etc.)
118
- - ALL page-specific CSS (game styles, presentation styles, layout rules for non-shared classes)
119
- - Chat message history in \`.chat-messages\`
120
- - \`<div id="thoughts">\` content
121
- - External CDN script tags (\`<script src="...">\`)
122
- - The two-panel layout structure (chat-panel + viewer-panel)
123
- - The \`<div id="loadingOverlay" class="loading-overlay"><div class="spinner"></div></div>\` element
124
- - The chat form, link-group, chat-header elements (structure only, their CSS is handled by theme)
125
-
126
- ## V2 page structure example
127
-
128
- \`\`\`html
129
- <!DOCTYPE html>
130
- <html lang="en">
131
- <head>
132
- <meta charset="UTF-8">
133
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
134
- <title>SynthOS - Page Title</title>
135
- <script src="/api/theme-info.js"></script>
136
- <link rel="stylesheet" href="/api/theme.css">
137
- <style>
138
- /* Only page-specific styles here — no shared chat/layout CSS */
139
- .my-custom-element {
140
- color: var(--text-primary);
141
- background: var(--bg-secondary);
142
- }
143
- </style>
144
- <!-- external CDN scripts if needed -->
145
- </head>
146
- <body>
76
+ /** Default chat panel HTML restored by post-processing if the LLM removes it. */
77
+ const DEFAULT_CHAT_PANEL = `
147
78
  <div class="chat-panel">
148
79
  <div class="chat-header">SynthOS</div>
149
80
  <div class="chat-messages" id="chatMessages">
150
- <!-- chat messages preserved -->
81
+ <div class="chat-message"><p>Welcome! How can I help you?</p></div>
151
82
  </div>
152
83
  <div class="link-group">
153
84
  <a href="#" id="saveLink">Save</a>
@@ -158,32 +89,26 @@ For gradients using these colors (e.g. \`linear-gradient(135deg, #667eea, #764ba
158
89
  <input type="text" class="chat-input" id="chatInput" name="message" placeholder="Type a message...">
159
90
  <button type="submit" class="chat-submit">Send</button>
160
91
  </form>
161
- </div>
162
- <div class="viewer-panel" id="viewerPanel">
163
- <!-- page content preserved -->
164
- <div id="loadingOverlay" class="loading-overlay"><div class="spinner"></div></div>
165
- </div>
166
- <div id="thoughts" style="display: none;">...</div>
167
- <script>
168
- // Only page-specific JS here — no shared chat handlers
169
- </script>
170
- </body>
171
- </html>
172
- \`\`\`
92
+ </div>`;
173
93
 
174
- ## Output format
175
-
176
- Return ONLY the complete migrated HTML. No markdown fences, no explanation, no commentary. Just the raw HTML starting with \`<!DOCTYPE html>\`.`;
94
+ /**
95
+ * Load migration rules from the migration-rules/ folder.
96
+ */
97
+ async function loadMigrationRules(filename: string): Promise<string> {
98
+ const rulesPath = path.join(__dirname, '..', 'migration-rules', filename);
99
+ return await fs.readFile(rulesPath, 'utf8');
100
+ }
177
101
 
178
102
  /**
179
103
  * v1 -> v2: LLM-based migration that strips shared code and adds theme support.
180
104
  * Post-processes with cheerio to verify critical elements are present.
181
105
  */
182
106
  async function migrateV1toV2(html: string, completePrompt: completePrompt): Promise<string> {
183
- const system = { role: 'system' as const, content: V1_TO_V2_SYSTEM_PROMPT };
107
+ const rules = await loadMigrationRules('v1-to-v2.md');
108
+ const system = { role: 'system' as const, content: rules };
184
109
  const prompt = { role: 'user' as const, content: `Convert this v1 page to v2 format:\n\n${html}` };
185
110
 
186
- const result = await completePrompt({ prompt, system, maxTokens: 16000 });
111
+ const result = await completePrompt({ prompt, system });
187
112
  if (!result.completed || !result.value) {
188
113
  throw new Error('LLM migration failed: ' + (result.error?.message ?? 'no response'));
189
114
  }
@@ -195,16 +120,72 @@ async function migrateV1toV2(html: string, completePrompt: completePrompt): Prom
195
120
  }
196
121
 
197
122
  // Post-process with cheerio to verify and fix critical elements
198
- migrated = postProcessV2(migrated);
123
+ migrated = postProcessV2(migrated, html);
199
124
 
200
125
  return migrated;
201
126
  }
202
127
 
203
128
  /**
204
129
  * Cheerio-based post-processing to verify the LLM output meets v2 requirements.
130
+ * Uses the original HTML as a fallback source for critical elements.
205
131
  */
206
- export function postProcessV2(html: string): string {
132
+ export function postProcessV2(html: string, originalHtml?: string): string {
207
133
  const $ = cheerio.load(html, { decodeEntities: false });
134
+ const $original = originalHtml ? cheerio.load(originalHtml, { decodeEntities: false }) : null;
135
+
136
+ // --- Critical structural checks ---
137
+
138
+ // Ensure chat-panel exists with chatForm
139
+ if ($('#chatForm').length === 0) {
140
+ if ($('.chat-panel').length > 0) {
141
+ // Chat panel exists but form is missing — restore form from original or default
142
+ const originalForm = $original?.('#chatForm').parent('.chat-panel');
143
+ if (originalForm && originalForm.length > 0) {
144
+ $('.chat-panel').replaceWith(originalForm.html()!);
145
+ } else {
146
+ // Append default form
147
+ $('.chat-panel').append(`
148
+ <form action="/" method="POST" id="chatForm">
149
+ <input type="text" class="chat-input" id="chatInput" name="message" placeholder="Type a message...">
150
+ <button type="submit" class="chat-submit">Send</button>
151
+ </form>`);
152
+ }
153
+ } else {
154
+ // Entire chat panel is missing — restore from original or use default
155
+ const originalPanel = $original?.('.chat-panel');
156
+ if (originalPanel && originalPanel.length > 0) {
157
+ $('body').prepend($.html(originalPanel));
158
+ } else {
159
+ $('body').prepend(DEFAULT_CHAT_PANEL);
160
+ }
161
+ }
162
+ }
163
+
164
+ // Ensure thoughts div exists
165
+ if ($('#thoughts').length === 0) {
166
+ const originalThoughts = $original?.('#thoughts');
167
+ if (originalThoughts && originalThoughts.length > 0) {
168
+ $('body').append($.html(originalThoughts));
169
+ } else {
170
+ $('body').append('<div id="thoughts" style="display: none;"></div>');
171
+ }
172
+ }
173
+
174
+ // Ensure loadingOverlay exists inside viewer-panel
175
+ const overlay = $('#loadingOverlay');
176
+ const viewerPanel = $('.viewer-panel');
177
+ if (overlay.length === 0 && viewerPanel.length > 0) {
178
+ viewerPanel.append('<div id="loadingOverlay" class="loading-overlay"><div class="spinner"></div></div>');
179
+ } else if (overlay.length > 0 && viewerPanel.length > 0) {
180
+ // Move inside viewer-panel if it's outside
181
+ if (overlay.closest('.viewer-panel').length === 0) {
182
+ const overlayHtml = $.html(overlay);
183
+ overlay.remove();
184
+ viewerPanel.append(overlayHtml);
185
+ }
186
+ }
187
+
188
+ // --- Theme refs ---
208
189
 
209
190
  // Ensure theme-info.js is in <head>
210
191
  if ($('script[src="/api/theme-info.js"]').length === 0) {
@@ -226,7 +207,8 @@ export function postProcessV2(html: string): string {
226
207
  }
227
208
  }
228
209
 
229
- // Remove leftover shared CSS selectors from <style> blocks
210
+ // --- Strip leftover shared CSS ---
211
+
230
212
  $('style').each(function (_, el) {
231
213
  let css = $(el).html() ?? '';
232
214
  for (const selector of SHARED_CSS_SELECTORS) {
@@ -236,10 +218,11 @@ export function postProcessV2(html: string): string {
236
218
  const pattern = new RegExp(`(?:^|\\n)\\s*${escaped}\\s*\\{[^}]*\\}`, 'g');
237
219
  css = css.replace(pattern, '');
238
220
  }
239
- // Remove @keyframes spin
221
+ // Remove @keyframes spin and nebula-pulse
240
222
  css = css.replace(/@keyframes\s+spin\s*\{[^}]*(?:\{[^}]*\}[^}]*)*\}/g, '');
223
+ css = css.replace(/@keyframes\s+nebula-pulse\s*\{[^}]*(?:\{[^}]*\}[^}]*)*\}/g, '');
241
224
  // Remove scrollbar pseudo-element rules
242
- css = css.replace(/(?:^|\n)\s*(?:\*|body|)::-webkit-scrollbar(?:-(?:track|thumb))?\s*\{[^}]*\}/g, '');
225
+ css = css.replace(/(?:^|\n)\s*(?:\*|body|)::-webkit-scrollbar(?:-(?:track|thumb|corner))?(?::hover)?\s*\{[^}]*\}/g, '');
243
226
  $(el).html(css);
244
227
  });
245
228
 
@@ -0,0 +1,89 @@
1
+ import Anthropic from '@anthropic-ai/sdk';
2
+ import { AgentCompletion, completePrompt, PromptCompletionArgs, RequestError } from './types';
3
+
4
+ export interface AnthropicArgs {
5
+ apiKey: string;
6
+ model: string;
7
+ baseURL?: string;
8
+ temperature?: number;
9
+ maxRetries?: number;
10
+ }
11
+
12
+ /**
13
+ * Build the messages array and system content for an Anthropic API request.
14
+ * Pure function — no SDK dependency.
15
+ */
16
+ export function buildAnthropicRequest(args: PromptCompletionArgs, defaultTemp: number): {
17
+ messages: { role: string; content: string }[];
18
+ system: string | undefined;
19
+ temperature: number;
20
+ } {
21
+ const reqTemp = args.temperature ?? defaultTemp;
22
+
23
+ const messages: { role: string; content: string }[] = [];
24
+ if (args.history) {
25
+ for (const msg of args.history) {
26
+ messages.push({ role: msg.role, content: msg.content });
27
+ }
28
+ }
29
+
30
+ const useJsonPrefill = args.jsonMode || args.jsonSchema;
31
+ if (useJsonPrefill) {
32
+ messages.push({ role: 'user', content: args.prompt.content });
33
+ messages.push({ role: 'assistant', content: '{' });
34
+ } else {
35
+ messages.push({ role: 'user', content: args.prompt.content });
36
+ }
37
+
38
+ let system = args.system?.content;
39
+ if (args.jsonSchema) {
40
+ const schemaInstruction = `\n\nYou must return valid JSON conforming to this schema:\n${JSON.stringify(args.jsonSchema)}`;
41
+ system = system ? system + schemaInstruction : schemaInstruction;
42
+ }
43
+
44
+ return { messages, system, temperature: reqTemp };
45
+ }
46
+
47
+ export function anthropic(args: AnthropicArgs): completePrompt {
48
+ const { apiKey, model, baseURL, temperature = 0.0, maxRetries } = args;
49
+
50
+ const client = new Anthropic({ apiKey, baseURL, maxRetries });
51
+
52
+ return async (completionArgs: PromptCompletionArgs): Promise<AgentCompletion<string>> => {
53
+ const { messages, system: systemContent, temperature: reqTemp } = buildAnthropicRequest(completionArgs, temperature);
54
+
55
+ const useJsonPrefill = completionArgs.jsonMode || completionArgs.jsonSchema;
56
+
57
+ try {
58
+ const stream = await client.messages.create({
59
+ model,
60
+ max_tokens: 32768,
61
+ temperature: reqTemp,
62
+ system: systemContent,
63
+ messages: messages as Anthropic.MessageParam[],
64
+ stream: true,
65
+ });
66
+
67
+ let text = '';
68
+ for await (const event of stream) {
69
+ if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') {
70
+ text += event.delta.text;
71
+ }
72
+ }
73
+
74
+ if (useJsonPrefill) {
75
+ text = '{' + text;
76
+ }
77
+
78
+ return { completed: true, value: text };
79
+ } catch (err: unknown) {
80
+ let error: Error;
81
+ if (err instanceof Anthropic.APIError && (err as any).status !== undefined) {
82
+ error = new RequestError(err.message, (err as any).status, err.name);
83
+ } else {
84
+ error = err as Error;
85
+ }
86
+ return { completed: false, error };
87
+ }
88
+ };
89
+ }
@@ -0,0 +1,56 @@
1
+ import { AgentCompletion, AgentArgs, SystemMessage, UserMessage } from './types';
2
+
3
+ export interface ExplainedAnswer {
4
+ explanation: string;
5
+ answer: string;
6
+ }
7
+
8
+ export interface ChainOfThoughtArgs extends AgentArgs {
9
+ question: string;
10
+ temperature?: number;
11
+ instructions?: string;
12
+ }
13
+
14
+ export async function chainOfThought(args: ChainOfThoughtArgs): Promise<AgentCompletion<ExplainedAnswer>> {
15
+ const { completePrompt, question, temperature, instructions } = args;
16
+
17
+ const systemContent = instructions
18
+ ? `${instructions}\n\nYou must return your response as a JSON object with "explanation" and "answer" fields.`
19
+ : 'You must return your response as a JSON object with "explanation" and "answer" fields.';
20
+
21
+ const system: SystemMessage = { role: 'system', content: systemContent };
22
+ const prompt: UserMessage = { role: 'user', content: question };
23
+
24
+ const result = await completePrompt({ prompt, system, temperature, jsonMode: true });
25
+
26
+ if (!result.completed || !result.value) {
27
+ return { completed: false, error: result.error };
28
+ }
29
+
30
+ try {
31
+ let parsed: any;
32
+ if (typeof result.value === 'object') {
33
+ parsed = result.value;
34
+ } else {
35
+ let text = result.value as string;
36
+ // Strip markdown code fences if present
37
+ text = text.replace(/^```(?:json)?\s*/i, '').replace(/\s*```\s*$/, '');
38
+ // Extract the first JSON object from the text
39
+ const start = text.indexOf('{');
40
+ const end = text.lastIndexOf('}');
41
+ if (start !== -1 && end > start) {
42
+ text = text.substring(start, end + 1);
43
+ }
44
+ parsed = JSON.parse(text);
45
+ }
46
+ return {
47
+ completed: true,
48
+ value: {
49
+ explanation: parsed.explanation ?? '',
50
+ answer: parsed.answer ?? '',
51
+ },
52
+ };
53
+ } catch {
54
+ return { completed: false, error: new Error('Failed to parse chain-of-thought response') };
55
+ }
56
+ }
@@ -0,0 +1,136 @@
1
+ import OpenAI from 'openai';
2
+ import { AgentCompletion, completePrompt, PromptCompletionArgs, RequestError } from './types';
3
+
4
+ export interface FireworksAIArgs {
5
+ apiKey: string;
6
+ model: string;
7
+ temperature?: number;
8
+ }
9
+
10
+ /** Known short-name → full Fireworks model path mappings. */
11
+ const FIREWORKS_MODEL_MAP: Record<string, string> = {
12
+ 'fireworks-glm-5': 'accounts/fireworks/models/glm-5',
13
+ 'fireworks-minimax-m2p5': 'accounts/fireworks/models/minimax-m2p5',
14
+ 'fireworks-kimi-k2p5': 'accounts/fireworks/models/kimi-k2p5',
15
+ };
16
+
17
+ const FIREWORKS_BASE_URL = 'https://api.fireworks.ai/inference/v1';
18
+
19
+ /**
20
+ * Extract JSON from a string that may be wrapped in markdown fences or prose.
21
+ * Returns the original string if no JSON object/array is found.
22
+ */
23
+ export function extractJSON(text: string): string {
24
+ // Strip markdown code fences (```json ... ``` or ``` ... ```)
25
+ const fenceMatch = text.match(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/);
26
+ if (fenceMatch) {
27
+ return fenceMatch[1].trim();
28
+ }
29
+
30
+ // Find the first top-level { ... } or [ ... ]
31
+ const start = text.search(/[\[{]/);
32
+ if (start === -1) return text;
33
+
34
+ const open = text[start];
35
+ const close = open === '{' ? '}' : ']';
36
+ let depth = 0;
37
+ let inString = false;
38
+ let escape = false;
39
+ for (let i = start; i < text.length; i++) {
40
+ const ch = text[i];
41
+ if (escape) { escape = false; continue; }
42
+ if (ch === '\\' && inString) { escape = true; continue; }
43
+ if (ch === '"') { inString = !inString; continue; }
44
+ if (inString) continue;
45
+ if (ch === open || ch === (open === '{' ? '[' : '{')) depth++;
46
+ else if (ch === close || ch === (close === '}' ? ']' : '}')) depth--;
47
+ if (depth === 0) {
48
+ return text.slice(start, i + 1);
49
+ }
50
+ }
51
+
52
+ // Fallback: return from the first brace to end
53
+ return text.slice(start);
54
+ }
55
+
56
+ /**
57
+ * Resolve a short Fireworks model name to the full API path.
58
+ * Returns the input unchanged if not a known short name.
59
+ */
60
+ export function resolveFireworksModel(model: string): string {
61
+ return FIREWORKS_MODEL_MAP[model] || model;
62
+ }
63
+
64
+ /**
65
+ * Build the messages array, temperature, and JSON flag for a FireworksAI request.
66
+ * Pure function — no SDK dependency.
67
+ */
68
+ export function buildFireworksRequest(args: PromptCompletionArgs, defaultTemp: number): {
69
+ messages: { role: string; content: string }[];
70
+ temperature: number;
71
+ useJson: boolean;
72
+ } {
73
+ const temperature = args.temperature ?? defaultTemp;
74
+ const useJson = !!(args.jsonMode || args.jsonSchema);
75
+
76
+ const messages: { role: string; content: string }[] = [];
77
+ if (args.system) {
78
+ messages.push({ role: 'system', content: args.system.content });
79
+ }
80
+ if (args.history) {
81
+ for (const msg of args.history) {
82
+ messages.push({ role: msg.role, content: msg.content });
83
+ }
84
+ }
85
+
86
+ let userContent = args.prompt.content;
87
+ if (useJson) {
88
+ userContent += '\n\nRespond with valid JSON only. No markdown fences.';
89
+ }
90
+ messages.push({ role: 'user', content: userContent });
91
+
92
+ return { messages, temperature, useJson };
93
+ }
94
+
95
+ export function fireworksai(args: FireworksAIArgs): completePrompt {
96
+ const { apiKey, temperature = 0.0 } = args;
97
+ const model = resolveFireworksModel(args.model);
98
+
99
+ const client = new OpenAI({ apiKey, baseURL: FIREWORKS_BASE_URL });
100
+
101
+ return async (completionArgs: PromptCompletionArgs): Promise<AgentCompletion<string>> => {
102
+ const { messages, temperature: reqTemp, useJson } = buildFireworksRequest(completionArgs, temperature);
103
+
104
+ try {
105
+ const response = await client.chat.completions.create({
106
+ model,
107
+ messages: messages as OpenAI.ChatCompletionMessageParam[],
108
+ temperature: reqTemp,
109
+ max_tokens: 32768,
110
+ ...(useJson ? { response_format: { type: 'json_object' } } : {}),
111
+ });
112
+
113
+ const choice = response.choices[0];
114
+ if (!choice?.message) {
115
+ return { completed: false, error: new Error('No response choice returned') };
116
+ }
117
+
118
+ let text = choice.message.content ?? '';
119
+
120
+ // Robust JSON extraction as a safety net
121
+ if (useJson && text) {
122
+ text = extractJSON(text);
123
+ }
124
+
125
+ return { completed: true, value: text };
126
+ } catch (err: unknown) {
127
+ let error: Error;
128
+ if (err instanceof OpenAI.APIError && (err as any).status !== undefined) {
129
+ error = new RequestError(err.message, (err as any).status, err.name ?? 'APIError');
130
+ } else {
131
+ error = err as Error;
132
+ }
133
+ return { completed: false, error };
134
+ }
135
+ };
136
+ }
@@ -1,2 +1,8 @@
1
- export { ProviderName, ProviderConfig, ModelEntry, Provider } from './types';
1
+ export { ProviderName, ProviderConfig, ModelEntry, Provider, SystemMessage, UserMessage, Message, AgentCompletion, completePrompt, PromptCompletionArgs, AgentArgs, RequestError } from './types';
2
2
  export { AnthropicProvider, OpenAIProvider, PROVIDERS, getProvider, detectProvider } from './providers';
3
+ export { anthropic, AnthropicArgs, buildAnthropicRequest } from './anthropic';
4
+ export { openai, OpenaiArgs, buildOpenAIRequest } from './openai';
5
+ export { fireworksai, FireworksAIArgs, resolveFireworksModel, buildFireworksRequest } from './fireworksai';
6
+ export { chainOfThought, ChainOfThoughtArgs, ExplainedAnswer } from './chainOfThought';
7
+ export { logCompletePrompt } from './logCompletePrompt';
8
+ export { variableToString } from './utils';
@@ -0,0 +1,25 @@
1
+ import { AgentCompletion, completePrompt, PromptCompletionArgs } from './types';
2
+
3
+ export function logCompletePrompt<TValue = string>(
4
+ inner: completePrompt<TValue>,
5
+ logDetails?: boolean
6
+ ): completePrompt<TValue> {
7
+ return async (args: PromptCompletionArgs): Promise<AgentCompletion<TValue>> => {
8
+ const result = await inner(args);
9
+
10
+ if (result.completed) {
11
+ const value = typeof result.value === 'string'
12
+ ? result.value
13
+ : JSON.stringify(result.value, null, 2);
14
+ console.log(`\x1b[32m${value}\x1b[0m`);
15
+ } else if (result.error) {
16
+ console.log(`\x1b[31m${result.error.message}\x1b[0m`);
17
+ }
18
+
19
+ if (logDetails) {
20
+ console.log('─'.repeat(80));
21
+ }
22
+
23
+ return result;
24
+ };
25
+ }