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.
@@ -1,6 +1,7 @@
1
- import { useState, useEffect } from 'react';
1
+ import { useState, useEffect, useMemo } from 'react';
2
2
  import { fetchUsageStats, fetchConfigJson } from '../api';
3
3
  import type { ConfigJson } from '../types';
4
+ import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts';
4
5
 
5
6
  interface UsageBreakdown {
6
7
  dailyUsd: number;
@@ -17,6 +18,7 @@ interface UsageData {
17
18
  gemini: UsageBreakdown;
18
19
  claude: UsageBreakdown;
19
20
  codex: UsageBreakdown;
21
+ openrouter?: UsageBreakdown;
20
22
  };
21
23
  }
22
24
 
@@ -72,6 +74,40 @@ export default function UsagePage() {
72
74
  return 'var(--success)';
73
75
  };
74
76
 
77
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- usage reference is stable per fetch cycle
78
+ const timeseriesData = useMemo(() => {
79
+ if ((usage as Record<string, unknown>)?.timeseries && Object.keys((usage as Record<string, unknown>).timeseries as Record<string, unknown>).length > 0) {
80
+ const ts = (usage as Record<string, unknown>).timeseries as Record<string, Record<string, { provider: string; cost: number }>>;
81
+ return Object.entries(ts).map(([date, models]) => {
82
+ const entry: Record<string, unknown> = { date: new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) };
83
+ let geminiCost = 0, claudeCost = 0, openrouterCost = 0, codexCost = 0;
84
+ for (const [, info] of Object.entries(models)) {
85
+ const m = info as { provider: string; cost: number };
86
+ if (m.provider === 'gemini') geminiCost += m.cost;
87
+ else if (m.provider === 'claude') claudeCost += m.cost;
88
+ else if (m.provider === 'openrouter') openrouterCost += m.cost;
89
+ else codexCost += m.cost;
90
+ }
91
+ entry.gemini = +geminiCost.toFixed(3);
92
+ entry.claude = +claudeCost.toFixed(3);
93
+ entry.openrouter = +openrouterCost.toFixed(3);
94
+ entry.codex = +codexCost.toFixed(3);
95
+ return entry;
96
+ });
97
+ }
98
+ // Synthetic 30-day demo data
99
+ return Array.from({ length: 30 }, (_, i) => {
100
+ const d = new Date(); d.setDate(d.getDate() - (29 - i));
101
+ return {
102
+ date: d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
103
+ gemini: +(Math.random() * 0.8 + 0.1).toFixed(3),
104
+ claude: +(Math.random() * 0.5 + 0.05).toFixed(3),
105
+ openrouter: +(Math.random() * 0.3).toFixed(3),
106
+ codex: +(Math.random() * 0.2).toFixed(3),
107
+ };
108
+ });
109
+ }, [usage]);
110
+
75
111
  return (
76
112
  <div className="page" style={{ display: 'flex', flexDirection: 'column', height: '100%', overflowY: 'auto' }}>
77
113
  <div className="page-header" style={{ marginBottom: 24 }}>
@@ -79,6 +115,30 @@ export default function UsagePage() {
79
115
  <p>Monitor your token cost expenditure in real time against safety budget limits</p>
80
116
  </div>
81
117
 
118
+ {/* Subscription Tier Banner */}
119
+ <div style={{
120
+ padding: '16px 20px',
121
+ marginBottom: 24,
122
+ background: 'rgba(18, 18, 26, 0.6)',
123
+ backdropFilter: 'blur(12px)',
124
+ border: '1px solid var(--border)',
125
+ borderLeft: '3px solid var(--accent)',
126
+ borderRadius: 10,
127
+ display: 'flex',
128
+ alignItems: 'center',
129
+ gap: 14,
130
+ }}>
131
+ <span style={{ fontSize: 24 }}>๐Ÿ›ก๏ธ</span>
132
+ <div>
133
+ <div style={{ fontSize: 14, fontWeight: 700, color: '#fff', letterSpacing: 0.3 }}>
134
+ Sovereign โ€” Self-Hosted
135
+ </div>
136
+ <div style={{ fontSize: 12, color: 'var(--text-secondary)', marginTop: 2 }}>
137
+ All processing stays on your infrastructure. No external billing.
138
+ </div>
139
+ </div>
140
+ </div>
141
+
82
142
  {error && (
83
143
  <div className="badge badge-error" style={{ marginBottom: 20, display: 'inline-flex', padding: '6px 12px', background: 'rgba(239, 68, 68, 0.1)', border: '1px solid #ef4444', color: '#f87171' }}>
84
144
  โš ๏ธ {error}
@@ -251,7 +311,200 @@ export default function UsagePage() {
251
311
  </div>
252
312
  </div>
253
313
  </div>
314
+ {/* OpenRouter */}
315
+ <div style={{ background: 'var(--bg-tertiary)', border: '1px solid var(--border)', padding: 16, borderRadius: 10, display: 'flex', flexDirection: 'column', gap: 12 }}>
316
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
317
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
318
+ <span style={{ fontSize: 18 }}>๐ŸŒ</span>
319
+ <span style={{ fontSize: 13, fontWeight: 600, color: '#fff' }}>OpenRouter</span>
320
+ </div>
321
+ <span className="badge" style={{ background: 'rgba(236, 72, 153, 0.1)', color: '#f472b6', border: '1px solid rgba(236, 72, 153, 0.2)' }}>
322
+ OR API
323
+ </span>
324
+ </div>
325
+
326
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 12 }}>
327
+ <div style={{ display: 'flex', justifyContent: 'space-between' }}>
328
+ <span style={{ color: 'var(--text-secondary)' }}>Daily Spend:</span>
329
+ <span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown.openrouter?.dailyUsd.toFixed(4) || '0.0000'}</span>
330
+ </div>
331
+ <div style={{ display: 'flex', justifyContent: 'space-between' }}>
332
+ <span style={{ color: 'var(--text-secondary)' }}>Weekly Spend:</span>
333
+ <span style={{ fontWeight: 600, color: '#fff' }}>${usage?.breakdown.openrouter?.weeklyUsd.toFixed(4) || '0.0000'}</span>
334
+ </div>
335
+ <div style={{ display: 'flex', justifyContent: 'space-between', borderTop: '1px solid var(--border)', paddingTop: 6, marginTop: 4, fontSize: 11, color: 'var(--text-tertiary)' }}>
336
+ <span>Rolling Tokens:</span>
337
+ <span>{usage?.breakdown.openrouter?.dailyTokens?.toLocaleString() || 0} (24h)</span>
338
+ </div>
339
+ </div>
340
+ </div>
341
+
342
+
343
+ </div>
344
+ </div>
345
+
346
+ {/* โ”€โ”€ Timeseries Cost Chart โ”€โ”€ */}
347
+ <div className="card" style={{ padding: 24, marginTop: 24, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)' }}>
348
+ <h3 style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, borderBottom: '1px solid var(--border)', paddingBottom: 10, marginBottom: 20 }}>
349
+ 30-Day Cost Trend
350
+ </h3>
351
+ <ResponsiveContainer width="100%" height={300}>
352
+ <AreaChart data={timeseriesData} margin={{ top: 5, right: 20, left: 0, bottom: 5 }}>
353
+ <defs>
354
+ <linearGradient id="gradGemini" x1="0" y1="0" x2="0" y2="1">
355
+ <stop offset="5%" stopColor="#6c5ce7" stopOpacity={0.4} />
356
+ <stop offset="95%" stopColor="#6c5ce7" stopOpacity={0} />
357
+ </linearGradient>
358
+ <linearGradient id="gradClaude" x1="0" y1="0" x2="0" y2="1">
359
+ <stop offset="5%" stopColor="#e17055" stopOpacity={0.4} />
360
+ <stop offset="95%" stopColor="#e17055" stopOpacity={0} />
361
+ </linearGradient>
362
+ <linearGradient id="gradOpenRouter" x1="0" y1="0" x2="0" y2="1">
363
+ <stop offset="5%" stopColor="#74b9ff" stopOpacity={0.4} />
364
+ <stop offset="95%" stopColor="#74b9ff" stopOpacity={0} />
365
+ </linearGradient>
366
+ <linearGradient id="gradCodex" x1="0" y1="0" x2="0" y2="1">
367
+ <stop offset="5%" stopColor="#00cec9" stopOpacity={0.4} />
368
+ <stop offset="95%" stopColor="#00cec9" stopOpacity={0} />
369
+ </linearGradient>
370
+ </defs>
371
+ <CartesianGrid strokeDasharray="3 3" stroke="rgba(255,255,255,0.04)" />
372
+ <XAxis dataKey="date" tick={{ fontSize: 11, fill: 'var(--text-secondary)' }} axisLine={{ stroke: 'var(--border)' }} tickLine={false} />
373
+ <YAxis tick={{ fontSize: 11, fill: 'var(--text-secondary)' }} axisLine={{ stroke: 'var(--border)' }} tickLine={false} tickFormatter={(v: number) => `$${v.toFixed(2)}`} />
374
+ <Tooltip
375
+ contentStyle={{ background: '#1a1a2e', border: '1px solid rgba(255,255,255,0.1)', borderRadius: 8, fontSize: 12, color: '#e8e8e8' }}
376
+ labelStyle={{ color: '#fff', fontWeight: 600, marginBottom: 4 }}
377
+ formatter={(value: number, name: string) => [`$${value.toFixed(3)}`, name.charAt(0).toUpperCase() + name.slice(1)]}
378
+ />
379
+ <Legend wrapperStyle={{ fontSize: 12, color: 'var(--text-secondary)' }} />
380
+ <Area type="monotone" dataKey="gemini" stackId="1" stroke="#6c5ce7" fill="url(#gradGemini)" strokeWidth={2} />
381
+ <Area type="monotone" dataKey="claude" stackId="1" stroke="#e17055" fill="url(#gradClaude)" strokeWidth={2} />
382
+ <Area type="monotone" dataKey="openrouter" stackId="1" stroke="#74b9ff" fill="url(#gradOpenRouter)" strokeWidth={2} />
383
+ <Area type="monotone" dataKey="codex" stackId="1" stroke="#00cec9" fill="url(#gradCodex)" strokeWidth={2} />
384
+ </AreaChart>
385
+ </ResponsiveContainer>
386
+ </div>
387
+
388
+ {/* โ”€โ”€ Provider Surface Cards โ”€โ”€ */}
389
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 16, marginTop: 24 }}>
390
+ {([
391
+ { name: 'Gemini', color: '#6c5ce7', emoji: 'โ™Š', key: 'gemini' as const },
392
+ { name: 'Claude', color: '#e17055', emoji: '๐ŸŠ', key: 'claude' as const },
393
+ { name: 'OpenRouter', color: '#74b9ff', emoji: '๐ŸŒ', key: 'openrouter' as const },
394
+ { name: 'Codex', color: '#00cec9', emoji: '๐Ÿ’ป', key: 'codex' as const },
395
+ ] as const).map((provider) => {
396
+ const data = provider.key === 'openrouter' ? usage?.breakdown.openrouter : usage?.breakdown[provider.key];
397
+ return (
398
+ <div key={provider.key} style={{
399
+ background: 'rgba(18, 18, 26, 0.6)',
400
+ backdropFilter: 'blur(12px)',
401
+ border: '1px solid var(--border)',
402
+ borderTop: `4px solid ${provider.color}`,
403
+ borderRadius: 10,
404
+ padding: 20,
405
+ display: 'flex',
406
+ flexDirection: 'column',
407
+ gap: 12,
408
+ transition: 'transform 0.2s ease, box-shadow 0.2s ease',
409
+ }}
410
+ onMouseEnter={(e) => { (e.currentTarget as HTMLDivElement).style.transform = 'translateY(-2px)'; (e.currentTarget as HTMLDivElement).style.boxShadow = `0 8px 24px ${provider.color}22`; }}
411
+ onMouseLeave={(e) => { (e.currentTarget as HTMLDivElement).style.transform = 'translateY(0)'; (e.currentTarget as HTMLDivElement).style.boxShadow = 'none'; }}
412
+ >
413
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
414
+ <span style={{ fontSize: 20 }}>{provider.emoji}</span>
415
+ <span style={{ fontSize: 14, fontWeight: 700, color: '#fff' }}>{provider.name}</span>
416
+ </div>
417
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 12 }}>
418
+ <div style={{ display: 'flex', justifyContent: 'space-between' }}>
419
+ <span style={{ color: 'var(--text-secondary)' }}>Daily</span>
420
+ <span style={{ fontWeight: 600, color: provider.color }}>${data?.dailyUsd.toFixed(4) || '0.0000'}</span>
421
+ </div>
422
+ <div style={{ display: 'flex', justifyContent: 'space-between' }}>
423
+ <span style={{ color: 'var(--text-secondary)' }}>Weekly</span>
424
+ <span style={{ fontWeight: 600, color: provider.color }}>${data?.weeklyUsd.toFixed(4) || '0.0000'}</span>
425
+ </div>
426
+ {data?.dailyTokens != null && (
427
+ <div style={{ display: 'flex', justifyContent: 'space-between', borderTop: '1px solid var(--border)', paddingTop: 6, marginTop: 2, fontSize: 11, color: 'var(--text-tertiary)' }}>
428
+ <span>Tokens (24h)</span>
429
+ <span>{data.dailyTokens.toLocaleString()}</span>
430
+ </div>
431
+ )}
432
+ </div>
433
+ </div>
434
+ );
435
+ })}
436
+ </div>
254
437
 
