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,335 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>SynthOS - Scripts</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #1a1a1a; color: #e0e0e0; height: 100vh; display: flex; }
10
+ .chat-panel { width: 30%; background: #2a2a2a; box-shadow: 0 0 10px rgba(0,0,0,0.3); padding: 20px; display: flex; flex-direction: column; }
11
+ .chat-header { font-size: 24px; padding: 10px; background: #3a7bc8; color: white; text-align: center; border-radius: 10px 10px 0 0; }
12
+ .chat-messages { flex-grow: 1; overflow-y: auto; padding: 15px; margin-top: 10px; background: #333; border-radius: 10px; }
13
+ .chat-message { margin-bottom: 15px; padding: 10px; background: #444; border-radius: 15px; box-shadow: 0 2px 5px rgba(0,0,0,0.2); }
14
+ .chat-message p { margin-bottom: 5px; line-height: 1.4; }
15
+ .chat-message p strong { font-weight: 600; color: #4a90e2; }
16
+ .chat-message p code { background: #555; padding: 2px 4px; border-radius: 3px; font-family: 'Courier New', Courier, monospace; color: #e0e0e0; }
17
+ .link-group { display: flex; justify-content: space-between; margin: 15px 0; }
18
+ .link-group a { font-size: 14px; color: #4a90e2; text-decoration: none; padding: 5px 10px; border-radius: 5px; transition: background-color 0.3s; }
19
+ .link-group a:hover { background-color: #3a3a3a; }
20
+ form { display: flex; flex-direction: column; width: 100%; }
21
+ .chat-input, .chat-submit { padding: 12px; border: none; border-radius: 25px; width: 100%; font-size: 14px; }
22
+ .chat-input { background: #444; color: #e0e0e0; margin-bottom: 10px; box-shadow: inset 0 1px 3px rgba(0,0,0,0.3); }
23
+ .chat-submit { background: #3a7bc8; color: white; cursor: pointer; transition: background-color 0.3s; }
24
+ .chat-submit:hover { background: #2a6cb8; }
25
+ .viewer-panel { width: 70%; padding: 20px; background: #2a2a2a; display: flex; flex-direction: column; justify-content: flex-start; align-items: center; box-shadow: -5px 0 10px rgba(0,0,0,0.2); }
26
+ .loading-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(26, 26, 26, 0.8); display: none; justify-content: center; align-items: center; z-index: 1000; }
27
+ .spinner { border: 8px solid #333; border-top: 8px solid #3a7bc8; border-radius: 50%; width: 60px; height: 60px; animation: spin 1s linear infinite; }
28
+ @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
29
+ ::-webkit-scrollbar { width: 10px; }
30
+ ::-webkit-scrollbar-track { background: #333; }
31
+ ::-webkit-scrollbar-thumb { background: #555; border-radius: 5px; }
32
+ ::-webkit-scrollbar-thumb:hover { background: #666; }
33
+ .application-title { font-size: 24px; color: white; background: #3a7bc8; padding: 10px; width: 100%; text-align: center; border-radius: 10px 10px 0 0; }
34
+ .application-content { font-size: 18px; color: #ccc; padding: 20px; margin-top: 10px; flex-grow: 1; width: 100%; background: #333; border-radius: 0 0 10px 10px; display: flex; }
35
+ .script-list { width: 30%; border-right: 1px solid #444; padding-right: 20px; }
36
+ .script-list ul { list-style-type: none; }
37
+ .script-list li { padding: 10px; cursor: pointer; transition: background-color 0.3s; }
38
+ .script-list li:hover { background-color: #444; }
39
+ .script-list li.active { background-color: #3a7bc8; color: white; }
40
+ .add-script-btn { margin-top: 20px; padding: 10px; background-color: #4CAF50; color: white; border: none; cursor: pointer; width: 100%; }
41
+ .script-detail { width: 70%; padding-left: 20px; }
42
+ .script-detail input, .script-detail select { width: 100%; padding: 10px; margin-bottom: 10px; background-color: #444; color: #e0e0e0; border: none; }
43
+ .save-script-btn { padding: 10px; background-color: #3a7bc8; color: white; border: none; cursor: pointer; width: 100%; }
44
+ .delete-script-btn { padding: 10px; background-color: #ff4444; color: white; border: none; cursor: pointer; width: 100%; margin-top: 10px; }
45
+ .placeholder-message { text-align: center; color: #888; padding: 20px; }
46
+ .error-message { color: #ff6b6b; font-size: 14px; margin-top: 5px; display: none; }
47
+ .instructions { margin-top: 20px; padding: 15px; background: #3a3a3a; border-radius: 5px; font-size: 13px; line-height: 1.6; color: #aaa; }
48
+ .instructions h3 { margin-bottom: 8px; color: #4a90e2; font-size: 15px; }
49
+ .instructions p { margin-bottom: 10px; }
50
+ </style>
51
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js"></script>
52
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/14.1.1/marked.min.js"></script>
53
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/11.1.0/mermaid.min.js"></script>
54
+ </head>
55
+ <body>
56
+ <div class="chat-panel">
57
+ <div class="chat-header">SynthOS</div>
58
+ <div class="chat-messages" id="chatMessages">
59
+ <div class="chat-message"><p><strong>SynthOS:</strong> Add or modify scripts that can be executed using the <code>/api/scripts/:id</code> API.</p></div>
60
+ </div>
61
+ <div class="link-group">
62
+ <a href="#" id="saveLink">Save</a>
63
+ <a href="/pages" id="pagesLink">Pages</a>
64
+ <a href="#" id="resetLink">Reset</a>
65
+ </div>
66
+ <form action="/" method="POST" id="chatForm">
67
+ <input type="text" class="chat-input" id="chatInput" name="message" placeholder="Type a message...">
68
+ <button type="submit" class="chat-submit">Send</button>
69
+ </form>
70
+ </div>
71
+ <div class="viewer-panel" id="viewerPanel">
72
+ <div class="application-title">Script Editor</div>
73
+ <div class="application-content">
74
+ <div class="script-list">
75
+ <ul id="scriptList">
76
+ <!-- Scripts will be loaded here -->
77
+ </ul>
78
+ <button class="add-script-btn">Add New Script</button>
79
+ </div>
80
+ <div class="script-detail">
81
+ <div id="placeholderMessage" class="placeholder-message">
82
+ Select an existing script to edit or add a new one using the "Add New Script" button.
83
+ </div>
84
+ <input type="text" id="scriptId" placeholder="Script ID" value="" style="display: none;">
85
+ <select id="scriptType" style="display: none;">
86
+ <option value="command">Command</option>
87
+ </select>
88
+ <input type="text" id="scriptCommand" placeholder="Script Command" style="display: none;">
89
+ <input type="text" id="scriptDescription" placeholder="Usage Description (optional)" style="display: none;">
90
+ <input type="text" id="scriptVariables" placeholder="Variables (optional, e.g., { city: string })" style="display: none;">
91
+ <div id="commandError" class="error-message">Script Command is required.</div>
92
+ <button class="save-script-btn" style="display: none;">Save Changes</button>
93
+ <button class="delete-script-btn" style="display: none;">Delete Script</button>
94
+ <div class="instructions">
95
+ <h3>Understanding SynthOS Scripts</h3>
96
+ <p>Scripts in SynthOS are powerful tools that allow you to define custom terminal commands for various tasks. When SynthOS executes a script, it runs the command and captures the console output, which is then returned for further processing or analysis.</p>
97
+
98
+ <h3>Creating Effective Scripts</h3>
99
+ <p>When writing a script, you can use any valid terminal command. For added flexibility, you can include {{variable}} placeholders, which SynthOS will replace with actual values during execution.</p>
100
+
101
+ <h3>Example: Weather Forecast Script</h3>
102
+ <p>Here's an example of an interesting script that SynthOS could use:</p>
103
+ <p><code>curl wttr.in/{{city}}?format=3</code></p>
104
+ <p>This script fetches a concise weather forecast for a specified city. SynthOS can call this script with different city names to get up-to-date weather information.</p>
105
+
106
+ <h3>Tips for Script Writing</h3>
107
+ <p>1. Keep commands concise and focused on a single task.</p>
108
+ <p>2. Use variables for dynamic inputs to make scripts more versatile.</p>
109
+ <p>3. Consider potential errors and how to handle them.</p>
110
+ <p>4. Test your scripts thoroughly to ensure they work as expected.</p>
111
+ <p>5. Provide a clear and concise usage description to help others understand how to use your script.</p>
112
+ <p>6. Define the expected variables in the Variables field to document the script's requirements.</p>
113
+
114
+ <h3>Using the Description Field</h3>
115
+ <p>The Description field allows you to provide a brief explanation of what SynthOS can use your script for.</p>
116
+
117
+ <h3>Defining Variables</h3>
118
+ <p>Use the Variables field to specify any input parameters your script expects. Format it as a JSON object, e.g., { city: string, days?: number }. This helps SynthOS know what inputs it needs to provide when running your script.</p>
119
+ </div>
120
+ </div>
121
+ </div>
122
+ </div>
123
+ <div id="thoughts" style="display: none;">I've show the user a list of available scripts.</div>
124
+ <div id="loadingOverlay" class="loading-overlay"><div class="spinner"></div></div>
125
+ <script>
126
+ // Basic chat functionality
127
+ document.getElementById("chatInput").focus();
128
+ document.getElementById("chatForm").addEventListener('submit', () => {
129
+ document.getElementById("loadingOverlay").style.display = 'flex';
130
+ document.getElementById("chatForm").action = window.location.pathname;
131
+ });
132
+ document.getElementById("saveLink").addEventListener("click", function() {
133
+ const pageName = prompt("Enter the name of the page to save as:");
134
+ if (pageName) {
135
+ window.location.href = `${window.location.pathname}/save?name=${encodeURIComponent(pageName)}`;
136
+ }
137
+ });
138
+ document.getElementById("resetLink").addEventListener("click", function() {
139
+ window.location.href = `${window.location.pathname}/reset`;
140
+ });
141
+ window.onload = function() {
142
+ const chatMessages = document.getElementById('chatMessages');
143
+ chatMessages.scrollTo({
144
+ top: chatMessages.scrollHeight,
145
+ behavior: 'smooth'
146
+ });
147
+ };
148
+
149
+ // Script Editor Functionality
150
+ const scriptList = document.getElementById('scriptList');
151
+ const scriptId = document.getElementById('scriptId');
152
+ const scriptType = document.getElementById('scriptType');
153
+ const scriptCommand = document.getElementById('scriptCommand');
154
+ const scriptDescription = document.getElementById('scriptDescription');
155
+ const scriptVariables = document.getElementById('scriptVariables');
156
+ const addScriptBtn = document.querySelector('.add-script-btn');
157
+ const saveScriptBtn = document.querySelector('.save-script-btn');
158
+ const deleteScriptBtn = document.querySelector('.delete-script-btn');
159
+ const placeholderMessage = document.getElementById('placeholderMessage');
160
+ const commandError = document.getElementById('commandError');
161
+
162
+ let isNewScript = false;
163
+
164
+ function showScriptDetails() {
165
+ placeholderMessage.style.display = 'none';
166
+ scriptId.style.display = 'block';
167
+ scriptType.style.display = 'block';
168
+ scriptCommand.style.display = 'block';
169
+ scriptDescription.style.display = 'block';
170
+ scriptVariables.style.display = 'block';
171
+ saveScriptBtn.style.display = 'block';
172
+ deleteScriptBtn.style.display = isNewScript ? 'none' : 'block';
173
+ }
174
+
175
+ function hideScriptDetails() {
176
+ placeholderMessage.style.display = 'block';
177
+ scriptId.style.display = 'none';
178
+ scriptType.style.display = 'none';
179
+ scriptCommand.style.display = 'none';
180
+ scriptDescription.style.display = 'none';
181
+ scriptVariables.style.display = 'none';
182
+ saveScriptBtn.style.display = 'none';
183
+ deleteScriptBtn.style.display = 'none';
184
+ commandError.style.display = 'none';
185
+ }
186
+
187
+ function formatId(id) {
188
+ return id.toLowerCase().replace(/\s+/g, '-');
189
+ }
190
+
191
+ function loadScripts() {
192
+ fetch('/api/data/scripts')
193
+ .then(response => response.json())
194
+ .then(scripts => {
195
+ scriptList.innerHTML = '';
196
+ scripts.forEach(script => {
197
+ const li = document.createElement('li');
198
+ li.textContent = script.id;
199
+ li.dataset.id = script.id;
200
+ li.dataset.type = script.type;
201
+ li.dataset.command = script.command;
202
+ li.dataset.description = script.description || '';
203
+ li.dataset.variables = script.variables || '';
204
+ scriptList.appendChild(li);
205
+ });
206
+ })
207
+ .catch(error => console.error('Error loading scripts:', error));
208
+ }
209
+
210
+ function saveScript(script) {
211
+ fetch('/api/data/scripts')
212
+ .then(response => response.json())
213
+ .then(scripts => {
214
+ const existingScript = scripts.find(s => s.id === script.id);
215
+ if (existingScript && isNewScript) {
216
+ if (confirm('A script with this ID already exists. Do you want to overwrite it?')) {
217
+ performSave(script);
218
+ }
219
+ } else {
220
+ performSave(script);
221
+ }
222
+ })
223
+ .catch(error => console.error('Error checking existing scripts:', error));
224
+ }
225
+
226
+ function performSave(script) {
227
+ fetch('/api/data/scripts', {
228
+ method: 'POST',
229
+ headers: {
230
+ 'Content-Type': 'application/json',
231
+ },
232
+ body: JSON.stringify(script),
233
+ })
234
+ .then(response => response.json())
235
+ .then(savedScript => {
236
+ loadScripts();
237
+ isNewScript = false;
238
+ setTimeout(() => {
239
+ const newScriptElement = document.querySelector(`[data-id="${savedScript.id}"]`);
240
+ if (newScriptElement) {
241
+ newScriptElement.click();
242
+ }
243
+ }, 100);
244
+ })
245
+ .catch(error => console.error('Error saving script:', error));
246
+ }
247
+
248
+ function deleteScript(id) {
249
+ if (confirm('Are you sure you want to delete this script?')) {
250
+ fetch(`/api/data/scripts/${id}`, {
251
+ method: 'DELETE',
252
+ })
253
+ .then(response => response.json())
254
+ .then(result => {
255
+ if (result.success) {
256
+ loadScripts();
257
+ hideScriptDetails();
258
+ } else {
259
+ console.error('Failed to delete script.');
260
+ }
261
+ })
262
+ .catch(error => console.error('Error deleting script:', error));
263
+ }
264
+ }
265
+
266
+ scriptList.addEventListener('click', (e) => {
267
+ if (e.target.tagName === 'LI') {
268
+ document.querySelectorAll('.script-list li').forEach(li => li.classList.remove('active'));
269
+ e.target.classList.add('active');
270
+ showScriptDetails();
271
+ scriptId.value = e.target.dataset.id;
272
+ scriptType.value = e.target.dataset.type || 'command';
273
+ scriptCommand.value = e.target.dataset.command || '';
274
+ scriptDescription.value = e.target.dataset.description || '';
275
+ scriptVariables.value = e.target.dataset.variables || '';
276
+ commandError.style.display = 'none';
277
+ isNewScript = false;
278
+ deleteScriptBtn.style.display = 'block';
279
+ }
280
+ });
281
+
282
+ addScriptBtn.addEventListener('click', () => {
283
+ const newId = formatId(`script-${Date.now()}`);
284
+ showScriptDetails();
285
+ scriptId.value = newId;
286
+ scriptType.value = 'command';
287
+ scriptCommand.value = '';
288
+ scriptDescription.value = '';
289
+ scriptVariables.value = '';
290
+ commandError.style.display = 'none';
291
+ document.querySelectorAll('.script-list li').forEach(li => li.classList.remove('active'));
292
+ isNewScript = true;
293
+ deleteScriptBtn.style.display = 'none';
294
+ });
295
+
296
+ saveScriptBtn.addEventListener('click', () => {
297
+ if (!scriptCommand.value.trim()) {
298
+ commandError.style.display = 'block';
299
+ return;
300
+ }
301
+
302
+ const script = {
303
+ id: formatId(scriptId.value),
304
+ type: scriptType.value,
305
+ command: scriptCommand.value,
306
+ description: scriptDescription.value.trim() || undefined,
307
+ variables: scriptVariables.value.trim() || undefined,
308
+ };
309
+
310
+ saveScript(script);
311
+ });
312
+
313
+ deleteScriptBtn.addEventListener('click', () => {
314
+ const id = scriptId.value;
315
+ if (id) {
316
+ deleteScript(id);
317
+ }
318
+ });
319
+
320
+ scriptId.addEventListener('input', (e) => {
321
+ e.target.value = formatId(e.target.value);
322
+ });
323
+
324
+ scriptCommand.addEventListener('input', () => {
325
+ commandError.style.display = 'none';
326
+ });
327
+
328
+ // Load scripts when the page loads
329
+ loadScripts();
330
+
331
+ // Initialize with empty fields and show placeholder
332
+ hideScriptDetails();
333
+ </script>
334
+ </body>
335
+ </html>
@@ -0,0 +1,158 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>SynthOS - Settings</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #1a1a1a; color: #e0e0e0; height: 100vh; display: flex; }
10
+ .chat-panel { width: 30%; background: #2a2a2a; box-shadow: 0 0 10px rgba(0,0,0,0.3); padding: 20px; display: flex; flex-direction: column; }
11
+ .chat-header { font-size: 24px; padding: 10px; background: #3a7bc8; color: white; text-align: center; border-radius: 10px 10px 0 0; }
12
+ .chat-messages { flex-grow: 1; overflow-y: auto; padding: 15px; margin-top: 10px; background: #333; border-radius: 10px; }
13
+ .chat-message { margin-bottom: 15px; padding: 10px; background: #444; border-radius: 15px; box-shadow: 0 2px 5px rgba(0,0,0,0.2); }
14
+ .chat-message p { margin-bottom: 5px; line-height: 1.4; }
15
+ .chat-message p strong { font-weight: 600; color: #4a90e2; }
16
+ .chat-message p code { background: #555; padding: 2px 4px; border-radius: 3px; font-family: 'Courier New', Courier, monospace; color: #e0e0e0; }
17
+ .link-group { display: flex; justify-content: space-between; margin: 15px 0; }
18
+ .link-group a { font-size: 14px; color: #4a90e2; text-decoration: none; padding: 5px 10px; border-radius: 5px; transition: background-color 0.3s; }
19
+ .link-group a:hover { background-color: #3a3a3a; }
20
+ form { display: flex; flex-direction: column; width: 100%; }
21
+ .chat-input, .chat-submit { padding: 12px; border: none; border-radius: 25px; width: 100%; font-size: 14px; }
22
+ .chat-input { background: #444; color: #e0e0e0; margin-bottom: 10px; box-shadow: inset 0 1px 3px rgba(0,0,0,0.3); }
23
+ .chat-submit { background: #3a7bc8; color: white; cursor: pointer; transition: background-color 0.3s; }
24
+ .chat-submit:hover { background: #2a6cb8; }
25
+ .viewer-panel { width: 70%; padding: 30px; background: #2a2a2a; display: flex; flex-direction: column; justify-content: center; align-items: center; box-shadow: -5px 0 10px rgba(0,0,0,0.2); }
26
+ .loading-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(26, 26, 26, 0.8); display: none; justify-content: center; align-items: center; z-index: 1000; }
27
+ .spinner { border: 8px solid #333; border-top: 8px solid #3a7bc8; border-radius: 50%; width: 60px; height: 60px; animation: spin 1s linear infinite; }
28
+ @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
29
+ ::-webkit-scrollbar { width: 10px; }
30
+ ::-webkit-scrollbar-track { background: #333; }
31
+ ::-webkit-scrollbar-thumb { background: #555; border-radius: 5px; }
32
+ ::-webkit-scrollbar-thumb:hover { background: #666; }
33
+ .dialog-title { font-size: 24px; color: #f0f0f0; background: #3a3a3a; padding: 15px; border-bottom: 1px solid #444; width: 100%; text-align: center; border-radius: 10px 10px 0 0; }
34
+ .dialog-content { font-size: 16px; color: #ccc; padding: 20px; margin-top: 10px; flex-grow: 1; width: 100%; background: #333; border-radius: 0 0 10px 10px; }
35
+ .form-group { margin-bottom: 20px; }
36
+ .form-group label { display: block; margin-bottom: 8px; color: #e0e0e0; font-weight: 600; }
37
+ .form-group input, .form-group select, .form-group textarea { width: 100%; padding: 12px; border-radius: 8px; border: 1px solid #555; background: #444; color: #fff; font-size: 14px; transition: all 0.3s ease; }
38
+ .form-group input:focus, .form-group select:focus, .form-group textarea:focus { outline: none; border-color: #3a7bc8; box-shadow: 0 0 0 2px rgba(58, 123, 200, 0.2); }
39
+ .form-group textarea { resize: vertical; min-height: 100px; }
40
+ .disabled { background: #555; cursor: not-allowed; }
41
+ .settings-form { background: #2a2a2a; border-radius: 10px; padding: 20px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); }
42
+ .info-text { color: #aaa; margin-top: 15px; font-size: 14px; line-height: 1.6; }
43
+ .info-text a { color: #4a90e2; text-decoration: none; transition: color 0.3s; }
44
+ .info-text a:hover { color: #3a7bc8; }
45
+ </style>
46
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js"></script>
47
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/14.1.1/marked.min.js"></script>
48
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/11.1.0/mermaid.min.js"></script>
49
+ </head>
50
+ <body>
51
+ <div class="chat-panel">
52
+ <div class="chat-header">SynthOS</div>
53
+ <div class="chat-messages" id="chatMessages">
54
+ <div class="chat-message">
55
+ <p><strong>SynthOS:</strong> Please fill in all required fields in the settings form and click 'Update'. Ensure your OpenAI or Anthropic API Key is valid and select the model you wish to use.</p>
56
+ <p><code>Max Output Tokens</code> controls how large of a page that can be rendered and <code>Additional Instructions</code> can be used to provide further prompt instructions for how pages should be updated.</p>
57
+ </div>
58
+ </div>
59
+ <div class="link-group">
60
+ <a href="#" id="saveLink">Save</a>
61
+ <a href="/pages" id="pagesLink">Pages</a>
62
+ <a href="#" id="resetLink">Reset</a>
63
+ </div>
64
+ <form action="/" method="POST" id="chatForm">
65
+ <input type="text" class="chat-input" id="chatInput" name="message" placeholder="Type a message...">
66
+ <button type="submit" class="chat-submit">Send</button>
67
+ </form>
68
+ </div>
69
+ <div class="viewer-panel" id="viewerPanel">
70
+ <div class="settings-form">
71
+ <div class="dialog-title">Settings</div>
72
+ <div class="dialog-content">
73
+ <form action="/api/settings" method="POST" id="settingsForm">
74
+ <div class="form-group">
75
+ <label for="serviceApiKey">Service API Key</label>
76
+ <input type="password" id="serviceApiKey" name="serviceApiKey" placeholder="Enter your API Key" required>
77
+ </div>
78
+ <div class="form-group">
79
+ <label for="model">Model</label>
80
+ <select id="model" name="model" required>
81
+ <option value="">Select a model</option>
82
+ </select>
83
+ </div>
84
+ <div class="form-group">
85
+ <label for="maxTokens">Max Output Tokens</label>
86
+ <input type="number" id="maxTokens" name="maxTokens" placeholder="Enter max token count" required>
87
+ </div>
88
+ <div class="form-group">
89
+ <label for="instructions">Additional Instructions</label>
90
+ <textarea id="instructions" name="instructions" placeholder="Enter any additional instructions"></textarea>
91
+ </div>
92
+ <button type="submit" class="chat-submit" action="/api/submit">Update</button>
93
+ </form>
94
+ <p class="info-text">An API Key is required which can be generated from either <a href="https://platform.openai.com/api-keys" target="_blank">OpenAI API Keys</a> or <a href="https://console.anthropic.com/settings/keys" target="_blank">Anthropic API Keys</a>.</p>
95
+ </div>
96
+ </div>
97
+ </div>
98
+ <div id="thoughts" style="display: none;">Ask the user to update their settings.</div>
99
+ <div id="loadingOverlay" class="loading-overlay"><div class="spinner"></div></div>
100
+ <script>
101
+ // Basic chat functionality
102
+ document.getElementById("chatInput").focus();
103
+ document.getElementById("chatForm").addEventListener('submit', (event) => {
104
+ document.getElementById("loadingOverlay").style.display = 'flex';
105
+ document.getElementById("chatForm").action = window.location.pathname;
106
+ });
107
+ document.getElementById("saveLink").addEventListener("click", function() {
108
+ const pageName = prompt("Enter the name of the page to save as:");
109
+ if (pageName) {
110
+ window.location.href = `${window.location.pathname}/save?name=${encodeURIComponent(pageName)}`;
111
+ }
112
+ });
113
+ document.getElementById("resetLink").addEventListener("click", function() {
114
+ window.location.href = `${window.location.pathname}/reset`;
115
+ });
116
+ window.onload = function() {
117
+ const chatMessages = document.getElementById('chatMessages');
118
+ chatMessages.scrollTop = chatMessages.scrollHeight;
119
+ };
120
+
121
+ // Form validation
122
+ document.getElementById('settingsForm').addEventListener('submit', function(event) {
123
+ const serviceApiKey = document.getElementById('serviceApiKey').value;
124
+ const model = document.getElementById('model').value;
125
+ const maxTokens = document.getElementById('maxTokens').value;
126
+
127
+ if (!serviceApiKey || !model || !maxTokens) {
128
+ alert('Please fill in all required fields before submitting.');
129
+ event.preventDefault();
130
+ }
131
+ });
132
+
133
+ // Fetch settings and populate form
134
+ let isConfigured = false;
135
+ fetch('/api/settings')
136
+ .then(response => response.json())
137
+ .then(data => {
138
+ console.log('Settings:', data);
139
+ const serviceApiKey = data.serviceApiKey || '';
140
+ const model = data.model || '';
141
+ const maxTokens = data.maxTokens || '';
142
+ isConfigured = serviceApiKey && model && maxTokens;
143
+ document.getElementById('serviceApiKey').value = serviceApiKey;
144
+ document.getElementById('model').innerHTML = data.availableModels.map(model => `<option value="${model}">${model}</option>`).join('');
145
+ document.getElementById('model').value = model;
146
+ document.getElementById('maxTokens').value = maxTokens;
147
+ document.getElementById('instructions').value = data.instructions || '';
148
+
149
+ // Disable chat input if not configured
150
+ if (!isConfigured) {
151
+ document.getElementById('chatInput').disabled = true;
152
+ document.getElementById('chatInput').classList.add('disabled');
153
+ }
154
+ })
155
+ .catch(error => console.error('Error fetching settings:', error));
156
+ </script>
157
+ </body>
158
+ </html>
package/src/files.ts ADDED
@@ -0,0 +1,49 @@
1
+ import * as fs from 'fs/promises';
2
+ import path from 'path';
3
+
4
+ export async function checkIfExists(path: string): Promise<boolean> {
5
+ try {
6
+ await fs.access(path);
7
+ return true;
8
+ } catch {
9
+ return false;
10
+ }
11
+ }
12
+
13
+ export async function copyFile(srcFile: string, destFolder: string): Promise<void> {
14
+ await ensureFolderExists(destFolder);
15
+ const fileName = path.basename(srcFile);
16
+ const destFile = path.join(destFolder, fileName);
17
+ await fs.copyFile(srcFile, destFile);
18
+ }
19
+
20
+ export async function copyFiles(srcFolder: string, destFolder: string): Promise<void> {
21
+ await ensureFolderExists(destFolder);
22
+
23
+ const files = await fs.readdir(srcFolder);
24
+ for (const file of files) {
25
+ const srcPath = `${srcFolder}/${file}`;
26
+ const destPath = `${destFolder}/${file}`;
27
+ await fs.copyFile(srcPath, destPath);
28
+ }
29
+ }
30
+
31
+ export async function ensureFolderExists(path: string): Promise<void> {
32
+ await fs.mkdir(path, { recursive: true });
33
+ }
34
+
35
+ export async function listFiles(path: string): Promise<string[]> {
36
+ return (await fs.readdir(path)).filter(file => !file.startsWith('.') && file.includes('.'));
37
+ }
38
+
39
+ export async function loadFile(path: string): Promise<string> {
40
+ return await fs.readFile(path, 'utf8');
41
+ }
42
+
43
+ export async function saveFile(path: string, content: string): Promise<void> {
44
+ await fs.writeFile(path, content, 'utf8');
45
+ }
46
+
47
+ export async function deleteFile(path: string): Promise<void> {
48
+ await fs.unlink(path);
49
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export * from './service';
2
+ export * from './files';
3
+ export * from './init';
4
+ export * from './pages';
5
+ export * from './scripts';
6
+ export * from './settings';
package/src/init.ts ADDED
@@ -0,0 +1,66 @@
1
+ import path from "path";
2
+ import { checkIfExists, copyFile, copyFiles, ensureFolderExists, saveFile } from "./files";
3
+ import { DefaultSettings } from "./settings";
4
+
5
+ export interface SynthOSConfig {
6
+ pagesFolder: string;
7
+ requiredPagesFolder: string;
8
+ defaultPagesFolder: string;
9
+ defaultScriptsFolder: string;
10
+ }
11
+
12
+ export function createConfig(pagesFolder = '.synthos'): SynthOSConfig {
13
+ return {
14
+ pagesFolder: path.join(process.cwd(), pagesFolder),
15
+ requiredPagesFolder: path.join(__dirname, '../required-pages'),
16
+ defaultPagesFolder: path.join(__dirname, '../default-pages'),
17
+ defaultScriptsFolder: path.join(__dirname, '../default-scripts')
18
+ };
19
+ }
20
+
21
+ export async function init(config: SynthOSConfig, includeDefaultPages: boolean = true): Promise<boolean> {
22
+ // Check for existing folder
23
+ if (await checkIfExists(config.pagesFolder)) {
24
+ return false;
25
+ }
26
+
27
+ console.log(`Initializing .synthos folder...`);
28
+
29
+ // Create pages folder
30
+ await ensureFolderExists(config.pagesFolder);
31
+
32
+ // Create mandatory files
33
+ await saveFile(path.join(config.pagesFolder, '.gitignore'), 'settings.json\n');
34
+ await saveFile(path.join(config.pagesFolder, 'settings.json'), JSON.stringify(DefaultSettings, null, 4));
35
+ await saveFile(path.join(config.pagesFolder, 'settings.json.example'), JSON.stringify(DefaultSettings, null, 4));
36
+
37
+ // Setup default scripts
38
+ console.log(`Copying default scripts to .synthos folder...`);
39
+ const scriptsFolder = path.join(config.pagesFolder, 'scripts');
40
+ await ensureFolderExists(scriptsFolder);
41
+ switch (process.platform) {
42
+ case 'win32':
43
+ await copyFile(path.join(config.defaultScriptsFolder, 'windows-terminal.json'), scriptsFolder);
44
+ break;
45
+ case 'darwin':
46
+ await copyFile(path.join(config.defaultScriptsFolder, 'mac-terminal.json'), scriptsFolder);
47
+ break;
48
+ case 'android':
49
+ await copyFile(path.join(config.defaultScriptsFolder, 'android-terminal.json'), scriptsFolder);
50
+ break;
51
+ case 'linux':
52
+ default:
53
+ await copyFile(path.join(config.defaultScriptsFolder, 'linux-terminal.json'), scriptsFolder);
54
+ break;
55
+ }
56
+
57
+ 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 .synthos/scripts/example.sh\n\n# This script will print "Hello, World!" to the console\n\necho "Hello, World!"\n');
58
+ // Copy pages
59
+ if (includeDefaultPages) {
60
+ console.log(`Copying default pages to .synthos folder...`);
61
+ await copyFiles(config.defaultPagesFolder, config.pagesFolder);
62
+ }
63
+
64
+ return true;
65
+ }
66
+