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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudeup",
3
- "version": "3.5.1",
3
+ "version": "3.6.1",
4
4
  "description": "TUI tool for managing Claude Code plugins, MCPs, and configuration",
5
5
  "type": "module",
6
6
  "main": "src/main.tsx",
@@ -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, saveInstalledPluginVersion, } from "../services/plugin-manager.js";
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 with available updates
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
- // Save new version - this will:
54
- // 1. Update settings.json
55
- // 2. Call updateInstalledPluginsRegistry()
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,
@@ -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 { recoverMarketplaceSettings, migrateMarketplaceRename } from "../services/claude-settings.js";
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 with available updates
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
- // Save new version - this will:
84
- // 1. Update settings.json
85
- // 2. Call updateInstalledPluginsRegistry()
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;
@@ -32,6 +32,7 @@ export function ConfirmModal({
32
32
  border
33
33
  borderStyle="rounded"
34
34
  borderColor="yellow"
35
+ backgroundColor="#1a1a2e"
35
36
  paddingLeft={2}
36
37
  paddingRight={2}
37
38
  paddingTop={1}
@@ -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;
@@ -37,6 +37,7 @@ export function InputModal({
37
37
  border
38
38
  borderStyle="rounded"
39
39
  borderColor="cyan"
40
+ backgroundColor="#1a1a2e"
40
41
  paddingLeft={2}
41
42
  paddingRight={2}
42
43
  paddingTop={1}
@@ -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;
@@ -26,6 +26,7 @@ export function LoadingModal({
26
26
  border
27
27
  borderStyle="rounded"
28
28
  borderColor="cyan"
29
+ backgroundColor="#1a1a2e"
29
30
  paddingLeft={2}
30
31
  paddingRight={2}
31
32
  paddingTop={1}
@@ -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;
@@ -37,6 +37,7 @@ export function MessageModal({
37
37
  border
38
38
  borderStyle="rounded"
39
39
  borderColor={config.color}
40
+ backgroundColor="#1a1a2e"
40
41
  paddingLeft={2}
41
42
  paddingRight={2}
42
43
  paddingTop={1}
@@ -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));
@@ -42,6 +42,7 @@ export function SelectModal({
42
42
  border
43
43
  borderStyle="rounded"
44
44
  borderColor="cyan"
45
+ backgroundColor="#1a1a2e"
45
46
  paddingLeft={2}
46
47
  paddingRight={2}
47
48
  paddingTop={1}
@@ -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, saveInstalledPluginVersion, removeInstalledPluginVersion, getLocalMarketplacesInfo, } from "../../services/plugin-manager.js";
12
- import { enablePlugin, enableGlobalPlugin, enableLocalPlugin, saveGlobalInstalledPluginVersion, removeGlobalInstalledPluginVersion, saveLocalInstalledPluginVersion, removeLocalInstalledPluginVersion, setMcpEnvVar, getMcpEnvVars, } from "../../services/claude-settings.js";
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
- // Uninstall from this scope
459
- if (scopeValue === "user") {
460
- await enableGlobalPlugin(plugin.id, false);
461
- await removeGlobalInstalledPluginVersion(plugin.id);
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
- // Install or update (both save the latest version)
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
- if (action === "install") {
488
- modal.hideModal();
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 isGlobal = pluginsState.scope === "global";
487
+ const scope = pluginsState.scope === "global" ? "user" : "project";
509
488
  modal.loading(`Updating ${plugin.name}...`);
510
489
  try {
511
- const versionToSave = plugin.version || "0.0.0";
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 isGlobal = pluginsState.scope === "global";
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
- const versionToSave = plugin.version || "0.0.0";
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
- // Uninstall from this scope
593
- if (scope === "user") {
594
- await enableGlobalPlugin(plugin.id, false);
595
- await removeGlobalInstalledPluginVersion(plugin.id);
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
- // Install or update to this scope (both save the latest version)
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
- if (action === "install") {
622
- modal.hideModal();
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
- if (scopeValue === "user") {
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
- // Uninstall from this scope
601
- if (scopeValue === "user") {
602
- await enableGlobalPlugin(plugin.id, false);
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
- // Install or update (both save the latest version)
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
- if (action === "install") {
637
- modal.hideModal();
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 isGlobal = pluginsState.scope === "global";
624
+ const scope: PluginScope = pluginsState.scope === "global" ? "user" : "project";
658
625
 
659
626
  modal.loading(`Updating ${plugin.name}...`);
660
627
  try {
661
- const versionToSave = plugin.version || "0.0.0";
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 isGlobal = pluginsState.scope === "global";
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
- const versionToSave = plugin.version || "0.0.0";
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
- // Uninstall from this scope
757
- if (scope === "user") {
758
- await enableGlobalPlugin(plugin.id, false);
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
- // Install or update to this scope (both save the latest version)
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
- if (action === "install") {
790
- modal.hideModal();
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
- if (scopeValue === "user") {
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) {