synthos 0.1.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 (102) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +71 -0
  3. package/bin/synthos.js +3 -0
  4. package/default-pages/[application].html +87 -0
  5. package/default-pages/[markdown].html +261 -0
  6. package/default-pages/[sidebar].html +89 -0
  7. package/default-pages/[split-application].html +133 -0
  8. package/default-pages/json_tools.html +176 -0
  9. package/default-scripts/android.terminal.json +7 -0
  10. package/default-scripts/linux-terminal.json +7 -0
  11. package/default-scripts/mac-terminal.json +7 -0
  12. package/default-scripts/windows-terminal.json +7 -0
  13. package/dist/files.d.ts +9 -0
  14. package/dist/files.d.ts.map +1 -0
  15. package/dist/files.js +79 -0
  16. package/dist/files.js.map +1 -0
  17. package/dist/index.d.ts +7 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +23 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/init.d.ts +9 -0
  22. package/dist/init.d.ts.map +1 -0
  23. package/dist/init.js +59 -0
  24. package/dist/init.js.map +1 -0
  25. package/dist/pages.d.ts +6 -0
  26. package/dist/pages.d.ts.map +1 -0
  27. package/dist/pages.js +55 -0
  28. package/dist/pages.js.map +1 -0
  29. package/dist/scripts.d.ts +14 -0
  30. package/dist/scripts.d.ts.map +1 -0
  31. package/dist/scripts.js +103 -0
  32. package/dist/scripts.js.map +1 -0
  33. package/dist/service/createCompletePrompt.d.ts +4 -0
  34. package/dist/service/createCompletePrompt.d.ts.map +1 -0
  35. package/dist/service/createCompletePrompt.js +42 -0
  36. package/dist/service/createCompletePrompt.js.map +1 -0
  37. package/dist/service/generateImage.d.ts +32 -0
  38. package/dist/service/generateImage.d.ts.map +1 -0
  39. package/dist/service/generateImage.js +71 -0
  40. package/dist/service/generateImage.js.map +1 -0
  41. package/dist/service/index.d.ts +8 -0
  42. package/dist/service/index.d.ts.map +1 -0
  43. package/dist/service/index.js +24 -0
  44. package/dist/service/index.js.map +1 -0
  45. package/dist/service/requiresSettings.d.ts +3 -0
  46. package/dist/service/requiresSettings.d.ts.map +1 -0
  47. package/dist/service/requiresSettings.js +24 -0
  48. package/dist/service/requiresSettings.js.map +1 -0
  49. package/dist/service/server.d.ts +4 -0
  50. package/dist/service/server.d.ts.map +1 -0
  51. package/dist/service/server.js +26 -0
  52. package/dist/service/server.js.map +1 -0
  53. package/dist/service/transformPage.d.ts +11 -0
  54. package/dist/service/transformPage.d.ts.map +1 -0
  55. package/dist/service/transformPage.js +119 -0
  56. package/dist/service/transformPage.js.map +1 -0
  57. package/dist/service/useApiRoutes.d.ts +4 -0
  58. package/dist/service/useApiRoutes.d.ts.map +1 -0
  59. package/dist/service/useApiRoutes.js +95 -0
  60. package/dist/service/useApiRoutes.js.map +1 -0
  61. package/dist/service/useDataRoutes.d.ts +4 -0
  62. package/dist/service/useDataRoutes.d.ts.map +1 -0
  63. package/dist/service/useDataRoutes.js +98 -0
  64. package/dist/service/useDataRoutes.js.map +1 -0
  65. package/dist/service/usePageRoutes.d.ts +5 -0
  66. package/dist/service/usePageRoutes.d.ts.map +1 -0
  67. package/dist/service/usePageRoutes.js +132 -0
  68. package/dist/service/usePageRoutes.js.map +1 -0
  69. package/dist/settings.d.ts +13 -0
  70. package/dist/settings.d.ts.map +1 -0
  71. package/dist/settings.js +55 -0
  72. package/dist/settings.js.map +1 -0
  73. package/dist/synthos-cli.d.ts +2 -0
  74. package/dist/synthos-cli.d.ts.map +1 -0
  75. package/dist/synthos-cli.js +43 -0
  76. package/dist/synthos-cli.js.map +1 -0
  77. package/images/home.png +0 -0
  78. package/images/page-management.png +0 -0
  79. package/images/settings.png +0 -0
  80. package/images/synthos-square.png +0 -0
  81. package/package.json +58 -0
  82. package/required-pages/apis.html +347 -0
  83. package/required-pages/home.html +83 -0
  84. package/required-pages/pages.html +135 -0
  85. package/required-pages/scripts.html +335 -0
  86. package/required-pages/settings.html +158 -0
  87. package/src/files.ts +49 -0
  88. package/src/index.ts +6 -0
  89. package/src/init.ts +66 -0
  90. package/src/pages.ts +55 -0
  91. package/src/scripts.ts +130 -0
  92. package/src/service/createCompletePrompt.ts +40 -0
  93. package/src/service/generateImage.ts +101 -0
  94. package/src/service/index.ts +7 -0
  95. package/src/service/requiresSettings.ts +22 -0
  96. package/src/service/server.ts +26 -0
  97. package/src/service/transformPage.ts +135 -0
  98. package/src/service/useApiRoutes.ts +95 -0
  99. package/src/service/useDataRoutes.ts +97 -0
  100. package/src/service/usePageRoutes.ts +147 -0
  101. package/src/settings.ts +60 -0
  102. package/src/synthos-cli.ts +38 -0
