claudeup 4.18.0 → 4.22.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 (191) hide show
  1. package/bin/claudeup.js +43 -19
  2. package/package.json +14 -19
  3. package/scripts/build-binaries.ts +77 -0
  4. package/scripts/check-optional-deps.ts +49 -0
  5. package/scripts/test-isolated.ts +43 -0
  6. package/src/__tests__/alias-adopt.test.ts +364 -0
  7. package/src/__tests__/alias-parser.test.ts +92 -0
  8. package/src/__tests__/alias-shell-writer.test.ts +7 -0
  9. package/src/__tests__/alias-store.test.ts +77 -0
  10. package/src/__tests__/cli-router.test.ts +65 -0
  11. package/src/__tests__/conventions-integration.test.ts +745 -0
  12. package/src/__tests__/conventions-manager.test.ts +1172 -0
  13. package/src/__tests__/doctor-bins.test.ts +35 -0
  14. package/src/__tests__/doctor.test.ts +450 -0
  15. package/src/__tests__/dual-write-prevention.test.ts +14 -4
  16. package/src/__tests__/file-locking.test.ts +208 -0
  17. package/src/__tests__/gitignore-prerun.test.ts +1 -0
  18. package/src/__tests__/install-command.test.ts +81 -0
  19. package/src/__tests__/install-plan.test.ts +98 -0
  20. package/src/__tests__/manifest.test.ts +136 -0
  21. package/src/__tests__/marketplace-refresh.test.ts +267 -0
  22. package/src/__tests__/plugin-requires.test.ts +133 -0
  23. package/src/__tests__/plugin-setup.test.ts +118 -0
  24. package/src/__tests__/profile-command.test.ts +116 -0
  25. package/src/__tests__/profile-materializer.test.ts +82 -0
  26. package/src/__tests__/profile-sync.test.ts +136 -0
  27. package/src/__tests__/registry-version-resolution.test.ts +74 -0
  28. package/src/__tests__/resolve-executable.test.ts +42 -0
  29. package/src/__tests__/resolver.test.ts +218 -0
  30. package/src/__tests__/symlink-manager.test.ts +99 -0
  31. package/src/__tests__/toolchain.test.ts +56 -0
  32. package/src/cli/claude.ts +21 -0
  33. package/src/cli/doctor.ts +132 -0
  34. package/src/cli/install.ts +360 -0
  35. package/src/cli/profile.ts +166 -0
  36. package/src/cli/router.ts +107 -0
  37. package/src/cli/update.ts +85 -0
  38. package/src/data/alias-flags.ts +11 -1
  39. package/src/data/cli-tools.ts +7 -7
  40. package/src/data/gitignore-defaults.ts +4 -0
  41. package/src/data/marketplaces.ts +12 -0
  42. package/src/data/predefined-profiles.ts +3 -4
  43. package/src/main.tsx +11 -154
  44. package/src/prerunner/index.ts +62 -27
  45. package/src/services/alias-shell-writer.ts +382 -8
  46. package/src/services/alias-store.ts +60 -0
  47. package/src/services/claude-cli.ts +19 -22
  48. package/src/services/claude-settings.ts +99 -26
  49. package/src/services/conventions-manager.ts +865 -0
  50. package/src/services/doctor-bins.ts +49 -0
  51. package/src/services/doctor.ts +509 -0
  52. package/src/services/install-plan.ts +107 -0
  53. package/src/services/manifest.ts +166 -0
  54. package/src/services/marketplace-refresh.ts +173 -0
  55. package/src/services/plugin-manager.ts +7 -1
  56. package/src/services/plugin-requires.ts +215 -0
  57. package/src/services/plugin-setup.ts +61 -4
  58. package/src/services/plugin-version-check.ts +9 -4
  59. package/src/services/profile-materializer.ts +61 -0
  60. package/src/services/profile-sync.ts +150 -0
  61. package/src/services/resolver.ts +293 -0
  62. package/src/services/symlink-manager.ts +146 -0
  63. package/src/services/toolchain.ts +97 -0
  64. package/src/services/version-check.ts +10 -11
  65. package/src/types/index.ts +138 -31
  66. package/src/ui/App.tsx +16 -11
  67. package/src/ui/components/FlagDetailEditor.tsx +0 -0
  68. package/src/ui/components/modals/ConfirmModal.tsx +1 -1
  69. package/src/ui/screens/AliasScreen.tsx +491 -359
  70. package/src/ui/screens/CliToolsScreen.tsx +11 -3
  71. package/src/ui/screens/PluginsScreen.tsx +3 -1
  72. package/src/ui/screens/index.ts +0 -1
  73. package/src/ui/state/reducer.ts +6 -102
  74. package/src/ui/state/types.ts +8 -35
  75. package/src/utils/command-utils.ts +23 -0
  76. package/src/utils/file-locking.ts +144 -0
  77. package/src/data/alias-flags.js +0 -196
  78. package/src/data/cli-tools.js +0 -123
  79. package/src/data/gitignore-defaults.js +0 -24
  80. package/src/data/gitignore-reasons.js +0 -97
  81. package/src/data/gitignore-templates.js +0 -21
  82. package/src/data/marketplaces.js +0 -138
  83. package/src/data/mcp-servers.js +0 -509
  84. package/src/data/predefined-profiles.js +0 -248
  85. package/src/data/settings-catalog.js +0 -625
  86. package/src/data/skill-repos.js +0 -160
  87. package/src/data/statuslines.js +0 -159
  88. package/src/data/statuslines.ts +0 -188
  89. package/src/index.js +0 -4
  90. package/src/index.ts +0 -5
  91. package/src/main.js +0 -170
  92. package/src/prerunner/index.js +0 -252
  93. package/src/services/alias-settings.js +0 -51
  94. package/src/services/alias-shell-writer.js +0 -764
  95. package/src/services/alias-store.js +0 -77
  96. package/src/services/claude-cli.js +0 -189
  97. package/src/services/claude-runner.js +0 -28
  98. package/src/services/claude-settings.js +0 -1223
  99. package/src/services/gitignore-detector.js +0 -155
  100. package/src/services/gitignore-fixer.js +0 -231
  101. package/src/services/gitignore-prerun.js +0 -46
  102. package/src/services/gitignore-resolver.js +0 -143
  103. package/src/services/gitignore-service.js +0 -117
  104. package/src/services/local-marketplace.js +0 -339
  105. package/src/services/marketplace-fetcher.js +0 -96
  106. package/src/services/marketplace-sync.js +0 -94
  107. package/src/services/mcp-registry.js +0 -87
  108. package/src/services/plugin-manager.js +0 -473
  109. package/src/services/plugin-mcp-config.js +0 -177
  110. package/src/services/plugin-setup.js +0 -444
  111. package/src/services/plugin-version-check.js +0 -262
  112. package/src/services/profiles.js +0 -161
  113. package/src/services/settings-manager.js +0 -243
  114. package/src/services/skills-manager.js +0 -393
  115. package/src/services/skillsmp-client.js +0 -87
  116. package/src/services/update-cache.js +0 -52
  117. package/src/services/version-check.js +0 -99
  118. package/src/types/gitignore.js +0 -6
  119. package/src/types/index.js +0 -1
  120. package/src/ui/App.js +0 -346
  121. package/src/ui/adapters/pluginsAdapter.js +0 -139
  122. package/src/ui/adapters/settingsAdapter.js +0 -111
  123. package/src/ui/adapters/skillsAdapter.js +0 -154
  124. package/src/ui/components/CategoryHeader.js +0 -9
  125. package/src/ui/components/EmptyFilterState.js +0 -4
  126. package/src/ui/components/ScopeIndicator.js +0 -30
  127. package/src/ui/components/ScrollableList.js +0 -36
  128. package/src/ui/components/SearchInput.js +0 -19
  129. package/src/ui/components/StyledText.js +0 -39
  130. package/src/ui/components/TabBar.js +0 -19
  131. package/src/ui/components/layout/FooterHints.js +0 -29
  132. package/src/ui/components/layout/Panel.js +0 -6
  133. package/src/ui/components/layout/ProgressBar.js +0 -14
  134. package/src/ui/components/layout/ScopeTabs.js +0 -6
  135. package/src/ui/components/layout/ScreenLayout.js +0 -16
  136. package/src/ui/components/layout/index.js +0 -5
  137. package/src/ui/components/modals/ConfirmModal.js +0 -14
  138. package/src/ui/components/modals/InputModal.js +0 -5
  139. package/src/ui/components/modals/LoadingModal.js +0 -17
  140. package/src/ui/components/modals/MessageModal.js +0 -16
  141. package/src/ui/components/modals/ModalContainer.js +0 -137
  142. package/src/ui/components/modals/SelectModal.js +0 -18
  143. package/src/ui/components/modals/VersionMismatchModal.js +0 -36
  144. package/src/ui/components/modals/index.js +0 -6
  145. package/src/ui/components/primitives/ActionHints.js +0 -13
  146. package/src/ui/components/primitives/DetailSection.js +0 -7
  147. package/src/ui/components/primitives/KeyValueLine.js +0 -8
  148. package/src/ui/components/primitives/ListCategoryRow.js +0 -8
  149. package/src/ui/components/primitives/MetaText.js +0 -8
  150. package/src/ui/components/primitives/ScopeDetail.js +0 -32
  151. package/src/ui/components/primitives/ScopeSquares.js +0 -11
  152. package/src/ui/components/primitives/SelectableRow.js +0 -5
  153. package/src/ui/components/primitives/index.js +0 -8
  154. package/src/ui/hooks/index.js +0 -4
  155. package/src/ui/hooks/useAsyncData.js +0 -77
  156. package/src/ui/hooks/useGitignoreModal.js +0 -74
  157. package/src/ui/hooks/useKeyboard.js +0 -13
  158. package/src/ui/hooks/useKeyboardHandler.js +0 -39
  159. package/src/ui/hooks/useMismatchModal.js +0 -77
  160. package/src/ui/registry.js +0 -1
  161. package/src/ui/renderers/cliToolRenderers.js +0 -54
  162. package/src/ui/renderers/gitignoreRenderers.js +0 -46
  163. package/src/ui/renderers/mcpRenderers.js +0 -26
  164. package/src/ui/renderers/pluginRenderers.js +0 -124
  165. package/src/ui/renderers/profileRenderers.js +0 -177
  166. package/src/ui/renderers/settingsRenderers.js +0 -73
  167. package/src/ui/renderers/skillRenderers.js +0 -138
  168. package/src/ui/screens/AliasScreen.js +0 -1008
  169. package/src/ui/screens/CliToolsScreen.js +0 -338
  170. package/src/ui/screens/EnvVarsScreen.js +0 -152
  171. package/src/ui/screens/GitignoreScreen.js +0 -328
  172. package/src/ui/screens/McpRegistryScreen.js +0 -238
  173. package/src/ui/screens/McpScreen.js +0 -176
  174. package/src/ui/screens/ModelSelectorScreen.js +0 -296
  175. package/src/ui/screens/ModelSelectorScreen.tsx +0 -444
  176. package/src/ui/screens/PluginsScreen.js +0 -766
  177. package/src/ui/screens/ProfilesScreen.js +0 -298
  178. package/src/ui/screens/SkillsScreen.js +0 -549
  179. package/src/ui/screens/StatusLineScreen.js +0 -206
  180. package/src/ui/screens/StatusLineScreen.tsx +0 -416
  181. package/src/ui/screens/index.js +0 -10
  182. package/src/ui/state/AnimationContext.js +0 -34
  183. package/src/ui/state/AppContext.js +0 -162
  184. package/src/ui/state/DimensionsContext.js +0 -71
  185. package/src/ui/state/reducer.js +0 -543
  186. package/src/ui/state/types.js +0 -1
  187. package/src/ui/theme.js +0 -47
  188. package/src/utils/clipboard.js +0 -56
  189. package/src/utils/command-utils.js +0 -19
  190. package/src/utils/fuzzy-search.js +0 -101
  191. package/src/utils/string-utils.js +0 -62
