magus-cli 7.2.0 → 7.3.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__/blackbox-plugin-state.test.ts +3 -0
- package/src/__tests__/doctor.test.ts +31 -0
- package/src/__tests__/plugin-resolution.test.ts +431 -0
- package/src/__tests__/plugin-row-text.test.ts +66 -0
- package/src/__tests__/stale-load-repair.test.ts +1076 -0
- package/src/__tests__/update-apply.test.ts +146 -1
- package/src/__tests__/update-plan.test.ts +338 -0
- package/src/__tests__/useStaleLoadModal.test.ts +177 -0
- package/src/cli/doctor.ts +128 -2
- package/src/cli/update-view.ts +31 -1
- package/src/services/active-closure.ts +23 -0
- package/src/services/claude-settings.ts +645 -34
- package/src/services/plugin-manager.ts +61 -0
- package/src/services/plugin-resolution.ts +280 -0
- package/src/services/update-engine.ts +104 -2
- package/src/services/update-plan.ts +188 -1
- package/src/ui/App.tsx +63 -0
- package/src/ui/hooks/useStaleLoadModal.ts +157 -0
- package/src/ui/renderers/pluginRenderers.tsx +29 -0
- package/src/ui/renderers/pluginStatusText.ts +14 -0
- package/src/ui/screens/PluginsScreen.tsx +46 -13
|
@@ -996,9 +996,16 @@ export function PluginsScreen() {
|
|
|
996
996
|
const handleUpdate = async () => {
|
|
997
997
|
const item = selectableItems[pluginsState.selectedIndex];
|
|
998
998
|
if (!item || item.kind !== "plugin") return;
|
|
999
|
-
// The row said "update"
|
|
999
|
+
// The row said "update", "stale — reinstall" or "BROKEN"; nothing else is
|
|
1000
|
+
// work for this key. `install` stays out — that is the scope keys' and the
|
|
1001
|
+
// profile's business, and the detail panel never offers U for it.
|
|
1000
1002
|
const status = statuses.get(item.plugin.id);
|
|
1001
|
-
if (
|
|
1003
|
+
if (
|
|
1004
|
+
status?.action !== "update" &&
|
|
1005
|
+
status?.action !== "repair" &&
|
|
1006
|
+
status?.action !== "stale-load"
|
|
1007
|
+
)
|
|
1008
|
+
return;
|
|
1002
1009
|
|
|
1003
1010
|
const plugin = item.plugin;
|
|
1004
1011
|
const scope: PluginScope =
|
|
@@ -1010,9 +1017,13 @@ export function PluginsScreen() {
|
|
|
1010
1017
|
const repairing = status.action === "repair";
|
|
1011
1018
|
|
|
1012
1019
|
modal.loading(
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1020
|
+
// A broken plugin runs NO claude command at all, so naming one here would
|
|
1021
|
+
// misdescribe the work: the repair rewrites installed_plugins.json rows.
|
|
1022
|
+
status.action === "stale-load"
|
|
1023
|
+
? `Repairing ${plugin.name}…\nmoving this repository's install records forward to v${status.target ?? "?"}`
|
|
1024
|
+
: repairing
|
|
1025
|
+
? `Repairing ${plugin.name}…\nclaude plugin uninstall ${plugin.id} --scope ${scope} && claude plugin install ${plugin.id} --scope ${scope}`
|
|
1026
|
+
: `Updating ${plugin.name}…\nclaude plugin install ${plugin.id} --scope ${scope}`,
|
|
1016
1027
|
);
|
|
1017
1028
|
const reasons = noMoveReasons();
|
|
1018
1029
|
try {
|
|
@@ -1069,24 +1080,38 @@ export function PluginsScreen() {
|
|
|
1069
1080
|
const handleUpdateAll = async () => {
|
|
1070
1081
|
if (pluginsState.plugins.status !== "success") return;
|
|
1071
1082
|
|
|
1072
|
-
// Exactly the rows that say "update"
|
|
1073
|
-
// under its real pin. Content-stale plugins are
|
|
1074
|
-
// repairing, and excluding them meant "update all" left
|
|
1075
|
-
// silently outdated files.
|
|
1083
|
+
// Exactly the rows that say "update", "stale — reinstall" or "BROKEN — press
|
|
1084
|
+
// a to fix", each planned under its real pin. Content-stale plugins are
|
|
1085
|
+
// included: they need repairing, and excluding them meant "update all" left
|
|
1086
|
+
// the machine with silently outdated files. Broken (`stale-load`) rows are
|
|
1087
|
+
// included because the row itself now names this key — leaving them out
|
|
1088
|
+
// would make that hint a lie. `install` stays out: a plugin nothing has
|
|
1089
|
+
// installed is `s`'s business, per the note above.
|
|
1076
1090
|
const items = [...statuses.values()]
|
|
1077
|
-
.filter(
|
|
1091
|
+
.filter(
|
|
1092
|
+
(s) =>
|
|
1093
|
+
s.action === "update" ||
|
|
1094
|
+
s.action === "repair" ||
|
|
1095
|
+
s.action === "stale-load",
|
|
1096
|
+
)
|
|
1078
1097
|
.sort((a, b) => a.pluginId.localeCompare(b.pluginId));
|
|
1079
1098
|
if (items.length === 0) return;
|
|
1080
1099
|
|
|
1081
1100
|
const projectPath = state.projectPath || process.cwd();
|
|
1082
1101
|
const total = items.filter(pluginNeedsWork).length;
|
|
1083
1102
|
|
|
1103
|
+
// A broken plugin is repaired in the registry, not updated, and the progress
|
|
1104
|
+
// line is the only place the user sees which one happened to which plugin.
|
|
1105
|
+
const actionOf = new Map(items.map((i) => [i.pluginId, i.action]));
|
|
1106
|
+
|
|
1084
1107
|
let done = 0;
|
|
1085
1108
|
const reasons = noMoveReasons();
|
|
1086
1109
|
const reporter: ApplyReporter = {
|
|
1087
1110
|
begin: (name) => {
|
|
1088
1111
|
done++;
|
|
1089
|
-
|
|
1112
|
+
const verb =
|
|
1113
|
+
actionOf.get(name) === "stale-load" ? "Repairing" : "Updating";
|
|
1114
|
+
modal.loading(`${verb} ${name} (${done}/${total})…`);
|
|
1090
1115
|
},
|
|
1091
1116
|
finish: reasons.reporter.finish,
|
|
1092
1117
|
detach: (work) => work(),
|
|
@@ -1348,8 +1373,16 @@ export function PluginsScreen() {
|
|
|
1348
1373
|
// "cannot tell rendered as fine" bug.
|
|
1349
1374
|
const unknownCount = counts.unknown;
|
|
1350
1375
|
const installCount = counts.install;
|
|
1351
|
-
|
|
1352
|
-
|
|
1376
|
+
// Broken: installed at the right version, loading an older one. Its own
|
|
1377
|
+
// number, never folded into `updateCount` or into silence — a clean header
|
|
1378
|
+
// over a session running a version nobody installed is the whole defect.
|
|
1379
|
+
//
|
|
1380
|
+
// "broken", not "loading older". The old wording described the mechanism, so
|
|
1381
|
+
// a reader had to already know what "loads" versus "installed" means here
|
|
1382
|
+
// before the count told them anything. `a` clears these.
|
|
1383
|
+
const brokenCount = counts["stale-load"];
|
|
1384
|
+
const subtitle = `${scopeLabel} │ ${installedCount} installed${updateCount > 0 ? ` │ ${updateCount} updates` : ""}${installCount > 0 ? ` │ ${installCount} to install` : ""}${brokenCount > 0 ? ` │ ${brokenCount} broken` : ""}${unknownCount > 0 ? ` │ ${unknownCount} unverified` : ""}`;
|
|
1385
|
+
const searchPlaceholder = `${scopeLabel} │ ${installedCount} installed${updateCount > 0 ? ` │ ${updateCount} ⬆` : ""}${installCount > 0 ? ` │ ${installCount} +` : ""}${brokenCount > 0 ? ` │ ${brokenCount} ✗` : ""}${unknownCount > 0 ? ` │ ${unknownCount} ?` : ""} │ / to search`;
|
|
1353
1386
|
|
|
1354
1387
|
const catalogNotice = buildCatalogNotice({
|
|
1355
1388
|
failures: catalogFailures,
|