magus-cli 7.5.1 → 7.5.3

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.
@@ -12,19 +12,38 @@ import type { ModalState } from "../state/types.js";
12
12
  export interface BuildModalArgs {
13
13
  violationCount: number;
14
14
  safeCount: number;
15
+ /**
16
+ * Files magus generates from `.claude/profiles.json` that git tracks. The
17
+ * fix untracks these in place; every other destructive fix stays in the tab.
18
+ */
19
+ generated?: readonly string[];
15
20
  onFixSafe: () => void;
16
21
  onOpenTab: () => void;
17
22
  onDismiss: () => void;
18
23
  }
19
24
 
25
+ function entries(n: number): string {
26
+ return `${n} missing .gitignore entr${n === 1 ? "y" : "ies"}`;
27
+ }
28
+
29
+ function fixLabel(safeCount: number, generated: number): string {
30
+ if (generated > 0) {
31
+ const files = `${generated} generated file${generated === 1 ? "" : "s"}`;
32
+ return safeCount > 0
33
+ ? `Stop tracking ${files} and add ${entries(safeCount)}`
34
+ : `Stop tracking ${files}`;
35
+ }
36
+ return safeCount > 0
37
+ ? `Add ${entries(safeCount)}`
38
+ : "(no missing .gitignore entries)";
39
+ }
40
+
20
41
  export function buildGitignoreModal(args: BuildModalArgs): ModalState {
21
42
  const { violationCount, safeCount, onFixSafe, onOpenTab, onDismiss } = args;
43
+ const generated = args.generated ?? [];
22
44
  const options = [
23
45
  {
24
- label:
25
- safeCount > 0
26
- ? `Add ${safeCount} missing .gitignore entr${safeCount === 1 ? "y" : "ies"}`
27
- : "(no missing .gitignore entries)",
46
+ label: fixLabel(safeCount, generated.length),
28
47
  value: "fix-safe",
29
48
  },
30
49
  {
@@ -36,10 +55,20 @@ export function buildGitignoreModal(args: BuildModalArgs): ModalState {
36
55
  value: "dismiss",
37
56
  },
38
57
  ];
58
+ const generatedNote =
59
+ generated.length > 0
60
+ ? `magus generates ${generated.length === 1 ? "this file" : "these files"} from .claude/profiles.json for each developer, so ${
61
+ generated.length === 1 ? "it is" : "they are"
62
+ } expected to be gitignored — but git tracks ${generated.length === 1 ? "it" : "them"}:\n${generated
63
+ .map((p) => ` ${p}`)
64
+ .join(
65
+ "\n",
66
+ )}\n\nThe first choice stops tracking ${generated.length === 1 ? "it" : "them"} (git rm --cached, staged for you to commit) and adds the .gitignore entries.\n\n`
67
+ : "";
39
68
  return {
40
69
  type: "select",
41
70
  title: "Gitignore violations detected",
42
- message: `Found ${violationCount} managed gitignore item(s) that do not match the current repo state.\n\nMissing .gitignore entries can be added now. Items that change git tracking are reviewed and fixed from the Gitignore tab.`,
71
+ message: `${generatedNote}Found ${violationCount} managed gitignore item(s) that do not match the current repo state.\n\nMissing .gitignore entries can be added now. Other items that change git tracking are reviewed and fixed from the Gitignore tab.`,
43
72
  options,
44
73
  onSelect: (value: string) => {
45
74
  if (value === "fix-safe") onFixSafe();
@@ -58,27 +87,44 @@ export function useGitignoreModal() {
58
87
  (args: {
59
88
  violationCount: number;
60
89
  safeCount: number;
61
- onFixSafe: () => Promise<void> | void;
90
+ generated?: readonly string[];
91
+ /** Returns what it did, shown before returning to the app. */
92
+ onFixSafe: () => Promise<string | undefined> | string | undefined;
62
93
  }) => {
63
94
  if (args.violationCount === 0) return;
64
95
  const dismiss = () => dispatch({ type: "HIDE_MODAL" });
65
96
  const modal = buildGitignoreModal({
66
97
  violationCount: args.violationCount,
67
98
  safeCount: args.safeCount,
99
+ generated: args.generated,
68
100
  onFixSafe: () => {
69
101
  dispatch({ type: "HIDE_MODAL" });
70
- void Promise.resolve(args.onFixSafe()).catch((err) => {
71
- dispatch({
72
- type: "SHOW_MODAL",
73
- modal: {
74
- type: "message",
75
- title: "Safe fixes failed",
76
- message: err instanceof Error ? err.message : String(err),
77
- variant: "error",
78
- onDismiss: dismiss,
79
- },
102
+ void Promise.resolve(args.onFixSafe())
103
+ .then((report) => {
104
+ if (!report) return;
105
+ dispatch({
106
+ type: "SHOW_MODAL",
107
+ modal: {
108
+ type: "message",
109
+ title: "Gitignore fixed",
110
+ message: report,
111
+ variant: "success",
112
+ onDismiss: dismiss,
113
+ },
114
+ });
115
+ })
116
+ .catch((err) => {
117
+ dispatch({
118
+ type: "SHOW_MODAL",
119
+ modal: {
120
+ type: "message",
121
+ title: "Safe fixes failed",
122
+ message: err instanceof Error ? err.message : String(err),
123
+ variant: "error",
124
+ onDismiss: dismiss,
125
+ },
126
+ });
80
127
  });
81
- });
82
128
  },
83
129
  onOpenTab: () => {
84
130
  dispatch({ type: "HIDE_MODAL" });
@@ -157,7 +157,11 @@ export function describeFailure(
157
157
  ].join("\n");
158
158
  }
159
159
 
160
- export function useProfileStartupModal(onQuit: () => void) {
160
+ export function useProfileStartupModal(
161
+ onQuit: () => void,
162
+ /** Called once a choice has activated a profile — see App's gitignore re-check. */
163
+ onActivated?: () => void,
164
+ ) {
161
165
  const { state, dispatch } = useApp();
162
166
  const { navigateToScreen } = useNavigation();
163
167
  const projectPath = state.projectPath;
@@ -191,6 +195,7 @@ export function useProfileStartupModal(onQuit: () => void) {
191
195
  onDisplace: (artifact) => moved.push(artifact),
192
196
  })
193
197
  .then((result: ActivationResult) => {
198
+ onActivated?.();
194
199
  const { profileId, displaced } = result;
195
200
  dispatch({
196
201
  type: "SHOW_MODAL",
@@ -270,7 +275,7 @@ export function useProfileStartupModal(onQuit: () => void) {
270
275
  }
271
276
  ask();
272
277
  },
273
- [dispatch, navigateToScreen, projectPath, onQuit],
278
+ [dispatch, navigateToScreen, projectPath, onQuit, onActivated],
274
279
  );
275
280
 
276
281
  return { show };
@@ -44,7 +44,6 @@ import {
44
44
  fetchPluginContents,
45
45
  fetchPluginSkillDetail,
46
46
  } from "../../services/skills-manager.js";
47
- import { LiveFileRefusal } from "../../services/tracked-live-files.js";
48
47
  // The one update implementation, shared with `magus update`. See the header
49
48
  // of update-engine.ts for what drifted while the TUI had its own loop.
50
49
  import {
@@ -983,11 +982,7 @@ export function PluginsScreen() {
983
982
  fetchData();
984
983
  } catch (error) {
985
984
  modal.hideModal();
986
- await modal.message(
987
- "Error",
988
- error instanceof LiveFileRefusal ? error.message : `Failed: ${error}`,
989
- "error",
990
- );
985
+ await modal.message("Error", `Failed: ${error}`, "error");
991
986
  }
992
987
  }
993
988
  };
@@ -1207,11 +1202,7 @@ export function PluginsScreen() {
1207
1202
  fetchData();
1208
1203
  } catch (error) {
1209
1204
  modal.hideModal();
1210
- await modal.message(
1211
- "Error",
1212
- error instanceof LiveFileRefusal ? error.message : `Failed: ${error}`,
1213
- "error",
1214
- );
1205
+ await modal.message("Error", `Failed: ${error}`, "error");
1215
1206
  }
1216
1207
  };
1217
1208
 
@@ -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
  }