magus-cli 7.5.2 → 7.5.4

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 (68) hide show
  1. package/package.json +4 -4
  2. package/src/__tests__/blackbox-convergence.test.ts +1 -2
  3. package/src/__tests__/blackbox-parity.test.ts +16 -14
  4. package/src/__tests__/blackbox-plugin-state.test.ts +13 -15
  5. package/src/__tests__/cli-apply-seams.test.ts +3 -3
  6. package/src/__tests__/cli-profile-prompts.test.ts +1165 -0
  7. package/src/__tests__/content-drift.test.ts +0 -1
  8. package/src/__tests__/files-missing-scope.test.ts +235 -0
  9. package/src/__tests__/{enabled-not-installed.test.ts → files-missing.test.ts} +32 -26
  10. package/src/__tests__/format-update-outcome.test.ts +367 -0
  11. package/src/__tests__/gitignore-check.test.ts +11 -29
  12. package/src/__tests__/gitignore-profile.test.ts +248 -0
  13. package/src/__tests__/installed-version-scope.test.tsx +425 -0
  14. package/src/__tests__/mate-availability.test.ts +11 -13
  15. package/src/__tests__/models-cli.test.ts +11 -0
  16. package/src/__tests__/plugin-contents.test.ts +0 -1
  17. package/src/__tests__/plugin-detail-scopes.test.tsx +270 -0
  18. package/src/__tests__/plugin-row-text.test.ts +18 -21
  19. package/src/__tests__/profile-command.test.ts +2 -2
  20. package/src/__tests__/profile-migration-offer.test.ts +5 -4
  21. package/src/__tests__/profile-skills.test.ts +17 -22
  22. package/src/__tests__/profile-startup-nested.test.ts +329 -0
  23. package/src/__tests__/profile-startup-old-format.test.ts +390 -0
  24. package/src/__tests__/profile-state.test.ts +152 -164
  25. package/src/__tests__/scope-action.test.ts +35 -38
  26. package/src/__tests__/scope-row-lookup.test.ts +126 -0
  27. package/src/__tests__/select-restore.test.ts +209 -0
  28. package/src/__tests__/stale-load-repair.test.ts +1 -2
  29. package/src/__tests__/update-apply.test.ts +18 -2
  30. package/src/__tests__/update-null-readback.test.ts +300 -0
  31. package/src/__tests__/update-plan-parity.test.ts +249 -0
  32. package/src/__tests__/update-plan.test.ts +66 -67
  33. package/src/__tests__/version-unknown-state.test.ts +38 -39
  34. package/src/cli/bootstrap.ts +239 -70
  35. package/src/cli/doctor.ts +7 -20
  36. package/src/cli/install.ts +83 -27
  37. package/src/cli/prompt.ts +7 -3
  38. package/src/cli/router.ts +6 -4
  39. package/src/cli/select.ts +168 -0
  40. package/src/cli/update-view.ts +118 -6
  41. package/src/cli/update.ts +10 -1
  42. package/src/services/claude-cli.ts +0 -24
  43. package/src/services/claude-settings.ts +28 -7
  44. package/src/services/gitignore-check.ts +37 -49
  45. package/src/services/gitignore-resolver.ts +10 -1
  46. package/src/services/gitignore-service.ts +67 -0
  47. package/src/services/manifest.ts +31 -0
  48. package/src/services/mate-availability.ts +23 -29
  49. package/src/services/plugin-manager.ts +247 -105
  50. package/src/services/profile-absorb.ts +41 -24
  51. package/src/services/profile-activate.ts +102 -127
  52. package/src/services/profile-startup.ts +160 -4
  53. package/src/services/profile-state.ts +7 -68
  54. package/src/services/tracked-live-files.ts +109 -74
  55. package/src/services/update-engine.ts +28 -10
  56. package/src/services/update-plan.ts +78 -94
  57. package/src/ui/App.tsx +41 -35
  58. package/src/ui/adapters/pluginsAdapter.ts +1 -1
  59. package/src/ui/components/primitives/ScopeSquares.tsx +10 -5
  60. package/src/ui/hooks/useGitignoreModal.ts +63 -17
  61. package/src/ui/hooks/useProfileStartupModal.ts +94 -9
  62. package/src/ui/keymap.ts +1 -1
  63. package/src/ui/renderers/pluginRenderers.tsx +39 -45
  64. package/src/ui/renderers/pluginRowText.ts +6 -2
  65. package/src/ui/renderers/pluginStatusText.ts +4 -3
  66. package/src/ui/screens/PluginsScreen.tsx +30 -52
  67. package/src/ui/screens/ProfilesScreen.tsx +3 -10
  68. package/src/cli/tui-prompt.tsx +0 -143
@@ -26,7 +26,6 @@ import {
26
26
  renameProfile,
27
27
  } from "../../services/profiles.js";
28
28
  import { writableMap } from "../../services/settings-file.js";