@@ -0,0 +1,97 @@
1
+ import { Application } from 'express';
2
+ import { SynthOSConfig } from "../init";
3
+ import { checkIfExists, deleteFile, ensureFolderExists, listFiles, loadFile, saveFile } from "../files";
4
+ import path from "path";
5
+ import { v4 } from "uuid";
6
+ import { clearCachedScripts } from '../scripts';
7
+
8
+ export function useDataRoutes(config: SynthOSConfig, app: Application): void {
9
+ // Retrieve all rows from a table
10
+ app.get('/api/data/:table', async (req, res) => {
11
+ // Get list of row ids
12
+ const { table } = req.params;
13
+ const folder = await tableFolder(config, table);
14
+ const ids = (await listFiles(folder)).filter(f => f.endsWith('.json')).map(f => f.replace('.json', ''));
15
+
16
+ // Enumerate all rows
17
+ const rows: Record<string, any>[] = [];
18
+ for (const id of ids) {
19
+ const file = recordFile(folder, id);
20
+ try {
21
+ const row = JSON.parse(await loadFile(file));
22
+ row.id = id;
23
+ rows.push(row);
24
+ } catch (err: unknown) {
25
+ console.error(err);
26
+ }
27
+ }
28
+
29
+ // Return rows
30
+ res.json(rows);
31
+ });
32
+
33
+ // Retrieve a single row from a table
34
+ app.get('/api/data/:table/:id', async (req, res) => {
35
+ const { table, id } = req.params;
36
+ const folder = await tableFolder(config, table);
37
+ const file = recordFile(folder, id);
38
+ try {
39
+ const row = JSON.parse(await loadFile(file));
40
+ row.id = id;
41
+ res.json(row);
42
+ } catch (err: unknown) {
43
+ res.json({});
44
+ }
45
+ });
46
+
47
+ // Upsert a single row into a table
48
+ app.post('/api/data/:table', async (req, res) => {
49
+ const { table } = req.params;
50
+ const id = req.body.id ?? v4();
51
+ const folder = await tableFolder(config, table);
52
+ const file = recordFile(folder, id);
53
+ try {
54
+ const row = { ...req.body, id };
55
+ await ensureFolderExists(folder);
56
+ await saveFile(file, JSON.stringify(row, null, 4));
57
+ if (table === 'scripts') {
58
+ clearCachedScripts();
59
+ }
60
+ res.json(row);
61
+ } catch (err: unknown) {
62
+ console.error(err);
63
+ res.status(500).send((err as Error).message);
64
+ }
65
+ });
66
+
67
+ // Delete a single row from a table
68
+ app.delete('/api/data/:table/:id', async (req, res) => {
69
+ const { table, id } = req.params;
70
+ const folder = await tableFolder(config, table);
71
+ const file = recordFile(folder, id);
72
+ try {
73
+ if (await checkIfExists(file)) {
74
+ await deleteFile(file);
75
+
76
+ // Clear cached scripts
77
+ if (table === 'scripts') {
78
+ clearCachedScripts();
79
+ }
80
+ }
81
+ res.json({ success: true });
82
+ } catch (err: unknown) {
83
+ console.error(err);
84
+ res.status(500).send((err as Error).message);
85
+ }
86
+ });
87
+ }
88
+
89
+ async function tableFolder(config: SynthOSConfig, table: string): Promise<string> {
90
+ const folder = path.join(config.pagesFolder, table);
91
+ await ensureFolderExists(folder);
92
+ return folder;
93
+ }
94
+
95
+ function recordFile(folder: string, id: string): string {
96
+ return path.join(folder, `${id}.json`);
97
+ }
@@ -0,0 +1,147 @@
1
+ import { loadPageState, normalizePageName, savePageState, updatePageState } from "../pages";
2
+ import { hasConfiguredSettings, loadSettings } from "../settings";
3
+ import { Application } from 'express';
4
+ import { transformPage, transformPageAsObject } from "./transformPage";
5
+ import { SynthOSConfig } from "../init";
6
+ import { createCompletePrompt } from "./createCompletePrompt";
7
+ import { completePrompt } from "agentm-core";
8
+
9
+ const HOME_PAGE_ROUTE = '/home';
10
+ const PAGE_NOT_FOUND = 'Page not found';
11
+
12
+ export function usePageRoutes(config: SynthOSConfig, app: Application): void {
13
+ // Redirect / to /home page
14
+ app.get('/', (req, res) => res.redirect(HOME_PAGE_ROUTE));
15
+
16
+ // Page retrieval
17
+ app.get('/:page', async (req, res) => {
18
+ // Redirect if settings not configured
19
+ const { page } = req.params;
20
+ const isConfigured = await hasConfiguredSettings(config.pagesFolder);
21
+ if (!isConfigured && page !== 'settings') {
22
+ res.redirect('/settings');
23
+ return;
24
+ }
25
+
26
+ // Ensure page exists
27
+ const pageState = await loadPageWithFallback(page, config, false);
28
+ if (!pageState) {
29
+ res.status(404).send(PAGE_NOT_FOUND);
30
+ return;
31
+ }
32
+
33
+ res.send(pageState);
34
+ });
35
+
36
+ // Page reset
37
+ app.get('/:page/reset', async (req, res) => {
38
+ // Redirect if settings not configured
39
+ const { page } = req.params;
40
+ const isConfigured = await hasConfiguredSettings(config.pagesFolder);
41
+ if (!isConfigured) {
42
+ res.redirect('/settings');
43
+ return;
44
+ }
45
+
46
+ // Ensure page exists
47
+ const pageState = await loadPageWithFallback(page, config, true);
48
+ if (!pageState) {
49
+ res.status(404).send(PAGE_NOT_FOUND);
50
+ return;
51
+ }
52
+
53
+ res.redirect(`/${page}`);
54
+ });
55
+
56
+ // Page save
57
+ app.get('/:page/save', async (req, res) => {
58
+ // Redirect if settings not configured
59
+ const { page } = req.params;
60
+ const isConfigured = await hasConfiguredSettings(config.pagesFolder);
61
+ if (!isConfigured) {
62
+ res.redirect('/settings');
63
+ return;
64
+ }
65
+
66
+ // Normalize and validate save-as parameter
67
+ const saveAs = normalizePageName(req.query['name'] as string);
68
+ if (!saveAs) {
69
+ res.status(400).send('Invalid or missing name parameter');
70
+ return;
71
+ }
72
+
73
+ // Load page state
74
+ const pageState = await loadPageWithFallback(page, config, false);
75
+ if (!pageState) {
76
+ res.status(404).send(PAGE_NOT_FOUND);
77
+ return;
78
+ }
79
+
80
+ // Save as new page and redirect to saved page
81
+ await savePageState(config.pagesFolder, saveAs, pageState);
82
+ res.redirect(`/${saveAs}`);
83
+ });
84
+
85
+ // Page transformation
86
+ app.post('/:page', async (req, res) => {
87
+ try {
88
+ // Ensure settings configured
89
+ const { page } = req.params;
90
+ const isConfigured = await hasConfiguredSettings(config.pagesFolder);
91
+ if (!isConfigured) {
92
+ res.status(400).send('Settings not configured');
93
+ return;
94
+ }
95
+
96
+ // Ensure page exists
97
+ const pageState = await loadPageWithFallback(page, config, false);
98
+ if (!pageState) {
99
+ res.status(404).send(PAGE_NOT_FOUND);
100
+ return;
101
+ }
102
+
103
+ // Get required and optional parameters
104
+ const { message } = req.body; // Extract the message from the request body
105
+ if (typeof message !== 'string') {
106
+ res.status(400).send('Invalid or missing message parameter');
107
+ return;
108
+ }
109
+
110
+ // Create model instance
111
+ const innerCompletePrompt = await createCompletePrompt(config.pagesFolder, req.body.model);
112
+ const completePrompt: completePrompt = (args) => {
113
+ // console.log(`SYSTEM:\n${args.system!.content}`);
114
+ // console.log(`PROMPT:\n${args.prompt!.content}`);
115
+ return innerCompletePrompt(args);
116
+ }
117
+
118
+
119
+ // Transform and cache updated page
120
+ const pagesFolder = config.pagesFolder;
121
+ const { maxTokens, instructions, model } = await loadSettings(config.pagesFolder);
122
+ const result = model.startsWith('gpt-') ?
123
+ await transformPageAsObject({ pagesFolder, pageState, message, maxTokens, instructions, completePrompt }) :
124
+ await transformPage({ pagesFolder, pageState, message, maxTokens, instructions, completePrompt });
125
+ if (result.completed) {
126
+ updatePageState(page, result.value!);
127
+ res.send(result.value!);
128
+ } else {
129
+ throw result.error;
130
+ }
131
+ } catch (err: unknown) {
132
+ console.error(err);
133
+ res.status(500).send((err as Error).message);
134
+ }
135
+ });
136
+ }
137
+
138
+ export async function loadPageWithFallback(page: string, config: SynthOSConfig, reset: boolean): Promise<string|undefined> {
139
+ // Try primary pages folder first
140
+ const pageState = await loadPageState(config.pagesFolder, page, reset);
141
+ if (pageState) {
142
+ return pageState;
143
+ }
144
+
145
+ // Try fallback pages folder second
146
+ return loadPageState(config.requiredPagesFolder, page, reset);
147
+ }
@@ -0,0 +1,60 @@
1
+ import {checkIfExists, loadFile, saveFile} from './files';
2
+ import path from 'path';
3
+
4
+ let _settings: Partial<Settings>|undefined;
5
+
6
+ export interface Settings {
7
+ serviceApiKey: string;
8
+ model: string;
9
+ maxTokens: number;
10
+ imageQuality: 'standard' | 'hd';
11
+ instructions?: string;
12
+ logCompletions?: boolean;
13
+ }
14
+
15
+ export const DefaultSettings: Settings = {
16
+ serviceApiKey: '',
17
+ model: '',
18
+ maxTokens: 8000,
19
+ imageQuality: 'standard',
20
+ instructions: '',
21
+ logCompletions: false
22
+ };
23
+
24
+ export async function hasConfiguredSettings(folder: string): Promise<boolean> {
25
+ const settings = await loadSettings(folder);
26
+ if (typeof settings.serviceApiKey !== 'string' || settings.serviceApiKey.length == 0) {
27
+ return false;
28
+ }
29
+ if (typeof settings.model !== 'string' || settings.model.length == 0) {
30
+ return false;
31
+ }
32
+ if (typeof settings.maxTokens !== 'number' || settings.maxTokens <= 0) {
33
+ return false;
34
+ }
35
+
36
+ return true;
37
+ }
38
+
39
+ export async function loadSettings(folder: string): Promise<Settings> {
40
+ if (_settings == undefined) {
41
+ // Check for file to exist
42
+ const filename = path.join(folder, 'settings.json');
43
+ if (await checkIfExists(filename)) {
44
+ try {
45
+ // Load and parse file
46
+ _settings = JSON.parse(await loadFile(filename));
47
+ } catch {
48
+ // Invalid JSON
49
+ }
50
+ }
51
+ }
52
+
53
+ // Return settings from file
54
+ return {...DefaultSettings, ..._settings};
55
+ }
56
+
57
+ export async function saveSettings(folder: string, settings: Partial<Settings>): Promise<void> {
58
+ _settings = {..._settings, ...settings};
59
+ await saveFile(path.join(folder, 'settings.json'), JSON.stringify(_settings, null, 4));
60
+ }
@@ -0,0 +1,38 @@
1
+ import yargs from "yargs";
2
+ import { hideBin } from "yargs/helpers";
3
+ import { server } from "./service";
4
+ import { createConfig, init } from "./init";
5
+
6
+ const dynamicImport = new Function('specifier', `return import(specifier)`);
7
+
8
+ export async function run() {
9
+ await yargs(hideBin(process.argv))
10
+ .scriptName('synthos')
11
+ .command('start', `Starts the SynthOS server.`, (yargs) => {
12
+ return yargs
13
+ .option('port', {
14
+ describe: `The port number to use.`,
15
+ type: 'number',
16
+ default: 4242
17
+ })
18
+ .option('pages', {
19
+ describe: `Include default pages when initializing a new .synthos folder.`,
20
+ type: 'boolean',
21
+ default: true
22
+ })
23
+ .demandOption([]);
24
+ }, async (args) => {
25
+ const config = createConfig();
26
+ await init(config, args.pages);
27
+ await server(config).listen(args.port, async () => {
28
+ console.log(`SynthOS server is running on http://localhost:${args.port}`);
29
+
30
+ // Open using default browser
31
+ const open = await dynamicImport('open');
32
+ open.default(`http://localhost:${args.port}`);
33
+ });
34
+ })
35
+ .help()
36
+ .demandCommand()
37
+ .parseAsync();
38
+ }