synthos 0.9.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/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/customizer/Customizer.ts +12 -0
- package/src/index.ts +2 -1
- package/src/init.ts +78 -42
- 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/pages.spec.ts +54 -84
package/src/init.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as fs from 'fs/promises';
|
|
2
2
|
import path from "path";
|
|
3
|
-
import { checkIfExists,
|
|
3
|
+
import { checkIfExists, findFileInFolders, listFolders } from "./files";
|
|
4
4
|
import { PAGE_VERSION, getRequiredPages } from "./pages";
|
|
5
5
|
import { DefaultSettings } from "./settings";
|
|
6
6
|
import { Customizer } from './customizer';
|
|
7
|
+
import { StorageProvider, FsStorageProvider } from './storage';
|
|
7
8
|
|
|
8
9
|
export interface SynthOSConfig {
|
|
9
10
|
localFolder: string;
|
|
@@ -15,6 +16,7 @@ export interface SynthOSConfig {
|
|
|
15
16
|
staticFilesFolders: string[];
|
|
16
17
|
serviceConnectorsFolders: string[];
|
|
17
18
|
requiredPages: string[];
|
|
19
|
+
storageProvider: StorageProvider;
|
|
18
20
|
debug: boolean;
|
|
19
21
|
debugPageUpdates: boolean;
|
|
20
22
|
}
|
|
@@ -62,14 +64,17 @@ export async function createConfig(
|
|
|
62
64
|
path.join(__dirname, '../service-connectors')
|
|
63
65
|
),
|
|
64
66
|
requiredPages,
|
|
67
|
+
storageProvider: customizer?.storageProvider ?? new FsStorageProvider(),
|
|
65
68
|
debug: options?.debug ?? false,
|
|
66
69
|
debugPageUpdates: options?.debugPageUpdates ?? false
|
|
67
70
|
};
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
export async function init(config: SynthOSConfig, includeDefaultPages: boolean = true): Promise<boolean> {
|
|
74
|
+
const sp = config.storageProvider;
|
|
75
|
+
|
|
71
76
|
// Check for existing folder
|
|
72
|
-
if (await checkIfExists(config.pagesFolder)) {
|
|
77
|
+
if (await sp.checkIfExists(config.pagesFolder)) {
|
|
73
78
|
await repairMissingFolders(config);
|
|
74
79
|
return false;
|
|
75
80
|
}
|
|
@@ -77,17 +82,17 @@ export async function init(config: SynthOSConfig, includeDefaultPages: boolean =
|
|
|
77
82
|
console.log(`Initializing ${config.localFolder} folder...`);
|
|
78
83
|
|
|
79
84
|
// Create pages folder
|
|
80
|
-
await ensureFolderExists(config.pagesFolder);
|
|
85
|
+
await sp.ensureFolderExists(config.pagesFolder);
|
|
81
86
|
|
|
82
87
|
// Create mandatory files
|
|
83
|
-
await saveFile(path.join(config.pagesFolder, '.gitignore'), 'settings.json\n');
|
|
84
|
-
await saveFile(path.join(config.pagesFolder, 'settings.json'), JSON.stringify(DefaultSettings, null, 4));
|
|
85
|
-
await saveFile(path.join(config.pagesFolder, 'settings.json.example'), JSON.stringify(DefaultSettings, null, 4));
|
|
88
|
+
await sp.saveFile(path.join(config.pagesFolder, '.gitignore'), 'settings.json\n');
|
|
89
|
+
await sp.saveFile(path.join(config.pagesFolder, 'settings.json'), JSON.stringify(DefaultSettings, null, 4));
|
|
90
|
+
await sp.saveFile(path.join(config.pagesFolder, 'settings.json.example'), JSON.stringify(DefaultSettings, null, 4));
|
|
86
91
|
|
|
87
92
|
// Setup default scripts
|
|
88
93
|
console.log(`Copying default scripts to ${config.localFolder} folder...`);
|
|
89
94
|
const scriptsFolder = path.join(config.pagesFolder, 'scripts');
|
|
90
|
-
await ensureFolderExists(scriptsFolder);
|
|
95
|
+
await sp.ensureFolderExists(scriptsFolder);
|
|
91
96
|
const scriptFilename = ({
|
|
92
97
|
win32: 'windows-terminal.json',
|
|
93
98
|
darwin: 'mac-terminal.json',
|
|
@@ -95,30 +100,34 @@ export async function init(config: SynthOSConfig, includeDefaultPages: boolean =
|
|
|
95
100
|
} as Record<string, string>)[process.platform] ?? 'linux-terminal.json';
|
|
96
101
|
const scriptSrc = await findFileInFolders(config.defaultScriptsFolders, scriptFilename);
|
|
97
102
|
if (scriptSrc) {
|
|
98
|
-
|
|
103
|
+
// Read from package (local fs), write to user storage (provider)
|
|
104
|
+
const data = await fs.readFile(scriptSrc);
|
|
105
|
+
await sp.saveBuffer(path.join(scriptsFolder, path.basename(scriptSrc)), data);
|
|
99
106
|
}
|
|
100
107
|
|
|
101
|
-
await saveFile(path.join(scriptsFolder, 'example.sh'), `#!/bin/bash\n\n# This is an example script\n\n# You can run this script using the following command:\n# sh ${config.localFolder}/scripts/example.sh\n\n# This script will print "Hello, World!" to the console\n\necho "Hello, World!"\n`);
|
|
108
|
+
await sp.saveFile(path.join(scriptsFolder, 'example.sh'), `#!/bin/bash\n\n# This is an example script\n\n# You can run this script using the following command:\n# sh ${config.localFolder}/scripts/example.sh\n\n# This script will print "Hello, World!" to the console\n\necho "Hello, World!"\n`);
|
|
102
109
|
|
|
103
110
|
// Create empty themes folder — default themes are served directly from
|
|
104
111
|
// defaultThemesFolders; users can add custom themes here.
|
|
105
|
-
await ensureFolderExists(path.join(config.pagesFolder, 'themes'));
|
|
112
|
+
await sp.ensureFolderExists(path.join(config.pagesFolder, 'themes'));
|
|
106
113
|
|
|
107
114
|
// Copy pages
|
|
108
115
|
if (includeDefaultPages) {
|
|
109
116
|
console.log(`Copying default pages to ${config.localFolder} folder...`);
|
|
110
|
-
await copyDefaultPages(config
|
|
117
|
+
await copyDefaultPages(config, config.defaultPagesFolders);
|
|
111
118
|
}
|
|
112
119
|
|
|
113
120
|
return true;
|
|
114
121
|
}
|
|
115
122
|
|
|
116
123
|
async function repairMissingFolders(config: SynthOSConfig): Promise<void> {
|
|
124
|
+
const sp = config.storageProvider;
|
|
125
|
+
|
|
117
126
|
// Rebuild scripts folder from defaults if missing
|
|
118
127
|
const scriptsFolder = path.join(config.pagesFolder, 'scripts');
|
|
119
|
-
if (!await checkIfExists(scriptsFolder)) {
|
|
128
|
+
if (!await sp.checkIfExists(scriptsFolder)) {
|
|
120
129
|
console.log(`Restoring default scripts to ${config.localFolder} folder...`);
|
|
121
|
-
await ensureFolderExists(scriptsFolder);
|
|
130
|
+
await sp.ensureFolderExists(scriptsFolder);
|
|
122
131
|
const scriptFilename = ({
|
|
123
132
|
win32: 'windows-terminal.json',
|
|
124
133
|
darwin: 'mac-terminal.json',
|
|
@@ -126,30 +135,31 @@ async function repairMissingFolders(config: SynthOSConfig): Promise<void> {
|
|
|
126
135
|
} as Record<string, string>)[process.platform] ?? 'linux-terminal.json';
|
|
127
136
|
const scriptSrc = await findFileInFolders(config.defaultScriptsFolders, scriptFilename);
|
|
128
137
|
if (scriptSrc) {
|
|
129
|
-
await
|
|
138
|
+
const data = await fs.readFile(scriptSrc);
|
|
139
|
+
await sp.saveBuffer(path.join(scriptsFolder, path.basename(scriptSrc)), data);
|
|
130
140
|
}
|
|
131
|
-
await saveFile(path.join(scriptsFolder, 'example.sh'), `#!/bin/bash\n\n# This is an example script\n\n# You can run this script using the following command:\n# sh ${config.localFolder}/scripts/example.sh\n\n# This script will print "Hello, World!" to the console\n\necho "Hello, World!"\n`);
|
|
141
|
+
await sp.saveFile(path.join(scriptsFolder, 'example.sh'), `#!/bin/bash\n\n# This is an example script\n\n# You can run this script using the following command:\n# sh ${config.localFolder}/scripts/example.sh\n\n# This script will print "Hello, World!" to the console\n\necho "Hello, World!"\n`);
|
|
132
142
|
}
|
|
133
143
|
|
|
134
144
|
// Ensure themes folder exists — default themes are served directly from
|
|
135
145
|
// defaultThemesFolders; this folder is for user-added custom themes only.
|
|
136
|
-
await ensureFolderExists(path.join(config.pagesFolder, 'themes'));
|
|
146
|
+
await sp.ensureFolderExists(path.join(config.pagesFolder, 'themes'));
|
|
137
147
|
|
|
138
148
|
// Ensure pages/ subfolder exists
|
|
139
149
|
const pagesSubdir = path.join(config.pagesFolder, 'pages');
|
|
140
|
-
if (!await checkIfExists(pagesSubdir)) {
|
|
150
|
+
if (!await sp.checkIfExists(pagesSubdir)) {
|
|
141
151
|
// No pages folder and no flat files — rebuild from defaults
|
|
142
|
-
const htmlFiles = (await listFiles(config.pagesFolder)).filter(f => f.endsWith('.html'));
|
|
152
|
+
const htmlFiles = (await sp.listFiles(config.pagesFolder)).filter(f => f.endsWith('.html'));
|
|
143
153
|
if (htmlFiles.length === 0) {
|
|
144
154
|
console.log(`Restoring default pages to ${config.localFolder}/pages/ folder...`);
|
|
145
|
-
await copyDefaultPages(config
|
|
155
|
+
await copyDefaultPages(config, config.defaultPagesFolders);
|
|
146
156
|
} else {
|
|
147
|
-
await ensureFolderExists(pagesSubdir);
|
|
157
|
+
await sp.ensureFolderExists(pagesSubdir);
|
|
148
158
|
}
|
|
149
159
|
}
|
|
150
160
|
|
|
151
161
|
// Migrate any stray flat .html files from root into pages/<name>/
|
|
152
|
-
await migrateFlatPages(config
|
|
162
|
+
await migrateFlatPages(config);
|
|
153
163
|
}
|
|
154
164
|
|
|
155
165
|
function toTitleCase(name: string): string {
|
|
@@ -160,13 +170,15 @@ function toTitleCase(name: string): string {
|
|
|
160
170
|
.replace(/\b\w/g, c => c.toUpperCase());
|
|
161
171
|
}
|
|
162
172
|
|
|
163
|
-
async function migrateFlatPages(
|
|
173
|
+
async function migrateFlatPages(config: SynthOSConfig): Promise<void> {
|
|
174
|
+
const sp = config.storageProvider;
|
|
175
|
+
const pagesFolder = config.pagesFolder;
|
|
164
176
|
const pagesSubdir = path.join(pagesFolder, 'pages');
|
|
165
|
-
const htmlFiles = (await listFiles(pagesFolder)).filter(f => f.endsWith('.html'));
|
|
177
|
+
const htmlFiles = (await sp.listFiles(pagesFolder)).filter(f => f.endsWith('.html'));
|
|
166
178
|
if (htmlFiles.length === 0) return;
|
|
167
179
|
|
|
168
|
-
console.log(`Migrating ${htmlFiles.length} page(s) to ${localFolder}/pages/ folder...`);
|
|
169
|
-
await ensureFolderExists(pagesSubdir);
|
|
180
|
+
console.log(`Migrating ${htmlFiles.length} page(s) to ${config.localFolder}/pages/ folder...`);
|
|
181
|
+
await sp.ensureFolderExists(pagesSubdir);
|
|
170
182
|
const now = new Date().toISOString();
|
|
171
183
|
|
|
172
184
|
for (const file of htmlFiles) {
|
|
@@ -174,12 +186,11 @@ async function migrateFlatPages(pagesFolder: string, localFolder: string): Promi
|
|
|
174
186
|
const category = pageName.startsWith('[') ? 'Builder' : 'Pages';
|
|
175
187
|
const title = toTitleCase(pageName);
|
|
176
188
|
const pageFolder = path.join(pagesSubdir, pageName);
|
|
177
|
-
await ensureFolderExists(pageFolder);
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
await saveFile(
|
|
189
|
+
await sp.ensureFolderExists(pageFolder);
|
|
190
|
+
// Both source and destination are user storage
|
|
191
|
+
const content = await sp.loadFile(path.join(pagesFolder, file));
|
|
192
|
+
await sp.saveFile(path.join(pageFolder, 'page.html'), content);
|
|
193
|
+
await sp.saveFile(
|
|
183
194
|
path.join(pageFolder, 'page.json'),
|
|
184
195
|
JSON.stringify({
|
|
185
196
|
title,
|
|
@@ -192,19 +203,20 @@ async function migrateFlatPages(pagesFolder: string, localFolder: string): Promi
|
|
|
192
203
|
mode: 'unlocked',
|
|
193
204
|
}, null, 4)
|
|
194
205
|
);
|
|
195
|
-
await deleteFile(path.join(pagesFolder, file));
|
|
206
|
+
await sp.deleteFile(path.join(pagesFolder, file));
|
|
196
207
|
}
|
|
197
208
|
}
|
|
198
209
|
|
|
199
|
-
async function copyDefaultPages(
|
|
200
|
-
const
|
|
201
|
-
|
|
210
|
+
async function copyDefaultPages(config: SynthOSConfig, srcFolders: string[]): Promise<void> {
|
|
211
|
+
const sp = config.storageProvider;
|
|
212
|
+
const pagesDir = path.join(config.pagesFolder, 'pages');
|
|
213
|
+
await sp.ensureFolderExists(pagesDir);
|
|
202
214
|
const now = new Date().toISOString();
|
|
203
215
|
const seen = new Set<string>();
|
|
204
216
|
|
|
205
217
|
for (const srcFolder of srcFolders) {
|
|
206
|
-
if (!await checkIfExists(srcFolder)) continue;
|
|
207
|
-
const dirs = await listFolders(srcFolder);
|
|
218
|
+
if (!await checkIfExists(srcFolder)) continue; // source is always local fs
|
|
219
|
+
const dirs = await listFolders(srcFolder); // source is always local fs
|
|
208
220
|
for (const dir of dirs) {
|
|
209
221
|
const srcPageDir = path.join(srcFolder, dir);
|
|
210
222
|
if (!await checkIfExists(path.join(srcPageDir, 'page.html'))) continue;
|
|
@@ -212,8 +224,11 @@ async function copyDefaultPages(srcFolders: string[], destFolder: string): Promi
|
|
|
212
224
|
seen.add(dir);
|
|
213
225
|
|
|
214
226
|
const pageFolder = path.join(pagesDir, dir);
|
|
215
|
-
await ensureFolderExists(pageFolder);
|
|
216
|
-
|
|
227
|
+
await sp.ensureFolderExists(pageFolder);
|
|
228
|
+
|
|
229
|
+
// Read from local fs, write via provider
|
|
230
|
+
const htmlData = await fs.readFile(path.join(srcPageDir, 'page.html'));
|
|
231
|
+
await sp.saveBuffer(path.join(pageFolder, 'page.html'), htmlData);
|
|
217
232
|
|
|
218
233
|
// Read companion page.json metadata from source folder, fall back to defaults
|
|
219
234
|
let metadata: Record<string, unknown> = {};
|
|
@@ -237,17 +252,38 @@ async function copyDefaultPages(srcFolders: string[], destFolder: string): Promi
|
|
|
237
252
|
: typeof metadata.uxVersion === 'number' ? metadata.uxVersion : PAGE_VERSION,
|
|
238
253
|
mode: metadata.mode === 'locked' ? 'locked' : 'unlocked',
|
|
239
254
|
};
|
|
240
|
-
await saveFile(path.join(pageFolder, 'page.json'), JSON.stringify(fullMetadata, null, 4));
|
|
255
|
+
await sp.saveFile(path.join(pageFolder, 'page.json'), JSON.stringify(fullMetadata, null, 4));
|
|
241
256
|
|
|
242
257
|
// Copy data subfolders (anything that isn't page.html/page.json)
|
|
258
|
+
// Read from local fs, write via provider using cross-provider copy
|
|
243
259
|
const subEntries = await fs.readdir(srcPageDir, { withFileTypes: true });
|
|
244
260
|
for (const entry of subEntries) {
|
|
245
261
|
if (!entry.isDirectory()) continue;
|
|
246
|
-
await
|
|
262
|
+
await crossProviderCopyFolder(
|
|
247
263
|
path.join(srcPageDir, entry.name),
|
|
248
|
-
path.join(pageFolder, entry.name)
|
|
264
|
+
path.join(pageFolder, entry.name),
|
|
265
|
+
sp
|
|
249
266
|
);
|
|
250
267
|
}
|
|
251
268
|
}
|
|
252
269
|
}
|
|
253
270
|
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Copy a folder from local filesystem to the storage provider.
|
|
274
|
+
* Reads from local fs, writes via provider.
|
|
275
|
+
*/
|
|
276
|
+
async function crossProviderCopyFolder(srcFolder: string, destFolder: string, sp: StorageProvider): Promise<void> {
|
|
277
|
+
await sp.ensureFolderExists(destFolder);
|
|
278
|
+
const entries = await fs.readdir(srcFolder, { withFileTypes: true });
|
|
279
|
+
for (const entry of entries) {
|
|
280
|
+
const srcPath = path.join(srcFolder, entry.name);
|
|
281
|
+
const destPath = path.join(destFolder, entry.name);
|
|
282
|
+
if (entry.isDirectory()) {
|
|
283
|
+
await crossProviderCopyFolder(srcPath, destPath, sp);
|
|
284
|
+
} else {
|
|
285
|
+
const data = await fs.readFile(srcPath);
|
|
286
|
+
await sp.saveBuffer(destPath, data);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
package/src/pages.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {checkIfExists,
|
|
1
|
+
import {checkIfExists, listFolders, loadFile} from './files';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { SynthOSConfig } from './init';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Derive the list of required page names by scanning *.html files
|
|
@@ -38,12 +39,15 @@ export interface PageInfo {
|
|
|
38
39
|
|
|
39
40
|
export type PageMetadata = Omit<PageInfo, 'name'>;
|
|
40
41
|
|
|
41
|
-
export async function loadPageMetadata(
|
|
42
|
+
export async function loadPageMetadata(config: SynthOSConfig, name: string, fallbackFolders?: string[]): Promise<PageMetadata | undefined> {
|
|
43
|
+
const pagesFolder = config.pagesFolder;
|
|
44
|
+
const sp = config.storageProvider;
|
|
45
|
+
|
|
42
46
|
// 1. Try user override: <localFolder>/pages/<name>/page.json
|
|
43
47
|
const metadataPath = path.join(pagesFolder, 'pages', name, 'page.json');
|
|
44
|
-
if (await checkIfExists(metadataPath)) {
|
|
48
|
+
if (await sp.checkIfExists(metadataPath)) {
|
|
45
49
|
try {
|
|
46
|
-
const raw = await loadFile(metadataPath);
|
|
50
|
+
const raw = await sp.loadFile(metadataPath);
|
|
47
51
|
const parsed = JSON.parse(raw);
|
|
48
52
|
return parseMetadata(parsed);
|
|
49
53
|
} catch {
|
|
@@ -51,7 +55,7 @@ export async function loadPageMetadata(pagesFolder: string, name: string, fallba
|
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
57
|
|
|
54
|
-
// 2. Try fallback folders: fallbackFolder/<name>/page.json
|
|
58
|
+
// 2. Try fallback folders: fallbackFolder/<name>/page.json (package content, always local fs)
|
|
55
59
|
if (fallbackFolders) {
|
|
56
60
|
for (const folder of fallbackFolders) {
|
|
57
61
|
const candidate = path.join(folder, name, 'page.json');
|
|
@@ -84,10 +88,11 @@ export function parseMetadata(parsed: Record<string, unknown>): PageMetadata {
|
|
|
84
88
|
};
|
|
85
89
|
}
|
|
86
90
|
|
|
87
|
-
export async function savePageMetadata(
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
await
|
|
91
|
+
export async function savePageMetadata(config: SynthOSConfig, name: string, metadata: PageMetadata): Promise<void> {
|
|
92
|
+
const sp = config.storageProvider;
|
|
93
|
+
const pageFolder = path.join(config.pagesFolder, 'pages', name);
|
|
94
|
+
await sp.ensureFolderExists(pageFolder);
|
|
95
|
+
await sp.saveFile(path.join(pageFolder, 'page.json'), JSON.stringify(metadata, null, 4));
|
|
91
96
|
}
|
|
92
97
|
|
|
93
98
|
const DEFAULT_METADATA: PageMetadata = {
|
|
@@ -101,16 +106,18 @@ const DEFAULT_METADATA: PageMetadata = {
|
|
|
101
106
|
mode: 'unlocked',
|
|
102
107
|
};
|
|
103
108
|
|
|
104
|
-
export async function listPages(
|
|
109
|
+
export async function listPages(config: SynthOSConfig, fallbackPagesFolders: string[]): Promise<PageInfo[]> {
|
|
110
|
+
const pagesFolder = config.pagesFolder;
|
|
111
|
+
const sp = config.storageProvider;
|
|
105
112
|
const pageMap = new Map<string, PageInfo>();
|
|
106
113
|
|
|
107
114
|
// Folder-based pages under pages/ subdirectory
|
|
108
115
|
const pagesSubdir = path.join(pagesFolder, 'pages');
|
|
109
|
-
if (await checkIfExists(pagesSubdir)) {
|
|
110
|
-
const folders = await listFolders(pagesSubdir);
|
|
116
|
+
if (await sp.checkIfExists(pagesSubdir)) {
|
|
117
|
+
const folders = await sp.listFolders(pagesSubdir);
|
|
111
118
|
for (const folder of folders) {
|
|
112
|
-
if (await checkIfExists(path.join(pagesSubdir, folder, 'page.html'))) {
|
|
113
|
-
const metadata = await loadPageMetadata(
|
|
119
|
+
if (await sp.checkIfExists(path.join(pagesSubdir, folder, 'page.html'))) {
|
|
120
|
+
const metadata = await loadPageMetadata(config, folder);
|
|
114
121
|
pageMap.set(folder, {
|
|
115
122
|
name: folder,
|
|
116
123
|
...(metadata ?? DEFAULT_METADATA),
|
|
@@ -120,7 +127,7 @@ export async function listPages(pagesFolder: string, fallbackPagesFolders: strin
|
|
|
120
127
|
}
|
|
121
128
|
|
|
122
129
|
// Legacy flat .html files in root (v1 pages)
|
|
123
|
-
const flatFiles = (await listFiles(pagesFolder)).filter(file => file.endsWith('.html'));
|
|
130
|
+
const flatFiles = (await sp.listFiles(pagesFolder)).filter(file => file.endsWith('.html'));
|
|
124
131
|
for (const file of flatFiles) {
|
|
125
132
|
const name = file.replace(/\.html$/, '');
|
|
126
133
|
if (!pageMap.has(name)) {
|
|
@@ -146,7 +153,7 @@ export async function listPages(pagesFolder: string, fallbackPagesFolders: strin
|
|
|
146
153
|
}
|
|
147
154
|
}
|
|
148
155
|
|
|
149
|
-
// Add pages from fallback (required) pages folders
|
|
156
|
+
// Add pages from fallback (required) pages folders (package content, always local fs)
|
|
150
157
|
for (const folder of fallbackPagesFolders) {
|
|
151
158
|
if (!await checkIfExists(folder)) continue;
|
|
152
159
|
const dirs = await listFolders(folder);
|
|
@@ -154,7 +161,7 @@ export async function listPages(pagesFolder: string, fallbackPagesFolders: strin
|
|
|
154
161
|
if (pageMap.has(name)) continue;
|
|
155
162
|
if (!await checkIfExists(path.join(folder, name, 'page.html'))) continue;
|
|
156
163
|
// System page not yet in map — check for user override, then fallback page.json
|
|
157
|
-
const metadata = await loadPageMetadata(
|
|
164
|
+
const metadata = await loadPageMetadata(config, name, fallbackPagesFolders);
|
|
158
165
|
pageMap.set(name, {
|
|
159
166
|
name,
|
|
160
167
|
title: metadata?.title ?? '',
|
|
@@ -176,11 +183,14 @@ export async function listPages(pagesFolder: string, fallbackPagesFolders: strin
|
|
|
176
183
|
return entries;
|
|
177
184
|
}
|
|
178
185
|
|
|
179
|
-
export async function loadPageState(
|
|
186
|
+
export async function loadPageState(config: SynthOSConfig, name: string): Promise<string|undefined> {
|
|
187
|
+
const pagesFolder = config.pagesFolder;
|
|
188
|
+
const sp = config.storageProvider;
|
|
189
|
+
|
|
180
190
|
// Check for working-state version files first
|
|
181
|
-
const latestVersion = await getLatestVersion(
|
|
191
|
+
const latestVersion = await getLatestVersion(config, name);
|
|
182
192
|
if (latestVersion > 0) {
|
|
183
|
-
const versionHtml = await loadPageVersion(
|
|
193
|
+
const versionHtml = await loadPageVersion(config, name, latestVersion);
|
|
184
194
|
if (versionHtml) return versionHtml;
|
|
185
195
|
}
|
|
186
196
|
|
|
@@ -189,12 +199,12 @@ export async function loadPageState(pagesFolder: string, name: string): Promise<
|
|
|
189
199
|
const directFolderPath = path.join(pagesFolder, name, 'page.html');
|
|
190
200
|
const flatPath = path.join(pagesFolder, `${name}.html`);
|
|
191
201
|
|
|
192
|
-
if (await checkIfExists(folderPath)) {
|
|
193
|
-
return loadFile(folderPath);
|
|
194
|
-
} else if (await checkIfExists(directFolderPath)) {
|
|
195
|
-
return loadFile(directFolderPath);
|
|
196
|
-
} else if (await checkIfExists(flatPath)) {
|
|
197
|
-
return loadFile(flatPath);
|
|
202
|
+
if (await sp.checkIfExists(folderPath)) {
|
|
203
|
+
return sp.loadFile(folderPath);
|
|
204
|
+
} else if (await sp.checkIfExists(directFolderPath)) {
|
|
205
|
+
return sp.loadFile(directFolderPath);
|
|
206
|
+
} else if (await sp.checkIfExists(flatPath)) {
|
|
207
|
+
return sp.loadFile(flatPath);
|
|
198
208
|
}
|
|
199
209
|
return undefined;
|
|
200
210
|
}
|
|
@@ -203,14 +213,15 @@ export function normalizePageName(name: string|undefined): string|undefined {
|
|
|
203
213
|
return typeof name == 'string' && name.length > 0 ? name.replace(/[^a-z0-9\-_\[\]\(\)\{\}@#\$%&]/gi, '_').toLowerCase() : undefined;
|
|
204
214
|
}
|
|
205
215
|
|
|
206
|
-
export async function savePageState(
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
await
|
|
216
|
+
export async function savePageState(config: SynthOSConfig, name: string, content: string, title?: string, categories?: string[]): Promise<void> {
|
|
217
|
+
const sp = config.storageProvider;
|
|
218
|
+
const pageFolder = path.join(config.pagesFolder, 'pages', name);
|
|
219
|
+
await sp.ensureFolderExists(pageFolder);
|
|
220
|
+
await sp.saveFile(path.join(pageFolder, 'page.html'), content);
|
|
210
221
|
|
|
211
222
|
// Create page.json with full metadata if it doesn't exist
|
|
212
223
|
const metadataPath = path.join(pageFolder, 'page.json');
|
|
213
|
-
if (!(await checkIfExists(metadataPath))) {
|
|
224
|
+
if (!(await sp.checkIfExists(metadataPath))) {
|
|
214
225
|
const now = new Date().toISOString();
|
|
215
226
|
const metadata: PageMetadata = {
|
|
216
227
|
title: title ?? '',
|
|
@@ -222,21 +233,24 @@ export async function savePageState(pagesFolder: string, name: string, content:
|
|
|
222
233
|
pageVersion: PAGE_VERSION,
|
|
223
234
|
mode: 'unlocked',
|
|
224
235
|
};
|
|
225
|
-
await saveFile(metadataPath, JSON.stringify(metadata, null, 4));
|
|
236
|
+
await sp.saveFile(metadataPath, JSON.stringify(metadata, null, 4));
|
|
226
237
|
}
|
|
227
238
|
}
|
|
228
239
|
|
|
229
|
-
export async function deletePage(
|
|
240
|
+
export async function deletePage(config: SynthOSConfig, name: string): Promise<void> {
|
|
241
|
+
const pagesFolder = config.pagesFolder;
|
|
242
|
+
const sp = config.storageProvider;
|
|
243
|
+
|
|
230
244
|
// Delete folder-based page: <pagesFolder>/pages/<name>/
|
|
231
245
|
const folderPath = path.join(pagesFolder, 'pages', name);
|
|
232
|
-
if (await checkIfExists(folderPath)) {
|
|
233
|
-
await deleteFolder(folderPath);
|
|
246
|
+
if (await sp.checkIfExists(folderPath)) {
|
|
247
|
+
await sp.deleteFolder(folderPath);
|
|
234
248
|
}
|
|
235
249
|
|
|
236
250
|
// Delete legacy flat file: <pagesFolder>/<name>.html
|
|
237
251
|
const flatPath = path.join(pagesFolder, `${name}.html`);
|
|
238
|
-
if (await checkIfExists(flatPath)) {
|
|
239
|
-
await deleteFile(flatPath);
|
|
252
|
+
if (await sp.checkIfExists(flatPath)) {
|
|
253
|
+
await sp.deleteFile(flatPath);
|
|
240
254
|
}
|
|
241
255
|
|
|
242
256
|
}
|
|
@@ -248,28 +262,31 @@ export async function deletePage(pagesFolder: string, name: string): Promise<voi
|
|
|
248
262
|
/**
|
|
249
263
|
* Save a version snapshot: <pagesFolder>/pages/<name>/page.v<version>.html
|
|
250
264
|
*/
|
|
251
|
-
export async function savePageVersion(
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
await
|
|
265
|
+
export async function savePageVersion(config: SynthOSConfig, name: string, version: number, html: string): Promise<void> {
|
|
266
|
+
const sp = config.storageProvider;
|
|
267
|
+
const pageFolder = path.join(config.pagesFolder, 'pages', name);
|
|
268
|
+
await sp.ensureFolderExists(pageFolder);
|
|
269
|
+
await sp.saveFile(path.join(pageFolder, `page.v${version}.html`), html);
|
|
255
270
|
}
|
|
256
271
|
|
|
257
272
|
/**
|
|
258
273
|
* Load a version snapshot (returns undefined if the file doesn't exist).
|
|
259
274
|
*/
|
|
260
|
-
export async function loadPageVersion(
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
275
|
+
export async function loadPageVersion(config: SynthOSConfig, name: string, version: number): Promise<string | undefined> {
|
|
276
|
+
const sp = config.storageProvider;
|
|
277
|
+
const filePath = path.join(config.pagesFolder, 'pages', name, `page.v${version}.html`);
|
|
278
|
+
if (!await sp.checkIfExists(filePath)) return undefined;
|
|
279
|
+
return sp.loadFile(filePath);
|
|
264
280
|
}
|
|
265
281
|
|
|
266
282
|
/**
|
|
267
283
|
* Scan page.v*.html files and return the highest version number (0 if none).
|
|
268
284
|
*/
|
|
269
|
-
export async function getLatestVersion(
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
285
|
+
export async function getLatestVersion(config: SynthOSConfig, name: string): Promise<number> {
|
|
286
|
+
const sp = config.storageProvider;
|
|
287
|
+
const pageFolder = path.join(config.pagesFolder, 'pages', name);
|
|
288
|
+
if (!await sp.checkIfExists(pageFolder)) return 0;
|
|
289
|
+
const files = await sp.listFiles(pageFolder);
|
|
273
290
|
let max = 0;
|
|
274
291
|
for (const file of files) {
|
|
275
292
|
const match = file.match(/^page\.v(\d+)\.html$/);
|
|
@@ -284,13 +301,14 @@ export async function getLatestVersion(pagesFolder: string, name: string): Promi
|
|
|
284
301
|
/**
|
|
285
302
|
* Delete all page.v*.html version files for a page.
|
|
286
303
|
*/
|
|
287
|
-
export async function clearVersions(
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
304
|
+
export async function clearVersions(config: SynthOSConfig, name: string): Promise<void> {
|
|
305
|
+
const sp = config.storageProvider;
|
|
306
|
+
const pageFolder = path.join(config.pagesFolder, 'pages', name);
|
|
307
|
+
if (!await sp.checkIfExists(pageFolder)) return;
|
|
308
|
+
const files = await sp.listFiles(pageFolder);
|
|
291
309
|
for (const file of files) {
|
|
292
310
|
if (/^page\.v\d+\.html$/.test(file)) {
|
|
293
|
-
await deleteFile(path.join(pageFolder, file));
|
|
311
|
+
await sp.deleteFile(path.join(pageFolder, file));
|
|
294
312
|
}
|
|
295
313
|
}
|
|
296
314
|
}
|
|
@@ -301,7 +319,7 @@ export interface CopyPageOptions {
|
|
|
301
319
|
}
|
|
302
320
|
|
|
303
321
|
export async function copyPage(
|
|
304
|
-
|
|
322
|
+
config: SynthOSConfig,
|
|
305
323
|
sourceName: string,
|
|
306
324
|
targetName: string,
|
|
307
325
|
title: string,
|
|
@@ -309,13 +327,15 @@ export async function copyPage(
|
|
|
309
327
|
requiredPagesFolders: string[],
|
|
310
328
|
options?: CopyPageOptions
|
|
311
329
|
): Promise<void> {
|
|
330
|
+
const pagesFolder = config.pagesFolder;
|
|
331
|
+
const sp = config.storageProvider;
|
|
312
332
|
const copyTables = options?.copyTables ?? false;
|
|
313
|
-
const
|
|
333
|
+
const cpFiles = options?.copyFiles ?? true;
|
|
314
334
|
|
|
315
335
|
// Resolve source page folder: user pages first, then required pages
|
|
316
336
|
let sourceFolder: string | undefined;
|
|
317
337
|
const userSourceFolder = path.join(pagesFolder, 'pages', sourceName);
|
|
318
|
-
if (await checkIfExists(path.join(userSourceFolder, 'page.html'))) {
|
|
338
|
+
if (await sp.checkIfExists(path.join(userSourceFolder, 'page.html'))) {
|
|
319
339
|
sourceFolder = userSourceFolder;
|
|
320
340
|
} else {
|
|
321
341
|
for (const folder of requiredPagesFolders) {
|
|
@@ -330,12 +350,17 @@ export async function copyPage(
|
|
|
330
350
|
// Load source HTML
|
|
331
351
|
let html: string | undefined;
|
|
332
352
|
if (sourceFolder) {
|
|
333
|
-
|
|
353
|
+
// Source could be user storage or package — try user first, fall back to local fs
|
|
354
|
+
if (sourceFolder === userSourceFolder) {
|
|
355
|
+
html = await sp.loadFile(path.join(sourceFolder, 'page.html'));
|
|
356
|
+
} else {
|
|
357
|
+
html = await loadFile(path.join(sourceFolder, 'page.html'));
|
|
358
|
+
}
|
|
334
359
|
} else {
|
|
335
360
|
// Try legacy flat file
|
|
336
361
|
const flatPath = path.join(pagesFolder, `${sourceName}.html`);
|
|
337
|
-
if (await checkIfExists(flatPath)) {
|
|
338
|
-
html = await loadFile(flatPath);
|
|
362
|
+
if (await sp.checkIfExists(flatPath)) {
|
|
363
|
+
html = await sp.loadFile(flatPath);
|
|
339
364
|
}
|
|
340
365
|
}
|
|
341
366
|
|
|
@@ -344,7 +369,7 @@ export async function copyPage(
|
|
|
344
369
|
}
|
|
345
370
|
|
|
346
371
|
// Save HTML to target (creates folder + page.html + page.json)
|
|
347
|
-
await savePageState(
|
|
372
|
+
await savePageState(config, targetName, html, title);
|
|
348
373
|
|
|
349
374
|
// Overwrite the generated metadata with provided title + categories
|
|
350
375
|
const now = new Date().toISOString();
|
|
@@ -358,27 +383,33 @@ export async function copyPage(
|
|
|
358
383
|
pageVersion: PAGE_VERSION,
|
|
359
384
|
mode: 'unlocked',
|
|
360
385
|
};
|
|
361
|
-
await savePageMetadata(
|
|
386
|
+
await savePageMetadata(config, targetName, metadata);
|
|
362
387
|
|
|
363
388
|
// Copy additional content from source if a folder was resolved
|
|
364
389
|
if (sourceFolder) {
|
|
365
390
|
const targetFolder = path.join(pagesFolder, 'pages', targetName);
|
|
366
391
|
|
|
367
392
|
if (copyTables) {
|
|
368
|
-
|
|
393
|
+
// Source could be user or package folder — use appropriate listing
|
|
394
|
+
const entries = sourceFolder === userSourceFolder
|
|
395
|
+
? await sp.listFolders(sourceFolder)
|
|
396
|
+
: await listFolders(sourceFolder);
|
|
369
397
|
for (const entry of entries) {
|
|
370
398
|
if (entry === 'files') continue; // handled separately
|
|
371
|
-
await copyFolderRecursive(
|
|
399
|
+
await sp.copyFolderRecursive(
|
|
372
400
|
path.join(sourceFolder, entry),
|
|
373
401
|
path.join(targetFolder, entry)
|
|
374
402
|
);
|
|
375
403
|
}
|
|
376
404
|
}
|
|
377
405
|
|
|
378
|
-
if (
|
|
406
|
+
if (cpFiles) {
|
|
379
407
|
const filesDir = path.join(sourceFolder, 'files');
|
|
380
|
-
|
|
381
|
-
await
|
|
408
|
+
const filesDirExists = sourceFolder === userSourceFolder
|
|
409
|
+
? await sp.checkIfExists(filesDir)
|
|
410
|
+
: await checkIfExists(filesDir);
|
|
411
|
+
if (filesDirExists) {
|
|
412
|
+
await sp.copyFolderRecursive(filesDir, path.join(targetFolder, 'files'));
|
|
382
413
|
}
|
|
383
414
|
}
|
|
384
415
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { anthropic, completePrompt, fireworksai, logCompletePrompt, openai } from '../models';
|
|
2
2
|
import { getModelEntry, loadSettings } from '../settings';
|
|
3
3
|
import { PROVIDERS } from '../models';
|
|
4
|
+
import { SynthOSConfig } from '../init';
|
|
4
5
|
|
|
5
|
-
export async function createCompletePrompt(
|
|
6
|
+
export async function createCompletePrompt(config: SynthOSConfig, use: 'builder' | 'chat', modelOverride?: string): Promise<completePrompt> {
|
|
6
7
|
// Get configuration settings
|
|
7
|
-
const settings = await loadSettings(
|
|
8
|
+
const settings = await loadSettings(config);
|
|
8
9
|
const entry = getModelEntry(settings, use);
|
|
9
10
|
|
|
10
11
|
if (!entry.configuration.apiKey) {
|
|
@@ -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);
|