magus-cli 7.3.1 → 7.4.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 (123) hide show
  1. package/package.json +4 -4
  2. package/src/__tests__/blind/c1-npm-package.test.ts +206 -0
  3. package/src/__tests__/blind/c10-colour-roles.test.ts +265 -0
  4. package/src/__tests__/blind/c11-cli-parse-refusal.test.ts +454 -0
  5. package/src/__tests__/blind/c12-skill-frontmatter.test.ts +323 -0
  6. package/src/__tests__/blind/c13-wrong-shape.test.ts +603 -0
  7. package/src/__tests__/blind/c14-cli-row-label.test.ts +259 -0
  8. package/src/__tests__/blind/c15-probe-ownership.test.ts +533 -0
  9. package/src/__tests__/blind/c16-nested-wrong-shape.test.ts +297 -0
  10. package/src/__tests__/blind/c17-enter-label.test.ts +625 -0
  11. package/src/__tests__/blind/c18-hook-shape.test.ts +354 -0
  12. package/src/__tests__/blind/c2-settings-parse.test.ts +350 -0
  13. package/src/__tests__/blind/c4-key-dispatch.test.ts +397 -0
  14. package/src/__tests__/blind/c5-keymap.test.ts +461 -0
  15. package/src/__tests__/blind/c6-request-gate.test.ts +133 -0
  16. package/src/__tests__/blind/c7-tab-tiers.test.ts +320 -0
  17. package/src/__tests__/blind/c8-text-width.test.ts +137 -0
  18. package/src/__tests__/blind/c9-cli-tool-version.test.ts +239 -0
  19. package/src/__tests__/blind/child-run.ts +114 -0
  20. package/src/__tests__/cli-tools-adapter.test.ts +5 -2
  21. package/src/__tests__/component-badge.test.ts +20 -36
  22. package/src/__tests__/footer-hints.test.ts +1 -1
  23. package/src/__tests__/manifest.test.ts +25 -17
  24. package/src/__tests__/marketplace-badge.test.ts +4 -4
  25. package/src/__tests__/models-screen-state.test.ts +36 -13
  26. package/src/__tests__/scope-squares.test.tsx +28 -114
  27. package/src/__tests__/shutdown.test.ts +578 -0
  28. package/src/__tests__/styles-manager.test.ts +4 -4
  29. package/src/__tests__/styles-screen-state.test.ts +26 -12
  30. package/src/__tests__/tabbar-layout.test.ts +44 -32
  31. package/src/__tests__/theme-adaptive-colors.test.ts +6 -59
  32. package/src/__tests__/tool-probe.test.ts +47 -22
  33. package/src/__tests__/uppercase-keybindings.test.ts +25 -12
  34. package/src/cli/ansi.ts +2 -2
  35. package/src/cli/bootstrap.ts +33 -2
  36. package/src/cli/doctor.ts +105 -14
  37. package/src/cli/install.ts +8 -2
  38. package/src/cli/profile.ts +18 -1
  39. package/src/cli/router.ts +31 -18
  40. package/src/cli/update-view.ts +1 -1
  41. package/src/data/cli-tools.ts +9 -1
  42. package/src/services/claude-settings.ts +151 -73
  43. package/src/services/cli-tool-commands.ts +15 -5
  44. package/src/services/cli-tool-version.ts +101 -0
  45. package/src/services/hook-registration.ts +23 -6
  46. package/src/services/manifest.ts +117 -52
  47. package/src/services/plugin-manager.ts +19 -5
  48. package/src/services/profile-adopt.ts +7 -2
  49. package/src/services/profile-startup.ts +87 -7
  50. package/src/services/profiles.ts +36 -23
  51. package/src/services/settings-file.ts +158 -4
  52. package/src/services/settings-manager.ts +19 -5
  53. package/src/services/skills-manager.ts +46 -0
  54. package/src/services/tool-probe.ts +272 -45
  55. package/src/services/toolchain.ts +39 -0
  56. package/src/services/update-engine.ts +5 -0
  57. package/src/services/update-plan.ts +9 -0
  58. package/src/tui.tsx +48 -21
  59. package/src/ui/App.tsx +58 -197
  60. package/src/ui/adapters/cliToolsAdapter.ts +141 -12
  61. package/src/ui/adapters/pluginsAdapter.ts +10 -7
  62. package/src/ui/adapters/settingsAdapter.ts +0 -21
  63. package/src/ui/adapters/skillsAdapter.ts +27 -8
  64. package/src/ui/adapters/stylesAdapter.ts +15 -11
  65. package/src/ui/components/CategoryHeader.tsx +7 -1
  66. package/src/ui/components/EmptyFilterState.tsx +2 -2
  67. package/src/ui/components/FlagDetailEditor.tsx +0 -0
  68. package/src/ui/components/ScreenErrorBoundary.tsx +105 -0
  69. package/src/ui/components/TabBar.tsx +93 -21
  70. package/src/ui/components/layout/FooterHints.tsx +6 -27
  71. package/src/ui/components/layout/ScreenLayout.tsx +68 -42
  72. package/src/ui/components/layout/index.ts +1 -5
  73. package/src/ui/components/modals/ConfirmModal.tsx +2 -19
  74. package/src/ui/components/modals/HelpOverlay.tsx +186 -0
  75. package/src/ui/components/modals/MessageModal.tsx +3 -15
  76. package/src/ui/components/modals/ModalContainer.tsx +99 -60
  77. package/src/ui/components/primitives/ErrorNotice.tsx +38 -0
  78. package/src/ui/components/primitives/ListCategoryRow.tsx +18 -7
  79. package/src/ui/components/primitives/MetaText.tsx +1 -1
  80. package/src/ui/components/primitives/ScopeDetail.tsx +31 -26
  81. package/src/ui/components/primitives/ScopeSquares.tsx +23 -28
  82. package/src/ui/components/primitives/SelectableRow.tsx +24 -1
  83. package/src/ui/components/primitives/index.ts +4 -3
  84. package/src/ui/hooks/useProfileStartupModal.ts +15 -3
  85. package/src/ui/input/KeyRouter.tsx +180 -0
  86. package/src/ui/input/key-dispatch.ts +149 -0
  87. package/src/ui/keymap.ts +898 -0
  88. package/src/ui/renderers/cliToolRenderers.tsx +128 -66
  89. package/src/ui/renderers/gitignoreRenderers.tsx +41 -43
  90. package/src/ui/renderers/markdownRenderer.tsx +2 -2
  91. package/src/ui/renderers/mcpRenderers.tsx +28 -14
  92. package/src/ui/renderers/modelRenderers.tsx +12 -13
  93. package/src/ui/renderers/modelVisuals.tsx +0 -2
  94. package/src/ui/renderers/pluginRenderers.tsx +47 -54
  95. package/src/ui/renderers/profileRenderers.tsx +32 -37
  96. package/src/ui/renderers/settingsRenderers.tsx +84 -55
  97. package/src/ui/renderers/skillRenderers.tsx +237 -190
  98. package/src/ui/renderers/styleRenderers.tsx +13 -2
  99. package/src/ui/runtime/shutdown.ts +268 -0
  100. package/src/ui/screens/AliasScreen.tsx +118 -100
  101. package/src/ui/screens/CliToolsScreen.tsx +98 -87
  102. package/src/ui/screens/EnvVarsScreen.tsx +44 -42
  103. package/src/ui/screens/GitignoreScreen.tsx +50 -55
  104. package/src/ui/screens/McpRegistryScreen.tsx +52 -86
  105. package/src/ui/screens/McpScreen.tsx +47 -33
  106. package/src/ui/screens/ModelsScreen.tsx +59 -104
  107. package/src/ui/screens/PluginsScreen.tsx +123 -127
  108. package/src/ui/screens/ProfilesScreen.tsx +49 -46
  109. package/src/ui/screens/SkillsScreen.tsx +184 -204
  110. package/src/ui/screens/StylesScreen.tsx +104 -157
  111. package/src/ui/state/AppContext.tsx +6 -0
  112. package/src/ui/state/DimensionsContext.tsx +1 -10
  113. package/src/ui/state/reducer.ts +5 -8
  114. package/src/ui/state/types.ts +11 -9
  115. package/src/ui/text-width.ts +341 -0
  116. package/src/ui/theme-mode.ts +34 -71
  117. package/src/ui/theme.ts +50 -28
  118. package/src/utils/request-gate.ts +37 -0
  119. package/src/ui/components/ScopeIndicator.tsx +0 -63
  120. package/src/ui/components/SearchInput.tsx +0 -57
  121. package/src/ui/hooks/index.ts +0 -2
  122. package/src/ui/hooks/useKeyboard.ts +0 -24
  123. package/src/ui/hooks/useKeyboardHandler.ts +0 -63
