// Mock data for the prototype const TOOLS = ['claude-code', 'opencode', 'crush', 'pi', 'kimi']; const HOSTS = ['laptop', 'workpc']; // Authenticated identity. Server derives `owner` from this on every ingest; // the wire format has no `owner` field. Admins can switch with ?owner= const ME = { user: 'bigbes', isAdmin: true, via: 'forward-auth' }; // Other owners (visible only when current user is admin and ?owner=* is set) const OWNERS = [ { user: 'bigbes', sessions: 121, ktok: 482, last: 'Today 14:22', via: 'forward-auth' }, { user: 'rin', sessions: 34, ktok: 61, last: 'Today 09:51', via: 'oidc-bearer' }, { user: 'noor', sessions: 18, ktok: 22, last: 'Apr 23 18:14', via: 'oidc-bearer' }, ]; const TOOL_COLORS = { 'claude-code': '#c96442', 'opencode': '#3b6e3b', 'crush': '#7a4ea8', 'pi': '#b8902a', 'kimi': '#2a6e9c', }; const SESSIONS = [ { id: 'a1', tool: 'claude-code', host: 'laptop', cwd: '~/code/tt-bundle', q: 'lockfile design for the tt bundle — should we hash inputs or outputs?', turns: 84, tok: '12.4k', when: 'Today 14:22', day: 0, model: 'claude-opus-4-7' }, { id: 'a2', tool: 'opencode', host: 'workpc', cwd: '~/work/atelier/migrations', q: 'why does the v3 migration drop the partial index on (org, ts)', turns: 12, tok: '2.1k', when: 'Today 11:08', day: 0, model: 'gpt-5-mini' }, { id: 'a3', tool: 'claude-code', host: 'workpc', cwd: '~/work/scarlet-svc', q: 'is there a way to make this retry loop tolerate 429s without a sleep', turns: 41, tok: '7.7k', when: 'Today 10:52', day: 0, model: 'claude-opus-4-7' }, { id: 'a4', tool: 'crush', host: 'laptop', cwd: '~/code/dotfiles', q: 'rewrite this zsh prompt to show ssh host only when on a remote', turns: 6, tok: '0.9k', when: 'Today 09:14', day: 0, model: 'gemini-2-flash' }, { id: 'a5', tool: 'pi', host: 'laptop', cwd: '~/notes', q: 'help me draft a one-pager for the offsite session on incident response', turns: 19, tok: '4.0k', when: 'Yest. 22:01', day: 1, model: 'inflection-3' }, { id: 'a6', tool: 'claude-code', host: 'laptop', cwd: '~/code/tt-bundle', q: 'when do we need to re-resolve transitive deps after a lock bump', turns: 28, tok: '5.3k', when: 'Yest. 18:40', day: 1, model: 'claude-opus-4-7' }, { id: 'a7', tool: 'kimi', host: 'workpc', cwd: '~/work/atelier', q: 'translate these comments to english but keep the tone', turns: 4, tok: '0.7k', when: 'Yest. 15:12', day: 1, model: 'kimi-k2' }, { id: 'a8', tool: 'claude-code', host: 'workpc', cwd: '~/work/scarlet-svc', q: 'add a structured logging adapter for the worker pool', turns: 22, tok: '4.8k', when: 'Yest. 11:30', day: 1, model: 'claude-opus-4-7' }, { id: 'a9', tool: 'opencode', host: 'workpc', cwd: '~/work/atelier/migrations', q: 'rollback strategy for v3 if we hit prod with the wrong index', turns: 8, tok: '1.4k', when: 'Apr 23 16:42', day: 2, model: 'gpt-5-mini' }, { id: 'a10', tool: 'claude-code', host: 'laptop', cwd: '~/code/tt-bundle', q: 'sketch a content-addressed cache for the build outputs', turns: 51, tok: '9.1k', when: 'Apr 23 09:18', day: 2, model: 'claude-opus-4-7' }, { id: 'a11', tool: 'pi', host: 'laptop', cwd: '~/notes', q: 'expand my standup notes into a proper status email', turns: 3, tok: '0.5k', when: 'Apr 22 19:00', day: 3, model: 'inflection-3' }, { id: 'a12', tool: 'crush', host: 'laptop', cwd: '~/code/dotfiles', q: 'fix this nvim mapping that breaks in tmux', turns: 4, tok: '0.6k', when: 'Apr 22 13:22', day: 3, model: 'gemini-2-flash' }, { id: 'a13', tool: 'claude-code', host: 'workpc', cwd: '~/work/scarlet-svc', q: 'redo the connection pool to support graceful drain', turns: 38, tok: '6.9k', when: 'Apr 21 14:00', day: 4, model: 'claude-opus-4-7' }, { id: 'a14', tool: 'claude-code', host: 'laptop', cwd: '~/code/tt-bundle', q: 'why does build.zig double-resolve when I touch only README', turns: 17, tok: '3.4k', when: 'Apr 21 10:11', day: 4, model: 'claude-opus-4-7' }, { id: 'a15', tool: 'opencode', host: 'workpc', cwd: '~/work/atelier', q: 'is there a cleaner way to express this state machine', turns: 11, tok: '1.8k', when: 'Apr 20 17:42', day: 5, model: 'gpt-5-mini' }, ]; // Project rollups const PROJECTS = [ { cwd: '~/code/tt-bundle', sessions: 41, tok: '213k', last: 'Today 14:22', topTool: 'claude-code', tok30: 213 }, { cwd: '~/work/scarlet-svc', sessions: 28, tok: '142k', last: 'Today 10:52', topTool: 'claude-code', tok30: 142 }, { cwd: '~/work/atelier/migrations', sessions: 14, tok: '38k', last: 'Today 11:08', topTool: 'opencode', tok30: 38 }, { cwd: '~/code/dotfiles', sessions: 7, tok: '4.1k', last: 'Today 09:14', topTool: 'crush', tok30: 4.1 }, { cwd: '~/notes', sessions: 5, tok: '11k', last: 'Yest 22:01', topTool: 'pi', tok30: 11 }, { cwd: '~/work/atelier', sessions: 4, tok: '2.0k', last: 'Yest 15:12', topTool: 'kimi', tok30: 2.0 }, ]; // Per-tool rollups (30d) const TOOL_ROLLUPS = [ { tool: 'claude-code', turns: 1820, ktok: 482, cost: 0, share: 0.92 }, { tool: 'opencode', turns: 211, ktok: 18, cost: 4.12, share: 0.18 }, { tool: 'crush', turns: 64, ktok: 2, cost: 0.71, share: 0.08 }, { tool: 'pi', turns: 32, ktok: 7, cost: 0, share: 0.05 }, { tool: 'kimi', turns: 11, ktok: 1, cost: 0, share: 0.02 }, ]; // Health collectors const COLLECTORS = [ { host: 'laptop', tool: 'claude-code', src: '~/.claude/projects', poll: '30s', lag: '4s', out: 0, last: 'now', ev: 412, status: 'ok' }, { host: 'laptop', tool: 'opencode', src: '~/.local/share/opencode', poll: '30s', lag: '12s', out: 0, last: '12s', ev: 38, status: 'ok' }, { host: 'laptop', tool: 'crush', src: '~/.cache/crush', poll: '60s', lag: '28s', out: 0, last: '28s', ev: 9, status: 'ok' }, { host: 'laptop', tool: 'pi', src: '~/.config/pi/history', poll: '60s', lag: '1m', out: 0, last: '1m', ev: 7, status: 'ok' }, { host: 'workpc', tool: 'claude-code', src: '~/.claude/projects', poll: '30s', lag: '7s', out: 0, last: 'now', ev: 228, status: 'ok' }, { host: 'workpc', tool: 'opencode', src: '~/.local/share/opencode', poll: '30s', lag: '18s', out: 0, last: '18s', ev: 54, status: 'ok' }, { host: 'workpc', tool: 'kimi', src: '~/.kimi-cli/history.jsonl', poll: '60s', lag: '2m', out: 127, last: '2m', ev: 3, status: 'warn' }, { host: 'workpc', tool: 'crush', src: '~/.cache/crush', poll: '60s', lag: '8m', out: 0, last: '8m', ev: 0, status: 'stale' }, ]; // Long sample turns for session view const TURNS = [ { role: 'user', text: 'lockfile design for the tt bundle — should we hash inputs or outputs?' }, { role: 'assistant', text: "Both, but for different reasons. Input hashes give you the 'should we rebuild' signal; output hashes give you the 'is this artifact equivalent' signal. The input hash lives in the lockfile next to each module entry; the output hash is computed post-build and stored in the artifact index.", model: 'claude-opus-4-7', tIn: 412, tOut: 980 }, { role: 'tool', text: 'Read("~/code/tt-bundle/build.zig")', kind: 'read' }, { role: 'tool', text: '→ 14.2 KB, 318 lines', kind: 'result' }, { role: 'assistant', text: "Looking at your current build.zig, the input set is already explicit — module list, compiler flags, target triple. So input-hashing is straightforward: sha256 over the canonical-JSON encoding of (module_list, flags, target).", model: 'claude-opus-4-7', tIn: 530, tOut: 1240 }, { role: 'user', text: 'ok so what about transitive deps that come from a vendored dir' }, { role: 'assistant', text: "Transitive deps need a separate manifest pass. The lockfile records the *resolved* tree, not the declared one — so when you bump a direct dep you re-resolve and rewrite the transitive section. The cache key is sha256(direct + transitive sorted).", model: 'claude-opus-4-7', tIn: 88, tOut: 760 }, { role: 'tool', text: 'Grep("vendor/", glob: "*.zig")', kind: 'grep' }, { role: 'tool', text: '→ 47 matches across 12 files', kind: 'result' }, { role: 'assistant', text: "Three of those are header-only style — they don't have their own deps. The other nine each declare 1–4 transitives. So your lock should expand to ~30 entries on first resolve.", model: 'claude-opus-4-7', tIn: 220, tOut: 540 }, { role: 'user', text: 'how would the cache key look if I bumped one transitive but nothing else changed' }, { role: 'assistant', text: "sha256 over the sorted list, so any change ripples up — but only the affected output keys flip. Direct deps that didn't move keep their input hash; their *output* hash also doesn't move because output depends on the resolved-tree slice they actually consume.", model: 'claude-opus-4-7', tIn: 110, tOut: 880 }, ]; // Saved searches per project (for tt-bundle) const SAVED_SEARCHES = ['lockfile', 'retry/backoff', 'vendored deps', 'build.zig', 'transitive']; // Filter dimensions for popover const FILTER_DIMS = { tool: ['any', 'claude-code', 'opencode', 'crush', 'pi', 'kimi'], host: ['any', 'laptop', 'workpc'], since: ['24h', '7d', '30d', '90d', 'all'], model: ['any', 'claude-opus-4-7', 'gpt-5-mini', 'gemini-2-flash', 'inflection-3', 'kimi-k2'], }; // Auth config (mirrors internal/config Auth substruct) const AUTH_CONFIG = { bind: '127.0.0.1:8401', allowedUsers: ['bigbes', 'rin', 'noor'], admins: ['bigbes'], forwardAuth: { enabled: true, userHeader: 'Remote-User' }, oidc: { enabled: true, issuer: 'https://auth.example.com', audience: 'lethe', usernameClaim: 'preferred_username', jwksLastFetch: '4m ago' }, }; // Recent auth events for the Auth settings panel const AUTH_EVENTS = [ { t: '14:22:14', user: 'bigbes', via: 'forward-auth', path: 'GET /api/v1/sessions', status: 200 }, { t: '14:18:02', user: 'rin', via: 'oidc-bearer', path: 'POST /api/v1/ingest', status: 200 }, { t: '14:11:48', user: 'rin', via: 'oidc-bearer', path: 'GET /api/v1/sessions/.../...', status: 200 }, { t: '13:54:30', user: '—', via: 'oidc-bearer', path: 'POST /api/v1/ingest', status: 401, note: 'expired jwt' }, { t: '13:40:19', user: 'noor', via: 'oidc-bearer', path: 'GET /api/v1/sessions?owner=*', status: 403, note: 'admin-only param' }, { t: '13:02:11', user: 'bigbes', via: 'forward-auth', path: 'GET /api/v1/sessions?owner=rin', status: 200 }, ]; Object.assign(window, { TOOLS, HOSTS, TOOL_COLORS, SESSIONS, PROJECTS, TOOL_ROLLUPS, COLLECTORS, TURNS, SAVED_SEARCHES, FILTER_DIMS, ME, OWNERS, AUTH_CONFIG, AUTH_EVENTS, });