total-recall-brain 3.26.0 → 3.28.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 (65) hide show
  1. package/bin/total-recall.mjs +31 -1
  2. package/docs/ARCHITECTURE.md +76 -9
  3. package/frontend/dist/assets/index-U7o-qU99.js +5742 -0
  4. package/frontend/dist/index.html +1 -1
  5. package/frontend/src/App.tsx +39 -1
  6. package/frontend/src/api/index.ts +1 -0
  7. package/frontend/src/api/plugins.ts +167 -0
  8. package/frontend/src/pages/ChatPage.tsx +44 -17
  9. package/frontend/src/pages/IntegrationsPage.tsx +7 -0
  10. package/frontend/src/pages/PluginsPage.spec.tsx +17 -0
  11. package/frontend/src/pages/PluginsPage.tsx +1331 -0
  12. package/package.json +1 -1
  13. package/scaffold/.agent/plugins/.gitkeep +0 -0
  14. package/scaffold/.agent/plugins/code-quality/README.md +16 -0
  15. package/scaffold/.agent/plugins/code-quality/cli.mjs +15 -0
  16. package/scaffold/.agent/plugins/code-quality/plugin.json +34 -0
  17. package/scaffold/.agent/plugins/git-sentinel/README.md +16 -0
  18. package/scaffold/.agent/plugins/git-sentinel/cli.mjs +86 -0
  19. package/scaffold/.agent/plugins/git-sentinel/plugin.json +34 -0
  20. package/scaffold/.agent/plugins/meta-harness/README.md +16 -0
  21. package/scaffold/.agent/plugins/meta-harness/cli.mjs +34 -0
  22. package/scaffold/.agent/plugins/meta-harness/plugin.json +34 -0
  23. package/scaffold/.agent/plugins/system-monitor/README.md +16 -0
  24. package/scaffold/.agent/plugins/system-monitor/cli.mjs +79 -0
  25. package/scaffold/.agent/plugins/system-monitor/generator.mjs +20 -0
  26. package/scaffold/.agent/plugins/system-monitor/plugin.json +38 -0
  27. package/scaffold/.agent/skills/meta-harness/SKILL.md +113 -0
  28. package/scaffold/.agent/skills/total-recall/references/cli-reference.md +23 -0
  29. package/src/cli/agent.mjs +173 -0
  30. package/src/cli/connect.mjs +89 -4
  31. package/src/cli/deploy-ui.mjs +6 -0
  32. package/src/cli/harness.mjs +135 -0
  33. package/src/cli/ingest-openwiki.mjs +71 -2
  34. package/src/cli/init.mjs +1 -0
  35. package/src/cli/lint.mjs +47 -3
  36. package/src/cli/mesh.mjs +286 -0
  37. package/src/cli/plugin/create.mjs +186 -0
  38. package/src/cli/plugin/index.mjs +139 -0
  39. package/src/cli/plugin/install.mjs +141 -0
  40. package/src/cli/plugin/list.mjs +83 -0
  41. package/src/cli/plugin/remove.mjs +51 -0
  42. package/src/cli/plugin/search.mjs +37 -0
  43. package/src/cli/skill-projection.mjs +14 -0
  44. package/src/cli/wizard.html +9 -0
  45. package/src/core/agent-manager.mjs +290 -0
  46. package/src/core/cache-prune.mjs +341 -0
  47. package/src/core/context-cache.mjs +130 -0
  48. package/src/core/daemon-loop.mjs +47 -1
  49. package/src/core/evolving-context.mjs +171 -0
  50. package/src/core/mesh-activity.mjs +140 -0
  51. package/src/core/mesh.mjs +174 -1
  52. package/src/core/meta-harness.mjs +251 -0
  53. package/src/core/plugin-context.mjs +206 -0
  54. package/src/core/plugin-loader.mjs +210 -0
  55. package/src/core/runtime.mjs +65 -13
  56. package/src/core/surface.mjs +32 -10
  57. package/src/server/api.mjs +18 -2
  58. package/src/server/index.mjs +8 -2
  59. package/src/server/rest.mjs +4 -0
  60. package/src/server/routes/integrations.mjs +5 -1
  61. package/src/server/routes/mesh-presence.mjs +143 -0
  62. package/src/server/routes/models.mjs +42 -30
  63. package/src/server/routes/plugins.mjs +517 -0
  64. package/src/server/tools.mjs +266 -0
  65. package/frontend/dist/assets/index-Bl3I11B0.js +0 -5742