@@ -0,0 +1,149 @@
1
+ /**
2
+ * One keypress, one handler.
3
+ *
4
+ * OpenTUI's `useKeyboard` is a broadcast: every subscriber receives every key,
5
+ * with no way for one to stop the next. magus used to have a dozen subscribers
6
+ * — the global keys, the panel scroll keys, each screen, each modal — and each
7
+ * one guessed from render-time flags (`modal`, `isSearching`, `detailActive`)
8
+ * whether the key was meant for it. The guesses disagreed: Esc that cleared a
9
+ * filter also ran the global Esc and quit, Enter in a confirm dialog also acted
10
+ * on the row underneath, and `j`/`k` moved the cursor before a search box could
11
+ * type them.
12
+ *
13
+ * This module replaces the guessing with an order. Every key is offered to the
14
+ * layers top first, and the first layer that consumes it is the only one that
15
+ * acts:
16
+ *
17
+ * system → modal → input → screen → scroll → global
18
+ *
19
+ * - system: Ctrl+C. Taken in `src/tui.tsx` on the renderer itself, before the
20
+ * React tree sees any key, so it quits even under a modal or a crashed screen.
21
+ * - modal: an open dialog (or the help overlay) takes every key.
22
+ * - input: a focused search box takes what it types — see `inputLayerConsumes`.
23
+ * - screen: the current screen's bindings, from `ui/keymap.ts`.
24
+ * - scroll: PgUp / PgDn / Ctrl+U / Ctrl+D scroll the detail panel.
25
+ * - global: tab digits, Tab, `q`, `?`, Esc.
26
+ *
27
+ * Pure: no React, no OpenTUI. `KeyRouter.tsx` feeds it the live layers.
28
+ */
29
+
30
+ /**
31
+ * A keypress, named the way OpenTUI names it: "a", "1", "?", "space",
32
+ * "return", "escape", "backspace", "up", "down", "pageup", "pagedown", "tab".
33
+ * A shifted letter keeps its lowercase name with `shift` set (Shift+U is
34
+ * `{ name: "u", shift: true, sequence: "U" }`); Ctrl+U is `{ name: "u", ctrl: true }`.
35
+ */
36
+ export interface KeyPress {
37
+ name: string;
38
+ sequence: string;
39
+ ctrl: boolean;
40
+ meta: boolean;
41
+ shift: boolean;
42
+ }
43
+
44
+ export type KeyLayerId =
45
+ | "system"
46
+ | "modal"
47
+ | "input"
48
+ | "screen"
49
+ | "scroll"
50
+ | "global";
51
+
52
+ /** Top first. A layer only sees a key every layer above it declined. */
53
+ export const LAYER_ORDER: readonly KeyLayerId[] = [
54
+ "system",
55
+ "modal",
56
+ "input",
57
+ "screen",
58
+ "scroll",
59
+ "global",
60
+ ];
61
+
62
+ export interface KeyLayer {
63
+ id: KeyLayerId;
64
+ /** An inactive layer is skipped without its handler being called. */
65
+ active: boolean;
66
+ /** True when the layer consumed the key; nothing below it will see it. */
67
+ handle(key: KeyPress): boolean;
68
+ }
69
+
70
+ /**
71
+ * Offer `key` to the active layers in LAYER_ORDER — whatever order the array is
72
+ * in — and stop at the first that consumes it. Returns that layer's id, or null
73
+ * when no layer wanted the key. Layers below the consumer are never called.
74
+ *
75
+ * Several layers may share an id (two components can each register a screen
76
+ * layer); within one id they are asked in array order.
77
+ */
78
+ export function dispatchKey(
79
+ layers: readonly KeyLayer[],
80
+ key: KeyPress,
81
+ ): KeyLayerId | null {
82
+ for (const id of LAYER_ORDER) {
83
+ for (const layer of layers) {
84
+ if (layer.id !== id || !layer.active) continue;
85
+ if (layer.handle(key)) return id;
86
+ }
87
+ }
88
+ return null;
89
+ }
90
+
91
+ /** PgUp, PgDn, Ctrl+U, Ctrl+D — the keys that scroll the detail panel. */
92
+ export function isScrollKey(key: KeyPress): boolean {
93
+ if (key.meta) return false;
94
+ if (key.name === "pageup" || key.name === "pagedown") return true;
95
+ return key.ctrl && (key.name === "u" || key.name === "d");
96
+ }
97
+
98
+ /** True for a single printable character: no control bytes, no escape sequences. */
99
+ function isPrintable(text: string): boolean {
100
+ const chars = [...text];
101
+ if (chars.length !== 1) return false;
102
+ const code = chars[0]?.codePointAt(0) ?? 0;
103
+ return code >= 0x20 && code !== 0x7f;
104
+ }
105
+
106
+ /**
107
+ * The character a keypress types, or null when it types nothing.
108
+ *
109
+ * `sequence` first: it is the literal text for a plain key, and under the kitty
110
+ * keyboard protocol OpenTUI puts the produced text there too. `name` is the
111
+ * fallback for a printable key whose sequence is an escape (kitty's Space).
112
+ */
113
+ export function typedCharacter(key: KeyPress): string | null {
114
+ if (key.ctrl || key.meta) return null;
115
+ if (isPrintable(key.sequence)) return key.sequence;
116
+ if (key.name === "space") return " ";
117
+ if (isPrintable(key.name)) {
118
+ return key.shift && /^[a-z]$/.test(key.name)
119
+ ? key.name.toUpperCase()
120
+ : key.name;
121
+ }
122
+ return null;
123
+ }
124
+
125
+ /** The editing keys a focused box owns besides the characters it types. */
126
+ const INPUT_EDIT_KEYS = new Set([
127
+ "backspace",
128
+ "delete",
129
+ "return",
130
+ "enter",
131
+ "escape",
132
+ ]);
133
+
134
+ /**
135
+ * Does a focused search/edit box take this key?
136
+ *
137
+ * Yes: any printable character — letters, digits, punctuation including `?`,
138
+ * space — plus Backspace, Enter and Esc. They are typed or edit the box; they
139
+ * do not switch tab, move the selection, quit or open help.
140
+ *
141
+ * No: Up/Down (the screen moves the selection), Tab (the global layer cycles
142
+ * tabs), the scroll keys (the panel keeps scrolling while you type), Ctrl+C,
143
+ * and any other Ctrl or Meta chord.
144
+ */
145
+ export function inputLayerConsumes(key: KeyPress): boolean {
146
+ if (key.ctrl || key.meta) return false;
147
+ if (INPUT_EDIT_KEYS.has(key.name)) return true;
148
+ return typedCharacter(key) !== null;
149
+ }