claudeup 4.12.0 → 4.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudeup",
3
- "version": "4.12.0",
3
+ "version": "4.13.0",
4
4
  "description": "TUI tool for managing Claude Code plugins, MCPs, and configuration",
5
5
  "type": "module",
6
6
  "main": "src/main.tsx",
@@ -26,7 +26,7 @@ export function VersionMismatchModal({ mismatches, defaultIndex, }) {
26
26
  // Column widths chosen to fit a 64-wide modal with padding
27
27
  const NAME_W = 22;
28
28
  const VER_W = 12;
29
- return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor: COLOR_BORDER, backgroundColor: COLOR_BG, paddingLeft: 3, paddingRight: 3, paddingTop: 1, paddingBottom: 1, width: 64, children: [_jsxs("box", { marginBottom: 1, children: [_jsx("text", { fg: COLOR_WARN, children: "\u26A0 " }), _jsx("text", { fg: COLOR_TITLE, children: _jsx("strong", { children: "Plugin Version Mismatch" }) })] }), _jsxs("box", { flexDirection: "column", marginBottom: 1, children: [_jsxs("text", { fg: COLOR_MUTED, children: ["Plugins in this project will load ", _jsx("strong", { children: "wrong versions" })] }), _jsx("text", { fg: COLOR_MUTED, children: "due to a Claude Code bug (#45997)." })] }), _jsxs("box", { flexDirection: "column", marginBottom: 1, children: [_jsx("text", { fg: COLOR_DIM, children: _jsxs("strong", { children: ["Plugin".padEnd(NAME_W), "Loaded".padEnd(VER_W), "Expected".padEnd(VER_W)] }) }), _jsxs("text", { fg: COLOR_BORDER, children: ["─".repeat(NAME_W - 1), " ", "─".repeat(VER_W - 1), " ", "─".repeat(VER_W - 1)] }), mismatches.map((m) => {
29
+ return (_jsxs("box", { flexDirection: "column", border: true, borderStyle: "rounded", borderColor: COLOR_BORDER, backgroundColor: COLOR_BG, paddingLeft: 3, paddingRight: 3, paddingTop: 1, paddingBottom: 1, width: 64, children: [_jsx("box", { marginBottom: 1, children: _jsxs("text", { fg: COLOR_TITLE, children: [_jsx("span", { fg: COLOR_WARN, children: "\u26A0 " }), _jsx("strong", { children: "Plugin Version Mismatch" })] }) }), _jsxs("box", { flexDirection: "column", marginBottom: 1, children: [_jsxs("text", { fg: COLOR_MUTED, children: ["Plugins in this project will load ", _jsx("strong", { children: "wrong versions" })] }), _jsx("text", { fg: COLOR_MUTED, children: "due to a Claude Code bug (#45997)." })] }), _jsxs("box", { flexDirection: "column", marginBottom: 1, children: [_jsx("text", { fg: COLOR_DIM, children: _jsxs("strong", { children: ["Plugin".padEnd(NAME_W), "Loaded".padEnd(VER_W), "Expected".padEnd(VER_W)] }) }), _jsxs("text", { fg: COLOR_BORDER, children: ["─".repeat(NAME_W - 1), " ", "─".repeat(VER_W - 1), " ", "─".repeat(VER_W - 1)] }), mismatches.map((m) => {
30
30
  const name = shortName(m.pluginId, NAME_W - 1);
31
31
  const loaded = `v${shortVersion(m.firstEntryVersion, VER_W - 2)}`;
32
32
  const expected = `v${shortVersion(m.currentProjectVersion, VER_W - 2)}`;
@@ -56,8 +56,8 @@ export function VersionMismatchModal({
56
56
  >
57
57
  {/* Title */}
58
58
  <box marginBottom={1}>
59
- <text fg={COLOR_WARN}>⚠ </text>
60
59
  <text fg={COLOR_TITLE}>
60
+ <span fg={COLOR_WARN}>⚠ </span>
61
61
  <strong>Plugin Version Mismatch</strong>
62
62
  </text>
63
63
  </box>
@@ -14,7 +14,7 @@ import { saveProfile } from "../../services/profiles.js";
14
14
  import { installPlugin as cliInstallPlugin, uninstallPlugin as cliUninstallPlugin, updatePlugin as cliUpdatePlugin, } from "../../services/claude-cli.js";
15
15
  import { getPluginEnvRequirements, getPluginSourcePath, } from "../../services/plugin-mcp-config.js";
16
16
  import { getPluginSetupFromSource, checkMissingDeps, installPluginDeps, } from "../../services/plugin-setup.js";
17
- import { checkSinglePluginMismatch, } from "../../services/plugin-version-check.js";
17
+ import { checkPluginVersionMismatches, checkSinglePluginMismatch, } from "../../services/plugin-version-check.js";
18
18
  import { useMismatchModal } from "../hooks/useMismatchModal.js";
19
19
  import { buildPluginBrowserItems, } from "../adapters/pluginsAdapter.js";
20
20
  import { renderPluginRow, renderPluginDetail, } from "../renderers/pluginRenderers.js";
@@ -215,6 +215,8 @@ export function PluginsScreen() {
215
215
  handleUpdate();
216
216
  else if (event.name === "a")
217
217
  handleUpdateAll();
218
+ else if (event.name === "m")
219
+ handleCheckMismatches();
218
220
  else if (event.name === "s")
219
221
  handleSaveAsProfile();
220
222
  });
@@ -589,6 +591,25 @@ export function PluginsScreen() {
589
591
  await modal.message("Error", `Failed to update: ${error}`, "error");
590
592
  }
591
593
  };
594
+ /**
595
+ * Manual on-demand check for plugin version mismatches across projects.
596
+ * Bound to the `m` keybind. Shows a success message if everything is
597
+ * aligned, or the interactive fix modal if any mismatches are found.
598
+ */
599
+ const handleCheckMismatches = async () => {
600
+ try {
601
+ const projectPath = state.projectPath || process.cwd();
602
+ const mismatches = await checkPluginVersionMismatches(projectPath);
603
+ if (mismatches.length === 0) {
604
+ await modal.message("No Mismatches", "All enabled plugins in this project will load their expected versions. No action needed.", "success");
605
+ return;
606
+ }
607
+ mismatchModal.show(mismatches);
608
+ }
609
+ catch (error) {
610
+ await modal.message("Error", `Failed to check mismatches: ${error}`, "error");
611
+ }
612
+ };
592
613
  const handleScopeToggle = async (scope) => {
593
614
  const item = selectableItems[pluginsState.selectedIndex];
594
615
  if (!item || item.kind !== "plugin")
@@ -715,7 +736,7 @@ export function PluginsScreen() {
715
736
  const selectedItem = selectableItems[pluginsState.selectedIndex];
716
737
  const footerHints = isSearchActive
717
738
  ? "type to filter │ Enter:done │ Esc:clear"
718
- : "u/p/l:toggle │ U:update │ a:all │ s:profile │ /:search";
739
+ : "u/p/l:toggle │ U:update │ a:all │ m:mismatches │ s:profile │ /:search";
719
740
  const scopeLabel = pluginsState.scope === "global" ? "Global" : "Project";
720
741
  const plugins = pluginsState.plugins.status === "success" ? pluginsState.plugins.data : [];
721
742
  const installedCount = plugins.filter((p) => p.enabled).length;
@@ -39,6 +39,7 @@ import {
39
39
  installPluginDeps,
40
40
  } from "../../services/plugin-setup.js";
41
41
  import {
42
+ checkPluginVersionMismatches,
42
43
  checkSinglePluginMismatch,
43
44
  type VersionMismatchInfo,
44
45
  } from "../../services/plugin-version-check.js";
@@ -266,6 +267,7 @@ export function PluginsScreen() {
266
267
  else if (event.name === "d") handleRemoveDeprecated();
267
268
  else if (event.name === "U") handleUpdate();
268
269
  else if (event.name === "a") handleUpdateAll();
270
+ else if (event.name === "m") handleCheckMismatches();
269
271
  else if (event.name === "s") handleSaveAsProfile();
270
272
  });
271
273
 
@@ -741,6 +743,33 @@ export function PluginsScreen() {
741
743
  }
742
744
  };
743
745
 
746
+ /**
747
+ * Manual on-demand check for plugin version mismatches across projects.
748
+ * Bound to the `m` keybind. Shows a success message if everything is
749
+ * aligned, or the interactive fix modal if any mismatches are found.
750
+ */
751
+ const handleCheckMismatches = async () => {
752
+ try {
753
+ const projectPath = state.projectPath || process.cwd();
754
+ const mismatches = await checkPluginVersionMismatches(projectPath);
755
+ if (mismatches.length === 0) {
756
+ await modal.message(
757
+ "No Mismatches",
758
+ "All enabled plugins in this project will load their expected versions. No action needed.",
759
+ "success",
760
+ );
761
+ return;
762
+ }
763
+ mismatchModal.show(mismatches);
764
+ } catch (error) {
765
+ await modal.message(
766
+ "Error",
767
+ `Failed to check mismatches: ${error}`,
768
+ "error",
769
+ );
770
+ }
771
+ };
772
+
744
773
  const handleScopeToggle = async (scope: "user" | "project" | "local") => {
745
774
  const item = selectableItems[pluginsState.selectedIndex];
746
775
  if (!item || item.kind !== "plugin") return;
@@ -909,7 +938,7 @@ export function PluginsScreen() {
909
938
 
910
939
  const footerHints = isSearchActive
911
940
  ? "type to filter │ Enter:done │ Esc:clear"
912
- : "u/p/l:toggle │ U:update │ a:all │ s:profile │ /:search";
941
+ : "u/p/l:toggle │ U:update │ a:all │ m:mismatches │ s:profile │ /:search";
913
942
 
914
943
  const scopeLabel = pluginsState.scope === "global" ? "Global" : "Project";
915
944
  const plugins: PluginInfo[] =