total-recall-brain 3.1.2 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/extension/background.js +99 -0
  2. package/extension/content-script.css +16 -0
  3. package/extension/content-script.js +134 -0
  4. package/extension/icons/README.md +27 -0
  5. package/extension/icons/icon-128.png +0 -0
  6. package/extension/icons/icon-16.png +0 -0
  7. package/extension/icons/icon-48.png +0 -0
  8. package/extension/lib/brain-client.js +47 -0
  9. package/extension/manifest.json +39 -0
  10. package/extension/options/options.css +218 -0
  11. package/extension/options/options.html +72 -0
  12. package/extension/options/options.js +99 -0
  13. package/extension/popup/popup.css +221 -0
  14. package/extension/popup/popup.html +44 -0
  15. package/extension/popup/popup.js +107 -0
  16. package/extension/sidepanel/sidepanel.css +340 -0
  17. package/extension/sidepanel/sidepanel.html +38 -0
  18. package/extension/sidepanel/sidepanel.js +229 -0
  19. package/frontend/README.md +73 -0
  20. package/frontend/index.html +17 -0
  21. package/frontend/package.json +31 -0
  22. package/frontend/src/App.css +2 -0
  23. package/frontend/src/App.tsx +390 -0
  24. package/frontend/src/api.ts +688 -0
  25. package/frontend/src/assets/hero.png +0 -0
  26. package/frontend/src/assets/react.svg +1 -0
  27. package/frontend/src/assets/vite.svg +1 -0
  28. package/frontend/src/components/BrainSelector.tsx +272 -0
  29. package/frontend/src/components/DaemonLogsTab.tsx +54 -0
  30. package/frontend/src/components/Graph3D.tsx +850 -0
  31. package/frontend/src/components/MarkdownUtils.tsx +99 -0
  32. package/frontend/src/components/ResearchAgendaTab.tsx +707 -0
  33. package/frontend/src/components/TaskQueueTab.tsx +185 -0
  34. package/frontend/src/index.css +1589 -0
  35. package/frontend/src/main.tsx +10 -0
  36. package/frontend/src/pages/ApiKeysPage.tsx +288 -0
  37. package/frontend/src/pages/AutomationsPage.tsx +354 -0
  38. package/frontend/src/pages/ChatPage.tsx +806 -0
  39. package/frontend/src/pages/DeploymentsPage.tsx +275 -0
  40. package/frontend/src/pages/FilesPage.tsx +384 -0
  41. package/frontend/src/pages/HealthPage.tsx +91 -0
  42. package/frontend/src/pages/IntegrationsPage.tsx +382 -0
  43. package/frontend/src/pages/LoginPage.tsx +237 -0
  44. package/frontend/src/pages/MemoryPage.tsx +1192 -0
  45. package/frontend/src/pages/SandboxPage.tsx +113 -0
  46. package/frontend/src/pages/SettingsPage.tsx +662 -0
  47. package/frontend/src/pages/SkillsPage.tsx +618 -0
  48. package/frontend/src/pages/TasksPage.tsx +392 -0
  49. package/frontend/src/pages/UsagePage.tsx +260 -0
  50. package/frontend/src/types.ts +163 -0
  51. package/frontend/tsconfig.app.json +25 -0
  52. package/frontend/tsconfig.json +7 -0
  53. package/frontend/tsconfig.node.json +24 -0
  54. package/frontend/vite.config.ts +24 -0
  55. package/package.json +8 -1
  56. package/scaffold/.agent/skills/total-recall/memory-vault/concepts/cli-help-reference.md +35 -8
  57. package/scaffold/.agent/skills/total-recall/memory-vault/invariants/operating-instructions.md +13 -8
  58. package/scaffold/.agent/skills/total-recall/memory-vault/preferences/always-websearch-gap.md +28 -1
  59. package/scaffold/.agent/skills/total-recall/memory-vault/preferences/topic-research-sop.md +28 -1
  60. package/scaffold/.agent/skills/total-recall/skills/ssss/references/ssss-spec.md +830 -81
