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
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Sync External Skills
|
|
4
|
+
*
|
|
5
|
+
* This script reads sources.yaml and syncs skills from external repositories.
|
|
6
|
+
* It clones external repos, copies specified files, and generates source metadata.
|
|
7
|
+
*
|
|
8
|
+
* Usage: node scripts/sync-external.mjs
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
import { execSync } from 'child_process';
|
|
15
|
+
import yaml from 'yaml';
|
|
16
|
+
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = path.dirname(__filename);
|
|
19
|
+
const ROOT_DIR = path.join(__dirname, '..');
|
|
20
|
+
const SOURCES_FILE = path.join(ROOT_DIR, 'sources.yaml');
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Logging Utilities
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
const COLORS = {
|
|
27
|
+
reset: '\x1b[0m',
|
|
28
|
+
green: '\x1b[32m',
|
|
29
|
+
blue: '\x1b[34m',
|
|
30
|
+
yellow: '\x1b[33m',
|
|
31
|
+
red: '\x1b[31m',
|
|
32
|
+
gray: '\x1b[90m'
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function log(message, color = 'reset') {
|
|
36
|
+
console.log(`${COLORS[color]}${message}${COLORS.reset}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function logStep(num, total, message) {
|
|
40
|
+
log(`[${num}/${total}] ${message}`, 'blue');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// File System Utilities
|
|
45
|
+
// ============================================================================
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Ensure a directory exists, create if not
|
|
49
|
+
*/
|
|
50
|
+
function ensureDir(dirPath) {
|
|
51
|
+
if (!fs.existsSync(dirPath)) {
|
|
52
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Recursively copy directory contents
|
|
58
|
+
*/
|
|
59
|
+
function copyRecursive(src, dest, include = [], exclude = []) {
|
|
60
|
+
let count = 0;
|
|
61
|
+
|
|
62
|
+
if (!fs.existsSync(src)) {
|
|
63
|
+
return count;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const stat = fs.statSync(src);
|
|
67
|
+
|
|
68
|
+
if (stat.isDirectory()) {
|
|
69
|
+
ensureDir(dest);
|
|
70
|
+
|
|
71
|
+
const entries = fs.readdirSync(src);
|
|
72
|
+
|
|
73
|
+
for (const entry of entries) {
|
|
74
|
+
// Check exclude patterns
|
|
75
|
+
const excluded = exclude.some(pattern => {
|
|
76
|
+
const regex = new RegExp(pattern.replace('*', '.*'));
|
|
77
|
+
return regex.test(entry);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (excluded) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Check include patterns (if specified)
|
|
85
|
+
if (include.length > 0) {
|
|
86
|
+
const included = include.some(pattern => {
|
|
87
|
+
const regex = new RegExp(pattern.replace('*', '.*'));
|
|
88
|
+
return regex.test(entry);
|
|
89
|
+
});
|
|
90
|
+
if (!included) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const srcPath = path.join(src, entry);
|
|
96
|
+
const destPath = path.join(dest, entry);
|
|
97
|
+
count += copyRecursive(srcPath, destPath, [], exclude);
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
fs.copyFileSync(src, dest);
|
|
101
|
+
count = 1;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return count;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Remove directory recursively
|
|
109
|
+
*/
|
|
110
|
+
function removeDir(dirPath) {
|
|
111
|
+
if (fs.existsSync(dirPath)) {
|
|
112
|
+
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ============================================================================
|
|
117
|
+
// Git Utilities
|
|
118
|
+
// ============================================================================
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Clone a Git repository to a temporary directory
|
|
122
|
+
*/
|
|
123
|
+
function cloneRepo(repo, ref, targetDir) {
|
|
124
|
+
const url = `https://github.com/${repo}.git`;
|
|
125
|
+
const command = `git clone --depth 1 --branch ${ref} ${url} ${targetDir}`;
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
execSync(command, { stdio: 'pipe' });
|
|
129
|
+
return true;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
log(`Failed to clone ${repo}: ${error.message}`, 'red');
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ============================================================================
|
|
137
|
+
// Source Metadata
|
|
138
|
+
// ============================================================================
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Write .source.json file for a synced skill
|
|
142
|
+
*/
|
|
143
|
+
function writeSourceJson(skill, targetPath) {
|
|
144
|
+
const sourceJson = {
|
|
145
|
+
name: skill.name,
|
|
146
|
+
description: skill.description,
|
|
147
|
+
source: {
|
|
148
|
+
repo: skill.source?.repo,
|
|
149
|
+
path: skill.source?.path,
|
|
150
|
+
ref: skill.source?.ref || 'main',
|
|
151
|
+
synced_at: new Date().toISOString()
|
|
152
|
+
},
|
|
153
|
+
author: skill.author,
|
|
154
|
+
license: skill.license,
|
|
155
|
+
homepage: skill.homepage,
|
|
156
|
+
verified: skill.verified || false
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const sourceJsonPath = path.join(targetPath, '.source.json');
|
|
160
|
+
fs.writeFileSync(sourceJsonPath, JSON.stringify(sourceJson, null, 2));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ============================================================================
|
|
164
|
+
// Main Sync Logic
|
|
165
|
+
// ============================================================================
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Parse sources.yaml file
|
|
169
|
+
*/
|
|
170
|
+
function parseSources() {
|
|
171
|
+
if (!fs.existsSync(SOURCES_FILE)) {
|
|
172
|
+
log('sources.yaml not found!', 'red');
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const content = fs.readFileSync(SOURCES_FILE, 'utf8');
|
|
177
|
+
return yaml.parse(content);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Sync a single external skill
|
|
182
|
+
*/
|
|
183
|
+
function syncSkill(skill, index, total) {
|
|
184
|
+
logStep(index, total, `Syncing: ${skill.name}`);
|
|
185
|
+
|
|
186
|
+
// Skip native skills (maintained directly in repo)
|
|
187
|
+
if (skill.native) {
|
|
188
|
+
log(` → Skipped (native skill)`, 'gray');
|
|
189
|
+
return { success: true, skipped: true };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Validate required fields
|
|
193
|
+
if (!skill.source?.repo) {
|
|
194
|
+
log(` → Skipped (no source.repo)`, 'yellow');
|
|
195
|
+
return { success: false, error: 'Missing source.repo' };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const targetPath = path.join(ROOT_DIR, skill.target.path);
|
|
199
|
+
const tmpDir = path.join(ROOT_DIR, '.tmp', skill.name);
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
// Clone repository
|
|
203
|
+
log(` → Cloning ${skill.source.repo}...`, 'gray');
|
|
204
|
+
if (!cloneRepo(skill.source.repo, skill.source.ref || 'main', tmpDir)) {
|
|
205
|
+
return { success: false, error: 'Clone failed' };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Determine source path
|
|
209
|
+
const sourcePath = path.join(tmpDir, skill.source.path || '');
|
|
210
|
+
|
|
211
|
+
if (!fs.existsSync(sourcePath)) {
|
|
212
|
+
log(` → Source path not found: ${skill.source.path}`, 'yellow');
|
|
213
|
+
removeDir(tmpDir);
|
|
214
|
+
return { success: false, error: 'Source path not found' };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Clear target directory
|
|
218
|
+
removeDir(targetPath);
|
|
219
|
+
ensureDir(targetPath);
|
|
220
|
+
|
|
221
|
+
// Copy files
|
|
222
|
+
const include = skill.sync?.include || [];
|
|
223
|
+
const exclude = skill.sync?.exclude || ['node_modules', '*.lock'];
|
|
224
|
+
|
|
225
|
+
log(` → Copying files...`, 'gray');
|
|
226
|
+
const count = copyRecursive(sourcePath, targetPath, include, exclude);
|
|
227
|
+
|
|
228
|
+
// Write source metadata
|
|
229
|
+
writeSourceJson(skill, targetPath);
|
|
230
|
+
|
|
231
|
+
// Cleanup
|
|
232
|
+
removeDir(tmpDir);
|
|
233
|
+
|
|
234
|
+
log(` → Synced ${count} files`, 'green');
|
|
235
|
+
return { success: true, fileCount: count };
|
|
236
|
+
|
|
237
|
+
} catch (error) {
|
|
238
|
+
removeDir(tmpDir);
|
|
239
|
+
log(` → Error: ${error.message}`, 'red');
|
|
240
|
+
return { success: false, error: error.message };
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Main sync function
|
|
246
|
+
*/
|
|
247
|
+
function syncAll() {
|
|
248
|
+
log('🔄 Syncing External Skills', 'blue');
|
|
249
|
+
log('=====================================', 'gray');
|
|
250
|
+
log('');
|
|
251
|
+
|
|
252
|
+
const sources = parseSources();
|
|
253
|
+
if (!sources || !sources.skills) {
|
|
254
|
+
log('No skills found in sources.yaml', 'yellow');
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const skills = sources.skills;
|
|
259
|
+
const total = skills.length;
|
|
260
|
+
|
|
261
|
+
log(`Found ${total} skill(s) in sources.yaml`, 'gray');
|
|
262
|
+
log('');
|
|
263
|
+
|
|
264
|
+
let synced = 0;
|
|
265
|
+
let skipped = 0;
|
|
266
|
+
let failed = 0;
|
|
267
|
+
|
|
268
|
+
for (let i = 0; i < total; i++) {
|
|
269
|
+
const result = syncSkill(skills[i], i + 1, total);
|
|
270
|
+
|
|
271
|
+
if (result.success) {
|
|
272
|
+
if (result.skipped) {
|
|
273
|
+
skipped++;
|
|
274
|
+
} else {
|
|
275
|
+
synced++;
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
failed++;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
log('');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Summary
|
|
285
|
+
log('=====================================', 'gray');
|
|
286
|
+
log(`✅ Synced: ${synced}`, 'green');
|
|
287
|
+
log(`⏭️ Skipped: ${skipped}`, 'yellow');
|
|
288
|
+
if (failed > 0) {
|
|
289
|
+
log(`❌ Failed: ${failed}`, 'red');
|
|
290
|
+
}
|
|
291
|
+
log('=====================================', 'gray');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ============================================================================
|
|
295
|
+
// Run
|
|
296
|
+
// ============================================================================
|
|
297
|
+
|
|
298
|
+
syncAll();
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Update Marketplace Registry
|
|
4
|
+
*
|
|
5
|
+
* This script scans the skills directory and generates the marketplace.json
|
|
6
|
+
* registry file for Claude Code's native plugin system.
|
|
7
|
+
*
|
|
8
|
+
* Usage: node scripts/update-registry.mjs
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
import { execSync } from 'child_process';
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = path.dirname(__filename);
|
|
18
|
+
const ROOT_DIR = path.join(__dirname, '..');
|
|
19
|
+
const MARKETPLACE_FILE = path.join(ROOT_DIR, '.claude-plugin', 'marketplace.json');
|
|
20
|
+
const SKILLS_DIR = path.join(ROOT_DIR, 'template', '.claude', 'skills');
|
|
21
|
+
const CATEGORIES_FILE = path.join(ROOT_DIR, 'config', 'skill-categories.json');
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Logging Utilities
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
const COLORS = {
|
|
28
|
+
reset: '\x1b[0m',
|
|
29
|
+
green: '\x1b[32m',
|
|
30
|
+
blue: '\x1b[34m',
|
|
31
|
+
yellow: '\x1b[33m',
|
|
32
|
+
gray: '\x1b[90m'
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function log(message, color = 'reset') {
|
|
36
|
+
console.log(`${COLORS[color]}${message}${COLORS.reset}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ============================================================================
|
|
40
|
+
// File System Utilities
|
|
41
|
+
// ============================================================================
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Recursively find all skill directories
|
|
45
|
+
*/
|
|
46
|
+
function findSkillDirs(dir, basePath = dir) {
|
|
47
|
+
if (!fs.existsSync(dir)) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const skills = [];
|
|
52
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
53
|
+
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
if (entry.isDirectory()) {
|
|
56
|
+
const fullPath = path.join(dir, entry.name);
|
|
57
|
+
|
|
58
|
+
// Skip template and examples directories
|
|
59
|
+
if (['template', 'examples', '__tests__'].includes(entry.name)) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Check if this is a skill directory (contains SKILL.md or metadata.yaml)
|
|
64
|
+
const hasSkillFile = fs.existsSync(path.join(fullPath, 'SKILL.md'));
|
|
65
|
+
const hasMetadata = fs.existsSync(path.join(fullPath, 'metadata.yaml'));
|
|
66
|
+
const hasSourceJson = fs.existsSync(path.join(fullPath, '.source.json'));
|
|
67
|
+
|
|
68
|
+
if (hasSkillFile || hasMetadata || hasSourceJson) {
|
|
69
|
+
const relativePath = path.relative(basePath, fullPath);
|
|
70
|
+
skills.push({
|
|
71
|
+
name: entry.name,
|
|
72
|
+
path: fullPath,
|
|
73
|
+
relativePath: relativePath,
|
|
74
|
+
hasSourceJson
|
|
75
|
+
});
|
|
76
|
+
} else {
|
|
77
|
+
// Recurse into subdirectories
|
|
78
|
+
skills.push(...findSkillDirs(fullPath, basePath));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return skills;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Parse YAML metadata file
|
|
88
|
+
*/
|
|
89
|
+
function parseMetadata(skillDir) {
|
|
90
|
+
const metadataPath = path.join(skillDir, 'metadata.yaml');
|
|
91
|
+
|
|
92
|
+
if (!fs.existsSync(metadataPath)) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const content = fs.readFileSync(metadataPath, 'utf8');
|
|
98
|
+
// Simple YAML parser for basic key: value format
|
|
99
|
+
const result = {};
|
|
100
|
+
let currentKey = null;
|
|
101
|
+
|
|
102
|
+
content.split('\n').forEach(line => {
|
|
103
|
+
const trimmed = line.trim();
|
|
104
|
+
|
|
105
|
+
// Skip comments and empty lines
|
|
106
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Key: Value pair
|
|
111
|
+
const match = trimmed.match(/^([^:]+):\s*(.*)$/);
|
|
112
|
+
if (match) {
|
|
113
|
+
currentKey = match[1];
|
|
114
|
+
let value = match[2];
|
|
115
|
+
|
|
116
|
+
// Handle array values
|
|
117
|
+
if (value.startsWith('[')) {
|
|
118
|
+
try {
|
|
119
|
+
value = JSON.parse(value.replace(/'/g, '"'));
|
|
120
|
+
} catch (e) {
|
|
121
|
+
value = [];
|
|
122
|
+
}
|
|
123
|
+
} else if (value === '[]') {
|
|
124
|
+
value = [];
|
|
125
|
+
} else if (value === 'true') {
|
|
126
|
+
value = true;
|
|
127
|
+
} else if (value === 'false') {
|
|
128
|
+
value = false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
result[currentKey] = value;
|
|
132
|
+
} else if (currentKey && trimmed.startsWith('-')) {
|
|
133
|
+
// Array item continuation
|
|
134
|
+
if (!Array.isArray(result[currentKey])) {
|
|
135
|
+
result[currentKey] = [];
|
|
136
|
+
}
|
|
137
|
+
result[currentKey].push(trimmed.slice(1).trim());
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return result;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
log(`Warning: Failed to parse ${metadataPath}`, 'yellow');
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Parse .source.json file
|
|
150
|
+
*/
|
|
151
|
+
function parseSourceJson(skillDir) {
|
|
152
|
+
const sourcePath = path.join(skillDir, '.source.json');
|
|
153
|
+
|
|
154
|
+
if (!fs.existsSync(sourcePath)) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
const content = fs.readFileSync(sourcePath, 'utf-8');
|
|
160
|
+
return JSON.parse(content);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Get skill description from SKILL.md
|
|
168
|
+
*/
|
|
169
|
+
function getSkillDescription(skillDir) {
|
|
170
|
+
const skillPath = path.join(skillDir, 'SKILL.md');
|
|
171
|
+
|
|
172
|
+
if (!fs.existsSync(skillPath)) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const content = fs.readFileSync(skillPath, 'utf-8');
|
|
178
|
+
// Extract first heading or first paragraph
|
|
179
|
+
const lines = content.split('\n');
|
|
180
|
+
|
|
181
|
+
for (const line of lines) {
|
|
182
|
+
const trimmed = line.trim();
|
|
183
|
+
// Skip title
|
|
184
|
+
if (trimmed.startsWith('#')) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
// Return first non-empty, non-comment line
|
|
188
|
+
if (trimmed && !trimmed.startsWith('>') && !trimmed.startsWith('<!--')) {
|
|
189
|
+
return trimmed.replace(/^>/, '').trim();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return null;
|
|
194
|
+
} catch (error) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Determine skill category
|
|
201
|
+
*/
|
|
202
|
+
function getSkillCategory(skillPath, categories) {
|
|
203
|
+
// Check if path contains a category
|
|
204
|
+
const pathParts = skillPath.split(path.sep);
|
|
205
|
+
|
|
206
|
+
for (const part of pathParts) {
|
|
207
|
+
if (categories[part]) {
|
|
208
|
+
return part;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return 'tools'; // Default category
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ============================================================================
|
|
216
|
+
// Registry Generation
|
|
217
|
+
// ============================================================================
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Generate the marketplace.json registry
|
|
221
|
+
*/
|
|
222
|
+
function generateRegistry() {
|
|
223
|
+
log('📋 Generating Marketplace Registry', 'blue');
|
|
224
|
+
log('=====================================', 'gray');
|
|
225
|
+
log('');
|
|
226
|
+
|
|
227
|
+
// Load categories
|
|
228
|
+
let categories = {};
|
|
229
|
+
if (fs.existsSync(CATEGORIES_FILE)) {
|
|
230
|
+
categories = JSON.parse(fs.readFileSync(CATEGORIES_FILE, 'utf-8')).categories || {};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Find all skill directories
|
|
234
|
+
log('Scanning skills directory...', 'gray');
|
|
235
|
+
const skills = findSkillDirs(SKILLS_DIR);
|
|
236
|
+
log(`Found ${skills.length} skill(s)`, 'gray');
|
|
237
|
+
log('');
|
|
238
|
+
|
|
239
|
+
if (skills.length === 0) {
|
|
240
|
+
log('No skills found. Registry will be empty.', 'yellow');
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Build plugin entries
|
|
245
|
+
const pluginSkills = [];
|
|
246
|
+
|
|
247
|
+
for (const skill of skills) {
|
|
248
|
+
log(`Processing: ${skill.name}`, 'gray');
|
|
249
|
+
|
|
250
|
+
// Get metadata
|
|
251
|
+
const metadata = parseMetadata(skill.path);
|
|
252
|
+
const sourceJson = parseSourceJson(skill.path);
|
|
253
|
+
const description = metadata?.description || getSkillDescription(skill.path) || 'No description';
|
|
254
|
+
|
|
255
|
+
// Determine category
|
|
256
|
+
const category = metadata?.category || getSkillCategory(skill.relativePath, categories);
|
|
257
|
+
|
|
258
|
+
// Build relative path from .claude-plugin
|
|
259
|
+
const relativeFromPlugin = path.join('..', 'template', '.claude', 'skills', skill.relativePath);
|
|
260
|
+
|
|
261
|
+
pluginSkills.push({
|
|
262
|
+
name: skill.name,
|
|
263
|
+
description: description,
|
|
264
|
+
path: `./${relativeFromPlugin}`,
|
|
265
|
+
category: category,
|
|
266
|
+
external: !!sourceJson
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Build the full registry
|
|
271
|
+
const registry = {
|
|
272
|
+
name: 'smc-skills',
|
|
273
|
+
description: 'Sumulige Claude Agent Harness - Curated skill collection for AI coding agents',
|
|
274
|
+
homepage: 'https://github.com/sumulige/sumulige-claude',
|
|
275
|
+
owner: {
|
|
276
|
+
name: 'sumulige',
|
|
277
|
+
email: 'sumulige@example.com'
|
|
278
|
+
},
|
|
279
|
+
plugins: [
|
|
280
|
+
{
|
|
281
|
+
name: 'smc-skills',
|
|
282
|
+
description: 'Multi-agent orchestration harness with curated skills. Includes Conductor, Architect, Builder, Reviewer, Librarian agents plus RAG-based skill discovery system.',
|
|
283
|
+
source: './',
|
|
284
|
+
skills: pluginSkills.map(s => s.path),
|
|
285
|
+
strict: false,
|
|
286
|
+
skill_list: pluginSkills
|
|
287
|
+
}
|
|
288
|
+
],
|
|
289
|
+
metadata: {
|
|
290
|
+
version: getPackageVersion(),
|
|
291
|
+
generated_at: new Date().toISOString(),
|
|
292
|
+
skill_count: skills.length,
|
|
293
|
+
categories: categories
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// Write registry file
|
|
298
|
+
log('', 'gray');
|
|
299
|
+
log(`Writing registry to: ${MARKETPLACE_FILE}`, 'gray');
|
|
300
|
+
fs.writeFileSync(MARKETPLACE_FILE, JSON.stringify(registry, null, 2));
|
|
301
|
+
|
|
302
|
+
log('', 'gray');
|
|
303
|
+
log('=====================================', 'gray');
|
|
304
|
+
log(`✅ Registry generated with ${skills.length} skills`, 'green');
|
|
305
|
+
log('=====================================', 'gray');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Get package version
|
|
310
|
+
*/
|
|
311
|
+
function getPackageVersion() {
|
|
312
|
+
try {
|
|
313
|
+
const pkgPath = path.join(ROOT_DIR, 'package.json');
|
|
314
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
315
|
+
return pkg.version || '1.0.0';
|
|
316
|
+
} catch (error) {
|
|
317
|
+
return '1.0.0';
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ============================================================================
|
|
322
|
+
// Run
|
|
323
|
+
// ============================================================================
|
|
324
|
+
|
|
325
|
+
generateRegistry();
|
package/sources.yaml
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# SMC External Skills Sources
|
|
2
|
+
# ===========================
|
|
3
|
+
# 此清单定义了从外部仓库同步到本项目的技能。
|
|
4
|
+
# GitHub Actions 工作流会读取此文件并每日自动同步上游技能。
|
|
5
|
+
#
|
|
6
|
+
# 添加技能:提交 PR 添加条目到此处。参考 CONTRIBUTING.md。
|
|
7
|
+
|
|
8
|
+
version: 1
|
|
9
|
+
skills:
|
|
10
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
11
|
+
# manus-kickoff - Native skill (maintained directly in this repo)
|
|
12
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
13
|
+
- name: manus-kickoff
|
|
14
|
+
description: Manus-style project kickoff workflow for AI autonomous development
|
|
15
|
+
native: true # Maintained directly in this repo
|
|
16
|
+
target:
|
|
17
|
+
category: workflow
|
|
18
|
+
path: template/.claude/skills/manus-kickoff
|
|
19
|
+
author:
|
|
20
|
+
name: sumulige
|
|
21
|
+
github: sumulige
|
|
22
|
+
license: MIT
|
|
23
|
+
homepage: https://github.com/sumulige/sumulige-claude
|
|
24
|
+
|
|
25
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
26
|
+
# Example: Adding an external skill (commented out)
|
|
27
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
28
|
+
# - name: dev-browser
|
|
29
|
+
# description: Browser automation with persistent page state
|
|
30
|
+
# source:
|
|
31
|
+
# repo: SawyerHood/dev-browser
|
|
32
|
+
# path: skills/dev-browser
|
|
33
|
+
# ref: main
|
|
34
|
+
# target:
|
|
35
|
+
# category: automation
|
|
36
|
+
# path: template/.claude/skills/automation/dev-browser
|
|
37
|
+
# author:
|
|
38
|
+
# name: Sawyer Hood
|
|
39
|
+
# github: SawyerHood
|
|
40
|
+
# license: MIT
|
|
41
|
+
# homepage: https://github.com/SawyerHood/dev-browser
|
|
42
|
+
# verified: false
|
|
43
|
+
# sync:
|
|
44
|
+
# include:
|
|
45
|
+
# - SKILL.md
|
|
46
|
+
# - references/
|
|
47
|
+
# - scripts/
|
|
48
|
+
# - src/
|
|
49
|
+
# - package.json
|
|
50
|
+
# exclude:
|
|
51
|
+
# - node_modules/
|
|
52
|
+
# - "*.lock"
|
|
53
|
+
|
|
54
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
55
|
+
# Schema Reference
|
|
56
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
57
|
+
#
|
|
58
|
+
# skills[]:
|
|
59
|
+
# name: Required. Unique skill identifier (lowercase, hyphens ok)
|
|
60
|
+
# description: Required. One-line description for marketplace listings
|
|
61
|
+
#
|
|
62
|
+
# native: Optional. Set true if skill is maintained directly in this repo
|
|
63
|
+
#
|
|
64
|
+
# source: Required for external skills
|
|
65
|
+
# repo: GitHub owner/repo (e.g., "SawyerHood/dev-browser")
|
|
66
|
+
# path: Path to skill folder within repo
|
|
67
|
+
# ref: Branch, tag, or commit SHA (default: main)
|
|
68
|
+
#
|
|
69
|
+
# target: Required
|
|
70
|
+
# category: One of: tools, development, productivity, automation, data, documentation, workflow
|
|
71
|
+
# path: Target path in smc-skills repo
|
|
72
|
+
#
|
|
73
|
+
# author: Required
|
|
74
|
+
# name: Display name
|
|
75
|
+
# github: GitHub username
|
|
76
|
+
#
|
|
77
|
+
# license: Required. SPDX identifier (MIT, Apache-2.0, etc.)
|
|
78
|
+
# homepage: Required. Link to project homepage or repo
|
|
79
|
+
# verified: Optional. Set true after manual review (default: false)
|
|
80
|
+
#
|
|
81
|
+
# sync: Optional. Fine-grained sync control
|
|
82
|
+
# include: List of files/folders to copy
|
|
83
|
+
# exclude: Glob patterns to skip
|