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.
- package/README.md +91 -108
- package/bin/antigravity.mjs +81 -52
- package/bin/total-recall.mjs +32 -57
- package/extension/background.js +1 -1
- package/extension/options/options.js +9 -3
- package/extension/sidepanel/sidepanel.js +0 -2
- package/frontend/index.html +4 -3
- package/frontend/package.json +1 -0
- package/frontend/src/App.tsx +109 -152
- package/frontend/src/api.ts +605 -23
- package/frontend/src/components/BrainSelector.tsx +211 -199
- package/frontend/src/components/DocumentEditorModal.tsx +57 -0
- package/frontend/src/components/DocumentTable.tsx +47 -0
- package/frontend/src/components/Graph3D.tsx +120 -71
- package/frontend/src/components/UsageChart.tsx +3 -3
- package/frontend/src/components/brand/BrandMark.tsx +95 -0
- package/frontend/src/index.css +286 -86
- package/frontend/src/pages/ApiKeysPage.tsx +1878 -215
- package/frontend/src/pages/ChatPage.tsx +103 -45
- package/frontend/src/pages/DesignDocsPage.tsx +5 -5
- package/frontend/src/pages/FilesPage.tsx +10 -6
- package/frontend/src/pages/GraphPage.tsx +5 -3
- package/frontend/src/pages/HelpPage.tsx +2 -2
- package/frontend/src/pages/InboxPage.tsx +82 -0
- package/frontend/src/pages/InstructionsPage.tsx +1 -1
- package/frontend/src/pages/IntegrationsPage.tsx +21 -10
- package/frontend/src/pages/LoginPage.tsx +95 -77
- package/frontend/src/pages/MemoryPage.tsx +31 -8
- package/frontend/src/pages/OnboardingPage.tsx +690 -0
- package/frontend/src/pages/OpenWikiPage.tsx +412 -323
- package/frontend/src/pages/SettingsPage.tsx +825 -922
- package/frontend/src/pages/SkillsPage.tsx +272 -147
- package/frontend/src/pages/UsagePage.tsx +23 -31
- package/frontend/src/pages/VaultPage.tsx +223 -0
- package/frontend/src/types.ts +13 -0
- package/package.json +4 -4
- package/scaffold/.agent/skills/total-recall/SKILL.md +43 -4
- package/scaffold/.agent/skills/total-recall/modules/README.md +10 -0
- package/scaffold/.agent/skills/total-recall/modules/agents/README.md +5 -0
- package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents → modules/agents}/agents.yml +8 -0
- package/scaffold/.agent/skills/total-recall/modules/skill-deploy/README.md +9 -0
- package/scaffold/.agent/skills/total-recall/openwiki/README.md +10 -0
- package/scaffold/.agent/skills/total-recall/openwiki/architecture.md +5 -0
- package/scaffold/.agent/skills/total-recall/openwiki/ide.md +10 -0
- package/scaffold/.agent/skills/total-recall/openwiki/memory.md +11 -0
- package/scaffold/.agent/skills/total-recall/openwiki/secrets.md +7 -0
- package/scaffold/.agent/skills/total-recall/openwiki/skills.md +7 -0
- package/src/cli/brain.mjs +63 -5
- package/src/cli/connect.mjs +11 -19
- package/src/cli/daemon.mjs +5 -4
- package/src/cli/deploy-ui.mjs +8 -3
- package/src/cli/deploy.mjs +84 -27
- package/src/cli/ingest/index.mjs +1 -1
- package/src/cli/init.mjs +63 -34
- package/src/cli/remember.mjs +3 -3
- package/src/cli/secret.mjs +812 -0
- package/src/cli/setup.mjs +34 -7
- package/src/cli/share.mjs +2 -2
- package/src/cli/skill.mjs +396 -35
- package/src/cli/sync.mjs +4 -4
- package/src/cli/task.mjs +287 -0
- package/src/core/clarity-rewriter.mjs +5 -5
- package/src/core/config.mjs +44 -2
- package/src/core/conflict-detector.mjs +5 -5
- package/src/core/crons.mjs +54 -0
- package/src/core/daemon-loop.mjs +87 -235
- package/src/core/dream.mjs +170 -10
- package/src/core/env-import.mjs +456 -0
- package/src/core/fact-seeker.mjs +14 -14
- package/src/core/import-rules.mjs +20 -7
- package/src/core/inference-engine.mjs +3 -3
- package/src/core/migrate.mjs +14 -8
- package/src/core/okf-adapter.mjs +2 -2
- package/src/core/operation-validator.mjs +39 -331
- package/src/core/optimizer.mjs +7 -0
- package/src/core/post-mortem.mjs +2 -2
- package/src/core/project-brain.mjs +349 -0
- package/src/core/provider-catalog.mjs +296 -0
- package/src/core/quick-capture.mjs +1 -1
- package/src/core/remote-vault-sync.mjs +194 -0
- package/src/core/research-queue.mjs +7 -11
- package/src/core/runtime.mjs +33 -9
- package/src/core/scheduler.mjs +59 -5
- package/src/core/schema.mjs +14 -0
- package/src/core/search.mjs +1 -1
- package/src/core/secrets-env-export.mjs +323 -0
- package/src/core/secrets-rotate.mjs +245 -0
- package/src/core/secrets-store.mjs +645 -0
- package/src/core/semantic-index.mjs +3 -3
- package/src/core/skills-registry.mjs +1062 -0
- package/src/core/source-adapters.mjs +4 -1
- package/src/core/ssss-host-extension.mjs +94 -0
- package/src/core/ssss-kernel-bridge.mjs +530 -0
- package/src/core/surface.mjs +14 -0
- package/src/core/task-envelope.mjs +298 -0
- package/src/core/task-executors.mjs +600 -0
- package/src/core/validated-write.mjs +124 -56
- package/src/core/vault.mjs +21 -34
- package/src/core/webauthn-store.mjs +272 -0
- package/src/server/api.mjs +26 -16
- package/src/server/auth.mjs +51 -0
- package/src/server/index.mjs +3 -3
- package/src/server/keys.mjs +30 -4
- package/src/server/rest.mjs +198 -25
- package/src/server/route-manifest.json +156 -0
- package/src/server/routes/_shared.mjs +40 -1
- package/src/server/routes/docs.mjs +260 -0
- package/src/server/routes/memory.mjs +145 -32
- package/src/server/routes/research.mjs +30 -0
- package/src/server/routes/secrets.mjs +470 -0
- package/src/server/routes/share.mjs +2 -2
- package/src/server/routes/skills.mjs +452 -54
- package/src/server/routes/sync.mjs +48 -0
- package/src/server/routes/webauthn.mjs +184 -0
- package/src/server/update-route-manifest.mjs +39 -0
- package/templates/onboarding-interview.md +32 -22
- package/templates/openwiki/README.md +10 -0
- package/templates/openwiki/architecture.md +5 -0
- package/templates/openwiki/ide.md +10 -0
- package/templates/openwiki/memory.md +11 -0
- package/templates/openwiki/secrets.md +7 -0
- package/templates/openwiki/skills.md +7 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/SKILL.md +0 -121
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/antigravity.txt +0 -20
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/claude.txt +0 -58
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/codex.txt +0 -81
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/gemini.txt +0 -44
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/scripts/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/subagents/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-research/SKILL.md +0 -338
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/SKILL.md +0 -282
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/assets/skill-template/SKILL.md +0 -20
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/evals/evals.json +0 -20
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-add.sh +0 -14
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-delete.sh +0 -14
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/learnings/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/references/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/create-skill.sh +0 -65
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/enforce-skill-optimization.mjs +0 -135
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/skill-watcher.mjs +0 -64
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/subagents/audit-all-skills.md +0 -62
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/SKILL.md +0 -298
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/evals/evals.json +0 -17
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/CHANGELOG.md +0 -124
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/ssss-spec.md +0 -1325
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/scripts/validate-schema.mjs +0 -232
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/subagents/audit-ssss-node.md +0 -9
- /package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents/evals → modules/skill-deploy/scripts}/.gitkeep +0 -0
- /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/find-skills.mjs +0 -0
- /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/install-skill.mjs +0 -0
- /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,
|
|
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
|
-
|
|
13
|
-
const [
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
76
|
-
setError(null);
|
|
77
|
-
setSuccess(false);
|
|
63
|
+
setSaveStatus('saving');
|
|
78
64
|
try {
|
|
79
65
|
await saveConfigJson(configData);
|
|
80
|
-
|
|
81
|
-
setTimeout(() =>
|
|
82
|
-
} catch (err
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
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
|
-
//
|
|
106
|
-
const updateSecurityProp = (
|
|
107
|
-
|
|
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
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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 = (
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
...
|
|
139
|
-
|
|
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 = (
|
|
146
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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"
|
|
169
|
-
<
|
|
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
|
|
172
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
<button
|
|
178
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
237
|
-
<div style={{
|
|
238
|
-
<
|
|
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="
|
|
244
|
-
type="
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
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="
|
|
259
|
-
type="
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
267
|
-
<div
|
|
268
|
-
<
|
|
269
|
-
<
|
|
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
|
-
|
|
290
|
-
<div
|
|
291
|
-
<
|
|
292
|
-
<
|
|
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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
<
|
|
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
|
-
|
|
327
|
-
<
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
342
|
-
<
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
-
|
|
364
|
-
<
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
<
|
|
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
|
-
|
|
397
|
-
<div
|
|
398
|
-
<div>
|
|
399
|
-
|
|
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
|
-
|
|
412
|
-
<div
|
|
413
|
-
<div>
|
|
414
|
-
|
|
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
|
-
|
|
427
|
-
<div
|
|
428
|
-
<div>
|
|
429
|
-
|
|
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
|
-
|
|
442
|
-
<div
|
|
443
|
-
<div style={{
|
|
444
|
-
|
|
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
|
-
|
|
460
|
-
|
|
461
|
-
<
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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
|
-
|
|
470
|
-
<div
|
|
471
|
-
<div style={{
|
|
472
|
-
|
|
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
|
-
|
|
510
|
-
<div style={{
|
|
511
|
-
<label htmlFor="
|
|
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="
|
|
514
|
-
type="
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
-
|
|
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="
|
|
645
|
+
id="weekly_cap"
|
|
536
646
|
type="number"
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
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
|
-
|
|
563
|
-
<
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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
|
-
|
|
584
|
-
<
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
-
|
|
606
|
-
<div style={{
|
|
607
|
-
<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
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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
|
-
|
|
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
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
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
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
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
|
-
|
|
839
|
-
|
|
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="
|
|
865
|
-
|
|
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="
|
|
868
|
-
<option value="
|
|
869
|
-
<option value="
|
|
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
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
<
|
|
986
|
-
<
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
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
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
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
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
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
|
);
|