@geminilight/mindos 0.2.1 → 0.4.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.
Files changed (82) hide show
  1. package/app/app/api/init/route.ts +7 -41
  2. package/app/app/api/mcp/agents/route.ts +72 -0
  3. package/app/app/api/mcp/install/route.ts +95 -0
  4. package/app/app/api/mcp/status/route.ts +47 -0
  5. package/app/app/api/settings/route.ts +3 -0
  6. package/app/app/api/setup/generate-token/route.ts +23 -0
  7. package/app/app/api/setup/route.ts +81 -0
  8. package/app/app/api/skills/route.ts +208 -0
  9. package/app/app/api/sync/route.ts +54 -3
  10. package/app/app/api/update-check/route.ts +52 -0
  11. package/app/app/globals.css +12 -0
  12. package/app/app/layout.tsx +4 -2
  13. package/app/app/login/page.tsx +20 -13
  14. package/app/app/page.tsx +22 -2
  15. package/app/app/setup/page.tsx +9 -0
  16. package/app/app/view/[...path]/ViewPageClient.tsx +47 -21
  17. package/app/app/view/[...path]/loading.tsx +1 -1
  18. package/app/app/view/[...path]/not-found.tsx +101 -0
  19. package/app/components/AskFab.tsx +1 -1
  20. package/app/components/AskModal.tsx +1 -1
  21. package/app/components/Backlinks.tsx +1 -1
  22. package/app/components/Breadcrumb.tsx +13 -3
  23. package/app/components/CsvView.tsx +5 -6
  24. package/app/components/DirView.tsx +42 -21
  25. package/app/components/FindInPage.tsx +211 -0
  26. package/app/components/HomeContent.tsx +97 -44
  27. package/app/components/JsonView.tsx +1 -2
  28. package/app/components/MarkdownEditor.tsx +1 -2
  29. package/app/components/OnboardingView.tsx +6 -7
  30. package/app/components/SettingsModal.tsx +5 -2
  31. package/app/components/SetupWizard.tsx +479 -0
  32. package/app/components/Sidebar.tsx +1 -1
  33. package/app/components/UpdateBanner.tsx +101 -0
  34. package/app/components/renderers/{AgentInspectorRenderer.tsx → agent-inspector/AgentInspectorRenderer.tsx} +13 -11
  35. package/app/components/renderers/agent-inspector/manifest.ts +14 -0
  36. package/app/components/renderers/{BacklinksRenderer.tsx → backlinks/BacklinksRenderer.tsx} +6 -6
  37. package/app/components/renderers/backlinks/manifest.ts +14 -0
  38. package/app/components/renderers/config/manifest.ts +14 -0
  39. package/app/components/renderers/csv/BoardView.tsx +12 -12
  40. package/app/components/renderers/csv/ConfigPanel.tsx +7 -8
  41. package/app/components/renderers/{CsvRenderer.tsx → csv/CsvRenderer.tsx} +8 -9
  42. package/app/components/renderers/csv/GalleryView.tsx +3 -3
  43. package/app/components/renderers/csv/TableView.tsx +4 -5
  44. package/app/components/renderers/csv/manifest.ts +14 -0
  45. package/app/components/renderers/{DiffRenderer.tsx → diff/DiffRenderer.tsx} +10 -9
  46. package/app/components/renderers/diff/manifest.ts +14 -0
  47. package/app/components/renderers/{GraphRenderer.tsx → graph/GraphRenderer.tsx} +4 -5
  48. package/app/components/renderers/graph/manifest.ts +14 -0
  49. package/app/components/renderers/{SummaryRenderer.tsx → summary/SummaryRenderer.tsx} +6 -6
  50. package/app/components/renderers/summary/manifest.ts +14 -0
  51. package/app/components/renderers/{TimelineRenderer.tsx → timeline/TimelineRenderer.tsx} +6 -6
  52. package/app/components/renderers/timeline/manifest.ts +14 -0
  53. package/app/components/renderers/{TodoRenderer.tsx → todo/TodoRenderer.tsx} +2 -2
  54. package/app/components/renderers/todo/manifest.ts +14 -0
  55. package/app/components/renderers/{WorkflowRenderer.tsx → workflow/WorkflowRenderer.tsx} +13 -13
  56. package/app/components/renderers/workflow/manifest.ts +14 -0
  57. package/app/components/settings/McpTab.tsx +549 -0
  58. package/app/components/settings/SyncTab.tsx +139 -50
  59. package/app/components/settings/types.ts +1 -1
  60. package/app/data/pages/home.png +0 -0
  61. package/app/lib/i18n.ts +270 -10
  62. package/app/lib/renderers/index.ts +20 -89
  63. package/app/lib/renderers/registry.ts +4 -1
  64. package/app/lib/settings.ts +15 -1
  65. package/app/lib/template.ts +45 -0
  66. package/app/package.json +1 -0
  67. package/app/types/semver.d.ts +8 -0
  68. package/bin/cli.js +137 -24
  69. package/bin/lib/build.js +53 -18
  70. package/bin/lib/colors.js +3 -1
  71. package/bin/lib/config.js +4 -0
  72. package/bin/lib/constants.js +2 -0
  73. package/bin/lib/debug.js +10 -0
  74. package/bin/lib/startup.js +21 -20
  75. package/bin/lib/stop.js +41 -3
  76. package/bin/lib/sync.js +65 -53
  77. package/bin/lib/update-check.js +94 -0
  78. package/bin/lib/utils.js +2 -2
  79. package/package.json +1 -1
  80. package/scripts/gen-renderer-index.js +57 -0
  81. package/scripts/setup.js +117 -1
  82. /package/app/components/renderers/{ConfigRenderer.tsx → config/ConfigRenderer.tsx} +0 -0
