claudeup 3.16.0 → 4.0.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 (45) hide show
  1. package/package.json +1 -1
  2. package/src/data/predefined-profiles.js +191 -0
  3. package/src/data/predefined-profiles.ts +205 -0
  4. package/src/ui/adapters/pluginsAdapter.js +139 -0
  5. package/src/ui/adapters/pluginsAdapter.ts +202 -0
  6. package/src/ui/adapters/settingsAdapter.js +111 -0
  7. package/src/ui/adapters/settingsAdapter.ts +165 -0
  8. package/src/ui/components/ScrollableList.js +4 -4
  9. package/src/ui/components/ScrollableList.tsx +4 -4
  10. package/src/ui/components/SearchInput.js +2 -2
  11. package/src/ui/components/SearchInput.tsx +3 -3
  12. package/src/ui/components/StyledText.js +1 -1
  13. package/src/ui/components/StyledText.tsx +5 -1
  14. package/src/ui/components/layout/ProgressBar.js +1 -1
  15. package/src/ui/components/layout/ProgressBar.tsx +1 -5
  16. package/src/ui/components/modals/InputModal.tsx +1 -6
  17. package/src/ui/components/modals/LoadingModal.js +1 -1
  18. package/src/ui/components/modals/LoadingModal.tsx +1 -3
  19. package/src/ui/hooks/index.js +3 -3
  20. package/src/ui/hooks/index.ts +3 -3
  21. package/src/ui/hooks/useKeyboard.ts +1 -3
  22. package/src/ui/hooks/useKeyboardHandler.js +9 -9
  23. package/src/ui/hooks/useKeyboardHandler.ts +9 -9
  24. package/src/ui/renderers/cliToolRenderers.js +33 -0
  25. package/src/ui/renderers/cliToolRenderers.tsx +153 -0
  26. package/src/ui/renderers/mcpRenderers.js +26 -0
  27. package/src/ui/renderers/mcpRenderers.tsx +145 -0
  28. package/src/ui/renderers/pluginRenderers.js +124 -0
  29. package/src/ui/renderers/pluginRenderers.tsx +362 -0
  30. package/src/ui/renderers/profileRenderers.js +172 -0
  31. package/src/ui/renderers/profileRenderers.tsx +410 -0
  32. package/src/ui/renderers/settingsRenderers.js +69 -0
  33. package/src/ui/renderers/settingsRenderers.tsx +205 -0
  34. package/src/ui/screens/CliToolsScreen.js +14 -58
  35. package/src/ui/screens/CliToolsScreen.tsx +36 -196
  36. package/src/ui/screens/EnvVarsScreen.js +12 -168
  37. package/src/ui/screens/EnvVarsScreen.tsx +16 -327
  38. package/src/ui/screens/McpScreen.js +12 -62
  39. package/src/ui/screens/McpScreen.tsx +21 -190
  40. package/src/ui/screens/PluginsScreen.js +52 -425
  41. package/src/ui/screens/PluginsScreen.tsx +70 -758
  42. package/src/ui/screens/ProfilesScreen.js +104 -68
  43. package/src/ui/screens/ProfilesScreen.tsx +147 -221
  44. package/src/ui/screens/SkillsScreen.js +16 -16
  45. package/src/ui/screens/SkillsScreen.tsx +20 -23
