claudeup 4.0.1 → 4.1.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 +1 -1
- package/src/data/cli-tools.ts +1 -2
- package/src/data/marketplaces.js +6 -2
- package/src/data/marketplaces.ts +10 -3
- package/src/prerunner/index.js +71 -7
- package/src/prerunner/index.ts +94 -6
- package/src/services/claude-settings.js +28 -11
- package/src/services/claude-settings.ts +47 -18
- package/src/types/index.ts +33 -0
- package/src/ui/renderers/profileRenderers.js +4 -4
- package/src/ui/renderers/profileRenderers.tsx +10 -12
package/package.json
CHANGED
package/src/data/cli-tools.ts
CHANGED
|
@@ -18,8 +18,7 @@ export const cliTools: CliTool[] = [
|
|
|
18
18
|
"TUI tool for managing Claude Code plugins, MCPs, and configuration",
|
|
19
19
|
installCommand: "npm install -g claudeup",
|
|
20
20
|
checkCommand: "claudeup --version",
|
|
21
|
-
website:
|
|
22
|
-
"https://github.com/MadAppGang/magus/tree/main/tools/claudeup",
|
|
21
|
+
website: "https://github.com/MadAppGang/magus/tree/main/tools/claudeup",
|
|
23
22
|
category: "ai-coding",
|
|
24
23
|
packageManager: "npm",
|
|
25
24
|
packageName: "claudeup",
|
package/src/data/marketplaces.js
CHANGED
|
@@ -70,7 +70,8 @@ export function getAllMarketplaces(localMarketplaces) {
|
|
|
70
70
|
const canonical = deprecatedMarketplaces[name];
|
|
71
71
|
if (canonical) {
|
|
72
72
|
// If canonical already in the map or in defaults, skip this entry
|
|
73
|
-
if (all.has(canonical) ||
|
|
73
|
+
if (all.has(canonical) ||
|
|
74
|
+
defaultMarketplaces.some((m) => m.name === canonical)) {
|
|
74
75
|
continue;
|
|
75
76
|
}
|
|
76
77
|
}
|
|
@@ -85,7 +86,10 @@ export function getAllMarketplaces(localMarketplaces) {
|
|
|
85
86
|
name,
|
|
86
87
|
// Prefer default displayName over stale local clone data
|
|
87
88
|
displayName: defaultMp?.displayName || local.name || formatMarketplaceName(name),
|
|
88
|
-
source: {
|
|
89
|
+
source: {
|
|
90
|
+
source: "github",
|
|
91
|
+
repo: defaultMp?.source.repo || local.gitRepo || "",
|
|
92
|
+
},
|
|
89
93
|
description: defaultMp?.description || local.description || "",
|
|
90
94
|
official: defaultMp?.official ?? repo.toLowerCase().includes("anthropics/"),
|
|
91
95
|
featured: defaultMp?.featured,
|
package/src/data/marketplaces.ts
CHANGED
|
@@ -85,7 +85,10 @@ export function getAllMarketplaces(
|
|
|
85
85
|
const canonical = deprecatedMarketplaces[name];
|
|
86
86
|
if (canonical) {
|
|
87
87
|
// If canonical already in the map or in defaults, skip this entry
|
|
88
|
-
if (
|
|
88
|
+
if (
|
|
89
|
+
all.has(canonical) ||
|
|
90
|
+
defaultMarketplaces.some((m) => m.name === canonical)
|
|
91
|
+
) {
|
|
89
92
|
continue;
|
|
90
93
|
}
|
|
91
94
|
}
|
|
@@ -100,8 +103,12 @@ export function getAllMarketplaces(
|
|
|
100
103
|
all.set(name, {
|
|
101
104
|
name,
|
|
102
105
|
// Prefer default displayName over stale local clone data
|
|
103
|
-
displayName:
|
|
104
|
-
|
|
106
|
+
displayName:
|
|
107
|
+
defaultMp?.displayName || local.name || formatMarketplaceName(name),
|
|
108
|
+
source: {
|
|
109
|
+
source: "github" as const,
|
|
110
|
+
repo: defaultMp?.source.repo || local.gitRepo || "",
|
|
111
|
+
},
|
|
105
112
|
description: defaultMp?.description || local.description || "",
|
|
106
113
|
official:
|
|
107
114
|
defaultMp?.official ?? repo.toLowerCase().includes("anthropics/"),
|
package/src/prerunner/index.js
CHANGED
|
@@ -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, saveGlobalInstalledPluginVersion, } from "../services/claude-settings.js";
|
|
7
|
+
import { recoverMarketplaceSettings, migrateMarketplaceRename, getGlobalEnabledPlugins, getEnabledPlugins, getLocalEnabledPlugins, saveGlobalInstalledPluginVersion, readGlobalSettings, writeGlobalSettings, } 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";
|
|
@@ -22,20 +22,26 @@ async function getReferencedMarketplaces(projectPath) {
|
|
|
22
22
|
for (const id of Object.keys(global))
|
|
23
23
|
allPluginIds.add(id);
|
|
24
24
|
}
|
|
25
|
-
catch {
|
|
25
|
+
catch {
|
|
26
|
+
/* skip if unreadable */
|
|
27
|
+
}
|
|
26
28
|
if (projectPath) {
|
|
27
29
|
try {
|
|
28
30
|
const project = await getEnabledPlugins(projectPath);
|
|
29
31
|
for (const id of Object.keys(project))
|
|
30
32
|
allPluginIds.add(id);
|
|
31
33
|
}
|
|
32
|
-
catch {
|
|
34
|
+
catch {
|
|
35
|
+
/* skip if unreadable */
|
|
36
|
+
}
|
|
33
37
|
try {
|
|
34
38
|
const local = await getLocalEnabledPlugins(projectPath);
|
|
35
39
|
for (const id of Object.keys(local))
|
|
36
40
|
allPluginIds.add(id);
|
|
37
41
|
}
|
|
38
|
-
catch {
|
|
42
|
+
catch {
|
|
43
|
+
/* skip if unreadable */
|
|
44
|
+
}
|
|
39
45
|
}
|
|
40
46
|
// Parse marketplace names from plugin IDs
|
|
41
47
|
for (const pluginId of allPluginIds) {
|
|
@@ -73,6 +79,57 @@ async function autoAddMissingMarketplaces(projectPath) {
|
|
|
73
79
|
}
|
|
74
80
|
return added;
|
|
75
81
|
}
|
|
82
|
+
const CONTINUITY_PLUGIN_SENTINEL = "tmux-claude-continuity";
|
|
83
|
+
const CONTINUITY_PLUGIN_SCRIPT = path.join(os.homedir(), ".tmux", "plugins", "tmux-claude-continuity", "scripts", "on_session_start.sh");
|
|
84
|
+
/**
|
|
85
|
+
* Ensure tmux-claude-continuity Claude Code hooks are present in global settings.
|
|
86
|
+
* If the tmux plugin is installed but the hooks are missing, they are appended.
|
|
87
|
+
* Returns a description of what was added, or null if nothing changed.
|
|
88
|
+
*/
|
|
89
|
+
async function ensureTmuxContinuityHooks() {
|
|
90
|
+
// Plugin not installed — nothing to do
|
|
91
|
+
if (!(await fs.pathExists(CONTINUITY_PLUGIN_SCRIPT))) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const settings = await readGlobalSettings();
|
|
95
|
+
// Check if hooks are already configured by looking for the sentinel string
|
|
96
|
+
const existingHooks = settings.hooks ?? {};
|
|
97
|
+
for (const groups of Object.values(existingHooks)) {
|
|
98
|
+
for (const group of groups) {
|
|
99
|
+
for (const hook of group.hooks) {
|
|
100
|
+
if (hook.command.includes(CONTINUITY_PLUGIN_SENTINEL)) {
|
|
101
|
+
return null; // Already configured
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Append hooks, preserving any existing entries in SessionStart and Stop
|
|
107
|
+
const sessionStartGroups = existingHooks["SessionStart"] ?? [];
|
|
108
|
+
const stopGroups = existingHooks["Stop"] ?? [];
|
|
109
|
+
sessionStartGroups.push({
|
|
110
|
+
hooks: [
|
|
111
|
+
{
|
|
112
|
+
type: "command",
|
|
113
|
+
command: "bash ~/.tmux/plugins/tmux-claude-continuity/scripts/on_session_start.sh",
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
});
|
|
117
|
+
stopGroups.push({
|
|
118
|
+
hooks: [
|
|
119
|
+
{
|
|
120
|
+
type: "command",
|
|
121
|
+
command: "bash ~/.tmux/plugins/tmux-claude-continuity/scripts/on_stop.sh",
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
});
|
|
125
|
+
settings.hooks = {
|
|
126
|
+
...existingHooks,
|
|
127
|
+
SessionStart: sessionStartGroups,
|
|
128
|
+
Stop: stopGroups,
|
|
129
|
+
};
|
|
130
|
+
await writeGlobalSettings(settings);
|
|
131
|
+
return "SessionStart + Stop hooks";
|
|
132
|
+
}
|
|
76
133
|
/**
|
|
77
134
|
* Prerun orchestration: Check for updates, apply them, then run claude
|
|
78
135
|
* @param claudeArgs - Arguments to pass to claude CLI
|
|
@@ -84,9 +141,11 @@ export async function prerunClaude(claudeArgs, options = {}) {
|
|
|
84
141
|
try {
|
|
85
142
|
// STEP 0: Migrate old marketplace names → magus (idempotent, no-ops if already migrated)
|
|
86
143
|
const migration = await migrateMarketplaceRename();
|
|
87
|
-
const migTotal = migration.projectMigrated +
|
|
88
|
-
|
|
89
|
-
|
|
144
|
+
const migTotal = migration.projectMigrated +
|
|
145
|
+
migration.globalMigrated +
|
|
146
|
+
migration.localMigrated +
|
|
147
|
+
migration.registryMigrated +
|
|
148
|
+
(migration.knownMarketplacesMigrated ? 1 : 0);
|
|
90
149
|
if (migTotal > 0) {
|
|
91
150
|
console.log(`✓ Migrated ${migTotal} plugin reference(s) → magus`);
|
|
92
151
|
}
|
|
@@ -99,6 +158,11 @@ export async function prerunClaude(claudeArgs, options = {}) {
|
|
|
99
158
|
console.log(`✓ Auto-added marketplace(s): ${addedMarketplaces.join(", ")}`);
|
|
100
159
|
}
|
|
101
160
|
}
|
|
161
|
+
// STEP 0.6: Ensure tmux-claude-continuity hooks are configured
|
|
162
|
+
const addedHooks = await ensureTmuxContinuityHooks();
|
|
163
|
+
if (addedHooks) {
|
|
164
|
+
console.log(`✓ Added tmux-claude-continuity hooks to ~/.claude/settings.json`);
|
|
165
|
+
}
|
|
102
166
|
// STEP 1: Check if we should update (time-based cache, or forced)
|
|
103
167
|
const shouldUpdate = options.force || (await cache.shouldCheckForUpdates());
|
|
104
168
|
if (options.force) {
|
package/src/prerunner/index.ts
CHANGED
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
getEnabledPlugins,
|
|
15
15
|
getLocalEnabledPlugins,
|
|
16
16
|
saveGlobalInstalledPluginVersion,
|
|
17
|
+
readGlobalSettings,
|
|
18
|
+
writeGlobalSettings,
|
|
17
19
|
} from "../services/claude-settings.js";
|
|
18
20
|
import { parsePluginId } from "../utils/string-utils.js";
|
|
19
21
|
import { defaultMarketplaces } from "../data/marketplaces.js";
|
|
@@ -49,18 +51,24 @@ async function getReferencedMarketplaces(
|
|
|
49
51
|
try {
|
|
50
52
|
const global = await getGlobalEnabledPlugins();
|
|
51
53
|
for (const id of Object.keys(global)) allPluginIds.add(id);
|
|
52
|
-
} catch {
|
|
54
|
+
} catch {
|
|
55
|
+
/* skip if unreadable */
|
|
56
|
+
}
|
|
53
57
|
|
|
54
58
|
if (projectPath) {
|
|
55
59
|
try {
|
|
56
60
|
const project = await getEnabledPlugins(projectPath);
|
|
57
61
|
for (const id of Object.keys(project)) allPluginIds.add(id);
|
|
58
|
-
} catch {
|
|
62
|
+
} catch {
|
|
63
|
+
/* skip if unreadable */
|
|
64
|
+
}
|
|
59
65
|
|
|
60
66
|
try {
|
|
61
67
|
const local = await getLocalEnabledPlugins(projectPath);
|
|
62
68
|
for (const id of Object.keys(local)) allPluginIds.add(id);
|
|
63
|
-
} catch {
|
|
69
|
+
} catch {
|
|
70
|
+
/* skip if unreadable */
|
|
71
|
+
}
|
|
64
72
|
}
|
|
65
73
|
|
|
66
74
|
// Parse marketplace names from plugin IDs
|
|
@@ -108,6 +116,75 @@ async function autoAddMissingMarketplaces(
|
|
|
108
116
|
return added;
|
|
109
117
|
}
|
|
110
118
|
|
|
119
|
+
const CONTINUITY_PLUGIN_SENTINEL = "tmux-claude-continuity";
|
|
120
|
+
const CONTINUITY_PLUGIN_SCRIPT = path.join(
|
|
121
|
+
os.homedir(),
|
|
122
|
+
".tmux",
|
|
123
|
+
"plugins",
|
|
124
|
+
"tmux-claude-continuity",
|
|
125
|
+
"scripts",
|
|
126
|
+
"on_session_start.sh",
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Ensure tmux-claude-continuity Claude Code hooks are present in global settings.
|
|
131
|
+
* If the tmux plugin is installed but the hooks are missing, they are appended.
|
|
132
|
+
* Returns a description of what was added, or null if nothing changed.
|
|
133
|
+
*/
|
|
134
|
+
async function ensureTmuxContinuityHooks(): Promise<string | null> {
|
|
135
|
+
// Plugin not installed — nothing to do
|
|
136
|
+
if (!(await fs.pathExists(CONTINUITY_PLUGIN_SCRIPT))) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const settings = await readGlobalSettings();
|
|
141
|
+
|
|
142
|
+
// Check if hooks are already configured by looking for the sentinel string
|
|
143
|
+
const existingHooks = settings.hooks ?? {};
|
|
144
|
+
for (const groups of Object.values(existingHooks)) {
|
|
145
|
+
for (const group of groups) {
|
|
146
|
+
for (const hook of group.hooks) {
|
|
147
|
+
if (hook.command.includes(CONTINUITY_PLUGIN_SENTINEL)) {
|
|
148
|
+
return null; // Already configured
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Append hooks, preserving any existing entries in SessionStart and Stop
|
|
155
|
+
const sessionStartGroups = existingHooks["SessionStart"] ?? [];
|
|
156
|
+
const stopGroups = existingHooks["Stop"] ?? [];
|
|
157
|
+
|
|
158
|
+
sessionStartGroups.push({
|
|
159
|
+
hooks: [
|
|
160
|
+
{
|
|
161
|
+
type: "command",
|
|
162
|
+
command:
|
|
163
|
+
"bash ~/.tmux/plugins/tmux-claude-continuity/scripts/on_session_start.sh",
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
stopGroups.push({
|
|
169
|
+
hooks: [
|
|
170
|
+
{
|
|
171
|
+
type: "command",
|
|
172
|
+
command:
|
|
173
|
+
"bash ~/.tmux/plugins/tmux-claude-continuity/scripts/on_stop.sh",
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
settings.hooks = {
|
|
179
|
+
...existingHooks,
|
|
180
|
+
SessionStart: sessionStartGroups,
|
|
181
|
+
Stop: stopGroups,
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
await writeGlobalSettings(settings);
|
|
185
|
+
return "SessionStart + Stop hooks";
|
|
186
|
+
}
|
|
187
|
+
|
|
111
188
|
/**
|
|
112
189
|
* Prerun orchestration: Check for updates, apply them, then run claude
|
|
113
190
|
* @param claudeArgs - Arguments to pass to claude CLI
|
|
@@ -123,9 +200,12 @@ export async function prerunClaude(
|
|
|
123
200
|
try {
|
|
124
201
|
// STEP 0: Migrate old marketplace names → magus (idempotent, no-ops if already migrated)
|
|
125
202
|
const migration = await migrateMarketplaceRename();
|
|
126
|
-
const migTotal =
|
|
127
|
-
|
|
128
|
-
|
|
203
|
+
const migTotal =
|
|
204
|
+
migration.projectMigrated +
|
|
205
|
+
migration.globalMigrated +
|
|
206
|
+
migration.localMigrated +
|
|
207
|
+
migration.registryMigrated +
|
|
208
|
+
(migration.knownMarketplacesMigrated ? 1 : 0);
|
|
129
209
|
if (migTotal > 0) {
|
|
130
210
|
console.log(`✓ Migrated ${migTotal} plugin reference(s) → magus`);
|
|
131
211
|
}
|
|
@@ -142,6 +222,14 @@ export async function prerunClaude(
|
|
|
142
222
|
}
|
|
143
223
|
}
|
|
144
224
|
|
|
225
|
+
// STEP 0.6: Ensure tmux-claude-continuity hooks are configured
|
|
226
|
+
const addedHooks = await ensureTmuxContinuityHooks();
|
|
227
|
+
if (addedHooks) {
|
|
228
|
+
console.log(
|
|
229
|
+
`✓ Added tmux-claude-continuity hooks to ~/.claude/settings.json`,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
145
233
|
// STEP 1: Check if we should update (time-based cache, or forced)
|
|
146
234
|
const shouldUpdate = options.force || (await cache.shouldCheckForUpdates());
|
|
147
235
|
|
|
@@ -498,7 +498,9 @@ export async function migrateMarketplaceRename(projectPath) {
|
|
|
498
498
|
result.projectMigrated = count;
|
|
499
499
|
}
|
|
500
500
|
}
|
|
501
|
-
catch {
|
|
501
|
+
catch {
|
|
502
|
+
/* skip if unreadable */
|
|
503
|
+
}
|
|
502
504
|
// 2. Global settings
|
|
503
505
|
try {
|
|
504
506
|
const settings = await readGlobalSettings();
|
|
@@ -508,7 +510,9 @@ export async function migrateMarketplaceRename(projectPath) {
|
|
|
508
510
|
result.globalMigrated = count;
|
|
509
511
|
}
|
|
510
512
|
}
|
|
511
|
-
catch {
|
|
513
|
+
catch {
|
|
514
|
+
/* skip if unreadable */
|
|
515
|
+
}
|
|
512
516
|
// 3. Local settings (settings.local.json)
|
|
513
517
|
try {
|
|
514
518
|
const local = await readLocalSettings(projectPath);
|
|
@@ -528,7 +532,9 @@ export async function migrateMarketplaceRename(projectPath) {
|
|
|
528
532
|
result.localMigrated = localCount;
|
|
529
533
|
}
|
|
530
534
|
}
|
|
531
|
-
catch {
|
|
535
|
+
catch {
|
|
536
|
+
/* skip if unreadable */
|
|
537
|
+
}
|
|
532
538
|
// 4. known_marketplaces.json — rename old keys + physical directory cleanup
|
|
533
539
|
const pluginsDir = path.join(os.homedir(), ".claude", "plugins", "marketplaces");
|
|
534
540
|
const newDir = path.join(pluginsDir, NEW_MARKETPLACE_NAME);
|
|
@@ -553,8 +559,7 @@ export async function migrateMarketplaceRename(projectPath) {
|
|
|
553
559
|
}
|
|
554
560
|
// Ensure installLocation doesn't reference old directory names
|
|
555
561
|
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);
|
|
562
|
+
known[NEW_MARKETPLACE_NAME].installLocation = known[NEW_MARKETPLACE_NAME].installLocation.replace(oldName, NEW_MARKETPLACE_NAME);
|
|
558
563
|
knownModified = true;
|
|
559
564
|
}
|
|
560
565
|
}
|
|
@@ -563,7 +568,9 @@ export async function migrateMarketplaceRename(projectPath) {
|
|
|
563
568
|
result.knownMarketplacesMigrated = true;
|
|
564
569
|
}
|
|
565
570
|
}
|
|
566
|
-
catch {
|
|
571
|
+
catch {
|
|
572
|
+
/* skip if unreadable */
|
|
573
|
+
}
|
|
567
574
|
// 4b. Rename/remove old physical directories (runs even if keys were already migrated)
|
|
568
575
|
for (const oldName of OLD_MARKETPLACE_NAMES) {
|
|
569
576
|
const oldDir = path.join(pluginsDir, oldName);
|
|
@@ -578,24 +585,32 @@ export async function migrateMarketplaceRename(projectPath) {
|
|
|
578
585
|
}
|
|
579
586
|
}
|
|
580
587
|
}
|
|
581
|
-
catch {
|
|
588
|
+
catch {
|
|
589
|
+
/* non-fatal: directory cleanup is best-effort */
|
|
590
|
+
}
|
|
582
591
|
}
|
|
583
592
|
// 4c. Update git remote URL in the marketplace clone (old → new repo)
|
|
584
593
|
try {
|
|
585
594
|
if (await fs.pathExists(path.join(newDir, ".git"))) {
|
|
586
595
|
const { execSync } = await import("node:child_process");
|
|
587
596
|
const remote = execSync("git remote get-url origin", {
|
|
588
|
-
cwd: newDir,
|
|
597
|
+
cwd: newDir,
|
|
598
|
+
encoding: "utf-8",
|
|
599
|
+
timeout: 5000,
|
|
589
600
|
}).trim();
|
|
590
601
|
if (remote.includes("claude-code") && remote.includes("MadAppGang")) {
|
|
591
602
|
const newRemote = remote.replace("claude-code", NEW_MARKETPLACE_NAME);
|
|
592
603
|
execSync(`git remote set-url origin "${newRemote}"`, {
|
|
593
|
-
cwd: newDir,
|
|
604
|
+
cwd: newDir,
|
|
605
|
+
encoding: "utf-8",
|
|
606
|
+
timeout: 5000,
|
|
594
607
|
});
|
|
595
608
|
}
|
|
596
609
|
}
|
|
597
610
|
}
|
|
598
|
-
catch {
|
|
611
|
+
catch {
|
|
612
|
+
/* non-fatal: git remote update is best-effort */
|
|
613
|
+
}
|
|
599
614
|
// 5. installed_plugins.json — rename plugin ID keys
|
|
600
615
|
try {
|
|
601
616
|
const registry = await readInstalledPluginsRegistry();
|
|
@@ -621,7 +636,9 @@ export async function migrateMarketplaceRename(projectPath) {
|
|
|
621
636
|
result.registryMigrated = regCount;
|
|
622
637
|
}
|
|
623
638
|
}
|
|
624
|
-
catch {
|
|
639
|
+
catch {
|
|
640
|
+
/* skip if unreadable */
|
|
641
|
+
}
|
|
625
642
|
return result;
|
|
626
643
|
}
|
|
627
644
|
/**
|
|
@@ -732,7 +732,9 @@ export async function migrateMarketplaceRename(
|
|
|
732
732
|
await writeSettings(settings, projectPath);
|
|
733
733
|
result.projectMigrated = count;
|
|
734
734
|
}
|
|
735
|
-
} catch {
|
|
735
|
+
} catch {
|
|
736
|
+
/* skip if unreadable */
|
|
737
|
+
}
|
|
736
738
|
|
|
737
739
|
// 2. Global settings
|
|
738
740
|
try {
|
|
@@ -742,24 +744,39 @@ export async function migrateMarketplaceRename(
|
|
|
742
744
|
await writeGlobalSettings(settings);
|
|
743
745
|
result.globalMigrated = count;
|
|
744
746
|
}
|
|
745
|
-
} catch {
|
|
747
|
+
} catch {
|
|
748
|
+
/* skip if unreadable */
|
|
749
|
+
}
|
|
746
750
|
|
|
747
751
|
// 3. Local settings (settings.local.json)
|
|
748
752
|
try {
|
|
749
753
|
const local = await readLocalSettings(projectPath);
|
|
750
754
|
let localCount = 0;
|
|
751
755
|
const [ep, epCount] = migratePluginKeys(local.enabledPlugins);
|
|
752
|
-
if (epCount > 0) {
|
|
756
|
+
if (epCount > 0) {
|
|
757
|
+
local.enabledPlugins = ep;
|
|
758
|
+
localCount += epCount;
|
|
759
|
+
}
|
|
753
760
|
const [iv, ivCount] = migratePluginKeys(local.installedPluginVersions);
|
|
754
|
-
if (ivCount > 0) {
|
|
761
|
+
if (ivCount > 0) {
|
|
762
|
+
local.installedPluginVersions = iv;
|
|
763
|
+
localCount += ivCount;
|
|
764
|
+
}
|
|
755
765
|
if (localCount > 0) {
|
|
756
766
|
await writeLocalSettings(local, projectPath);
|
|
757
767
|
result.localMigrated = localCount;
|
|
758
768
|
}
|
|
759
|
-
} catch {
|
|
769
|
+
} catch {
|
|
770
|
+
/* skip if unreadable */
|
|
771
|
+
}
|
|
760
772
|
|
|
761
773
|
// 4. known_marketplaces.json — rename old keys + physical directory cleanup
|
|
762
|
-
const pluginsDir = path.join(
|
|
774
|
+
const pluginsDir = path.join(
|
|
775
|
+
os.homedir(),
|
|
776
|
+
".claude",
|
|
777
|
+
"plugins",
|
|
778
|
+
"marketplaces",
|
|
779
|
+
);
|
|
763
780
|
const newDir = path.join(pluginsDir, NEW_MARKETPLACE_NAME);
|
|
764
781
|
|
|
765
782
|
try {
|
|
@@ -785,11 +802,9 @@ export async function migrateMarketplaceRename(
|
|
|
785
802
|
|
|
786
803
|
// Ensure installLocation doesn't reference old directory names
|
|
787
804
|
if (known[NEW_MARKETPLACE_NAME]?.installLocation?.includes(oldName)) {
|
|
788
|
-
known[NEW_MARKETPLACE_NAME].installLocation =
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
NEW_MARKETPLACE_NAME,
|
|
792
|
-
);
|
|
805
|
+
known[NEW_MARKETPLACE_NAME].installLocation = known[
|
|
806
|
+
NEW_MARKETPLACE_NAME
|
|
807
|
+
].installLocation.replace(oldName, NEW_MARKETPLACE_NAME);
|
|
793
808
|
knownModified = true;
|
|
794
809
|
}
|
|
795
810
|
}
|
|
@@ -798,7 +813,9 @@ export async function migrateMarketplaceRename(
|
|
|
798
813
|
await writeKnownMarketplaces(known);
|
|
799
814
|
result.knownMarketplacesMigrated = true;
|
|
800
815
|
}
|
|
801
|
-
} catch {
|
|
816
|
+
} catch {
|
|
817
|
+
/* skip if unreadable */
|
|
818
|
+
}
|
|
802
819
|
|
|
803
820
|
// 4b. Rename/remove old physical directories (runs even if keys were already migrated)
|
|
804
821
|
for (const oldName of OLD_MARKETPLACE_NAMES) {
|
|
@@ -812,7 +829,9 @@ export async function migrateMarketplaceRename(
|
|
|
812
829
|
await fs.remove(oldDir);
|
|
813
830
|
}
|
|
814
831
|
}
|
|
815
|
-
} catch {
|
|
832
|
+
} catch {
|
|
833
|
+
/* non-fatal: directory cleanup is best-effort */
|
|
834
|
+
}
|
|
816
835
|
}
|
|
817
836
|
|
|
818
837
|
// 4c. Update git remote URL in the marketplace clone (old → new repo)
|
|
@@ -820,16 +839,22 @@ export async function migrateMarketplaceRename(
|
|
|
820
839
|
if (await fs.pathExists(path.join(newDir, ".git"))) {
|
|
821
840
|
const { execSync } = await import("node:child_process");
|
|
822
841
|
const remote = execSync("git remote get-url origin", {
|
|
823
|
-
cwd: newDir,
|
|
842
|
+
cwd: newDir,
|
|
843
|
+
encoding: "utf-8",
|
|
844
|
+
timeout: 5000,
|
|
824
845
|
}).trim();
|
|
825
846
|
if (remote.includes("claude-code") && remote.includes("MadAppGang")) {
|
|
826
847
|
const newRemote = remote.replace("claude-code", NEW_MARKETPLACE_NAME);
|
|
827
848
|
execSync(`git remote set-url origin "${newRemote}"`, {
|
|
828
|
-
cwd: newDir,
|
|
849
|
+
cwd: newDir,
|
|
850
|
+
encoding: "utf-8",
|
|
851
|
+
timeout: 5000,
|
|
829
852
|
});
|
|
830
853
|
}
|
|
831
854
|
}
|
|
832
|
-
} catch {
|
|
855
|
+
} catch {
|
|
856
|
+
/* non-fatal: git remote update is best-effort */
|
|
857
|
+
}
|
|
833
858
|
|
|
834
859
|
// 5. installed_plugins.json — rename plugin ID keys
|
|
835
860
|
try {
|
|
@@ -837,7 +862,9 @@ export async function migrateMarketplaceRename(
|
|
|
837
862
|
let regCount = 0;
|
|
838
863
|
const newPlugins: typeof registry.plugins = {};
|
|
839
864
|
for (const [pluginId, entries] of Object.entries(registry.plugins)) {
|
|
840
|
-
const oldName = OLD_MARKETPLACE_NAMES.find((n) =>
|
|
865
|
+
const oldName = OLD_MARKETPLACE_NAMES.find((n) =>
|
|
866
|
+
pluginId.endsWith(`@${n}`),
|
|
867
|
+
);
|
|
841
868
|
if (oldName) {
|
|
842
869
|
const pluginName = pluginId.slice(0, pluginId.lastIndexOf("@"));
|
|
843
870
|
const newKey = `${pluginName}@${NEW_MARKETPLACE_NAME}`;
|
|
@@ -854,7 +881,9 @@ export async function migrateMarketplaceRename(
|
|
|
854
881
|
await writeInstalledPluginsRegistry(registry);
|
|
855
882
|
result.registryMigrated = regCount;
|
|
856
883
|
}
|
|
857
|
-
} catch {
|
|
884
|
+
} catch {
|
|
885
|
+
/* skip if unreadable */
|
|
886
|
+
}
|
|
858
887
|
|
|
859
888
|
return result;
|
|
860
889
|
}
|
package/src/types/index.ts
CHANGED
|
@@ -243,3 +243,36 @@ export interface ProfileEntry {
|
|
|
243
243
|
updatedAt: string;
|
|
244
244
|
scope: "user" | "project";
|
|
245
245
|
}
|
|
246
|
+
|
|
247
|
+
// ─── Predefined Profile Types ──────────────────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
/** A skill reference for predefined profiles */
|
|
250
|
+
export interface PredefinedSkill {
|
|
251
|
+
name: string;
|
|
252
|
+
repo: string;
|
|
253
|
+
skillPath: string;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Settings that can be configured in a predefined profile */
|
|
257
|
+
export interface PredefinedSettings {
|
|
258
|
+
effortLevel?: "low" | "medium" | "high";
|
|
259
|
+
alwaysThinkingEnabled?: boolean;
|
|
260
|
+
model?: "claude-sonnet-4-6" | "claude-opus-4-6";
|
|
261
|
+
outputStyle?: "concise" | "explanatory" | "formal";
|
|
262
|
+
CLAUDE_CODE_ENABLE_TASKS?: boolean;
|
|
263
|
+
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS?: boolean;
|
|
264
|
+
includeGitInstructions?: boolean;
|
|
265
|
+
respectGitignore?: boolean;
|
|
266
|
+
enableAllProjectMcpServers?: boolean;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** A predefined (built-in) profile for claudeup */
|
|
270
|
+
export interface PredefinedProfile {
|
|
271
|
+
id: string;
|
|
272
|
+
name: string;
|
|
273
|
+
description: string;
|
|
274
|
+
targetAudience: string;
|
|
275
|
+
plugins: Record<string, boolean>;
|
|
276
|
+
skills: PredefinedSkill[];
|
|
277
|
+
settings: PredefinedSettings;
|
|
278
|
+
}
|
|
@@ -87,11 +87,11 @@ const predefinedRenderer = {
|
|
|
87
87
|
const { profile } = item;
|
|
88
88
|
const pluginCount = profile.magusPlugins.length + profile.anthropicPlugins.length;
|
|
89
89
|
const skillCount = profile.skills.length;
|
|
90
|
-
const
|
|
90
|
+
const countStr = `${pluginCount}p ${skillCount}s`;
|
|
91
91
|
if (isSelected) {
|
|
92
|
-
return (_jsxs("text", { bg:
|
|
92
|
+
return (_jsxs("text", { bg: theme.selection.bg, fg: theme.selection.fg, children: [" ", profile.name, " ", countStr, " "] }));
|
|
93
93
|
}
|
|
94
|
-
return (_jsxs("text", { children: [
|
|
94
|
+
return (_jsxs("text", { children: [_jsxs("span", { fg: "white", children: [" ", profile.name] }), _jsxs("span", { fg: theme.colors.dim, children: [" ", countStr] })] }));
|
|
95
95
|
},
|
|
96
96
|
renderDetail: ({ item }) => {
|
|
97
97
|
const { profile } = item;
|
|
@@ -99,7 +99,7 @@ const predefinedRenderer = {
|
|
|
99
99
|
const envMap = profile.settings["env"] ?? {};
|
|
100
100
|
const tasksOn = envMap["CLAUDE_CODE_ENABLE_TASKS"] === "true";
|
|
101
101
|
const teamsOn = envMap["CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS"] === "true";
|
|
102
|
-
return (_jsxs("box", { flexDirection: "column", children: [_jsx("text", {
|
|
102
|
+
return (_jsxs("box", { flexDirection: "column", children: [_jsx("box", { children: _jsx("text", { bg: theme.colors.accent, fg: "white", children: _jsxs("strong", { children: [" ", profile.name, " "] }) }) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: theme.colors.muted, children: profile.description }) }), _jsx("box", { children: _jsx("text", { fg: theme.colors.muted, children: `\nMagus (${profile.magusPlugins.length})` }) }), _jsx("box", { children: _jsx("text", { fg: "#00bfa5", children: profile.magusPlugins.map((p) => ` ■ ${p}`).join("\n") }) }), _jsx("box", { children: _jsx("text", { fg: theme.colors.muted, children: `\nAnthropic (${profile.anthropicPlugins.length})` }) }), _jsx("box", { children: _jsx("text", { fg: "#b39ddb", children: profile.anthropicPlugins.map((p) => ` ■ ${p}`).join("\n") }) }), profile.skills.length > 0 && (_jsxs(_Fragment, { children: [_jsx("box", { children: _jsx("text", { fg: theme.colors.muted, children: `\nSkills (${profile.skills.length})` }) }), _jsx("box", { children: _jsx("text", { fg: "#ffd54f", children: profile.skills.map((s) => ` ■ ${s}`).join("\n") }) })] })), _jsx("box", { children: _jsx("text", { fg: theme.colors.dim, children: `\n${DIVIDER}` }) }), _jsx("box", { children: _jsx("text", { fg: theme.colors.muted, children: [
|
|
103
103
|
...settingEntries.map(([k, v]) => ` ${humanizeKey(k).padEnd(18)}${humanizeValue(k, v)}`),
|
|
104
104
|
...(tasksOn ? [` ${"Tasks".padEnd(18)}on`] : []),
|
|
105
105
|
...(teamsOn ? [` ${"Agent Teams".padEnd(18)}on`] : []),
|
|
@@ -112,24 +112,20 @@ const predefinedRenderer: ItemRenderer<{ kind: "predefined"; profile: Predefined
|
|
|
112
112
|
const pluginCount =
|
|
113
113
|
profile.magusPlugins.length + profile.anthropicPlugins.length;
|
|
114
114
|
const skillCount = profile.skills.length;
|
|
115
|
-
const
|
|
116
|
-
`${profile.name} — ${pluginCount} plugins · ${skillCount} skill${skillCount !== 1 ? "s" : ""}`,
|
|
117
|
-
45,
|
|
118
|
-
);
|
|
115
|
+
const countStr = `${pluginCount}p ${skillCount}s`;
|
|
119
116
|
|
|
120
117
|
if (isSelected) {
|
|
121
118
|
return (
|
|
122
|
-
<text bg=
|
|
123
|
-
{" "}
|
|
124
|
-
{label}{" "}
|
|
119
|
+
<text bg={theme.selection.bg} fg={theme.selection.fg}>
|
|
120
|
+
{" "}{profile.name} {countStr}{" "}
|
|
125
121
|
</text>
|
|
126
122
|
);
|
|
127
123
|
}
|
|
128
124
|
|
|
129
125
|
return (
|
|
130
126
|
<text>
|
|
131
|
-
<span fg=
|
|
132
|
-
<span fg={theme.colors.
|
|
127
|
+
<span fg="white">{" "}{profile.name}</span>
|
|
128
|
+
<span fg={theme.colors.dim}> {countStr}</span>
|
|
133
129
|
</text>
|
|
134
130
|
);
|
|
135
131
|
},
|
|
@@ -146,9 +142,11 @@ const predefinedRenderer: ItemRenderer<{ kind: "predefined"; profile: Predefined
|
|
|
146
142
|
|
|
147
143
|
return (
|
|
148
144
|
<box flexDirection="column">
|
|
149
|
-
<
|
|
150
|
-
<
|
|
151
|
-
|
|
145
|
+
<box>
|
|
146
|
+
<text bg={theme.colors.accent} fg="white">
|
|
147
|
+
<strong> {profile.name} </strong>
|
|
148
|
+
</text>
|
|
149
|
+
</box>
|
|
152
150
|
<box marginTop={1}>
|
|
153
151
|
<text fg={theme.colors.muted}>{profile.description}</text>
|
|
154
152
|
</box>
|