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,503 @@
1
+ import type { CatalogModel } from "../../services/mate-catalog.js";
2
+ import type { ModelsConfig, ModelsStatus } from "../../services/models-core.js";
3
+ import type { PluginInfo } from "../../services/plugin-manager.js";
4
+ import type {
5
+ PluginComponent,
6
+ PluginSkillDetail,
7
+ } from "../../services/skills-manager.js";
8
+ import type { StylesSnapshot } from "../../services/styles-manager.js";
9
+ import type {
10
+ Marketplace,
11
+ McpServer,
12
+ ProfileEntry,
13
+ SkillInfo,
14
+ } from "../../types/index.js";
15
+
16
+ // ============================================================================
17
+ // Route Types
18
+ // ============================================================================
19
+
20
+ export type Screen =
21
+ | "plugins"
22
+ | "mcp"
23
+ | "mcp-registry"
24
+ | "settings"
25
+ | "cli-tools"
26
+ | "profiles"
27
+ | "skills"
28
+ | "styles"
29
+ | "gitignore"
30
+ | "alias"
31
+ | "models";
32
+
33
+ export type Route =
34
+ | { screen: "plugins" }
35
+ | { screen: "mcp" }
36
+ | { screen: "mcp-registry"; query?: string }
37
+ | { screen: "settings" }
38
+ | { screen: "cli-tools" }
39
+ | { screen: "profiles" }
40
+ | { screen: "skills" }
41
+ | { screen: "styles" }
42
+ | { screen: "gitignore" }
43
+ | { screen: "alias" }
44
+ | { screen: "models" };
45
+
46
+ // ============================================================================
47
+ // Async Data Types
48
+ // ============================================================================
49
+
50
+ export type AsyncData<T> =
51
+ | { status: "idle" }
52
+ /**
53
+ * `previous` carries the last successful value through a reload.
54
+ *
55
+ * Without it a refetch is indistinguishable from a first load, so a screen
56
+ * that already had data blanked itself to "Loading..." every time it
57
+ * remounted — and the router unmounts screens on every tab switch. Optional so
58
+ * that a genuine first load is still just `{ status: "loading" }`.
59
+ */
60
+ | { status: "loading"; previous?: T }
61
+ | { status: "success"; data: T }
62
+ | { status: "error"; error: Error };
63
+
64
+ /** The value to render: fresh if loaded, else the last good one during a reload. */
65
+ export function asyncValue<T>(d: AsyncData<T>): T | undefined {
66
+ if (d.status === "success") return d.data;
67
+ if (d.status === "loading") return d.previous;
68
+ return undefined;
69
+ }
70
+
71
+ // ============================================================================
72
+ // Modal Types
73
+ // ============================================================================
74
+
75
+ export interface SelectOption {
76
+ label: string;
77
+ value: string;
78
+ description?: string;
79
+ }
80
+
81
+ export type ModalState =
82
+ | {
83
+ type: "confirm";
84
+ title: string;
85
+ message: string;
86
+ onConfirm: () => void;
87
+ onCancel: () => void;
88
+ }
89
+ | {
90
+ type: "input";
91
+ title: string;
92
+ label: string;
93
+ defaultValue?: string;
94
+ onSubmit: (value: string) => void;
95
+ onCancel: () => void;
96
+ }
97
+ | {
98
+ type: "select";
99
+ title: string;
100
+ message: string;
101
+ options: SelectOption[];
102
+ defaultIndex?: number;
103
+ onSelect: (value: string) => void;
104
+ onCancel: () => void;
105
+ }
106
+ | {
107
+ type: "message";
108
+ title: string;
109
+ message: string;
110
+ variant: "info" | "success" | "error";
111
+ onDismiss: () => void;
112
+ }
113
+ | {
114
+ type: "loading";
115
+ message: string;
116
+ };
117
+
118
+ // ============================================================================
119
+ // Progress Types
120
+ // ============================================================================
121
+
122
+ export interface ProgressState {
123
+ message: string;
124
+ current?: number;
125
+ total?: number;
126
+ }
127
+
128
+ // ============================================================================
129
+ // Screen State Types
130
+ // ============================================================================
131
+
132
+ /** Read-only listing of what one plugin ships, fetched on demand. */
133
+ export interface PluginContentsEntry {
134
+ status: "loading" | "success" | "error";
135
+ components?: PluginComponent[];
136
+ error?: string;
137
+ }
138
+
139
+ /** One skill's SKILL.md, fetched when the cursor settles on its row. */
140
+ export interface PluginSkillDetailEntry {
141
+ status: "loading" | "success" | "error";
142
+ detail?: PluginSkillDetail;
143
+ error?: string;
144
+ }
145
+
146
+ export interface PluginsScreenState {
147
+ scope: "project" | "global";
148
+ collapsedMarketplaces: Set<string>;
149
+ /**
150
+ * Plugin ids whose contents list is open. Opposite default to
151
+ * `collapsedMarketplaces`: listing a plugin's contents costs an HTTP call,
152
+ * so it stays shut until asked for.
153
+ */
154
+ expandedPlugins: Set<string>;
155
+ pluginContents: Map<string, PluginContentsEntry>;
156
+ /** SKILL.md content, keyed `<repo>/<repoPath>`. */
157
+ skillDetails: Map<string, PluginSkillDetailEntry>;
158
+ selectedIndex: number;
159
+ searchQuery: string;
160
+ marketplaces: AsyncData<Marketplace[]>;
161
+ plugins: AsyncData<PluginInfo[]>;
162
+ }
163
+
164
+ export interface McpScreenState {
165
+ selectedIndex: number;
166
+ servers: AsyncData<McpServer[]>;
167
+ installedServers: Record<string, boolean>;
168
+ }
169
+
170
+ export interface McpRegistryScreenState {
171
+ searchQuery: string;
172
+ selectedIndex: number;
173
+ servers: AsyncData<McpServer[]>;
174
+ }
175
+
176
+ export interface ScopedSettingValues {
177
+ user: string | undefined;
178
+ project: string | undefined;
179
+ }
180
+
181
+ export interface SettingsScreenState {
182
+ selectedIndex: number;
183
+ values: AsyncData<Map<string, ScopedSettingValues>>;
184
+ }
185
+
186
+ export interface CliTool {
187
+ name: string;
188
+ displayName: string;
189
+ description: string;
190
+ installCommand: string;
191
+ checkCommand: string;
192
+ homebrewFormula?: string;
193
+ npmPackage?: string;
194
+ installedVersion?: string;
195
+ latestVersion?: string;
196
+ hasUpdate?: boolean;
197
+ isInstalled: boolean;
198
+ isChecking: boolean;
199
+ }
200
+
201
+ export interface CliToolsScreenState {
202
+ selectedIndex: number;
203
+ tools: AsyncData<CliTool[]>;
204
+ }
205
+
206
+ export interface ProfilesScreenState {
207
+ selectedIndex: number;
208
+ profiles: AsyncData<ProfileEntry[]>;
209
+ }
210
+
211
+ export interface SkillsScreenState {
212
+ scope: "user" | "project";
213
+ selectedIndex: number;
214
+ searchQuery: string;
215
+ /** Available skills from Tree API, cross-referenced with lock files */
216
+ skills: AsyncData<SkillInfo[]>;
217
+ /** Null until check completes; then Map<skillName, hasUpdate> */
218
+ updateStatus: Map<string, boolean> | null;
219
+ }
220
+
221
+ export interface StylesScreenState {
222
+ selectedIndex: number;
223
+ searchQuery: string;
224
+ /** Presets + importable styles + what is currently live, in one read. */
225
+ snapshot: AsyncData<StylesSnapshot>;
226
+ /**
227
+ * Pending selection, by source id. Staged rather than applied on each
228
+ * keystroke: composing writes a file and flips the harness's active output
229
+ * style, which is not something a stray Space press should do.
230
+ */
231
+ selected: Set<string>;
232
+ /**
233
+ * The status line's current message, or null for the default line.
234
+ *
235
+ * App state for the same reason `isFilling` is: `Router` swaps the component
236
+ * type on a tab change, so anything held in the screen's own `useState` is
237
+ * destroyed by `9 → 1 → 9`. It also stops being cleared on a wall clock —
238
+ * the message now survives until the next action replaces it, so the last
239
+ * thing you did is still on screen whenever you look.
240
+ */
241
+ status: { text: string; tone: "success" | "error" } | null;
242
+ /**
243
+ * True while a template fill (Claude Code over the whole project) is running.
244
+ *
245
+ * App state, not screen state, because the screen UNMOUNTS when you switch
246
+ * tabs. A fill runs for minutes, so switching away is the normal thing to do
247
+ * — and with the flag held locally the returning user saw no sign one was in
248
+ * flight and could launch a second subprocess on top of the first.
249
+ */
250
+ isFilling: boolean;
251
+ }
252
+
253
+ /**
254
+ * What the Models screen reads in one go.
255
+ *
256
+ * Both halves are needed and neither implies the other: `status` says whether
257
+ * the routing is in force, and `config` is the routing itself — which the
258
+ * screen needs in full to give a hand-edited config a row of its own.
259
+ */
260
+ export interface ModelsSnapshot {
261
+ status: ModelsStatus;
262
+ /** The validated config, or null when there is none (or it failed validation). */
263
+ config: ModelsConfig | null;
264
+ /** Where `.claude/models.json` is, whether or not it exists. */
265
+ path: string;
266
+ }
267
+
268
+ export interface ModelsScreenState {
269
+ selectedIndex: number;
270
+ searchQuery: string;
271
+ /** Routing status + the config behind it, in one read. */
272
+ data: AsyncData<ModelsSnapshot>;
273
+ /**
274
+ * The status line's current message, or null for the default line.
275
+ *
276
+ * App state for the same reason the Styles screen's is: `Router` swaps the
277
+ * component type on a tab change, so anything held in the screen's own
278
+ * `useState` is destroyed by `0 → 1 → 0`. The message survives until the next
279
+ * action replaces it, so the last thing you did is still on screen.
280
+ */
281
+ status: { text: string; tone: "success" | "error" } | null;
282
+ /**
283
+ * True while an apply or a clear is writing.
284
+ *
285
+ * App state, not screen state, because the screen UNMOUNTS on a tab switch.
286
+ * An apply rewrites the profile, re-materializes it and registers a hook —
287
+ * two of those running at once would interleave writes to the same files.
288
+ */
289
+ isApplying: boolean;
290
+ /**
291
+ * Is `multimodel@magus` installed — i.e. is there anything to serve a mate?
292
+ *
293
+ * App state for the reason the two fields above are, plus one of its own. The screen
294
+ * unmounts on a tab switch, so a local flag would be destroyed by `0 → 1 → 0`; and unlike
295
+ * the two file reads the screen does on mount, THIS answer costs a marketplace resolution
296
+ * with network timeouts behind it. Re-asking it on every visit to the tab would pay that
297
+ * cost again for an answer that cannot change while the TUI is open.
298
+ *
299
+ * Starts false, which is also what a failed lookup yields (`areMatesAvailable` fails
300
+ * closed): both render as the screen exactly as it was before mates existed, rather than
301
+ * as three rows put there by an error.
302
+ */
303
+ matesAvailable: boolean;
304
+ /**
305
+ * claudish's live model catalogue, or [] when it could not be read.
306
+ *
307
+ * App state for the reasons above, plus the one that matters most here: reading it spawns
308
+ * a process. Held per session, it is spawned once; held in the component it would be
309
+ * spawned on every visit to the tab.
310
+ *
311
+ * [] is deliberately NOT distinguished from "not yet loaded". Every consumer treats an
312
+ * empty catalogue as "unknown" and says nothing, so the loading frame, a machine without
313
+ * claudish, and a lookup that timed out all draw the same screen — bound ids as written,
314
+ * with no annotation. Advisory only: nothing here ever changes what is routed.
315
+ */
316
+ mateCatalog: CatalogModel[];
317
+ }
318
+
319
+ // ============================================================================
320
+ // App State
321
+ // ============================================================================
322
+
323
+ export interface AppState {
324
+ // Navigation
325
+ currentRoute: Route;
326
+
327
+ // Project context
328
+ projectPath: string;
329
+
330
+ // Global UI state
331
+ modal: ModalState | null;
332
+ progress: ProgressState | null;
333
+ isSearching: boolean;
334
+ // True while a screen has a focused sub-view open that owns Escape (e.g.
335
+ // the Alias tab's flag-detail editor). The global key handler skips its
336
+ // Escape→navigate branch so Escape backs out of the sub-view instead of
337
+ // jumping to the home tab. Mirrors how `modal`/`isSearching` gate keys.
338
+ detailActive: boolean;
339
+
340
+ // Refresh counter - incremented when marketplace sync completes
341
+ // Screens can watch this to know when to refetch data
342
+ dataRefreshVersion: number;
343
+
344
+ // Screen-specific state
345
+ plugins: PluginsScreenState;
346
+ mcp: McpScreenState;
347
+ mcpRegistry: McpRegistryScreenState;
348
+ settings: SettingsScreenState;
349
+ cliTools: CliToolsScreenState;
350
+ profiles: ProfilesScreenState;
351
+ skills: SkillsScreenState;
352
+ styles: StylesScreenState;
353
+ models: ModelsScreenState;
354
+ }
355
+
356
+ // ============================================================================
357
+ // Action Types
358
+ // ============================================================================
359
+
360
+ export type AppAction =
361
+ // Navigation
362
+ | { type: "NAVIGATE"; route: Route }
363
+
364
+ // Plugins screen
365
+ | { type: "PLUGINS_SET_SCOPE"; scope: "project" | "global" }
366
+ | { type: "PLUGINS_TOGGLE_SCOPE" }
367
+ | { type: "PLUGINS_TOGGLE_MARKETPLACE"; name: string }
368
+ | { type: "PLUGINS_TOGGLE_PLUGIN_CONTENTS"; pluginId: string }
369
+ | { type: "PLUGINS_CONTENTS_LOADING"; pluginId: string }
370
+ | {
371
+ type: "PLUGINS_CONTENTS_SUCCESS";
372
+ pluginId: string;
373
+ components: PluginComponent[];
374
+ }
375
+ | { type: "PLUGINS_CONTENTS_ERROR"; pluginId: string; error: string }
376
+ | { type: "PLUGINS_SKILL_DETAIL_LOADING"; key: string }
377
+ | {
378
+ type: "PLUGINS_SKILL_DETAIL_SUCCESS";
379
+ key: string;
380
+ detail: PluginSkillDetail;
381
+ }
382
+ | { type: "PLUGINS_SKILL_DETAIL_ERROR"; key: string; error: string }
383
+ | { type: "PLUGINS_SELECT"; index: number }
384
+ | { type: "PLUGINS_SET_SEARCH"; query: string }
385
+ | { type: "PLUGINS_DATA_LOADING" }
386
+ | {
387
+ type: "PLUGINS_DATA_SUCCESS";
388
+ marketplaces: Marketplace[];
389
+ plugins: PluginInfo[];
390
+ }
391
+ | { type: "PLUGINS_DATA_ERROR"; error: Error }
392
+
393
+ // MCP screen
394
+ | { type: "MCP_SELECT"; index: number }
395
+ | { type: "MCP_DATA_LOADING" }
396
+ | {
397
+ type: "MCP_DATA_SUCCESS";
398
+ servers: McpServer[];
399
+ installedServers: Record<string, boolean>;
400
+ }
401
+ | { type: "MCP_DATA_ERROR"; error: Error }
402
+
403
+ // MCP Registry screen
404
+ | { type: "MCP_REGISTRY_SET_QUERY"; query: string }
405
+ | { type: "MCPREGISTRY_SEARCH"; query: string } // Alias for MCP_REGISTRY_SET_QUERY
406
+ | { type: "MCPREGISTRY_SELECT"; index: number } // Alias for MCP_REGISTRY_SELECT
407
+ | { type: "MCP_REGISTRY_SELECT"; index: number }
408
+ | { type: "MCP_REGISTRY_DATA_LOADING" }
409
+ | { type: "MCP_REGISTRY_DATA_SUCCESS"; servers: McpServer[] }
410
+ | { type: "MCP_REGISTRY_DATA_ERROR"; error: Error }
411
+
412
+ // Settings screen
413
+ | { type: "SETTINGS_SELECT"; index: number }
414
+ | { type: "SETTINGS_DATA_LOADING" }
415
+ | { type: "SETTINGS_DATA_SUCCESS"; values: Map<string, ScopedSettingValues> }
416
+ | { type: "SETTINGS_DATA_ERROR"; error: Error }
417
+
418
+ // CLI tools screen
419
+ | { type: "CLITOOLS_SELECT"; index: number }
420
+ | { type: "CLITOOLS_DATA_LOADING" }
421
+ | { type: "CLITOOLS_DATA_SUCCESS"; tools: CliTool[] }
422
+ | { type: "CLITOOLS_DATA_ERROR"; error: Error }
423
+ | { type: "CLITOOLS_UPDATE_TOOL"; name: string; updates: Partial<CliTool> }
424
+
425
+ // Modals
426
+ | { type: "SHOW_MODAL"; modal: ModalState }
427
+ | { type: "HIDE_MODAL" }
428
+
429
+ // Progress
430
+ | { type: "SHOW_PROGRESS"; state: ProgressState }
431
+ | { type: "UPDATE_PROGRESS"; state: ProgressState }
432
+ | { type: "HIDE_PROGRESS" }
433
+
434
+ // Search state (blocks global key handlers)
435
+ | { type: "SET_SEARCHING"; isSearching: boolean }
436
+
437
+ // Detail sub-view state (gates global Escape→navigate)
438
+ | { type: "SET_DETAIL_ACTIVE"; detailActive: boolean }
439
+
440
+ // Profiles screen
441
+ | { type: "PROFILES_SELECT"; index: number }
442
+ | { type: "PROFILES_DATA_LOADING" }
443
+ | { type: "PROFILES_DATA_SUCCESS"; profiles: ProfileEntry[] }
444
+ | { type: "PROFILES_DATA_ERROR"; error: Error }
445
+
446
+ // Skills screen
447
+ | { type: "SKILLS_SET_SCOPE"; scope: "user" | "project" }
448
+ | { type: "SKILLS_TOGGLE_SCOPE" }
449
+ | { type: "SKILLS_SELECT"; index: number }
450
+ | { type: "SKILLS_SET_SEARCH"; query: string }
451
+ | { type: "SKILLS_DATA_LOADING" }
452
+ | { type: "SKILLS_DATA_SUCCESS"; skills: SkillInfo[] }
453
+ | { type: "SKILLS_DATA_ERROR"; error: Error }
454
+ | { type: "SKILLS_UPDATE_STATUS"; updates: Map<string, boolean> }
455
+ | { type: "SKILLS_UPDATE_ITEM"; name: string; updates: Partial<SkillInfo> }
456
+
457
+ // Styles screen
458
+ | { type: "STYLES_SELECT"; index: number }
459
+ | { type: "STYLES_SET_SEARCH"; query: string }
460
+ // Append/backspace are computed IN the reducer, from current state. A
461
+ // screen-side `searchQuery + char` reads a value captured at render time, so
462
+ // keystrokes arriving faster than React re-renders all overwrite each other
463
+ // and typing "slop" leaves just "p".
464
+ | { type: "STYLES_SEARCH_APPEND"; char: string }
465
+ | { type: "STYLES_SEARCH_BACKSPACE" }
466
+ | { type: "STYLES_DATA_LOADING" }
467
+ | { type: "STYLES_DATA_SUCCESS"; snapshot: StylesSnapshot }
468
+ | { type: "STYLES_DATA_ERROR"; error: Error }
469
+ | { type: "STYLES_TOGGLE"; id: string }
470
+ | {
471
+ type: "STYLES_STATUS_SET";
472
+ status: { text: string; tone: "success" | "error" };
473
+ }
474
+ | { type: "STYLES_STATUS_CLEAR" }
475
+ | { type: "STYLES_FILL_START" }
476
+ | { type: "STYLES_FILL_END" }
477
+ | { type: "STYLES_SET_SELECTION"; ids: string[] }
478
+
479
+ // Models screen
480
+ | { type: "MODELS_SELECT"; index: number }
481
+ | { type: "MODELS_SET_SEARCH"; query: string }
482
+ // Computed IN the reducer, from current state, for the same reason the
483
+ // STYLES_SEARCH_* pair is: a screen-side `searchQuery + char` reads a value
484
+ // captured at render time, so keystrokes arriving faster than React
485
+ // re-renders all overwrite each other.
486
+ | { type: "MODELS_SEARCH_APPEND"; char: string }
487
+ | { type: "MODELS_SEARCH_BACKSPACE" }
488
+ | { type: "MODELS_DATA_LOADING" }
489
+ | { type: "MODELS_DATA_SUCCESS"; snapshot: ModelsSnapshot }
490
+ | { type: "MODELS_DATA_ERROR"; error: Error }
491
+ | {
492
+ type: "MODELS_STATUS_SET";
493
+ status: { text: string; tone: "success" | "error" };
494
+ }
495
+ | { type: "MODELS_STATUS_CLEAR" }
496
+ | { type: "MODELS_APPLY_START" }
497
+ | { type: "MODELS_APPLY_END" }
498
+ // Dispatched once, when the plugin lookup behind the mate slots lands.
499
+ | { type: "MODELS_MATES_AVAILABLE"; available: boolean }
500
+ | { type: "MODELS_CATALOG_LOADED"; catalog: CatalogModel[] }
501
+
502
+ // Data refresh - triggers screens to refetch
503
+ | { type: "DATA_REFRESH_COMPLETE" };