claudeup 3.5.1 → 3.6.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 +1 -1
- package/src/prerunner/index.js +87 -9
- package/src/prerunner/index.ts +116 -9
- package/src/services/claude-cli.js +94 -0
- package/src/services/claude-cli.ts +132 -0
- package/src/ui/components/modals/ConfirmModal.js +1 -1
- package/src/ui/components/modals/ConfirmModal.tsx +1 -0
- package/src/ui/components/modals/InputModal.js +1 -1
- package/src/ui/components/modals/InputModal.tsx +1 -0
- package/src/ui/components/modals/LoadingModal.js +1 -1
- package/src/ui/components/modals/LoadingModal.tsx +1 -0
- package/src/ui/components/modals/MessageModal.js +1 -1
- package/src/ui/components/modals/MessageModal.tsx +1 -0
- package/src/ui/components/modals/ModalContainer.js +2 -2
- package/src/ui/components/modals/ModalContainer.tsx +2 -1
- package/src/ui/components/modals/SelectModal.js +1 -1
- package/src/ui/components/modals/SelectModal.tsx +1 -0
- package/src/ui/screens/PluginsScreen.js +27 -95
- package/src/ui/screens/PluginsScreen.tsx +28 -117
package/package.json
CHANGED
package/src/prerunner/index.js
CHANGED
|
@@ -1,7 +1,78 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
1
4
|
import { UpdateCache } from "../services/update-cache.js";
|
|
2
|
-
import { getAvailablePlugins, clearMarketplaceCache,
|
|
5
|
+
import { getAvailablePlugins, clearMarketplaceCache, } from "../services/plugin-manager.js";
|
|
3
6
|
import { runClaude } from "../services/claude-runner.js";
|
|
4
|
-
import { recoverMarketplaceSettings, migrateMarketplaceRename } from "../services/claude-settings.js";
|
|
7
|
+
import { recoverMarketplaceSettings, migrateMarketplaceRename, getGlobalEnabledPlugins, getEnabledPlugins, getLocalEnabledPlugins, } from "../services/claude-settings.js";
|
|
8
|
+
import { parsePluginId } from "../utils/string-utils.js";
|
|
9
|
+
import { defaultMarketplaces } from "../data/marketplaces.js";
|
|
10
|
+
import { updatePlugin, addMarketplace, isClaudeAvailable, } from "../services/claude-cli.js";
|
|
11
|
+
const MARKETPLACES_DIR = path.join(os.homedir(), ".claude", "plugins", "marketplaces");
|
|
12
|
+
/**
|
|
13
|
+
* Collect all unique marketplace names from enabled plugins across all settings scopes.
|
|
14
|
+
* Returns a Set of marketplace names (e.g., "magus", "claude-plugins-official").
|
|
15
|
+
*/
|
|
16
|
+
async function getReferencedMarketplaces(projectPath) {
|
|
17
|
+
const marketplaceNames = new Set();
|
|
18
|
+
// Collect plugin IDs from all scopes
|
|
19
|
+
const allPluginIds = new Set();
|
|
20
|
+
try {
|
|
21
|
+
const global = await getGlobalEnabledPlugins();
|
|
22
|
+
for (const id of Object.keys(global))
|
|
23
|
+
allPluginIds.add(id);
|
|
24
|
+
}
|
|
25
|
+
catch { /* skip if unreadable */ }
|
|
26
|
+
if (projectPath) {
|
|
27
|
+
try {
|
|
28
|
+
const project = await getEnabledPlugins(projectPath);
|
|
29
|
+
for (const id of Object.keys(project))
|
|
30
|
+
allPluginIds.add(id);
|
|
31
|
+
}
|
|
32
|
+
catch { /* skip if unreadable */ }
|
|
33
|
+
try {
|
|
34
|
+
const local = await getLocalEnabledPlugins(projectPath);
|
|
35
|
+
for (const id of Object.keys(local))
|
|
36
|
+
allPluginIds.add(id);
|
|
37
|
+
}
|
|
38
|
+
catch { /* skip if unreadable */ }
|
|
39
|
+
}
|
|
40
|
+
// Parse marketplace names from plugin IDs
|
|
41
|
+
for (const pluginId of allPluginIds) {
|
|
42
|
+
const parsed = parsePluginId(pluginId);
|
|
43
|
+
if (parsed) {
|
|
44
|
+
marketplaceNames.add(parsed.marketplace);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return marketplaceNames;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Check which referenced marketplaces are missing locally and auto-add them.
|
|
51
|
+
* Only adds marketplaces with known repos (from defaultMarketplaces).
|
|
52
|
+
*/
|
|
53
|
+
async function autoAddMissingMarketplaces(projectPath) {
|
|
54
|
+
const referenced = await getReferencedMarketplaces(projectPath);
|
|
55
|
+
const added = [];
|
|
56
|
+
for (const mpName of referenced) {
|
|
57
|
+
// Check if marketplace directory exists locally
|
|
58
|
+
const mpDir = path.join(MARKETPLACES_DIR, mpName);
|
|
59
|
+
if (await fs.pathExists(mpDir))
|
|
60
|
+
continue;
|
|
61
|
+
// Look up the repo URL from default marketplaces
|
|
62
|
+
const defaultMp = defaultMarketplaces.find((m) => m.name === mpName);
|
|
63
|
+
if (!defaultMp?.source.repo)
|
|
64
|
+
continue;
|
|
65
|
+
try {
|
|
66
|
+
await addMarketplace(defaultMp.source.repo);
|
|
67
|
+
added.push(mpName);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
// Non-fatal: log and continue
|
|
71
|
+
console.warn(`⚠ Failed to auto-add marketplace ${mpName}:`, error instanceof Error ? error.message : "Unknown error");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return added;
|
|
75
|
+
}
|
|
5
76
|
/**
|
|
6
77
|
* Prerun orchestration: Check for updates, apply them, then run claude
|
|
7
78
|
* @param claudeArgs - Arguments to pass to claude CLI
|
|
@@ -19,6 +90,15 @@ export async function prerunClaude(claudeArgs, options = {}) {
|
|
|
19
90
|
if (migTotal > 0) {
|
|
20
91
|
console.log(`✓ Migrated ${migTotal} plugin reference(s) → magus`);
|
|
21
92
|
}
|
|
93
|
+
// STEP 0.5: Auto-add missing marketplaces
|
|
94
|
+
// When plugins reference a marketplace that's not installed locally
|
|
95
|
+
// (e.g., settings synced from another machine), add it automatically.
|
|
96
|
+
if (await isClaudeAvailable()) {
|
|
97
|
+
const addedMarketplaces = await autoAddMissingMarketplaces();
|
|
98
|
+
if (addedMarketplaces.length > 0) {
|
|
99
|
+
console.log(`✓ Auto-added marketplace(s): ${addedMarketplaces.join(", ")}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
22
102
|
// STEP 1: Check if we should update (time-based cache, or forced)
|
|
23
103
|
const shouldUpdate = options.force || (await cache.shouldCheckForUpdates());
|
|
24
104
|
if (options.force) {
|
|
@@ -34,12 +114,12 @@ export async function prerunClaude(claudeArgs, options = {}) {
|
|
|
34
114
|
console.log(`✓ Removed stale marketplaces: ${recovery.removed.join(", ")}`);
|
|
35
115
|
}
|
|
36
116
|
// STEP 2: Clear cache to force fresh plugin info
|
|
37
|
-
// Note: Marketplace updates should be done via Claude Code's /plugin marketplace update
|
|
38
117
|
clearMarketplaceCache();
|
|
39
118
|
// STEP 3: Get updated plugin info (to detect versions)
|
|
40
119
|
const plugins = await getAvailablePlugins();
|
|
41
|
-
// STEP 4: Auto-update enabled plugins
|
|
120
|
+
// STEP 4: Auto-update enabled plugins via claude CLI
|
|
42
121
|
const autoUpdatedPlugins = [];
|
|
122
|
+
const cliAvailable = await isClaudeAvailable();
|
|
43
123
|
for (const plugin of plugins) {
|
|
44
124
|
// Only update if:
|
|
45
125
|
// 1. Plugin is enabled
|
|
@@ -50,11 +130,9 @@ export async function prerunClaude(claudeArgs, options = {}) {
|
|
|
50
130
|
plugin.installedVersion &&
|
|
51
131
|
plugin.version) {
|
|
52
132
|
try {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// 3. Copy plugin files to cache (via copyPluginToCache())
|
|
57
|
-
await saveInstalledPluginVersion(plugin.id, plugin.version);
|
|
133
|
+
if (cliAvailable) {
|
|
134
|
+
await updatePlugin(plugin.id, "user");
|
|
135
|
+
}
|
|
58
136
|
autoUpdatedPlugins.push({
|
|
59
137
|
pluginId: plugin.id,
|
|
60
138
|
oldVersion: plugin.installedVersion,
|
package/src/prerunner/index.ts
CHANGED
|
@@ -1,16 +1,112 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
1
4
|
import { UpdateCache } from "../services/update-cache.js";
|
|
2
5
|
import {
|
|
3
6
|
getAvailablePlugins,
|
|
4
7
|
clearMarketplaceCache,
|
|
5
|
-
saveInstalledPluginVersion,
|
|
6
8
|
} from "../services/plugin-manager.js";
|
|
7
9
|
import { runClaude } from "../services/claude-runner.js";
|
|
8
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
recoverMarketplaceSettings,
|
|
12
|
+
migrateMarketplaceRename,
|
|
13
|
+
getGlobalEnabledPlugins,
|
|
14
|
+
getEnabledPlugins,
|
|
15
|
+
getLocalEnabledPlugins,
|
|
16
|
+
} from "../services/claude-settings.js";
|
|
17
|
+
import { parsePluginId } from "../utils/string-utils.js";
|
|
18
|
+
import { defaultMarketplaces } from "../data/marketplaces.js";
|
|
19
|
+
import {
|
|
20
|
+
updatePlugin,
|
|
21
|
+
addMarketplace,
|
|
22
|
+
isClaudeAvailable,
|
|
23
|
+
} from "../services/claude-cli.js";
|
|
24
|
+
|
|
25
|
+
const MARKETPLACES_DIR = path.join(
|
|
26
|
+
os.homedir(),
|
|
27
|
+
".claude",
|
|
28
|
+
"plugins",
|
|
29
|
+
"marketplaces",
|
|
30
|
+
);
|
|
9
31
|
|
|
10
32
|
export interface PrerunOptions {
|
|
11
33
|
force?: boolean; // Bypass cache and force update check
|
|
12
34
|
}
|
|
13
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Collect all unique marketplace names from enabled plugins across all settings scopes.
|
|
38
|
+
* Returns a Set of marketplace names (e.g., "magus", "claude-plugins-official").
|
|
39
|
+
*/
|
|
40
|
+
async function getReferencedMarketplaces(
|
|
41
|
+
projectPath?: string,
|
|
42
|
+
): Promise<Set<string>> {
|
|
43
|
+
const marketplaceNames = new Set<string>();
|
|
44
|
+
|
|
45
|
+
// Collect plugin IDs from all scopes
|
|
46
|
+
const allPluginIds = new Set<string>();
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const global = await getGlobalEnabledPlugins();
|
|
50
|
+
for (const id of Object.keys(global)) allPluginIds.add(id);
|
|
51
|
+
} catch { /* skip if unreadable */ }
|
|
52
|
+
|
|
53
|
+
if (projectPath) {
|
|
54
|
+
try {
|
|
55
|
+
const project = await getEnabledPlugins(projectPath);
|
|
56
|
+
for (const id of Object.keys(project)) allPluginIds.add(id);
|
|
57
|
+
} catch { /* skip if unreadable */ }
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const local = await getLocalEnabledPlugins(projectPath);
|
|
61
|
+
for (const id of Object.keys(local)) allPluginIds.add(id);
|
|
62
|
+
} catch { /* skip if unreadable */ }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Parse marketplace names from plugin IDs
|
|
66
|
+
for (const pluginId of allPluginIds) {
|
|
67
|
+
const parsed = parsePluginId(pluginId);
|
|
68
|
+
if (parsed) {
|
|
69
|
+
marketplaceNames.add(parsed.marketplace);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return marketplaceNames;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check which referenced marketplaces are missing locally and auto-add them.
|
|
78
|
+
* Only adds marketplaces with known repos (from defaultMarketplaces).
|
|
79
|
+
*/
|
|
80
|
+
async function autoAddMissingMarketplaces(
|
|
81
|
+
projectPath?: string,
|
|
82
|
+
): Promise<string[]> {
|
|
83
|
+
const referenced = await getReferencedMarketplaces(projectPath);
|
|
84
|
+
const added: string[] = [];
|
|
85
|
+
|
|
86
|
+
for (const mpName of referenced) {
|
|
87
|
+
// Check if marketplace directory exists locally
|
|
88
|
+
const mpDir = path.join(MARKETPLACES_DIR, mpName);
|
|
89
|
+
if (await fs.pathExists(mpDir)) continue;
|
|
90
|
+
|
|
91
|
+
// Look up the repo URL from default marketplaces
|
|
92
|
+
const defaultMp = defaultMarketplaces.find((m) => m.name === mpName);
|
|
93
|
+
if (!defaultMp?.source.repo) continue;
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
await addMarketplace(defaultMp.source.repo);
|
|
97
|
+
added.push(mpName);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
// Non-fatal: log and continue
|
|
100
|
+
console.warn(
|
|
101
|
+
`⚠ Failed to auto-add marketplace ${mpName}:`,
|
|
102
|
+
error instanceof Error ? error.message : "Unknown error",
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return added;
|
|
108
|
+
}
|
|
109
|
+
|
|
14
110
|
/**
|
|
15
111
|
* Prerun orchestration: Check for updates, apply them, then run claude
|
|
16
112
|
* @param claudeArgs - Arguments to pass to claude CLI
|
|
@@ -33,6 +129,18 @@ export async function prerunClaude(
|
|
|
33
129
|
console.log(`✓ Migrated ${migTotal} plugin reference(s) → magus`);
|
|
34
130
|
}
|
|
35
131
|
|
|
132
|
+
// STEP 0.5: Auto-add missing marketplaces
|
|
133
|
+
// When plugins reference a marketplace that's not installed locally
|
|
134
|
+
// (e.g., settings synced from another machine), add it automatically.
|
|
135
|
+
if (await isClaudeAvailable()) {
|
|
136
|
+
const addedMarketplaces = await autoAddMissingMarketplaces();
|
|
137
|
+
if (addedMarketplaces.length > 0) {
|
|
138
|
+
console.log(
|
|
139
|
+
`✓ Auto-added marketplace(s): ${addedMarketplaces.join(", ")}`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
36
144
|
// STEP 1: Check if we should update (time-based cache, or forced)
|
|
37
145
|
const shouldUpdate = options.force || (await cache.shouldCheckForUpdates());
|
|
38
146
|
|
|
@@ -55,19 +163,20 @@ export async function prerunClaude(
|
|
|
55
163
|
}
|
|
56
164
|
|
|
57
165
|
// STEP 2: Clear cache to force fresh plugin info
|
|
58
|
-
// Note: Marketplace updates should be done via Claude Code's /plugin marketplace update
|
|
59
166
|
clearMarketplaceCache();
|
|
60
167
|
|
|
61
168
|
// STEP 3: Get updated plugin info (to detect versions)
|
|
62
169
|
const plugins = await getAvailablePlugins();
|
|
63
170
|
|
|
64
|
-
// STEP 4: Auto-update enabled plugins
|
|
171
|
+
// STEP 4: Auto-update enabled plugins via claude CLI
|
|
65
172
|
const autoUpdatedPlugins: Array<{
|
|
66
173
|
pluginId: string;
|
|
67
174
|
oldVersion: string;
|
|
68
175
|
newVersion: string;
|
|
69
176
|
}> = [];
|
|
70
177
|
|
|
178
|
+
const cliAvailable = await isClaudeAvailable();
|
|
179
|
+
|
|
71
180
|
for (const plugin of plugins) {
|
|
72
181
|
// Only update if:
|
|
73
182
|
// 1. Plugin is enabled
|
|
@@ -80,11 +189,9 @@ export async function prerunClaude(
|
|
|
80
189
|
plugin.version
|
|
81
190
|
) {
|
|
82
191
|
try {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// 3. Copy plugin files to cache (via copyPluginToCache())
|
|
87
|
-
await saveInstalledPluginVersion(plugin.id, plugin.version);
|
|
192
|
+
if (cliAvailable) {
|
|
193
|
+
await updatePlugin(plugin.id, "user");
|
|
194
|
+
}
|
|
88
195
|
|
|
89
196
|
autoUpdatedPlugins.push({
|
|
90
197
|
pluginId: plugin.id,
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude CLI wrapper service
|
|
3
|
+
*
|
|
4
|
+
* Delegates plugin and marketplace management to the `claude` CLI
|
|
5
|
+
* instead of manipulating settings JSON files directly.
|
|
6
|
+
*
|
|
7
|
+
* CLI commands used:
|
|
8
|
+
* - claude plugin install/uninstall/enable/disable/update
|
|
9
|
+
* - claude marketplace add
|
|
10
|
+
*/
|
|
11
|
+
import { execFile } from "node:child_process";
|
|
12
|
+
import { promisify } from "node:util";
|
|
13
|
+
import { which } from "../utils/command-utils.js";
|
|
14
|
+
const execFileAsync = promisify(execFile);
|
|
15
|
+
/**
|
|
16
|
+
* Get the path to the claude CLI binary
|
|
17
|
+
* @throws Error if claude CLI is not found in PATH
|
|
18
|
+
*/
|
|
19
|
+
async function getClaudePath() {
|
|
20
|
+
const claudePath = await which("claude");
|
|
21
|
+
if (!claudePath) {
|
|
22
|
+
throw new Error("claude CLI not found in PATH. Install with: npm install -g @anthropic-ai/claude-code");
|
|
23
|
+
}
|
|
24
|
+
return claudePath;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Execute a claude CLI command and return stdout
|
|
28
|
+
* @param args - Arguments to pass to claude
|
|
29
|
+
* @param timeoutMs - Timeout in milliseconds (default: 30s)
|
|
30
|
+
* @returns stdout from the command
|
|
31
|
+
*/
|
|
32
|
+
async function execClaude(args, timeoutMs = 30000) {
|
|
33
|
+
const claudePath = await getClaudePath();
|
|
34
|
+
try {
|
|
35
|
+
const { stdout } = await execFileAsync(claudePath, args, {
|
|
36
|
+
timeout: timeoutMs,
|
|
37
|
+
});
|
|
38
|
+
return stdout.trim();
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
const execError = error;
|
|
42
|
+
const msg = execError.stderr?.trim() ||
|
|
43
|
+
execError.stdout?.trim() ||
|
|
44
|
+
execError.message ||
|
|
45
|
+
"claude command failed";
|
|
46
|
+
throw new Error(msg);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Install a plugin using claude CLI
|
|
51
|
+
* Handles enabling + version tracking + cache copy in one shot
|
|
52
|
+
*/
|
|
53
|
+
export async function installPlugin(pluginId, scope = "user") {
|
|
54
|
+
await execClaude(["plugin", "install", pluginId, "--scope", scope]);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Uninstall a plugin using claude CLI
|
|
58
|
+
* Handles disabling + version removal in one shot
|
|
59
|
+
*/
|
|
60
|
+
export async function uninstallPlugin(pluginId, scope = "user") {
|
|
61
|
+
await execClaude(["plugin", "uninstall", pluginId, "--scope", scope]);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Enable a previously disabled plugin
|
|
65
|
+
*/
|
|
66
|
+
export async function enablePlugin(pluginId, scope = "user") {
|
|
67
|
+
await execClaude(["plugin", "enable", pluginId, "--scope", scope]);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Disable a plugin without uninstalling it
|
|
71
|
+
*/
|
|
72
|
+
export async function disablePlugin(pluginId, scope = "user") {
|
|
73
|
+
await execClaude(["plugin", "disable", pluginId, "--scope", scope]);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Update a plugin to the latest version
|
|
77
|
+
*/
|
|
78
|
+
export async function updatePlugin(pluginId, scope = "user") {
|
|
79
|
+
await execClaude(["plugin", "update", pluginId, "--scope", scope], 60000);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Add a marketplace by GitHub repo (e.g., "MadAppGang/magus")
|
|
83
|
+
* Uses longer timeout since this involves cloning a git repo
|
|
84
|
+
*/
|
|
85
|
+
export async function addMarketplace(repo) {
|
|
86
|
+
await execClaude(["plugin", "marketplace", "add", repo], 60000);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Check if the claude CLI is available
|
|
90
|
+
* @returns true if claude CLI is found in PATH
|
|
91
|
+
*/
|
|
92
|
+
export async function isClaudeAvailable() {
|
|
93
|
+
return (await which("claude")) !== null;
|
|
94
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude CLI wrapper service
|
|
3
|
+
*
|
|
4
|
+
* Delegates plugin and marketplace management to the `claude` CLI
|
|
5
|
+
* instead of manipulating settings JSON files directly.
|
|
6
|
+
*
|
|
7
|
+
* CLI commands used:
|
|
8
|
+
* - claude plugin install/uninstall/enable/disable/update
|
|
9
|
+
* - claude marketplace add
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { execFile } from "node:child_process";
|
|
13
|
+
import { promisify } from "node:util";
|
|
14
|
+
import { which } from "../utils/command-utils.js";
|
|
15
|
+
|
|
16
|
+
const execFileAsync = promisify(execFile);
|
|
17
|
+
|
|
18
|
+
export type PluginScope = "user" | "project" | "local";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get the path to the claude CLI binary
|
|
22
|
+
* @throws Error if claude CLI is not found in PATH
|
|
23
|
+
*/
|
|
24
|
+
async function getClaudePath(): Promise<string> {
|
|
25
|
+
const claudePath = await which("claude");
|
|
26
|
+
if (!claudePath) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
"claude CLI not found in PATH. Install with: npm install -g @anthropic-ai/claude-code",
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return claudePath;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Execute a claude CLI command and return stdout
|
|
36
|
+
* @param args - Arguments to pass to claude
|
|
37
|
+
* @param timeoutMs - Timeout in milliseconds (default: 30s)
|
|
38
|
+
* @returns stdout from the command
|
|
39
|
+
*/
|
|
40
|
+
async function execClaude(
|
|
41
|
+
args: string[],
|
|
42
|
+
timeoutMs = 30000,
|
|
43
|
+
): Promise<string> {
|
|
44
|
+
const claudePath = await getClaudePath();
|
|
45
|
+
try {
|
|
46
|
+
const { stdout } = await execFileAsync(claudePath, args, {
|
|
47
|
+
timeout: timeoutMs,
|
|
48
|
+
});
|
|
49
|
+
return stdout.trim();
|
|
50
|
+
} catch (error: unknown) {
|
|
51
|
+
const execError = error as {
|
|
52
|
+
stderr?: string;
|
|
53
|
+
stdout?: string;
|
|
54
|
+
message?: string;
|
|
55
|
+
code?: number;
|
|
56
|
+
};
|
|
57
|
+
const msg =
|
|
58
|
+
execError.stderr?.trim() ||
|
|
59
|
+
execError.stdout?.trim() ||
|
|
60
|
+
execError.message ||
|
|
61
|
+
"claude command failed";
|
|
62
|
+
throw new Error(msg);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Install a plugin using claude CLI
|
|
68
|
+
* Handles enabling + version tracking + cache copy in one shot
|
|
69
|
+
*/
|
|
70
|
+
export async function installPlugin(
|
|
71
|
+
pluginId: string,
|
|
72
|
+
scope: PluginScope = "user",
|
|
73
|
+
): Promise<void> {
|
|
74
|
+
await execClaude(["plugin", "install", pluginId, "--scope", scope]);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Uninstall a plugin using claude CLI
|
|
79
|
+
* Handles disabling + version removal in one shot
|
|
80
|
+
*/
|
|
81
|
+
export async function uninstallPlugin(
|
|
82
|
+
pluginId: string,
|
|
83
|
+
scope: PluginScope = "user",
|
|
84
|
+
): Promise<void> {
|
|
85
|
+
await execClaude(["plugin", "uninstall", pluginId, "--scope", scope]);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Enable a previously disabled plugin
|
|
90
|
+
*/
|
|
91
|
+
export async function enablePlugin(
|
|
92
|
+
pluginId: string,
|
|
93
|
+
scope: PluginScope = "user",
|
|
94
|
+
): Promise<void> {
|
|
95
|
+
await execClaude(["plugin", "enable", pluginId, "--scope", scope]);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Disable a plugin without uninstalling it
|
|
100
|
+
*/
|
|
101
|
+
export async function disablePlugin(
|
|
102
|
+
pluginId: string,
|
|
103
|
+
scope: PluginScope = "user",
|
|
104
|
+
): Promise<void> {
|
|
105
|
+
await execClaude(["plugin", "disable", pluginId, "--scope", scope]);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Update a plugin to the latest version
|
|
110
|
+
*/
|
|
111
|
+
export async function updatePlugin(
|
|
112
|
+
pluginId: string,
|
|
113
|
+
scope: PluginScope = "user",
|
|
114
|
+
): Promise<void> {
|
|
115
|
+
await execClaude(["plugin", "update", pluginId, "--scope", scope], 60000);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Add a marketplace by GitHub repo (e.g., "MadAppGang/magus")
|
|
120
|
+
* Uses longer timeout since this involves cloning a git repo
|
|
121
|
+
*/
|
|
122
|
+
export async function addMarketplace(repo: string): Promise<void> {
|
|
123
|
+
await execClaude(["plugin", "marketplace", "add", repo], 60000);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Check if the claude CLI is available
|
|
128
|
+
* @returns true if claude CLI is found in PATH
|
|
129
|
+
*/
|
|
130
|
+
export async function isClaudeAvailable(): Promise<boolean> {
|
|
131
|
+
return (await which("claude")) !== null;
|
|
132
|
+
}
|
|
@@ -9,6 +9,6 @@ export function ConfirmModal({ title, message, onConfirm, onCancel, }) {
|
|
|
9
9
|
onCancel();
|
|
10
10
|
}
|
|
11
11
|
});
|
|
12
|
-
return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor: "yellow", paddingLeft: 2, paddingRight: 2, paddingTop: 1, paddingBottom: 1, width: 60, children: [_jsx("text", { children: _jsx("strong", { children: title }) }), _jsx("box", { marginTop: 1, marginBottom: 1, children: _jsx("text", { children: message }) }), _jsx("box", { children: _jsxs("text", { children: [_jsx("span", { fg: "green", children: "[Y]" }), _jsx("span", { children: "es " }), _jsx("span", { fg: "red", children: "[N]" }), _jsx("span", { children: "o" })] }) })] }));
|
|
12
|
+
return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor: "yellow", backgroundColor: "#1a1a2e", paddingLeft: 2, paddingRight: 2, paddingTop: 1, paddingBottom: 1, width: 60, children: [_jsx("text", { children: _jsx("strong", { children: title }) }), _jsx("box", { marginTop: 1, marginBottom: 1, children: _jsx("text", { children: message }) }), _jsx("box", { children: _jsxs("text", { children: [_jsx("span", { fg: "green", children: "[Y]" }), _jsx("span", { children: "es " }), _jsx("span", { fg: "red", children: "[N]" }), _jsx("span", { children: "o" })] }) })] }));
|
|
13
13
|
}
|
|
14
14
|
export default ConfirmModal;
|
|
@@ -11,6 +11,6 @@ export function InputModal({ title, label, defaultValue = "", onSubmit, onCancel
|
|
|
11
11
|
onCancel();
|
|
12
12
|
}
|
|
13
13
|
});
|
|
14
|
-
return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor: "cyan", paddingLeft: 2, paddingRight: 2, paddingTop: 1, paddingBottom: 1, width: 60, children: [_jsx("text", { children: _jsx("strong", { children: title }) }), _jsx("box", { marginTop: 1, marginBottom: 1, children: _jsx("text", { children: label }) }), _jsx("input", { value: value, onChange: setValue, focused: true, width: 54 }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "#666666", children: "Enter to confirm \u2022 Escape to cancel" }) })] }));
|
|
14
|
+
return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor: "cyan", backgroundColor: "#1a1a2e", paddingLeft: 2, paddingRight: 2, paddingTop: 1, paddingBottom: 1, width: 60, children: [_jsx("text", { children: _jsx("strong", { children: title }) }), _jsx("box", { marginTop: 1, marginBottom: 1, children: _jsx("text", { children: label }) }), _jsx("input", { value: value, onChange: setValue, focused: true, width: 54 }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "#666666", children: "Enter to confirm \u2022 Escape to cancel" }) })] }));
|
|
15
15
|
}
|
|
16
16
|
export default InputModal;
|
|
@@ -9,6 +9,6 @@ export function LoadingModal({ message, }) {
|
|
|
9
9
|
}, 80);
|
|
10
10
|
return () => clearInterval(interval);
|
|
11
11
|
}, []);
|
|
12
|
-
return (_jsxs("box", { flexDirection: "row", border: true, borderStyle: "rounded", borderColor: "cyan", paddingLeft: 2, paddingRight: 2, paddingTop: 1, paddingBottom: 1, children: [_jsx("text", { fg: "cyan", children: SPINNER_FRAMES[frame] }), _jsxs("text", { children: [" ", message] })] }));
|
|
12
|
+
return (_jsxs("box", { flexDirection: "row", border: true, borderStyle: "rounded", borderColor: "cyan", backgroundColor: "#1a1a2e", paddingLeft: 2, paddingRight: 2, paddingTop: 1, paddingBottom: 1, children: [_jsx("text", { fg: "cyan", children: SPINNER_FRAMES[frame] }), _jsxs("text", { children: [" ", message] })] }));
|
|
13
13
|
}
|
|
14
14
|
export default LoadingModal;
|
|
@@ -11,6 +11,6 @@ export function MessageModal({ title, message, variant, onDismiss, }) {
|
|
|
11
11
|
// Any key dismisses
|
|
12
12
|
onDismiss();
|
|
13
13
|
});
|
|
14
|
-
return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor: config.color, paddingLeft: 2, paddingRight: 2, paddingTop: 1, paddingBottom: 1, width: 60, children: [_jsxs("box", { children: [_jsxs("text", { fg: config.color, children: [config.icon, " "] }), _jsx("text", { children: _jsx("strong", { children: title }) })] }), _jsx("box", { marginTop: 1, marginBottom: 1, children: _jsx("text", { children: message }) }), _jsx("box", { children: _jsx("text", { fg: "#666666", children: "Press any key to continue" }) })] }));
|
|
14
|
+
return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor: config.color, backgroundColor: "#1a1a2e", paddingLeft: 2, paddingRight: 2, paddingTop: 1, paddingBottom: 1, width: 60, children: [_jsxs("box", { children: [_jsxs("text", { fg: config.color, children: [config.icon, " "] }), _jsx("text", { children: _jsx("strong", { children: title }) })] }), _jsx("box", { marginTop: 1, marginBottom: 1, children: _jsx("text", { children: message }) }), _jsx("box", { children: _jsx("text", { fg: "#666666", children: "Press any key to continue" }) })] }));
|
|
15
15
|
}
|
|
16
16
|
export default MessageModal;
|
|
@@ -50,7 +50,7 @@ export function ModalContainer() {
|
|
|
50
50
|
return null;
|
|
51
51
|
}
|
|
52
52
|
};
|
|
53
|
-
// Center the modal on screen
|
|
54
|
-
return (_jsx("box", { position: "absolute", width: "100%", height: "100%", justifyContent: "center", alignItems: "center", children: renderModal() }));
|
|
53
|
+
// Center the modal on screen with opaque backdrop
|
|
54
|
+
return (_jsx("box", { position: "absolute", width: "100%", height: "100%", justifyContent: "center", alignItems: "center", backgroundColor: "#1a1a2e", children: renderModal() }));
|
|
55
55
|
}
|
|
56
56
|
export default ModalContainer;
|
|
@@ -87,7 +87,7 @@ export function ModalContainer() {
|
|
|
87
87
|
}
|
|
88
88
|
};
|
|
89
89
|
|
|
90
|
-
// Center the modal on screen
|
|
90
|
+
// Center the modal on screen with opaque backdrop
|
|
91
91
|
return (
|
|
92
92
|
<box
|
|
93
93
|
position="absolute"
|
|
@@ -95,6 +95,7 @@ export function ModalContainer() {
|
|
|
95
95
|
height="100%"
|
|
96
96
|
justifyContent="center"
|
|
97
97
|
alignItems="center"
|
|
98
|
+
backgroundColor="#1a1a2e"
|
|
98
99
|
>
|
|
99
100
|
{renderModal()}
|
|
100
101
|
</box>
|
|
@@ -17,7 +17,7 @@ export function SelectModal({ title, message, options, onSelect, onCancel, }) {
|
|
|
17
17
|
setSelectedIndex((prev) => Math.min(options.length - 1, prev + 1));
|
|
18
18
|
}
|
|
19
19
|
});
|
|
20
|
-
return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor: "cyan", paddingLeft: 2, paddingRight: 2, paddingTop: 1, paddingBottom: 1, width: 50, children: [_jsx("text", { children: _jsx("strong", { children: title }) }), _jsx("box", { marginTop: 1, marginBottom: 1, children: _jsx("text", { children: message }) }), _jsx("box", { flexDirection: "column", children: options.map((option, idx) => {
|
|
20
|
+
return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor: "cyan", backgroundColor: "#1a1a2e", paddingLeft: 2, paddingRight: 2, paddingTop: 1, paddingBottom: 1, width: 50, children: [_jsx("text", { children: _jsx("strong", { children: title }) }), _jsx("box", { marginTop: 1, marginBottom: 1, children: _jsx("text", { children: message }) }), _jsx("box", { flexDirection: "column", children: options.map((option, idx) => {
|
|
21
21
|
const isSelected = idx === selectedIndex;
|
|
22
22
|
const label = isSelected ? `> ${option.label}` : ` ${option.label}`;
|
|
23
23
|
return (_jsxs("text", { fg: isSelected ? "cyan" : "#666666", children: [isSelected && _jsx("strong", { children: label }), !isSelected && label] }, option.value));
|
|
@@ -8,8 +8,9 @@ import { CategoryHeader } from "../components/CategoryHeader.js";
|
|
|
8
8
|
import { ScrollableList } from "../components/ScrollableList.js";
|
|
9
9
|
import { fuzzyFilter, highlightMatches } from "../../utils/fuzzy-search.js";
|
|
10
10
|
import { getAllMarketplaces } from "../../data/marketplaces.js";
|
|
11
|
-
import { getAvailablePlugins, refreshAllMarketplaces, clearMarketplaceCache,
|
|
12
|
-
import {
|
|
11
|
+
import { getAvailablePlugins, refreshAllMarketplaces, clearMarketplaceCache, getLocalMarketplacesInfo, } from "../../services/plugin-manager.js";
|
|
12
|
+
import { setMcpEnvVar, getMcpEnvVars, } from "../../services/claude-settings.js";
|
|
13
|
+
import { installPlugin as cliInstallPlugin, uninstallPlugin as cliUninstallPlugin, updatePlugin as cliUpdatePlugin, } from "../../services/claude-cli.js";
|
|
13
14
|
import { getPluginEnvRequirements, getPluginSourcePath, } from "../../services/plugin-mcp-config.js";
|
|
14
15
|
export function PluginsScreen() {
|
|
15
16
|
const { state, dispatch } = useApp();
|
|
@@ -263,10 +264,10 @@ export function PluginsScreen() {
|
|
|
263
264
|
};
|
|
264
265
|
const handleShowAddMarketplaceInstructions = async () => {
|
|
265
266
|
await modal.message("Add Marketplace", "To add a marketplace, run this command in your terminal:\n\n" +
|
|
266
|
-
" claude marketplace add owner/repo\n\n" +
|
|
267
|
+
" claude plugin marketplace add owner/repo\n\n" +
|
|
267
268
|
"Examples:\n" +
|
|
268
|
-
" claude marketplace add MadAppGang/magus\n" +
|
|
269
|
-
" claude marketplace add anthropics/claude-plugins-official\n\n" +
|
|
269
|
+
" claude plugin marketplace add MadAppGang/magus\n" +
|
|
270
|
+
" claude plugin marketplace add anthropics/claude-plugins-official\n\n" +
|
|
270
271
|
"Auto-update is enabled by default for new marketplaces.\n\n" +
|
|
271
272
|
"After adding, refresh claudeup with 'r' to see the new marketplace.", "info");
|
|
272
273
|
};
|
|
@@ -377,7 +378,7 @@ export function PluginsScreen() {
|
|
|
377
378
|
else {
|
|
378
379
|
// Show add marketplace instructions
|
|
379
380
|
await modal.message(`Add ${mp.displayName}?`, `To add this marketplace, run in your terminal:\n\n` +
|
|
380
|
-
` claude marketplace add ${mp.source.repo || mp.name}\n\n` +
|
|
381
|
+
` claude plugin marketplace add ${mp.source.repo || mp.name}\n\n` +
|
|
381
382
|
`Auto-update is enabled by default.\n\n` +
|
|
382
383
|
`After adding, refresh claudeup with 'r' to see it.`, "info");
|
|
383
384
|
}
|
|
@@ -454,40 +455,18 @@ export function PluginsScreen() {
|
|
|
454
455
|
: `Uninstalling from ${scopeLabel}`;
|
|
455
456
|
modal.loading(`${actionLabel}...`);
|
|
456
457
|
try {
|
|
458
|
+
const scope = scopeValue;
|
|
457
459
|
if (action === "uninstall") {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
}
|
|
463
|
-
else if (scopeValue === "project") {
|
|
464
|
-
await enablePlugin(plugin.id, false, state.projectPath);
|
|
465
|
-
await removeInstalledPluginVersion(plugin.id, state.projectPath);
|
|
466
|
-
}
|
|
467
|
-
else {
|
|
468
|
-
await enableLocalPlugin(plugin.id, false, state.projectPath);
|
|
469
|
-
await removeLocalInstalledPluginVersion(plugin.id, state.projectPath);
|
|
470
|
-
}
|
|
460
|
+
await cliUninstallPlugin(plugin.id, scope);
|
|
461
|
+
}
|
|
462
|
+
else if (action === "update") {
|
|
463
|
+
await cliUpdatePlugin(plugin.id, scope);
|
|
471
464
|
}
|
|
472
465
|
else {
|
|
473
|
-
|
|
474
|
-
if (scopeValue === "user") {
|
|
475
|
-
await enableGlobalPlugin(plugin.id, true);
|
|
476
|
-
await saveGlobalInstalledPluginVersion(plugin.id, latestVersion);
|
|
477
|
-
}
|
|
478
|
-
else if (scopeValue === "project") {
|
|
479
|
-
await enablePlugin(plugin.id, true, state.projectPath);
|
|
480
|
-
await saveInstalledPluginVersion(plugin.id, latestVersion, state.projectPath);
|
|
481
|
-
}
|
|
482
|
-
else {
|
|
483
|
-
await enableLocalPlugin(plugin.id, true, state.projectPath);
|
|
484
|
-
await saveLocalInstalledPluginVersion(plugin.id, latestVersion, state.projectPath);
|
|
485
|
-
}
|
|
466
|
+
await cliInstallPlugin(plugin.id, scope);
|
|
486
467
|
// On fresh install, prompt for MCP server env vars if needed
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
await collectPluginEnvVars(plugin.name, plugin.marketplace);
|
|
490
|
-
}
|
|
468
|
+
modal.hideModal();
|
|
469
|
+
await collectPluginEnvVars(plugin.name, plugin.marketplace);
|
|
491
470
|
}
|
|
492
471
|
if (action !== "install") {
|
|
493
472
|
modal.hideModal();
|
|
@@ -505,16 +484,10 @@ export function PluginsScreen() {
|
|
|
505
484
|
if (!item || item.type !== "plugin" || !item.plugin?.hasUpdate)
|
|
506
485
|
return;
|
|
507
486
|
const plugin = item.plugin;
|
|
508
|
-
const
|
|
487
|
+
const scope = pluginsState.scope === "global" ? "user" : "project";
|
|
509
488
|
modal.loading(`Updating ${plugin.name}...`);
|
|
510
489
|
try {
|
|
511
|
-
|
|
512
|
-
if (isGlobal) {
|
|
513
|
-
await saveGlobalInstalledPluginVersion(plugin.id, versionToSave);
|
|
514
|
-
}
|
|
515
|
-
else {
|
|
516
|
-
await saveInstalledPluginVersion(plugin.id, versionToSave, state.projectPath);
|
|
517
|
-
}
|
|
490
|
+
await cliUpdatePlugin(plugin.id, scope);
|
|
518
491
|
modal.hideModal();
|
|
519
492
|
fetchData();
|
|
520
493
|
}
|
|
@@ -529,17 +502,11 @@ export function PluginsScreen() {
|
|
|
529
502
|
const updatable = pluginsState.plugins.data.filter((p) => p.hasUpdate);
|
|
530
503
|
if (updatable.length === 0)
|
|
531
504
|
return;
|
|
532
|
-
const
|
|
505
|
+
const scope = pluginsState.scope === "global" ? "user" : "project";
|
|
533
506
|
modal.loading(`Updating ${updatable.length} plugin(s)...`);
|
|
534
507
|
try {
|
|
535
508
|
for (const plugin of updatable) {
|
|
536
|
-
|
|
537
|
-
if (isGlobal) {
|
|
538
|
-
await saveGlobalInstalledPluginVersion(plugin.id, versionToSave);
|
|
539
|
-
}
|
|
540
|
-
else {
|
|
541
|
-
await saveInstalledPluginVersion(plugin.id, versionToSave, state.projectPath);
|
|
542
|
-
}
|
|
509
|
+
await cliUpdatePlugin(plugin.id, scope);
|
|
543
510
|
}
|
|
544
511
|
modal.hideModal();
|
|
545
512
|
fetchData();
|
|
@@ -589,39 +556,16 @@ export function PluginsScreen() {
|
|
|
589
556
|
modal.loading(`${actionLabel}...`);
|
|
590
557
|
try {
|
|
591
558
|
if (action === "uninstall") {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
}
|
|
597
|
-
else if (scope === "project") {
|
|
598
|
-
await enablePlugin(plugin.id, false, state.projectPath);
|
|
599
|
-
await removeInstalledPluginVersion(plugin.id, state.projectPath);
|
|
600
|
-
}
|
|
601
|
-
else {
|
|
602
|
-
await enableLocalPlugin(plugin.id, false, state.projectPath);
|
|
603
|
-
await removeLocalInstalledPluginVersion(plugin.id, state.projectPath);
|
|
604
|
-
}
|
|
559
|
+
await cliUninstallPlugin(plugin.id, scope);
|
|
560
|
+
}
|
|
561
|
+
else if (action === "update") {
|
|
562
|
+
await cliUpdatePlugin(plugin.id, scope);
|
|
605
563
|
}
|
|
606
564
|
else {
|
|
607
|
-
|
|
608
|
-
if (scope === "user") {
|
|
609
|
-
await enableGlobalPlugin(plugin.id, true);
|
|
610
|
-
await saveGlobalInstalledPluginVersion(plugin.id, latestVersion);
|
|
611
|
-
}
|
|
612
|
-
else if (scope === "project") {
|
|
613
|
-
await enablePlugin(plugin.id, true, state.projectPath);
|
|
614
|
-
await saveInstalledPluginVersion(plugin.id, latestVersion, state.projectPath);
|
|
615
|
-
}
|
|
616
|
-
else {
|
|
617
|
-
await enableLocalPlugin(plugin.id, true, state.projectPath);
|
|
618
|
-
await saveLocalInstalledPluginVersion(plugin.id, latestVersion, state.projectPath);
|
|
619
|
-
}
|
|
565
|
+
await cliInstallPlugin(plugin.id, scope);
|
|
620
566
|
// On fresh install, prompt for MCP server env vars if needed
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
await collectPluginEnvVars(plugin.name, plugin.marketplace);
|
|
624
|
-
}
|
|
567
|
+
modal.hideModal();
|
|
568
|
+
await collectPluginEnvVars(plugin.name, plugin.marketplace);
|
|
625
569
|
}
|
|
626
570
|
if (action !== "install") {
|
|
627
571
|
modal.hideModal();
|
|
@@ -667,19 +611,7 @@ export function PluginsScreen() {
|
|
|
667
611
|
return; // Cancelled
|
|
668
612
|
modal.loading(`Uninstalling ${plugin.name}...`);
|
|
669
613
|
try {
|
|
670
|
-
|
|
671
|
-
await enableGlobalPlugin(plugin.id, false);
|
|
672
|
-
await removeGlobalInstalledPluginVersion(plugin.id);
|
|
673
|
-
}
|
|
674
|
-
else if (scopeValue === "project") {
|
|
675
|
-
await enablePlugin(plugin.id, false, state.projectPath);
|
|
676
|
-
await removeInstalledPluginVersion(plugin.id, state.projectPath);
|
|
677
|
-
}
|
|
678
|
-
else {
|
|
679
|
-
// local scope
|
|
680
|
-
await enableLocalPlugin(plugin.id, false, state.projectPath);
|
|
681
|
-
await removeLocalInstalledPluginVersion(plugin.id, state.projectPath);
|
|
682
|
-
}
|
|
614
|
+
await cliUninstallPlugin(plugin.id, scopeValue);
|
|
683
615
|
modal.hideModal();
|
|
684
616
|
fetchData();
|
|
685
617
|
}
|
|
@@ -11,22 +11,19 @@ import {
|
|
|
11
11
|
getAvailablePlugins,
|
|
12
12
|
refreshAllMarketplaces,
|
|
13
13
|
clearMarketplaceCache,
|
|
14
|
-
saveInstalledPluginVersion,
|
|
15
|
-
removeInstalledPluginVersion,
|
|
16
14
|
getLocalMarketplacesInfo,
|
|
17
15
|
type PluginInfo,
|
|
18
16
|
} from "../../services/plugin-manager.js";
|
|
19
17
|
import {
|
|
20
|
-
enablePlugin,
|
|
21
|
-
enableGlobalPlugin,
|
|
22
|
-
enableLocalPlugin,
|
|
23
|
-
saveGlobalInstalledPluginVersion,
|
|
24
|
-
removeGlobalInstalledPluginVersion,
|
|
25
|
-
saveLocalInstalledPluginVersion,
|
|
26
|
-
removeLocalInstalledPluginVersion,
|
|
27
18
|
setMcpEnvVar,
|
|
28
19
|
getMcpEnvVars,
|
|
29
20
|
} from "../../services/claude-settings.js";
|
|
21
|
+
import {
|
|
22
|
+
installPlugin as cliInstallPlugin,
|
|
23
|
+
uninstallPlugin as cliUninstallPlugin,
|
|
24
|
+
updatePlugin as cliUpdatePlugin,
|
|
25
|
+
type PluginScope,
|
|
26
|
+
} from "../../services/claude-cli.js";
|
|
30
27
|
import {
|
|
31
28
|
getPluginEnvRequirements,
|
|
32
29
|
getPluginSourcePath,
|
|
@@ -342,10 +339,10 @@ export function PluginsScreen() {
|
|
|
342
339
|
await modal.message(
|
|
343
340
|
"Add Marketplace",
|
|
344
341
|
"To add a marketplace, run this command in your terminal:\n\n" +
|
|
345
|
-
" claude marketplace add owner/repo\n\n" +
|
|
342
|
+
" claude plugin marketplace add owner/repo\n\n" +
|
|
346
343
|
"Examples:\n" +
|
|
347
|
-
" claude marketplace add MadAppGang/magus\n" +
|
|
348
|
-
" claude marketplace add anthropics/claude-plugins-official\n\n" +
|
|
344
|
+
" claude plugin marketplace add MadAppGang/magus\n" +
|
|
345
|
+
" claude plugin marketplace add anthropics/claude-plugins-official\n\n" +
|
|
349
346
|
"Auto-update is enabled by default for new marketplaces.\n\n" +
|
|
350
347
|
"After adding, refresh claudeup with 'r' to see the new marketplace.",
|
|
351
348
|
"info",
|
|
@@ -500,7 +497,7 @@ export function PluginsScreen() {
|
|
|
500
497
|
await modal.message(
|
|
501
498
|
`Add ${mp.displayName}?`,
|
|
502
499
|
`To add this marketplace, run in your terminal:\n\n` +
|
|
503
|
-
` claude marketplace add ${mp.source.repo || mp.name}\n\n` +
|
|
500
|
+
` claude plugin marketplace add ${mp.source.repo || mp.name}\n\n` +
|
|
504
501
|
`Auto-update is enabled by default.\n\n` +
|
|
505
502
|
`After adding, refresh claudeup with 'r' to see it.`,
|
|
506
503
|
"info",
|
|
@@ -596,47 +593,17 @@ export function PluginsScreen() {
|
|
|
596
593
|
modal.loading(`${actionLabel}...`);
|
|
597
594
|
|
|
598
595
|
try {
|
|
596
|
+
const scope = scopeValue as PluginScope;
|
|
599
597
|
if (action === "uninstall") {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
await removeGlobalInstalledPluginVersion(plugin.id);
|
|
604
|
-
} else if (scopeValue === "project") {
|
|
605
|
-
await enablePlugin(plugin.id, false, state.projectPath);
|
|
606
|
-
await removeInstalledPluginVersion(plugin.id, state.projectPath);
|
|
607
|
-
} else {
|
|
608
|
-
await enableLocalPlugin(plugin.id, false, state.projectPath);
|
|
609
|
-
await removeLocalInstalledPluginVersion(
|
|
610
|
-
plugin.id,
|
|
611
|
-
state.projectPath,
|
|
612
|
-
);
|
|
613
|
-
}
|
|
598
|
+
await cliUninstallPlugin(plugin.id, scope);
|
|
599
|
+
} else if (action === "update") {
|
|
600
|
+
await cliUpdatePlugin(plugin.id, scope);
|
|
614
601
|
} else {
|
|
615
|
-
|
|
616
|
-
if (scopeValue === "user") {
|
|
617
|
-
await enableGlobalPlugin(plugin.id, true);
|
|
618
|
-
await saveGlobalInstalledPluginVersion(plugin.id, latestVersion);
|
|
619
|
-
} else if (scopeValue === "project") {
|
|
620
|
-
await enablePlugin(plugin.id, true, state.projectPath);
|
|
621
|
-
await saveInstalledPluginVersion(
|
|
622
|
-
plugin.id,
|
|
623
|
-
latestVersion,
|
|
624
|
-
state.projectPath,
|
|
625
|
-
);
|
|
626
|
-
} else {
|
|
627
|
-
await enableLocalPlugin(plugin.id, true, state.projectPath);
|
|
628
|
-
await saveLocalInstalledPluginVersion(
|
|
629
|
-
plugin.id,
|
|
630
|
-
latestVersion,
|
|
631
|
-
state.projectPath,
|
|
632
|
-
);
|
|
633
|
-
}
|
|
602
|
+
await cliInstallPlugin(plugin.id, scope);
|
|
634
603
|
|
|
635
604
|
// On fresh install, prompt for MCP server env vars if needed
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
await collectPluginEnvVars(plugin.name, plugin.marketplace);
|
|
639
|
-
}
|
|
605
|
+
modal.hideModal();
|
|
606
|
+
await collectPluginEnvVars(plugin.name, plugin.marketplace);
|
|
640
607
|
}
|
|
641
608
|
if (action !== "install") {
|
|
642
609
|
modal.hideModal();
|
|
@@ -654,20 +621,11 @@ export function PluginsScreen() {
|
|
|
654
621
|
if (!item || item.type !== "plugin" || !item.plugin?.hasUpdate) return;
|
|
655
622
|
|
|
656
623
|
const plugin = item.plugin;
|
|
657
|
-
const
|
|
624
|
+
const scope: PluginScope = pluginsState.scope === "global" ? "user" : "project";
|
|
658
625
|
|
|
659
626
|
modal.loading(`Updating ${plugin.name}...`);
|
|
660
627
|
try {
|
|
661
|
-
|
|
662
|
-
if (isGlobal) {
|
|
663
|
-
await saveGlobalInstalledPluginVersion(plugin.id, versionToSave);
|
|
664
|
-
} else {
|
|
665
|
-
await saveInstalledPluginVersion(
|
|
666
|
-
plugin.id,
|
|
667
|
-
versionToSave,
|
|
668
|
-
state.projectPath,
|
|
669
|
-
);
|
|
670
|
-
}
|
|
628
|
+
await cliUpdatePlugin(plugin.id, scope);
|
|
671
629
|
modal.hideModal();
|
|
672
630
|
fetchData();
|
|
673
631
|
} catch (error) {
|
|
@@ -682,21 +640,12 @@ export function PluginsScreen() {
|
|
|
682
640
|
const updatable = pluginsState.plugins.data.filter((p) => p.hasUpdate);
|
|
683
641
|
if (updatable.length === 0) return;
|
|
684
642
|
|
|
685
|
-
const
|
|
643
|
+
const scope: PluginScope = pluginsState.scope === "global" ? "user" : "project";
|
|
686
644
|
modal.loading(`Updating ${updatable.length} plugin(s)...`);
|
|
687
645
|
|
|
688
646
|
try {
|
|
689
647
|
for (const plugin of updatable) {
|
|
690
|
-
|
|
691
|
-
if (isGlobal) {
|
|
692
|
-
await saveGlobalInstalledPluginVersion(plugin.id, versionToSave);
|
|
693
|
-
} else {
|
|
694
|
-
await saveInstalledPluginVersion(
|
|
695
|
-
plugin.id,
|
|
696
|
-
versionToSave,
|
|
697
|
-
state.projectPath,
|
|
698
|
-
);
|
|
699
|
-
}
|
|
648
|
+
await cliUpdatePlugin(plugin.id, scope);
|
|
700
649
|
}
|
|
701
650
|
modal.hideModal();
|
|
702
651
|
fetchData();
|
|
@@ -753,43 +702,15 @@ export function PluginsScreen() {
|
|
|
753
702
|
|
|
754
703
|
try {
|
|
755
704
|
if (action === "uninstall") {
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
await removeGlobalInstalledPluginVersion(plugin.id);
|
|
760
|
-
} else if (scope === "project") {
|
|
761
|
-
await enablePlugin(plugin.id, false, state.projectPath);
|
|
762
|
-
await removeInstalledPluginVersion(plugin.id, state.projectPath);
|
|
763
|
-
} else {
|
|
764
|
-
await enableLocalPlugin(plugin.id, false, state.projectPath);
|
|
765
|
-
await removeLocalInstalledPluginVersion(plugin.id, state.projectPath);
|
|
766
|
-
}
|
|
705
|
+
await cliUninstallPlugin(plugin.id, scope);
|
|
706
|
+
} else if (action === "update") {
|
|
707
|
+
await cliUpdatePlugin(plugin.id, scope);
|
|
767
708
|
} else {
|
|
768
|
-
|
|
769
|
-
if (scope === "user") {
|
|
770
|
-
await enableGlobalPlugin(plugin.id, true);
|
|
771
|
-
await saveGlobalInstalledPluginVersion(plugin.id, latestVersion);
|
|
772
|
-
} else if (scope === "project") {
|
|
773
|
-
await enablePlugin(plugin.id, true, state.projectPath);
|
|
774
|
-
await saveInstalledPluginVersion(
|
|
775
|
-
plugin.id,
|
|
776
|
-
latestVersion,
|
|
777
|
-
state.projectPath,
|
|
778
|
-
);
|
|
779
|
-
} else {
|
|
780
|
-
await enableLocalPlugin(plugin.id, true, state.projectPath);
|
|
781
|
-
await saveLocalInstalledPluginVersion(
|
|
782
|
-
plugin.id,
|
|
783
|
-
latestVersion,
|
|
784
|
-
state.projectPath,
|
|
785
|
-
);
|
|
786
|
-
}
|
|
709
|
+
await cliInstallPlugin(plugin.id, scope);
|
|
787
710
|
|
|
788
711
|
// On fresh install, prompt for MCP server env vars if needed
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
await collectPluginEnvVars(plugin.name, plugin.marketplace);
|
|
792
|
-
}
|
|
712
|
+
modal.hideModal();
|
|
713
|
+
await collectPluginEnvVars(plugin.name, plugin.marketplace);
|
|
793
714
|
}
|
|
794
715
|
if (action !== "install") {
|
|
795
716
|
modal.hideModal();
|
|
@@ -848,17 +769,7 @@ export function PluginsScreen() {
|
|
|
848
769
|
modal.loading(`Uninstalling ${plugin.name}...`);
|
|
849
770
|
|
|
850
771
|
try {
|
|
851
|
-
|
|
852
|
-
await enableGlobalPlugin(plugin.id, false);
|
|
853
|
-
await removeGlobalInstalledPluginVersion(plugin.id);
|
|
854
|
-
} else if (scopeValue === "project") {
|
|
855
|
-
await enablePlugin(plugin.id, false, state.projectPath);
|
|
856
|
-
await removeInstalledPluginVersion(plugin.id, state.projectPath);
|
|
857
|
-
} else {
|
|
858
|
-
// local scope
|
|
859
|
-
await enableLocalPlugin(plugin.id, false, state.projectPath);
|
|
860
|
-
await removeLocalInstalledPluginVersion(plugin.id, state.projectPath);
|
|
861
|
-
}
|
|
772
|
+
await cliUninstallPlugin(plugin.id, scopeValue as PluginScope);
|
|
862
773
|
modal.hideModal();
|
|
863
774
|
fetchData();
|
|
864
775
|
} catch (error) {
|