claudeup 3.7.0 → 3.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudeup",
3
- "version": "3.7.0",
3
+ "version": "3.7.2",
4
4
  "description": "TUI tool for managing Claude Code plugins, MCPs, and configuration",
5
5
  "type": "module",
6
6
  "main": "src/main.tsx",
@@ -4,7 +4,7 @@ import os from "node:os";
4
4
  import { UpdateCache } from "../services/update-cache.js";
5
5
  import { getAvailablePlugins, clearMarketplaceCache, } from "../services/plugin-manager.js";
6
6
  import { runClaude } from "../services/claude-runner.js";
7
- import { recoverMarketplaceSettings, migrateMarketplaceRename, getGlobalEnabledPlugins, getEnabledPlugins, getLocalEnabledPlugins, } from "../services/claude-settings.js";
7
+ import { recoverMarketplaceSettings, migrateMarketplaceRename, getGlobalEnabledPlugins, getEnabledPlugins, getLocalEnabledPlugins, saveGlobalInstalledPluginVersion, } from "../services/claude-settings.js";
8
8
  import { parsePluginId } from "../utils/string-utils.js";
9
9
  import { defaultMarketplaces } from "../data/marketplaces.js";
10
10
  import { updatePlugin, addMarketplace, isClaudeAvailable, } from "../services/claude-cli.js";
