total-recall-brain 3.13.0 → 3.13.2

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 (152) hide show
  1. package/README.md +91 -108
  2. package/bin/antigravity.mjs +81 -52
  3. package/bin/total-recall.mjs +32 -57
  4. package/extension/background.js +1 -1
  5. package/extension/options/options.js +9 -3
  6. package/extension/sidepanel/sidepanel.js +0 -2
  7. package/frontend/index.html +4 -3
  8. package/frontend/package.json +1 -0
  9. package/frontend/src/App.tsx +109 -152
  10. package/frontend/src/api.ts +605 -23
  11. package/frontend/src/components/BrainSelector.tsx +211 -199
  12. package/frontend/src/components/DocumentEditorModal.tsx +57 -0
  13. package/frontend/src/components/DocumentTable.tsx +47 -0
  14. package/frontend/src/components/Graph3D.tsx +120 -71
  15. package/frontend/src/components/UsageChart.tsx +3 -3
  16. package/frontend/src/components/brand/BrandMark.tsx +95 -0
  17. package/frontend/src/index.css +286 -86
  18. package/frontend/src/pages/ApiKeysPage.tsx +1878 -215
  19. package/frontend/src/pages/ChatPage.tsx +103 -45
  20. package/frontend/src/pages/DesignDocsPage.tsx +5 -5
  21. package/frontend/src/pages/FilesPage.tsx +10 -6
  22. package/frontend/src/pages/GraphPage.tsx +5 -3
  23. package/frontend/src/pages/HelpPage.tsx +2 -2
  24. package/frontend/src/pages/InboxPage.tsx +82 -0
  25. package/frontend/src/pages/InstructionsPage.tsx +1 -1
  26. package/frontend/src/pages/IntegrationsPage.tsx +21 -10
  27. package/frontend/src/pages/LoginPage.tsx +95 -77
  28. package/frontend/src/pages/MemoryPage.tsx +31 -8
  29. package/frontend/src/pages/OnboardingPage.tsx +690 -0
  30. package/frontend/src/pages/OpenWikiPage.tsx +412 -323
  31. package/frontend/src/pages/SettingsPage.tsx +825 -922
  32. package/frontend/src/pages/SkillsPage.tsx +272 -147
  33. package/frontend/src/pages/UsagePage.tsx +23 -31
  34. package/frontend/src/pages/VaultPage.tsx +223 -0
  35. package/frontend/src/types.ts +13 -0
  36. package/package.json +4 -4
  37. package/scaffold/.agent/skills/total-recall/SKILL.md +43 -4
  38. package/scaffold/.agent/skills/total-recall/modules/README.md +10 -0
  39. package/scaffold/.agent/skills/total-recall/modules/agents/README.md +5 -0
  40. package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents → modules/agents}/agents.yml +8 -0
  41. package/scaffold/.agent/skills/total-recall/modules/skill-deploy/README.md +9 -0
  42. package/scaffold/.agent/skills/total-recall/openwiki/README.md +10 -0
  43. package/scaffold/.agent/skills/total-recall/openwiki/architecture.md +5 -0
  44. package/scaffold/.agent/skills/total-recall/openwiki/ide.md +10 -0
  45. package/scaffold/.agent/skills/total-recall/openwiki/memory.md +11 -0
  46. package/scaffold/.agent/skills/total-recall/openwiki/secrets.md +7 -0
  47. package/scaffold/.agent/skills/total-recall/openwiki/skills.md +7 -0
  48. package/src/cli/brain.mjs +63 -5
  49. package/src/cli/connect.mjs +11 -19
  50. package/src/cli/daemon.mjs +5 -4
  51. package/src/cli/deploy-ui.mjs +8 -3
  52. package/src/cli/deploy.mjs +84 -27
  53. package/src/cli/ingest/index.mjs +1 -1
  54. package/src/cli/init.mjs +63 -34
  55. package/src/cli/remember.mjs +3 -3
  56. package/src/cli/secret.mjs +812 -0
  57. package/src/cli/setup.mjs +34 -7
  58. package/src/cli/share.mjs +2 -2
  59. package/src/cli/skill.mjs +396 -35
  60. package/src/cli/sync.mjs +4 -4
  61. package/src/cli/task.mjs +287 -0
  62. package/src/core/clarity-rewriter.mjs +5 -5
  63. package/src/core/config.mjs +44 -2
  64. package/src/core/conflict-detector.mjs +5 -5
  65. package/src/core/crons.mjs +54 -0
  66. package/src/core/daemon-loop.mjs +87 -235
  67. package/src/core/dream.mjs +170 -10
  68. package/src/core/env-import.mjs +456 -0
  69. package/src/core/fact-seeker.mjs +14 -14
  70. package/src/core/import-rules.mjs +20 -7
  71. package/src/core/inference-engine.mjs +3 -3
  72. package/src/core/migrate.mjs +14 -8
  73. package/src/core/okf-adapter.mjs +2 -2
  74. package/src/core/operation-validator.mjs +39 -331
  75. package/src/core/optimizer.mjs +7 -0
  76. package/src/core/post-mortem.mjs +2 -2
  77. package/src/core/project-brain.mjs +349 -0
  78. package/src/core/provider-catalog.mjs +296 -0
  79. package/src/core/quick-capture.mjs +1 -1
  80. package/src/core/remote-vault-sync.mjs +194 -0
  81. package/src/core/research-queue.mjs +7 -11
  82. package/src/core/runtime.mjs +33 -9
  83. package/src/core/scheduler.mjs +59 -5
  84. package/src/core/schema.mjs +14 -0
  85. package/src/core/search.mjs +1 -1
  86. package/src/core/secrets-env-export.mjs +323 -0
  87. package/src/core/secrets-rotate.mjs +245 -0
  88. package/src/core/secrets-store.mjs +645 -0
  89. package/src/core/semantic-index.mjs +3 -3
  90. package/src/core/skills-registry.mjs +1062 -0
  91. package/src/core/source-adapters.mjs +4 -1
  92. package/src/core/ssss-host-extension.mjs +94 -0
  93. package/src/core/ssss-kernel-bridge.mjs +530 -0
  94. package/src/core/surface.mjs +14 -0
  95. package/src/core/task-envelope.mjs +298 -0
  96. package/src/core/task-executors.mjs +600 -0
  97. package/src/core/validated-write.mjs +124 -56
  98. package/src/core/vault.mjs +21 -34
  99. package/src/core/webauthn-store.mjs +272 -0
  100. package/src/server/api.mjs +26 -16
  101. package/src/server/auth.mjs +51 -0
  102. package/src/server/index.mjs +3 -3
  103. package/src/server/keys.mjs +30 -4
  104. package/src/server/rest.mjs +198 -25
  105. package/src/server/route-manifest.json +156 -0
  106. package/src/server/routes/_shared.mjs +40 -1
  107. package/src/server/routes/docs.mjs +260 -0
  108. package/src/server/routes/memory.mjs +145 -32
  109. package/src/server/routes/research.mjs +30 -0
  110. package/src/server/routes/secrets.mjs +470 -0
  111. package/src/server/routes/share.mjs +2 -2
  112. package/src/server/routes/skills.mjs +452 -54
  113. package/src/server/routes/sync.mjs +48 -0
  114. package/src/server/routes/webauthn.mjs +184 -0
  115. package/src/server/update-route-manifest.mjs +39 -0
  116. package/templates/onboarding-interview.md +32 -22
  117. package/templates/openwiki/README.md +10 -0
  118. package/templates/openwiki/architecture.md +5 -0
  119. package/templates/openwiki/ide.md +10 -0
  120. package/templates/openwiki/memory.md +11 -0
  121. package/templates/openwiki/secrets.md +7 -0
  122. package/templates/openwiki/skills.md +7 -0
  123. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/SKILL.md +0 -121
  124. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/antigravity.txt +0 -20
  125. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/claude.txt +0 -58
  126. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/codex.txt +0 -81
  127. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/gemini.txt +0 -44
  128. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/scripts/.gitkeep +0 -0
  129. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/subagents/.gitkeep +0 -0
  130. package/scaffold/.agent/skills/total-recall/skills/tr-research/SKILL.md +0 -338
  131. package/scaffold/.agent/skills/total-recall/skills/tr-skill/SKILL.md +0 -282
  132. package/scaffold/.agent/skills/total-recall/skills/tr-skill/assets/skill-template/SKILL.md +0 -20
  133. package/scaffold/.agent/skills/total-recall/skills/tr-skill/evals/evals.json +0 -20
  134. package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-add.sh +0 -14
  135. package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-delete.sh +0 -14
  136. package/scaffold/.agent/skills/total-recall/skills/tr-skill/learnings/.gitkeep +0 -0
  137. package/scaffold/.agent/skills/total-recall/skills/tr-skill/references/.gitkeep +0 -0
  138. package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/.gitkeep +0 -0
  139. package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/create-skill.sh +0 -65
  140. package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/enforce-skill-optimization.mjs +0 -135
  141. package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/skill-watcher.mjs +0 -64
  142. package/scaffold/.agent/skills/total-recall/skills/tr-skill/subagents/audit-all-skills.md +0 -62
  143. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/SKILL.md +0 -298
  144. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/evals/evals.json +0 -17
  145. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/CHANGELOG.md +0 -124
  146. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/ssss-spec.md +0 -1325
  147. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/scripts/validate-schema.mjs +0 -232
  148. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/subagents/audit-ssss-node.md +0 -9
  149. /package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents/evals → modules/skill-deploy/scripts}/.gitkeep +0 -0
  150. /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/find-skills.mjs +0 -0
  151. /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/install-skill.mjs +0 -0
  152. /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/scan-skill.mjs +0 -0
@@ -1,1039 +1,942 @@
1
- import { useState, useEffect } from 'react';
2
- import { fetchConfigJson, saveConfigJson, fetchConfig, saveConfig } from '../api';
1
+ import React, { useState, useEffect } from 'react';
2
+ import { fetchConfigJson, saveConfigJson, runSandbox, fetchHealth, runAgentDiagnostics } from '../api';
3
+ import type { HealthData } from '../types';
3
4
  import type { ConfigJson } from '../types';
4
5
 
