claudeup 4.5.3 → 4.5.5

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.5.3",
3
+ "version": "4.5.5",
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, readGlobalSettings, writeGlobalSettings, saveGlobalInstalledPluginVersion, } from "../services/claude-settings.js";
7
+ import { recoverMarketplaceSettings, migrateMarketplaceRename, cleanupExtraKnownMarketplaces, getGlobalEnabledPlugins, getEnabledPlugins, getLocalEnabledPlugins, readGlobalSettings, writeGlobalSettings, 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, updateMarketplace, isClaudeAvailable, } from "../services/claude-cli.js";
@@ -170,6 +170,17 @@ export async function prerunClaude(claudeArgs, options = {}) {
170
170
  console.log(`✓ Auto-added marketplace(s): ${addedMarketplaces.join(", ")}`);
171
171
  }
172
172
  }
173
+ // STEP 0.55: Clean up extraKnownMarketplaces entries that are now in known_marketplaces.json
174
+ // extraKnownMarketplaces was the old install mechanism; the official way is known_marketplaces.json
175
+ try {
176
+ const cleaned = await cleanupExtraKnownMarketplaces();
177
+ if (cleaned.length > 0) {
178
+ console.log(`✓ Cleaned up legacy extraKnownMarketplaces: ${cleaned.join(", ")}`);
179
+ }
180
+ }
181
+ catch {
182
+ // Non-fatal: cleanup is best-effort
183
+ }
173
184
  // STEP 0.6: Ensure tmux-claude-continuity hooks are configured
174
185
  const addedHooks = await ensureTmuxContinuityHooks();
