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 CHANGED
@@ -1,56 +1,51 @@
1
1
  {
2
2
  "version": "1.0.0",
3
- "lastUpdated": "2025-02-15T00:00:00Z",
3
+ "lastUpdated": "2026-02-16T00:00:00Z",
4
4
  "profiles": [
5
5
  {
6
- "name": "senior-developer",
6
+ "name": "devtools",
7
7
  "author": "marketplace",
8
8
  "version": "1.0.0",
9
- "description": "Senior software engineer persona with focus on code review, architecture, and mentoring",
10
- "tags": ["code-review", "architecture", "mentoring"],
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": "2025-02-15T00:00:00Z"
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": "security-auditor",
21
+ "name": "code-quality",
17
22
  "author": "marketplace",
18
23
  "version": "1.0.0",
19
- "description": "Security-focused reviewer checking for OWASP vulnerabilities and best practices",
20
- "tags": ["security", "owasp", "code-review"],
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": "2025-02-15T00:00:00Z"
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": "python-expert",
36
+ "name": "git-workflow",
27
37
  "author": "marketplace",
28
38
  "version": "1.0.0",
29
- "description": "Python development expert with focus on idiomatic code, typing, and async patterns",
30
- "tags": ["python", "typing", "async"],
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": "2025-02-15T00:00:00Z"
34
- },
35
- {
36
- "name": "test-writer",
37
- "author": "marketplace",
38
- "version": "1.0.0",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-profile-manager",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "Save, share, and load Claude CLI profiles - a marketplace for Claude configurations",
5
5
  "type": "module",
6
6
  "main": "src/cli.js",
@@ -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
- if (metadata.files?.length) {
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
- console.log('');
239
- console.log(chalk.bold('Files Included:'));
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
- const maxShow = 15;
243
- const files = metadata.files.slice(0, maxShow);
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}`));
@@ -17,17 +17,13 @@ const DEFAULT_EXCLUDES = [
17
17
  '.git'
18
18
  ];
19
19
 
20
- // Files that are safe to include (allowlist approach)
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
- 'templates',
30
- 'templates/**',
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
- 'keybindings.json'
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
  */