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,17 +1,18 @@
|
|
|
1
1
|
import { hasConfiguredSettings, loadSettings, SettingsV2 } from "../settings";
|
|
2
|
+
import { SynthOSConfig } from "../init";
|
|
2
3
|
|
|
3
4
|
|
|
4
|
-
export async function requiresSettings(res: any,
|
|
5
|
+
export async function requiresSettings(res: any, config: SynthOSConfig, cb: (settings: SettingsV2) => Promise<void>) {
|
|
5
6
|
try {
|
|
6
7
|
// Ensure settings configured
|
|
7
|
-
const isConfigured = await hasConfiguredSettings(
|
|
8
|
+
const isConfigured = await hasConfiguredSettings(config);
|
|
8
9
|
if (!isConfigured) {
|
|
9
10
|
res.status(400).send('Settings not configured');
|
|
10
11
|
return;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
// Load settings
|
|
14
|
-
const settings = await loadSettings(
|
|
15
|
+
const settings = await loadSettings(config);
|
|
15
16
|
|
|
16
17
|
// Call the callback
|
|
17
18
|
await cb(settings);
|
|
@@ -54,7 +54,7 @@ export function useAgentRoutes(config: SynthOSConfig, app: Application, customiz
|
|
|
54
54
|
// Auto-connect all enabled OpenClaw agents on startup
|
|
55
55
|
(async () => {
|
|
56
56
|
try {
|
|
57
|
-
const settings = await loadSettings(config
|
|
57
|
+
const settings = await loadSettings(config);
|
|
58
58
|
for (const agent of settings.agents ?? []) {
|
|
59
59
|
tryConnectAgent(agent);
|
|
60
60
|
}
|
|
@@ -66,7 +66,7 @@ export function useAgentRoutes(config: SynthOSConfig, app: Application, customiz
|
|
|
66
66
|
// GET /api/agents — List configured agents (with optional filters)
|
|
67
67
|
app.get('/api/agents', async (req, res) => {
|
|
68
68
|
try {
|
|
69
|
-
const settings = await loadSettings(config
|
|
69
|
+
const settings = await loadSettings(config);
|
|
70
70
|
let agents = settings.agents ?? [];
|
|
71
71
|
|
|
72
72
|
// Filter by enabled
|
|
@@ -122,7 +122,7 @@ export function useAgentRoutes(config: SynthOSConfig, app: Application, customiz
|
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
const settings = await loadSettings(config
|
|
125
|
+
const settings = await loadSettings(config);
|
|
126
126
|
const agents = settings.agents ? [...settings.agents] : [];
|
|
127
127
|
|
|
128
128
|
const agentConfig: AgentConfig = {
|
|
@@ -159,7 +159,7 @@ export function useAgentRoutes(config: SynthOSConfig, app: Application, customiz
|
|
|
159
159
|
agents.push(agentConfig);
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
await saveSettings(config
|
|
162
|
+
await saveSettings(config, { agents });
|
|
163
163
|
|
|
164
164
|
// Auto-connect OpenClaw agents after save
|
|
165
165
|
tryConnectAgent(agentConfig);
|
|
@@ -175,7 +175,7 @@ export function useAgentRoutes(config: SynthOSConfig, app: Application, customiz
|
|
|
175
175
|
app.patch('/api/agents/:id', async (req, res) => {
|
|
176
176
|
try {
|
|
177
177
|
const { id } = req.params;
|
|
178
|
-
const settings = await loadSettings(config
|
|
178
|
+
const settings = await loadSettings(config);
|
|
179
179
|
const agents = settings.agents ? [...settings.agents] : [];
|
|
180
180
|
const idx = agents.findIndex(a => a.id === id);
|
|
181
181
|
if (idx === -1) {
|
|
@@ -188,7 +188,7 @@ export function useAgentRoutes(config: SynthOSConfig, app: Application, customiz
|
|
|
188
188
|
if (typeof body.name === 'string') agents[idx].name = body.name;
|
|
189
189
|
if (typeof body.description === 'string') agents[idx].description = body.description;
|
|
190
190
|
|
|
191
|
-
await saveSettings(config
|
|
191
|
+
await saveSettings(config, { agents });
|
|
192
192
|
|
|
193
193
|
// Connect or disconnect based on enabled state
|
|
194
194
|
if (agents[idx].provider === 'openclaw') {
|
|
@@ -210,7 +210,7 @@ export function useAgentRoutes(config: SynthOSConfig, app: Application, customiz
|
|
|
210
210
|
app.delete('/api/agents/:id', async (req, res) => {
|
|
211
211
|
try {
|
|
212
212
|
const { id } = req.params;
|
|
213
|
-
const settings = await loadSettings(config
|
|
213
|
+
const settings = await loadSettings(config);
|
|
214
214
|
const agent = (settings.agents ?? []).find(a => a.id === id);
|
|
215
215
|
|
|
216
216
|
// Disconnect if OpenClaw
|
|
@@ -219,7 +219,7 @@ export function useAgentRoutes(config: SynthOSConfig, app: Application, customiz
|
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
const agents = (settings.agents ?? []).filter(a => a.id !== id);
|
|
222
|
-
await saveSettings(config
|
|
222
|
+
await saveSettings(config, { agents });
|
|
223
223
|
res.json({ deleted: true });
|
|
224
224
|
} catch (err: unknown) {
|
|
225
225
|
console.error(err);
|
|
@@ -231,7 +231,7 @@ export function useAgentRoutes(config: SynthOSConfig, app: Application, customiz
|
|
|
231
231
|
app.post('/api/agents/:id/connect', async (req, res) => {
|
|
232
232
|
try {
|
|
233
233
|
const { id } = req.params;
|
|
234
|
-
const settings = await loadSettings(config
|
|
234
|
+
const settings = await loadSettings(config);
|
|
235
235
|
const agent = (settings.agents ?? []).find(a => a.id === id);
|
|
236
236
|
if (!agent) {
|
|
237
237
|
res.status(404).json({ error: `Agent "${id}" not found` });
|
|
@@ -277,7 +277,7 @@ export function useAgentRoutes(config: SynthOSConfig, app: Application, customiz
|
|
|
277
277
|
return;
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
const settings = await loadSettings(config
|
|
280
|
+
const settings = await loadSettings(config);
|
|
281
281
|
const agent = (settings.agents ?? []).find(a => a.id === id);
|
|
282
282
|
if (!agent) {
|
|
283
283
|
res.status(404).json({ error: `Agent "${id}" not found` });
|
|
@@ -307,7 +307,7 @@ export function useAgentRoutes(config: SynthOSConfig, app: Application, customiz
|
|
|
307
307
|
res: import('express').Response,
|
|
308
308
|
fn: (agent: AgentConfig, provider: AgentProvider) => Promise<void>,
|
|
309
309
|
): Promise<void> {
|
|
310
|
-
const settings = await loadSettings(config
|
|
310
|
+
const settings = await loadSettings(config);
|
|
311
311
|
const agent = (settings.agents ?? []).find(a => a.id === id);
|
|
312
312
|
if (!agent) {
|
|
313
313
|
res.status(404).json({ error: `Agent "${id}" not found` });
|
|
@@ -386,7 +386,7 @@ export function useAgentRoutes(config: SynthOSConfig, app: Application, customiz
|
|
|
386
386
|
return;
|
|
387
387
|
}
|
|
388
388
|
|
|
389
|
-
const settings = await loadSettings(config
|
|
389
|
+
const settings = await loadSettings(config);
|
|
390
390
|
const agent = (settings.agents ?? []).find(a => a.id === id);
|
|
391
391
|
if (!agent) {
|
|
392
392
|
res.status(404).json({ error: `Agent "${id}" not found` });
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import path from "path";
|
|
2
|
-
import fs from "fs/promises";
|
|
3
2
|
import AdmZip from "adm-zip";
|
|
4
|
-
import { listPages, loadPageMetadata, PageMetadata, savePageMetadata, deletePage, copyPage,
|
|
5
|
-
import { checkIfExists,
|
|
3
|
+
import { listPages, loadPageMetadata, PageMetadata, savePageMetadata, deletePage, copyPage, savePageState, clearVersions, PAGE_VERSION } from "../pages";
|
|
4
|
+
import { checkIfExists, findFileInFolders, listFiles, listFolders, loadFile } from "../files";
|
|
6
5
|
import {getModelEntry, loadSettings, saveSettings, ServicesConfig } from "../settings";
|
|
7
6
|
import { Application } from 'express';
|
|
8
7
|
import express from 'express';
|
|
@@ -50,9 +49,11 @@ const SERVICE_REGISTRY: ServiceDefinition[] = [
|
|
|
50
49
|
];
|
|
51
50
|
|
|
52
51
|
export function useApiRoutes(config: SynthOSConfig, app: Application, customizer?: Customizer): void {
|
|
52
|
+
const sp = config.storageProvider;
|
|
53
|
+
|
|
53
54
|
// List pages
|
|
54
55
|
app.get('/api/pages', async (req, res) => {
|
|
55
|
-
const pages = await listPages(config
|
|
56
|
+
const pages = await listPages(config, config.requiredPagesFolders);
|
|
56
57
|
res.json(pages);
|
|
57
58
|
});
|
|
58
59
|
|
|
@@ -98,13 +99,13 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
98
99
|
// Auto-append _1, _2, etc. on name conflicts
|
|
99
100
|
let finalName = pageName;
|
|
100
101
|
let suffix = 0;
|
|
101
|
-
while (await checkIfExists(path.join(config.pagesFolder, 'pages', finalName))) {
|
|
102
|
+
while (await sp.checkIfExists(path.join(config.pagesFolder, 'pages', finalName))) {
|
|
102
103
|
suffix++;
|
|
103
104
|
finalName = `${pageName}_${suffix}`;
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
const targetDir = path.join(config.pagesFolder, 'pages', finalName);
|
|
107
|
-
await ensureFolderExists(targetDir);
|
|
108
|
+
await sp.ensureFolderExists(targetDir);
|
|
108
109
|
|
|
109
110
|
// Extract entries with path traversal protection
|
|
110
111
|
for (const entry of entries) {
|
|
@@ -120,13 +121,13 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
120
121
|
continue;
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
await ensureFolderExists(path.dirname(resolvedPath));
|
|
124
|
-
await
|
|
124
|
+
await sp.ensureFolderExists(path.dirname(resolvedPath));
|
|
125
|
+
await sp.saveBuffer(resolvedPath, entry.getData());
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
// Update metadata: set createdDate and lastModified to now
|
|
128
129
|
const now = new Date().toISOString();
|
|
129
|
-
const existingMeta = await loadPageMetadata(config
|
|
130
|
+
const existingMeta = await loadPageMetadata(config, finalName);
|
|
130
131
|
const metadata: PageMetadata = {
|
|
131
132
|
title: existingMeta?.title ?? '',
|
|
132
133
|
categories: existingMeta?.categories ?? [],
|
|
@@ -137,7 +138,7 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
137
138
|
pageVersion: existingMeta?.pageVersion ?? PAGE_VERSION,
|
|
138
139
|
mode: existingMeta?.mode ?? 'unlocked',
|
|
139
140
|
};
|
|
140
|
-
await savePageMetadata(config
|
|
141
|
+
await savePageMetadata(config, finalName, metadata);
|
|
141
142
|
|
|
142
143
|
res.status(201).json({ name: finalName, title: metadata.title });
|
|
143
144
|
} catch (err: unknown) {
|
|
@@ -150,7 +151,7 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
150
151
|
app.get('/api/pages/:name', async (req, res) => {
|
|
151
152
|
try {
|
|
152
153
|
const { name } = req.params;
|
|
153
|
-
const metadata = await loadPageMetadata(config
|
|
154
|
+
const metadata = await loadPageMetadata(config, name, config.requiredPagesFolders);
|
|
154
155
|
if (metadata) {
|
|
155
156
|
res.json(metadata);
|
|
156
157
|
} else {
|
|
@@ -201,7 +202,7 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
201
202
|
}
|
|
202
203
|
|
|
203
204
|
// Load existing metadata (or defaults)
|
|
204
|
-
const existing = await loadPageMetadata(config
|
|
205
|
+
const existing = await loadPageMetadata(config, name, config.requiredPagesFolders);
|
|
205
206
|
const metadata: PageMetadata = {
|
|
206
207
|
title: existing?.title ?? '',
|
|
207
208
|
categories: existing?.categories ?? [],
|
|
@@ -226,19 +227,23 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
226
227
|
// Promote required page to user folder if being unlocked/designed
|
|
227
228
|
if (metadata.mode !== 'locked') {
|
|
228
229
|
const userPagePath = path.join(config.pagesFolder, 'pages', name, 'page.html');
|
|
229
|
-
if (!(await checkIfExists(userPagePath))) {
|
|
230
|
+
if (!(await sp.checkIfExists(userPagePath))) {
|
|
231
|
+
// Read from required pages (package content, always local fs)
|
|
230
232
|
let html: string | undefined;
|
|
231
233
|
for (const folder of config.requiredPagesFolders) {
|
|
232
|
-
|
|
233
|
-
if (
|
|
234
|
+
const candidate = path.join(folder, name, 'page.html');
|
|
235
|
+
if (await checkIfExists(candidate)) {
|
|
236
|
+
html = await loadFile(candidate);
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
234
239
|
}
|
|
235
240
|
if (html) {
|
|
236
|
-
await savePageState(config
|
|
241
|
+
await savePageState(config, name, html);
|
|
237
242
|
}
|
|
238
243
|
}
|
|
239
244
|
}
|
|
240
245
|
|
|
241
|
-
await savePageMetadata(config
|
|
246
|
+
await savePageMetadata(config, name, metadata);
|
|
242
247
|
res.json(metadata);
|
|
243
248
|
} catch (err: unknown) {
|
|
244
249
|
console.error(err);
|
|
@@ -257,7 +262,7 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
257
262
|
}
|
|
258
263
|
|
|
259
264
|
// Load existing metadata (user override → fallback .json → defaults)
|
|
260
|
-
let metadata = await loadPageMetadata(config
|
|
265
|
+
let metadata = await loadPageMetadata(config, name, config.requiredPagesFolders);
|
|
261
266
|
if (!metadata) {
|
|
262
267
|
metadata = {
|
|
263
268
|
title: '',
|
|
@@ -272,7 +277,7 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
272
277
|
}
|
|
273
278
|
|
|
274
279
|
metadata.pinned = pinned;
|
|
275
|
-
await savePageMetadata(config
|
|
280
|
+
await savePageMetadata(config, name, metadata);
|
|
276
281
|
res.json(metadata);
|
|
277
282
|
} catch (err: unknown) {
|
|
278
283
|
console.error(err);
|
|
@@ -294,13 +299,13 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
294
299
|
// Check if page exists (folder-based or legacy flat file)
|
|
295
300
|
const folderPath = path.join(config.pagesFolder, 'pages', name, 'page.html');
|
|
296
301
|
const flatPath = path.join(config.pagesFolder, `${name}.html`);
|
|
297
|
-
const exists = await checkIfExists(folderPath) || await checkIfExists(flatPath);
|
|
302
|
+
const exists = await sp.checkIfExists(folderPath) || await sp.checkIfExists(flatPath);
|
|
298
303
|
if (!exists) {
|
|
299
304
|
res.status(404).json({ error: `Page "${name}" not found` });
|
|
300
305
|
return;
|
|
301
306
|
}
|
|
302
307
|
|
|
303
|
-
await deletePage(config
|
|
308
|
+
await deletePage(config, name);
|
|
304
309
|
res.json({ deleted: true });
|
|
305
310
|
} catch (err: unknown) {
|
|
306
311
|
console.error(err);
|
|
@@ -316,7 +321,7 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
316
321
|
// Resolve page folder: user pages first, then required pages
|
|
317
322
|
let pageFolder: string | undefined;
|
|
318
323
|
const userFolder = path.join(config.pagesFolder, 'pages', name);
|
|
319
|
-
if (await checkIfExists(path.join(userFolder, 'page.html'))) {
|
|
324
|
+
if (await sp.checkIfExists(path.join(userFolder, 'page.html'))) {
|
|
320
325
|
pageFolder = userFolder;
|
|
321
326
|
} else {
|
|
322
327
|
for (const folder of config.requiredPagesFolders) {
|
|
@@ -335,14 +340,23 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
335
340
|
|
|
336
341
|
// List subdirectories, filtering out non-table entries
|
|
337
342
|
const EXCLUDED = new Set(['files']);
|
|
338
|
-
|
|
343
|
+
// Use provider for user folder, local fs for required pages
|
|
344
|
+
const isUserFolder = pageFolder === userFolder;
|
|
345
|
+
const subdirs = isUserFolder
|
|
346
|
+
? await sp.listFolders(pageFolder)
|
|
347
|
+
: await listFolders(pageFolder);
|
|
339
348
|
const tables = subdirs.filter(d => !EXCLUDED.has(d));
|
|
340
349
|
|
|
341
350
|
// Check if files/ exists and has entries
|
|
342
351
|
const filesDir = path.join(pageFolder, 'files');
|
|
343
352
|
let hasFiles = false;
|
|
344
|
-
|
|
345
|
-
|
|
353
|
+
const filesDirExists = isUserFolder
|
|
354
|
+
? await sp.checkIfExists(filesDir)
|
|
355
|
+
: await checkIfExists(filesDir);
|
|
356
|
+
if (filesDirExists) {
|
|
357
|
+
const entries = isUserFolder
|
|
358
|
+
? await sp.listFiles(filesDir)
|
|
359
|
+
: await listFiles(filesDir);
|
|
346
360
|
hasFiles = entries.length > 0;
|
|
347
361
|
}
|
|
348
362
|
|
|
@@ -384,8 +398,8 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
384
398
|
const candidate = path.join(folder, sourceName, 'page.html');
|
|
385
399
|
if (await checkIfExists(candidate)) { sourceRequiredPath = candidate; break; }
|
|
386
400
|
}
|
|
387
|
-
const sourceExists = await checkIfExists(sourceFolderPath)
|
|
388
|
-
|| await checkIfExists(sourceFlatPath)
|
|
401
|
+
const sourceExists = await sp.checkIfExists(sourceFolderPath)
|
|
402
|
+
|| await sp.checkIfExists(sourceFlatPath)
|
|
389
403
|
|| !!sourceRequiredPath;
|
|
390
404
|
if (!sourceExists) {
|
|
391
405
|
res.status(404).json({ error: `Source page "${sourceName}" not found` });
|
|
@@ -395,13 +409,13 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
395
409
|
// Check target doesn't already exist
|
|
396
410
|
const targetFolderPath = path.join(config.pagesFolder, 'pages', targetName, 'page.html');
|
|
397
411
|
const targetFlatPath = path.join(config.pagesFolder, `${targetName}.html`);
|
|
398
|
-
if (await checkIfExists(targetFolderPath) || await checkIfExists(targetFlatPath)) {
|
|
412
|
+
if (await sp.checkIfExists(targetFolderPath) || await sp.checkIfExists(targetFlatPath)) {
|
|
399
413
|
res.status(409).json({ error: `Page "${targetName}" already exists` });
|
|
400
414
|
return;
|
|
401
415
|
}
|
|
402
416
|
|
|
403
417
|
await copyPage(
|
|
404
|
-
config
|
|
418
|
+
config,
|
|
405
419
|
sourceName,
|
|
406
420
|
targetName,
|
|
407
421
|
typeof title === 'string' ? title : '',
|
|
@@ -414,7 +428,7 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
414
428
|
);
|
|
415
429
|
|
|
416
430
|
// Return the new page metadata
|
|
417
|
-
const metadata = await loadPageMetadata(config
|
|
431
|
+
const metadata = await loadPageMetadata(config, targetName);
|
|
418
432
|
res.status(201).json({ name: targetName, ...metadata });
|
|
419
433
|
} catch (err: unknown) {
|
|
420
434
|
console.error(err);
|
|
@@ -424,7 +438,7 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
424
438
|
|
|
425
439
|
// Define a route to return settings
|
|
426
440
|
app.get('/api/settings', async (req, res) => {
|
|
427
|
-
const settings = await loadSettings(config
|
|
441
|
+
const settings = await loadSettings(config);
|
|
428
442
|
const providers = PROVIDERS.map(p => ({ name: p.name, builderModels: p.builderModels, chatModels: p.chatModels }));
|
|
429
443
|
res.json({...settings, providers});
|
|
430
444
|
});
|
|
@@ -445,7 +459,7 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
445
459
|
}
|
|
446
460
|
|
|
447
461
|
// Save settings
|
|
448
|
-
await saveSettings(config
|
|
462
|
+
await saveSettings(config, settings);
|
|
449
463
|
res.redirect('/builder');
|
|
450
464
|
} catch (err: unknown) {
|
|
451
465
|
console.error(err);
|
|
@@ -455,7 +469,7 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
455
469
|
|
|
456
470
|
// Define a route to generate an image
|
|
457
471
|
app.post('/api/generate/image', async (req, res) => {
|
|
458
|
-
await requiresSettings(res, config
|
|
472
|
+
await requiresSettings(res, config, async (settings) => {
|
|
459
473
|
const { prompt, shape, style } = req.body;
|
|
460
474
|
const builder = getModelEntry(settings, 'builder');
|
|
461
475
|
const { configuration, imageQuality, provider } = builder;
|
|
@@ -472,9 +486,9 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
472
486
|
|
|
473
487
|
// Define a route to generate a completion using chain-of-thought
|
|
474
488
|
app.post('/api/generate/completion', async (req, res) => {
|
|
475
|
-
await requiresSettings(res, config
|
|
489
|
+
await requiresSettings(res, config, async (settings) => {
|
|
476
490
|
const { prompt, temperature } = req.body;
|
|
477
|
-
const completePrompt = await createCompletePrompt(config
|
|
491
|
+
const completePrompt = await createCompletePrompt(config, 'chat', req.body.model);
|
|
478
492
|
const response = await chainOfThought({ question: prompt, temperature, completePrompt });
|
|
479
493
|
if (response.completed) {
|
|
480
494
|
res.json(response.value ?? {});
|
|
@@ -488,9 +502,9 @@ export function useApiRoutes(config: SynthOSConfig, app: Application, customizer
|
|
|
488
502
|
// Brainstorm endpoint
|
|
489
503
|
if (!customizer || customizer.isEnabled('brainstorm'))
|
|
490
504
|
app.post('/api/brainstorm', async (req, res) => {
|
|
491
|
-
await requiresSettings(res, config
|
|
505
|
+
await requiresSettings(res, config, async (settings) => {
|
|
492
506
|
const { context, messages } = req.body;
|
|
493
|
-
const completePrompt = await createCompletePrompt(config
|
|
507
|
+
const completePrompt = await createCompletePrompt(config, 'chat');
|
|
494
508
|
|
|
495
509
|
const productName = customizer?.productName ?? 'SynthOS';
|
|
496
510
|
const system: { role: 'system'; content: string } = {
|
|
@@ -557,11 +571,10 @@ Return ONLY the JSON object.`};
|
|
|
557
571
|
// Define a route for running configured scripts
|
|
558
572
|
if (!customizer || customizer.isEnabled('scripts'))
|
|
559
573
|
app.post('/api/scripts/:id', async (req, res) => {
|
|
560
|
-
await requiresSettings(res, config
|
|
574
|
+
await requiresSettings(res, config, async (settings) => {
|
|
561
575
|
const { id } = req.params;
|
|
562
|
-
const pagesFolder = config.pagesFolder;
|
|
563
576
|
const scriptId = id;
|
|
564
|
-
const response = await executeScript({ pagesFolder, scriptId, variables: req.body });
|
|
577
|
+
const response = await executeScript({ pagesFolder: config.pagesFolder, scriptId, variables: req.body });
|
|
565
578
|
if (response.completed) {
|
|
566
579
|
// Return the response as text
|
|
567
580
|
const value = (response.value?.output ?? (response.value?.errors ?? []).join('\n')).trim();
|
|
@@ -577,7 +590,7 @@ Return ONLY the JSON object.`};
|
|
|
577
590
|
// Return theme info as a self-executing JS script
|
|
578
591
|
app.get('/api/theme-info.js', async (req, res) => {
|
|
579
592
|
try {
|
|
580
|
-
const settings = await loadSettings(config
|
|
593
|
+
const settings = await loadSettings(config);
|
|
581
594
|
const themeName = settings.theme ?? 'nebula-dusk';
|
|
582
595
|
const info = await loadThemeInfo(themeName, config);
|
|
583
596
|
if (!info) {
|
|
@@ -607,7 +620,7 @@ Return ONLY the JSON object.`};
|
|
|
607
620
|
res.status(400).send('// Missing page query parameter');
|
|
608
621
|
return;
|
|
609
622
|
}
|
|
610
|
-
const metadata = await loadPageMetadata(config
|
|
623
|
+
const metadata = await loadPageMetadata(config, page, config.requiredPagesFolders);
|
|
611
624
|
const mode = metadata?.mode ?? 'unlocked';
|
|
612
625
|
const title = metadata?.title ?? '';
|
|
613
626
|
const categories = metadata?.categories ?? [];
|
|
@@ -634,7 +647,7 @@ Return ONLY the JSON object.`};
|
|
|
634
647
|
// Return the current theme as CSS
|
|
635
648
|
app.get('/api/theme.css', async (req, res) => {
|
|
636
649
|
try {
|
|
637
|
-
const settings = await loadSettings(config
|
|
650
|
+
const settings = await loadSettings(config);
|
|
638
651
|
const themeName = settings.theme ?? 'nebula-dusk';
|
|
639
652
|
const css = await loadTheme(themeName, config);
|
|
640
653
|
if (!css) {
|
|
@@ -718,7 +731,7 @@ Return ONLY the JSON object.`};
|
|
|
718
731
|
// Return user's configured services (API keys masked)
|
|
719
732
|
app.get('/api/services', async (_req, res) => {
|
|
720
733
|
try {
|
|
721
|
-
const settings = await loadSettings(config
|
|
734
|
+
const settings = await loadSettings(config);
|
|
722
735
|
const services = settings.services ?? {};
|
|
723
736
|
const masked: Record<string, { enabled: boolean; hasKey: boolean }> = {};
|
|
724
737
|
for (const [id, cfg] of Object.entries(services)) {
|
|
@@ -738,7 +751,7 @@ Return ONLY the JSON object.`};
|
|
|
738
751
|
app.post('/api/services', async (req, res) => {
|
|
739
752
|
try {
|
|
740
753
|
const incoming = req.body as ServicesConfig;
|
|
741
|
-
const settings = await loadSettings(config
|
|
754
|
+
const settings = await loadSettings(config);
|
|
742
755
|
const existing = settings.services ?? {};
|
|
743
756
|
|
|
744
757
|
// Build merged config — empty apiKey means "keep existing"
|
|
@@ -759,7 +772,7 @@ Return ONLY the JSON object.`};
|
|
|
759
772
|
}
|
|
760
773
|
}
|
|
761
774
|
|
|
762
|
-
await saveSettings(config
|
|
775
|
+
await saveSettings(config, { services: merged });
|
|
763
776
|
res.json({ saved: true });
|
|
764
777
|
} catch (err: unknown) {
|
|
765
778
|
console.error(err);
|
|
@@ -780,7 +793,7 @@ Return ONLY the JSON object.`};
|
|
|
780
793
|
return;
|
|
781
794
|
}
|
|
782
795
|
|
|
783
|
-
const settings = await loadSettings(config
|
|
796
|
+
const settings = await loadSettings(config);
|
|
784
797
|
const braveConfig = settings.connectors?.['brave-search'] ?? settings.services?.['brave-search'];
|
|
785
798
|
if (!braveConfig || !braveConfig.enabled || !braveConfig.apiKey) {
|
|
786
799
|
res.status(400).json({ error: 'Brave Search is not configured or not enabled. Add your API key in Settings > Services.' });
|
|
@@ -826,7 +839,7 @@ Return ONLY the JSON object.`};
|
|
|
826
839
|
const { name } = req.params;
|
|
827
840
|
|
|
828
841
|
// Load current metadata
|
|
829
|
-
const metadata = await loadPageMetadata(config
|
|
842
|
+
const metadata = await loadPageMetadata(config, name, config.requiredPagesFolders);
|
|
830
843
|
if (!metadata) {
|
|
831
844
|
res.status(404).json({ error: `Page "${name}" not found` });
|
|
832
845
|
return;
|
|
@@ -846,35 +859,37 @@ Return ONLY the JSON object.`};
|
|
|
846
859
|
}
|
|
847
860
|
|
|
848
861
|
// Run LLM-based migration
|
|
849
|
-
const completePrompt = await createCompletePrompt(config
|
|
862
|
+
const completePrompt = await createCompletePrompt(config, 'builder');
|
|
850
863
|
const migratedHtml = await migratePage(html, currentVersion, PAGE_VERSION, completePrompt);
|
|
851
864
|
|
|
852
865
|
// Save upgraded HTML to v2 folder structure
|
|
853
|
-
await savePageState(config
|
|
866
|
+
await savePageState(config, name, migratedHtml);
|
|
854
867
|
|
|
855
868
|
// Backup original page to .migrated/ before overwriting
|
|
856
869
|
const migratedFolder = path.join(config.pagesFolder, '.migrated');
|
|
857
870
|
|
|
858
871
|
// Handle legacy flat file (<localFolder>/pagename.html)
|
|
859
872
|
const flatPath = path.join(config.pagesFolder, `${name}.html`);
|
|
860
|
-
if (await checkIfExists(flatPath)) {
|
|
861
|
-
await
|
|
862
|
-
await
|
|
873
|
+
if (await sp.checkIfExists(flatPath)) {
|
|
874
|
+
await sp.ensureFolderExists(migratedFolder);
|
|
875
|
+
const flatData = await sp.loadBuffer(flatPath);
|
|
876
|
+
await sp.saveBuffer(path.join(migratedFolder, `${name}.html`), flatData);
|
|
877
|
+
await sp.deleteFile(flatPath);
|
|
863
878
|
}
|
|
864
879
|
|
|
865
880
|
// Handle folder-based page (<localFolder>/pages/name/)
|
|
866
881
|
const folderPath = path.join(config.pagesFolder, 'pages', name);
|
|
867
|
-
if (await checkIfExists(folderPath)) {
|
|
868
|
-
await copyFolderRecursive(folderPath, path.join(migratedFolder, name));
|
|
882
|
+
if (await sp.checkIfExists(folderPath)) {
|
|
883
|
+
await sp.copyFolderRecursive(folderPath, path.join(migratedFolder, name));
|
|
869
884
|
}
|
|
870
885
|
|
|
871
886
|
// Clear stale version files (undo snapshots from the old page version)
|
|
872
|
-
await clearVersions(config
|
|
887
|
+
await clearVersions(config, name);
|
|
873
888
|
|
|
874
889
|
// Update metadata
|
|
875
890
|
metadata.pageVersion = PAGE_VERSION;
|
|
876
891
|
metadata.lastModified = new Date().toISOString();
|
|
877
|
-
await savePageMetadata(config
|
|
892
|
+
await savePageMetadata(config, name, metadata);
|
|
878
893
|
|
|
879
894
|
res.json({ upgraded: true, fromVersion: currentVersion, toVersion: PAGE_VERSION });
|
|
880
895
|
} catch (err: unknown) {
|
|
@@ -892,14 +907,14 @@ Return ONLY the JSON object.`};
|
|
|
892
907
|
const userPageDir = path.join(config.pagesFolder, 'pages', name);
|
|
893
908
|
let requiredPageDir: string | undefined;
|
|
894
909
|
for (const folder of config.requiredPagesFolders) {
|
|
895
|
-
if (await checkIfExists(path.join(folder, name, 'page.html'))) {
|
|
910
|
+
if (await checkIfExists(path.join(folder, name, 'page.html'))) { // package content, local fs
|
|
896
911
|
requiredPageDir = path.join(folder, name);
|
|
897
912
|
break;
|
|
898
913
|
}
|
|
899
914
|
}
|
|
900
915
|
let sourceDir: string | null = null;
|
|
901
916
|
|
|
902
|
-
if (await checkIfExists(path.join(userPageDir, 'page.html'))) {
|
|
917
|
+
if (await sp.checkIfExists(path.join(userPageDir, 'page.html'))) {
|
|
903
918
|
sourceDir = userPageDir;
|
|
904
919
|
} else if (requiredPageDir) {
|
|
905
920
|
// For required pages, create a temp-like zip with just the HTML
|
|
@@ -940,7 +955,7 @@ Return ONLY the JSON object.`};
|
|
|
940
955
|
|
|
941
956
|
// Ask a question about a page (with full page HTML context)
|
|
942
957
|
app.post('/api/pages/:name/ask', async (req, res) => {
|
|
943
|
-
await requiresSettings(res, config
|
|
958
|
+
await requiresSettings(res, config, async (settings) => {
|
|
944
959
|
const { name } = req.params;
|
|
945
960
|
const { question } = req.body;
|
|
946
961
|
if (typeof question !== 'string' || !question.trim()) {
|
|
@@ -956,7 +971,7 @@ Return ONLY the JSON object.`};
|
|
|
956
971
|
}
|
|
957
972
|
|
|
958
973
|
// Create completion (uses 'chat' model, not 'builder')
|
|
959
|
-
const complete = await createCompletePrompt(config
|
|
974
|
+
const complete = await createCompletePrompt(config, 'chat', req.body.model);
|
|
960
975
|
|
|
961
976
|
const system = {
|
|
962
977
|
role: 'system' as const,
|
|
@@ -15,7 +15,7 @@ export function useConnectorRoutes(config: SynthOSConfig, app: Application): voi
|
|
|
15
15
|
// Also handles POST /api/connectors — Proxy call (see below)
|
|
16
16
|
app.get('/api/connectors', async (req, res) => {
|
|
17
17
|
try {
|
|
18
|
-
const settings = await loadSettings(config
|
|
18
|
+
const settings = await loadSettings(config);
|
|
19
19
|
const connectors = settings.connectors ?? {};
|
|
20
20
|
|
|
21
21
|
const categoryFilter = req.query.category as string | undefined;
|
|
@@ -60,7 +60,7 @@ export function useConnectorRoutes(config: SynthOSConfig, app: Application): voi
|
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
const settings = await loadSettings(config
|
|
63
|
+
const settings = await loadSettings(config);
|
|
64
64
|
const cfg = (settings.connectors ?? {})[id];
|
|
65
65
|
const isOAuth = def.authStrategy === 'oauth2';
|
|
66
66
|
const oauthCfg = cfg as ConnectorOAuthConfig | undefined;
|
|
@@ -94,7 +94,7 @@ export function useConnectorRoutes(config: SynthOSConfig, app: Application): voi
|
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
const settings = await loadSettings(config
|
|
97
|
+
const settings = await loadSettings(config);
|
|
98
98
|
const existing = settings.connectors ?? {};
|
|
99
99
|
|
|
100
100
|
if (def.authStrategy === 'oauth2') {
|
|
@@ -121,7 +121,7 @@ export function useConnectorRoutes(config: SynthOSConfig, app: Application): voi
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
const updated = { ...existing, [id]: entry };
|
|
124
|
-
await saveSettings(config
|
|
124
|
+
await saveSettings(config, { connectors: updated });
|
|
125
125
|
} else {
|
|
126
126
|
const { apiKey, enabled } = req.body;
|
|
127
127
|
const resolvedKey = (typeof apiKey === 'string' && apiKey.length > 0)
|
|
@@ -132,7 +132,7 @@ export function useConnectorRoutes(config: SynthOSConfig, app: Application): voi
|
|
|
132
132
|
...existing,
|
|
133
133
|
[id]: { apiKey: resolvedKey, enabled: !!enabled }
|
|
134
134
|
};
|
|
135
|
-
await saveSettings(config
|
|
135
|
+
await saveSettings(config, { connectors: updated });
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
res.json({ saved: true });
|
|
@@ -146,13 +146,13 @@ export function useConnectorRoutes(config: SynthOSConfig, app: Application): voi
|
|
|
146
146
|
app.delete('/api/connectors/:id', async (req, res) => {
|
|
147
147
|
try {
|
|
148
148
|
const { id } = req.params;
|
|
149
|
-
const settings = await loadSettings(config
|
|
149
|
+
const settings = await loadSettings(config);
|
|
150
150
|
const existing = settings.connectors ?? {};
|
|
151
151
|
|
|
152
152
|
const updated = { ...existing };
|
|
153
153
|
delete updated[id];
|
|
154
154
|
|
|
155
|
-
await saveSettings(config
|
|
155
|
+
await saveSettings(config, { connectors: updated });
|
|
156
156
|
res.json({ deleted: true });
|
|
157
157
|
} catch (err: unknown) {
|
|
158
158
|
console.error(err);
|
|
@@ -170,7 +170,7 @@ export function useConnectorRoutes(config: SynthOSConfig, app: Application): voi
|
|
|
170
170
|
return;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
const settings = await loadSettings(config
|
|
173
|
+
const settings = await loadSettings(config);
|
|
174
174
|
const cfg = (settings.connectors ?? {})[id] as ConnectorOAuthConfig | undefined;
|
|
175
175
|
if (!cfg?.clientId || !cfg?.clientSecret) {
|
|
176
176
|
res.status(400).json({ error: 'Client ID and Client Secret must be saved before authorizing' });
|
|
@@ -220,7 +220,7 @@ export function useConnectorRoutes(config: SynthOSConfig, app: Application): voi
|
|
|
220
220
|
return;
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
const settings = await loadSettings(config
|
|
223
|
+
const settings = await loadSettings(config);
|
|
224
224
|
const cfg = (settings.connectors ?? {})[connectorId] as ConnectorOAuthConfig | undefined;
|
|
225
225
|
if (!cfg?.clientId || !cfg?.clientSecret) {
|
|
226
226
|
res.status(400).json({ error: 'Client credentials not found' });
|
|
@@ -307,7 +307,7 @@ export function useConnectorRoutes(config: SynthOSConfig, app: Application): voi
|
|
|
307
307
|
enabled: true
|
|
308
308
|
}
|
|
309
309
|
};
|
|
310
|
-
await saveSettings(config
|
|
310
|
+
await saveSettings(config, { connectors: updated });
|
|
311
311
|
|
|
312
312
|
res.redirect(`/settings?tab=connectors&connected=${encodeURIComponent(connectorId)}`);
|
|
313
313
|
} catch (err: unknown) {
|
|
@@ -332,7 +332,7 @@ export function useConnectorRoutes(config: SynthOSConfig, app: Application): voi
|
|
|
332
332
|
return;
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
-
const settings = await loadSettings(config
|
|
335
|
+
const settings = await loadSettings(config);
|
|
336
336
|
const cfg = (settings.connectors ?? {})[request.connector];
|
|
337
337
|
|
|
338
338
|
if (def.authStrategy === 'oauth2') {
|