@@ -1,124 +0,0 @@
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 — only show if plugin is installed in at least one scope
54
- let versionStr = "";
55
- if (plugin.isOrphaned) {
56
- versionStr = " deprecated";
57
- }
58
- else if (hasAnyScope && 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." }) }), _jsx(ActionHints, { hints: [{ key: "d", label: isInstalled ? "Remove from all scopes" : "Clean up stale reference", tone: "danger" }] })] }));
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 = isInstalled && 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, isInstalled && 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
- }
@@ -1,177 +0,0 @@
1
- import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "@opentui/react/jsx-runtime";
2
- import { theme } from "../theme.js";
3
- // ─── Helpers ───────────────────────────────────────────────────────────────────
4
- function truncate(s, maxLen) {
5
- if (s.length <= maxLen)
6
- return s;
7
- return s.slice(0, maxLen - 1) + "\u2026";
8
- }
9
- export function humanizeValue(_key, value) {
10
- if (typeof value === "boolean")
11
- return value ? "on" : "off";
12
- if (typeof value !== "string")
13
- return String(value);
14
- if (value === "claude-sonnet-4-6")
15
- return "Sonnet";
16
- if (value === "claude-opus-4-6")
17
- return "Opus";
18
- if (value === "claude-haiku-4-6")
19
- return "Haiku";
20
- if (value.startsWith("claude-sonnet"))
21
- return "Sonnet";
22
- if (value.startsWith("claude-opus"))
23
- return "Opus";
24
- if (value.startsWith("claude-haiku"))
25
- return "Haiku";
26
- return value;
27
- }
28
- export function humanizeKey(key) {
29
- const labels = {
30
- effortLevel: "Effort",
31
- model: "Model",
32
- outputStyle: "Output",
33
- alwaysThinkingEnabled: "Thinking",
34
- includeGitInstructions: "Git Instructions",
35
- respectGitignore: "Gitignore",
36
- enableAllProjectMcpServers: "Auto MCP",
37
- autoUpdatesChannel: "Updates",
38
- language: "Language",
39
- cleanupPeriodDays: "Cleanup",
40
- };
41
- return labels[key] ?? key;
42
- }
43
- export function stripSuffix(pluginName) {
44
- return pluginName
45
- .replace(/@magus$/, "")
46
- .replace(/@claude-plugins-official$/, "");
47
- }
48
- export function wrapNames(names, lineMax = 40) {
49
- const lines = [];
50
- let current = "";
51
- for (const name of names) {
52
- const add = current ? `, ${name}` : name;
53
- if (current && current.length + add.length > lineMax) {
54
- lines.push(current);
55
- current = name;
56
- }
57
- else {
58
- current += current ? `, ${name}` : name;
59
- }
60
- }
61
- if (current)
62
- lines.push(current);
63
- return lines;
64
- }
65
- export function formatDate(iso) {
66
- try {
67
- const d = new Date(iso);
68
- return d.toLocaleDateString("en-US", {
69
- month: "short",
70
- day: "numeric",
71
- year: "numeric",
72
- });
73
- }
74
- catch {
75
- return iso;
76
- }
77
- }
78
- // ─── Header renderer ───────────────────────────────────────────────────────────
79
- const headerRenderer = {
80
- renderRow: ({ item }) => (_jsx("text", { bg: item.color, fg: "white", children: _jsxs("strong", { children: [" ", item.label, " (", item.count, ")", " "] }) })),
81
- renderDetail: () => (_jsx("text", { fg: theme.colors.muted, children: "Select a profile to see details" })),
82
- };
83
- // ─── Predefined renderer ───────────────────────────────────────────────────────
84
- const DIVIDER = "────────────────────────";
85
- const predefinedRenderer = {
86
- renderRow: ({ item, isSelected }) => {
87
- const { profile } = item;
88
- const pluginCount = profile.magusPlugins.length + profile.anthropicPlugins.length;
89
- const skillCount = profile.skills.length;
90
- const countStr = `${pluginCount}p ${skillCount}s`;
91
- if (isSelected) {
92
- return (_jsxs("text", { bg: theme.selection.bg, fg: theme.selection.fg, children: [" ", profile.name, " ", countStr, " "] }));
93
- }
94
- return (_jsxs("text", { children: [_jsxs("span", { fg: "white", children: [" ", profile.name] }), _jsxs("span", { fg: theme.colors.dim, children: [" ", countStr] })] }));
95
- },
96
- renderDetail: ({ item }) => {
97
- const { profile } = item;
98
- const settingEntries = Object.entries(profile.settings).filter(([k]) => k !== "env");
99
- const envMap = profile.settings["env"] ?? {};
100
- const tasksOn = envMap["CLAUDE_CODE_ENABLE_TASKS"] === "true";
101
- const teamsOn = envMap["CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS"] === "true";
102
- return (_jsxs("box", { flexDirection: "column", children: [_jsx("box", { children: _jsx("text", { bg: theme.colors.accent, fg: "white", children: _jsxs("strong", { children: [" ", profile.name, " "] }) }) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: theme.colors.muted, children: profile.description }) }), _jsx("box", { children: _jsx("text", { fg: theme.colors.muted, children: `\nMagus (${profile.magusPlugins.length})` }) }), _jsx("box", { children: _jsx("text", { fg: "#00bfa5", children: profile.magusPlugins.map((p) => ` ■ ${p}`).join("\n") }) }), _jsx("box", { children: _jsx("text", { fg: theme.colors.muted, children: `\nAnthropic (${profile.anthropicPlugins.length})` }) }), _jsx("box", { children: _jsx("text", { fg: "#b39ddb", children: profile.anthropicPlugins.map((p) => ` ■ ${p}`).join("\n") }) }), profile.skills.length > 0 && (_jsxs(_Fragment, { children: [_jsx("box", { children: _jsx("text", { fg: theme.colors.muted, children: `\nSkills (${profile.skills.length})` }) }), _jsx("box", { children: _jsx("text", { fg: "#ffd54f", children: profile.skills.map((s) => ` ■ ${s}`).join("\n") }) })] })), _jsx("box", { children: _jsx("text", { fg: theme.colors.dim, children: `\n${DIVIDER}` }) }), _jsx("box", { children: _jsx("text", { fg: theme.colors.muted, children: [
103
- ...settingEntries.map(([k, v]) => ` ${humanizeKey(k).padEnd(18)}${humanizeValue(k, v)}`),
104
- ...(tasksOn ? [` ${"Tasks".padEnd(18)}on`] : []),
105
- ...(teamsOn ? [` ${"Agent Teams".padEnd(18)}on`] : []),
106
- ].join("\n") }) }), _jsx("box", { children: _jsx("text", { fg: theme.colors.dim, children: `\n${DIVIDER}` }) }), _jsx("box", { children: _jsx("text", { fg: theme.colors.info, children: " Enter/a to apply" }) })] }));
107
- },
108
- };
109
- // ─── Saved renderer ────────────────────────────────────────────────────────────
110
- const savedRenderer = {
111
- renderRow: ({ item, isSelected }) => {
112
- const { entry } = item;
113
- const pluginCount = Object.keys(entry.plugins).length;
114
- const dateStr = formatDate(entry.updatedAt);
115
- const label = truncate(`${entry.name} — ${pluginCount} plugin${pluginCount !== 1 ? "s" : ""} · ${dateStr}`, 45);
116
- if (isSelected) {
117
- return (_jsxs("text", { bg: theme.selection.bg, fg: theme.selection.fg, children: [" ", label, " "] }));
118
- }
119
- const scopeColor = entry.scope === "user" ? theme.scopes.user : theme.scopes.project;
120
- const scopeLabel = entry.scope === "user" ? "[user]" : "[proj]";
121
- return (_jsxs("text", { children: [_jsxs("span", { fg: scopeColor, children: [scopeLabel, " "] }), _jsx("span", { fg: theme.colors.text, children: label })] }));
122
- },
123
- renderDetail: ({ item }) => {
124
- const { entry: selectedProfile } = item;
125
- const plugins = Object.keys(selectedProfile.plugins);
126
- const cleanPlugins = plugins.map(stripSuffix);
127
- const scopeColor = selectedProfile.scope === "user" ? theme.scopes.user : theme.scopes.project;
128
- const scopeLabel = selectedProfile.scope === "user"
129
- ? "User (~/.claude/profiles.json)"
130
- : "Project (.claude/profiles.json — committed to git)";
131
- return (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { fg: theme.colors.info, children: _jsx("strong", { children: selectedProfile.name }) }), _jsxs("box", { marginTop: 1, children: [_jsx("text", { fg: theme.colors.muted, children: "Scope: " }), _jsx("text", { fg: scopeColor, children: scopeLabel })] }), _jsx("box", { marginTop: 1, children: _jsxs("text", { fg: theme.colors.muted, children: ["Created: ", formatDate(selectedProfile.createdAt), " \u00B7 Updated:", " ", formatDate(selectedProfile.updatedAt)] }) }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("text", { fg: theme.colors.muted, children: ["Plugins (", plugins.length, plugins.length === 0 ? " — applying will disable all plugins" : "", "):"] }), cleanPlugins.length === 0 ? (_jsx("text", { fg: theme.colors.warning, children: " (none)" })) : (wrapNames(cleanPlugins).map((line, i) => (_jsxs("text", { fg: theme.colors.text, children: [" ", line] }, i))))] }), _jsxs("box", { marginTop: 2, flexDirection: "column", children: [_jsxs("box", { children: [_jsxs("text", { bg: theme.selection.bg, fg: theme.selection.fg, children: [" ", "Enter/a", " "] }), _jsx("text", { fg: theme.colors.muted, children: " Apply profile" })] }), _jsxs("box", { marginTop: 1, children: [_jsxs("text", { bg: theme.colors.dim, fg: "white", children: [" ", "r", " "] }), _jsx("text", { fg: theme.colors.muted, children: " Rename" })] }), _jsxs("box", { marginTop: 1, children: [_jsxs("text", { bg: theme.colors.danger, fg: "white", children: [" ", "d", " "] }), _jsx("text", { fg: theme.colors.muted, children: " Delete" })] }), _jsxs("box", { marginTop: 1, children: [_jsxs("text", { bg: "blue", fg: "white", children: [" ", "c", " "] }), _jsx("text", { fg: theme.colors.muted, children: " Copy JSON to clipboard" })] }), _jsxs("box", { marginTop: 1, children: [_jsxs("text", { bg: theme.colors.success, fg: "white", children: [" ", "i", " "] }), _jsx("text", { fg: theme.colors.muted, children: " Import from clipboard" })] })] })] }));
132
- },
133
- };
134
- // ─── Dispatch helpers ──────────────────────────────────────────────────────────
135
- export function renderProfileRow(item, _index, isSelected) {
136
- if (item.kind === "header") {
137
- return headerRenderer.renderRow({ item, isSelected });
138
- }
139
- if (item.kind === "predefined") {
140
- return predefinedRenderer.renderRow({ item, isSelected });
141
- }
142
- return savedRenderer.renderRow({ item, isSelected });
143
- }
144
- export function renderProfileDetail(item, loading, error) {
145
- if (loading)
146
- return _jsx("text", { fg: theme.colors.muted, children: "Loading profiles..." });
147
- if (error)
148
- return _jsxs("text", { fg: theme.colors.danger, children: ["Error: ", error] });
149
- if (!item || item.kind === "header")
150
- return _jsx("text", { fg: theme.colors.muted, children: "Select a profile to see details" });
151
- if (item.kind === "predefined") {
152
- return predefinedRenderer.renderDetail({ item });
153
- }
154
- return savedRenderer.renderDetail({ item });
155
- }
156
- export function buildProfileListItems(profileList, PREDEFINED_PROFILES) {
157
- const items = [];
158
- items.push({
159
- kind: "header",
160
- label: "Presets",
161
- color: "#4527a0",
162
- count: PREDEFINED_PROFILES.length,
163
- });
164
- for (const p of PREDEFINED_PROFILES) {
165
- items.push({ kind: "predefined", profile: p });
166
- }
167
- items.push({
168
- kind: "header",
169
- label: "Your Profiles",
170
- color: "#00695c",
171
- count: profileList.length,
172
- });
173
- for (const e of profileList) {
174
- items.push({ kind: "saved", entry: e });
175
- }
176
- return items;
177
- }
@@ -1,73 +0,0 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "@opentui/react/jsx-runtime";
2
- import { CATEGORY_LABELS, CATEGORY_BG, CATEGORY_COLOR, CATEGORY_DESCRIPTIONS, formatValue, } from "../adapters/settingsAdapter.js";
3
- import { MetaText } from "../components/primitives/index.js";
4
- import { theme } from "../theme.js";
5
- // ─── Category renderer ─────────────────────────────────────────────────────────
6
- const categoryRenderer = {
7
- renderRow: ({ item, isSelected }) => {
8
- const star = item.category === "recommended" ? "★ " : "";
9
- const label = `${star}${CATEGORY_LABELS[item.category]}`;
10
- const bg = isSelected ? theme.selection.bg : CATEGORY_BG[item.category];
11
- return (_jsx("box", { width: "100%", children: _jsx("text", { bg: bg, fg: "white", children: _jsxs("strong", { children: [" ", label, " "] }) }) }));
12
- },
13
- renderDetail: ({ item }) => {
14
- const cat = item.category;
15
- return (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { fg: CATEGORY_COLOR[cat], children: _jsx("strong", { children: CATEGORY_LABELS[cat] }) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: theme.colors.muted, children: CATEGORY_DESCRIPTIONS[cat] }) })] }));
16
- },
17
- };
18
- // ─── Setting renderer ──────────────────────────────────────────────────────────
19
- const settingRenderer = {
20
- renderRow: ({ item, isSelected }) => {
21
- const { setting } = item;
22
- const indicator = item.isDefault ? "○" : "●";
23
- const displayValue = formatValue(setting, item.effectiveValue);
24
- if (isSelected) {
25
- return (_jsxs("text", { bg: theme.selection.bg, fg: theme.selection.fg, children: [" ", indicator, " ", setting.name.padEnd(28), displayValue, " "] }));
26
- }
27
- const indicatorColor = item.isDefault ? theme.colors.muted : theme.colors.info;
28
- const valueColor = item.isDefault ? theme.colors.muted : theme.colors.success;
29
- return (_jsxs("text", { children: [_jsxs("span", { fg: indicatorColor, children: [" ", indicator, " "] }), _jsx("span", { children: setting.name.padEnd(28) }), _jsx("span", { fg: valueColor, children: displayValue })] }));
30
- },
31
- renderDetail: ({ item }) => {
32
- const { setting } = item;
33
- const scoped = item.scopedValues;
34
- const storageDesc = setting.storage.type === "attribution"
35
- ? "settings.json: attribution"
36
- : setting.storage.type === "attribution-text"
37
- ? "settings.json: attribution"
38
- : setting.storage.type === "env"
39
- ? `env: ${setting.storage.key}`
40
- : `settings.json: ${setting.storage.key}`;
41
- const userValue = formatValue(setting, scoped.user);
42
- const projectValue = formatValue(setting, scoped.project);
43
- const userIsSet = scoped.user !== undefined && scoped.user !== "";
44
- const projectIsSet = scoped.project !== undefined && scoped.project !== "";
45
- const actionLabel = setting.type === "boolean"
46
- ? "toggle"
47
- : setting.type === "select"
48
- ? "choose"
49
- : "edit";
50
- return (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { fg: theme.colors.info, children: _jsx("strong", { children: setting.name }) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: theme.colors.text, children: setting.description }) }), _jsx("box", { marginTop: 1, children: _jsxs("text", { children: [_jsx("span", { fg: theme.colors.muted, children: "Stored " }), _jsx("span", { fg: theme.colors.link, children: storageDesc })] }) }), setting.defaultValue !== undefined && (_jsx("box", { children: _jsxs("text", { children: [_jsx("span", { fg: theme.colors.muted, children: "Default " }), _jsx("span", { children: setting.defaultValue })] }) })), _jsxs("box", { flexDirection: "column", marginTop: 1, children: [_jsx("text", { children: "────────────────────────" }), _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: userIsSet ? theme.scopes.user : theme.colors.muted, children: userIsSet ? " ● " : " ○ " }), _jsx("span", { fg: theme.scopes.user, children: "User" }), _jsx("span", { children: " global" }), _jsxs("span", { fg: userIsSet ? theme.scopes.user : theme.colors.muted, children: [" ", userValue] })] }), _jsxs("text", { children: [_jsxs("span", { bg: theme.scopes.project, fg: "black", children: [" ", "p", " "] }), _jsx("span", { fg: projectIsSet ? theme.scopes.project : theme.colors.muted, children: projectIsSet ? " ● " : " ○ " }), _jsx("span", { fg: theme.scopes.project, children: "Project" }), _jsx("span", { children: " team" }), _jsxs("span", { fg: projectIsSet ? theme.scopes.project : theme.colors.muted, children: [" ", projectValue] })] })] })] }), _jsx("box", { marginTop: 1, children: _jsxs("text", { fg: theme.colors.muted, children: ["Press u/p to ", actionLabel, " in scope"] }) })] }));
51
- },
52
- };
53
- // ─── Dispatch helpers ──────────────────────────────────────────────────────────
54
- export const settingsRenderers = {
55
- category: categoryRenderer,
56
- setting: settingRenderer,
57
- };
58
- export function renderSettingRow(item, _index, isSelected) {
59
- if (item.kind === "category") {
60
- return settingsRenderers.category.renderRow({ item, isSelected });
61
- }
62
- return settingsRenderers.setting.renderRow({ item, isSelected });
63
- }
64
- export function renderSettingDetail(item) {
65
- if (!item)
66
- return _jsx("text", { fg: theme.colors.muted, children: "Select a setting to see details" });
67
- if (item.kind === "category") {
68
- return settingsRenderers.category.renderDetail({ item });
69
- }
70
- return settingsRenderers.setting.renderDetail({ item });
71
- }
72
- // Suppress unused import warning — MetaText may be used in future extensions
73
- void MetaText;
@@ -1,138 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
2
- import { STAR_RELIABILITY_INFO } from "../../data/skill-repos.js";
3
- import { SelectableRow, ListCategoryRow, ScopeSquares, MetaText, KeyValueLine, DetailSection, } from "../components/primitives/index.js";
4
- // ─── Helpers ──────────────────────────────────────────────────────────────────
5
- function formatStarsNum(stars) {
6
- if (!stars)
7
- return "";
8
- if (stars >= 1000000)
9
- return `${(stars / 1000000).toFixed(1)}M`;
10
- if (stars >= 10000)
11
- return `${Math.round(stars / 1000)}K`;
12
- if (stars >= 1000)
13
- return `${(stars / 1000).toFixed(1)}K`;
14
- return `${stars}`;
15
- }
16
- /** Star icon and color based on reliability classification */
17
- function starIcon(reliability) {
18
- if (reliability === "mega-repo")
19
- return "☆";
20
- if (reliability === "skill-dump")
21
- return "☆";
22
- return "★";
23
- }
24
- function starColor(reliability) {
25
- if (reliability === "mega-repo")
26
- return "gray";
27
- if (reliability === "skill-dump")
28
- return "#888800";
29
- return "yellow";
30
- }
31
- function formatStars(stars, reliability) {
32
- if (!stars)
33
- return "";
34
- return `${starIcon(reliability)} ${formatStarsNum(stars)}`;
35
- }
36
- // ─── Category renderer ────────────────────────────────────────────────────────
37
- const categoryRenderer = {
38
- renderRow: ({ item, isSelected }) => {
39
- const label = `${item.star ?? ""}${item.title}`;
40
- return (_jsx(ListCategoryRow, { selected: isSelected, title: label, count: item.count, badge: item.badge, tone: item.tone }));
41
- },
42
- renderDetail: ({ item }) => {
43
- const isRec = item.categoryKey === "recommended";
44
- return (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { fg: isRec ? "green" : "cyan", children: _jsxs("strong", { children: [item.star ?? "", item.title] }) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "gray", children: isRec
45
- ? "Curated skills recommended for most projects"
46
- : "Popular skills sorted by stars" }) })] }));
47
- },
48
- };
49
- // ─── Skill renderer ───────────────────────────────────────────────────────────
50
- const MAX_SKILL_NAME_LEN = 35;
51
- function truncateName(name) {
52
- return name.length > MAX_SKILL_NAME_LEN
53
- ? name.slice(0, MAX_SKILL_NAME_LEN - 1) + "\u2026"
54
- : name;
55
- }
56
- const skillRenderer = {
57
- renderRow: ({ item, isSelected }) => {
58
- const { skill } = item;
59
- const hasUser = skill.installedScope === "user";
60
- const hasProject = skill.installedScope === "project";
61
- const reliability = skill.starReliability;
62
- const starsStr = formatStars(skill.stars, reliability);
63
- const sColor = starColor(reliability);
64
- const displayName = truncateName(skill.name);
65
- const indentLevel = item.indent ?? 1;
66
- return (_jsxs(SelectableRow, { selected: isSelected, indent: indentLevel, children: [_jsx(ScopeSquares, { user: hasUser, project: hasProject, selected: isSelected }), _jsx("span", { children: " " }), _jsx("span", { fg: isSelected ? "white" : skill.installed ? "white" : "gray", children: displayName }), skill.hasUpdate ? _jsx(MetaText, { text: " \u2B06", tone: "warning" }) : null, starsStr ? _jsx("span", { fg: sColor, children: ` ${starsStr}` }) : null] }));
67
- },
68
- renderDetail: ({ item }) => {
69
- const { skill } = item;
70
- const fm = skill.frontmatter;
71
- const description = fm?.description || skill.description || "Loading...";
72
- const reliability = skill.starReliability;
73
- const starsStr = formatStars(skill.stars, reliability);
74
- const sColor = starColor(reliability);
75
- const reliabilityInfo = reliability ? STAR_RELIABILITY_INFO[reliability] : null;
76
- return (_jsxs("box", { flexDirection: "column", children: [_jsxs("text", { fg: "cyan", children: [_jsx("strong", { children: skill.name }), starsStr ? _jsxs("span", { fg: sColor, children: [" ", starsStr] }) : null] }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "white", children: description }) }), fm?.category ? (_jsx(KeyValueLine, { label: "Category", value: _jsx("span", { fg: "cyan", children: fm.category }) })) : null, fm?.author ? (_jsx(KeyValueLine, { label: "Author", value: _jsx("span", { children: fm.author }) })) : null, fm?.version ? (_jsx(KeyValueLine, { label: "Version", value: _jsx("span", { children: fm.version }) })) : null, fm?.tags && fm.tags.length > 0 ? (_jsx(KeyValueLine, { label: "Tags", value: _jsx("span", { children: fm.tags.join(", ") }) })) : null, _jsxs(DetailSection, { children: [_jsxs("text", { children: [_jsx("span", { fg: "gray", children: "Source " }), _jsx("span", { fg: "#5c9aff", children: skill.source.repo })] }), _jsxs("text", { children: [_jsx("span", { fg: "gray", children: " " }), _jsx("span", { fg: "gray", children: skill.repoPath })] })] }), reliabilityInfo && reliability !== "dedicated" && (_jsx("box", { marginTop: 1, children: _jsxs("text", { children: [_jsxs("span", { fg: sColor, children: [starIcon(reliability), " "] }), _jsx("span", { fg: sColor, children: _jsx("strong", { children: reliabilityInfo.label }) }), _jsxs("span", { fg: "gray", children: [" \u2014 ", reliabilityInfo.description] })] }) })), _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: [_jsx("span", { bg: "cyan", fg: "black", children: " u " }), _jsx("span", { fg: skill.installedScope === "user" ? "cyan" : "gray", children: skill.installedScope === "user" ? " ● " : " ○ " }), _jsx("span", { fg: "cyan", children: "User" }), _jsx("span", { fg: "gray", children: " ~/.claude/skills/" })] }), _jsxs("text", { children: [_jsx("span", { bg: "green", fg: "black", children: " p " }), _jsx("span", { fg: skill.installedScope === "project" ? "green" : "gray", children: skill.installedScope === "project" ? " ● " : " ○ " }), _jsx("span", { fg: "green", children: "Project" }), _jsx("span", { fg: "gray", children: " .claude/skills/" })] })] })] }), skill.hasUpdate && (_jsx("box", { marginTop: 1, children: _jsx("text", { bg: "yellow", fg: "black", children: " UPDATE AVAILABLE " }) })), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "gray", children: skill.installed
77
- ? "Press u/p to toggle scope"
78
- : "Press u/p to install" }) }), _jsx("box", { children: _jsxs("text", { children: [_jsx("span", { bg: "#555555", fg: "white", children: " o " }), _jsx("span", { fg: "gray", children: " Open in browser" })] }) })] }));
79
- },
80
- };
81
- // ─── Skill Set renderer ──────────────────────────────────────────────────────
82
- const MAX_SET_NAME_LEN = 28;
83
- const skillSetRenderer = {
84
- renderRow: ({ item, isSelected }) => {
85
- const { skillSet, expanded } = item;
86
- const arrow = expanded ? "\u25BC" : "\u25B6";
87
- const displayName = skillSet.name.length > MAX_SET_NAME_LEN
88
- ? skillSet.name.slice(0, MAX_SET_NAME_LEN - 1) + "\u2026"
89
- : skillSet.name;
90
- // Count installed child skills
91
- const installedCount = skillSet.loaded
92
- ? skillSet.skills.filter((s) => s.installed).length
93
- : 0;
94
- const totalCount = skillSet.loaded ? skillSet.skills.length : "...";
95
- const countStr = `(${installedCount}/${totalCount})`;
96
- // Any child installed at user or project scope?
97
- const hasUser = skillSet.loaded && skillSet.skills.some((s) => s.installedScope === "user");
98
- const hasProject = skillSet.loaded && skillSet.skills.some((s) => s.installedScope === "project");
99
- const starsStr = formatStars(skillSet.stars);
100
- return (_jsxs(SelectableRow, { selected: isSelected, indent: 1, children: [_jsxs("span", { fg: isSelected ? "white" : "gray", children: [arrow, " "] }), _jsxs("span", { fg: isSelected ? "white" : "yellow", children: [skillSet.icon, " "] }), _jsx("span", { fg: isSelected ? "white" : "cyan", children: _jsx("strong", { children: displayName }) }), _jsxs("span", { fg: "gray", children: [" ", countStr] }), starsStr ? _jsx(MetaText, { text: ` ${starsStr}`, tone: "warning" }) : null, _jsx("span", { children: " " }), _jsx(ScopeSquares, { user: hasUser, project: hasProject, selected: isSelected })] }));
101
- },
102
- renderDetail: ({ item }) => {
103
- const { skillSet, expanded } = item;
104
- const starsStr = formatStars(skillSet.stars);
105
- return (_jsxs("box", { flexDirection: "column", children: [_jsxs("text", { fg: "cyan", children: [_jsxs("strong", { children: [skillSet.icon, " ", skillSet.name] }), starsStr ? _jsxs("span", { fg: "yellow", children: [" ", starsStr] }) : null] }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "white", children: skillSet.description }) }), _jsx(DetailSection, { children: _jsxs("text", { children: [_jsx("span", { fg: "gray", children: "Source " }), _jsx("span", { fg: "#5c9aff", children: skillSet.repo })] }) }), skillSet.loading && (_jsx("box", { marginTop: 1, children: _jsx("text", { fg: "yellow", children: "Loading skills..." }) })), skillSet.error && (_jsx("box", { marginTop: 1, children: _jsxs("text", { fg: "red", children: ["Error: ", skillSet.error] }) })), skillSet.loaded && skillSet.skills.length > 0 && (_jsxs(DetailSection, { children: [_jsxs("text", { children: [_jsx("strong", { children: "Skills in this set:" }), _jsxs("span", { fg: "gray", children: [" ", "(", skillSet.skills.filter((s) => s.installed).length, "/", skillSet.skills.length, " installed)"] })] }), _jsx("box", { marginTop: 1, flexDirection: "column", children: skillSet.skills.map((s) => (_jsxs("text", { children: [_jsx("span", { fg: s.installedScope === "user" ? "cyan" : "gray", children: s.installedScope === "user" ? "\u25A0" : "\u25A1" }), _jsx("span", { fg: s.installedScope === "project" ? "green" : "gray", children: s.installedScope === "project" ? "\u25A0" : "\u25A1" }), _jsxs("span", { fg: s.installed ? "white" : "gray", children: [" ", s.name] })] }, s.id))) })] })), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "gray", children: expanded
106
- ? "Press Enter to collapse \u2022 u/p to install all"
107
- : "Press Enter to expand \u2022 u/p to install all" }) }), _jsx("box", { children: _jsxs("text", { children: [_jsx("span", { bg: "#555555", fg: "white", children: " o " }), _jsx("span", { fg: "gray", children: " Open in browser" })] }) })] }));
108
- },
109
- };
110
- // ─── Registry ─────────────────────────────────────────────────────────────────
111
- export const skillRenderers = {
112
- category: categoryRenderer,
113
- skill: skillRenderer,
114
- skillset: skillSetRenderer,
115
- };
116
- /**
117
- * Dispatch rendering by item kind.
118
- */
119
- export function renderSkillRow(item, _index, isSelected) {
120
- if (item.kind === "category") {
121
- return skillRenderers.category.renderRow({ item, isSelected });
122
- }
123
- if (item.kind === "skillset") {
124
- return skillRenderers.skillset.renderRow({ item, isSelected });
125
- }
126
- return skillRenderers.skill.renderRow({ item, isSelected });
127
- }
128
- export function renderSkillDetail(item) {
129
- if (!item)
130
- return _jsx("text", { fg: "gray", children: "Select a skill to see details" });
131
- if (item.kind === "category") {
132
- return skillRenderers.category.renderDetail({ item });
133
- }
134
- if (item.kind === "skillset") {
135
- return skillRenderers.skillset.renderDetail({ item });
136
- }
137
- return skillRenderers.skill.renderDetail({ item });
138
- }