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.
- package/package.json +4 -4
- package/src/__tests__/blackbox-convergence.test.ts +1 -2
- package/src/__tests__/blackbox-parity.test.ts +16 -14
- package/src/__tests__/blackbox-plugin-state.test.ts +13 -15
- package/src/__tests__/cli-apply-seams.test.ts +3 -3
- package/src/__tests__/cli-profile-prompts.test.ts +1165 -0
- package/src/__tests__/content-drift.test.ts +0 -1
- package/src/__tests__/files-missing-scope.test.ts +235 -0
- package/src/__tests__/{enabled-not-installed.test.ts → files-missing.test.ts} +32 -26
- package/src/__tests__/format-update-outcome.test.ts +367 -0
- package/src/__tests__/gitignore-check.test.ts +11 -29
- package/src/__tests__/gitignore-profile.test.ts +248 -0
- package/src/__tests__/installed-version-scope.test.tsx +425 -0
- package/src/__tests__/mate-availability.test.ts +11 -13
- package/src/__tests__/models-cli.test.ts +11 -0
- package/src/__tests__/plugin-contents.test.ts +0 -1
- package/src/__tests__/plugin-detail-scopes.test.tsx +270 -0
- package/src/__tests__/plugin-row-text.test.ts +18 -21
- package/src/__tests__/profile-command.test.ts +2 -2
- package/src/__tests__/profile-migration-offer.test.ts +5 -4
- package/src/__tests__/profile-skills.test.ts +17 -22
- package/src/__tests__/profile-startup-nested.test.ts +329 -0
- package/src/__tests__/profile-startup-old-format.test.ts +390 -0
- package/src/__tests__/profile-state.test.ts +152 -164
- package/src/__tests__/scope-action.test.ts +35 -38
- package/src/__tests__/scope-row-lookup.test.ts +126 -0
- package/src/__tests__/select-restore.test.ts +209 -0
- package/src/__tests__/stale-load-repair.test.ts +1 -2
- package/src/__tests__/update-apply.test.ts +18 -2
- package/src/__tests__/update-null-readback.test.ts +300 -0
- package/src/__tests__/update-plan-parity.test.ts +249 -0
- package/src/__tests__/update-plan.test.ts +66 -67
- package/src/__tests__/version-unknown-state.test.ts +38 -39
- package/src/cli/bootstrap.ts +239 -70
- package/src/cli/doctor.ts +7 -20
- package/src/cli/install.ts +83 -27
- package/src/cli/prompt.ts +7 -3
- package/src/cli/router.ts +6 -4
- package/src/cli/select.ts +168 -0
- package/src/cli/update-view.ts +118 -6
- package/src/cli/update.ts +10 -1
- package/src/services/claude-cli.ts +0 -24
- package/src/services/claude-settings.ts +28 -7
- package/src/services/gitignore-check.ts +37 -49
- package/src/services/gitignore-resolver.ts +10 -1
- package/src/services/gitignore-service.ts +67 -0
- package/src/services/manifest.ts +31 -0
- package/src/services/mate-availability.ts +23 -29
- package/src/services/plugin-manager.ts +247 -105
- package/src/services/profile-absorb.ts +41 -24
- package/src/services/profile-activate.ts +102 -127
- package/src/services/profile-startup.ts +160 -4
- package/src/services/profile-state.ts +7 -68
- package/src/services/tracked-live-files.ts +109 -74
- package/src/services/update-engine.ts +28 -10
- package/src/services/update-plan.ts +78 -94
- package/src/ui/App.tsx +41 -35
- package/src/ui/adapters/pluginsAdapter.ts +1 -1
- package/src/ui/components/primitives/ScopeSquares.tsx +10 -5
- package/src/ui/hooks/useGitignoreModal.ts +63 -17
- package/src/ui/hooks/useProfileStartupModal.ts +94 -9
- package/src/ui/keymap.ts +1 -1
- package/src/ui/renderers/pluginRenderers.tsx +39 -45
- package/src/ui/renderers/pluginRowText.ts +6 -2
- package/src/ui/renderers/pluginStatusText.ts +4 -3
- package/src/ui/screens/PluginsScreen.tsx +30 -52
- package/src/ui/screens/ProfilesScreen.tsx +3 -10
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
284
|
-
? error.message
|
|
285
|
-
: `Failed to apply profile: ${error}`,
|
|
278
|
+
`Failed to apply profile: ${error}`,
|
|
286
279
|
"error",
|
|
287
280
|
);
|
|
288
281
|
}
|
package/src/cli/tui-prompt.tsx
DELETED
|
@@ -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
|
-
}
|