total-recall-brain 3.14.4 → 3.14.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/frontend/dist/assets/index-D9-jAOr9.js +502 -0
  2. package/frontend/dist/assets/index-L0eEIoBf.css +1 -0
  3. package/frontend/dist/brand/total-recall-badge.jpg +0 -0
  4. package/frontend/dist/brand/total-recall-icon.svg +6 -0
  5. package/frontend/dist/brand/total-recall-lockup.png +0 -0
  6. package/frontend/dist/brand/total-recall-mark.svg +9 -0
  7. package/frontend/dist/favicon.svg +6 -0
  8. package/frontend/dist/icons.svg +24 -0
  9. package/frontend/dist/index.html +27 -0
  10. package/frontend/dist/manifest.json +25 -0
  11. package/frontend/dist/sw.js +272 -0
  12. package/frontend/index.html +7 -2
  13. package/frontend/package.json +2 -0
  14. package/frontend/src/App.spec.tsx +82 -0
  15. package/frontend/src/api/_base.ts +35 -0
  16. package/frontend/src/api/api.spec.ts +297 -0
  17. package/frontend/src/api/auth.ts +73 -0
  18. package/frontend/src/api/chat.ts +93 -0
  19. package/frontend/src/api/config.ts +35 -0
  20. package/frontend/src/api/docs.ts +146 -0
  21. package/frontend/src/api/extension.ts +9 -0
  22. package/frontend/src/api/index.ts +20 -0
  23. package/frontend/src/api/integrations.ts +22 -0
  24. package/frontend/src/api/keys.ts +384 -0
  25. package/frontend/src/api/memory.ts +234 -0
  26. package/frontend/src/api/models.ts +36 -0
  27. package/frontend/src/api/research.ts +43 -0
  28. package/frontend/src/api/sandbox.ts +19 -0
  29. package/frontend/src/api/sessions.ts +27 -0
  30. package/frontend/src/api/skills.ts +127 -0
  31. package/frontend/src/api/system.ts +165 -0
  32. package/frontend/src/api/update.ts +21 -0
  33. package/frontend/src/components/BrainSelector.spec.tsx +72 -0
  34. package/frontend/src/components/DaemonLogsTab.spec.tsx +21 -0
  35. package/frontend/src/components/DocumentEditorModal.spec.tsx +80 -0
  36. package/frontend/src/components/DocumentTable.spec.tsx +45 -0
  37. package/frontend/src/components/Graph3D.spec.tsx +75 -0
  38. package/frontend/src/components/MarkdownUtils.spec.tsx +82 -0
  39. package/frontend/src/components/MobilePairing.spec.tsx +24 -0
  40. package/frontend/src/components/MobilePairing.tsx +37 -0
  41. package/frontend/src/components/ResearchAgendaTab.spec.tsx +127 -0
  42. package/frontend/src/components/TaskQueueTab.spec.tsx +70 -0
  43. package/frontend/src/components/TaskQueueTab.tsx +9 -1
  44. package/frontend/src/components/UsageChart.spec.tsx +89 -0
  45. package/frontend/src/components/VoiceInput.spec.tsx +38 -0
  46. package/frontend/src/components/VoiceInput.tsx +68 -0
  47. package/frontend/src/index.css +30 -0
  48. package/frontend/src/main.tsx +13 -0
  49. package/frontend/src/pages/ApiKeysPage.spec.tsx +32 -0
  50. package/frontend/src/pages/AutomationsPage.spec.tsx +60 -0
  51. package/frontend/src/pages/ChatPage.spec.tsx +37 -0
  52. package/frontend/src/pages/ChatPage.tsx +14 -0
  53. package/frontend/src/pages/CollabPage.spec.tsx +64 -0
  54. package/frontend/src/pages/DesignDocsPage.spec.tsx +43 -0
  55. package/frontend/src/pages/FilesPage.spec.tsx +46 -0
  56. package/frontend/src/pages/GraphPage.spec.tsx +24 -0
  57. package/frontend/src/pages/HealthPage.spec.tsx +31 -0
  58. package/frontend/src/pages/HelpPage.spec.tsx +63 -0
  59. package/frontend/src/pages/InboxPage.spec.tsx +55 -0
  60. package/frontend/src/pages/InstructionsPage.spec.tsx +52 -0
  61. package/frontend/src/pages/InstructionsPage.tsx +1 -1
  62. package/frontend/src/pages/IntegrationsPage.spec.tsx +55 -0
  63. package/frontend/src/pages/LoginPage.spec.tsx +73 -0
  64. package/frontend/src/pages/LoginPage.tsx +20 -0
  65. package/frontend/src/pages/MemoryPage.spec.tsx +51 -0
  66. package/frontend/src/pages/ModelsPage.spec.tsx +64 -0
  67. package/frontend/src/pages/ModelsPage.tsx +18 -11
  68. package/frontend/src/pages/OkfPage.spec.tsx +46 -0
  69. package/frontend/src/pages/OnboardingPage.spec.tsx +58 -0
  70. package/frontend/src/pages/OpenWikiPage.spec.tsx +32 -0
  71. package/frontend/src/pages/SandboxPage.spec.tsx +33 -0
  72. package/frontend/src/pages/SettingsPage.spec.tsx +33 -0
  73. package/frontend/src/pages/SettingsPage.tsx +6 -1
  74. package/frontend/src/pages/SkillsPage.spec.tsx +20 -0
  75. package/frontend/src/pages/TasksPage.spec.tsx +17 -0
  76. package/frontend/src/pages/UsagePage.spec.tsx +35 -0
  77. package/frontend/src/pages/VaultPage.spec.tsx +26 -0
  78. package/frontend/src/setupTests.ts +23 -0
  79. package/frontend/vite.config.ts +6 -0
  80. package/package.json +16 -4
  81. package/scaffold/.agent/skills/total-recall/SKILL.md +6 -2
  82. package/scaffold/.agent/skills/total-recall/skills-registry/index.yaml +2575 -0
  83. package/src/cli/connect.mjs +7 -1
  84. package/src/cli/deploy.mjs +8 -8
  85. package/src/cli/init.mjs +14 -4
  86. package/src/cli/recall.mjs +3 -1
  87. package/src/cli/remember.mjs +2 -1
  88. package/src/cli/restore.mjs +2 -2
  89. package/src/cli/upgrade.mjs +2 -2
  90. package/src/core/clarity-rewriter.mjs +2 -2
  91. package/src/core/crons.mjs +23 -55
  92. package/src/core/crypto.mjs +4 -8
  93. package/src/core/daemon-control.mjs +5 -0
  94. package/src/core/embeddings.mjs +207 -374
  95. package/src/core/fact-seeker.mjs +114 -0
  96. package/src/core/github-sync.mjs +319 -0
  97. package/src/core/obsidian-sync.mjs +129 -0
  98. package/src/core/project-brain.mjs +2 -1
  99. package/src/core/remote-vault-sync.mjs +6 -6
  100. package/src/core/runtime.mjs +1 -1
  101. package/src/core/skills-registry.mjs +11 -0
  102. package/src/core/snapshot.mjs +1 -1
  103. package/src/core/vault-watcher.mjs +6 -3
  104. package/src/server/api.mjs +1 -1
  105. package/src/server/rest.mjs +50 -2163
  106. package/src/server/route-manifest.json +57 -5
  107. package/src/server/routes/brains.mjs +212 -0
  108. package/src/server/routes/capture.mjs +33 -0
  109. package/src/server/routes/collab.mjs +1 -1
  110. package/src/server/routes/config.mjs +206 -0
  111. package/src/server/routes/context.mjs +68 -0
  112. package/src/server/routes/dashboard.mjs +53 -0
  113. package/src/server/routes/dream.mjs +30 -0
  114. package/src/server/routes/export.mjs +40 -0
  115. package/src/server/routes/extension.mjs +83 -0
  116. package/src/server/routes/field.mjs +48 -0
  117. package/src/server/routes/files.mjs +34 -0
  118. package/src/server/routes/graph.mjs +323 -0
  119. package/src/server/routes/help.mjs +184 -0
  120. package/src/server/routes/import.mjs +39 -0
  121. package/src/server/routes/instructions.mjs +29 -0
  122. package/src/server/routes/integrations.mjs +123 -0
  123. package/src/server/routes/memory.mjs +19 -1
  124. package/src/server/routes/models.mjs +388 -0
  125. package/src/server/routes/scripts.mjs +110 -0
  126. package/src/server/routes/sessions.mjs +1 -1
  127. package/src/server/routes/ssss.mjs +173 -0
  128. package/src/server/routes/system.mjs +167 -0
  129. package/src/server/routes/tasks.mjs +96 -0
  130. package/src/server/routes/tts.mjs +39 -0
  131. package/src/server/routes/update.mjs +42 -0
  132. package/src/server/routes/vault.mjs +99 -0
  133. package/frontend/src/api.ts +0 -1441
  134. package/scaffold/.agent/skills/ssss/SKILL.md +0 -235
  135. package/scaffold/.agent/skills/ssss/evals/evals.json +0 -37
  136. package/scaffold/.agent/skills/ssss/references/contract-reference.md +0 -95
  137. package/scaffold/.agent/skills/ssss/scripts/validate-schema.mjs +0 -215
  138. package/scaffold/.agent/skills/ssss/subagents/audit-ssss-contract.md +0 -22
  139. package/src/core/pattern_detector.mjs +0 -113
  140. package/src/core/promotion-pipeline.mjs +0 -54
  141. package/src/core/semantic-index.mjs +0 -276
  142. package/src/core/task_runner.mjs +0 -131