175
186
  if (addedHooks) {
@@ -10,6 +10,7 @@ import { runClaude } from "../services/claude-runner.js";
10
10
  import {
11
11
  recoverMarketplaceSettings,
12
12
  migrateMarketplaceRename,
13
+ cleanupExtraKnownMarketplaces,
13
14
  getGlobalEnabledPlugins,
14
15
  getEnabledPlugins,
15
16
  getLocalEnabledPlugins,
@@ -234,6 +235,19 @@ export async function prerunClaude(
234
235
  }
235
236
  }
236
237
 
238
+ // STEP 0.55: Clean up extraKnownMarketplaces entries that are now in known_marketplaces.json
239
+ // extraKnownMarketplaces was the old install mechanism; the official way is known_marketplaces.json
240
+ try {
241
+ const cleaned = await cleanupExtraKnownMarketplaces();
242
+ if (cleaned.length > 0) {
243
+ console.log(
244
+ `✓ Cleaned up legacy extraKnownMarketplaces: ${cleaned.join(", ")}`,
245
+ );
246
+ }
247
+ } catch {
248
+ // Non-fatal: cleanup is best-effort
249
+ }
250
+
237
251
  // STEP 0.6: Ensure tmux-claude-continuity hooks are configured
238
252
  const addedHooks = await ensureTmuxContinuityHooks();
239
253
  if (addedHooks) {
@@ -279,16 +279,48 @@ export async function getEnabledMcpServers(projectPath) {
279
279
  }
280
280
  return enabled;
281
281
  }
282
- // Get all configured marketplaces
282
+ // Get all configured marketplaces — merges known_marketplaces.json (primary) with extraKnownMarketplaces (legacy fallback)
283
283
  export async function getConfiguredMarketplaces(projectPath) {
284
+ const result = {};
285
+ // Primary: known_marketplaces.json (official Claude Code registry)
286
+ const known = await readKnownMarketplaces();
287
+ for (const [name, entry] of Object.entries(known)) {
288
+ if (entry.source?.repo) {
289
+ result[name] = {
290
+ source: { source: "github", repo: entry.source.repo },
291
+ };
292
+ }
293
+ }
294
+ // Fallback: extraKnownMarketplaces (legacy — only adds entries not already in known)
284
295
  const settings = await readSettings(projectPath);
285
- return settings.extraKnownMarketplaces || {};
296
+ for (const [name, config] of Object.entries(settings.extraKnownMarketplaces || {})) {
297
+ if (!result[name]) {
298
+ result[name] = config;
299
+ }
300
+ }
301
+ return result;
286
302
  }
287
303
  // Global marketplace management - READ ONLY
288
304
  // Marketplaces are managed via Claude Code's native system
289
305
  export async function getGlobalConfiguredMarketplaces() {
306
+ const result = {};
307
+ // Primary: known_marketplaces.json
308
+ const known = await readKnownMarketplaces();
309
+ for (const [name, entry] of Object.entries(known)) {
310
+ if (entry.source?.repo) {
311
+ result[name] = {
312
+ source: { source: "github", repo: entry.source.repo },
313
+ };
314
+ }
315
+ }
316
+ // Fallback: extraKnownMarketplaces
290
317
  const settings = await readGlobalSettings();
291
- return settings.extraKnownMarketplaces || {};
318
+ for (const [name, config] of Object.entries(settings.extraKnownMarketplaces || {})) {
319
+ if (!result[name]) {
320
+ result[name] = config;
321
+ }
322
+ }
323
+ return result;
292
324
  }
293
325
  // Global plugin management
294
326
  export async function enableGlobalPlugin(pluginId, enabled) {
@@ -331,13 +363,29 @@ export async function removeGlobalInstalledPluginVersion(pluginId) {
331
363
  await removeFromInstalledPluginsRegistry(pluginId, "user");
332
364
  }
333
365
  // Shared logic for discovering marketplaces from settings
334
- function discoverMarketplacesFromSettings(settings) {
366
+ function discoverMarketplacesFromSettings(settings, knownMarketplaces) {
335
367
  const discovered = new Map();
336
- // 1. From extraKnownMarketplaces (explicitly configured)
368
+ // 1. From known_marketplaces.json (official Claude Code registry — primary source)
369
+ if (knownMarketplaces) {
370
+ for (const [name, entry] of Object.entries(knownMarketplaces)) {
371
+ if (entry.source?.repo) {
372
+ discovered.set(name, {
373
+ name,
374
+ source: "configured",
375
+ config: {
376
+ source: { source: "github", repo: entry.source.repo },
377
+ },
378
+ });
379
+ }
380
+ }
381
+ }
382
+ // 2. From extraKnownMarketplaces (legacy/team fallback — only adds entries not already in known_marketplaces.json)
337
383
  for (const [name, config] of Object.entries(settings.extraKnownMarketplaces || {})) {
338
- discovered.set(name, { name, source: "configured", config });
384
+ if (!discovered.has(name)) {
385
+ discovered.set(name, { name, source: "configured", config });
386
+ }
339
387
  }
340
- // 2. From enabledPlugins (infer marketplace from plugin ID format: pluginName@marketplaceName)
388
+ // 3. From enabledPlugins (infer marketplace from plugin ID format: pluginName@marketplaceName)
341
389
  for (const pluginId of Object.keys(settings.enabledPlugins || {})) {
342
390
  const parsed = parsePluginId(pluginId);
343
391
  if (parsed && !discovered.has(parsed.marketplace)) {
@@ -347,7 +395,7 @@ function discoverMarketplacesFromSettings(settings) {
347
395
  });
348
396
  }
349
397
  }
350
- // 3. From installedPluginVersions (same format)
398
+ // 4. From installedPluginVersions (same format)
351
399
  for (const pluginId of Object.keys(settings.installedPluginVersions || {})) {
352
400
  const parsed = parsePluginId(pluginId);
353
401
  if (parsed && !discovered.has(parsed.marketplace)) {
@@ -363,7 +411,8 @@ function discoverMarketplacesFromSettings(settings) {
363
411
  export async function discoverAllMarketplaces(projectPath) {
364
412
  try {
365
413
  const settings = await readSettings(projectPath);
366
- return discoverMarketplacesFromSettings(settings);
414
+ const known = await readKnownMarketplaces();
415
+ return discoverMarketplacesFromSettings(settings, known);
367
416
  }
368
417
  catch (error) {
369
418
  // Graceful degradation - return empty array instead of crashing
@@ -375,7 +424,8 @@ export async function discoverAllMarketplaces(projectPath) {
375
424
  export async function discoverAllGlobalMarketplaces() {
376
425
  try {
377
426
  const settings = await readGlobalSettings();
378
- return discoverMarketplacesFromSettings(settings);
427
+ const known = await readKnownMarketplaces();
428
+ return discoverMarketplacesFromSettings(settings, known);
379
429
  }
380
430
  catch (error) {
381
431
  // Graceful degradation - return empty array instead of crashing
@@ -758,6 +808,74 @@ export async function recoverMarketplaceSettings() {
758
808
  }
759
809
  return result;
760
810
  }
811
+ /**
812
+ * Clean up extraKnownMarketplaces entries that are now properly registered
813
+ * in known_marketplaces.json (the official Claude Code marketplace registry).
814
+ *
815
+ * extraKnownMarketplaces was the old claudeup install mechanism. The official
816
+ * way is `claude plugin marketplace add` which writes to known_marketplaces.json.
817
+ * extraKnownMarketplaces should only be used in project settings for team
818
+ * marketplace suggestions — not in user settings as a primary install path.
819
+ *
820
+ * This function removes magus-related entries from extraKnownMarketplaces in
821
+ * both user and project settings when they're already in known_marketplaces.json.
822
+ */
823
+ export async function cleanupExtraKnownMarketplaces(projectPath) {
824
+ const known = await readKnownMarketplaces();
825
+ const cleaned = [];
826
+ // Clean user-level settings
827
+ try {
828
+ const globalSettings = await readGlobalSettings();
829
+ if (globalSettings.extraKnownMarketplaces) {
830
+ let changed = false;
831
+ for (const name of Object.keys(globalSettings.extraKnownMarketplaces)) {
832
+ if (known[name]) {
833
+ delete globalSettings.extraKnownMarketplaces[name];
834
+ cleaned.push(`user:${name}`);
835
+ changed = true;
836
+ }
837
+ }
838
+ // Remove the field entirely if empty
839
+ if (changed &&
840
+ Object.keys(globalSettings.extraKnownMarketplaces).length === 0) {
841
+ delete globalSettings.extraKnownMarketplaces;
842
+ }
843
+ if (changed) {
844
+ await writeGlobalSettings(globalSettings);
845
+ }
846
+ }
847
+ }
848
+ catch {
849
+ // Non-fatal: user settings cleanup is best-effort
850
+ }
851
+ // Clean project-level settings
852
+ if (projectPath) {
853
+ try {
854
+ const projectSettings = await readSettings(projectPath);
855
+ if (projectSettings.extraKnownMarketplaces) {
856
+ let changed = false;
857
+ for (const name of Object.keys(projectSettings.extraKnownMarketplaces)) {
858
+ if (known[name]) {
859
+ delete projectSettings.extraKnownMarketplaces[name];
860
+ cleaned.push(`project:${name}`);
861
+ changed = true;
862
+ }
863
+ }
864
+ if (changed &&
865
+ Object.keys(projectSettings.extraKnownMarketplaces).length === 0) {
866
+ delete projectSettings.extraKnownMarketplaces;
867
+ }
868
+ if (changed) {
869
+ await writeSettings(projectSettings, projectPath);
870
+ }
871
+ }
872
+ }
873
+ catch {
874
+ // Non-fatal: project settings cleanup is best-effort
875
+ }
876
+ }
877
+ return cleaned;
878
+ }
761
879
  /**
762
880
  * Read known_marketplaces.json to get marketplace source info
763
881
  */
@@ -415,12 +415,33 @@ export async function getEnabledMcpServers(
415
415
  return enabled;
416
416
  }
417
417
 
418
- // Get all configured marketplaces
418
+ // Get all configured marketplaces — merges known_marketplaces.json (primary) with extraKnownMarketplaces (legacy fallback)
419
419
  export async function getConfiguredMarketplaces(
420
420
  projectPath?: string,
421
421
  ): Promise<Record<string, MarketplaceSource>> {
422
+ const result: Record<string, MarketplaceSource> = {};
423
+
424
+ // Primary: known_marketplaces.json (official Claude Code registry)
425
+ const known = await readKnownMarketplaces();
426
+ for (const [name, entry] of Object.entries(known)) {
427
+ if (entry.source?.repo) {
428
+ result[name] = {
429
+ source: { source: "github" as const, repo: entry.source.repo },
430
+ };
431
+ }
432
+ }
433
+
434
+ // Fallback: extraKnownMarketplaces (legacy — only adds entries not already in known)
422
435
  const settings = await readSettings(projectPath);
423
- return settings.extraKnownMarketplaces || {};
436
+ for (const [name, config] of Object.entries(
437
+ settings.extraKnownMarketplaces || {},
438
+ )) {
439
+ if (!result[name]) {
440
+ result[name] = config;
441
+ }
442
+ }
443
+
444
+ return result;
424
445
  }
425
446
 
426
447
  // Global marketplace management - READ ONLY
@@ -429,8 +450,29 @@ export async function getConfiguredMarketplaces(
429
450
  export async function getGlobalConfiguredMarketplaces(): Promise<
430
451
  Record<string, MarketplaceSource>
431
452
  > {
453
+ const result: Record<string, MarketplaceSource> = {};
454
+
455
+ // Primary: known_marketplaces.json
456
+ const known = await readKnownMarketplaces();
457
+ for (const [name, entry] of Object.entries(known)) {
458
+ if (entry.source?.repo) {
459
+ result[name] = {
460
+ source: { source: "github" as const, repo: entry.source.repo },
461
+ };
462
+ }
463
+ }
464
+
465
+ // Fallback: extraKnownMarketplaces
432
466
  const settings = await readGlobalSettings();
433
- return settings.extraKnownMarketplaces || {};
467
+ for (const [name, config] of Object.entries(
468
+ settings.extraKnownMarketplaces || {},
469
+ )) {
470
+ if (!result[name]) {
471
+ result[name] = config;
472
+ }
473
+ }
474
+
475
+ return result;
434
476
  }
435
477
 
436
478
  // Global plugin management
@@ -494,17 +536,35 @@ export async function removeGlobalInstalledPluginVersion(
494
536
  // Shared logic for discovering marketplaces from settings
495
537
  function discoverMarketplacesFromSettings(
496
538
  settings: ClaudeSettings,
539
+ knownMarketplaces?: KnownMarketplaces,
497
540
  ): DiscoveredMarketplace[] {
498
541
  const discovered = new Map<string, DiscoveredMarketplace>();
499
542
 
500
- // 1. From extraKnownMarketplaces (explicitly configured)
543
+ // 1. From known_marketplaces.json (official Claude Code registry — primary source)
544
+ if (knownMarketplaces) {
545
+ for (const [name, entry] of Object.entries(knownMarketplaces)) {
546
+ if (entry.source?.repo) {
547
+ discovered.set(name, {
548
+ name,
549
+ source: "configured",
550
+ config: {
551
+ source: { source: "github" as const, repo: entry.source.repo },
552
+ },
553
+ });
554
+ }
555
+ }
556
+ }
557
+
558
+ // 2. From extraKnownMarketplaces (legacy/team fallback — only adds entries not already in known_marketplaces.json)
501
559
  for (const [name, config] of Object.entries(
502
560
  settings.extraKnownMarketplaces || {},
503
561
  )) {
504
- discovered.set(name, { name, source: "configured", config });
562
+ if (!discovered.has(name)) {
563
+ discovered.set(name, { name, source: "configured", config });
564
+ }
505
565
  }
506
566
 
507
- // 2. From enabledPlugins (infer marketplace from plugin ID format: pluginName@marketplaceName)
567
+ // 3. From enabledPlugins (infer marketplace from plugin ID format: pluginName@marketplaceName)
508
568
  for (const pluginId of Object.keys(settings.enabledPlugins || {})) {
509
569
  const parsed = parsePluginId(pluginId);
510
570
  if (parsed && !discovered.has(parsed.marketplace)) {
@@ -515,7 +575,7 @@ function discoverMarketplacesFromSettings(
515
575
  }
516
576
  }
517
577
 
518
- // 3. From installedPluginVersions (same format)
578
+ // 4. From installedPluginVersions (same format)
519
579
  for (const pluginId of Object.keys(settings.installedPluginVersions || {})) {
520
580
  const parsed = parsePluginId(pluginId);
521
581
  if (parsed && !discovered.has(parsed.marketplace)) {
@@ -535,7 +595,8 @@ export async function discoverAllMarketplaces(
535
595
  ): Promise<DiscoveredMarketplace[]> {
536
596
  try {
537
597
  const settings = await readSettings(projectPath);
538
- return discoverMarketplacesFromSettings(settings);
598
+ const known = await readKnownMarketplaces();
599
+ return discoverMarketplacesFromSettings(settings, known);
539
600
  } catch (error) {
540
601
  // Graceful degradation - return empty array instead of crashing
541
602
  console.error(
@@ -552,7 +613,8 @@ export async function discoverAllGlobalMarketplaces(): Promise<
552
613
  > {
553
614
  try {
554
615
  const settings = await readGlobalSettings();
555
- return discoverMarketplacesFromSettings(settings);
616
+ const known = await readKnownMarketplaces();
617
+ return discoverMarketplacesFromSettings(settings, known);
556
618
  } catch (error) {
557
619
  // Graceful degradation - return empty array instead of crashing
558
620
  console.error(
@@ -1019,6 +1081,84 @@ export async function recoverMarketplaceSettings(): Promise<MarketplaceRecoveryR
1019
1081
  return result;
1020
1082
  }
1021
1083
 
1084
+ /**
1085
+ * Clean up extraKnownMarketplaces entries that are now properly registered
1086
+ * in known_marketplaces.json (the official Claude Code marketplace registry).
1087
+ *
1088
+ * extraKnownMarketplaces was the old claudeup install mechanism. The official
1089
+ * way is `claude plugin marketplace add` which writes to known_marketplaces.json.
1090
+ * extraKnownMarketplaces should only be used in project settings for team
1091
+ * marketplace suggestions — not in user settings as a primary install path.
1092
+ *
1093
+ * This function removes magus-related entries from extraKnownMarketplaces in
1094
+ * both user and project settings when they're already in known_marketplaces.json.
1095
+ */
1096
+ export async function cleanupExtraKnownMarketplaces(
1097
+ projectPath?: string,
1098
+ ): Promise<string[]> {
1099
+ const known = await readKnownMarketplaces();
1100
+ const cleaned: string[] = [];
1101
+
1102
+ // Clean user-level settings
1103
+ try {
1104
+ const globalSettings = await readGlobalSettings();
1105
+ if (globalSettings.extraKnownMarketplaces) {
1106
+ let changed = false;
1107
+ for (const name of Object.keys(globalSettings.extraKnownMarketplaces)) {
1108
+ if (known[name]) {
1109
+ delete globalSettings.extraKnownMarketplaces[name];
1110
+ cleaned.push(`user:${name}`);
1111
+ changed = true;
1112
+ }
1113
+ }
1114
+ // Remove the field entirely if empty
1115
+ if (
1116
+ changed &&
1117
+ Object.keys(globalSettings.extraKnownMarketplaces).length === 0
1118
+ ) {
1119
+ delete globalSettings.extraKnownMarketplaces;
1120
+ }
1121
+ if (changed) {
1122
+ await writeGlobalSettings(globalSettings);
1123
+ }
1124
+ }
1125
+ } catch {
1126
+ // Non-fatal: user settings cleanup is best-effort
1127
+ }
1128
+
1129
+ // Clean project-level settings
1130
+ if (projectPath) {
1131
+ try {
1132
+ const projectSettings = await readSettings(projectPath);
1133
+ if (projectSettings.extraKnownMarketplaces) {
1134
+ let changed = false;
1135
+ for (const name of Object.keys(
1136
+ projectSettings.extraKnownMarketplaces,
1137
+ )) {
1138
+ if (known[name]) {
1139
+ delete projectSettings.extraKnownMarketplaces[name];
1140
+ cleaned.push(`project:${name}`);
1141
+ changed = true;
1142
+ }
1143
+ }
1144
+ if (
1145
+ changed &&
1146
+ Object.keys(projectSettings.extraKnownMarketplaces).length === 0
1147
+ ) {
1148
+ delete projectSettings.extraKnownMarketplaces;
1149
+ }
1150
+ if (changed) {
1151
+ await writeSettings(projectSettings, projectPath);
1152
+ }
1153
+ }
1154
+ } catch {
1155
+ // Non-fatal: project settings cleanup is best-effort
1156
+ }
1157
+ }
1158
+
1159
+ return cleaned;
1160
+ }
1161
+
1022
1162
  /**
1023
1163
  * Read known_marketplaces.json to get marketplace source info
1024
1164
  */
@@ -325,20 +325,22 @@ export function SkillsScreen() {
325
325
  }
326
326
  return;
327
327
  }
328
- // Action keys work regardless of search mode when a skill is selected
329
- if (event.name === "u" && selectedSkill) {
330
- if (selectedSkill.installed && selectedSkill.installedScope === "user")
331
- handleUninstall();
332
- else
333
- handleInstall("user");
334
- return;
335
- }
336
- else if (event.name === "p" && selectedSkill) {
337
- if (selectedSkill.installed && selectedSkill.installedScope === "project")
338
- handleUninstall();
339
- else
340
- handleInstall("project");
341
- return;
328
+ // Action keys only when NOT actively typing in search
329
+ if (!isSearchActive) {
330
+ if (event.name === "u" && selectedSkill) {
331
+ if (selectedSkill.installed && selectedSkill.installedScope === "user")
332
+ handleUninstall();
333
+ else
334
+ handleInstall("user");
335
+ return;
336
+ }
337
+ else if (event.name === "p" && selectedSkill) {
338
+ if (selectedSkill.installed && selectedSkill.installedScope === "project")
339
+ handleUninstall();
340
+ else
341
+ handleInstall("project");
342
+ return;
343
+ }
342
344
  }
343
345
  if (isSearchActive) {
344
346
  if (event.name === "k" || event.name === "j") {
@@ -358,15 +358,17 @@ export function SkillsScreen() {
358
358
  return;
359
359
  }
360
360
 
361
- // Action keys work regardless of search mode when a skill is selected
362
- if (event.name === "u" && selectedSkill) {
363
- if (selectedSkill.installed && selectedSkill.installedScope === "user") handleUninstall();
364
- else handleInstall("user");
365
- return;
366
- } else if (event.name === "p" && selectedSkill) {
367
- if (selectedSkill.installed && selectedSkill.installedScope === "project") handleUninstall();
368
- else handleInstall("project");
369
- return;
361
+ // Action keys only when NOT actively typing in search
362
+ if (!isSearchActive) {
363
+ if (event.name === "u" && selectedSkill) {
364
+ if (selectedSkill.installed && selectedSkill.installedScope === "user") handleUninstall();
365
+ else handleInstall("user");
366
+ return;
367
+ } else if (event.name === "p" && selectedSkill) {
368
+ if (selectedSkill.installed && selectedSkill.installedScope === "project") handleUninstall();
369
+ else handleInstall("project");
370
+ return;
371
+ }
370
372
  }
371
373
 
372
374
  if (isSearchActive) {