5
6
  export default function SettingsPage() {
6
- const [viewMode, setViewMode] = useState<'visual' | 'yaml'>('visual');
7
- const [activeYamlTab, setActiveYamlTab] = useState<'frontier.yml' | 'security.yml' | 'budget.yml'>('security.yml');
8
-
9
- // Visual Form State
10
7
  const [configData, setConfigData] = useState<ConfigJson | null>(null);
8
+ const [saveStatus, setSaveStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle');
9
+ const [sandboxLog, setSandboxLog] = useState('');
11
10
 
12
- // YAML Text Area State
13
- const [yamlContent, setYamlContent] = useState('');
14
-
15
- // UI States
16
- const [loading, setLoading] = useState(true);
17
- const [saving, setSaving] = useState(false);
18
- const [error, setError] = useState<string | null>(null);
19
- const [success, setSuccess] = useState(false);
20
-
21
- // Load JSON for Visual Form
22
- const loadVisualConfig = async () => {
23
- setLoading(true);
24
- setError(null);
25
- try {
26
- const data = await fetchConfigJson();
27
- // Enforce default structures if missing
28
- if (!data.security) data.security = {};
29
- if (!data.security.privacy) data.security.privacy = { enforce_local_only: true, allow_frontier_export: 'ask_per_skill' };
30
- if (!data.security.dashboard) data.security.dashboard = { session_ttl_hours: 24 };
31
- if (!data.security.bind) data.security.bind = { host: '127.0.0.1', port: 3000, allow_public_bind: false };
32
- if (!data.security.network) data.security.network = { require_https: true, public_health: false, allowed_origins: [] };
33
- if (!data.security.rate_limits) data.security.rate_limits = { api_requests_per_minute: 60, sandbox_requests_per_minute: 10, ingest_requests_per_minute: 120 };
34
- if (!data.budget) data.budget = {};
35
- if (!data.budget.budget) data.budget.budget = { daily_cap_usd: 5.0, weekly_cap_usd: 25.0, enabled: true };
36
- if (!data.brain) data.brain = {};
37
- if (!data.brain.preferred_agent) data.brain.preferred_agent = 'auto';
38
- if (!data.secrets) data.secrets = {};
39
-
40
- setConfigData(data);
41
- } catch (err: unknown) {
42
- setError((err as Error).message || 'Failed to load configuration.');
43
- } finally {
44
- setLoading(false);
45
- }
46
- };
11
+ const [health, setHealth] = useState<HealthData | null>(null);
12
+ const [runningDiagnostics, setRunningDiagnostics] = useState(false);
13
+ const [diagnosticLogs, setDiagnosticLogs] = useState<string | null>(null);
47
14
 
48
- // Load YAML string for Raw Editor
49
- const loadYamlConfig = async (name: string) => {
50
- setLoading(true);
51
- setError(null);
15
+
16
+ useEffect(() => {
17
+ fetchHealth().then(setHealth).catch(console.error);
18
+ fetchConfigJson()
19
+ .then(data => {
20
+ // Ensure all nested structures exist for controlled inputs
21
+ if (!data.security) data.security = {};
22
+ if (!data.security.dashboard) data.security.dashboard = {};
23
+ if (!data.security.api) data.security.api = {};
24
+ if (!data.security.network) data.security.network = {};
25
+ if (!data.security.bind) data.security.bind = {};
26
+ if (!data.security.rate_limits) data.security.rate_limits = {};
27
+ if (!data.security.sandbox) data.security.sandbox = {};
28
+ if (!data.security.privacy) data.security.privacy = {};
29
+ if (!data.budget) data.budget = { budget: {} };
30
+ if (!data.budget.budget) data.budget.budget = {};
31
+ if (!data.brain) data.brain = {};
32
+ if (!data.secrets) data.secrets = {};
33
+ setConfigData(data);
34
+ })
35
+ .catch(console.error);
36
+ }, []);
37
+
38
+
39
+ const AGENTS_LIST = [
40
+ { id: 'antigravity', name: 'Antigravity (Gemini SDK)', desc: 'Primary core developer agent' },
41
+ { id: 'gemini', name: 'Gemini CLI', desc: 'Direct Gemini assistant binary' },
42
+ { id: 'claude', name: 'Claude Code', desc: 'Anthropic developer CLI wrapper' },
43
+ { id: 'codex', name: 'Codex CLI', desc: 'OpenAI agent binary integration' }
44
+ ];
45
+
46
+ const handleRunDiagnostics = async (e: React.FormEvent) => {
47
+ e.preventDefault();
48
+ setRunningDiagnostics(true);
49
+ setDiagnosticLogs(null);
52
50
  try {
53
- const data = await fetchConfig(name);
54
- setYamlContent(data);
55
- } catch (err: unknown) {
56
- setError((err as Error).message || `Failed to load ${name}.`);
51
+ const res = await runAgentDiagnostics();
52
+ setDiagnosticLogs(res.output);
53
+ fetchHealth().then(setHealth).catch(console.error);
54
+ } catch (err: any) {
55
+ setDiagnosticLogs('Error: ' + err.message);
57
56
  } finally {
58
- setLoading(false);
57
+ setRunningDiagnostics(false);
59
58
  }
60
59
  };
61
60
 
62
- useEffect(() => {
63
- if (viewMode === 'visual') {
64
- // eslint-disable-next-line react-hooks/set-state-in-effect -- legitimate config loader on visual active
65
- void loadVisualConfig();
66
- } else {
67
-
68
- void loadYamlConfig(activeYamlTab);
69
- }
70
- }, [viewMode, activeYamlTab]);
71
-
72
- // Save Visual Form JSON
73
61
  const handleSaveVisual = async () => {
74
62
  if (!configData) return;
75
- setSaving(true);
76
- setError(null);
77
- setSuccess(false);
63
+ setSaveStatus('saving');
78
64
  try {
79
65
  await saveConfigJson(configData);
80
- setSuccess(true);
81
- setTimeout(() => setSuccess(false), 4000);
82
- } catch (err: unknown) {
83
- setError((err as Error).message || 'Failed to save configuration.');
84
- } finally {
85
- setSaving(false);
66
+ setSaveStatus('saved');
67
+ setTimeout(() => setSaveStatus('idle'), 2500);
68
+ } catch (err) {
69
+ console.error(err);
70
+ setSaveStatus('error');
71
+ setTimeout(() => setSaveStatus('idle'), 3000);
86
72
  }
87
73
  };
88
74
 
89
- // Save YAML Text
90
- const handleSaveYaml = async () => {
91
- setSaving(true);
92
- setError(null);
93
- setSuccess(false);
94
- try {
95
- await saveConfig(activeYamlTab, yamlContent);
96
- setSuccess(true);
97
- setTimeout(() => setSuccess(false), 4000);
98
- } catch (err: unknown) {
99
- setError((err as Error).message || `Failed to save ${activeYamlTab}.`);
100
- } finally {
101
- setSaving(false);
102
- }
75
+ const executeSandboxCommand = async (command: string, description: string) => {
76
+ setSandboxLog(`[Running] ${description}...\n$ ${command}`);
77
+ const res = await runSandbox(`
78
+ const { execSync } = require('child_process');
79
+ try {
80
+ const out = execSync('${command}', { encoding: 'utf8' });
81
+ console.log(out);
82
+ } catch (e) {
83
+ console.error(e.stdout || e.message);
84
+ }
85
+ `);
86
+ setSandboxLog(`[Finished] ${description}\n\n` + res.output);
103
87
  };
104
88
 
105
- // Safe mutators for deeply nested properties
106
- const updateSecurityProp = (section: string, prop: string, value: unknown) => {
107
- if (!configData) return;
108
- setConfigData({
109
- ...configData,
110
- security: {
111
- ...configData.security,
112
- [section]: {
113
- ...(configData.security[section] as Record<string, unknown>),
114
- [prop]: value
115
- }
116
- }
117
- });
89
+ // State Update Helpers
90
+ const updateSecurityProp = (key: string, value: any) => {
91
+ setConfigData(prev => prev ? { ...prev, security: { ...prev.security, [key]: value } } : null);
118
92
  };
119
93
 
120
- const updateRootSecurityProp = (prop: string, value: unknown) => {
121
- if (!configData) return;
122
- setConfigData({
123
- ...configData,
124
- security: {
125
- ...configData.security,
126
- [prop]: value
127
- }
94
+ const updateSecurityNested = (category: string, key: string, value: any) => {
95
+ setConfigData(prev => {
96
+ if (!prev) return prev;
97
+ return {
98
+ ...prev,
99
+ security: {
100
+ ...prev.security,
101
+ [category]: {
102
+ ...((prev.security as any)[category] || {}),
103
+ [key]: value
104
+ }
105
+ }
106
+ };
128
107
  });
129
108
  };
130
109
 
131
- const updateBudgetProp = (prop: string, value: unknown) => {
132
- if (!configData) return;
133
- setConfigData({
134
- ...configData,
135
- budget: {
136
- ...configData.budget,
110
+ const updateBudgetProp = (key: string, value: any) => {
111
+ setConfigData(prev => {
112
+ if (!prev) return prev;
113
+ return {
114
+ ...prev,
137
115
  budget: {
138
- ...configData.budget.budget,
139
- [prop]: value
116
+ ...prev.budget,
117
+ budget: {
118
+ ...(prev.budget.budget || {}),
119
+ [key]: value
120
+ }
140
121
  }
141
- }
122
+ };
142
123
  });
143
124
  };
144
125
 
145
- const updateBrainProp = (prop: string, value: unknown) => {
146
- if (!configData) return;
147
- setConfigData({
148
- ...configData,
149
- brain: {
150
- ...configData.brain,
151
- [prop]: value as string
152
- }
153
- });
126
+ const updateBrainProp = (key: string, value: any) => {
127
+ setConfigData(prev => prev ? { ...prev, brain: { ...prev.brain, [key]: value } } : null);
154
128
  };
155
129
 
156
- const updateSecretsProp = (prop: string, value: string) => {
157
- if (!configData) return;
158
- setConfigData({
159
- ...configData,
160
- secrets: {
161
- ...configData.secrets,
162
- [prop]: value
163
- }
164
- });
165
- };
130
+
131
+ const getCsv = (arr: any) => Array.isArray(arr) ? arr.join(', ') : '';
132
+ const setCsv = (val: string) => val.split(',').map(s => s.trim()).filter(Boolean);
133
+
134
+ if (!configData) {
135
+ return (
136
+ <div style={{ padding: 40, color: 'var(--text-secondary)' }}>
137
+ <div className="glass-card" style={{ display: 'inline-block', padding: '12px 24px' }}>
138
+ Loading Settings...
139
+ </div>
140
+ </div>
141
+ );
142
+ }
166
143
 
167
144
  return (
168
- <div className="page" style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
169
- <div className="page-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 16 }}>
145
+ <div className="settings-page-wrapper">
146
+ <style>{`
147
+ .settings-page-wrapper {
148
+ padding: 32px 40px 100px;
149
+ max-width: 1200px;
150
+ margin: 0 auto;
151
+ animation: fade-in 0.4s ease-out;
152
+ }
153
+
154
+ .settings-header-sticky {
155
+ position: sticky;
156
+ top: 0;
157
+ z-index: 50;
158
+ background: rgba(13, 17, 23, 0.85);
159
+ backdrop-filter: blur(24px);
160
+ -webkit-backdrop-filter: blur(24px);
161
+ padding: 24px 0;
162
+ margin-bottom: 32px;
163
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
164
+ display: flex;
165
+ justify-content: space-between;
166
+ align-items: center;
167
+ }
168
+
169
+ .settings-grid {
170
+ display: grid;
171
+ grid-template-columns: repeat(auto-fit, minmax(450px, 1fr));
172
+ gap: 24px;
173
+ }
174
+
175
+ .settings-card {
176
+ background: rgba(23, 32, 51, 0.4);
177
+ border: 1px solid rgba(255, 255, 255, 0.06);
178
+ border-radius: 16px;
179
+ padding: 28px;
180
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
181
+ backdrop-filter: blur(16px);
182
+ -webkit-backdrop-filter: blur(16px);
183
+ transition: all 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
184
+ position: relative;
185
+ overflow: hidden;
186
+ }
187
+
188
+ .settings-card::before {
189
+ content: '';
190
+ position: absolute;
191
+ top: 0; left: 0; right: 0; height: 100%;
192
+ background: radial-gradient(circle at top right, rgba(255,255,255,0.03), transparent 60%);
193
+ pointer-events: none;
194
+ }
195
+
196
+ .settings-card:hover {
197
+ border-color: rgba(255, 255, 255, 0.12);
198
+ box-shadow: 0 16px 48px rgba(0, 0, 0, 0.4);
199
+ }
200
+
201
+ .settings-icon-wrap {
202
+ width: 44px;
203
+ height: 44px;
204
+ border-radius: 12px;
205
+ display: flex;
206
+ align-items: center;
207
+ justify-content: center;
208
+ font-size: 20px;
209
+ border: 1px solid rgba(255,255,255,0.1);
210
+ flex-shrink: 0;
211
+ }
212
+
213
+ .field-col {
214
+ display: flex;
215
+ flex-direction: column;
216
+ gap: 8px;
217
+ margin-top: 20px;
218
+ }
219
+
220
+ .field-row {
221
+ display: flex;
222
+ align-items: center;
223
+ justify-content: space-between;
224
+ margin-top: 20px;
225
+ padding: 12px 16px;
226
+ background: rgba(0, 0, 0, 0.2);
227
+ border-radius: 12px;
228
+ border: 1px solid rgba(255,255,255,0.03);
229
+ transition: border-color 0.2s ease;
230
+ }
231
+
232
+ .field-row:hover {
233
+ border-color: rgba(255,255,255,0.1);
234
+ }
235
+
236
+ .field-label {
237
+ font-size: 13px;
238
+ font-weight: 600;
239
+ color: var(--text-secondary);
240
+ letter-spacing: 0.3px;
241
+ text-transform: uppercase;
242
+ }
243
+
244
+ .settings-input, .settings-select {
245
+ background: rgba(9, 13, 20, 0.6);
246
+ border: 1px solid rgba(255, 255, 255, 0.1);
247
+ color: var(--text-primary);
248
+ padding: 12px 16px;
249
+ border-radius: 10px;
250
+ font-size: 14px;
251
+ transition: all 0.2s ease;
252
+ width: 100%;
253
+ outline: none;
254
+ box-shadow: inset 0 2px 4px rgba(0,0,0,0.2);
255
+ }
256
+
257
+ .settings-input.secret {
258
+ font-family: 'JetBrains Mono', monospace;
259
+ letter-spacing: 1px;
260
+ }
261
+
262
+ .settings-input:focus, .settings-select:focus {
263
+ border-color: var(--accent);
264
+ box-shadow: inset 0 2px 4px rgba(0,0,0,0.2), 0 0 0 3px rgba(59, 130, 246, 0.2);
265
+ }
266
+
267
+ .settings-checkbox {
268
+ appearance: none;
269
+ width: 44px;
270
+ height: 24px;
271
+ background: rgba(255, 255, 255, 0.1);
272
+ border-radius: 20px;
273
+ position: relative;
274
+ cursor: pointer;
275
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
276
+ outline: none;
277
+ border: 1px solid rgba(255,255,255,0.05);
278
+ flex-shrink: 0;
279
+ }
280
+
281
+ .settings-checkbox::after {
282
+ content: '';
283
+ position: absolute;
284
+ top: 2px;
285
+ left: 2px;
286
+ width: 18px;
287
+ height: 18px;
288
+ background: #fff;
289
+ border-radius: 50%;
290
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
291
+ box-shadow: 0 2px 4px rgba(0,0,0,0.3);
292
+ }
293
+
294
+ .settings-checkbox:checked {
295
+ background: var(--accent);
296
+ border-color: var(--accent);
297
+ box-shadow: 0 0 12px rgba(59, 130, 246, 0.4);
298
+ }
299
+
300
+ .settings-checkbox:checked::after {
301
+ transform: translateX(20px);
302
+ }
303
+
304
+ .btn-primary {
305
+ background: linear-gradient(135deg, var(--accent), #2563eb);
306
+ color: #fff;
307
+ border: none;
308
+ padding: 10px 24px;
309
+ border-radius: 8px;
310
+ font-weight: 600;
311
+ font-size: 14px;
312
+ cursor: pointer;
313
+ transition: all 0.2s ease;
314
+ box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
315
+ }
316
+
317
+ .btn-primary:hover {
318
+ transform: translateY(-2px);
319
+ box-shadow: 0 6px 16px rgba(37, 99, 235, 0.4);
320
+ }
321
+
322
+ .btn-secondary {
323
+ background: rgba(255, 255, 255, 0.05);
324
+ color: var(--text-primary);
325
+ border: 1px solid rgba(255, 255, 255, 0.1);
326
+ padding: 8px 16px;
327
+ border-radius: 8px;
328
+ font-weight: 500;
329
+ font-size: 13px;
330
+ cursor: pointer;
331
+ transition: all 0.2s ease;
332
+ }
333
+
334
+ .btn-secondary:hover {
335
+ background: rgba(255, 255, 255, 0.1);
336
+ border-color: rgba(255, 255, 255, 0.2);
337
+ }
338
+
339
+ .terminal-log {
340
+ background: rgba(0, 0, 0, 0.6);
341
+ border: 1px solid rgba(255,255,255,0.08);
342
+ border-radius: 8px;
343
+ padding: 16px;
344
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
345
+ font-size: 12px;
346
+ color: #a8b2d1;
347
+ white-space: pre-wrap;
348
+ max-height: 200px;
349
+ overflow-y: auto;
350
+ margin-top: 16px;
351
+ }
352
+
353
+ @keyframes fade-in {
354
+ from { opacity: 0; transform: translateY(10px); }
355
+ to { opacity: 1; transform: translateY(0); }
356
+ }
357
+ `}</style>
358
+
359
+ <div className="settings-header-sticky">
170
360
  <div>
171
- <h1>System Settings</h1>
172
- <p>Read, write, and toggle UI and dashboard settings directly from the terminal or control panel</p>
361
+ <h1 style={{ fontSize: 28, fontWeight: 700, margin: 0, background: 'linear-gradient(to right, #fff, #a8b2d1)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>
362
+ System Settings
363
+ </h1>
364
+ <p style={{ margin: '4px 0 0', color: 'var(--text-secondary)', fontSize: 14 }}>
365
+ Configure core OS behavior, security, API keys, and integration layers.
366
+ </p>
173
367
  </div>
174
-
175
- {/* Toggle Mode Button */}
176
- <div style={{ display: 'flex', gap: 8, background: 'var(--bg-secondary)', padding: 4, borderRadius: 8, border: '1px solid var(--border)' }}>
177
- <button
178
- onClick={() => setViewMode('visual')}
179
- className="btn btn-sm"
180
- style={{
181
- background: viewMode === 'visual' ? 'var(--accent)' : 'transparent',
182
- color: viewMode === 'visual' ? '#fff' : 'var(--text-secondary)',
183
- borderRadius: 6,
184
- fontSize: 12
185
- }}
186
- >
187
- 🎛️ Control Panel
188
- </button>
189
- <button
190
- onClick={() => setViewMode('yaml')}
191
- className="btn btn-sm"
192
- style={{
193
- background: viewMode === 'yaml' ? 'var(--accent)' : 'transparent',
194
- color: viewMode === 'yaml' ? '#fff' : 'var(--text-secondary)',
195
- borderRadius: 6,
196
- fontSize: 12
197
- }}
198
- >
199
- 📝 Raw YAML Editor
368
+ <div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
369
+ {saveStatus === 'saved' && <span style={{ color: '#34d399', fontSize: 14, fontWeight: 600 }}>✓ Saved successfully</span>}
370
+ {saveStatus === 'error' && <span style={{ color: '#ef4444', fontSize: 14, fontWeight: 600 }}>✗ Save failed</span>}
371
+ <button className="btn-primary" onClick={handleSaveVisual} disabled={saveStatus === 'saving'}>
372
+ {saveStatus === 'saving' ? 'Saving...' : 'Save Configuration'}
200
373
  </button>
201
374
  </div>
202
375
  </div>
203
376
 
204
- {error && (
205
- <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' }}>
206
- ⚠️ {error}
207
- </div>
208
- )}
209
- {success && (
210
- <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' }}>
211
- ✓ Saved successfully! The memory kernel will hot-reload configuration settings instantly.
212
- </div>
213
- )}
214
-
215
- {loading ? (
216
- <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-secondary)' }}>
217
- <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 }}>
218
- <path d="M21 12a9 9 0 1 1-6.219-8.56"/>
219
- </svg>
220
- <div>Retrieving brain configuration state...</div>
221
- </div>
222
- ) : viewMode === 'visual' && configData ? (
223
- /* ==================== VISUAL CONTROL PANEL UI ==================== */
224
- <form onSubmit={(e) => { e.preventDefault(); handleSaveVisual(); }} style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(360px, 1fr))', gap: 24, flex: 1, minHeight: 0, overflowY: 'auto', paddingBottom: 24 }}>
225
-
226
- {/* Card 1: AI Reasoning & Autonomy */}
227
- <div className="card" style={{ padding: 24, display: 'flex', flexDirection: 'column', gap: 20, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)' }}>
228
- <div style={{ display: 'flex', alignItems: 'center', gap: 10, borderBottom: '1px solid var(--border)', paddingBottom: 12 }}>
229
- <span style={{ fontSize: 20 }}>🧠</span>
230
- <div>
231
- <h3 style={{ fontSize: 15, fontWeight: 600 }}>Reasoning & Autonomy</h3>
232
- <p style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>Control AI routing agent behaviors and local boundaries</p>
233
- </div>
377
+ <div className="settings-grid">
378
+
379
+ {/* Network & Binding */}
380
+ <div className="settings-card">
381
+ <div style={{ display: 'flex', alignItems: 'center', gap: 16, borderBottom: '1px solid rgba(255,255,255,0.05)', paddingBottom: 20 }}>
382
+ <div className="settings-icon-wrap" style={{ background: 'linear-gradient(135deg, rgba(59, 130, 246, 0.2), rgba(37, 99, 235, 0.05))', borderColor: 'rgba(59, 130, 246, 0.2)' }}>🌐</div>
383
+ <div>
384
+ <h3 style={{ fontSize: 16, fontWeight: 600, margin: 0 }}>Network & Binding</h3>
385
+ <p style={{ fontSize: 12, color: 'var(--text-tertiary)', margin: '4px 0 0' }}>Configure server host and ports</p>
234
386
  </div>
387
+ </div>
235
388
 
236
- {/* YOLO Mode Switch */}
237
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
238
- <div>
239
- <label htmlFor="yolo_mode" style={{ fontSize: 13, fontWeight: 500, display: 'block' }}>YOLO Mode (Fully Autonomous)</label>
240
- <span style={{ fontSize: 11, color: 'var(--text-secondary)' }}>Execute workflows without prompt confirmation gates</span>
241
- </div>
389
+ <div style={{ display: 'flex', gap: 16 }}>
390
+ <div className="field-col" style={{ flex: 1 }}>
391
+ <label htmlFor="bind_host" className="field-label">Bind Host</label>
242
392
  <input
243
- id="yolo_mode"
244
- type="checkbox"
245
- checked={configData.security.yolo_mode || false}
246
- onChange={(e) => updateRootSecurityProp('yolo_mode', e.target.checked)}
247
- style={{ width: 44, height: 22, cursor: 'pointer', accentColor: 'var(--accent)' }}
393
+ id="bind_host"
394
+ type="text"
395
+ className="settings-input"
396
+ value={configData.security.bind?.host ?? '127.0.0.1'}
397
+ onChange={(e) => updateSecurityNested('bind', 'host', e.target.value)}
248
398
  />
249
399
  </div>
250
-
251
- {/* Local Privacy Switch */}
252
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
253
- <div>
254
- <label htmlFor="enforce_local_only" style={{ fontSize: 13, fontWeight: 500, display: 'block' }}>Local Privacy Isolation</label>
255
- <span style={{ fontSize: 11, color: 'var(--text-secondary)' }}>Enforce local execution; never stream to cloud APIs</span>
256
- </div>
400
+ <div className="field-col" style={{ width: 100 }}>
401
+ <label htmlFor="bind_port" className="field-label">Port</label>
257
402
  <input
258
- id="enforce_local_only"
259
- type="checkbox"
260
- checked={configData.security.privacy?.enforce_local_only || false}
261
- onChange={(e) => updateSecurityProp('privacy', 'enforce_local_only', e.target.checked)}
262
- style={{ width: 44, height: 22, cursor: 'pointer', accentColor: 'var(--accent)' }}
403
+ id="bind_port"
404
+ type="number"
405
+ className="settings-input"
406
+ value={configData.security.bind?.port ?? 3000}
407
+ onChange={(e) => updateSecurityNested('bind', 'port', parseInt(e.target.value, 10))}
263
408
  />
264
409
  </div>
410
+ </div>
265
411
 
266
- {/* Export policy */}
267
- <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
268
- <label htmlFor="allow_frontier_export" style={{ fontSize: 13, fontWeight: 500 }}>Frontier API Export Policy</label>
269
- <select
270
- id="allow_frontier_export"
271
- value={configData.security.privacy?.allow_frontier_export || 'ask_per_skill'}
272
- onChange={(e) => updateSecurityProp('privacy', 'allow_frontier_export', e.target.value)}
273
- style={{
274
- background: 'var(--bg-tertiary)',
275
- color: 'var(--text-primary)',
276
- border: '1px solid var(--border)',
277
- padding: '8px 12px',
278
- borderRadius: 6,
279
- outline: 'none',
280
- fontSize: 13
281
- }}
282
- >
283
- <option value="always">Always Route (YOLO Mode)</option>
284
- <option value="ask_per_skill">Ask Per Skill Trigger</option>
285
- <option value="never">Never Route (Fully Air-Gapped)</option>
286
- </select>
412
+ <div className="field-row">
413
+ <div>
414
+ <div style={{ fontSize: 14, fontWeight: 500 }}>Allow Public Bind</div>
415
+ <div style={{ fontSize: 12, color: 'var(--text-tertiary)' }}>Allow listening on 0.0.0.0 interfaces</div>
287
416
  </div>
417
+ <input
418
+ type="checkbox"
419
+ className="settings-checkbox"
420
+ checked={!!configData.security.bind?.allow_public_bind}
421
+ onChange={(e) => updateSecurityNested('bind', 'allow_public_bind', e.target.checked)}
422
+ />
423
+ </div>
288
424
 
289
- {/* Preferred CLI Agent Selector */}
290
- <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
291
- <label htmlFor="preferred_agent" style={{ fontSize: 13, fontWeight: 500 }}>Preferred CLI Agent</label>
292
- <select
293
- id="preferred_agent"
294
- value={configData.brain?.preferred_agent || 'auto'}
295
- onChange={(e) => updateBrainProp('preferred_agent', e.target.value)}
296
- style={{
297
- background: 'var(--bg-tertiary)',
298
- color: 'var(--text-primary)',
299
- border: '1px solid var(--border)',
300
- padding: '8px 12px',
301
- borderRadius: 6,
302
- outline: 'none',
303
- fontSize: 13
304
- }}
305
- >
306
- <option value="auto">Auto (Smart Routing / System Default)</option>
307
- <option value="claude">Claude Code (Anthropic CLI Agent)</option>
308
- <option value="gemini">Gemini CLI (Google Developer Agent)</option>
309
- <option value="codex">OpenAI Codex (OpenAI Workspace Suite)</option>
310
- <option value="antigravity">Antigravity (Local Sovereign Agent)</option>
311
- </select>
312
- <span style={{ fontSize: 10, color: 'var(--text-tertiary)' }}>Elevates selected agent as the active primary for all dispatches</span>
425
+ <div className="field-row">
426
+ <div>
427
+ <div style={{ fontSize: 14, fontWeight: 500 }}>Require HTTPS</div>
428
+ <div style={{ fontSize: 12, color: 'var(--text-tertiary)' }}>Reject insecure HTTP requests</div>
313
429
  </div>
430
+ <input
431
+ type="checkbox"
432
+ className="settings-checkbox"
433
+ checked={!!configData.security.network?.require_https}
434
+ onChange={(e) => updateSecurityNested('network', 'require_https', e.target.checked)}
435
+ />
314
436
  </div>
315
437
 
316
- {/* Card 2: Cost Control & Budget */}
317
- <div className="card" style={{ padding: 24, display: 'flex', flexDirection: 'column', gap: 20, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)' }}>
318
- <div style={{ display: 'flex', alignItems: 'center', gap: 10, borderBottom: '1px solid var(--border)', paddingBottom: 12 }}>
319
- <span style={{ fontSize: 20 }}>🪙</span>
320
- <div>
321
- <h3 style={{ fontSize: 15, fontWeight: 600 }}>Sovereign Cost Supervisor</h3>
322
- <p style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>Track dollar cost consumption limits dynamically</p>
323
- </div>
438
+ <div className="field-row">
439
+ <div>
440
+ <div style={{ fontSize: 14, fontWeight: 500 }}>Public Health Check</div>
441
+ <div style={{ fontSize: 12, color: 'var(--text-tertiary)' }}>Allow pinging /health without auth</div>
324
442
  </div>
443
+ <input
444
+ type="checkbox"
445
+ className="settings-checkbox"
446
+ checked={!!configData.security.network?.public_health}
447
+ onChange={(e) => updateSecurityNested('network', 'public_health', e.target.checked)}
448
+ />
449
+ </div>
325
450
 
326
- {/* Budget enabled Switch */}
327
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
328
- <div>
329
- <label htmlFor="budget_enabled" style={{ fontSize: 13, fontWeight: 500, display: 'block' }}>Supervision Active</label>
330
- <span style={{ fontSize: 11, color: 'var(--text-secondary)' }}>Block LLM requests if cap limits are exceeded</span>
331
- </div>
332
- <input
333
- id="budget_enabled"
334
- type="checkbox"
335
- checked={configData.budget.budget?.enabled !== false}
336
- onChange={(e) => updateBudgetProp('enabled', e.target.checked)}
337
- style={{ width: 44, height: 22, cursor: 'pointer', accentColor: 'var(--accent)' }}
338
- />
339
- </div>
451
+ <div className="field-col">
452
+ <label className="field-label">Allowed Origins (CORS)</label>
453
+ <input
454
+ type="text"
455
+ className="settings-input"
456
+ value={getCsv(configData.security.network?.allowed_origins)}
457
+ onChange={(e) => updateSecurityNested('network', 'allowed_origins', setCsv(e.target.value))}
458
+ placeholder="e.g. https://example.com, https://app.example.com"
459
+ />
460
+ </div>
340
461
 
341
- {/* Daily limit */}
342
- <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
343
- <label htmlFor="daily_cap" style={{ fontSize: 13, fontWeight: 500 }}>Daily Capital Cap ($ USD)</label>
344
- <input
345
- id="daily_cap"
346
- type="number"
347
- step="0.01"
348
- min="0"
349
- value={configData.budget.budget?.daily_cap_usd || ''}
350
- onChange={(e) => updateBudgetProp('daily_cap_usd', Number(e.target.value))}
351
- style={{
352
- background: 'var(--bg-tertiary)',
353
- color: 'var(--text-primary)',
354
- border: '1px solid var(--border)',
355
- padding: '8px 12px',
356
- borderRadius: 6,
357
- outline: 'none',
358
- fontSize: 13
359
- }}
360
- />
361
- </div>
462
+ <div className="field-col">
463
+ <label className="field-label">Trusted Proxies</label>
464
+ <input
465
+ type="text"
466
+ className="settings-input"
467
+ value={getCsv(configData.security.network?.trusted_proxies)}
468
+ onChange={(e) => updateSecurityNested('network', 'trusted_proxies', setCsv(e.target.value))}
469
+ placeholder="e.g. 10.0.0.1, 192.168.1.1"
470
+ />
471
+ </div>
472
+
473
+ <div className="field-col">
474
+ <label className="field-label">API Requests Per Minute (Rate Limit)</label>
475
+ <input
476
+ type="number"
477
+ className="settings-input"
478
+ value={configData.security.rate_limits?.api_requests_per_minute ?? 1200}
479
+ onChange={(e) => updateSecurityNested('rate_limits', 'api_requests_per_minute', parseInt(e.target.value, 10))}
480
+ />
481
+ </div>
362
482
 
363
- {/* Weekly limit */}
364
- <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
365
- <label htmlFor="weekly_cap" style={{ fontSize: 13, fontWeight: 500 }}>Weekly Capital Cap ($ USD)</label>
366
- <input
367
- id="weekly_cap"
368
- type="number"
369
- step="0.01"
370
- min="0"
371
- value={configData.budget.budget?.weekly_cap_usd || ''}
372
- onChange={(e) => updateBudgetProp('weekly_cap_usd', Number(e.target.value))}
373
- style={{
374
- background: 'var(--bg-tertiary)',
375
- color: 'var(--text-primary)',
376
- border: '1px solid var(--border)',
377
- padding: '8px 12px',
378
- borderRadius: 6,
379
- outline: 'none',
380
- fontSize: 13
381
- }}
382
- />
483
+ <div className="field-col">
484
+ <label className="field-label">Sandbox Requests Per Minute</label>
485
+ <input
486
+ type="number"
487
+ className="settings-input"
488
+ value={configData.security.rate_limits?.sandbox_requests_per_minute ?? 60}
489
+ onChange={(e) => updateSecurityNested('rate_limits', 'sandbox_requests_per_minute', parseInt(e.target.value, 10))}
490
+ />
491
+ </div>
492
+
493
+ <div className="field-col">
494
+ <label className="field-label">Ingest Requests Per Minute</label>
495
+ <input
496
+ type="number"
497
+ className="settings-input"
498
+ value={configData.security.rate_limits?.ingest_requests_per_minute ?? 300}
499
+ onChange={(e) => updateSecurityNested('rate_limits', 'ingest_requests_per_minute', parseInt(e.target.value, 10))}
500
+ />
501
+ </div>
502
+
503
+
504
+ </div>
505
+
506
+ {/* Security & Privacy */}
507
+ <div className="settings-card">
508
+ <div style={{ display: 'flex', alignItems: 'center', gap: 16, borderBottom: '1px solid rgba(255,255,255,0.05)', paddingBottom: 20 }}>
509
+ <div className="settings-icon-wrap" style={{ background: 'linear-gradient(135deg, rgba(16, 185, 129, 0.2), rgba(5, 150, 105, 0.05))', borderColor: 'rgba(16, 185, 129, 0.2)' }}>🛡️</div>
510
+ <div>
511
+ <h3 style={{ fontSize: 16, fontWeight: 600, margin: 0 }}>Security & Privacy</h3>
512
+ <p style={{ fontSize: 12, color: 'var(--text-tertiary)', margin: '4px 0 0' }}>Data sovereignty protocols</p>
383
513
  </div>
384
514
  </div>
385
515
 
386
- {/* Card 3: Dashboard & Sessions */}
387
- <div className="card" style={{ padding: 24, display: 'flex', flexDirection: 'column', gap: 20, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)' }}>
388
- <div style={{ display: 'flex', alignItems: 'center', gap: 10, borderBottom: '1px solid var(--border)', paddingBottom: 12 }}>
389
- <span style={{ fontSize: 20 }}>🔒</span>
390
- <div>
391
- <h3 style={{ fontSize: 15, fontWeight: 600 }}>Security & Tunnels</h3>
392
- <p style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>Manage network exposure, binding, and session lifetimes</p>
393
- </div>
516
+ <div className="field-row">
517
+ <div>
518
+ <div style={{ fontSize: 14, fontWeight: 500 }}>YOLO Mode</div>
519
+ <div style={{ fontSize: 12, color: 'var(--text-tertiary)' }}>Disable write protection for AI agents</div>
394
520
  </div>
521
+ <input
522
+ type="checkbox"
523
+ className="settings-checkbox"
524
+ checked={!!configData.security.yolo_mode}
525
+ onChange={(e) => updateSecurityProp('yolo_mode', e.target.checked)}
526
+ />
527
+ </div>
395
528
 
396
- {/* Force Reset */}
397
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
398
- <div>
399
- <label htmlFor="force_reset" style={{ fontSize: 13, fontWeight: 500, display: 'block' }}>Force Password Change</label>
400
- <span style={{ fontSize: 11, color: 'var(--text-secondary)' }}>Prompt admin to reset credentials on next dashboard visit</span>
401
- </div>
402
- <input
403
- id="force_reset"
404
- type="checkbox"
405
- checked={configData.security.dashboard?.force_password_reset || false}
406
- onChange={(e) => updateSecurityProp('dashboard', 'force_password_reset', e.target.checked)}
407
- style={{ width: 44, height: 22, cursor: 'pointer', accentColor: 'var(--accent)' }}
408
- />
529
+ <div className="field-row">
530
+ <div>
531
+ <div style={{ fontSize: 14, fontWeight: 500 }}>Sandbox Enabled</div>
532
+ <div style={{ fontSize: 12, color: 'var(--text-tertiary)' }}>Enforce strict code sandboxing</div>
409
533
  </div>
534
+ <input
535
+ type="checkbox"
536
+ className="settings-checkbox"
537
+ checked={!!(configData.security as any).sandbox?.enabled}
538
+ onChange={(e) => updateSecurityNested('sandbox', 'enabled', e.target.checked)}
539
+ />
540
+ </div>
410
541
 
411
- {/* Enforce HTTPS */}
412
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
413
- <div>
414
- <label htmlFor="require_https" style={{ fontSize: 13, fontWeight: 500, display: 'block' }}>Require HTTPS Tunneling</label>
415
- <span style={{ fontSize: 11, color: 'var(--text-secondary)' }}>Block non-localhost requests that arrive without SSL</span>
416
- </div>
417
- <input
418
- id="require_https"
419
- type="checkbox"
420
- checked={configData.security.network?.require_https || false}
421
- onChange={(e) => updateSecurityProp('network', 'require_https', e.target.checked)}
422
- style={{ width: 44, height: 22, cursor: 'pointer', accentColor: 'var(--accent)' }}
423
- />
542
+ <div className="field-row">
543
+ <div>
544
+ <div style={{ fontSize: 14, fontWeight: 500 }}>Enforce Local Only</div>
545
+ <div style={{ fontSize: 12, color: 'var(--text-tertiary)' }}>Force agents to run offline models</div>
424
546
  </div>
547
+ <input
548
+ type="checkbox"
549
+ className="settings-checkbox"
550
+ checked={!!configData.security.privacy?.enforce_local_only}
551
+ onChange={(e) => updateSecurityNested('privacy', 'enforce_local_only', e.target.checked)}
552
+ />
553
+ </div>
425
554
 
426
- {/* Public Health */}
427
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
428
- <div>
429
- <label htmlFor="public_health" style={{ fontSize: 13, fontWeight: 500, display: 'block' }}>Public Health Route</label>
430
- <span style={{ fontSize: 11, color: 'var(--text-secondary)' }}>Keep /health status checker accessible to public probes</span>
431
- </div>
432
- <input
433
- id="public_health"
434
- type="checkbox"
435
- checked={configData.security.network?.public_health || false}
436
- onChange={(e) => updateSecurityProp('network', 'public_health', e.target.checked)}
437
- style={{ width: 44, height: 22, cursor: 'pointer', accentColor: 'var(--accent)' }}
438
- />
555
+ <div className="field-row">
556
+ <div>
557
+ <div style={{ fontSize: 14, fontWeight: 500 }}>Force Password Reset</div>
558
+ <div style={{ fontSize: 12, color: 'var(--text-tertiary)' }}>Require password change on next login</div>
439
559
  </div>
560
+ <input
561
+ type="checkbox"
562
+ className="settings-checkbox"
563
+ checked={!!configData.security.dashboard?.force_password_reset}
564
+ onChange={(e) => updateSecurityNested('dashboard', 'force_password_reset', e.target.checked)}
565
+ />
566
+ </div>
440
567
 
441
- {/* Session TTL Slider */}
442
- <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
443
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
444
- <label htmlFor="session_ttl" style={{ fontSize: 13, fontWeight: 500 }}>Session Lifetime (TTL)</label>
445
- <span style={{ fontSize: 12, fontWeight: 600, color: 'var(--accent)' }}>{configData.security.dashboard?.session_ttl_hours || 24} hours</span>
446
- </div>
447
- <input
448
- id="session_ttl"
449
- type="range"
450
- min="1"
451
- max="168"
452
- value={configData.security.dashboard?.session_ttl_hours || 24}
453
- onChange={(e) => updateSecurityProp('dashboard', 'session_ttl_hours', Number(e.target.value))}
454
- style={{ cursor: 'pointer', accentColor: 'var(--accent)' }}
455
- />
568
+ <div className="field-row">
569
+ <div>
570
+ <div style={{ fontSize: 14, fontWeight: 500 }}>Allow Static PATs</div>
571
+ <div style={{ fontSize: 12, color: 'var(--text-tertiary)' }}>Enable Personal Access Tokens</div>
456
572
  </div>
573
+ <input
574
+ type="checkbox"
575
+ className="settings-checkbox"
576
+ checked={!!(configData.security as any).api?.allow_static_pats}
577
+ onChange={(e) => updateSecurityNested('api', 'allow_static_pats', e.target.checked)}
578
+ />
457
579
  </div>
458
580
 
459
- {/* Card 4: Network & CORS Limits */}
460
- <div className="card" style={{ padding: 24, display: 'flex', flexDirection: 'column', gap: 20, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)' }}>
461
- <div style={{ display: 'flex', alignItems: 'center', gap: 10, borderBottom: '1px solid var(--border)', paddingBottom: 12 }}>
462
- <span style={{ fontSize: 20 }}>🛡️</span>
463
- <div>
464
- <h3 style={{ fontSize: 15, fontWeight: 600 }}>CORS & Traffic Gateways</h3>
465
- <p style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>Adjust port bindings and enforce request quotas</p>
466
- </div>
581
+ <div className="field-col">
582
+ <label htmlFor="allow_frontier_export" className="field-label">Allow Frontier Export</label>
583
+ <select
584
+ id="allow_frontier_export"
585
+ className="settings-select"
586
+ value={configData.security.privacy?.allow_frontier_export || 'ask_per_skill'}
587
+ onChange={(e) => updateSecurityNested('privacy', 'allow_frontier_export', e.target.value)}
588
+ >
589
+ <option value="always">Always (Not Recommended)</option>
590
+ <option value="ask_per_skill">Ask Per Skill (Recommended)</option>
591
+ <option value="never">Never (Air-Gapped)</option>
592
+ </select>
593
+ </div>
594
+
595
+ <div className="field-col">
596
+ <label htmlFor="session_ttl_hours" className="field-label">Dashboard Session TTL (Hours)</label>
597
+ <input
598
+ id="session_ttl_hours"
599
+ type="number"
600
+ className="settings-input"
601
+ value={configData.security.dashboard?.session_ttl_hours ?? 24}
602
+ onChange={(e) => updateSecurityNested('dashboard', 'session_ttl_hours', parseInt(e.target.value, 10))}
603
+ />
604
+ </div>
605
+ </div>
606
+
607
+ {/* Budget Controls */}
608
+ <div className="settings-card">
609
+ <div style={{ display: 'flex', alignItems: 'center', gap: 16, borderBottom: '1px solid rgba(255,255,255,0.05)', paddingBottom: 20 }}>
610
+ <div className="settings-icon-wrap" style={{ background: 'linear-gradient(135deg, rgba(245, 158, 11, 0.2), rgba(217, 119, 6, 0.05))', borderColor: 'rgba(245, 158, 11, 0.2)' }}>💰</div>
611
+ <div>
612
+ <h3 style={{ fontSize: 16, fontWeight: 600, margin: 0 }}>Budget Limits</h3>
613
+ <p style={{ fontSize: 12, color: 'var(--text-tertiary)', margin: '4px 0 0' }}>Control AI spending dynamically</p>
467
614
  </div>
615
+ </div>
468
616
 
469
- {/* Port input */}
470
- <div style={{ display: 'flex', gap: 12 }}>
471
- <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 6 }}>
472
- <label htmlFor="bind_host" style={{ fontSize: 12, fontWeight: 500 }}>Binding Host</label>
473
- <input
474
- id="bind_host"
475
- type="text"
476
- value={configData.security.bind?.host || ''}
477
- onChange={(e) => updateSecurityProp('bind', 'host', e.target.value)}
478
- style={{
479
- background: 'var(--bg-tertiary)',
480
- color: 'var(--text-primary)',
481
- border: '1px solid var(--border)',
482
- padding: '8px 12px',
483
- borderRadius: 6,
484
- outline: 'none',
485
- fontSize: 13
486
- }}
487
- />
488
- </div>
489
- <div style={{ width: 100, display: 'flex', flexDirection: 'column', gap: 6 }}>
490
- <label htmlFor="bind_port" style={{ fontSize: 12, fontWeight: 500 }}>Port</label>
491
- <input
492
- id="bind_port"
493
- type="number"
494
- value={configData.security.bind?.port || 3000}
495
- onChange={(e) => updateSecurityProp('bind', 'port', Number(e.target.value))}
496
- style={{
497
- background: 'var(--bg-tertiary)',
498
- color: 'var(--text-primary)',
499
- border: '1px solid var(--border)',
500
- padding: '8px 12px',
501
- borderRadius: 6,
502
- outline: 'none',
503
- fontSize: 13
504
- }}
505
- />
506
- </div>
617
+ <div className="field-row">
618
+ <div>
619
+ <div style={{ fontSize: 14, fontWeight: 500 }}>Enable Budget Caps</div>
620
+ <div style={{ fontSize: 12, color: 'var(--text-tertiary)' }}>Enforce hard limits on token spend</div>
507
621
  </div>
622
+ <input
623
+ type="checkbox"
624
+ className="settings-checkbox"
625
+ checked={!!configData.budget.budget?.enabled}
626
+ onChange={(e) => updateBudgetProp('enabled', e.target.checked)}
627
+ />
628
+ </div>
508
629
 
509
- {/* Allowed origins input */}
510
- <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
511
- <label htmlFor="allowed_origins" style={{ fontSize: 13, fontWeight: 500 }}>CORS Allowed Origins</label>
630
+ <div style={{ display: 'flex', gap: 16 }}>
631
+ <div className="field-col" style={{ flex: 1 }}>
632
+ <label htmlFor="daily_cap" className="field-label">Daily Cap ($)</label>
512
633
  <input
513
- id="allowed_origins"
514
- type="text"
515
- placeholder="e.g. http://localhost:5173,http://localhost:3000"
516
- value={Array.isArray(configData.security.network?.allowed_origins) ? configData.security.network.allowed_origins.join(',') : ''}
517
- onChange={(e) => updateSecurityProp('network', 'allowed_origins', e.target.value.split(',').map(s => s.trim()).filter(Boolean))}
518
- style={{
519
- background: 'var(--bg-tertiary)',
520
- color: 'var(--text-primary)',
521
- border: '1px solid var(--border)',
522
- padding: '8px 12px',
523
- borderRadius: 6,
524
- outline: 'none',
525
- fontSize: 13
526
- }}
634
+ id="daily_cap"
635
+ type="number"
636
+ step="0.01"
637
+ className="settings-input"
638
+ value={configData.budget.budget?.daily_cap_usd ?? 5.0}
639
+ onChange={(e) => updateBudgetProp('daily_cap_usd', parseFloat(e.target.value))}
527
640
  />
528
- <span style={{ fontSize: 10, color: 'var(--text-tertiary)' }}>Separate multiple domains with commas</span>
529
641
  </div>
530
-
531
- {/* API Requests rate limit */}
532
- <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
533
- <label htmlFor="api_requests" style={{ fontSize: 13, fontWeight: 500 }}>API Requests Rate Limit (/min)</label>
642
+ <div className="field-col" style={{ flex: 1 }}>
643
+ <label htmlFor="weekly_cap" className="field-label">Weekly Cap ($)</label>
534
644
  <input
535
- id="api_requests"
645
+ id="weekly_cap"
536
646
  type="number"
537
- value={configData.security.rate_limits?.api_requests_per_minute || ''}
538
- onChange={(e) => updateSecurityProp('rate_limits', 'api_requests_per_minute', Number(e.target.value))}
539
- style={{
540
- background: 'var(--bg-tertiary)',
541
- color: 'var(--text-primary)',
542
- border: '1px solid var(--border)',
543
- padding: '8px 12px',
544
- borderRadius: 6,
545
- outline: 'none',
546
- fontSize: 13
547
- }}
647
+ step="0.01"
648
+ className="settings-input"
649
+ value={configData.budget.budget?.weekly_cap_usd ?? 20.0}
650
+ onChange={(e) => updateBudgetProp('weekly_cap_usd', parseFloat(e.target.value))}
548
651
  />
549
652
  </div>
550
653
  </div>
654
+ </div>
551
655
 
552
- {/* Card 5: API Keys & Integrations */}
553
- <div className="card" style={{ padding: 24, display: 'flex', flexDirection: 'column', gap: 20, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)' }}>
554
- <div style={{ display: 'flex', alignItems: 'center', gap: 10, borderBottom: '1px solid var(--border)', paddingBottom: 12 }}>
555
- <span style={{ fontSize: 20 }}>🔑</span>
556
- <div>
557
- <h3 style={{ fontSize: 15, fontWeight: 600 }}>API Keys & Integrations</h3>
558
- <p style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>Manage access keys and tokens for AI models and search providers</p>
559
- </div>
656
+ {/* Brain & Agent Rules */}
657
+ <div className="settings-card">
658
+ <div style={{ display: 'flex', alignItems: 'center', gap: 16, borderBottom: '1px solid rgba(255,255,255,0.05)', paddingBottom: 20 }}>
659
+ <div className="settings-icon-wrap" style={{ background: 'linear-gradient(135deg, rgba(168, 85, 247, 0.2), rgba(147, 51, 234, 0.05))', borderColor: 'rgba(168, 85, 247, 0.2)' }}>🧠</div>
660
+ <div>
661
+ <h3 style={{ fontSize: 16, fontWeight: 600, margin: 0 }}>Brain & Reasoning</h3>
662
+ <p style={{ fontSize: 12, color: 'var(--text-tertiary)', margin: '4px 0 0' }}>Core inference parameters</p>
560
663
  </div>
664
+ </div>
561
665
 
562
- {/* Google Gemini API Key */}
563
- <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
564
- <label htmlFor="google_api_key" style={{ fontSize: 13, fontWeight: 500 }}>Google Gemini API Key</label>
565
- <input
566
- id="google_api_key"
567
- type="password"
568
- placeholder="AIzaSy..."
569
- value={configData.secrets?.google_api_key || ''}
570
- onChange={(e) => updateSecretsProp('google_api_key', e.target.value)}
571
- style={{
572
- background: 'var(--bg-tertiary)',
573
- color: 'var(--text-primary)',
574
- border: '1px solid var(--border)',
575
- padding: '8px 12px',
576
- borderRadius: 6,
577
- outline: 'none',
578
- fontSize: 13
579
- }}
580
- />
581
- </div>
666
+ <div className="field-col">
667
+ <label className="field-label">Brain URL</label>
668
+ <input
669
+ type="text"
670
+ className="settings-input"
671
+ value={configData.brain?.url || ''}
672
+ onChange={(e) => updateBrainProp('url', e.target.value)}
673
+ />
674
+ </div>
582
675
 
583
- {/* Anthropic API Key */}
584
- <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
585
- <label htmlFor="anthropic_api_key" style={{ fontSize: 13, fontWeight: 500 }}>Anthropic API Key (Claude Code)</label>
586
- <input
587
- id="anthropic_api_key"
588
- type="password"
589
- placeholder="sk-ant-..."
590
- value={configData.secrets?.anthropic_api_key || ''}
591
- onChange={(e) => updateSecretsProp('anthropic_api_key', e.target.value)}
592
- style={{
593
- background: 'var(--bg-tertiary)',
594
- color: 'var(--text-primary)',
595
- border: '1px solid var(--border)',
596
- padding: '8px 12px',
597
- borderRadius: 6,
598
- outline: 'none',
599
- fontSize: 13
600
- }}
601
- />
602
- <span style={{ fontSize: 10, color: 'var(--text-tertiary)' }}>Configuring this allows Claude Code CLI to run without prompting for OAuth login.</span>
603
- </div>
676
+ <div className="field-col">
677
+ <label className="field-label">Brain Name</label>
678
+ <input
679
+ type="text"
680
+ className="settings-input"
681
+ value={configData.brain?.name || ''}
682
+ onChange={(e) => updateBrainProp('name', e.target.value)}
683
+ />
684
+ </div>
604
685
 
605
- {/* OpenAI API Key */}
606
- <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
607
- <label htmlFor="openai_api_key" style={{ fontSize: 13, fontWeight: 500 }}>OpenAI API Key (Codex)</label>
686
+ <div style={{ display: 'flex', gap: 16 }}>
687
+ <div className="field-col" style={{ flex: 1 }}>
688
+ <label className="field-label">Role</label>
608
689
  <input
609
- id="openai_api_key"
610
- type="password"
611
- placeholder="sk-proj-..."
612
- value={configData.secrets?.openai_api_key || ''}
613
- onChange={(e) => updateSecretsProp('openai_api_key', e.target.value)}
614
- style={{
615
- background: 'var(--bg-tertiary)',
616
- color: 'var(--text-primary)',
617
- border: '1px solid var(--border)',
618
- padding: '8px 12px',
619
- borderRadius: 6,
620
- outline: 'none',
621
- fontSize: 13
622
- }}
690
+ type="text"
691
+ className="settings-input"
692
+ value={configData.brain?.role || ''}
693
+ onChange={(e) => updateBrainProp('role', e.target.value)}
623
694
  />
624
695
  </div>
625
-
626
- {/* Tavily API Key */}
627
- <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
628
- <label htmlFor="tavily_api_key" style={{ fontSize: 13, fontWeight: 500 }}>Tavily Search API Key</label>
696
+ <div className="field-col" style={{ flex: 1 }}>
697
+ <label className="field-label">Layer</label>
629
698
  <input
630
- id="tavily_api_key"
631
- type="password"
632
- placeholder="tvly-..."
633
- value={configData.secrets?.tavily_api_key || ''}
634
- onChange={(e) => updateSecretsProp('tavily_api_key', e.target.value)}
635
- style={{
636
- background: 'var(--bg-tertiary)',
637
- color: 'var(--text-primary)',
638
- border: '1px solid var(--border)',
639
- padding: '8px 12px',
640
- borderRadius: 6,
641
- outline: 'none',
642
- fontSize: 13
643
- }}
699
+ type="text"
700
+ className="settings-input"
701
+ value={configData.brain?.layer || ''}
702
+ onChange={(e) => updateBrainProp('layer', e.target.value)}
644
703
  />
645
704
  </div>
646
-
647
- {/* Other Search Keys (Brave, Exa, Serper) */}
648
- <details style={{ fontSize: 13, color: 'var(--text-secondary)' }}>
649
- <summary style={{ cursor: 'pointer', fontWeight: 500, outline: 'none', padding: '4px 0' }}>More Provider Keys (Brave, Exa, Serper, GitHub)</summary>
650
- <div style={{ display: 'flex', flexDirection: 'column', gap: 16, marginTop: 12, paddingLeft: 8, borderLeft: '2px solid var(--border)' }}>
651
- {/* Brave Search Key */}
652
- <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
653
- <label htmlFor="brave_api_key" style={{ fontSize: 12, fontWeight: 500 }}>Brave Search API Key</label>
654
- <input
655
- id="brave_api_key"
656
- type="password"
657
- value={configData.secrets?.brave_api_key || ''}
658
- onChange={(e) => updateSecretsProp('brave_api_key', e.target.value)}
659
- style={{
660
- background: 'var(--bg-tertiary)',
661
- color: 'var(--text-primary)',
662
- border: '1px solid var(--border)',
663
- padding: '8px 12px',
664
- borderRadius: 6,
665
- outline: 'none',
666
- fontSize: 13
667
- }}
668
- />
669
- </div>
670
-
671
- {/* Exa Search Key */}
672
- <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
673
- <label htmlFor="exa_api_key" style={{ fontSize: 12, fontWeight: 500 }}>Exa API Key</label>
674
- <input
675
- id="exa_api_key"
676
- type="password"
677
- value={configData.secrets?.exa_api_key || ''}
678
- onChange={(e) => updateSecretsProp('exa_api_key', e.target.value)}
679
- style={{
680
- background: 'var(--bg-tertiary)',
681
- color: 'var(--text-primary)',
682
- border: '1px solid var(--border)',
683
- padding: '8px 12px',
684
- borderRadius: 6,
685
- outline: 'none',
686
- fontSize: 13
687
- }}
688
- />
689
- </div>
690
-
691
- {/* Serper API Key */}
692
- <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
693
- <label htmlFor="serper_api_key" style={{ fontSize: 12, fontWeight: 500 }}>Serper (Google Search API) Key</label>
694
- <input
695
- id="serper_api_key"
696
- type="password"
697
- value={configData.secrets?.serper_api_key || ''}
698
- onChange={(e) => updateSecretsProp('serper_api_key', e.target.value)}
699
- style={{
700
- background: 'var(--bg-tertiary)',
701
- color: 'var(--text-primary)',
702
- border: '1px solid var(--border)',
703
- padding: '8px 12px',
704
- borderRadius: 6,
705
- outline: 'none',
706
- fontSize: 13
707
- }}
708
- />
709
- </div>
710
-
711
- {/* GitHub Token */}
712
- <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
713
- <label htmlFor="github_token" style={{ fontSize: 12, fontWeight: 500 }}>GitHub Personal Access Token</label>
714
- <input
715
- id="github_token"
716
- type="password"
717
- placeholder="ghp_..."
718
- value={configData.secrets?.github_token || ''}
719
- onChange={(e) => updateSecretsProp('github_token', e.target.value)}
720
- style={{
721
- background: 'var(--bg-tertiary)',
722
- color: 'var(--text-primary)',
723
- border: '1px solid var(--border)',
724
- padding: '8px 12px',
725
- borderRadius: 6,
726
- outline: 'none',
727
- fontSize: 13
728
- }}
729
- />
730
- </div>
731
- </div>
732
- </details>
733
705
  </div>
734
706
 
735
- </form>
736
- ) : (
737
- /* ==================== RAW YAML EDITOR UI ==================== */
738
- <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0 }}>
739
-
740
- {/* Sub tabs for config selection */}
741
- <div style={{ display: 'flex', gap: 8, marginBottom: 16, borderBottom: '1px solid var(--border)', paddingBottom: 12, flexShrink: 0 }}>
742
- <button
743
- className={`btn btn-sm ${activeYamlTab === 'frontier.yml' ? 'btn-primary' : 'btn-ghost'}`}
744
- onClick={() => setActiveYamlTab('frontier.yml')}
745
- style={{ fontSize: 12 }}
746
- >
747
- 🌐 Frontier (frontier.yml)
748
- </button>
749
- <button
750
- className={`btn btn-sm ${activeYamlTab === 'security.yml' ? 'btn-primary' : 'btn-ghost'}`}
751
- onClick={() => setActiveYamlTab('security.yml')}
752
- style={{ fontSize: 12 }}
753
- >
754
- 🔒 Security (security.yml)
755
- </button>
756
- <button
757
- className={`btn btn-sm ${activeYamlTab === 'budget.yml' ? 'btn-primary' : 'btn-ghost'}`}
758
- onClick={() => setActiveYamlTab('budget.yml')}
759
- style={{ fontSize: 12 }}
760
- >
761
- 🪙 Budget (budget.yml)
762
- </button>
707
+ <div className="field-col">
708
+ <label className="field-label">Tags (Comma-Separated)</label>
709
+ <input
710
+ type="text"
711
+ className="settings-input"
712
+ value={getCsv(configData.brain?.tags)}
713
+ onChange={(e) => updateBrainProp('tags', setCsv(e.target.value))}
714
+ />
763
715
  </div>
764
716
 
765
- {/* Code text editor */}
766
- <div style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
767
- <textarea
768
- value={yamlContent}
769
- onChange={(e) => setYamlContent(e.target.value)}
770
- style={{
771
- flex: 1,
772
- width: '100%',
773
- fontFamily: 'monospace',
774
- fontSize: 14,
775
- padding: 16,
776
- background: 'var(--bg-secondary)',
777
- color: 'var(--text-primary)',
778
- border: '1px solid var(--border)',
779
- borderRadius: 8,
780
- resize: 'none',
781
- outline: 'none',
782
- lineHeight: 1.5,
783
- whiteSpace: 'pre',
784
- overflowWrap: 'normal',
785
- overflowX: 'auto'
786
- }}
787
- spellCheck={false}
717
+ <div className="field-row">
718
+ <div>
719
+ <div style={{ fontSize: 14, fontWeight: 500 }}>Full Brain Mode</div>
720
+ <div style={{ fontSize: 12, color: 'var(--text-tertiary)' }}>Enable omni-directional indexing</div>
721
+ </div>
722
+ <input
723
+ type="checkbox"
724
+ className="settings-checkbox"
725
+ checked={!!configData.brain?.full_brain}
726
+ onChange={(e) => updateBrainProp('full_brain', e.target.checked)}
788
727
  />
789
728
  </div>
790
- </div>
791
- )}
792
-
793
- {/* CHROME EXTENSION */}
794
- <div style={{
795
- marginTop: 24,
796
- padding: 20,
797
- background: 'linear-gradient(135deg, rgba(108, 92, 231, 0.08), rgba(99, 179, 237, 0.08))',
798
- border: '1px solid rgba(108, 92, 231, 0.25)',
799
- borderRadius: 12
800
- }}>
801
- <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 12 }}>
802
- <span style={{ fontSize: 24 }}>🧩</span>
803
- <div>
804
- <h3 style={{ margin: 0, fontSize: 15, fontWeight: 600, color: 'var(--text-primary)' }}>Chrome Extension</h3>
805
- <p style={{ margin: '2px 0 0', fontSize: 12, color: 'var(--text-secondary)' }}>
806
- Browse with your brain — contextual memory, quick capture, and research from any page
807
- </p>
808
- </div>
809
- </div>
810
- <div style={{ display: 'flex', gap: 12, alignItems: 'center', flexWrap: 'wrap' }}>
811
- <a
812
- href="/api/extension/download"
813
- download
814
- style={{
815
- display: 'inline-flex',
816
- alignItems: 'center',
817
- gap: 8,
818
- padding: '8px 16px',
819
- background: 'linear-gradient(135deg, #6c5ce7, #a29bfe)',
820
- color: '#fff',
821
- borderRadius: 8,
822
- fontWeight: 500,
823
- fontSize: 13,
824
- textDecoration: 'none',
825
- transition: 'opacity 0.2s'
826
- }}
827
- onMouseEnter={e => (e.currentTarget.style.opacity = '0.85')}
828
- onMouseLeave={e => (e.currentTarget.style.opacity = '1')}
829
- >
830
- ⬇ Download Extension
831
- </a>
832
- <span style={{ fontSize: 12, color: 'var(--text-tertiary)' }}>
833
- Unzip → chrome://extensions → Developer mode → Load unpacked
834
- </span>
835
- </div>
836
- </div>
837
729
 
838
- {/* UCW WORKSPACE EXPORT (SSSS §16 .ucw Bundle Format) */}
839
- <div style={{
840
- background: 'rgba(255,255,255,0.03)',
841
- border: '1px solid var(--border)',
842
- borderRadius: 10,
843
- padding: 20,
844
- marginTop: 20,
845
- backdropFilter: 'blur(12px)',
846
- }}>
847
- <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 16 }}>
848
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
849
- <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4" />
850
- <polyline points="7 10 12 15 17 10" />
851
- <line x1="12" y1="15" x2="12" y2="3" />
852
- </svg>
853
- <h3 style={{ margin: 0, fontSize: 16, fontWeight: 600, color: 'var(--text-primary)' }}>UCW Bundle (SSSS §16)</h3>
854
- <span className="badge badge-accent" style={{ fontSize: 10, padding: '2px 8px' }}>UltraChat Workspace</span>
855
- </div>
856
- <p style={{ fontSize: 13, color: 'var(--text-secondary)', marginBottom: 16 }}>
857
- Export your vault as a <code style={{ background: 'rgba(255,255,255,0.06)', padding: '2px 6px', borderRadius: 4, fontFamily: 'var(--font-mono, monospace)' }}>package.ucw.json</code> bundle
858
- per the SSSS §16 Universal Containerized Workspace format. Bundles can be backed up, shared, sold, and re-provisioned across any SSSS-compatible host.
859
- </p>
860
- <div style={{ display: 'flex', gap: 16, flexWrap: 'wrap', alignItems: 'center', marginBottom: 16 }}>
861
- <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
862
- <label style={{ fontSize: 11, fontWeight: 500, color: 'var(--text-secondary)' }}>Export Profile</label>
730
+ <div className="field-col">
731
+ <label htmlFor="preferred_agent" className="field-label">Preferred CLI Agent</label>
863
732
  <select
864
- id="ucw-export-profile"
865
- style={{ background: 'var(--bg-secondary)', color: 'var(--text-primary)', border: '1px solid var(--border)', borderRadius: 6, padding: '6px 12px', fontSize: 12, outline: 'none' }}
733
+ id="preferred_agent"
734
+ className="settings-select"
735
+ value={configData.brain?.preferred_agent || 'auto'}
736
+ onChange={(e) => updateBrainProp('preferred_agent', e.target.value)}
866
737
  >
867
- <option value="backup">backup — full vault snapshot</option>
868
- <option value="template">template — strips tenant_private</option>
869
- <option value="sale">sale — portable marketplace bundle</option>
738
+ <option value="auto">Auto-Select (Recommended)</option>
739
+ <option value="gemini">Gemini (Google Deepmind)</option>
740
+ <option value="claude">Claude (Anthropic)</option>
741
+ <option value="openai">OpenAI (O-Series)</option>
870
742
  </select>
871
743
  </div>
872
- <div style={{ display: 'flex', flexDirection: 'column', gap: 4, flex: '1 1 200px' }}>
873
- <label style={{ fontSize: 11, fontWeight: 500, color: 'var(--text-secondary)' }}>Output Filename</label>
874
- <input
875
- id="ucw-export-filename"
876
- type="text"
877
- defaultValue="package.ucw.json"
878
- style={{ background: 'var(--bg-secondary)', color: 'var(--text-primary)', border: '1px solid var(--border)', borderRadius: 6, padding: '6px 12px', fontSize: 12, outline: 'none', fontFamily: 'var(--font-mono, monospace)' }}
879
- />
880
- </div>
881
- </div>
882
- <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
883
- <button
884
- id="ucw-export-btn"
885
- className="btn btn-primary"
886
- style={{ background: 'linear-gradient(135deg, var(--accent), #5a4bd1)', color: '#fff', borderRadius: 6, fontSize: 13, padding: '8px 20px' }}
887
- onClick={async () => {
888
- try {
889
- const profile = (document.getElementById('ucw-export-profile') as HTMLSelectElement)?.value || 'backup';
890
- const filename = (document.getElementById('ucw-export-filename') as HTMLInputElement)?.value || 'package.ucw.json';
891
- const { runSandbox } = await import('../api');
892
- const result = await runSandbox(`const { execSync } = require("child_process"); try { const out = execSync("npx ssss export . --profile ${profile} --out ${filename} 2>&1", { encoding: "utf-8", cwd: process.env.HOME + "/.agent/skills/total-recall" }); console.log(JSON.stringify({ success: true, output: out })); } catch(e) { console.log(JSON.stringify({ success: false, output: e.stdout || e.message })); }`, 60000);
893
- if (result.success) {
894
- setSuccess(true);
895
- setTimeout(() => setSuccess(false), 5000);
896
- } else {
897
- setError(result.output || 'UCW export failed');
898
- }
899
- } catch (err: unknown) {
900
- setError((err as Error).message || 'Export failed');
901
- }
902
- }}
903
- >
904
- ⬇ Export .ucw Bundle
905
- </button>
906
- <button
907
- id="ucw-validate-btn"
908
- className="btn btn-ghost"
909
- style={{ borderRadius: 6, fontSize: 13, padding: '8px 20px' }}
910
- onClick={async () => {
911
- try {
912
- const { runSandbox } = await import('../api');
913
- const result = await runSandbox('const { execSync } = require("child_process"); try { const out = execSync("npx ssss validate package.ucw.json 2>&1", { encoding: "utf-8", cwd: process.env.HOME + "/.agent/skills/total-recall" }); console.log(JSON.stringify({ success: true, output: out })); } catch(e) { console.log(JSON.stringify({ success: false, output: e.stdout || e.message })); }', 30000);
914
- if (result.success) {
915
- setSuccess(true);
916
- setTimeout(() => setSuccess(false), 5000);
917
- } else {
918
- setError(result.output || 'Validation failed');
919
- }
920
- } catch (err: unknown) {
921
- setError((err as Error).message || 'Validation failed');
922
- }
923
- }}
924
- >
925
- ✓ Validate Bundle
926
- </button>
927
- <button
928
- id="ucw-import-btn"
929
- className="btn btn-ghost"
930
- style={{ borderRadius: 6, fontSize: 13, padding: '8px 20px' }}
931
- onClick={async () => {
932
- try {
933
- const { runSandbox } = await import('../api');
934
- const result = await runSandbox('const { execSync } = require("child_process"); try { const out = execSync("npx ssss import package.ucw.json --vault . 2>&1", { encoding: "utf-8", cwd: process.env.HOME + "/.agent/skills/total-recall" }); console.log(JSON.stringify({ success: true, output: out })); } catch(e) { console.log(JSON.stringify({ success: false, output: e.stdout || e.message })); }', 60000);
935
- if (result.success) {
936
- setSuccess(true);
937
- setTimeout(() => setSuccess(false), 5000);
938
- } else {
939
- setError(result.output || 'Import failed');
940
- }
941
- } catch (err: unknown) {
942
- setError((err as Error).message || 'Import failed');
943
- }
944
- }}
945
- >
946
- ⬆ Import .ucw Bundle
947
- </button>
948
- <button
949
- id="ucw-inspect-btn"
950
- className="btn btn-ghost"
951
- style={{ borderRadius: 6, fontSize: 13, padding: '8px 20px' }}
952
- onClick={async () => {
953
- try {
954
- const { runSandbox } = await import('../api');
955
- const result = await runSandbox('const { execSync } = require("child_process"); try { const out = execSync("npx ssss inspect package.ucw.json --files 2>&1", { encoding: "utf-8", cwd: process.env.HOME + "/.agent/skills/total-recall" }); console.log(JSON.stringify({ success: true, output: out })); } catch(e) { console.log(JSON.stringify({ success: false, output: e.stdout || e.message })); }', 30000);
956
- if (result.success) {
957
- setSuccess(true);
958
- setTimeout(() => setSuccess(false), 5000);
959
- } else {
960
- setError(result.output || 'Inspect failed');
961
- }
962
- } catch (err: unknown) {
963
- setError((err as Error).message || 'Inspect failed');
964
- }
965
- }}
966
- >
967
- 🔍 Inspect Bundle
968
- </button>
969
744
  </div>
970
- <p style={{ fontSize: 11, color: 'var(--text-tertiary)', marginTop: 12 }}>
971
- Powered by <code style={{ fontFamily: 'var(--font-mono, monospace)' }}>@ssss/cli</code> v0.7.0 — <code style={{ fontFamily: 'var(--font-mono, monospace)' }}>npx ssss export|import|validate|inspect</code>
972
- </p>
973
- </div>
974
745
 
975
- {/* SECRETS.ENC KEY VIEWER */}
976
- <div style={{
977
- background: 'rgba(255,255,255,0.03)',
978
- border: '1px solid var(--border)',
979
- borderRadius: 10,
980
- padding: 20,
981
- marginTop: 16,
982
- backdropFilter: 'blur(12px)',
983
- }}>
984
- <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 16 }}>
985
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--warning)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
986
- <rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
987
- <path d="M7 11V7a5 5 0 0110 0v4" />
988
- </svg>
989
- <h3 style={{ margin: 0, fontSize: 16, fontWeight: 600, color: 'var(--text-primary)' }}>Stored Secrets (secrets.enc)</h3>
990
- </div>
991
- <p style={{ fontSize: 13, color: 'var(--text-secondary)', marginBottom: 16 }}>
992
- These credentials are stored in your encrypted secrets file. Values are masked for security.
993
- </p>
994
- <div style={{ display: 'grid', gap: 8 }}>
995
- {configData?.secrets && Object.entries(configData.secrets).map(([key, value]) => (
996
- <div key={key} style={{
997
- display: 'flex',
998
- alignItems: 'center',
999
- justifyContent: 'space-between',
1000
- padding: '8px 12px',
1001
- background: 'rgba(255,255,255,0.02)',
1002
- borderRadius: 6,
1003
- border: '1px solid rgba(255,255,255,0.04)',
1004
- }}>
1005
- <span style={{ fontSize: 13, fontFamily: 'var(--font-mono, monospace)', color: 'var(--text-primary)' }}>
1006
- {key}
1007
- </span>
1008
- <span style={{ fontSize: 12, fontFamily: 'var(--font-mono, monospace)', color: 'var(--text-tertiary)' }}>
1009
- {value ? `${String(value).slice(0, 4)}${'•'.repeat(Math.min(20, String(value).length - 4))}` : '(not set)'}
1010
- </span>
746
+ {/* Extensions & Ecosystem */}
747
+ <div className="settings-card" style={{ gridColumn: '1 / -1' }}>
748
+ <div style={{ display: 'flex', alignItems: 'center', gap: 16, borderBottom: '1px solid rgba(255,255,255,0.05)', paddingBottom: 20 }}>
749
+ <div className="settings-icon-wrap" style={{ background: 'linear-gradient(135deg, rgba(236, 72, 153, 0.2), rgba(219, 39, 119, 0.05))', borderColor: 'rgba(236, 72, 153, 0.2)' }}>🧩</div>
750
+ <div>
751
+ <h3 style={{ fontSize: 16, fontWeight: 600, margin: 0 }}>Ecosystem Integrations</h3>
752
+ <p style={{ fontSize: 12, color: 'var(--text-tertiary)', margin: '4px 0 0' }}>Chrome extensions, UCW bundles, and tools</p>
753
+ </div>
754
+ </div>
755
+
756
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24, marginTop: 20 }}>
757
+ <div style={{ background: 'rgba(255,255,255,0.03)', padding: 20, borderRadius: 12, border: '1px solid rgba(255,255,255,0.06)' }}>
758
+ <div style={{ fontSize: 24, marginBottom: 12 }}>🦊</div>
759
+ <h4 style={{ margin: '0 0 8px', fontSize: 15 }}>Total Recall Browser Extension</h4>
760
+ <p style={{ fontSize: 13, color: 'var(--text-tertiary)', margin: '0 0 16px', lineHeight: 1.5 }}>
761
+ Capture context directly from your browser. Install from the Chrome Web Store or load unpacked from <code>/extension</code> directory.
762
+ </p>
763
+ <button className="btn-primary" style={{ width: '100%' }}>Download Extension</button>
764
+ </div>
765
+
766
+ <div style={{ background: 'rgba(255,255,255,0.03)', padding: 20, borderRadius: 12, border: '1px solid rgba(255,255,255,0.06)' }}>
767
+ <div style={{ fontSize: 24, marginBottom: 12 }}>📦</div>
768
+ <h4 style={{ margin: '0 0 8px', fontSize: 15 }}>Sovereign Toolbox</h4>
769
+ <p style={{ fontSize: 13, color: 'var(--text-tertiary)', margin: '0 0 16px', lineHeight: 1.5 }}>
770
+ Execute quick administrative operations via the UCW toolkit and sandbox shell.
771
+ </p>
772
+ <div style={{ display: 'flex', gap: 12 }}>
773
+ <button
774
+ className="btn-secondary"
775
+ style={{ flex: 1 }}
776
+ onClick={() => executeSandboxCommand('npx total-recall compile', 'Manual Index Recompilation')}
777
+ >
778
+ Compile
779
+ </button>
780
+ <button
781
+ className="btn-primary"
782
+ style={{ flex: 1 }}
783
+ onClick={() => executeSandboxCommand('npx @ssss/cli export ./bundle.ucw', 'Exporting UCW Bundle')}
784
+ >
785
+ Export Bundle
786
+ </button>
787
+ </div>
1011
788
  </div>
1012
- ))}
1013
- {(!configData?.secrets || Object.keys(configData.secrets).length === 0) && (
1014
- <div style={{ fontSize: 13, color: 'var(--text-tertiary)', padding: '12px', textAlign: 'center' }}>
1015
- No secrets configured. Add API keys through the visual settings editor above.
789
+ </div>
790
+
791
+ {sandboxLog && (
792
+ <div className="terminal-log">
793
+ {sandboxLog}
1016
794
  </div>
1017
795
  )}
1018
796
  </div>
797
+
1019
798
  </div>
799
+ {/* Ollama Panel */}
800
+ <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 }}>
801
+ <div style={{ display: 'flex', alignItems: 'center', gap: 10, borderBottom: '1px solid var(--border)', paddingBottom: 12 }}>
802
+ <span style={{ fontSize: 20 }}>🦙</span>
803
+ <div>
804
+ <h3 style={{ fontSize: 14, fontWeight: 600 }}>Local Provider</h3>
805
+ <p style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>Connect Ollama, LM Studio, vLLM, or Llama.cpp</p>
806
+ </div>
807
+ </div>
808
+
809
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
810
+ <label htmlFor="local_endpoint" style={{ fontSize: 13, fontWeight: 500 }}>Provider Base URL</label>
811
+ <input
812
+ id="local_endpoint"
813
+ type="text"
814
+ placeholder="http://127.0.0.1:11434 or http://127.0.0.1:1234/v1"
815
+ value={configData?.brain?.local_endpoint || ''}
816
+ onChange={(e) => updateBrainProp('local_endpoint', e.target.value)}
817
+ style={{
818
+ background: 'var(--bg-tertiary)',
819
+ color: 'var(--text-primary)',
820
+ border: '1px solid var(--border)',
821
+ padding: '8px 12px',
822
+ borderRadius: 6,
823
+ outline: 'none',
824
+ fontSize: 13
825
+ }}
826
+ />
827
+ <span style={{ fontSize: 10, color: 'var(--text-tertiary)' }}>Leave blank to use the default localhost endpoint.</span>
828
+ </div>
829
+
830
+ <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginTop: 8 }}>
831
+ <span className="badge" style={{
832
+ background: health?.ollama === 'running' ? 'rgba(16, 185, 129, 0.1)' : 'rgba(239, 68, 68, 0.1)',
833
+ color: health?.ollama === 'running' ? '#10b981' : '#ef4444',
834
+ border: health?.ollama === 'running' ? '1px solid rgba(16, 185, 129, 0.2)' : '1px solid rgba(239, 68, 68, 0.2)',
835
+ fontSize: 10,
836
+ padding: '4px 8px'
837
+ }}>
838
+ {health?.ollama === 'running' ? 'Connected' : 'Offline'}
839
+ </span>
840
+ {health?.ollama === 'running' && health?.ollama_models && (
841
+ <span style={{ fontSize: 11, color: 'var(--text-secondary)' }}>
842
+ {health.ollama_models.length} local models available
843
+ </span>
844
+ )}
845
+ </div>
846
+ </div>
847
+ {/* CLI Agents */}
848
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(360px, 1fr))', gap: 24 }}>
849
+
850
+ {/* Catalog */}
851
+ <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 }}>
852
+ <div style={{ display: 'flex', alignItems: 'center', gap: 10, borderBottom: '1px solid var(--border)', paddingBottom: 12 }}>
853
+ <span style={{ fontSize: 20 }}>🧠</span>
854
+ <div>
855
+ <h3 style={{ fontSize: 14, fontWeight: 600 }}>CLI Reasoning Agents</h3>
856
+ <p style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>Headless CLI reasoning agents configured on system</p>
857
+ </div>
858
+ </div>
859
+
860
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
861
+ {AGENTS_LIST.map((a) => {
862
+ const activeAgent = health?.cli_agents?.includes(a.id);
863
+ return (
864
+ <div key={a.id} style={{ display: 'flex', alignItems: 'center', gap: 12, background: 'var(--bg-tertiary)', padding: 12, borderRadius: 8, border: '1px solid var(--border)' }}>
865
+ <div style={{ flex: 1 }}>
866
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
867
+ <span style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-primary)' }}>{a.name}</span>
868
+ <span className="badge" style={{
869
+ fontSize: 9,
870
+ padding: '2px 6px',
871
+ background: activeAgent ? 'rgba(16, 185, 129, 0.1)' : 'rgba(239, 68, 68, 0.1)',
872
+ color: activeAgent ? '#10b981' : '#ef4444',
873
+ border: activeAgent ? '1px solid rgba(16, 185, 129, 0.2)' : '1px solid rgba(239, 68, 68, 0.2)'
874
+ }}>
875
+ {activeAgent ? 'available' : 'missing'}
876
+ </span>
877
+ </div>
878
+ <div style={{ fontSize: 10, color: 'var(--text-secondary)', marginTop: 2 }}>{a.desc}</div>
879
+ <div style={{ fontSize: 9, color: 'var(--text-tertiary)', marginTop: 4 }}>Registry ID: {a.id}</div>
880
+ </div>
881
+ </div>
882
+ );
883
+ })}
884
+ </div>
885
+ </div>
886
+
887
+ {/* Diagnostics Form */}
888
+ <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 }}>
889
+ <div style={{ display: 'flex', alignItems: 'center', gap: 10, borderBottom: '1px solid var(--border)', paddingBottom: 12 }}>
890
+ <span style={{ fontSize: 20 }}>📡</span>
891
+ <div>
892
+ <h3 style={{ fontSize: 14, fontWeight: 600 }}>CLI Agent Diagnostics</h3>
893
+ <p style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>Trigger active paths check and verify registered agents</p>
894
+ </div>
895
+ </div>
1020
896
 
