total-recall-brain 3.12.2 → 3.13.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.
@@ -12,7 +12,8 @@
12
12
  "dependencies": {
13
13
  "react": "^19.2.6",
14
14
  "react-dom": "^19.2.6",
15
- "react-router-dom": "^7.15.0"
15
+ "react-router-dom": "^7.15.0",
16
+ "recharts": "^3.9.2"
16
17
  },
17
18
  "devDependencies": {
18
19
  "@eslint/js": "^10.0.1",
@@ -21,6 +21,10 @@ import UsagePage from './pages/UsagePage'
21
21
  import CollabPage from './pages/CollabPage'
22
22
  import HelpPage from './pages/HelpPage'
23
23
  import GraphPage from './pages/GraphPage'
24
+ import InstructionsPage from './pages/InstructionsPage'
25
+ import DesignDocsPage from './pages/DesignDocsPage'
26
+ import OkfPage from './pages/OkfPage'
27
+ import OpenWikiPage from './pages/OpenWikiPage'
24
28
 
25
29
  // ─── Auth state type ──────────────────────────────────────────────────────────
26
30
  type AuthState = 'loading' | 'authed' | 'unauthed'
@@ -152,6 +156,42 @@ function Sidebar({ onLogout, health, activeBrainId, onBrainChange }: SidebarProp
152
156
  </svg>
153
157
  Collaboration
154
158
  </NavLink>
159
+ <NavLink to="/instructions" className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`} id="nav-instructions">
160
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
161
+ <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" />
162
+ <polyline points="14 2 14 8 20 8" />
163
+ <line x1="16" y1="13" x2="8" y2="13" />
164
+ <line x1="16" y1="17" x2="8" y2="17" />
165
+ <polyline points="10 9 9 9 8 9" />
166
+ </svg>
167
+ Instructions
168
+ </NavLink>
169
+ <NavLink to="/design" className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`} id="nav-design">
170
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
171
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
172
+ <line x1="3" y1="9" x2="21" y2="9" />
173
+ <line x1="9" y1="21" x2="9" y2="9" />
174
+ </svg>
175
+ Design Docs
176
+ </NavLink>
177
+ <NavLink to="/okf" className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`} id="nav-okf">
178
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
179
+ <path d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z" />
180
+ <polyline points="7.5 4.21 12 6.81 16.5 4.21" />
181
+ <polyline points="7.5 19.79 7.5 14.6 3 12" />
182
+ <polyline points="21 12 16.5 14.6 16.5 19.79" />
183
+ <polyline points="3.27 6.96 12 12.01 20.73 6.96" />
184
+ <line x1="12" y1="22.08" x2="12" y2="12" />
185
+ </svg>
186
+ OKF Manager
187
+ </NavLink>
188
+ <NavLink to="/openwiki" className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`} id="nav-openwiki">
189
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
190
+ <path d="M2 3h6a4 4 0 014 4v14a3 3 0 00-3-3H2z" />
191
+ <path d="M22 3h-6a4 4 0 00-4 4v14a3 3 0 013-3h7z" />
192
+ </svg>
193
+ OpenWiki
194
+ </NavLink>
155
195
  <NavLink to="/help" className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`} id="nav-help">
156
196
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
157
197
  <circle cx="12" cy="12" r="10" />
@@ -317,6 +357,10 @@ function MainContent({ activeBrainId, onBrainChange }: { activeBrainId: string;
317
357
  <Route path="/integrations" element={<IntegrationsPage />} />
318
358
  <Route path="/skills" element={<SkillsPage />} />
319
359
  <Route path="/collab" element={<CollabPage />} />
360
+ <Route path="/instructions" element={<InstructionsPage />} />
361
+ <Route path="/design" element={<DesignDocsPage />} />
362
+ <Route path="/okf" element={<OkfPage />} />
363
+ <Route path="/openwiki" element={<OpenWikiPage />} />
320
364
  <Route path="/help" element={<HelpPage />} />
321
365
  <Route path="/graph" element={<GraphPage activeBrainId={activeBrainId} />} />
322
366
  </Routes>
@@ -667,12 +667,33 @@ export interface GeminiModelInfo {
667
667
  }
668
668
 
669
669
  export async function fetchGeminiModels(): Promise<GeminiModelInfo[]> {
670
- const res = await apiFetch(`${API_BASE}/api/gemini-models`)
670
+ const res = await apiFetch(`${API_BASE}/api/gemini-models?t=${Date.now()}`)
671
671
  if (!res.ok) throw new Error(`Gemini models API error: ${res.status}`)
672
672
  const data = await res.json()
673
673
  return data.models ?? []
674
674
  }
675
675
 
676
+ export async function fetchClaudeModels(): Promise<GeminiModelInfo[]> {
677
+ const res = await apiFetch(`${API_BASE}/api/claude-models?t=${Date.now()}`)
678
+ if (!res.ok) throw new Error(`Claude models API error: ${res.status}`)
679
+ const data = await res.json()
680
+ return data.models ?? []
681
+ }
682
+
683
+ export async function fetchOpenaiModels(): Promise<GeminiModelInfo[]> {
684
+ const res = await apiFetch(`${API_BASE}/api/openai-models?t=${Date.now()}`)
685
+ if (!res.ok) throw new Error(`OpenAI models API error: ${res.status}`)
686
+ const data = await res.json()
687
+ return data.models ?? []
688
+ }
689
+
690
+ export async function fetchOpenRouterModels(): Promise<GeminiModelInfo[]> {
691
+ const res = await apiFetch(`${API_BASE}/api/openrouter-models?t=${Date.now()}`)
692
+ if (!res.ok) throw new Error(`OpenRouter models API error: ${res.status}`)
693
+ const data = await res.json()
694
+ return data.models ?? []
695
+ }
696
+
676
697
  // ─── Share API ──────────────────────────────────────────────────────────────────
677
698
 
678
699
  export async function shareToApi(payload: { url: string; action: string; title?: string; tags?: string[] }): Promise<{ success: boolean; message?: string }> {
@@ -732,3 +753,82 @@ export async function fetchHelpContent(topicId: string): Promise<{ topic: string
732
753
  if (!res.ok) throw new Error('Failed to load help content')
733
754
  return res.json()
734
755
  }
756
+
757
+ // ─── Instructions APIs ─────────────────────────────────────────────────────────
758
+
759
+ export async function fetchInstructions(): Promise<{ surfaces: Array<{ name: string; filename: string; size: number; lastCompiled: string; active: boolean }>; lastCompileTimestamp: string; totalNodes: number }> {
760
+ const res = await apiFetch(`${API_BASE}/api/instructions`)
761
+ if (!res.ok) throw new Error(`Instructions API error: ${res.status}`)
762
+ return res.json()
763
+ }
764
+
765
+ export async function fetchInstructionContent(name: string): Promise<{ name: string; content: string }> {
766
+ const res = await apiFetch(`${API_BASE}/api/ssss/instructions?surface=${encodeURIComponent(name)}`)
767
+ if (!res.ok) throw new Error(`Instruction content error: ${res.status}`)
768
+ return res.json()
769
+ }
770
+
771
+ // ─── OKF APIs ──────────────────────────────────────────────────────────────────
772
+
773
+ export async function runOkfLint(): Promise<{ results: Array<{ slug: string; field: string; severity: string; message: string }>; summary: { total: number; errors: number; warnings: number } }> {
774
+ const res = await apiFetch(`${API_BASE}/api/sandbox`, {
775
+ method: 'POST',
776
+ headers: { 'Content-Type': 'application/json' },
777
+ body: JSON.stringify({ code: 'const { execSync } = require("child_process"); console.log(JSON.stringify({ output: execSync("npx total-recall lint --okf 2>&1 || true", { encoding: "utf-8" }) }));', timeout: 30000 }),
778
+ })
779
+ if (!res.ok) throw new Error(`OKF lint error: ${res.status}`)
780
+ return res.json()
781
+ }
782
+
783
+ export async function triggerOkfExport(path: string, options: { stripSsss?: boolean; format?: string; scope?: string }): Promise<{ success: boolean; message: string; path?: string }> {
784
+ const args = [`export ${path} --okf`]
785
+ if (options.stripSsss) args.push('--strip-ssss')
786
+ if (options.format) args.push(`--format ${options.format}`)
787
+ if (options.scope === 'global') args.push('--global')
788
+ if (options.scope === 'project') args.push('--project')
789
+
790
+ const res = await apiFetch(`${API_BASE}/api/sandbox`, {
791
+ method: 'POST',
792
+ headers: { 'Content-Type': 'application/json' },
793
+ body: JSON.stringify({ code: `const { execSync } = require("child_process"); try { const out = execSync("npx total-recall ${args.join(' ')} 2>&1", { encoding: "utf-8" }); console.log(JSON.stringify({ success: true, message: out })); } catch(e) { console.log(JSON.stringify({ success: false, message: e.stdout || e.message })); }`, timeout: 60000 }),
794
+ })
795
+ if (!res.ok) throw new Error(`OKF export error: ${res.status}`)
796
+ return res.json()
797
+ }
798
+
799
+ // ─── OpenWiki APIs ─────────────────────────────────────────────────────────────
800
+
801
+ export async function fetchOpenWikiNodes(): Promise<import('./types').MemoryNode[]> {
802
+ const res = await apiFetch(`${API_BASE}/api/memory?tags=openwiki`)
803
+ if (!res.ok) throw new Error(`OpenWiki nodes error: ${res.status}`)
804
+ const data = await res.json()
805
+ return Array.isArray(data) ? data : (data.nodes ?? [])
806
+ }
807
+
808
+ // UCW export/import/validate handled via runSandbox + @ssss/cli (SSSS §16)
809
+
810
+ export async function fetchVaultStatus(): Promise<{ totalNodes: number; categories: Record<string, number>; embeddings: number; lastCompiled: string }> {
811
+ const res = await apiFetch(`${API_BASE}/api/vault/status`)
812
+ if (!res.ok) throw new Error(`Vault status error: ${res.status}`)
813
+ return res.json()
814
+ }
815
+
816
+ export async function fetchMemoryStats(): Promise<{ total: number; byCategory: Record<string, number>; byPriority: Record<string, number>; byStatus: Record<string, number> }> {
817
+ const res = await apiFetch(`${API_BASE}/api/memory/stats`)
818
+ if (!res.ok) throw new Error(`Memory stats error: ${res.status}`)
819
+ return res.json()
820
+ }
821
+
822
+ // ─── Design Docs APIs ──────────────────────────────────────────────────────────
823
+
824
+ export async function fetchDesignDocs(): Promise<Array<{ name: string; path: string; size: number; modified: string; category: string }>> {
825
+ const res = await apiFetch(`${API_BASE}/api/files?path=docs`)
826
+ if (!res.ok) throw new Error(`Design docs error: ${res.status}`)
827
+ return res.json()
828
+ }
829
+
830
+ export async function fetchDesignDocContent(path: string): Promise<{ content: string }> {
831
+ const res = await apiFetch(`${API_BASE}/api/files?path=${encodeURIComponent(path)}&content=true`)
832
+ if (!res.ok) throw new Error(`Design doc content error: ${res.status}`)
833
+ return res.json()
834
+ }
@@ -0,0 +1,78 @@
1
+ import React from 'react';
2
+ import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Legend, CartesianGrid } from 'recharts';
3
+ import type { UsageData } from '../types';
4
+
5
+ interface UsageChartProps {
6
+ usageData: UsageData | null;
7
+ geminiModels: any[];
8
+ claudeModels: any[];
9
+ openaiModels: any[];
10
+ }
11
+
12
+ export const UsageChart: React.FC<UsageChartProps> = ({ usageData }) => {
13
+ if (!usageData || !usageData.timeseries) return null;
14
+
15
+ // Transform timeseries into chart data
16
+ const data = Object.keys(usageData.timeseries).sort().map(dateStr => {
17
+ const dayData = usageData.timeseries![dateStr];
18
+
19
+ let totalUsd = 0;
20
+ const providersCost: Record<string, number> = { gemini: 0, claude: 0, codex: 0, openrouter: 0 };
21
+
22
+ Object.keys(dayData).forEach(model => {
23
+ const { cost, provider } = dayData[model];
24
+
25
+ // Safety fallback to ensure numbers
26
+ const finalCost = typeof cost === 'number' && !isNaN(cost) ? cost : 0;
27
+
28
+ totalUsd += finalCost;
29
+ if (providersCost[provider] !== undefined) {
30
+ providersCost[provider] += finalCost;
31
+ } else if (provider) {
32
+ providersCost[provider] = finalCost;
33
+ }
34
+ });
35
+
36
+ return {
37
+ date: dateStr.split('-').slice(1).join('/'), // MM/DD
38
+ gemini: parseFloat(providersCost.gemini.toFixed(4)),
39
+ claude: parseFloat(providersCost.claude.toFixed(4)),
40
+ codex: parseFloat(providersCost.codex.toFixed(4)),
41
+ openrouter: parseFloat((providersCost.openrouter || 0).toFixed(4)),
42
+ total: parseFloat(totalUsd.toFixed(4))
43
+ };
44
+ });
45
+
46
+ if (data.length === 0) return null;
47
+
48
+ return (
49
+ <div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)', display: 'flex', flexDirection: 'column', gap: 16 }}>
50
+ <div style={{ display: 'flex', alignItems: 'center', gap: 10, borderBottom: '1px solid var(--border)', paddingBottom: 12 }}>
51
+ <span style={{ fontSize: 20 }}>📊</span>
52
+ <div>
53
+ <h3 style={{ fontSize: 14, fontWeight: 600 }}>API Costs Over Time</h3>
54
+ <p style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>Historical cost breakdown by provider (USD)</p>
55
+ </div>
56
+ </div>
57
+ <div style={{ width: '100%', height: 250, marginTop: 10 }}>
58
+ <ResponsiveContainer width="100%" height="100%">
59
+ <BarChart data={data} margin={{ top: 5, right: 20, left: -20, bottom: 5 }}>
60
+ <CartesianGrid strokeDasharray="3 3" stroke="rgba(255,255,255,0.05)" />
61
+ <XAxis dataKey="date" stroke="var(--text-tertiary)" fontSize={11} tickLine={false} axisLine={false} />
62
+ <YAxis stroke="var(--text-tertiary)" fontSize={11} tickLine={false} axisLine={false} tickFormatter={(v) => `$${v}`} />
63
+ <Tooltip
64
+ contentStyle={{ background: 'var(--bg-tertiary)', border: '1px solid var(--border)', borderRadius: 8, fontSize: 12 }}
65
+ itemStyle={{ fontSize: 12 }}
66
+ formatter={(value: any) => [`$${Number(value).toFixed(4)}`, undefined]}
67
+ />
68
+ <Legend wrapperStyle={{ fontSize: 11 }} />
69
+ <Bar isAnimationActive={false} dataKey="gemini" name="Gemini" stackId="a" fill="#10b981" radius={[0,0,0,0]} />
70
+ <Bar isAnimationActive={false} dataKey="claude" name="Claude" stackId="a" fill="#d97757" radius={[0,0,0,0]} />
71
+ <Bar isAnimationActive={false} dataKey="codex" name="OpenAI" stackId="a" fill="#3b82f6" radius={[0,0,0,0]} />
72
+ <Bar isAnimationActive={false} dataKey="openrouter" name="OpenRouter" stackId="a" fill="#8b5cf6" radius={[4,4,0,0]} />
73
+ </BarChart>
74
+ </ResponsiveContainer>
75
+ </div>
76
+ </div>
77
+ );
78
+ };