@@ -133,6 +133,7 @@ export async function prerunClaude(claudeArgs, options = {}) {
133
133
  if (cliAvailable) {
134
134
  await updatePlugin(plugin.id, "user");
135
135
  }
136
+ await saveGlobalInstalledPluginVersion(plugin.id, plugin.version);
136
137
  autoUpdatedPlugins.push({
137
138
  pluginId: plugin.id,
138
139
  oldVersion: plugin.installedVersion,
@@ -13,6 +13,7 @@ import {
13
13
  getGlobalEnabledPlugins,
14
14
  getEnabledPlugins,
15
15
  getLocalEnabledPlugins,
16
+ saveGlobalInstalledPluginVersion,
16
17
  } from "../services/claude-settings.js";
17
18
  import { parsePluginId } from "../utils/string-utils.js";
18
19
  import { defaultMarketplaces } from "../data/marketplaces.js";
@@ -192,6 +193,7 @@ export async function prerunClaude(
192
193
  if (cliAvailable) {
193
194
  await updatePlugin(plugin.id, "user");
194
195
  }
196
+ await saveGlobalInstalledPluginVersion(plugin.id, plugin.version);
195
197
 
196
198
  autoUpdatedPlugins.push({
197
199
  pluginId: plugin.id,
@@ -160,13 +160,28 @@ async function installBrewPackages(packages, result) {
160
160
  }
161
161
  }
162
162
  /**
163
- * Install npm global packages
163
+ * Detect the best available Node.js package installer for globals
164
+ * Prefers: bun > npm
164
165
  */
165
- async function installNpmPackages(packages, result) {
166
+ async function detectNpmCommand() {
167
+ const bunPath = await which("bun");
168
+ if (bunPath) {
169
+ return { cmd: bunPath, args: ["install", "-g"], label: "bun" };
170
+ }
166
171
  const npmPath = await which("npm");
167
- if (!npmPath) {
172
+ if (npmPath) {
173
+ return { cmd: npmPath, args: ["install", "-g"], label: "npm" };
174
+ }
175
+ return null;
176
+ }
177
+ /**
178
+ * Install global Node.js packages (prefers bun > npm)
179
+ */
180
+ async function installNpmPackages(packages, result) {
181
+ const installer = await detectNpmCommand();
182
+ if (!installer) {
168
183
  for (const pkg of packages) {
169
- result.failed.push({ pkg: `npm:${pkg}`, error: "npm not found" });
184
+ result.failed.push({ pkg: `npm:${pkg}`, error: "No bun or npm found" });
170
185
  }
171
186
  return;
172
187
  }
@@ -175,12 +190,12 @@ async function installNpmPackages(packages, result) {
175
190
  result.skipped.push(`npm:${pkg}`);
176
191
  continue;
177
192
  }
178
- const { ok, stderr } = await run(npmPath, ["install", "-g", pkg]);
193
+ const { ok, stderr } = await run(installer.cmd, [...installer.args, pkg]);
179
194
  if (ok) {
180
- result.installed.push(`npm:${pkg}`);
195
+ result.installed.push(`${installer.label}:${pkg}`);
181
196
  }
182
197
  else {
183
- result.failed.push({ pkg: `npm:${pkg}`, error: stderr });
198
+ result.failed.push({ pkg: `${installer.label}:${pkg}`, error: stderr });
184
199
  }
185
200
  }
186
201
  }
@@ -196,16 +196,36 @@ async function installBrewPackages(
196
196
  }
197
197
 
198
198
  /**
199
- * Install npm global packages
199
+ * Detect the best available Node.js package installer for globals
200
+ * Prefers: bun > npm
201
+ */
202
+ async function detectNpmCommand(): Promise<{
203
+ cmd: string;
204
+ args: string[];
205
+ label: string;
206
+ } | null> {
207
+ const bunPath = await which("bun");
208
+ if (bunPath) {
209
+ return { cmd: bunPath, args: ["install", "-g"], label: "bun" };
210
+ }
211
+ const npmPath = await which("npm");
212
+ if (npmPath) {
213
+ return { cmd: npmPath, args: ["install", "-g"], label: "npm" };
214
+ }
215
+ return null;
216
+ }
217
+
218
+ /**
219
+ * Install global Node.js packages (prefers bun > npm)
200
220
  */
201
221
  async function installNpmPackages(
202
222
  packages: string[],
203
223
  result: SetupResult,
204
224
  ): Promise<void> {
205
- const npmPath = await which("npm");
206
- if (!npmPath) {
225
+ const installer = await detectNpmCommand();
226
+ if (!installer) {
207
227
  for (const pkg of packages) {
208
- result.failed.push({ pkg: `npm:${pkg}`, error: "npm not found" });
228
+ result.failed.push({ pkg: `npm:${pkg}`, error: "No bun or npm found" });
209
229
  }
210
230
  return;
211
231
  }
@@ -216,11 +236,11 @@ async function installNpmPackages(
216
236
  continue;
217
237
  }
218
238
 
219
- const { ok, stderr } = await run(npmPath, ["install", "-g", pkg]);
239
+ const { ok, stderr } = await run(installer.cmd, [...installer.args, pkg]);
220
240
  if (ok) {
221
- result.installed.push(`npm:${pkg}`);
241
+ result.installed.push(`${installer.label}:${pkg}`);
222
242
  } else {
223
- result.failed.push({ pkg: `npm:${pkg}`, error: stderr });
243
+ result.failed.push({ pkg: `${installer.label}:${pkg}`, error: stderr });
224
244
  }
225
245
  }
226
246
  }
@@ -9,7 +9,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
11
  import { getAvailablePlugins, refreshAllMarketplaces, clearMarketplaceCache, getLocalMarketplacesInfo, } from "../../services/plugin-manager.js";
12
- import { setMcpEnvVar, getMcpEnvVars, } from "../../services/claude-settings.js";
12
+ import { setMcpEnvVar, getMcpEnvVars, saveGlobalInstalledPluginVersion, saveLocalInstalledPluginVersion, } from "../../services/claude-settings.js";
13
+ import { saveInstalledPluginVersion, } from "../../services/plugin-manager.js";
13
14
  import { installPlugin as cliInstallPlugin, uninstallPlugin as cliUninstallPlugin, updatePlugin as cliUpdatePlugin, } from "../../services/claude-cli.js";
14
15
  import { getPluginEnvRequirements, getPluginSourcePath, } from "../../services/plugin-mcp-config.js";
15
16
  import { getPluginSetupFromSource, checkMissingDeps, installPluginDeps, } from "../../services/plugin-setup.js";
@@ -399,6 +400,27 @@ export function PluginsScreen() {
399
400
  console.error("Error installing plugin deps:", error);
400
401
  }
401
402
  };
403
+ /**
404
+ * Save the installed version to settings after CLI install/update.
405
+ * Claude CLI doesn't update installedPluginVersions in settings.json,
406
+ * so we do it ourselves to keep the TUI version display accurate.
407
+ */
408
+ const saveVersionAfterInstall = async (pluginId, version, scope) => {
409
+ try {
410
+ if (scope === "user") {
411
+ await saveGlobalInstalledPluginVersion(pluginId, version);
412
+ }
413
+ else if (scope === "local") {
414
+ await saveLocalInstalledPluginVersion(pluginId, version, state.projectPath);
415
+ }
416
+ else {
417
+ await saveInstalledPluginVersion(pluginId, version, state.projectPath);
418
+ }
419
+ }
420
+ catch {
421
+ // Non-fatal: version display may be stale but plugin still works
422
+ }
423
+ };
402
424
  const handleSelect = async () => {
403
425
  const item = selectableItems[pluginsState.selectedIndex];
404
426
  if (!item)
@@ -508,9 +530,11 @@ export function PluginsScreen() {
508
530
  }
509
531
  else if (action === "update") {
510
532
  await cliUpdatePlugin(plugin.id, scope);
533
+ await saveVersionAfterInstall(plugin.id, latestVersion, scope);
511
534
  }
512
535
  else {
513
536
  await cliInstallPlugin(plugin.id, scope);
537
+ await saveVersionAfterInstall(plugin.id, latestVersion, scope);
514
538
  // On fresh install, configure env vars and install system deps
515
539
  modal.hideModal();
516
540
  await collectPluginEnvVars(plugin.name, plugin.marketplace);
@@ -536,6 +560,7 @@ export function PluginsScreen() {
536
560
  modal.loading(`Updating ${plugin.name}...`);
537
561
  try {
538
562
  await cliUpdatePlugin(plugin.id, scope);
563
+ await saveVersionAfterInstall(plugin.id, plugin.version || "0.0.0", scope);
539
564
  modal.hideModal();
540
565
  fetchData();
541
566
  }
@@ -555,6 +580,7 @@ export function PluginsScreen() {
555
580
  try {
556
581
  for (const plugin of updatable) {
557
582
  await cliUpdatePlugin(plugin.id, scope);
583
+ await saveVersionAfterInstall(plugin.id, plugin.version || "0.0.0", scope);
558
584
  }
559
585
  modal.hideModal();
560
586
  fetchData();
@@ -608,9 +634,11 @@ export function PluginsScreen() {
608
634
  }
609
635
  else if (action === "update") {
610
636
  await cliUpdatePlugin(plugin.id, scope);
637
+ await saveVersionAfterInstall(plugin.id, latestVersion, scope);
611
638
  }
612
639
  else {
613
640
  await cliInstallPlugin(plugin.id, scope);
641
+ await saveVersionAfterInstall(plugin.id, latestVersion, scope);
614
642
  // On fresh install, configure env vars and install system deps
615
643
  modal.hideModal();
616
644
  await collectPluginEnvVars(plugin.name, plugin.marketplace);
@@ -17,7 +17,12 @@ import {
17
17
  import {
18
18
  setMcpEnvVar,
19
19
  getMcpEnvVars,
20
+ saveGlobalInstalledPluginVersion,
21
+ saveLocalInstalledPluginVersion,
20
22
  } from "../../services/claude-settings.js";
23
+ import {
24
+ saveInstalledPluginVersion,
25
+ } from "../../services/plugin-manager.js";
21
26
  import {
22
27
  installPlugin as cliInstallPlugin,
23
28
  uninstallPlugin as cliUninstallPlugin,
@@ -531,6 +536,29 @@ export function PluginsScreen() {
531
536
  }
532
537
  };
533
538
 
539
+ /**
540
+ * Save the installed version to settings after CLI install/update.
541
+ * Claude CLI doesn't update installedPluginVersions in settings.json,
542
+ * so we do it ourselves to keep the TUI version display accurate.
543
+ */
544
+ const saveVersionAfterInstall = async (
545
+ pluginId: string,
546
+ version: string,
547
+ scope: PluginScope,
548
+ ): Promise<void> => {
549
+ try {
550
+ if (scope === "user") {
551
+ await saveGlobalInstalledPluginVersion(pluginId, version);
552
+ } else if (scope === "local") {
553
+ await saveLocalInstalledPluginVersion(pluginId, version, state.projectPath);
554
+ } else {
555
+ await saveInstalledPluginVersion(pluginId, version, state.projectPath);
556
+ }
557
+ } catch {
558
+ // Non-fatal: version display may be stale but plugin still works
559
+ }
560
+ };
561
+
534
562
  const handleSelect = async () => {
535
563
  const item = selectableItems[pluginsState.selectedIndex];
536
564
  if (!item) return;
@@ -663,8 +691,10 @@ export function PluginsScreen() {
663
691
  await cliUninstallPlugin(plugin.id, scope, state.projectPath);
664
692
  } else if (action === "update") {
665
693
  await cliUpdatePlugin(plugin.id, scope);
694
+ await saveVersionAfterInstall(plugin.id, latestVersion, scope);
666
695
  } else {
667
696
  await cliInstallPlugin(plugin.id, scope);
697
+ await saveVersionAfterInstall(plugin.id, latestVersion, scope);
668
698
 
669
699
  // On fresh install, configure env vars and install system deps
670
700
  modal.hideModal();
@@ -692,6 +722,7 @@ export function PluginsScreen() {
692
722
  modal.loading(`Updating ${plugin.name}...`);
693
723
  try {
694
724
  await cliUpdatePlugin(plugin.id, scope);
725
+ await saveVersionAfterInstall(plugin.id, plugin.version || "0.0.0", scope);
695
726
  modal.hideModal();
696
727
  fetchData();
697
728
  } catch (error) {
@@ -712,6 +743,7 @@ export function PluginsScreen() {
712
743
  try {
713
744
  for (const plugin of updatable) {
714
745
  await cliUpdatePlugin(plugin.id, scope);
746
+ await saveVersionAfterInstall(plugin.id, plugin.version || "0.0.0", scope);
715
747
  }
716
748
  modal.hideModal();
717
749
  fetchData();
@@ -771,8 +803,10 @@ export function PluginsScreen() {
771
803
  await cliUninstallPlugin(plugin.id, scope, state.projectPath);
772
804
  } else if (action === "update") {
773
805
  await cliUpdatePlugin(plugin.id, scope);
806
+ await saveVersionAfterInstall(plugin.id, latestVersion, scope);
774
807
  } else {
775
808
  await cliInstallPlugin(plugin.id, scope);
809
+ await saveVersionAfterInstall(plugin.id, latestVersion, scope);
776
810
 
777
811
  // On fresh install, configure env vars and install system deps
778
812
  modal.hideModal();