@@ -128,10 +128,10 @@ function OpCard({ op }: { op: AgentOp }) {
128
128
  onClick={() => setExpanded(v => !v)}
129
129
  >
130
130
  {/* kind badge */}
131
- <span style={{
131
+ <span className="font-display" style={{
132
132
  display: 'inline-flex', alignItems: 'center', gap: 4,
133
133
  padding: '2px 8px', borderRadius: 999, fontSize: '0.68rem',
134
- fontFamily: "'IBM Plex Mono',monospace", fontWeight: 600,
134
+ fontWeight: 600,
135
135
  background: style.bg, color: style.text, border: `1px solid ${style.border}`,
136
136
  flexShrink: 0,
137
137
  }}>
@@ -140,16 +140,17 @@ function OpCard({ op }: { op: AgentOp }) {
140
140
  </span>
141
141
 
142
142
  {/* tool name */}
143
- <span style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.78rem', color: 'var(--foreground)', fontWeight: 600, flexShrink: 0 }}>
143
+ <span className="font-display" style={{ fontSize: '0.78rem', color: 'var(--foreground)', fontWeight: 600, flexShrink: 0 }}>
144
144
  {toolShort}
145
145
  </span>
146
146
 
147
147
  {/* file path */}
148
148
  {filePath && (
149
149
  <span
150
- style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.72rem', color: 'var(--amber)', flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', cursor: 'pointer' }}
150
+ style={{ fontSize: '0.72rem', color: 'var(--amber)', flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', cursor: 'pointer' }}
151
151
  onClick={e => { e.stopPropagation(); router.push('/view/' + filePath.split('/').map(encodeURIComponent).join('/')); }}
152
152
  title={filePath}
153
+ className="font-display"
153
154
  >
154
155
  {filePath}
155
156
  </span>
@@ -162,7 +163,7 @@ function OpCard({ op }: { op: AgentOp }) {
162
163
  : <AlertCircle size={13} style={{ color: '#c85050' }} />
163
164
  }
164
165
  {/* timestamp */}
165
- <span style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.68rem', color: 'var(--muted-foreground)', opacity: 0.6 }} title={formatTs(op.ts)}>
166
+ <span className="font-display" style={{ fontSize: '0.68rem', color: 'var(--muted-foreground)', opacity: 0.6 }} title={formatTs(op.ts)}>
166
167
  {relativeTs(op.ts)}
167
168
  </span>
168
169
  {/* chevron */}
@@ -177,10 +178,10 @@ function OpCard({ op }: { op: AgentOp }) {
177
178
  <div style={{ display: 'flex', flexDirection: 'column', gap: 4, marginBottom: op.message ? 8 : 0 }}>
178
179
  {Object.entries(op.params).map(([k, v]) => (
179
180
  <div key={k} style={{ display: 'flex', gap: 8, alignItems: 'flex-start' }}>
180
- <span style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.68rem', color: 'var(--muted-foreground)', opacity: 0.7, flexShrink: 0, minWidth: 80 }}>
181
+ <span className="font-display" style={{ fontSize: '0.68rem', color: 'var(--muted-foreground)', opacity: 0.7, flexShrink: 0, minWidth: 80 }}>
181
182
  {k}
182
183
  </span>
183
- <span style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.72rem', color: 'var(--foreground)', wordBreak: 'break-all', lineHeight: 1.5 }}>
184
+ <span className="font-display" style={{ fontSize: '0.72rem', color: 'var(--foreground)', wordBreak: 'break-all', lineHeight: 1.5 }}>
184
185
  {truncateContent(v)}
185
186
  </span>
186
187
  </div>
@@ -188,7 +189,7 @@ function OpCard({ op }: { op: AgentOp }) {
188
189
  </div>
189
190
  {/* result message */}
190
191
  {op.message && (
191
- <div style={{ marginTop: 6, padding: '5px 9px', borderRadius: 5, fontSize: '0.72rem', fontFamily: "'IBM Plex Mono',monospace",
192
+ <div className="font-display" style={{ marginTop: 6, padding: '5px 9px', borderRadius: 5, fontSize: '0.72rem',
192
193
  background: op.result === 'error' ? 'rgba(200,80,80,0.08)' : 'rgba(122,173,128,0.08)',
193
194
  color: op.result === 'error' ? '#c85050' : '#7aad80',
194
195
  border: `1px solid ${op.result === 'error' ? 'rgba(200,80,80,0.2)' : 'rgba(122,173,128,0.2)'}`,
@@ -197,7 +198,7 @@ function OpCard({ op }: { op: AgentOp }) {
197
198
  </div>
198
199
  )}
199
200
  {/* absolute timestamp */}
200
- <div style={{ marginTop: 6, fontSize: '0.65rem', fontFamily: "'IBM Plex Mono',monospace", color: 'var(--muted-foreground)', opacity: 0.5 }}>
201
+ <div className="font-display" style={{ marginTop: 6, fontSize: '0.65rem', color: 'var(--muted-foreground)', opacity: 0.5 }}>
201
202
  {formatTs(op.ts)}
202
203
  </div>
203
204
  </div>
@@ -231,7 +232,7 @@ export function AgentInspectorRenderer({ content }: RendererContext) {
231
232
 
232
233
  if (ops.length === 0) {
233
234
  return (
234
- <div style={{ padding: '3rem 1rem', textAlign: 'center', color: 'var(--muted-foreground)', fontFamily: "'IBM Plex Mono',monospace", fontSize: 12 }}>
235
+ <div className="font-display" style={{ padding: '3rem 1rem', textAlign: 'center', color: 'var(--muted-foreground)', fontSize: 12 }}>
235
236
  <Terminal size={28} style={{ margin: '0 auto 10px', opacity: 0.3 }} />
236
237
  <p>No agent operations logged yet.</p>
237
238
  <p style={{ marginTop: 6, opacity: 0.6, fontSize: 11 }}>
@@ -254,10 +255,11 @@ export function AgentInspectorRenderer({ content }: RendererContext) {
254
255
  <button
255
256
  key={k}
256
257
  onClick={() => setFilter(k)}
258
+ className="font-display"
257
259
  style={{
258
260
  display: 'inline-flex', alignItems: 'center', gap: 4,
259
261
  padding: '3px 10px', borderRadius: 999, fontSize: '0.7rem',
260
- fontFamily: "'IBM Plex Mono',monospace", cursor: 'pointer', border: 'none',
262
+ cursor: 'pointer', border: 'none',
261
263
  background: active ? (style?.bg ?? 'var(--accent)') : 'var(--muted)',
262
264
  color: active ? (style?.text ?? 'var(--foreground)') : 'var(--muted-foreground)',
263
265
  outline: active ? `1px solid ${style?.border ?? 'var(--border)'}` : 'none',
@@ -0,0 +1,14 @@
1
+ import type { RendererDefinition } from '@/lib/renderers/registry';
2
+
3
+ export const manifest: RendererDefinition = {
4
+ id: 'agent-inspector',
5
+ name: 'Agent Inspector',
6
+ description: 'Visualizes agent tool-call logs as a filterable timeline. Auto-activates on .agent-log.json (JSON Lines format).',
7
+ author: 'MindOS',
8
+ icon: '🔍',
9
+ tags: ['agent', 'inspector', 'log', 'mcp', 'tools'],
10
+ builtin: true,
11
+ entryPath: '.agent-log.json',
12
+ match: ({ filePath }) => /\.agent-log\.json$/i.test(filePath),
13
+ load: () => import('./AgentInspectorRenderer').then(m => ({ default: m.AgentInspectorRenderer })),
14
+ };
@@ -51,7 +51,7 @@ export function BacklinksRenderer({ filePath }: RendererContext) {
51
51
 
52
52
  if (loading) {
53
53
  return (
54
- <div style={{ padding: '3rem 1rem', textAlign: 'center', fontFamily: "'IBM Plex Mono',monospace", fontSize: 12, color: 'var(--muted-foreground)' }}>
54
+ <div className="font-display" style={{ padding: '3rem 1rem', textAlign: 'center', fontSize: 12, color: 'var(--muted-foreground)' }}>
55
55
  Scanning backlinks…
56
56
  </div>
57
57
  );
@@ -63,7 +63,7 @@ export function BacklinksRenderer({ filePath }: RendererContext) {
63
63
  <div style={{ maxWidth: 720, margin: '0 auto', padding: '1.5rem 0' }}>
64
64
  {/* header */}
65
65
  <div style={{ marginBottom: '1.5rem', display: 'flex', alignItems: 'center', gap: 8 }}>
66
- <span style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: 11, color: 'var(--muted-foreground)' }}>
66
+ <span className="font-display" style={{ fontSize: 11, color: 'var(--muted-foreground)' }}>
67
67
  {items.length === 0 ? 'No backlinks found' : `${items.length} file${items.length === 1 ? '' : 's'} link here`}
68
68
  </span>
69
69
  </div>
@@ -78,7 +78,7 @@ export function BacklinksRenderer({ filePath }: RendererContext) {
78
78
  fontSize: 13,
79
79
  }}>
80
80
  <FileText size={28} style={{ margin: '0 auto 10px', opacity: 0.3 }} />
81
- <p style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: 12 }}>
81
+ <p className="font-display" style={{ fontSize: 12 }}>
82
82
  No other files link to <strong style={{ color: 'var(--foreground)' }}>{basename(filePath)}</strong> yet.
83
83
  </p>
84
84
  </div>
@@ -112,11 +112,11 @@ export function BacklinksRenderer({ filePath }: RendererContext) {
112
112
  background: 'var(--muted)',
113
113
  }}>
114
114
  <FileText size={13} style={{ color: 'var(--muted-foreground)', flexShrink: 0 }} />
115
- <span style={{ fontFamily: "'IBM Plex Sans',sans-serif", fontWeight: 600, fontSize: '0.85rem', color: 'var(--foreground)', flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
115
+ <span style={{ fontWeight: 600, fontSize: '0.85rem', color: 'var(--foreground)', flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
116
116
  {name}
117
117
  </span>
118
118
  {dir && (
119
- <span style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.68rem', color: 'var(--muted-foreground)', opacity: 0.6, flexShrink: 0 }}>
119
+ <span className="font-display" style={{ fontSize: '0.68rem', color: 'var(--muted-foreground)', opacity: 0.6, flexShrink: 0 }}>
120
120
  {dir}
121
121
  </span>
122
122
  )}
@@ -131,7 +131,7 @@ export function BacklinksRenderer({ filePath }: RendererContext) {
131
131
  background: 'var(--background)',
132
132
  }}>
133
133
  {snippet.split('\n').map((line: string, j: number) => (
134
- <div key={j} style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.72rem', color: 'var(--muted-foreground)', lineHeight: 1.6, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
134
+ <div key={j} className="font-display" style={{ fontSize: '0.72rem', color: 'var(--muted-foreground)', lineHeight: 1.6, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
135
135
  <SnippetLine text={line} />
136
136
  </div>
137
137
  ))}
@@ -0,0 +1,14 @@
1
+ import type { RendererDefinition } from '@/lib/renderers/registry';
2
+
3
+ export const manifest: RendererDefinition = {
4
+ id: 'backlinks',
5
+ name: 'Backlinks Explorer',
6
+ description: 'Shows all files that link to the current page via wikilinks or markdown links, with highlighted snippet context.',
7
+ author: 'MindOS',
8
+ icon: '🔗',
9
+ tags: ['backlinks', 'wiki', 'links', 'references'],
10
+ builtin: true,
11
+ entryPath: 'BACKLINKS.md',
12
+ match: ({ filePath }) => /\bBACKLINKS\b.*\.md$/i.test(filePath),
13
+ load: () => import('./BacklinksRenderer').then(m => ({ default: m.BacklinksRenderer })),
14
+ };
@@ -0,0 +1,14 @@
1
+ import type { RendererDefinition } from '@/lib/renderers/registry';
2
+
3
+ export const manifest: RendererDefinition = {
4
+ id: 'config-panel',
5
+ name: 'Config Panel',
6
+ description: 'Renders CONFIG.json as an editable control panel based on uiSchema/keySpecs. Changes are written back to the JSON file directly.',
7
+ author: 'MindOS',
8
+ icon: '🧩',
9
+ tags: ['config', 'json', 'settings', 'schema'],
10
+ builtin: true,
11
+ entryPath: 'CONFIG.json',
12
+ match: ({ filePath, extension }) => extension === 'json' && /(^|\/)CONFIG\.json$/i.test(filePath),
13
+ load: () => import('./ConfigRenderer').then(m => ({ default: m.ConfigRenderer })),
14
+ };
@@ -50,7 +50,7 @@ export function BoardView({ headers, rows, cfg, saveAction }: {
50
50
  <div className="flex-shrink-0 w-64 flex flex-col gap-2">
51
51
  <div className="flex items-center gap-2 px-1 py-1.5">
52
52
  <span className="w-2.5 h-2.5 rounded-full shrink-0" style={{ background: tc.text }} />
53
- <span className="text-xs font-semibold uppercase tracking-wider truncate" style={{ color: tc.text, fontFamily: "'IBM Plex Mono',monospace" }}>{group}</span>
53
+ <span className="text-xs font-semibold uppercase tracking-wider truncate font-display" style={{ color: tc.text }}>{group}</span>
54
54
  <span className="text-xs ml-auto shrink-0" style={{ color: 'var(--muted-foreground)', opacity: 0.5 }}>{cards.length}</span>
55
55
  </div>
56
56
  <div
@@ -74,14 +74,14 @@ export function BoardView({ headers, rows, cfg, saveAction }: {
74
74
  className="rounded-lg border p-3 flex flex-col gap-1.5 cursor-grab active:cursor-grabbing hover:bg-muted/50 transition-colors"
75
75
  style={{ borderColor: 'var(--border)', background: 'var(--card)' }}
76
76
  >
77
- <p className="text-sm font-medium leading-snug" style={{ color: 'var(--foreground)', fontFamily: "'IBM Plex Sans',sans-serif" }}>{title}</p>
77
+ <p className="text-sm font-medium leading-snug" style={{ color: 'var(--foreground)' }}>{title}</p>
78
78
  {desc && <p className="text-xs leading-relaxed line-clamp-2" style={{ color: 'var(--muted-foreground)' }}>{desc}</p>}
79
79
  <div className="flex flex-wrap gap-1 mt-0.5">
80
80
  {headers.map((h, ci) => {
81
81
  if (ci === groupIdx || ci === titleIdx || ci === descIdx) return null;
82
82
  const v = row[ci]; if (!v) return null;
83
- return <span key={ci} className="text-[10px] px-1.5 py-0.5 rounded"
84
- style={{ background: 'var(--muted)', color: 'var(--muted-foreground)', fontFamily: "'IBM Plex Mono',monospace" }}
83
+ return <span key={ci} className="text-[10px] px-1.5 py-0.5 rounded font-display"
84
+ style={{ background: 'var(--muted)', color: 'var(--muted-foreground)' }}
85
85
  >{h}: {v}</span>;
86
86
  })}
87
87
  </div>
@@ -115,27 +115,27 @@ export function BoardView({ headers, rows, cfg, saveAction }: {
115
115
  if (e.key === 'Escape') { setNewColInput(''); setShowNewCol(false); }
116
116
  }}
117
117
  placeholder="Column name…"
118
- className="text-xs bg-transparent outline-none w-full"
119
- style={{ color: 'var(--foreground)', borderBottom: '1px solid var(--amber)', fontFamily: "'IBM Plex Mono',monospace" }}
118
+ className="text-xs bg-transparent outline-none w-full font-display"
119
+ style={{ color: 'var(--foreground)', borderBottom: '1px solid var(--amber)' }}
120
120
  />
121
121
  <div className="flex gap-2">
122
122
  <button onClick={() => {
123
123
  setNewColInput('');
124
124
  setShowNewCol(false);
125
125
  }}
126
- className="text-xs px-2 py-1 rounded"
127
- style={{ background: 'var(--amber)', color: '#131210', fontFamily: "'IBM Plex Mono',monospace" }}
126
+ className="text-xs px-2 py-1 rounded font-display"
127
+ style={{ background: 'var(--amber)', color: '#131210' }}
128
128
  >Create</button>
129
129
  <button onClick={() => { setNewColInput(''); setShowNewCol(false); }}
130
- className="text-xs px-2 py-1 rounded"
131
- style={{ color: 'var(--muted-foreground)', fontFamily: "'IBM Plex Mono',monospace" }}
130
+ className="text-xs px-2 py-1 rounded font-display"
131
+ style={{ color: 'var(--muted-foreground)' }}
132
132
  >Cancel</button>
133
133
  </div>
134
134
  </div>
135
135
  ) : (
136
136
  <button onClick={() => setShowNewCol(true)}
137
- className="flex items-center gap-1.5 text-xs px-3 py-2 rounded-xl border border-dashed w-full transition-colors hover:bg-muted"
138
- style={{ borderColor: 'var(--border)', color: 'var(--muted-foreground)', fontFamily: "'IBM Plex Mono',monospace" }}
137
+ className="flex items-center gap-1.5 text-xs px-3 py-2 rounded-xl border border-dashed w-full transition-colors hover:bg-muted font-display"
138
+ style={{ borderColor: 'var(--border)', color: 'var(--muted-foreground)' }}
139
139
  >
140
140
  <Plus size={12} /> Add column
141
141
  </button>
@@ -10,15 +10,15 @@ export function ConfigPanel({ headers, cfg, view, onClose, onChange }: {
10
10
  onClose: () => void;
11
11
  onChange: (cfg: CsvConfig) => void;
12
12
  }) {
13
- const labelStyle: React.CSSProperties = { color: 'var(--muted-foreground)', fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.72rem' };
14
- const selectStyle: React.CSSProperties = { background: 'var(--background)', color: 'var(--foreground)', borderColor: 'var(--border)', fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.72rem' };
13
+ const labelStyle: React.CSSProperties = { color: 'var(--muted-foreground)', fontSize: '0.72rem' };
14
+ const selectStyle: React.CSSProperties = { background: 'var(--background)', color: 'var(--foreground)', borderColor: 'var(--border)', fontSize: '0.72rem' };
15
15
 
16
16
  function FieldSelect({ label, value, onChange: onCh }: { label: string; value: string; onChange: (v: string) => void }) {
17
17
  return (
18
18
  <div className="flex items-center justify-between gap-2">
19
- <span style={labelStyle}>{label}</span>
19
+ <span className="font-display" style={labelStyle}>{label}</span>
20
20
  <select value={value} onChange={e => onCh(e.target.value)}
21
- className="rounded px-2 py-1 outline-none border" style={selectStyle}
21
+ className="rounded px-2 py-1 outline-none border font-display" style={selectStyle}
22
22
  >
23
23
  <option value="">— none —</option>
24
24
  {headers.map(h => <option key={h} value={h}>{h}</option>)}
@@ -47,9 +47,9 @@ export function ConfigPanel({ headers, cfg, view, onClose, onChange }: {
47
47
  <div className="flex rounded overflow-hidden border" style={{ borderColor: 'var(--border)' }}>
48
48
  {(['asc', 'desc'] as const).map(d => (
49
49
  <button key={d} onClick={() => onChange({ ...cfg, table: { ...cfg.table, sortDir: d } })}
50
- className="px-3 py-1 text-xs transition-colors"
50
+ className="px-3 py-1 text-xs transition-colors font-display"
51
51
  style={{
52
- fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.72rem',
52
+ fontSize: '0.72rem',
53
53
  background: cfg.table.sortDir === d ? 'var(--amber)' : 'var(--background)',
54
54
  color: cfg.table.sortDir === d ? '#131210' : 'var(--muted-foreground)',
55
55
  }}
@@ -77,9 +77,8 @@ export function ConfigPanel({ headers, cfg, view, onClose, onChange }: {
77
77
  : [...cfg.table.hiddenFields, h];
78
78
  onChange({ ...cfg, table: { ...cfg.table, hiddenFields: next } });
79
79
  }}
80
- className="text-[11px] px-2 py-0.5 rounded transition-colors"
80
+ className="text-[11px] px-2 py-0.5 rounded transition-colors font-display"
81
81
  style={{
82
- fontFamily: "'IBM Plex Mono',monospace",
83
82
  background: hidden ? 'var(--muted)' : 'var(--amber-dim)',
84
83
  color: hidden ? 'var(--muted-foreground)' : 'var(--amber)',
85
84
  }}
@@ -3,12 +3,12 @@
3
3
  import { useState, useMemo, useCallback, useEffect } from 'react';
4
4
  import { LayoutGrid, Columns, Table2, Settings2 } from 'lucide-react';
5
5
  import type { RendererContext } from '@/lib/renderers/registry';
6
- import type { ViewType, CsvConfig } from './csv/types';
7
- import { defaultConfig, loadConfig, saveConfig, parseCSV } from './csv/types';
8
- import { TableView } from './csv/TableView';
9
- import { GalleryView } from './csv/GalleryView';
10
- import { BoardView } from './csv/BoardView';
11
- import { ConfigPanel } from './csv/ConfigPanel';
6
+ import type { ViewType, CsvConfig } from './types';
7
+ import { defaultConfig, loadConfig, saveConfig, parseCSV } from './types';
8
+ import { TableView } from './TableView';
9
+ import { GalleryView } from './GalleryView';
10
+ import { BoardView } from './BoardView';
11
+ import { ConfigPanel } from './ConfigPanel';
12
12
 
13
13
  const VIEW_TABS: { id: ViewType; icon: React.ReactNode; label: string }[] = [
14
14
  { id: 'table', icon: <Table2 size={13} />, label: 'Table' },
@@ -42,9 +42,8 @@ export function CsvRenderer({ filePath, content, saveAction }: RendererContext)
42
42
  <div className="flex items-center gap-0.5 p-1 rounded-lg" style={{ background: 'var(--muted)' }}>
43
43
  {VIEW_TABS.map(tab => (
44
44
  <button key={tab.id} onClick={() => updateConfig({ ...cfg, activeView: tab.id })}
45
- className="flex items-center gap-1.5 px-3 py-1.5 rounded text-xs font-medium transition-colors"
45
+ className="flex items-center gap-1.5 px-3 py-1.5 rounded text-xs font-medium transition-colors font-display"
46
46
  style={{
47
- fontFamily: "'IBM Plex Mono',monospace",
48
47
  background: view === tab.id ? 'var(--card)' : 'transparent',
49
48
  color: view === tab.id ? 'var(--foreground)' : 'var(--muted-foreground)',
50
49
  boxShadow: view === tab.id ? '0 1px 3px rgba(0,0,0,0.1)' : 'none',
@@ -53,7 +52,7 @@ export function CsvRenderer({ filePath, content, saveAction }: RendererContext)
53
52
  ))}
54
53
  </div>
55
54
  <div className="flex-1" />
56
- <span className="text-xs" style={{ color: 'var(--muted-foreground)', fontFamily: "'IBM Plex Mono',monospace", opacity: 0.5 }}>
55
+ <span className="text-xs font-display" style={{ color: 'var(--muted-foreground)', opacity: 0.5 }}>
57
56
  {rows.length} rows
58
57
  </span>
59
58
  <div className="relative">
@@ -19,15 +19,15 @@ export function GalleryView({ headers, rows, cfg }: { headers: string[]; rows: s
19
19
  style={{ borderColor: 'var(--border)', background: 'var(--card)' }}
20
20
  >
21
21
  {tag && tc && <span className="self-start text-[11px] px-2 py-0.5 rounded-full font-medium"
22
- style={{ background: tc.bg, color: tc.text, fontFamily: "'IBM Plex Mono',monospace" }}>{tag}</span>}
23
- <p className="text-sm font-semibold leading-snug" style={{ color: 'var(--foreground)', fontFamily: "'IBM Plex Sans',sans-serif" }}>{title}</p>
22
+ style={{ background: tc.bg, color: tc.text }}>{tag}</span>}
23
+ <p className="text-sm font-semibold leading-snug" style={{ color: 'var(--foreground)' }}>{title}</p>
24
24
  {desc && <p className="text-xs leading-relaxed line-clamp-3" style={{ color: 'var(--muted-foreground)' }}>{desc}</p>}
25
25
  <div className="mt-1 flex flex-col gap-0.5">
26
26
  {headers.map((h, ci) => {
27
27
  if (ci === titleIdx || ci === descIdx || ci === tagIdx) return null;
28
28
  const v = row[ci]; if (!v) return null;
29
29
  return <div key={ci} className="flex items-baseline gap-1.5 text-xs">
30
- <span style={{ color: 'var(--muted-foreground)', opacity: 0.6, fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.68rem' }}>{h}</span>
30
+ <span className="font-display" style={{ color: 'var(--muted-foreground)', opacity: 0.6, fontSize: '0.68rem' }}>{h}</span>
31
31
  <span className="truncate" style={{ color: 'var(--muted-foreground)' }}>{v}</span>
32
32
  </div>;
33
33
  })}
@@ -71,7 +71,6 @@ export function TableView({ headers, rows, cfg, saveAction }: {
71
71
 
72
72
  const thStyle: React.CSSProperties = {
73
73
  borderBottom: '1px solid var(--border)',
74
- fontFamily: "'IBM Plex Sans',sans-serif",
75
74
  fontSize: '0.72rem',
76
75
  letterSpacing: '0.05em',
77
76
  textTransform: 'uppercase',
@@ -110,7 +109,7 @@ export function TableView({ headers, rows, cfg, saveAction }: {
110
109
  <td colSpan={visibleIndices.length + 1} className="px-4 py-1.5"
111
110
  style={{ background: 'var(--accent)', borderBottom: '1px solid var(--border)', borderTop: '1px solid var(--border)' }}
112
111
  >
113
- <span className="text-xs font-semibold" style={{ color: 'var(--muted-foreground)', fontFamily: "'IBM Plex Mono',monospace" }}>
112
+ <span className="text-xs font-semibold font-display" style={{ color: 'var(--muted-foreground)' }}>
114
113
  {section.key} · {section.rows.length}
115
114
  </span>
116
115
  </td>
@@ -147,15 +146,15 @@ export function TableView({ headers, rows, cfg, saveAction }: {
147
146
  </table>
148
147
  </div>
149
148
  <div className="px-4 py-2 flex items-center justify-between" style={{ background: 'var(--muted)', borderTop: '1px solid var(--border)' }}>
150
- <span className="text-xs" style={{ color: 'var(--muted-foreground)', fontFamily: "'IBM Plex Mono',monospace" }}>
149
+ <span className="text-xs font-display" style={{ color: 'var(--muted-foreground)' }}>
151
150
  {localRows.length} rows · {headers.length} cols
152
151
  </span>
153
152
  {!showAdd
154
153
  ? <button onClick={() => setShowAdd(true)} className="flex items-center gap-1 text-xs px-2.5 py-1 rounded-md"
155
- style={{ color: 'var(--amber)', background: 'var(--amber-dim)', fontFamily: "'IBM Plex Mono',monospace" }}
154
+ style={{ color: 'var(--amber)', background: 'var(--amber-dim)' }}
156
155
  ><Plus size={12} /> Add row</button>
157
156
  : <button onClick={() => setShowAdd(false)} className="text-xs px-2.5 py-1 rounded-md"
158
- style={{ color: 'var(--muted-foreground)', fontFamily: "'IBM Plex Mono',monospace" }}
157
+ style={{ color: 'var(--muted-foreground)' }}
159
158
  >Cancel</button>
160
159
  }
161
160
  </div>
@@ -0,0 +1,14 @@
1
+ import type { RendererDefinition } from '@/lib/renderers/registry';
2
+
3
+ export const manifest: RendererDefinition = {
4
+ id: 'csv',
5
+ name: 'CSV Views',
6
+ description: 'Renders any CSV file as Table, Gallery, or Board. Each view is independently configurable — choose which columns map to title, description, tag, and group.',
7
+ author: 'MindOS',
8
+ icon: '📊',
9
+ tags: ['csv', 'table', 'gallery', 'board', 'data'],
10
+ builtin: true,
11
+ entryPath: 'Resources/Products.csv',
12
+ match: ({ extension, filePath }) => extension === 'csv' && !/\bTODO\b/i.test(filePath),
13
+ load: () => import('./CsvRenderer').then(m => ({ default: m.CsvRenderer })),
14
+ };
@@ -182,7 +182,8 @@ function DiffCard({ entry, saveAction, fullContent }: {
182
182
  <div style={{ display: 'flex', alignItems: 'center', gap: 9, padding: '10px 14px', borderBottom: expanded ? '1px solid var(--border)' : 'none' }}>
183
183
  <FileEdit size={13} style={{ color: 'var(--amber)', flexShrink: 0 }} />
184
184
  <span
185
- style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.78rem', color: 'var(--amber)', flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', cursor: 'pointer' }}
185
+ className="font-display"
186
+ style={{ fontSize: '0.78rem', color: 'var(--amber)', flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', cursor: 'pointer' }}
186
187
  onClick={() => router.push('/view/' + entry.path.split('/').map(encodeURIComponent).join('/'))}
187
188
  title={entry.path}
188
189
  >
@@ -190,16 +191,16 @@ function DiffCard({ entry, saveAction, fullContent }: {
190
191
  </span>
191
192
 
192
193
  {/* diff stats */}
193
- <span style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.7rem', color: '#7aad80', flexShrink: 0 }}>+{added}</span>
194
- <span style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.7rem', color: '#c85050', flexShrink: 0 }}>−{removed}</span>
194
+ <span className="font-display" style={{ fontSize: '0.7rem', color: '#7aad80', flexShrink: 0 }}>+{added}</span>
195
+ <span className="font-display" style={{ fontSize: '0.7rem', color: '#c85050', flexShrink: 0 }}>−{removed}</span>
195
196
 
196
197
  {/* tool badge */}
197
- <span style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.65rem', padding: '1px 7px', borderRadius: 999, background: 'var(--muted)', color: 'var(--muted-foreground)', flexShrink: 0 }}>
198
+ <span className="font-display" style={{ fontSize: '0.65rem', padding: '1px 7px', borderRadius: 999, background: 'var(--muted)', color: 'var(--muted-foreground)', flexShrink: 0 }}>
198
199
  {toolShort}
199
200
  </span>
200
201
 
201
202
  {/* timestamp */}
202
- <span style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.65rem', color: 'var(--muted-foreground)', opacity: 0.6, flexShrink: 0 }}>
203
+ <span className="font-display" style={{ fontSize: '0.65rem', color: 'var(--muted-foreground)', opacity: 0.6, flexShrink: 0 }}>
203
204
  {relativeTs(entry.ts)}
204
205
  </span>
205
206
 
@@ -222,7 +223,7 @@ function DiffCard({ entry, saveAction, fullContent }: {
222
223
  </button>
223
224
  </>
224
225
  ) : (
225
- <span style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.68rem', color: approved ? '#7aad80' : '#c85050' }}>
226
+ <span className="font-display" style={{ fontSize: '0.68rem', color: approved ? '#7aad80' : '#c85050' }}>
226
227
  {approved ? '✓ approved' : '✕ reverted'}
227
228
  </span>
228
229
  )}
@@ -237,7 +238,7 @@ function DiffCard({ entry, saveAction, fullContent }: {
237
238
 
238
239
  {/* diff view */}
239
240
  {expanded && (
240
- <div style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: '0.72rem', lineHeight: 1.5, overflowX: 'auto' }}>
241
+ <div className="font-display" style={{ fontSize: '0.72rem', lineHeight: 1.5, overflowX: 'auto' }}>
241
242
  {collapsed.map((line, i) => {
242
243
  if (line.type === 'collapse') {
243
244
  return (
@@ -280,7 +281,7 @@ export function DiffRenderer({ content, saveAction }: RendererContext) {
280
281
 
281
282
  if (entries.length === 0) {
282
283
  return (
283
- <div style={{ padding: '3rem 1rem', textAlign: 'center', color: 'var(--muted-foreground)', fontFamily: "'IBM Plex Mono',monospace", fontSize: 12 }}>
284
+ <div className="font-display" style={{ padding: '3rem 1rem', textAlign: 'center', color: 'var(--muted-foreground)', fontSize: 12 }}>
284
285
  <GitCompare size={28} style={{ margin: '0 auto 10px', opacity: 0.3 }} />
285
286
  <p>No agent diffs logged yet.</p>
286
287
  <p style={{ marginTop: 6, opacity: 0.6, fontSize: 11 }}>
@@ -296,7 +297,7 @@ export function DiffRenderer({ content, saveAction }: RendererContext) {
296
297
  return (
297
298
  <div style={{ maxWidth: 800, margin: '0 auto', padding: '1.5rem 0' }}>
298
299
  {/* stats bar */}
299
- <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: '1.2rem', fontFamily: "'IBM Plex Mono',monospace", fontSize: 11, color: 'var(--muted-foreground)' }}>
300
+ <div className="font-display" style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: '1.2rem', fontSize: 11, color: 'var(--muted-foreground)' }}>
300
301
  <span>{entries.length} change{entries.length !== 1 ? 's' : ''}</span>
301
302
  <span style={{ color: '#7aad80' }}>+{totalAdded}</span>
302
303
  <span style={{ color: '#c85050' }}>−{totalRemoved}</span>
@@ -0,0 +1,14 @@
1
+ import type { RendererDefinition } from '@/lib/renderers/registry';
2
+
3
+ export const manifest: RendererDefinition = {
4
+ id: 'diff-viewer',
5
+ name: 'Diff Viewer',
6
+ description: 'Visualizes agent file changes as a side-by-side diff timeline. Auto-activates on Agent-Diff.md with embedded agent-diff blocks.',
7
+ author: 'MindOS',
8
+ icon: '📝',
9
+ tags: ['diff', 'agent', 'changes', 'history'],
10
+ builtin: true,
11
+ entryPath: 'Agent-Diff.md',
12
+ match: ({ filePath }) => /\bAgent-Diff\b.*\.md$/i.test(filePath),
13
+ load: () => import('./DiffRenderer').then(m => ({ default: m.DiffRenderer })),
14
+ };
@@ -136,9 +136,8 @@ const WikiNode = memo(function WikiNode({ data }: NodeProps) {
136
136
  <div
137
137
  onClick={handleClick}
138
138
  title={id as string}
139
- className="group"
139
+ className="group font-display"
140
140
  style={{
141
- fontFamily: "'IBM Plex Mono', monospace",
142
141
  fontSize: 10 * scale,
143
142
  padding: `${4 * scale}px ${12 * scale}px`,
144
143
  borderRadius: 999, // Pill shape
@@ -326,7 +325,7 @@ export function GraphRenderer({ filePath }: RendererContext) {
326
325
  justifyContent: 'center',
327
326
  }}
328
327
  >
329
- <span style={{ color: 'var(--muted-foreground)', fontFamily: "'IBM Plex Mono', monospace", fontSize: 12 }}>
328
+ <span className="font-display" style={{ color: 'var(--muted-foreground)', fontSize: 12 }}>
330
329
  {loading ? 'Building graph…' : 'Loading…'}
331
330
  </span>
332
331
  </div>
@@ -346,8 +345,8 @@ export function GraphRenderer({ filePath }: RendererContext) {
346
345
  }}
347
346
  >
348
347
  <span
348
+ className="font-display"
349
349
  style={{
350
- fontFamily: "'IBM Plex Mono', monospace",
351
350
  fontSize: 11,
352
351
  color: 'var(--muted-foreground)',
353
352
  }}
@@ -368,11 +367,11 @@ export function GraphRenderer({ filePath }: RendererContext) {
368
367
  <button
369
368
  key={btn.id}
370
369
  onClick={() => setScope(btn.id)}
370
+ className="font-display"
371
371
  style={{
372
372
  padding: '3px 12px',
373
373
  borderRadius: 5,
374
374
  fontSize: 11,
375
- fontFamily: "'IBM Plex Mono', monospace",
376
375
  cursor: 'pointer',
377
376
  border: 'none',
378
377
  outline: 'none',
@@ -0,0 +1,14 @@
1
+ import type { RendererDefinition } from '@/lib/renderers/registry';
2
+
3
+ export const manifest: RendererDefinition = {
4
+ id: 'graph',
5
+ name: 'Wiki Graph',
6
+ description: 'Force-directed graph of wikilink references across all markdown files. Supports Global and Local (2-hop) scope filters.',
7
+ author: 'MindOS',
8
+ icon: '🕸️',
9
+ tags: ['graph', 'wiki', 'links', 'visualization'],
10
+ builtin: true,
11
+ entryPath: 'README.md',
12
+ match: ({ extension }) => extension === 'md',
13
+ load: () => import('./GraphRenderer').then(m => ({ default: m.GraphRenderer })),
14
+ };
@@ -34,7 +34,7 @@ function renderMarkdown(md: string): string {
34
34
  .replace(/^# (.+)$/gm, '<h1 style="font-size:1rem;font-weight:700;color:var(--foreground);margin:1.2em 0 .4em">$1</h1>')
35
35
  .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
36
36
  .replace(/\*(.+?)\*/g, '<em>$1</em>')
37
- .replace(/`(.+?)`/g, '<code style="font-family:\'IBM Plex Mono\',monospace;font-size:.82em;padding:1px 5px;border-radius:4px;background:var(--muted)">$1</code>')
37
+ .replace(/`(.+?)`/g, '<code class="font-display" style="font-size:.82em;padding:1px 5px;border-radius:4px;background:var(--muted)">$1</code>')
38
38
  .replace(/^[-*] (.+)$/gm, '<li style="margin:.2em 0;padding-left:.3em">$1</li>')
39
39
  .replace(/(<li[^>]*>.*<\/li>\n?)+/g, s => `<ul style="margin:.4em 0;padding-left:1.4em;list-style:disc">${s}</ul>`)
40
40
  .replace(/\n{2,}/g, '</p><p style="margin:.5em 0;font-size:.85rem;line-height:1.7;color:var(--foreground)">')
@@ -136,7 +136,7 @@ Be specific. Reference actual content from the files. Keep the total response un
136
136
  <div style={{ maxWidth: 720, margin: '0 auto', padding: '1.5rem 0' }}>
137
137
  {/* header row */}
138
138
  <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: '1.5rem', flexWrap: 'wrap' }}>
139
- <span style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: 11, color: 'var(--muted-foreground)' }}>
139
+ <span className="font-display" style={{ fontSize: 11, color: 'var(--muted-foreground)' }}>
140
140
  {recentFiles.length > 0
141
141
  ? `${recentFiles.length} recently modified files`
142
142
  : 'Loading recent files…'}
@@ -144,6 +144,7 @@ Be specific. Reference actual content from the files. Keep the total response un
144
144
  <button
145
145
  onClick={generate}
146
146
  disabled={streaming || recentFiles.length === 0}
147
+ className="font-display"
147
148
  style={{
148
149
  display: 'flex',
149
150
  alignItems: 'center',
@@ -151,7 +152,6 @@ Be specific. Reference actual content from the files. Keep the total response un
151
152
  padding: '5px 14px',
152
153
  borderRadius: 7,
153
154
  fontSize: 12,
154
- fontFamily: "'IBM Plex Mono',monospace",
155
155
  cursor: streaming || recentFiles.length === 0 ? 'not-allowed' : 'pointer',
156
156
  border: 'none',
157
157
  background: streaming ? 'var(--muted)' : 'var(--amber)',
@@ -176,6 +176,7 @@ Be specific. Reference actual content from the files. Keep the total response un
176
176
  <a
177
177
  key={f.path}
178
178
  href={`/view/${encodePath(f.path)}`}
179
+ className="font-display"
179
180
  style={{
180
181
  display: 'inline-flex',
181
182
  alignItems: 'center',
@@ -183,7 +184,6 @@ Be specific. Reference actual content from the files. Keep the total response un
183
184
  padding: '3px 10px',
184
185
  borderRadius: 999,
185
186
  fontSize: '0.7rem',
186
- fontFamily: "'IBM Plex Mono',monospace",
187
187
  background: 'var(--muted)',
188
188
  color: 'var(--muted-foreground)',
189
189
  textDecoration: 'none',
@@ -207,7 +207,7 @@ Be specific. Reference actual content from the files. Keep the total response un
207
207
 
208
208
  {/* error */}
209
209
  {error && (
210
- <div style={{ padding: '10px 14px', borderRadius: 8, background: 'rgba(200,60,60,0.1)', border: '1px solid rgba(200,60,60,0.3)', color: '#c83c3c', fontFamily: "'IBM Plex Mono',monospace", fontSize: 12, marginBottom: '1rem' }}>
210
+ <div className="font-display" style={{ padding: '10px 14px', borderRadius: 8, background: 'rgba(200,60,60,0.1)', border: '1px solid rgba(200,60,60,0.3)', color: '#c83c3c', fontSize: 12, marginBottom: '1rem' }}>
211
211
  {error}
212
212
  </div>
213
213
  )}
@@ -235,7 +235,7 @@ Be specific. Reference actual content from the files. Keep the total response un
235
235
  color: 'var(--muted-foreground)',
236
236
  }}>
237
237
  <Sparkles size={28} style={{ margin: '0 auto 10px', opacity: 0.3, color: 'var(--amber)' }} />
238
- <p style={{ fontFamily: "'IBM Plex Mono',monospace", fontSize: 12 }}>
238
+ <p className="font-display" style={{ fontSize: 12 }}>
239
239
  Click <strong style={{ color: 'var(--foreground)' }}>Generate briefing</strong> to summarize recent changes with AI.
240
240
  </p>
241
241
  </div>