@@ -0,0 +1,297 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
3
+
4
+ // Mock fetch globally before importing API functions
5
+ const mockFetch = vi.fn()
6
+ vi.stubGlobal('fetch', mockFetch)
7
+
8
+ // Mock localStorage
9
+ const localStorageMock = (() => {
10
+ let store: Record<string, string> = {}
11
+ return {
12
+ getItem: (key: string) => store[key] ?? null,
13
+ setItem: (key: string, value: string) => { store[key] = value },
14
+ removeItem: (key: string) => { delete store[key] },
15
+ clear: () => { store = {} },
16
+ }
17
+ })()
18
+ vi.stubGlobal('localStorage', localStorageMock)
19
+
20
+ // Import from the barrel
21
+ import {
22
+ // _base
23
+ getApiBase,
24
+ setApiBase,
25
+ registerUnauthedCallback,
26
+ clearUnauthedCallback,
27
+ // auth
28
+ checkSession,
29
+ login,
30
+ logout,
31
+ changePassword,
32
+ getAuthStatus,
33
+ setupPassword,
34
+ // chat
35
+ sendChat,
36
+ fetchChatHistory,
37
+ fetchChatThreads,
38
+ deleteChatThread,
39
+ fetchChatSuggestions,
40
+ fetchTtsStatus,
41
+ fetchTtsAudio,
42
+ // memory
43
+ listMemory,
44
+ searchMemory,
45
+ readMemory,
46
+ saveMemory,
47
+ createMemory,
48
+ deleteMemory,
49
+ fetchMemoryStats,
50
+ fetchGraph,
51
+ fetchConflicts,
52
+ resolveConflict,
53
+ fetchVaultStatus,
54
+ fetchOpenWikiNodes,
55
+ // sandbox
56
+ runSandbox,
57
+ // skills
58
+ listSkills,
59
+ fetchSkill,
60
+ fetchSkillFiles,
61
+ saveSkill,
62
+ deleteSkill,
63
+ searchSkillsRegistry,
64
+ installRegistrySkill,
65
+ toggleSkillRepo,
66
+ auditSkill,
67
+ // keys
68
+ listApiKeys,
69
+ issueApiKey,
70
+ revokeApiKey,
71
+ scanEnvSecrets,
72
+ parseEnvPaste,
73
+ importEnvSecrets,
74
+ fetchSecretsCatalog,
75
+ updateSecretMeta,
76
+ rotateSecretValue,
77
+ deleteProviderSecret,
78
+ recordSecretUsage,
79
+ exportEnvFromSecrets,
80
+ fetchRotationDue,
81
+ enqueueRotationDue,
82
+ revealSecretValue,
83
+ fetchWebAuthnStatus,
84
+ webauthnRegisterOptions,
85
+ webauthnRegisterVerify,
86
+ webauthnAssertOptions,
87
+ webauthnAssertVerify,
88
+ webauthnPasswordStepUp,
89
+ deletePasskey,
90
+ // research
91
+ listResearch,
92
+ createResearch,
93
+ patchResearch,
94
+ deleteResearch,
95
+ // system
96
+ fetchHealth,
97
+ fetchUsageStats,
98
+ fetchLogs,
99
+ triggerRecompile,
100
+ triggerDream,
101
+ runAgentDiagnostics,
102
+ fetchBrains,
103
+ listTasks,
104
+ createTask,
105
+ listFiles,
106
+ listScripts,
107
+ readScript,
108
+ saveScript,
109
+ runScript,
110
+ shareToApi,
111
+ runOkfLint,
112
+ triggerOkfExport,
113
+ postDecision,
114
+ // update
115
+ checkUpdate,
116
+ runUpdate,
117
+ // models
118
+ fetchGeminiModels,
119
+ fetchClaudeModels,
120
+ fetchOpenaiModels,
121
+ fetchOpenRouterModels,
122
+ // docs
123
+ fetchDesignDocs,
124
+ fetchDesignDocContent,
125
+ fetchDocs,
126
+ readDoc,
127
+ createDoc,
128
+ updateDoc,
129
+ deleteDoc,
130
+ fetchViews,
131
+ createView,
132
+ deleteView,
133
+ fetchInstructions,
134
+ fetchInstructionContent,
135
+ fetchHelpTopics,
136
+ fetchHelpContent,
137
+ // extension
138
+ fetchExtensionStatus,
139
+ // sessions
140
+ fetchSessions,
141
+ deleteSession,
142
+ // integrations
143
+ connectClient,
144
+ fetchActiveIntegrations,
145
+ // config
146
+ fetchConfig,
147
+ saveConfig,
148
+ fetchConfigJson,
149
+ saveConfigJson,
150
+ } from './index'
151
+
152
+ function makeOkResponse(body: unknown = {}) {
153
+ return Promise.resolve({
154
+ ok: true,
155
+ status: 200,
156
+ json: () => Promise.resolve(body),
157
+ blob: () => Promise.resolve(new Blob()),
158
+ } as Response)
159
+ }
160
+
161
+ beforeEach(() => {
162
+ mockFetch.mockReset()
163
+ mockFetch.mockImplementation(() => makeOkResponse())
164
+ })
165
+
166
+ describe('API barrel exports — all functions exist', () => {
167
+ const fns = [
168
+ // _base
169
+ getApiBase, setApiBase, registerUnauthedCallback, clearUnauthedCallback,
170
+ // auth
171
+ checkSession, login, logout, changePassword, getAuthStatus, setupPassword,
172
+ // chat
173
+ sendChat, fetchChatHistory, fetchChatThreads, deleteChatThread, fetchChatSuggestions,
174
+ fetchTtsStatus, fetchTtsAudio,
175
+ // memory
176
+ listMemory, searchMemory, readMemory, saveMemory, createMemory, deleteMemory,
177
+ fetchMemoryStats, fetchGraph, fetchConflicts, resolveConflict, fetchVaultStatus, fetchOpenWikiNodes,
178
+ // sandbox
179
+ runSandbox,
180
+ // skills
181
+ listSkills, fetchSkill, fetchSkillFiles, saveSkill, deleteSkill,
182
+ searchSkillsRegistry, installRegistrySkill, toggleSkillRepo, auditSkill,
183
+ // keys
184
+ listApiKeys, issueApiKey, revokeApiKey, scanEnvSecrets, parseEnvPaste, importEnvSecrets,
185
+ fetchSecretsCatalog, updateSecretMeta, rotateSecretValue, deleteProviderSecret, recordSecretUsage,
186
+ exportEnvFromSecrets, fetchRotationDue, enqueueRotationDue, revealSecretValue,
187
+ fetchWebAuthnStatus, webauthnRegisterOptions, webauthnRegisterVerify,
188
+ webauthnAssertOptions, webauthnAssertVerify, webauthnPasswordStepUp, deletePasskey,
189
+ // research
190
+ listResearch, createResearch, patchResearch, deleteResearch,
191
+ // system
192
+ fetchHealth, fetchUsageStats, fetchLogs, triggerRecompile, triggerDream, runAgentDiagnostics,
193
+ fetchBrains, listTasks, createTask, listFiles, listScripts, readScript, saveScript, runScript,
194
+ shareToApi, runOkfLint, triggerOkfExport, postDecision,
195
+ // update
196
+ checkUpdate, runUpdate,
197
+ // models
198
+ fetchGeminiModels, fetchClaudeModels, fetchOpenaiModels, fetchOpenRouterModels,
199
+ // docs
200
+ fetchDesignDocs, fetchDesignDocContent, fetchDocs, readDoc, createDoc, updateDoc, deleteDoc,
201
+ fetchViews, createView, deleteView, fetchInstructions, fetchInstructionContent,
202
+ fetchHelpTopics, fetchHelpContent,
203
+ // extension
204
+ fetchExtensionStatus,
205
+ // sessions
206
+ fetchSessions, deleteSession,
207
+ // integrations
208
+ connectClient, fetchActiveIntegrations,
209
+ // config
210
+ fetchConfig, saveConfig, fetchConfigJson, saveConfigJson,
211
+ ]
212
+
213
+ it('exports all functions as typeof function', () => {
214
+ for (const fn of fns) {
215
+ expect(typeof fn, `${fn.name} should be a function`).toBe('function')
216
+ }
217
+ })
218
+ })
219
+
220
+ describe('checkSession', () => {
221
+ it('returns true when fetch succeeds with ok response', async () => {
222
+ mockFetch.mockResolvedValueOnce({ ok: true } as Response)
223
+ const result = await checkSession()
224
+ expect(result).toBe(true)
225
+ })
226
+
227
+ it('returns false on network error', async () => {
228
+ mockFetch.mockRejectedValueOnce(new Error('Network error'))
229
+ const result = await checkSession()
230
+ expect(result).toBe(false)
231
+ })
232
+ })
233
+
234
+ describe('login', () => {
235
+ it('returns ok:true on successful login', async () => {
236
+ mockFetch.mockResolvedValueOnce({
237
+ ok: true,
238
+ json: () => Promise.resolve({ requiresPasswordReset: false }),
239
+ } as Response)
240
+ const result = await login('password123')
241
+ expect(result.ok).toBe(true)
242
+ })
243
+
244
+ it('returns ok:false with error on failed login', async () => {
245
+ mockFetch.mockResolvedValueOnce({
246
+ ok: false,
247
+ json: () => Promise.resolve({ error: 'Invalid password' }),
248
+ } as Response)
249
+ const result = await login('wrongpassword')
250
+ expect(result.ok).toBe(false)
251
+ expect(result.error).toBe('Invalid password')
252
+ })
253
+ })
254
+
255
+ describe('getApiBase', () => {
256
+ it('returns the current API base URL', () => {
257
+ const base = getApiBase()
258
+ expect(typeof base).toBe('string')
259
+ })
260
+ })
261
+
262
+ describe('fetchExtensionStatus', () => {
263
+ it('returns unavailable on non-ok response', async () => {
264
+ mockFetch.mockResolvedValueOnce({ ok: false, status: 404 } as Response)
265
+ const result = await fetchExtensionStatus()
266
+ expect(result).toEqual({ available: false, connected: false })
267
+ })
268
+
269
+ it('returns extension status on ok response', async () => {
270
+ mockFetch.mockResolvedValueOnce({
271
+ ok: true,
272
+ json: () => Promise.resolve({ available: true, connected: true }),
273
+ } as Response)
274
+ const result = await fetchExtensionStatus()
275
+ expect(result).toEqual({ available: true, connected: true })
276
+ })
277
+ })
278
+
279
+ describe('runSandbox', () => {
280
+ it('returns success result on ok response', async () => {
281
+ mockFetch.mockResolvedValueOnce({
282
+ ok: true,
283
+ json: () => Promise.resolve({ success: true, output: 'hello' }),
284
+ } as Response)
285
+ const result = await runSandbox('console.log("hello")')
286
+ expect(result.success).toBe(true)
287
+ expect(result.output).toBe('hello')
288
+ expect(result.isError).toBe(false)
289
+ })
290
+
291
+ it('returns error result on fetch exception', async () => {
292
+ mockFetch.mockRejectedValueOnce(new Error('Connection refused'))
293
+ const result = await runSandbox('bad code')
294
+ expect(result.success).toBe(false)
295
+ expect(result.isError).toBe(true)
296
+ })
297
+ })
@@ -0,0 +1,73 @@
1
+ // ─── Auth domain ──────────────────────────────────────────────────────────────
2
+
3
+ import { API_BASE } from './_base'
4
+
5
+ export async function checkSession(): Promise<boolean> {
6
+ try {
7
+ const res = await fetch(API_BASE + '/auth/me', { credentials: 'include' })
8
+ return res.ok
9
+ } catch {
10
+ return false
11
+ }
12
+ }
13
+
14
+ export async function login(password: string): Promise<{ ok: boolean; requiresPasswordReset?: boolean; error?: string }> {
15
+ try {
16
+ const res = await fetch(API_BASE + '/auth/login', {
17
+ method: 'POST',
18
+ headers: { 'Content-Type': 'application/json' },
19
+ credentials: 'include',
20
+ body: JSON.stringify({ password }),
21
+ })
22
+ const data = await res.json().catch(() => ({}))
23
+ if (res.ok) return { ok: true, requiresPasswordReset: data.requiresPasswordReset }
24
+ return { ok: false, error: data.error || 'Invalid password' }
25
+ } catch {
26
+ return { ok: false, error: 'Network error — is the server running?' }
27
+ }
28
+ }
29
+
30
+ export async function changePassword(newPassword: string): Promise<{ ok: boolean; error?: string }> {
31
+ try {
32
+ const res = await fetch(API_BASE + '/auth/change-password', {
33
+ method: 'POST',
34
+ headers: { 'Content-Type': 'application/json' },
35
+ credentials: 'include',
36
+ body: JSON.stringify({ newPassword }),
37
+ })
38
+ if (res.ok) return { ok: true }
39
+ const data = await res.json().catch(() => ({}))
40
+ return { ok: false, error: data.error || 'Failed to change password' }
41
+ } catch {
42
+ return { ok: false, error: 'Network error' }
43
+ }
44
+ }
45
+
46
+ export async function getAuthStatus(): Promise<{ configured: boolean }> {
47
+ try {
48
+ const res = await fetch(API_BASE + "/auth/status")
49
+ if (res.ok) return await res.json()
50
+ return { configured: true }
51
+ } catch {
52
+ return { configured: true }
53
+ }
54
+ }
55
+
56
+ export async function setupPassword(newPassword: string): Promise<{ ok: boolean; error?: string }> {
57
+ try {
58
+ const res = await fetch(API_BASE + "/auth/setup", {
59
+ method: "POST",
60
+ headers: { "Content-Type": "application/json" },
61
+ body: JSON.stringify({ newPassword }),
62
+ })
63
+ const data = await res.json().catch(() => ({}))
64
+ if (res.ok) return { ok: true }
65
+ return { ok: false, error: data.error || "Failed to setup password" }
66
+ } catch {
67
+ return { ok: false, error: "Network error — is the server running?" }
68
+ }
69
+ }
70
+
71
+ export async function logout(): Promise<void> {
72
+ await fetch(API_BASE + '/auth/logout', { method: 'POST', credentials: 'include' }).catch(() => {})
73
+ }
@@ -0,0 +1,93 @@
1
+ // ─── Chat domain ──────────────────────────────────────────────────────────────
2
+
3
+ import { apiFetch, API_BASE } from './_base'
4
+
5
+ export interface ChatThread {
6
+ id: string
7
+ title: string
8
+ turns: number
9
+ lastUpdated: number
10
+ brainId: string
11
+ }
12
+
13
+ export interface ChatSuggestion {
14
+ type: string
15
+ title: string
16
+ text: string
17
+ nodes: string[]
18
+ }
19
+
20
+ export async function sendChat(
21
+ messages: { role: string; content: string }[],
22
+ signal?: AbortSignal,
23
+ sessionId?: string,
24
+ groundingNodes?: string[],
25
+ model?: string,
26
+ brainId?: string
27
+ ): Promise<string> {
28
+ const headers: Record<string, string> = { 'Content-Type': 'application/json' }
29
+ if (sessionId) {
30
+ headers['x-session-id'] = sessionId
31
+ }
32
+ const res = await apiFetch(API_BASE + '/v1/chat/completions', {
33
+ method: 'POST',
34
+ signal,
35
+ headers,
36
+ body: JSON.stringify({ messages, groundingNodes, model, brainId }),
37
+ })
38
+ if (!res.ok) throw new Error(`Chat API error: ${res.status}`)
39
+ const data = await res.json()
40
+ return data.choices?.[0]?.message?.content ?? '(empty response)'
41
+ }
42
+
43
+ export async function fetchChatHistory(sessionId?: string): Promise<{ role: string; content: string }[]> {
44
+ const headers: Record<string, string> = {}
45
+ if (sessionId) {
46
+ headers['x-session-id'] = sessionId
47
+ }
48
+ const res = await apiFetch(API_BASE + '/v1/chat/history', { headers })
49
+ if (!res.ok) throw new Error(`Chat history error: ${res.status}`)
50
+ const data = await res.json()
51
+ return data.messages ?? []
52
+ }
53
+
54
+ export async function fetchChatThreads(): Promise<ChatThread[]> {
55
+ const res = await apiFetch(API_BASE + '/v1/chat/threads')
56
+ if (!res.ok) throw new Error(`Chat threads list error: ${res.status}`)
57
+ return res.json()
58
+ }
59
+
60
+ export async function deleteChatThread(id: string): Promise<{ deleted: boolean; id: string }> {
61
+ const res = await apiFetch(API_BASE + `/v1/chat/threads/${id}`, { method: 'DELETE' })
62
+ if (!res.ok) throw new Error(`Delete chat thread error: ${res.status}`)
63
+ return res.json()
64
+ }
65
+
66
+ export async function fetchChatSuggestions(): Promise<ChatSuggestion[]> {
67
+ const res = await apiFetch(API_BASE + '/v1/chat/suggestions')
68
+ if (!res.ok) throw new Error(`Chat suggestions list error: ${res.status}`)
69
+ return res.json()
70
+ }
71
+
72
+ // ─── TTS (Kokoro-82M) ──────────────────────────────────────────────────────────
73
+
74
+ export async function fetchTtsStatus(): Promise<{ enabled: boolean }> {
75
+ try {
76
+ const res = await apiFetch(API_BASE + '/api/tts/status')
77
+ if (!res.ok) return { enabled: false }
78
+ return res.json()
79
+ } catch {
80
+ return { enabled: false }
81
+ }
82
+ }
83
+
84
+ export async function fetchTtsAudio(text: string): Promise<Blob | null> {
85
+ const res = await apiFetch(API_BASE + '/api/tts', {
86
+ method: 'POST',
87
+ headers: { 'Content-Type': 'application/json' },
88
+ body: JSON.stringify({ text }),
89
+ })
90
+ if (res.status === 503) return null // Kokoro not configured — caller should fall back.
91
+ if (!res.ok) throw new Error(`TTS error: ${res.status}`)
92
+ return res.blob()
93
+ }
@@ -0,0 +1,35 @@
1
+ // ─── Config domain ────────────────────────────────────────────────────────────
2
+
3
+ import { apiFetch, API_BASE } from './_base'
4
+ import type { ConfigJson } from './_base'
5
+
6
+ export async function fetchConfig(name: string): Promise<string> {
7
+ const res = await apiFetch(`${API_BASE}/api/config/${name}`)
8
+ if (!res.ok) throw new Error(`Config API error: ${res.status}`)
9
+ const data = await res.json()
10
+ return data.content
11
+ }
12
+
13
+ export async function saveConfig(name: string, content: string): Promise<void> {
14
+ const res = await apiFetch(`${API_BASE}/api/config/${name}`, {
15
+ method: 'PUT',
16
+ headers: { 'Content-Type': 'application/json' },
17
+ body: JSON.stringify({ content }),
18
+ })
19
+ if (!res.ok) throw new Error(`Config API error: ${res.status}`)
20
+ }
21
+
22
+ export async function fetchConfigJson(): Promise<ConfigJson> {
23
+ const res = await apiFetch(`${API_BASE}/api/config-json`)
24
+ if (!res.ok) throw new Error(`Config JSON API error: ${res.status}`)
25
+ return res.json()
26
+ }
27
+
28
+ export async function saveConfigJson(config: ConfigJson): Promise<void> {
29
+ const res = await apiFetch(`${API_BASE}/api/config-json`, {
30
+ method: 'POST',
31
+ headers: { 'Content-Type': 'application/json' },
32
+ body: JSON.stringify(config),
33
+ })
34
+ if (!res.ok) throw new Error(`Config JSON API error: ${res.status}`)
35
+ }
@@ -0,0 +1,146 @@
1
+ // ─── Docs domain ──────────────────────────────────────────────────────────────
2
+
3
+ import { apiFetch, API_BASE } from './_base'
4
+
5
+ // ─── Design Docs ──────────────────────────────────────────────────────────────
6
+
7
+ export async function fetchDesignDocs(): Promise<Array<{ name: string; path: string; size: number; modified: string; category: string }>> {
8
+ const res = await apiFetch(`${API_BASE}/api/files?path=docs`)
9
+ if (!res.ok) throw new Error(`Design docs error: ${res.status}`)
10
+ return res.json()
11
+ }
12
+
13
+ export async function fetchDesignDocContent(path: string): Promise<{ content: string }> {
14
+ const res = await apiFetch(`${API_BASE}/api/files?path=${encodeURIComponent(path)}&content=true`)
15
+ if (!res.ok) throw new Error(`Design doc content error: ${res.status}`)
16
+ return res.json()
17
+ }
18
+
19
+ // ─── Vault Documents ──────────────────────────────────────────────────────────
20
+
21
+ export interface VaultDocument extends Record<string, unknown> {
22
+ id?: string
23
+ type?: string
24
+ name?: string
25
+ path: string
26
+ status?: string
27
+ portability?: string
28
+ updatedAt?: string | number
29
+ }
30
+
31
+ export interface DocsResponse {
32
+ docs: VaultDocument[]
33
+ }
34
+
35
+ export interface DocumentResponse {
36
+ raw: string
37
+ }
38
+
39
+ export interface SavedView {
40
+ id: string
41
+ name: string
42
+ filters: Record<string, string>
43
+ }
44
+
45
+ export async function fetchDocs(brain?: string, params?: Record<string, string>): Promise<DocsResponse> {
46
+ const p = new URLSearchParams()
47
+ if (brain) p.set('brain', brain)
48
+ if (params) {
49
+ for (const [k, v] of Object.entries(params)) if (v) p.set(k, v)
50
+ }
51
+ const res = await apiFetch(`/api/docs?${p.toString()}`)
52
+ if (!res.ok) throw new Error('Failed to fetch docs')
53
+ return res.json()
54
+ }
55
+
56
+ export async function readDoc(path: string, brain?: string): Promise<DocumentResponse> {
57
+ const p = new URLSearchParams({ path })
58
+ if (brain) p.set('brain', brain)
59
+ const res = await apiFetch(`/api/docs/read?${p.toString()}`)
60
+ if (!res.ok) throw new Error('Failed to read doc')
61
+ return res.json()
62
+ }
63
+
64
+ export async function createDoc(path: string, content: string, brain?: string) {
65
+ const res = await apiFetch('/api/docs', {
66
+ method: 'POST',
67
+ headers: { 'Content-Type': 'application/json' },
68
+ body: JSON.stringify({ path, content, brainId: brain })
69
+ })
70
+ if (!res.ok) throw new Error('Failed to create doc')
71
+ return res.json()
72
+ }
73
+
74
+ export async function updateDoc(path: string, content: string, brain?: string) {
75
+ const res = await apiFetch('/api/docs', {
76
+ method: 'PUT',
77
+ headers: { 'Content-Type': 'application/json' },
78
+ body: JSON.stringify({ path, content, brainId: brain })
79
+ })
80
+ if (!res.ok) throw new Error('Failed to update doc')
81
+ return res.json()
82
+ }
83
+
84
+ export async function deleteDoc(path: string, brain?: string) {
85
+ const p = new URLSearchParams({ path })
86
+ if (brain) p.set('brain', brain)
87
+ const res = await apiFetch(`/api/docs?${p.toString()}`, { method: 'DELETE' })
88
+ if (!res.ok) throw new Error('Failed to delete doc')
89
+ return res.json()
90
+ }
91
+
92
+ export async function fetchViews(): Promise<SavedView[]> {
93
+ const res = await apiFetch('/api/views')
94
+ if (!res.ok) throw new Error('Failed to fetch views')
95
+ return res.json()
96
+ }
97
+
98
+ export async function createView(name: string, filters: Record<string, string>) {
99
+ const res = await apiFetch('/api/views', {
100
+ method: 'POST',
101
+ headers: { 'Content-Type': 'application/json' },
102
+ body: JSON.stringify({ name, filters })
103
+ })
104
+ if (!res.ok) throw new Error('Failed to create view')
105
+ return res.json()
106
+ }
107
+
108
+ export async function deleteView(id: string) {
109
+ const res = await apiFetch(`/api/views/${id}`, { method: 'DELETE' })
110
+ if (!res.ok) throw new Error('Failed to delete view')
111
+ return res.json()
112
+ }
113
+
114
+ // ─── Instructions ─────────────────────────────────────────────────────────────
115
+
116
+ export async function fetchInstructions(): Promise<{ surfaces: Array<{ name: string; filename: string; size: number; lastCompiled: string; active: boolean }>; lastCompileTimestamp: string; totalNodes: number }> {
117
+ const res = await apiFetch(`${API_BASE}/api/dashboard/instructions`)
118
+ if (!res.ok) throw new Error(`Instructions API error: ${res.status}`)
119
+ return res.json()
120
+ }
121
+
122
+ export async function fetchInstructionContent(name: string): Promise<{ name: string; content: string }> {
123
+ const res = await apiFetch(`${API_BASE}/api/ssss/instructions?surface=${encodeURIComponent(name)}`)
124
+ if (!res.ok) throw new Error(`Instruction content error: ${res.status}`)
125
+ return res.json()
126
+ }
127
+
128
+ // ─── Help ──────────────────────────────────────────────────────────────────────
129
+
130
+ export interface HelpTopic {
131
+ id: string
132
+ title: string
133
+ description: string
134
+ }
135
+
136
+ export async function fetchHelpTopics(): Promise<{ topics: HelpTopic[] }> {
137
+ const res = await apiFetch(`${API_BASE}/api/help`)
138
+ if (!res.ok) throw new Error('Failed to load help topics')
139
+ return res.json()
140
+ }
141
+
142
+ export async function fetchHelpContent(topicId: string): Promise<{ topic: string; content: string }> {
143
+ const res = await apiFetch(`${API_BASE}/api/help?topic=${encodeURIComponent(topicId)}`)
144
+ if (!res.ok) throw new Error('Failed to load help content')
145
+ return res.json()
146
+ }
@@ -0,0 +1,9 @@
1
+ // ─── Extension domain ─────────────────────────────────────────────────────────
2
+
3
+ import { apiFetch, API_BASE } from './_base'
4
+
5
+ export async function fetchExtensionStatus(): Promise<{ available: boolean; connected: boolean }> {
6
+ const res = await apiFetch(`${API_BASE}/api/extension/status`)
7
+ if (!res.ok) return { available: false, connected: false }
8
+ return res.json()
9
+ }
@@ -0,0 +1,20 @@
1
+ // ─── Barrel re-export — all domain modules ────────────────────────────────────
2
+ // Imports from '../api' or '../../api' will resolve here automatically via
3
+ // TypeScript module resolution (directory index).
4
+
5
+ export * from './_base'
6
+ export * from './auth'
7
+ export * from './chat'
8
+ export * from './memory'
9
+ export * from './sandbox'
10
+ export * from './skills'
11
+ export * from './keys'
12
+ export * from './research'
13
+ export * from './system'
14
+ export * from './update'
15
+ export * from './models'
16
+ export * from './docs'
17
+ export * from './extension'
18
+ export * from './sessions'
19
+ export * from './integrations'
20
+ export * from './config'