magus-cli 0.0.0-placeholder → 7.1.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 (330) hide show
  1. package/README.md +309 -0
  2. package/bin/magus.js +170 -0
  3. package/package.json +67 -3
  4. package/scripts/build-binaries.ts +113 -0
  5. package/scripts/capture-builtin.ts +412 -0
  6. package/scripts/check-optional-deps.ts +49 -0
  7. package/scripts/test-isolated.ts +49 -0
  8. package/scripts/verify-binary.ts +95 -0
  9. package/scripts/verify-community-registry.ts +272 -0
  10. package/src/__tests__/active-closure.test.ts +184 -0
  11. package/src/__tests__/alias-adopt.test.ts +365 -0
  12. package/src/__tests__/alias-parser.test.ts +417 -0
  13. package/src/__tests__/alias-shell-writer.test.ts +681 -0
  14. package/src/__tests__/alias-store.test.ts +163 -0
  15. package/src/__tests__/binary-signing.test.ts +120 -0
  16. package/src/__tests__/blackbox-active-closure.test.ts +119 -0
  17. package/src/__tests__/blackbox-convergence.test.ts +334 -0
  18. package/src/__tests__/blackbox-parity.test.ts +725 -0
  19. package/src/__tests__/blackbox-plugin-state.test.ts +545 -0
  20. package/src/__tests__/blackbox-tool-state.test.ts +669 -0
  21. package/src/__tests__/cache-provenance.test.ts +84 -0
  22. package/src/__tests__/capture-builtin.test.ts +92 -0
  23. package/src/__tests__/catalog-cache-store.test.ts +309 -0
  24. package/src/__tests__/catalog-notice.test.ts +155 -0
  25. package/src/__tests__/cli-ansi.test.ts +297 -0
  26. package/src/__tests__/cli-apply-seams.test.ts +288 -0
  27. package/src/__tests__/cli-live.test.ts +391 -0
  28. package/src/__tests__/cli-router.test.ts +240 -0
  29. package/src/__tests__/cli-tool-commands.test.ts +239 -0
  30. package/src/__tests__/cli-tools-adapter.test.ts +294 -0
  31. package/src/__tests__/cli-update-view.test.ts +524 -0
  32. package/src/__tests__/community-fetch.test.ts +545 -0
  33. package/src/__tests__/community-registry.test.ts +269 -0
  34. package/src/__tests__/community-staleness.test.ts +720 -0
  35. package/src/__tests__/component-badge.test.ts +98 -0
  36. package/src/__tests__/component-icons.test.ts +94 -0
  37. package/src/__tests__/content-drift.test.ts +353 -0
  38. package/src/__tests__/conventions-integration.test.ts +830 -0
  39. package/src/__tests__/conventions-manager.test.ts +1236 -0
  40. package/src/__tests__/doctor-bins.test.ts +43 -0
  41. package/src/__tests__/doctor.test.ts +463 -0
  42. package/src/__tests__/dotenv.test.ts +195 -0
  43. package/src/__tests__/enabled-not-installed.test.ts +94 -0
  44. package/src/__tests__/file-locking.test.ts +208 -0
  45. package/src/__tests__/fixtures/tool-probe/brew-list-versions-tmux.txt +2 -0
  46. package/src/__tests__/fixtures/tool-probe/brew-outdated-behind-homebrew-spec.txt +29 -0
  47. package/src/__tests__/fixtures/tool-probe/brew-outdated-tmux-current.txt +7 -0
  48. package/src/__tests__/fixtures/tool-probe/brew-outdated-unknown-formula.txt +6 -0
  49. package/src/__tests__/fixtures/tool-probe/go-version-m-tmux-mcp.txt +27 -0
  50. package/src/__tests__/fixtures/tool-probe/npm-dist-tags-missing-scoped.txt +4 -0
  51. package/src/__tests__/fixtures/tool-probe/npm-dist-tags-openai-codex.txt +4 -0
  52. package/src/__tests__/fixtures/tool-probe/pypi-cowsay.txt +3 -0
  53. package/src/__tests__/fixtures/tool-probe/uv-receipt-browser-use.toml +10 -0
  54. package/src/__tests__/fixtures/tool-probe/uv-receipt-cowsay-git.toml +8 -0
  55. package/src/__tests__/fixtures/tool-probe/uv-receipt-cowsay-pinned.toml +8 -0
  56. package/src/__tests__/fixtures/tool-probe/uv-receipt-graphifyy.toml +7 -0
  57. package/src/__tests__/fixtures/tool-probe/uv-receipt-mcp.toml +6 -0
  58. package/src/__tests__/fixtures/tool-probe/uv-receipt-serena-agent.toml +9 -0
  59. package/src/__tests__/fixtures/tool-probe/uv-tool-list-outdated-pinned.txt +5 -0
  60. package/src/__tests__/fixtures/tool-probe/uv-tool-list-outdated.txt +11 -0
  61. package/src/__tests__/fixtures/tool-probe/uv-tool-list.txt +22 -0
  62. package/src/__tests__/footer-hints.test.ts +40 -0
  63. package/src/__tests__/format-command.test.ts +78 -0
  64. package/src/__tests__/git-worktree.test.ts +108 -0
  65. package/src/__tests__/github-budget.test.ts +206 -0
  66. package/src/__tests__/gitignore-check.test.ts +71 -0
  67. package/src/__tests__/gitignore-detector.test.ts +176 -0
  68. package/src/__tests__/gitignore-fixer.test.ts +317 -0
  69. package/src/__tests__/gitignore-resolver.test.ts +193 -0
  70. package/src/__tests__/gitignore-service.test.ts +137 -0
  71. package/src/__tests__/go-module.test.ts +43 -0
  72. package/src/__tests__/hook-import-policy.test.ts +90 -0
  73. package/src/__tests__/hook-process.test.ts +256 -0
  74. package/src/__tests__/hook-registration.test.ts +224 -0
  75. package/src/__tests__/install-command.test.ts +81 -0
  76. package/src/__tests__/install-plan.test.ts +104 -0
  77. package/src/__tests__/launcher-signal.test.ts +308 -0
  78. package/src/__tests__/local-marketplace-scan.test.ts +248 -0
  79. package/src/__tests__/manifest.test.ts +281 -0
  80. package/src/__tests__/markdown-renderer.test.ts +142 -0
  81. package/src/__tests__/marketplace-badge.test.ts +140 -0
  82. package/src/__tests__/marketplace-refresh.test.ts +340 -0
  83. package/src/__tests__/marketplace-version.test.ts +84 -0
  84. package/src/__tests__/marketplaces.test.ts +128 -0
  85. package/src/__tests__/mate-availability.test.ts +156 -0
  86. package/src/__tests__/mate-catalog.test.ts +295 -0
  87. package/src/__tests__/mcp-registry.test.ts +158 -0
  88. package/src/__tests__/model-visuals.test.tsx +2466 -0
  89. package/src/__tests__/models-adapter.test.ts +332 -0
  90. package/src/__tests__/models-cli.test.ts +273 -0
  91. package/src/__tests__/models-core.test.ts +802 -0
  92. package/src/__tests__/models-manager.test.ts +500 -0
  93. package/src/__tests__/models-presets-marketplace.test.ts +168 -0
  94. package/src/__tests__/models-screen-state.test.ts +315 -0
  95. package/src/__tests__/open-file.test.ts +59 -0
  96. package/src/__tests__/owned-marketplaces.test.ts +93 -0
  97. package/src/__tests__/plugin-checkout.test.ts +66 -0
  98. package/src/__tests__/plugin-cli-argv.test.ts +109 -0
  99. package/src/__tests__/plugin-contents.test.ts +371 -0
  100. package/src/__tests__/plugin-manager-fallback.test.ts +320 -0
  101. package/src/__tests__/plugin-releases.test.ts +98 -0
  102. package/src/__tests__/plugin-requires.test.ts +187 -0
  103. package/src/__tests__/plugin-row-text.test.ts +345 -0
  104. package/src/__tests__/plugin-setup.test.ts +1016 -0
  105. package/src/__tests__/profile-adopt.test.ts +177 -0
  106. package/src/__tests__/profile-command.test.ts +110 -0
  107. package/src/__tests__/profile-materializer.test.ts +238 -0
  108. package/src/__tests__/profile-sync.test.ts +154 -0
  109. package/src/__tests__/registry-version-resolution.test.ts +88 -0
  110. package/src/__tests__/resolve-executable.test.ts +42 -0
  111. package/src/__tests__/resolver.test.ts +351 -0
  112. package/src/__tests__/scope-action.test.ts +134 -0
  113. package/src/__tests__/scope-squares.test.tsx +164 -0
  114. package/src/__tests__/selection-scope.test.ts +100 -0
  115. package/src/__tests__/settings-file.test.ts +179 -0
  116. package/src/__tests__/shell-script-callers.test.ts +92 -0
  117. package/src/__tests__/skill-install-files.test.ts +136 -0
  118. package/src/__tests__/skills-catalog-client.test.ts +48 -0
  119. package/src/__tests__/style-wrap.test.ts +220 -0
  120. package/src/__tests__/styles-manager.test.ts +1098 -0
  121. package/src/__tests__/styles-origins.test.ts +414 -0
  122. package/src/__tests__/styles-screen-state.test.ts +457 -0
  123. package/src/__tests__/styles-status-line.test.ts +72 -0
  124. package/src/__tests__/styles-sync.test.ts +452 -0
  125. package/src/__tests__/symlink-manager.test.ts +180 -0
  126. package/src/__tests__/tabbar-layout.test.ts +100 -0
  127. package/src/__tests__/terminology-filler.test.ts +214 -0
  128. package/src/__tests__/theme-adaptive-colors.test.ts +354 -0
  129. package/src/__tests__/theme-env.test.ts +310 -0
  130. package/src/__tests__/theme-resolve.test.ts +380 -0
  131. package/src/__tests__/tool-probe.test.ts +1400 -0
  132. package/src/__tests__/toolchain.test.ts +232 -0
  133. package/src/__tests__/ui-version-writers.test.ts +88 -0
  134. package/src/__tests__/update-apply.test.ts +637 -0
  135. package/src/__tests__/update-plan.test.ts +1396 -0
  136. package/src/__tests__/uppercase-keybindings.test.ts +101 -0
  137. package/src/__tests__/useGitignoreModal.test.ts +79 -0
  138. package/src/__tests__/version-check-refresh.test.ts +45 -0
  139. package/src/__tests__/version-snapshot.test.ts +212 -0
  140. package/src/__tests__/version-unknown-state.test.ts +403 -0
  141. package/src/__tests__/worktree-registry-resolution.test.ts +107 -0
  142. package/src/cli/ansi.ts +528 -0
  143. package/src/cli/bootstrap.ts +134 -0
  144. package/src/cli/doctor.ts +218 -0
  145. package/src/cli/hook.ts +129 -0
  146. package/src/cli/install.ts +382 -0
  147. package/src/cli/live.ts +374 -0
  148. package/src/cli/models.ts +297 -0
  149. package/src/cli/profile.ts +197 -0
  150. package/src/cli/prompt.ts +32 -0
  151. package/src/cli/router.ts +264 -0
  152. package/src/cli/update-view.ts +717 -0
  153. package/src/cli/update.ts +600 -0
  154. package/src/cli/upgrade.ts +97 -0
  155. package/src/data/alias-flags.ts +301 -0
  156. package/src/data/cli-tools.ts +146 -0
  157. package/src/data/community-styles.ts +531 -0
  158. package/src/data/gitignore-defaults.ts +33 -0
  159. package/src/data/gitignore-reasons.ts +93 -0
  160. package/src/data/gitignore-templates.ts +25 -0
  161. package/src/data/marketplaces.ts +278 -0
  162. package/src/data/mcp-servers.ts +51 -0
  163. package/src/data/models-presets.ts +329 -0
  164. package/src/data/predefined-profiles.ts +252 -0
  165. package/src/data/settings-catalog.ts +714 -0
  166. package/src/data/skill-repos.ts +91 -0
  167. package/src/data/styles/asd-ste100.md +38 -0
  168. package/src/data/styles/calibrated.md +24 -0
  169. package/src/data/styles/direct.md +23 -0
  170. package/src/data/styles/evidence-first.md +22 -0
  171. package/src/data/styles/explanatory.md +23 -0
  172. package/src/data/styles/index.ts +57 -0
  173. package/src/data/styles/no-slop.md +32 -0
  174. package/src/data/styles/plain-language.md +23 -0
  175. package/src/data/styles/structured.md +30 -0
  176. package/src/data/styles/terminology.md +28 -0
  177. package/src/data/styles/terse.md +20 -0
  178. package/src/main.tsx +110 -0
  179. package/src/markdown.d.ts +13 -0
  180. package/src/opentui.d.ts +203 -0
  181. package/src/services/active-closure.ts +117 -0
  182. package/src/services/alias-settings.ts +60 -0
  183. package/src/services/alias-shell-writer.ts +1244 -0
  184. package/src/services/alias-store.ts +172 -0
  185. package/src/services/binary-signing.ts +78 -0
  186. package/src/services/catalog-cache-store.ts +316 -0
  187. package/src/services/claude-cli.ts +304 -0
  188. package/src/services/claude-runner.ts +31 -0
  189. package/src/services/claude-settings.ts +1564 -0
  190. package/src/services/cli-tool-commands.ts +133 -0
  191. package/src/services/community-fetcher.ts +90 -0
  192. package/src/services/community-styles.ts +1194 -0
  193. package/src/services/content-drift.ts +208 -0
  194. package/src/services/conventions-manager.ts +945 -0
  195. package/src/services/doctor-bins.ts +63 -0
  196. package/src/services/doctor.ts +570 -0
  197. package/src/services/dotenv.ts +181 -0
  198. package/src/services/git-worktree.ts +129 -0
  199. package/src/services/github-budget.ts +274 -0
  200. package/src/services/gitignore-check.ts +63 -0
  201. package/src/services/gitignore-detector.ts +167 -0
  202. package/src/services/gitignore-fixer.ts +280 -0
  203. package/src/services/gitignore-resolver.ts +208 -0
  204. package/src/services/gitignore-service.ts +167 -0
  205. package/src/services/hook-registration.ts +218 -0
  206. package/src/services/install-plan.ts +107 -0
  207. package/src/services/local-marketplace.ts +657 -0
  208. package/src/services/manifest.ts +250 -0
  209. package/src/services/marketplace-catalog-git.ts +173 -0
  210. package/src/services/marketplace-catalog.ts +129 -0
  211. package/src/services/marketplace-fetcher.ts +400 -0
  212. package/src/services/marketplace-refresh.ts +245 -0
  213. package/src/services/marketplace-sync.ts +145 -0
  214. package/src/services/mate-availability.ts +133 -0
  215. package/src/services/mate-catalog.ts +265 -0
  216. package/src/services/mcp-registry.ts +171 -0
  217. package/src/services/models-core.ts +969 -0
  218. package/src/services/models-manager.ts +606 -0
  219. package/src/services/plugin-checkout.ts +186 -0
  220. package/src/services/plugin-manager.ts +847 -0
  221. package/src/services/plugin-mcp-config.ts +242 -0
  222. package/src/services/plugin-releases.ts +47 -0
  223. package/src/services/plugin-requires.ts +227 -0
  224. package/src/services/plugin-setup.ts +965 -0
  225. package/src/services/profile-adopt.ts +206 -0
  226. package/src/services/profile-materializer.ts +133 -0
  227. package/src/services/profile-sync.ts +158 -0
  228. package/src/services/profiles.ts +225 -0
  229. package/src/services/resolver.ts +377 -0
  230. package/src/services/settings-file.ts +69 -0
  231. package/src/services/settings-manager.ts +307 -0
  232. package/src/services/skills-manager.ts +1111 -0
  233. package/src/services/skillsmp-client.ts +193 -0
  234. package/src/services/styles-manager.ts +1294 -0
  235. package/src/services/symlink-manager.ts +189 -0
  236. package/src/services/terminology-filler.ts +266 -0
  237. package/src/services/tool-probe.ts +1637 -0
  238. package/src/services/toolchain.ts +431 -0
  239. package/src/services/update-engine.ts +691 -0
  240. package/src/services/update-plan.ts +820 -0
  241. package/src/services/version-check.ts +127 -0
  242. package/src/services/version-snapshot.ts +189 -0
  243. package/src/tui.tsx +112 -0
  244. package/src/types/bun.d.ts +27 -0
  245. package/src/types/gitignore.ts +68 -0
  246. package/src/types/index.ts +447 -0
  247. package/src/ui/App.tsx +535 -0
  248. package/src/ui/adapters/catalogNotice.ts +122 -0
  249. package/src/ui/adapters/cliToolsAdapter.ts +176 -0
  250. package/src/ui/adapters/modelsAdapter.ts +213 -0
  251. package/src/ui/adapters/pluginsAdapter.ts +413 -0
  252. package/src/ui/adapters/settingsAdapter.ts +168 -0
  253. package/src/ui/adapters/skillsAdapter.ts +229 -0
  254. package/src/ui/adapters/stylesAdapter.ts +403 -0
  255. package/src/ui/components/CategoryHeader.tsx +64 -0
  256. package/src/ui/components/EmptyFilterState.tsx +32 -0
  257. package/src/ui/components/FlagDetailEditor.tsx +0 -0
  258. package/src/ui/components/ScopeIndicator.tsx +63 -0
  259. package/src/ui/components/ScrollableList.tsx +95 -0
  260. package/src/ui/components/SearchInput.tsx +57 -0
  261. package/src/ui/components/StyledText.tsx +75 -0
  262. package/src/ui/components/TabBar.tsx +101 -0
  263. package/src/ui/components/layout/FooterHints.tsx +78 -0
  264. package/src/ui/components/layout/Panel.tsx +63 -0
  265. package/src/ui/components/layout/ProgressBar.tsx +44 -0
  266. package/src/ui/components/layout/ScopeTabs.tsx +54 -0
  267. package/src/ui/components/layout/ScreenLayout.tsx +321 -0
  268. package/src/ui/components/layout/index.ts +9 -0
  269. package/src/ui/components/modals/ConfirmModal.tsx +62 -0
  270. package/src/ui/components/modals/InputModal.tsx +75 -0
  271. package/src/ui/components/modals/LoadingModal.tsx +51 -0
  272. package/src/ui/components/modals/MessageModal.tsx +66 -0
  273. package/src/ui/components/modals/ModalContainer.tsx +159 -0
  274. package/src/ui/components/modals/SelectModal.tsx +90 -0
  275. package/src/ui/components/modals/index.ts +6 -0
  276. package/src/ui/components/primitives/ActionHints.tsx +44 -0
  277. package/src/ui/components/primitives/DetailSection.tsx +23 -0
  278. package/src/ui/components/primitives/KeyValueLine.tsx +26 -0
  279. package/src/ui/components/primitives/ListCategoryRow.tsx +45 -0
  280. package/src/ui/components/primitives/MetaText.tsx +40 -0
  281. package/src/ui/components/primitives/ScopeDetail.tsx +67 -0
  282. package/src/ui/components/primitives/ScopeSquares.tsx +58 -0
  283. package/src/ui/components/primitives/SelectableRow.tsx +30 -0
  284. package/src/ui/components/primitives/index.ts +9 -0
  285. package/src/ui/hooks/index.ts +2 -0
  286. package/src/ui/hooks/useGitignoreModal.ts +95 -0
  287. package/src/ui/hooks/useKeyboard.ts +24 -0
  288. package/src/ui/hooks/useKeyboardHandler.ts +63 -0
  289. package/src/ui/registry.ts +33 -0
  290. package/src/ui/renderers/cliToolRenderers.tsx +326 -0
  291. package/src/ui/renderers/gitignoreRenderers.tsx +80 -0
  292. package/src/ui/renderers/markdownRenderer.tsx +302 -0
  293. package/src/ui/renderers/mcpRenderers.tsx +149 -0
  294. package/src/ui/renderers/modelRenderers.tsx +1309 -0
  295. package/src/ui/renderers/modelVisuals.tsx +1402 -0
  296. package/src/ui/renderers/pluginRenderers.tsx +1089 -0
  297. package/src/ui/renderers/pluginRowText.ts +70 -0
  298. package/src/ui/renderers/pluginStatusText.ts +56 -0
  299. package/src/ui/renderers/profileRenderers.tsx +406 -0
  300. package/src/ui/renderers/settingsRenderers.tsx +218 -0
  301. package/src/ui/renderers/skillRenderers.tsx +479 -0
  302. package/src/ui/renderers/styleRenderers.tsx +813 -0
  303. package/src/ui/screens/AliasScreen.tsx +1558 -0
  304. package/src/ui/screens/CliToolsScreen.tsx +367 -0
  305. package/src/ui/screens/EnvVarsScreen.tsx +221 -0
  306. package/src/ui/screens/GitignoreScreen.tsx +562 -0
  307. package/src/ui/screens/McpRegistryScreen.tsx +366 -0
  308. package/src/ui/screens/McpScreen.tsx +256 -0
  309. package/src/ui/screens/ModelsScreen.tsx +542 -0
  310. package/src/ui/screens/PluginsScreen.tsx +1423 -0
  311. package/src/ui/screens/ProfilesScreen.tsx +438 -0
  312. package/src/ui/screens/SkillsScreen.tsx +853 -0
  313. package/src/ui/screens/StylesScreen.tsx +1064 -0
  314. package/src/ui/screens/index.ts +11 -0
  315. package/src/ui/state/AppContext.tsx +233 -0
  316. package/src/ui/state/DimensionsContext.tsx +154 -0
  317. package/src/ui/state/reducer.ts +810 -0
  318. package/src/ui/state/types.ts +503 -0
  319. package/src/ui/theme-mode.ts +440 -0
  320. package/src/ui/theme-resolve.ts +147 -0
  321. package/src/ui/theme.ts +175 -0
  322. package/src/utils/clipboard.ts +58 -0
  323. package/src/utils/command-utils.ts +149 -0
  324. package/src/utils/config-dir.ts +45 -0
  325. package/src/utils/file-locking.ts +146 -0
  326. package/src/utils/fuzzy-search.ts +138 -0
  327. package/src/utils/go-module.ts +36 -0
  328. package/src/utils/open-file.ts +84 -0
  329. package/src/utils/run.ts +67 -0
  330. package/src/utils/string-utils.ts +88 -0
