claudeup 4.10.2 → 4.11.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/__tests__/plugin-setup.test.ts +300 -0
- package/src/__tests__/plugin-version-check.test.ts +762 -0
- package/src/prerunner/index.js +17 -0
- package/src/prerunner/index.ts +20 -0
- package/src/services/plugin-setup.js +88 -1
- package/src/services/plugin-setup.ts +99 -1
- package/src/services/plugin-version-check.js +257 -0
- package/src/services/plugin-version-check.ts +355 -0
- package/src/ui/App.js +96 -18
- package/src/ui/App.tsx +148 -62
- package/src/ui/screens/PluginsScreen.js +86 -13
- package/src/ui/screens/PluginsScreen.tsx +135 -24
- package/src/ui/state/DimensionsContext.js +8 -6
- package/src/ui/state/DimensionsContext.tsx +10 -1
package/src/ui/App.tsx
CHANGED
|
@@ -28,6 +28,12 @@ import {
|
|
|
28
28
|
migrateMarketplaceRename,
|
|
29
29
|
recoverMarketplaceSettings,
|
|
30
30
|
} from "../services/claude-settings.js";
|
|
31
|
+
import {
|
|
32
|
+
checkPluginVersionMismatches,
|
|
33
|
+
fixAllPluginVersionMismatches,
|
|
34
|
+
formatMismatchModal,
|
|
35
|
+
type VersionMismatchInfo,
|
|
36
|
+
} from "../services/plugin-version-check.js";
|
|
31
37
|
import {
|
|
32
38
|
checkForUpdates,
|
|
33
39
|
getCurrentVersion,
|
|
@@ -250,6 +256,7 @@ interface AppContentInnerProps {
|
|
|
250
256
|
onDebugToggle: () => void;
|
|
251
257
|
updateInfo: VersionCheckResult | null;
|
|
252
258
|
onExit: () => void;
|
|
259
|
+
recoveryReport: string | null;
|
|
253
260
|
}
|
|
254
261
|
|
|
255
262
|
function AppContentInner({
|
|
@@ -257,11 +264,66 @@ function AppContentInner({
|
|
|
257
264
|
onDebugToggle,
|
|
258
265
|
updateInfo,
|
|
259
266
|
onExit,
|
|
267
|
+
recoveryReport,
|
|
260
268
|
}: AppContentInnerProps) {
|
|
261
|
-
const { state
|
|
269
|
+
const { state } = useApp();
|
|
262
270
|
const { progress } = state;
|
|
263
271
|
const dimensions = useDimensions();
|
|
272
|
+
|
|
273
|
+
return (
|
|
274
|
+
<box flexDirection="column" height={dimensions.terminalHeight}>
|
|
275
|
+
{updateInfo?.updateAvailable && <UpdateBanner result={updateInfo} />}
|
|
276
|
+
{recoveryReport && (
|
|
277
|
+
<box paddingLeft={1} paddingRight={1}>
|
|
278
|
+
<text fg="green">✓ Fixed: {recoveryReport}</text>
|
|
279
|
+
</box>
|
|
280
|
+
)}
|
|
281
|
+
{showDebug && (
|
|
282
|
+
<box paddingLeft={1} paddingRight={1}>
|
|
283
|
+
<text fg="#888888">
|
|
284
|
+
DEBUG: {dimensions.terminalWidth}x{dimensions.terminalHeight} |
|
|
285
|
+
content={dimensions.contentHeight} | screen=
|
|
286
|
+
{state.currentRoute.screen}
|
|
287
|
+
</text>
|
|
288
|
+
</box>
|
|
289
|
+
)}
|
|
290
|
+
{progress && <ProgressIndicator {...progress} />}
|
|
291
|
+
<box
|
|
292
|
+
flexDirection="column"
|
|
293
|
+
height={dimensions.contentHeight}
|
|
294
|
+
paddingLeft={1}
|
|
295
|
+
paddingRight={1}
|
|
296
|
+
>
|
|
297
|
+
<Router />
|
|
298
|
+
</box>
|
|
299
|
+
<GlobalKeyHandler onDebugToggle={onDebugToggle} onExit={onExit} />
|
|
300
|
+
<ModalContainer />
|
|
301
|
+
</box>
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* AppContent Component
|
|
307
|
+
* Wraps app with DimensionsProvider and manages state for debug/updates
|
|
308
|
+
*/
|
|
309
|
+
interface AppContentProps {
|
|
310
|
+
onExit: () => void;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function AppContent({ onExit }: AppContentProps) {
|
|
314
|
+
const { state, dispatch } = useApp();
|
|
315
|
+
const { progress } = state;
|
|
316
|
+
const [showDebug, setShowDebug] = useState(false);
|
|
317
|
+
const [updateInfo, setUpdateInfo] = useState<VersionCheckResult | null>(null);
|
|
264
318
|
const [recoveryReport, setRecoveryReport] = useState<string | null>(null);
|
|
319
|
+
const [mismatchData, setMismatchData] = useState<VersionMismatchInfo[] | null>(null);
|
|
320
|
+
|
|
321
|
+
// Check for updates on startup (non-blocking)
|
|
322
|
+
useEffect(() => {
|
|
323
|
+
checkForUpdates()
|
|
324
|
+
.then(setUpdateInfo)
|
|
325
|
+
.catch(() => {});
|
|
326
|
+
}, []);
|
|
265
327
|
|
|
266
328
|
// Auto-dismiss recovery banner after 5 seconds
|
|
267
329
|
useEffect(() => {
|
|
@@ -270,6 +332,72 @@ function AppContentInner({
|
|
|
270
332
|
return () => clearTimeout(timer);
|
|
271
333
|
}, [recoveryReport]);
|
|
272
334
|
|
|
335
|
+
// Show mismatch modal when data arrives
|
|
336
|
+
useEffect(() => {
|
|
337
|
+
if (!mismatchData || mismatchData.length === 0) return;
|
|
338
|
+
dispatch({
|
|
339
|
+
type: "SHOW_MODAL",
|
|
340
|
+
modal: {
|
|
341
|
+
type: "select",
|
|
342
|
+
title: "⚠ Plugin Version Mismatch",
|
|
343
|
+
message: formatMismatchModal(mismatchData),
|
|
344
|
+
options: [
|
|
345
|
+
{
|
|
346
|
+
label: "Fix all projects",
|
|
347
|
+
value: "fix",
|
|
348
|
+
description: "Align all projects to this project's versions",
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
label: "Dismiss",
|
|
352
|
+
value: "dismiss",
|
|
353
|
+
description: "Ignore for now",
|
|
354
|
+
},
|
|
355
|
+
],
|
|
356
|
+
onSelect: async (value: string) => {
|
|
357
|
+
dispatch({ type: "HIDE_MODAL" });
|
|
358
|
+
if (value === "fix") {
|
|
359
|
+
dispatch({
|
|
360
|
+
type: "SHOW_PROGRESS",
|
|
361
|
+
state: { message: "Fixing plugin versions..." },
|
|
362
|
+
});
|
|
363
|
+
try {
|
|
364
|
+
await fixAllPluginVersionMismatches(mismatchData);
|
|
365
|
+
dispatch({ type: "HIDE_PROGRESS" });
|
|
366
|
+
dispatch({
|
|
367
|
+
type: "SHOW_MODAL",
|
|
368
|
+
modal: {
|
|
369
|
+
type: "message",
|
|
370
|
+
title: "Fixed",
|
|
371
|
+
message:
|
|
372
|
+
"All plugin versions aligned. Restart Claude Code for changes to take effect.",
|
|
373
|
+
variant: "success",
|
|
374
|
+
onDismiss: () => dispatch({ type: "HIDE_MODAL" }),
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
} catch {
|
|
378
|
+
dispatch({ type: "HIDE_PROGRESS" });
|
|
379
|
+
dispatch({
|
|
380
|
+
type: "SHOW_MODAL",
|
|
381
|
+
modal: {
|
|
382
|
+
type: "message",
|
|
383
|
+
title: "Error",
|
|
384
|
+
message: "Failed to fix plugin versions. Try running claudeup again.",
|
|
385
|
+
variant: "error",
|
|
386
|
+
onDismiss: () => dispatch({ type: "HIDE_MODAL" }),
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
setMismatchData(null);
|
|
392
|
+
},
|
|
393
|
+
onCancel: () => {
|
|
394
|
+
dispatch({ type: "HIDE_MODAL" });
|
|
395
|
+
setMismatchData(null);
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
}, [mismatchData, dispatch]);
|
|
400
|
+
|
|
273
401
|
// Auto-refresh marketplaces on startup
|
|
274
402
|
useEffect(() => {
|
|
275
403
|
const noRefresh = process.argv.includes("--no-refresh");
|
|
@@ -280,10 +408,10 @@ function AppContentInner({
|
|
|
280
408
|
state: { message: "Scanning marketplaces..." },
|
|
281
409
|
});
|
|
282
410
|
|
|
283
|
-
// Migrate old marketplace names
|
|
411
|
+
// Migrate old marketplace names -> magus (idempotent), then repair plugin.json files
|
|
284
412
|
migrateMarketplaceRename().catch(() => {}); // non-blocking, best-effort
|
|
285
413
|
|
|
286
|
-
// Recover stale marketplace registry entries (e.g. "directory"
|
|
414
|
+
// Recover stale marketplace registry entries (e.g. "directory" -> "github")
|
|
287
415
|
recoverMarketplaceSettings()
|
|
288
416
|
.then(async (recovery) => {
|
|
289
417
|
const parts: string[] = [];
|
|
@@ -302,14 +430,10 @@ function AppContentInner({
|
|
|
302
430
|
}
|
|
303
431
|
}
|
|
304
432
|
if (recovery.enabledAutoUpdate.length > 0) {
|
|
305
|
-
parts.push(
|
|
306
|
-
`auto-update: ${recovery.enabledAutoUpdate.join(", ")}`,
|
|
307
|
-
);
|
|
433
|
+
parts.push(`auto-update: ${recovery.enabledAutoUpdate.join(", ")}`);
|
|
308
434
|
}
|
|
309
435
|
if (recovery.removed.length > 0) {
|
|
310
|
-
parts.push(
|
|
311
|
-
`removed: ${recovery.removed.join(", ")}`,
|
|
312
|
-
);
|
|
436
|
+
parts.push(`removed: ${recovery.removed.join(", ")}`);
|
|
313
437
|
}
|
|
314
438
|
if (parts.length > 0) {
|
|
315
439
|
setRecoveryReport(parts.join(" | "));
|
|
@@ -317,6 +441,15 @@ function AppContentInner({
|
|
|
317
441
|
})
|
|
318
442
|
.catch(() => {}); // non-fatal
|
|
319
443
|
|
|
444
|
+
// Check for plugin version mismatches (the [0] index bug)
|
|
445
|
+
checkPluginVersionMismatches(process.cwd())
|
|
446
|
+
.then((mismatches) => {
|
|
447
|
+
if (mismatches.length > 0) {
|
|
448
|
+
setMismatchData(mismatches);
|
|
449
|
+
}
|
|
450
|
+
})
|
|
451
|
+
.catch(() => {}); // non-fatal
|
|
452
|
+
|
|
320
453
|
repairAllMarketplaces()
|
|
321
454
|
.then(async () => {
|
|
322
455
|
dispatch({ type: "HIDE_PROGRESS" });
|
|
@@ -327,71 +460,24 @@ function AppContentInner({
|
|
|
327
460
|
});
|
|
328
461
|
}, [dispatch]);
|
|
329
462
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
{recoveryReport && (
|
|
334
|
-
<box paddingLeft={1} paddingRight={1}>
|
|
335
|
-
<text fg="green">✓ Fixed: {recoveryReport}</text>
|
|
336
|
-
</box>
|
|
337
|
-
)}
|
|
338
|
-
{showDebug && (
|
|
339
|
-
<box paddingLeft={1} paddingRight={1}>
|
|
340
|
-
<text fg="#888888">
|
|
341
|
-
DEBUG: {dimensions.terminalWidth}x{dimensions.terminalHeight} |
|
|
342
|
-
content={dimensions.contentHeight} | screen=
|
|
343
|
-
{state.currentRoute.screen}
|
|
344
|
-
</text>
|
|
345
|
-
</box>
|
|
346
|
-
)}
|
|
347
|
-
{progress && <ProgressIndicator {...progress} />}
|
|
348
|
-
<box
|
|
349
|
-
flexDirection="column"
|
|
350
|
-
height={dimensions.contentHeight}
|
|
351
|
-
paddingLeft={1}
|
|
352
|
-
paddingRight={1}
|
|
353
|
-
>
|
|
354
|
-
<Router />
|
|
355
|
-
</box>
|
|
356
|
-
<GlobalKeyHandler onDebugToggle={onDebugToggle} onExit={onExit} />
|
|
357
|
-
<ModalContainer />
|
|
358
|
-
</box>
|
|
359
|
-
);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
/**
|
|
363
|
-
* AppContent Component
|
|
364
|
-
* Wraps app with DimensionsProvider and manages state for debug/updates
|
|
365
|
-
*/
|
|
366
|
-
interface AppContentProps {
|
|
367
|
-
onExit: () => void;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
function AppContent({ onExit }: AppContentProps) {
|
|
371
|
-
const { state } = useApp();
|
|
372
|
-
const { progress } = state;
|
|
373
|
-
const [showDebug, setShowDebug] = useState(false);
|
|
374
|
-
const [updateInfo, setUpdateInfo] = useState<VersionCheckResult | null>(null);
|
|
375
|
-
|
|
376
|
-
// Check for updates on startup (non-blocking)
|
|
377
|
-
useEffect(() => {
|
|
378
|
-
checkForUpdates()
|
|
379
|
-
.then(setUpdateInfo)
|
|
380
|
-
.catch(() => {});
|
|
381
|
-
}, []);
|
|
463
|
+
// Count transient banners for dimension calculation
|
|
464
|
+
const transientBannerCount =
|
|
465
|
+
recoveryReport ? 1 : 0;
|
|
382
466
|
|
|
383
467
|
return (
|
|
384
468
|
<DimensionsProvider
|
|
385
469
|
showProgress={!!progress}
|
|
386
470
|
showDebug={showDebug}
|
|
387
471
|
showUpdateBanner={!!updateInfo?.updateAvailable}
|
|
472
|
+
transientBannerCount={transientBannerCount}
|
|
388
473
|
>
|
|
389
474
|
<AppContentInner
|
|
390
475
|
showDebug={showDebug}
|
|
391
476
|
onDebugToggle={() => setShowDebug((s) => !s)}
|
|
392
477
|
updateInfo={updateInfo}
|
|
393
478
|
onExit={onExit}
|
|
394
|
-
|
|
479
|
+
recoveryReport={recoveryReport}
|
|
480
|
+
/>
|
|
395
481
|
</DimensionsProvider>
|
|
396
482
|
);
|
|
397
483
|
}
|
|
@@ -14,8 +14,9 @@ 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
18
|
import { buildPluginBrowserItems, } from "../adapters/pluginsAdapter.js";
|
|
18
|
-
import { renderPluginRow, renderPluginDetail } from "../renderers/pluginRenderers.js";
|
|
19
|
+
import { renderPluginRow, renderPluginDetail, } from "../renderers/pluginRenderers.js";
|
|
19
20
|
export function PluginsScreen() {
|
|
20
21
|
const { state, dispatch } = useApp();
|
|
21
22
|
const { plugins: pluginsState } = state;
|
|
@@ -140,7 +141,10 @@ export function PluginsScreen() {
|
|
|
140
141
|
if (event.name === "up" || event.name === "k") {
|
|
141
142
|
if (isSearchActive)
|
|
142
143
|
dispatch({ type: "SET_SEARCHING", isSearching: false });
|
|
143
|
-
dispatch({
|
|
144
|
+
dispatch({
|
|
145
|
+
type: "PLUGINS_SELECT",
|
|
146
|
+
index: Math.max(0, pluginsState.selectedIndex - 1),
|
|
147
|
+
});
|
|
144
148
|
return;
|
|
145
149
|
}
|
|
146
150
|
if (event.name === "down" || event.name === "j") {
|
|
@@ -167,12 +171,18 @@ export function PluginsScreen() {
|
|
|
167
171
|
selectableItems[pluginsState.selectedIndex]?.kind === "category") {
|
|
168
172
|
const item = selectableItems[pluginsState.selectedIndex];
|
|
169
173
|
if (item?.kind === "category") {
|
|
170
|
-
dispatch({
|
|
174
|
+
dispatch({
|
|
175
|
+
type: "PLUGINS_TOGGLE_MARKETPLACE",
|
|
176
|
+
name: item.marketplace.name,
|
|
177
|
+
});
|
|
171
178
|
}
|
|
172
179
|
return;
|
|
173
180
|
}
|
|
174
181
|
if (isSearchActive) {
|
|
175
|
-
if (event.name.length === 1 &&
|
|
182
|
+
if (event.name.length === 1 &&
|
|
183
|
+
!event.ctrl &&
|
|
184
|
+
!event.meta &&
|
|
185
|
+
!/[0-9]/.test(event.name)) {
|
|
176
186
|
dispatch({
|
|
177
187
|
type: "PLUGINS_SET_SEARCH",
|
|
178
188
|
query: pluginsState.searchQuery + event.name,
|
|
@@ -223,6 +233,29 @@ export function PluginsScreen() {
|
|
|
223
233
|
await saveInstalledPluginVersion(pluginId, version, state.projectPath);
|
|
224
234
|
}
|
|
225
235
|
};
|
|
236
|
+
/**
|
|
237
|
+
* Check for version mismatch after a plugin update and warn the user.
|
|
238
|
+
* The [0] index bug in Claude Code means the update may not actually
|
|
239
|
+
* take effect if another project's entry is at index 0.
|
|
240
|
+
*/
|
|
241
|
+
const warnIfVersionMismatch = async (pluginId) => {
|
|
242
|
+
try {
|
|
243
|
+
const projectPath = state.projectPath || process.cwd();
|
|
244
|
+
const mismatch = await checkSinglePluginMismatch(pluginId, projectPath);
|
|
245
|
+
if (mismatch) {
|
|
246
|
+
await modal.message("Version Mismatch Warning", `${pluginId} was updated to v${mismatch.currentProjectVersion} for this project, ` +
|
|
247
|
+
`but Claude Code will load v${mismatch.firstEntryVersion} due to a known bug.\n\n` +
|
|
248
|
+
`The plugin loader always uses the first entry in installed_plugins.json, ` +
|
|
249
|
+
`which belongs to another project.\n\n` +
|
|
250
|
+
`Bug: https://github.com/anthropics/claude-code/issues/45997\n\n` +
|
|
251
|
+
`To fix: open claudeup and use the version alignment tool, ` +
|
|
252
|
+
`or update the plugin in all projects to the same version.`, "error");
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
// Non-fatal: mismatch check is best-effort
|
|
257
|
+
}
|
|
258
|
+
};
|
|
226
259
|
// ── Action handlers ────────────────────────────────────────────────────────
|
|
227
260
|
const handleRefresh = async () => {
|
|
228
261
|
progress.show("Refreshing cache...");
|
|
@@ -360,7 +393,9 @@ export function PluginsScreen() {
|
|
|
360
393
|
const result = await installPluginDeps(missing);
|
|
361
394
|
modal.hideModal();
|
|
362
395
|
if (result.failed.length > 0) {
|
|
363
|
-
const failMsg = result.failed
|
|
396
|
+
const failMsg = result.failed
|
|
397
|
+
.map((f) => `${f.pkg}: ${f.error}`)
|
|
398
|
+
.join("\n");
|
|
364
399
|
await modal.message("Partial Install", `Installed: ${result.installed.length}\nFailed:\n${failMsg}`, "error");
|
|
365
400
|
}
|
|
366
401
|
else if (result.installed.length > 0) {
|
|
@@ -404,7 +439,10 @@ export function PluginsScreen() {
|
|
|
404
439
|
const buildScopeLabel = (name, scope, desc) => {
|
|
405
440
|
const installed = scope?.enabled;
|
|
406
441
|
const ver = scope?.version;
|
|
407
|
-
const hasUpdate = ver &&
|
|
442
|
+
const hasUpdate = ver &&
|
|
443
|
+
latestVersion &&
|
|
444
|
+
ver !== latestVersion &&
|
|
445
|
+
latestVersion !== "0.0.0";
|
|
408
446
|
let label = installed ? `● ${name}` : `○ ${name}`;
|
|
409
447
|
label += ` (${desc})`;
|
|
410
448
|
if (ver)
|
|
@@ -414,9 +452,18 @@ export function PluginsScreen() {
|
|
|
414
452
|
return label;
|
|
415
453
|
};
|
|
416
454
|
const scopeOptions = [
|
|
417
|
-
{
|
|
418
|
-
|
|
419
|
-
|
|
455
|
+
{
|
|
456
|
+
label: buildScopeLabel("User", plugin.userScope, "global"),
|
|
457
|
+
value: "user",
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
label: buildScopeLabel("Project", plugin.projectScope, "team"),
|
|
461
|
+
value: "project",
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
label: buildScopeLabel("Local", plugin.localScope, "private"),
|
|
465
|
+
value: "local",
|
|
466
|
+
},
|
|
420
467
|
];
|
|
421
468
|
const scopeValue = await modal.select(plugin.name, `Select scope to toggle:`, scopeOptions);
|
|
422
469
|
if (scopeValue === null)
|
|
@@ -428,7 +475,11 @@ export function PluginsScreen() {
|
|
|
428
475
|
: plugin.localScope;
|
|
429
476
|
const isInstalledInScope = selectedScope?.enabled;
|
|
430
477
|
const installedVersion = selectedScope?.version;
|
|
431
|
-
const scopeLabel = scopeValue === "user"
|
|
478
|
+
const scopeLabel = scopeValue === "user"
|
|
479
|
+
? "User"
|
|
480
|
+
: scopeValue === "project"
|
|
481
|
+
? "Project"
|
|
482
|
+
: "Local";
|
|
432
483
|
const hasUpdateInScope = isInstalledInScope &&
|
|
433
484
|
installedVersion &&
|
|
434
485
|
latestVersion !== "0.0.0" &&
|
|
@@ -465,6 +516,9 @@ export function PluginsScreen() {
|
|
|
465
516
|
if (action !== "install") {
|
|
466
517
|
modal.hideModal();
|
|
467
518
|
}
|
|
519
|
+
if (action === "update") {
|
|
520
|
+
await warnIfVersionMismatch(plugin.id);
|
|
521
|
+
}
|
|
468
522
|
fetchData();
|
|
469
523
|
}
|
|
470
524
|
catch (error) {
|
|
@@ -486,6 +540,7 @@ export function PluginsScreen() {
|
|
|
486
540
|
await saveVersionForScope(plugin.id, plugin.version, scope);
|
|
487
541
|
}
|
|
488
542
|
modal.hideModal();
|
|
543
|
+
await warnIfVersionMismatch(plugin.id);
|
|
489
544
|
fetchData();
|
|
490
545
|
}
|
|
491
546
|
catch (error) {
|
|
@@ -500,6 +555,7 @@ export function PluginsScreen() {
|
|
|
500
555
|
if (updatable.length === 0)
|
|
501
556
|
return;
|
|
502
557
|
const scope = pluginsState.scope === "global" ? "user" : "project";
|
|
558
|
+
const updatedPluginIds = [];
|
|
503
559
|
try {
|
|
504
560
|
for (let i = 0; i < updatable.length; i++) {
|
|
505
561
|
const plugin = updatable[i];
|
|
@@ -508,8 +564,13 @@ export function PluginsScreen() {
|
|
|
508
564
|
if (plugin.version) {
|
|
509
565
|
await saveVersionForScope(plugin.id, plugin.version, scope);
|
|
510
566
|
}
|
|
567
|
+
updatedPluginIds.push(plugin.id);
|
|
511
568
|
}
|
|
512
569
|
modal.hideModal();
|
|
570
|
+
// Check for mismatches on all updated plugins
|
|
571
|
+
for (const pluginId of updatedPluginIds) {
|
|
572
|
+
await warnIfVersionMismatch(pluginId);
|
|
573
|
+
}
|
|
513
574
|
fetchData();
|
|
514
575
|
}
|
|
515
576
|
catch (error) {
|
|
@@ -566,6 +627,9 @@ export function PluginsScreen() {
|
|
|
566
627
|
if (action !== "install") {
|
|
567
628
|
modal.hideModal();
|
|
568
629
|
}
|
|
630
|
+
if (action === "update") {
|
|
631
|
+
await warnIfVersionMismatch(plugin.id);
|
|
632
|
+
}
|
|
569
633
|
fetchData();
|
|
570
634
|
}
|
|
571
635
|
catch (error) {
|
|
@@ -605,8 +669,14 @@ export function PluginsScreen() {
|
|
|
605
669
|
if (name === null || !name.trim())
|
|
606
670
|
return;
|
|
607
671
|
const scopeChoice = await modal.select("Save to scope", "Where should this profile be saved?", [
|
|
608
|
-
{
|
|
609
|
-
|
|
672
|
+
{
|
|
673
|
+
label: "User — ~/.claude/profiles.json (available everywhere)",
|
|
674
|
+
value: "user",
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
label: "Project — .claude/profiles.json (shared with team via git)",
|
|
678
|
+
value: "project",
|
|
679
|
+
},
|
|
610
680
|
]);
|
|
611
681
|
if (scopeChoice === null)
|
|
612
682
|
return;
|
|
@@ -638,7 +708,10 @@ export function PluginsScreen() {
|
|
|
638
708
|
const scopeLabel = pluginsState.scope === "global" ? "Global" : "Project";
|
|
639
709
|
const plugins = pluginsState.plugins.status === "success" ? pluginsState.plugins.data : [];
|
|
640
710
|
const installedCount = plugins.filter((p) => p.enabled).length;
|
|
641
|
-
const updateCount = plugins.filter((p) => p.hasUpdate &&
|
|
711
|
+
const updateCount = plugins.filter((p) => p.hasUpdate &&
|
|
712
|
+
(p.userScope?.enabled ||
|
|
713
|
+
p.projectScope?.enabled ||
|
|
714
|
+
p.localScope?.enabled)).length;
|
|
642
715
|
const subtitle = `${scopeLabel} │ ${installedCount} installed${updateCount > 0 ? ` │ ${updateCount} updates` : ""}`;
|
|
643
716
|
const searchPlaceholder = `${scopeLabel} │ ${installedCount} installed${updateCount > 0 ? ` │ ${updateCount} ⬆` : ""} │ / to search`;
|
|
644
717
|
return (_jsx(ScreenLayout, { title: "claudeup Plugins", subtitle: subtitle, currentScreen: "plugins", search: {
|