claudeup 4.23.0 → 4.24.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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudeup",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.24.0",
|
|
4
4
|
"description": "TUI tool for managing Claude Code plugins, MCPs, and configuration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/main.tsx",
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
"typescript": "^5.6.3"
|
|
65
65
|
},
|
|
66
66
|
"optionalDependencies": {
|
|
67
|
-
"claudeup-darwin-arm64": "4.
|
|
68
|
-
"claudeup-darwin-x64": "4.
|
|
69
|
-
"claudeup-linux-x64": "4.
|
|
67
|
+
"claudeup-darwin-arm64": "4.24.0",
|
|
68
|
+
"claudeup-darwin-x64": "4.24.0",
|
|
69
|
+
"claudeup-linux-x64": "4.24.0"
|
|
70
70
|
}
|
|
71
71
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { isEnabledButNotInstalled } from "../services/plugin-manager.js";
|
|
3
|
+
import type { PluginInfo } from "../services/plugin-manager.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* "Enabled but not installed" is a broken state that previously rendered
|
|
7
|
+
* identically to a healthy install: a green scope square and no version.
|
|
8
|
+
*
|
|
9
|
+
* It is also invisible to every update path, because the check is
|
|
10
|
+
* `installedVersion && compare(latest, installedVersion) > 0` — with no
|
|
11
|
+
* installed version that is false, so neither the update list nor the
|
|
12
|
+
* prerunner's auto-update will ever touch it.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const plugin = (over: Partial<PluginInfo>): PluginInfo =>
|
|
16
|
+
({
|
|
17
|
+
id: "x@magus",
|
|
18
|
+
name: "x",
|
|
19
|
+
version: "1.0.0",
|
|
20
|
+
description: "",
|
|
21
|
+
marketplace: "magus",
|
|
22
|
+
marketplaceDisplay: "Magus",
|
|
23
|
+
enabled: true,
|
|
24
|
+
...over,
|
|
25
|
+
}) as PluginInfo;
|
|
26
|
+
|
|
27
|
+
describe("isEnabledButNotInstalled", () => {
|
|
28
|
+
test("enabled in project scope with no installed version is flagged", () => {
|
|
29
|
+
// The real case: terminal/code-analysis are in this project's enabledPlugins
|
|
30
|
+
// but have no registry entry for it and none at user scope.
|
|
31
|
+
expect(
|
|
32
|
+
isEnabledButNotInstalled(
|
|
33
|
+
plugin({ projectScope: { enabled: true }, installedVersion: undefined }),
|
|
34
|
+
),
|
|
35
|
+
).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("a healthy install is not flagged", () => {
|
|
39
|
+
expect(
|
|
40
|
+
isEnabledButNotInstalled(
|
|
41
|
+
plugin({ projectScope: { enabled: true }, installedVersion: "1.0.0" }),
|
|
42
|
+
),
|
|
43
|
+
).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('installedVersion "0.0.0" means installed-with-unknown-version, not missing', () => {
|
|
47
|
+
// Anthropic's official plugins record exactly this. Treating it as missing
|
|
48
|
+
// would mislabel every one of them as broken.
|
|
49
|
+
expect(
|
|
50
|
+
isEnabledButNotInstalled(
|
|
51
|
+
plugin({ userScope: { enabled: true }, installedVersion: "0.0.0" }),
|
|
52
|
+
),
|
|
53
|
+
).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("not enabled anywhere is not flagged — it is simply available", () => {
|
|
57
|
+
expect(isEnabledButNotInstalled(plugin({ installedVersion: undefined }))).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("orphaned plugins are excluded — they have their own 'deprecated' state", () => {
|
|
61
|
+
expect(
|
|
62
|
+
isEnabledButNotInstalled(
|
|
63
|
+
plugin({
|
|
64
|
+
isOrphaned: true,
|
|
65
|
+
projectScope: { enabled: true },
|
|
66
|
+
installedVersion: undefined,
|
|
67
|
+
}),
|
|
68
|
+
),
|
|
69
|
+
).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("any enabled scope counts, not just project", () => {
|
|
73
|
+
for (const scope of ["userScope", "projectScope", "localScope"] as const) {
|
|
74
|
+
expect(
|
|
75
|
+
isEnabledButNotInstalled(plugin({ [scope]: { enabled: true } })),
|
|
76
|
+
).toBe(true);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("a scope present but disabled does not count as enabled", () => {
|
|
81
|
+
expect(
|
|
82
|
+
isEnabledButNotInstalled(
|
|
83
|
+
plugin({ projectScope: { enabled: false }, installedVersion: undefined }),
|
|
84
|
+
),
|
|
85
|
+
).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -74,6 +74,30 @@ export interface PluginInfo {
|
|
|
74
74
|
isOrphaned?: boolean;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/**
|
|
78
|
+
* True when a plugin is enabled in some scope but no installed version resolves
|
|
79
|
+
* anywhere — it is listed in `enabledPlugins` but was never actually installed.
|
|
80
|
+
*
|
|
81
|
+
* This is a broken state, not a healthy one: Claude Code will not load the
|
|
82
|
+
* plugin, and because the update check is `installedVersion && compare(...)`,
|
|
83
|
+
* it is also invisible to both the update list and the prerunner's auto-update.
|
|
84
|
+
* It previously rendered identically to a working install.
|
|
85
|
+
*
|
|
86
|
+
* `isOrphaned` is a different failure — installed, but no longer offered by any
|
|
87
|
+
* marketplace — and is rendered as "deprecated" already.
|
|
88
|
+
*
|
|
89
|
+
* "0.0.0" means installed-with-unknown-version (Anthropic's official plugins
|
|
90
|
+
* record exactly that), so it must NOT count as missing.
|
|
91
|
+
*/
|
|
92
|
+
export function isEnabledButNotInstalled(plugin: PluginInfo): boolean {
|
|
93
|
+
if (plugin.isOrphaned) return false;
|
|
94
|
+
const enabledSomewhere =
|
|
95
|
+
!!plugin.userScope?.enabled ||
|
|
96
|
+
!!plugin.projectScope?.enabled ||
|
|
97
|
+
!!plugin.localScope?.enabled;
|
|
98
|
+
return enabledSomewhere && !plugin.installedVersion;
|
|
99
|
+
}
|
|
100
|
+
|
|
77
101
|
|
|
78
102
|
export async function getAvailablePlugins(
|
|
79
103
|
projectPath?: string,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from "../components/primitives/index.js";
|
|
16
16
|
import { theme } from "../theme.js";
|
|
17
17
|
import { highlightMatches } from "../../utils/fuzzy-search.js";
|
|
18
|
+
import { isEnabledButNotInstalled } from "../../services/plugin-manager.js";
|
|
18
19
|
|
|
19
20
|
// ─── Category renderers ───────────────────────────────────────────────────────
|
|
20
21
|
|
|
@@ -113,10 +114,16 @@ function pluginRow(item: PluginPluginItem, isSelected: boolean): React.ReactNode
|
|
|
113
114
|
const hasLocal = !!plugin.localScope?.enabled;
|
|
114
115
|
const hasAnyScope = hasUser || hasProject || hasLocal;
|
|
115
116
|
|
|
117
|
+
// Enabled somewhere but nothing actually installed — a broken state that used
|
|
118
|
+
// to render exactly like a healthy install.
|
|
119
|
+
const notInstalled = isEnabledButNotInstalled(plugin);
|
|
120
|
+
|
|
116
121
|
// Build version string — only show if plugin is installed in at least one scope
|
|
117
122
|
let versionStr = "";
|
|
118
123
|
if (plugin.isOrphaned) {
|
|
119
124
|
versionStr = " deprecated";
|
|
125
|
+
} else if (notInstalled) {
|
|
126
|
+
versionStr = " not installed";
|
|
120
127
|
} else if (hasAnyScope && plugin.installedVersion && plugin.installedVersion !== "0.0.0") {
|
|
121
128
|
versionStr = ` v${plugin.installedVersion}`;
|
|
122
129
|
if (plugin.hasUpdate && plugin.version) {
|
|
@@ -158,8 +165,13 @@ function pluginRow(item: PluginPluginItem, isSelected: boolean): React.ReactNode
|
|
|
158
165
|
<span> </span>
|
|
159
166
|
<ScopeSquares user={hasUser} project={hasProject} local={hasLocal} />
|
|
160
167
|
<span> </span>
|
|
161
|
-
<span fg={hasAnyScope ? theme.colors.text : theme.colors.muted}>
|
|
162
|
-
|
|
168
|
+
<span fg={hasAnyScope && !notInstalled ? theme.colors.text : theme.colors.muted}>
|
|
169
|
+
{displayName}
|
|
170
|
+
</span>
|
|
171
|
+
<MetaText
|
|
172
|
+
text={versionStr}
|
|
173
|
+
tone={notInstalled || plugin.hasUpdate ? "warning" : "muted"}
|
|
174
|
+
/>
|
|
163
175
|
</text>
|
|
164
176
|
);
|
|
165
177
|
}
|