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.
- package/README.md +1 -1
- package/default-pages/application/page.html +42 -0
- package/default-pages/application/page.json +10 -0
- package/default-pages/elevenlabs_effects_studio/page.html +1363 -0
- package/default-pages/elevenlabs_effects_studio/page.json +11 -0
- package/default-pages/elevenlabs_voice_studio/page.html +801 -0
- package/default-pages/elevenlabs_voice_studio/page.json +11 -0
- package/default-pages/{json_tools.html → json_tools/page.html} +13 -11
- package/default-pages/json_tools/page.json +10 -0
- package/default-pages/my_notes/notes/a1b2c3d4-e5f6-7890-abcd-ef1234567890.json +5 -0
- package/default-pages/my_notes/page.html +132 -0
- package/default-pages/{my_notes.json → my_notes/page.json} +2 -2
- package/default-pages/neon_asteroids/files/Ambient_Space.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Ambient_Space2.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Ambient_Space3.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Asteroid_Explosion.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Hyperspace_Jump.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Laser_Fire.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Menu_Navigate.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Power_Up_Collect.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Saucer_Alert.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Ship_Thrust.mp3 +0 -0
- package/default-pages/neon_asteroids/files/effects.json +74 -0
- package/default-pages/neon_asteroids/page.html +1803 -0
- package/default-pages/{neon_asteroids.json → neon_asteroids/page.json} +3 -3
- package/default-pages/{oregon_trail.html → oregon_trail/page.html} +16 -30
- package/default-pages/{oregon_trail.json → oregon_trail/page.json} +2 -2
- package/default-pages/retro_game_starter/page.html +1308 -0
- package/default-pages/retro_game_starter/page.json +12 -0
- package/default-pages/{sidebar_page.html → sidebar_page/page.html} +12 -10
- package/default-pages/sidebar_page/page.json +10 -0
- package/default-pages/{solar_explorer.html → solar_explorer/page.html} +15 -12
- package/default-pages/{solar_explorer.json → solar_explorer/page.json} +2 -2
- package/default-pages/{solar_tutorial.html → solar_tutorial/page.html} +12 -10
- package/default-pages/solar_tutorial/page.json +10 -0
- package/default-pages/{two-panel_page.html → two-panel_page/page.html} +13 -11
- package/default-pages/two-panel_page/page.json +10 -0
- package/default-pages/{us_map.html → us_map/page.html} +193 -192
- package/default-pages/{us_map.json → us_map/page.json} +12 -12
- package/default-pages/{us_map_1850.html → us_map_1850/page.html} +326 -325
- package/default-pages/{us_map_1850.json → us_map_1850/page.json} +12 -12
- package/default-pages/{western_cities_1850.html → western_cities_1850/page.html} +527 -526
- package/default-pages/{western_cities_1850.json → western_cities_1850/page.json} +12 -12
- package/default-themes/aurora-dawn.json +19 -0
- package/default-themes/aurora-dawn.v3.css +198 -0
- package/default-themes/aurora-dusk.json +19 -0
- package/default-themes/aurora-dusk.v3.css +200 -0
- package/default-themes/cosmos-dawn.json +19 -0
- package/default-themes/cosmos-dawn.v3.css +198 -0
- package/default-themes/cosmos-dusk.json +19 -0
- package/default-themes/cosmos-dusk.v3.css +200 -0
- package/default-themes/high-contrast-dark.json +19 -0
- package/default-themes/high-contrast-dark.v3.css +200 -0
- package/default-themes/high-contrast-light.json +19 -0
- package/default-themes/high-contrast-light.v3.css +198 -0
- package/default-themes/nebula-dawn.v2.css +110 -0
- package/default-themes/nebula-dawn.v3.css +199 -0
- package/default-themes/nebula-dusk.v2.css +104 -0
- package/default-themes/nebula-dusk.v3.css +201 -0
- package/default-themes/solar-flare-dawn.json +19 -0
- package/default-themes/solar-flare-dawn.v3.css +198 -0
- package/default-themes/solar-flare-dusk.json +19 -0
- package/default-themes/solar-flare-dusk.v3.css +200 -0
- package/dist/agents/index.d.ts +1 -1
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +2 -1
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/openclaw/gatewayManager.d.ts +4 -0
- package/dist/agents/openclaw/gatewayManager.d.ts.map +1 -1
- package/dist/agents/openclaw/gatewayManager.js +27 -11
- package/dist/agents/openclaw/gatewayManager.js.map +1 -1
- package/dist/agents/openclaw/openclawProvider.d.ts.map +1 -1
- package/dist/agents/openclaw/openclawProvider.js +2 -4
- package/dist/agents/openclaw/openclawProvider.js.map +1 -1
- package/dist/agents/openclaw/sshTunnelManager.d.ts +2 -0
- package/dist/agents/openclaw/sshTunnelManager.d.ts.map +1 -1
- package/dist/agents/openclaw/sshTunnelManager.js +31 -12
- package/dist/agents/openclaw/sshTunnelManager.js.map +1 -1
- package/dist/builders/anthropic.d.ts +31 -0
- package/dist/builders/anthropic.d.ts.map +1 -0
- package/dist/builders/anthropic.js +227 -0
- package/dist/builders/anthropic.js.map +1 -0
- package/dist/builders/fireworksai.d.ts +9 -0
- package/dist/builders/fireworksai.d.ts.map +1 -0
- package/dist/builders/fireworksai.js +57 -0
- package/dist/builders/fireworksai.js.map +1 -0
- package/dist/builders/index.d.ts +13 -0
- package/dist/builders/index.d.ts.map +1 -0
- package/dist/builders/index.js +31 -0
- package/dist/builders/index.js.map +1 -0
- package/dist/builders/openai.d.ts +8 -0
- package/dist/builders/openai.d.ts.map +1 -0
- package/dist/builders/openai.js +87 -0
- package/dist/builders/openai.js.map +1 -0
- package/dist/builders/types.d.ts +54 -0
- package/dist/builders/types.d.ts.map +1 -0
- package/dist/builders/types.js +211 -0
- package/dist/builders/types.js.map +1 -0
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.d.ts.map +1 -1
- package/dist/connectors/index.js +3 -2
- package/dist/connectors/index.js.map +1 -1
- package/dist/connectors/registry.d.ts +2 -1
- package/dist/connectors/registry.d.ts.map +1 -1
- package/dist/connectors/registry.js +31 -8
- package/dist/connectors/registry.js.map +1 -1
- package/dist/customizer/Customizer.d.ts +62 -0
- package/dist/customizer/Customizer.d.ts.map +1 -0
- package/dist/customizer/Customizer.js +134 -0
- package/dist/customizer/Customizer.js.map +1 -0
- package/dist/customizer/index.d.ts +4 -0
- package/dist/customizer/index.d.ts.map +1 -0
- package/dist/customizer/index.js +9 -0
- package/dist/customizer/index.js.map +1 -0
- package/dist/files.d.ts +16 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +60 -1
- package/dist/files.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +12 -6
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +150 -133
- package/dist/init.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +23 -10
- package/dist/migrations.js.map +1 -1
- package/dist/models/anthropic.d.ts +4 -2
- package/dist/models/anthropic.d.ts.map +1 -1
- package/dist/models/anthropic.js +33 -6
- package/dist/models/anthropic.js.map +1 -1
- package/dist/models/fireworksai.d.ts.map +1 -1
- package/dist/models/fireworksai.js +9 -1
- package/dist/models/fireworksai.js.map +1 -1
- package/dist/models/index.d.ts +1 -1
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/index.js +2 -1
- package/dist/models/index.js.map +1 -1
- package/dist/models/openai.d.ts +1 -1
- package/dist/models/openai.d.ts.map +1 -1
- package/dist/models/openai.js +24 -3
- package/dist/models/openai.js.map +1 -1
- package/dist/models/types.d.ts +20 -1
- package/dist/models/types.d.ts.map +1 -1
- package/dist/models/types.js +6 -1
- package/dist/models/types.js.map +1 -1
- package/dist/pages.d.ts +34 -10
- package/dist/pages.d.ts.map +1 -1
- package/dist/pages.js +229 -79
- package/dist/pages.js.map +1 -1
- package/dist/service/createCompletePrompt.d.ts +2 -1
- package/dist/service/createCompletePrompt.d.ts.map +1 -1
- package/dist/service/createCompletePrompt.js +2 -2
- package/dist/service/createCompletePrompt.js.map +1 -1
- package/dist/service/requiresSettings.d.ts +2 -1
- package/dist/service/requiresSettings.d.ts.map +1 -1
- package/dist/service/requiresSettings.js +3 -3
- package/dist/service/requiresSettings.js.map +1 -1
- package/dist/service/server.d.ts +2 -1
- package/dist/service/server.d.ts.map +1 -1
- package/dist/service/server.js +37 -8
- package/dist/service/server.js.map +1 -1
- package/dist/service/transformPage.d.ts +47 -20
- package/dist/service/transformPage.d.ts.map +1 -1
- package/dist/service/transformPage.js +514 -293
- package/dist/service/transformPage.js.map +1 -1
- package/dist/service/useAgentRoutes.d.ts +2 -1
- package/dist/service/useAgentRoutes.d.ts.map +1 -1
- package/dist/service/useAgentRoutes.js +17 -14
- package/dist/service/useAgentRoutes.js.map +1 -1
- package/dist/service/useApiRoutes.d.ts +2 -1
- package/dist/service/useApiRoutes.d.ts.map +1 -1
- package/dist/service/useApiRoutes.js +287 -172
- package/dist/service/useApiRoutes.js.map +1 -1
- package/dist/service/useConnectorRoutes.js +17 -17
- package/dist/service/useConnectorRoutes.js.map +1 -1
- package/dist/service/useDataRoutes.d.ts.map +1 -1
- package/dist/service/useDataRoutes.js +13 -10
- package/dist/service/useDataRoutes.js.map +1 -1
- package/dist/service/useFileRoutes.d.ts +4 -0
- package/dist/service/useFileRoutes.d.ts.map +1 -0
- package/dist/service/useFileRoutes.js +122 -0
- package/dist/service/useFileRoutes.js.map +1 -0
- package/dist/service/usePageRoutes.d.ts +2 -1
- package/dist/service/usePageRoutes.d.ts.map +1 -1
- package/dist/service/usePageRoutes.js +671 -74
- package/dist/service/usePageRoutes.js.map +1 -1
- package/dist/service/useSharedDataRoutes.d.ts +4 -0
- package/dist/service/useSharedDataRoutes.d.ts.map +1 -0
- package/dist/service/useSharedDataRoutes.js +107 -0
- package/dist/service/useSharedDataRoutes.js.map +1 -0
- package/dist/service/useSharedFileRoutes.d.ts +4 -0
- package/dist/service/useSharedFileRoutes.d.ts.map +1 -0
- package/dist/service/useSharedFileRoutes.js +121 -0
- package/dist/service/useSharedFileRoutes.js.map +1 -0
- package/dist/settings.d.ts +5 -3
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +12 -10
- package/dist/settings.js.map +1 -1
- package/dist/storage/FsStorageProvider.d.ts +25 -0
- package/dist/storage/FsStorageProvider.d.ts.map +1 -0
- package/dist/storage/FsStorageProvider.js +103 -0
- package/dist/storage/FsStorageProvider.js.map +1 -0
- package/dist/storage/StorageProvider.d.ts +31 -0
- package/dist/storage/StorageProvider.d.ts.map +1 -0
- package/dist/storage/StorageProvider.js +3 -0
- package/dist/storage/StorageProvider.js.map +1 -0
- package/dist/storage/index.d.ts +3 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +6 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/synthos-cli.d.ts.map +1 -1
- package/dist/synthos-cli.js +4 -3
- package/dist/synthos-cli.js.map +1 -1
- package/dist/themes.d.ts +1 -0
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +65 -28
- package/dist/themes.js.map +1 -1
- package/migration-rules/v1-to-v2.md +193 -0
- package/migration-rules/v2-to-v3.md +481 -0
- package/package.json +11 -10
- package/required-pages/builder/page.html +43 -0
- package/required-pages/builder/page.json +10 -0
- package/required-pages/{pages.html → pages/page.html} +238 -233
- package/required-pages/pages/page.json +10 -0
- package/required-pages/{settings.html → settings/page.html} +389 -275
- package/required-pages/settings/page.json +10 -0
- package/required-pages/synthos_apis/page.html +846 -0
- package/required-pages/synthos_apis/page.json +10 -0
- package/required-pages/{synthos_scripts.html → synthos_scripts/page.html} +13 -11
- package/required-pages/synthos_scripts/page.json +10 -0
- package/src/agents/index.ts +1 -1
- package/src/agents/openclaw/gatewayManager.ts +22 -11
- package/src/agents/openclaw/openclawProvider.ts +2 -4
- package/src/agents/openclaw/sshTunnelManager.ts +19 -11
- package/src/builders/anthropic.ts +283 -0
- package/src/builders/fireworksai.ts +59 -0
- package/src/builders/index.ts +33 -0
- package/src/builders/openai.ts +89 -0
- package/src/builders/types.ts +261 -0
- package/src/connectors/index.ts +1 -1
- package/src/connectors/registry.ts +28 -8
- package/src/customizer/Customizer.ts +163 -0
- package/src/customizer/index.ts +5 -0
- package/src/files.ts +57 -0
- package/src/index.ts +3 -1
- package/src/init.ts +195 -145
- package/src/migrations.ts +30 -10
- package/src/models/anthropic.ts +40 -10
- package/src/models/fireworksai.ts +9 -2
- package/src/models/index.ts +1 -1
- package/src/models/openai.ts +26 -6
- package/src/models/types.ts +31 -1
- package/src/pages.ts +230 -77
- package/src/service/createCompletePrompt.ts +3 -2
- package/src/service/requiresSettings.ts +4 -3
- package/src/service/server.ts +36 -9
- package/src/service/transformPage.ts +557 -326
- package/src/service/useAgentRoutes.ts +19 -14
- package/src/service/useApiRoutes.ts +208 -84
- package/src/service/useConnectorRoutes.ts +18 -18
- package/src/service/useDataRoutes.ts +13 -10
- package/src/service/useFileRoutes.ts +128 -0
- package/src/service/usePageRoutes.ts +730 -81
- package/src/service/useSharedDataRoutes.ts +109 -0
- package/src/service/useSharedFileRoutes.ts +127 -0
- package/src/settings.ts +14 -10
- package/src/storage/FsStorageProvider.ts +87 -0
- package/src/storage/StorageProvider.ts +34 -0
- package/src/storage/index.ts +2 -0
- package/src/synthos-cli.ts +4 -3
- package/src/themes.ts +64 -27
- package/static-files/favicon.svg +12 -0
- package/static-files/fluentlm-instructions.llmd +868 -0
- package/static-files/fluentlm-instructions.md +1595 -0
- package/static-files/fluentlm.css +4844 -0
- package/static-files/fluentlm.js +3602 -0
- package/static-files/fluentlm.min.css +1 -0
- package/static-files/fluentlm.min.js +1 -0
- package/{page-scripts/helpers-v2.js → static-files/helpers.v3.js} +82 -0
- package/static-files/page.v3.js +1290 -0
- package/static-files/recommended-frameworks.llmd +81 -0
- package/static-files/recommended-frameworks.md +137 -0
- package/static-files/retro-game.js +877 -0
- package/static-files/shell.css +797 -0
- package/static-files/theme-dark.css +169 -0
- package/static-files/theme-light.css +169 -0
- package/tests/builders.spec.ts +139 -0
- package/tests/pages.spec.ts +54 -84
- package/tests/transformPage.spec.ts +299 -360
- package/default-pages/application.html +0 -40
- package/default-pages/application.json +0 -1
- package/default-pages/json_tools.json +0 -1
- package/default-pages/my_notes.html +0 -33
- package/default-pages/neon_asteroids.html +0 -77
- package/default-pages/sidebar_page.json +0 -1
- package/default-pages/solar_tutorial.json +0 -1
- package/default-pages/two-panel_page.json +0 -1
- package/dist/service/useGatewayRoutes.d.ts +0 -4
- package/dist/service/useGatewayRoutes.d.ts.map +0 -1
- package/dist/service/useGatewayRoutes.js +0 -168
- package/dist/service/useGatewayRoutes.js.map +0 -1
- package/page-scripts/page-v2.js +0 -656
- package/required-pages/builder.html +0 -48
- package/required-pages/builder.json +0 -1
- package/required-pages/pages.json +0 -1
- package/required-pages/settings.json +0 -1
- package/required-pages/synthos_apis.html +0 -327
- package/required-pages/synthos_apis.json +0 -1
- package/required-pages/synthos_scripts.json +0 -1
- package/src/connectors/airtable/connector.json +0 -27
- package/src/connectors/alpha-vantage/connector.json +0 -26
- package/src/connectors/brave-search/connector.json +0 -26
- package/src/connectors/cloudinary/connector.json +0 -27
- package/src/connectors/deepl/connector.json +0 -28
- package/src/connectors/elevenlabs/connector.json +0 -30
- package/src/connectors/giphy/connector.json +0 -27
- package/src/connectors/github/connector.json +0 -29
- package/src/connectors/huggingface/connector.json +0 -27
- package/src/connectors/imgur/connector.json +0 -29
- package/src/connectors/instagram/connector.json +0 -43
- package/src/connectors/jira/connector.json +0 -28
- package/src/connectors/mapbox/connector.json +0 -26
- package/src/connectors/nasa/connector.json +0 -27
- package/src/connectors/newsapi/connector.json +0 -27
- package/src/connectors/notion/connector.json +0 -28
- package/src/connectors/open-exchange-rates/connector.json +0 -27
- package/src/connectors/openweathermap/connector.json +0 -26
- package/src/connectors/pexels/connector.json +0 -27
- package/src/connectors/resend/connector.json +0 -29
- package/src/connectors/rss2json/connector.json +0 -27
- package/src/connectors/sendgrid/connector.json +0 -27
- package/src/connectors/spoonacular/connector.json +0 -28
- package/src/connectors/stability-ai/connector.json +0 -27
- package/src/connectors/twilio/connector.json +0 -28
- package/src/connectors/unsplash/connector.json +0 -27
- package/src/connectors/wolfram-alpha/connector.json +0 -26
- package/src/connectors/youtube-data/connector.json +0 -30
- /package/{dist/connectors → service-connectors}/airtable/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/alpha-vantage/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/brave-search/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/cloudinary/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/deepl/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/elevenlabs/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/giphy/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/github/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/huggingface/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/imgur/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/instagram/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/jira/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/mapbox/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/nasa/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/newsapi/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/notion/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/open-exchange-rates/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/openweathermap/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/pexels/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/resend/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/rss2json/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/sendgrid/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/spoonacular/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/stability-ai/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/twilio/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/unsplash/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/wolfram-alpha/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/youtube-data/connector.json +0 -0
|
@@ -0,0 +1,109 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { Application } from 'express';
|
|
2
|
+
import express from 'express';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { SynthOSConfig } from '../init';
|
|
5
|
+
|
|
6
|
+
export function useSharedFileRoutes(config: SynthOSConfig, app: Application): void {
|
|
7
|
+
const sp = config.storageProvider;
|
|
8
|
+
|
|
9
|
+
// List files in the shared files folder
|
|
10
|
+
app.get('/api/shared/files', async (req, res) => {
|
|
11
|
+
try {
|
|
12
|
+
const folder = sharedFilesFolder(config);
|
|
13
|
+
if (!(await sp.checkIfExists(folder))) {
|
|
14
|
+
res.json({ files: [] });
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const entries = await sp.listFiles(folder);
|
|
19
|
+
const files: { name: string; size: number }[] = [];
|
|
20
|
+
for (const entry of entries) {
|
|
21
|
+
const stat = await sp.stat(path.join(folder, entry));
|
|
22
|
+
if (stat.isFile) {
|
|
23
|
+
files.push({ name: entry, size: stat.size });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
res.json({ files });
|
|
27
|
+
} catch (err: unknown) {
|
|
28
|
+
console.error(err);
|
|
29
|
+
res.status(500).json({ error: (err as Error).message });
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Download/serve a specific shared file
|
|
34
|
+
app.get('/api/shared/files/:filename', async (req, res) => {
|
|
35
|
+
try {
|
|
36
|
+
const filePath = safeFilePath(config, req.params.filename);
|
|
37
|
+
if (!filePath) {
|
|
38
|
+
res.status(400).json({ error: 'Invalid filename' });
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!(await sp.checkIfExists(filePath))) {
|
|
43
|
+
res.status(404).json({ error: 'File not found' });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
res.type(path.extname(req.params.filename));
|
|
48
|
+
sp.createReadStream(filePath).pipe(res);
|
|
49
|
+
} catch (err: unknown) {
|
|
50
|
+
console.error(err);
|
|
51
|
+
res.status(500).json({ error: (err as Error).message });
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Upload a shared file (raw body + x-filename header)
|
|
56
|
+
app.post('/api/shared/files', express.raw({ type: '*/*', limit: '50mb' }), async (req, res) => {
|
|
57
|
+
try {
|
|
58
|
+
const filename = req.headers['x-filename'] as string | undefined;
|
|
59
|
+
if (!filename || filename.trim().length === 0) {
|
|
60
|
+
res.status(400).json({ error: 'x-filename header is required' });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const filePath = safeFilePath(config, filename);
|
|
65
|
+
if (!filePath) {
|
|
66
|
+
res.status(400).json({ error: 'Invalid filename' });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const folder = sharedFilesFolder(config);
|
|
71
|
+
await sp.ensureFolderExists(folder);
|
|
72
|
+
await sp.saveBuffer(filePath, req.body as Buffer);
|
|
73
|
+
|
|
74
|
+
const stat = await sp.stat(filePath);
|
|
75
|
+
res.status(201).json({ name: filename, size: stat.size });
|
|
76
|
+
} catch (err: unknown) {
|
|
77
|
+
console.error(err);
|
|
78
|
+
res.status(500).json({ error: (err as Error).message });
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Delete a shared file
|
|
83
|
+
app.delete('/api/shared/files/:filename', async (req, res) => {
|
|
84
|
+
try {
|
|
85
|
+
const filePath = safeFilePath(config, req.params.filename);
|
|
86
|
+
if (!filePath) {
|
|
87
|
+
res.status(400).json({ error: 'Invalid filename' });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!(await sp.checkIfExists(filePath))) {
|
|
92
|
+
res.status(404).json({ error: 'File not found' });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
await sp.deleteFile(filePath);
|
|
97
|
+
res.json({ deleted: true });
|
|
98
|
+
} catch (err: unknown) {
|
|
99
|
+
console.error(err);
|
|
100
|
+
res.status(500).json({ error: (err as Error).message });
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
// Helpers
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
function sharedFilesFolder(config: SynthOSConfig): string {
|
|
110
|
+
return path.join(config.pagesFolder, 'shared', 'files');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Resolve a filename inside the shared files folder with path-traversal protection.
|
|
115
|
+
* Returns the absolute path if safe, or null if the filename is invalid.
|
|
116
|
+
*/
|
|
117
|
+
function safeFilePath(config: SynthOSConfig, filename: string): string | null {
|
|
118
|
+
if (!filename || filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
const folder = sharedFilesFolder(config);
|
|
122
|
+
const resolved = path.resolve(folder, filename);
|
|
123
|
+
if (!resolved.startsWith(path.resolve(folder))) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return resolved;
|
|
127
|
+
}
|
package/src/settings.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {checkIfExists, loadFile, saveFile} from './files';
|
|
2
1
|
import path from 'path';
|
|
3
2
|
import { ModelEntry, ProviderName, detectProvider } from './models';
|
|
4
3
|
import { AgentConfig } from './agents';
|
|
4
|
+
import { SynthOSConfig } from './init';
|
|
5
5
|
|
|
6
6
|
let _settings: Partial<SettingsV2>|undefined;
|
|
7
7
|
|
|
@@ -39,6 +39,7 @@ export interface SettingsV2 {
|
|
|
39
39
|
services?: ServicesConfig;
|
|
40
40
|
connectors?: ServicesConfig;
|
|
41
41
|
agents?: AgentConfig[];
|
|
42
|
+
toolbarPosition?: 'left' | 'right';
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
export const DefaultSettings: SettingsV2 = {
|
|
@@ -66,6 +67,7 @@ export const DefaultSettings: SettingsV2 = {
|
|
|
66
67
|
services: {},
|
|
67
68
|
connectors: {},
|
|
68
69
|
agents: [],
|
|
70
|
+
toolbarPosition: 'left',
|
|
69
71
|
};
|
|
70
72
|
|
|
71
73
|
/**
|
|
@@ -124,8 +126,8 @@ function migrateV1toV2(raw: Record<string, unknown>): SettingsV2 {
|
|
|
124
126
|
};
|
|
125
127
|
}
|
|
126
128
|
|
|
127
|
-
export async function hasConfiguredSettings(
|
|
128
|
-
const settings = await loadSettings(
|
|
129
|
+
export async function hasConfiguredSettings(config: SynthOSConfig): Promise<boolean> {
|
|
130
|
+
const settings = await loadSettings(config);
|
|
129
131
|
const builder = getModelEntry(settings, 'builder');
|
|
130
132
|
if (typeof builder.configuration.apiKey !== 'string' || builder.configuration.apiKey.length == 0) {
|
|
131
133
|
return false;
|
|
@@ -136,18 +138,19 @@ export async function hasConfiguredSettings(folder: string): Promise<boolean> {
|
|
|
136
138
|
return true;
|
|
137
139
|
}
|
|
138
140
|
|
|
139
|
-
export async function loadSettings(
|
|
141
|
+
export async function loadSettings(config: SynthOSConfig): Promise<SettingsV2> {
|
|
142
|
+
const sp = config.storageProvider;
|
|
140
143
|
if (_settings == undefined) {
|
|
141
|
-
const filename = path.join(
|
|
142
|
-
if (await checkIfExists(filename)) {
|
|
144
|
+
const filename = path.join(config.pagesFolder, 'settings.json');
|
|
145
|
+
if (await sp.checkIfExists(filename)) {
|
|
143
146
|
try {
|
|
144
|
-
const raw = JSON.parse(await loadFile(filename));
|
|
147
|
+
const raw = JSON.parse(await sp.loadFile(filename));
|
|
145
148
|
if (!raw.version) {
|
|
146
149
|
// V1 file — migrate
|
|
147
150
|
console.log('Migrating settings.json from v1 to v2...');
|
|
148
151
|
const migrated = migrateV1toV2(raw);
|
|
149
152
|
_settings = migrated;
|
|
150
|
-
await saveFile(filename, JSON.stringify(migrated, null, 4));
|
|
153
|
+
await sp.saveFile(filename, JSON.stringify(migrated, null, 4));
|
|
151
154
|
} else {
|
|
152
155
|
_settings = raw as Partial<SettingsV2>;
|
|
153
156
|
}
|
|
@@ -169,11 +172,12 @@ export async function loadSettings(folder: string): Promise<SettingsV2> {
|
|
|
169
172
|
return merged;
|
|
170
173
|
}
|
|
171
174
|
|
|
172
|
-
export async function saveSettings(
|
|
175
|
+
export async function saveSettings(config: SynthOSConfig, settings: Partial<SettingsV2>): Promise<void> {
|
|
176
|
+
const sp = config.storageProvider;
|
|
173
177
|
_settings = {..._settings, ...settings};
|
|
174
178
|
if (settings.models) {
|
|
175
179
|
_settings.models = settings.models;
|
|
176
180
|
}
|
|
177
181
|
_settings.version = 2;
|
|
178
|
-
await saveFile(path.join(
|
|
182
|
+
await sp.saveFile(path.join(config.pagesFolder, 'settings.json'), JSON.stringify(_settings, null, 4));
|
|
179
183
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import { createReadStream } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { StorageProvider } from './StorageProvider';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Default StorageProvider backed by the local filesystem via `fs/promises`.
|
|
8
|
+
* Mirrors the existing logic in `files.ts`.
|
|
9
|
+
*/
|
|
10
|
+
export class FsStorageProvider implements StorageProvider {
|
|
11
|
+
// --- Text file operations ---
|
|
12
|
+
|
|
13
|
+
async checkIfExists(filePath: string): Promise<boolean> {
|
|
14
|
+
try {
|
|
15
|
+
await fs.access(filePath);
|
|
16
|
+
return true;
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async loadFile(filePath: string): Promise<string> {
|
|
23
|
+
return await fs.readFile(filePath, 'utf8');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async saveFile(filePath: string, content: string): Promise<void> {
|
|
27
|
+
await fs.writeFile(filePath, content, 'utf8');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async deleteFile(filePath: string): Promise<void> {
|
|
31
|
+
await fs.unlink(filePath);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// --- Binary file operations ---
|
|
35
|
+
|
|
36
|
+
async saveBuffer(filePath: string, data: Buffer): Promise<void> {
|
|
37
|
+
await fs.writeFile(filePath, data);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async loadBuffer(filePath: string): Promise<Buffer> {
|
|
41
|
+
return await fs.readFile(filePath);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async stat(filePath: string): Promise<{ size: number; isFile: boolean }> {
|
|
45
|
+
const s = await fs.stat(filePath);
|
|
46
|
+
return { size: s.size, isFile: s.isFile() };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
createReadStream(filePath: string): NodeJS.ReadableStream {
|
|
50
|
+
return createReadStream(filePath);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// --- Directory operations ---
|
|
54
|
+
|
|
55
|
+
async listFiles(dirPath: string): Promise<string[]> {
|
|
56
|
+
return (await fs.readdir(dirPath)).filter(file => !file.startsWith('.') && file.includes('.'));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async listFolders(dirPath: string): Promise<string[]> {
|
|
60
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
61
|
+
return entries
|
|
62
|
+
.filter(entry => entry.isDirectory() && !entry.name.startsWith('.'))
|
|
63
|
+
.map(entry => entry.name);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async ensureFolderExists(dirPath: string): Promise<void> {
|
|
67
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async deleteFolder(dirPath: string): Promise<void> {
|
|
71
|
+
await fs.rm(dirPath, { recursive: true });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async copyFolderRecursive(srcPath: string, destPath: string): Promise<void> {
|
|
75
|
+
await this.ensureFolderExists(destPath);
|
|
76
|
+
const entries = await fs.readdir(srcPath, { withFileTypes: true });
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
const src = path.join(srcPath, entry.name);
|
|
79
|
+
const dest = path.join(destPath, entry.name);
|
|
80
|
+
if (entry.isDirectory()) {
|
|
81
|
+
await this.copyFolderRecursive(src, dest);
|
|
82
|
+
} else {
|
|
83
|
+
await fs.copyFile(src, dest);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstraction over user-mutable storage (pages, settings, themes, data, uploads).
|
|
3
|
+
*
|
|
4
|
+
* All paths are absolute. Callers build paths with `path.join(config.pagesFolder, ...)`
|
|
5
|
+
* exactly as they do today — the provider simply executes the I/O against
|
|
6
|
+
* whatever backend it wraps (local filesystem, blob storage, etc.).
|
|
7
|
+
*
|
|
8
|
+
* Package/read-only content (required-pages, default-themes, static-files,
|
|
9
|
+
* service-connectors) stays on local filesystem and is NOT routed through
|
|
10
|
+
* this interface.
|
|
11
|
+
*/
|
|
12
|
+
export interface StorageProvider {
|
|
13
|
+
// --- Text file operations ---
|
|
14
|
+
|
|
15
|
+
checkIfExists(filePath: string): Promise<boolean>;
|
|
16
|
+
loadFile(filePath: string): Promise<string>;
|
|
17
|
+
saveFile(filePath: string, content: string): Promise<void>;
|
|
18
|
+
deleteFile(filePath: string): Promise<void>;
|
|
19
|
+
|
|
20
|
+
// --- Binary file operations (uploads / images) ---
|
|
21
|
+
|
|
22
|
+
saveBuffer(filePath: string, data: Buffer): Promise<void>;
|
|
23
|
+
loadBuffer(filePath: string): Promise<Buffer>;
|
|
24
|
+
stat(filePath: string): Promise<{ size: number; isFile: boolean }>;
|
|
25
|
+
createReadStream(filePath: string): NodeJS.ReadableStream;
|
|
26
|
+
|
|
27
|
+
// --- Directory operations ---
|
|
28
|
+
|
|
29
|
+
listFiles(dirPath: string): Promise<string[]>;
|
|
30
|
+
listFolders(dirPath: string): Promise<string[]>;
|
|
31
|
+
ensureFolderExists(dirPath: string): Promise<void>;
|
|
32
|
+
deleteFolder(dirPath: string): Promise<void>;
|
|
33
|
+
copyFolderRecursive(srcPath: string, destPath: string): Promise<void>;
|
|
34
|
+
}
|
package/src/synthos-cli.ts
CHANGED
|
@@ -2,6 +2,7 @@ import yargs from "yargs";
|
|
|
2
2
|
import { hideBin } from "yargs/helpers";
|
|
3
3
|
import { server } from "./service";
|
|
4
4
|
import { createConfig, init } from "./init";
|
|
5
|
+
import { customizer } from "./customizer";
|
|
5
6
|
|
|
6
7
|
const dynamicImport = new Function('specifier', `return import(specifier)`);
|
|
7
8
|
|
|
@@ -16,7 +17,7 @@ export async function run() {
|
|
|
16
17
|
default: 4242
|
|
17
18
|
})
|
|
18
19
|
.option('pages', {
|
|
19
|
-
describe: `Include default pages when initializing a new
|
|
20
|
+
describe: `Include default pages when initializing a new local folder.`,
|
|
20
21
|
type: 'boolean',
|
|
21
22
|
default: true
|
|
22
23
|
})
|
|
@@ -32,9 +33,9 @@ export async function run() {
|
|
|
32
33
|
})
|
|
33
34
|
.demandOption([]);
|
|
34
35
|
}, async (args) => {
|
|
35
|
-
const config = createConfig(
|
|
36
|
+
const config = await createConfig(customizer.localFolder, { debug: args.debug, debugPageUpdates: args.debugPageUpdates }, customizer);
|
|
36
37
|
await init(config, args.pages);
|
|
37
|
-
await server(config).listen(args.port, async () => {
|
|
38
|
+
await server(config, customizer).listen(args.port, async () => {
|
|
38
39
|
console.log(`SynthOS server is running on http://localhost:${args.port}`);
|
|
39
40
|
|
|
40
41
|
// Open using default browser
|
package/src/themes.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
-
import { checkIfExists, listFiles, loadFile } from './files';
|
|
2
|
+
import { checkIfExists, findFileInFolders, listFiles, listFilesFromFolders, loadFile } from './files';
|
|
3
3
|
import { SynthOSConfig } from './init';
|
|
4
4
|
|
|
5
5
|
export const THEME_VERSION = 2;
|
|
@@ -28,10 +28,29 @@ export function parseThemeFilename(filename: string): { name: string; version: n
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
|
-
* Find the CSS file for a theme by name in a folder.
|
|
31
|
+
* Find the CSS file for a theme by name in a user folder (via storageProvider).
|
|
32
32
|
* Prefers the highest-versioned file (e.g. name.v2.css over name.css).
|
|
33
33
|
*/
|
|
34
|
-
async function
|
|
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> {
|
|
35
54
|
if (!await checkIfExists(folder)) return undefined;
|
|
36
55
|
const files = await listFiles(folder);
|
|
37
56
|
let best: { path: string; version: number } | undefined;
|
|
@@ -46,16 +65,29 @@ async function findThemeCssFile(folder: string, name: string): Promise<{ path: s
|
|
|
46
65
|
return best;
|
|
47
66
|
}
|
|
48
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
|
+
|
|
49
78
|
export async function loadThemeInfo(name: string, config: SynthOSConfig): Promise<ThemeInfo | undefined> {
|
|
50
|
-
|
|
79
|
+
const sp = config.storageProvider;
|
|
80
|
+
|
|
81
|
+
// Check user's local themes first (user storage)
|
|
51
82
|
const localPath = path.join(userThemesFolder(config), `${name}.json`);
|
|
52
|
-
if (await checkIfExists(localPath)) {
|
|
53
|
-
const raw = await loadFile(localPath);
|
|
83
|
+
if (await sp.checkIfExists(localPath)) {
|
|
84
|
+
const raw = await sp.loadFile(localPath);
|
|
54
85
|
return raw ? JSON.parse(raw) : undefined;
|
|
55
86
|
}
|
|
56
87
|
|
|
57
|
-
|
|
58
|
-
|
|
88
|
+
// Fall back to package defaults (local fs)
|
|
89
|
+
const defaultPath = await findFileInFolders(config.defaultThemesFolders, `${name}.json`);
|
|
90
|
+
if (defaultPath) {
|
|
59
91
|
const raw = await loadFile(defaultPath);
|
|
60
92
|
return raw ? JSON.parse(raw) : undefined;
|
|
61
93
|
}
|
|
@@ -64,40 +96,44 @@ export async function loadThemeInfo(name: string, config: SynthOSConfig): Promis
|
|
|
64
96
|
}
|
|
65
97
|
|
|
66
98
|
export async function loadTheme(name: string, config: SynthOSConfig): Promise<string | undefined> {
|
|
67
|
-
|
|
68
|
-
|
|
99
|
+
const sp = config.storageProvider;
|
|
100
|
+
|
|
101
|
+
// Check user's local themes first (user storage)
|
|
102
|
+
const local = await findUserThemeCssFile(config, userThemesFolder(config), name);
|
|
69
103
|
if (local) {
|
|
70
|
-
return await loadFile(local.path);
|
|
104
|
+
return await sp.loadFile(local.path);
|
|
71
105
|
}
|
|
72
106
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
+
}
|
|
76
113
|
}
|
|
77
114
|
|
|
78
115
|
return undefined;
|
|
79
116
|
}
|
|
80
117
|
|
|
81
118
|
export async function listThemes(config: SynthOSConfig): Promise<string[]> {
|
|
119
|
+
const sp = config.storageProvider;
|
|
82
120
|
const names = new Set<string>();
|
|
83
121
|
|
|
84
|
-
// Collect from user's local themes folder
|
|
122
|
+
// Collect from user's local themes folder (user storage)
|
|
85
123
|
const localFolder = userThemesFolder(config);
|
|
86
|
-
if (await checkIfExists(localFolder)) {
|
|
87
|
-
const files = await listFiles(localFolder);
|
|
124
|
+
if (await sp.checkIfExists(localFolder)) {
|
|
125
|
+
const files = await sp.listFiles(localFolder);
|
|
88
126
|
for (const f of files) {
|
|
89
127
|
const parsed = parseThemeFilename(f);
|
|
90
128
|
if (parsed) names.add(parsed.name);
|
|
91
129
|
}
|
|
92
130
|
}
|
|
93
131
|
|
|
94
|
-
// Collect from
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (parsed) names.add(parsed.name);
|
|
100
|
-
}
|
|
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);
|
|
101
137
|
}
|
|
102
138
|
|
|
103
139
|
return Array.from(names).sort();
|
|
@@ -107,11 +143,12 @@ export async function listThemes(config: SynthOSConfig): Promise<string[]> {
|
|
|
107
143
|
* Compare local theme versions against defaults and return themes that need upgrading.
|
|
108
144
|
*/
|
|
109
145
|
export async function getOutdatedThemes(config: SynthOSConfig): Promise<string[]> {
|
|
146
|
+
const sp = config.storageProvider;
|
|
110
147
|
const localFolder = userThemesFolder(config);
|
|
111
|
-
if (!await checkIfExists(localFolder)) return [];
|
|
148
|
+
if (!await sp.checkIfExists(localFolder)) return [];
|
|
112
149
|
|
|
113
|
-
const defaultFiles = await
|
|
114
|
-
const localFiles = await listFiles(localFolder);
|
|
150
|
+
const defaultFiles = await listFilesFromFolders(config.defaultThemesFolders);
|
|
151
|
+
const localFiles = await sp.listFiles(localFolder);
|
|
115
152
|
|
|
116
153
|
// Build maps: theme name → highest version
|
|
117
154
|
const defaultVersions = new Map<string, number>();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
+
<stop offset="0%" stop-color="#a855f7"/>
|
|
5
|
+
<stop offset="100%" stop-color="#3b82f6"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="32" height="32" rx="7" fill="#0f0f13"/>
|
|
9
|
+
<path d="M9 11 C9 8.8 10.8 7 13 7 L20 7 C22.2 7 24 8.8 24 11 C24 13.2 22.2 15 20 15 L13 15 C10.8 15 9 16.8 9 19 C9 21.2 10.8 23 13 23 L20 23" stroke="url(#g)" stroke-width="3" stroke-linecap="round" fill="none"/>
|
|
10
|
+
<circle cx="9" cy="11" r="2" fill="#a855f7"/>
|
|
11
|
+
<circle cx="23" cy="23" r="2" fill="#3b82f6"/>
|
|
12
|
+
</svg>
|