claudeup 3.4.0 → 3.5.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.4.0",
3
+ "version": "3.5.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,4 +1,24 @@
1
1
  import { formatMarketplaceName } from "../utils/string-utils.js";
2
+ /**
3
+ * Known repo aliases — GitHub redirects the old repo to the new one,
4
+ * so these should be treated as identical for deduplication.
5
+ */
6
+ const REPO_ALIASES = {
7
+ "madappgang/claude-code": "madappgang/magus",
8
+ };
9
+ /** Normalize a repo string through known aliases. */
10
+ function normalizeRepo(repo) {
11
+ const lower = repo.toLowerCase();
12
+ return REPO_ALIASES[lower] || lower;
13
+ }
14
+ /**
15
+ * Deprecated marketplace names that should be hidden if the canonical
16
+ * marketplace is already present. Maps deprecated name → canonical name.
17
+ */
18
+ export const deprecatedMarketplaces = {
19
+ "mag-claude-plugins": "magus",
20
+ "MadAppGang-claude-code": "magus",
21
+ };
2
22
  export const defaultMarketplaces = [
3
23
  {
4
24
  name: "magus",
@@ -37,23 +57,36 @@ export function getMarketplaceByName(name) {
37
57
  }
38
58
  // Get all available marketplaces from local cache + hardcoded defaults
39
59
  // Local cache is primary source of truth, defaults are fallback
40
- // Deduplicates by repo URL to avoid showing same marketplace twice
60
+ // Deduplicates by normalized repo URL to avoid showing same marketplace twice
41
61
  export function getAllMarketplaces(localMarketplaces) {
42
62
  const all = new Map();
43
63
  const seenRepos = new Set();
44
64
  // Primary source: local cache (what's actually cloned)
45
65
  if (localMarketplaces) {
46
66
  for (const [name, local] of localMarketplaces) {
47
- const repo = local.gitRepo || "";
67
+ const repo = normalizeRepo(local.gitRepo || "");
68
+ // Skip deprecated marketplaces if their canonical replacement exists
69
+ // (or will be added from defaults)
70
+ const canonical = deprecatedMarketplaces[name];
71
+ if (canonical) {
72
+ // If canonical already in the map or in defaults, skip this entry
73
+ if (all.has(canonical) || defaultMarketplaces.some((m) => m.name === canonical)) {
74
+ continue;
75
+ }
76
+ }
77
+ // Skip if another marketplace already claimed this repo URL
78
+ if (repo && seenRepos.has(repo))
79
+ continue;
48
80
  if (repo)
49
- seenRepos.add(repo.toLowerCase());
81
+ seenRepos.add(repo);
50
82
  // Check if this marketplace has defaults (for official/featured flags)
51
83
  const defaultMp = defaultMarketplaces.find((m) => m.name === name);
52
84
  all.set(name, {
53
85
  name,
54
- displayName: local.name || formatMarketplaceName(name),
55
- source: { source: "github", repo },
56
- description: local.description || "",
86
+ // Prefer default displayName over stale local clone data
87
+ displayName: defaultMp?.displayName || local.name || formatMarketplaceName(name),
88
+ source: { source: "github", repo: defaultMp?.source.repo || local.gitRepo || "" },
89
+ description: defaultMp?.description || local.description || "",
57
90
  official: defaultMp?.official ?? repo.toLowerCase().includes("anthropics/"),
58
91
  featured: defaultMp?.featured,
59
92
  });
@@ -61,7 +94,7 @@ export function getAllMarketplaces(localMarketplaces) {
61
94
  }
62
95
  // Fallback: hardcoded defaults (only if their repo isn't already represented)
63
96
  for (const mp of defaultMarketplaces) {
64
- const repo = mp.source.repo?.toLowerCase() || "";
97
+ const repo = normalizeRepo(mp.source.repo || "");
65
98
  if (!all.has(mp.name) && !seenRepos.has(repo)) {
66
99
  all.set(mp.name, mp);
67
100
  if (repo)
@@ -2,6 +2,29 @@ import type { Marketplace } from "../types/index.js";
2
2
  import type { LocalMarketplace } from "../services/local-marketplace.js";
3
3
  import { formatMarketplaceName } from "../utils/string-utils.js";
4
4
 
5
+ /**
6
+ * Known repo aliases — GitHub redirects the old repo to the new one,
7
+ * so these should be treated as identical for deduplication.
8
+ */
9
+ const REPO_ALIASES: Record<string, string> = {
10
+ "madappgang/claude-code": "madappgang/magus",
11
+ };
12
+
13
+ /** Normalize a repo string through known aliases. */
14
+ function normalizeRepo(repo: string): string {
15
+ const lower = repo.toLowerCase();
16
+ return REPO_ALIASES[lower] || lower;
17
+ }
18
+
19
+ /**
20
+ * Deprecated marketplace names that should be hidden if the canonical
21
+ * marketplace is already present. Maps deprecated name → canonical name.
22
+ */
23
+ export const deprecatedMarketplaces: Record<string, string> = {
24
+ "mag-claude-plugins": "magus",
25
+ "MadAppGang-claude-code": "magus",
26
+ };
27
+
5
28
  export const defaultMarketplaces: Marketplace[] = [
6
29
  {
7
30
  name: "magus",
@@ -45,7 +68,7 @@ export function getMarketplaceByName(name: string): Marketplace | undefined {
45
68
 
46
69
  // Get all available marketplaces from local cache + hardcoded defaults
47
70
  // Local cache is primary source of truth, defaults are fallback
48
- // Deduplicates by repo URL to avoid showing same marketplace twice
71
+ // Deduplicates by normalized repo URL to avoid showing same marketplace twice
49
72
  export function getAllMarketplaces(
50
73
  localMarketplaces?: Map<string, LocalMarketplace>,
51
74
  ): Marketplace[] {
@@ -55,17 +78,31 @@ export function getAllMarketplaces(
55
78
  // Primary source: local cache (what's actually cloned)
56
79
  if (localMarketplaces) {
57
80
  for (const [name, local] of localMarketplaces) {
58
- const repo = local.gitRepo || "";
59
- if (repo) seenRepos.add(repo.toLowerCase());
81
+ const repo = normalizeRepo(local.gitRepo || "");
82
+
83
+ // Skip deprecated marketplaces if their canonical replacement exists
84
+ // (or will be added from defaults)
85
+ const canonical = deprecatedMarketplaces[name];
86
+ if (canonical) {
87
+ // If canonical already in the map or in defaults, skip this entry
88
+ if (all.has(canonical) || defaultMarketplaces.some((m) => m.name === canonical)) {
89
+ continue;
90
+ }
91
+ }
92
+
93
+ // Skip if another marketplace already claimed this repo URL
94
+ if (repo && seenRepos.has(repo)) continue;
95
+ if (repo) seenRepos.add(repo);
60
96
 
61
97
  // Check if this marketplace has defaults (for official/featured flags)
62
98
  const defaultMp = defaultMarketplaces.find((m) => m.name === name);
63
99
 
64
100
  all.set(name, {
65
101
  name,
66
- displayName: local.name || formatMarketplaceName(name),
67
- source: { source: "github" as const, repo },
68
- description: local.description || "",
102
+ // Prefer default displayName over stale local clone data
103
+ displayName: defaultMp?.displayName || local.name || formatMarketplaceName(name),
104
+ source: { source: "github" as const, repo: defaultMp?.source.repo || local.gitRepo || "" },
105
+ description: defaultMp?.description || local.description || "",
69
106
  official:
70
107
  defaultMp?.official ?? repo.toLowerCase().includes("anthropics/"),
71
108
  featured: defaultMp?.featured,
@@ -75,7 +112,7 @@ export function getAllMarketplaces(
75
112
 
76
113
  // Fallback: hardcoded defaults (only if their repo isn't already represented)
77
114
  for (const mp of defaultMarketplaces) {
78
- const repo = mp.source.repo?.toLowerCase() || "";
115
+ const repo = normalizeRepo(mp.source.repo || "");
79
116
  if (!all.has(mp.name) && !seenRepos.has(repo)) {
80
117
  all.set(mp.name, mp);
81
118
  if (repo) seenRepos.add(repo);
@@ -11,13 +11,13 @@ import { recoverMarketplaceSettings, migrateMarketplaceRename } from "../service
11
11
  export async function prerunClaude(claudeArgs, options = {}) {
12
12
  const cache = new UpdateCache();
13
13
  try {
14
- // STEP 0: Migrate mag-claude-plugins → magus (idempotent, no-ops if already migrated)
14
+ // STEP 0: Migrate old marketplace names → magus (idempotent, no-ops if already migrated)
15
15
  const migration = await migrateMarketplaceRename();
16
16
  const migTotal = migration.projectMigrated + migration.globalMigrated
17
17
  + migration.localMigrated + migration.registryMigrated
18
18
  + (migration.knownMarketplacesMigrated ? 1 : 0);
19
19
  if (migTotal > 0) {
20
- console.log(`✓ Migrated ${migTotal} plugin reference(s) from mag-claude-plugins → magus`);
20
+ console.log(`✓ Migrated ${migTotal} plugin reference(s) → magus`);
21
21
  }
22
22
  // STEP 1: Check if we should update (time-based cache, or forced)
23
23
  const shouldUpdate = options.force || (await cache.shouldCheckForUpdates());
@@ -24,13 +24,13 @@ export async function prerunClaude(
24
24
  const cache = new UpdateCache();
25
25
 
26
26
  try {
27
- // STEP 0: Migrate mag-claude-plugins → magus (idempotent, no-ops if already migrated)
27
+ // STEP 0: Migrate old marketplace names → magus (idempotent, no-ops if already migrated)
28
28
  const migration = await migrateMarketplaceRename();
29
29
  const migTotal = migration.projectMigrated + migration.globalMigrated
30
30
  + migration.localMigrated + migration.registryMigrated
31
31
  + (migration.knownMarketplacesMigrated ? 1 : 0);
32
32
  if (migTotal > 0) {
33
- console.log(`✓ Migrated ${migTotal} plugin reference(s) from mag-claude-plugins → magus`);
33
+ console.log(`✓ Migrated ${migTotal} plugin reference(s) → magus`);
34
34
  }
35
35
 
36
36
  // STEP 1: Check if we should update (time-based cache, or forced)
@@ -414,13 +414,14 @@ export async function getMarketplaceAutoUpdate(marketplaceName) {
414
414
  return known[marketplaceName]?.autoUpdate;
415
415
  }
416
416
  // =============================================================================
417
- // MARKETPLACE RENAME MIGRATION: mag-claude-plugins → magus
417
+ // MARKETPLACE RENAME MIGRATION: mag-claude-plugins / MadAppGang-claude-code → magus
418
418
  // =============================================================================
419
- const OLD_MARKETPLACE_NAME = "mag-claude-plugins";
419
+ const OLD_MARKETPLACE_NAMES = ["mag-claude-plugins", "MadAppGang-claude-code"];
420
420
  const NEW_MARKETPLACE_NAME = "magus";
421
421
  /**
422
- * Rename plugin keys in a Record from old marketplace to new.
422
+ * Rename plugin keys in a Record from any old marketplace name to new.
423
423
  * e.g., "frontend@mag-claude-plugins" → "frontend@magus"
424
+ * "dev@MadAppGang-claude-code" → "dev@magus"
424
425
  * Returns [migratedRecord, count] — count=0 means no changes.
425
426
  */
426
427
  function migratePluginKeys(record) {
@@ -429,9 +430,14 @@ function migratePluginKeys(record) {
429
430
  const migrated = {};
430
431
  let count = 0;
431
432
  for (const [key, value] of Object.entries(record)) {
432
- if (key.endsWith(`@${OLD_MARKETPLACE_NAME}`)) {
433
+ const oldName = OLD_MARKETPLACE_NAMES.find((n) => key.endsWith(`@${n}`));
434
+ if (oldName) {
433
435
  const pluginName = key.slice(0, key.lastIndexOf("@"));
434
- migrated[`${pluginName}@${NEW_MARKETPLACE_NAME}`] = value;
436
+ const newKey = `${pluginName}@${NEW_MARKETPLACE_NAME}`;
437
+ // Don't overwrite if canonical key already exists
438
+ if (!record[newKey]) {
439
+ migrated[newKey] = value;
440
+ }
435
441
  count++;
436
442
  }
437
443
  else {
@@ -456,12 +462,16 @@ function migrateSettingsObject(settings) {
456
462
  settings.installedPluginVersions = iv;
457
463
  total += ivCount;
458
464
  }
459
- // Migrate extraKnownMarketplaces key
460
- if (settings.extraKnownMarketplaces?.[OLD_MARKETPLACE_NAME]) {
461
- const entry = settings.extraKnownMarketplaces[OLD_MARKETPLACE_NAME];
462
- delete settings.extraKnownMarketplaces[OLD_MARKETPLACE_NAME];
463
- settings.extraKnownMarketplaces[NEW_MARKETPLACE_NAME] = entry;
464
- total++;
465
+ // Migrate extraKnownMarketplaces keys
466
+ for (const oldName of OLD_MARKETPLACE_NAMES) {
467
+ if (settings.extraKnownMarketplaces?.[oldName]) {
468
+ const entry = settings.extraKnownMarketplaces[oldName];
469
+ delete settings.extraKnownMarketplaces[oldName];
470
+ if (!settings.extraKnownMarketplaces[NEW_MARKETPLACE_NAME]) {
471
+ settings.extraKnownMarketplaces[NEW_MARKETPLACE_NAME] = entry;
472
+ }
473
+ total++;
474
+ }
465
475
  }
466
476
  return total;
467
477
  }
@@ -519,32 +529,86 @@ export async function migrateMarketplaceRename(projectPath) {
519
529
  }
520
530
  }
521
531
  catch { /* skip if unreadable */ }
522
- // 4. known_marketplaces.json — rename the key
532
+ // 4. known_marketplaces.json — rename old keys + physical directory cleanup
533
+ const pluginsDir = path.join(os.homedir(), ".claude", "plugins", "marketplaces");
534
+ const newDir = path.join(pluginsDir, NEW_MARKETPLACE_NAME);
523
535
  try {
524
536
  const known = await readKnownMarketplaces();
525
- if (known[OLD_MARKETPLACE_NAME]) {
526
- known[NEW_MARKETPLACE_NAME] = {
527
- ...known[OLD_MARKETPLACE_NAME],
528
- source: {
529
- ...known[OLD_MARKETPLACE_NAME].source,
530
- repo: "MadAppGang/magus",
531
- },
532
- };
533
- delete known[OLD_MARKETPLACE_NAME];
537
+ let knownModified = false;
538
+ for (const oldName of OLD_MARKETPLACE_NAMES) {
539
+ if (known[oldName]) {
540
+ const oldEntry = known[oldName];
541
+ // If canonical entry doesn't exist yet, create it
542
+ if (!known[NEW_MARKETPLACE_NAME]) {
543
+ known[NEW_MARKETPLACE_NAME] = {
544
+ ...oldEntry,
545
+ source: {
546
+ ...oldEntry.source,
547
+ repo: "MadAppGang/magus",
548
+ },
549
+ };
550
+ }
551
+ delete known[oldName];
552
+ knownModified = true;
553
+ }
554
+ // Ensure installLocation doesn't reference old directory names
555
+ if (known[NEW_MARKETPLACE_NAME]?.installLocation?.includes(oldName)) {
556
+ known[NEW_MARKETPLACE_NAME].installLocation =
557
+ known[NEW_MARKETPLACE_NAME].installLocation.replace(oldName, NEW_MARKETPLACE_NAME);
558
+ knownModified = true;
559
+ }
560
+ }
561
+ if (knownModified) {
534
562
  await writeKnownMarketplaces(known);
535
563
  result.knownMarketplacesMigrated = true;
536
564
  }
537
565
  }
538
566
  catch { /* skip if unreadable */ }
567
+ // 4b. Rename/remove old physical directories (runs even if keys were already migrated)
568
+ for (const oldName of OLD_MARKETPLACE_NAMES) {
569
+ const oldDir = path.join(pluginsDir, oldName);
570
+ try {
571
+ if (await fs.pathExists(oldDir)) {
572
+ if (!(await fs.pathExists(newDir))) {
573
+ await fs.rename(oldDir, newDir);
574
+ }
575
+ else {
576
+ // Both exist — remove the old one (magus dir is canonical)
577
+ await fs.remove(oldDir);
578
+ }
579
+ }
580
+ }
581
+ catch { /* non-fatal: directory cleanup is best-effort */ }
582
+ }
583
+ // 4c. Update git remote URL in the marketplace clone (old → new repo)
584
+ try {
585
+ if (await fs.pathExists(path.join(newDir, ".git"))) {
586
+ const { execSync } = await import("node:child_process");
587
+ const remote = execSync("git remote get-url origin", {
588
+ cwd: newDir, encoding: "utf-8", timeout: 5000,
589
+ }).trim();
590
+ if (remote.includes("claude-code") && remote.includes("MadAppGang")) {
591
+ const newRemote = remote.replace("claude-code", NEW_MARKETPLACE_NAME);
592
+ execSync(`git remote set-url origin "${newRemote}"`, {
593
+ cwd: newDir, encoding: "utf-8", timeout: 5000,
594
+ });
595
+ }
596
+ }
597
+ }
598
+ catch { /* non-fatal: git remote update is best-effort */ }
539
599
  // 5. installed_plugins.json — rename plugin ID keys
540
600
  try {
541
601
  const registry = await readInstalledPluginsRegistry();
542
602
  let regCount = 0;
543
603
  const newPlugins = {};
544
604
  for (const [pluginId, entries] of Object.entries(registry.plugins)) {
545
- if (pluginId.endsWith(`@${OLD_MARKETPLACE_NAME}`)) {
605
+ const oldName = OLD_MARKETPLACE_NAMES.find((n) => pluginId.endsWith(`@${n}`));
606
+ if (oldName) {
546
607
  const pluginName = pluginId.slice(0, pluginId.lastIndexOf("@"));
547
- newPlugins[`${pluginName}@${NEW_MARKETPLACE_NAME}`] = entries;
608
+ const newKey = `${pluginName}@${NEW_MARKETPLACE_NAME}`;
609
+ if (!newPlugins[newKey]) {
610
+ newPlugins[newKey] = entries;
611
+ }
548
612
  regCount++;
549
613
  }
550
614
  else {
@@ -630,15 +630,16 @@ export interface MarketplaceRecoveryResult {
630
630
  }
631
631
 
632
632
  // =============================================================================
633
- // MARKETPLACE RENAME MIGRATION: mag-claude-plugins → magus
633
+ // MARKETPLACE RENAME MIGRATION: mag-claude-plugins / MadAppGang-claude-code → magus
634
634
  // =============================================================================
635
635
 
636
- const OLD_MARKETPLACE_NAME = "mag-claude-plugins";
636
+ const OLD_MARKETPLACE_NAMES = ["mag-claude-plugins", "MadAppGang-claude-code"];
637
637
  const NEW_MARKETPLACE_NAME = "magus";
638
638
 
639
639
  /**
640
- * Rename plugin keys in a Record from old marketplace to new.
640
+ * Rename plugin keys in a Record from any old marketplace name to new.
641
641
  * e.g., "frontend@mag-claude-plugins" → "frontend@magus"
642
+ * "dev@MadAppGang-claude-code" → "dev@magus"
642
643
  * Returns [migratedRecord, count] — count=0 means no changes.
643
644
  */
644
645
  function migratePluginKeys<T>(
@@ -648,9 +649,14 @@ function migratePluginKeys<T>(
648
649
  const migrated: Record<string, T> = {};
649
650
  let count = 0;
650
651
  for (const [key, value] of Object.entries(record)) {
651
- if (key.endsWith(`@${OLD_MARKETPLACE_NAME}`)) {
652
+ const oldName = OLD_MARKETPLACE_NAMES.find((n) => key.endsWith(`@${n}`));
653
+ if (oldName) {
652
654
  const pluginName = key.slice(0, key.lastIndexOf("@"));
653
- migrated[`${pluginName}@${NEW_MARKETPLACE_NAME}`] = value;
655
+ const newKey = `${pluginName}@${NEW_MARKETPLACE_NAME}`;
656
+ // Don't overwrite if canonical key already exists
657
+ if (!record[newKey]) {
658
+ migrated[newKey] = value;
659
+ }
654
660
  count++;
655
661
  } else {
656
662
  migrated[key] = value;
@@ -678,12 +684,16 @@ function migrateSettingsObject(settings: ClaudeSettings): number {
678
684
  total += ivCount;
679
685
  }
680
686
 
681
- // Migrate extraKnownMarketplaces key
682
- if (settings.extraKnownMarketplaces?.[OLD_MARKETPLACE_NAME]) {
683
- const entry = settings.extraKnownMarketplaces[OLD_MARKETPLACE_NAME];
684
- delete settings.extraKnownMarketplaces[OLD_MARKETPLACE_NAME];
685
- settings.extraKnownMarketplaces[NEW_MARKETPLACE_NAME] = entry;
686
- total++;
687
+ // Migrate extraKnownMarketplaces keys
688
+ for (const oldName of OLD_MARKETPLACE_NAMES) {
689
+ if (settings.extraKnownMarketplaces?.[oldName]) {
690
+ const entry = settings.extraKnownMarketplaces[oldName];
691
+ delete settings.extraKnownMarketplaces[oldName];
692
+ if (!settings.extraKnownMarketplaces[NEW_MARKETPLACE_NAME]) {
693
+ settings.extraKnownMarketplaces[NEW_MARKETPLACE_NAME] = entry;
694
+ }
695
+ total++;
696
+ }
687
697
  }
688
698
 
689
699
  return total;
@@ -748,32 +758,92 @@ export async function migrateMarketplaceRename(
748
758
  }
749
759
  } catch { /* skip if unreadable */ }
750
760
 
751
- // 4. known_marketplaces.json — rename the key
761
+ // 4. known_marketplaces.json — rename old keys + physical directory cleanup
762
+ const pluginsDir = path.join(os.homedir(), ".claude", "plugins", "marketplaces");
763
+ const newDir = path.join(pluginsDir, NEW_MARKETPLACE_NAME);
764
+
752
765
  try {
753
766
  const known = await readKnownMarketplaces();
754
- if (known[OLD_MARKETPLACE_NAME]) {
755
- known[NEW_MARKETPLACE_NAME] = {
756
- ...known[OLD_MARKETPLACE_NAME],
757
- source: {
758
- ...known[OLD_MARKETPLACE_NAME].source,
759
- repo: "MadAppGang/magus",
760
- },
761
- };
762
- delete known[OLD_MARKETPLACE_NAME];
767
+ let knownModified = false;
768
+
769
+ for (const oldName of OLD_MARKETPLACE_NAMES) {
770
+ if (known[oldName]) {
771
+ const oldEntry = known[oldName];
772
+ // If canonical entry doesn't exist yet, create it
773
+ if (!known[NEW_MARKETPLACE_NAME]) {
774
+ known[NEW_MARKETPLACE_NAME] = {
775
+ ...oldEntry,
776
+ source: {
777
+ ...oldEntry.source,
778
+ repo: "MadAppGang/magus",
779
+ },
780
+ };
781
+ }
782
+ delete known[oldName];
783
+ knownModified = true;
784
+ }
785
+
786
+ // Ensure installLocation doesn't reference old directory names
787
+ if (known[NEW_MARKETPLACE_NAME]?.installLocation?.includes(oldName)) {
788
+ known[NEW_MARKETPLACE_NAME].installLocation =
789
+ known[NEW_MARKETPLACE_NAME].installLocation.replace(
790
+ oldName,
791
+ NEW_MARKETPLACE_NAME,
792
+ );
793
+ knownModified = true;
794
+ }
795
+ }
796
+
797
+ if (knownModified) {
763
798
  await writeKnownMarketplaces(known);
764
799
  result.knownMarketplacesMigrated = true;
765
800
  }
766
801
  } catch { /* skip if unreadable */ }
767
802
 
803
+ // 4b. Rename/remove old physical directories (runs even if keys were already migrated)
804
+ for (const oldName of OLD_MARKETPLACE_NAMES) {
805
+ const oldDir = path.join(pluginsDir, oldName);
806
+ try {
807
+ if (await fs.pathExists(oldDir)) {
808
+ if (!(await fs.pathExists(newDir))) {
809
+ await fs.rename(oldDir, newDir);
810
+ } else {
811
+ // Both exist — remove the old one (magus dir is canonical)
812
+ await fs.remove(oldDir);
813
+ }
814
+ }
815
+ } catch { /* non-fatal: directory cleanup is best-effort */ }
816
+ }
817
+
818
+ // 4c. Update git remote URL in the marketplace clone (old → new repo)
819
+ try {
820
+ if (await fs.pathExists(path.join(newDir, ".git"))) {
821
+ const { execSync } = await import("node:child_process");
822
+ const remote = execSync("git remote get-url origin", {
823
+ cwd: newDir, encoding: "utf-8", timeout: 5000,
824
+ }).trim();
825
+ if (remote.includes("claude-code") && remote.includes("MadAppGang")) {
826
+ const newRemote = remote.replace("claude-code", NEW_MARKETPLACE_NAME);
827
+ execSync(`git remote set-url origin "${newRemote}"`, {
828
+ cwd: newDir, encoding: "utf-8", timeout: 5000,
829
+ });
830
+ }
831
+ }
832
+ } catch { /* non-fatal: git remote update is best-effort */ }
833
+
768
834
  // 5. installed_plugins.json — rename plugin ID keys
769
835
  try {
770
836
  const registry = await readInstalledPluginsRegistry();
771
837
  let regCount = 0;
772
838
  const newPlugins: typeof registry.plugins = {};
773
839
  for (const [pluginId, entries] of Object.entries(registry.plugins)) {
774
- if (pluginId.endsWith(`@${OLD_MARKETPLACE_NAME}`)) {
840
+ const oldName = OLD_MARKETPLACE_NAMES.find((n) => pluginId.endsWith(`@${n}`));
841
+ if (oldName) {
775
842
  const pluginName = pluginId.slice(0, pluginId.lastIndexOf("@"));
776
- newPlugins[`${pluginName}@${NEW_MARKETPLACE_NAME}`] = entries;
843
+ const newKey = `${pluginName}@${NEW_MARKETPLACE_NAME}`;
844
+ if (!newPlugins[newKey]) {
845
+ newPlugins[newKey] = entries;
846
+ }
777
847
  regCount++;
778
848
  } else {
779
849
  newPlugins[pluginId] = entries;
package/src/ui/App.js CHANGED
@@ -182,7 +182,7 @@ function AppContentInner({ showDebug, onDebugToggle, updateInfo, onExit, }) {
182
182
  type: "SHOW_PROGRESS",
183
183
  state: { message: "Scanning marketplaces..." },
184
184
  });
185
- // Migrate mag-claude-plugins → magus (idempotent), then repair plugin.json files
185
+ // Migrate old marketplace names → magus (idempotent), then repair plugin.json files
186
186
  migrateMarketplaceRename()
187
187
  .catch(() => { }); // non-blocking, best-effort
188
188
  repairAllMarketplaces()
package/src/ui/App.tsx CHANGED
@@ -265,7 +265,7 @@ function AppContentInner({
265
265
  state: { message: "Scanning marketplaces..." },
266
266
  });
267
267
 
268
- // Migrate mag-claude-plugins → magus (idempotent), then repair plugin.json files
268
+ // Migrate old marketplace names → magus (idempotent), then repair plugin.json files
269
269
  migrateMarketplaceRename()
270
270
  .catch(() => {}); // non-blocking, best-effort
271
271