@geminilight/mindos 0.5.51 → 0.5.54

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.
@@ -0,0 +1,116 @@
1
+ 'use client';
2
+
3
+ import { ChevronDown, ChevronRight } from 'lucide-react';
4
+ import type { AgentInfo } from '../settings/types';
5
+ import type { McpContextValue } from '@/hooks/useMcpData';
6
+ import AgentsPanelAgentListRow, { type AgentsPanelAgentListRowCopy } from './AgentsPanelAgentListRow';
7
+
8
+ type AgentsCopy = {
9
+ rosterLabel: string;
10
+ sectionConnected: string;
11
+ sectionDetected: string;
12
+ sectionNotDetected: string;
13
+ };
14
+
15
+ export function AgentsPanelAgentGroups({
16
+ connected,
17
+ detected,
18
+ notFound,
19
+ onOpenDetail,
20
+ selectedAgentKey,
21
+ mcp,
22
+ listCopy,
23
+ showNotDetected,
24
+ setShowNotDetected,
25
+ p,
26
+ }: {
27
+ connected: AgentInfo[];
28
+ detected: AgentInfo[];
29
+ notFound: AgentInfo[];
30
+ onOpenDetail?: (key: string) => void;
31
+ selectedAgentKey?: string | null;
32
+ mcp: Pick<McpContextValue, 'installAgent'>;
33
+ listCopy: AgentsPanelAgentListRowCopy;
34
+ showNotDetected: boolean;
35
+ setShowNotDetected: (v: boolean | ((prev: boolean) => boolean)) => void;
36
+ p: AgentsCopy;
37
+ }) {
38
+ const open = onOpenDetail ?? (() => {});
39
+
40
+ return (
41
+ <div>
42
+ <div className="px-0 py-1 mb-0.5">
43
+ <span className="text-2xs font-semibold text-muted-foreground uppercase tracking-wider">{p.rosterLabel}</span>
44
+ </div>
45
+ {connected.length > 0 && (
46
+ <section className="mb-3">
47
+ <h3 className="text-[11px] font-medium text-muted-foreground/90 uppercase tracking-wider mb-2 pl-0.5">
48
+ {p.sectionConnected} ({connected.length})
49
+ </h3>
50
+ <div className="space-y-1.5">
51
+ {connected.map(agent => (
52
+ <AgentsPanelAgentListRow
53
+ key={agent.key}
54
+ agent={agent}
55
+ agentStatus="connected"
56
+ selected={selectedAgentKey === agent.key}
57
+ onOpenDetail={() => open(agent.key)}
58
+ onInstallAgent={mcp.installAgent}
59
+ copy={listCopy}
60
+ />
61
+ ))}
62
+ </div>
63
+ </section>
64
+ )}
65
+
66
+ {detected.length > 0 && (
67
+ <section className="mb-3">
68
+ <h3 className="text-[11px] font-medium text-muted-foreground/90 uppercase tracking-wider mb-2 pl-0.5">
69
+ {p.sectionDetected} ({detected.length})
70
+ </h3>
71
+ <div className="space-y-1.5">
72
+ {detected.map(agent => (
73
+ <AgentsPanelAgentListRow
74
+ key={agent.key}
75
+ agent={agent}
76
+ agentStatus="detected"
77
+ selected={selectedAgentKey === agent.key}
78
+ onOpenDetail={() => open(agent.key)}
79
+ onInstallAgent={mcp.installAgent}
80
+ copy={listCopy}
81
+ />
82
+ ))}
83
+ </div>
84
+ </section>
85
+ )}
86
+
87
+ {notFound.length > 0 && (
88
+ <section>
89
+ <button
90
+ type="button"
91
+ onClick={() => setShowNotDetected(!showNotDetected)}
92
+ className="flex items-center gap-1 text-[11px] font-medium text-muted-foreground uppercase tracking-wider mb-2 hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm pl-0.5"
93
+ >
94
+ {showNotDetected ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
95
+ {p.sectionNotDetected} ({notFound.length})
96
+ </button>
97
+ {showNotDetected && (
98
+ <div className="space-y-1.5">
99
+ {notFound.map(agent => (
100
+ <AgentsPanelAgentListRow
101
+ key={agent.key}
102
+ agent={agent}
103
+ agentStatus="notFound"
104
+ selected={selectedAgentKey === agent.key}
105
+ onOpenDetail={() => open(agent.key)}
106
+ onInstallAgent={mcp.installAgent}
107
+ copy={listCopy}
108
+ />
109
+ ))}
110
+ </div>
111
+ )}
112
+ </section>
113
+ )}
114
+ </div>
115
+ );
116
+ }
@@ -0,0 +1,101 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { ChevronRight, Loader2 } from 'lucide-react';
5
+ import type { AgentInfo } from '../settings/types';
6
+
7
+ export type AgentsPanelAgentListStatus = 'connected' | 'detected' | 'notFound';
8
+
9
+ export interface AgentsPanelAgentListRowCopy {
10
+ installing: string;
11
+ install: (name: string) => string;
12
+ }
13
+
14
+ export default function AgentsPanelAgentListRow({
15
+ agent,
16
+ agentStatus,
17
+ selected = false,
18
+ onOpenDetail,
19
+ onInstallAgent,
20
+ copy,
21
+ }: {
22
+ agent: AgentInfo;
23
+ agentStatus: AgentsPanelAgentListStatus;
24
+ selected?: boolean;
25
+ onOpenDetail: () => void;
26
+ onInstallAgent: (key: string) => Promise<boolean>;
27
+ copy: AgentsPanelAgentListRowCopy;
28
+ }) {
29
+ const dot =
30
+ agentStatus === 'connected' ? 'bg-emerald-500' : agentStatus === 'detected' ? 'bg-amber-500' : 'bg-zinc-400';
31
+
32
+ return (
33
+ <div
34
+ className={`
35
+ group flex items-center gap-0 rounded-xl border transition-all duration-150
36
+ ${selected
37
+ ? 'border-border ring-2 ring-ring/50 bg-[var(--amber-dim)]/45'
38
+ : 'border-border/70 bg-card/50 hover:border-border hover:bg-muted/25'}
39
+ `}
40
+ >
41
+ <button
42
+ type="button"
43
+ onClick={onOpenDetail}
44
+ className="flex flex-1 min-w-0 items-center gap-2.5 text-left rounded-xl pl-3 pr-2 py-2.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
45
+ >
46
+ <span className={`w-2 h-2 rounded-full shrink-0 ring-2 ring-background ${dot}`} />
47
+ <span className="text-sm font-medium text-foreground truncate leading-tight">{agent.name}</span>
48
+ {agentStatus === 'connected' && agent.transport && (
49
+ <span className="text-2xs font-mono tabular-nums px-1.5 py-0.5 rounded-md bg-muted/90 text-muted-foreground shrink-0 border border-border/50">
50
+ {agent.transport}
51
+ </span>
52
+ )}
53
+ <span className="flex-1 min-w-[4px]" />
54
+ <ChevronRight
55
+ size={15}
56
+ className={`shrink-0 transition-opacity duration-150 ${selected ? 'text-[var(--amber)] opacity-90' : 'text-muted-foreground/45 group-hover:text-muted-foreground/80'}`}
57
+ aria-hidden
58
+ />
59
+ </button>
60
+
61
+ {agentStatus === 'detected' && (
62
+ <div className="pr-2 py-2 shrink-0">
63
+ <AgentInstallButton agentKey={agent.key} agentName={agent.name} onInstallAgent={onInstallAgent} copy={copy} />
64
+ </div>
65
+ )}
66
+ </div>
67
+ );
68
+ }
69
+
70
+ function AgentInstallButton({
71
+ agentKey,
72
+ agentName,
73
+ onInstallAgent,
74
+ copy,
75
+ }: {
76
+ agentKey: string;
77
+ agentName: string;
78
+ onInstallAgent: (key: string) => Promise<boolean>;
79
+ copy: AgentsPanelAgentListRowCopy;
80
+ }) {
81
+ const [installing, setInstalling] = useState(false);
82
+
83
+ const handleInstall = async (e: React.MouseEvent) => {
84
+ e.stopPropagation();
85
+ setInstalling(true);
86
+ await onInstallAgent(agentKey);
87
+ setInstalling(false);
88
+ };
89
+
90
+ return (
91
+ <button
92
+ type="button"
93
+ onClick={handleInstall}
94
+ disabled={installing}
95
+ className="flex items-center gap-1 px-2 py-1.5 text-2xs rounded-lg font-medium text-[var(--amber-foreground)] disabled:opacity-50 transition-colors shrink-0 bg-[var(--amber)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
96
+ >
97
+ {installing ? <Loader2 size={10} className="animate-spin" /> : null}
98
+ {installing ? copy.installing : copy.install(agentName)}
99
+ </button>
100
+ );
101
+ }
@@ -0,0 +1,48 @@
1
+ 'use client';
2
+
3
+ import { type RefObject } from 'react';
4
+ import { LayoutDashboard, Server, Zap } from 'lucide-react';
5
+ import { PanelNavRow } from './PanelNavRow';
6
+
7
+ type HubCopy = {
8
+ navOverview: string;
9
+ navMcp: string;
10
+ navSkills: string;
11
+ };
12
+
13
+ export function AgentsPanelHubNav({
14
+ copy,
15
+ connectedCount,
16
+ overviewRef,
17
+ skillsRef,
18
+ scrollTo,
19
+ openAdvancedConfig,
20
+ }: {
21
+ copy: HubCopy;
22
+ connectedCount: number;
23
+ overviewRef: RefObject<HTMLDivElement | null>;
24
+ skillsRef: RefObject<HTMLDivElement | null>;
25
+ scrollTo: (el: HTMLElement | null) => void;
26
+ openAdvancedConfig: () => void;
27
+ }) {
28
+ return (
29
+ <div className="py-2">
30
+ <PanelNavRow
31
+ icon={<LayoutDashboard size={14} className="text-[var(--amber)]" />}
32
+ title={copy.navOverview}
33
+ badge={<span className="text-2xs tabular-nums text-muted-foreground">{connectedCount}</span>}
34
+ onClick={() => scrollTo(overviewRef.current)}
35
+ />
36
+ <PanelNavRow
37
+ icon={<Server size={14} className="text-muted-foreground" />}
38
+ title={copy.navMcp}
39
+ onClick={openAdvancedConfig}
40
+ />
41
+ <PanelNavRow
42
+ icon={<Zap size={14} className="text-muted-foreground" />}
43
+ title={copy.navSkills}
44
+ onClick={() => scrollTo(skillsRef.current)}
45
+ />
46
+ </div>
47
+ );
48
+ }
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
 
3
- import Link from 'next/link';
4
- import { Lightbulb, Blocks, Zap, LayoutTemplate, ChevronRight, User, Download, RefreshCw, Repeat, Rocket, Search, Handshake, ShieldCheck } from 'lucide-react';
3
+ import { Lightbulb, Blocks, Zap, LayoutTemplate, User, Download, RefreshCw, Repeat, Rocket, Search, Handshake, ShieldCheck } from 'lucide-react';
5
4
  import PanelHeader from './PanelHeader';
5
+ import { PanelNavRow, ComingSoonBadge } from './PanelNavRow';
6
6
  import { useLocale } from '@/lib/LocaleContext';
7
7
  import { useCases } from '@/components/explore/use-cases';
8
8
  import { openAskModal } from '@/hooks/useAskModal';
@@ -13,46 +13,6 @@ interface DiscoverPanelProps {
13
13
  onMaximize?: () => void;
14
14
  }
15
15
 
16
- /** Navigation entry — clickable row linking to a page or showing coming soon */
17
- function NavEntry({
18
- icon,
19
- title,
20
- badge,
21
- href,
22
- onClick,
23
- }: {
24
- icon: React.ReactNode;
25
- title: string;
26
- badge?: React.ReactNode;
27
- href?: string;
28
- onClick?: () => void;
29
- }) {
30
- const content = (
31
- <>
32
- <span className="flex items-center justify-center w-7 h-7 rounded-md bg-muted shrink-0">{icon}</span>
33
- <span className="text-sm font-medium text-foreground flex-1">{title}</span>
34
- {badge}
35
- <ChevronRight size={14} className="text-muted-foreground shrink-0" />
36
- </>
37
- );
38
-
39
- const className = "flex items-center gap-3 px-4 py-2.5 hover:bg-muted/50 transition-colors cursor-pointer";
40
-
41
- if (href) {
42
- return <Link href={href} className={className}>{content}</Link>;
43
- }
44
- return <button onClick={onClick} className={`${className} w-full`}>{content}</button>;
45
- }
46
-
47
- /** Coming soon badge */
48
- function ComingSoonBadge({ label }: { label: string }) {
49
- return (
50
- <span className="text-2xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground shrink-0">
51
- {label}
52
- </span>
53
- );
54
- }
55
-
56
16
  /** Compact use case row */
57
17
  function UseCaseRow({
58
18
  icon,
@@ -112,23 +72,23 @@ export default function DiscoverPanel({ active, maximized, onMaximize }: Discove
112
72
  <div className="flex-1 overflow-y-auto min-h-0">
113
73
  {/* Navigation entries */}
114
74
  <div className="py-2">
115
- <NavEntry
75
+ <PanelNavRow
116
76
  icon={<Lightbulb size={14} className="text-[var(--amber)]" />}
117
77
  title={d.useCases}
118
78
  badge={<span className="text-2xs tabular-nums text-muted-foreground">{useCases.length}</span>}
119
79
  href="/explore"
120
80
  />
121
- <NavEntry
81
+ <PanelNavRow
122
82
  icon={<Blocks size={14} className="text-muted-foreground" />}
123
83
  title={d.pluginMarket}
124
84
  badge={<ComingSoonBadge label={d.comingSoon} />}
125
85
  />
126
- <NavEntry
86
+ <PanelNavRow
127
87
  icon={<Zap size={14} className="text-muted-foreground" />}
128
88
  title={d.skillMarket}
129
89
  badge={<ComingSoonBadge label={d.comingSoon} />}
130
90
  />
131
- <NavEntry
91
+ <PanelNavRow
132
92
  icon={<LayoutTemplate size={14} className="text-muted-foreground" />}
133
93
  title={d.spaceTemplates}
134
94
  badge={<ComingSoonBadge label={d.comingSoon} />}
@@ -0,0 +1,82 @@
1
+ 'use client';
2
+
3
+ import type { ReactNode } from 'react';
4
+ import { UserRound, Bookmark, Sun, History, Brain } from 'lucide-react';
5
+ import PanelHeader from './PanelHeader';
6
+ import { ComingSoonBadge } from './PanelNavRow';
7
+ import { useLocale } from '@/lib/LocaleContext';
8
+
9
+ interface EchoPanelProps {
10
+ active: boolean;
11
+ maximized?: boolean;
12
+ onMaximize?: () => void;
13
+ }
14
+
15
+ function EchoPlaceholdSection({
16
+ icon,
17
+ title,
18
+ hint,
19
+ comingSoonLabel,
20
+ }: {
21
+ icon: ReactNode;
22
+ title: string;
23
+ hint: string;
24
+ comingSoonLabel: string;
25
+ }) {
26
+ return (
27
+ <div className="px-4 py-2.5 border-b border-border/60 last:border-b-0">
28
+ <div className="flex items-center gap-2.5">
29
+ <span className="flex items-center justify-center w-7 h-7 rounded-md bg-muted shrink-0 text-muted-foreground">{icon}</span>
30
+ <span className="text-sm font-medium text-foreground flex-1 text-left">{title}</span>
31
+ <ComingSoonBadge label={comingSoonLabel} />
32
+ </div>
33
+ <p className="text-2xs text-muted-foreground leading-relaxed mt-1.5 pl-9">{hint}</p>
34
+ </div>
35
+ );
36
+ }
37
+
38
+ export default function EchoPanel({ active, maximized, onMaximize }: EchoPanelProps) {
39
+ const { t } = useLocale();
40
+ const e = t.panels.echo;
41
+ const soon = e.comingSoon;
42
+
43
+ return (
44
+ <div className={`flex flex-col h-full ${active ? '' : 'hidden'}`}>
45
+ <PanelHeader title={e.title} maximized={maximized} onMaximize={onMaximize} />
46
+ <div className="flex-1 overflow-y-auto min-h-0">
47
+ <div className="py-1">
48
+ <EchoPlaceholdSection
49
+ icon={<UserRound size={14} />}
50
+ title={e.aboutYouTitle}
51
+ hint={e.aboutYouHint}
52
+ comingSoonLabel={soon}
53
+ />
54
+ <EchoPlaceholdSection
55
+ icon={<Bookmark size={14} />}
56
+ title={e.continuedTitle}
57
+ hint={e.continuedHint}
58
+ comingSoonLabel={soon}
59
+ />
60
+ <EchoPlaceholdSection
61
+ icon={<Sun size={14} />}
62
+ title={e.dailyEchoTitle}
63
+ hint={e.dailyEchoHint}
64
+ comingSoonLabel={soon}
65
+ />
66
+ <EchoPlaceholdSection
67
+ icon={<History size={14} />}
68
+ title={e.pastYouTitle}
69
+ hint={e.pastYouHint}
70
+ comingSoonLabel={soon}
71
+ />
72
+ <EchoPlaceholdSection
73
+ icon={<Brain size={14} />}
74
+ title={e.intentGrowthTitle}
75
+ hint={e.intentGrowthHint}
76
+ comingSoonLabel={soon}
77
+ />
78
+ </div>
79
+ </div>
80
+ </div>
81
+ );
82
+ }
@@ -0,0 +1,51 @@
1
+ 'use client';
2
+
3
+ import type { ReactNode } from 'react';
4
+ import Link from 'next/link';
5
+ import { ChevronRight } from 'lucide-react';
6
+
7
+ /** Row matching Discover panel nav: icon tile, title, optional badge, chevron. */
8
+ export function PanelNavRow({
9
+ icon,
10
+ title,
11
+ badge,
12
+ href,
13
+ onClick,
14
+ }: {
15
+ icon: ReactNode;
16
+ title: string;
17
+ badge?: React.ReactNode;
18
+ href?: string;
19
+ onClick?: () => void;
20
+ }) {
21
+ const content = (
22
+ <>
23
+ <span className="flex items-center justify-center w-7 h-7 rounded-md bg-muted shrink-0">{icon}</span>
24
+ <span className="text-sm font-medium text-foreground flex-1 text-left">{title}</span>
25
+ {badge}
26
+ <ChevronRight size={14} className="text-muted-foreground shrink-0" />
27
+ </>
28
+ );
29
+
30
+ const className =
31
+ 'flex items-center gap-3 px-4 py-2.5 hover:bg-muted/50 transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm';
32
+
33
+ if (href) {
34
+ return (
35
+ <Link href={href} className={className}>
36
+ {content}
37
+ </Link>
38
+ );
39
+ }
40
+ return (
41
+ <button type="button" onClick={onClick} className={`${className} w-full`}>
42
+ {content}
43
+ </button>
44
+ );
45
+ }
46
+
47
+ export function ComingSoonBadge({ label }: { label: string }) {
48
+ return (
49
+ <span className="text-2xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground shrink-0">{label}</span>
50
+ );
51
+ }
@@ -0,0 +1,13 @@
1
+ export type AgentsPanelAgentDetailStatus = 'connected' | 'detected' | 'notFound';
2
+
3
+ export function resolveAgentDetailStatus(
4
+ key: string,
5
+ connected: { key: string }[],
6
+ detected: { key: string }[],
7
+ notFound: { key: string }[],
8
+ ): AgentsPanelAgentDetailStatus | null {
9
+ if (connected.some(a => a.key === key)) return 'connected';
10
+ if (detected.some(a => a.key === key)) return 'detected';
11
+ if (notFound.some(a => a.key === key)) return 'notFound';
12
+ return null;
13
+ }
@@ -3,10 +3,11 @@
3
3
  import { useState, useEffect, useCallback, useMemo } from 'react';
4
4
  import {
5
5
  Loader2, ChevronDown, ChevronRight,
6
- Plus, X, Search,
6
+ Plus, X, Search, Copy, Check,
7
7
  } from 'lucide-react';
8
8
  import { apiFetch } from '@/lib/api';
9
9
  import { useMcpDataOptional } from '@/hooks/useMcpData';
10
+ import { copyToClipboard } from '@/lib/clipboard';
10
11
  import type { SkillInfo, McpSkillsSectionProps } from './types';
11
12
  import SkillRow from './McpSkillRow';
12
13
  import SkillCreateForm from './McpSkillCreateForm';
@@ -24,7 +25,7 @@ export default function SkillsSection({ t }: McpSkillsSectionProps) {
24
25
  const [createError, setCreateError] = useState('');
25
26
 
26
27
  const [search, setSearch] = useState('');
27
- const [builtinCollapsed, setBuiltinCollapsed] = useState(true);
28
+ const [builtinCollapsed, setBuiltinCollapsed] = useState(false);
28
29
  const [editing, setEditing] = useState<string | null>(null);
29
30
  const [editContent, setEditContent] = useState('');
30
31
  const [editError, setEditError] = useState('');
@@ -354,6 +355,91 @@ export default function SkillsSection({ t }: McpSkillsSectionProps) {
354
355
  {m?.addSkill ?? '+ Add Skill'}
355
356
  </button>
356
357
  )}
358
+
359
+ {/* CLI install hint with agent selector */}
360
+ <SkillCliHint
361
+ agents={mcp?.agents ?? []}
362
+ skillName={(() => {
363
+ const mindosEnabled = skills.find(s => s.name === 'mindos')?.enabled ?? true;
364
+ return mindosEnabled ? 'mindos' : 'mindos-zh';
365
+ })()}
366
+ m={m}
367
+ />
368
+ </div>
369
+ );
370
+ }
371
+
372
+ /* ── Skill CLI Install Hint ── */
373
+
374
+ function SkillCliHint({ agents, skillName, m }: {
375
+ agents: { key: string; name: string; present?: boolean; installed?: boolean }[];
376
+ skillName: string;
377
+ m: Record<string, any> | undefined;
378
+ }) {
379
+ const [selectedAgent, setSelectedAgent] = useState('claude-code');
380
+ const [copied, setCopied] = useState(false);
381
+
382
+ const cmd = `npx skills add GeminiLight/MindOS --skill ${skillName} -a ${selectedAgent} -g -y`;
383
+ const skillPath = `~/.agents/skills/${skillName}/SKILL.md`;
384
+
385
+ const handleCopy = async () => {
386
+ const ok = await copyToClipboard(cmd);
387
+ if (ok) { setCopied(true); setTimeout(() => setCopied(false), 2000); }
388
+ };
389
+
390
+ // Group agents: connected first, then detected, then not found
391
+ const connected = agents.filter(a => a.present && a.installed);
392
+ const detected = agents.filter(a => a.present && !a.installed);
393
+ const notFound = agents.filter(a => !a.present);
394
+
395
+ return (
396
+ <div className="border-t border-border pt-3 mt-3 space-y-2.5">
397
+ <p className="text-2xs font-medium text-muted-foreground">
398
+ {m?.cliInstallHint ?? 'Install via CLI:'}
399
+ </p>
400
+
401
+ {/* Agent selector */}
402
+ <div className="relative">
403
+ <select
404
+ value={selectedAgent}
405
+ onChange={(e) => setSelectedAgent(e.target.value)}
406
+ className="w-full appearance-none px-2.5 py-1.5 pr-7 text-2xs rounded-md border border-border bg-background text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
407
+ >
408
+ {connected.length > 0 && (
409
+ <optgroup label={m?.connectedGroup ?? 'Connected'}>
410
+ {connected.map(a => <option key={a.key} value={a.key}>✓ {a.name}</option>)}
411
+ </optgroup>
412
+ )}
413
+ {detected.length > 0 && (
414
+ <optgroup label={m?.detectedGroup ?? 'Detected'}>
415
+ {detected.map(a => <option key={a.key} value={a.key}>○ {a.name}</option>)}
416
+ </optgroup>
417
+ )}
418
+ {notFound.length > 0 && (
419
+ <optgroup label={m?.notFoundGroup ?? 'Not Installed'}>
420
+ {notFound.map(a => <option key={a.key} value={a.key}>· {a.name}</option>)}
421
+ </optgroup>
422
+ )}
423
+ </select>
424
+ <ChevronDown size={12} className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground pointer-events-none" />
425
+ </div>
426
+
427
+ {/* Command */}
428
+ <div className="flex items-center gap-1.5">
429
+ <code className="flex-1 text-[10px] font-mono bg-muted/50 border border-border rounded-lg px-2.5 py-2 text-muted-foreground select-all overflow-x-auto whitespace-nowrap">
430
+ {cmd}
431
+ </code>
432
+ <button onClick={handleCopy}
433
+ className="p-1.5 rounded-md border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors shrink-0">
434
+ {copied ? <Check size={11} /> : <Copy size={11} />}
435
+ </button>
436
+ </div>
437
+
438
+ {/* Path hint */}
439
+ <p className="text-2xs text-muted-foreground">
440
+ {m?.skillPathHint ?? 'Skill files installed at:'}{' '}
441
+ <code className="font-mono text-[10px] bg-muted px-1 py-0.5 rounded">{skillPath}</code>
442
+ </p>
357
443
  </div>
358
444
  );
359
445
  }
@@ -219,6 +219,32 @@ function AgentConfigViewer({ connectedAgents, detectedAgents, notFoundAgents, cu
219
219
 
220
220
  {currentAgent && (
221
221
  <>
222
+ {/* Agent status badge */}
223
+ <div className="flex items-center gap-2">
224
+ {currentAgent.present && currentAgent.installed ? (
225
+ <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-2xs font-medium bg-emerald-500/10 text-emerald-600 dark:text-emerald-400">
226
+ <span className="w-1.5 h-1.5 rounded-full bg-emerald-500 inline-block" />
227
+ {m?.tagConnected ?? 'Connected'}
228
+ </span>
229
+ ) : currentAgent.present && !currentAgent.installed ? (
230
+ <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-2xs font-medium" style={{ background: 'var(--amber-subtle, rgba(200,135,58,0.1))', color: 'var(--amber)' }}>
231
+ <span className="w-1.5 h-1.5 rounded-full inline-block" style={{ background: 'var(--amber)' }} />
232
+ {m?.tagDetected ?? 'Detected — not configured'}
233
+ </span>
234
+ ) : (
235
+ <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-2xs font-medium bg-muted text-muted-foreground">
236
+ <span className="w-1.5 h-1.5 rounded-full bg-zinc-400 inline-block" />
237
+ {m?.tagNotInstalled ?? 'Not installed'}
238
+ </span>
239
+ )}
240
+ {currentAgent.transport && (
241
+ <span className="px-1.5 py-0.5 rounded text-2xs bg-muted text-muted-foreground">{currentAgent.transport}</span>
242
+ )}
243
+ {currentAgent.scope && (
244
+ <span className="px-1.5 py-0.5 rounded text-2xs bg-muted text-muted-foreground">{currentAgent.scope}</span>
245
+ )}
246
+ </div>
247
+
222
248
  {/* Transport toggle */}
223
249
  <div className="flex items-center rounded-lg border border-border overflow-hidden w-fit">
224
250
  <button