sumulige-claude 1.0.6 → 1.0.8
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/.claude/.kickoff-hint.txt +51 -0
- package/.claude/ANCHORS.md +40 -0
- package/.claude/MEMORY.md +34 -0
- package/.claude/PROJECT_LOG.md +101 -0
- package/.claude/THINKING_CHAIN_GUIDE.md +287 -0
- package/.claude/commands/commit-push-pr.md +59 -0
- package/.claude/commands/commit.md +53 -0
- package/.claude/commands/pr.md +76 -0
- package/.claude/commands/review.md +61 -0
- package/.claude/commands/sessions.md +62 -0
- package/.claude/commands/skill-create.md +131 -0
- package/.claude/commands/test.md +56 -0
- package/.claude/commands/todos.md +99 -0
- package/.claude/commands/verify-work.md +63 -0
- package/.claude/hooks/code-formatter.cjs +187 -0
- package/.claude/hooks/code-tracer.cjs +331 -0
- package/.claude/hooks/conversation-recorder.cjs +340 -0
- package/.claude/hooks/decision-tracker.cjs +398 -0
- package/.claude/hooks/export.cjs +329 -0
- package/.claude/hooks/multi-session.cjs +181 -0
- package/.claude/hooks/privacy-filter.js +224 -0
- package/.claude/hooks/project-kickoff.cjs +114 -0
- package/.claude/hooks/rag-skill-loader.cjs +159 -0
- package/.claude/hooks/session-end.sh +61 -0
- package/.claude/hooks/sync-to-log.sh +83 -0
- package/.claude/hooks/thinking-silent.cjs +145 -0
- package/.claude/hooks/tl-summary.sh +54 -0
- package/.claude/hooks/todo-manager.cjs +248 -0
- package/.claude/hooks/verify-work.cjs +134 -0
- package/.claude/sessions/active-sessions.json +359 -0
- package/.claude/settings.local.json +43 -2
- package/.claude/thinking-routes/.last-sync +1 -0
- package/.claude/thinking-routes/QUICKREF.md +98 -0
- package/.claude-plugin/marketplace.json +71 -0
- package/.github/workflows/sync-skills.yml +74 -0
- package/AGENTS.md +81 -22
- package/DEV_TOOLS_GUIDE.md +190 -0
- package/PROJECT_STRUCTURE.md +10 -1
- package/README.md +142 -708
- package/cli.js +116 -822
- package/config/defaults.json +34 -0
- package/config/skill-categories.json +40 -0
- package/development/todos/INDEX.md +14 -58
- package/docs/DEVELOPMENT.md +423 -0
- package/docs/MARKETPLACE.md +352 -0
- package/lib/commands.js +698 -0
- package/lib/config.js +71 -0
- package/lib/marketplace.js +486 -0
- package/lib/utils.js +62 -0
- package/package.json +11 -5
- package/scripts/sync-external.mjs +298 -0
- package/scripts/update-registry.mjs +325 -0
- package/sources.yaml +83 -0
package/lib/config.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config - Configuration management
|
|
3
|
+
*
|
|
4
|
+
* Loads default config and merges with user config from ~/.claude/config.json
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const defaults = require('../config/defaults.json');
|
|
10
|
+
|
|
11
|
+
const CONFIG_DIR = path.join(process.env.HOME, '.claude');
|
|
12
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Deep merge two objects
|
|
16
|
+
*/
|
|
17
|
+
function deepMerge(target, source) {
|
|
18
|
+
const result = { ...target };
|
|
19
|
+
for (const key in source) {
|
|
20
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
21
|
+
result[key] = deepMerge(target[key] || {}, source[key]);
|
|
22
|
+
} else {
|
|
23
|
+
result[key] = source[key];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Load configuration (defaults + user overrides)
|
|
31
|
+
* @returns {Object} Merged configuration
|
|
32
|
+
*/
|
|
33
|
+
exports.loadConfig = function() {
|
|
34
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
35
|
+
try {
|
|
36
|
+
const userConfig = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
37
|
+
// Deep merge: user config overrides defaults
|
|
38
|
+
return deepMerge(defaults, userConfig);
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.warn('Warning: Failed to parse user config, using defaults');
|
|
41
|
+
return defaults;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return defaults;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Save configuration to file
|
|
49
|
+
* @param {Object} config - Configuration to save
|
|
50
|
+
*/
|
|
51
|
+
exports.saveConfig = function(config) {
|
|
52
|
+
exports.ensureDir(CONFIG_DIR);
|
|
53
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Ensure a directory exists
|
|
58
|
+
*/
|
|
59
|
+
exports.ensureDir = function(dir) {
|
|
60
|
+
if (!fs.existsSync(dir)) {
|
|
61
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Export constants
|
|
66
|
+
exports.CONFIG_DIR = CONFIG_DIR;
|
|
67
|
+
exports.CONFIG_FILE = CONFIG_FILE;
|
|
68
|
+
exports.DEFAULTS = defaults;
|
|
69
|
+
|
|
70
|
+
// Calculate derived paths
|
|
71
|
+
exports.SKILLS_DIR = path.join(CONFIG_DIR, 'skills');
|
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marketplace Commands
|
|
3
|
+
*
|
|
4
|
+
* Commands for managing the skill marketplace and external skill sources.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { execSync } = require('child_process');
|
|
10
|
+
|
|
11
|
+
const ROOT_DIR = path.join(__dirname, '..');
|
|
12
|
+
const SOURCES_FILE = path.join(ROOT_DIR, 'sources.yaml');
|
|
13
|
+
const MARKETPLACE_FILE = path.join(ROOT_DIR, '.claude-plugin', 'marketplace.json');
|
|
14
|
+
const CATEGORIES_FILE = path.join(ROOT_DIR, 'config', 'skill-categories.json');
|
|
15
|
+
const TEMPLATE_SKILLS_DIR = path.join(ROOT_DIR, 'template', '.claude', 'skills');
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Logging Utilities
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
const COLORS = {
|
|
22
|
+
reset: '\x1b[0m',
|
|
23
|
+
green: '\x1b[32m',
|
|
24
|
+
blue: '\x1b[34m',
|
|
25
|
+
yellow: '\x1b[33m',
|
|
26
|
+
red: '\x1b[31m',
|
|
27
|
+
cyan: '\x1b[36m',
|
|
28
|
+
gray: '\x1b[90m',
|
|
29
|
+
magenta: '\x1b[35m'
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function log(message, color = 'reset') {
|
|
33
|
+
console.log(`${COLORS[color]}${message}${COLORS.reset}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// YAML Parser (Simple)
|
|
38
|
+
// ============================================================================
|
|
39
|
+
|
|
40
|
+
function parseSimpleYaml(content) {
|
|
41
|
+
const result = { skills: [] };
|
|
42
|
+
let currentSection = null;
|
|
43
|
+
let currentSkill = null;
|
|
44
|
+
let currentKey = null;
|
|
45
|
+
|
|
46
|
+
content.split('\n').forEach(line => {
|
|
47
|
+
const trimmed = line.trim();
|
|
48
|
+
const indent = line.search(/\S/);
|
|
49
|
+
|
|
50
|
+
// Skip empty lines and comments
|
|
51
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Version
|
|
56
|
+
if (trimmed.startsWith('version:')) {
|
|
57
|
+
result.version = parseInt(trimmed.split(':')[1].trim());
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Skills array starts
|
|
62
|
+
if (trimmed === 'skills:') {
|
|
63
|
+
currentSection = 'skills';
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// New skill entry (starts with -)
|
|
68
|
+
if (trimmed.startsWith('- name:')) {
|
|
69
|
+
if (currentSkill) {
|
|
70
|
+
result.skills.push(currentSkill);
|
|
71
|
+
}
|
|
72
|
+
currentSkill = { name: trimmed.split(':')[1].trim() };
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Skill properties
|
|
77
|
+
if (currentSection === 'skills' && currentSkill) {
|
|
78
|
+
// Determine nesting level by indent
|
|
79
|
+
const isTopLevel = indent === 2;
|
|
80
|
+
|
|
81
|
+
if (isTopLevel) {
|
|
82
|
+
const match = trimmed.match(/^([\w-]+):\s*(.*)$/);
|
|
83
|
+
if (match) {
|
|
84
|
+
currentKey = match[1];
|
|
85
|
+
let value = match[2].trim();
|
|
86
|
+
|
|
87
|
+
// Handle arrays
|
|
88
|
+
if (value === '[]') {
|
|
89
|
+
value = [];
|
|
90
|
+
} else if (value === 'true') {
|
|
91
|
+
value = true;
|
|
92
|
+
} else if (value === 'false') {
|
|
93
|
+
value = false;
|
|
94
|
+
} else if (value.startsWith('"') || value.startsWith("'")) {
|
|
95
|
+
value = value.slice(1, -1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
currentSkill[currentKey] = value;
|
|
99
|
+
|
|
100
|
+
// Initialize nested objects
|
|
101
|
+
if (['source', 'target', 'author', 'sync'].includes(currentKey)) {
|
|
102
|
+
currentSkill[currentKey] = {};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} else if (currentKey) {
|
|
106
|
+
// Nested property
|
|
107
|
+
const match = trimmed.match(/^([\w-]+):\s*(.*)$/);
|
|
108
|
+
if (match) {
|
|
109
|
+
let value = match[2].trim();
|
|
110
|
+
if (value === 'true') value = true;
|
|
111
|
+
if (value === 'false') value = false;
|
|
112
|
+
if (value === '[]') value = [];
|
|
113
|
+
currentSkill[currentKey][match[1]] = value;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Push last skill
|
|
120
|
+
if (currentSkill) {
|
|
121
|
+
result.skills.push(currentSkill);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ============================================================================
|
|
128
|
+
// Marketplace Commands
|
|
129
|
+
// ============================================================================
|
|
130
|
+
|
|
131
|
+
const marketplaceCommands = {
|
|
132
|
+
// -------------------------------------------------------------------------
|
|
133
|
+
'marketplace:list': () => {
|
|
134
|
+
log('📋 SMC Skill Marketplace', 'blue');
|
|
135
|
+
log('=====================================', 'gray');
|
|
136
|
+
log('');
|
|
137
|
+
|
|
138
|
+
// Load marketplace registry
|
|
139
|
+
let registry = { plugins: [], metadata: { skill_count: 0, categories: {} } };
|
|
140
|
+
if (fs.existsSync(MARKETPLACE_FILE)) {
|
|
141
|
+
try {
|
|
142
|
+
registry = JSON.parse(fs.readFileSync(MARKETPLACE_FILE, 'utf-8'));
|
|
143
|
+
} catch (e) {
|
|
144
|
+
log('Warning: Failed to parse marketplace.json', 'yellow');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Load sources.yaml
|
|
149
|
+
let sources = { skills: [] };
|
|
150
|
+
if (fs.existsSync(SOURCES_FILE)) {
|
|
151
|
+
try {
|
|
152
|
+
const content = fs.readFileSync(SOURCES_FILE, 'utf-8');
|
|
153
|
+
sources = parseSimpleYaml(content);
|
|
154
|
+
} catch (e) {
|
|
155
|
+
log('Warning: Failed to parse sources.yaml', 'yellow');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Display by category
|
|
160
|
+
const categories = registry.metadata.categories || {
|
|
161
|
+
tools: { name: 'CLI 工具', icon: '🔧' },
|
|
162
|
+
workflow: { name: '工作流编排', icon: '🎼' },
|
|
163
|
+
development: { name: '开发辅助', icon: '💻' },
|
|
164
|
+
productivity: { name: '效率工具', icon: '⚡' },
|
|
165
|
+
automation: { name: '自动化', icon: '🤖' },
|
|
166
|
+
data: { name: '数据处理', icon: '📊' },
|
|
167
|
+
documentation: { name: '文档', icon: '📚' }
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// Group skills by category
|
|
171
|
+
const byCategory = {};
|
|
172
|
+
for (const [key, cat] of Object.entries(categories)) {
|
|
173
|
+
byCategory[key] = [];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Add from registry
|
|
177
|
+
for (const plugin of registry.plugins) {
|
|
178
|
+
if (plugin.skill_list) {
|
|
179
|
+
for (const skill of plugin.skill_list) {
|
|
180
|
+
if (byCategory[skill.category]) {
|
|
181
|
+
byCategory[skill.category].push({
|
|
182
|
+
name: skill.name,
|
|
183
|
+
description: skill.description,
|
|
184
|
+
external: skill.external
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Add from sources.yaml
|
|
192
|
+
for (const skill of sources.skills) {
|
|
193
|
+
const cat = skill.target?.category || 'tools';
|
|
194
|
+
if (!byCategory[cat]) {
|
|
195
|
+
byCategory[cat] = [];
|
|
196
|
+
}
|
|
197
|
+
if (!byCategory[cat].some(s => s.name === skill.name)) {
|
|
198
|
+
byCategory[cat].push({
|
|
199
|
+
name: skill.name,
|
|
200
|
+
description: skill.description,
|
|
201
|
+
native: skill.native
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Display
|
|
207
|
+
for (const [key, skills] of Object.entries(byCategory)) {
|
|
208
|
+
if (skills.length === 0) continue;
|
|
209
|
+
|
|
210
|
+
const cat = categories[key] || { name: key, icon: '📁' };
|
|
211
|
+
log(`${cat.icon} ${cat.name}`, 'cyan');
|
|
212
|
+
log('', 'gray');
|
|
213
|
+
|
|
214
|
+
for (const skill of skills) {
|
|
215
|
+
const badge = skill.native ? ' [native]' : skill.external ? ' [external]' : '';
|
|
216
|
+
log(` ${skill.name}${badge}`, 'green');
|
|
217
|
+
if (skill.description) {
|
|
218
|
+
log(` ${skill.description}`, 'gray');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
log('', 'gray');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
log('=====================================', 'gray');
|
|
225
|
+
log(`Total: ${registry.metadata.skill_count || sources.skills.length} skills`, 'gray');
|
|
226
|
+
log('', 'gray');
|
|
227
|
+
log('Commands:', 'gray');
|
|
228
|
+
log(' smc marketplace:install <name> Install a skill', 'gray');
|
|
229
|
+
log(' smc marketplace:sync Sync external skills', 'gray');
|
|
230
|
+
log(' smc marketplace:add <repo> Add external source', 'gray');
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
// -------------------------------------------------------------------------
|
|
234
|
+
'marketplace:install': (skillName) => {
|
|
235
|
+
if (!skillName) {
|
|
236
|
+
log('Usage: smc marketplace:install <skill-name>', 'yellow');
|
|
237
|
+
log('', 'gray');
|
|
238
|
+
log('Examples:', 'gray');
|
|
239
|
+
log(' smc marketplace:install dev-browser', 'gray');
|
|
240
|
+
log(' smc marketplace:install gastown', 'gray');
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
log(`📦 Installing skill: ${skillName}`, 'blue');
|
|
245
|
+
log('', 'gray');
|
|
246
|
+
|
|
247
|
+
// Load sources.yaml to find the skill
|
|
248
|
+
let sources = { skills: [] };
|
|
249
|
+
if (fs.existsSync(SOURCES_FILE)) {
|
|
250
|
+
try {
|
|
251
|
+
const content = fs.readFileSync(SOURCES_FILE, 'utf-8');
|
|
252
|
+
sources = parseSimpleYaml(content);
|
|
253
|
+
} catch (e) {
|
|
254
|
+
log('Warning: Failed to parse sources.yaml', 'yellow');
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const skill = sources.skills.find(s => s.name === skillName);
|
|
259
|
+
if (!skill) {
|
|
260
|
+
log(`Skill "${skillName}" not found in sources.yaml`, 'yellow');
|
|
261
|
+
log('', 'gray');
|
|
262
|
+
log('Available skills:', 'gray');
|
|
263
|
+
log(' smc marketplace:list', 'gray');
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Native skills are already in the repo
|
|
268
|
+
if (skill.native) {
|
|
269
|
+
log(`✅ Skill "${skillName}" is a native skill, already available at:`, 'green');
|
|
270
|
+
log(` ${skill.target.path}`, 'gray');
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// External skills - sync them
|
|
275
|
+
log(`Syncing from external source...`, 'gray');
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
execSync('npm run sync', { stdio: 'inherit' });
|
|
279
|
+
log(``, 'gray');
|
|
280
|
+
log(`✅ Skill "${skillName}" installed`, 'green');
|
|
281
|
+
} catch (e) {
|
|
282
|
+
log(`❌ Failed to install skill`, 'red');
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
// -------------------------------------------------------------------------
|
|
287
|
+
'marketplace:sync': () => {
|
|
288
|
+
log('🔄 Syncing external skills...', 'blue');
|
|
289
|
+
log('', 'gray');
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
execSync('npm run sync:all', { stdio: 'inherit' });
|
|
293
|
+
log('', 'gray');
|
|
294
|
+
log('✅ Sync complete', 'green');
|
|
295
|
+
} catch (e) {
|
|
296
|
+
log('❌ Sync failed', 'red');
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
// -------------------------------------------------------------------------
|
|
301
|
+
'marketplace:add': (repo) => {
|
|
302
|
+
if (!repo) {
|
|
303
|
+
log('Usage: smc marketplace:add <owner/repo>', 'yellow');
|
|
304
|
+
log('', 'gray');
|
|
305
|
+
log('This command adds an external skill repository to sources.yaml', 'gray');
|
|
306
|
+
log('', 'gray');
|
|
307
|
+
log('Example:', 'gray');
|
|
308
|
+
log(' smc marketplace:add SawyerHood/dev-browser', 'gray');
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Parse repo
|
|
313
|
+
const match = repo.match(/^([^/]+)\/(.+)$/);
|
|
314
|
+
if (!match) {
|
|
315
|
+
log('Invalid repo format. Use: owner/repo', 'red');
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const [, owner, name] = match;
|
|
320
|
+
const skillName = name.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
321
|
+
|
|
322
|
+
log(`📝 Adding skill source: ${repo}`, 'blue');
|
|
323
|
+
log('', 'gray');
|
|
324
|
+
|
|
325
|
+
// Check sources.yaml exists
|
|
326
|
+
if (!fs.existsSync(SOURCES_FILE)) {
|
|
327
|
+
log(`sources.yaml not found. Creating...`, 'yellow');
|
|
328
|
+
fs.writeFileSync(SOURCES_FILE, `version: 1\nskills:\n`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Read existing sources
|
|
332
|
+
const content = fs.readFileSync(SOURCES_FILE, 'utf-8');
|
|
333
|
+
const lines = content.split('\n');
|
|
334
|
+
|
|
335
|
+
// Check if skill already exists
|
|
336
|
+
if (lines.some(l => l.includes(`name: ${skillName}`))) {
|
|
337
|
+
log(`Skill "${skillName}" already exists in sources.yaml`, 'yellow');
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Find where to insert (after existing skills or before # Comments)
|
|
342
|
+
let insertIndex = lines.length;
|
|
343
|
+
for (let i = 0; i < lines.length; i++) {
|
|
344
|
+
if (lines[i].trim().startsWith('#') && lines[i].includes('Schema')) {
|
|
345
|
+
insertIndex = i;
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Build new skill entry
|
|
351
|
+
const newEntry = `
|
|
352
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
353
|
+
# ${skillName} - External skill from ${repo}
|
|
354
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
355
|
+
- name: ${skillName}
|
|
356
|
+
description: "TODO: Add description for ${skillName}"
|
|
357
|
+
source:
|
|
358
|
+
repo: ${repo}
|
|
359
|
+
path: skills/${skillName}
|
|
360
|
+
ref: main
|
|
361
|
+
target:
|
|
362
|
+
category: tools
|
|
363
|
+
path: template/.claude/skills/tools/${skillName}
|
|
364
|
+
author:
|
|
365
|
+
name: ${owner}
|
|
366
|
+
github: ${owner}
|
|
367
|
+
license: MIT
|
|
368
|
+
homepage: https://github.com/${repo}
|
|
369
|
+
verified: false
|
|
370
|
+
`;
|
|
371
|
+
|
|
372
|
+
// Insert new entry
|
|
373
|
+
lines.splice(insertIndex, 0, newEntry.trim());
|
|
374
|
+
|
|
375
|
+
// Write back
|
|
376
|
+
fs.writeFileSync(SOURCES_FILE, lines.join('\n'));
|
|
377
|
+
|
|
378
|
+
log(`✅ Added "${skillName}" to sources.yaml`, 'green');
|
|
379
|
+
log('', 'gray');
|
|
380
|
+
log('Next steps:', 'gray');
|
|
381
|
+
log(` 1. Edit sources.yaml to update description and category`, 'gray');
|
|
382
|
+
log(` 2. Run: smc marketplace:sync`, 'gray');
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
// -------------------------------------------------------------------------
|
|
386
|
+
'marketplace:remove': (skillName) => {
|
|
387
|
+
if (!skillName) {
|
|
388
|
+
log('Usage: smc marketplace:remove <skill-name>', 'yellow');
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
log(`🗑️ Removing skill: ${skillName}`, 'blue');
|
|
393
|
+
log('', 'gray');
|
|
394
|
+
|
|
395
|
+
// Read sources.yaml
|
|
396
|
+
if (!fs.existsSync(SOURCES_FILE)) {
|
|
397
|
+
log('sources.yaml not found', 'yellow');
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const content = fs.readFileSync(SOURCES_FILE, 'utf-8');
|
|
402
|
+
const lines = content.split('\n');
|
|
403
|
+
|
|
404
|
+
// Find and remove the skill entry
|
|
405
|
+
let inSkillEntry = false;
|
|
406
|
+
let removed = false;
|
|
407
|
+
const newLines = [];
|
|
408
|
+
|
|
409
|
+
for (let i = 0; i < lines.length; i++) {
|
|
410
|
+
const line = lines[i];
|
|
411
|
+
|
|
412
|
+
// Check if this is the skill to remove
|
|
413
|
+
if (line.includes(`name: ${skillName}`) && line.trim().startsWith('-')) {
|
|
414
|
+
inSkillEntry = true;
|
|
415
|
+
removed = true;
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Skip lines while in the skill entry
|
|
420
|
+
if (inSkillEntry) {
|
|
421
|
+
// End of skill entry
|
|
422
|
+
if (line.trim().startsWith('- name:') || line.trim().startsWith('#')) {
|
|
423
|
+
inSkillEntry = false;
|
|
424
|
+
newLines.push(line);
|
|
425
|
+
}
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
newLines.push(line);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (!removed) {
|
|
433
|
+
log(`Skill "${skillName}" not found in sources.yaml`, 'yellow');
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Write back
|
|
438
|
+
fs.writeFileSync(SOURCES_FILE, newLines.join('\n'));
|
|
439
|
+
|
|
440
|
+
log(`✅ Removed "${skillName}" from sources.yaml`, 'green');
|
|
441
|
+
log('', 'gray');
|
|
442
|
+
log('Note: The skill files remain in the template directory.', 'yellow');
|
|
443
|
+
log(' To remove them, delete:', 'yellow');
|
|
444
|
+
log(` template/.claude/skills/*/${skillName}`, 'gray');
|
|
445
|
+
},
|
|
446
|
+
|
|
447
|
+
// -------------------------------------------------------------------------
|
|
448
|
+
'marketplace:status': () => {
|
|
449
|
+
log('📊 Marketplace Status', 'blue');
|
|
450
|
+
log('=====================================', 'gray');
|
|
451
|
+
log('');
|
|
452
|
+
|
|
453
|
+
// Check marketplace.json
|
|
454
|
+
if (fs.existsSync(MARKETPLACE_FILE)) {
|
|
455
|
+
const registry = JSON.parse(fs.readFileSync(MARKETPLACE_FILE, 'utf-8'));
|
|
456
|
+
log(`Registry: ${MARKETPLACE_FILE}`, 'green');
|
|
457
|
+
log(`Version: ${registry.metadata.version}`, 'gray');
|
|
458
|
+
log(`Skills: ${registry.metadata.skill_count}`, 'gray');
|
|
459
|
+
log(`Updated: ${registry.metadata.generated_at}`, 'gray');
|
|
460
|
+
} else {
|
|
461
|
+
log(`Registry: Not found (run: smc marketplace:sync)`, 'yellow');
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
log('');
|
|
465
|
+
|
|
466
|
+
// Check sources.yaml
|
|
467
|
+
if (fs.existsSync(SOURCES_FILE)) {
|
|
468
|
+
const content = fs.readFileSync(SOURCES_FILE, 'utf-8');
|
|
469
|
+
const sources = parseSimpleYaml(content);
|
|
470
|
+
log(`Sources: ${SOURCES_FILE}`, 'green');
|
|
471
|
+
log(`External sources: ${sources.skills.filter(s => !s.native).length}`, 'gray');
|
|
472
|
+
log(`Native skills: ${sources.skills.filter(s => s.native).length}`, 'gray');
|
|
473
|
+
} else {
|
|
474
|
+
log(`Sources: Not found`, 'yellow');
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
log('');
|
|
478
|
+
log('=====================================', 'gray');
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
// ============================================================================
|
|
483
|
+
// Exports
|
|
484
|
+
// ============================================================================
|
|
485
|
+
|
|
486
|
+
exports.marketplaceCommands = marketplaceCommands;
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utils - Common utility functions
|
|
3
|
+
*
|
|
4
|
+
* Extracted from cli.js to eliminate code duplication
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Recursively copy directory contents
|
|
12
|
+
* @param {string} src - Source directory
|
|
13
|
+
* @param {string} dest - Destination directory
|
|
14
|
+
* @param {boolean} overwrite - Whether to overwrite existing files
|
|
15
|
+
* @returns {number} Number of files copied
|
|
16
|
+
*/
|
|
17
|
+
exports.copyRecursive = function(src, dest, overwrite = false) {
|
|
18
|
+
if (!fs.existsSync(src)) return 0;
|
|
19
|
+
|
|
20
|
+
if (!fs.existsSync(dest)) {
|
|
21
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let count = 0;
|
|
25
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
26
|
+
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
const srcPath = path.join(src, entry.name);
|
|
29
|
+
const destPath = path.join(dest, entry.name);
|
|
30
|
+
|
|
31
|
+
if (entry.isDirectory()) {
|
|
32
|
+
count += exports.copyRecursive(srcPath, destPath, overwrite);
|
|
33
|
+
} else if (overwrite || !fs.existsSync(destPath)) {
|
|
34
|
+
fs.copyFileSync(srcPath, destPath);
|
|
35
|
+
// Add execute permission for scripts
|
|
36
|
+
if (entry.name.endsWith('.sh') || entry.name.endsWith('.cjs')) {
|
|
37
|
+
fs.chmodSync(destPath, 0o755);
|
|
38
|
+
}
|
|
39
|
+
count++;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return count;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Ensure a directory exists
|
|
47
|
+
* @param {string} dir - Directory path
|
|
48
|
+
*/
|
|
49
|
+
exports.ensureDir = function(dir) {
|
|
50
|
+
if (!fs.existsSync(dir)) {
|
|
51
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Convert string to Title Case
|
|
57
|
+
* @param {string} str - Input string
|
|
58
|
+
* @returns {string} Title cased string
|
|
59
|
+
*/
|
|
60
|
+
exports.toTitleCase = function(str) {
|
|
61
|
+
return str.replace(/\b\w/g, char => char.toUpperCase());
|
|
62
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sumulige-claude",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "The Best Agent Harness for Claude Code",
|
|
5
5
|
"main": "cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
12
|
-
"postinstall": "node cli.js init"
|
|
12
|
+
"postinstall": "node cli.js init",
|
|
13
|
+
"sync": "node scripts/sync-external.mjs",
|
|
14
|
+
"update-registry": "node scripts/update-registry.mjs",
|
|
15
|
+
"sync:all": "npm run sync && npm run update-registry"
|
|
13
16
|
},
|
|
14
17
|
"keywords": [
|
|
15
18
|
"claude",
|
|
@@ -17,19 +20,22 @@
|
|
|
17
20
|
"agent",
|
|
18
21
|
"ai",
|
|
19
22
|
"skills",
|
|
20
|
-
"mcp"
|
|
23
|
+
"mcp",
|
|
24
|
+
"marketplace",
|
|
25
|
+
"plugin"
|
|
21
26
|
],
|
|
22
27
|
"author": "sumulige",
|
|
23
28
|
"license": "MIT",
|
|
24
29
|
"type": "commonjs",
|
|
25
30
|
"repository": {
|
|
26
31
|
"type": "git",
|
|
27
|
-
"url": "git+https://github.com/sumulige/
|
|
32
|
+
"url": "git+https://github.com/sumulige/sumulige-claude.git"
|
|
28
33
|
},
|
|
29
34
|
"engines": {
|
|
30
35
|
"node": ">=16.0.0"
|
|
31
36
|
},
|
|
32
37
|
"devDependencies": {
|
|
33
|
-
"prettier": "^3.7.4"
|
|
38
|
+
"prettier": "^3.7.4",
|
|
39
|
+
"yaml": "^2.8.2"
|
|
34
40
|
}
|
|
35
41
|
}
|