@@ -0,0 +1,218 @@
1
+ import type React from "react";
2
+ import type {
3
+ SettingsBrowserItem,
4
+ SettingsCategoryItem,
5
+ SettingsSettingItem,
6
+ } from "../adapters/settingsAdapter.js";
7
+ import {
8
+ CATEGORY_BG,
9
+ CATEGORY_COLOR,
10
+ CATEGORY_DESCRIPTIONS,
11
+ CATEGORY_LABELS,
12
+ formatValue,
13
+ } from "../adapters/settingsAdapter.js";
14
+ import { MetaText, SelectableRow } from "../components/primitives/index.js";
15
+ import type { ItemRenderer } from "../registry.js";
16
+ import { theme } from "../theme.js";
17
+
18
+ // ─── Category renderer ─────────────────────────────────────────────────────────
19
+
20
+ const categoryRenderer: ItemRenderer<SettingsCategoryItem> = {
21
+ renderRow: ({ item, isSelected }) => {
22
+ const star = item.category === "recommended" ? "★ " : "";
23
+ const label = `${star}${CATEGORY_LABELS[item.category]}`;
24
+ const bg = isSelected ? theme.selection.bg : CATEGORY_BG[item.category];
25
+
26
+ return (
27
+ <box width="100%">
28
+ <text bg={bg} fg={theme.hints.fg}>
29
+ <strong> {label} </strong>
30
+ </text>
31
+ </box>
32
+ );
33
+ },
34
+
35
+ renderDetail: ({ item }) => {
36
+ const cat = item.category;
37
+ return (
38
+ <box flexDirection="column">
39
+ <text fg={CATEGORY_COLOR[cat]}>
40
+ <strong>{CATEGORY_LABELS[cat]}</strong>
41
+ </text>
42
+ <box marginTop={1}>
43
+ <text fg={theme.colors.muted}>{CATEGORY_DESCRIPTIONS[cat]}</text>
44
+ </box>
45
+ </box>
46
+ );
47
+ },
48
+ };
49
+
50
+ // ─── Setting renderer ──────────────────────────────────────────────────────────
51
+
52
+ const settingRenderer: ItemRenderer<SettingsSettingItem> = {
53
+ renderRow: ({ item, isSelected }) => {
54
+ const { setting } = item;
55
+ const indicator = item.isDefault ? "○" : "●";
56
+ const displayValue = formatValue(setting, item.effectiveValue);
57
+
58
+ if (isSelected) {
59
+ return (
60
+ <text bg={theme.selection.bg} fg={theme.selection.fg}>
61
+ {" "}
62
+ {indicator} {setting.name.padEnd(28)}
63
+ {displayValue}{" "}
64
+ </text>
65
+ );
66
+ }
67
+
68
+ const indicatorColor = item.isDefault
69
+ ? theme.colors.muted
70
+ : theme.colors.info;
71
+ const valueColor = item.isDefault
72
+ ? theme.colors.muted
73
+ : theme.colors.success;
74
+
75
+ return (
76
+ <text fg={theme.colors.text}>
77
+ <span fg={indicatorColor}> {indicator} </span>
78
+ <span>{setting.name.padEnd(28)}</span>
79
+ <span fg={valueColor}>{displayValue}</span>
80
+ </text>
81
+ );
82
+ },
83
+
84
+ renderDetail: ({ item }) => {
85
+ const { setting } = item;
86
+ const scoped = item.scopedValues;
87
+ const storageDesc =
88
+ setting.storage.type === "attribution"
89
+ ? "settings.json: attribution"
90
+ : setting.storage.type === "attribution-text"
91
+ ? "settings.json: attribution"
92
+ : setting.storage.type === "env"
93
+ ? `env: ${setting.storage.key}`
94
+ : `settings.json: ${setting.storage.key}`;
95
+
96
+ const userValue = formatValue(setting, scoped.user);
97
+ const projectValue = formatValue(setting, scoped.project);
98
+ const userIsSet = scoped.user !== undefined && scoped.user !== "";
99
+ const projectIsSet = scoped.project !== undefined && scoped.project !== "";
100
+
101
+ const actionLabel =
102
+ setting.type === "boolean"
103
+ ? "toggle"
104
+ : setting.type === "select"
105
+ ? "choose"
106
+ : "edit";
107
+
108
+ return (
109
+ <box flexDirection="column">
110
+ <text fg={theme.colors.info}>
111
+ <strong>{setting.name}</strong>
112
+ </text>
113
+ <box marginTop={1}>
114
+ <text fg={theme.colors.text}>{setting.description}</text>
115
+ </box>
116
+
117
+ <box marginTop={1}>
118
+ <text fg={theme.colors.text}>
119
+ <span fg={theme.colors.muted}>Stored </span>
120
+ <span fg={theme.colors.link}>{storageDesc}</span>
121
+ </text>
122
+ </box>
123
+ {setting.defaultValue !== undefined && (
124
+ <box>
125
+ <text fg={theme.colors.text}>
126
+ <span fg={theme.colors.muted}>Default </span>
127
+ <span>{setting.defaultValue}</span>
128
+ </text>
129
+ </box>
130
+ )}
131
+
132
+ <box flexDirection="column" marginTop={1}>
133
+ <text fg={theme.colors.text}>{"────────────────────────"}</text>
134
+ <text fg={theme.colors.text}>
135
+ <strong>Scopes:</strong>
136
+ </text>
137
+ <box marginTop={1} flexDirection="column">
138
+ <text fg={theme.colors.text}>
139
+ <span bg={theme.scopes.user} fg={theme.hints.fg}>
140
+ {" "}
141
+ u{" "}
142
+ </span>
143
+ <span fg={userIsSet ? theme.scopes.user : theme.colors.muted}>
144
+ {userIsSet ? " ● " : " ○ "}
145
+ </span>
146
+ <span fg={theme.scopes.user}>User</span>
147
+ <span> global</span>
148
+ <span fg={userIsSet ? theme.scopes.user : theme.colors.muted}>
149
+ {" "}
150
+ {userValue}
151
+ </span>
152
+ </text>
153
+ <text fg={theme.colors.text}>
154
+ <span bg={theme.scopes.project} fg={theme.hints.fg}>
155
+ {" "}
156
+ p{" "}
157
+ </span>
158
+ <span
159
+ fg={projectIsSet ? theme.scopes.project : theme.colors.muted}
160
+ >
161
+ {projectIsSet ? " ● " : " ○ "}
162
+ </span>
163
+ <span fg={theme.scopes.project}>Project</span>
164
+ <span> team</span>
165
+ <span
166
+ fg={projectIsSet ? theme.scopes.project : theme.colors.muted}
167
+ >
168
+ {" "}
169
+ {projectValue}
170
+ </span>
171
+ </text>
172
+ </box>
173
+ </box>
174
+
175
+ <box marginTop={1}>
176
+ <text fg={theme.colors.muted}>
177
+ Press u/p to {actionLabel} in scope
178
+ </text>
179
+ </box>
180
+ </box>
181
+ );
182
+ },
183
+ };
184
+
185
+ // ─── Dispatch helpers ──────────────────────────────────────────────────────────
186
+
187
+ export const settingsRenderers: {
188
+ category: ItemRenderer<SettingsCategoryItem>;
189
+ setting: ItemRenderer<SettingsSettingItem>;
190
+ } = {
191
+ category: categoryRenderer,
192
+ setting: settingRenderer,
193
+ };
194
+
195
+ export function renderSettingRow(
196
+ item: SettingsBrowserItem,
197
+ _index: number,
198
+ isSelected: boolean,
199
+ ): React.ReactNode {
200
+ if (item.kind === "category") {
201
+ return settingsRenderers.category.renderRow({ item, isSelected });
202
+ }
203
+ return settingsRenderers.setting.renderRow({ item, isSelected });
204
+ }
205
+
206
+ export function renderSettingDetail(
207
+ item: SettingsBrowserItem | undefined,
208
+ ): React.ReactNode {
209
+ if (!item)
210
+ return <text fg={theme.colors.muted}>Select a setting to see details</text>;
211
+ if (item.kind === "category") {
212
+ return settingsRenderers.category.renderDetail({ item });
213
+ }
214
+ return settingsRenderers.setting.renderDetail({ item });
215
+ }
216
+
217
+ // Suppress unused import warning — MetaText may be used in future extensions
218
+ void (MetaText as unknown);
@@ -0,0 +1,479 @@
1
+ import type React from "react";
2
+ import { STAR_RELIABILITY_INFO } from "../../data/skill-repos.js";
3
+ import type { StarReliability } from "../../types/index.js";
4
+ import type {
5
+ SkillBrowserItem,
6
+ SkillCategoryItem,
7
+ SkillSetItem,
8
+ SkillSkillItem,
9
+ } from "../adapters/skillsAdapter.js";
10
+ import {
11
+ ActionHints,
12
+ DetailSection,
13
+ KeyValueLine,
14
+ ListCategoryRow,
15
+ MetaText,
16
+ ScopeDetail,
17
+ ScopeSquares,
18
+ SelectableRow,
19
+ } from "../components/primitives/index.js";
20
+ import type { ItemRenderer } from "../registry.js";
21
+ import { type UiColor, theme } from "../theme.js";
22
+
23
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
24
+
25
+ function formatStarsNum(stars?: number): string {
26
+ if (!stars) return "";
27
+ if (stars >= 1000000) return `${(stars / 1000000).toFixed(1)}M`;
28
+ if (stars >= 10000) return `${Math.round(stars / 1000)}K`;
29
+ if (stars >= 1000) return `${(stars / 1000).toFixed(1)}K`;
30
+ return `${stars}`;
31
+ }
32
+
33
+ /** Star icon and color based on reliability classification */
34
+ function starIcon(reliability?: StarReliability): string {
35
+ if (reliability === "mega-repo") return "☆";
36
+ if (reliability === "skill-dump") return "☆";
37
+ return "★";
38
+ }
39
+
40
+ function starColor(reliability?: StarReliability): UiColor {
41
+ if (reliability === "mega-repo") return theme.colors.muted;
42
+ if (reliability === "skill-dump") return theme.colors.warning;
43
+ return theme.colors.warning;
44
+ }
45
+
46
+ function formatStars(stars?: number, reliability?: StarReliability): string {
47
+ if (!stars) return "";
48
+ return `${starIcon(reliability)} ${formatStarsNum(stars)}`;
49
+ }
50
+
51
+ // ─── Category renderer ────────────────────────────────────────────────────────
52
+
53
+ const categoryRenderer: ItemRenderer<SkillCategoryItem> = {
54
+ renderRow: ({ item, isSelected }) => {
55
+ const label = `${item.star ?? ""}${item.title}`;
56
+ return (
57
+ <ListCategoryRow
58
+ selected={isSelected}
59
+ title={label}
60
+ count={item.count}
61
+ badge={item.badge}
62
+ tone={item.tone}
63
+ />
64
+ );
65
+ },
66
+
67
+ renderDetail: ({ item }) => {
68
+ const isRec = item.categoryKey === "recommended";
69
+ return (
70
+ <box flexDirection="column">
71
+ <text fg={isRec ? theme.colors.success : theme.colors.info}>
72
+ <strong>
73
+ {item.star ?? ""}
74
+ {item.title}
75
+ </strong>
76
+ </text>
77
+ <box marginTop={1}>
78
+ <text fg={theme.colors.muted}>
79
+ {isRec
80
+ ? "Curated skills recommended for most projects"
81
+ : "Popular skills sorted by stars"}
82
+ </text>
83
+ </box>
84
+ </box>
85
+ );
86
+ },
87
+ };
88
+
89
+ // ─── Skill renderer ───────────────────────────────────────────────────────────
90
+
91
+ const MAX_SKILL_NAME_LEN = 35;
92
+
93
+ function truncateName(name: string): string {
94
+ return name.length > MAX_SKILL_NAME_LEN
95
+ ? `${name.slice(0, MAX_SKILL_NAME_LEN - 1)}\u2026`
96
+ : name;
97
+ }
98
+
99
+ const skillRenderer: ItemRenderer<SkillSkillItem> = {
100
+ renderRow: ({ item, isSelected }) => {
101
+ const { skill } = item;
102
+ const hasUser = skill.installedScope === "user";
103
+ const hasProject = skill.installedScope === "project";
104
+ const reliability = skill.starReliability;
105
+ const starsStr = formatStars(skill.stars, reliability);
106
+ const sColor = starColor(reliability);
107
+ const displayName = truncateName(skill.name);
108
+ const indentLevel = item.indent ?? 1;
109
+
110
+ return (
111
+ <SelectableRow selected={isSelected} indent={indentLevel}>
112
+ <ScopeSquares
113
+ user={hasUser}
114
+ project={hasProject}
115
+ selected={isSelected}
116
+ />
117
+ <span> </span>
118
+ <span
119
+ fg={
120
+ isSelected
121
+ ? theme.colors.text
122
+ : skill.installed
123
+ ? theme.colors.text
124
+ : theme.colors.muted
125
+ }
126
+ >
127
+ {displayName}
128
+ </span>
129
+ {skill.hasUpdate ? (
130
+ <MetaText text=" ⬆" tone="warning" selected={isSelected} />
131
+ ) : null}
132
+ {starsStr ? <span fg={sColor}>{` ${starsStr}`}</span> : null}
133
+ </SelectableRow>
134
+ );
135
+ },
136
+
137
+ renderDetail: ({ item }) => {
138
+ const { skill } = item;
139
+ const fm = skill.frontmatter;
140
+ const description = fm?.description || skill.description || "Loading...";
141
+ const reliability = skill.starReliability;
142
+ const starsStr = formatStars(skill.stars, reliability);
143
+ const sColor = starColor(reliability);
144
+ const reliabilityInfo = reliability
145
+ ? STAR_RELIABILITY_INFO[reliability]
146
+ : null;
147
+
148
+ return (
149
+ <box flexDirection="column">
150
+ <text fg={theme.colors.info}>
151
+ <strong>{skill.name}</strong>
152
+ {starsStr ? <span fg={sColor}> {starsStr}</span> : null}
153
+ </text>
154
+
155
+ <box marginTop={1}>
156
+ <text fg={theme.colors.text}>{description}</text>
157
+ </box>
158
+
159
+ {fm?.category ? (
160
+ <KeyValueLine
161
+ label="Category"
162
+ value={<span fg={theme.colors.info}>{fm.category}</span>}
163
+ />
164
+ ) : null}
165
+ {fm?.author ? (
166
+ <KeyValueLine label="Author" value={<span>{fm.author}</span>} />
167
+ ) : null}
168
+ {fm?.version ? (
169
+ <KeyValueLine label="Version" value={<span>{fm.version}</span>} />
170
+ ) : null}
171
+ {fm?.tags && fm.tags.length > 0 ? (
172
+ <KeyValueLine
173
+ label="Tags"
174
+ value={<span>{(fm.tags as string[]).join(", ")}</span>}
175
+ />
176
+ ) : null}
177
+
178
+ <DetailSection>
179
+ <text fg={theme.colors.text}>
180
+ <span fg={theme.colors.muted}>Source </span>
181
+ <span fg={theme.colors.link}>{skill.source.repo}</span>
182
+ </text>
183
+ <text fg={theme.colors.text}>
184
+ <span fg={theme.colors.muted}>{" "}</span>
185
+ <span fg={theme.colors.muted}>{skill.repoPath}</span>
186
+ </text>
187
+ </DetailSection>
188
+
189
+ {reliabilityInfo && reliability !== "dedicated" && (
190
+ <box marginTop={1}>
191
+ <text fg={theme.colors.text}>
192
+ <span fg={sColor}>{starIcon(reliability)} </span>
193
+ <span fg={sColor}>
194
+ <strong>{reliabilityInfo.label}</strong>
195
+ </span>
196
+ <span fg={theme.colors.muted}>
197
+ {" "}
198
+ — {reliabilityInfo.description}
199
+ </span>
200
+ </text>
201
+ </box>
202
+ )}
203
+
204
+ <DetailSection>
205
+ <text fg={theme.colors.text}>{"─".repeat(24)}</text>
206
+ <text fg={theme.colors.text}>
207
+ <strong>Scopes:</strong>
208
+ </text>
209
+ <box marginTop={1} flexDirection="column">
210
+ <text fg={theme.colors.text}>
211
+ <span bg={theme.colors.info} fg={theme.hints.fg}>
212
+ {" "}
213
+ u{" "}
214
+ </span>
215
+ <span
216
+ fg={
217
+ skill.installedScope === "user"
218
+ ? theme.colors.info
219
+ : theme.colors.muted
220
+ }
221
+ >
222
+ {skill.installedScope === "user" ? " ● " : " ○ "}
223
+ </span>
224
+ <span fg={theme.colors.info}>User</span>
225
+ <span fg={theme.colors.muted}> ~/.claude/skills/</span>
226
+ </text>
227
+ <text fg={theme.colors.text}>
228
+ <span bg={theme.colors.success} fg={theme.hints.fg}>
229
+ {" "}
230
+ p{" "}
231
+ </span>
232
+ <span
233
+ fg={
234
+ skill.installedScope === "project"
235
+ ? theme.colors.success
236
+ : theme.colors.muted
237
+ }
238
+ >
239
+ {skill.installedScope === "project" ? " ● " : " ○ "}
240
+ </span>
241
+ <span fg={theme.colors.success}>Project</span>
242
+ <span fg={theme.colors.muted}> .claude/skills/</span>
243
+ </text>
244
+ </box>
245
+ </DetailSection>
246
+
247
+ {skill.hasUpdate && (
248
+ <box marginTop={1}>
249
+ <text bg={theme.colors.warning} fg={theme.hints.fg}>
250
+ {" "}
251
+ UPDATE AVAILABLE{" "}
252
+ </text>
253
+ </box>
254
+ )}
255
+
256
+ <box marginTop={1}>
257
+ <text fg={theme.colors.muted}>
258
+ {skill.installed
259
+ ? "Press u/p to toggle scope"
260
+ : "Press u/p to install"}
261
+ </text>
262
+ </box>
263
+ <box>
264
+ <text fg={theme.colors.text}>
265
+ <span bg={theme.colors.dim} fg={theme.hints.fg}>
266
+ {" "}
267
+ o{" "}
268
+ </span>
269
+ <span fg={theme.colors.muted}> Open in browser</span>
270
+ </text>
271
+ </box>
272
+ </box>
273
+ );
274
+ },
275
+ };
276
+
277
+ // ─── Skill Set renderer ──────────────────────────────────────────────────────
278
+
279
+ const MAX_SET_NAME_LEN = 28;
280
+
281
+ const skillSetRenderer: ItemRenderer<SkillSetItem> = {
282
+ renderRow: ({ item, isSelected }) => {
283
+ const { skillSet, expanded } = item;
284
+ const arrow = expanded ? "\u25BC" : "\u25B6";
285
+ const displayName =
286
+ skillSet.name.length > MAX_SET_NAME_LEN
287
+ ? `${skillSet.name.slice(0, MAX_SET_NAME_LEN - 1)}\u2026`
288
+ : skillSet.name;
289
+
290
+ // Count installed child skills
291
+ const installedCount = skillSet.loaded
292
+ ? skillSet.skills.filter((s) => s.installed).length
293
+ : 0;
294
+ const totalCount = skillSet.loaded ? skillSet.skills.length : "...";
295
+ const countStr = `(${installedCount}/${totalCount})`;
296
+
297
+ // Any child installed at user or project scope?
298
+ const hasUser =
299
+ skillSet.loaded &&
300
+ skillSet.skills.some((s) => s.installedScope === "user");
301
+ const hasProject =
302
+ skillSet.loaded &&
303
+ skillSet.skills.some((s) => s.installedScope === "project");
304
+
305
+ const starsStr = formatStars(skillSet.stars);
306
+
307
+ return (
308
+ <SelectableRow selected={isSelected} indent={1}>
309
+ <span fg={isSelected ? theme.colors.text : theme.colors.muted}>
310
+ {arrow}{" "}
311
+ </span>
312
+ <span fg={isSelected ? theme.colors.text : theme.colors.warning}>
313
+ {skillSet.icon}{" "}
314
+ </span>
315
+ <span fg={isSelected ? theme.colors.text : theme.colors.info}>
316
+ <strong>{displayName}</strong>
317
+ </span>
318
+ <span fg={isSelected ? theme.selection.dim : theme.colors.muted}>
319
+ {` ${countStr}`}
320
+ </span>
321
+ {starsStr ? (
322
+ <MetaText
323
+ text={` ${starsStr}`}
324
+ tone="warning"
325
+ selected={isSelected}
326
+ />
327
+ ) : null}
328
+ <span> </span>
329
+ <ScopeSquares
330
+ user={hasUser}
331
+ project={hasProject}
332
+ selected={isSelected}
333
+ />
334
+ </SelectableRow>
335
+ );
336
+ },
337
+
338
+ renderDetail: ({ item }) => {
339
+ const { skillSet, expanded } = item;
340
+ const starsStr = formatStars(skillSet.stars);
341
+
342
+ return (
343
+ <box flexDirection="column">
344
+ <text fg={theme.colors.info}>
345
+ <strong>
346
+ {skillSet.icon} {skillSet.name}
347
+ </strong>
348
+ {starsStr ? <span fg={theme.colors.warning}> {starsStr}</span> : null}
349
+ </text>
350
+
351
+ <box marginTop={1}>
352
+ <text fg={theme.colors.text}>{skillSet.description}</text>
353
+ </box>
354
+
355
+ <DetailSection>
356
+ <text fg={theme.colors.text}>
357
+ <span fg={theme.colors.muted}>Source </span>
358
+ <span fg={theme.colors.link}>{skillSet.repo}</span>
359
+ </text>
360
+ </DetailSection>
361
+
362
+ {skillSet.loading && (
363
+ <box marginTop={1}>
364
+ <text fg={theme.colors.warning}>Loading skills...</text>
365
+ </box>
366
+ )}
367
+
368
+ {skillSet.error && (
369
+ <box marginTop={1}>
370
+ <text fg={theme.colors.danger}>Error: {skillSet.error}</text>
371
+ </box>
372
+ )}
373
+
374
+ {skillSet.loaded && skillSet.skills.length > 0 && (
375
+ <DetailSection>
376
+ <text fg={theme.colors.text}>
377
+ <strong>Skills in this set:</strong>
378
+ <span fg={theme.colors.muted}>
379
+ {" "}
380
+ ({skillSet.skills.filter((s) => s.installed).length}/
381
+ {skillSet.skills.length} installed)
382
+ </span>
383
+ </text>
384
+ <box marginTop={1} flexDirection="column">
385
+ {skillSet.skills.map((s) => (
386
+ <text key={s.id}>
387
+ <span
388
+ fg={
389
+ s.installedScope === "user"
390
+ ? theme.colors.info
391
+ : theme.colors.muted
392
+ }
393
+ >
394
+ {s.installedScope === "user" ? "\u25A0" : "\u25A1"}
395
+ </span>
396
+ <span
397
+ fg={
398
+ s.installedScope === "project"
399
+ ? theme.colors.success
400
+ : theme.colors.muted
401
+ }
402
+ >
403
+ {s.installedScope === "project" ? "\u25A0" : "\u25A1"}
404
+ </span>
405
+ <span
406
+ fg={s.installed ? theme.colors.text : theme.colors.muted}
407
+ >
408
+ {" "}
409
+ {s.name}
410
+ </span>
411
+ </text>
412
+ ))}
413
+ </box>
414
+ </DetailSection>
415
+ )}
416
+
417
+ <box marginTop={1}>
418
+ <text fg={theme.colors.muted}>
419
+ {expanded
420
+ ? "Press Enter to collapse \u2022 u/p to install all"
421
+ : "Press Enter to expand \u2022 u/p to install all"}
422
+ </text>
423
+ </box>
424
+ <box>
425
+ <text fg={theme.colors.text}>
426
+ <span bg={theme.colors.dim} fg={theme.hints.fg}>
427
+ {" "}
428
+ o{" "}
429
+ </span>
430
+ <span fg={theme.colors.muted}> Open in browser</span>
431
+ </text>
432
+ </box>
433
+ </box>
434
+ );
435
+ },
436
+ };
437
+
438
+ // ─── Registry ─────────────────────────────────────────────────────────────────
439
+
440
+ export const skillRenderers: {
441
+ category: ItemRenderer<SkillCategoryItem>;
442
+ skill: ItemRenderer<SkillSkillItem>;
443
+ skillset: ItemRenderer<SkillSetItem>;
444
+ } = {
445
+ category: categoryRenderer,
446
+ skill: skillRenderer,
447
+ skillset: skillSetRenderer,
448
+ };
449
+
450
+ /**
451
+ * Dispatch rendering by item kind.
452
+ */
453
+ export function renderSkillRow(
454
+ item: SkillBrowserItem,
455
+ _index: number,
456
+ isSelected: boolean,
457
+ ): React.ReactNode {
458
+ if (item.kind === "category") {
459
+ return skillRenderers.category.renderRow({ item, isSelected });
460
+ }
461
+ if (item.kind === "skillset") {
462
+ return skillRenderers.skillset.renderRow({ item, isSelected });
463
+ }
464
+ return skillRenderers.skill.renderRow({ item, isSelected });
465
+ }
466
+
467
+ export function renderSkillDetail(
468
+ item: SkillBrowserItem | undefined,
469
+ ): React.ReactNode {
470
+ if (!item)
471
+ return <text fg={theme.colors.muted}>Select a skill to see details</text>;
472
+ if (item.kind === "category") {
473
+ return skillRenderers.category.renderDetail({ item });
474
+ }
475
+ if (item.kind === "skillset") {
476
+ return skillRenderers.skillset.renderDetail({ item });
477
+ }
478
+ return skillRenderers.skill.renderDetail({ item });
479
+ }