magus-cli 7.6.0 → 7.7.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.
- package/package.json +4 -4
- package/src/__tests__/format-update-outcome.test.ts +9 -8
- package/src/__tests__/gitignore-profile.test.ts +9 -5
- package/src/__tests__/notices-rules.test.ts +476 -0
- package/src/__tests__/notices.test.tsx +604 -0
- package/src/__tests__/update-apply.test.ts +3 -3
- package/src/__tests__/useStaleLoadModal.test.ts +27 -12
- package/src/services/gitignore-service.ts +0 -19
- package/src/services/profile-absorb.ts +9 -0
- package/src/services/profile-activate.ts +2 -1
- package/src/services/profile-startup.ts +45 -17
- package/src/services/profile-state.ts +9 -0
- package/src/ui/App.tsx +216 -97
- package/src/ui/adapters/pluginsAdapter.ts +28 -9
- package/src/ui/components/modals/MessageModal.tsx +1 -2
- package/src/ui/hooks/useGitignoreModal.ts +45 -23
- package/src/ui/hooks/useProfileStartupModal.ts +112 -84
- package/src/ui/hooks/useStaleLoadModal.ts +38 -28
- package/src/ui/screens/AliasScreen.tsx +7 -6
- package/src/ui/screens/CliToolsScreen.tsx +19 -16
- package/src/ui/screens/EnvVarsScreen.tsx +4 -1
- package/src/ui/screens/GitignoreScreen.tsx +20 -27
- package/src/ui/screens/McpRegistryScreen.tsx +9 -6
- package/src/ui/screens/McpScreen.tsx +16 -12
- package/src/ui/screens/PluginsScreen.tsx +35 -56
- package/src/ui/screens/ProfilesScreen.tsx +27 -51
- package/src/ui/state/AppContext.tsx +46 -3
- package/src/ui/state/reducer.ts +100 -1
- package/src/ui/state/types.ts +53 -2
|
@@ -93,25 +93,6 @@ export async function applyStartupFixes(
|
|
|
93
93
|
return result;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
/** What {@link applyStartupFixes} did, for the message the TUI shows. */
|
|
97
|
-
export function describeStartupFix(result: StartupFixResult): string {
|
|
98
|
-
const lines: string[] = [];
|
|
99
|
-
if (result.untracked.length > 0) {
|
|
100
|
-
lines.push(
|
|
101
|
-
"Stopped tracking (the removal is staged — commit it):",
|
|
102
|
-
...result.untracked.map((p) => ` ${p}`),
|
|
103
|
-
' git commit -m "Stop tracking generated Claude Code config"',
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
if (result.appended.length > 0) {
|
|
107
|
-
lines.push("Added to .gitignore:", ...result.appended.map((p) => ` ${p}`));
|
|
108
|
-
}
|
|
109
|
-
for (const f of result.failed) {
|
|
110
|
-
lines.push(`Could not untrack ${f.path}: ${f.reason}`);
|
|
111
|
-
}
|
|
112
|
-
return lines.length > 0 ? lines.join("\n") : "Nothing needed changing.";
|
|
113
|
-
}
|
|
114
|
-
|
|
115
96
|
export async function applyAllSafeFixes(
|
|
116
97
|
cwd: string,
|
|
117
98
|
violations: Violation[],
|
|
@@ -59,6 +59,7 @@ import { readManifest, writeManifest } from "./manifest.js";
|
|
|
59
59
|
import { validateModelsConfig } from "./models-core.js";
|
|
60
60
|
import {
|
|
61
61
|
type ActivateOptions,
|
|
62
|
+
type ActivationResult,
|
|
62
63
|
activateProfile,
|
|
63
64
|
activationNotes,
|
|
64
65
|
releasedCommittedSkillNote,
|
|
@@ -588,6 +589,12 @@ export interface ReconcileResult {
|
|
|
588
589
|
conflicts: string[];
|
|
589
590
|
/** Everything worth printing, in order. */
|
|
590
591
|
lines: string[];
|
|
592
|
+
/**
|
|
593
|
+
* The re-render, when profiles.json changed and the live config was
|
|
594
|
+
* generated again. `lines` already carries its CLI wording; this is for a
|
|
595
|
+
* surface that words it differently (the TUI's notices).
|
|
596
|
+
*/
|
|
597
|
+
activation: ActivationResult | null;
|
|
591
598
|
}
|
|
592
599
|
|
|
593
600
|
function emptyResult(profile: string | null): ReconcileResult {
|
|
@@ -600,6 +607,7 @@ function emptyResult(profile: string | null): ReconcileResult {
|
|
|
600
607
|
unreadable: [],
|
|
601
608
|
conflicts: [],
|
|
602
609
|
lines: [],
|
|
610
|
+
activation: null,
|
|
603
611
|
};
|
|
604
612
|
}
|
|
605
613
|
|
|
@@ -1529,6 +1537,7 @@ export async function reconcileActiveProfile(
|
|
|
1529
1537
|
// command that ran it reports that once, itself, and repeating it here
|
|
1530
1538
|
// printed the same warning three times in one `magus update`.
|
|
1531
1539
|
notes.push(...activationNotes(activation, { tracked: false }));
|
|
1540
|
+
result.activation = activation;
|
|
1532
1541
|
} catch (error) {
|
|
1533
1542
|
result.problems.push(
|
|
1534
1543
|
error instanceof Error ? error.message : String(error),
|
|
@@ -209,7 +209,8 @@ export function releasedCommittedSkillNote(folder: string): string {
|
|
|
209
209
|
|
|
210
210
|
/**
|
|
211
211
|
* Lines describing what activation did beyond the switch itself, phrased for
|
|
212
|
-
*
|
|
212
|
+
* the terminal. The TUI words its own, shorter set (`activationNotices` in
|
|
213
|
+
* ui/hooks/useProfileStartupModal.ts).
|
|
213
214
|
*
|
|
214
215
|
* `tracked: false` leaves the tracked-path notices out, for the CLI, which
|
|
215
216
|
* prints them itself as a warning with the fix beneath it
|
|
@@ -69,7 +69,11 @@ import {
|
|
|
69
69
|
findEnclosingManifest,
|
|
70
70
|
readManifest,
|
|
71
71
|
} from "./manifest.js";
|
|
72
|
-
import
|
|
72
|
+
import {
|
|
73
|
+
type ActivationResult,
|
|
74
|
+
type ProfileChoice,
|
|
75
|
+
activationNotes,
|
|
76
|
+
} from "./profile-activate.js";
|
|
73
77
|
import { freeProfileId } from "./profile-adopt.js";
|
|
74
78
|
import {
|
|
75
79
|
activeProfile,
|
|
@@ -310,7 +314,10 @@ export async function oldFormatNeed(
|
|
|
310
314
|
/** What the TUI shows at startup, beyond the question (if any). */
|
|
311
315
|
export interface ProfileStartup {
|
|
312
316
|
need: ProfileStartupNeed;
|
|
313
|
-
/**
|
|
317
|
+
/**
|
|
318
|
+
* One line per thing the gate did: absorbed edits, regeneration, repairs.
|
|
319
|
+
* Not what an activation reported — see `activations`.
|
|
320
|
+
*/
|
|
314
321
|
notices: string[];
|
|
315
322
|
/**
|
|
316
323
|
* One line per thing the gate REFUSED — the lines the CLI stops on. Kept
|
|
@@ -319,6 +326,14 @@ export interface ProfileStartup {
|
|
|
319
326
|
* still names its profile, so only this says the update did not happen.
|
|
320
327
|
*/
|
|
321
328
|
problems: string[];
|
|
329
|
+
/**
|
|
330
|
+
* The activations the gate ran (a re-render, a format update). Handed over
|
|
331
|
+
* as results rather than lines because the CLI's lines are the wrong words
|
|
332
|
+
* for the TUI: they name plugin.json and backups, and they say "will not
|
|
333
|
+
* load" in a line nothing marks as a warning. The TUI words them itself, in
|
|
334
|
+
* user terms and with a tone (`activationWarnings` in the startup hook).
|
|
335
|
+
*/
|
|
336
|
+
activations: ActivationResult[];
|
|
322
337
|
}
|
|
323
338
|
|
|
324
339
|
/**
|
|
@@ -338,14 +353,24 @@ export async function prepareProfileStartup(
|
|
|
338
353
|
): Promise<ProfileStartup> {
|
|
339
354
|
if (!opts.convert) {
|
|
340
355
|
const format = await oldFormatNeed(projectPath);
|
|
341
|
-
if (format !== null)
|
|
356
|
+
if (format !== null) {
|
|
357
|
+
return { need: format, notices: [], problems: [], activations: [] };
|
|
358
|
+
}
|
|
342
359
|
}
|
|
343
360
|
const notices: string[] = [];
|
|
344
361
|
const problems: string[] = [];
|
|
362
|
+
const activations: ActivationResult[] = [];
|
|
345
363
|
try {
|
|
346
364
|
const prep = await prepareProfileState(projectPath);
|
|
347
|
-
|
|
365
|
+
// The gate's lines, less the ones its activations contributed: those
|
|
366
|
+
// reach the TUI as `activations`, and passing both would say the same
|
|
367
|
+
// thing twice — once in the CLI's words, as if it were a success.
|
|
368
|
+
const fromActivations = new Set(
|
|
369
|
+
prep.activations.flatMap((a) => activationNotes(a, { tracked: false })),
|
|
370
|
+
);
|
|
371
|
+
notices.push(...prep.lines.filter((line) => !fromActivations.has(line)));
|
|
348
372
|
problems.push(...prep.problems);
|
|
373
|
+
activations.push(...prep.activations);
|
|
349
374
|
} catch (error) {
|
|
350
375
|
if (!(error instanceof SettingsParseError)) {
|
|
351
376
|
problems.push(error instanceof Error ? error.message : String(error));
|
|
@@ -355,13 +380,17 @@ export async function prepareProfileStartup(
|
|
|
355
380
|
need: await detectProfileStartupNeed(projectPath),
|
|
356
381
|
notices,
|
|
357
382
|
problems,
|
|
383
|
+
activations,
|
|
358
384
|
};
|
|
359
385
|
}
|
|
360
386
|
|
|
361
|
-
/**
|
|
387
|
+
/**
|
|
388
|
+
* What the TUI shows once "Update it now" has run: another question, a notice
|
|
389
|
+
* line, or an error modal.
|
|
390
|
+
*/
|
|
362
391
|
export type FormatUpdateOutcome =
|
|
363
392
|
| { kind: "ask"; need: ProfileStartupNeed }
|
|
364
|
-
| { kind: "updated";
|
|
393
|
+
| { kind: "updated"; notice: string }
|
|
365
394
|
| { kind: "failed"; title: string; message: string };
|
|
366
395
|
|
|
367
396
|
/**
|
|
@@ -381,14 +410,12 @@ export function formatUpdateOutcome(
|
|
|
381
410
|
};
|
|
382
411
|
}
|
|
383
412
|
if (!needsNothing(result.need)) return { kind: "ask", need: result.need };
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
: "This project now uses the current profile format.",
|
|
391
|
-
};
|
|
413
|
+
// One line, not the gate's notices: those are the conversion's working log
|
|
414
|
+
// (edits carried over, the old format's backup), and the result the user
|
|
415
|
+
// cares about is only that the update is done. What the conversion's
|
|
416
|
+
// activation warns about is NOT dropped: the caller words it from
|
|
417
|
+
// `result.activations`, as it does for any activation.
|
|
418
|
+
return { kind: "updated", notice: "Profile format updated" };
|
|
392
419
|
}
|
|
393
420
|
|
|
394
421
|
// ─── What to offer, as data both surfaces render ─────────────────────────────
|
|
@@ -426,9 +453,10 @@ export const UPDATE_FORMAT_VALUE = "update-format";
|
|
|
426
453
|
* Neither mentions the backup, deliberately. A sentence promising "nothing is
|
|
427
454
|
* deleted — the old files are moved to a backup" raises a question the reader
|
|
428
455
|
* did not have (which files? what was at risk?) and implies data loss, when what
|
|
429
|
-
* changes is settings.
|
|
430
|
-
*
|
|
431
|
-
*
|
|
456
|
+
* changes is settings. The TUI does not name the backup afterwards either: the
|
|
457
|
+
* backup is magus's own storage, and the user acts on nothing there. Only a
|
|
458
|
+
* failed activation lists what was moved, because then it is something to act
|
|
459
|
+
* on (see `describeFailure` in ui/hooks/useProfileStartupModal.ts).
|
|
432
460
|
*
|
|
433
461
|
* No file paths in these strings, deliberately. The TUI modal soft-wraps its
|
|
434
462
|
* message at whatever width the terminal gives it, and OpenTUI treats `.` as a
|
|
@@ -299,6 +299,12 @@ export interface ProfileStatePrep {
|
|
|
299
299
|
problems: string[];
|
|
300
300
|
/** The files behind `problems` that could not be parsed. */
|
|
301
301
|
unreadable: SettingsParseError[];
|
|
302
|
+
/**
|
|
303
|
+
* Every activation the gate ran, in order: a re-render after profiles.json
|
|
304
|
+
* changed, an older layout converted. `lines` already carries them in the
|
|
305
|
+
* CLI's words; the TUI words them itself from these.
|
|
306
|
+
*/
|
|
307
|
+
activations: ActivationResult[];
|
|
302
308
|
}
|
|
303
309
|
|
|
304
310
|
/**
|
|
@@ -319,11 +325,13 @@ export async function prepareProfileState(
|
|
|
319
325
|
lines: [],
|
|
320
326
|
problems: [],
|
|
321
327
|
unreadable: [],
|
|
328
|
+
activations: [],
|
|
322
329
|
};
|
|
323
330
|
const take = (r: ReconcileResult) => {
|
|
324
331
|
out.lines.push(...r.lines.filter((l) => !r.problems.includes(l)));
|
|
325
332
|
out.problems.push(...r.problems);
|
|
326
333
|
out.unreadable.push(...r.unreadable);
|
|
334
|
+
if (r.activation) out.activations.push(r.activation);
|
|
327
335
|
};
|
|
328
336
|
|
|
329
337
|
if (check.state === "active" && check.restored) {
|
|
@@ -358,6 +366,7 @@ export async function prepareProfileState(
|
|
|
358
366
|
}
|
|
359
367
|
// Tracked files are the command's to report, once (see profile-absorb).
|
|
360
368
|
out.lines.push(...activationNotes(result, { tracked: false }));
|
|
369
|
+
out.activations.push(result);
|
|
361
370
|
}
|
|
362
371
|
out.check = { state: "active", profile, restored: false };
|
|
363
372
|
}
|