@@ -0,0 +1,1331 @@
1
+ import { useState, useEffect, useMemo, useCallback } from "react"
2
+ import {
3
+ fetchPlugins,
4
+ fetchPluginCatalog,
5
+ installPlugin,
6
+ removePlugin,
7
+ ratePlugin,
8
+ runPluginCommand,
9
+ fetchPluginReadme,
10
+ type PluginInfo,
11
+ type CatalogPlugin
12
+ } from "../api"
13
+ import { useSearchParams } from "react-router-dom"
14
+
15
+ interface PluginsPageProps {
16
+ activeBrainId?: string | null
17
+ }
18
+
19
+ function StarRatingDisplay({ rating, reviewCount, installCount }: { rating: number; reviewCount?: number; installCount?: string }) {
20
+ const stars = [1, 2, 3, 4, 5]
21
+ return (
22
+ <div style={{ display: "flex", alignItems: "center", gap: "6px", fontSize: "12px" }}>
23
+ <div style={{ display: "flex", color: "#fbbf24" }}>
24
+ {stars.map((s) => (
25
+ <span key={s} style={{ opacity: s <= Math.round(rating) ? 1 : 0.35 }}>
26
+ ★
27
+ </span>
28
+ ))}
29
+ </div>
30
+ <span style={{ fontWeight: "700", color: "var(--text-primary)" }}>{rating.toFixed(1)}</span>
31
+ {reviewCount !== undefined && (
32
+ <span style={{ color: "var(--text-tertiary)" }}>({reviewCount})</span>
33
+ )}
34
+ {installCount && (
35
+ <>
36
+ <span style={{ color: "var(--text-tertiary)" }}>•</span>
37
+ <span style={{ color: "var(--text-secondary)" }}>{installCount} installs</span>
38
+ </>
39
+ )}
40
+ </div>
41
+ )
42
+ }
43
+
44
+ function InteractiveRater({
45
+ pluginId,
46
+ currentRating,
47
+ onRated
48
+ }: {
49
+ pluginId: string;
50
+ currentRating?: number | null;
51
+ onRated: (score: number) => void
52
+ }) {
53
+ const [hoverRating, setHoverRating] = useState<number | null>(null)
54
+ const [submitting, setSubmitting] = useState(false)
55
+
56
+ const handleRate = async (score: number) => {
57
+ setSubmitting(true)
58
+ const res = await ratePlugin(pluginId, score)
59
+ setSubmitting(false)
60
+ if (res.success) {
61
+ onRated(score)
62
+ }
63
+ }
64
+
65
+ const activeScore = hoverRating !== null ? hoverRating : (currentRating || 0)
66
+
67
+ return (
68
+ <div style={{ display: "flex", alignItems: "center", gap: "6px", fontSize: "12px" }}>
69
+ <span style={{ color: "var(--text-tertiary)", fontSize: "11px" }}>Rate:</span>
70
+ <div style={{ display: "flex", gap: "2px", cursor: submitting ? "wait" : "pointer" }}>
71
+ {[1, 2, 3, 4, 5].map((star) => (
72
+ <span
73
+ key={star}
74
+ onMouseEnter={() => setHoverRating(star)}
75
+ onMouseLeave={() => setHoverRating(null)}
76
+ onClick={() => handleRate(star)}
77
+ style={{
78
+ fontSize: "14px",
79
+ color: star <= activeScore ? "#fbbf24" : "var(--text-tertiary)",
80
+ transition: "transform 0.1s ease",
81
+ transform: hoverRating === star ? "scale(1.2)" : "scale(1)"
82
+ }}
83
+ title={`Rate ${star} star${star > 1 ? "s" : ""}`}
84
+ >
85
+ ★
86
+ </span>
87
+ ))}
88
+ </div>
89
+ {currentRating && (
90
+ <span style={{ color: "var(--success)", fontSize: "11px", fontWeight: "600" }}>
91
+ (You rated {currentRating}★)
92
+ </span>
93
+ )}
94
+ </div>
95
+ )
96
+ }
97
+
98
+ export default function PluginsPage({ activeBrainId }: PluginsPageProps) {
99
+ const [searchParams, setSearchParams] = useSearchParams()
100
+ const [plugins, setPlugins] = useState<PluginInfo[]>([])
101
+ const [catalog, setCatalog] = useState<CatalogPlugin[]>([])
102
+ const [loading, setLoading] = useState(true)
103
+ const [activeTab, setActiveTab] = useState<"installed" | "catalog">("installed")
104
+ const [searchQuery, setSearchQuery] = useState("")
105
+ const [sortBy, setSortBy] = useState<"rating" | "reviews" | "name">("rating")
106
+ const [installModalOpen, setInstallModalOpen] = useState(false)
107
+
108
+ // Plugin Detail & Runner Modal State
109
+ const [detailModalPlugin, setDetailModalPlugin] = useState<PluginInfo | null>(null)
110
+ const [detailTab, setDetailTab] = useState<"runner" | "readme" | "schemas" | "tasks" | "manifest">("runner")
111
+ const [subcommand, setSubcommand] = useState("")
112
+ const [customArgs, setCustomArgs] = useState("")
113
+ const [executing, setExecuting] = useState(false)
114
+ const [consoleOutput, setConsoleOutput] = useState("")
115
+ const [readmeText, setReadmeText] = useState("")
116
+ const [readmeLoading, setReadmeLoading] = useState(false)
117
+
118
+ // Install Form State
119
+ const [installSource, setInstallSource] = useState("")
120
+ const [installLink, setInstallLink] = useState(false)
121
+ const [installGlobal, setInstallGlobal] = useState(false)
122
+ const [installing, setInstalling] = useState(false)
123
+ const [alert, setAlert] = useState<{ type: "success" | "error"; message: string } | null>(null)
124
+
125
+ const loadData = useCallback(async () => {
126
+ setLoading(true)
127
+ try {
128
+ const [installedList, catalogList] = await Promise.all([
129
+ fetchPlugins(),
130
+ fetchPluginCatalog()
131
+ ])
132
+ setPlugins(installedList)
133
+ setCatalog(catalogList)
134
+ } finally {
135
+ setLoading(false)
136
+ }
137
+ }, [])
138
+
139
+ useEffect(() => {
140
+ loadData()
141
+ }, [loadData, activeBrainId])
142
+
143
+ const openDetail = async (p: PluginInfo, tab: "runner" | "readme" | "schemas" | "tasks" | "manifest" = "runner") => {
144
+ setDetailModalPlugin(p)
145
+ const subcommands = (p.manifest as any)?.cli?.subcommands as Array<{ name: string; description: string }> | undefined
146
+ const defaultSub = subcommands?.[0]?.name || (p.cli?.command === "git-sentinel" ? "audit" : "status")
147
+ setSubcommand(defaultSub)
148
+ setCustomArgs("")
149
+ setConsoleOutput("")
150
+ setDetailTab(tab)
151
+ setReadmeLoading(true)
152
+ try {
153
+ const rm = await fetchPluginReadme(p.id)
154
+ setReadmeText(rm)
155
+ } finally {
156
+ setReadmeLoading(false)
157
+ }
158
+ }
159
+
160
+ const closeDetail = () => {
161
+ setDetailModalPlugin(null)
162
+ if (searchParams.has("id")) {
163
+ const next = new URLSearchParams(searchParams)
164
+ next.delete("id")
165
+ setSearchParams(next)
166
+ }
167
+ }
168
+
169
+ const handleExecute = async () => {
170
+ if (!detailModalPlugin) return
171
+ setExecuting(true)
172
+ const args = customArgs.trim() ? customArgs.trim().split(/\s+/) : []
173
+ const res = await runPluginCommand(detailModalPlugin.id, subcommand, args)
174
+ setExecuting(false)
175
+ if (res.success) {
176
+ setConsoleOutput(res.output || "(Command completed with no output)")
177
+ } else {
178
+ setConsoleOutput(`❌ Error: ${res.error || "Failed to execute command"}`)
179
+ }
180
+ }
181
+
182
+ useEffect(() => {
183
+ const idParam = searchParams.get("id")
184
+ if (idParam && plugins.length > 0) {
185
+ const match = plugins.find(p => p.id === idParam)
186
+ if (match && (!detailModalPlugin || detailModalPlugin.id !== idParam)) {
187
+ openDetail(match, "runner")
188
+ }
189
+ }
190
+ }, [searchParams, plugins])
191
+
192
+ const handleInstallSubmit = async (e?: React.FormEvent) => {
193
+ if (e) e.preventDefault()
194
+ if (!installSource.trim()) return
195
+
196
+ setInstalling(true)
197
+ setAlert(null)
198
+
199
+ const res = await installPlugin({
200
+ source: installSource.trim(),
201
+ link: installLink,
202
+ global: installGlobal
203
+ })
204
+
205
+ setInstalling(false)
206
+
207
+ if (res.success) {
208
+ setAlert({ type: "success", message: res.message || "Plugin installed successfully" })
209
+ setInstallSource("")
210
+ setInstallModalOpen(false)
211
+ loadData()
212
+ } else {
213
+ setAlert({ type: "error", message: res.error || "Failed to install plugin" })
214
+ }
215
+ }
216
+
217
+ const handleCatalogInstall = async (item: CatalogPlugin) => {
218
+ setInstalling(true)
219
+ setAlert(null)
220
+ const res = await installPlugin({
221
+ source: item.sourceUrl,
222
+ link: item.sourceUrl.startsWith("./") || item.sourceUrl.startsWith("local:"),
223
+ global: false
224
+ })
225
+ setInstalling(false)
226
+
227
+ if (res.success) {
228
+ setAlert({ type: "success", message: `Installed ${item.name} successfully` })
229
+ loadData()
230
+ } else {
231
+ setAlert({ type: "error", message: res.error || `Failed to install ${item.name}` })
232
+ }
233
+ }
234
+
235
+ const handleUninstall = async (plugin: PluginInfo) => {
236
+ if (!window.confirm(`Are you sure you want to uninstall "${plugin.name}"?`)) return
237
+
238
+ const res = await removePlugin(plugin.id)
239
+ if (res.success) {
240
+ setAlert({ type: "success", message: `Uninstalled ${plugin.name}` })
241
+ loadData()
242
+ } else {
243
+ setAlert({ type: "error", message: res.error || "Failed to uninstall plugin" })
244
+ }
245
+ }
246
+
247
+ const handlePluginRated = (id: string, score: number) => {
248
+ setPlugins(prev => prev.map(p => p.id === id ? { ...p, userRating: score } : p))
249
+ setAlert({ type: "success", message: "Thank you! Your rating has been recorded." })
250
+ }
251
+
252
+ const filteredInstalled = useMemo(() => {
253
+ const q = searchQuery.toLowerCase().trim()
254
+ let list = plugins.filter(p =>
255
+ p.name.toLowerCase().includes(q) ||
256
+ p.id.toLowerCase().includes(q) ||
257
+ p.description.toLowerCase().includes(q) ||
258
+ (p.cli?.command && p.cli.command.toLowerCase().includes(q))
259
+ )
260
+
261
+ if (sortBy === "rating") {
262
+ list.sort((a, b) => (b.rating || 0) - (a.rating || 0))
263
+ } else if (sortBy === "reviews") {
264
+ list.sort((a, b) => (b.reviewCount || 0) - (a.reviewCount || 0))
265
+ } else {
266
+ list.sort((a, b) => a.name.localeCompare(b.name))
267
+ }
268
+ return list
269
+ }, [plugins, searchQuery, sortBy])
270
+
271
+ const filteredCatalog = useMemo(() => {
272
+ const q = searchQuery.toLowerCase().trim()
273
+ let list = catalog.filter(c =>
274
+ c.name.toLowerCase().includes(q) ||
275
+ c.id.toLowerCase().includes(q) ||
276
+ c.description.toLowerCase().includes(q) ||
277
+ c.tags.some(t => t.toLowerCase().includes(q))
278
+ )
279
+
280
+ if (sortBy === "rating") {
281
+ list.sort((a, b) => (b.rating || 0) - (a.rating || 0))
282
+ } else if (sortBy === "reviews") {
283
+ list.sort((a, b) => (b.reviewCount || 0) - (a.reviewCount || 0))
284
+ } else {
285
+ list.sort((a, b) => a.name.localeCompare(b.name))
286
+ }
287
+ return list
288
+ }, [catalog, searchQuery, sortBy])
289
+
290
+ return (
291
+ <div style={{ padding: "32px 40px", maxWidth: "1280px", margin: "0 auto", color: "var(--text-primary)" }}>
292
+ {/* Header */}
293
+ <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: "28px" }}>
294
+ <div>
295
+ <div style={{ display: "flex", alignItems: "center", gap: "12px", marginBottom: "6px" }}>
296
+ <h1 style={{ fontSize: "28px", fontWeight: "700", margin: 0, letterSpacing: "-0.02em" }}>
297
+ Plugins & Extensions
298
+ </h1>
299
+ <span style={{
300
+ background: "var(--accent-muted)",
301
+ color: "var(--accent-hover)",
302
+ fontSize: "12px",
303
+ fontWeight: "600",
304
+ padding: "3px 10px",
305
+ borderRadius: "20px",
306
+ border: "1px solid var(--border-accent)"
307
+ }}>
308
+ {plugins.length} Installed
309
+ </span>
310
+ </div>
311
+ <p style={{ margin: 0, color: "var(--text-secondary)", fontSize: "14px", lineHeight: "1.5" }}>
312
+ Modular autonomous engines, scholarly daemons, and custom SSSS graph schemas extending your Total Recall brain.
313
+ </p>
314
+ </div>
315
+
316
+ <button
317
+ onClick={() => { setInstallModalOpen(true); setAlert(null); }}
318
+ style={{
319
+ display: "flex",
320
+ alignItems: "center",
321
+ gap: "8px",
322
+ background: "linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)",
323
+ color: "#ffffff",
324
+ border: "none",
325
+ borderRadius: "var(--radius-md)",
326
+ padding: "10px 18px",
327
+ fontSize: "14px",
328
+ fontWeight: "600",
329
+ cursor: "pointer",
330
+ boxShadow: "0 4px 14px var(--accent-glow)",
331
+ transition: "all 0.15s ease"
332
+ }}
333
+ onMouseOver={(e) => (e.currentTarget.style.filter = "brightness(1.1)")}
334
+ onMouseOut={(e) => (e.currentTarget.style.filter = "brightness(1.0)")}
335
+ >
336
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
337
+ <line x1="12" y1="5" x2="12" y2="19" />
338
+ <line x1="5" y1="12" x2="19" y2="12" />
339
+ </svg>
340
+ Install Plugin
341
+ </button>
342
+ </div>
343
+
344
+ {/* Alert Banner */}
345
+ {alert && (
346
+ <div style={{
347
+ padding: "12px 18px",
348
+ borderRadius: "var(--radius-md)",
349
+ marginBottom: "24px",
350
+ fontSize: "14px",
351
+ display: "flex",
352
+ justifyContent: "space-between",
353
+ alignItems: "center",
354
+ background: alert.type === "success" ? "var(--success-muted)" : "var(--error-muted)",
355
+ border: `1px solid ${alert.type === "success" ? "var(--success)" : "var(--error)"}`,
356
+ color: alert.type === "success" ? "var(--success)" : "var(--error)"
357
+ }}>
358
+ <span>{alert.message}</span>
359
+ <button
360
+ onClick={() => setAlert(null)}
361
+ style={{ background: "none", border: "none", color: "inherit", cursor: "pointer", fontSize: "16px" }}
362
+ >
363
+ ×
364
+ </button>
365
+ </div>
366
+ )}
367
+
368
+ {/* Tabs, Search & Sort Bar */}
369
+ <div style={{
370
+ display: "flex",
371
+ justifyContent: "space-between",
372
+ alignItems: "center",
373
+ borderBottom: "1px solid var(--border)",
374
+ paddingBottom: "16px",
375
+ marginBottom: "24px",
376
+ gap: "16px",
377
+ flexWrap: "wrap"
378
+ }}>
379
+ <div style={{ display: "flex", gap: "8px" }}>
380
+ <button
381
+ onClick={() => setActiveTab("installed")}
382
+ style={{
383
+ background: activeTab === "installed" ? "var(--bg-elevated)" : "transparent",
384
+ color: activeTab === "installed" ? "var(--text-primary)" : "var(--text-secondary)",
385
+ border: activeTab === "installed" ? "1px solid var(--border-accent)" : "1px solid transparent",
386
+ borderRadius: "var(--radius-sm)",
387
+ padding: "8px 16px",
388
+ fontSize: "14px",
389
+ fontWeight: "600",
390
+ cursor: "pointer",
391
+ display: "flex",
392
+ alignItems: "center",
393
+ gap: "8px"
394
+ }}
395
+ >
396
+ <span>Installed</span>
397
+ <span style={{
398
+ background: "var(--bg-tertiary)",
399
+ padding: "2px 7px",
400
+ borderRadius: "10px",
401
+ fontSize: "12px",
402
+ color: "var(--text-tertiary)"
403
+ }}>
404
+ {plugins.length}
405
+ </span>
406
+ </button>
407
+
408
+ <button
409
+ onClick={() => setActiveTab("catalog")}
410
+ style={{
411
+ background: activeTab === "catalog" ? "var(--bg-elevated)" : "transparent",
412
+ color: activeTab === "catalog" ? "var(--text-primary)" : "var(--text-secondary)",
413
+ border: activeTab === "catalog" ? "1px solid var(--border-accent)" : "1px solid transparent",
414
+ borderRadius: "var(--radius-sm)",
415
+ padding: "8px 16px",
416
+ fontSize: "14px",
417
+ fontWeight: "600",
418
+ cursor: "pointer",
419
+ display: "flex",
420
+ alignItems: "center",
421
+ gap: "8px"
422
+ }}
423
+ >
424
+ <span>Discover Catalog</span>
425
+ <span style={{
426
+ background: "var(--accent-muted)",
427
+ color: "var(--accent-hover)",
428
+ padding: "2px 7px",
429
+ borderRadius: "10px",
430
+ fontSize: "12px"
431
+ }}>
432
+ {catalog.length}
433
+ </span>
434
+ </button>
435
+ </div>
436
+
437
+ <div style={{ display: "flex", gap: "12px", alignItems: "center" }}>
438
+ <div style={{ display: "flex", alignItems: "center", gap: "6px", fontSize: "13px", color: "var(--text-secondary)" }}>
439
+ <span>Sort:</span>
440
+ <select
441
+ value={sortBy}
442
+ onChange={(e) => setSortBy(e.target.value as any)}
443
+ style={{
444
+ background: "var(--bg-secondary)",
445
+ border: "1px solid var(--border)",
446
+ borderRadius: "var(--radius-sm)",
447
+ color: "var(--text-primary)",
448
+ padding: "6px 10px",
449
+ fontSize: "13px",
450
+ outline: "none",
451
+ cursor: "pointer"
452
+ }}
453
+ >
454
+ <option value="rating">Highest Rated</option>
455
+ <option value="reviews">Most Reviews</option>
456
+ <option value="name">Alphabetical</option>
457
+ </select>
458
+ </div>
459
+
460
+ <div style={{ position: "relative", minWidth: "260px" }}>
461
+ <input
462
+ type="text"
463
+ placeholder={activeTab === "installed" ? "Filter installed..." : "Search catalog..."}
464
+ value={searchQuery}
465
+ onChange={(e) => setSearchQuery(e.target.value)}
466
+ style={{
467
+ width: "100%",
468
+ background: "var(--bg-secondary)",
469
+ border: "1px solid var(--border)",
470
+ borderRadius: "var(--radius-md)",
471
+ padding: "8px 14px 8px 36px",
472
+ color: "var(--text-primary)",
473
+ fontSize: "13px",
474
+ outline: "none"
475
+ }}
476
+ />
477
+ <svg
478
+ width="15"
479
+ height="15"
480
+ viewBox="0 0 24 24"
481
+ fill="none"
482
+ stroke="var(--text-tertiary)"
483
+ strokeWidth="2"
484
+ style={{ position: "absolute", left: "12px", top: "50%", transform: "translateY(-50%)" }}
485
+ >
486
+ <circle cx="11" cy="11" r="8" />
487
+ <line x1="21" y1="21" x2="16.65" y2="16.65" />
488
+ </svg>
489
+ </div>
490
+ </div>
491
+ </div>
492
+
493
+ {/* Tab Content */}
494
+ {loading ? (
495
+ <div style={{ padding: "60px 0", textAlign: "center", color: "var(--text-tertiary)" }}>
496
+ Loading plugins...
497
+ </div>
498
+ ) : activeTab === "installed" ? (
499
+ filteredInstalled.length === 0 ? (
500
+ <div style={{
501
+ padding: "60px 20px",
502
+ textAlign: "center",
503
+ background: "var(--bg-secondary)",
504
+ borderRadius: "var(--radius-lg)",
505
+ border: "1px dashed var(--border)"
506
+ }}>
507
+ <div style={{ fontSize: "36px", marginBottom: "12px" }}>🧩</div>
508
+ <h3 style={{ margin: "0 0 8px 0", fontSize: "18px" }}>No Plugins Found</h3>
509
+ <p style={{ margin: "0 0 20px 0", color: "var(--text-secondary)", fontSize: "14px" }}>
510
+ {searchQuery ? "No installed plugins match your search filter." : "You do not have any plugins installed in this brain."}
511
+ </p>
512
+ <button
513
+ onClick={() => setActiveTab("catalog")}
514
+ style={{
515
+ background: "var(--bg-elevated)",
516
+ border: "1px solid var(--border-accent)",
517
+ color: "var(--accent-hover)",
518
+ padding: "8px 18px",
519
+ borderRadius: "var(--radius-sm)",
520
+ cursor: "pointer",
521
+ fontWeight: "600",
522
+ fontSize: "14px"
523
+ }}
524
+ >
525
+ Browse Discovery Catalog
526
+ </button>
527
+ </div>
528
+ ) : (
529
+ <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(360px, 1fr))", gap: "20px" }}>
530
+ {filteredInstalled.map((p) => (
531
+ <div
532
+ key={p.id}
533
+ style={{
534
+ background: "var(--bg-secondary)",
535
+ border: "1px solid var(--border)",
536
+ borderRadius: "var(--radius-lg)",
537
+ padding: "24px",
538
+ display: "flex",
539
+ flexDirection: "column",
540
+ justifyContent: "space-between",
541
+ transition: "all 0.2s ease"
542
+ }}
543
+ >
544
+ <div>
545
+ <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: "10px" }}>
546
+ <div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
547
+ <div style={{
548
+ width: "38px",
549
+ height: "38px",
550
+ borderRadius: "8px",
551
+ background: "var(--accent-muted)",
552
+ color: "var(--accent-hover)",
553
+ display: "flex",
554
+ alignItems: "center",
555
+ justifyContent: "center",
556
+ fontSize: "20px"
557
+ }}>
558
+ 🧩
559
+ </div>
560
+ <div>
561
+ <h3 style={{ margin: 0, fontSize: "16px", fontWeight: "700" }}>{p.name}</h3>
562
+ <span style={{ fontSize: "12px", color: "var(--text-tertiary)", fontFamily: "var(--font-mono)" }}>
563
+ {p.id}
564
+ </span>
565
+ </div>
566
+ </div>
567
+
568
+ <span style={{
569
+ fontSize: "12px",
570
+ fontWeight: "600",
571
+ padding: "2px 8px",
572
+ borderRadius: "6px",
573
+ background: p.valid ? "var(--success-muted)" : "var(--error-muted)",
574
+ color: p.valid ? "var(--success)" : "var(--error)",
575
+ border: `1px solid ${p.valid ? "rgba(52, 211, 153, 0.2)" : "rgba(248, 113, 113, 0.2)"}`
576
+ }}>
577
+ v{p.version}
578
+ </span>
579
+ </div>
580
+
581
+ {/* Rating & Installs */}
582
+ <div style={{ marginBottom: "12px" }}>
583
+ <StarRatingDisplay
584
+ rating={p.rating || 4.9}
585
+ reviewCount={p.reviewCount || 1}
586
+ installCount={p.installCount || "1k"}
587
+ />
588
+ </div>
589
+
590
+ <p style={{ fontSize: "13px", color: "var(--text-secondary)", lineHeight: "1.5", margin: "0 0 16px 0" }}>
591
+ {p.description}
592
+ </p>
593
+
594
+ {/* Badges / Features */}
595
+ <div style={{ display: "flex", flexWrap: "wrap", gap: "6px", marginBottom: "16px" }}>
596
+ {p.cli?.command && (
597
+ <span style={{
598
+ background: "var(--bg-elevated)",
599
+ border: "1px solid var(--border)",
600
+ color: "var(--text-primary)",
601
+ fontFamily: "var(--font-mono)",
602
+ fontSize: "11px",
603
+ padding: "3px 8px",
604
+ borderRadius: "4px"
605
+ }}>
606
+ cli: total-recall {p.cli.command}
607
+ </span>
608
+ )}
609
+ {p.categories?.length > 0 && (
610
+ <span style={{
611
+ background: "var(--accent-muted)",
612
+ color: "var(--accent-hover)",
613
+ fontSize: "11px",
614
+ padding: "3px 8px",
615
+ borderRadius: "4px"
616
+ }}>
617
+ {p.categories.length} SSSS schemas
618
+ </span>
619
+ )}
620
+ {p.tasks?.length > 0 && (
621
+ <span style={{
622
+ background: "var(--bg-elevated)",
623
+ color: "var(--text-secondary)",
624
+ fontSize: "11px",
625
+ padding: "3px 8px",
626
+ borderRadius: "4px"
627
+ }}>
628
+ {p.tasks.length} tasks
629
+ </span>
630
+ )}
631
+ </div>
632
+ </div>
633
+
634
+ <div>
635
+ {/* User Rating Widget */}
636
+ <div style={{
637
+ background: "var(--bg-primary)",
638
+ borderRadius: "var(--radius-sm)",
639
+ padding: "8px 12px",
640
+ marginBottom: "14px",
641
+ display: "flex",
642
+ justifyContent: "space-between",
643
+ alignItems: "center"
644
+ }}>
645
+ <InteractiveRater
646
+ pluginId={p.id}
647
+ currentRating={p.userRating}
648
+ onRated={(score) => handlePluginRated(p.id, score)}
649
+ />
650
+ </div>
651
+
652
+ <div style={{
653
+ display: "flex",
654
+ justifyContent: "space-between",
655
+ alignItems: "center",
656
+ borderTop: "1px solid var(--border)",
657
+ paddingTop: "14px"
658
+ }}>
659
+ <div style={{ display: "flex", gap: "8px", alignItems: "center" }}>
660
+ {p.cli?.command && (
661
+ <button
662
+ onClick={() => openDetail(p, "runner")}
663
+ style={{
664
+ background: "var(--accent-muted)",
665
+ border: "1px solid var(--border-accent)",
666
+ color: "var(--accent-hover)",
667
+ borderRadius: "var(--radius-sm)",
668
+ padding: "5px 12px",
669
+ fontSize: "12px",
670
+ fontWeight: "600",
671
+ cursor: "pointer",
672
+ display: "flex",
673
+ alignItems: "center",
674
+ gap: "5px"
675
+ }}
676
+ >
677
+ ▶ Run
678
+ </button>
679
+ )}
680
+ <button
681
+ onClick={() => openDetail(p, "readme")}
682
+ style={{
683
+ background: "none",
684
+ border: "1px solid var(--border)",
685
+ color: "var(--text-secondary)",
686
+ borderRadius: "var(--radius-sm)",
687
+ padding: "5px 10px",
688
+ fontSize: "12px",
689
+ cursor: "pointer"
690
+ }}
691
+ >
692
+ Inspect
693
+ </button>
694
+ </div>
695
+
696
+ <button
697
+ onClick={() => handleUninstall(p)}
698
+ style={{
699
+ background: "none",
700
+ border: "none",
701
+ color: "var(--error)",
702
+ fontSize: "12px",
703
+ cursor: "pointer",
704
+ padding: "5px 8px",
705
+ borderRadius: "var(--radius-sm)"
706
+ }}
707
+ >
708
+ Uninstall
709
+ </button>
710
+ </div>
711
+ </div>
712
+ </div>
713
+ ))}
714
+ </div>
715
+ )
716
+ ) : (
717
+ /* Catalog View */
718
+ <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(360px, 1fr))", gap: "20px" }}>
719
+ {filteredCatalog.map((item) => (
720
+ <div
721
+ key={item.id}
722
+ style={{
723
+ background: "var(--bg-secondary)",
724
+ border: "1px solid var(--border)",
725
+ borderRadius: "var(--radius-lg)",
726
+ padding: "24px",
727
+ display: "flex",
728
+ flexDirection: "column",
729
+ justifyContent: "space-between"
730
+ }}
731
+ >
732
+ <div>
733
+ <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: "8px" }}>
734
+ <div>
735
+ <div style={{ display: "flex", alignItems: "center", gap: "6px" }}>
736
+ <h3 style={{ margin: 0, fontSize: "16px", fontWeight: "700" }}>{item.name}</h3>
737
+ {item.verified && (
738
+ <span title="Verified Plugin" style={{ color: "var(--accent)", fontSize: "14px" }}>
739
+ ✓
740
+ </span>
741
+ )}
742
+ </div>
743
+ <span style={{ fontSize: "12px", color: "var(--text-tertiary)", fontFamily: "var(--font-mono)" }}>
744
+ {item.id} • by {item.author}
745
+ </span>
746
+ </div>
747
+ <span style={{ fontSize: "12px", color: "var(--text-secondary)", fontWeight: "600" }}>
748
+ v{item.version}
749
+ </span>
750
+ </div>
751
+
752
+ {/* Rating display */}
753
+ <div style={{ marginBottom: "12px" }}>
754
+ <StarRatingDisplay
755
+ rating={item.rating}
756
+ reviewCount={item.reviewCount}
757
+ installCount={item.installCount}
758
+ />
759
+ </div>
760
+
761
+ <p style={{ fontSize: "13px", color: "var(--text-secondary)", lineHeight: "1.5", margin: "0 0 16px 0" }}>
762
+ {item.description}
763
+ </p>
764
+
765
+ <div style={{ display: "flex", flexWrap: "wrap", gap: "6px", marginBottom: "20px" }}>
766
+ {item.tags.map((tag) => (
767
+ <span key={tag} style={{
768
+ background: "var(--bg-elevated)",
769
+ color: "var(--text-tertiary)",
770
+ fontSize: "11px",
771
+ padding: "2px 8px",
772
+ borderRadius: "4px"
773
+ }}>
774
+ #{tag}
775
+ </span>
776
+ ))}
777
+ </div>
778
+ </div>
779
+
780
+ <div style={{ borderTop: "1px solid var(--border)", paddingTop: "16px", display: "flex", justifyContent: "flex-end" }}>
781
+ {item.isInstalled ? (
782
+ <span style={{
783
+ color: "var(--success)",
784
+ fontSize: "13px",
785
+ fontWeight: "600",
786
+ display: "flex",
787
+ alignItems: "center",
788
+ gap: "6px"
789
+ }}>
790
+ ✓ Installed
791
+ </span>
792
+ ) : (
793
+ <button
794
+ disabled={installing}
795
+ onClick={() => handleCatalogInstall(item)}
796
+ style={{
797
+ background: "var(--accent)",
798
+ color: "#ffffff",
799
+ border: "none",
800
+ borderRadius: "var(--radius-sm)",
801
+ padding: "7px 16px",
802
+ fontSize: "13px",
803
+ fontWeight: "600",
804
+ cursor: installing ? "not-allowed" : "pointer"
805
+ }}
806
+ >
807
+ {installing ? "Installing..." : "Install"}
808
+ </button>
809
+ )}
810
+ </div>
811
+ </div>
812
+ ))}
813
+ </div>
814
+ )}
815
+
816
+ {/* Install Plugin Modal */}
817
+ {installModalOpen && (
818
+ <div style={{
819
+ position: "fixed",
820
+ inset: 0,
821
+ background: "rgba(0, 0, 0, 0.75)",
822
+ display: "flex",
823
+ alignItems: "center",
824
+ justifyContent: "center",
825
+ zIndex: 1000,
826
+ backdropFilter: "blur(4px)"
827
+ }}>
828
+ <div style={{
829
+ background: "var(--bg-secondary)",
830
+ border: "1px solid var(--border-accent)",
831
+ borderRadius: "var(--radius-xl)",
832
+ padding: "32px",
833
+ width: "100%",
834
+ maxWidth: "520px",
835
+ boxShadow: "0 20px 40px rgba(0,0,0,0.6)"
836
+ }}>
837
+ <h2 style={{ margin: "0 0 8px 0", fontSize: "20px", fontWeight: "700" }}>Install Plugin</h2>
838
+ <p style={{ margin: "0 0 24px 0", color: "var(--text-secondary)", fontSize: "14px" }}>
839
+ Install a plugin from a local directory path or Git repository URL.
840
+ </p>
841
+
842
+ <form onSubmit={handleInstallSubmit}>
843
+ <div style={{ marginBottom: "18px" }}>
844
+ <label style={{ display: "block", fontSize: "13px", fontWeight: "600", marginBottom: "6px", color: "var(--text-secondary)" }}>
845
+ Source (Local Directory or Git URL)
846
+ </label>
847
+ <input
848
+ type="text"
849
+ required
850
+ placeholder="e.g. ./.agent/plugins/my-plugin or https://github.com/..."
851
+ value={installSource}
852
+ onChange={(e) => setInstallSource(e.target.value)}
853
+ style={{
854
+ width: "100%",
855
+ background: "var(--bg-primary)",
856
+ border: "1px solid var(--border)",
857
+ borderRadius: "var(--radius-md)",
858
+ padding: "10px 14px",
859
+ color: "var(--text-primary)",
860
+ fontSize: "14px",
861
+ outline: "none"
862
+ }}
863
+ />
864
+ </div>
865
+
866
+ <div style={{ marginBottom: "14px" }}>
867
+ <label style={{ display: "flex", alignItems: "center", gap: "8px", fontSize: "13px", cursor: "pointer" }}>
868
+ <input
869
+ type="checkbox"
870
+ checked={installLink}
871
+ onChange={(e) => setInstallLink(e.target.checked)}
872
+ />
873
+ <span>Link as Symlink (development mode — live edits sync without reinstalling)</span>
874
+ </label>
875
+ </div>
876
+
877
+ <div style={{ marginBottom: "24px" }}>
878
+ <label style={{ display: "flex", alignItems: "center", gap: "8px", fontSize: "13px", cursor: "pointer" }}>
879
+ <input
880
+ type="checkbox"
881
+ checked={installGlobal}
882
+ onChange={(e) => setInstallGlobal(e.target.checked)}
883
+ />
884
+ <span>Install Globally (~/.agent/plugins across all projects)</span>
885
+ </label>
886
+ </div>
887
+
888
+ <div style={{ display: "flex", justifyContent: "flex-end", gap: "12px" }}>
889
+ <button
890
+ type="button"
891
+ onClick={() => setInstallModalOpen(false)}
892
+ style={{
893
+ background: "transparent",
894
+ border: "1px solid var(--border)",
895
+ color: "var(--text-secondary)",
896
+ padding: "8px 16px",
897
+ borderRadius: "var(--radius-md)",
898
+ fontSize: "14px",
899
+ cursor: "pointer"
900
+ }}
901
+ >
902
+ Cancel
903
+ </button>
904
+ <button
905
+ type="submit"
906
+ disabled={installing || !installSource.trim()}
907
+ style={{
908
+ background: "var(--accent)",
909
+ border: "none",
910
+ color: "#ffffff",
911
+ padding: "8px 20px",
912
+ borderRadius: "var(--radius-md)",
913
+ fontSize: "14px",
914
+ fontWeight: "600",
915
+ cursor: installing ? "not-allowed" : "pointer"
916
+ }}
917
+ >
918
+ {installing ? "Installing..." : "Install Plugin"}
919
+ </button>
920
+ </div>
921
+ </form>
922
+ </div>
923
+ </div>
924
+ )}
925
+
926
+ {/* Plugin Detail & Interactive Runner Modal */}
927
+ {detailModalPlugin && (
928
+ <div style={{
929
+ position: "fixed",
930
+ inset: 0,
931
+ background: "rgba(0, 0, 0, 0.75)",
932
+ display: "flex",
933
+ alignItems: "center",
934
+ justifyContent: "center",
935
+ zIndex: 1000,
936
+ backdropFilter: "blur(6px)",
937
+ padding: "20px"
938
+ }}>
939
+ <div style={{
940
+ background: "var(--bg-secondary)",
941
+ border: "1px solid var(--border-accent)",
942
+ borderRadius: "var(--radius-xl)",
943
+ padding: "24px 28px",
944
+ width: "100%",
945
+ maxWidth: "760px",
946
+ maxHeight: "88vh",
947
+ display: "flex",
948
+ flexDirection: "column",
949
+ boxShadow: "0 20px 40px rgba(0, 0, 0, 0.5)"
950
+ }}>
951
+ {/* Modal Header */}
952
+ <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: "18px" }}>
953
+ <div style={{ display: "flex", alignItems: "center", gap: "12px" }}>
954
+ <div style={{
955
+ width: "42px",
956
+ height: "42px",
957
+ borderRadius: "10px",
958
+ background: "var(--accent-muted)",
959
+ color: "var(--accent-hover)",
960
+ display: "flex",
961
+ alignItems: "center",
962
+ justifyContent: "center",
963
+ fontSize: "22px"
964
+ }}>
965
+ 🧩
966
+ </div>
967
+ <div>
968
+ <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
969
+ <h2 style={{ margin: 0, fontSize: "18px", fontWeight: "700" }}>{detailModalPlugin.name}</h2>
970
+ <span style={{
971
+ fontSize: "11px",
972
+ fontWeight: "600",
973
+ padding: "2px 7px",
974
+ borderRadius: "6px",
975
+ background: detailModalPlugin.valid ? "var(--success-muted)" : "var(--error-muted)",
976
+ color: detailModalPlugin.valid ? "var(--success)" : "var(--error)",
977
+ border: `1px solid ${detailModalPlugin.valid ? "rgba(52, 211, 153, 0.2)" : "rgba(248, 113, 113, 0.2)"}`
978
+ }}>
979
+ v{detailModalPlugin.version}
980
+ </span>
981
+ </div>
982
+ <span style={{ fontSize: "12px", color: "var(--text-tertiary)", fontFamily: "var(--font-mono)" }}>
983
+ id: {detailModalPlugin.id}
984
+ </span>
985
+ </div>
986
+ </div>
987
+
988
+ <button
989
+ onClick={closeDetail}
990
+ style={{
991
+ background: "none",
992
+ border: "none",
993
+ color: "var(--text-secondary)",
994
+ fontSize: "24px",
995
+ cursor: "pointer",
996
+ lineHeight: 1
997
+ }}
998
+ >
999
+ ×
1000
+ </button>
1001
+ </div>
1002
+
1003
+ {/* Sub-Navigation Tabs */}
1004
+ <div style={{
1005
+ display: "flex",
1006
+ gap: "6px",
1007
+ borderBottom: "1px solid var(--border)",
1008
+ paddingBottom: "12px",
1009
+ marginBottom: "18px",
1010
+ overflowX: "auto"
1011
+ }}>
1012
+ <button
1013
+ onClick={() => setDetailTab("runner")}
1014
+ style={{
1015
+ background: detailTab === "runner" ? "var(--bg-elevated)" : "transparent",
1016
+ color: detailTab === "runner" ? "var(--accent-hover)" : "var(--text-secondary)",
1017
+ border: detailTab === "runner" ? "1px solid var(--border-accent)" : "1px solid transparent",
1018
+ borderRadius: "var(--radius-sm)",
1019
+ padding: "6px 12px",
1020
+ fontSize: "13px",
1021
+ fontWeight: "600",
1022
+ cursor: "pointer",
1023
+ display: "flex",
1024
+ alignItems: "center",
1025
+ gap: "6px"
1026
+ }}
1027
+ >
1028
+ <span>⚡ Interactive Runner</span>
1029
+ </button>
1030
+
1031
+ <button
1032
+ onClick={() => setDetailTab("readme")}
1033
+ style={{
1034
+ background: detailTab === "readme" ? "var(--bg-elevated)" : "transparent",
1035
+ color: detailTab === "readme" ? "var(--accent-hover)" : "var(--text-secondary)",
1036
+ border: detailTab === "readme" ? "1px solid var(--border-accent)" : "1px solid transparent",
1037
+ borderRadius: "var(--radius-sm)",
1038
+ padding: "6px 12px",
1039
+ fontSize: "13px",
1040
+ fontWeight: "600",
1041
+ cursor: "pointer"
1042
+ }}
1043
+ >
1044
+ 📖 Documentation
1045
+ </button>
1046
+
1047
+ <button
1048
+ onClick={() => setDetailTab("schemas")}
1049
+ style={{
1050
+ background: detailTab === "schemas" ? "var(--bg-elevated)" : "transparent",
1051
+ color: detailTab === "schemas" ? "var(--accent-hover)" : "var(--text-secondary)",
1052
+ border: detailTab === "schemas" ? "1px solid var(--border-accent)" : "1px solid transparent",
1053
+ borderRadius: "var(--radius-sm)",
1054
+ padding: "6px 12px",
1055
+ fontSize: "13px",
1056
+ fontWeight: "600",
1057
+ cursor: "pointer"
1058
+ }}
1059
+ >
1060
+ 🧩 SSSS Schemas ({detailModalPlugin.categories?.length || 0})
1061
+ </button>
1062
+
1063
+ <button
1064
+ onClick={() => setDetailTab("tasks")}
1065
+ style={{
1066
+ background: detailTab === "tasks" ? "var(--bg-elevated)" : "transparent",
1067
+ color: detailTab === "tasks" ? "var(--accent-hover)" : "var(--text-secondary)",
1068
+ border: detailTab === "tasks" ? "1px solid var(--border-accent)" : "1px solid transparent",
1069
+ borderRadius: "var(--radius-sm)",
1070
+ padding: "6px 12px",
1071
+ fontSize: "13px",
1072
+ fontWeight: "600",
1073
+ cursor: "pointer"
1074
+ }}
1075
+ >
1076
+ ⏱️ Tasks ({detailModalPlugin.tasks?.length || 0})
1077
+ </button>
1078
+
1079
+ <button
1080
+ onClick={() => setDetailTab("manifest")}
1081
+ style={{
1082
+ background: detailTab === "manifest" ? "var(--bg-elevated)" : "transparent",
1083
+ color: detailTab === "manifest" ? "var(--accent-hover)" : "var(--text-secondary)",
1084
+ border: detailTab === "manifest" ? "1px solid var(--border-accent)" : "1px solid transparent",
1085
+ borderRadius: "var(--radius-sm)",
1086
+ padding: "6px 12px",
1087
+ fontSize: "13px",
1088
+ fontWeight: "600",
1089
+ cursor: "pointer"
1090
+ }}
1091
+ >
1092
+ 📋 Manifest
1093
+ </button>
1094
+ </div>
1095
+
1096
+ {/* Tab Body */}
1097
+ <div style={{ flex: 1, overflowY: "auto", minHeight: "320px" }}>
1098
+ {detailTab === "runner" && (
1099
+ <div>
1100
+ <div style={{ display: "flex", gap: "10px", alignItems: "center", marginBottom: "14px", flexWrap: "wrap" }}>
1101
+ <span style={{ fontSize: "13px", fontWeight: "600", color: "var(--text-secondary)" }}>
1102
+ Command: <code style={{ color: "var(--accent-hover)", background: "var(--bg-primary)", padding: "2px 6px", borderRadius: "4px" }}>total-recall {detailModalPlugin.cli?.command || detailModalPlugin.id}</code>
1103
+ </span>
1104
+
1105
+ <input
1106
+ type="text"
1107
+ placeholder="subcommand (e.g. status, audit, sample)"
1108
+ value={subcommand}
1109
+ onChange={(e) => setSubcommand(e.target.value)}
1110
+ style={{
1111
+ background: "var(--bg-primary)",
1112
+ border: "1px solid var(--border)",
1113
+ borderRadius: "var(--radius-sm)",
1114
+ color: "var(--text-primary)",
1115
+ padding: "6px 10px",
1116
+ fontSize: "13px",
1117
+ width: "160px"
1118
+ }}
1119
+ />
1120
+
1121
+ <input
1122
+ type="text"
1123
+ placeholder="extra args (e.g. --json)"
1124
+ value={customArgs}
1125
+ onChange={(e) => setCustomArgs(e.target.value)}
1126
+ style={{
1127
+ background: "var(--bg-primary)",
1128
+ border: "1px solid var(--border)",
1129
+ borderRadius: "var(--radius-sm)",
1130
+ color: "var(--text-primary)",
1131
+ padding: "6px 10px",
1132
+ fontSize: "13px",
1133
+ flex: 1,
1134
+ minWidth: "140px"
1135
+ }}
1136
+ />
1137
+
1138
+ <button
1139
+ onClick={handleExecute}
1140
+ disabled={executing}
1141
+ style={{
1142
+ background: "linear-gradient(135deg, #10b981 0%, #059669 100%)",
1143
+ color: "#ffffff",
1144
+ border: "none",
1145
+ borderRadius: "var(--radius-sm)",
1146
+ padding: "7px 18px",
1147
+ fontSize: "13px",
1148
+ fontWeight: "600",
1149
+ cursor: executing ? "wait" : "pointer",
1150
+ display: "flex",
1151
+ alignItems: "center",
1152
+ gap: "6px"
1153
+ }}
1154
+ >
1155
+ {executing ? "Running..." : "▶ Execute"}
1156
+ </button>
1157
+ </div>
1158
+
1159
+ {/* Terminal Console View */}
1160
+ <div style={{
1161
+ background: "#090d16",
1162
+ border: "1px solid var(--border)",
1163
+ borderRadius: "var(--radius-md)",
1164
+ padding: "16px 20px",
1165
+ minHeight: "260px",
1166
+ maxHeight: "360px",
1167
+ overflowY: "auto",
1168
+ fontFamily: "var(--font-mono)",
1169
+ fontSize: "12px",
1170
+ lineHeight: "1.6",
1171
+ color: "#e2e8f0",
1172
+ position: "relative"
1173
+ }}>
1174
+ {consoleOutput ? (
1175
+ <pre style={{ margin: 0, whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
1176
+ {consoleOutput}
1177
+ </pre>
1178
+ ) : (
1179
+ <div style={{ color: "var(--text-tertiary)", fontStyle: "italic", paddingTop: "50px", textAlign: "center" }}>
1180
+ Select a subcommand and click "Execute" to run the plugin CLI and capture live output.
1181
+ </div>
1182
+ )}
1183
+
1184
+ {consoleOutput && (
1185
+ <div style={{ position: "absolute", top: "10px", right: "12px", display: "flex", gap: "6px" }}>
1186
+ <button
1187
+ onClick={() => navigator.clipboard.writeText(consoleOutput)}
1188
+ style={{
1189
+ background: "rgba(255, 255, 255, 0.08)",
1190
+ border: "none",
1191
+ color: "var(--text-secondary)",
1192
+ borderRadius: "4px",
1193
+ padding: "3px 8px",
1194
+ fontSize: "11px",
1195
+ cursor: "pointer"
1196
+ }}
1197
+ >
1198
+ Copy
1199
+ </button>
1200
+ <button
1201
+ onClick={() => setConsoleOutput("")}
1202
+ style={{
1203
+ background: "rgba(255, 255, 255, 0.08)",
1204
+ border: "none",
1205
+ color: "var(--text-secondary)",
1206
+ borderRadius: "4px",
1207
+ padding: "3px 8px",
1208
+ fontSize: "11px",
1209
+ cursor: "pointer"
1210
+ }}
1211
+ >
1212
+ Clear
1213
+ </button>
1214
+ </div>
1215
+ )}
1216
+ </div>
1217
+ </div>
1218
+ )}
1219
+
1220
+ {detailTab === "readme" && (
1221
+ <div style={{
1222
+ background: "var(--bg-primary)",
1223
+ border: "1px solid var(--border)",
1224
+ borderRadius: "var(--radius-md)",
1225
+ padding: "20px 24px",
1226
+ maxHeight: "420px",
1227
+ overflowY: "auto",
1228
+ lineHeight: "1.6"
1229
+ }}>
1230
+ {readmeLoading ? (
1231
+ <div style={{ color: "var(--text-tertiary)", textAlign: "center", padding: "40px" }}>Loading documentation...</div>
1232
+ ) : (
1233
+ <pre style={{ margin: 0, whiteSpace: "pre-wrap", wordBreak: "break-word", fontFamily: "inherit", fontSize: "14px", color: "var(--text-primary)" }}>
1234
+ {readmeText || "No README.md documentation found for this plugin."}
1235
+ </pre>
1236
+ )}
1237
+ </div>
1238
+ )}
1239
+
1240
+ {detailTab === "schemas" && (
1241
+ <div style={{ maxHeight: "420px", overflowY: "auto" }}>
1242
+ {detailModalPlugin.categories && detailModalPlugin.categories.length > 0 ? (
1243
+ <div style={{ display: "flex", flexDirection: "column", gap: "10px" }}>
1244
+ {detailModalPlugin.categories.map((cat) => (
1245
+ <div key={cat.name} style={{
1246
+ background: "var(--bg-primary)",
1247
+ border: "1px solid var(--border)",
1248
+ borderRadius: "var(--radius-md)",
1249
+ padding: "14px 18px"
1250
+ }}>
1251
+ <div style={{ display: "flex", alignItems: "center", gap: "8px", marginBottom: "4px" }}>
1252
+ <span style={{ fontWeight: "700", color: "var(--accent-hover)", fontFamily: "var(--font-mono)" }}>
1253
+ {cat.name}
1254
+ </span>
1255
+ <span style={{ fontSize: "11px", background: "var(--bg-elevated)", padding: "2px 6px", borderRadius: "4px", color: "var(--text-tertiary)" }}>
1256
+ type: memory
1257
+ </span>
1258
+ </div>
1259
+ <p style={{ margin: 0, fontSize: "13px", color: "var(--text-secondary)" }}>
1260
+ {cat.description || "Plugin declared SSSS category schema."}
1261
+ </p>
1262
+ </div>
1263
+ ))}
1264
+ </div>
1265
+ ) : (
1266
+ <div style={{ padding: "40px", textAlign: "center", color: "var(--text-tertiary)" }}>
1267
+ This plugin does not declare custom SSSS category schemas.
1268
+ </div>
1269
+ )}
1270
+ </div>
1271
+ )}
1272
+
1273
+ {detailTab === "tasks" && (
1274
+ <div style={{ maxHeight: "420px", overflowY: "auto" }}>
1275
+ {detailModalPlugin.tasks && detailModalPlugin.tasks.length > 0 ? (
1276
+ <div style={{ display: "flex", flexDirection: "column", gap: "10px" }}>
1277
+ {detailModalPlugin.tasks.map((t, idx) => (
1278
+ <div key={idx} style={{
1279
+ background: "var(--bg-primary)",
1280
+ border: "1px solid var(--border)",
1281
+ borderRadius: "var(--radius-md)",
1282
+ padding: "14px 18px",
1283
+ display: "flex",
1284
+ justifyContent: "space-between",
1285
+ alignItems: "center"
1286
+ }}>
1287
+ <div>
1288
+ <div style={{ fontWeight: "600", fontSize: "14px", marginBottom: "4px" }}>
1289
+ {t.intent}
1290
+ </div>
1291
+ <span style={{ fontSize: "12px", color: "var(--text-tertiary)", fontFamily: "var(--font-mono)" }}>
1292
+ cron: {t.schedule}
1293
+ </span>
1294
+ </div>
1295
+ <span style={{ fontSize: "12px", background: "var(--success-muted)", color: "var(--success)", padding: "3px 8px", borderRadius: "6px", fontWeight: "600" }}>
1296
+ Scheduled
1297
+ </span>
1298
+ </div>
1299
+ ))}
1300
+ </div>
1301
+ ) : (
1302
+ <div style={{ padding: "40px", textAlign: "center", color: "var(--text-tertiary)" }}>
1303
+ No background daemon tasks registered for this plugin.
1304
+ </div>
1305
+ )}
1306
+ </div>
1307
+ )}
1308
+
1309
+ {detailTab === "manifest" && (
1310
+ <pre style={{
1311
+ margin: 0,
1312
+ background: "var(--bg-primary)",
1313
+ border: "1px solid var(--border)",
1314
+ borderRadius: "var(--radius-md)",
1315
+ padding: "16px",
1316
+ fontFamily: "var(--font-mono)",
1317
+ fontSize: "12px",
1318
+ color: "var(--accent-hover)",
1319
+ maxHeight: "420px",
1320
+ overflow: "auto"
1321
+ }}>
1322
+ {JSON.stringify(detailModalPlugin.manifest || detailModalPlugin, null, 2)}
1323
+ </pre>
1324
+ )}
1325
+ </div>
1326
+ </div>
1327
+ </div>
1328
+ )}
1329
+ </div>
1330
+ )
1331
+ }