1021
- {/* FOOTER ACTIONS */}
1022
- <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 16, borderTop: '1px solid var(--border)', paddingTop: 16, flexShrink: 0 }}>
1023
- <button
1024
- className="btn btn-primary"
1025
- onClick={viewMode === 'visual' ? handleSaveVisual : handleSaveYaml}
1026
- disabled={loading || saving}
1027
- style={{
1028
- width: 140,
1029
- background: 'linear-gradient(135deg, var(--accent), var(--accent-hover))',
1030
- color: '#fff',
1031
- fontWeight: 500,
1032
- borderRadius: 6
1033
- }}
1034
- >
1035
- {saving ? 'Saving...' : 'Save Settings'}
1036
- </button>
897
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
898
+ <p style={{ fontSize: 12, lineHeight: 1.5, color: 'var(--text-secondary)' }}>
899
+ This executes a diagnostic check on registered reasoning agents (equivalent to running <code>npx total-recall upgrade --agents</code> in the background) to audit paths and verify binaries.
900
+ </p>
901
+
902
+ <button
903
+ onClick={handleRunDiagnostics}
904
+ disabled={runningDiagnostics}
905
+ style={{
906
+ background: 'linear-gradient(135deg, var(--accent), var(--accent-hover))',
907
+ color: '#fff',
908
+ padding: '10px',
909
+ borderRadius: 6,
910
+ fontWeight: 500,
911
+ border: 'none',
912
+ marginTop: 8,
913
+ cursor: runningDiagnostics ? 'not-allowed' : 'pointer'
914
+ }}
915
+ >
916
+ {runningDiagnostics ? '⏳ Running Audit...' : '🚀 Run Diagnostics Audit'}
917
+ </button>
918
+ </div>
919
+
920
+ {diagnosticLogs && (
921
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginTop: 10 }}>
922
+ <span style={{ fontSize: 11, fontWeight: 500, color: 'var(--text-secondary)' }}>Diagnostics Console Output:</span>
923
+ <pre style={{
924
+ background: '#07070a',
925
+ border: '1px solid var(--border)',
926
+ padding: 10,
927
+ borderRadius: 6,
928
+ fontFamily: 'var(--font-mono)',
929
+ fontSize: 11,
930
+ color: '#e6edf3',
931
+ whiteSpace: 'pre-wrap',
932
+ maxHeight: 150,
933
+ overflowY: 'auto'
934
+ }}>
935
+ {diagnosticLogs}
936
+ </pre>
937
+ </div>
938
+ )}
939
+ </div>
1037
940
  </div>
1038
941
  </div>
1039
942
  );