magus-cli 7.11.0 → 7.11.1
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magus-cli",
|
|
3
|
-
"version": "7.11.
|
|
3
|
+
"version": "7.11.1",
|
|
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.11.
|
|
69
|
-
"magus-cli-darwin-x64": "7.11.
|
|
70
|
-
"magus-cli-linux-x64": "7.11.
|
|
68
|
+
"magus-cli-darwin-arm64": "7.11.1",
|
|
69
|
+
"magus-cli-darwin-x64": "7.11.1",
|
|
70
|
+
"magus-cli-linux-x64": "7.11.1"
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -39,8 +39,9 @@ describe("shipped plugin manifests under schema 2", () => {
|
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
// REQ-26: claudish gets minVersion
|
|
43
|
-
|
|
42
|
+
// REQ-26: claudish gets a minVersion floor; no other requirement gets one. The floor
|
|
43
|
+
// moved from 10.7.0 to 11.1.0 with claudish plugin 2.5.0, which needs SlotRow.recent.
|
|
44
|
+
test("REQ-26 claudish's claudish requirement has minVersion 11.1.0", () => {
|
|
44
45
|
const claudish = declaring.find(({ name }) => name === "claudish");
|
|
45
46
|
if (!claudish) throw new Error("plugins/claudish declares no requires");
|
|
46
47
|
const result = parseRequires(claudish.json);
|
|
@@ -50,7 +51,7 @@ describe("shipped plugin manifests under schema 2", () => {
|
|
|
50
51
|
| (Record<string, unknown> & { name: string })
|
|
51
52
|
| undefined;
|
|
52
53
|
expect(req).toBeDefined();
|
|
53
|
-
expect(req?.minVersion).toBe("
|
|
54
|
+
expect(req?.minVersion).toBe("11.1.0");
|
|
54
55
|
});
|
|
55
56
|
|
|
56
57
|
test("REQ-26 no other shipped requirement declares a floor", () => {
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
type ScopeStatus,
|
|
4
|
+
isInstalledInScope,
|
|
5
|
+
resolveScopeAction,
|
|
6
|
+
} from "../services/plugin-manager.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* MTL-70: a deprecated plugin (no longer in any marketplace,
|
|
10
|
+
* `PluginInfo.isOrphaned === true`) can only be removed from a scope key.
|
|
11
|
+
* Installed in the scope → "uninstall"; not installed → "none". `behind` never
|
|
12
|
+
* matters for a deprecated plugin. Written blind from the spec.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const installedScopes: Array<[string, ScopeStatus]> = [
|
|
16
|
+
["installed with a registry version", { installed: true, version: "3.0.2" }],
|
|
17
|
+
["installed with no version (files missing)", { installed: true }],
|
|
18
|
+
['installed at "0.0.0"', { installed: true, version: "0.0.0" }],
|
|
19
|
+
[
|
|
20
|
+
"installed with an unorderable sha",
|
|
21
|
+
{ installed: true, version: "3ea32df27be7" },
|
|
22
|
+
],
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const notInstalledScopes: Array<[string, ScopeStatus | undefined]> = [
|
|
26
|
+
["an absent scope", undefined],
|
|
27
|
+
["installed: false", { installed: false }],
|
|
28
|
+
[
|
|
29
|
+
"installed: false with a registry version",
|
|
30
|
+
{ installed: false, version: "3.0.2" },
|
|
31
|
+
],
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
describe("resolveScopeAction — deprecated plugin installed in the scope (REQ-1)", () => {
|
|
35
|
+
for (const [label, scope] of installedScopes) {
|
|
36
|
+
test(`${label} is installed per isInstalledInScope (precondition)`, () => {
|
|
37
|
+
expect(isInstalledInScope(scope)).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
for (const behind of [false, true]) {
|
|
41
|
+
test(`REQ-1: ${label}, behind=${behind} resolves to uninstall`, () => {
|
|
42
|
+
expect(resolveScopeAction(scope, behind, true)).toBe("uninstall");
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("resolveScopeAction — deprecated plugin NOT installed in the scope (REQ-2)", () => {
|
|
49
|
+
for (const [label, scope] of notInstalledScopes) {
|
|
50
|
+
test(`${label} is not installed per isInstalledInScope (precondition)`, () => {
|
|
51
|
+
expect(isInstalledInScope(scope)).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
for (const behind of [false, true]) {
|
|
55
|
+
test(`REQ-2: ${label}, behind=${behind} resolves to none — no install, no update`, () => {
|
|
56
|
+
const action = resolveScopeAction(scope, behind, true);
|
|
57
|
+
expect(action).toBe("none");
|
|
58
|
+
expect(action).not.toBe("install");
|
|
59
|
+
expect(action).not.toBe("update");
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("resolveScopeAction — orphaned false or omitted is unchanged (REQ-3)", () => {
|
|
66
|
+
const cases: Array<[string, ScopeStatus | undefined, boolean, string]> = [
|
|
67
|
+
["files missing", { installed: true }, false, "install"],
|
|
68
|
+
["files missing, behind", { installed: true }, true, "install"],
|
|
69
|
+
["never installed (absent)", undefined, false, "install"],
|
|
70
|
+
["never installed (flag off)", { installed: false }, false, "install"],
|
|
71
|
+
[
|
|
72
|
+
"healthy at latest",
|
|
73
|
+
{ installed: true, version: "3.0.2" },
|
|
74
|
+
false,
|
|
75
|
+
"uninstall",
|
|
76
|
+
],
|
|
77
|
+
[
|
|
78
|
+
"healthy and behind",
|
|
79
|
+
{ installed: true, version: "2.9.0" },
|
|
80
|
+
true,
|
|
81
|
+
"update",
|
|
82
|
+
],
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
for (const [label, scope, behind, expected] of cases) {
|
|
86
|
+
test(`REQ-3: ${label} with orphaned omitted resolves to ${expected}`, () => {
|
|
87
|
+
expect(resolveScopeAction(scope, behind)).toBe(expected as never);
|
|
88
|
+
});
|
|
89
|
+
test(`REQ-3: ${label} with orphaned=false resolves to ${expected}`, () => {
|
|
90
|
+
expect(resolveScopeAction(scope, behind, false)).toBe(expected as never);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
test("REQ-3: a non-deprecated plugin never resolves to none", () => {
|
|
95
|
+
const all: Array<ScopeStatus | undefined> = [
|
|
96
|
+
...installedScopes.map(([, s]) => s),
|
|
97
|
+
...notInstalledScopes.map(([, s]) => s),
|
|
98
|
+
];
|
|
99
|
+
for (const scope of all) {
|
|
100
|
+
for (const behind of [false, true]) {
|
|
101
|
+
expect(resolveScopeAction(scope, behind, false)).not.toBe("none");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
});
|
package/src/cli/router.ts
CHANGED
|
@@ -103,7 +103,12 @@ const SUBCOMMANDS = new Set([
|
|
|
103
103
|
"doctor",
|
|
104
104
|
]);
|
|
105
105
|
|
|
106
|
-
/**
|
|
106
|
+
/**
|
|
107
|
+
* Subcommands that read or write profile state, and so pass the gate first.
|
|
108
|
+
* code-search setup runs `magus profile list` to regenerate settings.json after it
|
|
109
|
+
* edits profiles.json, so `profile` must keep running the gate
|
|
110
|
+
* (ai-docs/code-search-setup-profiles.md).
|
|
111
|
+
*/
|
|
107
112
|
const PROFILE_GATED = new Set(["install", "update", "profile", "models"]);
|
|
108
113
|
|
|
109
114
|
export async function route(
|
|
@@ -371,7 +371,11 @@ export function buildScopeStatus(
|
|
|
371
371
|
export function resolveScopeAction(
|
|
372
372
|
scope: ScopeStatus | undefined,
|
|
373
373
|
behind: boolean,
|
|
374
|
-
|
|
374
|
+
orphaned = false,
|
|
375
|
+
): "install" | "update" | "uninstall" | "none" {
|
|
376
|
+
// A deprecated plugin is no longer in any marketplace, so install and update
|
|
377
|
+
// can only fail with a raw CLI error. Removing it is the one action left.
|
|
378
|
+
if (orphaned) return isInstalledInScope(scope) ? "uninstall" : "none";
|
|
375
379
|
if (!isInstalledInScope(scope) || scopeFilesMissing(scope)) return "install";
|
|
376
380
|
return behind ? "update" : "uninstall";
|
|
377
381
|
}
|
|
@@ -1009,7 +1009,9 @@ export function PluginsScreen() {
|
|
|
1009
1009
|
const action = resolveScopeAction(
|
|
1010
1010
|
selectedScope,
|
|
1011
1011
|
scopeIsBehind(status, scopeValue as PluginScope),
|
|
1012
|
+
plugin.isOrphaned,
|
|
1012
1013
|
);
|
|
1014
|
+
if (action === "none") return;
|
|
1013
1015
|
|
|
1014
1016
|
try {
|
|
1015
1017
|
const scope = scopeValue as PluginScope;
|
|
@@ -1174,7 +1176,9 @@ export function PluginsScreen() {
|
|
|
1174
1176
|
const action = resolveScopeAction(
|
|
1175
1177
|
scopeData,
|
|
1176
1178
|
scopeIsBehind(statuses.get(plugin.id), scope),
|
|
1179
|
+
plugin.isOrphaned,
|
|
1177
1180
|
);
|
|
1181
|
+
if (action === "none") return;
|
|
1178
1182
|
|
|
1179
1183
|
try {
|
|
1180
1184
|
if (action === "uninstall") {
|