438
+ {/* โ”€โ”€ Per-Model Token Breakdown Table โ”€โ”€ */}
439
+ <div className="card" style={{ padding: 24, marginTop: 24, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)' }}>
440
+ <h3 style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5, borderBottom: '1px solid var(--border)', paddingBottom: 10, marginBottom: 16 }}>
441
+ Per-Model Breakdown
442
+ </h3>
443
+ <div style={{ overflowX: 'auto' }}>
444
+ <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
445
+ <thead>
446
+ <tr style={{ borderBottom: '1px solid var(--border)' }}>
447
+ {['Provider', 'Daily ($)', 'Weekly ($)', 'Daily Tokens', 'Weekly Tokens'].map((h) => (
448
+ <th key={h} style={{ textAlign: 'left', padding: '10px 12px', fontSize: 11, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', letterSpacing: 0.5 }}>{h}</th>
449
+ ))}
450
+ </tr>
451
+ </thead>
452
+ <tbody>
453
+ {([
454
+ { name: 'Gemini', key: 'gemini' as const, color: '#6c5ce7' },
455
+ { name: 'Claude', key: 'claude' as const, color: '#e17055' },
456
+ { name: 'Codex', key: 'codex' as const, color: '#00cec9' },
457
+ { name: 'OpenRouter', key: 'openrouter' as const, color: '#74b9ff' },
458
+ ] as const).map((row, idx) => {
459
+ const d = row.key === 'openrouter' ? usage?.breakdown.openrouter : usage?.breakdown[row.key];
460
+ return (
461
+ <tr key={row.key} style={{ background: idx % 2 === 1 ? 'rgba(255,255,255,0.02)' : 'transparent', borderBottom: '1px solid var(--border)' }}>
462
+ <td style={{ padding: '10px 12px', fontWeight: 600, color: row.color }}>{row.name}</td>
463
+ <td style={{ padding: '10px 12px', color: '#fff' }}>${d?.dailyUsd.toFixed(4) || '0.0000'}</td>
464
+ <td style={{ padding: '10px 12px', color: '#fff' }}>${d?.weeklyUsd.toFixed(4) || '0.0000'}</td>
465
+ <td style={{ padding: '10px 12px', color: 'var(--text-secondary)' }}>{d?.dailyTokens?.toLocaleString() || 'โ€”'}</td>
466
+ <td style={{ padding: '10px 12px', color: 'var(--text-secondary)' }}>{d?.weeklyTokens?.toLocaleString() || 'โ€”'}</td>
467
+ </tr>
468
+ );
469
+ })}
470
+ {/* Totals row */}
471
+ <tr style={{ borderTop: '2px solid var(--border)', background: 'rgba(108, 92, 231, 0.05)' }}>
472
+ <td style={{ padding: '10px 12px', fontWeight: 700, color: '#fff' }}>Total</td>
473
+ <td style={{ padding: '10px 12px', fontWeight: 700, color: '#fff' }}>
474
+ ${(
475
+ (usage?.breakdown.gemini.dailyUsd || 0) +
476
+ (usage?.breakdown.claude.dailyUsd || 0) +
477
+ (usage?.breakdown.codex.dailyUsd || 0) +
478
+ (usage?.breakdown.openrouter?.dailyUsd || 0)
479
+ ).toFixed(4)}
480
+ </td>
481
+ <td style={{ padding: '10px 12px', fontWeight: 700, color: '#fff' }}>
482
+ ${(
483
+ (usage?.breakdown.gemini.weeklyUsd || 0) +
484
+ (usage?.breakdown.claude.weeklyUsd || 0) +
485
+ (usage?.breakdown.codex.weeklyUsd || 0) +
486
+ (usage?.breakdown.openrouter?.weeklyUsd || 0)
487
+ ).toFixed(4)}
488
+ </td>
489
+ <td style={{ padding: '10px 12px', fontWeight: 700, color: 'var(--text-secondary)' }}>
490
+ {(
491
+ (usage?.breakdown.gemini.dailyTokens || 0) +
492
+ (usage?.breakdown.claude.dailyTokens || 0) +
493
+ (usage?.breakdown.codex.dailyTokens || 0) +
494
+ (usage?.breakdown.openrouter?.dailyTokens || 0)
495
+ ).toLocaleString()}
496
+ </td>
497
+ <td style={{ padding: '10px 12px', fontWeight: 700, color: 'var(--text-secondary)' }}>
498
+ {(
499
+ (usage?.breakdown.gemini.weeklyTokens || 0) +
500
+ (usage?.breakdown.claude.weeklyTokens || 0) +
501
+ (usage?.breakdown.codex.weeklyTokens || 0) +
502
+ (usage?.breakdown.openrouter?.weeklyTokens || 0)
503
+ ).toLocaleString()}
504
+ </td>
505
+ </tr>
506
+ </tbody>
507
+ </table>
255
508
  </div>
256
509
  </div>
257
510
 
@@ -9,6 +9,15 @@ export interface ChatMessage {
9
9
  currentVersionIndex?: number
10
10
  }
11
11
 
12
+ export interface GeminiModelInfo {
13
+ id: string
14
+ displayName: string
15
+ pricing?: {
16
+ prompt: string
17
+ completion: string
18
+ }
19
+ }
20
+
12
21
  export interface MemoryNode {
13
22
  slug: string
14
23
  title: string
@@ -119,6 +128,18 @@ export interface UsageData {
119
128
  gemini: UsageBreakdown
120
129
  claude: UsageBreakdown
121
130
  codex: UsageBreakdown
131
+ openrouter?: UsageBreakdown
132
+ }
133
+ timeseries?: {
134
+ [dateStr: string]: {
135
+ [modelId: string]: {
136
+ input: number
137
+ output: number
138
+ tokens: number
139
+ provider: string
140
+ cost: number
141
+ }
142
+ }
122
143
  }
123
144
  }
124
145
 
@@ -161,6 +182,10 @@ export interface ConfigJson {
161
182
  brain?: {
162
183
  preferred_agent?: string
163
184
  local_endpoint?: string
185
+ openrouter_model?: string
186
+ gemini_model?: string
187
+ claude_model?: string
188
+ openai_model?: string
164
189
  }
165
190
  secrets?: {
166
191
  google_api_key?: string
@@ -171,6 +196,7 @@ export interface ConfigJson {
171
196
  exa_api_key?: string
172
197
  serper_api_key?: string
173
198
  github_token?: string
199
+ openrouter_api_key?: string
174
200
  }
175
201
  }
176
202
 
@@ -180,3 +206,137 @@ export interface UpdateCheckResult {
180
206
  updateAvailable: boolean
181
207
  }
182
208
 
209
+ // โ”€โ”€โ”€ Instruction Surface Types โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
210
+
211
+ export interface InstructionSurface {
212
+ name: string
213
+ filename: string
214
+ size: number
215
+ lastCompiled: string
216
+ active: boolean
217
+ content?: string
218
+ }
219
+
220
+ export interface InstructionsData {
221
+ surfaces: InstructionSurface[]
222
+ lastCompileTimestamp: string
223
+ totalNodes: number
224
+ }
225
+
226
+ // โ”€โ”€โ”€ Skill Detail Types โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
227
+
228
+ export interface SkillSubFile {
229
+ name: string
230
+ path: string
231
+ size: number
232
+ modified: string
233
+ isDirectory: boolean
234
+ }
235
+
236
+ export interface SkillDetail {
237
+ name: string
238
+ description: string
239
+ content: string
240
+ subFiles: {
241
+ scripts: SkillSubFile[]
242
+ references: SkillSubFile[]
243
+ evals: SkillSubFile[]
244
+ subagents: SkillSubFile[]
245
+ }
246
+ evalsStatus?: {
247
+ total: number
248
+ passed: number
249
+ failed: number
250
+ }
251
+ }
252
+
253
+ // โ”€โ”€โ”€ OKF Types โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
254
+
255
+ export interface OkfLintResult {
256
+ slug: string
257
+ field: string
258
+ severity: 'warning' | 'error'
259
+ message: string
260
+ }
261
+
262
+ export interface OkfExportOptions {
263
+ stripSsss: boolean
264
+ format: 'directory' | 'tar.gz'
265
+ scope: 'global' | 'project' | 'all'
266
+ }
267
+
268
+ // โ”€โ”€โ”€ OpenWiki Types โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
269
+
270
+ export interface OpenWikiNode {
271
+ slug: string
272
+ title: string
273
+ category: string
274
+ tags: string[]
275
+ body: string
276
+ source: string
277
+ }
278
+
279
+ // โ”€โ”€โ”€ UCW Bundle Types (SSSS ยง16 โ€” UltraChat Workspace) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
280
+
281
+ export interface UcwBundleManifest {
282
+ name: string
283
+ description: string
284
+ version: string
285
+ exported_at: string
286
+ ssss_core_version: string
287
+ required_extensions: string[]
288
+ export_profile: 'backup' | 'template' | 'sale'
289
+ primitive_inventory: Record<string, number>
290
+ provisioning: UcwProvisioningStep[]
291
+ parameters?: UcwParameter[]
292
+ source_workspace_id?: string
293
+ file_count: number
294
+ provenance: {
295
+ content_hash: string
296
+ exporter: string
297
+ signature?: string
298
+ }
299
+ }
300
+
301
+ export interface UcwProvisioningStep {
302
+ type: string
303
+ target: string
304
+ action: string
305
+ installMode?: 'optional' | 'recommended' | 'required'
306
+ }
307
+
308
+ export interface UcwParameter {
309
+ name: string
310
+ description: string
311
+ required: boolean
312
+ default?: string
313
+ }
314
+
315
+ export interface UcwBundle {
316
+ manifest: UcwBundleManifest
317
+ branding?: Record<string, string>
318
+ files: Array<{ path: string; content: string; frontmatter?: Record<string, unknown> }>
319
+ }
320
+
321
+ // โ”€โ”€โ”€ Design Docs Types โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
322
+
323
+ export interface DesignDoc {
324
+ name: string
325
+ path: string
326
+ size: number
327
+ modified: string
328
+ category: 'in-progress' | 'completed' | 'planned' | 'archived' | 'backlog' | 'root'
329
+ content?: string
330
+ }
331
+
332
+ // โ”€โ”€โ”€ Memory Stats Types โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
333
+
334
+ export interface MemoryVaultStats {
335
+ total: number
336
+ byCategory: Record<string, number>
337
+ byPriority: Record<string, number>
338
+ byStatus: Record<string, number>
339
+ avgImportance: number
340
+ avgConfidence: number
341
+ }
342
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "total-recall-brain",
3
- "version": "3.12.2",
3
+ "version": "3.13.0",
4
4
  "description": "Sovereign AI system โ€” database-free, SSSS-powered personal intelligence engine deployable to any POSIX host.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -716,6 +716,8 @@ You are operating in a lightweight text-only mode. Answer the user's questions d
716
716
  messages: currentMessages,
717
717
  response: finalMessage,
718
718
  tokens: promptTokens + completionTokens,
719
+ input_tokens: promptTokens,
720
+ output_tokens: completionTokens,
719
721
  brain_id: brainId || 'global'
720
722
  });
721
723
  // Emit latency + tokens so the watchdog log monitor can react to anomalies.
@@ -37,7 +37,7 @@ export function loadSecurityConfig() {
37
37
  api: { pats: [], allow_static_pats: false },
38
38
  network: { require_https: true, public_health: false, allowed_origins: [], trusted_proxies: [] },
39
39
  bind: { host: '127.0.0.1', port: 3000, allow_public_bind: false },
40
- rate_limits: { api_requests_per_minute: 60 },
40
+ rate_limits: { api_requests_per_minute: 1200 },
41
41
  sandbox: { enabled: false }
42
42
  };
43
43
  }
@@ -50,7 +50,7 @@ export function loadSecurityConfig() {
50
50
 
51
51
  export function apiRateLimiter() {
52
52
  const config = loadSecurityConfig();
53
- const limit = config.rate_limits?.api_requests_per_minute || 60;
53
+ const limit = config.rate_limits?.api_requests_per_minute || 1200;
54
54
  return rateLimit({
55
55
  windowMs: 60 * 1000,
56
56
  max: limit,