claudeup 4.5.0 → 4.5.2
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 +1 -1
- package/src/__tests__/dual-write-prevention.test.ts +13 -10
- package/src/main.js +48 -4
- package/src/main.tsx +56 -4
- package/src/prerunner/index.js +20 -7
- package/src/prerunner/index.ts +22 -8
- package/src/ui/screens/PluginsScreen.js +29 -2
- package/src/ui/screens/PluginsScreen.tsx +34 -0
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Version tracking tests
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* The Claude CLI's `plugin install` command does NOT update `installedPluginVersions`
|
|
5
|
+
* in settings.json — it only manages `enabledPlugins`. Claudeup must save the version
|
|
6
|
+
* itself after a successful CLI install/update.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* PluginsScreen: no saveVersionAfterInstall helper (removed in v4.5.0), but each
|
|
9
|
+
* action handler calls saveVersionForScope after CLI success.
|
|
10
|
+
* Prerunner: calls saveGlobalInstalledPluginVersion after successful updatePlugin,
|
|
11
|
+
* but NOT when CLI is unavailable or update fails (no phantom state).
|
|
10
12
|
*/
|
|
11
13
|
|
|
12
14
|
import { describe, it, expect, mock, beforeEach, afterEach } from "bun:test";
|
|
@@ -121,6 +123,7 @@ mock.module("../services/claude-cli.js", () => ({
|
|
|
121
123
|
updatePlugin: (...args: unknown[]) => mockUpdatePlugin(...args),
|
|
122
124
|
isClaudeAvailable: (...args: unknown[]) => mockIsClaudeAvailable(...args),
|
|
123
125
|
addMarketplace: mock(() => Promise.resolve()),
|
|
126
|
+
updateMarketplace: mock(() => Promise.resolve()),
|
|
124
127
|
}));
|
|
125
128
|
|
|
126
129
|
mock.module("../services/plugin-manager.js", () => ({
|
|
@@ -194,16 +197,16 @@ describe("prerunner — saveGlobalInstalledPluginVersion call count", () => {
|
|
|
194
197
|
);
|
|
195
198
|
});
|
|
196
199
|
|
|
197
|
-
it("
|
|
200
|
+
it("calls saveGlobalInstalledPluginVersion once after successful CLI update", async () => {
|
|
198
201
|
// CLI is available, updatePlugin succeeds.
|
|
199
|
-
// The
|
|
200
|
-
// the
|
|
202
|
+
// The CLI does NOT update installedPluginVersions, so claudeup must save
|
|
203
|
+
// the version after a successful update.
|
|
201
204
|
mockIsClaudeAvailable = mock(() => Promise.resolve(true));
|
|
202
205
|
mockUpdatePlugin = mock(() => Promise.resolve());
|
|
203
206
|
|
|
204
207
|
await prerunClaude(["--help"], { force: true });
|
|
205
208
|
|
|
206
|
-
expect(mockSaveGlobal).toHaveBeenCalledTimes(
|
|
209
|
+
expect(mockSaveGlobal).toHaveBeenCalledTimes(1);
|
|
207
210
|
});
|
|
208
211
|
|
|
209
212
|
it("does NOT call saveGlobalInstalledPluginVersion when CLI is unavailable", async () => {
|
package/src/main.js
CHANGED
|
@@ -27,8 +27,54 @@ async function main() {
|
|
|
27
27
|
}
|
|
28
28
|
// Handle "claudeup update" - self-update command
|
|
29
29
|
if (args[0] === "update") {
|
|
30
|
-
// Detect how claudeup was installed by checking the executable path
|
|
31
30
|
const { execSync } = await import("node:child_process");
|
|
31
|
+
const { existsSync } = await import("node:fs");
|
|
32
|
+
// Detect all installations of claudeup across package managers
|
|
33
|
+
const installations = [];
|
|
34
|
+
// Check bun global
|
|
35
|
+
try {
|
|
36
|
+
const bunGlobalBin = execSync("bun pm -g bin", {
|
|
37
|
+
encoding: "utf-8",
|
|
38
|
+
timeout: 5000,
|
|
39
|
+
}).trim();
|
|
40
|
+
const bunPath = `${bunGlobalBin}/claudeup`;
|
|
41
|
+
if (existsSync(bunPath))
|
|
42
|
+
installations.push({ manager: "bun", path: bunPath });
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// bun not installed or no global claudeup
|
|
46
|
+
}
|
|
47
|
+
// Check npm global
|
|
48
|
+
try {
|
|
49
|
+
const npmPrefix = execSync("npm prefix -g", {
|
|
50
|
+
encoding: "utf-8",
|
|
51
|
+
timeout: 5000,
|
|
52
|
+
}).trim();
|
|
53
|
+
const npmPath = `${npmPrefix}/bin/claudeup`;
|
|
54
|
+
if (existsSync(npmPath))
|
|
55
|
+
installations.push({ manager: "npm", path: npmPath });
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// npm not installed or no global claudeup
|
|
59
|
+
}
|
|
60
|
+
// Warn about duplicate installations
|
|
61
|
+
if (installations.length > 1) {
|
|
62
|
+
const activePath = execSync("which claudeup", {
|
|
63
|
+
encoding: "utf-8",
|
|
64
|
+
timeout: 5000,
|
|
65
|
+
}).trim();
|
|
66
|
+
console.log(`⚠ claudeup is installed via multiple package managers:\n`);
|
|
67
|
+
for (const inst of installations) {
|
|
68
|
+
const tag = inst.path === activePath ? " (active)" : " (shadowed)";
|
|
69
|
+
console.log(` ${inst.manager}: ${inst.path}${tag}`);
|
|
70
|
+
}
|
|
71
|
+
console.log(`\nTo fix, keep one and remove the other:`);
|
|
72
|
+
for (const inst of installations) {
|
|
73
|
+
console.log(` ${inst.manager} uninstall -g claudeup`);
|
|
74
|
+
}
|
|
75
|
+
console.log();
|
|
76
|
+
}
|
|
77
|
+
// Determine which package manager to use for the update
|
|
32
78
|
let usesBun = false;
|
|
33
79
|
try {
|
|
34
80
|
const claudeupPath = execSync("which claudeup", {
|
|
@@ -42,9 +88,7 @@ async function main() {
|
|
|
42
88
|
}
|
|
43
89
|
const pkgManager = usesBun ? "bun" : "npm";
|
|
44
90
|
console.log(`Updating claudeup using ${pkgManager}...`);
|
|
45
|
-
const installArgs =
|
|
46
|
-
? ["install", "-g", "claudeup@latest"]
|
|
47
|
-
: ["install", "-g", "claudeup@latest"];
|
|
91
|
+
const installArgs = ["install", "-g", "claudeup@latest"];
|
|
48
92
|
const proc = spawn(pkgManager, installArgs, {
|
|
49
93
|
stdio: "inherit",
|
|
50
94
|
shell: false, // Avoid shell for security (fixes DEP0190 warning)
|
package/src/main.tsx
CHANGED
|
@@ -38,8 +38,62 @@ async function main(): Promise<void> {
|
|
|
38
38
|
|
|
39
39
|
// Handle "claudeup update" - self-update command
|
|
40
40
|
if (args[0] === "update") {
|
|
41
|
-
// Detect how claudeup was installed by checking the executable path
|
|
42
41
|
const { execSync } = await import("node:child_process");
|
|
42
|
+
const { existsSync } = await import("node:fs");
|
|
43
|
+
|
|
44
|
+
// Detect all installations of claudeup across package managers
|
|
45
|
+
const installations: Array<{
|
|
46
|
+
manager: "bun" | "npm";
|
|
47
|
+
path: string;
|
|
48
|
+
}> = [];
|
|
49
|
+
|
|
50
|
+
// Check bun global
|
|
51
|
+
try {
|
|
52
|
+
const bunGlobalBin = execSync("bun pm -g bin", {
|
|
53
|
+
encoding: "utf-8",
|
|
54
|
+
timeout: 5000,
|
|
55
|
+
}).trim();
|
|
56
|
+
const bunPath = `${bunGlobalBin}/claudeup`;
|
|
57
|
+
if (existsSync(bunPath)) installations.push({ manager: "bun", path: bunPath });
|
|
58
|
+
} catch {
|
|
59
|
+
// bun not installed or no global claudeup
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Check npm global
|
|
63
|
+
try {
|
|
64
|
+
const npmPrefix = execSync("npm prefix -g", {
|
|
65
|
+
encoding: "utf-8",
|
|
66
|
+
timeout: 5000,
|
|
67
|
+
}).trim();
|
|
68
|
+
const npmPath = `${npmPrefix}/bin/claudeup`;
|
|
69
|
+
if (existsSync(npmPath)) installations.push({ manager: "npm", path: npmPath });
|
|
70
|
+
} catch {
|
|
71
|
+
// npm not installed or no global claudeup
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Warn about duplicate installations
|
|
75
|
+
if (installations.length > 1) {
|
|
76
|
+
const activePath = execSync("which claudeup", {
|
|
77
|
+
encoding: "utf-8",
|
|
78
|
+
timeout: 5000,
|
|
79
|
+
}).trim();
|
|
80
|
+
|
|
81
|
+
console.log(
|
|
82
|
+
`⚠ claudeup is installed via multiple package managers:\n`,
|
|
83
|
+
);
|
|
84
|
+
for (const inst of installations) {
|
|
85
|
+
const tag =
|
|
86
|
+
inst.path === activePath ? " (active)" : " (shadowed)";
|
|
87
|
+
console.log(` ${inst.manager}: ${inst.path}${tag}`);
|
|
88
|
+
}
|
|
89
|
+
console.log(`\nTo fix, keep one and remove the other:`);
|
|
90
|
+
for (const inst of installations) {
|
|
91
|
+
console.log(` ${inst.manager} uninstall -g claudeup`);
|
|
92
|
+
}
|
|
93
|
+
console.log();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Determine which package manager to use for the update
|
|
43
97
|
let usesBun = false;
|
|
44
98
|
try {
|
|
45
99
|
const claudeupPath = execSync("which claudeup", {
|
|
@@ -54,9 +108,7 @@ async function main(): Promise<void> {
|
|
|
54
108
|
const pkgManager = usesBun ? "bun" : "npm";
|
|
55
109
|
console.log(`Updating claudeup using ${pkgManager}...`);
|
|
56
110
|
|
|
57
|
-
const installArgs =
|
|
58
|
-
? ["install", "-g", "claudeup@latest"]
|
|
59
|
-
: ["install", "-g", "claudeup@latest"];
|
|
111
|
+
const installArgs = ["install", "-g", "claudeup@latest"];
|
|
60
112
|
|
|
61
113
|
const proc = spawn(pkgManager, installArgs, {
|
|
62
114
|
stdio: "inherit",
|
package/src/prerunner/index.js
CHANGED
|
@@ -4,10 +4,10 @@ import os from "node:os";
|
|
|
4
4
|
import { UpdateCache } from "../services/update-cache.js";
|
|
5
5
|
import { getAvailablePlugins, clearMarketplaceCache, } from "../services/plugin-manager.js";
|
|
6
6
|
import { runClaude } from "../services/claude-runner.js";
|
|
7
|
-
import { recoverMarketplaceSettings, migrateMarketplaceRename, getGlobalEnabledPlugins, getEnabledPlugins, getLocalEnabledPlugins, readGlobalSettings, writeGlobalSettings, } from "../services/claude-settings.js";
|
|
7
|
+
import { recoverMarketplaceSettings, migrateMarketplaceRename, getGlobalEnabledPlugins, getEnabledPlugins, getLocalEnabledPlugins, readGlobalSettings, writeGlobalSettings, saveGlobalInstalledPluginVersion, } from "../services/claude-settings.js";
|
|
8
8
|
import { parsePluginId } from "../utils/string-utils.js";
|
|
9
9
|
import { defaultMarketplaces } from "../data/marketplaces.js";
|
|
10
|
-
import { updatePlugin, addMarketplace, isClaudeAvailable, } from "../services/claude-cli.js";
|
|
10
|
+
import { updatePlugin, addMarketplace, updateMarketplace, isClaudeAvailable, } from "../services/claude-cli.js";
|
|
11
11
|
const MARKETPLACES_DIR = path.join(os.homedir(), ".claude", "plugins", "marketplaces");
|
|
12
12
|
/**
|
|
13
13
|
* Collect all unique marketplace names from enabled plugins across all settings scopes.
|
|
@@ -55,6 +55,11 @@ async function getReferencedMarketplaces(projectPath) {
|
|
|
55
55
|
/**
|
|
56
56
|
* Check which referenced marketplaces are missing locally and auto-add them.
|
|
57
57
|
* Only adds marketplaces with known repos (from defaultMarketplaces).
|
|
58
|
+
*
|
|
59
|
+
* Uses `marketplace update` (not `marketplace add`) for recovery because
|
|
60
|
+
* `add` short-circuits when the marketplace is already declared in settings
|
|
61
|
+
* — even if the directory was deleted. `update` detects the missing dir and
|
|
62
|
+
* re-clones automatically.
|
|
58
63
|
*/
|
|
59
64
|
async function autoAddMissingMarketplaces(projectPath) {
|
|
60
65
|
const referenced = await getReferencedMarketplaces(projectPath);
|
|
@@ -69,12 +74,19 @@ async function autoAddMissingMarketplaces(projectPath) {
|
|
|
69
74
|
if (!defaultMp?.source.repo)
|
|
70
75
|
continue;
|
|
71
76
|
try {
|
|
72
|
-
|
|
77
|
+
// Try `marketplace update` first — it re-clones missing directories
|
|
78
|
+
await updateMarketplace(mpName);
|
|
73
79
|
added.push(mpName);
|
|
74
80
|
}
|
|
75
|
-
catch
|
|
76
|
-
//
|
|
77
|
-
|
|
81
|
+
catch {
|
|
82
|
+
// If update fails (marketplace not in settings yet), try add
|
|
83
|
+
try {
|
|
84
|
+
await addMarketplace(defaultMp.source.repo);
|
|
85
|
+
added.push(mpName);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
console.warn(`⚠ Failed to auto-add marketplace ${mpName}:`, error instanceof Error ? error.message : "Unknown error");
|
|
89
|
+
}
|
|
78
90
|
}
|
|
79
91
|
}
|
|
80
92
|
return added;
|
|
@@ -199,7 +211,8 @@ export async function prerunClaude(claudeArgs, options = {}) {
|
|
|
199
211
|
continue;
|
|
200
212
|
}
|
|
201
213
|
await updatePlugin(plugin.id, "user");
|
|
202
|
-
// CLI
|
|
214
|
+
// CLI does NOT update installedPluginVersions — save it ourselves
|
|
215
|
+
await saveGlobalInstalledPluginVersion(plugin.id, plugin.version);
|
|
203
216
|
autoUpdatedPlugins.push({
|
|
204
217
|
pluginId: plugin.id,
|
|
205
218
|
oldVersion: plugin.installedVersion,
|
package/src/prerunner/index.ts
CHANGED
|
@@ -15,12 +15,14 @@ import {
|
|
|
15
15
|
getLocalEnabledPlugins,
|
|
16
16
|
readGlobalSettings,
|
|
17
17
|
writeGlobalSettings,
|
|
18
|
+
saveGlobalInstalledPluginVersion,
|
|
18
19
|
} from "../services/claude-settings.js";
|
|
19
20
|
import { parsePluginId } from "../utils/string-utils.js";
|
|
20
21
|
import { defaultMarketplaces } from "../data/marketplaces.js";
|
|
21
22
|
import {
|
|
22
23
|
updatePlugin,
|
|
23
24
|
addMarketplace,
|
|
25
|
+
updateMarketplace,
|
|
24
26
|
isClaudeAvailable,
|
|
25
27
|
} from "../services/claude-cli.js";
|
|
26
28
|
|
|
@@ -84,6 +86,11 @@ async function getReferencedMarketplaces(
|
|
|
84
86
|
/**
|
|
85
87
|
* Check which referenced marketplaces are missing locally and auto-add them.
|
|
86
88
|
* Only adds marketplaces with known repos (from defaultMarketplaces).
|
|
89
|
+
*
|
|
90
|
+
* Uses `marketplace update` (not `marketplace add`) for recovery because
|
|
91
|
+
* `add` short-circuits when the marketplace is already declared in settings
|
|
92
|
+
* — even if the directory was deleted. `update` detects the missing dir and
|
|
93
|
+
* re-clones automatically.
|
|
87
94
|
*/
|
|
88
95
|
async function autoAddMissingMarketplaces(
|
|
89
96
|
projectPath?: string,
|
|
@@ -101,14 +108,20 @@ async function autoAddMissingMarketplaces(
|
|
|
101
108
|
if (!defaultMp?.source.repo) continue;
|
|
102
109
|
|
|
103
110
|
try {
|
|
104
|
-
|
|
111
|
+
// Try `marketplace update` first — it re-clones missing directories
|
|
112
|
+
await updateMarketplace(mpName);
|
|
105
113
|
added.push(mpName);
|
|
106
|
-
} catch
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
)
|
|
114
|
+
} catch {
|
|
115
|
+
// If update fails (marketplace not in settings yet), try add
|
|
116
|
+
try {
|
|
117
|
+
await addMarketplace(defaultMp.source.repo);
|
|
118
|
+
added.push(mpName);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.warn(
|
|
121
|
+
`⚠ Failed to auto-add marketplace ${mpName}:`,
|
|
122
|
+
error instanceof Error ? error.message : "Unknown error",
|
|
123
|
+
);
|
|
124
|
+
}
|
|
112
125
|
}
|
|
113
126
|
}
|
|
114
127
|
|
|
@@ -282,7 +295,8 @@ export async function prerunClaude(
|
|
|
282
295
|
continue;
|
|
283
296
|
}
|
|
284
297
|
await updatePlugin(plugin.id, "user");
|
|
285
|
-
// CLI
|
|
298
|
+
// CLI does NOT update installedPluginVersions — save it ourselves
|
|
299
|
+
await saveGlobalInstalledPluginVersion(plugin.id, plugin.version);
|
|
286
300
|
|
|
287
301
|
autoUpdatedPlugins.push({
|
|
288
302
|
pluginId: plugin.id,
|
|
@@ -8,8 +8,8 @@ import { ScrollableList } from "../components/ScrollableList.js";
|
|
|
8
8
|
import { EmptyFilterState } from "../components/EmptyFilterState.js";
|
|
9
9
|
import { fuzzyFilter } from "../../utils/fuzzy-search.js";
|
|
10
10
|
import { getAllMarketplaces } from "../../data/marketplaces.js";
|
|
11
|
-
import { getAvailablePlugins, refreshAllMarketplaces, clearMarketplaceCache, getLocalMarketplacesInfo, } from "../../services/plugin-manager.js";
|
|
12
|
-
import { setMcpEnvVar, getMcpEnvVars, readSettings, } from "../../services/claude-settings.js";
|
|
11
|
+
import { getAvailablePlugins, refreshAllMarketplaces, clearMarketplaceCache, getLocalMarketplacesInfo, saveInstalledPluginVersion, } from "../../services/plugin-manager.js";
|
|
12
|
+
import { setMcpEnvVar, getMcpEnvVars, readSettings, saveGlobalInstalledPluginVersion, saveLocalInstalledPluginVersion, } from "../../services/claude-settings.js";
|
|
13
13
|
import { saveProfile } from "../../services/profiles.js";
|
|
14
14
|
import { installPlugin as cliInstallPlugin, uninstallPlugin as cliUninstallPlugin, updatePlugin as cliUpdatePlugin, } from "../../services/claude-cli.js";
|
|
15
15
|
import { getPluginEnvRequirements, getPluginSourcePath, } from "../../services/plugin-mcp-config.js";
|
|
@@ -206,6 +206,23 @@ export function PluginsScreen() {
|
|
|
206
206
|
else if (event.name === "s")
|
|
207
207
|
handleSaveAsProfile();
|
|
208
208
|
});
|
|
209
|
+
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
210
|
+
/**
|
|
211
|
+
* Save the installed plugin version to the correct settings file for the scope.
|
|
212
|
+
* The Claude CLI's `plugin install` does NOT update installedPluginVersions,
|
|
213
|
+
* so claudeup must do it after a successful CLI install/update.
|
|
214
|
+
*/
|
|
215
|
+
const saveVersionForScope = async (pluginId, version, scope) => {
|
|
216
|
+
if (scope === "user") {
|
|
217
|
+
await saveGlobalInstalledPluginVersion(pluginId, version);
|
|
218
|
+
}
|
|
219
|
+
else if (scope === "local") {
|
|
220
|
+
await saveLocalInstalledPluginVersion(pluginId, version, state.projectPath);
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
await saveInstalledPluginVersion(pluginId, version, state.projectPath);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
209
226
|
// ── Action handlers ────────────────────────────────────────────────────────
|
|
210
227
|
const handleRefresh = async () => {
|
|
211
228
|
progress.show("Refreshing cache...");
|
|
@@ -439,9 +456,11 @@ export function PluginsScreen() {
|
|
|
439
456
|
}
|
|
440
457
|
else if (action === "update") {
|
|
441
458
|
await cliUpdatePlugin(plugin.id, scope);
|
|
459
|
+
await saveVersionForScope(plugin.id, latestVersion, scope);
|
|
442
460
|
}
|
|
443
461
|
else {
|
|
444
462
|
await cliInstallPlugin(plugin.id, scope);
|
|
463
|
+
await saveVersionForScope(plugin.id, latestVersion, scope);
|
|
445
464
|
modal.hideModal();
|
|
446
465
|
await collectPluginEnvVars(plugin.name, plugin.marketplace);
|
|
447
466
|
await installPluginSystemDeps(plugin.name, plugin.marketplace);
|
|
@@ -466,6 +485,9 @@ export function PluginsScreen() {
|
|
|
466
485
|
modal.loading(`Updating ${plugin.name}...`);
|
|
467
486
|
try {
|
|
468
487
|
await cliUpdatePlugin(plugin.id, scope);
|
|
488
|
+
if (plugin.version) {
|
|
489
|
+
await saveVersionForScope(plugin.id, plugin.version, scope);
|
|
490
|
+
}
|
|
469
491
|
modal.hideModal();
|
|
470
492
|
fetchData();
|
|
471
493
|
}
|
|
@@ -485,6 +507,9 @@ export function PluginsScreen() {
|
|
|
485
507
|
try {
|
|
486
508
|
for (const plugin of updatable) {
|
|
487
509
|
await cliUpdatePlugin(plugin.id, scope);
|
|
510
|
+
if (plugin.version) {
|
|
511
|
+
await saveVersionForScope(plugin.id, plugin.version, scope);
|
|
512
|
+
}
|
|
488
513
|
}
|
|
489
514
|
modal.hideModal();
|
|
490
515
|
fetchData();
|
|
@@ -534,9 +559,11 @@ export function PluginsScreen() {
|
|
|
534
559
|
}
|
|
535
560
|
else if (action === "update") {
|
|
536
561
|
await cliUpdatePlugin(plugin.id, scope);
|
|
562
|
+
await saveVersionForScope(plugin.id, latestVersion, scope);
|
|
537
563
|
}
|
|
538
564
|
else {
|
|
539
565
|
await cliInstallPlugin(plugin.id, scope);
|
|
566
|
+
await saveVersionForScope(plugin.id, latestVersion, scope);
|
|
540
567
|
modal.hideModal();
|
|
541
568
|
await collectPluginEnvVars(plugin.name, plugin.marketplace);
|
|
542
569
|
await installPluginSystemDeps(plugin.name, plugin.marketplace);
|
|
@@ -12,12 +12,15 @@ import {
|
|
|
12
12
|
refreshAllMarketplaces,
|
|
13
13
|
clearMarketplaceCache,
|
|
14
14
|
getLocalMarketplacesInfo,
|
|
15
|
+
saveInstalledPluginVersion,
|
|
15
16
|
type PluginInfo,
|
|
16
17
|
} from "../../services/plugin-manager.js";
|
|
17
18
|
import {
|
|
18
19
|
setMcpEnvVar,
|
|
19
20
|
getMcpEnvVars,
|
|
20
21
|
readSettings,
|
|
22
|
+
saveGlobalInstalledPluginVersion,
|
|
23
|
+
saveLocalInstalledPluginVersion,
|
|
21
24
|
} from "../../services/claude-settings.js";
|
|
22
25
|
import { saveProfile } from "../../services/profiles.js";
|
|
23
26
|
import {
|
|
@@ -241,6 +244,27 @@ export function PluginsScreen() {
|
|
|
241
244
|
else if (event.name === "s") handleSaveAsProfile();
|
|
242
245
|
});
|
|
243
246
|
|
|
247
|
+
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Save the installed plugin version to the correct settings file for the scope.
|
|
251
|
+
* The Claude CLI's `plugin install` does NOT update installedPluginVersions,
|
|
252
|
+
* so claudeup must do it after a successful CLI install/update.
|
|
253
|
+
*/
|
|
254
|
+
const saveVersionForScope = async (
|
|
255
|
+
pluginId: string,
|
|
256
|
+
version: string,
|
|
257
|
+
scope: PluginScope,
|
|
258
|
+
): Promise<void> => {
|
|
259
|
+
if (scope === "user") {
|
|
260
|
+
await saveGlobalInstalledPluginVersion(pluginId, version);
|
|
261
|
+
} else if (scope === "local") {
|
|
262
|
+
await saveLocalInstalledPluginVersion(pluginId, version, state.projectPath);
|
|
263
|
+
} else {
|
|
264
|
+
await saveInstalledPluginVersion(pluginId, version, state.projectPath);
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
244
268
|
// ── Action handlers ────────────────────────────────────────────────────────
|
|
245
269
|
|
|
246
270
|
const handleRefresh = async () => {
|
|
@@ -549,8 +573,10 @@ export function PluginsScreen() {
|
|
|
549
573
|
await cliUninstallPlugin(plugin.id, scope, state.projectPath);
|
|
550
574
|
} else if (action === "update") {
|
|
551
575
|
await cliUpdatePlugin(plugin.id, scope);
|
|
576
|
+
await saveVersionForScope(plugin.id, latestVersion, scope);
|
|
552
577
|
} else {
|
|
553
578
|
await cliInstallPlugin(plugin.id, scope);
|
|
579
|
+
await saveVersionForScope(plugin.id, latestVersion, scope);
|
|
554
580
|
modal.hideModal();
|
|
555
581
|
await collectPluginEnvVars(plugin.name, plugin.marketplace);
|
|
556
582
|
await installPluginSystemDeps(plugin.name, plugin.marketplace);
|
|
@@ -576,6 +602,9 @@ export function PluginsScreen() {
|
|
|
576
602
|
modal.loading(`Updating ${plugin.name}...`);
|
|
577
603
|
try {
|
|
578
604
|
await cliUpdatePlugin(plugin.id, scope);
|
|
605
|
+
if (plugin.version) {
|
|
606
|
+
await saveVersionForScope(plugin.id, plugin.version, scope);
|
|
607
|
+
}
|
|
579
608
|
modal.hideModal();
|
|
580
609
|
fetchData();
|
|
581
610
|
} catch (error) {
|
|
@@ -596,6 +625,9 @@ export function PluginsScreen() {
|
|
|
596
625
|
try {
|
|
597
626
|
for (const plugin of updatable) {
|
|
598
627
|
await cliUpdatePlugin(plugin.id, scope);
|
|
628
|
+
if (plugin.version) {
|
|
629
|
+
await saveVersionForScope(plugin.id, plugin.version, scope);
|
|
630
|
+
}
|
|
599
631
|
}
|
|
600
632
|
modal.hideModal();
|
|
601
633
|
fetchData();
|
|
@@ -651,8 +683,10 @@ export function PluginsScreen() {
|
|
|
651
683
|
await cliUninstallPlugin(plugin.id, scope, state.projectPath);
|
|
652
684
|
} else if (action === "update") {
|
|
653
685
|
await cliUpdatePlugin(plugin.id, scope);
|
|
686
|
+
await saveVersionForScope(plugin.id, latestVersion, scope);
|
|
654
687
|
} else {
|
|
655
688
|
await cliInstallPlugin(plugin.id, scope);
|
|
689
|
+
await saveVersionForScope(plugin.id, latestVersion, scope);
|
|
656
690
|
modal.hideModal();
|
|
657
691
|
await collectPluginEnvVars(plugin.name, plugin.marketplace);
|
|
658
692
|
await installPluginSystemDeps(plugin.name, plugin.marketplace);
|