magus-cli 7.6.0 → 7.8.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-parity.test.ts +29 -15
- package/src/__tests__/blackbox-plugin-state.test.ts +25 -6
- package/src/__tests__/blind-780-cli-tools.test.ts +808 -0
- package/src/__tests__/blind-780-deprecated.test.ts +631 -0
- package/src/__tests__/blind-780-failure-reasons.test.ts +1004 -0
- package/src/__tests__/blind-780-plugin-resolution.test.ts +431 -0
- package/src/__tests__/blind-780-review2.test.ts +1203 -0
- package/src/__tests__/cli-update-view.test.ts +3 -3
- package/src/__tests__/deprecated-remove.test.ts +827 -0
- package/src/__tests__/doctor.test.ts +159 -1
- package/src/__tests__/failure-popup-review2.test.tsx +920 -0
- package/src/__tests__/failure-popup.test.tsx +419 -0
- package/src/__tests__/final-fixes-780.test.tsx +724 -0
- package/src/__tests__/format-update-outcome.test.ts +9 -34
- package/src/__tests__/gitignore-profile.test.ts +0 -4
- package/src/__tests__/models-screen-state.test.ts +18 -40
- package/src/__tests__/plugin-resolution.test.ts +52 -13
- package/src/__tests__/plugin-row-text.test.ts +30 -37
- package/src/__tests__/profile-dependencies.test.ts +7 -0
- package/src/__tests__/profile-startup-old-format.test.ts +0 -3
- package/src/__tests__/profile-startup.test.ts +40 -12
- package/src/__tests__/stale-load-repair.test.ts +31 -23
- package/src/__tests__/update-apply.test.ts +13 -29
- package/src/__tests__/update-plan.test.ts +23 -17
- package/src/__tests__/useStaleLoadModal.test.ts +1 -42
- package/src/__tests__/version-unknown-state.test.ts +15 -17
- package/src/cli/doctor.ts +151 -30
- package/src/cli/install.ts +5 -6
- package/src/cli/update-view.ts +58 -15
- package/src/cli/update.ts +27 -5
- package/src/services/catalog-cache-store.ts +8 -6
- package/src/services/claude-cli.ts +106 -19
- package/src/services/claude-settings.ts +84 -36
- package/src/services/gitignore-service.ts +0 -19
- package/src/services/plugin-env-needs.ts +69 -0
- package/src/services/plugin-manager.ts +31 -19
- package/src/services/plugin-resolution.ts +34 -42
- package/src/services/profile-absorb.ts +9 -0
- package/src/services/profile-activate.ts +1 -1
- package/src/services/profile-startup.ts +30 -27
- package/src/services/profile-state.ts +81 -2
- package/src/services/resolver.ts +95 -1
- package/src/services/update-engine.ts +326 -31
- package/src/services/update-plan.ts +293 -33
- package/src/types/index.ts +7 -0
- package/src/ui/App.tsx +186 -123
- package/src/ui/adapters/cliToolsAdapter.ts +203 -1
- package/src/ui/adapters/failureReport.ts +137 -0
- package/src/ui/adapters/pluginsAdapter.ts +299 -28
- package/src/ui/adapters/startupReports.ts +282 -0
- package/src/ui/components/modals/FailureListModal.tsx +231 -0
- package/src/ui/components/modals/MessageModal.tsx +7 -13
- package/src/ui/components/modals/ModalContainer.tsx +10 -5
- package/src/ui/components/modals/index.ts +1 -0
- package/src/ui/hooks/startupQueue.ts +56 -0
- package/src/ui/hooks/useGitignoreModal.ts +31 -31
- package/src/ui/hooks/useProfileStartupModal.ts +127 -122
- package/src/ui/hooks/useStaleLoadModal.ts +33 -53
- package/src/ui/keymap.ts +14 -1
- package/src/ui/renderers/mcpRenderers.tsx +110 -3
- package/src/ui/renderers/pluginRenderers.tsx +77 -6
- package/src/ui/renderers/pluginRowText.ts +25 -25
- package/src/ui/renderers/pluginStatusText.ts +18 -1
- package/src/ui/screens/AliasScreen.tsx +38 -44
- package/src/ui/screens/CliToolsScreen.tsx +65 -51
- package/src/ui/screens/EnvVarsScreen.tsx +21 -6
- package/src/ui/screens/GitignoreScreen.tsx +49 -75
- package/src/ui/screens/McpRegistryScreen.tsx +72 -40
- package/src/ui/screens/McpScreen.tsx +55 -24
- package/src/ui/screens/ModelsScreen.tsx +46 -86
- package/src/ui/screens/PluginsScreen.tsx +316 -245
- package/src/ui/screens/ProfilesScreen.tsx +88 -73
- package/src/ui/screens/SkillsScreen.tsx +83 -80
- package/src/ui/screens/StylesScreen.tsx +211 -275
- package/src/ui/state/AppContext.tsx +72 -7
- package/src/ui/state/DimensionsContext.tsx +3 -22
- package/src/ui/state/reducer.ts +25 -14
- package/src/ui/state/types.ts +61 -31
- package/src/utils/user-path.ts +60 -0
- package/src/__tests__/styles-status-line.test.ts +0 -72
- package/src/__tests__/version-snapshot.test.ts +0 -212
- package/src/services/version-snapshot.ts +0 -189
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magus-cli",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.8.0",
|
|
4
4
|
"description": "TUI tool for managing Claude Code plugins, MCPs, and configuration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/main.tsx",
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
"typescript": "^5.6.3"
|
|
66
66
|
},
|
|
67
67
|
"optionalDependencies": {
|
|
68
|
-
"magus-cli-darwin-arm64": "7.
|
|
69
|
-
"magus-cli-darwin-x64": "7.
|
|
70
|
-
"magus-cli-linux-x64": "7.
|
|
68
|
+
"magus-cli-darwin-arm64": "7.8.0",
|
|
69
|
+
"magus-cli-darwin-x64": "7.8.0",
|
|
70
|
+
"magus-cli-linux-x64": "7.8.0"
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -430,7 +430,15 @@ describe("R7 parity: one fixture through the CLI path and the TUI path", () => {
|
|
|
430
430
|
if (!row) throw new Error(`no TUI row for ${id}`);
|
|
431
431
|
const { text } = pluginRowText(row);
|
|
432
432
|
expect({ id, empty: text.trim() === "" }).toEqual({ id, empty: false });
|
|
433
|
-
|
|
433
|
+
// A deprecated row says its verdict and the key that acts on it, in the
|
|
434
|
+
// same words on every row, rather than the note — which the CLI does not
|
|
435
|
+
// print for it either (its DEPRECATED heading carries the fact).
|
|
436
|
+
if (cli.action === "remove") {
|
|
437
|
+
expect({ id, text }).toEqual({
|
|
438
|
+
id,
|
|
439
|
+
text: expect.stringContaining("deprecated — press a to remove"),
|
|
440
|
+
});
|
|
441
|
+
} else if (cli.note) {
|
|
434
442
|
expect({ id, containsNote: text.includes(cli.note) }).toEqual({
|
|
435
443
|
id,
|
|
436
444
|
containsNote: true,
|
|
@@ -490,13 +498,13 @@ describe("R7 parity: one fixture through the CLI path and the TUI path", () => {
|
|
|
490
498
|
reason: "catalog-unverified",
|
|
491
499
|
target: null,
|
|
492
500
|
},
|
|
493
|
-
//
|
|
494
|
-
//
|
|
495
|
-
//
|
|
496
|
-
"seo@magus": { action: "unknown", reason: "
|
|
501
|
+
// Its marketplace no longer lists it, but magus-marketing lists the
|
|
502
|
+
// same name: it MOVED. Not removed — the plugin still exists — and
|
|
503
|
+
// nothing is installed in its place: `unknown`, never applied.
|
|
504
|
+
"seo@magus": { action: "unknown", reason: "moved", target: null },
|
|
497
505
|
"legacy-tool@magus": {
|
|
498
|
-
action: "
|
|
499
|
-
reason: "
|
|
506
|
+
action: "remove",
|
|
507
|
+
reason: "deprecated",
|
|
500
508
|
target: null,
|
|
501
509
|
},
|
|
502
510
|
"pinned-behind@magus": {
|
|
@@ -535,7 +543,7 @@ describe("R7 parity: one fixture through the CLI path and the TUI path", () => {
|
|
|
535
543
|
expect(undeliverable?.note).toBeTruthy();
|
|
536
544
|
|
|
537
545
|
// declared but absent from the catalog: a CLI item, never a TUI row
|
|
538
|
-
expect(p.cliPlugins.get("gone@magus")?.action).toBe("
|
|
546
|
+
expect(p.cliPlugins.get("gone@magus")?.action).toBe("remove");
|
|
539
547
|
expect(p.tuiRows.has("gone@magus")).toBe(false);
|
|
540
548
|
});
|
|
541
549
|
|
|
@@ -584,9 +592,11 @@ describe("R7 parity: one fixture through the CLI path and the TUI path", () => {
|
|
|
584
592
|
// − pinned-unorderable (sha install against a semver pin)
|
|
585
593
|
// What is left is unknown because something is genuinely wrong or unread:
|
|
586
594
|
// claudish catalog-unverified — the check itself failed
|
|
587
|
-
// seo@magus
|
|
588
|
-
//
|
|
589
|
-
|
|
595
|
+
// seo@magus moved — magus-marketing lists the same name
|
|
596
|
+
// The orphan nothing else lists is deprecated:
|
|
597
|
+
// legacy-tool remove — no longer published
|
|
598
|
+
expect(cliCounts.unknown).toBe(2);
|
|
599
|
+
expect(cliCounts.remove).toBe(1);
|
|
590
600
|
expect(cliCounts.update).toBeGreaterThanOrEqual(2); // designer, pinned-behind
|
|
591
601
|
});
|
|
592
602
|
|
|
@@ -716,12 +726,16 @@ describe("fixture discriminates the pre-change logic", () => {
|
|
|
716
726
|
|
|
717
727
|
// Where they still disagree outright: an orphan carries neither
|
|
718
728
|
// `hasUpdate` nor `updateCheckFailed`, so the flags call it healthy while
|
|
719
|
-
// the classifier says no
|
|
720
|
-
|
|
729
|
+
// the classifier says it moved, or is no longer published and removes it.
|
|
730
|
+
const orphans: Array<[string, string, string]> = [
|
|
731
|
+
["seo@magus", "unknown", "moved"],
|
|
732
|
+
["legacy-tool@magus", "remove", "deprecated"],
|
|
733
|
+
];
|
|
734
|
+
for (const [id, action, reason] of orphans) {
|
|
721
735
|
const info = p.catalog.find((c) => c.id === id) as PluginInfo;
|
|
722
736
|
expect(legacyFlagState(info)).toBe("healthy");
|
|
723
|
-
expect(p.tuiRows.get(id)?.status?.action).toBe(
|
|
724
|
-
expect(p.tuiRows.get(id)?.status?.reason).toBe(
|
|
737
|
+
expect(p.tuiRows.get(id)?.status?.action).toBe(action);
|
|
738
|
+
expect(p.tuiRows.get(id)?.status?.reason).toBe(reason);
|
|
725
739
|
}
|
|
726
740
|
});
|
|
727
741
|
});
|
|
@@ -90,7 +90,7 @@ describe("indexCatalog", () => {
|
|
|
90
90
|
});
|
|
91
91
|
|
|
92
92
|
describe("classifyPlugin — one state plus a human reason", () => {
|
|
93
|
-
test("declared, absent from the catalog, marketplace read:
|
|
93
|
+
test("declared, absent from the catalog, marketplace read: remove / deprecated, no target", () => {
|
|
94
94
|
const idx = indexCatalog([info("dev@magus", "1.0.0", "1.0.0")]);
|
|
95
95
|
|
|
96
96
|
const got = classifyPlugin("gone@magus", "latest", idx);
|
|
@@ -100,8 +100,8 @@ describe("classifyPlugin — one state plus a human reason", () => {
|
|
|
100
100
|
reason: got.reason,
|
|
101
101
|
target: got.target,
|
|
102
102
|
}).toEqual({
|
|
103
|
-
action: "
|
|
104
|
-
reason: "
|
|
103
|
+
action: "remove",
|
|
104
|
+
reason: "deprecated",
|
|
105
105
|
target: null,
|
|
106
106
|
});
|
|
107
107
|
expect(got.note).toBeTruthy();
|
|
@@ -219,7 +219,11 @@ describe("classifyPlugin — one state plus a human reason", () => {
|
|
|
219
219
|
// version is `current` now, not unknown. It is the negative control
|
|
220
220
|
// below — without it this loop would pass even if every state collapsed
|
|
221
221
|
// into one.
|
|
222
|
-
|
|
222
|
+
//
|
|
223
|
+
// Neither are `b@magus` (an orphan of a marketplace we read) and `d@magus`
|
|
224
|
+
// (declared, listed nowhere, same marketplace): both are DEPRECATED now, a
|
|
225
|
+
// `remove` — not a guess. They are the second control, after the loop.
|
|
226
|
+
for (const id of ["c@magus", "e@nowhere"]) {
|
|
223
227
|
const got = classifyPlugin(id, "latest", idx);
|
|
224
228
|
expect({ id, action: got.action }).toEqual({ id, action: "unknown" });
|
|
225
229
|
expect({ id, target: got.target, hasNote: Boolean(got.note) }).toEqual({
|
|
@@ -234,6 +238,12 @@ describe("classifyPlugin — one state plus a human reason", () => {
|
|
|
234
238
|
const a = classifyPlugin("a@magus", "latest", idx);
|
|
235
239
|
expect(a.action).toBe("current");
|
|
236
240
|
expect(a.note).toBeUndefined();
|
|
241
|
+
for (const id of ["b@magus", "d@magus"]) {
|
|
242
|
+
expect({ id, action: classifyPlugin(id, "latest", idx).action }).toEqual({
|
|
243
|
+
id,
|
|
244
|
+
action: "remove",
|
|
245
|
+
});
|
|
246
|
+
}
|
|
237
247
|
});
|
|
238
248
|
});
|
|
239
249
|
|
|
@@ -318,6 +328,7 @@ describe("countPluginStates", () => {
|
|
|
318
328
|
repair: 1,
|
|
319
329
|
current: 1,
|
|
320
330
|
"stale-load": 0,
|
|
331
|
+
remove: 0,
|
|
321
332
|
unknown: 1,
|
|
322
333
|
});
|
|
323
334
|
});
|
|
@@ -329,6 +340,7 @@ describe("countPluginStates", () => {
|
|
|
329
340
|
repair: 0,
|
|
330
341
|
current: 0,
|
|
331
342
|
"stale-load": 0,
|
|
343
|
+
remove: 0,
|
|
332
344
|
unknown: 0,
|
|
333
345
|
});
|
|
334
346
|
});
|
|
@@ -499,8 +511,12 @@ describe("pluginRowText", () => {
|
|
|
499
511
|
// The fixture is an ORPHAN at 0.0.0, not a plain 0.0.0 install. A plain one
|
|
500
512
|
// is `current` with no note now, which would leave nothing for this test to
|
|
501
513
|
// find and let the gate come back unnoticed.
|
|
514
|
+
//
|
|
515
|
+
// Its marketplace is one we did NOT read, so it stays `unknown` with a note.
|
|
516
|
+
// An orphan of a marketplace we read is deprecated now, whose row says
|
|
517
|
+
// "deprecated — press a to remove" instead of its note.
|
|
502
518
|
test("a note renders on a 0.0.0 install — no version gate", () => {
|
|
503
|
-
const plugin = info("zero-version@
|
|
519
|
+
const plugin = info("zero-version@unread-mp", null, "0.0.0", {
|
|
504
520
|
isOrphaned: true,
|
|
505
521
|
});
|
|
506
522
|
const sibling = info("dev@magus", "1.0.0", "1.0.0");
|
|
@@ -529,8 +545,11 @@ describe("pluginRowText", () => {
|
|
|
529
545
|
expect(out.text).toContain(pluginStatusText(status).suffix);
|
|
530
546
|
});
|
|
531
547
|
|
|
548
|
+
// An orphan whose marketplace was not read; see the note on the 0.0.0 test.
|
|
532
549
|
test("an orphan row renders its note", () => {
|
|
533
|
-
const plugin = info("legacy@
|
|
550
|
+
const plugin = info("legacy@unread-mp", null, "0.5.0", {
|
|
551
|
+
isOrphaned: true,
|
|
552
|
+
});
|
|
534
553
|
const sibling = info("dev@magus", "1.0.0", "1.0.0");
|
|
535
554
|
const status = classifyPlugin(
|
|
536
555
|
plugin.id,
|