claude-profile-manager 1.1.2 → 1.1.3
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/index.json +28 -33
- package/package.json +1 -1
- package/src/commands/local.js +72 -26
- package/src/commands/marketplace.js +50 -4
- package/src/utils/snapshot.js +71 -8
package/index.json
CHANGED
|
@@ -1,56 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "1.0.0",
|
|
3
|
-
"lastUpdated": "
|
|
3
|
+
"lastUpdated": "2026-02-16T00:00:00Z",
|
|
4
4
|
"profiles": [
|
|
5
5
|
{
|
|
6
|
-
"name": "
|
|
6
|
+
"name": "devtools",
|
|
7
7
|
"author": "marketplace",
|
|
8
8
|
"version": "1.0.0",
|
|
9
|
-
"description": "
|
|
10
|
-
"tags": ["
|
|
9
|
+
"description": "Developer productivity commands for dependency auditing, performance profiling, and scaffolding",
|
|
10
|
+
"tags": ["productivity", "scaffolding", "dependencies"],
|
|
11
11
|
"downloads": 0,
|
|
12
12
|
"stars": 0,
|
|
13
|
-
"createdAt": "
|
|
13
|
+
"createdAt": "2026-02-16T00:00:00Z",
|
|
14
|
+
"contents": {
|
|
15
|
+
"instructions": ["CLAUDE.md"],
|
|
16
|
+
"commands": ["deps", "perf", "scaffold"],
|
|
17
|
+
"hooks": ["pre-commit"]
|
|
18
|
+
}
|
|
14
19
|
},
|
|
15
20
|
{
|
|
16
|
-
"name": "
|
|
21
|
+
"name": "code-quality",
|
|
17
22
|
"author": "marketplace",
|
|
18
23
|
"version": "1.0.0",
|
|
19
|
-
"description": "
|
|
20
|
-
"tags": ["
|
|
24
|
+
"description": "Code review, test generation, and type safety commands with quality-focused hooks",
|
|
25
|
+
"tags": ["code-review", "testing", "type-safety"],
|
|
21
26
|
"downloads": 0,
|
|
22
27
|
"stars": 0,
|
|
23
|
-
"createdAt": "
|
|
28
|
+
"createdAt": "2026-02-16T00:00:00Z",
|
|
29
|
+
"contents": {
|
|
30
|
+
"instructions": ["CLAUDE.md"],
|
|
31
|
+
"commands": ["review", "test-gen", "types"],
|
|
32
|
+
"hooks": ["post-save"]
|
|
33
|
+
}
|
|
24
34
|
},
|
|
25
35
|
{
|
|
26
|
-
"name": "
|
|
36
|
+
"name": "git-workflow",
|
|
27
37
|
"author": "marketplace",
|
|
28
38
|
"version": "1.0.0",
|
|
29
|
-
"description": "
|
|
30
|
-
"tags": ["
|
|
39
|
+
"description": "Git workflow commands for PR creation, changelog generation, and commit management",
|
|
40
|
+
"tags": ["git", "workflow", "pull-requests"],
|
|
31
41
|
"downloads": 0,
|
|
32
42
|
"stars": 0,
|
|
33
|
-
"createdAt": "
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"description": "Specialized in generating comprehensive test suites with high coverage",
|
|
40
|
-
"tags": ["testing", "tdd", "quality"],
|
|
41
|
-
"downloads": 0,
|
|
42
|
-
"stars": 0,
|
|
43
|
-
"createdAt": "2025-02-15T00:00:00Z"
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
"name": "docs-generator",
|
|
47
|
-
"author": "marketplace",
|
|
48
|
-
"version": "1.0.0",
|
|
49
|
-
"description": "Technical documentation writer for APIs, READMEs, and code comments",
|
|
50
|
-
"tags": ["documentation", "api-docs", "readme"],
|
|
51
|
-
"downloads": 0,
|
|
52
|
-
"stars": 0,
|
|
53
|
-
"createdAt": "2025-02-15T00:00:00Z"
|
|
43
|
+
"createdAt": "2026-02-16T00:00:00Z",
|
|
44
|
+
"contents": {
|
|
45
|
+
"instructions": ["CLAUDE.md"],
|
|
46
|
+
"commands": ["pr", "changelog", "squash"],
|
|
47
|
+
"hooks": ["pre-push"]
|
|
48
|
+
}
|
|
54
49
|
}
|
|
55
50
|
]
|
|
56
51
|
}
|
package/package.json
CHANGED
package/src/commands/local.js
CHANGED
|
@@ -3,14 +3,54 @@ import ora from 'ora';
|
|
|
3
3
|
import inquirer from 'inquirer';
|
|
4
4
|
import { existsSync, rmSync, readdirSync, statSync } from 'fs';
|
|
5
5
|
import { join } from 'path';
|
|
6
|
-
import {
|
|
7
|
-
createSnapshot,
|
|
8
|
-
extractSnapshot,
|
|
6
|
+
import {
|
|
7
|
+
createSnapshot,
|
|
8
|
+
extractSnapshot,
|
|
9
9
|
readProfileMetadata,
|
|
10
|
-
listLocalProfileNames
|
|
10
|
+
listLocalProfileNames,
|
|
11
|
+
deriveContents
|
|
11
12
|
} from '../utils/snapshot.js';
|
|
12
13
|
import { getConfig, claudeDirExists, getProfilePath } from '../utils/config.js';
|
|
13
14
|
|
|
15
|
+
// Display labels for content categories
|
|
16
|
+
const CATEGORY_LABELS = {
|
|
17
|
+
commands: 'Commands',
|
|
18
|
+
skills: 'Skills',
|
|
19
|
+
mcp: 'MCP Servers',
|
|
20
|
+
mcp_servers: 'MCP Servers',
|
|
21
|
+
agents: 'Agents',
|
|
22
|
+
plugins: 'Plugins',
|
|
23
|
+
hooks: 'Hooks',
|
|
24
|
+
instructions: 'Instructions'
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get contents from metadata, falling back to deriving from files list
|
|
29
|
+
*/
|
|
30
|
+
function getContents(metadata) {
|
|
31
|
+
if (metadata?.contents) return metadata.contents;
|
|
32
|
+
if (metadata?.files) return deriveContents(metadata.files);
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Format a contents object into display lines
|
|
38
|
+
*/
|
|
39
|
+
function formatContentsLines(contents, indent = ' ') {
|
|
40
|
+
if (!contents || Object.keys(contents).length === 0) return [];
|
|
41
|
+
|
|
42
|
+
const lines = [];
|
|
43
|
+
for (const [category, items] of Object.entries(contents)) {
|
|
44
|
+
if (!items || items.length === 0) continue;
|
|
45
|
+
const label = CATEGORY_LABELS[category] || category;
|
|
46
|
+
const display = category === 'commands'
|
|
47
|
+
? items.map(i => `/${i}`).join(', ')
|
|
48
|
+
: items.join(', ');
|
|
49
|
+
lines.push(`${indent}${chalk.white(label + ':')} ${chalk.dim(display)}`);
|
|
50
|
+
}
|
|
51
|
+
return lines;
|
|
52
|
+
}
|
|
53
|
+
|
|
14
54
|
/**
|
|
15
55
|
* Save current .claude folder as a profile
|
|
16
56
|
*/
|
|
@@ -145,14 +185,21 @@ export async function listLocalProfiles() {
|
|
|
145
185
|
|
|
146
186
|
for (const name of profiles) {
|
|
147
187
|
const metadata = readProfileMetadata(name);
|
|
148
|
-
|
|
188
|
+
|
|
149
189
|
console.log(chalk.cyan(' ' + name));
|
|
150
|
-
|
|
190
|
+
|
|
151
191
|
if (metadata) {
|
|
152
192
|
if (metadata.description) {
|
|
153
193
|
console.log(chalk.dim(` ${metadata.description}`));
|
|
154
194
|
}
|
|
155
|
-
|
|
195
|
+
|
|
196
|
+
// Show contents breakdown
|
|
197
|
+
const contents = getContents(metadata);
|
|
198
|
+
const contentsLines = formatContentsLines(contents);
|
|
199
|
+
for (const line of contentsLines) {
|
|
200
|
+
console.log(line);
|
|
201
|
+
}
|
|
202
|
+
|
|
156
203
|
const info = [];
|
|
157
204
|
if (metadata.tags?.length) {
|
|
158
205
|
info.push(chalk.yellow(metadata.tags.join(', ')));
|
|
@@ -161,15 +208,12 @@ export async function listLocalProfiles() {
|
|
|
161
208
|
const date = new Date(metadata.createdAt).toLocaleDateString();
|
|
162
209
|
info.push(chalk.dim(date));
|
|
163
210
|
}
|
|
164
|
-
|
|
165
|
-
info.push(chalk.dim(`${metadata.files.length} files`));
|
|
166
|
-
}
|
|
167
|
-
|
|
211
|
+
|
|
168
212
|
if (info.length) {
|
|
169
213
|
console.log(` ${info.join(' • ')}`);
|
|
170
214
|
}
|
|
171
215
|
}
|
|
172
|
-
|
|
216
|
+
|
|
173
217
|
console.log('');
|
|
174
218
|
}
|
|
175
219
|
|
|
@@ -235,24 +279,26 @@ export async function showProfileInfo(name) {
|
|
|
235
279
|
console.log(chalk.cyan('Platform: ') + (metadata?.platform || chalk.dim('Unknown')));
|
|
236
280
|
console.log(chalk.cyan('Claude Ver: ') + (metadata?.claudeVersion || chalk.dim('Unknown')));
|
|
237
281
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
282
|
+
// Show structured contents
|
|
283
|
+
const contents = getContents(metadata);
|
|
284
|
+
if (Object.keys(contents).length > 0) {
|
|
285
|
+
console.log('');
|
|
286
|
+
console.log(chalk.bold('Contents:'));
|
|
287
|
+
const contentsLines = formatContentsLines(contents, ' ');
|
|
288
|
+
for (const line of contentsLines) {
|
|
289
|
+
console.log(line);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Show raw file list for full detail
|
|
241
294
|
if (metadata?.files?.length) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
for (const file of files) {
|
|
295
|
+
console.log('');
|
|
296
|
+
console.log(chalk.bold('Files:'));
|
|
297
|
+
for (const file of metadata.files) {
|
|
246
298
|
console.log(chalk.dim(' • ') + file);
|
|
247
299
|
}
|
|
248
|
-
|
|
249
|
-
if (metadata.files.length > maxShow) {
|
|
250
|
-
console.log(chalk.dim(` ... and ${metadata.files.length - maxShow} more files`));
|
|
251
|
-
}
|
|
252
|
-
} else {
|
|
253
|
-
console.log(chalk.dim(' No file information available'));
|
|
254
300
|
}
|
|
255
|
-
|
|
301
|
+
|
|
256
302
|
console.log('');
|
|
257
303
|
console.log(chalk.dim('Location: ') + profilePath);
|
|
258
304
|
console.log('');
|
|
@@ -9,6 +9,36 @@ import { extractDownloadedSnapshot } from '../utils/snapshot.js';
|
|
|
9
9
|
|
|
10
10
|
const INDEX_CACHE_TIME = 60 * 60 * 1000; // 1 hour
|
|
11
11
|
|
|
12
|
+
// Display labels for content categories
|
|
13
|
+
const CATEGORY_LABELS = {
|
|
14
|
+
commands: 'Commands',
|
|
15
|
+
skills: 'Skills',
|
|
16
|
+
mcp: 'MCP Servers',
|
|
17
|
+
mcp_servers: 'MCP Servers',
|
|
18
|
+
agents: 'Agents',
|
|
19
|
+
plugins: 'Plugins',
|
|
20
|
+
hooks: 'Hooks',
|
|
21
|
+
instructions: 'Instructions'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Format a contents object into display lines
|
|
26
|
+
*/
|
|
27
|
+
function formatContentsLines(contents, indent = ' ') {
|
|
28
|
+
if (!contents || Object.keys(contents).length === 0) return [];
|
|
29
|
+
|
|
30
|
+
const lines = [];
|
|
31
|
+
for (const [category, items] of Object.entries(contents)) {
|
|
32
|
+
if (!items || items.length === 0) continue;
|
|
33
|
+
const label = CATEGORY_LABELS[category] || category;
|
|
34
|
+
const display = category === 'commands'
|
|
35
|
+
? items.map(i => `/${i}`).join(', ')
|
|
36
|
+
: items.join(', ');
|
|
37
|
+
lines.push(`${indent}${chalk.white(label + ':')} ${chalk.dim(display)}`);
|
|
38
|
+
}
|
|
39
|
+
return lines;
|
|
40
|
+
}
|
|
41
|
+
|
|
12
42
|
/**
|
|
13
43
|
* Get the marketplace index (list of all profiles)
|
|
14
44
|
*/
|
|
@@ -102,18 +132,24 @@ export async function listMarketplace(options) {
|
|
|
102
132
|
for (const profile of profiles) {
|
|
103
133
|
const fullName = `${profile.author}/${profile.name}`;
|
|
104
134
|
console.log(` ${chalk.cyan(fullName)} ${chalk.dim('v' + (profile.version || '1.0.0'))}`);
|
|
105
|
-
|
|
135
|
+
|
|
106
136
|
if (profile.description) {
|
|
107
137
|
console.log(` ${chalk.dim(profile.description.slice(0, 60))}${profile.description.length > 60 ? '...' : ''}`);
|
|
108
138
|
}
|
|
109
|
-
|
|
139
|
+
|
|
140
|
+
// Show contents breakdown
|
|
141
|
+
const contentsLines = formatContentsLines(profile.contents);
|
|
142
|
+
for (const line of contentsLines) {
|
|
143
|
+
console.log(line);
|
|
144
|
+
}
|
|
145
|
+
|
|
110
146
|
const stats = [];
|
|
111
147
|
if (profile.downloads) stats.push(`↓${profile.downloads}`);
|
|
112
148
|
if (profile.stars) stats.push(`★${profile.stars}`);
|
|
113
149
|
if (stats.length) {
|
|
114
150
|
console.log(` ${chalk.yellow(stats.join(' • '))}`);
|
|
115
151
|
}
|
|
116
|
-
|
|
152
|
+
|
|
117
153
|
console.log('');
|
|
118
154
|
}
|
|
119
155
|
}
|
|
@@ -251,7 +287,17 @@ export async function showMarketplaceInfo(profilePath) {
|
|
|
251
287
|
if (metadata.updatedAt) {
|
|
252
288
|
console.log(chalk.cyan('Updated: ') + new Date(metadata.updatedAt).toLocaleDateString());
|
|
253
289
|
}
|
|
254
|
-
|
|
290
|
+
|
|
291
|
+
// Show contents breakdown
|
|
292
|
+
if (metadata.contents && Object.keys(metadata.contents).length > 0) {
|
|
293
|
+
console.log('');
|
|
294
|
+
console.log(chalk.bold('Contents:'));
|
|
295
|
+
const contentsLines = formatContentsLines(metadata.contents, ' ');
|
|
296
|
+
for (const line of contentsLines) {
|
|
297
|
+
console.log(line);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
255
301
|
console.log('');
|
|
256
302
|
console.log(chalk.dim('Install with:'));
|
|
257
303
|
console.log(chalk.cyan(` cpm install ${author}/${name}`));
|
package/src/utils/snapshot.js
CHANGED
|
@@ -17,17 +17,13 @@ const DEFAULT_EXCLUDES = [
|
|
|
17
17
|
'.git'
|
|
18
18
|
];
|
|
19
19
|
|
|
20
|
-
// Files that are safe to include (
|
|
20
|
+
// Files that are safe to include (functional customizations only)
|
|
21
21
|
const SAFE_INCLUDES = [
|
|
22
|
-
'settings.json',
|
|
23
|
-
'settings.local.json',
|
|
24
22
|
'CLAUDE.md',
|
|
25
|
-
'README.md',
|
|
26
|
-
'README',
|
|
27
23
|
'commands',
|
|
28
24
|
'commands/**',
|
|
29
|
-
'
|
|
30
|
-
'
|
|
25
|
+
'skills',
|
|
26
|
+
'skills/**',
|
|
31
27
|
'hooks',
|
|
32
28
|
'hooks/**',
|
|
33
29
|
'plugins',
|
|
@@ -35,7 +31,8 @@ const SAFE_INCLUDES = [
|
|
|
35
31
|
'mcp.json',
|
|
36
32
|
'mcp_servers',
|
|
37
33
|
'mcp_servers/**',
|
|
38
|
-
'
|
|
34
|
+
'agents',
|
|
35
|
+
'agents/**'
|
|
39
36
|
];
|
|
40
37
|
|
|
41
38
|
/**
|
|
@@ -79,6 +76,8 @@ export async function createSnapshot(profileName, options = {}) {
|
|
|
79
76
|
|
|
80
77
|
return new Promise((resolve, reject) => {
|
|
81
78
|
output.on('close', async () => {
|
|
79
|
+
// Derive structured contents from file list
|
|
80
|
+
metadata.contents = deriveContentsWithMcp(metadata.files, claudeDir);
|
|
82
81
|
// Save metadata
|
|
83
82
|
writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
|
|
84
83
|
resolve({ profileDir, metadata });
|
|
@@ -109,6 +108,70 @@ export async function createSnapshot(profileName, options = {}) {
|
|
|
109
108
|
});
|
|
110
109
|
}
|
|
111
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Derive a structured contents summary from a list of file paths.
|
|
113
|
+
* Returns an object with category keys mapping to arrays of item names.
|
|
114
|
+
*/
|
|
115
|
+
export function deriveContents(files) {
|
|
116
|
+
const contents = {};
|
|
117
|
+
|
|
118
|
+
for (const file of files) {
|
|
119
|
+
const normalized = file.split(sep).join('/');
|
|
120
|
+
|
|
121
|
+
if (normalized === 'CLAUDE.md') {
|
|
122
|
+
if (!contents.instructions) contents.instructions = [];
|
|
123
|
+
contents.instructions.push('CLAUDE.md');
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (normalized === 'mcp.json') {
|
|
128
|
+
// Parse MCP server names from mcp.json if possible — but at derivation
|
|
129
|
+
// time we may not have access to file content, so just flag it
|
|
130
|
+
if (!contents.mcp) contents.mcp = [];
|
|
131
|
+
contents.mcp.push('mcp.json');
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const parts = normalized.split('/');
|
|
136
|
+
if (parts.length >= 2) {
|
|
137
|
+
const category = parts[0];
|
|
138
|
+
// Use the immediate child name (file or subfolder)
|
|
139
|
+
const itemName = parts[1].replace(/\.[^.]+$/, ''); // strip extension
|
|
140
|
+
if (!contents[category]) contents[category] = [];
|
|
141
|
+
if (!contents[category].includes(itemName)) {
|
|
142
|
+
contents[category].push(itemName);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return contents;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Derive contents and try to enrich MCP server names from the actual mcp.json file
|
|
152
|
+
*/
|
|
153
|
+
function deriveContentsWithMcp(files, claudeDir) {
|
|
154
|
+
const contents = deriveContents(files);
|
|
155
|
+
|
|
156
|
+
// If mcp.json is in the file list, try to read server names from it
|
|
157
|
+
if (contents.mcp && claudeDir) {
|
|
158
|
+
try {
|
|
159
|
+
const mcpPath = join(claudeDir, 'mcp.json');
|
|
160
|
+
if (existsSync(mcpPath)) {
|
|
161
|
+
const mcpData = JSON.parse(readFileSync(mcpPath, 'utf-8'));
|
|
162
|
+
const serverNames = Object.keys(mcpData.mcpServers || mcpData);
|
|
163
|
+
if (serverNames.length > 0) {
|
|
164
|
+
contents.mcp = serverNames;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
} catch {
|
|
168
|
+
// Keep the fallback
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return contents;
|
|
173
|
+
}
|
|
174
|
+
|
|
112
175
|
/**
|
|
113
176
|
* Get list of files to archive (using allowlist approach)
|
|
114
177
|
*/
|