total-recall-brain 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +173 -0
- package/bin/antigravity.mjs +212 -0
- package/bin/init.mjs +36 -0
- package/bin/oci-sniper.sh +69 -0
- package/bin/total-recall.mjs +229 -0
- package/docs/ARCHITECTURE.md +251 -0
- package/docs/IDE_INTEGRATION.md +112 -0
- package/package.json +71 -0
- package/scaffold/.agent/skills/total-recall/SKILL.md +195 -0
- package/scaffold/.agent/skills/total-recall/evals/evals.json +20 -0
- package/scaffold/.agent/skills/total-recall/memory-vault/concepts/cli-help-reference.md +145 -0
- package/scaffold/.agent/skills/total-recall/memory-vault/invariants/operating-instructions.md +75 -0
- package/scaffold/.agent/skills/total-recall/memory-vault/preferences/always-websearch-gap.md +16 -0
- package/scaffold/.agent/skills/total-recall/memory-vault/preferences/topic-research-sop.md +17 -0
- package/scaffold/.agent/skills/total-recall/references/api-reference.md +187 -0
- package/scaffold/.agent/skills/total-recall/references/architecture-reference.md +81 -0
- package/scaffold/.agent/skills/total-recall/references/cli-reference.md +103 -0
- package/scaffold/.agent/skills/total-recall/references/ssss-reference.md +88 -0
- package/scaffold/.agent/skills/total-recall/references/troubleshooting.md +68 -0
- package/scaffold/.agent/skills/total-recall/scripts/sync-repo.mjs +83 -0
- package/scaffold/.agent/skills/total-recall/skills/cli-agents/SKILL.md +122 -0
- package/scaffold/.agent/skills/total-recall/skills/cli-agents/agents.yml +27 -0
- package/scaffold/.agent/skills/total-recall/skills/cli-agents/references/antigravity.txt +20 -0
- package/scaffold/.agent/skills/total-recall/skills/cli-agents/references/claude.txt +58 -0
- package/scaffold/.agent/skills/total-recall/skills/cli-agents/references/codex.txt +81 -0
- package/scaffold/.agent/skills/total-recall/skills/cli-agents/references/gemini.txt +44 -0
- package/scaffold/.agent/skills/total-recall/skills/research/SKILL.md +337 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/SKILL.md +281 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/assets/skill-template/SKILL.md +20 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/evals/evals.json +20 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/hooks/on-skill-add.sh +14 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/hooks/on-skill-delete.sh +14 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/learnings/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/references/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/scripts/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/scripts/create-skill.sh +65 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/scripts/enforce-skill-optimization.mjs +135 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/scripts/find-skills.mjs +124 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/scripts/scan-skill.mjs +207 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/scripts/skill-watcher.mjs +64 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/subagents/audit-all-skills.md +62 -0
- package/scaffold/.agent/skills/total-recall/skills/ssss/SKILL.md +27 -0
- package/scaffold/.agent/skills/total-recall/skills/ssss/evals/evals.json +17 -0
- package/scaffold/.agent/skills/total-recall/skills/ssss/references/ssss-spec.md +117 -0
- package/scaffold/.agent/skills/total-recall/skills/ssss/scripts/validate-schema.mjs +54 -0
- package/scaffold/.agent/skills/total-recall/skills/ssss/subagents/audit-ssss-node.md +9 -0
- package/scaffold/.agent/skills/total-recall/subagents/total-recall-diagnostician.md +13 -0
- package/src/cli/agent-dir.mjs +105 -0
- package/src/cli/backup.mjs +313 -0
- package/src/cli/brain.mjs +224 -0
- package/src/cli/chat.mjs +74 -0
- package/src/cli/config.mjs +247 -0
- package/src/cli/connect.mjs +872 -0
- package/src/cli/daemon.mjs +228 -0
- package/src/cli/deploy-ui.mjs +1793 -0
- package/src/cli/deploy.mjs +1726 -0
- package/src/cli/doctor.mjs +254 -0
- package/src/cli/dream.mjs +80 -0
- package/src/cli/forget.mjs +92 -0
- package/src/cli/friction.mjs +21 -0
- package/src/cli/generate-pat.mjs +65 -0
- package/src/cli/hash-password.mjs +22 -0
- package/src/cli/help.mjs +153 -0
- package/src/cli/import-rules.mjs +102 -0
- package/src/cli/ingest/index.mjs +318 -0
- package/src/cli/ingest/parsers/chrome-bookmarks.mjs +238 -0
- package/src/cli/ingest/parsers/google-keep.mjs +147 -0
- package/src/cli/ingest/parsers/search-history.mjs +142 -0
- package/src/cli/ingest/parsers/youtube-history.mjs +181 -0
- package/src/cli/ingest/utils/dedup.mjs +69 -0
- package/src/cli/ingest/utils/takeout-walker.mjs +101 -0
- package/src/cli/ingest.mjs +102 -0
- package/src/cli/init.mjs +736 -0
- package/src/cli/integration-dispatcher.mjs +128 -0
- package/src/cli/lint.mjs +166 -0
- package/src/cli/map.mjs +194 -0
- package/src/cli/migrate.mjs +87 -0
- package/src/cli/rebuild.mjs +88 -0
- package/src/cli/recall.mjs +219 -0
- package/src/cli/relay.mjs +518 -0
- package/src/cli/remember.mjs +306 -0
- package/src/cli/research.mjs +381 -0
- package/src/cli/reset-password.mjs +75 -0
- package/src/cli/restore.mjs +135 -0
- package/src/cli/setup.mjs +616 -0
- package/src/cli/share.mjs +180 -0
- package/src/cli/skill.mjs +313 -0
- package/src/cli/snapshot.mjs +108 -0
- package/src/cli/start.mjs +63 -0
- package/src/cli/status.mjs +296 -0
- package/src/cli/sync.mjs +383 -0
- package/src/cli/uninstall.mjs +613 -0
- package/src/cli/upgrade.mjs +141 -0
- package/src/cli/wizard.html +3682 -0
- package/src/core/blackboard.mjs +54 -0
- package/src/core/clarity-rewriter.mjs +770 -0
- package/src/core/conclusion-writer.mjs +259 -0
- package/src/core/config.mjs +277 -0
- package/src/core/conflict-detector.mjs +536 -0
- package/src/core/crypto.mjs +107 -0
- package/src/core/daemon-control.mjs +158 -0
- package/src/core/daemon-loop.mjs +364 -0
- package/src/core/dream.mjs +347 -0
- package/src/core/drift-detector.mjs +143 -0
- package/src/core/embeddings.mjs +632 -0
- package/src/core/emergency-alerts.mjs +137 -0
- package/src/core/evolution.mjs +107 -0
- package/src/core/fact-seeker.mjs +1602 -0
- package/src/core/friction.mjs +95 -0
- package/src/core/import-rules.mjs +308 -0
- package/src/core/inference-engine.mjs +367 -0
- package/src/core/logger.mjs +75 -0
- package/src/core/memory-layers.mjs +96 -0
- package/src/core/migrate.mjs +116 -0
- package/src/core/notifications.mjs +77 -0
- package/src/core/operation-validator.mjs +272 -0
- package/src/core/optimizer.mjs +330 -0
- package/src/core/pattern_detector.mjs +113 -0
- package/src/core/post-mortem.mjs +413 -0
- package/src/core/protect-instructions.mjs +148 -0
- package/src/core/quick-capture.mjs +145 -0
- package/src/core/research-queue.mjs +431 -0
- package/src/core/research.mjs +461 -0
- package/src/core/runtime.mjs +515 -0
- package/src/core/sandbox.mjs +163 -0
- package/src/core/scheduler.mjs +569 -0
- package/src/core/schema.mjs +333 -0
- package/src/core/search.mjs +219 -0
- package/src/core/semantic-index.mjs +276 -0
- package/src/core/session-watcher.mjs +850 -0
- package/src/core/snapshot.mjs +133 -0
- package/src/core/source-adapters.mjs +1038 -0
- package/src/core/steering.mjs +137 -0
- package/src/core/surface.mjs +444 -0
- package/src/core/task_runner.mjs +125 -0
- package/src/core/tts.mjs +142 -0
- package/src/core/usage-tracker.mjs +349 -0
- package/src/core/vault-cache.mjs +76 -0
- package/src/core/vault.mjs +273 -0
- package/src/core/watchdog.mjs +236 -0
- package/src/server/api.mjs +944 -0
- package/src/server/auth.mjs +410 -0
- package/src/server/index.mjs +868 -0
- package/src/server/keys.mjs +181 -0
- package/src/server/rest.mjs +1652 -0
- package/src/server/route-manifest.json +77 -0
- package/src/server/routes/_shared.mjs +93 -0
- package/src/server/routes/keys.mjs +55 -0
- package/src/server/routes/memory.mjs +249 -0
- package/src/server/routes/sessions.mjs +336 -0
- package/src/server/routes/share.mjs +138 -0
- package/src/server/tools.mjs +618 -0
- package/templates/Caddyfile +37 -0
- package/templates/com.totalrecall.backup.plist +42 -0
- package/templates/com.totalrecall.daemon.plist +40 -0
- package/templates/com.totalrecall.duckdns.plist +23 -0
- package/templates/com.totalrecall.server.plist +46 -0
- package/templates/default-config/budget.yml +6 -0
- package/templates/default-config/runtime.yml +11 -0
- package/templates/default-config/security.yml +53 -0
- package/templates/default-config/voice.yml +34 -0
- package/templates/obsidian-queries/conflicts.md +26 -0
- package/templates/obsidian-queries/daily.md +17 -0
- package/templates/obsidian-queries/index.md +38 -0
- package/templates/obsidian-queries/skills.md +14 -0
- package/templates/onboarding-interview.md +72 -0
- package/templates/skill_engineering.md +40 -0
- package/templates/total-recall-daemon.service +21 -0
- package/templates/total-recall-server.service +20 -0
|
@@ -0,0 +1,3682 @@
|
|
|
1
|
+
<html lang="en">
|
|
2
|
+
<head>
|
|
3
|
+
<meta charset="UTF-8">
|
|
4
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
5
|
+
<title>Total Recall — Setup Wizard</title>
|
|
6
|
+
<style>
|
|
7
|
+
:root {
|
|
8
|
+
--bg: #0d1117;
|
|
9
|
+
--panel: #161b22;
|
|
10
|
+
--panel2: #1c2128;
|
|
11
|
+
--border: #30363d;
|
|
12
|
+
--green: #3fb950;
|
|
13
|
+
--yellow: #d29922;
|
|
14
|
+
--red: #f85149;
|
|
15
|
+
--blue: #58a6ff;
|
|
16
|
+
--muted: #8b949e;
|
|
17
|
+
--text: #e6edf3;
|
|
18
|
+
--accent: #7ee787;
|
|
19
|
+
--purple: #bc8cff;
|
|
20
|
+
}
|
|
21
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
22
|
+
html, body {
|
|
23
|
+
height: 100vh;
|
|
24
|
+
overflow: hidden;
|
|
25
|
+
scroll-behavior: smooth;
|
|
26
|
+
}
|
|
27
|
+
body {
|
|
28
|
+
background: var(--bg);
|
|
29
|
+
color: var(--text);
|
|
30
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
31
|
+
font-size: 14px;
|
|
32
|
+
line-height: 1.6;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* ── Security Badges & Tooltips ── */
|
|
36
|
+
.security-badge {
|
|
37
|
+
display: inline-flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
gap: 4px;
|
|
40
|
+
background: rgba(88, 166, 255, 0.1);
|
|
41
|
+
color: var(--blue);
|
|
42
|
+
border: 1px solid rgba(88, 166, 255, 0.3);
|
|
43
|
+
border-radius: 4px;
|
|
44
|
+
padding: 2px 6px;
|
|
45
|
+
font-size: 11px;
|
|
46
|
+
font-weight: 500;
|
|
47
|
+
margin-left: 8px;
|
|
48
|
+
vertical-align: middle;
|
|
49
|
+
cursor: help;
|
|
50
|
+
position: relative;
|
|
51
|
+
}
|
|
52
|
+
.security-badge:hover {
|
|
53
|
+
background: rgba(88, 166, 255, 0.2);
|
|
54
|
+
border-color: var(--blue);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* ── Layout ── */
|
|
58
|
+
.wizard-shell {
|
|
59
|
+
display: flex;
|
|
60
|
+
height: 100vh;
|
|
61
|
+
overflow: hidden;
|
|
62
|
+
}
|
|
63
|
+
.sidebar {
|
|
64
|
+
width: 220px;
|
|
65
|
+
flex-shrink: 0;
|
|
66
|
+
background: var(--panel);
|
|
67
|
+
border-right: 1px solid var(--border);
|
|
68
|
+
padding: 28px 0;
|
|
69
|
+
position: sticky;
|
|
70
|
+
top: 0;
|
|
71
|
+
height: 100vh;
|
|
72
|
+
overflow-y: auto;
|
|
73
|
+
}
|
|
74
|
+
.sidebar-brand {
|
|
75
|
+
padding: 0 20px 24px;
|
|
76
|
+
border-bottom: 1px solid var(--border);
|
|
77
|
+
margin-bottom: 12px;
|
|
78
|
+
}
|
|
79
|
+
.sidebar-brand h2 {
|
|
80
|
+
font-size: 15px;
|
|
81
|
+
font-weight: 700;
|
|
82
|
+
color: var(--accent);
|
|
83
|
+
letter-spacing: -0.3px;
|
|
84
|
+
}
|
|
85
|
+
.sidebar-brand p {
|
|
86
|
+
font-size: 11px;
|
|
87
|
+
color: var(--muted);
|
|
88
|
+
margin-top: 3px;
|
|
89
|
+
}
|
|
90
|
+
.step-nav { list-style: none; }
|
|
91
|
+
.step-nav li {
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
gap: 10px;
|
|
95
|
+
padding: 9px 20px;
|
|
96
|
+
font-size: 13px;
|
|
97
|
+
color: var(--muted);
|
|
98
|
+
cursor: pointer;
|
|
99
|
+
transition: background 0.15s, color 0.15s;
|
|
100
|
+
position: relative;
|
|
101
|
+
}
|
|
102
|
+
.step-nav li:hover {
|
|
103
|
+
color: var(--text);
|
|
104
|
+
background: rgba(255,255,255,0.05);
|
|
105
|
+
}
|
|
106
|
+
.step-nav li.active {
|
|
107
|
+
color: var(--text);
|
|
108
|
+
background: rgba(88,166,255,0.1);
|
|
109
|
+
}
|
|
110
|
+
.step-nav li.active::before {
|
|
111
|
+
content: '';
|
|
112
|
+
position: absolute;
|
|
113
|
+
left: 0; top: 0; bottom: 0;
|
|
114
|
+
width: 3px;
|
|
115
|
+
background: var(--blue);
|
|
116
|
+
border-radius: 0 2px 2px 0;
|
|
117
|
+
}
|
|
118
|
+
.step-nav li.done { color: var(--green); }
|
|
119
|
+
.step-badge {
|
|
120
|
+
width: 22px; height: 22px;
|
|
121
|
+
border-radius: 50%;
|
|
122
|
+
background: var(--border);
|
|
123
|
+
display: flex; align-items: center; justify-content: center;
|
|
124
|
+
font-size: 11px; font-weight: 600;
|
|
125
|
+
flex-shrink: 0;
|
|
126
|
+
}
|
|
127
|
+
.active .step-badge { background: var(--blue); color: #0d1117; }
|
|
128
|
+
.done .step-badge { background: var(--green); color: #0d1117; }
|
|
129
|
+
|
|
130
|
+
.main-content {
|
|
131
|
+
flex: 1;
|
|
132
|
+
height: 100vh;
|
|
133
|
+
overflow-y: auto;
|
|
134
|
+
padding: 40px 48px;
|
|
135
|
+
max-width: 820px;
|
|
136
|
+
}
|
|
137
|
+
.main-content::-webkit-scrollbar {
|
|
138
|
+
width: 8px;
|
|
139
|
+
}
|
|
140
|
+
.main-content::-webkit-scrollbar-track {
|
|
141
|
+
background: transparent;
|
|
142
|
+
}
|
|
143
|
+
.main-content::-webkit-scrollbar-thumb {
|
|
144
|
+
background: var(--border);
|
|
145
|
+
border-radius: 4px;
|
|
146
|
+
}
|
|
147
|
+
.main-content::-webkit-scrollbar-thumb:hover {
|
|
148
|
+
background: var(--muted);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* ── Phases ── */
|
|
152
|
+
.phase { display: none; animation: fadeIn 0.2s ease; }
|
|
153
|
+
.phase.active { display: block; }
|
|
154
|
+
@keyframes fadeIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
|
|
155
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
156
|
+
|
|
157
|
+
.phase-header { margin-bottom: 28px; }
|
|
158
|
+
.phase-header h1 { font-size: 24px; font-weight: 700; letter-spacing: -0.5px; }
|
|
159
|
+
.phase-header p { color: var(--muted); margin-top: 6px; font-size: 14px; }
|
|
160
|
+
|
|
161
|
+
/* ── Cards ── */
|
|
162
|
+
.card {
|
|
163
|
+
background: var(--panel);
|
|
164
|
+
border: 1px solid var(--border);
|
|
165
|
+
border-radius: 10px;
|
|
166
|
+
padding: 24px;
|
|
167
|
+
margin-bottom: 20px;
|
|
168
|
+
}
|
|
169
|
+
.card h3 { font-size: 14px; font-weight: 600; margin-bottom: 12px; }
|
|
170
|
+
.card p, .card li { color: var(--muted); font-size: 13px; }
|
|
171
|
+
.card ul { padding-left: 18px; display: flex; flex-direction: column; gap: 5px; }
|
|
172
|
+
.card li { color: var(--text); }
|
|
173
|
+
.card li span { color: var(--muted); }
|
|
174
|
+
|
|
175
|
+
/* ── Arch diagram ── */
|
|
176
|
+
.arch-row {
|
|
177
|
+
display: flex;
|
|
178
|
+
align-items: center;
|
|
179
|
+
gap: 8px;
|
|
180
|
+
margin: 10px 0;
|
|
181
|
+
font-size: 13px;
|
|
182
|
+
}
|
|
183
|
+
.arch-box {
|
|
184
|
+
background: var(--panel2);
|
|
185
|
+
border: 1px solid var(--border);
|
|
186
|
+
border-radius: 6px;
|
|
187
|
+
padding: 6px 12px;
|
|
188
|
+
font-size: 12px;
|
|
189
|
+
color: var(--blue);
|
|
190
|
+
white-space: nowrap;
|
|
191
|
+
}
|
|
192
|
+
.arch-arrow { color: var(--muted); font-size: 16px; }
|
|
193
|
+
|
|
194
|
+
/* ── Forms ── */
|
|
195
|
+
.form-group { margin-bottom: 18px; }
|
|
196
|
+
.form-group label {
|
|
197
|
+
display: block;
|
|
198
|
+
font-size: 12px;
|
|
199
|
+
font-weight: 600;
|
|
200
|
+
color: var(--muted);
|
|
201
|
+
text-transform: uppercase;
|
|
202
|
+
letter-spacing: 0.8px;
|
|
203
|
+
margin-bottom: 6px;
|
|
204
|
+
}
|
|
205
|
+
.form-group input[type=text], .form-group input[type=password] {
|
|
206
|
+
width: 100%;
|
|
207
|
+
background: var(--panel2);
|
|
208
|
+
border: 1px solid var(--border);
|
|
209
|
+
border-radius: 6px;
|
|
210
|
+
padding: 9px 12px;
|
|
211
|
+
color: var(--text);
|
|
212
|
+
font-size: 14px;
|
|
213
|
+
font-family: inherit;
|
|
214
|
+
outline: none;
|
|
215
|
+
transition: border-color 0.15s;
|
|
216
|
+
}
|
|
217
|
+
.form-group input:focus { border-color: var(--blue); }
|
|
218
|
+
.form-group .hint { font-size: 11px; color: var(--muted); margin-top: 4px; }
|
|
219
|
+
.form-group .hint a { color: var(--blue); text-decoration: none; }
|
|
220
|
+
|
|
221
|
+
.radio-group { display: flex; flex-direction: column; gap: 8px; }
|
|
222
|
+
.radio-item {
|
|
223
|
+
display: flex;
|
|
224
|
+
align-items: flex-start;
|
|
225
|
+
gap: 10px;
|
|
226
|
+
padding: 10px 14px;
|
|
227
|
+
background: var(--panel2);
|
|
228
|
+
border: 2px solid var(--border);
|
|
229
|
+
border-radius: 8px;
|
|
230
|
+
cursor: pointer;
|
|
231
|
+
transition: border-color 0.15s;
|
|
232
|
+
}
|
|
233
|
+
.radio-item:hover { border-color: #444c56; }
|
|
234
|
+
.radio-item.selected { border-color: var(--blue); background: rgba(88,166,255,0.05); }
|
|
235
|
+
.radio-item input[type=radio] { margin-top: 2px; accent-color: var(--blue); flex-shrink: 0; }
|
|
236
|
+
.radio-item .ri-label { font-size: 13px; font-weight: 600; }
|
|
237
|
+
.radio-item .ri-desc { font-size: 12px; color: var(--muted); margin-top: 2px; }
|
|
238
|
+
|
|
239
|
+
.checkbox-group { display: flex; flex-direction: column; gap: 8px; }
|
|
240
|
+
.checkbox-item {
|
|
241
|
+
display: flex;
|
|
242
|
+
align-items: center;
|
|
243
|
+
gap: 10px;
|
|
244
|
+
font-size: 13px;
|
|
245
|
+
cursor: pointer;
|
|
246
|
+
}
|
|
247
|
+
.checkbox-item input[type=checkbox] { accent-color: var(--blue); width: 15px; height: 15px; }
|
|
248
|
+
|
|
249
|
+
.sub-field { margin-top: 12px; padding-left: 4px; }
|
|
250
|
+
|
|
251
|
+
/* ── Buttons ── */
|
|
252
|
+
.btn {
|
|
253
|
+
display: inline-flex;
|
|
254
|
+
align-items: center;
|
|
255
|
+
gap: 8px;
|
|
256
|
+
padding: 10px 20px;
|
|
257
|
+
border: none;
|
|
258
|
+
border-radius: 7px;
|
|
259
|
+
font-size: 14px;
|
|
260
|
+
font-weight: 600;
|
|
261
|
+
cursor: pointer;
|
|
262
|
+
transition: opacity 0.15s, transform 0.1s;
|
|
263
|
+
}
|
|
264
|
+
.btn:hover { opacity: 0.88; }
|
|
265
|
+
.btn:active { transform: scale(0.98); }
|
|
266
|
+
.btn-primary { background: #238636; color: #fff; }
|
|
267
|
+
.btn-secondary { background: var(--panel2); border: 1px solid var(--border); color: var(--text); }
|
|
268
|
+
.btn-blue { background: #1f6feb; color: #fff; }
|
|
269
|
+
.btn-row { display: flex; gap: 10px; align-items: center; margin-top: 24px; }
|
|
270
|
+
|
|
271
|
+
/* ── Progress (Phase 2) ── */
|
|
272
|
+
#progress-track {
|
|
273
|
+
height: 8px; background: var(--border); border-radius: 4px; overflow: hidden; margin-bottom: 12px;
|
|
274
|
+
}
|
|
275
|
+
#progress-bar {
|
|
276
|
+
height: 100%; width: 0%;
|
|
277
|
+
background: linear-gradient(90deg, #238636, var(--accent));
|
|
278
|
+
border-radius: 4px;
|
|
279
|
+
transition: width 0.4s ease;
|
|
280
|
+
}
|
|
281
|
+
#progress-label { font-size: 12px; color: var(--muted); margin-bottom: 16px; }
|
|
282
|
+
#log {
|
|
283
|
+
max-height: 340px;
|
|
284
|
+
overflow-y: auto;
|
|
285
|
+
display: flex;
|
|
286
|
+
flex-direction: column;
|
|
287
|
+
gap: 5px;
|
|
288
|
+
}
|
|
289
|
+
.log-entry {
|
|
290
|
+
display: flex;
|
|
291
|
+
align-items: flex-start;
|
|
292
|
+
gap: 10px;
|
|
293
|
+
padding: 5px 8px;
|
|
294
|
+
border-radius: 5px;
|
|
295
|
+
font-size: 12px;
|
|
296
|
+
font-family: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
|
|
297
|
+
line-height: 1.4;
|
|
298
|
+
}
|
|
299
|
+
.log-entry.step { color: var(--blue); }
|
|
300
|
+
.log-entry.ok { color: var(--green); }
|
|
301
|
+
.log-entry.warn { color: var(--yellow); background: rgba(210,153,34,.06); }
|
|
302
|
+
.log-entry.error { color: var(--red); background: rgba(248,81,73,.06); }
|
|
303
|
+
.log-entry .icon { flex-shrink: 0; width: 16px; }
|
|
304
|
+
|
|
305
|
+
/* ── Tabs (Phase 4) ── */
|
|
306
|
+
.tab-bar { display: flex; gap: 2px; border-bottom: 1px solid var(--border); margin-bottom: 20px; }
|
|
307
|
+
.tab-btn {
|
|
308
|
+
padding: 8px 14px;
|
|
309
|
+
font-size: 13px;
|
|
310
|
+
font-weight: 500;
|
|
311
|
+
color: var(--muted);
|
|
312
|
+
background: none;
|
|
313
|
+
border: none;
|
|
314
|
+
border-bottom: 2px solid transparent;
|
|
315
|
+
cursor: pointer;
|
|
316
|
+
margin-bottom: -1px;
|
|
317
|
+
transition: color 0.15s, border-color 0.15s;
|
|
318
|
+
}
|
|
319
|
+
.tab-btn.active { color: var(--text); border-bottom-color: var(--blue); }
|
|
320
|
+
.tab-pane { display: none; }
|
|
321
|
+
.tab-pane.active { display: block; }
|
|
322
|
+
|
|
323
|
+
/* ── Code blocks ── */
|
|
324
|
+
.code-block {
|
|
325
|
+
position: relative;
|
|
326
|
+
background: #010409;
|
|
327
|
+
border: 1px solid var(--border);
|
|
328
|
+
border-radius: 8px;
|
|
329
|
+
padding: 14px 16px;
|
|
330
|
+
margin: 12px 0;
|
|
331
|
+
font-family: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
|
|
332
|
+
font-size: 12px;
|
|
333
|
+
color: var(--text);
|
|
334
|
+
overflow-x: auto;
|
|
335
|
+
white-space: pre-wrap;
|
|
336
|
+
word-break: break-all;
|
|
337
|
+
}
|
|
338
|
+
.code-block .copy-btn {
|
|
339
|
+
position: absolute;
|
|
340
|
+
top: 8px;
|
|
341
|
+
right: 8px;
|
|
342
|
+
padding: 4px 8px;
|
|
343
|
+
background: var(--panel);
|
|
344
|
+
border: 1px solid var(--border);
|
|
345
|
+
border-radius: 5px;
|
|
346
|
+
font-size: 11px;
|
|
347
|
+
color: var(--muted);
|
|
348
|
+
cursor: pointer;
|
|
349
|
+
font-family: inherit;
|
|
350
|
+
transition: all 0.2s ease-in-out;
|
|
351
|
+
}
|
|
352
|
+
.code-block .copy-btn:hover { color: var(--text); background: var(--border); }
|
|
353
|
+
.code-block .copy-btn.copied { color: #34d399; border-color: #34d399; background: rgba(52, 211, 153, 0.1); }
|
|
354
|
+
|
|
355
|
+
/* ── API table ── */
|
|
356
|
+
.api-table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
|
357
|
+
.api-table th, .api-table td {
|
|
358
|
+
padding: 8px 12px;
|
|
359
|
+
text-align: left;
|
|
360
|
+
border-bottom: 1px solid var(--border);
|
|
361
|
+
}
|
|
362
|
+
.api-table th { color: var(--muted); font-weight: 600; text-transform: uppercase; font-size: 10px; letter-spacing: 0.8px; }
|
|
363
|
+
.api-table td { color: var(--text); }
|
|
364
|
+
.api-table td:first-child { font-family: monospace; color: var(--blue); white-space: nowrap; }
|
|
365
|
+
.method-badge {
|
|
366
|
+
display: inline-block;
|
|
367
|
+
padding: 1px 6px;
|
|
368
|
+
border-radius: 4px;
|
|
369
|
+
font-size: 10px;
|
|
370
|
+
font-weight: 700;
|
|
371
|
+
font-family: monospace;
|
|
372
|
+
}
|
|
373
|
+
.method-get { background: rgba(63,185,80,.15); color: var(--green); }
|
|
374
|
+
.method-post { background: rgba(88,166,255,.15); color: var(--blue); }
|
|
375
|
+
.method-put { background: rgba(210,153,34,.15); color: var(--yellow); }
|
|
376
|
+
.method-del { background: rgba(248,81,73,.15); color: var(--red); }
|
|
377
|
+
|
|
378
|
+
/* ── Token display ── */
|
|
379
|
+
.token-box {
|
|
380
|
+
display: flex;
|
|
381
|
+
gap: 8px;
|
|
382
|
+
align-items: center;
|
|
383
|
+
background: #010409;
|
|
384
|
+
border: 1px solid var(--green);
|
|
385
|
+
border-radius: 8px;
|
|
386
|
+
padding: 12px 16px;
|
|
387
|
+
margin: 12px 0;
|
|
388
|
+
}
|
|
389
|
+
.token-box code {
|
|
390
|
+
flex: 1;
|
|
391
|
+
font-family: monospace;
|
|
392
|
+
font-size: 13px;
|
|
393
|
+
color: var(--accent);
|
|
394
|
+
word-break: break-all;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/* ── URL cards (Phase 6) ── */
|
|
398
|
+
.url-grid { display: flex; flex-direction: column; gap: 12px; }
|
|
399
|
+
.url-item {
|
|
400
|
+
display: flex;
|
|
401
|
+
align-items: center;
|
|
402
|
+
gap: 12px;
|
|
403
|
+
background: var(--panel2);
|
|
404
|
+
border: 1px solid var(--border);
|
|
405
|
+
border-radius: 8px;
|
|
406
|
+
padding: 12px 16px;
|
|
407
|
+
}
|
|
408
|
+
.url-item .ui-icon { font-size: 20px; width: 32px; text-align: center; }
|
|
409
|
+
.url-item .ui-label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.8px; }
|
|
410
|
+
.url-item .ui-value { font-size: 14px; color: var(--blue); word-break: break-all; }
|
|
411
|
+
.url-item a.ui-open {
|
|
412
|
+
margin-left: auto;
|
|
413
|
+
padding: 5px 12px;
|
|
414
|
+
background: var(--panel);
|
|
415
|
+
border: 1px solid var(--border);
|
|
416
|
+
border-radius: 5px;
|
|
417
|
+
color: var(--text);
|
|
418
|
+
font-size: 12px;
|
|
419
|
+
text-decoration: none;
|
|
420
|
+
white-space: nowrap;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.health-row {
|
|
424
|
+
display: flex;
|
|
425
|
+
align-items: center;
|
|
426
|
+
gap: 10px;
|
|
427
|
+
margin-top: 16px;
|
|
428
|
+
font-size: 13px;
|
|
429
|
+
}
|
|
430
|
+
.health-dot {
|
|
431
|
+
width: 10px; height: 10px; border-radius: 50%;
|
|
432
|
+
background: var(--yellow);
|
|
433
|
+
flex-shrink: 0;
|
|
434
|
+
}
|
|
435
|
+
.health-dot.ok { background: var(--green); }
|
|
436
|
+
.health-dot.err { background: var(--red); }
|
|
437
|
+
|
|
438
|
+
/* ── Notice ── */
|
|
439
|
+
.notice {
|
|
440
|
+
display: flex;
|
|
441
|
+
gap: 10px;
|
|
442
|
+
padding: 12px 14px;
|
|
443
|
+
border-radius: 7px;
|
|
444
|
+
font-size: 13px;
|
|
445
|
+
margin-bottom: 16px;
|
|
446
|
+
}
|
|
447
|
+
.notice.info { background: rgba(88,166,255,.07); border: 1px solid rgba(88,166,255,.2); }
|
|
448
|
+
.notice.warn { background: rgba(210,153,34,.07); border: 1px solid rgba(210,153,34,.2); }
|
|
449
|
+
.notice.success { background: rgba(63,185,80,.07); border: 1px solid rgba(63,185,80,.2); }
|
|
450
|
+
|
|
451
|
+
/* ── IDE picker ── */
|
|
452
|
+
.ide-card {
|
|
453
|
+
display: flex;
|
|
454
|
+
align-items: flex-start;
|
|
455
|
+
gap: 12px;
|
|
456
|
+
padding: 14px;
|
|
457
|
+
border: 1px solid var(--border);
|
|
458
|
+
border-radius: 8px;
|
|
459
|
+
cursor: pointer;
|
|
460
|
+
transition: border-color .15s, background .15s;
|
|
461
|
+
position: relative;
|
|
462
|
+
padding-bottom: 36px;
|
|
463
|
+
}
|
|
464
|
+
.ide-card:hover { border-color: var(--blue); background: rgba(88,166,255,.04); }
|
|
465
|
+
.ide-card input[type=checkbox] { display: none; }
|
|
466
|
+
.ide-card .ide-icon { font-size: 22px; flex-shrink: 0; }
|
|
467
|
+
.ide-card .ide-name { font-weight: 600; font-size: 13px; }
|
|
468
|
+
.ide-card .ide-desc { font-size: 11px; color: var(--muted); margin-top: 2px; }
|
|
469
|
+
.ide-card .ide-check { position: absolute; top: 10px; right: 10px; color: var(--green); font-size: 16px; display: none; }
|
|
470
|
+
.ide-card.selected { border-color: var(--green); background: rgba(63,185,80,.06); }
|
|
471
|
+
.ide-card.selected .ide-check { display: block; }
|
|
472
|
+
|
|
473
|
+
.connect-result-line { padding: 2px 0; }
|
|
474
|
+
.connect-result-line.ok { color: var(--green); }
|
|
475
|
+
.connect-result-line.err { color: var(--red); }
|
|
476
|
+
.connect-result-line.info { color: var(--muted); }
|
|
477
|
+
|
|
478
|
+
/* Collapsible Groups for Rules & Skills */
|
|
479
|
+
details.group-details summary::-webkit-details-marker { display: none; }
|
|
480
|
+
details.group-details summary { list-style: none; }
|
|
481
|
+
details.group-details[open] .details-arrow { transform: rotate(0deg); }
|
|
482
|
+
details.group-details:not([open]) .details-arrow { transform: rotate(-90deg); }
|
|
483
|
+
|
|
484
|
+
/* Skills specific accent styling */
|
|
485
|
+
details.group-details.skill-group {
|
|
486
|
+
border-color: rgba(188, 140, 255, 0.25) !important;
|
|
487
|
+
}
|
|
488
|
+
details.group-details.skill-group summary {
|
|
489
|
+
background: rgba(188, 140, 255, 0.03) !important;
|
|
490
|
+
}
|
|
491
|
+
details.group-details.skill-group summary:hover {
|
|
492
|
+
background: rgba(188, 140, 255, 0.06) !important;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/* ── Floating Persistent Install Status Bar ── */
|
|
496
|
+
.persistent-install-bar {
|
|
497
|
+
position: fixed;
|
|
498
|
+
bottom: 24px;
|
|
499
|
+
right: 24px;
|
|
500
|
+
width: 380px;
|
|
501
|
+
background: rgba(22, 27, 34, 0.85);
|
|
502
|
+
backdrop-filter: blur(16px) saturate(120%);
|
|
503
|
+
-webkit-backdrop-filter: blur(16px) saturate(120%);
|
|
504
|
+
border: 1px solid rgba(48, 54, 61, 0.85);
|
|
505
|
+
border-radius: 12px;
|
|
506
|
+
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
|
|
507
|
+
padding: 16px;
|
|
508
|
+
z-index: 10000;
|
|
509
|
+
transition: opacity 0.3s ease, transform 0.3s ease;
|
|
510
|
+
display: none; /* hidden by default */
|
|
511
|
+
}
|
|
512
|
+
.persistent-install-bar.visible {
|
|
513
|
+
display: block;
|
|
514
|
+
animation: slideInBar 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
|
515
|
+
}
|
|
516
|
+
@keyframes slideInBar {
|
|
517
|
+
from {
|
|
518
|
+
opacity: 0;
|
|
519
|
+
transform: translateY(20px) scale(0.95);
|
|
520
|
+
}
|
|
521
|
+
to {
|
|
522
|
+
opacity: 1;
|
|
523
|
+
transform: translateY(0) scale(1);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
.pbar-header {
|
|
527
|
+
display: flex;
|
|
528
|
+
align-items: center;
|
|
529
|
+
justify-content: space-between;
|
|
530
|
+
margin-bottom: 10px;
|
|
531
|
+
}
|
|
532
|
+
.pbar-title {
|
|
533
|
+
font-size: 13px;
|
|
534
|
+
font-weight: 600;
|
|
535
|
+
color: var(--text);
|
|
536
|
+
display: flex;
|
|
537
|
+
align-items: center;
|
|
538
|
+
gap: 8px;
|
|
539
|
+
}
|
|
540
|
+
.pbar-spinner {
|
|
541
|
+
width: 8px;
|
|
542
|
+
height: 8px;
|
|
543
|
+
background-color: var(--blue);
|
|
544
|
+
border-radius: 50%;
|
|
545
|
+
animation: pbar-pulse 1.4s infinite ease-in-out;
|
|
546
|
+
}
|
|
547
|
+
@keyframes pbar-pulse {
|
|
548
|
+
0%, 100% { transform: scale(0.6); opacity: 0.4; }
|
|
549
|
+
50% { transform: scale(1.2); opacity: 1; }
|
|
550
|
+
}
|
|
551
|
+
.pbar-details-link {
|
|
552
|
+
font-size: 11px;
|
|
553
|
+
color: var(--blue);
|
|
554
|
+
text-decoration: none;
|
|
555
|
+
cursor: pointer;
|
|
556
|
+
font-weight: 500;
|
|
557
|
+
}
|
|
558
|
+
.pbar-details-link:hover {
|
|
559
|
+
text-decoration: underline;
|
|
560
|
+
}
|
|
561
|
+
.pbar-track {
|
|
562
|
+
height: 6px;
|
|
563
|
+
background: var(--border);
|
|
564
|
+
border-radius: 3px;
|
|
565
|
+
overflow: hidden;
|
|
566
|
+
margin-bottom: 8px;
|
|
567
|
+
}
|
|
568
|
+
.pbar-fill {
|
|
569
|
+
height: 100%;
|
|
570
|
+
width: 0%;
|
|
571
|
+
background: linear-gradient(90deg, var(--green), var(--accent));
|
|
572
|
+
border-radius: 3px;
|
|
573
|
+
transition: width 0.4s ease;
|
|
574
|
+
}
|
|
575
|
+
.pbar-status-text {
|
|
576
|
+
font-size: 11px;
|
|
577
|
+
color: var(--muted);
|
|
578
|
+
white-space: nowrap;
|
|
579
|
+
overflow: hidden;
|
|
580
|
+
text-overflow: ellipsis;
|
|
581
|
+
margin-bottom: 4px;
|
|
582
|
+
}
|
|
583
|
+
.pbar-latest-log {
|
|
584
|
+
font-size: 10px;
|
|
585
|
+
font-family: 'SF Mono', monospace;
|
|
586
|
+
color: var(--blue);
|
|
587
|
+
background: rgba(88, 166, 255, 0.05);
|
|
588
|
+
border-radius: 4px;
|
|
589
|
+
padding: 4px 8px;
|
|
590
|
+
white-space: nowrap;
|
|
591
|
+
overflow: hidden;
|
|
592
|
+
text-overflow: ellipsis;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/* ── GitHub automated backup checklist ── */
|
|
596
|
+
.progress-step-item {
|
|
597
|
+
display: flex;
|
|
598
|
+
align-items: center;
|
|
599
|
+
gap: 12px;
|
|
600
|
+
padding: 8px 12px;
|
|
601
|
+
border-radius: 6px;
|
|
602
|
+
font-size: 13px;
|
|
603
|
+
margin-bottom: 8px;
|
|
604
|
+
background: rgba(255, 255, 255, 0.02);
|
|
605
|
+
border: 1px solid var(--border);
|
|
606
|
+
transition: all 0.2s ease;
|
|
607
|
+
}
|
|
608
|
+
.progress-step-item.pending {
|
|
609
|
+
color: var(--muted);
|
|
610
|
+
opacity: 0.6;
|
|
611
|
+
}
|
|
612
|
+
.progress-step-item.active {
|
|
613
|
+
color: var(--blue);
|
|
614
|
+
background: rgba(88, 166, 255, 0.05);
|
|
615
|
+
border-color: rgba(88, 166, 255, 0.3);
|
|
616
|
+
}
|
|
617
|
+
.progress-step-item.success {
|
|
618
|
+
color: var(--green);
|
|
619
|
+
background: rgba(63, 185, 80, 0.05);
|
|
620
|
+
border-color: rgba(63, 185, 80, 0.3);
|
|
621
|
+
}
|
|
622
|
+
.progress-step-item.error {
|
|
623
|
+
color: var(--red);
|
|
624
|
+
background: rgba(248, 81, 73, 0.05);
|
|
625
|
+
border-color: rgba(248, 81, 73, 0.3);
|
|
626
|
+
}
|
|
627
|
+
.progress-step-item .step-icon {
|
|
628
|
+
font-size: 14px;
|
|
629
|
+
font-weight: bold;
|
|
630
|
+
width: 18px;
|
|
631
|
+
display: inline-flex;
|
|
632
|
+
justify-content: center;
|
|
633
|
+
}
|
|
634
|
+
.github-log-console {
|
|
635
|
+
background: #0d1117;
|
|
636
|
+
border: 1px solid var(--border);
|
|
637
|
+
border-radius: 6px;
|
|
638
|
+
padding: 12px;
|
|
639
|
+
max-height: 200px;
|
|
640
|
+
overflow-y: auto;
|
|
641
|
+
font-family: 'SF Mono', 'Cascadia Code', monospace;
|
|
642
|
+
font-size: 12px;
|
|
643
|
+
color: #c9d1d9;
|
|
644
|
+
white-space: pre-wrap;
|
|
645
|
+
word-break: break-all;
|
|
646
|
+
margin-top: 14px;
|
|
647
|
+
}
|
|
648
|
+
</style>
|
|
649
|
+
</head>
|
|
650
|
+
<body>
|
|
651
|
+
|
|
652
|
+
<div class="wizard-shell">
|
|
653
|
+
|
|
654
|
+
<!-- Sidebar nav -->
|
|
655
|
+
<aside class="sidebar">
|
|
656
|
+
<div class="sidebar-brand">
|
|
657
|
+
<h2>⚡ Total Recall</h2>
|
|
658
|
+
<p>Sovereign AI Setup</p>
|
|
659
|
+
</div>
|
|
660
|
+
<ul class="step-nav" id="step-nav">
|
|
661
|
+
<li class="active" data-phase="0"><span class="step-badge">1</span>Welcome</li>
|
|
662
|
+
<li data-phase="1"><span class="step-badge">2</span>Where to Run</li>
|
|
663
|
+
<li data-phase="2"><span class="step-badge">3</span>Installing</li>
|
|
664
|
+
<li data-phase="3"><span class="step-badge">4</span>Auth & Keys</li>
|
|
665
|
+
<li data-phase="4"><span class="step-badge">5</span>Integrations</li>
|
|
666
|
+
<li data-phase="5"><span class="step-badge">6</span>API Reference</li>
|
|
667
|
+
<li data-phase="6"><span class="step-badge">7</span>Done</li>
|
|
668
|
+
<li data-phase="7" style="margin-top:auto;border-top:1px solid var(--border);padding-top:12px"><span class="step-badge">⚙</span>Settings</li>
|
|
669
|
+
</ul>
|
|
670
|
+
</aside>
|
|
671
|
+
|
|
672
|
+
<!-- Main area -->
|
|
673
|
+
<main class="main-content">
|
|
674
|
+
|
|
675
|
+
<!-- ═══════════════════════════════ PHASE 0 — Welcome ═══════════════════════ -->
|
|
676
|
+
<section class="phase active" id="phase-0">
|
|
677
|
+
<div class="phase-header">
|
|
678
|
+
<h1>Welcome to Total Recall</h1>
|
|
679
|
+
<p>Your own private AI memory that feeds your IDEs and coding agents. Takes about 5 minutes to set up.</p>
|
|
680
|
+
</div>
|
|
681
|
+
|
|
682
|
+
<div class="card">
|
|
683
|
+
<h3>What does it do?</h3>
|
|
684
|
+
<p style="margin-bottom:14px">Total Recall gives your AI coding agents a persistent, sovereign memory. Your AI always knows who you are, what you're working on, and how you like code to be written. No cloud subscriptions. 100% local ownership.</p>
|
|
685
|
+
<ul>
|
|
686
|
+
<li>🧠 <strong>Remembers across sessions</strong> <span>— your preferences, projects, and decisions survive every new chat</span></li>
|
|
687
|
+
<li>🔗 <strong>Works with your AI tools</strong> <span>— Claude Code, Codex, Gemini CLI, and any OpenAI-compatible app</span></li>
|
|
688
|
+
<li>🔮 <strong>Syncs with Obsidian</strong> <span>— browse and explore your personal VFS memory files visually in a markdown note graph</span></li>
|
|
689
|
+
<li>🔒 <strong>100% private</strong> <span>— runs entirely on your hardware under your control</span></li>
|
|
690
|
+
</ul>
|
|
691
|
+
</div>
|
|
692
|
+
|
|
693
|
+
<div class="card">
|
|
694
|
+
<h3>How it works</h3>
|
|
695
|
+
<p style="color:var(--muted);font-size:13px;margin-bottom:14px">Your memory lives in plain text Markdown files on your machine. Your AI agents read them automatically at the start of every conversation.</p>
|
|
696
|
+
<div class="arch-row">
|
|
697
|
+
<span class="arch-box">📁 Your memory files</span>
|
|
698
|
+
<span class="arch-arrow">→</span>
|
|
699
|
+
<span class="arch-box">🧠 AI reads them</span>
|
|
700
|
+
<span class="arch-arrow">→</span>
|
|
701
|
+
<span class="arch-box">💬 Every chat is personalized</span>
|
|
702
|
+
</div>
|
|
703
|
+
<div class="arch-row" style="margin-top:10px">
|
|
704
|
+
<span class="arch-box">Claude Code / Codex / Gemini CLI</span>
|
|
705
|
+
<span class="arch-arrow">→</span>
|
|
706
|
+
<span class="arch-box">All connect to the same sovereign VFS brain</span>
|
|
707
|
+
</div>
|
|
708
|
+
</div>
|
|
709
|
+
|
|
710
|
+
<div class="btn-row">
|
|
711
|
+
<button class="btn btn-primary" onclick="goPhase(1)">Let's Set It Up →</button>
|
|
712
|
+
</div>
|
|
713
|
+
</section>
|
|
714
|
+
|
|
715
|
+
<!-- ═══════════════════════════════ PHASE 1 — Local Setup & Credentials ══════════════════ -->
|
|
716
|
+
<section class="phase" id="phase-1">
|
|
717
|
+
<div class="phase-header">
|
|
718
|
+
<h1>Configure sovereign VFS Brain Server</h1>
|
|
719
|
+
<p>Set up your private VFS brain server. You can run the database server locally on this computer, or offload it to an always-on remote server/VM while keeping your local coding integrations active.</p>
|
|
720
|
+
</div>
|
|
721
|
+
|
|
722
|
+
<!-- Dual-Layer Relationship Explainer -->
|
|
723
|
+
<div class="notice success" style="margin-bottom:20px;line-height:1.5;font-size:13px;border-left:4px solid var(--green)">
|
|
724
|
+
<strong>💡 Workstation & Server Relationship:</strong>
|
|
725
|
+
<p style="color:var(--muted);margin-top:4px;font-size:12px">
|
|
726
|
+
Regardless of where your brain server is hosted, <strong>the wizard will automatically configure this computer</strong> in Phase 4. This ensures your local IDEs, background log relays, and CLI coding agents (like Claude Code) can securely communicate with your memory files.
|
|
727
|
+
</p>
|
|
728
|
+
</div>
|
|
729
|
+
|
|
730
|
+
<!-- Deploy Target Selection -->
|
|
731
|
+
<div class="card">
|
|
732
|
+
<h3>📍 Where to Host the Brain Server</h3>
|
|
733
|
+
<p style="color:var(--muted);font-size:13px;margin-bottom:16px">
|
|
734
|
+
Choose where to host your core plain-text VFS database and SQLite caches.
|
|
735
|
+
</p>
|
|
736
|
+
<div class="radio-group" id="deploy-target" style="margin-bottom:0">
|
|
737
|
+
<label class="radio-item selected">
|
|
738
|
+
<input type="radio" name="deploy-target" value="local" checked>
|
|
739
|
+
<div>
|
|
740
|
+
<div class="ri-label">This Computer <span style="font-weight:400;color:var(--muted)">(Mac or PC)</span></div>
|
|
741
|
+
<div class="ri-desc">Run both the VFS brain server and editor integrations on this machine. Ideal for single-workstation setups. <span style="color:var(--yellow);font-weight:600;display:block;margin-top:4px">⚠️ Note: This computer must remain turned on and awake 24/7 to run background memory optimizations (Dream Cycle) and task schedulers.</span></div>
|
|
742
|
+
</div>
|
|
743
|
+
</label>
|
|
744
|
+
<label class="radio-item" style="margin-top:8px">
|
|
745
|
+
<input type="radio" name="deploy-target" value="localnet">
|
|
746
|
+
<div>
|
|
747
|
+
<div class="ri-label">Mac Mini / Home Server via SSH <span style="font-weight:400;color:var(--muted)">(Local Network Server, Mac Mini, PC, or private VPS)</span></div>
|
|
748
|
+
<div class="ri-desc">Deploy remotely onto a dedicated home server, a local Mac Mini, or a pre-provisioned Linux machine on your private network.</div>
|
|
749
|
+
</div>
|
|
750
|
+
</label>
|
|
751
|
+
<label class="radio-item" style="margin-top:8px">
|
|
752
|
+
<input type="radio" name="deploy-target" value="agent-cloud">
|
|
753
|
+
<div>
|
|
754
|
+
<div class="ri-label">Cloud VM / VPS — Fully Automated <span class="security-badge" title="No CLI/Terminal Needed: Our built-in CLI agent automatically boots the VM on your provider, registers your SSH key, and deploys everything behind the scenes.">🔒 Zero-Terminal</span></div>
|
|
755
|
+
<div class="ri-desc">No server? No problem! Paste your cloud provider API key, and our built-in agent will automatically spin up a fresh $4–$6 VPS instance, authorize your SSH keys, and deploy Total Recall in one click.</div>
|
|
756
|
+
</div>
|
|
757
|
+
</label>
|
|
758
|
+
</div>
|
|
759
|
+
|
|
760
|
+
<!-- Localnet SSH details (shown only when Localnet is selected) -->
|
|
761
|
+
<div id="localnet-details" style="display:none;margin-top:14px;padding-top:14px;border-top:1px solid var(--border)">
|
|
762
|
+
<div class="form-group" style="margin-bottom:12px">
|
|
763
|
+
<label>Mac Mini or Home Server Host / IP Address <span style="font-weight:400;color:var(--muted)">(e.g. macmini.local or 192.168.1.150)</span></label>
|
|
764
|
+
<input type="text" id="cfg-localnet-host" placeholder="e.g. macmini.local or 192.168.1.100" autocomplete="off" spellcheck="false">
|
|
765
|
+
</div>
|
|
766
|
+
<div class="form-group" style="margin-bottom:0">
|
|
767
|
+
<label>SSH Username <span style="font-weight:400;color:var(--muted)">(e.g. root, admin, ubuntu)</span></label>
|
|
768
|
+
<input type="text" id="cfg-localnet-user" placeholder="root" autocomplete="off" spellcheck="false">
|
|
769
|
+
</div>
|
|
770
|
+
</div>
|
|
771
|
+
|
|
772
|
+
<!-- Agent-Automated details (shown only when agent-cloud is selected) -->
|
|
773
|
+
<div id="agent-cloud-details" style="display:none;margin-top:14px;padding-top:14px;border-top:1px solid var(--border)">
|
|
774
|
+
<div class="notice info" style="margin-bottom:16px;line-height:1.5;font-size:12px;border:1px solid rgba(88,166,255,0.3);background:rgba(88,166,255,0.05)">
|
|
775
|
+
<div><strong>🤖 Zero-Terminal Automated Cloud Installation:</strong></div>
|
|
776
|
+
<p style="color:var(--muted);margin-top:4px;font-size:11px">
|
|
777
|
+
No need to open a terminal, configure Linux, or manage SSH keys! The local CLI agent will automatically generate a secure SSH key pair, provision a lightweight standard VM under your account, and set up your brain securely.
|
|
778
|
+
</p>
|
|
779
|
+
</div>
|
|
780
|
+
<div class="form-group" style="margin-bottom:12px">
|
|
781
|
+
<label>Cloud Provider (e.g. DigitalOcean, Hetzner, AWS, Linode, Vultr)</label>
|
|
782
|
+
<input type="text" id="cfg-cloud-provider" placeholder="Enter your cloud provider name" style="width:100%;padding:8px;background:var(--panel2);border:1px solid var(--border);border-radius:6px;color:var(--text);font-family:inherit">
|
|
783
|
+
</div>
|
|
784
|
+
<div class="form-group" style="margin-bottom:12px">
|
|
785
|
+
<label>Provider API Key / Token <span class="security-badge" title="Local Encryption: This token is encrypted locally on this machine and never exposed. It is used strictly to provision your server instance.">🔒 Secure Local</span></label>
|
|
786
|
+
<input type="password" id="cfg-cloud-token" placeholder="Paste your cloud provider's API key or token" autocomplete="off" style="font-family:monospace">
|
|
787
|
+
</div>
|
|
788
|
+
<div class="form-group" style="margin-bottom:0">
|
|
789
|
+
<label>Preferred Region / Zone (Optional)</label>
|
|
790
|
+
<input type="text" id="cfg-cloud-region" placeholder="e.g. nyc3, fra1, us-east-1 (or leave empty for auto)" style="width:100%;padding:8px;background:var(--panel2);border:1px solid var(--border);border-radius:6px;color:var(--text);font-family:inherit">
|
|
791
|
+
</div>
|
|
792
|
+
</div>
|
|
793
|
+
</div>
|
|
794
|
+
|
|
795
|
+
<!-- Project Brain Selector -->
|
|
796
|
+
<div class="card" id="project-brain-card">
|
|
797
|
+
<h3>📂 Install Location — Select Brains to Provision</h3>
|
|
798
|
+
<p style="color:var(--muted);font-size:13px;margin-bottom:16px">
|
|
799
|
+
Choose where to install Total Recall memory brains. Select <strong>Global</strong> for universal identity rules and preferences, and/or individual <strong>project folders</strong> for project-specific knowledge.
|
|
800
|
+
</p>
|
|
801
|
+
|
|
802
|
+
<!-- Global toggle -->
|
|
803
|
+
<label class="checkbox-item" style="padding:10px 14px;background:var(--panel2);border:2px solid var(--green);border-radius:8px;margin-bottom:12px;gap:12px">
|
|
804
|
+
<input type="checkbox" id="brain-global" checked style="width:16px;height:16px;accent-color:var(--green)">
|
|
805
|
+
<div style="flex:1">
|
|
806
|
+
<div style="font-weight:600;font-size:13px">🌐 Global Brain <span style="font-weight:400;color:var(--green);font-size:11px;margin-left:6px">Recommended</span></div>
|
|
807
|
+
<div style="font-size:11px;color:var(--muted);margin-top:2px">~/.agent/ — Universal identity, preferences, invariants. Shared across all projects.</div>
|
|
808
|
+
</div>
|
|
809
|
+
</label>
|
|
810
|
+
|
|
811
|
+
<!-- Detected projects list -->
|
|
812
|
+
<div style="border-top:1px solid var(--border);padding-top:14px;margin-top:4px">
|
|
813
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">
|
|
814
|
+
<span style="font-size:12px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:0.8px">Detected Projects</span>
|
|
815
|
+
<button class="btn btn-secondary" style="padding:4px 12px;font-size:11px" onclick="loadDetectedProjects()">⟳ Refresh</button>
|
|
816
|
+
</div>
|
|
817
|
+
<div id="project-list-container" style="max-height:280px;overflow-y:auto;display:flex;flex-direction:column;gap:6px">
|
|
818
|
+
<div id="project-list-loading" style="text-align:center;padding:20px;color:var(--muted);font-size:12px">
|
|
819
|
+
<span class="spinner" style="display:inline-block;width:14px;height:14px;border:2px solid var(--border);border-top-color:var(--blue);border-radius:50%;animation:spin 0.6s linear infinite;vertical-align:middle;margin-right:6px"></span>
|
|
820
|
+
Scanning for code projects...
|
|
821
|
+
</div>
|
|
822
|
+
<div id="project-list" style="display:none"></div>
|
|
823
|
+
<div id="project-list-empty" style="display:none;text-align:center;padding:16px;color:var(--muted);font-size:12px">
|
|
824
|
+
No projects detected. Use "Browse" to add project folders manually.
|
|
825
|
+
</div>
|
|
826
|
+
</div>
|
|
827
|
+
</div>
|
|
828
|
+
|
|
829
|
+
<!-- Custom path / Browse -->
|
|
830
|
+
<div style="border-top:1px solid var(--border);padding-top:14px;margin-top:14px">
|
|
831
|
+
<div style="display:flex;gap:8px;align-items:flex-end">
|
|
832
|
+
<div class="form-group" style="flex:1;margin-bottom:0">
|
|
833
|
+
<label style="margin-bottom:4px">Add Custom Project Path</label>
|
|
834
|
+
<input type="text" id="custom-project-path" placeholder="/path/to/your/project" autocomplete="off" spellcheck="false">
|
|
835
|
+
</div>
|
|
836
|
+
<button class="btn btn-secondary" style="padding:9px 14px;font-size:12px;white-space:nowrap" onclick="addCustomProject()">+ Add</button>
|
|
837
|
+
</div>
|
|
838
|
+
</div>
|
|
839
|
+
|
|
840
|
+
<!-- Selection summary -->
|
|
841
|
+
<div id="brain-selection-summary" style="margin-top:14px;padding:10px 14px;background:rgba(35,134,54,0.08);border:1px solid rgba(35,134,54,0.2);border-radius:6px;font-size:12px;color:var(--green);display:none">
|
|
842
|
+
<strong>Selected:</strong> <span id="brain-selection-count">0</span> brain(s) will be provisioned.
|
|
843
|
+
</div>
|
|
844
|
+
</div>
|
|
845
|
+
|
|
846
|
+
<!-- API Keys card -->
|
|
847
|
+
<div class="card">
|
|
848
|
+
<h3>🔑 Embedding API Keys (Semantic Search Only)</h3>
|
|
849
|
+
<p style="color:var(--muted);font-size:13px;margin-bottom:16px">
|
|
850
|
+
Total Recall routes all semantic search indexing tasks to lightweight embedding API calls (strictly for vector generation). All reasoning, codegen, and cognitive loops run 100% locally in your developer CLI coding agents (like Claude Code, Codex, or Antigravity) without any third-party routing. At least one key is required.
|
|
851
|
+
</p>
|
|
852
|
+
<div class="form-group" style="margin-bottom:14px">
|
|
853
|
+
<label>Google API Key <span class="security-badge" title="End-to-End Encrypted: Your keys are encrypted locally using AES-256-GCM + scrypt. They never leave this machine.">🔒 Secure Local</span> <span style="font-weight:400;color:var(--muted)">(recommended — text-embedding-004)</span></label>
|
|
854
|
+
<div style="display:flex;gap:8px;align-items:center">
|
|
855
|
+
<input type="password" id="cfg-google-key" placeholder="AIzaSy..." autocomplete="off" style="font-family:monospace;flex:1">
|
|
856
|
+
<button class="btn btn-secondary" style="padding:5px 10px;font-size:12px" onclick="toggleShow('cfg-google-key')">Show</button>
|
|
857
|
+
</div>
|
|
858
|
+
<div class="hint">Get a key from <a href="https://aistudio.google.com" target="_blank" rel="noopener" style="color:var(--blue)">Google AI Studio</a>. Generous free tier.</div>
|
|
859
|
+
</div>
|
|
860
|
+
<div class="form-group" style="margin-bottom:0">
|
|
861
|
+
<label>OpenAI API Key <span class="security-badge" title="End-to-End Encrypted: Your keys are encrypted locally using AES-256-GCM + scrypt. They never leave this machine.">🔒 Secure Local</span> <span style="font-weight:400;color:var(--muted)">(optional fallback — text-embedding-3)</span></label>
|
|
862
|
+
<div style="display:flex;gap:8px;align-items:center">
|
|
863
|
+
<input type="password" id="cfg-openai-key" placeholder="sk-proj-..." autocomplete="off" style="font-family:monospace;flex:1">
|
|
864
|
+
<button class="btn btn-secondary" style="padding:5px 10px;font-size:12px" onclick="toggleShow('cfg-openai-key')">Show</button>
|
|
865
|
+
</div>
|
|
866
|
+
<div class="hint" style="margin-top:4px">Get a key from the <a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener" style="color:var(--blue)">OpenAI Platform</a>. Paid account/credits required.</div>
|
|
867
|
+
</div>
|
|
868
|
+
</div>
|
|
869
|
+
|
|
870
|
+
<!-- Public Access Sharing -->
|
|
871
|
+
<div class="card" style="margin-top:20px">
|
|
872
|
+
<h3>🌐 Public Access Sharing (Optional)</h3>
|
|
873
|
+
<p style="color:var(--muted);font-size:13px;margin-bottom:12px">
|
|
874
|
+
Configure secure remote access to your brain server.
|
|
875
|
+
</p>
|
|
876
|
+
<div class="form-group" style="margin-bottom:0">
|
|
877
|
+
<label style="display:flex;align-items:center;gap:10px;cursor:pointer">
|
|
878
|
+
<input type="checkbox" id="cfg-cloudflare-quick" checked style="width:16px;height:16px;accent-color:var(--green)">
|
|
879
|
+
<span style="font-size:13px;font-weight:600">Share over secure internet (free Cloudflare Quick Tunnel)</span>
|
|
880
|
+
</label>
|
|
881
|
+
<div class="hint" style="margin-top:5px;color:var(--muted);font-size:11px">Generates a secure, temporary <code>*.trycloudflare.com</code> HTTPS URL so you can connect coding agents and tools safely over the internet without configuring ports.</div>
|
|
882
|
+
</div>
|
|
883
|
+
</div>
|
|
884
|
+
|
|
885
|
+
<div class="btn-row">
|
|
886
|
+
<button class="btn btn-secondary" onclick="goPhase(0)">← Back</button>
|
|
887
|
+
<button class="btn btn-primary" onclick="startInstall()"><span id="install-btn-text">Install on this computer</span> →</button>
|
|
888
|
+
</div>
|
|
889
|
+
</section>
|
|
890
|
+
|
|
891
|
+
<!-- ═══════════════════════════════ PHASE 2 — Installing ═══════════════════ -->
|
|
892
|
+
<section class="phase" id="phase-2">
|
|
893
|
+
<div class="phase-header">
|
|
894
|
+
<h1>Installing...</h1>
|
|
895
|
+
<p>Total Recall is being provisioned on your server. This is a lightweight install and usually takes less than 60 seconds.</p>
|
|
896
|
+
</div>
|
|
897
|
+
|
|
898
|
+
<div class="card">
|
|
899
|
+
<div id="progress-track"><div id="progress-bar"></div></div>
|
|
900
|
+
<div id="progress-label">Waiting for install to begin...</div>
|
|
901
|
+
<div id="log"></div>
|
|
902
|
+
</div>
|
|
903
|
+
|
|
904
|
+
<!-- shown after error event -->
|
|
905
|
+
<div id="install-error-notice" style="display:none">
|
|
906
|
+
<div class="notice warn">
|
|
907
|
+
⚠️ Provisioning/Installation failed. Check the error above, go back to edit your options/API key, and try again.
|
|
908
|
+
</div>
|
|
909
|
+
<div class="btn-row">
|
|
910
|
+
<button class="btn btn-secondary" onclick="goPhase(1)">← Go Back & Edit Options</button>
|
|
911
|
+
</div>
|
|
912
|
+
</div>
|
|
913
|
+
|
|
914
|
+
<!-- shown after done event -->
|
|
915
|
+
<div id="install-done-notice" style="display:none">
|
|
916
|
+
<div class="notice success">
|
|
917
|
+
✅ Install complete! Continue to generate your API key.
|
|
918
|
+
</div>
|
|
919
|
+
<div class="btn-row">
|
|
920
|
+
<button class="btn btn-primary" onclick="goPhase(3)">Next: Generate API Key →</button>
|
|
921
|
+
</div>
|
|
922
|
+
</div>
|
|
923
|
+
</section>
|
|
924
|
+
|
|
925
|
+
<!-- ═══════════════════════════════ PHASE 3 — Auth ════════════════════════ -->
|
|
926
|
+
<section class="phase" id="phase-3">
|
|
927
|
+
<div class="phase-header">
|
|
928
|
+
<h1>Auth & API Keys</h1>
|
|
929
|
+
<p>Generate a Personal Access Token (PAT) to authenticate all your integrations.</p>
|
|
930
|
+
</div>
|
|
931
|
+
|
|
932
|
+
<div class="card">
|
|
933
|
+
<h3>What are PATs?</h3>
|
|
934
|
+
<p>Every request to your brain must include a Bearer token. You can create multiple tokens with different scopes — one for Claude Code, one for Cursor, etc.</p>
|
|
935
|
+
</div>
|
|
936
|
+
|
|
937
|
+
<div class="card">
|
|
938
|
+
<h3>Generate your first key</h3>
|
|
939
|
+
|
|
940
|
+
<div class="form-group">
|
|
941
|
+
<label>Key Name</label>
|
|
942
|
+
<input type="text" id="key-name" placeholder="my-key" value="default" autocomplete="off">
|
|
943
|
+
</div>
|
|
944
|
+
|
|
945
|
+
<div class="form-group">
|
|
946
|
+
<label>Scope</label>
|
|
947
|
+
<div class="radio-group" id="key-scope">
|
|
948
|
+
<label class="radio-item selected">
|
|
949
|
+
<input type="radio" name="key-scope" value="*" checked>
|
|
950
|
+
<div>
|
|
951
|
+
<div class="ri-label">Full Access <code style="font-size:11px">/*</code></div>
|
|
952
|
+
<div class="ri-desc">Read + write memory, run sandbox, manage keys. Recommended for personal use.</div>
|
|
953
|
+
</div>
|
|
954
|
+
</label>
|
|
955
|
+
<label class="radio-item">
|
|
956
|
+
<input type="radio" name="key-scope" value="/v1/chat/completions">
|
|
957
|
+
<div>
|
|
958
|
+
<div class="ri-label">Chat Only <code style="font-size:11px">/v1/chat/completions</code></div>
|
|
959
|
+
<div class="ri-desc">Only allows chat completions. Good for sharing with apps.</div>
|
|
960
|
+
</div>
|
|
961
|
+
</label>
|
|
962
|
+
<label class="radio-item">
|
|
963
|
+
<input type="radio" name="key-scope" value="/memory:read">
|
|
964
|
+
<div>
|
|
965
|
+
<div class="ri-label">Read Only <code style="font-size:11px">/memory:read</code></div>
|
|
966
|
+
<div class="ri-desc">Can read memory but not modify it.</div>
|
|
967
|
+
</div>
|
|
968
|
+
</label>
|
|
969
|
+
</div>
|
|
970
|
+
</div>
|
|
971
|
+
|
|
972
|
+
<div id="key-error" class="notice warn" style="display:none">⚠️ <span id="key-error-text"></span></div>
|
|
973
|
+
|
|
974
|
+
<div class="btn-row">
|
|
975
|
+
<button class="btn btn-primary" id="gen-key-btn" onclick="generateKey()">Generate Key</button>
|
|
976
|
+
</div>
|
|
977
|
+
|
|
978
|
+
<div id="key-result" style="display:none; margin-top:20px">
|
|
979
|
+
<div class="notice success" id="key-success-notice">✅ API key active and saved securely in your local configuration. You can copy it at any time (even when hidden) or find it in the Settings tab later. No need to write it down or worry about losing it!</div>
|
|
980
|
+
<div class="token-box" style="display:flex; gap:8px; align-items:center">
|
|
981
|
+
<input type="password" id="pat-key-display" readonly style="font-family:monospace; flex:1; background:var(--surface); border:1px solid var(--border); color:var(--text); border-radius:6px; padding:7px 10px; font-size:13px" />
|
|
982
|
+
<button class="btn btn-secondary" style="padding:5px 10px;font-size:12px" onclick="toggleShow('pat-key-display')">Show</button>
|
|
983
|
+
<button class="btn btn-secondary" style="padding:5px 10px;font-size:12px" onclick="copyToken('pat-key-display')">Copy</button>
|
|
984
|
+
</div>
|
|
985
|
+
</div>
|
|
986
|
+
</div>
|
|
987
|
+
|
|
988
|
+
<div style="height:24px"></div>
|
|
989
|
+
|
|
990
|
+
<div class="card">
|
|
991
|
+
<h3>📦 GitHub Automatic Backup <span style="font-weight:400;font-size:13px;color:var(--muted);margin-left:8px">optional</span></h3>
|
|
992
|
+
<p style="color:var(--muted);font-size:13px;margin-bottom:8px">
|
|
993
|
+
Securely back up your brain's VFS (your personal memory files, rules, and configurations) to a private GitHub repository automatically. We will create a private repository named <strong style="color:var(--text)">total-recall-brain</strong> on your GitHub account, perform the initial push, and schedule a daily automatic update.
|
|
994
|
+
</p>
|
|
995
|
+
<p style="color:var(--muted);font-size:13px;margin-bottom:20px">
|
|
996
|
+
To set this up, create a Personal Access Token (PAT) with <strong style="color:var(--text)">repo</strong> scope.
|
|
997
|
+
</p>
|
|
998
|
+
|
|
999
|
+
<div class="form-group">
|
|
1000
|
+
<label>GitHub Personal Access Token</label>
|
|
1001
|
+
<input type="password" id="cfg-github-token" placeholder="ghp_..." autocomplete="off">
|
|
1002
|
+
<div style="margin-top:5px;font-size:11px;color:var(--muted)">
|
|
1003
|
+
Generate one at <a href="https://github.com/settings/tokens/new?scopes=repo&description=Total%20Recall%20Backup" target="_blank" style="color:var(--blue)">github.com/settings/tokens</a> with the <code>repo</code> scope.
|
|
1004
|
+
</div>
|
|
1005
|
+
</div>
|
|
1006
|
+
|
|
1007
|
+
<div id="github-progress" style="display:none; margin-top:20px; gap:8px">
|
|
1008
|
+
<div class="progress-step-item pending" id="gh-step-1">
|
|
1009
|
+
<span class="step-icon">○</span> Authenticate with GitHub
|
|
1010
|
+
</div>
|
|
1011
|
+
<div class="progress-step-item pending" id="gh-step-2">
|
|
1012
|
+
<span class="step-icon">○</span> Provision private repository "total-recall-brain"
|
|
1013
|
+
</div>
|
|
1014
|
+
<div class="progress-step-item pending" id="gh-step-3">
|
|
1015
|
+
<span class="step-icon">○</span> Initialize & configure local git repository
|
|
1016
|
+
</div>
|
|
1017
|
+
<div class="progress-step-item pending" id="gh-step-4">
|
|
1018
|
+
<span class="step-icon">○</span> Perform initial push to GitHub
|
|
1019
|
+
</div>
|
|
1020
|
+
<div class="progress-step-item pending" id="gh-step-5">
|
|
1021
|
+
<span class="step-icon">○</span> Schedule daily automated backup (2:00 AM)
|
|
1022
|
+
</div>
|
|
1023
|
+
</div>
|
|
1024
|
+
|
|
1025
|
+
<div id="github-log" class="github-log-console" style="display:none"></div>
|
|
1026
|
+
|
|
1027
|
+
<div id="github-result-notice" class="notice" style="display:none; margin-top:14px"></div>
|
|
1028
|
+
|
|
1029
|
+
<div class="btn-row" style="margin-top:16px">
|
|
1030
|
+
<button class="btn btn-primary" id="cfg-github-btn" onclick="saveGitHubBackup()">Configure & Back Up</button>
|
|
1031
|
+
</div>
|
|
1032
|
+
</div>
|
|
1033
|
+
|
|
1034
|
+
<div style="height:24px"></div>
|
|
1035
|
+
|
|
1036
|
+
<div class="card">
|
|
1037
|
+
<h3>🔍 Web Search for Automatic Research <span style="font-weight:400;font-size:13px;color:var(--muted);margin-left:8px">optional</span></h3>
|
|
1038
|
+
<p style="color:var(--muted);font-size:13px;margin-bottom:8px">
|
|
1039
|
+
Total Recall runs in the background and automatically looks things up on the web — finding relevant articles, documentation, and facts related to what you're working on, and saving them to your memory.
|
|
1040
|
+
</p>
|
|
1041
|
+
<p style="color:var(--muted);font-size:13px;margin-bottom:20px">
|
|
1042
|
+
<strong style="color:var(--text)">You don't need a key to get started.</strong> Without one, it uses DuckDuckGo and Wikipedia (both free, no sign-up). Adding any one of the providers below gives it full web search results. Pick whichever you prefer — they're all roughly equivalent in quality.
|
|
1043
|
+
</p>
|
|
1044
|
+
|
|
1045
|
+
<div style="display:grid;gap:14px">
|
|
1046
|
+
|
|
1047
|
+
<div style="border:1px solid var(--border);border-radius:8px;padding:14px">
|
|
1048
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
|
|
1049
|
+
<strong>Tavily</strong>
|
|
1050
|
+
<span style="font-size:11px;background:var(--green);color:#000;padding:2px 7px;border-radius:10px">Best for AI agents</span>
|
|
1051
|
+
</div>
|
|
1052
|
+
<p style="font-size:12px;color:var(--muted);margin-bottom:8px">Designed specifically for AI research — returns clean, readable text from each page instead of just a link and a short description. Saves time. 1,000 free searches/month.</p>
|
|
1053
|
+
<input type="password" id="tavily-key" placeholder="tvly-…" autocomplete="off" style="font-family:monospace;width:100%">
|
|
1054
|
+
<div style="margin-top:5px;font-size:11px;color:var(--muted)">Get a free key at <a href="https://tavily.com" target="_blank" style="color:var(--blue)">tavily.com</a></div>
|
|
1055
|
+
</div>
|
|
1056
|
+
|
|
1057
|
+
<div style="border:1px solid var(--border);border-radius:8px;padding:14px">
|
|
1058
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
|
|
1059
|
+
<strong>Brave Search</strong>
|
|
1060
|
+
<span style="font-size:11px;color:var(--muted)">~1,000 free queries/month</span>
|
|
1061
|
+
</div>
|
|
1062
|
+
<p style="font-size:12px;color:var(--muted);margin-bottom:8px">Independent web search (not Google). Returns a list of URLs and short descriptions for each result. Solid general-purpose choice.</p>
|
|
1063
|
+
<input type="password" id="brave-key" placeholder="BSA…" autocomplete="off" style="font-family:monospace;width:100%">
|
|
1064
|
+
<div style="margin-top:5px;font-size:11px;color:var(--muted)">Get a free key at <a href="https://brave.com/search/api/" target="_blank" style="color:var(--blue)">brave.com/search/api</a></div>
|
|
1065
|
+
</div>
|
|
1066
|
+
|
|
1067
|
+
<div style="border:1px solid var(--border);border-radius:8px;padding:14px">
|
|
1068
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
|
|
1069
|
+
<strong>Exa</strong>
|
|
1070
|
+
<span style="font-size:11px;color:var(--muted)">~1,000 free queries/month</span>
|
|
1071
|
+
</div>
|
|
1072
|
+
<p style="font-size:12px;color:var(--muted);margin-bottom:8px">Finds pages by meaning rather than exact keywords — good at surfacing recent articles and blog posts that are thematically relevant, not just keyword matches.</p>
|
|
1073
|
+
<input type="password" id="exa-key" placeholder="…" autocomplete="off" style="font-family:monospace;width:100%">
|
|
1074
|
+
<div style="margin-top:5px;font-size:11px;color:var(--muted)">Get a free key at <a href="https://exa.ai" target="_blank" style="color:var(--blue)">exa.ai</a></div>
|
|
1075
|
+
</div>
|
|
1076
|
+
|
|
1077
|
+
<div style="border:1px solid var(--border);border-radius:8px;padding:14px">
|
|
1078
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
|
|
1079
|
+
<strong>Serper</strong>
|
|
1080
|
+
<span style="font-size:11px;color:var(--muted)">2,500 free credits (one-time)</span>
|
|
1081
|
+
</div>
|
|
1082
|
+
<p style="font-size:12px;color:var(--muted);margin-bottom:8px">Returns Google Search results. The free credits are a one-time trial rather than a monthly allowance, so it's best used as a last fallback or for occasional use.</p>
|
|
1083
|
+
<input type="password" id="serper-key" placeholder="…" autocomplete="off" style="font-family:monospace;width:100%">
|
|
1084
|
+
<div style="margin-top:5px;font-size:11px;color:var(--muted)">Get a free key at <a href="https://serper.dev" target="_blank" style="color:var(--blue)">serper.dev</a></div>
|
|
1085
|
+
</div>
|
|
1086
|
+
|
|
1087
|
+
</div>
|
|
1088
|
+
|
|
1089
|
+
<div style="margin-top:14px;padding:10px 12px;background:rgba(255,255,255,0.04);border-radius:6px;font-size:12px;color:var(--muted)">
|
|
1090
|
+
⏱ <strong style="color:var(--text)">Daily limit:</strong> To stay within free tiers, the engine makes at most <strong style="color:var(--text)">50 paid searches per day</strong> by default (~1,500/month). When that's used up it switches back to DuckDuckGo for the rest of the day. You can change this limit anytime in <code>~/.agent/config/research.yml</code>.
|
|
1091
|
+
</div>
|
|
1092
|
+
|
|
1093
|
+
<div id="search-save-result" class="notice" style="display:none;margin-top:14px"></div>
|
|
1094
|
+
|
|
1095
|
+
<div class="btn-row" style="margin-top:16px">
|
|
1096
|
+
<button class="btn btn-secondary" onclick="saveSearchKeys()">Save Search Keys</button>
|
|
1097
|
+
<span style="font-size:12px;color:var(--muted);align-self:center">Saved to ~/.agent/config/research.yml</span>
|
|
1098
|
+
</div>
|
|
1099
|
+
</div>
|
|
1100
|
+
|
|
1101
|
+
<div class="btn-row">
|
|
1102
|
+
<button class="btn btn-secondary" onclick="goPhase(2)">← Back</button>
|
|
1103
|
+
<button class="btn btn-primary" onclick="goPhase(4)">Next: Integrations →</button>
|
|
1104
|
+
</div>
|
|
1105
|
+
</section>
|
|
1106
|
+
|
|
1107
|
+
<!-- ═══════════════════════════════ PHASE 4 — Integrations ════════════════ -->
|
|
1108
|
+
<!-- ═══════════════════════════════ PHASE 4 — Connect Your Tools ═══════════ -->
|
|
1109
|
+
<section class="phase" id="phase-4">
|
|
1110
|
+
<div class="phase-header">
|
|
1111
|
+
<h1>Connect Your Tools</h1>
|
|
1112
|
+
<p>Select every IDE and agent you use. Total Recall will wire them up automatically — no manual config needed.</p>
|
|
1113
|
+
</div>
|
|
1114
|
+
|
|
1115
|
+
<div class="card">
|
|
1116
|
+
<h3 style="margin-bottom:14px">Which IDEs and coding agents do you use?</h3>
|
|
1117
|
+
<p style="color:var(--muted);font-size:13px;margin-bottom:18px">Select all that apply. Total Recall's relay will watch their session logs and compile your memory automatically.</p>
|
|
1118
|
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px" id="ide-grid">
|
|
1119
|
+
<label class="ide-card" id="ide-claude-code">
|
|
1120
|
+
<input type="checkbox" value="claude-code" onchange="toggleIde(this)">
|
|
1121
|
+
<span class="ide-icon">🤖</span>
|
|
1122
|
+
<div>
|
|
1123
|
+
<div class="ide-name">Claude Code</div>
|
|
1124
|
+
<div class="ide-desc">Anthropic's coding agent — CLAUDE.md + auto memory</div>
|
|
1125
|
+
</div>
|
|
1126
|
+
<span class="ide-check">✓</span>
|
|
1127
|
+
</label>
|
|
1128
|
+
<label class="ide-card" id="ide-codex">
|
|
1129
|
+
<input type="checkbox" value="codex" onchange="toggleIde(this)">
|
|
1130
|
+
<span class="ide-icon">🟢</span>
|
|
1131
|
+
<div>
|
|
1132
|
+
<div class="ide-name">Codex (OpenAI)</div>
|
|
1133
|
+
<div class="ide-desc">OpenAI's CLI agent — AGENTS.md + memories</div>
|
|
1134
|
+
</div>
|
|
1135
|
+
<span class="ide-check">✓</span>
|
|
1136
|
+
</label>
|
|
1137
|
+
<label class="ide-card" id="ide-cursor">
|
|
1138
|
+
<input type="checkbox" value="cursor" onchange="toggleIde(this)">
|
|
1139
|
+
<span class="ide-icon">🖱️</span>
|
|
1140
|
+
<div>
|
|
1141
|
+
<div class="ide-name">Cursor</div>
|
|
1142
|
+
<div class="ide-desc">.cursor/rules/ — modular scoped rules</div>
|
|
1143
|
+
</div>
|
|
1144
|
+
<span class="ide-check">✓</span>
|
|
1145
|
+
</label>
|
|
1146
|
+
<label class="ide-card" id="ide-antigravity">
|
|
1147
|
+
<input type="checkbox" value="antigravity" onchange="toggleIde(this)">
|
|
1148
|
+
<span class="ide-icon">⚡</span>
|
|
1149
|
+
<div>
|
|
1150
|
+
<div class="ide-name">Antigravity</div>
|
|
1151
|
+
<div class="ide-desc">Google DeepMind — AGENTS.md + Knowledge Items</div>
|
|
1152
|
+
</div>
|
|
1153
|
+
<span class="ide-check">✓</span>
|
|
1154
|
+
</label>
|
|
1155
|
+
<label class="ide-card" id="ide-vscode">
|
|
1156
|
+
<input type="checkbox" value="vscode" onchange="toggleIde(this)">
|
|
1157
|
+
<span class="ide-icon">💙</span>
|
|
1158
|
+
<div>
|
|
1159
|
+
<div class="ide-name">VS Code Copilot</div>
|
|
1160
|
+
<div class="ide-desc">.github/copilot-instructions.md + native memory</div>
|
|
1161
|
+
</div>
|
|
1162
|
+
<span class="ide-check">✓</span>
|
|
1163
|
+
</label>
|
|
1164
|
+
<label class="ide-card" id="ide-gemini">
|
|
1165
|
+
<input type="checkbox" value="gemini" onchange="toggleIde(this)">
|
|
1166
|
+
<span class="ide-icon">💎</span>
|
|
1167
|
+
<div>
|
|
1168
|
+
<div class="ide-name">Gemini CLI</div>
|
|
1169
|
+
<div class="ide-desc">GEMINI.md + session context</div>
|
|
1170
|
+
</div>
|
|
1171
|
+
<span class="ide-check">✓</span>
|
|
1172
|
+
</label>
|
|
1173
|
+
<label class="ide-card" id="ide-pi">
|
|
1174
|
+
<input type="checkbox" value="pi" onchange="toggleIde(this)">
|
|
1175
|
+
<span class="ide-icon">π</span>
|
|
1176
|
+
<div>
|
|
1177
|
+
<div class="ide-name">Pi Coding Agent</div>
|
|
1178
|
+
<div class="ide-desc">~/.pi/agent/AGENTS.md + JSONL session trees</div>
|
|
1179
|
+
</div>
|
|
1180
|
+
<span class="ide-check">✓</span>
|
|
1181
|
+
</label>
|
|
1182
|
+
<label class="ide-card" id="ide-hermes">
|
|
1183
|
+
<input type="checkbox" value="hermes" onchange="toggleIde(this)">
|
|
1184
|
+
<span class="ide-icon">🔱</span>
|
|
1185
|
+
<div>
|
|
1186
|
+
<div class="ide-name">Hermes Agent</div>
|
|
1187
|
+
<div class="ide-desc">Nous Research — MEMORY.md + USER.md injection</div>
|
|
1188
|
+
</div>
|
|
1189
|
+
<span class="ide-check">✓</span>
|
|
1190
|
+
</label>
|
|
1191
|
+
<label class="ide-card" id="ide-openclaw">
|
|
1192
|
+
<input type="checkbox" value="openclaw" onchange="toggleIde(this)">
|
|
1193
|
+
<span class="ide-icon">🦀</span>
|
|
1194
|
+
<div>
|
|
1195
|
+
<div class="ide-name">OpenClaw</div>
|
|
1196
|
+
<div class="ide-desc">MEMORY.md + SOUL.md + daily logs + SQLite index</div>
|
|
1197
|
+
</div>
|
|
1198
|
+
<span class="ide-check">✓</span>
|
|
1199
|
+
</label>
|
|
1200
|
+
</div>
|
|
1201
|
+
</div>
|
|
1202
|
+
|
|
1203
|
+
<div class="card">
|
|
1204
|
+
<h3 style="margin-bottom:10px">🛠️ CLI Agents Setup & Installer</h3>
|
|
1205
|
+
<p style="color:var(--muted);font-size:13px;margin-bottom:14px">Install globally and initialize your selected CLI coding agents on the target computer in one click.</p>
|
|
1206
|
+
|
|
1207
|
+
<div style="display:grid;gap:12px">
|
|
1208
|
+
<!-- Claude Code -->
|
|
1209
|
+
<div style="display:flex;justify-content:space-between;align-items:center;padding:12px;background:rgba(255,255,255,0.02);border:1px solid var(--border);border-radius:8px">
|
|
1210
|
+
<div>
|
|
1211
|
+
<strong style="color:var(--text)">Claude Code</strong>
|
|
1212
|
+
<div style="font-size:11px;color:var(--muted);margin-top:2px">Official Anthropic CLI agent package (<code>@anthropic-ai/claude-code</code>)</div>
|
|
1213
|
+
</div>
|
|
1214
|
+
<div style="display:flex;gap:8px;align-items:center">
|
|
1215
|
+
<button class="btn btn-secondary" style="font-size:12px;padding:6px 12px" onclick="installCliAgent('claude-code', this)">Install Globally</button>
|
|
1216
|
+
<button class="btn btn-secondary" style="font-size:12px;padding:6px 12px;border-color:rgba(88,166,255,0.4);color:var(--blue)" onclick="runCliAuth('claude')">🔑 Login</button>
|
|
1217
|
+
</div>
|
|
1218
|
+
</div>
|
|
1219
|
+
|
|
1220
|
+
<!-- Antigravity -->
|
|
1221
|
+
<div style="display:flex;justify-content:space-between;align-items:center;padding:12px;background:rgba(255,255,255,0.02);border:1px solid var(--border);border-radius:8px">
|
|
1222
|
+
<div>
|
|
1223
|
+
<strong style="color:var(--text)">Antigravity CLI</strong>
|
|
1224
|
+
<div style="font-size:11px;color:var(--muted);margin-top:2px">Sovereign OS agent — links local repository binary globally (<code>npm link</code>)</div>
|
|
1225
|
+
</div>
|
|
1226
|
+
<div style="display:flex;gap:8px;align-items:center">
|
|
1227
|
+
<button class="btn btn-secondary" style="font-size:12px;padding:6px 12px" onclick="installCliAgent('antigravity', this)">Link Package</button>
|
|
1228
|
+
<button class="btn btn-secondary" style="font-size:12px;padding:6px 12px;border-color:rgba(88,166,255,0.4);color:var(--blue)" onclick="runCliAuth('antigravity')">🔑 Login</button>
|
|
1229
|
+
</div>
|
|
1230
|
+
</div>
|
|
1231
|
+
|
|
1232
|
+
<!-- Codex -->
|
|
1233
|
+
<div style="display:flex;justify-content:space-between;align-items:center;padding:12px;background:rgba(255,255,255,0.02);border:1px solid var(--border);border-radius:8px">
|
|
1234
|
+
<div>
|
|
1235
|
+
<strong style="color:var(--text)">Codex CLI</strong>
|
|
1236
|
+
<div style="font-size:11px;color:var(--muted);margin-top:2px">OpenAI's coding agent executable binary (<code>@openai/codex</code>)</div>
|
|
1237
|
+
</div>
|
|
1238
|
+
<div style="display:flex;gap:8px;align-items:center">
|
|
1239
|
+
<button class="btn btn-secondary" style="font-size:12px;padding:6px 12px" onclick="installCliAgent('codex', this)">Install Globally</button>
|
|
1240
|
+
<button class="btn btn-secondary" style="font-size:12px;padding:6px 12px;border-color:rgba(88,166,255,0.4);color:var(--blue)" onclick="runCliAuth('codex')">🔑 Login</button>
|
|
1241
|
+
</div>
|
|
1242
|
+
</div>
|
|
1243
|
+
</div>
|
|
1244
|
+
|
|
1245
|
+
<div id="cli-install-result" style="display:none;margin-top:14px;padding:10px;background:rgba(0,0,0,0.2);border-radius:6px;font-size:12px;font-family:monospace;white-space:pre-wrap;color:var(--muted)"></div>
|
|
1246
|
+
</div>
|
|
1247
|
+
|
|
1248
|
+
<div class="card">
|
|
1249
|
+
<h3 style="margin-bottom:10px">🔄 Relay Daemon</h3>
|
|
1250
|
+
<p style="color:var(--muted);font-size:13px;margin-bottom:14px">The relay watches your IDE session files and ships them to your brain every 60 seconds automatically — no manual steps needed.</p>
|
|
1251
|
+
<label style="display:flex;align-items:center;gap:10px;cursor:pointer">
|
|
1252
|
+
<input type="checkbox" id="install-relay" checked style="width:16px;height:16px;accent-color:var(--green)">
|
|
1253
|
+
<span>Install relay as system service (starts on boot, runs forever)</span>
|
|
1254
|
+
</label>
|
|
1255
|
+
</div>
|
|
1256
|
+
|
|
1257
|
+
<div class="card">
|
|
1258
|
+
<h3 style="margin-bottom:10px">💾 Backup & Sync</h3>
|
|
1259
|
+
<p style="color:var(--muted);font-size:13px;margin-bottom:14px">Browse your memory graph visually or download a full backup of your brain.</p>
|
|
1260
|
+
<label style="display:flex;align-items:center;gap:10px;cursor:pointer;margin-bottom:12px">
|
|
1261
|
+
<input type="checkbox" id="install-obsidian" checked style="width:16px;height:16px;accent-color:var(--green)">
|
|
1262
|
+
<span>🔮 <strong>Obsidian vault sync</strong> — browse your memory in graph view (connects vault directory)</span>
|
|
1263
|
+
</label>
|
|
1264
|
+
<div style="margin-top:8px">
|
|
1265
|
+
<button class="btn btn-secondary" style="font-size:12px;padding:6px 14px" onclick="downloadBrain()">⬇️ Download Brain (.tar.gz)</button>
|
|
1266
|
+
<span id="brain-dl-status" style="margin-left:10px;font-size:12px;color:var(--muted)"></span>
|
|
1267
|
+
</div>
|
|
1268
|
+
</div>
|
|
1269
|
+
|
|
1270
|
+
<div class="card">
|
|
1271
|
+
<h3 style="margin-bottom:6px">📥 Import Existing Rules & Skills</h3>
|
|
1272
|
+
<p style="color:var(--muted);font-size:13px;margin-bottom:14px">Already have <code>AGENTS.md</code>, <code>.cursorrules</code>, <code>CLAUDE.md</code>, or modular <strong>Skills</strong> (under <code>.agent/skills</code>)? Scan and import them so Total Recall compiles them into every session.</p>
|
|
1273
|
+
<div style="display:flex;gap:10px;align-items:center;margin-bottom:12px">
|
|
1274
|
+
<input type="text" id="import-dir" placeholder="Directory to scan (default: current project)" style="flex:1;background:var(--surface);border:1px solid var(--border);color:var(--text);border-radius:6px;padding:7px 10px;font-size:13px">
|
|
1275
|
+
<button class="btn btn-secondary" style="font-size:12px;padding:6px 14px;white-space:nowrap" onclick="scanRuleFiles()">🔍 Scan</button>
|
|
1276
|
+
</div>
|
|
1277
|
+
<div id="import-rules-list" style="display:none;margin-bottom:10px"></div>
|
|
1278
|
+
<div id="import-rules-result" style="font-size:12px;font-family:monospace;color:var(--muted)"></div>
|
|
1279
|
+
</div>
|
|
1280
|
+
|
|
1281
|
+
<div class="card" id="connect-results" style="display:none">
|
|
1282
|
+
<h3 style="margin-bottom:10px">Connection Results</h3>
|
|
1283
|
+
<div id="connect-log" style="font-family:monospace;font-size:12px;line-height:1.8"></div>
|
|
1284
|
+
</div>
|
|
1285
|
+
|
|
1286
|
+
<div class="btn-row">
|
|
1287
|
+
<button class="btn btn-secondary" onclick="goPhase(3)">← Back</button>
|
|
1288
|
+
<button class="btn btn-primary" id="btn-connect-ides" onclick="connectSelectedIDEs()">Connect Selected Tools →</button>
|
|
1289
|
+
</div>
|
|
1290
|
+
</section>
|
|
1291
|
+
|
|
1292
|
+
|
|
1293
|
+
<!-- ═══════════════════════════════ PHASE 5 — API Reference ═══════════════ -->
|
|
1294
|
+
<section class="phase" id="phase-5">
|
|
1295
|
+
<div class="phase-header">
|
|
1296
|
+
<h1>API Reference</h1>
|
|
1297
|
+
<p>Full endpoint list for your brain. All requests require <code>Authorization: Bearer <PAT></code>.</p>
|
|
1298
|
+
</div>
|
|
1299
|
+
|
|
1300
|
+
<div class="card">
|
|
1301
|
+
<h3>OpenAI-compatible</h3>
|
|
1302
|
+
<table class="api-table">
|
|
1303
|
+
<tr><th>Method</th><th>Path</th><th>Description</th></tr>
|
|
1304
|
+
<tr><td><span class="method-badge method-post">POST</span></td><td>/v1/chat/completions</td><td>Chat completions (streaming supported)</td></tr>
|
|
1305
|
+
<tr><td><span class="method-badge method-get">GET</span></td><td>/v1/models</td><td>List available models</td></tr>
|
|
1306
|
+
</table>
|
|
1307
|
+
</div>
|
|
1308
|
+
|
|
1309
|
+
<div class="card">
|
|
1310
|
+
<h3>Memory</h3>
|
|
1311
|
+
<table class="api-table">
|
|
1312
|
+
<tr><th>Method</th><th>Path</th><th>Description</th></tr>
|
|
1313
|
+
<tr><td><span class="method-badge method-get">GET</span></td><td>/memory</td><td>List all memory files</td></tr>
|
|
1314
|
+
<tr><td><span class="method-badge method-get">GET</span></td><td>/memory/:path</td><td>Read a memory file</td></tr>
|
|
1315
|
+
<tr><td><span class="method-badge method-post">POST</span></td><td>/memory/:path</td><td>Create / overwrite memory file</td></tr>
|
|
1316
|
+
<tr><td><span class="method-badge method-put">PUT</span></td><td>/memory/:path</td><td>Append to memory file</td></tr>
|
|
1317
|
+
<tr><td><span class="method-badge method-del">DELETE</span></td><td>/memory/:path</td><td>Delete memory file</td></tr>
|
|
1318
|
+
<tr><td><span class="method-badge method-get">GET</span></td><td>/memory/search?q=…</td><td>Search memory</td></tr>
|
|
1319
|
+
<tr><td><span class="method-badge method-post">POST</span></td><td>/vault/compile</td><td>Recompile the vault surface</td></tr>
|
|
1320
|
+
<tr><td><span class="method-badge method-get">GET</span></td><td>/vault/status</td><td>Last compile status</td></tr>
|
|
1321
|
+
</table>
|
|
1322
|
+
</div>
|
|
1323
|
+
|
|
1324
|
+
<div class="card">
|
|
1325
|
+
<h3>Keys & Sessions</h3>
|
|
1326
|
+
<table class="api-table">
|
|
1327
|
+
<tr><th>Method</th><th>Path</th><th>Description</th></tr>
|
|
1328
|
+
<tr><td><span class="method-badge method-get">GET</span></td><td>/api/keys</td><td>List keys</td></tr>
|
|
1329
|
+
<tr><td><span class="method-badge method-post">POST</span></td><td>/api/keys</td><td>Create key</td></tr>
|
|
1330
|
+
<tr><td><span class="method-badge method-del">DELETE</span></td><td>/api/keys/:id</td><td>Revoke key</td></tr>
|
|
1331
|
+
<tr><td><span class="method-badge method-get">GET</span></td><td>/api/sessions</td><td>List sessions</td></tr>
|
|
1332
|
+
<tr><td><span class="method-badge method-del">DELETE</span></td><td>/api/sessions/:id</td><td>Delete session</td></tr>
|
|
1333
|
+
</table>
|
|
1334
|
+
</div>
|
|
1335
|
+
|
|
1336
|
+
<div class="card">
|
|
1337
|
+
<h3>Sandbox & Discovery</h3>
|
|
1338
|
+
<table class="api-table">
|
|
1339
|
+
<tr><th>Method</th><th>Path</th><th>Description</th></tr>
|
|
1340
|
+
<tr><td><span class="method-badge method-post">POST</span></td><td>/sandbox</td><td>Execute code in sandbox</td></tr>
|
|
1341
|
+
<tr><td><span class="method-badge method-get">GET</span></td><td>/health</td><td>Health check (no auth)</td></tr>
|
|
1342
|
+
<tr><td><span class="method-badge method-get">GET</span></td><td>/api</td><td>Full API reference JSON</td></tr>
|
|
1343
|
+
<tr><td><span class="method-badge method-get">GET</span></td><td>/.well-known/total-recall.json</td><td>Discovery manifest</td></tr>
|
|
1344
|
+
</table>
|
|
1345
|
+
</div>
|
|
1346
|
+
|
|
1347
|
+
<div class="card">
|
|
1348
|
+
<h3>Example curl request</h3>
|
|
1349
|
+
<div class="code-block" id="example-curl"><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>
|
|
1350
|
+
</div>
|
|
1351
|
+
|
|
1352
|
+
<div class="btn-row">
|
|
1353
|
+
<button class="btn btn-secondary" onclick="goPhase(4)">← Back</button>
|
|
1354
|
+
<button class="btn btn-primary" onclick="goPhase(6)">Finish →</button>
|
|
1355
|
+
</div>
|
|
1356
|
+
</section>
|
|
1357
|
+
|
|
1358
|
+
<!-- ═══════════════════════════════ PHASE 6 — Done ════════════════════════ -->
|
|
1359
|
+
<section class="phase" id="phase-6">
|
|
1360
|
+
<div class="phase-header">
|
|
1361
|
+
<h1>🎉 Your Brain is Live</h1>
|
|
1362
|
+
<p>Total Recall is installed and ready. Here are your URLs.</p>
|
|
1363
|
+
</div>
|
|
1364
|
+
|
|
1365
|
+
<!-- SSH Fallback Warning Card -->
|
|
1366
|
+
<div id="ssh-fallback-warning" class="card" style="display:none; border: 1px solid var(--yellow); background: rgba(210, 153, 34, 0.05);">
|
|
1367
|
+
<h3 style="color:var(--yellow); display:flex; align-items:center; gap:8px; margin-bottom:8px">
|
|
1368
|
+
⚠️ Secure Tunnel Fallback (SSH Connection)
|
|
1369
|
+
</h3>
|
|
1370
|
+
<p style="color:var(--text); font-size:13px; line-height:1.5; margin-bottom:12px">
|
|
1371
|
+
We detected that your brain URLs are pointing directly to the VM's SSH host (<strong><span id="warning-ssh-host">ssh1.vast.ai</span></strong>).
|
|
1372
|
+
Chrome and other modern browsers <strong>cannot make HTTP requests to an SSH port</strong> (doing so triggers a <code>net::ERR_INVALID_HTTP_RESPONSE</code> error).
|
|
1373
|
+
</p>
|
|
1374
|
+
<p style="color:var(--muted); font-size:12px; line-height:1.5; margin-bottom:12px">
|
|
1375
|
+
This usually happens when the Cloudflare quick tunnel failed to provision on the remote GPU instance in time, or timed out.
|
|
1376
|
+
The background installer is likely still running on your VM, or you can paste your custom Cloudflare tunnel URL below.
|
|
1377
|
+
</p>
|
|
1378
|
+
<div style="border-top:1px solid rgba(210, 153, 34, 0.15); padding-top:10px; display:flex; flex-direction:column; gap:6px">
|
|
1379
|
+
<div style="font-size:12px; color:var(--text)">
|
|
1380
|
+
<strong>What to do next:</strong>
|
|
1381
|
+
</div>
|
|
1382
|
+
<ul style="padding-left:16px; font-size:12px; color:var(--muted); display:flex; flex-direction:column; gap:4px">
|
|
1383
|
+
<li style="color:var(--muted)">1. Keep an eye on the <strong>persistent status bar</strong> at the bottom right — it will update as the background VM installer continues running.</li>
|
|
1384
|
+
<li style="color:var(--muted)">2. Once the installer finishes successfully, it may output your public tunnel URL (e.g. <code>https://xyz.trycloudflare.com</code>).</li>
|
|
1385
|
+
<li style="color:var(--muted)">3. Alternatively, if you set up your own tunnel or want to manually connect, scroll down to the <strong>Manual Endpoint Override</strong> panel below and paste your secure URL.</li>
|
|
1386
|
+
</ul>
|
|
1387
|
+
</div>
|
|
1388
|
+
</div>
|
|
1389
|
+
|
|
1390
|
+
<!-- Dashboard Password Credentials Card -->
|
|
1391
|
+
<div class="card" style="margin-top: 20px; border-left: 4px solid var(--accent);">
|
|
1392
|
+
<h3 style="display:flex; align-items:center; gap:8px; margin-bottom:8px">
|
|
1393
|
+
🔑 Dashboard Sign In Credentials
|
|
1394
|
+
</h3>
|
|
1395
|
+
<p style="color:var(--text); font-size:13px; margin-bottom:12px">
|
|
1396
|
+
Use the credentials below to log into your settings dashboard (<code>/dashboard</code>). For security, you will be forced to change this temporary password upon your first access.
|
|
1397
|
+
</p>
|
|
1398
|
+
<div style="display:flex; gap:16px; flex-wrap:wrap">
|
|
1399
|
+
<div style="flex:1; min-width:200px">
|
|
1400
|
+
<div style="font-size:11px; text-transform:uppercase; color:var(--muted); font-weight:600; margin-bottom:4px">Username</div>
|
|
1401
|
+
<div style="background:var(--panel); border:1px solid var(--border); border-radius:6px; padding:8px 12px; font-family:monospace; font-size:13px; color:var(--text)">
|
|
1402
|
+
admin
|
|
1403
|
+
</div>
|
|
1404
|
+
</div>
|
|
1405
|
+
<div style="flex:1; min-width:200px">
|
|
1406
|
+
<div style="font-size:11px; text-transform:uppercase; color:var(--muted); font-weight:600; margin-bottom:4px">Temporary Password</div>
|
|
1407
|
+
<div style="display:flex; gap:8px; align-items:center">
|
|
1408
|
+
<input type="password" id="done-dashboard-password" readonly style="background:var(--panel); border:1px solid var(--border); border-radius:6px; padding:8px 12px; font-family:monospace; font-size:13px; color:var(--text); flex:1">
|
|
1409
|
+
<button class="btn btn-secondary" style="padding:7px 12px; font-size:12px" onclick="toggleShow('done-dashboard-password')">Show</button>
|
|
1410
|
+
<button class="btn btn-secondary" style="padding:7px 12px; font-size:12px" onclick="copyToken('done-dashboard-password')">Copy</button>
|
|
1411
|
+
</div>
|
|
1412
|
+
</div>
|
|
1413
|
+
</div>
|
|
1414
|
+
</div>
|
|
1415
|
+
|
|
1416
|
+
<div class="card">
|
|
1417
|
+
<div class="url-grid">
|
|
1418
|
+
<div class="url-item">
|
|
1419
|
+
<span class="ui-icon">🔌</span>
|
|
1420
|
+
<div>
|
|
1421
|
+
<div class="ui-label">API Endpoint</div>
|
|
1422
|
+
<div class="ui-value" id="final-api-url">…</div>
|
|
1423
|
+
</div>
|
|
1424
|
+
<a id="final-api-link" class="ui-open" href="#" target="_blank" rel="noopener">Open</a>
|
|
1425
|
+
</div>
|
|
1426
|
+
<div class="url-item">
|
|
1427
|
+
<span class="ui-icon">🖥</span>
|
|
1428
|
+
<div>
|
|
1429
|
+
<div class="ui-label">Dashboard</div>
|
|
1430
|
+
<div class="ui-value" id="final-dash-url">…</div>
|
|
1431
|
+
</div>
|
|
1432
|
+
<a id="final-dash-link" class="ui-open" href="#" target="_blank" rel="noopener">Open</a>
|
|
1433
|
+
</div>
|
|
1434
|
+
<div class="url-item">
|
|
1435
|
+
<span class="ui-icon">❤️</span>
|
|
1436
|
+
<div>
|
|
1437
|
+
<div class="ui-label">Health Check</div>
|
|
1438
|
+
<div class="ui-value" id="final-health-url">…</div>
|
|
1439
|
+
</div>
|
|
1440
|
+
<a id="final-health-link" class="ui-open" href="#" target="_blank" rel="noopener">Check</a>
|
|
1441
|
+
</div>
|
|
1442
|
+
</div>
|
|
1443
|
+
<div class="health-row">
|
|
1444
|
+
<span class="health-dot" id="health-dot"></span>
|
|
1445
|
+
<span id="health-status">Checking health...</span>
|
|
1446
|
+
<button class="btn btn-secondary" style="padding:5px 12px;font-size:12px;margin-left:auto" onclick="checkHealth()">Re-check</button>
|
|
1447
|
+
</div>
|
|
1448
|
+
</div>
|
|
1449
|
+
|
|
1450
|
+
<div class="card">
|
|
1451
|
+
<h3>What's next?</h3>
|
|
1452
|
+
<ul>
|
|
1453
|
+
<li>💾 <strong>Start chatting</strong> — open the dashboard or point Claude Code at your brain</li>
|
|
1454
|
+
<li>🧠 <strong>Build your memory</strong> — run <code>npx total-recall ingest</code> to import your chat history</li>
|
|
1455
|
+
<li>🔄 <strong>Keep it synced</strong> — set up <code>npx total-recall sync --watch</code> for continuous ingest</li>
|
|
1456
|
+
<li>📚 <strong>Docs</strong> — <a href="https://github.com/gregiteen/total-recall" target="_blank" rel="noopener" style="color:var(--blue)">github.com/gregiteen/total-recall</a></li>
|
|
1457
|
+
</ul>
|
|
1458
|
+
</div>
|
|
1459
|
+
|
|
1460
|
+
<!-- Manual Override Card -->
|
|
1461
|
+
<div class="card" style="margin-top:24px">
|
|
1462
|
+
<h3>🔧 Manual Endpoint Override</h3>
|
|
1463
|
+
<p style="color:var(--muted);font-size:13px;margin-bottom:12px">
|
|
1464
|
+
If your secure tunnel URL or host endpoint is stale (e.g. <code>http://ssh1.vast.ai:10120</code>), or you have a custom Cloudflare tunnel URL, you can manually override your brain server endpoints below.
|
|
1465
|
+
</p>
|
|
1466
|
+
|
|
1467
|
+
<div style="border:1px solid var(--border);border-radius:8px;padding:16px;background:rgba(255,255,255,0.02)">
|
|
1468
|
+
<div class="form-group" style="margin-bottom:12px">
|
|
1469
|
+
<label>Brain Base URL or Host</label>
|
|
1470
|
+
<input type="text" id="override-base-url" placeholder="e.g. https://xyz.trycloudflare.com or http://localhost:3000" autocomplete="off" spellcheck="false">
|
|
1471
|
+
<div class="hint">The base domain/URL where your brain server is running.</div>
|
|
1472
|
+
</div>
|
|
1473
|
+
|
|
1474
|
+
<div class="form-group" style="margin-bottom:14px">
|
|
1475
|
+
<label>Authentication Token (PAT)</label>
|
|
1476
|
+
<input type="password" id="override-pat-key" placeholder="Bearer Token (PAT)" autocomplete="off">
|
|
1477
|
+
<div class="hint">If you generated a PAT in Phase 3, paste it here. Defaults to your generated PAT.</div>
|
|
1478
|
+
</div>
|
|
1479
|
+
|
|
1480
|
+
<div id="override-save-result" class="notice" style="display:none;margin-bottom:12px"></div>
|
|
1481
|
+
|
|
1482
|
+
<div style="display:flex;gap:10px">
|
|
1483
|
+
<button class="btn btn-blue" style="font-size:12px;padding:6px 14px" onclick="saveManualOverride()">Save Override & Re-check</button>
|
|
1484
|
+
<button class="btn btn-secondary" style="font-size:12px;padding:6px 14px" onclick="toggleShow('override-pat-key')">Show Token</button>
|
|
1485
|
+
</div>
|
|
1486
|
+
</div>
|
|
1487
|
+
</div>
|
|
1488
|
+
|
|
1489
|
+
<div class="btn-row" style="margin-top:24px">
|
|
1490
|
+
<button class="btn btn-secondary" onclick="goPhase(5)">← Go Back & Edit Options</button>
|
|
1491
|
+
</div>
|
|
1492
|
+
</section>
|
|
1493
|
+
|
|
1494
|
+
<!-- ═══════════════════════════ PHASE 7 — Settings ══════════════════════════ -->
|
|
1495
|
+
<section class="phase" id="phase-7">
|
|
1496
|
+
<div class="phase-header">
|
|
1497
|
+
<h1>⚙️ Settings</h1>
|
|
1498
|
+
<p>Manage your search providers, usage limits, and other preferences.</p>
|
|
1499
|
+
</div>
|
|
1500
|
+
|
|
1501
|
+
<!-- Search Providers -->
|
|
1502
|
+
<div class="card">
|
|
1503
|
+
<h3>🔍 Web Search Providers</h3>
|
|
1504
|
+
<p style="color:var(--muted);font-size:13px;margin-bottom:6px">
|
|
1505
|
+
The background research engine tries these in order — Tavily → Brave → Exa → Serper — and falls back to DuckDuckGo (free) when none are set or the daily limit is reached.
|
|
1506
|
+
</p>
|
|
1507
|
+
|
|
1508
|
+
<div id="settings-usage-bar" style="background:rgba(255,255,255,0.05);border-radius:6px;padding:10px 12px;font-size:12px;margin-bottom:18px">
|
|
1509
|
+
<span style="color:var(--muted)">Today's paid searches: </span>
|
|
1510
|
+
<strong id="settings-usage-count" style="color:var(--text)">—</strong>
|
|
1511
|
+
<span style="color:var(--muted)"> / </span>
|
|
1512
|
+
<strong id="settings-usage-limit" style="color:var(--text)">—</strong>
|
|
1513
|
+
<span style="color:var(--muted)"> (<span id="settings-usage-remaining">—</span> remaining today)</span>
|
|
1514
|
+
</div>
|
|
1515
|
+
|
|
1516
|
+
<div style="display:grid;gap:14px;margin-bottom:20px">
|
|
1517
|
+
|
|
1518
|
+
<div style="border:1px solid var(--border);border-radius:8px;padding:14px">
|
|
1519
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px">
|
|
1520
|
+
<strong>Tavily</strong>
|
|
1521
|
+
<span style="font-size:11px;background:var(--green);color:#000;padding:2px 7px;border-radius:10px">Best for AI agents</span>
|
|
1522
|
+
</div>
|
|
1523
|
+
<p style="font-size:12px;color:var(--muted);margin-bottom:8px">Returns full page text, not just links. 1,000 free searches/month. <a href="https://tavily.com" target="_blank" style="color:var(--blue)">tavily.com</a></p>
|
|
1524
|
+
<div style="display:flex;gap:8px;align-items:center">
|
|
1525
|
+
<input type="password" id="s-tavily-key" placeholder="tvly-… (leave blank to remove)" autocomplete="off" style="font-family:monospace;flex:1">
|
|
1526
|
+
<button class="btn btn-secondary" style="padding:5px 10px;font-size:12px" onclick="toggleShow('s-tavily-key')">Show</button>
|
|
1527
|
+
</div>
|
|
1528
|
+
</div>
|
|
1529
|
+
|
|
1530
|
+
<div style="border:1px solid var(--border);border-radius:8px;padding:14px">
|
|
1531
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px">
|
|
1532
|
+
<strong>Brave Search</strong>
|
|
1533
|
+
<span style="font-size:11px;color:var(--muted)">~1,000 free/month</span>
|
|
1534
|
+
</div>
|
|
1535
|
+
<p style="font-size:12px;color:var(--muted);margin-bottom:8px">Independent web index. <a href="https://brave.com/search/api/" target="_blank" style="color:var(--blue)">brave.com/search/api</a></p>
|
|
1536
|
+
<div style="display:flex;gap:8px;align-items:center">
|
|
1537
|
+
<input type="password" id="s-brave-key" placeholder="BSA… (leave blank to remove)" autocomplete="off" style="font-family:monospace;flex:1">
|
|
1538
|
+
<button class="btn btn-secondary" style="padding:5px 10px;font-size:12px" onclick="toggleShow('s-brave-key')">Show</button>
|
|
1539
|
+
</div>
|
|
1540
|
+
</div>
|
|
1541
|
+
|
|
1542
|
+
<div style="border:1px solid var(--border);border-radius:8px;padding:14px">
|
|
1543
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px">
|
|
1544
|
+
<strong>Exa</strong>
|
|
1545
|
+
<span style="font-size:11px;color:var(--muted)">~1,000 free/month</span>
|
|
1546
|
+
</div>
|
|
1547
|
+
<p style="font-size:12px;color:var(--muted);margin-bottom:8px">Neural/semantic search — finds by meaning. <a href="https://exa.ai" target="_blank" style="color:var(--blue)">exa.ai</a></p>
|
|
1548
|
+
<div style="display:flex;gap:8px;align-items:center">
|
|
1549
|
+
<input type="password" id="s-exa-key" placeholder="… (leave blank to remove)" autocomplete="off" style="font-family:monospace;flex:1">
|
|
1550
|
+
<button class="btn btn-secondary" style="padding:5px 10px;font-size:12px" onclick="toggleShow('s-exa-key')">Show</button>
|
|
1551
|
+
</div>
|
|
1552
|
+
</div>
|
|
1553
|
+
|
|
1554
|
+
<div style="border:1px solid var(--border);border-radius:8px;padding:14px">
|
|
1555
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px">
|
|
1556
|
+
<strong>Serper</strong>
|
|
1557
|
+
<span style="font-size:11px;color:var(--muted)">2,500 one-time free credits</span>
|
|
1558
|
+
</div>
|
|
1559
|
+
<p style="font-size:12px;color:var(--muted);margin-bottom:8px">Google Search results. <a href="https://serper.dev" target="_blank" style="color:var(--blue)">serper.dev</a></p>
|
|
1560
|
+
<div style="display:flex;gap:8px;align-items:center">
|
|
1561
|
+
<input type="password" id="s-serper-key" placeholder="… (leave blank to remove)" autocomplete="off" style="font-family:monospace;flex:1">
|
|
1562
|
+
<button class="btn btn-secondary" style="padding:5px 10px;font-size:12px" onclick="toggleShow('s-serper-key')">Show</button>
|
|
1563
|
+
</div>
|
|
1564
|
+
</div>
|
|
1565
|
+
|
|
1566
|
+
</div>
|
|
1567
|
+
|
|
1568
|
+
<div style="margin-bottom:20px">
|
|
1569
|
+
<label style="display:block;font-size:13px;font-weight:600;margin-bottom:6px">
|
|
1570
|
+
Daily search limit
|
|
1571
|
+
<span style="font-weight:400;color:var(--muted);margin-left:6px">paid API calls per day — resets at midnight</span>
|
|
1572
|
+
</label>
|
|
1573
|
+
<div style="display:flex;gap:10px;align-items:center">
|
|
1574
|
+
<input type="number" id="s-daily-limit" min="0" max="5000" value="50"
|
|
1575
|
+
style="width:90px;font-size:14px;text-align:center">
|
|
1576
|
+
<span style="font-size:12px;color:var(--muted)">
|
|
1577
|
+
Set to <strong>0</strong> to disable the limit entirely (if you're on a paid plan).
|
|
1578
|
+
Default <strong>50/day ≈ 1,500/month</strong> stays within most free tiers.
|
|
1579
|
+
</span>
|
|
1580
|
+
</div>
|
|
1581
|
+
</div>
|
|
1582
|
+
|
|
1583
|
+
<div id="settings-save-result" class="notice" style="display:none;margin-bottom:14px"></div>
|
|
1584
|
+
|
|
1585
|
+
<div class="btn-row">
|
|
1586
|
+
<button class="btn btn-primary" onclick="saveSettings()">Save Settings</button>
|
|
1587
|
+
</div>
|
|
1588
|
+
</div>
|
|
1589
|
+
|
|
1590
|
+
<!-- Total Recall API Key -->
|
|
1591
|
+
<div class="card" style="margin-top: 24px;">
|
|
1592
|
+
<h3>🔑 Total Recall API Key</h3>
|
|
1593
|
+
<p style="color:var(--muted);font-size:13px;margin-bottom:12px">
|
|
1594
|
+
This is the primary Personal Access Token (PAT) used by extensions and IDEs to securely connect to your memory vault.
|
|
1595
|
+
</p>
|
|
1596
|
+
<div style="border:1px solid var(--border);border-radius:8px;padding:14px;background:rgba(255,255,255,0.02)">
|
|
1597
|
+
<div style="display:flex;gap:8px;align-items:center">
|
|
1598
|
+
<input type="password" id="settings-pat-key" readonly placeholder="No API key active" style="font-family:monospace;flex:1;background:var(--surface);border:1px solid var(--border);color:var(--text);border-radius:6px;padding:7px 10px;font-size:13px">
|
|
1599
|
+
<button class="btn btn-secondary" style="padding:5px 10px;font-size:12px" onclick="toggleShow('settings-pat-key')">Show</button>
|
|
1600
|
+
<button class="btn btn-secondary" style="padding:5px 10px;font-size:12px" onclick="copyToken('settings-pat-key')">Copy</button>
|
|
1601
|
+
</div>
|
|
1602
|
+
<p style="font-size:11px;color:var(--muted);margin-top:6px;margin-bottom:0">
|
|
1603
|
+
🔒 Saved securely in your local configuration. You can copy it directly at any time (even when hidden) or click <strong>Show</strong> to reveal it. If you need a new key, you can regenerate one at <a href="#" onclick="goPhase(3); return false;" style="color:var(--blue);text-decoration:underline">Phase 3 (API Key)</a>.
|
|
1604
|
+
</p>
|
|
1605
|
+
</div>
|
|
1606
|
+
</div>
|
|
1607
|
+
|
|
1608
|
+
<!-- Remote Dashboard Password Settings -->
|
|
1609
|
+
<div class="card" style="margin-top: 24px;">
|
|
1610
|
+
<h3>🔑 Remote Dashboard Password</h3>
|
|
1611
|
+
<p style="color:var(--muted);font-size:13px;margin-bottom:16px">
|
|
1612
|
+
Reset or change the administrator password of your active local or remote brain instance.
|
|
1613
|
+
</p>
|
|
1614
|
+
<div id="settings-password-result" class="notice" style="display:none;margin-bottom:14px"></div>
|
|
1615
|
+
<div class="form-group" style="margin-bottom:0">
|
|
1616
|
+
<label>New Admin Password <span style="font-weight:400;color:var(--muted)">(minimum 8 characters)</span></label>
|
|
1617
|
+
<div style="display:flex;gap:8px;align-items:center">
|
|
1618
|
+
<input type="text" id="settings-new-password" placeholder="Enter new dashboard password..." autocomplete="off" style="font-family:monospace;flex:1">
|
|
1619
|
+
<button class="btn btn-primary" onclick="resetRemotePassword()">Update Remote Password</button>
|
|
1620
|
+
</div>
|
|
1621
|
+
<div class="hint">
|
|
1622
|
+
This will update the configuration on your active brain (locally or over SSH on your remote server/VM) and force a password reset on next dashboard login.
|
|
1623
|
+
</div>
|
|
1624
|
+
</div>
|
|
1625
|
+
</div>
|
|
1626
|
+
|
|
1627
|
+
<!-- Danger Zone -->
|
|
1628
|
+
<div class="card" style="margin-top: 24px; border-color: var(--red); background: rgba(248,81,73,0.02)">
|
|
1629
|
+
<h3 style="color:var(--red)">⚠️ Danger Zone</h3>
|
|
1630
|
+
<p style="color:var(--muted);font-size:13px;margin-bottom:12px">
|
|
1631
|
+
Reset all configurations to start fresh. This will clear the wizard's memory from this browser and the server.
|
|
1632
|
+
</p>
|
|
1633
|
+
<div style="border:1px solid rgba(248,81,73,0.2);border-radius:8px;padding:14px;background:rgba(248,81,73,0.03)">
|
|
1634
|
+
<p style="font-size:12px;color:var(--muted);margin-bottom:12px">
|
|
1635
|
+
This clears both local storage and the server-side configuration file (`~/.agent/config/wizard-config.json`). If you deployed to a Cloud VM/VPS, remember to destroy or stop it directly in your cloud provider's console to avoid ongoing charges.
|
|
1636
|
+
</p>
|
|
1637
|
+
<button class="btn" style="background:var(--red);color:#fff;font-size:12px;padding:8px 16px" onclick="resetWizardSetup()">Reset Wizard Setup</button>
|
|
1638
|
+
</div>
|
|
1639
|
+
</div>
|
|
1640
|
+
|
|
1641
|
+
</section>
|
|
1642
|
+
|
|
1643
|
+
</main>
|
|
1644
|
+
</div>
|
|
1645
|
+
|
|
1646
|
+
<script>
|
|
1647
|
+
(function () {
|
|
1648
|
+
'use strict';
|
|
1649
|
+
|
|
1650
|
+
// ── Intercept fetch to rewrite relative paths to absolute wizard server when opened via file:// ──
|
|
1651
|
+
(function() {
|
|
1652
|
+
var originalFetch = window.fetch;
|
|
1653
|
+
window.fetch = function(url, options) {
|
|
1654
|
+
if (typeof url === 'string' && !url.startsWith('http://') && !url.startsWith('https://') && !url.startsWith('//')) {
|
|
1655
|
+
var base = '';
|
|
1656
|
+
if (window.location.protocol === 'file:') {
|
|
1657
|
+
base = 'http://localhost:3001';
|
|
1658
|
+
} else {
|
|
1659
|
+
base = window.location.origin;
|
|
1660
|
+
}
|
|
1661
|
+
var cleanPath = url.startsWith('/') ? url : '/' + url;
|
|
1662
|
+
url = base + cleanPath;
|
|
1663
|
+
}
|
|
1664
|
+
return originalFetch(url, options);
|
|
1665
|
+
};
|
|
1666
|
+
})();
|
|
1667
|
+
|
|
1668
|
+
// ── Safe LocalStorage wrapper with sessionStorage fallback ──
|
|
1669
|
+
var safeStorage = {
|
|
1670
|
+
getItem: function (key) {
|
|
1671
|
+
try {
|
|
1672
|
+
return localStorage.getItem(key) || sessionStorage.getItem(key);
|
|
1673
|
+
} catch (e) {
|
|
1674
|
+
try {
|
|
1675
|
+
return sessionStorage.getItem(key);
|
|
1676
|
+
} catch (e2) {
|
|
1677
|
+
return null;
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
},
|
|
1681
|
+
setItem: function (key, value) {
|
|
1682
|
+
try {
|
|
1683
|
+
localStorage.setItem(key, value);
|
|
1684
|
+
sessionStorage.setItem(key, value);
|
|
1685
|
+
return true;
|
|
1686
|
+
} catch (e) {
|
|
1687
|
+
try {
|
|
1688
|
+
sessionStorage.setItem(key, value);
|
|
1689
|
+
return true;
|
|
1690
|
+
} catch (e2) {
|
|
1691
|
+
return false;
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
};
|
|
1696
|
+
|
|
1697
|
+
window.saveAllToLocalStorage = function () {
|
|
1698
|
+
var textInputs = [
|
|
1699
|
+
'cfg-domain',
|
|
1700
|
+
'cfg-cloudflare-token',
|
|
1701
|
+
'cfg-localnet-host',
|
|
1702
|
+
'cfg-localnet-user',
|
|
1703
|
+
'cfg-vastai-key',
|
|
1704
|
+
'cfg-github-token',
|
|
1705
|
+
'cfg-cloud-token'
|
|
1706
|
+
];
|
|
1707
|
+
var selectInputs = [
|
|
1708
|
+
'cfg-cloud-provider',
|
|
1709
|
+
'cfg-cloud-region'
|
|
1710
|
+
];
|
|
1711
|
+
var payload = {};
|
|
1712
|
+
textInputs.forEach(function (id) {
|
|
1713
|
+
var el = document.getElementById(id);
|
|
1714
|
+
if (el) {
|
|
1715
|
+
safeStorage.setItem('tr_wizard_' + id, el.value);
|
|
1716
|
+
payload[id] = el.value;
|
|
1717
|
+
}
|
|
1718
|
+
});
|
|
1719
|
+
selectInputs.forEach(function (id) {
|
|
1720
|
+
var el = document.getElementById(id);
|
|
1721
|
+
if (el) {
|
|
1722
|
+
safeStorage.setItem('tr_wizard_' + id, el.value);
|
|
1723
|
+
payload[id] = el.value;
|
|
1724
|
+
}
|
|
1725
|
+
});
|
|
1726
|
+
|
|
1727
|
+
var targetRadio = document.querySelector('#deploy-target input[type=radio]:checked');
|
|
1728
|
+
if (targetRadio) {
|
|
1729
|
+
safeStorage.setItem('tr_wizard_deploy_target', targetRadio.value);
|
|
1730
|
+
payload['deployTarget'] = targetRadio.value;
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
var httpsRadio = document.querySelector('input[name="https-method"]:checked');
|
|
1734
|
+
if (httpsRadio) {
|
|
1735
|
+
safeStorage.setItem('tr_wizard_https_method', httpsRadio.value);
|
|
1736
|
+
payload['httpsMethod'] = httpsRadio.value;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
fetch('/api/save-wizard-config', {
|
|
1740
|
+
method: 'POST',
|
|
1741
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1742
|
+
body: JSON.stringify(payload)
|
|
1743
|
+
}).catch(function (e) { console.error('Error saving wizard config on saveAll:', e); });
|
|
1744
|
+
};
|
|
1745
|
+
|
|
1746
|
+
// ── Wizard state ──
|
|
1747
|
+
var W = {
|
|
1748
|
+
phase: 0,
|
|
1749
|
+
domain: '',
|
|
1750
|
+
pat: '',
|
|
1751
|
+
apiUrl: '',
|
|
1752
|
+
dashUrl: '',
|
|
1753
|
+
healthUrl: '',
|
|
1754
|
+
deployTarget: '',
|
|
1755
|
+
};
|
|
1756
|
+
var activeEs = null;
|
|
1757
|
+
|
|
1758
|
+
function getBaseUrl() {
|
|
1759
|
+
if (W.dashUrl) {
|
|
1760
|
+
return W.dashUrl.replace(/\/$/, '');
|
|
1761
|
+
}
|
|
1762
|
+
if (W.apiUrl) {
|
|
1763
|
+
return W.apiUrl.replace(/\/v1\/chat\/completions\/?$/, '').replace(/\/$/, '');
|
|
1764
|
+
}
|
|
1765
|
+
return '';
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
function saveWizardState(updates) {
|
|
1769
|
+
if (!updates) return;
|
|
1770
|
+
for (var k in updates) {
|
|
1771
|
+
if (updates.hasOwnProperty(k)) {
|
|
1772
|
+
W[k] = updates[k];
|
|
1773
|
+
// Save to browser safeStorage
|
|
1774
|
+
safeStorage.setItem('tr_wizard_state_' + k, updates[k]);
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
// Also save to server config disk
|
|
1778
|
+
fetch('/api/save-wizard-config', {
|
|
1779
|
+
method: 'POST',
|
|
1780
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1781
|
+
body: JSON.stringify(updates)
|
|
1782
|
+
}).catch(function (e) {
|
|
1783
|
+
console.error('Error auto-persisting wizard state:', e);
|
|
1784
|
+
});
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
function syncUIFromWizardState() {
|
|
1788
|
+
if (W.pat) {
|
|
1789
|
+
var patInput = document.getElementById('pat-key-display');
|
|
1790
|
+
if (patInput) patInput.value = W.pat;
|
|
1791
|
+
var settingsPatInput = document.getElementById('settings-pat-key');
|
|
1792
|
+
if (settingsPatInput) settingsPatInput.value = W.pat;
|
|
1793
|
+
var patResEl = document.getElementById('key-result');
|
|
1794
|
+
if (patResEl) patResEl.style.display = '';
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
// ── Phase navigation ──
|
|
1799
|
+
window.goPhase = function (n) {
|
|
1800
|
+
document.querySelectorAll('.phase').forEach(function (el) { el.classList.remove('active'); });
|
|
1801
|
+
document.getElementById('phase-' + n).classList.add('active');
|
|
1802
|
+
document.querySelectorAll('#step-nav li').forEach(function (li, i) {
|
|
1803
|
+
li.classList.remove('active');
|
|
1804
|
+
if (i < n) li.classList.add('done');
|
|
1805
|
+
if (i === n) li.classList.add('active');
|
|
1806
|
+
});
|
|
1807
|
+
W.phase = n;
|
|
1808
|
+
saveWizardState({ phase: n });
|
|
1809
|
+
syncPersistentBarVisibility();
|
|
1810
|
+
|
|
1811
|
+
if (n === 1) loadDetectedProjects();
|
|
1812
|
+
if (n === 2) {
|
|
1813
|
+
startSSE();
|
|
1814
|
+
}
|
|
1815
|
+
if (n === 4) populateIntegrations();
|
|
1816
|
+
if (n === 5) populateApiRef();
|
|
1817
|
+
if (n === 6) populateDone();
|
|
1818
|
+
if (n === 7) populateSettings();
|
|
1819
|
+
window.scrollTo({ top: 0 });
|
|
1820
|
+
};
|
|
1821
|
+
|
|
1822
|
+
document.querySelectorAll('#deploy-target input[type=radio]').forEach(function (r) {
|
|
1823
|
+
r.addEventListener('change', function () {
|
|
1824
|
+
document.querySelectorAll('#deploy-target .radio-item').forEach(function (el) { el.classList.remove('selected'); });
|
|
1825
|
+
var parentItem = r.closest('.radio-item');
|
|
1826
|
+
if (parentItem) parentItem.classList.add('selected');
|
|
1827
|
+
|
|
1828
|
+
var val = r.value;
|
|
1829
|
+
// Show/hide path-specific detail panels
|
|
1830
|
+
var localnetDetails = document.getElementById('localnet-details');
|
|
1831
|
+
if (localnetDetails) {
|
|
1832
|
+
localnetDetails.style.display = val === 'localnet' ? '' : 'none';
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
var agentCloudDetails = document.getElementById('agent-cloud-details');
|
|
1836
|
+
if (agentCloudDetails) {
|
|
1837
|
+
agentCloudDetails.style.display = val === 'agent-cloud' ? '' : 'none';
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
var installBtnText = document.getElementById('install-btn-text');
|
|
1841
|
+
if (installBtnText) {
|
|
1842
|
+
if (val === 'localnet') {
|
|
1843
|
+
installBtnText.innerText = 'Install on remote network computer';
|
|
1844
|
+
} else if (val === 'agent-cloud') {
|
|
1845
|
+
installBtnText.innerText = '🚀 Provision & Install via Agent';
|
|
1846
|
+
} else {
|
|
1847
|
+
installBtnText.innerText = 'Install on this computer';
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
W.deployTarget = val;
|
|
1852
|
+
});
|
|
1853
|
+
});
|
|
1854
|
+
|
|
1855
|
+
// ── Phase 1: Project Brain Selector ──
|
|
1856
|
+
var _detectedProjects = [];
|
|
1857
|
+
var _customProjects = [];
|
|
1858
|
+
var _projectsLoaded = false;
|
|
1859
|
+
|
|
1860
|
+
window.loadDetectedProjects = function () {
|
|
1861
|
+
if (_projectsLoaded) return;
|
|
1862
|
+
var listEl = document.getElementById('project-list');
|
|
1863
|
+
var loadingEl = document.getElementById('project-list-loading');
|
|
1864
|
+
var emptyEl = document.getElementById('project-list-empty');
|
|
1865
|
+
if (loadingEl) loadingEl.style.display = '';
|
|
1866
|
+
if (listEl) listEl.style.display = 'none';
|
|
1867
|
+
if (emptyEl) emptyEl.style.display = 'none';
|
|
1868
|
+
|
|
1869
|
+
fetch('/api/detect-projects')
|
|
1870
|
+
.then(function (r) { return r.json(); })
|
|
1871
|
+
.then(function (data) {
|
|
1872
|
+
_detectedProjects = data.projects || [];
|
|
1873
|
+
_projectsLoaded = true;
|
|
1874
|
+
if (loadingEl) loadingEl.style.display = 'none';
|
|
1875
|
+
if (_detectedProjects.length === 0) {
|
|
1876
|
+
if (emptyEl) emptyEl.style.display = '';
|
|
1877
|
+
return;
|
|
1878
|
+
}
|
|
1879
|
+
if (listEl) {
|
|
1880
|
+
listEl.style.display = 'flex';
|
|
1881
|
+
listEl.style.flexDirection = 'column';
|
|
1882
|
+
listEl.style.gap = '6px';
|
|
1883
|
+
listEl.innerHTML = '';
|
|
1884
|
+
_detectedProjects.forEach(function (proj) {
|
|
1885
|
+
listEl.appendChild(createProjectRow(proj));
|
|
1886
|
+
});
|
|
1887
|
+
}
|
|
1888
|
+
updateBrainSummary();
|
|
1889
|
+
})
|
|
1890
|
+
.catch(function () {
|
|
1891
|
+
if (loadingEl) loadingEl.style.display = 'none';
|
|
1892
|
+
if (emptyEl) {
|
|
1893
|
+
emptyEl.textContent = 'Failed to scan projects. Use "Add" to enter paths manually.';
|
|
1894
|
+
emptyEl.style.display = '';
|
|
1895
|
+
}
|
|
1896
|
+
});
|
|
1897
|
+
};
|
|
1898
|
+
|
|
1899
|
+
function createProjectRow(proj) {
|
|
1900
|
+
var row = document.createElement('label');
|
|
1901
|
+
row.className = 'checkbox-item';
|
|
1902
|
+
row.style.cssText = 'padding:8px 12px;background:var(--panel2);border:1px solid var(--border);border-radius:6px;gap:10px;transition:border-color 0.15s';
|
|
1903
|
+
var cb = document.createElement('input');
|
|
1904
|
+
cb.type = 'checkbox';
|
|
1905
|
+
cb.style.cssText = 'width:15px;height:15px;accent-color:var(--blue);flex-shrink:0';
|
|
1906
|
+
cb.dataset.projectPath = proj.path;
|
|
1907
|
+
cb.addEventListener('change', function () {
|
|
1908
|
+
row.style.borderColor = cb.checked ? 'var(--blue)' : 'var(--border)';
|
|
1909
|
+
updateBrainSummary();
|
|
1910
|
+
});
|
|
1911
|
+
var info = document.createElement('div');
|
|
1912
|
+
info.style.cssText = 'flex:1;min-width:0';
|
|
1913
|
+
var nameRow = document.createElement('div');
|
|
1914
|
+
nameRow.style.cssText = 'font-size:13px;font-weight:600;display:flex;align-items:center;gap:8px';
|
|
1915
|
+
nameRow.textContent = proj.name;
|
|
1916
|
+
if (proj.hasBrain) {
|
|
1917
|
+
var badge = document.createElement('span');
|
|
1918
|
+
badge.style.cssText = 'font-size:10px;font-weight:500;padding:1px 6px;border-radius:4px;background:rgba(35,134,54,0.15);color:var(--green)';
|
|
1919
|
+
badge.textContent = '✓ Brain installed';
|
|
1920
|
+
nameRow.appendChild(badge);
|
|
1921
|
+
}
|
|
1922
|
+
var pathRow = document.createElement('div');
|
|
1923
|
+
pathRow.style.cssText = 'font-size:11px;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
|
|
1924
|
+
pathRow.textContent = proj.path;
|
|
1925
|
+
info.appendChild(nameRow);
|
|
1926
|
+
info.appendChild(pathRow);
|
|
1927
|
+
row.appendChild(cb);
|
|
1928
|
+
row.appendChild(info);
|
|
1929
|
+
return row;
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
window.addCustomProject = function () {
|
|
1933
|
+
var input = document.getElementById('custom-project-path');
|
|
1934
|
+
var val = (input ? input.value : '').trim();
|
|
1935
|
+
if (!val) return;
|
|
1936
|
+
if (!val.startsWith('/')) {
|
|
1937
|
+
alert('Please enter an absolute path (starting with /).');
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
// Check for duplicates
|
|
1941
|
+
var allPaths = _detectedProjects.map(function (p) { return p.path; }).concat(_customProjects.map(function (p) { return p.path; }));
|
|
1942
|
+
if (allPaths.indexOf(val) !== -1) {
|
|
1943
|
+
alert('This project is already in the list.');
|
|
1944
|
+
return;
|
|
1945
|
+
}
|
|
1946
|
+
var proj = { name: val.split('/').pop(), path: val, hasGit: false, hasBrain: false, custom: true };
|
|
1947
|
+
_customProjects.push(proj);
|
|
1948
|
+
var listEl = document.getElementById('project-list');
|
|
1949
|
+
var emptyEl = document.getElementById('project-list-empty');
|
|
1950
|
+
if (emptyEl) emptyEl.style.display = 'none';
|
|
1951
|
+
if (listEl) {
|
|
1952
|
+
listEl.style.display = 'flex';
|
|
1953
|
+
listEl.style.flexDirection = 'column';
|
|
1954
|
+
listEl.style.gap = '6px';
|
|
1955
|
+
var row = createProjectRow(proj);
|
|
1956
|
+
// Auto-check custom additions
|
|
1957
|
+
var cb = row.querySelector('input[type=checkbox]');
|
|
1958
|
+
if (cb) {
|
|
1959
|
+
cb.checked = true;
|
|
1960
|
+
row.style.borderColor = 'var(--blue)';
|
|
1961
|
+
}
|
|
1962
|
+
listEl.appendChild(row);
|
|
1963
|
+
}
|
|
1964
|
+
if (input) input.value = '';
|
|
1965
|
+
updateBrainSummary();
|
|
1966
|
+
};
|
|
1967
|
+
|
|
1968
|
+
function updateBrainSummary() {
|
|
1969
|
+
var globalChecked = document.getElementById('brain-global') && document.getElementById('brain-global').checked;
|
|
1970
|
+
var projectCheckboxes = document.querySelectorAll('#project-list input[type=checkbox]:checked');
|
|
1971
|
+
var total = (globalChecked ? 1 : 0) + projectCheckboxes.length;
|
|
1972
|
+
var summaryEl = document.getElementById('brain-selection-summary');
|
|
1973
|
+
var countEl = document.getElementById('brain-selection-count');
|
|
1974
|
+
if (summaryEl && countEl) {
|
|
1975
|
+
if (total > 0) {
|
|
1976
|
+
countEl.textContent = total;
|
|
1977
|
+
summaryEl.style.display = '';
|
|
1978
|
+
} else {
|
|
1979
|
+
summaryEl.style.display = 'none';
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
// Wire global checkbox
|
|
1985
|
+
var globalCb = document.getElementById('brain-global');
|
|
1986
|
+
if (globalCb) {
|
|
1987
|
+
globalCb.addEventListener('change', function () {
|
|
1988
|
+
var parentLabel = globalCb.closest('.checkbox-item');
|
|
1989
|
+
if (parentLabel) {
|
|
1990
|
+
parentLabel.style.borderColor = globalCb.checked ? 'var(--green)' : 'var(--border)';
|
|
1991
|
+
}
|
|
1992
|
+
updateBrainSummary();
|
|
1993
|
+
});
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
// ── Phase 1: Install on another computer on the network ──
|
|
1997
|
+
window.installOnNetwork = function () {
|
|
1998
|
+
var pass = 'totalrecall';
|
|
1999
|
+
window.saveAllToLocalStorage();
|
|
2000
|
+
var host = (document.getElementById('cfg-localnet-host') || {}).value;
|
|
2001
|
+
var user = (document.getElementById('cfg-localnet-user') || {}).value;
|
|
2002
|
+
if (!host || !host.trim()) { alert('Please enter the IP address or hostname of that computer.'); return; }
|
|
2003
|
+
if (!user || !user.trim()) { alert('Please enter the username on that computer.'); return; }
|
|
2004
|
+
|
|
2005
|
+
W.dashboardPassword = pass;
|
|
2006
|
+
goPhase(2);
|
|
2007
|
+
startSSE();
|
|
2008
|
+
fetch('/api/start-install', {
|
|
2009
|
+
method: 'POST',
|
|
2010
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2011
|
+
body: JSON.stringify({
|
|
2012
|
+
deployTarget: 'localnet',
|
|
2013
|
+
localnetHost: host.trim(),
|
|
2014
|
+
localnetUser: user.trim(),
|
|
2015
|
+
model: 'flash',
|
|
2016
|
+
httpsMethod: 'local',
|
|
2017
|
+
skipSearxng: true,
|
|
2018
|
+
skipCaddy: true,
|
|
2019
|
+
skipCompile: false,
|
|
2020
|
+
skipModels: false,
|
|
2021
|
+
dashboardPassword: pass
|
|
2022
|
+
}),
|
|
2023
|
+
}).catch(function (e) { console.error(e); });
|
|
2024
|
+
};
|
|
2025
|
+
|
|
2026
|
+
// ── Phase 1: Vast.ai provision (client-side) ──
|
|
2027
|
+
window.provisionVastAI = function (instanceId) {
|
|
2028
|
+
var pass = 'totalrecall';
|
|
2029
|
+
window.saveAllToLocalStorage();
|
|
2030
|
+
var key = (document.getElementById('cfg-vastai-key') || {}).value;
|
|
2031
|
+
if (!key || !key.trim()) {
|
|
2032
|
+
alert('Please paste your Vast.ai API key first (Step 2 above).');
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
W.dashboardPassword = pass;
|
|
2037
|
+
goPhase(2);
|
|
2038
|
+
startSSE();
|
|
2039
|
+
var payload = {
|
|
2040
|
+
vastaiKey: key.trim(),
|
|
2041
|
+
dashboardPassword: pass
|
|
2042
|
+
};
|
|
2043
|
+
if (instanceId) {
|
|
2044
|
+
payload.instanceId = instanceId;
|
|
2045
|
+
}
|
|
2046
|
+
fetch('/api/provision-vastai', {
|
|
2047
|
+
method: 'POST',
|
|
2048
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2049
|
+
body: JSON.stringify(payload),
|
|
2050
|
+
}).catch(function (err) { console.error('provision-vastai error', err); });
|
|
2051
|
+
};
|
|
2052
|
+
|
|
2053
|
+
// ── Phase 1: Scan Vast.ai account for existing instances ──
|
|
2054
|
+
window.scanVastAIInstances = function (e) {
|
|
2055
|
+
if (e) e.preventDefault();
|
|
2056
|
+
var key = (document.getElementById('cfg-vastai-key') || {}).value;
|
|
2057
|
+
if (!key || !key.trim()) {
|
|
2058
|
+
alert('Please paste your Vast.ai API key first to scan your account.');
|
|
2059
|
+
return;
|
|
2060
|
+
}
|
|
2061
|
+
var scanBtn = document.getElementById('vastai-scan-btn');
|
|
2062
|
+
var container = document.getElementById('vastai-existing-container');
|
|
2063
|
+
var list = document.getElementById('vastai-existing-list');
|
|
2064
|
+
|
|
2065
|
+
if (scanBtn) {
|
|
2066
|
+
scanBtn.disabled = true;
|
|
2067
|
+
scanBtn.textContent = 'Scanning...';
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
fetch('/api/vastai-instances', {
|
|
2071
|
+
method: 'POST',
|
|
2072
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2073
|
+
body: JSON.stringify({ vastaiKey: key.trim() })
|
|
2074
|
+
})
|
|
2075
|
+
.then(function (r) { return r.json(); })
|
|
2076
|
+
.then(function (data) {
|
|
2077
|
+
if (scanBtn) {
|
|
2078
|
+
scanBtn.disabled = false;
|
|
2079
|
+
scanBtn.textContent = '🔍 Scan Account';
|
|
2080
|
+
}
|
|
2081
|
+
if (!data.ok || !data.instances || !data.instances.length) {
|
|
2082
|
+
if (container) container.style.display = 'none';
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
var failedIds = [];
|
|
2087
|
+
|
|
2088
|
+
if (list) {
|
|
2089
|
+
list.innerHTML = '';
|
|
2090
|
+
data.instances.forEach(function (inst) {
|
|
2091
|
+
var gpu = inst.gpu_name || 'GPU';
|
|
2092
|
+
var price = inst.dph_total ? ('$' + (inst.dph_total * 24 * 30).toFixed(2) + '/mo') : 'unknown price';
|
|
2093
|
+
var status = inst.actual_status || inst.status || 'loading';
|
|
2094
|
+
|
|
2095
|
+
var isFailed = status === 'broken' || status === 'offline' || status === 'exited' || status === 'stopped';
|
|
2096
|
+
if (isFailed) {
|
|
2097
|
+
failedIds.push(inst.id);
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
var div = document.createElement('div');
|
|
2101
|
+
div.style.display = 'flex';
|
|
2102
|
+
div.style.alignItems = 'center';
|
|
2103
|
+
div.style.justifyContent = 'space-between';
|
|
2104
|
+
div.style.padding = '8px 12px';
|
|
2105
|
+
div.style.border = '1px solid var(--border)';
|
|
2106
|
+
div.style.borderRadius = '6px';
|
|
2107
|
+
div.style.background = 'var(--panel)';
|
|
2108
|
+
div.style.fontSize = '12px';
|
|
2109
|
+
|
|
2110
|
+
var createdStr = '';
|
|
2111
|
+
if (inst.start_date) {
|
|
2112
|
+
try {
|
|
2113
|
+
var d = new Date(inst.start_date * 1000);
|
|
2114
|
+
createdStr = ' | Created: ' + d.toLocaleString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
|
|
2115
|
+
} catch (e) {}
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
var detailsDiv = document.createElement('div');
|
|
2119
|
+
detailsDiv.innerHTML = '<strong style="color:var(--text)">' + gpu + ' (ID: ' + inst.id + ')</strong><br>' +
|
|
2120
|
+
'<span style="color:var(--muted)">Status: ' + status + ' | ' + price + createdStr + '</span>';
|
|
2121
|
+
|
|
2122
|
+
var actionsDiv = document.createElement('div');
|
|
2123
|
+
actionsDiv.style.display = 'flex';
|
|
2124
|
+
actionsDiv.style.gap = '8px';
|
|
2125
|
+
|
|
2126
|
+
var adoptBtn = document.createElement('button');
|
|
2127
|
+
adoptBtn.className = 'btn btn-primary';
|
|
2128
|
+
adoptBtn.style.padding = '4px 10px';
|
|
2129
|
+
adoptBtn.style.fontSize = '11px';
|
|
2130
|
+
adoptBtn.textContent = 'Adopt & Connect';
|
|
2131
|
+
adoptBtn.onclick = function (event) {
|
|
2132
|
+
if (event) event.preventDefault();
|
|
2133
|
+
if (confirm('Are you sure you want to adopt instance ' + inst.id + ' (' + gpu + ')? Total Recall will connect and finish installation there.')) {
|
|
2134
|
+
window.provisionVastAI(inst.id);
|
|
2135
|
+
}
|
|
2136
|
+
};
|
|
2137
|
+
|
|
2138
|
+
var destroyBtn = document.createElement('button');
|
|
2139
|
+
destroyBtn.className = 'btn btn-secondary';
|
|
2140
|
+
destroyBtn.style.padding = '4px 10px';
|
|
2141
|
+
destroyBtn.style.fontSize = '11px';
|
|
2142
|
+
destroyBtn.style.color = 'var(--red)';
|
|
2143
|
+
destroyBtn.style.borderColor = 'var(--red)';
|
|
2144
|
+
destroyBtn.textContent = '🗑️ Destroy';
|
|
2145
|
+
destroyBtn.onclick = function (event) {
|
|
2146
|
+
if (event) event.preventDefault();
|
|
2147
|
+
if (confirm('⚠️ WARNING: This will permanently delete/destroy instance ' + inst.id + ' (' + gpu + '). Any data on it will be lost and billing will stop immediately. Proceed?')) {
|
|
2148
|
+
destroyBtn.disabled = true;
|
|
2149
|
+
destroyBtn.textContent = 'Destroying...';
|
|
2150
|
+
fetch('/api/destroy-vastai-instance', {
|
|
2151
|
+
method: 'POST',
|
|
2152
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2153
|
+
body: JSON.stringify({ vastaiKey: key.trim(), instanceId: inst.id })
|
|
2154
|
+
})
|
|
2155
|
+
.then(function (r) { return r.json(); })
|
|
2156
|
+
.then(function (data) {
|
|
2157
|
+
if (data.ok) {
|
|
2158
|
+
window.scanVastAIInstances();
|
|
2159
|
+
} else {
|
|
2160
|
+
destroyBtn.disabled = false;
|
|
2161
|
+
destroyBtn.textContent = '🗑️ Destroy';
|
|
2162
|
+
alert('Failed to destroy instance: ' + (data.error || 'Unknown error'));
|
|
2163
|
+
}
|
|
2164
|
+
})
|
|
2165
|
+
.catch(function (err) {
|
|
2166
|
+
destroyBtn.disabled = false;
|
|
2167
|
+
destroyBtn.textContent = '🗑️ Destroy';
|
|
2168
|
+
alert('Error: ' + err.message);
|
|
2169
|
+
});
|
|
2170
|
+
}
|
|
2171
|
+
};
|
|
2172
|
+
|
|
2173
|
+
actionsDiv.appendChild(adoptBtn);
|
|
2174
|
+
actionsDiv.appendChild(destroyBtn);
|
|
2175
|
+
|
|
2176
|
+
div.appendChild(detailsDiv);
|
|
2177
|
+
div.appendChild(actionsDiv);
|
|
2178
|
+
list.appendChild(div);
|
|
2179
|
+
});
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
// Update failed cleanup banner
|
|
2183
|
+
var cleanupBanner = document.getElementById('vastai-cleanup-banner');
|
|
2184
|
+
if (cleanupBanner) {
|
|
2185
|
+
if (failedIds.length > 0) {
|
|
2186
|
+
cleanupBanner.style.display = 'flex';
|
|
2187
|
+
window._onboardingFailedInstanceIds = failedIds;
|
|
2188
|
+
} else {
|
|
2189
|
+
cleanupBanner.style.display = 'none';
|
|
2190
|
+
window._onboardingFailedInstanceIds = [];
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
if (container) {
|
|
2195
|
+
container.style.display = 'block';
|
|
2196
|
+
}
|
|
2197
|
+
})
|
|
2198
|
+
.catch(function (err) {
|
|
2199
|
+
if (scanBtn) {
|
|
2200
|
+
scanBtn.disabled = false;
|
|
2201
|
+
scanBtn.textContent = '🔍 Scan Account';
|
|
2202
|
+
}
|
|
2203
|
+
alert('Network error scanning Vast.ai instances: ' + err.message);
|
|
2204
|
+
});
|
|
2205
|
+
};
|
|
2206
|
+
|
|
2207
|
+
// ── Phase 1: Clean Up Failed Onboarding Instances ──
|
|
2208
|
+
window.cleanupOnboardingFailedInstances = function (e) {
|
|
2209
|
+
if (e) e.preventDefault();
|
|
2210
|
+
var key = (document.getElementById('cfg-vastai-key') || {}).value;
|
|
2211
|
+
var failedIds = window._onboardingFailedInstanceIds || [];
|
|
2212
|
+
if (!failedIds.length) return;
|
|
2213
|
+
|
|
2214
|
+
if (confirm('⚠️ WARNING: This will permanently delete/destroy ALL ' + failedIds.length + ' failed/broken instances from your Vast.ai account. Billing will stop immediately. Proceed?')) {
|
|
2215
|
+
var cleanupBtn = document.getElementById('vastai-cleanup-btn');
|
|
2216
|
+
if (cleanupBtn) {
|
|
2217
|
+
cleanupBtn.disabled = true;
|
|
2218
|
+
cleanupBtn.textContent = 'Cleaning up...';
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
var currentIdx = 0;
|
|
2222
|
+
function destroyNext() {
|
|
2223
|
+
if (currentIdx >= failedIds.length) {
|
|
2224
|
+
alert('Successfully cleaned up all failed instances!');
|
|
2225
|
+
window.scanVastAIInstances();
|
|
2226
|
+
return;
|
|
2227
|
+
}
|
|
2228
|
+
var id = failedIds[currentIdx];
|
|
2229
|
+
fetch('/api/destroy-vastai-instance', {
|
|
2230
|
+
method: 'POST',
|
|
2231
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2232
|
+
body: JSON.stringify({ vastaiKey: key.trim(), instanceId: id })
|
|
2233
|
+
})
|
|
2234
|
+
.then(function () {
|
|
2235
|
+
currentIdx++;
|
|
2236
|
+
destroyNext();
|
|
2237
|
+
})
|
|
2238
|
+
.catch(function () {
|
|
2239
|
+
currentIdx++;
|
|
2240
|
+
destroyNext();
|
|
2241
|
+
});
|
|
2242
|
+
}
|
|
2243
|
+
destroyNext();
|
|
2244
|
+
}
|
|
2245
|
+
};
|
|
2246
|
+
|
|
2247
|
+
// ── Phase 1: Start Install ──
|
|
2248
|
+
window.startInstall = function () {
|
|
2249
|
+
var pass = 'totalrecall';
|
|
2250
|
+
|
|
2251
|
+
var googleApiKey = ((document.getElementById('cfg-google-key') || {}).value || '').trim();
|
|
2252
|
+
var openaiApiKey = ((document.getElementById('cfg-openai-key') || {}).value || '').trim();
|
|
2253
|
+
if (!googleApiKey && !openaiApiKey) {
|
|
2254
|
+
alert('At least one API Key (Google or OpenAI) is required for semantic VFS indexing & reasoning.');
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
var deployTarget = 'local';
|
|
2259
|
+
var targetRadio = document.querySelector('#deploy-target input[type=radio]:checked');
|
|
2260
|
+
if (targetRadio) {
|
|
2261
|
+
deployTarget = targetRadio.value;
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
var domain = 'localhost';
|
|
2265
|
+
var httpsMethod = 'local';
|
|
2266
|
+
var localnetHost = null;
|
|
2267
|
+
var localnetUser = null;
|
|
2268
|
+
var cloudProvider = null;
|
|
2269
|
+
var cloudToken = null;
|
|
2270
|
+
var cloudRegion = null;
|
|
2271
|
+
|
|
2272
|
+
var quickTunnel = document.getElementById('cfg-cloudflare-quick');
|
|
2273
|
+
if (quickTunnel && quickTunnel.checked) {
|
|
2274
|
+
httpsMethod = 'cloudflare-quick';
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
if (deployTarget === 'localnet') {
|
|
2278
|
+
localnetHost = ((document.getElementById('cfg-localnet-host') || {}).value || '').trim();
|
|
2279
|
+
localnetUser = ((document.getElementById('cfg-localnet-user') || {}).value || '').trim() || 'root';
|
|
2280
|
+
if (!localnetHost) {
|
|
2281
|
+
alert('Please enter the Remote Host or IP Address of the network computer.');
|
|
2282
|
+
return;
|
|
2283
|
+
}
|
|
2284
|
+
httpsMethod = 'cloudflare-quick'; // Always use Quick Tunnel for remote localnet access
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
if (deployTarget === 'agent-cloud') {
|
|
2288
|
+
cloudProvider = document.getElementById('cfg-cloud-provider').value;
|
|
2289
|
+
cloudToken = ((document.getElementById('cfg-cloud-token') || {}).value || '').trim();
|
|
2290
|
+
cloudRegion = document.getElementById('cfg-cloud-region').value;
|
|
2291
|
+
if (!cloudToken) {
|
|
2292
|
+
alert('Please enter your Cloud Provider API Key / Token.');
|
|
2293
|
+
return;
|
|
2294
|
+
}
|
|
2295
|
+
httpsMethod = 'cloudflare-quick'; // Always Quick Tunnel for fully automated setups
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
window.saveAllToLocalStorage();
|
|
2299
|
+
W.domain = domain;
|
|
2300
|
+
W.dashboardPassword = pass;
|
|
2301
|
+
W.deployTarget = deployTarget;
|
|
2302
|
+
saveWizardState({ domain: domain, deployTarget: deployTarget });
|
|
2303
|
+
|
|
2304
|
+
var payload = {
|
|
2305
|
+
domain: domain,
|
|
2306
|
+
httpsMethod: httpsMethod,
|
|
2307
|
+
model: 'flash',
|
|
2308
|
+
duckdnsToken: null,
|
|
2309
|
+
cloudflareToken: null,
|
|
2310
|
+
skipSearxng: true,
|
|
2311
|
+
skipCaddy: (httpsMethod === 'local' || httpsMethod === 'cloudflare-quick'),
|
|
2312
|
+
skipCompile: false,
|
|
2313
|
+
skipModels: false,
|
|
2314
|
+
deployTarget: deployTarget,
|
|
2315
|
+
dashboardPassword: pass,
|
|
2316
|
+
googleApiKey: googleApiKey,
|
|
2317
|
+
openaiApiKey: openaiApiKey,
|
|
2318
|
+
installGlobal: !!(document.getElementById('brain-global') && document.getElementById('brain-global').checked),
|
|
2319
|
+
projectPaths: Array.from(document.querySelectorAll('#project-list input[type=checkbox]:checked')).map(function (cb) { return cb.dataset.projectPath; })
|
|
2320
|
+
};
|
|
2321
|
+
|
|
2322
|
+
if (deployTarget === 'localnet') {
|
|
2323
|
+
payload.localnetHost = localnetHost;
|
|
2324
|
+
payload.localnetUser = localnetUser;
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
if (deployTarget === 'agent-cloud') {
|
|
2328
|
+
payload.cloudProvider = cloudProvider;
|
|
2329
|
+
payload.cloudToken = cloudToken;
|
|
2330
|
+
payload.cloudRegion = cloudRegion;
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
goPhase(2);
|
|
2334
|
+
startSSE();
|
|
2335
|
+
|
|
2336
|
+
fetch('/api/start-install', {
|
|
2337
|
+
method: 'POST',
|
|
2338
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2339
|
+
body: JSON.stringify(payload),
|
|
2340
|
+
}).catch(function (e) { console.error('start-install error:', e); });
|
|
2341
|
+
};
|
|
2342
|
+
|
|
2343
|
+
// ── Phase 2: SSE progress stream ──
|
|
2344
|
+
var _stepCount = 0;
|
|
2345
|
+
var TOTAL_STEPS = 12;
|
|
2346
|
+
|
|
2347
|
+
// ── Persistent Bar Update Helpers ──
|
|
2348
|
+
function syncPersistentBarVisibility() {
|
|
2349
|
+
var pbar = document.getElementById('persistent-install-bar');
|
|
2350
|
+
if (!pbar) return;
|
|
2351
|
+
|
|
2352
|
+
// Do not show the persistent bar on Phase 0, Phase 1, or Phase 2
|
|
2353
|
+
if (W.phase === 0 || W.phase === 1 || W.phase === 2) {
|
|
2354
|
+
pbar.classList.remove('visible');
|
|
2355
|
+
return;
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
var isActive = (safeStorage.getItem('tr_wizard_install_active') === 'true') || activeEs;
|
|
2359
|
+
var statusType = safeStorage.getItem('tr_wizard_install_status_type');
|
|
2360
|
+
var isDoneOrError = (statusType === 'done' || statusType === 'error');
|
|
2361
|
+
|
|
2362
|
+
// On Phase 6 (Done URLs page), only show the persistent bar if the installation is still active or has errored out
|
|
2363
|
+
if (W.phase === 6) {
|
|
2364
|
+
if (isActive || statusType === 'error') {
|
|
2365
|
+
pbar.classList.add('visible');
|
|
2366
|
+
} else {
|
|
2367
|
+
pbar.classList.remove('visible');
|
|
2368
|
+
}
|
|
2369
|
+
return;
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
if (isActive || isDoneOrError) {
|
|
2373
|
+
pbar.classList.add('visible');
|
|
2374
|
+
} else {
|
|
2375
|
+
pbar.classList.remove('visible');
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
function updatePersistentBarState(type, msg, pctOrStep) {
|
|
2380
|
+
var pbar = document.getElementById('persistent-install-bar');
|
|
2381
|
+
if (!pbar) return;
|
|
2382
|
+
|
|
2383
|
+
var fill = pbar.querySelector('.pbar-fill');
|
|
2384
|
+
var status = pbar.querySelector('.pbar-status-text');
|
|
2385
|
+
var log = pbar.querySelector('.pbar-latest-log');
|
|
2386
|
+
var spinner = pbar.querySelector('.pbar-spinner');
|
|
2387
|
+
|
|
2388
|
+
var pct = 0;
|
|
2389
|
+
if (type === 'done') {
|
|
2390
|
+
pct = 100;
|
|
2391
|
+
} else if (type === 'step') {
|
|
2392
|
+
pct = pctOrStep;
|
|
2393
|
+
} else {
|
|
2394
|
+
pct = Math.min(Math.round((_stepCount / TOTAL_STEPS) * 100), 95);
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
if (fill) fill.style.width = pct + '%';
|
|
2398
|
+
|
|
2399
|
+
if (type === 'done') {
|
|
2400
|
+
if (status) status.innerHTML = '🎉 Install complete!';
|
|
2401
|
+
if (log) {
|
|
2402
|
+
log.textContent = 'All services active. Click to proceed.';
|
|
2403
|
+
log.style.color = 'var(--green)';
|
|
2404
|
+
}
|
|
2405
|
+
if (spinner) {
|
|
2406
|
+
spinner.style.backgroundColor = 'var(--green)';
|
|
2407
|
+
spinner.style.animation = 'none';
|
|
2408
|
+
}
|
|
2409
|
+
} else if (type === 'error') {
|
|
2410
|
+
if (status) status.innerHTML = '❌ Installation failed';
|
|
2411
|
+
if (log) {
|
|
2412
|
+
log.textContent = msg;
|
|
2413
|
+
log.style.color = 'var(--red)';
|
|
2414
|
+
}
|
|
2415
|
+
if (spinner) {
|
|
2416
|
+
spinner.style.backgroundColor = 'var(--red)';
|
|
2417
|
+
spinner.style.animation = 'none';
|
|
2418
|
+
}
|
|
2419
|
+
} else {
|
|
2420
|
+
if (status) status.innerHTML = '⚡ Installing Total Recall (' + pct + '%)';
|
|
2421
|
+
if (log) {
|
|
2422
|
+
log.textContent = msg;
|
|
2423
|
+
log.style.color = type === 'warn' ? 'var(--yellow)' : 'var(--blue)';
|
|
2424
|
+
}
|
|
2425
|
+
if (spinner) {
|
|
2426
|
+
spinner.style.backgroundColor = 'var(--blue)';
|
|
2427
|
+
spinner.style.animation = 'pbar-pulse 1.4s infinite ease-in-out';
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
safeStorage.setItem('tr_wizard_install_pct', pct);
|
|
2432
|
+
safeStorage.setItem('tr_wizard_install_status_msg', msg);
|
|
2433
|
+
safeStorage.setItem('tr_wizard_install_status_type', type);
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
function startSSE() {
|
|
2437
|
+
if (activeEs) {
|
|
2438
|
+
return;
|
|
2439
|
+
}
|
|
2440
|
+
// Record that installation is actively running
|
|
2441
|
+
safeStorage.setItem('tr_wizard_install_active', 'true');
|
|
2442
|
+
|
|
2443
|
+
var es = new EventSource('/events');
|
|
2444
|
+
activeEs = es;
|
|
2445
|
+
var bar = document.getElementById('progress-bar');
|
|
2446
|
+
var label = document.getElementById('progress-label');
|
|
2447
|
+
var log = document.getElementById('log');
|
|
2448
|
+
|
|
2449
|
+
// Reset progress, logs and notices
|
|
2450
|
+
_stepCount = 0;
|
|
2451
|
+
if (bar) bar.style.width = '0%';
|
|
2452
|
+
if (label) label.textContent = 'Waiting for install to begin...';
|
|
2453
|
+
if (log) log.innerHTML = '';
|
|
2454
|
+
|
|
2455
|
+
var errorNotice = document.getElementById('install-error-notice');
|
|
2456
|
+
if (errorNotice) errorNotice.style.display = 'none';
|
|
2457
|
+
var doneNotice = document.getElementById('install-done-notice');
|
|
2458
|
+
if (doneNotice) doneNotice.style.display = 'none';
|
|
2459
|
+
|
|
2460
|
+
syncPersistentBarVisibility();
|
|
2461
|
+
|
|
2462
|
+
var icons = { step: '>>', ok: '✓', warn: '!!', error: '✗', info: '--' };
|
|
2463
|
+
|
|
2464
|
+
function addEntry(type, msg) {
|
|
2465
|
+
if (!log) return;
|
|
2466
|
+
var div = document.createElement('div');
|
|
2467
|
+
div.className = 'log-entry ' + (type || 'info');
|
|
2468
|
+
var icon = document.createElement('span');
|
|
2469
|
+
icon.className = 'icon';
|
|
2470
|
+
icon.textContent = icons[type] || '--';
|
|
2471
|
+
var text = document.createElement('span');
|
|
2472
|
+
text.textContent = msg;
|
|
2473
|
+
div.appendChild(icon);
|
|
2474
|
+
div.appendChild(text);
|
|
2475
|
+
log.appendChild(div);
|
|
2476
|
+
log.scrollTop = log.scrollHeight;
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
es.onmessage = function (e) {
|
|
2480
|
+
var data = JSON.parse(e.data);
|
|
2481
|
+
if (data.type === 'step') {
|
|
2482
|
+
_stepCount++;
|
|
2483
|
+
var pct = Math.min(Math.round((_stepCount / TOTAL_STEPS) * 100), 95);
|
|
2484
|
+
if (bar) bar.style.width = pct + '%';
|
|
2485
|
+
if (label) label.textContent = data.msg;
|
|
2486
|
+
addEntry('step', data.msg);
|
|
2487
|
+
updatePersistentBarState('step', data.msg, pct);
|
|
2488
|
+
} else if (data.type === 'done') {
|
|
2489
|
+
if (bar) bar.style.width = '100%';
|
|
2490
|
+
if (label) label.textContent = 'Install complete!';
|
|
2491
|
+
addEntry('ok', 'Install complete!');
|
|
2492
|
+
var apiUrl = data.apiUrl || ('https://' + W.domain + '/v1/chat/completions');
|
|
2493
|
+
var dashUrl = data.dashUrl || ('https://' + W.domain + '/');
|
|
2494
|
+
var healthUrl = data.healthUrl || ('https://' + W.domain + '/health');
|
|
2495
|
+
|
|
2496
|
+
saveWizardState({
|
|
2497
|
+
apiUrl: apiUrl,
|
|
2498
|
+
dashUrl: dashUrl,
|
|
2499
|
+
healthUrl: healthUrl
|
|
2500
|
+
});
|
|
2501
|
+
|
|
2502
|
+
if (doneNotice) doneNotice.style.display = '';
|
|
2503
|
+
safeStorage.setItem('tr_wizard_install_active', 'false');
|
|
2504
|
+
updatePersistentBarState('done', 'Install complete!', 100);
|
|
2505
|
+
es.close();
|
|
2506
|
+
if (activeEs === es) activeEs = null;
|
|
2507
|
+
syncPersistentBarVisibility();
|
|
2508
|
+
} else if (data.type === 'error') {
|
|
2509
|
+
addEntry('error', data.msg);
|
|
2510
|
+
if (errorNotice) errorNotice.style.display = '';
|
|
2511
|
+
safeStorage.setItem('tr_wizard_install_active', 'false');
|
|
2512
|
+
updatePersistentBarState('error', data.msg, _stepCount);
|
|
2513
|
+
es.close();
|
|
2514
|
+
if (activeEs === es) activeEs = null;
|
|
2515
|
+
syncPersistentBarVisibility();
|
|
2516
|
+
} else if (data.type === 'warn') {
|
|
2517
|
+
addEntry('warn', data.msg);
|
|
2518
|
+
updatePersistentBarState('warn', data.msg, _stepCount);
|
|
2519
|
+
} else {
|
|
2520
|
+
addEntry('info', data.msg);
|
|
2521
|
+
updatePersistentBarState('info', data.msg, _stepCount);
|
|
2522
|
+
}
|
|
2523
|
+
};
|
|
2524
|
+
|
|
2525
|
+
es.onerror = function () {
|
|
2526
|
+
addEntry('warn', 'Connection closed (install may still be running in the terminal).');
|
|
2527
|
+
updatePersistentBarState('warn', 'Connection closed (install may still be running in the terminal).', _stepCount);
|
|
2528
|
+
es.close();
|
|
2529
|
+
if (activeEs === es) activeEs = null;
|
|
2530
|
+
syncPersistentBarVisibility();
|
|
2531
|
+
};
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
// ── Phase 3: Generate key ──
|
|
2535
|
+
window.generateKey = function () {
|
|
2536
|
+
var name = document.getElementById('key-name').value.trim() || 'default';
|
|
2537
|
+
var scope = document.querySelector('#key-scope input[type=radio]:checked').value;
|
|
2538
|
+
var btn = document.getElementById('gen-key-btn');
|
|
2539
|
+
var errBox = document.getElementById('key-error');
|
|
2540
|
+
errBox.style.display = 'none';
|
|
2541
|
+
btn.disabled = true;
|
|
2542
|
+
btn.textContent = 'Generating…';
|
|
2543
|
+
|
|
2544
|
+
fetch('/api/generate-pat', {
|
|
2545
|
+
method: 'POST',
|
|
2546
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2547
|
+
body: JSON.stringify({ name: name, scope: scope }),
|
|
2548
|
+
})
|
|
2549
|
+
.then(function (r) { return r.json(); })
|
|
2550
|
+
.then(function (data) {
|
|
2551
|
+
if (data.token || data.key || data.pat) {
|
|
2552
|
+
var tok = data.token || data.key || data.pat;
|
|
2553
|
+
saveWizardState({ pat: tok });
|
|
2554
|
+
var patInput = document.getElementById('pat-key-display');
|
|
2555
|
+
if (patInput) {
|
|
2556
|
+
patInput.value = tok;
|
|
2557
|
+
patInput.type = 'text'; // Auto-reveal key on generation so user can see it right away
|
|
2558
|
+
var patToggle = patInput.nextElementSibling;
|
|
2559
|
+
if (patToggle) patToggle.textContent = 'Hide';
|
|
2560
|
+
}
|
|
2561
|
+
var settingsPatInput = document.getElementById('settings-pat-key');
|
|
2562
|
+
if (settingsPatInput) settingsPatInput.value = tok;
|
|
2563
|
+
document.getElementById('key-result').style.display = '';
|
|
2564
|
+
btn.textContent = 'Generate Another';
|
|
2565
|
+
btn.disabled = false;
|
|
2566
|
+
} else {
|
|
2567
|
+
throw new Error(data.error || JSON.stringify(data));
|
|
2568
|
+
}
|
|
2569
|
+
})
|
|
2570
|
+
.catch(function (e) {
|
|
2571
|
+
document.getElementById('key-error-text').textContent = e.message;
|
|
2572
|
+
errBox.style.display = '';
|
|
2573
|
+
btn.textContent = 'Generate Key';
|
|
2574
|
+
btn.disabled = false;
|
|
2575
|
+
});
|
|
2576
|
+
};
|
|
2577
|
+
|
|
2578
|
+
window.copyToken = function (id) {
|
|
2579
|
+
var el = document.getElementById(id || 'pat-key-display');
|
|
2580
|
+
if (!el) return;
|
|
2581
|
+
var tok = el.tagName === 'INPUT' ? el.value : el.textContent;
|
|
2582
|
+
navigator.clipboard.writeText(tok).catch(function () {
|
|
2583
|
+
var ta = document.createElement('textarea');
|
|
2584
|
+
ta.value = tok;
|
|
2585
|
+
document.body.appendChild(ta);
|
|
2586
|
+
ta.select();
|
|
2587
|
+
document.execCommand('copy');
|
|
2588
|
+
document.body.removeChild(ta);
|
|
2589
|
+
});
|
|
2590
|
+
|
|
2591
|
+
if (event && event.currentTarget) {
|
|
2592
|
+
var btn = event.currentTarget;
|
|
2593
|
+
var origText = btn.textContent;
|
|
2594
|
+
btn.textContent = 'Copied!';
|
|
2595
|
+
btn.style.borderColor = 'var(--green)';
|
|
2596
|
+
btn.style.color = 'var(--green)';
|
|
2597
|
+
setTimeout(function() {
|
|
2598
|
+
btn.textContent = origText;
|
|
2599
|
+
btn.style.borderColor = '';
|
|
2600
|
+
btn.style.color = '';
|
|
2601
|
+
}, 1500);
|
|
2602
|
+
}
|
|
2603
|
+
};
|
|
2604
|
+
|
|
2605
|
+
// ── Phase 4: Integrations ──
|
|
2606
|
+
// IDE picker toggle
|
|
2607
|
+
window.toggleIde = function(checkbox) {
|
|
2608
|
+
var label = checkbox.closest('.ide-card');
|
|
2609
|
+
if (checkbox.checked) {
|
|
2610
|
+
label.classList.add('selected');
|
|
2611
|
+
} else {
|
|
2612
|
+
label.classList.remove('selected');
|
|
2613
|
+
}
|
|
2614
|
+
};
|
|
2615
|
+
|
|
2616
|
+
// Configure and configure GitHub backup
|
|
2617
|
+
window.saveGitHubBackup = function() {
|
|
2618
|
+
var tokenInput = document.getElementById('cfg-github-token');
|
|
2619
|
+
var token = (tokenInput.value || '').trim();
|
|
2620
|
+
var resultEl = document.getElementById('github-result-notice');
|
|
2621
|
+
var progressEl = document.getElementById('github-progress');
|
|
2622
|
+
var logEl = document.getElementById('github-log');
|
|
2623
|
+
var btn = document.getElementById('cfg-github-btn');
|
|
2624
|
+
|
|
2625
|
+
if (!token) {
|
|
2626
|
+
resultEl.style.display = 'block';
|
|
2627
|
+
resultEl.className = 'notice warn';
|
|
2628
|
+
resultEl.innerHTML = '⚠️ GitHub Personal Access Token is required.';
|
|
2629
|
+
return;
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
resultEl.style.display = 'none';
|
|
2633
|
+
logEl.style.display = 'block';
|
|
2634
|
+
logEl.textContent = '';
|
|
2635
|
+
progressEl.style.display = 'grid';
|
|
2636
|
+
btn.disabled = true;
|
|
2637
|
+
|
|
2638
|
+
// Reset progress steps
|
|
2639
|
+
for (var i = 1; i <= 5; i++) {
|
|
2640
|
+
var stepEl = document.getElementById('gh-step-' + i);
|
|
2641
|
+
if (stepEl) {
|
|
2642
|
+
stepEl.className = 'progress-step-item pending';
|
|
2643
|
+
stepEl.querySelector('.step-icon').textContent = '○';
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
fetch('/api/github/configure', {
|
|
2648
|
+
method: 'POST',
|
|
2649
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2650
|
+
body: JSON.stringify({ token: token })
|
|
2651
|
+
})
|
|
2652
|
+
.then(function(response) {
|
|
2653
|
+
if (!response.ok) {
|
|
2654
|
+
throw new Error('Server returned HTTP ' + response.status);
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
var reader = response.body.getReader();
|
|
2658
|
+
var decoder = new TextDecoder();
|
|
2659
|
+
var buffer = '';
|
|
2660
|
+
|
|
2661
|
+
function read() {
|
|
2662
|
+
return reader.read().then(function(result) {
|
|
2663
|
+
if (result.done) {
|
|
2664
|
+
return;
|
|
2665
|
+
}
|
|
2666
|
+
buffer += decoder.decode(result.value, { stream: true });
|
|
2667
|
+
var lines = buffer.split('\n');
|
|
2668
|
+
buffer = lines.pop(); // Keep partial line
|
|
2669
|
+
|
|
2670
|
+
lines.forEach(function(lineStr) {
|
|
2671
|
+
if (!lineStr.trim()) return;
|
|
2672
|
+
try {
|
|
2673
|
+
var line = JSON.parse(lineStr);
|
|
2674
|
+
if (line.type === 'log') {
|
|
2675
|
+
logEl.textContent += line.msg + '\n';
|
|
2676
|
+
logEl.scrollTop = logEl.scrollHeight;
|
|
2677
|
+
} else if (line.type === 'step') {
|
|
2678
|
+
var stepEl = document.getElementById('gh-step-' + line.stepIndex);
|
|
2679
|
+
if (stepEl) {
|
|
2680
|
+
stepEl.className = 'progress-step-item ' + line.status;
|
|
2681
|
+
var icon = '○';
|
|
2682
|
+
if (line.status === 'active') icon = '🔄';
|
|
2683
|
+
else if (line.status === 'success') icon = '✅';
|
|
2684
|
+
else if (line.status === 'error') icon = '❌';
|
|
2685
|
+
stepEl.innerHTML = '<span class="step-icon">' + icon + '</span> ' + line.msg;
|
|
2686
|
+
}
|
|
2687
|
+
} else if (line.type === 'success') {
|
|
2688
|
+
resultEl.style.display = 'block';
|
|
2689
|
+
resultEl.className = 'notice success';
|
|
2690
|
+
resultEl.innerHTML = '✅ ' + line.msg;
|
|
2691
|
+
btn.disabled = false;
|
|
2692
|
+
} else if (line.type === 'error') {
|
|
2693
|
+
resultEl.style.display = 'block';
|
|
2694
|
+
resultEl.className = 'notice warn';
|
|
2695
|
+
resultEl.innerHTML = '⚠️ ' + line.msg;
|
|
2696
|
+
btn.disabled = false;
|
|
2697
|
+
}
|
|
2698
|
+
} catch(e) {
|
|
2699
|
+
console.error('Failed to parse NDJSON line:', lineStr, e);
|
|
2700
|
+
}
|
|
2701
|
+
});
|
|
2702
|
+
return read();
|
|
2703
|
+
});
|
|
2704
|
+
}
|
|
2705
|
+
return read();
|
|
2706
|
+
})
|
|
2707
|
+
.catch(function(err) {
|
|
2708
|
+
resultEl.style.display = 'block';
|
|
2709
|
+
resultEl.className = 'notice warn';
|
|
2710
|
+
resultEl.innerHTML = '⚠️ Configuration failed: ' + err.message;
|
|
2711
|
+
btn.disabled = false;
|
|
2712
|
+
});
|
|
2713
|
+
};
|
|
2714
|
+
|
|
2715
|
+
// Save search API keys (Brave / Tavily / Exa / Serper)
|
|
2716
|
+
window.saveSearchKeys = function() {
|
|
2717
|
+
var tavilyKey = (document.getElementById('tavily-key').value || '').trim();
|
|
2718
|
+
var braveKey = (document.getElementById('brave-key').value || '').trim();
|
|
2719
|
+
var exaKey = (document.getElementById('exa-key').value || '').trim();
|
|
2720
|
+
var serperKey = (document.getElementById('serper-key').value || '').trim();
|
|
2721
|
+
var resultEl = document.getElementById('search-save-result');
|
|
2722
|
+
|
|
2723
|
+
if (!braveKey && !tavilyKey && !exaKey && !serperKey) {
|
|
2724
|
+
resultEl.style.display = '';
|
|
2725
|
+
resultEl.className = 'notice warn';
|
|
2726
|
+
resultEl.textContent = 'No keys entered — that\'s fine. The engine will use DuckDuckGo and Wikipedia for free.';
|
|
2727
|
+
return;
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
resultEl.style.display = '';
|
|
2731
|
+
resultEl.className = 'notice';
|
|
2732
|
+
resultEl.textContent = 'Saving…';
|
|
2733
|
+
|
|
2734
|
+
fetch('/api/save-search-config', {
|
|
2735
|
+
method: 'POST',
|
|
2736
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2737
|
+
body: JSON.stringify({ braveKey, tavilyKey, exaKey, serperKey })
|
|
2738
|
+
})
|
|
2739
|
+
.then(function(r) { return r.json(); })
|
|
2740
|
+
.then(function(data) {
|
|
2741
|
+
if (data.ok) {
|
|
2742
|
+
resultEl.className = 'notice success';
|
|
2743
|
+
resultEl.textContent = '✅ Saved. Primary search source: ' + data.primarySource + '.';
|
|
2744
|
+
} else {
|
|
2745
|
+
resultEl.className = 'notice warn';
|
|
2746
|
+
resultEl.textContent = '⚠️ ' + (data.error || 'Could not save keys.');
|
|
2747
|
+
}
|
|
2748
|
+
})
|
|
2749
|
+
.catch(function() {
|
|
2750
|
+
resultEl.className = 'notice warn';
|
|
2751
|
+
resultEl.textContent = '⚠️ Could not reach the setup server.';
|
|
2752
|
+
});
|
|
2753
|
+
};
|
|
2754
|
+
|
|
2755
|
+
// Auto-detect installed IDEs on page load
|
|
2756
|
+
function autoDetectIDEs() {
|
|
2757
|
+
fetch('/api/detect-ides')
|
|
2758
|
+
.then(function(r) { return r.json(); })
|
|
2759
|
+
.then(function(data) {
|
|
2760
|
+
if (!data.detected) return;
|
|
2761
|
+
data.detected.forEach(function(ide) {
|
|
2762
|
+
var cb = document.querySelector('#ide-' + ide + ' input[type=checkbox]');
|
|
2763
|
+
if (cb && !cb.checked) { cb.checked = true; toggleIde(cb); }
|
|
2764
|
+
});
|
|
2765
|
+
})
|
|
2766
|
+
.catch(function() { /* auto-detect optional */ });
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
function populateIntegrations() {
|
|
2770
|
+
autoDetectIDEs();
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
|
|
2774
|
+
|
|
2775
|
+
// Download full brain as .tar.gz
|
|
2776
|
+
window.downloadBrain = function() {
|
|
2777
|
+
var status = document.getElementById('brain-dl-status');
|
|
2778
|
+
var brainUrl = getBaseUrl();
|
|
2779
|
+
var pat = W.pat || '';
|
|
2780
|
+
if (!brainUrl || !pat) {
|
|
2781
|
+
if (status) status.textContent = '⚠️ Connect your brain first (need URL + PAT)';
|
|
2782
|
+
return;
|
|
2783
|
+
}
|
|
2784
|
+
if (status) status.textContent = 'Preparing download...';
|
|
2785
|
+
// Fetch with auth then trigger blob download
|
|
2786
|
+
fetch(brainUrl + '/api/brain/export', {
|
|
2787
|
+
headers: { 'Authorization': 'Bearer ' + pat }
|
|
2788
|
+
})
|
|
2789
|
+
.then(function(r) {
|
|
2790
|
+
if (!r.ok) throw new Error('Export failed: ' + r.status);
|
|
2791
|
+
return r.blob();
|
|
2792
|
+
})
|
|
2793
|
+
.then(function(blob) {
|
|
2794
|
+
var url = URL.createObjectURL(blob);
|
|
2795
|
+
var a = document.createElement('a');
|
|
2796
|
+
var date = new Date().toISOString().slice(0, 10);
|
|
2797
|
+
a.href = url;
|
|
2798
|
+
a.download = 'total-recall-brain-' + date + '.tar.gz';
|
|
2799
|
+
document.body.appendChild(a);
|
|
2800
|
+
a.click();
|
|
2801
|
+
setTimeout(function() { URL.revokeObjectURL(url); a.remove(); }, 1000);
|
|
2802
|
+
if (status) status.textContent = '✅ Downloaded!';
|
|
2803
|
+
})
|
|
2804
|
+
.catch(function(e) {
|
|
2805
|
+
if (status) status.textContent = '❌ ' + e.message;
|
|
2806
|
+
});
|
|
2807
|
+
};
|
|
2808
|
+
|
|
2809
|
+
// Scan for existing rule files and skills
|
|
2810
|
+
window.scanRuleFiles = function() {
|
|
2811
|
+
var dir = (document.getElementById('import-dir').value || '').trim();
|
|
2812
|
+
var resultEl = document.getElementById('import-rules-result');
|
|
2813
|
+
var listEl = document.getElementById('import-rules-list');
|
|
2814
|
+
resultEl.textContent = 'Scanning...';
|
|
2815
|
+
listEl.style.display = 'none';
|
|
2816
|
+
var url = '/api/import/rules' + (dir ? '?dir=' + encodeURIComponent(dir) : '');
|
|
2817
|
+
fetch(url)
|
|
2818
|
+
.then(function(r) { return r.json(); })
|
|
2819
|
+
.then(function(data) {
|
|
2820
|
+
var detected = data.detected || [];
|
|
2821
|
+
if (detected.length === 0) { resultEl.textContent = 'No rule or skill files found in that directory.'; return; }
|
|
2822
|
+
resultEl.textContent = '';
|
|
2823
|
+
listEl.style.display = '';
|
|
2824
|
+
|
|
2825
|
+
// Group detected files
|
|
2826
|
+
var groups = {};
|
|
2827
|
+
var groupOrder = [];
|
|
2828
|
+
|
|
2829
|
+
detected.forEach(function(f) {
|
|
2830
|
+
var groupId, groupName, isSkill = false;
|
|
2831
|
+
if (f.filename.startsWith('.agent/skills/')) {
|
|
2832
|
+
// Extract skill name: .agent/skills/<name>/...
|
|
2833
|
+
var parts = f.filename.split('/');
|
|
2834
|
+
var skillName = parts[2] || 'unknown';
|
|
2835
|
+
groupId = 'skill:' + skillName;
|
|
2836
|
+
groupName = '🧠 Skill: ' + skillName;
|
|
2837
|
+
isSkill = true;
|
|
2838
|
+
} else if (f.filename.indexOf('/') !== -1 || f.filename.indexOf('\\') !== -1) {
|
|
2839
|
+
var norm = f.filename.replace(/\\/g, '/');
|
|
2840
|
+
var dirPath = norm.substring(0, norm.lastIndexOf('/'));
|
|
2841
|
+
groupId = 'dir:' + dirPath;
|
|
2842
|
+
groupName = '📂 Rules in ' + dirPath;
|
|
2843
|
+
} else {
|
|
2844
|
+
groupId = 'global';
|
|
2845
|
+
groupName = '🌐 Global IDE Rules';
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
if (!groups[groupId]) {
|
|
2849
|
+
groups[groupId] = {
|
|
2850
|
+
id: groupId,
|
|
2851
|
+
name: groupName,
|
|
2852
|
+
isSkill: isSkill,
|
|
2853
|
+
files: []
|
|
2854
|
+
};
|
|
2855
|
+
groupOrder.push(groupId);
|
|
2856
|
+
}
|
|
2857
|
+
groups[groupId].files.push(f);
|
|
2858
|
+
});
|
|
2859
|
+
|
|
2860
|
+
// Sort groups: skills first, then directories, then global
|
|
2861
|
+
groupOrder.sort(function(a, b) {
|
|
2862
|
+
var gA = groups[a];
|
|
2863
|
+
var gB = groups[b];
|
|
2864
|
+
if (gA.isSkill && !gB.isSkill) return -1;
|
|
2865
|
+
if (!gA.isSkill && gB.isSkill) return 1;
|
|
2866
|
+
if (gA.id === 'global') return 1;
|
|
2867
|
+
if (gB.id === 'global') return -1;
|
|
2868
|
+
return gA.name.localeCompare(gB.name);
|
|
2869
|
+
});
|
|
2870
|
+
|
|
2871
|
+
var html = '<div style="font-size:12px;color:var(--muted);margin-bottom:12px">Select files to import:</div>';
|
|
2872
|
+
|
|
2873
|
+
groupOrder.forEach(function(groupId) {
|
|
2874
|
+
var group = groups[groupId];
|
|
2875
|
+
var allAlreadyImported = group.files.every(function(f) { return f.alreadyImported; });
|
|
2876
|
+
|
|
2877
|
+
var groupClass = 'group-details' + (group.isSkill ? ' skill-group' : '');
|
|
2878
|
+
var badgeColor = group.isSkill ? 'var(--purple)' : 'var(--blue)';
|
|
2879
|
+
var badgeText = group.isSkill ? 'Skill Vault' : 'Modular';
|
|
2880
|
+
if (group.id === 'global') {
|
|
2881
|
+
badgeColor = 'var(--green)';
|
|
2882
|
+
badgeText = 'Global';
|
|
2883
|
+
}
|
|
2884
|
+
|
|
2885
|
+
html += '<details class="' + groupClass + '" open style="background:var(--panel2); border:1px solid var(--border); border-radius:8px; margin-bottom:12px; overflow:hidden">';
|
|
2886
|
+
html += '<summary style="display:flex; align-items:center; gap:10px; padding:10px 14px; background:rgba(255,255,255,0.02); border-bottom:1px solid var(--border); cursor:pointer; list-style:none; user-select:none">';
|
|
2887
|
+
html += '<span class="details-arrow" style="font-size:10px; transition:transform 0.15s; display:inline-block; color:var(--muted)">▼</span>';
|
|
2888
|
+
html += '<input type="checkbox" class="import-rule-group-checkbox" data-group-id="' + group.id + '"' + (allAlreadyImported ? '' : ' checked') + ' onclick="event.stopPropagation(); window.toggleGroupCheck(this, \'' + group.id + '\')" style="width:15px; height:15px; accent-color:var(--green); cursor:pointer">';
|
|
2889
|
+
html += '<span style="font-size:13px; font-weight:600; flex:1; display:flex; align-items:center; gap:8px">' +
|
|
2890
|
+
group.name +
|
|
2891
|
+
' <span style="font-size:10px; font-weight:normal; background:rgba(255,255,255,0.06); padding:2px 6px; border-radius:10px; color:var(--muted)">' + group.files.length + ' file' + (group.files.length > 1 ? 's' : '') + '</span>' +
|
|
2892
|
+
'</span>';
|
|
2893
|
+
html += '<span style="font-size:11px; font-weight:600; color:' + badgeColor + '; background:rgba(255,255,255,0.04); border:1px solid ' + badgeColor + '33; padding:1px 6px; border-radius:4px">' + badgeText + '</span>';
|
|
2894
|
+
html += '</summary>';
|
|
2895
|
+
|
|
2896
|
+
html += '<div style="padding:14px; display:flex; flex-direction:column; gap:12px">';
|
|
2897
|
+
|
|
2898
|
+
group.files.forEach(function(f) {
|
|
2899
|
+
var badge = f.alreadyImported ? ' <span style="color:var(--muted);font-size:11px">(already imported)</span>' : '';
|
|
2900
|
+
var shortFilename = f.filename;
|
|
2901
|
+
// Shorten filename if it is inside the group
|
|
2902
|
+
if (group.isSkill) {
|
|
2903
|
+
var prefix = '.agent/skills/' + group.id.split(':')[1] + '/';
|
|
2904
|
+
if (f.filename.startsWith(prefix)) {
|
|
2905
|
+
shortFilename = f.filename.substring(prefix.length);
|
|
2906
|
+
}
|
|
2907
|
+
} else if (group.id !== 'global' && group.id.startsWith('dir:')) {
|
|
2908
|
+
var prefix = group.id.substring(4) + '/';
|
|
2909
|
+
if (f.filename.startsWith(prefix)) {
|
|
2910
|
+
shortFilename = f.filename.substring(prefix.length);
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2914
|
+
html += '<label style="display:flex;align-items:flex-start;gap:10px;cursor:pointer">';
|
|
2915
|
+
html += '<input type="checkbox" class="import-rule-file" data-path="' + f.absolutePath + '" data-group-id="' + group.id + '"' + (f.alreadyImported ? '' : ' checked') + ' onchange="window.updateGroupCheckboxState(\'' + group.id + '\')" style="margin-top:3px; width:14px; height:14px; accent-color:var(--green)">';
|
|
2916
|
+
html += '<div style="flex:1">';
|
|
2917
|
+
html += '<div style="font-size:13px;font-weight:500;color:var(--text)">' + shortFilename + badge + '</div>';
|
|
2918
|
+
html += '<div style="font-size:11px;color:var(--muted)">' + f.label + ' · ' + (f.size / 1024).toFixed(1) + 'KB</div>';
|
|
2919
|
+
html += '<div style="font-size:11px;color:var(--muted);margin-top:4px;padding:6px 10px;background:var(--panel);border:1px solid var(--border);border-radius:4px;font-family:monospace;white-space:pre-wrap;word-break:break-all;max-height:80px;overflow-y:auto">' + f.preview.slice(0, 150).replace(/</g, '<') + '…</div>';
|
|
2920
|
+
html += '</div>';
|
|
2921
|
+
html += '</label>';
|
|
2922
|
+
});
|
|
2923
|
+
|
|
2924
|
+
html += '</div>';
|
|
2925
|
+
html += '</details>';
|
|
2926
|
+
});
|
|
2927
|
+
|
|
2928
|
+
html += '<button class="btn btn-primary" style="font-size:12px;padding:6px 14px;margin-top:6px" onclick="importSelectedRules()">📥 Import Selected Rules & Skills</button>';
|
|
2929
|
+
|
|
2930
|
+
listEl.innerHTML = html;
|
|
2931
|
+
|
|
2932
|
+
// Initialize indeterminate state of parent checkboxes
|
|
2933
|
+
groupOrder.forEach(function(groupId) {
|
|
2934
|
+
window.updateGroupCheckboxState(groupId);
|
|
2935
|
+
});
|
|
2936
|
+
})
|
|
2937
|
+
.catch(function(e) { resultEl.textContent = '❌ ' + e.message; });
|
|
2938
|
+
};
|
|
2939
|
+
|
|
2940
|
+
// Toggle group checkboxes helper
|
|
2941
|
+
window.toggleGroupCheck = function(cb, groupId) {
|
|
2942
|
+
var checked = cb.checked;
|
|
2943
|
+
var items = document.querySelectorAll('.import-rule-file[data-group-id="' + groupId + '"]');
|
|
2944
|
+
items.forEach(function(item) {
|
|
2945
|
+
item.checked = checked;
|
|
2946
|
+
});
|
|
2947
|
+
};
|
|
2948
|
+
|
|
2949
|
+
// Update group parent checkbox state helper (supporting intermediate state)
|
|
2950
|
+
window.updateGroupCheckboxState = function(groupId) {
|
|
2951
|
+
var groupCb = document.querySelector('.import-rule-group-checkbox[data-group-id="' + groupId + '"]');
|
|
2952
|
+
if (!groupCb) return;
|
|
2953
|
+
var items = document.querySelectorAll('.import-rule-file[data-group-id="' + groupId + '"]');
|
|
2954
|
+
var allChecked = true;
|
|
2955
|
+
var noneChecked = true;
|
|
2956
|
+
items.forEach(function(item) {
|
|
2957
|
+
if (item.checked) noneChecked = false;
|
|
2958
|
+
else allChecked = false;
|
|
2959
|
+
});
|
|
2960
|
+
groupCb.checked = allChecked;
|
|
2961
|
+
groupCb.indeterminate = (!allChecked && !noneChecked);
|
|
2962
|
+
};
|
|
2963
|
+
|
|
2964
|
+
// Import selected rule files into the vault
|
|
2965
|
+
window.importSelectedRules = function() {
|
|
2966
|
+
var resultEl = document.getElementById('import-rules-result');
|
|
2967
|
+
var selected = [];
|
|
2968
|
+
document.querySelectorAll('.import-rule-file:checked').forEach(function(cb) { selected.push(cb.dataset.path); });
|
|
2969
|
+
if (!selected.length) { resultEl.textContent = 'No files selected.'; return; }
|
|
2970
|
+
resultEl.textContent = 'Importing ' + selected.length + ' file(s)...';
|
|
2971
|
+
fetch('/api/import/rules', {
|
|
2972
|
+
method: 'POST',
|
|
2973
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2974
|
+
body: JSON.stringify({ files: selected })
|
|
2975
|
+
})
|
|
2976
|
+
.then(function(r) { return r.json(); })
|
|
2977
|
+
.then(function(data) {
|
|
2978
|
+
var lines = [];
|
|
2979
|
+
(data.imported || []).forEach(function(f) { lines.push('✅ Imported: ' + f.title); });
|
|
2980
|
+
(data.skipped || []).forEach(function(f) { lines.push('⚪ Skipped: ' + (f.filename || f.slug) + ' (' + (f.reason || '') + ')'); });
|
|
2981
|
+
(data.failed || []).forEach(function(f) { lines.push('❌ Failed: ' + (f.filename || f.slug) + ' — ' + (f.error || '')); });
|
|
2982
|
+
resultEl.innerHTML = lines.join('<br>');
|
|
2983
|
+
if ((data.imported || []).length > 0) {
|
|
2984
|
+
resultEl.innerHTML += '<br><span style="color:var(--muted)">💡 Run <strong>Recompile Surface</strong> or call POST /api/vault/compile to update INSTRUCTIONS.md.</span>';
|
|
2985
|
+
}
|
|
2986
|
+
})
|
|
2987
|
+
.catch(function(e) { resultEl.textContent = '❌ ' + e.message; });
|
|
2988
|
+
};
|
|
2989
|
+
|
|
2990
|
+
// Connect all selected IDEs
|
|
2991
|
+
window.connectSelectedIDEs = function() {
|
|
2992
|
+
var selected = [];
|
|
2993
|
+
document.querySelectorAll('#ide-grid input[type=checkbox]:checked').forEach(function(cb) {
|
|
2994
|
+
selected.push(cb.value);
|
|
2995
|
+
});
|
|
2996
|
+
var installRelay = document.getElementById('install-relay').checked;
|
|
2997
|
+
var installObsidian = document.getElementById('install-obsidian') && document.getElementById('install-obsidian').checked;
|
|
2998
|
+
|
|
2999
|
+
var resultsCard = document.getElementById('connect-results');
|
|
3000
|
+
var logEl = document.getElementById('connect-log');
|
|
3001
|
+
resultsCard.style.display = '';
|
|
3002
|
+
var extras = [];
|
|
3003
|
+
if (installRelay) extras.push('relay');
|
|
3004
|
+
if (installObsidian) extras.push('Obsidian sync');
|
|
3005
|
+
logEl.innerHTML = '<div class="connect-result-line info">Connecting ' + (selected.length || 'no') + ' tools' + (extras.length ? ' + ' + extras.join(' + ') : '') + '...</div>';
|
|
3006
|
+
|
|
3007
|
+
document.getElementById('btn-connect-ides').disabled = true;
|
|
3008
|
+
document.getElementById('btn-connect-ides').textContent = 'Connecting...';
|
|
3009
|
+
|
|
3010
|
+
fetch('/api/connect-ides', {
|
|
3011
|
+
method: 'POST',
|
|
3012
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3013
|
+
body: JSON.stringify({ ides: selected, installRelay: installRelay, installObsidian: installObsidian, brainUrl: W.apiUrl, token: W.pat })
|
|
3014
|
+
})
|
|
3015
|
+
.then(function(r) { return r.json(); })
|
|
3016
|
+
.then(function(data) {
|
|
3017
|
+
logEl.innerHTML = '';
|
|
3018
|
+
(data.results || []).forEach(function(r) {
|
|
3019
|
+
var cls = r.ok ? 'ok' : (r.skipped ? 'info' : 'err');
|
|
3020
|
+
var icon = r.ok ? '✅' : (r.skipped ? '⚪' : '❌');
|
|
3021
|
+
logEl.innerHTML += '<div class="connect-result-line ' + cls + '">' + icon + ' ' + r.label + ': ' + r.message + '</div>';
|
|
3022
|
+
});
|
|
3023
|
+
if (data.obsidianResult) {
|
|
3024
|
+
var oc = data.obsidianResult.ok ? 'ok' : 'err';
|
|
3025
|
+
logEl.innerHTML += '<div class="connect-result-line ' + oc + '">' + (data.obsidianResult.ok ? '✅' : '❌') + ' Obsidian: ' + data.obsidianResult.message + '</div>';
|
|
3026
|
+
}
|
|
3027
|
+
if (data.relayResult) {
|
|
3028
|
+
var rc = data.relayResult.ok ? 'ok' : 'err';
|
|
3029
|
+
logEl.innerHTML += '<div class="connect-result-line ' + rc + '">' + (data.relayResult.ok ? '✅' : '❌') + ' Relay: ' + data.relayResult.message + '</div>';
|
|
3030
|
+
}
|
|
3031
|
+
logEl.innerHTML += '<div class="connect-result-line info" style="margin-top:8px">🔄 Memory will start compiling automatically. Ready!</div>';
|
|
3032
|
+
document.getElementById('btn-connect-ides').textContent = 'Done — Continue →';
|
|
3033
|
+
document.getElementById('btn-connect-ides').disabled = false;
|
|
3034
|
+
document.getElementById('btn-connect-ides').onclick = function() { goPhase(5); };
|
|
3035
|
+
})
|
|
3036
|
+
.catch(function(e) {
|
|
3037
|
+
logEl.innerHTML += '<div class="connect-result-line err">❌ Error: ' + e.message + '</div>';
|
|
3038
|
+
document.getElementById('btn-connect-ides').textContent = 'Retry';
|
|
3039
|
+
document.getElementById('btn-connect-ides').disabled = false;
|
|
3040
|
+
});
|
|
3041
|
+
};
|
|
3042
|
+
|
|
3043
|
+
// Install CLI agent globally via POST /api/install-cli
|
|
3044
|
+
window.installCliAgent = function(agent, btn) {
|
|
3045
|
+
var originalText = btn.textContent;
|
|
3046
|
+
btn.disabled = true;
|
|
3047
|
+
btn.textContent = 'Installing...';
|
|
3048
|
+
|
|
3049
|
+
var resultEl = document.getElementById('cli-install-result');
|
|
3050
|
+
resultEl.style.display = 'none';
|
|
3051
|
+
resultEl.textContent = '';
|
|
3052
|
+
|
|
3053
|
+
fetch('/api/install-cli', {
|
|
3054
|
+
method: 'POST',
|
|
3055
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3056
|
+
body: JSON.stringify({ agent: agent })
|
|
3057
|
+
})
|
|
3058
|
+
.then(function(r) { return r.json(); })
|
|
3059
|
+
.then(function(data) {
|
|
3060
|
+
btn.disabled = false;
|
|
3061
|
+
btn.textContent = originalText;
|
|
3062
|
+
resultEl.style.display = '';
|
|
3063
|
+
if (data.ok) {
|
|
3064
|
+
btn.textContent = 'Installed ✓';
|
|
3065
|
+
btn.style.borderColor = 'var(--green)';
|
|
3066
|
+
btn.style.color = 'var(--green)';
|
|
3067
|
+
resultEl.style.color = 'var(--green)';
|
|
3068
|
+
resultEl.textContent = '✅ Success! CLI agent successfully installed on target machine.\n' + (data.output || '');
|
|
3069
|
+
} else {
|
|
3070
|
+
resultEl.style.color = 'var(--red)';
|
|
3071
|
+
resultEl.textContent = '❌ Installation failed: ' + (data.error || 'Unknown error');
|
|
3072
|
+
}
|
|
3073
|
+
})
|
|
3074
|
+
.catch(function(err) {
|
|
3075
|
+
btn.disabled = false;
|
|
3076
|
+
btn.textContent = originalText;
|
|
3077
|
+
resultEl.style.display = '';
|
|
3078
|
+
resultEl.style.color = 'var(--red)';
|
|
3079
|
+
resultEl.textContent = '❌ Request failed: ' + err.message;
|
|
3080
|
+
});
|
|
3081
|
+
};
|
|
3082
|
+
|
|
3083
|
+
window.runCliAuth = function(agent) {
|
|
3084
|
+
var resultEl = document.getElementById('cli-install-result');
|
|
3085
|
+
resultEl.style.display = '';
|
|
3086
|
+
resultEl.style.color = 'var(--blue)';
|
|
3087
|
+
if (agent === 'claude') {
|
|
3088
|
+
resultEl.textContent = '🔑 Claude Code Authorization:\nTo complete authorization on the Mac Mini, open a terminal on the host machine and run:\n claude login';
|
|
3089
|
+
} else if (agent === 'antigravity') {
|
|
3090
|
+
resultEl.textContent = '🔑 Google Antigravity Authorization:\nTo authorize Antigravity CLI on the Mac Mini, run:\n antigravity login';
|
|
3091
|
+
} else if (agent === 'codex') {
|
|
3092
|
+
resultEl.textContent = '🔑 Codex Authorization:\nConfigure your OpenAI credentials by running:\n codex login';
|
|
3093
|
+
}
|
|
3094
|
+
};
|
|
3095
|
+
|
|
3096
|
+
|
|
3097
|
+
// ── Phase 5: API Reference ──
|
|
3098
|
+
function populateApiRef() {
|
|
3099
|
+
var domain = W.domain || 'your-domain.example';
|
|
3100
|
+
var baseUrl = domain === 'localhost' ? 'http://localhost:3000' : ('https://' + domain);
|
|
3101
|
+
var pat = W.pat || '<YOUR-PAT>';
|
|
3102
|
+
|
|
3103
|
+
setText('example-curl',
|
|
3104
|
+
'curl -s ' + baseUrl + '/v1/chat/completions \\\\\\n' +
|
|
3105
|
+
' -H "Authorization: Bearer ' + pat + '" \\\\\\n' +
|
|
3106
|
+
' -H "Content-Type: application/json" \\\\\\n' +
|
|
3107
|
+
' -d \'{"model":"total-recall","messages":[{"role":"user","content":"hello"}],"stream":false}\'');
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3110
|
+
// ── Phase 6: Done ──
|
|
3111
|
+
function populateDone() {
|
|
3112
|
+
var domain = W.domain || 'your-domain.example';
|
|
3113
|
+
var baseUrl = domain === 'localhost' ? 'http://localhost:3000' : ('https://' + domain);
|
|
3114
|
+
|
|
3115
|
+
var apiUrl = W.apiUrl || baseUrl + '/v1/chat/completions';
|
|
3116
|
+
var dashUrl = W.dashUrl || baseUrl + '/';
|
|
3117
|
+
var healthUrl = W.healthUrl || baseUrl + '/health';
|
|
3118
|
+
|
|
3119
|
+
document.getElementById('final-api-url').textContent = apiUrl;
|
|
3120
|
+
document.getElementById('final-dash-url').textContent = dashUrl;
|
|
3121
|
+
document.getElementById('final-health-url').textContent = healthUrl;
|
|
3122
|
+
document.getElementById('final-api-link').href = apiUrl;
|
|
3123
|
+
document.getElementById('final-dash-link').href = dashUrl;
|
|
3124
|
+
document.getElementById('final-health-link').href = healthUrl;
|
|
3125
|
+
|
|
3126
|
+
// Detect SSH Fallback URL to show warning notice
|
|
3127
|
+
var isSshUrl = apiUrl.includes('vast.ai') || apiUrl.includes(':10120') || (W.vastSshHost && apiUrl.includes(W.vastSshHost));
|
|
3128
|
+
var warningCard = document.getElementById('ssh-fallback-warning');
|
|
3129
|
+
if (warningCard) {
|
|
3130
|
+
if (isSshUrl) {
|
|
3131
|
+
warningCard.style.display = 'block';
|
|
3132
|
+
var hostSpan = document.getElementById('warning-ssh-host');
|
|
3133
|
+
if (hostSpan) {
|
|
3134
|
+
hostSpan.textContent = W.vastSshHost || domain;
|
|
3135
|
+
}
|
|
3136
|
+
} else {
|
|
3137
|
+
warningCard.style.display = 'none';
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
// Pre-populate manual override fields with current active values
|
|
3142
|
+
var overrideUrlInput = document.getElementById('override-base-url');
|
|
3143
|
+
var overridePatInput = document.getElementById('override-pat-key');
|
|
3144
|
+
if (overrideUrlInput && !overrideUrlInput.value) {
|
|
3145
|
+
overrideUrlInput.value = getBaseUrl();
|
|
3146
|
+
}
|
|
3147
|
+
if (overridePatInput && !overridePatInput.value) {
|
|
3148
|
+
overridePatInput.value = W.pat;
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3151
|
+
var donePassInput = document.getElementById('done-dashboard-password');
|
|
3152
|
+
if (donePassInput) {
|
|
3153
|
+
donePassInput.value = W.dashboardPassword || 'totalrecall';
|
|
3154
|
+
}
|
|
3155
|
+
|
|
3156
|
+
checkHealth();
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
window.checkHealth = function () {
|
|
3160
|
+
var dot = document.getElementById('health-dot');
|
|
3161
|
+
var status = document.getElementById('health-status');
|
|
3162
|
+
var healthUrl = W.healthUrl || ('https://' + (W.domain || 'localhost') + '/health');
|
|
3163
|
+
dot.className = 'health-dot';
|
|
3164
|
+
status.textContent = 'Checking…';
|
|
3165
|
+
var headers = {};
|
|
3166
|
+
if (W.pat) {
|
|
3167
|
+
headers['Authorization'] = 'Bearer ' + W.pat;
|
|
3168
|
+
}
|
|
3169
|
+
fetch(healthUrl, { headers: headers, signal: AbortSignal.timeout(5000) })
|
|
3170
|
+
.then(function (r) {
|
|
3171
|
+
if (r.ok) {
|
|
3172
|
+
dot.classList.add('ok');
|
|
3173
|
+
status.textContent = 'Brain is healthy ✓';
|
|
3174
|
+
} else {
|
|
3175
|
+
throw new Error('HTTP ' + r.status);
|
|
3176
|
+
}
|
|
3177
|
+
})
|
|
3178
|
+
.catch(function (e) {
|
|
3179
|
+
dot.classList.add('err');
|
|
3180
|
+
status.textContent = 'Health check failed: ' + e.message;
|
|
3181
|
+
});
|
|
3182
|
+
};
|
|
3183
|
+
|
|
3184
|
+
window.saveManualOverride = function () {
|
|
3185
|
+
var rawUrl = document.getElementById('override-base-url').value.trim();
|
|
3186
|
+
var pat = document.getElementById('override-pat-key').value.trim();
|
|
3187
|
+
var resEl = document.getElementById('override-save-result');
|
|
3188
|
+
if (!resEl) return;
|
|
3189
|
+
|
|
3190
|
+
if (!rawUrl) {
|
|
3191
|
+
resEl.textContent = '❌ Base URL is required.';
|
|
3192
|
+
resEl.className = 'notice error';
|
|
3193
|
+
resEl.style.display = '';
|
|
3194
|
+
return;
|
|
3195
|
+
}
|
|
3196
|
+
|
|
3197
|
+
// Ensure absolute protocol
|
|
3198
|
+
var baseUrl = rawUrl;
|
|
3199
|
+
if (!/^https?:\/\//i.test(baseUrl)) {
|
|
3200
|
+
baseUrl = 'http://' + baseUrl;
|
|
3201
|
+
}
|
|
3202
|
+
// Strip trailing slashes
|
|
3203
|
+
baseUrl = baseUrl.replace(/\/+$/, '');
|
|
3204
|
+
|
|
3205
|
+
var apiUrl = baseUrl + '/v1/chat/completions';
|
|
3206
|
+
var dashUrl = baseUrl + '/';
|
|
3207
|
+
var healthUrl = baseUrl + '/health';
|
|
3208
|
+
|
|
3209
|
+
saveWizardState({
|
|
3210
|
+
apiUrl: apiUrl,
|
|
3211
|
+
dashUrl: dashUrl,
|
|
3212
|
+
healthUrl: healthUrl,
|
|
3213
|
+
pat: pat
|
|
3214
|
+
});
|
|
3215
|
+
|
|
3216
|
+
// Re-check Final phase URLs
|
|
3217
|
+
populateDone();
|
|
3218
|
+
|
|
3219
|
+
resEl.textContent = '⚡ Override saved successfully! Re-checking health...';
|
|
3220
|
+
resEl.className = 'notice info';
|
|
3221
|
+
resEl.style.display = '';
|
|
3222
|
+
|
|
3223
|
+
// Check health with a callback or timeout to update success/error
|
|
3224
|
+
setTimeout(function() {
|
|
3225
|
+
var headers = {};
|
|
3226
|
+
if (pat) {
|
|
3227
|
+
headers['Authorization'] = 'Bearer ' + pat;
|
|
3228
|
+
}
|
|
3229
|
+
fetch(healthUrl, { headers: headers, signal: AbortSignal.timeout(5000) })
|
|
3230
|
+
.then(function (r) {
|
|
3231
|
+
if (r.ok) {
|
|
3232
|
+
resEl.textContent = '🎉 Connected successfully! Brain is healthy ✓';
|
|
3233
|
+
resEl.className = 'notice ok';
|
|
3234
|
+
} else {
|
|
3235
|
+
resEl.textContent = '⚠️ Saved, but health check returned: HTTP ' + r.status;
|
|
3236
|
+
resEl.className = 'notice warn';
|
|
3237
|
+
}
|
|
3238
|
+
})
|
|
3239
|
+
.catch(function (e) {
|
|
3240
|
+
resEl.textContent = '❌ Saved, but health check failed: ' + e.message;
|
|
3241
|
+
resEl.className = 'notice error';
|
|
3242
|
+
});
|
|
3243
|
+
}, 800);
|
|
3244
|
+
};
|
|
3245
|
+
|
|
3246
|
+
window.resetWizardSetup = function () {
|
|
3247
|
+
if (!confirm('Are you absolutely sure you want to reset the setup wizard? This will clear all stored wizard configurations and return to Phase 0.')) {
|
|
3248
|
+
return;
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
fetch('/api/reset-wizard-config', { method: 'POST' })
|
|
3252
|
+
.then(function (r) { return r.json(); })
|
|
3253
|
+
.then(function (data) {
|
|
3254
|
+
if (data.ok) {
|
|
3255
|
+
// Clear all keys from safeStorage (both localStorage and sessionStorage)
|
|
3256
|
+
try {
|
|
3257
|
+
var keysToClear = [];
|
|
3258
|
+
for (var i = 0; i < localStorage.length; i++) {
|
|
3259
|
+
var key = localStorage.key(i);
|
|
3260
|
+
if (key && key.indexOf('tr_wizard_') === 0) {
|
|
3261
|
+
keysToClear.push(key);
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
keysToClear.forEach(function (k) { localStorage.removeItem(k); });
|
|
3265
|
+
} catch(e) {}
|
|
3266
|
+
try {
|
|
3267
|
+
var keysToClearSession = [];
|
|
3268
|
+
for (var i = 0; i < sessionStorage.length; i++) {
|
|
3269
|
+
var key = sessionStorage.key(i);
|
|
3270
|
+
if (key && key.indexOf('tr_wizard_') === 0) {
|
|
3271
|
+
keysToClearSession.push(key);
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
keysToClearSession.forEach(function (k) { sessionStorage.removeItem(k); });
|
|
3275
|
+
} catch(e) {}
|
|
3276
|
+
|
|
3277
|
+
// Reload page to start completely fresh
|
|
3278
|
+
window.location.reload();
|
|
3279
|
+
} else {
|
|
3280
|
+
alert('Error resetting wizard: ' + (data.error || 'Unknown error'));
|
|
3281
|
+
}
|
|
3282
|
+
})
|
|
3283
|
+
.catch(function (e) {
|
|
3284
|
+
alert('Failed to contact server to reset configuration: ' + e.message);
|
|
3285
|
+
});
|
|
3286
|
+
};
|
|
3287
|
+
|
|
3288
|
+
// ── Phase 7: Settings ──
|
|
3289
|
+
function populateSettings() {
|
|
3290
|
+
// Load current key values (masked) and usage stats
|
|
3291
|
+
fetch('/api/get-search-config')
|
|
3292
|
+
.then(function(r) { return r.json(); })
|
|
3293
|
+
.then(function(data) {
|
|
3294
|
+
// Show placeholder if key exists, empty if not — never show actual key
|
|
3295
|
+
if (data.hasTavily) document.getElementById('s-tavily-key').placeholder = 'tvly-\u2026\u2026 (key set — enter new value to change)';
|
|
3296
|
+
if (data.hasBrave) document.getElementById('s-brave-key').placeholder = 'BSA\u2026\u2026 (key set — enter new value to change)';
|
|
3297
|
+
if (data.hasExa) document.getElementById('s-exa-key').placeholder = '\u2026\u2026 (key set — enter new value to change)';
|
|
3298
|
+
if (data.hasSerper) document.getElementById('s-serper-key').placeholder = '\u2026\u2026 (key set — enter new value to change)';
|
|
3299
|
+
document.getElementById('s-daily-limit').value = data.dailyLimit ?? 50;
|
|
3300
|
+
})
|
|
3301
|
+
.catch(function() { /* config not saved yet — defaults stay */ });
|
|
3302
|
+
|
|
3303
|
+
fetch('/api/get-search-usage')
|
|
3304
|
+
.then(function(r) { return r.json(); })
|
|
3305
|
+
.then(function(data) {
|
|
3306
|
+
document.getElementById('settings-usage-count').textContent = data.today;
|
|
3307
|
+
document.getElementById('settings-usage-limit').textContent = data.limit === 'unlimited' ? '\u221e' : data.limit;
|
|
3308
|
+
document.getElementById('settings-usage-remaining').textContent = data.remaining === 'unlimited' ? 'unlimited' : data.remaining;
|
|
3309
|
+
})
|
|
3310
|
+
.catch(function() {});
|
|
3311
|
+
}
|
|
3312
|
+
|
|
3313
|
+
|
|
3314
|
+
|
|
3315
|
+
window.saveSettings = function() {
|
|
3316
|
+
var tavilyKey = (document.getElementById('s-tavily-key').value || '').trim();
|
|
3317
|
+
var braveKey = (document.getElementById('s-brave-key').value || '').trim();
|
|
3318
|
+
var exaKey = (document.getElementById('s-exa-key').value || '').trim();
|
|
3319
|
+
var serperKey = (document.getElementById('s-serper-key').value || '').trim();
|
|
3320
|
+
var dailyLimit = parseInt(document.getElementById('s-daily-limit').value, 10) || 0;
|
|
3321
|
+
var resultEl = document.getElementById('settings-save-result');
|
|
3322
|
+
|
|
3323
|
+
resultEl.style.display = '';
|
|
3324
|
+
resultEl.className = 'notice';
|
|
3325
|
+
resultEl.textContent = 'Saving\u2026';
|
|
3326
|
+
|
|
3327
|
+
fetch('/api/save-search-config', {
|
|
3328
|
+
method: 'POST',
|
|
3329
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3330
|
+
body: JSON.stringify({ tavilyKey: tavilyKey, braveKey: braveKey, exaKey: exaKey, serperKey: serperKey, dailyLimit: dailyLimit, mergeExisting: true })
|
|
3331
|
+
})
|
|
3332
|
+
.then(function(r) { return r.json(); })
|
|
3333
|
+
.then(function(data) {
|
|
3334
|
+
if (data.ok) {
|
|
3335
|
+
resultEl.className = 'notice success';
|
|
3336
|
+
var src = data.primarySource;
|
|
3337
|
+
resultEl.textContent = '\u2705 Saved. ' + (src === 'DuckDuckGo + Wikipedia (free)' ? 'No paid key set \u2014 using DuckDuckGo + Wikipedia.' : 'Primary source: ' + src + '.');
|
|
3338
|
+
// Clear fields and re-load placeholders
|
|
3339
|
+
['s-tavily-key','s-brave-key','s-exa-key','s-serper-key'].forEach(function(id) {
|
|
3340
|
+
document.getElementById(id).value = '';
|
|
3341
|
+
});
|
|
3342
|
+
populateSettings();
|
|
3343
|
+
} else {
|
|
3344
|
+
resultEl.className = 'notice warn';
|
|
3345
|
+
resultEl.textContent = '\u26a0\ufe0f ' + (data.error || 'Could not save.');
|
|
3346
|
+
}
|
|
3347
|
+
})
|
|
3348
|
+
.catch(function() {
|
|
3349
|
+
resultEl.className = 'notice warn';
|
|
3350
|
+
resultEl.textContent = '\u26a0\ufe0f Could not reach server.';
|
|
3351
|
+
});
|
|
3352
|
+
};
|
|
3353
|
+
|
|
3354
|
+
window.resetRemotePassword = function() {
|
|
3355
|
+
var newPass = (document.getElementById('settings-new-password').value || '').trim();
|
|
3356
|
+
var resultEl = document.getElementById('settings-password-result');
|
|
3357
|
+
if (!newPass || newPass.length < 8) {
|
|
3358
|
+
alert('Password must be at least 8 characters long.');
|
|
3359
|
+
return;
|
|
3360
|
+
}
|
|
3361
|
+
|
|
3362
|
+
resultEl.style.display = '';
|
|
3363
|
+
resultEl.className = 'notice';
|
|
3364
|
+
resultEl.textContent = 'Updating remote password…';
|
|
3365
|
+
|
|
3366
|
+
fetch('/api/reset-remote-password', {
|
|
3367
|
+
method: 'POST',
|
|
3368
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3369
|
+
body: JSON.stringify({ password: newPass })
|
|
3370
|
+
})
|
|
3371
|
+
.then(function(r) { return r.json(); })
|
|
3372
|
+
.then(function(data) {
|
|
3373
|
+
if (data.ok) {
|
|
3374
|
+
resultEl.className = 'notice success';
|
|
3375
|
+
resultEl.textContent = '✅ Success! Remote dashboard password reset and configured. You will be forced to change it upon your next login.';
|
|
3376
|
+
document.getElementById('settings-new-password').value = '';
|
|
3377
|
+
// Also update local state
|
|
3378
|
+
safeStorage.setItem('tr_wizard_cfg-dashboard-password', newPass);
|
|
3379
|
+
W.dashboardPassword = newPass;
|
|
3380
|
+
// Also update Phase 1 password field
|
|
3381
|
+
var inputEl = document.getElementById('cfg-dashboard-password');
|
|
3382
|
+
if (inputEl) {
|
|
3383
|
+
inputEl.value = newPass;
|
|
3384
|
+
}
|
|
3385
|
+
} else {
|
|
3386
|
+
resultEl.className = 'notice warn';
|
|
3387
|
+
resultEl.textContent = '⚠️ ' + (data.error || 'Could not update password.');
|
|
3388
|
+
}
|
|
3389
|
+
})
|
|
3390
|
+
.catch(function(e) {
|
|
3391
|
+
resultEl.className = 'notice warn';
|
|
3392
|
+
resultEl.textContent = '⚠️ Connection error. Is the setup server running?';
|
|
3393
|
+
});
|
|
3394
|
+
};
|
|
3395
|
+
|
|
3396
|
+
window.toggleShow = function(id) {
|
|
3397
|
+
var el = document.getElementById(id);
|
|
3398
|
+
var btn = el.nextElementSibling;
|
|
3399
|
+
if (el.type === 'password') {
|
|
3400
|
+
el.type = 'text';
|
|
3401
|
+
btn.textContent = 'Hide';
|
|
3402
|
+
} else {
|
|
3403
|
+
el.type = 'password';
|
|
3404
|
+
btn.textContent = 'Show';
|
|
3405
|
+
}
|
|
3406
|
+
};
|
|
3407
|
+
|
|
3408
|
+
// ── Tabs ──
|
|
3409
|
+
window.openTab = function (id) {
|
|
3410
|
+
document.querySelectorAll('.tab-pane').forEach(function (p) { p.classList.remove('active'); });
|
|
3411
|
+
document.querySelectorAll('.tab-btn').forEach(function (b) { b.classList.remove('active'); });
|
|
3412
|
+
document.getElementById(id).classList.add('active');
|
|
3413
|
+
event.target.classList.add('active');
|
|
3414
|
+
};
|
|
3415
|
+
|
|
3416
|
+
// ── Code helpers ──
|
|
3417
|
+
function setText(id, text) {
|
|
3418
|
+
var el = document.getElementById(id);
|
|
3419
|
+
if (!el) return;
|
|
3420
|
+
var btn = el.querySelector('.copy-btn');
|
|
3421
|
+
el.textContent = text;
|
|
3422
|
+
if (btn) el.appendChild(btn);
|
|
3423
|
+
}
|
|
3424
|
+
function setCode(id, text) { setText(id, text); }
|
|
3425
|
+
|
|
3426
|
+
window.copyCode = function (btn) {
|
|
3427
|
+
var block = btn.closest('.code-block');
|
|
3428
|
+
var text = block.childNodes[0].textContent || block.textContent;
|
|
3429
|
+
navigator.clipboard.writeText(text.trim()).then(function () {
|
|
3430
|
+
var orig = btn.textContent;
|
|
3431
|
+
btn.textContent = '✓ Copied!';
|
|
3432
|
+
btn.classList.add('copied');
|
|
3433
|
+
setTimeout(function () {
|
|
3434
|
+
btn.textContent = orig;
|
|
3435
|
+
btn.classList.remove('copied');
|
|
3436
|
+
}, 1500);
|
|
3437
|
+
}).catch(function () {});
|
|
3438
|
+
};
|
|
3439
|
+
|
|
3440
|
+
// ── Persist & Restore Inputs ──
|
|
3441
|
+
function initPersistence() {
|
|
3442
|
+
var textInputs = [
|
|
3443
|
+
'cfg-domain',
|
|
3444
|
+
'cfg-cloudflare-token',
|
|
3445
|
+
'cfg-localnet-host',
|
|
3446
|
+
'cfg-localnet-user',
|
|
3447
|
+
'cfg-vastai-key',
|
|
3448
|
+
'cfg-github-token',
|
|
3449
|
+
'cfg-cloud-token',
|
|
3450
|
+
'cfg-cloud-provider',
|
|
3451
|
+
'cfg-cloud-region'
|
|
3452
|
+
];
|
|
3453
|
+
var selectInputs = [
|
|
3454
|
+
];
|
|
3455
|
+
|
|
3456
|
+
// 1. Initial restore from browser safeStorage (extremely fast)
|
|
3457
|
+
var initialKey = null;
|
|
3458
|
+
textInputs.forEach(function (id) {
|
|
3459
|
+
var el = document.getElementById(id);
|
|
3460
|
+
if (!el) return;
|
|
3461
|
+
var saved = safeStorage.getItem('tr_wizard_' + id);
|
|
3462
|
+
if (saved !== null) {
|
|
3463
|
+
el.value = saved;
|
|
3464
|
+
if (id === 'cfg-vastai-key') initialKey = saved;
|
|
3465
|
+
if (id === 'cfg-dashboard-password') W.dashboardPassword = saved;
|
|
3466
|
+
}
|
|
3467
|
+
});
|
|
3468
|
+
selectInputs.forEach(function (id) {
|
|
3469
|
+
var el = document.getElementById(id);
|
|
3470
|
+
if (!el) return;
|
|
3471
|
+
var saved = safeStorage.getItem('tr_wizard_' + id);
|
|
3472
|
+
if (saved !== null) {
|
|
3473
|
+
el.value = saved;
|
|
3474
|
+
}
|
|
3475
|
+
});
|
|
3476
|
+
|
|
3477
|
+
if (initialKey) {
|
|
3478
|
+
setTimeout(function () {
|
|
3479
|
+
window.scanVastAIInstances();
|
|
3480
|
+
}, 200);
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3483
|
+
var savedTarget = safeStorage.getItem('tr_wizard_deploy_target');
|
|
3484
|
+
if (savedTarget) {
|
|
3485
|
+
var targetRadio = document.querySelector('#deploy-target input[value="' + savedTarget + '"]');
|
|
3486
|
+
if (targetRadio) {
|
|
3487
|
+
targetRadio.checked = true;
|
|
3488
|
+
targetRadio.dispatchEvent(new Event('change'));
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
|
|
3492
|
+
var savedHttps = safeStorage.getItem('tr_wizard_https_method');
|
|
3493
|
+
if (savedHttps) {
|
|
3494
|
+
var httpsRadio = document.querySelector('input[name="https-method"][value="' + savedHttps + '"]');
|
|
3495
|
+
if (httpsRadio) {
|
|
3496
|
+
httpsRadio.checked = true;
|
|
3497
|
+
httpsRadio.dispatchEvent(new Event('change'));
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3501
|
+
// Restore W state from browser storage
|
|
3502
|
+
var stateKeys = ['phase', 'domain', 'pat', 'apiUrl', 'dashUrl', 'healthUrl', 'deployTarget'];
|
|
3503
|
+
stateKeys.forEach(function (k) {
|
|
3504
|
+
var saved = safeStorage.getItem('tr_wizard_state_' + k);
|
|
3505
|
+
if (saved !== null && saved !== undefined) {
|
|
3506
|
+
if (k === 'phase') {
|
|
3507
|
+
W.phase = parseInt(saved, 10) || 0;
|
|
3508
|
+
} else {
|
|
3509
|
+
W[k] = saved;
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3512
|
+
});
|
|
3513
|
+
|
|
3514
|
+
syncUIFromWizardState();
|
|
3515
|
+
|
|
3516
|
+
if (W.phase > 0) {
|
|
3517
|
+
goPhase(W.phase);
|
|
3518
|
+
}
|
|
3519
|
+
|
|
3520
|
+
// 2. Fetch persistent configuration from server disk to restore everything securely (e.g. for incognito/private tabs)
|
|
3521
|
+
fetch('/api/get-wizard-config')
|
|
3522
|
+
.then(function (r) { return r.json(); })
|
|
3523
|
+
.then(function (data) {
|
|
3524
|
+
var serverKey = null;
|
|
3525
|
+
textInputs.forEach(function (id) {
|
|
3526
|
+
var el = document.getElementById(id);
|
|
3527
|
+
if (el && data[id] !== undefined && data[id] !== null) {
|
|
3528
|
+
el.value = data[id];
|
|
3529
|
+
if (id === 'cfg-vastai-key') serverKey = data[id];
|
|
3530
|
+
if (id === 'cfg-dashboard-password') W.dashboardPassword = data[id];
|
|
3531
|
+
// Update browser storage to stay in sync
|
|
3532
|
+
safeStorage.setItem('tr_wizard_' + id, data[id]);
|
|
3533
|
+
}
|
|
3534
|
+
});
|
|
3535
|
+
|
|
3536
|
+
if (serverKey) {
|
|
3537
|
+
setTimeout(function () {
|
|
3538
|
+
window.scanVastAIInstances();
|
|
3539
|
+
}, 200);
|
|
3540
|
+
}
|
|
3541
|
+
|
|
3542
|
+
if (data.deployTarget) {
|
|
3543
|
+
var targetRadio = document.querySelector('#deploy-target input[value="' + data.deployTarget + '"]');
|
|
3544
|
+
if (targetRadio) {
|
|
3545
|
+
targetRadio.checked = true;
|
|
3546
|
+
targetRadio.dispatchEvent(new Event('change'));
|
|
3547
|
+
safeStorage.setItem('tr_wizard_deploy_target', data.deployTarget);
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
|
|
3551
|
+
if (data.httpsMethod) {
|
|
3552
|
+
var httpsRadio = document.querySelector('input[name="https-method"][value="' + data.httpsMethod + '"]');
|
|
3553
|
+
if (httpsRadio) {
|
|
3554
|
+
httpsRadio.checked = true;
|
|
3555
|
+
httpsRadio.dispatchEvent(new Event('change'));
|
|
3556
|
+
safeStorage.setItem('tr_wizard_https_method', data.httpsMethod);
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
|
|
3560
|
+
// Restore W values from server config disk and sync/navigate if needed
|
|
3561
|
+
var stateUpdated = false;
|
|
3562
|
+
stateKeys.forEach(function (k) {
|
|
3563
|
+
if (data[k] !== undefined && data[k] !== null) {
|
|
3564
|
+
if (k === 'phase') {
|
|
3565
|
+
var p = parseInt(data[k], 10);
|
|
3566
|
+
if (!isNaN(p) && p !== W.phase) {
|
|
3567
|
+
W.phase = p;
|
|
3568
|
+
stateUpdated = true;
|
|
3569
|
+
}
|
|
3570
|
+
} else if (data[k] !== W[k]) {
|
|
3571
|
+
W[k] = data[k];
|
|
3572
|
+
stateUpdated = true;
|
|
3573
|
+
}
|
|
3574
|
+
safeStorage.setItem('tr_wizard_state_' + k, data[k]);
|
|
3575
|
+
}
|
|
3576
|
+
});
|
|
3577
|
+
|
|
3578
|
+
if (stateUpdated) {
|
|
3579
|
+
syncUIFromWizardState();
|
|
3580
|
+
if (W.phase > 0) {
|
|
3581
|
+
goPhase(W.phase);
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
})
|
|
3585
|
+
.catch(function (e) {
|
|
3586
|
+
console.warn('Could not load wizard config from server, falling back to local storage:', e);
|
|
3587
|
+
});
|
|
3588
|
+
|
|
3589
|
+
// Helper to send individual key/value updates to the server-side disk config
|
|
3590
|
+
function saveToServer(key, val) {
|
|
3591
|
+
var payload = {};
|
|
3592
|
+
payload[key] = val;
|
|
3593
|
+
fetch('/api/save-wizard-config', {
|
|
3594
|
+
method: 'POST',
|
|
3595
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3596
|
+
body: JSON.stringify(payload)
|
|
3597
|
+
}).catch(function (e) { console.error('Error saving wizard config to server:', e); });
|
|
3598
|
+
}
|
|
3599
|
+
|
|
3600
|
+
// Set up listeners for text inputs to save both locally and on server
|
|
3601
|
+
textInputs.forEach(function (id) {
|
|
3602
|
+
var el = document.getElementById(id);
|
|
3603
|
+
if (!el) return;
|
|
3604
|
+
['input', 'change', 'blur', 'keyup', 'paste'].forEach(function (evt) {
|
|
3605
|
+
el.addEventListener(evt, function () {
|
|
3606
|
+
safeStorage.setItem('tr_wizard_' + id, el.value);
|
|
3607
|
+
saveToServer(id, el.value);
|
|
3608
|
+
});
|
|
3609
|
+
});
|
|
3610
|
+
});
|
|
3611
|
+
|
|
3612
|
+
// Set up listeners for radio buttons to save both locally and on server
|
|
3613
|
+
document.querySelectorAll('#deploy-target input[type=radio]').forEach(function (r) {
|
|
3614
|
+
r.addEventListener('change', function () {
|
|
3615
|
+
if (r.checked) {
|
|
3616
|
+
safeStorage.setItem('tr_wizard_deploy_target', r.value);
|
|
3617
|
+
saveToServer('deployTarget', r.value);
|
|
3618
|
+
}
|
|
3619
|
+
});
|
|
3620
|
+
});
|
|
3621
|
+
|
|
3622
|
+
document.querySelectorAll('input[name="https-method"]').forEach(function (r) {
|
|
3623
|
+
r.addEventListener('change', function () {
|
|
3624
|
+
if (r.checked) {
|
|
3625
|
+
safeStorage.setItem('tr_wizard_https_method', r.value);
|
|
3626
|
+
saveToServer('httpsMethod', r.value);
|
|
3627
|
+
}
|
|
3628
|
+
});
|
|
3629
|
+
});
|
|
3630
|
+
|
|
3631
|
+
// Restore persistent bar values and auto-reconnect SSE if installation is in progress
|
|
3632
|
+
setTimeout(function () {
|
|
3633
|
+
var savedPct = safeStorage.getItem('tr_wizard_install_pct');
|
|
3634
|
+
var savedMsg = safeStorage.getItem('tr_wizard_install_status_msg');
|
|
3635
|
+
var savedType = safeStorage.getItem('tr_wizard_install_status_type');
|
|
3636
|
+
if (savedPct !== null && savedMsg !== null && savedType !== null) {
|
|
3637
|
+
updatePersistentBarState(savedType, savedMsg, parseInt(savedPct, 10));
|
|
3638
|
+
}
|
|
3639
|
+
|
|
3640
|
+
syncPersistentBarVisibility();
|
|
3641
|
+
|
|
3642
|
+
// Automatically reconnect SSE if phase is between 2 and 5 OR if safeStorage indicates install is active
|
|
3643
|
+
var isInstallActive = safeStorage.getItem('tr_wizard_install_active') === 'true';
|
|
3644
|
+
if ((W.phase >= 2 && W.phase < 6) || isInstallActive) {
|
|
3645
|
+
startSSE();
|
|
3646
|
+
}
|
|
3647
|
+
}, 50);
|
|
3648
|
+
}
|
|
3649
|
+
|
|
3650
|
+
// 3. Make sidebar navigation items clickable
|
|
3651
|
+
document.querySelectorAll('#step-nav li').forEach(function (li) {
|
|
3652
|
+
var phase = parseInt(li.getAttribute('data-phase'), 10);
|
|
3653
|
+
if (!isNaN(phase)) {
|
|
3654
|
+
li.style.cursor = 'pointer';
|
|
3655
|
+
li.addEventListener('click', function () {
|
|
3656
|
+
goPhase(phase);
|
|
3657
|
+
});
|
|
3658
|
+
}
|
|
3659
|
+
});
|
|
3660
|
+
|
|
3661
|
+
// Run persistence initialization
|
|
3662
|
+
initPersistence();
|
|
3663
|
+
|
|
3664
|
+
}());
|
|
3665
|
+
</script>
|
|
3666
|
+
<!-- Floating Persistent Status Bar -->
|
|
3667
|
+
<div id="persistent-install-bar" class="persistent-install-bar">
|
|
3668
|
+
<div class="pbar-header">
|
|
3669
|
+
<div class="pbar-title">
|
|
3670
|
+
<div class="pbar-spinner"></div>
|
|
3671
|
+
<span id="pbar-title-text">Installing...</span>
|
|
3672
|
+
</div>
|
|
3673
|
+
<a class="pbar-details-link" onclick="goPhase(2); return false;">View Details</a>
|
|
3674
|
+
</div>
|
|
3675
|
+
<div class="pbar-track">
|
|
3676
|
+
<div class="pbar-fill"></div>
|
|
3677
|
+
</div>
|
|
3678
|
+
<div class="pbar-status-text">Connecting to VM...</div>
|
|
3679
|
+
<div class="pbar-latest-log">Waiting for logs...</div>
|
|
3680
|
+
</div>
|
|
3681
|
+
|
|
3682
|
+
</body>
|