synthos 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/README.md +33 -1
  2. package/default-pages/app_builder.html +40 -0
  3. package/default-pages/app_builder.json +1 -0
  4. package/default-pages/json_tools.html +89 -159
  5. package/default-pages/json_tools.json +1 -0
  6. package/default-pages/my_notes.html +33 -0
  7. package/default-pages/my_notes.json +12 -0
  8. package/default-pages/neon_asteroids.html +77 -0
  9. package/default-pages/neon_asteroids.json +12 -0
  10. package/default-pages/sidebar_builder.html +49 -0
  11. package/default-pages/sidebar_builder.json +1 -0
  12. package/default-pages/solar_explorer.html +1956 -0
  13. package/default-pages/solar_explorer.json +12 -0
  14. package/default-pages/solar_tutorial.html +476 -0
  15. package/default-pages/solar_tutorial.json +1 -0
  16. package/default-pages/two-panel_builder.html +66 -0
  17. package/default-pages/two-panel_builder.json +1 -0
  18. package/dist/connectors/index.d.ts +3 -0
  19. package/dist/connectors/index.d.ts.map +1 -0
  20. package/dist/connectors/index.js +6 -0
  21. package/dist/connectors/index.js.map +1 -0
  22. package/dist/connectors/registry.d.ts +3 -0
  23. package/dist/connectors/registry.d.ts.map +1 -0
  24. package/dist/connectors/registry.js +100 -0
  25. package/dist/connectors/registry.js.map +1 -0
  26. package/dist/connectors/types.d.ts +61 -0
  27. package/dist/connectors/types.d.ts.map +1 -0
  28. package/dist/connectors/types.js +3 -0
  29. package/dist/connectors/types.js.map +1 -0
  30. package/dist/files.d.ts +2 -0
  31. package/dist/files.d.ts.map +1 -1
  32. package/dist/files.js +12 -1
  33. package/dist/files.js.map +1 -1
  34. package/dist/init.d.ts +8 -1
  35. package/dist/init.d.ts.map +1 -1
  36. package/dist/init.js +155 -3
  37. package/dist/init.js.map +1 -1
  38. package/dist/migrations.d.ts +11 -0
  39. package/dist/migrations.d.ts.map +1 -0
  40. package/dist/migrations.js +281 -0
  41. package/dist/migrations.js.map +1 -0
  42. package/dist/models/index.d.ts +3 -0
  43. package/dist/models/index.d.ts.map +1 -0
  44. package/dist/models/index.js +10 -0
  45. package/dist/models/index.js.map +1 -0
  46. package/dist/models/providers.d.ts +7 -0
  47. package/dist/models/providers.d.ts.map +1 -0
  48. package/dist/models/providers.js +33 -0
  49. package/dist/models/providers.js.map +1 -0
  50. package/dist/models/types.d.ts +21 -0
  51. package/dist/models/types.d.ts.map +1 -0
  52. package/dist/models/types.js +3 -0
  53. package/dist/models/types.js.map +1 -0
  54. package/dist/pages.d.ts +21 -2
  55. package/dist/pages.d.ts.map +1 -1
  56. package/dist/pages.js +202 -23
  57. package/dist/pages.js.map +1 -1
  58. package/dist/scripts.js +2 -2
  59. package/dist/scripts.js.map +1 -1
  60. package/dist/service/createCompletePrompt.d.ts +3 -2
  61. package/dist/service/createCompletePrompt.d.ts.map +1 -1
  62. package/dist/service/createCompletePrompt.js +11 -16
  63. package/dist/service/createCompletePrompt.js.map +1 -1
  64. package/dist/service/debugLog.d.ts +11 -0
  65. package/dist/service/debugLog.d.ts.map +1 -0
  66. package/dist/service/debugLog.js +26 -0
  67. package/dist/service/debugLog.js.map +1 -0
  68. package/dist/service/modelInstructions.d.ts +7 -0
  69. package/dist/service/modelInstructions.d.ts.map +1 -0
  70. package/dist/service/modelInstructions.js +16 -0
  71. package/dist/service/modelInstructions.js.map +1 -0
  72. package/dist/service/requiresSettings.d.ts +2 -2
  73. package/dist/service/requiresSettings.d.ts.map +1 -1
  74. package/dist/service/requiresSettings.js.map +1 -1
  75. package/dist/service/server.d.ts.map +1 -1
  76. package/dist/service/server.js +15 -0
  77. package/dist/service/server.js.map +1 -1
  78. package/dist/service/transformPage.d.ts +81 -2
  79. package/dist/service/transformPage.d.ts.map +1 -1
  80. package/dist/service/transformPage.js +672 -82
  81. package/dist/service/transformPage.js.map +1 -1
  82. package/dist/service/useApiRoutes.d.ts.map +1 -1
  83. package/dist/service/useApiRoutes.js +579 -13
  84. package/dist/service/useApiRoutes.js.map +1 -1
  85. package/dist/service/useConnectorRoutes.d.ts +4 -0
  86. package/dist/service/useConnectorRoutes.d.ts.map +1 -0
  87. package/dist/service/useConnectorRoutes.js +389 -0
  88. package/dist/service/useConnectorRoutes.js.map +1 -0
  89. package/dist/service/useDataRoutes.d.ts.map +1 -1
  90. package/dist/service/useDataRoutes.js +83 -70
  91. package/dist/service/useDataRoutes.js.map +1 -1
  92. package/dist/service/usePageRoutes.d.ts.map +1 -1
  93. package/dist/service/usePageRoutes.js +243 -38
  94. package/dist/service/usePageRoutes.js.map +1 -1
  95. package/dist/settings.d.ts +33 -4
  96. package/dist/settings.d.ts.map +1 -1
  97. package/dist/settings.js +108 -15
  98. package/dist/settings.js.map +1 -1
  99. package/dist/synthos-cli.d.ts.map +1 -1
  100. package/dist/synthos-cli.js +11 -1
  101. package/dist/synthos-cli.js.map +1 -1
  102. package/dist/themes.d.ts +9 -0
  103. package/dist/themes.d.ts.map +1 -0
  104. package/dist/themes.js +64 -0
  105. package/dist/themes.js.map +1 -0
  106. package/package.json +5 -3
  107. package/required-pages/builder.html +74 -0
  108. package/required-pages/builder.json +1 -0
  109. package/required-pages/pages.html +169 -126
  110. package/required-pages/pages.json +1 -0
  111. package/required-pages/settings.html +812 -156
  112. package/required-pages/settings.json +1 -0
  113. package/required-pages/synthos_apis.html +272 -0
  114. package/required-pages/synthos_apis.json +1 -0
  115. package/required-pages/synthos_scripts.html +87 -0
  116. package/required-pages/synthos_scripts.json +1 -0
  117. package/src/connectors/index.ts +12 -0
  118. package/src/connectors/registry.ts +98 -0
  119. package/src/connectors/types.ts +68 -0
  120. package/src/files.ts +11 -0
  121. package/src/init.ts +151 -5
  122. package/src/migrations.ts +266 -0
  123. package/src/models/index.ts +2 -0
  124. package/src/models/providers.ts +33 -0
  125. package/src/models/types.ts +23 -0
  126. package/src/pages.ts +234 -26
  127. package/src/scripts.ts +2 -2
  128. package/src/service/createCompletePrompt.ts +14 -18
  129. package/src/service/debugLog.ts +17 -0
  130. package/src/service/modelInstructions.ts +14 -0
  131. package/src/service/requiresSettings.ts +3 -3
  132. package/src/service/server.ts +19 -2
  133. package/src/service/transformPage.ts +709 -88
  134. package/src/service/useApiRoutes.ts +632 -16
  135. package/src/service/useConnectorRoutes.ts +427 -0
  136. package/src/service/useDataRoutes.ts +87 -71
  137. package/src/service/usePageRoutes.ts +237 -44
  138. package/src/settings.ts +143 -20
  139. package/src/synthos-cli.ts +11 -1
  140. package/src/themes.ts +71 -0
  141. package/default-pages/[application].html +0 -95
  142. package/default-pages/[markdown].html +0 -271
  143. package/default-pages/[sidebar].html +0 -114
  144. package/default-pages/[split-application].html +0 -118
  145. package/default-pages/solar_system.html +0 -432
  146. package/default-pages/space_invaders.html +0 -617
  147. package/required-pages/apis.html +0 -362
  148. package/required-pages/home.html +0 -126
  149. package/required-pages/scripts.html +0 -350
