synthos 0.9.0 → 0.10.1
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/default-pages/neon_asteroids/page.html +2 -21
- package/default-pages/oregon_trail/page.html +3 -19
- package/default-pages/solar_explorer/page.html +1 -1
- package/default-pages/western_cities_1850/page.html +2 -2
- package/dist/agents/a2a/a2aProvider.d.ts +3 -0
- package/dist/agents/discovery.d.ts +30 -0
- package/dist/agents/openclaw/openclawProvider.d.ts +3 -0
- package/dist/agents/types.d.ts +64 -0
- package/dist/connectors/index.d.ts +3 -0
- package/dist/connectors/types.d.ts +84 -0
- package/dist/customizer/Customizer.d.ts +5 -0
- package/dist/customizer/Customizer.d.ts.map +1 -1
- package/dist/customizer/Customizer.js +10 -0
- package/dist/customizer/Customizer.js.map +1 -1
- package/dist/customizer/index.d.ts +4 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +2 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +71 -37
- package/dist/init.js.map +1 -1
- package/dist/migrations.d.ts +12 -0
- package/dist/models/chainOfThought.d.ts +12 -0
- package/dist/models/fireworksai.d.ts +30 -0
- package/dist/models/logCompletePrompt.d.ts +3 -0
- package/dist/models/providers.d.ts +8 -0
- package/dist/models/utils.d.ts +6 -0
- package/dist/pages.d.ts +12 -11
- package/dist/pages.d.ts.map +1 -1
- package/dist/pages.js +94 -66
- package/dist/pages.js.map +1 -1
- package/dist/scripts.d.ts +15 -0
- package/dist/service/createCompletePrompt.d.ts +6 -0
- 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/debugLog.d.ts +11 -0
- package/dist/service/generateImage.d.ts +32 -0
- package/dist/service/index.d.ts +8 -0
- package/dist/service/modelInstructions.d.ts +7 -0
- package/dist/service/requiresSettings.d.ts +4 -0
- 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 +5 -0
- package/dist/service/useAgentRoutes.js +12 -12
- package/dist/service/useAgentRoutes.js.map +1 -1
- package/dist/service/useApiRoutes.d.ts +5 -0
- package/dist/service/useApiRoutes.d.ts.map +1 -1
- package/dist/service/useApiRoutes.js +74 -60
- package/dist/service/useApiRoutes.js.map +1 -1
- package/dist/service/useConnectorRoutes.d.ts +4 -0
- package/dist/service/useConnectorRoutes.js +11 -11
- package/dist/service/useConnectorRoutes.js.map +1 -1
- package/dist/service/useDataRoutes.d.ts +4 -0
- 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.map +1 -1
- package/dist/service/useFileRoutes.js +13 -13
- package/dist/service/useFileRoutes.js.map +1 -1
- package/dist/service/usePageRoutes.d.ts +6 -0
- package/dist/service/usePageRoutes.d.ts.map +1 -1
- package/dist/service/usePageRoutes.js +54 -38
- package/dist/service/usePageRoutes.js.map +1 -1
- package/dist/service/useSharedDataRoutes.d.ts.map +1 -1
- package/dist/service/useSharedDataRoutes.js +13 -10
- package/dist/service/useSharedDataRoutes.js.map +1 -1
- package/dist/service/useSharedFileRoutes.d.ts.map +1 -1
- package/dist/service/useSharedFileRoutes.js +13 -13
- package/dist/service/useSharedFileRoutes.js.map +1 -1
- package/dist/settings.d.ts +4 -3
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +11 -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 +2 -0
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +42 -18
- package/dist/themes.js.map +1 -1
- package/package.json +1 -1
- package/src/builders/anthropic.ts +5 -5
- package/src/customizer/Customizer.ts +12 -0
- package/src/index.ts +2 -1
- package/src/init.ts +78 -42
- package/src/models/providers.ts +2 -2
- package/src/pages.ts +98 -67
- package/src/service/createCompletePrompt.ts +3 -2
- package/src/service/requiresSettings.ts +4 -3
- package/src/service/useAgentRoutes.ts +12 -12
- package/src/service/useApiRoutes.ts +76 -61
- package/src/service/useConnectorRoutes.ts +11 -11
- package/src/service/useDataRoutes.ts +13 -10
- package/src/service/useFileRoutes.ts +14 -13
- package/src/service/usePageRoutes.ts +42 -40
- package/src/service/useSharedDataRoutes.ts +13 -10
- package/src/service/useSharedFileRoutes.ts +14 -13
- package/src/settings.ts +12 -10
- package/src/storage/FsStorageProvider.ts +87 -0
- package/src/storage/StorageProvider.ts +34 -0
- package/src/storage/index.ts +2 -0
- package/src/themes.ts +44 -18
- package/tests/builders.spec.ts +2 -2
- package/tests/pages.spec.ts +54 -84
- package/tests/providers.spec.ts +1 -1
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Application, Response } from 'express';
|
|
2
2
|
import { SynthOSConfig } from "../init";
|
|
3
|
-
import { checkIfExists, deleteFile, ensureFolderExists, listFiles, loadFile, saveFile } from "../files";
|
|
4
3
|
import path from "path";
|
|
5
4
|
import { v4 } from "uuid";
|
|
6
5
|
import { clearCachedScripts } from '../scripts';
|
|
@@ -17,19 +16,20 @@ export function useDataRoutes(config: SynthOSConfig, app: Application): void {
|
|
|
17
16
|
// ---------------------------------------------------------------------------
|
|
18
17
|
|
|
19
18
|
async function handleList(config: SynthOSConfig, page: string, table: string, query: Record<string, any>, res: Response): Promise<void> {
|
|
19
|
+
const sp = config.storageProvider;
|
|
20
20
|
const folder = tableFolder(config, page, table);
|
|
21
|
-
if (!(await checkIfExists(folder))) {
|
|
21
|
+
if (!(await sp.checkIfExists(folder))) {
|
|
22
22
|
res.status(404).json({ error: 'table_not_found', page, table });
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
const ids = (await listFiles(folder)).filter(f => f.endsWith('.json')).map(f => f.replace('.json', ''));
|
|
26
|
+
const ids = (await sp.listFiles(folder)).filter(f => f.endsWith('.json')).map(f => f.replace('.json', ''));
|
|
27
27
|
|
|
28
28
|
const rows: Record<string, any>[] = [];
|
|
29
29
|
for (const id of ids) {
|
|
30
30
|
const file = recordFile(folder, id);
|
|
31
31
|
try {
|
|
32
|
-
const row = JSON.parse(await loadFile(file));
|
|
32
|
+
const row = JSON.parse(await sp.loadFile(file));
|
|
33
33
|
row.id = id;
|
|
34
34
|
rows.push(row);
|
|
35
35
|
} catch (err: unknown) {
|
|
@@ -49,15 +49,16 @@ async function handleList(config: SynthOSConfig, page: string, table: string, qu
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
async function handleGet(config: SynthOSConfig, page: string, table: string, id: string, res: Response): Promise<void> {
|
|
52
|
+
const sp = config.storageProvider;
|
|
52
53
|
const folder = tableFolder(config, page, table);
|
|
53
|
-
if (!(await checkIfExists(folder))) {
|
|
54
|
+
if (!(await sp.checkIfExists(folder))) {
|
|
54
55
|
res.status(404).json({ error: 'table_not_found', page, table });
|
|
55
56
|
return;
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
const file = recordFile(folder, id);
|
|
59
60
|
try {
|
|
60
|
-
const row = JSON.parse(await loadFile(file));
|
|
61
|
+
const row = JSON.parse(await sp.loadFile(file));
|
|
61
62
|
row.id = id;
|
|
62
63
|
res.json(row);
|
|
63
64
|
} catch (err: unknown) {
|
|
@@ -66,13 +67,14 @@ async function handleGet(config: SynthOSConfig, page: string, table: string, id:
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
async function handleUpsert(config: SynthOSConfig, page: string, table: string, body: any, res: Response): Promise<void> {
|
|
70
|
+
const sp = config.storageProvider;
|
|
69
71
|
const id = body.id ?? v4();
|
|
70
72
|
const folder = tableFolder(config, page, table);
|
|
71
73
|
const file = recordFile(folder, id);
|
|
72
74
|
try {
|
|
73
75
|
const row = { ...body, id };
|
|
74
|
-
await ensureFolderExists(folder);
|
|
75
|
-
await saveFile(file, JSON.stringify(row, null, 4));
|
|
76
|
+
await sp.ensureFolderExists(folder);
|
|
77
|
+
await sp.saveFile(file, JSON.stringify(row, null, 4));
|
|
76
78
|
if (table === 'scripts') {
|
|
77
79
|
clearCachedScripts();
|
|
78
80
|
}
|
|
@@ -84,11 +86,12 @@ async function handleUpsert(config: SynthOSConfig, page: string, table: string,
|
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
async function handleDelete(config: SynthOSConfig, page: string, table: string, id: string, res: Response): Promise<void> {
|
|
89
|
+
const sp = config.storageProvider;
|
|
87
90
|
const folder = tableFolder(config, page, table);
|
|
88
91
|
const file = recordFile(folder, id);
|
|
89
92
|
try {
|
|
90
|
-
if (await checkIfExists(file)) {
|
|
91
|
-
await deleteFile(file);
|
|
93
|
+
if (await sp.checkIfExists(file)) {
|
|
94
|
+
await sp.deleteFile(file);
|
|
92
95
|
if (table === 'scripts') {
|
|
93
96
|
clearCachedScripts();
|
|
94
97
|
}
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
import { Application } from 'express';
|
|
2
2
|
import express from 'express';
|
|
3
|
-
import fs from 'fs/promises';
|
|
4
3
|
import path from 'path';
|
|
5
4
|
import { SynthOSConfig } from '../init';
|
|
6
|
-
import { checkIfExists, ensureFolderExists } from '../files';
|
|
7
5
|
|
|
8
6
|
export function useFileRoutes(config: SynthOSConfig, app: Application): void {
|
|
7
|
+
const sp = config.storageProvider;
|
|
8
|
+
|
|
9
9
|
// List files in a page's files/ folder
|
|
10
10
|
app.get('/api/files/:page', async (req, res) => {
|
|
11
11
|
try {
|
|
12
12
|
const folder = filesFolder(config, req.params.page);
|
|
13
|
-
if (!(await checkIfExists(folder))) {
|
|
13
|
+
if (!(await sp.checkIfExists(folder))) {
|
|
14
14
|
res.json({ files: [] });
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const entries = await
|
|
18
|
+
const entries = await sp.listFiles(folder);
|
|
19
19
|
const files: { name: string; size: number }[] = [];
|
|
20
20
|
for (const entry of entries) {
|
|
21
|
-
const stat = await
|
|
22
|
-
if (stat.isFile
|
|
21
|
+
const stat = await sp.stat(path.join(folder, entry));
|
|
22
|
+
if (stat.isFile) {
|
|
23
23
|
files.push({ name: entry, size: stat.size });
|
|
24
24
|
}
|
|
25
25
|
}
|
|
@@ -39,12 +39,13 @@ export function useFileRoutes(config: SynthOSConfig, app: Application): void {
|
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
if (!(await checkIfExists(filePath))) {
|
|
42
|
+
if (!(await sp.checkIfExists(filePath))) {
|
|
43
43
|
res.status(404).json({ error: 'File not found' });
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
res.
|
|
47
|
+
res.type(path.extname(req.params.filename));
|
|
48
|
+
sp.createReadStream(filePath).pipe(res);
|
|
48
49
|
} catch (err: unknown) {
|
|
49
50
|
console.error(err);
|
|
50
51
|
res.status(500).json({ error: (err as Error).message });
|
|
@@ -67,10 +68,10 @@ export function useFileRoutes(config: SynthOSConfig, app: Application): void {
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
const folder = filesFolder(config, req.params.page);
|
|
70
|
-
await ensureFolderExists(folder);
|
|
71
|
-
await
|
|
71
|
+
await sp.ensureFolderExists(folder);
|
|
72
|
+
await sp.saveBuffer(filePath, req.body as Buffer);
|
|
72
73
|
|
|
73
|
-
const stat = await
|
|
74
|
+
const stat = await sp.stat(filePath);
|
|
74
75
|
res.status(201).json({ name: filename, size: stat.size });
|
|
75
76
|
} catch (err: unknown) {
|
|
76
77
|
console.error(err);
|
|
@@ -87,12 +88,12 @@ export function useFileRoutes(config: SynthOSConfig, app: Application): void {
|
|
|
87
88
|
return;
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
if (!(await checkIfExists(filePath))) {
|
|
91
|
+
if (!(await sp.checkIfExists(filePath))) {
|
|
91
92
|
res.status(404).json({ error: 'File not found' });
|
|
92
93
|
return;
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
await
|
|
96
|
+
await sp.deleteFile(filePath);
|
|
96
97
|
res.json({ deleted: true });
|
|
97
98
|
} catch (err: unknown) {
|
|
98
99
|
console.error(err);
|
|
@@ -13,7 +13,7 @@ import { getConnectorRegistry, ConnectorsConfig, ConnectorOAuthConfig } from "..
|
|
|
13
13
|
import { AgentConfig } from "../agents";
|
|
14
14
|
import { listScripts } from "../scripts";
|
|
15
15
|
import path from 'path';
|
|
16
|
-
import { checkIfExists,
|
|
16
|
+
import { checkIfExists, findFileInFolders, loadFile } from "../files";
|
|
17
17
|
import * as cheerio from 'cheerio';
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -455,7 +455,7 @@ export function usePageRoutes(config: SynthOSConfig, app: Application, customize
|
|
|
455
455
|
app.get('/:page', async (req, res) => {
|
|
456
456
|
// Redirect if settings not configured
|
|
457
457
|
const { page } = req.params;
|
|
458
|
-
const isConfigured = await hasConfiguredSettings(config
|
|
458
|
+
const isConfigured = await hasConfiguredSettings(config);
|
|
459
459
|
if (!isConfigured && page !== 'settings') {
|
|
460
460
|
res.redirect('/settings?firstRun=1');
|
|
461
461
|
return;
|
|
@@ -470,7 +470,7 @@ export function usePageRoutes(config: SynthOSConfig, app: Application, customize
|
|
|
470
470
|
}
|
|
471
471
|
|
|
472
472
|
// Load page metadata for version-based script injection
|
|
473
|
-
const metadata = await loadPageMetadata(config
|
|
473
|
+
const metadata = await loadPageMetadata(config, page, config.requiredPagesFolders);
|
|
474
474
|
const pageVersion = metadata?.pageVersion ?? 0;
|
|
475
475
|
|
|
476
476
|
// Block outdated pages (redirect to tabs list so user sees upgrade UI)
|
|
@@ -480,7 +480,7 @@ export function usePageRoutes(config: SynthOSConfig, app: Application, customize
|
|
|
480
480
|
}
|
|
481
481
|
|
|
482
482
|
// Load settings to determine theme version for FluentLM base injection
|
|
483
|
-
const settings = await loadSettings(config
|
|
483
|
+
const settings = await loadSettings(config);
|
|
484
484
|
const themeName = settings.theme ?? 'nebula-dusk';
|
|
485
485
|
const themeVersion = await loadThemeVersion(themeName, config);
|
|
486
486
|
|
|
@@ -493,7 +493,7 @@ export function usePageRoutes(config: SynthOSConfig, app: Application, customize
|
|
|
493
493
|
|
|
494
494
|
// Inject version meta tag so undo/try-again links appear on page load
|
|
495
495
|
{
|
|
496
|
-
const latestVersion = await getLatestVersion(config
|
|
496
|
+
const latestVersion = await getLatestVersion(config, page);
|
|
497
497
|
if (latestVersion > 0) {
|
|
498
498
|
html = html.replace('</head>', `<meta name="synthos-version" content="${latestVersion}">\n</head>`);
|
|
499
499
|
}
|
|
@@ -504,7 +504,7 @@ export function usePageRoutes(config: SynthOSConfig, app: Application, customize
|
|
|
504
504
|
// Replace branding for white-label forks
|
|
505
505
|
const productName = customizer?.productName ?? 'SynthOS';
|
|
506
506
|
if (productName !== 'SynthOS') {
|
|
507
|
-
html = html.replace(/
|
|
507
|
+
html = html.replace(/synthos/gi, productName);
|
|
508
508
|
}
|
|
509
509
|
|
|
510
510
|
res.send(html);
|
|
@@ -515,7 +515,7 @@ export function usePageRoutes(config: SynthOSConfig, app: Application, customize
|
|
|
515
515
|
try {
|
|
516
516
|
// Redirect if settings not configured
|
|
517
517
|
const { page } = req.params;
|
|
518
|
-
const isConfigured = await hasConfiguredSettings(config
|
|
518
|
+
const isConfigured = await hasConfiguredSettings(config);
|
|
519
519
|
if (!isConfigured) {
|
|
520
520
|
res.status(400).json({ error: 'Settings not configured' });
|
|
521
521
|
return;
|
|
@@ -571,7 +571,7 @@ export function usePageRoutes(config: SynthOSConfig, app: Application, customize
|
|
|
571
571
|
}
|
|
572
572
|
|
|
573
573
|
// Inject save-line marker at the end of chat messages (skip for locked pages)
|
|
574
|
-
const sourceMetadata = await loadPageMetadata(config
|
|
574
|
+
const sourceMetadata = await loadPageMetadata(config, page, config.requiredPagesFolders);
|
|
575
575
|
if (sourceMetadata?.mode !== 'locked') {
|
|
576
576
|
const $ = cheerio.load(pageState);
|
|
577
577
|
// Remove any existing save-line first
|
|
@@ -584,14 +584,14 @@ export function usePageRoutes(config: SynthOSConfig, app: Application, customize
|
|
|
584
584
|
}
|
|
585
585
|
|
|
586
586
|
// Save as new page
|
|
587
|
-
await savePageState(config
|
|
587
|
+
await savePageState(config, saveAs, pageState, title, categories);
|
|
588
588
|
|
|
589
589
|
// Copy files (sound effects, etc.) from source page when saving as a different name
|
|
590
590
|
if (page !== saveAs) {
|
|
591
591
|
// Check source page's files/ dir in user pages, then fallback folders
|
|
592
592
|
let sourceFilesDir: string | undefined;
|
|
593
593
|
const userFilesDir = path.join(config.pagesFolder, 'pages', page, 'files');
|
|
594
|
-
if (await checkIfExists(userFilesDir)) {
|
|
594
|
+
if (await config.storageProvider.checkIfExists(userFilesDir)) {
|
|
595
595
|
sourceFilesDir = userFilesDir;
|
|
596
596
|
} else {
|
|
597
597
|
for (const folder of config.requiredPagesFolders) {
|
|
@@ -604,15 +604,15 @@ export function usePageRoutes(config: SynthOSConfig, app: Application, customize
|
|
|
604
604
|
}
|
|
605
605
|
if (sourceFilesDir) {
|
|
606
606
|
const targetFilesDir = path.join(config.pagesFolder, 'pages', saveAs, 'files');
|
|
607
|
-
await copyFolderRecursive(sourceFilesDir, targetFilesDir);
|
|
607
|
+
await config.storageProvider.copyFolderRecursive(sourceFilesDir, targetFilesDir);
|
|
608
608
|
}
|
|
609
609
|
}
|
|
610
610
|
|
|
611
611
|
// Clear version files after saving (fresh baseline)
|
|
612
|
-
await clearVersions(config
|
|
612
|
+
await clearVersions(config, saveAs);
|
|
613
613
|
|
|
614
614
|
// Also update metadata with categories (in case page.json already existed)
|
|
615
|
-
await savePageMetadata(config
|
|
615
|
+
await savePageMetadata(config, saveAs, {
|
|
616
616
|
title,
|
|
617
617
|
categories,
|
|
618
618
|
pinned: false,
|
|
@@ -634,25 +634,25 @@ export function usePageRoutes(config: SynthOSConfig, app: Application, customize
|
|
|
634
634
|
app.post('/:page/undo', async (req, res) => {
|
|
635
635
|
try {
|
|
636
636
|
const { page } = req.params;
|
|
637
|
-
const
|
|
638
|
-
const cv = await getLatestVersion(
|
|
637
|
+
const sp = config.storageProvider;
|
|
638
|
+
const cv = await getLatestVersion(config, page);
|
|
639
639
|
if (cv <= 0) {
|
|
640
640
|
res.status(400).send('Nothing to undo');
|
|
641
641
|
return;
|
|
642
642
|
}
|
|
643
643
|
|
|
644
644
|
// Delete the current version file
|
|
645
|
-
const pageFolder = path.join(pagesFolder, 'pages', page);
|
|
645
|
+
const pageFolder = path.join(config.pagesFolder, 'pages', page);
|
|
646
646
|
const versionFile = path.join(pageFolder, `page.v${cv}.html`);
|
|
647
|
-
if (await checkIfExists(versionFile)) {
|
|
648
|
-
await
|
|
647
|
+
if (await sp.checkIfExists(versionFile)) {
|
|
648
|
+
await sp.deleteFile(versionFile);
|
|
649
649
|
}
|
|
650
650
|
|
|
651
651
|
// Load previous version (v{cv-1}.html, or page.html if rolling back to v0)
|
|
652
652
|
const prevVersion = cv - 1;
|
|
653
653
|
let previousHtml: string | undefined;
|
|
654
654
|
if (prevVersion > 0) {
|
|
655
|
-
previousHtml = await loadPageVersion(
|
|
655
|
+
previousHtml = await loadPageVersion(config, page, prevVersion);
|
|
656
656
|
} else {
|
|
657
657
|
// v0 = the saved page.html baseline (user folder → required folders fallback)
|
|
658
658
|
previousHtml = await loadPageWithFallback(page, config, true);
|
|
@@ -664,8 +664,8 @@ export function usePageRoutes(config: SynthOSConfig, app: Application, customize
|
|
|
664
664
|
}
|
|
665
665
|
|
|
666
666
|
// Inject shell assets (same as GET handler)
|
|
667
|
-
const settings = await loadSettings(config
|
|
668
|
-
const metadata = await loadPageMetadata(
|
|
667
|
+
const settings = await loadSettings(config);
|
|
668
|
+
const metadata = await loadPageMetadata(config, page, config.requiredPagesFolders);
|
|
669
669
|
const pv = metadata?.pageVersion ?? 0;
|
|
670
670
|
const themeName = settings.theme ?? 'nebula-dusk';
|
|
671
671
|
const themeVersion = await loadThemeVersion(themeName, config);
|
|
@@ -688,7 +688,7 @@ export function usePageRoutes(config: SynthOSConfig, app: Application, customize
|
|
|
688
688
|
// Replace branding for white-label forks
|
|
689
689
|
const productName = customizer?.productName ?? 'SynthOS';
|
|
690
690
|
if (productName !== 'SynthOS') {
|
|
691
|
-
out = out.replace(/
|
|
691
|
+
out = out.replace(/synthos/gi, productName);
|
|
692
692
|
}
|
|
693
693
|
|
|
694
694
|
res.send(out);
|
|
@@ -703,7 +703,7 @@ export function usePageRoutes(config: SynthOSConfig, app: Application, customize
|
|
|
703
703
|
try {
|
|
704
704
|
// Ensure settings configured
|
|
705
705
|
const { page } = req.params;
|
|
706
|
-
const isConfigured = await hasConfiguredSettings(config
|
|
706
|
+
const isConfigured = await hasConfiguredSettings(config);
|
|
707
707
|
if (!isConfigured) {
|
|
708
708
|
res.status(400).send('Settings not configured');
|
|
709
709
|
return;
|
|
@@ -717,7 +717,7 @@ export function usePageRoutes(config: SynthOSConfig, app: Application, customize
|
|
|
717
717
|
}
|
|
718
718
|
|
|
719
719
|
// Reject modifications to locked pages
|
|
720
|
-
const lockMetadata = await loadPageMetadata(config
|
|
720
|
+
const lockMetadata = await loadPageMetadata(config, page, config.requiredPagesFolders);
|
|
721
721
|
if (lockMetadata?.mode === 'locked') {
|
|
722
722
|
res.status(403).send('This page is locked and cannot be modified');
|
|
723
723
|
return;
|
|
@@ -743,7 +743,7 @@ export function usePageRoutes(config: SynthOSConfig, app: Application, customize
|
|
|
743
743
|
}
|
|
744
744
|
|
|
745
745
|
// Create model instance
|
|
746
|
-
const innerCompletePrompt = await createCompletePrompt(config
|
|
746
|
+
const innerCompletePrompt = await createCompletePrompt(config, 'builder', req.body.model);
|
|
747
747
|
const debugVerbose = config.debugPageUpdates;
|
|
748
748
|
let inputChars = 0;
|
|
749
749
|
let outputChars = 0;
|
|
@@ -775,7 +775,7 @@ export function usePageRoutes(config: SynthOSConfig, app: Application, customize
|
|
|
775
775
|
|
|
776
776
|
// Load settings and build context
|
|
777
777
|
const pagesFolder = config.pagesFolder;
|
|
778
|
-
const settings = await loadSettings(config
|
|
778
|
+
const settings = await loadSettings(config);
|
|
779
779
|
const entry = getModelEntry(settings, 'builder');
|
|
780
780
|
const { instructions } = entry;
|
|
781
781
|
const productName = customizer?.productName ?? 'SynthOS';
|
|
@@ -837,8 +837,8 @@ export function usePageRoutes(config: SynthOSConfig, app: Application, customize
|
|
|
837
837
|
// Detect first edit (v0→v1) for saved pages
|
|
838
838
|
const isRequiredPage = config.requiredPages.includes(page);
|
|
839
839
|
const pageFolder = path.join(pagesFolder, 'pages', page);
|
|
840
|
-
const pageFileExists = await checkIfExists(path.join(pageFolder, 'page.html'));
|
|
841
|
-
let currentVersion = await getLatestVersion(
|
|
840
|
+
const pageFileExists = await config.storageProvider.checkIfExists(path.join(pageFolder, 'page.html'));
|
|
841
|
+
let currentVersion = await getLatestVersion(config, page);
|
|
842
842
|
const isFirstEdit = !isRequiredPage && pageFileExists && currentVersion === 0;
|
|
843
843
|
|
|
844
844
|
// Try again — roll back to previous version before re-running the transform
|
|
@@ -847,13 +847,13 @@ export function usePageRoutes(config: SynthOSConfig, app: Application, customize
|
|
|
847
847
|
if (tryAgain && currentVersion > 0) {
|
|
848
848
|
// Delete current version file
|
|
849
849
|
const versionFile = path.join(pageFolder, `page.v${currentVersion}.html`);
|
|
850
|
-
if (await checkIfExists(versionFile)) {
|
|
851
|
-
await
|
|
850
|
+
if (await config.storageProvider.checkIfExists(versionFile)) {
|
|
851
|
+
await config.storageProvider.deleteFile(versionFile);
|
|
852
852
|
}
|
|
853
853
|
// Load previous version
|
|
854
854
|
const prevVersion = currentVersion - 1;
|
|
855
855
|
if (prevVersion > 0) {
|
|
856
|
-
transformInput = await loadPageVersion(
|
|
856
|
+
transformInput = await loadPageVersion(config, page, prevVersion) ?? pageState;
|
|
857
857
|
} else {
|
|
858
858
|
transformInput = await loadPageWithFallback(page, config, true) ?? pageState;
|
|
859
859
|
}
|
|
@@ -931,13 +931,13 @@ export function usePageRoutes(config: SynthOSConfig, app: Application, customize
|
|
|
931
931
|
|
|
932
932
|
// Save version snapshot (working state for all pages, undo support for saved pages)
|
|
933
933
|
const nextVersion = currentVersion + 1;
|
|
934
|
-
await savePageVersion(
|
|
934
|
+
await savePageVersion(config, page, nextVersion, html);
|
|
935
935
|
|
|
936
936
|
// Update lastModified timestamp in page metadata
|
|
937
|
-
const metadata = await loadPageMetadata(
|
|
937
|
+
const metadata = await loadPageMetadata(config, page, config.requiredPagesFolders);
|
|
938
938
|
if (metadata) {
|
|
939
939
|
metadata.lastModified = new Date().toISOString();
|
|
940
|
-
await savePageMetadata(
|
|
940
|
+
await savePageMetadata(config, page, metadata);
|
|
941
941
|
}
|
|
942
942
|
|
|
943
943
|
// Inject required imports and page scripts (same as GET)
|
|
@@ -964,7 +964,7 @@ export function usePageRoutes(config: SynthOSConfig, app: Application, customize
|
|
|
964
964
|
|
|
965
965
|
// Replace branding for white-label forks
|
|
966
966
|
if (productName !== 'SynthOS') {
|
|
967
|
-
out = out.replace(/
|
|
967
|
+
out = out.replace(/synthos/gi, productName);
|
|
968
968
|
}
|
|
969
969
|
|
|
970
970
|
res.send(out);
|
|
@@ -985,17 +985,19 @@ export function usePageRoutes(config: SynthOSConfig, app: Application, customize
|
|
|
985
985
|
export async function loadPageWithFallback(page: string, config: SynthOSConfig, reset: boolean): Promise<string|undefined> {
|
|
986
986
|
if (reset) {
|
|
987
987
|
// Clear working-state versions so we get the fresh template
|
|
988
|
-
await clearVersions(config
|
|
988
|
+
await clearVersions(config, page);
|
|
989
989
|
}
|
|
990
990
|
|
|
991
991
|
// Try primary pages folder first
|
|
992
|
-
const pageState = await loadPageState(config
|
|
992
|
+
const pageState = await loadPageState(config, page);
|
|
993
993
|
if (pageState) return pageState;
|
|
994
994
|
|
|
995
|
-
// Try fallback pages folders
|
|
995
|
+
// Try fallback pages folders (package content, always local fs)
|
|
996
996
|
for (const folder of config.requiredPagesFolders) {
|
|
997
|
-
const
|
|
998
|
-
if (
|
|
997
|
+
const candidate = path.join(folder, page, 'page.html');
|
|
998
|
+
if (await checkIfExists(candidate)) {
|
|
999
|
+
return await loadFile(candidate);
|
|
1000
|
+
}
|
|
999
1001
|
}
|
|
1000
1002
|
return undefined;
|
|
1001
1003
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Application, Response } from 'express';
|
|
2
2
|
import { SynthOSConfig } from "../init";
|
|
3
|
-
import { checkIfExists, deleteFile, ensureFolderExists, listFiles, loadFile, saveFile } from "../files";
|
|
4
3
|
import path from "path";
|
|
5
4
|
import { v4 } from "uuid";
|
|
6
5
|
|
|
@@ -16,19 +15,20 @@ export function useSharedDataRoutes(config: SynthOSConfig, app: Application): vo
|
|
|
16
15
|
// ---------------------------------------------------------------------------
|
|
17
16
|
|
|
18
17
|
async function handleList(config: SynthOSConfig, table: string, query: Record<string, any>, res: Response): Promise<void> {
|
|
18
|
+
const sp = config.storageProvider;
|
|
19
19
|
const folder = sharedTableFolder(config, table);
|
|
20
|
-
if (!(await checkIfExists(folder))) {
|
|
20
|
+
if (!(await sp.checkIfExists(folder))) {
|
|
21
21
|
res.status(404).json({ error: 'table_not_found', table });
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const ids = (await listFiles(folder)).filter(f => f.endsWith('.json')).map(f => f.replace('.json', ''));
|
|
25
|
+
const ids = (await sp.listFiles(folder)).filter(f => f.endsWith('.json')).map(f => f.replace('.json', ''));
|
|
26
26
|
|
|
27
27
|
const rows: Record<string, any>[] = [];
|
|
28
28
|
for (const id of ids) {
|
|
29
29
|
const file = recordFile(folder, id);
|
|
30
30
|
try {
|
|
31
|
-
const row = JSON.parse(await loadFile(file));
|
|
31
|
+
const row = JSON.parse(await sp.loadFile(file));
|
|
32
32
|
row.id = id;
|
|
33
33
|
rows.push(row);
|
|
34
34
|
} catch (err: unknown) {
|
|
@@ -48,15 +48,16 @@ async function handleList(config: SynthOSConfig, table: string, query: Record<st
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
async function handleGet(config: SynthOSConfig, table: string, id: string, res: Response): Promise<void> {
|
|
51
|
+
const sp = config.storageProvider;
|
|
51
52
|
const folder = sharedTableFolder(config, table);
|
|
52
|
-
if (!(await checkIfExists(folder))) {
|
|
53
|
+
if (!(await sp.checkIfExists(folder))) {
|
|
53
54
|
res.status(404).json({ error: 'table_not_found', table });
|
|
54
55
|
return;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
const file = recordFile(folder, id);
|
|
58
59
|
try {
|
|
59
|
-
const row = JSON.parse(await loadFile(file));
|
|
60
|
+
const row = JSON.parse(await sp.loadFile(file));
|
|
60
61
|
row.id = id;
|
|
61
62
|
res.json(row);
|
|
62
63
|
} catch (err: unknown) {
|
|
@@ -65,13 +66,14 @@ async function handleGet(config: SynthOSConfig, table: string, id: string, res:
|
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
async function handleUpsert(config: SynthOSConfig, table: string, body: any, res: Response): Promise<void> {
|
|
69
|
+
const sp = config.storageProvider;
|
|
68
70
|
const id = body.id ?? v4();
|
|
69
71
|
const folder = sharedTableFolder(config, table);
|
|
70
72
|
const file = recordFile(folder, id);
|
|
71
73
|
try {
|
|
72
74
|
const row = { ...body, id };
|
|
73
|
-
await ensureFolderExists(folder);
|
|
74
|
-
await saveFile(file, JSON.stringify(row, null, 4));
|
|
75
|
+
await sp.ensureFolderExists(folder);
|
|
76
|
+
await sp.saveFile(file, JSON.stringify(row, null, 4));
|
|
75
77
|
res.json(row);
|
|
76
78
|
} catch (err: unknown) {
|
|
77
79
|
console.error(err);
|
|
@@ -80,11 +82,12 @@ async function handleUpsert(config: SynthOSConfig, table: string, body: any, res
|
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
async function handleDelete(config: SynthOSConfig, table: string, id: string, res: Response): Promise<void> {
|
|
85
|
+
const sp = config.storageProvider;
|
|
83
86
|
const folder = sharedTableFolder(config, table);
|
|
84
87
|
const file = recordFile(folder, id);
|
|
85
88
|
try {
|
|
86
|
-
if (await checkIfExists(file)) {
|
|
87
|
-
await deleteFile(file);
|
|
89
|
+
if (await sp.checkIfExists(file)) {
|
|
90
|
+
await sp.deleteFile(file);
|
|
88
91
|
}
|
|
89
92
|
res.json({ success: true });
|
|
90
93
|
} catch (err: unknown) {
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
import { Application } from 'express';
|
|
2
2
|
import express from 'express';
|
|
3
|
-
import fs from 'fs/promises';
|
|
4
3
|
import path from 'path';
|
|
5
4
|
import { SynthOSConfig } from '../init';
|
|
6
|
-
import { checkIfExists, ensureFolderExists } from '../files';
|
|
7
5
|
|
|
8
6
|
export function useSharedFileRoutes(config: SynthOSConfig, app: Application): void {
|
|
7
|
+
const sp = config.storageProvider;
|
|
8
|
+
|
|
9
9
|
// List files in the shared files folder
|
|
10
10
|
app.get('/api/shared/files', async (req, res) => {
|
|
11
11
|
try {
|
|
12
12
|
const folder = sharedFilesFolder(config);
|
|
13
|
-
if (!(await checkIfExists(folder))) {
|
|
13
|
+
if (!(await sp.checkIfExists(folder))) {
|
|
14
14
|
res.json({ files: [] });
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const entries = await
|
|
18
|
+
const entries = await sp.listFiles(folder);
|
|
19
19
|
const files: { name: string; size: number }[] = [];
|
|
20
20
|
for (const entry of entries) {
|
|
21
|
-
const stat = await
|
|
22
|
-
if (stat.isFile
|
|
21
|
+
const stat = await sp.stat(path.join(folder, entry));
|
|
22
|
+
if (stat.isFile) {
|
|
23
23
|
files.push({ name: entry, size: stat.size });
|
|
24
24
|
}
|
|
25
25
|
}
|
|
@@ -39,12 +39,13 @@ export function useSharedFileRoutes(config: SynthOSConfig, app: Application): vo
|
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
if (!(await checkIfExists(filePath))) {
|
|
42
|
+
if (!(await sp.checkIfExists(filePath))) {
|
|
43
43
|
res.status(404).json({ error: 'File not found' });
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
res.
|
|
47
|
+
res.type(path.extname(req.params.filename));
|
|
48
|
+
sp.createReadStream(filePath).pipe(res);
|
|
48
49
|
} catch (err: unknown) {
|
|
49
50
|
console.error(err);
|
|
50
51
|
res.status(500).json({ error: (err as Error).message });
|
|
@@ -67,10 +68,10 @@ export function useSharedFileRoutes(config: SynthOSConfig, app: Application): vo
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
const folder = sharedFilesFolder(config);
|
|
70
|
-
await ensureFolderExists(folder);
|
|
71
|
-
await
|
|
71
|
+
await sp.ensureFolderExists(folder);
|
|
72
|
+
await sp.saveBuffer(filePath, req.body as Buffer);
|
|
72
73
|
|
|
73
|
-
const stat = await
|
|
74
|
+
const stat = await sp.stat(filePath);
|
|
74
75
|
res.status(201).json({ name: filename, size: stat.size });
|
|
75
76
|
} catch (err: unknown) {
|
|
76
77
|
console.error(err);
|
|
@@ -87,12 +88,12 @@ export function useSharedFileRoutes(config: SynthOSConfig, app: Application): vo
|
|
|
87
88
|
return;
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
if (!(await checkIfExists(filePath))) {
|
|
91
|
+
if (!(await sp.checkIfExists(filePath))) {
|
|
91
92
|
res.status(404).json({ error: 'File not found' });
|
|
92
93
|
return;
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
await
|
|
96
|
+
await sp.deleteFile(filePath);
|
|
96
97
|
res.json({ deleted: true });
|
|
97
98
|
} catch (err: unknown) {
|
|
98
99
|
console.error(err);
|
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
|
|
|
@@ -126,8 +126,8 @@ function migrateV1toV2(raw: Record<string, unknown>): SettingsV2 {
|
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
export async function hasConfiguredSettings(
|
|
130
|
-
const settings = await loadSettings(
|
|
129
|
+
export async function hasConfiguredSettings(config: SynthOSConfig): Promise<boolean> {
|
|
130
|
+
const settings = await loadSettings(config);
|
|
131
131
|
const builder = getModelEntry(settings, 'builder');
|
|
132
132
|
if (typeof builder.configuration.apiKey !== 'string' || builder.configuration.apiKey.length == 0) {
|
|
133
133
|
return false;
|
|
@@ -138,18 +138,19 @@ export async function hasConfiguredSettings(folder: string): Promise<boolean> {
|
|
|
138
138
|
return true;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
export async function loadSettings(
|
|
141
|
+
export async function loadSettings(config: SynthOSConfig): Promise<SettingsV2> {
|
|
142
|
+
const sp = config.storageProvider;
|
|
142
143
|
if (_settings == undefined) {
|
|
143
|
-
const filename = path.join(
|
|
144
|
-
if (await checkIfExists(filename)) {
|
|
144
|
+
const filename = path.join(config.pagesFolder, 'settings.json');
|
|
145
|
+
if (await sp.checkIfExists(filename)) {
|
|
145
146
|
try {
|
|
146
|
-
const raw = JSON.parse(await loadFile(filename));
|
|
147
|
+
const raw = JSON.parse(await sp.loadFile(filename));
|
|
147
148
|
if (!raw.version) {
|
|
148
149
|
// V1 file — migrate
|
|
149
150
|
console.log('Migrating settings.json from v1 to v2...');
|
|
150
151
|
const migrated = migrateV1toV2(raw);
|
|
151
152
|
_settings = migrated;
|
|
152
|
-
await saveFile(filename, JSON.stringify(migrated, null, 4));
|
|
153
|
+
await sp.saveFile(filename, JSON.stringify(migrated, null, 4));
|
|
153
154
|
} else {
|
|
154
155
|
_settings = raw as Partial<SettingsV2>;
|
|
155
156
|
}
|
|
@@ -171,11 +172,12 @@ export async function loadSettings(folder: string): Promise<SettingsV2> {
|
|
|
171
172
|
return merged;
|
|
172
173
|
}
|
|
173
174
|
|
|
174
|
-
export async function saveSettings(
|
|
175
|
+
export async function saveSettings(config: SynthOSConfig, settings: Partial<SettingsV2>): Promise<void> {
|
|
176
|
+
const sp = config.storageProvider;
|
|
175
177
|
_settings = {..._settings, ...settings};
|
|
176
178
|
if (settings.models) {
|
|
177
179
|
_settings.models = settings.models;
|
|
178
180
|
}
|
|
179
181
|
_settings.version = 2;
|
|
180
|
-
await saveFile(path.join(
|
|
182
|
+
await sp.saveFile(path.join(config.pagesFolder, 'settings.json'), JSON.stringify(_settings, null, 4));
|
|
181
183
|
}
|