29
- import { LiveFileRefusal } from "../../services/tracked-live-files.js";
30
29
  import {
31
30
  ClipboardUnavailableError,
32
31
  readClipboard,
@@ -178,9 +177,7 @@ export function ProfilesScreen() {
178
177
  modal.hideModal();
179
178
  await modal.message(
180
179
  "Error",
181
- error instanceof LiveFileRefusal
182
- ? error.message
183
- : `Failed to apply profile: ${error}`,
180
+ `Failed to apply profile: ${error}`,
184
181
  "error",
185
182
  );
186
183
  }
@@ -245,9 +242,7 @@ export function ProfilesScreen() {
245
242
  modal.hideModal();
246
243
  await modal.message(
247
244
  "Error",
248
- error instanceof LiveFileRefusal
249
- ? error.message
250
- : `Failed to switch profile: ${error}`,
245
+ `Failed to switch profile: ${error}`,
251
246
  "error",
252
247
  );
253
248
  }
@@ -280,9 +275,7 @@ export function ProfilesScreen() {
280
275
  modal.hideModal();
281
276
  await modal.message(
282
277
  "Error",
283
- error instanceof LiveFileRefusal
284
- ? error.message
285
- : `Failed to apply profile: ${error}`,
278
+ `Failed to apply profile: ${error}`,
286
279
  "error",
287
280
  );
288
281
  }
@@ -1,143 +0,0 @@
1
- /**
2
- * One OpenTUI dialog, for a command that is otherwise not a TUI.
3
- *
4
- * `magus update` prints a report and exits; it is not the interactive app. But
5
- * a question it must ask deserves the same dialog the TUI asks with, so this
6
- * mounts the REAL `SelectModal` — the component tab 5 and tab 0 use — for the
7
- * length of one question, then tears the renderer down and lets the command
8
- * carry on printing. Nothing here re-draws a list, a border or a highlight: the
9
- * component and the theme own all of that, and a second implementation is how
10
- * the two surfaces drift apart.
11
- *
12
- * Every import of `@opentui/*` and `ui/` is DYNAMIC. `main.tsx` splits the TUI
13
- * out precisely so `magus hook …`, which Claude Code runs on every matching
14
- * tool call, never pays to load the renderer — a static import here would undo
15
- * that for every command that imports this module's file, whether or not it
16
- * ever asks a question.
17
- *
18
- * `screenMode: "main-screen"` keeps the dialog in the scrollback beside the
19
- * report. The alternate screen would take the terminal over and then hand back
20
- * a screen with no trace the question was ever asked.
21
- */
22
-
23
- import type { SelectOption } from "../ui/state/types.js";
24
-
25
- export interface SelectDialogInput {
26
- title: string;
27
- message: string;
28
- options: SelectOption[];
29
- /** Row to start on. Defaults to the first. */
30
- defaultIndex?: number;
31
- }
32
-
33
- /**
34
- * Ask the user to pick one option. Resolves to the chosen value, or null when
35
- * they cancel with Esc or Ctrl+C — the caller decides whether that is fatal.
36
- *
37
- * Falls back to `null` when stdin or stdout is not a TTY. A renderer needs both:
38
- * `magus update > file` has a TTY stdin and a pipe for stdout, and mounting
39
- * there writes escape sequences into the file.
40
- */
41
- export async function selectDialog(
42
- input: SelectDialogInput,
43
- ): Promise<string | null> {
44
- if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
45
- return null;
46
- }
47
-
48
- const [{ createCliRenderer }, { createRoot }, React, modals, dimensions] =
49
- await Promise.all([
50
- import("@opentui/core"),
51
- import("@opentui/react"),
52
- import("react"),
53
- import("../ui/components/modals/index.js"),
54
- import("../ui/state/DimensionsContext.js"),
55
- ]);
56
-
57
- const renderer = await createCliRenderer({
58
- screenMode: "main-screen",
59
- // BOTH, so the renderer registers no handler of its own and teardown
60
- // below is the only path. With either left at its default, a Ctrl+C
61
- // destroys the renderer underneath this promise and it never settles.
62
- exitSignals: [],
63
- exitOnCtrlC: false,
64
- });
65
-
66
- const root = createRoot(renderer);
67
-
68
- return await new Promise<string | null>((resolve) => {
69
- // Clamped, not trusted: a caller computing an index from a list it built
70
- // separately could hand over one that is out of range, and a cursor
71
- // pointing past the end makes Enter resolve to null.
72
- let index = Math.min(
73
- Math.max(0, input.defaultIndex ?? 0),
74
- Math.max(0, input.options.length - 1),
75
- );
76
- let done = false;
77
-
78
- const finish = (value: string | null): void => {
79
- if (done) return;
80
- done = true;
81
- renderer.keyInput.off("keypress", onKey);
82
- root.unmount();
83
- renderer.destroy();
84
- resolve(value);
85
- };
86
-
87
- function onKey(key: {
88
- name?: string;
89
- ctrl?: boolean;
90
- sequence?: string;
91
- }): void {
92
- if ((key.ctrl && key.name === "c") || key.name === "escape") {
93
- finish(null);
94
- return;
95
- }
96
- if (key.name === "return" || key.name === "enter") {
97
- finish(input.options[index]?.value ?? null);
98
- return;
99
- }
100
- if (key.name === "up" || key.name === "k") {
101
- index = (index - 1 + input.options.length) % input.options.length;
102
- draw();
103
- return;
104
- }
105
- if (key.name === "down" || key.name === "j") {
106
- index = (index + 1) % input.options.length;
107
- draw();
108
- return;
109
- }
110
- // The numbers stay live: the list is short, and the plain-text prompt
111
- // this replaced numbered it, so the habit already exists.
112
- const digit = Number(key.sequence);
113
- if (
114
- Number.isInteger(digit) &&
115
- digit >= 1 &&
116
- digit <= input.options.length
117
- ) {
118
- index = digit - 1;
119
- finish(input.options[index]?.value ?? null);
120
- }
121
- }
122
-
123
- const draw = (): void => {
124
- root.render(
125
- React.createElement(
126
- dimensions.DimensionsProvider,
127
- null,
128
- React.createElement(modals.SelectModal, {
129
- title: input.title,
130
- message: input.message,
131
- options: input.options,
132
- defaultIndex: index,
133
- onSelect: finish,
134
- onCancel: () => finish(null),
135
- }),
136
- ),
137
- );
138
- };
139
-
140
- renderer.keyInput.on("keypress", onKey);
141
- draw();
142
- });
143
- }