@@ -1,4 +1,4 @@
1
- import { useKeyboard as useOpenTUIKeyboard } from '@opentui/react';
1
+ import { useKeyboard as useOpenTUIKeyboard } from "@opentui/react";
2
2
  /**
3
3
  * useKeyboardHandler hook
4
4
  * Provides Ink-like API for keyboard events
@@ -23,17 +23,17 @@ export function useKeyboardHandler(handler) {
23
23
  ctrl: event.ctrl || false,
24
24
  shift: event.shift || false,
25
25
  meta: event.meta || false,
26
- return: event.name === 'enter' || event.name === 'return',
27
- upArrow: event.name === 'up',
28
- downArrow: event.name === 'down',
29
- leftArrow: event.name === 'left',
30
- rightArrow: event.name === 'right',
31
- escape: event.name === 'escape',
32
- tab: event.name === 'tab',
26
+ return: event.name === "enter" || event.name === "return",
27
+ upArrow: event.name === "up",
28
+ downArrow: event.name === "down",
29
+ leftArrow: event.name === "left",
30
+ rightArrow: event.name === "right",
31
+ escape: event.name === "escape",
32
+ tab: event.name === "tab",
33
33
  };
34
34
  // For printable characters, pass the character as input
35
35
  // For special keys, pass empty string
36
- const input = event.name.length === 1 ? event.name : '';
36
+ const input = event.name.length === 1 ? event.name : "";
37
37
  handler(input, key);
38
38
  });
39
39
  }
@@ -1,4 +1,4 @@
1
- import { useKeyboard as useOpenTUIKeyboard } from '@opentui/react';
1
+ import { useKeyboard as useOpenTUIKeyboard } from "@opentui/react";
2
2
 
3
3
  /**
4
4
  * Wrapper hook for OpenTUI's useKeyboard that provides an Ink-like API
@@ -45,18 +45,18 @@ export function useKeyboardHandler(handler: KeyHandler): void {
45
45
  ctrl: event.ctrl || false,
46
46
  shift: event.shift || false,
47
47
  meta: event.meta || false,
48
- return: event.name === 'enter' || event.name === 'return',
49
- upArrow: event.name === 'up',
50
- downArrow: event.name === 'down',
51
- leftArrow: event.name === 'left',
52
- rightArrow: event.name === 'right',
53
- escape: event.name === 'escape',
54
- tab: event.name === 'tab',
48
+ return: event.name === "enter" || event.name === "return",
49
+ upArrow: event.name === "up",
50
+ downArrow: event.name === "down",
51
+ leftArrow: event.name === "left",
52
+ rightArrow: event.name === "right",
53
+ escape: event.name === "escape",
54
+ tab: event.name === "tab",
55
55
  };
56
56
 
57
57
  // For printable characters, pass the character as input
58
58
  // For special keys, pass empty string
59
- const input = event.name.length === 1 ? event.name : '';
59
+ const input = event.name.length === 1 ? event.name : "";
60
60
 
61
61
  handler(input, key);
62
62
  });
@@ -0,0 +1,33 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "@opentui/react/jsx-runtime";
2
+ import { theme } from "../theme.js";
3
+ // ─── Row renderer ──────────────────────────────────────────────────────────────
4
+ export function renderCliToolRow(status, _index, isSelected) {
5
+ const { tool, installed, installedVersion, hasUpdate, checking } = status;
6
+ let icon;
7
+ let iconColor;
8
+ if (!installed) {
9
+ icon = "○";
10
+ iconColor = theme.colors.muted;
11
+ }
12
+ else if (hasUpdate) {
13
+ icon = "⬆";
14
+ iconColor = theme.colors.warning;
15
+ }
16
+ else {
17
+ icon = "●";
18
+ iconColor = theme.colors.success;
19
+ }
20
+ const versionText = installedVersion ? `v${installedVersion}` : "";
21
+ if (isSelected) {
22
+ return (_jsxs("text", { bg: theme.selection.bg, fg: theme.selection.fg, children: [" ", icon, " ", tool.displayName, " ", versionText, checking ? "..." : "", " "] }));
23
+ }
24
+ return (_jsxs("text", { children: [_jsx("span", { fg: iconColor, children: icon }), _jsxs("span", { fg: theme.colors.text, children: [" ", tool.displayName] }), versionText ? _jsxs("span", { fg: theme.colors.success, children: [" ", versionText] }) : null, checking ? _jsx("span", { fg: theme.colors.muted, children: "..." }) : null] }));
25
+ }
26
+ // ─── Detail renderer ───────────────────────────────────────────────────────────
27
+ export function renderCliToolDetail(status) {
28
+ if (!status) {
29
+ return (_jsx("box", { flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1, children: _jsx("text", { fg: theme.colors.muted, children: "Select a tool to see details" }) }));
30
+ }
31
+ const { tool, installed, installedVersion, latestVersion, hasUpdate, checking } = status;
32
+ return (_jsxs("box", { flexDirection: "column", children: [_jsxs("box", { marginBottom: 1, children: [_jsx("text", { fg: theme.colors.info, children: _jsxs("strong", { children: ["⚙ ", tool.displayName] }) }), hasUpdate ? _jsx("text", { fg: theme.colors.warning, children: " \u2B06" }) : null] }), _jsx("text", { fg: theme.colors.muted, children: tool.description }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("box", { children: [_jsx("text", { fg: theme.colors.muted, children: "Status " }), !installed ? (_jsx("text", { fg: theme.colors.muted, children: "○ Not installed" })) : checking ? (_jsx("text", { fg: theme.colors.success, children: "● Checking..." })) : hasUpdate ? (_jsx("text", { fg: theme.colors.warning, children: "● Update available" })) : (_jsx("text", { fg: theme.colors.success, children: "● Up to date" }))] }), installedVersion ? (_jsxs("box", { children: [_jsx("text", { fg: theme.colors.muted, children: "Installed " }), _jsxs("text", { fg: theme.colors.success, children: ["v", installedVersion] })] })) : null, latestVersion ? (_jsxs("box", { children: [_jsx("text", { fg: theme.colors.muted, children: "Latest " }), _jsxs("text", { fg: theme.colors.text, children: ["v", latestVersion] })] })) : null, _jsxs("box", { children: [_jsx("text", { fg: theme.colors.muted, children: "Website " }), _jsx("text", { fg: theme.colors.link, children: tool.website })] })] }), _jsx("box", { marginTop: 2, children: !installed ? (_jsxs("box", { children: [_jsxs("text", { bg: theme.colors.success, fg: "black", children: [" ", "Enter", " "] }), _jsx("text", { fg: theme.colors.muted, children: " Install" })] })) : hasUpdate ? (_jsxs("box", { children: [_jsxs("text", { bg: theme.colors.warning, fg: "black", children: [" ", "Enter", " "] }), _jsxs("text", { fg: theme.colors.muted, children: [" Update to v", latestVersion] })] })) : (_jsxs("box", { children: [_jsxs("text", { bg: theme.colors.muted, fg: "white", children: [" ", "Enter", " "] }), _jsx("text", { fg: theme.colors.muted, children: " Reinstall" })] })) })] }));
33
+ }
@@ -0,0 +1,153 @@
1
+ import React from "react";
2
+ import type { CliTool } from "../../data/cli-tools.js";
3
+ import { theme } from "../theme.js";
4
+
5
+ // ─── Status type ───────────────────────────────────────────────────────────────
6
+
7
+ export interface CliToolStatus {
8
+ tool: CliTool;
9
+ installed: boolean;
10
+ installedVersion?: string;
11
+ latestVersion?: string;
12
+ hasUpdate?: boolean;
13
+ checking: boolean;
14
+ }
15
+
16
+ // ─── Row renderer ──────────────────────────────────────────────────────────────
17
+
18
+ export function renderCliToolRow(
19
+ status: CliToolStatus,
20
+ _index: number,
21
+ isSelected: boolean,
22
+ ): React.ReactNode {
23
+ const { tool, installed, installedVersion, hasUpdate, checking } = status;
24
+
25
+ let icon: string;
26
+ let iconColor: string;
27
+
28
+ if (!installed) {
29
+ icon = "○";
30
+ iconColor = theme.colors.muted;
31
+ } else if (hasUpdate) {
32
+ icon = "⬆";
33
+ iconColor = theme.colors.warning;
34
+ } else {
35
+ icon = "●";
36
+ iconColor = theme.colors.success;
37
+ }
38
+
39
+ const versionText = installedVersion ? `v${installedVersion}` : "";
40
+
41
+ if (isSelected) {
42
+ return (
43
+ <text bg={theme.selection.bg} fg={theme.selection.fg}>
44
+ {" "}
45
+ {icon} {tool.displayName} {versionText}
46
+ {checking ? "..." : ""}{" "}
47
+ </text>
48
+ );
49
+ }
50
+
51
+ return (
52
+ <text>
53
+ <span fg={iconColor}>{icon}</span>
54
+ <span fg={theme.colors.text}> {tool.displayName}</span>
55
+ {versionText ? <span fg={theme.colors.success}> {versionText}</span> : null}
56
+ {checking ? <span fg={theme.colors.muted}>{"..."}</span> : null}
57
+ </text>
58
+ );
59
+ }
60
+
61
+ // ─── Detail renderer ───────────────────────────────────────────────────────────
62
+
63
+ export function renderCliToolDetail(
64
+ status: CliToolStatus | undefined,
65
+ ): React.ReactNode {
66
+ if (!status) {
67
+ return (
68
+ <box
69
+ flexDirection="column"
70
+ alignItems="center"
71
+ justifyContent="center"
72
+ flexGrow={1}
73
+ >
74
+ <text fg={theme.colors.muted}>Select a tool to see details</text>
75
+ </box>
76
+ );
77
+ }
78
+
79
+ const { tool, installed, installedVersion, latestVersion, hasUpdate, checking } =
80
+ status;
81
+
82
+ return (
83
+ <box flexDirection="column">
84
+ <box marginBottom={1}>
85
+ <text fg={theme.colors.info}>
86
+ <strong>{"⚙ "}{tool.displayName}</strong>
87
+ </text>
88
+ {hasUpdate ? <text fg={theme.colors.warning}> ⬆</text> : null}
89
+ </box>
90
+
91
+ <text fg={theme.colors.muted}>{tool.description}</text>
92
+
93
+ <box marginTop={1} flexDirection="column">
94
+ <box>
95
+ <text fg={theme.colors.muted}>{"Status "}</text>
96
+ {!installed ? (
97
+ <text fg={theme.colors.muted}>{"○ Not installed"}</text>
98
+ ) : checking ? (
99
+ <text fg={theme.colors.success}>{"● Checking..."}</text>
100
+ ) : hasUpdate ? (
101
+ <text fg={theme.colors.warning}>{"● Update available"}</text>
102
+ ) : (
103
+ <text fg={theme.colors.success}>{"● Up to date"}</text>
104
+ )}
105
+ </box>
106
+ {installedVersion ? (
107
+ <box>
108
+ <text fg={theme.colors.muted}>{"Installed "}</text>
109
+ <text fg={theme.colors.success}>v{installedVersion}</text>
110
+ </box>
111
+ ) : null}
112
+ {latestVersion ? (
113
+ <box>
114
+ <text fg={theme.colors.muted}>{"Latest "}</text>
115
+ <text fg={theme.colors.text}>v{latestVersion}</text>
116
+ </box>
117
+ ) : null}
118
+ <box>
119
+ <text fg={theme.colors.muted}>{"Website "}</text>
120
+ <text fg={theme.colors.link}>{tool.website}</text>
121
+ </box>
122
+ </box>
123
+
124
+ <box marginTop={2}>
125
+ {!installed ? (
126
+ <box>
127
+ <text bg={theme.colors.success} fg="black">
128
+ {" "}
129
+ Enter{" "}
130
+ </text>
131
+ <text fg={theme.colors.muted}> Install</text>
132
+ </box>
133
+ ) : hasUpdate ? (
134
+ <box>
135
+ <text bg={theme.colors.warning} fg="black">
136
+ {" "}
137
+ Enter{" "}
138
+ </text>
139
+ <text fg={theme.colors.muted}> Update to v{latestVersion}</text>
140
+ </box>
141
+ ) : (
142
+ <box>
143
+ <text bg={theme.colors.muted} fg="white">
144
+ {" "}
145
+ Enter{" "}
146
+ </text>
147
+ <text fg={theme.colors.muted}> Reinstall</text>
148
+ </box>
149
+ )}
150
+ </box>
151
+ </box>
152
+ );
153
+ }
@@ -0,0 +1,26 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "@opentui/react/jsx-runtime";
2
+ import { theme } from "../theme.js";
3
+ // ─── Row renderers ─────────────────────────────────────────────────────────────
4
+ export function renderMcpRow(item, _index, isSelected) {
5
+ if (item.kind === "category") {
6
+ return (_jsx("text", { fg: theme.selection.bg, children: _jsxs("strong", { children: ["▸ ", item.label] }) }));
7
+ }
8
+ const { server, isInstalled } = item;
9
+ const icon = isInstalled ? "●" : "○";
10
+ const iconColor = isInstalled ? theme.colors.success : theme.colors.muted;
11
+ if (isSelected) {
12
+ return (_jsxs("text", { bg: theme.selection.bg, fg: theme.selection.fg, children: [" ", icon, " ", server.name, " "] }));
13
+ }
14
+ return (_jsxs("text", { children: [_jsx("span", { fg: iconColor, children: icon }), _jsxs("span", { fg: theme.colors.text, children: [" ", server.name] }), server.requiresConfig ? (_jsx("span", { fg: theme.colors.warning, children: " \u2699" })) : null] }));
15
+ }
16
+ // ─── Detail renderer ───────────────────────────────────────────────────────────
17
+ export function renderMcpDetail(item, loading) {
18
+ if (loading) {
19
+ return _jsx("text", { fg: theme.colors.muted, children: "Loading MCP servers..." });
20
+ }
21
+ if (!item || item.kind === "category") {
22
+ return (_jsx("box", { flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1, children: _jsx("text", { fg: theme.colors.muted, children: "Select a server to see details" }) }));
23
+ }
24
+ const { server, isInstalled } = item;
25
+ return (_jsxs("box", { flexDirection: "column", children: [_jsxs("box", { marginBottom: 1, children: [_jsx("text", { fg: theme.colors.info, children: _jsxs("strong", { children: ["⚡ ", server.name] }) }), server.requiresConfig ? (_jsx("text", { fg: theme.colors.warning, children: " \u2699" })) : null] }), _jsx("text", { fg: theme.colors.muted, children: server.description }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("box", { children: [_jsx("text", { fg: theme.colors.muted, children: "Status " }), isInstalled ? (_jsx("text", { fg: theme.colors.success, children: "● Installed" })) : (_jsx("text", { fg: theme.colors.muted, children: "○ Not installed" }))] }), _jsxs("box", { children: [_jsx("text", { fg: theme.colors.muted, children: "Type " }), _jsx("text", { fg: theme.colors.text, children: server.type === "http" ? "HTTP" : "Command" })] }), server.type === "http" ? (_jsxs("box", { children: [_jsx("text", { fg: theme.colors.muted, children: "URL " }), _jsx("text", { fg: theme.colors.link, children: server.url })] })) : (_jsxs("box", { children: [_jsx("text", { fg: theme.colors.muted, children: "Command " }), _jsx("text", { fg: theme.colors.info, children: server.command })] })), server.requiresConfig ? (_jsxs("box", { children: [_jsx("text", { fg: theme.colors.muted, children: "Config " }), _jsxs("text", { fg: theme.colors.warning, children: [server.configFields?.length ?? 0, " fields required"] })] })) : null] }), _jsx("box", { marginTop: 2, children: isInstalled ? (_jsxs("box", { children: [_jsxs("text", { bg: theme.colors.danger, fg: "white", children: [" ", "Enter", " "] }), _jsx("text", { fg: theme.colors.muted, children: " Remove server" })] })) : (_jsxs("box", { children: [_jsxs("text", { bg: theme.colors.success, fg: "black", children: [" ", "Enter", " "] }), _jsx("text", { fg: theme.colors.muted, children: " Install server" })] })) })] }));
26
+ }
@@ -0,0 +1,145 @@
1
+ import React from "react";
2
+ import type { McpServer } from "../../types/index.js";
3
+ import { theme } from "../theme.js";
4
+
5
+ // ─── List item types ───────────────────────────────────────────────────────────
6
+
7
+ export type McpListItem =
8
+ | { kind: "category"; label: string }
9
+ | { kind: "server"; server: McpServer; isInstalled: boolean };
10
+
11
+ // ─── Row renderers ─────────────────────────────────────────────────────────────
12
+
13
+ export function renderMcpRow(
14
+ item: McpListItem,
15
+ _index: number,
16
+ isSelected: boolean,
17
+ ): React.ReactNode {
18
+ if (item.kind === "category") {
19
+ return (
20
+ <text fg={theme.selection.bg}>
21
+ <strong>{"▸ "}{item.label}</strong>
22
+ </text>
23
+ );
24
+ }
25
+
26
+ const { server, isInstalled } = item;
27
+ const icon = isInstalled ? "●" : "○";
28
+ const iconColor = isInstalled ? theme.colors.success : theme.colors.muted;
29
+
30
+ if (isSelected) {
31
+ return (
32
+ <text bg={theme.selection.bg} fg={theme.selection.fg}>
33
+ {" "}
34
+ {icon} {server.name}{" "}
35
+ </text>
36
+ );
37
+ }
38
+
39
+ return (
40
+ <text>
41
+ <span fg={iconColor}>{icon}</span>
42
+ <span fg={theme.colors.text}> {server.name}</span>
43
+ {server.requiresConfig ? (
44
+ <span fg={theme.colors.warning}> ⚙</span>
45
+ ) : null}
46
+ </text>
47
+ );
48
+ }
49
+
50
+ // ─── Detail renderer ───────────────────────────────────────────────────────────
51
+
52
+ export function renderMcpDetail(
53
+ item: McpListItem | undefined,
54
+ loading: boolean,
55
+ ): React.ReactNode {
56
+ if (loading) {
57
+ return <text fg={theme.colors.muted}>Loading MCP servers...</text>;
58
+ }
59
+
60
+ if (!item || item.kind === "category") {
61
+ return (
62
+ <box
63
+ flexDirection="column"
64
+ alignItems="center"
65
+ justifyContent="center"
66
+ flexGrow={1}
67
+ >
68
+ <text fg={theme.colors.muted}>Select a server to see details</text>
69
+ </box>
70
+ );
71
+ }
72
+
73
+ const { server, isInstalled } = item;
74
+
75
+ return (
76
+ <box flexDirection="column">
77
+ <box marginBottom={1}>
78
+ <text fg={theme.colors.info}>
79
+ <strong>{"⚡ "}{server.name}</strong>
80
+ </text>
81
+ {server.requiresConfig ? (
82
+ <text fg={theme.colors.warning}> ⚙</text>
83
+ ) : null}
84
+ </box>
85
+
86
+ <text fg={theme.colors.muted}>{server.description}</text>
87
+
88
+ <box marginTop={1} flexDirection="column">
89
+ <box>
90
+ <text fg={theme.colors.muted}>{"Status "}</text>
91
+ {isInstalled ? (
92
+ <text fg={theme.colors.success}>{"● Installed"}</text>
93
+ ) : (
94
+ <text fg={theme.colors.muted}>{"○ Not installed"}</text>
95
+ )}
96
+ </box>
97
+ <box>
98
+ <text fg={theme.colors.muted}>{"Type "}</text>
99
+ <text fg={theme.colors.text}>
100
+ {server.type === "http" ? "HTTP" : "Command"}
101
+ </text>
102
+ </box>
103
+ {server.type === "http" ? (
104
+ <box>
105
+ <text fg={theme.colors.muted}>{"URL "}</text>
106
+ <text fg={theme.colors.link}>{server.url}</text>
107
+ </box>
108
+ ) : (
109
+ <box>
110
+ <text fg={theme.colors.muted}>{"Command "}</text>
111
+ <text fg={theme.colors.info}>{server.command}</text>
112
+ </box>
113
+ )}
114
+ {server.requiresConfig ? (
115
+ <box>
116
+ <text fg={theme.colors.muted}>{"Config "}</text>
117
+ <text fg={theme.colors.warning}>
118
+ {server.configFields?.length ?? 0} fields required
119
+ </text>
120
+ </box>
121
+ ) : null}
122
+ </box>
123
+
124
+ <box marginTop={2}>
125
+ {isInstalled ? (
126
+ <box>
127
+ <text bg={theme.colors.danger} fg="white">
128
+ {" "}
129
+ Enter{" "}
130
+ </text>
131
+ <text fg={theme.colors.muted}> Remove server</text>
132
+ </box>
133
+ ) : (
134
+ <box>
135
+ <text bg={theme.colors.success} fg="black">
136
+ {" "}
137
+ Enter{" "}
138
+ </text>
139
+ <text fg={theme.colors.muted}> Install server</text>
140
+ </box>
141
+ )}
142
+ </box>
143
+ </box>
144
+ );
145
+ }
@@ -0,0 +1,124 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "@opentui/react/jsx-runtime";
2
+ import { CategoryHeader } from "../components/CategoryHeader.js";
3
+ import { SelectableRow, ScopeSquares, ActionHints, MetaText, KeyValueLine, DetailSection, } from "../components/primitives/index.js";
4
+ import { theme } from "../theme.js";
5
+ import { highlightMatches } from "../../utils/fuzzy-search.js";
6
+ // ─── Category renderers ───────────────────────────────────────────────────────
7
+ function categoryRow(item, isSelected) {
8
+ const mp = item.marketplace;
9
+ if (isSelected) {
10
+ const arrow = item.isExpanded ? "▼" : "▶";
11
+ const count = item.pluginCount > 0 ? ` (${item.pluginCount})` : "";
12
+ return (_jsx(SelectableRow, { selected: true, children: _jsxs("strong", { children: [" ", arrow, " ", mp.displayName, count, " "] }) }));
13
+ }
14
+ // Map tone to a statusColor for CategoryHeader (legacy component)
15
+ const statusColorMap = {
16
+ yellow: "yellow",
17
+ gray: "gray",
18
+ green: "green",
19
+ red: "red",
20
+ purple: theme.colors.accent,
21
+ teal: "cyan",
22
+ };
23
+ return (_jsx(CategoryHeader, { title: mp.displayName, expanded: item.isExpanded, count: item.pluginCount, status: item.badge, statusColor: statusColorMap[item.tone] }));
24
+ }
25
+ function categoryDetail(item, collapsedMarketplaces) {
26
+ const mp = item.marketplace;
27
+ const isEnabled = item.marketplaceEnabled;
28
+ const isCollapsed = collapsedMarketplaces.has(mp.name);
29
+ const hasPlugins = item.pluginCount > 0;
30
+ let actionHint = "Add";
31
+ if (isEnabled) {
32
+ if (isCollapsed) {
33
+ actionHint = "Expand";
34
+ }
35
+ else if (hasPlugins) {
36
+ actionHint = "Collapse";
37
+ }
38
+ else {
39
+ actionHint = "Remove";
40
+ }
41
+ }
42
+ return (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { fg: theme.colors.info, children: _jsxs("strong", { children: [mp.displayName, item.badge ? ` ${item.badge}` : ""] }) }), _jsx("text", { fg: theme.colors.muted, children: mp.description || "No description" }), _jsx("text", { fg: isEnabled ? theme.colors.success : theme.colors.muted, children: isEnabled ? "● Added" : "○ Not added" }), _jsxs("text", { fg: theme.colors.link, children: ["github.com/", mp.source.repo] }), _jsxs("text", { children: ["Plugins: ", item.pluginCount] }), _jsx(ActionHints, { hints: [
43
+ { key: "Enter", label: actionHint, tone: isEnabled ? "primary" : "default" },
44
+ ] }), isEnabled ? (_jsx("box", { children: _jsx("text", { fg: theme.colors.muted, children: "\u2190 \u2192 to expand/collapse" }) })) : null] }));
45
+ }
46
+ // ─── Plugin renderers ─────────────────────────────────────────────────────────
47
+ function pluginRow(item, isSelected) {
48
+ const { plugin } = item;
49
+ const hasUser = !!plugin.userScope?.enabled;
50
+ const hasProject = !!plugin.projectScope?.enabled;
51
+ const hasLocal = !!plugin.localScope?.enabled;
52
+ const hasAnyScope = hasUser || hasProject || hasLocal;
53
+ // Build version string
54
+ let versionStr = "";
55
+ if (plugin.isOrphaned) {
56
+ versionStr = " deprecated";
57
+ }
58
+ else if (plugin.installedVersion && plugin.installedVersion !== "0.0.0") {
59
+ versionStr = ` v${plugin.installedVersion}`;
60
+ if (plugin.hasUpdate && plugin.version) {
61
+ versionStr += ` → v${plugin.version}`;
62
+ }
63
+ }
64
+ if (isSelected) {
65
+ return (_jsxs("text", { bg: theme.selection.bg, fg: theme.selection.fg, children: [" ", _jsx(ScopeSquares, { user: hasUser, project: hasProject, local: hasLocal, selected: true }), " ", plugin.name, versionStr, " "] }));
66
+ }
67
+ // Fuzzy highlight
68
+ const segments = item.matches ? highlightMatches(plugin.name, item.matches) : null;
69
+ const displayName = segments ? segments.map((seg) => seg.text).join("") : plugin.name;
70
+ if (plugin.isOrphaned) {
71
+ const ver = plugin.installedVersion && plugin.installedVersion !== "0.0.0"
72
+ ? ` v${plugin.installedVersion}`
73
+ : "";
74
+ return (_jsxs("text", { children: [_jsx("span", { fg: theme.colors.danger, children: " \u25A0\u25A0\u25A0 " }), _jsx("span", { fg: theme.colors.muted, children: displayName }), ver ? _jsx("span", { fg: theme.colors.warning, children: ver }) : null, _jsx("span", { fg: theme.colors.danger, children: " deprecated" })] }));
75
+ }
76
+ return (_jsxs("text", { children: [_jsx("span", { children: " " }), _jsx(ScopeSquares, { user: hasUser, project: hasProject, local: hasLocal }), _jsx("span", { children: " " }), _jsx("span", { fg: hasAnyScope ? theme.colors.text : theme.colors.muted, children: displayName }), _jsx(MetaText, { text: versionStr, tone: plugin.hasUpdate ? "warning" : "muted" })] }));
77
+ }
78
+ function pluginDetail(item) {
79
+ const { plugin } = item;
80
+ const isInstalled = plugin.userScope?.enabled ||
81
+ plugin.projectScope?.enabled ||
82
+ plugin.localScope?.enabled;
83
+ // Orphaned/deprecated plugin
84
+ if (plugin.isOrphaned) {
85
+ return (_jsxs("box", { flexDirection: "column", children: [_jsx("box", { justifyContent: "center", children: _jsx("text", { bg: theme.colors.warning, fg: "black", children: _jsxs("strong", { children: [" ", plugin.name, " \u2014 DEPRECATED "] }) }) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: theme.colors.warning, children: "This plugin is no longer in the marketplace." }) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: theme.colors.muted, children: "It was removed from the marketplace but still referenced in your settings. Press d to uninstall and clean up." }) }), isInstalled ? (_jsx(ActionHints, { hints: [{ key: "d", label: "Uninstall (recommended)", tone: "danger" }] })) : null] }));
86
+ }
87
+ // Build component counts
88
+ const components = [];
89
+ if (plugin.agents?.length)
90
+ components.push(`${plugin.agents.length} agents`);
91
+ if (plugin.commands?.length)
92
+ components.push(`${plugin.commands.length} commands`);
93
+ if (plugin.skills?.length)
94
+ components.push(`${plugin.skills.length} skills`);
95
+ if (plugin.mcpServers?.length)
96
+ components.push(`${plugin.mcpServers.length} MCP`);
97
+ if (plugin.lspServers && Object.keys(plugin.lspServers).length) {
98
+ components.push(`${Object.keys(plugin.lspServers).length} LSP`);
99
+ }
100
+ const showVersion = plugin.version && plugin.version !== "0.0.0";
101
+ const showInstalledVersion = plugin.installedVersion && plugin.installedVersion !== "0.0.0";
102
+ return (_jsxs("box", { flexDirection: "column", children: [_jsx("box", { justifyContent: "center", children: _jsx("text", { bg: theme.selection.bg, fg: theme.selection.fg, children: _jsxs("strong", { children: [" ", plugin.name, plugin.hasUpdate ? " ⬆" : "", " "] }) }) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: isInstalled ? theme.colors.success : theme.colors.muted, children: isInstalled ? "● Installed" : "○ Not installed" }) }), _jsx("box", { marginTop: 1, marginBottom: 1, children: _jsx("text", { fg: theme.colors.text, children: plugin.description }) }), showVersion ? (_jsx(KeyValueLine, { label: "Version", value: _jsxs("span", { children: [_jsxs("span", { fg: theme.colors.link, children: ["v", plugin.version] }), showInstalledVersion && plugin.installedVersion !== plugin.version ? (_jsxs("span", { children: [" (v", plugin.installedVersion, " installed)"] })) : null] }) })) : null, plugin.category ? (_jsx(KeyValueLine, { label: "Category", value: _jsx("span", { fg: theme.colors.accent, children: plugin.category }) })) : null, plugin.author ? (_jsx(KeyValueLine, { label: "Author", value: _jsx("span", { children: plugin.author.name }) })) : null, components.length > 0 ? (_jsx(KeyValueLine, { label: "Contains", value: _jsx("span", { fg: theme.colors.warning, children: components.join(" · ") }) })) : null, _jsxs(DetailSection, { children: [_jsx("text", { children: "─".repeat(24) }), _jsx("text", { children: _jsx("strong", { children: "Scopes:" }) }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("text", { children: [_jsxs("span", { bg: theme.scopes.user, fg: "black", children: [" ", "u", " "] }), _jsx("span", { fg: plugin.userScope?.enabled ? theme.scopes.user : theme.colors.muted, children: plugin.userScope?.enabled ? " ● " : " ○ " }), _jsx("span", { fg: theme.scopes.user, children: "User" }), _jsx("span", { children: " global" }), plugin.userScope?.version ? (_jsxs("span", { fg: theme.scopes.user, children: [" v", plugin.userScope.version] })) : null] }), _jsxs("text", { children: [_jsxs("span", { bg: theme.scopes.project, fg: "black", children: [" ", "p", " "] }), _jsx("span", { fg: plugin.projectScope?.enabled ? theme.scopes.project : theme.colors.muted, children: plugin.projectScope?.enabled ? " ● " : " ○ " }), _jsx("span", { fg: theme.scopes.project, children: "Project" }), _jsx("span", { children: " team" }), plugin.projectScope?.version ? (_jsxs("span", { fg: theme.scopes.project, children: [" v", plugin.projectScope.version] })) : null] }), _jsxs("text", { children: [_jsxs("span", { bg: theme.scopes.local, fg: "black", children: [" ", "l", " "] }), _jsx("span", { fg: plugin.localScope?.enabled ? theme.scopes.local : theme.colors.muted, children: plugin.localScope?.enabled ? " ● " : " ○ " }), _jsx("span", { fg: theme.scopes.local, children: "Local" }), _jsx("span", { children: " private" }), plugin.localScope?.version ? (_jsxs("span", { fg: theme.scopes.local, children: [" v", plugin.localScope.version] })) : null] })] })] }), isInstalled && plugin.hasUpdate ? (_jsx(ActionHints, { hints: [{ key: "U", label: `Update to v${plugin.version}`, tone: "primary" }] })) : null] }));
103
+ }
104
+ // ─── Public dispatch functions ────────────────────────────────────────────────
105
+ /**
106
+ * Render a plugin browser list row by item kind.
107
+ */
108
+ export function renderPluginRow(item, _index, isSelected) {
109
+ if (item.kind === "category") {
110
+ return categoryRow(item, isSelected);
111
+ }
112
+ return pluginRow(item, isSelected);
113
+ }
114
+ /**
115
+ * Render the detail panel for a plugin browser item.
116
+ */
117
+ export function renderPluginDetail(item, collapsedMarketplaces) {
118
+ if (!item)
119
+ return _jsx("text", { fg: theme.colors.muted, children: "Select an item" });
120
+ if (item.kind === "category") {
121
+ return categoryDetail(item, collapsedMarketplaces);
122
+ }
123
+ return pluginDetail(item);
124
+ }