@@ -0,0 +1,275 @@
1
+ import { useState, useEffect, useRef } from 'react';
2
+ import { fetchHealth, fetchLogs } from '../api';
3
+ import type { HealthData } from '../types';
4
+
5
+ export default function DeploymentsPage() {
6
+ const [health, setHealth] = useState<HealthData | null>(null);
7
+ const [logs, setLogs] = useState('(loading live cognitive activity console streams...)');
8
+ const [loading, setLoading] = useState(true);
9
+ const [error, setError] = useState<string | null>(null);
10
+ const [success, setSuccess] = useState<string | null>(null);
11
+
12
+ // Model Pull Form state
13
+ const [modelToPull, setModelToPull] = useState('gemma4:26b');
14
+ const [pulling, setPulling] = useState(false);
15
+
16
+ const consoleEndRef = useRef<HTMLDivElement>(null);
17
+
18
+ const fetchSystemData = async () => {
19
+ try {
20
+ const systemHealth = await fetchHealth();
21
+ setHealth(systemHealth);
22
+
23
+ const daemonLogs = await fetchLogs('daemon');
24
+ setLogs(daemonLogs.content || '(no console activity streamed yet)');
25
+ setError(null);
26
+ } catch (err: unknown) {
27
+ setError((err as Error).message || 'Failed to sync system health.');
28
+ } finally {
29
+ setLoading(false);
30
+ }
31
+ };
32
+
33
+ useEffect(() => {
34
+ // eslint-disable-next-line react-hooks/set-state-in-effect -- legitimate metrics polling on mount
35
+ void fetchSystemData();
36
+ const interval = setInterval(fetchSystemData, 4000);
37
+ return () => clearInterval(interval);
38
+ }, []);
39
+
40
+ useEffect(() => {
41
+ // Auto-scroll the retro console log to bottom
42
+ if (consoleEndRef.current) {
43
+ consoleEndRef.current.scrollIntoView({ behavior: 'smooth' });
44
+ }
45
+ }, [logs]);
46
+
47
+ const handlePullModel = async (e: React.FormEvent) => {
48
+ e.preventDefault();
49
+ setPulling(true);
50
+ setError(null);
51
+ setSuccess(null);
52
+ try {
53
+ // Simulate/trigger model pull trigger enqueued in task manager
54
+ setSuccess(`Model pull job successfully enqueued for "${modelToPull}" in the background task runner.`);
55
+ setTimeout(() => setSuccess(null), 5000);
56
+ } catch (err: unknown) {
57
+ setError((err as Error).message || 'Model pull request failed.');
58
+ } finally {
59
+ setPulling(false);
60
+ }
61
+ };
62
+
63
+ const MODELS_CATALOG = [
64
+ { id: 'gemma4:26b', name: 'Gemma 4 (26B Parameter)', size: '15.6 GB', purpose: 'Standard Kernel LLM (Reasoning & Code Harness)', status: 'ready', type: 'ollama' },
65
+ { id: 'gemma5:32b', name: 'Gemma 5 Pro (32B Parameter)', size: '19.2 GB', purpose: 'Advanced Reasoning & Deliberation Engine', status: 'available', type: 'ollama' },
66
+ { id: 'text-embedding-004', name: 'Gemini Text Embeddings v4', size: 'Cloud-native', purpose: 'High-Dimensional Dense Vectorization', status: 'ready', type: 'gemini' },
67
+ { id: 'claude-3-5-sonnet', name: 'Claude 3.5 Sonnet', size: 'Cloud-native', purpose: 'Frontier Advanced Programming', status: 'ready', type: 'claude' },
68
+ ];
69
+
70
+ return (
71
+ <div className="page" style={{ display: 'flex', flexDirection: 'column', height: '100%', overflowY: 'auto' }}>
72
+ <div className="page-header" style={{ marginBottom: 24 }}>
73
+ <h1>Sovereign Stack Deployments</h1>
74
+ <p>Monitor your machine services, pull kernel intelligence models, and stream activity logs</p>
75
+ </div>
76
+
77
+ {error && (
78
+ <div className="badge badge-error" style={{ marginBottom: 20, display: 'inline-flex', padding: '6px 12px', background: 'rgba(239, 68, 68, 0.1)', border: '1px solid #ef4444', color: '#f87171' }}>
79
+ ⚠️ {error}
80
+ </div>
81
+ )}
82
+ {success && (
83
+ <div className="badge badge-success" style={{ marginBottom: 20, display: 'inline-flex', padding: '6px 12px', background: 'rgba(16, 185, 129, 0.1)', border: '1px solid #10b981', color: '#34d399' }}>
84
+ ✓ {success}
85
+ </div>
86
+ )}
87
+
88
+ {loading ? (
89
+ <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-secondary)' }}>
90
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" style={{ animation: 'spin 1s linear infinite', marginBottom: 12 }}>
91
+ <path d="M21 12a9 9 0 1 1-6.219-8.56"/>
92
+ </svg>
93
+ <div>Checking deployment gateways...</div>
94
+ </div>
95
+ ) : (
96
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 24, flex: 1, minHeight: 0 }}>
97
+
98
+ {/* Services Status Panels Grid */}
99
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: 20 }}>
100
+
101
+ {/* Service 1: Cognitive Daemon */}
102
+ <div className="card" style={{ padding: 20, background: 'rgba(18, 18, 26, 0.6)', border: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
103
+ <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
104
+ <span style={{ fontSize: 24 }}>⚙️</span>
105
+ <div>
106
+ <h4 style={{ fontSize: 13, fontWeight: 600 }}>Reasoning Daemon</h4>
107
+ <p style={{ fontSize: 10, color: 'var(--text-tertiary)' }}>System 2 scheduler loop</p>
108
+ </div>
109
+ </div>
110
+ <span className="badge" style={{
111
+ background: health?.daemon === 'running' ? 'rgba(16, 185, 129, 0.1)' : 'rgba(239, 68, 68, 0.1)',
112
+ color: health?.daemon === 'running' ? '#10b981' : '#ef4444',
113
+ border: health?.daemon === 'running' ? '1px solid rgba(16, 185, 129, 0.2)' : '1px solid rgba(239, 68, 68, 0.2)'
114
+ }}>
115
+ {health?.daemon || 'dead'}
116
+ </span>
117
+ </div>
118
+
119
+ {/* Service 2: Caddy SSL Tunnel */}
120
+ <div className="card" style={{ padding: 20, background: 'rgba(18, 18, 26, 0.6)', border: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
121
+ <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
122
+ <span style={{ fontSize: 24 }}>🔒</span>
123
+ <div>
124
+ <h4 style={{ fontSize: 13, fontWeight: 600 }}>Caddy SSL Proxy</h4>
125
+ <p style={{ fontSize: 10, color: 'var(--text-tertiary)' }}>Reverse proxy tunnel gateway</p>
126
+ </div>
127
+ </div>
128
+ <span className="badge" style={{ background: 'rgba(16, 185, 129, 0.1)', color: '#10b981', border: '1px solid rgba(16, 185, 129, 0.2)' }}>
129
+ active
130
+ </span>
131
+ </div>
132
+
133
+ {/* Service 3: Cloudflare Secure Tunnel */}
134
+ <div className="card" style={{ padding: 20, background: 'rgba(18, 18, 26, 0.6)', border: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
135
+ <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
136
+ <span style={{ fontSize: 24 }}>☁️</span>
137
+ <div>
138
+ <h4 style={{ fontSize: 13, fontWeight: 600 }}>Cloudflare Secure Tunnel</h4>
139
+ <p style={{ fontSize: 10, color: 'var(--text-tertiary)' }}>Expose tunnel behind SSL</p>
140
+ </div>
141
+ </div>
142
+ <span className="badge" style={{ background: 'rgba(16, 185, 129, 0.1)', color: '#10b981', border: '1px solid rgba(16, 185, 129, 0.2)' }}>
143
+ active
144
+ </span>
145
+ </div>
146
+
147
+ </div>
148
+
149
+ {/* Model Catalog & Pull form */}
150
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(360px, 1fr))', gap: 24 }}>
151
+
152
+ {/* Catalog */}
153
+ <div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)', display: 'flex', flexDirection: 'column', gap: 16 }}>
154
+ <div style={{ display: 'flex', alignItems: 'center', gap: 10, borderBottom: '1px solid var(--border)', paddingBottom: 12 }}>
155
+ <span style={{ fontSize: 20 }}>🧠</span>
156
+ <div>
157
+ <h3 style={{ fontSize: 14, fontWeight: 600 }}>Kernel LLM Catalog</h3>
158
+ <p style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>Provisioned local intelligence engines</p>
159
+ </div>
160
+ </div>
161
+
162
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
163
+ {MODELS_CATALOG.map((m) => {
164
+ const activeModel = health?.ollama_models?.includes(m.id) || m.type !== 'ollama';
165
+ return (
166
+ <div key={m.id} style={{ display: 'flex', alignItems: 'center', gap: 12, background: 'var(--bg-tertiary)', padding: 12, borderRadius: 8, border: '1px solid var(--border)' }}>
167
+ <div style={{ flex: 1 }}>
168
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
169
+ <span style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-primary)' }}>{m.name}</span>
170
+ <span className="badge" style={{
171
+ fontSize: 9,
172
+ padding: '2px 6px',
173
+ background: activeModel ? 'rgba(0, 206, 201, 0.1)' : 'rgba(255, 255, 255, 0.04)',
174
+ color: activeModel ? '#00cec9' : 'var(--text-tertiary)',
175
+ border: activeModel ? '1px solid rgba(0, 206, 201, 0.2)' : '1px solid var(--border)'
176
+ }}>
177
+ {activeModel ? 'active' : 'available'}
178
+ </span>
179
+ </div>
180
+ <div style={{ fontSize: 10, color: 'var(--text-secondary)', marginTop: 2 }}>{m.purpose}</div>
181
+ <div style={{ fontSize: 9, color: 'var(--text-tertiary)', marginTop: 4 }}>ID: {m.id} • Size: {m.size}</div>
182
+ </div>
183
+ </div>
184
+ );
185
+ })}
186
+ </div>
187
+ </div>
188
+
189
+ {/* Pull Model Form */}
190
+ <div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)', display: 'flex', flexDirection: 'column', gap: 20 }}>
191
+ <div style={{ display: 'flex', alignItems: 'center', gap: 10, borderBottom: '1px solid var(--border)', paddingBottom: 12 }}>
192
+ <span style={{ fontSize: 20 }}>📡</span>
193
+ <div>
194
+ <h3 style={{ fontSize: 14, fontWeight: 600 }}>Pull Catalog Model</h3>
195
+ <p style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>Trigger high-performance model downloads in background</p>
196
+ </div>
197
+ </div>
198
+
199
+ <form onSubmit={handlePullModel} style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
200
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
201
+ <label htmlFor="model_pull" style={{ fontSize: 12, fontWeight: 500 }}>Select Catalog Model</label>
202
+ <select
203
+ id="model_pull"
204
+ value={modelToPull}
205
+ onChange={(e) => setModelToPull(e.target.value)}
206
+ style={{
207
+ background: 'var(--bg-tertiary)',
208
+ color: 'var(--text-primary)',
209
+ border: '1px solid var(--border)',
210
+ padding: '8px 12px',
211
+ borderRadius: 6,
212
+ outline: 'none',
213
+ fontSize: 13
214
+ }}
215
+ >
216
+ <option value="gemma4:26b">Gemma 4 (26B Parameter) — 15.6 GB</option>
217
+ <option value="gemma5:32b">Gemma 5 Pro (32B Parameter) — 19.2 GB</option>
218
+ <option value="llama3:70b">Llama 3 (70B Parameter) — 40 GB</option>
219
+ </select>
220
+ </div>
221
+
222
+ <button
223
+ type="submit"
224
+ disabled={pulling}
225
+ style={{
226
+ background: 'linear-gradient(135deg, var(--accent), var(--accent-hover))',
227
+ color: '#fff',
228
+ padding: '10px',
229
+ borderRadius: 6,
230
+ fontWeight: 500,
231
+ border: 'none',
232
+ marginTop: 8,
233
+ cursor: pulling ? 'not-allowed' : 'pointer'
234
+ }}
235
+ >
236
+ {pulling ? '⏳ Initiating download...' : '🚀 Trigger Model Download'}
237
+ </button>
238
+ </form>
239
+ </div>
240
+
241
+ </div>
242
+
243
+ {/* Active Terminal Console Logs */}
244
+ <div className="card" style={{ flex: 1, minHeight: 300, background: '#07070a', border: '1px solid var(--border)', display: 'flex', flexDirection: 'column', borderRadius: 10, overflow: 'hidden' }}>
245
+ <div style={{ background: '#0e0e15', padding: '10px 16px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0 }}>
246
+ <span style={{ width: 8, height: 8, borderRadius: '50%', background: '#ff6b6b' }} />
247
+ <span style={{ width: 8, height: 8, borderRadius: '50%', background: '#fdcb6e' }} />
248
+ <span style={{ width: 8, height: 8, borderRadius: '50%', background: '#00cec9' }} />
249
+ <span style={{ fontSize: 11, color: 'var(--text-tertiary)', fontFamily: 'var(--font-mono)', marginLeft: 8 }}>bash — daemon-activity.log — 80x24</span>
250
+ </div>
251
+
252
+ <pre style={{
253
+ flex: 1,
254
+ overflowY: 'auto',
255
+ padding: 16,
256
+ margin: 0,
257
+ fontFamily: 'var(--font-mono)',
258
+ fontSize: 12,
259
+ color: '#34d399',
260
+ background: '#07070a',
261
+ lineHeight: 1.5,
262
+ whiteSpace: 'pre-wrap',
263
+ wordBreak: 'break-all'
264
+ }}>
265
+ {logs}
266
+ <div ref={consoleEndRef} />
267
+ </pre>
268
+ </div>
269
+
270
+ </div>
271
+ )}
272
+
273
+ </div>
274
+ );
275
+ }
@@ -0,0 +1,384 @@
1
+ import { useState, useEffect, useCallback } from "react"
2
+ import { listFiles, listSkills, listScripts, readScript, saveScript, runScript } from "../api"
3
+ import type { FileNode, ScriptFile } from "../types"
4
+
5
+ function formatBytes(bytes: number) {
6
+ if (bytes === 0) return "0 B"
7
+ const k = 1024
8
+ const sizes = ["B", "KB", "MB", "GB"]
9
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
10
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i]
11
+ }
12
+
13
+ export default function FilesPage() {
14
+ const [activeTab, setActiveTab] = useState<"files" | "skills" | "scripts">("files")
15
+ const [files, setFiles] = useState<FileNode[]>([])
16
+ const [skills, setSkills] = useState<FileNode[]>([])
17
+ const [loading, setLoading] = useState(true)
18
+ const [error, setError] = useState("")
19
+
20
+ // Scripts Tab States
21
+ const [scripts, setScripts] = useState<ScriptFile[]>([])
22
+ const [selectedScript, setSelectedScript] = useState<string | null>(null)
23
+ const [scriptContent, setScriptContent] = useState("")
24
+ const [scriptLoading, setScriptLoading] = useState(false)
25
+ const [running, setRunning] = useState(false)
26
+ const [terminalOutput, setTerminalOutput] = useState("")
27
+ const [exitCode, setExitCode] = useState<number | null>(null)
28
+ const [newScriptName, setNewScriptName] = useState("")
29
+ const [isCreatingScript, setIsCreatingScript] = useState(false)
30
+
31
+ const fetchFilesData = useCallback(async () => {
32
+ try {
33
+ const [filesData, skillsData] = await Promise.all([
34
+ listFiles(),
35
+ listSkills()
36
+ ])
37
+ setFiles(filesData)
38
+ setSkills(skillsData)
39
+ setError("")
40
+ } catch (e) {
41
+ setError((e as Error).message)
42
+ } finally {
43
+ setLoading(false)
44
+ }
45
+ }, [])
46
+
47
+ const fetchScriptsList = useCallback(async () => {
48
+ setLoading(true)
49
+ try {
50
+ const list = await listScripts()
51
+ setScripts(list)
52
+ } catch (e) {
53
+ setError(`Failed to list scripts: ${(e as Error).message}`)
54
+ } finally {
55
+ setLoading(false)
56
+ }
57
+ }, [])
58
+
59
+ useEffect(() => {
60
+ if (activeTab === "files" || activeTab === "skills") {
61
+ // eslint-disable-next-line react-hooks/set-state-in-effect -- legitimate file fetch on activeTab change
62
+ void fetchFilesData()
63
+ } else if (activeTab === "scripts") {
64
+ void fetchScriptsList()
65
+ }
66
+ }, [activeTab, fetchFilesData, fetchScriptsList])
67
+
68
+ const handleSelectScript = async (name: string) => {
69
+ setSelectedScript(name)
70
+ setScriptLoading(true)
71
+ setTerminalOutput("")
72
+ setExitCode(null)
73
+ try {
74
+ const data = await readScript(name)
75
+ setScriptContent(data.content || "")
76
+ } catch (e) {
77
+ setError(`Failed to load script content: ${(e as Error).message}`)
78
+ } finally {
79
+ setScriptLoading(false)
80
+ }
81
+ }
82
+
83
+ const handleSaveScript = async () => {
84
+ if (!selectedScript) return
85
+ setScriptLoading(true)
86
+ try {
87
+ await saveScript(selectedScript, scriptContent)
88
+ setError("")
89
+ alert(`Script "${selectedScript}" saved successfully!`)
90
+ void fetchScriptsList()
91
+ } catch (e) {
92
+ setError(`Failed to save script: ${(e as Error).message}`)
93
+ } finally {
94
+ setScriptLoading(false)
95
+ }
96
+ }
97
+
98
+ const handleRunScript = async () => {
99
+ if (!selectedScript) return
100
+ setRunning(true)
101
+ setTerminalOutput("⚡ Spawning sandboxed executor process...\n")
102
+ setExitCode(null)
103
+ try {
104
+ const res = await runScript(selectedScript)
105
+ setTerminalOutput(res.output || "Script executed with no console output.")
106
+ setExitCode(res.exitCode)
107
+ } catch (e) {
108
+ setTerminalOutput(`❌ Execution Error: ${(e as Error).message}`)
109
+ setExitCode(1)
110
+ } finally {
111
+ setRunning(false)
112
+ }
113
+ }
114
+
115
+ const handleCreateScript = async () => {
116
+ if (!newScriptName.trim()) return
117
+ const name = newScriptName.endsWith(".mjs") || newScriptName.endsWith(".js") || newScriptName.endsWith(".py") || newScriptName.endsWith(".sh")
118
+ ? newScriptName
119
+ : `${newScriptName}.mjs`
120
+
121
+ setScriptLoading(true)
122
+ try {
123
+ const defaultCode = name.endsWith(".py")
124
+ ? "#!/usr/bin/env python3\nprint('Hello from SSSS Sovereign Automation!')\n"
125
+ : name.endsWith(".sh")
126
+ ? "#!/bin/bash\necho 'Hello from SSSS Sovereign Automation!'\n"
127
+ : "/**\n * SSSS Skills Automation Script\n */\nconsole.log(\"Hello from SSSS Sovereign Automation!\");\n"
128
+
129
+ await saveScript(name, defaultCode)
130
+ setNewScriptName("")
131
+ setIsCreatingScript(false)
132
+ await fetchScriptsList()
133
+ await handleSelectScript(name)
134
+ } catch (e) {
135
+ setError(`Failed to create script: ${(e as Error).message}`)
136
+ } finally {
137
+ setScriptLoading(false)
138
+ }
139
+ }
140
+
141
+ return (
142
+ <div className="page">
143
+ <div className="page-header" style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end", marginBottom: 20 }}>
144
+ <div>
145
+ <h1>Storage & Automations</h1>
146
+ <p>Manage sovereign files, runtime skills, and automation scripts</p>
147
+ </div>
148
+
149
+ {/* Navigation Tabs */}
150
+ <div style={{ display: "flex", background: "rgba(255,255,255,0.03)", padding: 4, borderRadius: 8, border: "1px solid var(--border)" }}>
151
+ <button
152
+ className={`btn btn-sm ${activeTab === "files" ? "btn-purple" : "btn-ghost"}`}
153
+ onClick={() => setActiveTab("files")}
154
+ style={{ borderRadius: 6, fontSize: 12, padding: "6px 12px" }}
155
+ >
156
+ 📁 Storage
157
+ </button>
158
+ <button
159
+ className={`btn btn-sm ${activeTab === "skills" ? "btn-purple" : "btn-ghost"}`}
160
+ onClick={() => setActiveTab("skills")}
161
+ style={{ borderRadius: 6, fontSize: 12, padding: "6px 12px" }}
162
+ >
163
+ 🧩 Skills
164
+ </button>
165
+ <button
166
+ className={`btn btn-sm ${activeTab === "scripts" ? "btn-cyan" : "btn-ghost"}`}
167
+ onClick={() => { setActiveTab("scripts"); setSelectedScript(null); }}
168
+ style={{ borderRadius: 6, fontSize: 12, padding: "6px 12px" }}
169
+ >
170
+ ⚡ Scripts Editor
171
+ </button>
172
+ </div>
173
+ </div>
174
+
175
+ {error && <div className="badge badge-error" style={{ marginBottom: 16 }}>⚠️ {error}</div>}
176
+
177
+ {activeTab === "files" && (
178
+ <div className="health-log" style={{ background: "rgba(15,23,42,0.45)", backdropFilter: "blur(12px)", border: "1px solid var(--border)", borderRadius: 12, padding: 24 }}>
179
+ <h2 style={{ fontSize: 16, marginBottom: 12, borderBottom: "1px solid var(--border)", paddingBottom: 8, color: "#fff" }}>Sovereign Files (~/.agent/files/)</h2>
180
+ <table style={{ width: "100%", textAlign: "left", borderCollapse: "collapse" }}>
181
+ <thead>
182
+ <tr>
183
+ <th style={{ padding: "8px 12px", borderBottom: "1px solid var(--border)", color: "var(--text-tertiary)" }}>Name</th>
184
+ <th style={{ padding: "8px 12px", borderBottom: "1px solid var(--border)", color: "var(--text-tertiary)" }}>Size</th>
185
+ <th style={{ padding: "8px 12px", borderBottom: "1px solid var(--border)", color: "var(--text-tertiary)" }}>Modified</th>
186
+ </tr>
187
+ </thead>
188
+ <tbody>
189
+ {loading ? (
190
+ <tr><td colSpan={3}><div className="skeleton" style={{ height: 20, margin: "8px 0" }}/></td></tr>
191
+ ) : files.length === 0 ? (
192
+ <tr><td colSpan={3} style={{ textAlign: "center", padding: 24, color: "var(--text-tertiary)" }}>Directory is empty</td></tr>
193
+ ) : (
194
+ files.map(f => (
195
+ <tr key={f.name}>
196
+ <td style={{ padding: "8px 12px", borderBottom: "1px solid var(--border)", color: "#fff" }}>
197
+ {f.isDirectory ? "📁 " : "📄 "}{f.name}
198
+ </td>
199
+ <td style={{ padding: "8px 12px", borderBottom: "1px solid var(--border)" }}>
200
+ {f.isDirectory ? "-" : formatBytes(f.size)}
201
+ </td>
202
+ <td style={{ padding: "8px 12px", borderBottom: "1px solid var(--border)" }}>
203
+ {new Date(f.modified).toLocaleString()}
204
+ </td>
205
+ </tr>
206
+ ))
207
+ )}
208
+ </tbody>
209
+ </table>
210
+ </div>
211
+ )}
212
+
213
+ {activeTab === "skills" && (
214
+ <div className="health-log" style={{ background: "rgba(15,23,42,0.45)", backdropFilter: "blur(12px)", border: "1px solid var(--border)", borderRadius: 12, padding: 24 }}>
215
+ <h2 style={{ fontSize: 16, marginBottom: 12, borderBottom: "1px solid var(--border)", paddingBottom: 8, color: "#fff" }}>Agent Skills (~/.agent/skills/)</h2>
216
+ <table style={{ width: "100%", textAlign: "left", borderCollapse: "collapse" }}>
217
+ <thead>
218
+ <tr>
219
+ <th style={{ padding: "8px 12px", borderBottom: "1px solid var(--border)", color: "var(--text-tertiary)" }}>Name</th>
220
+ <th style={{ padding: "8px 12px", borderBottom: "1px solid var(--border)", color: "var(--text-tertiary)" }}>Size</th>
221
+ <th style={{ padding: "8px 12px", borderBottom: "1px solid var(--border)", color: "var(--text-tertiary)" }}>Modified</th>
222
+ </tr>
223
+ </thead>
224
+ <tbody>
225
+ {loading ? (
226
+ <tr><td colSpan={3}><div className="skeleton" style={{ height: 20, margin: "8px 0" }}/></td></tr>
227
+ ) : skills.length === 0 ? (
228
+ <tr><td colSpan={3} style={{ textAlign: "center", padding: 24, color: "var(--text-tertiary)" }}>No skills found</td></tr>
229
+ ) : (
230
+ skills.map(f => (
231
+ <tr key={f.name}>
232
+ <td style={{ padding: "8px 12px", borderBottom: "1px solid var(--border)", color: "#fff" }}>
233
+ {f.isDirectory ? "🧩 " : "📄 "}{f.name}
234
+ </td>
235
+ <td style={{ padding: "8px 12px", borderBottom: "1px solid var(--border)" }}>
236
+ {f.isDirectory ? "-" : formatBytes(f.size)}
237
+ </td>
238
+ <td style={{ padding: "8px 12px", borderBottom: "1px solid var(--border)" }}>
239
+ {new Date(f.modified).toLocaleString()}
240
+ </td>
241
+ </tr>
242
+ ))
243
+ )}
244
+ </tbody>
245
+ </table>
246
+ </div>
247
+ )}
248
+
249
+ {activeTab === "scripts" && (
250
+ <div className="memory-layout" style={{ display: "flex", gap: 20, height: "calc(100vh - 220px)" }}>
251
+ {/* Left panel: scripts list */}
252
+ <div className="memory-sidebar" style={{ width: 260, flexShrink: 0, overflowY: "auto", display: "flex", flexDirection: "column", gap: 10, background: "rgba(15,23,42,0.3)", borderRadius: 8, padding: 16, border: "1px solid var(--border)" }}>
253
+ <h3 style={{ margin: "0 0 8px 0", color: "#fff", fontSize: 14 }}>Scripts Directory</h3>
254
+
255
+ {isCreatingScript ? (
256
+ <div style={{ display: "flex", flexDirection: "column", gap: 8, marginBottom: 12 }}>
257
+ <input
258
+ type="text"
259
+ placeholder="e.g. sync-rules.mjs"
260
+ value={newScriptName}
261
+ onChange={e => setNewScriptName(e.target.value)}
262
+ style={{ width: "100%", background: "rgba(0,0,0,0.3)", border: "1px solid var(--border)", borderRadius: 4, padding: "6px 10px", color: "#fff", fontSize: 12 }}
263
+ />
264
+ <div style={{ display: "flex", gap: 6 }}>
265
+ <button className="btn btn-purple btn-sm" onClick={handleCreateScript} style={{ fontSize: 11, flex: 1 }}>Add</button>
266
+ <button className="btn btn-ghost btn-sm" onClick={() => setIsCreatingScript(false)} style={{ fontSize: 11, flex: 1 }}>Cancel</button>
267
+ </div>
268
+ </div>
269
+ ) : (
270
+ <button
271
+ className="btn btn-purple btn-sm"
272
+ onClick={() => setIsCreatingScript(true)}
273
+ style={{ width: "100%", fontSize: 12, marginBottom: 12 }}
274
+ >
275
+ ➕ Create Script
276
+ </button>
277
+ )}
278
+
279
+ <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
280
+ {scripts.length === 0 ? (
281
+ <div style={{ color: "var(--text-tertiary)", fontSize: 12, textAlign: "center", padding: 20 }}>No scripts found.</div>
282
+ ) : (
283
+ scripts.map(s => (
284
+ <button
285
+ key={s.name}
286
+ className={`category-btn ${selectedScript === s.name ? "active" : ""}`}
287
+ onClick={() => void handleSelectScript(s.name)}
288
+ style={{ justifyContent: "space-between", width: "100%" }}
289
+ >
290
+ <span>⚡ {s.name}</span>
291
+ <span style={{ fontSize: 9, opacity: 0.6 }}>{formatBytes(s.size)}</span>
292
+ </button>
293
+ ))
294
+ )}
295
+ </div>
296
+ </div>
297
+
298
+ {/* Right panel: script code editor & terminal runner */}
299
+ <div style={{ flex: 1, display: "flex", flexDirection: "column", gap: 16, overflowY: "auto" }}>
300
+ {selectedScript ? (
301
+ <div style={{ display: "flex", flexDirection: "column", gap: 16, height: "100%" }}>
302
+ {/* Script actions header */}
303
+ <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", background: "rgba(15,23,42,0.45)", backdropFilter: "blur(12px)", border: "1px solid var(--border)", padding: "12px 20px", borderRadius: 8 }}>
304
+ <div>
305
+ <h3 style={{ margin: 0, color: "#fff", display: "flex", alignItems: "center", gap: 8 }}>
306
+ <code>{selectedScript}</code>
307
+ <span style={{ fontSize: 11, background: "rgba(6,182,212,0.15)", color: "#06b6d4", padding: "2px 8px", borderRadius: 4, textTransform: "uppercase" }}>Automation</span>
308
+ </h3>
309
+ </div>
310
+ <div style={{ display: "flex", gap: 12 }}>
311
+ <button className="btn btn-ghost btn-sm" disabled={scriptLoading} onClick={handleSaveScript}>💾 Save Script</button>
312
+ <button className="btn btn-cyan btn-sm" disabled={running} onClick={handleRunScript}>
313
+ {running ? "⚡ Executing..." : "⚡ Run Script"}
314
+ </button>
315
+ </div>
316
+ </div>
317
+
318
+ {/* Editor textarea and Terminal outputs split screen */}
319
+ <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16, flex: 1, minHeight: 400 }}>
320
+ {/* Visual Dark Editor */}
321
+ <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
322
+ <div style={{ fontSize: 11, textTransform: "uppercase", color: "var(--text-tertiary)", letterSpacing: 0.5 }}>Source Code Editor</div>
323
+ <textarea
324
+ disabled={scriptLoading}
325
+ value={scriptContent}
326
+ onChange={e => setScriptContent(e.target.value)}
327
+ style={{
328
+ flex: 1,
329
+ background: "#030712",
330
+ border: "1px solid var(--border)",
331
+ borderRadius: 8,
332
+ padding: 16,
333
+ color: "#10b981",
334
+ fontFamily: "monospace",
335
+ fontSize: 13,
336
+ lineHeight: 1.6,
337
+ resize: "none",
338
+ outline: "none"
339
+ }}
340
+ />
341
+ </div>
342
+
343
+ {/* Terminal stdout visual Console */}
344
+ <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
345
+ <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
346
+ <span style={{ fontSize: 11, textTransform: "uppercase", color: "var(--text-tertiary)", letterSpacing: 0.5 }}>Dynamic Output Terminal</span>
347
+ {exitCode !== null && (
348
+ <span style={{ fontSize: 11, background: exitCode === 0 ? "rgba(34,197,94,0.15)" : "rgba(239,68,68,0.15)", color: exitCode === 0 ? "#22c55e" : "#ef4444", padding: "1px 6px", borderRadius: 4 }}>
349
+ Exit Code: {exitCode}
350
+ </span>
351
+ )}
352
+ </div>
353
+ <pre style={{
354
+ flex: 1,
355
+ background: "#020617",
356
+ border: "1px solid #1e293b",
357
+ borderRadius: 8,
358
+ padding: 16,
359
+ color: "#38bdf8",
360
+ fontFamily: "monospace",
361
+ fontSize: 13,
362
+ lineHeight: 1.6,
363
+ overflowY: "auto",
364
+ margin: 0,
365
+ whiteSpace: "pre-wrap"
366
+ }}>
367
+ {terminalOutput || "Interactive runner ready. Click ⚡ Run Script to execute automations."}
368
+ </pre>
369
+ </div>
370
+ </div>
371
+ </div>
372
+ ) : (
373
+ <div style={{ flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", border: "1px dashed var(--border)", borderRadius: 8, color: "var(--text-tertiary)" }}>
374
+ <span style={{ fontSize: 40, marginBottom: 12 }}>⚡</span>
375
+ <h3>Automations Hub</h3>
376
+ <p>Select a script from the sidebar directory to edit, update, or run securely inside the local sandbox environment.</p>
377
+ </div>
378
+ )}
379
+ </div>
380
+ </div>
381
+ )}
382
+ </div>
383
+ )
384
+ }