package/src/pages.ts CHANGED
@@ -1,41 +1,174 @@
1
- import {checkIfExists, listFiles, loadFile, saveFile} from './files';
1
+ import {checkIfExists, deleteFile, deleteFolder, ensureFolderExists, listFiles, listFolders, loadFile, saveFile} from './files';
2
2
  import path from 'path';
3
3
 
4
4
  // Page State Cache
5
5
  const _pages: { [name: string]: string } = {};
6
6
 
7
- export async function listPages(pagesFolder: string, fallbackPagesFolder: string): Promise<string[]> {
8
- // Load all pages from primary pages folder
9
- const all = (await listFiles(pagesFolder)).filter(file => file.endsWith('.html'));
7
+ export const REQUIRED_PAGES = ['builder', 'pages', 'settings', 'apis', 'scripts'];
10
8
 
11
- // Add pages from fallback pages folder that don't already exist
12
- (await listFiles(fallbackPagesFolder)).forEach(file => {
13
- if (file.endsWith('.html') && !all.includes(file)) {
14
- all.push(file);
9
+ export const PAGE_VERSION = 2;
10
+
11
+ export interface PageInfo {
12
+ name: string;
13
+ title: string;
14
+ categories: string[];
15
+ pinned: boolean;
16
+ showInAll: boolean; // true = visible in "All" filter, false = only in category filters
17
+ createdDate: string; // ISO 8601, empty string if unknown
18
+ lastModified: string; // ISO 8601, empty string if unknown
19
+ pageVersion: number; // integer, 0 = pre-versioning
20
+ mode: 'unlocked' | 'locked';
21
+ }
22
+
23
+ export type PageMetadata = Omit<PageInfo, 'name'>;
24
+
25
+ export async function loadPageMetadata(pagesFolder: string, name: string, fallbackFolder?: string): Promise<PageMetadata | undefined> {
26
+ // 1. Try user override: .synthos/pages/<name>/page.json
27
+ const metadataPath = path.join(pagesFolder, 'pages', name, 'page.json');
28
+ if (await checkIfExists(metadataPath)) {
29
+ try {
30
+ const raw = await loadFile(metadataPath);
31
+ const parsed = JSON.parse(raw);
32
+ return parseMetadata(parsed);
33
+ } catch {
34
+ // fall through
35
+ }
36
+ }
37
+
38
+ // 2. Try fallback: fallbackFolder/<name>.json
39
+ if (fallbackFolder) {
40
+ const fallbackPath = path.join(fallbackFolder, `${name}.json`);
41
+ if (await checkIfExists(fallbackPath)) {
42
+ try {
43
+ const raw = await loadFile(fallbackPath);
44
+ const parsed = JSON.parse(raw);
45
+ return parseMetadata(parsed);
46
+ } catch {
47
+ // fall through
48
+ }
49
+ }
50
+ }
51
+
52
+ return undefined;
53
+ }
54
+
55
+ export function parseMetadata(parsed: Record<string, unknown>): PageMetadata {
56
+ return {
57
+ title: typeof parsed.title === 'string' ? parsed.title : '',
58
+ categories: Array.isArray(parsed.categories) ? parsed.categories : [],
59
+ pinned: typeof parsed.pinned === 'boolean' ? parsed.pinned : false,
60
+ showInAll: typeof parsed.showInAll === 'boolean' ? parsed.showInAll : true,
61
+ createdDate: typeof parsed.createdDate === 'string' ? parsed.createdDate : '',
62
+ lastModified: typeof parsed.lastModified === 'string' ? parsed.lastModified : '',
63
+ pageVersion: typeof parsed.pageVersion === 'number' ? parsed.pageVersion
64
+ : typeof parsed.uxVersion === 'number' ? parsed.uxVersion : 0,
65
+ mode: parsed.mode === 'locked' ? 'locked' : 'unlocked',
66
+ };
67
+ }
68
+
69
+ export async function savePageMetadata(pagesFolder: string, name: string, metadata: PageMetadata): Promise<void> {
70
+ const pageFolder = path.join(pagesFolder, 'pages', name);
71
+ await ensureFolderExists(pageFolder);
72
+ await saveFile(path.join(pageFolder, 'page.json'), JSON.stringify(metadata, null, 4));
73
+ }
74
+
75
+ const DEFAULT_METADATA: PageMetadata = {
76
+ title: '',
77
+ categories: [],
78
+ pinned: false,
79
+ showInAll: true,
80
+ createdDate: '',
81
+ lastModified: '',
82
+ pageVersion: 0,
83
+ mode: 'unlocked',
84
+ };
85
+
86
+ export async function listPages(pagesFolder: string, fallbackPagesFolder: string): Promise<PageInfo[]> {
87
+ const pageMap = new Map<string, PageInfo>();
88
+
89
+ // Folder-based pages under pages/ subdirectory
90
+ const pagesSubdir = path.join(pagesFolder, 'pages');
91
+ if (await checkIfExists(pagesSubdir)) {
92
+ const folders = await listFolders(pagesSubdir);
93
+ for (const folder of folders) {
94
+ if (await checkIfExists(path.join(pagesSubdir, folder, 'page.html'))) {
95
+ const metadata = await loadPageMetadata(pagesFolder, folder);
96
+ pageMap.set(folder, {
97
+ name: folder,
98
+ ...(metadata ?? DEFAULT_METADATA),
99
+ });
100
+ }
101
+ }
102
+ }
103
+
104
+ // Legacy flat .html files in root (v1 pages)
105
+ const flatFiles = (await listFiles(pagesFolder)).filter(file => file.endsWith('.html'));
106
+ for (const file of flatFiles) {
107
+ const name = file.replace(/\.html$/, '');
108
+ if (!pageMap.has(name)) {
109
+ // Derive title: strip brackets, replace underscores with spaces, title-case
110
+ const stripped = name.replace(/^\[/, '').replace(/\]$/, '');
111
+ const title = stripped
112
+ .replace(/_/g, ' ')
113
+ .replace(/\b\w/g, c => c.toUpperCase());
114
+ // Assign category: bracketed names → "Builder", otherwise → "Page"
115
+ const hasBrackets = name.startsWith('[') && name.endsWith(']');
116
+ const categories = hasBrackets ? ['Builder'] : ['Page'];
117
+ pageMap.set(name, {
118
+ name,
119
+ title,
120
+ categories,
121
+ pinned: false,
122
+ showInAll: true,
123
+ createdDate: '',
124
+ lastModified: '',
125
+ pageVersion: 1,
126
+ mode: 'unlocked',
127
+ });
128
+ }
129
+ }
130
+
131
+ // Add pages from fallback (required) pages folder
132
+ const fallbackFiles = (await listFiles(fallbackPagesFolder)).filter(file => file.endsWith('.html'));
133
+ for (const file of fallbackFiles) {
134
+ const name = file.replace(/\.html$/, '');
135
+ if (!pageMap.has(name)) {
136
+ // System page not yet in map — check for user override, then fallback .json
137
+ const metadata = await loadPageMetadata(pagesFolder, name, fallbackPagesFolder);
138
+ pageMap.set(name, {
139
+ name,
140
+ title: metadata?.title ?? '',
141
+ categories: metadata?.categories ?? ['System'],
142
+ pinned: metadata?.pinned ?? true,
143
+ showInAll: metadata?.showInAll ?? true,
144
+ createdDate: metadata?.createdDate ?? '',
145
+ lastModified: metadata?.lastModified ?? '',
146
+ pageVersion: metadata?.pageVersion ?? 0,
147
+ mode: metadata?.mode ?? 'unlocked',
148
+ });
15
149
  }
16
- });
150
+ }
17
151
 
18
- // Remove .html and sort all files
19
- const sorted = all.map(file => file.substring(0, file.length - 5)).sort();
20
-
21
- // Move [templates] to end of array
22
- const pages = sorted.filter(page => !page.startsWith('['));
23
- const templates = sorted.filter(page => page.startsWith('['));
24
- pages.push(...templates);
152
+ // Sort alphabetically
153
+ const entries = Array.from(pageMap.values());
154
+ entries.sort((a, b) => a.name.localeCompare(b.name));
25
155
 
26
- return pages;
156
+ return entries;
27
157
  }
28
158
 
29
159
  export async function loadPageState(pagesFolder: string, name: string, reset: boolean): Promise<string|undefined> {
30
160
  if (!_pages[name] || reset) {
31
- // Check for file to exist
32
- const filename = path.join(pagesFolder, `${name}.html`);
33
- if (!(await checkIfExists(filename))) {
161
+ // Try folder-based path under pages/ first, then fall back to flat file
162
+ const folderPath = path.join(pagesFolder, 'pages', name, 'page.html');
163
+ const flatPath = path.join(pagesFolder, `${name}.html`);
164
+
165
+ if (await checkIfExists(folderPath)) {
166
+ _pages[name] = await loadFile(folderPath);
167
+ } else if (await checkIfExists(flatPath)) {
168
+ _pages[name] = await loadFile(flatPath);
169
+ } else {
34
170
  return undefined;
35
171
  }
36
-
37
- // Load file
38
- _pages[name] = await loadFile(filename);
39
172
  }
40
173
 
41
174
  return _pages[name];
@@ -45,11 +178,86 @@ export function normalizePageName(name: string|undefined): string|undefined {
45
178
  return typeof name == 'string' && name.length > 0 ? name.replace(/[^a-z0-9\-_\[\]\(\)\{\}@#\$%&]/gi, '_').toLowerCase() : undefined;
46
179
  }
47
180
 
48
- export async function savePageState(pagesFolder: string, name: string, content: string): Promise<void> {
181
+ export async function savePageState(pagesFolder: string, name: string, content: string, title?: string, categories?: string[]): Promise<void> {
49
182
  _pages[name] = content;
50
- await saveFile(path.join(pagesFolder, `${name}.html`), content);
183
+ const pageFolder = path.join(pagesFolder, 'pages', name);
184
+ await ensureFolderExists(pageFolder);
185
+ await saveFile(path.join(pageFolder, 'page.html'), content);
186
+
187
+ // Create page.json with full metadata if it doesn't exist
188
+ const metadataPath = path.join(pageFolder, 'page.json');
189
+ if (!(await checkIfExists(metadataPath))) {
190
+ const now = new Date().toISOString();
191
+ const metadata: PageMetadata = {
192
+ title: title ?? '',
193
+ categories: categories ?? [],
194
+ pinned: false,
195
+ showInAll: true,
196
+ createdDate: now,
197
+ lastModified: now,
198
+ pageVersion: PAGE_VERSION,
199
+ mode: 'unlocked',
200
+ };
201
+ await saveFile(metadataPath, JSON.stringify(metadata, null, 4));
202
+ }
51
203
  }
52
204
 
53
205
  export function updatePageState(name: string, content: string): void {
54
206
  _pages[name] = content;
55
- }
207
+ }
208
+
209
+ export async function deletePage(pagesFolder: string, name: string): Promise<void> {
210
+ // Delete folder-based page: <pagesFolder>/pages/<name>/
211
+ const folderPath = path.join(pagesFolder, 'pages', name);
212
+ if (await checkIfExists(folderPath)) {
213
+ await deleteFolder(folderPath);
214
+ }
215
+
216
+ // Delete legacy flat file: <pagesFolder>/<name>.html
217
+ const flatPath = path.join(pagesFolder, `${name}.html`);
218
+ if (await checkIfExists(flatPath)) {
219
+ await deleteFile(flatPath);
220
+ }
221
+
222
+ // Clear in-memory cache
223
+ delete _pages[name];
224
+ }
225
+
226
+ export async function copyPage(
227
+ pagesFolder: string,
228
+ sourceName: string,
229
+ targetName: string,
230
+ title: string,
231
+ categories: string[],
232
+ requiredPagesFolder: string
233
+ ): Promise<void> {
234
+ // Load source HTML from user folder, then try required folder as fallback
235
+ let html = await loadPageState(pagesFolder, sourceName, true);
236
+ if (!html) {
237
+ const requiredPath = path.join(requiredPagesFolder, `${sourceName}.html`);
238
+ if (await checkIfExists(requiredPath)) {
239
+ html = await loadFile(requiredPath);
240
+ }
241
+ }
242
+
243
+ if (!html) {
244
+ throw new Error(`Source page "${sourceName}" not found`);
245
+ }
246
+
247
+ // Save HTML to target (creates folder + page.html + page.json)
248
+ await savePageState(pagesFolder, targetName, html, title);
249
+
250
+ // Overwrite the generated metadata with provided title + categories
251
+ const now = new Date().toISOString();
252
+ const metadata: PageMetadata = {
253
+ title,
254
+ categories,
255
+ pinned: false,
256
+ showInAll: true,
257
+ createdDate: now,
258
+ lastModified: now,
259
+ pageVersion: PAGE_VERSION,
260
+ mode: 'unlocked',
261
+ };
262
+ await savePageMetadata(pagesFolder, targetName, metadata);
263
+ }
package/src/scripts.ts CHANGED
@@ -17,7 +17,7 @@ export interface ExecuteScriptResponse {
17
17
  export async function listScripts(pagesFolder: string): Promise<string> {
18
18
  if (!scriptsList) {
19
19
  let list: string[] = [];
20
- const folder = path.join(pagesFolder, 'scripts');
20
+ const folder = path.join(pagesFolder, 'pages', 'scripts', 'scripts');
21
21
  if (await checkIfExists(folder)) {
22
22
  const files = (await listFiles(folder)).filter(f => f.endsWith('.json'));
23
23
  for (const file of files) {
@@ -52,7 +52,7 @@ export async function executeScript(args: ExecuteScriptArgs): Promise<AgentCompl
52
52
  const { pagesFolder, scriptId, variables } = args;
53
53
 
54
54
  // Load the script
55
- const script = await loadFile(path.join(pagesFolder, `scripts`, `${scriptId}.json`));
55
+ const script = await loadFile(path.join(pagesFolder, 'pages', 'scripts', 'scripts', `${scriptId}.json`));
56
56
  if (!script) {
57
57
  return { completed: false, error: new Error(`Script not found: ${scriptId}`) };
58
58
  }
@@ -1,41 +1,37 @@
1
1
  import {anthropic, completePrompt, logCompletePrompt, openai} from 'agentm-core';
2
- import { loadSettings } from '../settings';
2
+ import { getModelEntry, loadSettings } from '../settings';
3
+ import { PROVIDERS } from '../models';
3
4
 
4
- export const availableModels = [
5
- 'claude-opus-4-5',
6
- 'Claude Sonnet 4.5',
7
- 'Claude Haiku 4.5',
8
- 'GPT-5.2',
9
- 'GPT-5 mini',
10
- 'GPT-5 nano'
11
- ];
12
-
13
- export async function createCompletePrompt(pagesFolder: string, model?: string): Promise<completePrompt> {
5
+ export async function createCompletePrompt(pagesFolder: string, use: 'builder' | 'chat', modelOverride?: string): Promise<completePrompt> {
14
6
  // Get configuration settings
15
7
  const settings = await loadSettings(pagesFolder);
16
- if (!settings.serviceApiKey) {
17
- throw new Error('OpenAI API key not configured');
8
+ const entry = getModelEntry(settings, use);
9
+
10
+ if (!entry.configuration.apiKey) {
11
+ throw new Error('API key not configured');
18
12
  }
19
13
 
20
14
  // Validate model
21
- model = model || settings.model;
15
+ const model = modelOverride || entry.configuration.model;
22
16
  if (!model) {
23
17
  throw new Error('Model not configured');
24
18
  }
25
19
 
26
20
  // Create completion functions
27
21
  let modelInstance: completePrompt;
28
- const apiKey = settings.serviceApiKey;
29
- if (model.startsWith('claude-')) {
22
+ const apiKey = entry.configuration.apiKey;
23
+ if (entry.provider === 'Anthropic') {
30
24
  modelInstance = anthropic({apiKey, model});
31
25
  } else {
32
26
  modelInstance = openai({apiKey, model});
33
27
  }
34
28
 
35
29
  // Return new model instance
36
- if (settings.logCompletions) {
30
+ if (entry.logCompletions) {
37
31
  return logCompletePrompt(modelInstance, true);
38
32
  } else {
39
33
  return modelInstance;
40
34
  }
41
- }
35
+ }
36
+
37
+ export { PROVIDERS };
@@ -0,0 +1,17 @@
1
+ // ANSI color helpers — no external dependency
2
+ export const reset = '\x1b[0m';
3
+ export const cyan = (s: string): string => `\x1b[36m${s}${reset}`;
4
+ export const yellow = (s: string): string => `\x1b[33m${s}${reset}`;
5
+ export const green = (s: string): string => `\x1b[32m${s}${reset}`;
6
+ export const red = (s: string): string => `\x1b[31m${s}${reset}`;
7
+ export const dim = (s: string): string => `\x1b[2m${s}${reset}`;
8
+
9
+ /** Format milliseconds as `X.XXXs` */
10
+ export function formatTime(ms: number): string {
11
+ return `${(ms / 1000).toFixed(3)}s`;
12
+ }
13
+
14
+ /** Rough token estimate based on chars / 4 heuristic */
15
+ export function estimateTokens(chars: number): number {
16
+ return Math.ceil(chars / 4);
17
+ }
@@ -0,0 +1,14 @@
1
+ import { ProviderName } from '../models';
2
+
3
+ /**
4
+ * Returns provider-specific prompt instructions for how the model should
5
+ * format its change-list response.
6
+ */
7
+ export function getModelInstructions(provider: ProviderName): string {
8
+ if (provider === 'Anthropic') {
9
+ return `Return ONLY the JSON array of change operations. Do not wrap it in markdown code fences or add any other text.`;
10
+ }
11
+
12
+ // OpenAI and other providers
13
+ return `Return ONLY the JSON array of change operations. Do not wrap it in markdown code fences or add any other text.`;
14
+ }
@@ -1,7 +1,7 @@
1
- import { hasConfiguredSettings, loadSettings, Settings } from "../settings";
1
+ import { hasConfiguredSettings, loadSettings, SettingsV2 } from "../settings";
2
2
 
3
3
 
4
- export async function requiresSettings(res: any, folder: string, cb: (settings: Settings) => Promise<void>) {
4
+ export async function requiresSettings(res: any, folder: string, cb: (settings: SettingsV2) => Promise<void>) {
5
5
  try {
6
6
  // Ensure settings configured
7
7
  const isConfigured = await hasConfiguredSettings(folder);
@@ -19,4 +19,4 @@ export async function requiresSettings(res: any, folder: string, cb: (settings:
19
19
  console.error(err);
20
20
  res.status(500).send((err as Error).message);
21
21
  }
22
- }
22
+ }
@@ -3,10 +3,24 @@ import { usePageRoutes } from './usePageRoutes';
3
3
  import { useApiRoutes } from './useApiRoutes';
4
4
  import { SynthOSConfig } from '../init';
5
5
  import { useDataRoutes } from './useDataRoutes';
6
+ import { useConnectorRoutes } from './useConnectorRoutes';
7
+ import { cyan, yellow, formatTime } from './debugLog';
6
8
 
7
9
  export function server(config: SynthOSConfig): Application {
8
10
  const app = express();
9
-
11
+
12
+ // Debug request-logging middleware
13
+ if (config.debug) {
14
+ app.use((req, res, next) => {
15
+ const start = Date.now();
16
+ res.on('finish', () => {
17
+ const ms = Date.now() - start;
18
+ console.log(`${cyan(req.method + ' ' + req.originalUrl)} → ${res.statusCode} ${yellow('(' + formatTime(ms) + ')')}`);
19
+ });
20
+ next();
21
+ });
22
+ }
23
+
10
24
  // Middleware to parse URL-encoded data (form data)
11
25
  app.use(express.urlencoded({ extended: true }));
12
26
 
@@ -19,8 +33,11 @@ export function server(config: SynthOSConfig): Application {
19
33
  // API routes
20
34
  useApiRoutes(config, app);
21
35
 
36
+ // Connector routes
37
+ useConnectorRoutes(config, app);
38
+
22
39
  // Data routes
23
40
  useDataRoutes(config, app);
24
-
41
+
25
42
  return app;
26
43
  }