joyskills-cli 0.2.10 → 0.3.1
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/README.md +22 -238
- package/package.json +10 -3
- package/src/agents.js +198 -0
- package/src/commands/check.js +69 -0
- package/src/commands/install.js +91 -23
- package/src/commands/list.js +66 -124
- package/src/commands/remove.js +10 -1
- package/src/commands/sync.js +10 -4
- package/src/commands/update.js +320 -0
- package/src/commands/upgrade.js +103 -13
- package/src/index.js +4 -0
- package/src/installer.js +190 -0
- package/src/skill-loader.js +207 -0
- package/src/version-checker.js +129 -0
package/src/commands/install.js
CHANGED
|
@@ -2,6 +2,8 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { LocalManager } from '../local.js';
|
|
3
3
|
import { LockfileManager } from '../lockfile.js';
|
|
4
4
|
import { RegistryManager } from '../registry.js';
|
|
5
|
+
import { getAgent, getAgentProjectPath, getAgentGlobalPath, detectAgents } from '../agents.js';
|
|
6
|
+
import { installSkill, InstallMethod } from '../installer.js';
|
|
5
7
|
import simpleGit from 'simple-git';
|
|
6
8
|
import * as fs from 'fs';
|
|
7
9
|
import * as path from 'path';
|
|
@@ -15,25 +17,22 @@ const CACHE_DIR = process.env.JOYSKILL_CACHE_DIR || path.join(os.homedir(), '.jo
|
|
|
15
17
|
export function installCommand(program) {
|
|
16
18
|
program
|
|
17
19
|
.command('install [skill]')
|
|
20
|
+
.alias('add')
|
|
18
21
|
.description('Install a skill from registry or GitHub')
|
|
19
22
|
.option('-v, --version <version>', 'Specify version to install')
|
|
20
23
|
.option('-r, --registry <name>', 'Install from specific registry')
|
|
21
|
-
.option('-g, --global', 'Install
|
|
22
|
-
.option('--
|
|
24
|
+
.option('-g, --global', 'Install to user-level directory (~/.joycode/skills/)')
|
|
25
|
+
.option('-a, --agent <agent>', 'Target specific agent (joycode, claude-code, cursor, etc.)')
|
|
26
|
+
.option('-d, --dir <path>', 'Install to custom directory')
|
|
27
|
+
.option('--copy', 'Copy files instead of symlinking')
|
|
23
28
|
.option('--force', 'Force installation even if version is not recommended')
|
|
24
29
|
.option('-y, --yes', 'Skip all prompts (CI mode)')
|
|
25
30
|
.action(async (skillInput, options) => {
|
|
26
31
|
try {
|
|
27
32
|
const projectRoot = process.cwd();
|
|
28
|
-
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
// default → .claude/skills/
|
|
32
|
-
const targetDir = options.global
|
|
33
|
-
? path.join(os.homedir(), '.claude', 'skills')
|
|
34
|
-
: options.universal
|
|
35
|
-
? path.join(projectRoot, '.agent', 'skills')
|
|
36
|
-
: path.join(projectRoot, '.claude', 'skills');
|
|
33
|
+
|
|
34
|
+
// 解析目标目录
|
|
35
|
+
const targetDir = resolveTargetDir(projectRoot, options);
|
|
37
36
|
|
|
38
37
|
if (!fs.existsSync(targetDir)) {
|
|
39
38
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
@@ -54,6 +53,35 @@ export function installCommand(program) {
|
|
|
54
53
|
});
|
|
55
54
|
}
|
|
56
55
|
|
|
56
|
+
/**
|
|
57
|
+
* 解析目标目录
|
|
58
|
+
* 优先级: --dir > --agent > --global > 默认(joycode)
|
|
59
|
+
*/
|
|
60
|
+
function resolveTargetDir(projectRoot, options) {
|
|
61
|
+
// 1. 自定义目录最高优先级
|
|
62
|
+
if (options.dir) {
|
|
63
|
+
return path.resolve(options.dir);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 2. 指定 Agent
|
|
67
|
+
if (options.agent) {
|
|
68
|
+
const agent = getAgent(options.agent);
|
|
69
|
+
if (options.global) {
|
|
70
|
+
return getAgentGlobalPath(agent.id);
|
|
71
|
+
}
|
|
72
|
+
return getAgentProjectPath(agent.id, projectRoot);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 3. 仅 --global,使用 joycode 用户级目录
|
|
76
|
+
if (options.global) {
|
|
77
|
+
return getAgentGlobalPath('joycode');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 4. 默认: 始终使用 joycode 项目级目录
|
|
81
|
+
// 这是 JoyCode 的品牌策略,不随检测到的其他 Agent 改变
|
|
82
|
+
return getAgentProjectPath('joycode', projectRoot);
|
|
83
|
+
}
|
|
84
|
+
|
|
57
85
|
/**
|
|
58
86
|
* Priority resolution chain:
|
|
59
87
|
* 1. Local absolute/relative path
|
|
@@ -65,10 +93,24 @@ async function resolveAndInstall(skillInput, targetDir, projectRoot, options) {
|
|
|
65
93
|
const JOYSKILL_CONFIG_DIR = process.env.JOYSKILL_CONFIG_DIR || path.join(os.homedir(), '.joyskill');
|
|
66
94
|
|
|
67
95
|
// ── Step 0: Direct Git URL (git@xxx or https://github.com/...) ─────────
|
|
96
|
+
// Support: git@host:org/repo.git or git@host:org/repo.git/subpath
|
|
68
97
|
if (skillInput.startsWith('git@') || skillInput.startsWith('https://') || skillInput.startsWith('http://')) {
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
98
|
+
// Parse URL and optional subpath
|
|
99
|
+
// Format: git@host:org/repo.git/subpath/to/skill
|
|
100
|
+
let gitUrl = skillInput;
|
|
101
|
+
let subPath = '';
|
|
102
|
+
|
|
103
|
+
// Check for subpath after .git
|
|
104
|
+
const gitExtMatch = skillInput.match(/^(.+?\.git)(\/.*)$/);
|
|
105
|
+
if (gitExtMatch) {
|
|
106
|
+
gitUrl = gitExtMatch[1];
|
|
107
|
+
subPath = gitExtMatch[2].replace(/^\//, ''); // Remove leading slash
|
|
108
|
+
} else if (!skillInput.endsWith('.git')) {
|
|
109
|
+
// No .git extension, treat entire input as URL
|
|
110
|
+
gitUrl = skillInput;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
await installFromGitUrl(gitUrl, targetDir, options, subPath);
|
|
72
114
|
return;
|
|
73
115
|
}
|
|
74
116
|
|
|
@@ -151,13 +193,26 @@ async function resolveAndInstall(skillInput, targetDir, projectRoot, options) {
|
|
|
151
193
|
|
|
152
194
|
// ── Fallback: create template ────────────────────────────────────
|
|
153
195
|
console.log(chalk.yellow(`⚠️ "${skillInput}" not found in any registry, creating template...`));
|
|
154
|
-
|
|
196
|
+
|
|
197
|
+
// 使用 targetDir 安装模板
|
|
198
|
+
const skillPath = path.join(targetDir, skillInput);
|
|
199
|
+
if (!fs.existsSync(skillPath)) {
|
|
200
|
+
fs.mkdirSync(skillPath, { recursive: true });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const skillMdPath = path.join(skillPath, 'SKILL.md');
|
|
204
|
+
fs.writeFileSync(skillMdPath, generateSkillTemplate(skillInput));
|
|
205
|
+
|
|
155
206
|
const lockfileManager = new LockfileManager(projectRoot);
|
|
156
|
-
localManager.installSkill(skillInput, generateSkillTemplate(skillInput));
|
|
157
207
|
await lockfileManager.load();
|
|
158
|
-
lockfileManager.updateSkill(skillInput, {
|
|
208
|
+
lockfileManager.updateSkill(skillInput, {
|
|
209
|
+
version: '1.0.0',
|
|
210
|
+
source: 'template',
|
|
211
|
+
path: skillPath,
|
|
212
|
+
installedAt: new Date().toISOString()
|
|
213
|
+
});
|
|
159
214
|
await lockfileManager.save();
|
|
160
|
-
console.log(chalk.green(`✅ Created template for ${skillInput}`));
|
|
215
|
+
console.log(chalk.green(`✅ Created template for ${skillInput} in ${targetDir}`));
|
|
161
216
|
}
|
|
162
217
|
|
|
163
218
|
/**
|
|
@@ -188,7 +243,14 @@ async function tryInstallFromRegistryDir(skillName, registryDirPath, targetDir,
|
|
|
188
243
|
if (!fs.existsSync(sourcePath)) return false;
|
|
189
244
|
|
|
190
245
|
const targetPath = path.join(targetDir, skillName);
|
|
191
|
-
|
|
246
|
+
|
|
247
|
+
// 使用新的 installer 模块,支持 symlink/copy
|
|
248
|
+
const method = options.copy ? InstallMethod.COPY : InstallMethod.SYMLINK;
|
|
249
|
+
installSkill(sourcePath, targetPath, method);
|
|
250
|
+
|
|
251
|
+
// 获取安装方式信息
|
|
252
|
+
const installInfo = fs.lstatSync(targetPath);
|
|
253
|
+
const installMethod = installInfo.isSymbolicLink() ? 'symlink' : 'copy';
|
|
192
254
|
|
|
193
255
|
const lockfileManager = new LockfileManager(projectRoot);
|
|
194
256
|
await lockfileManager.load();
|
|
@@ -196,11 +258,13 @@ async function tryInstallFromRegistryDir(skillName, registryDirPath, targetDir,
|
|
|
196
258
|
version,
|
|
197
259
|
source: sourceLabel,
|
|
198
260
|
registry: registryManager.getRegistryInfo().registryId,
|
|
261
|
+
installMethod,
|
|
199
262
|
installedAt: new Date().toISOString()
|
|
200
263
|
});
|
|
201
264
|
await lockfileManager.save();
|
|
202
265
|
|
|
203
|
-
|
|
266
|
+
const methodLabel = installMethod === 'symlink' ? chalk.gray('(symlink)') : chalk.gray('(copy)');
|
|
267
|
+
console.log(chalk.green(`✅ Installed ${skillName} v${version} from ${sourceLabel}`) + ' ' + methodLabel);
|
|
204
268
|
return true;
|
|
205
269
|
} catch (e) {
|
|
206
270
|
return false;
|
|
@@ -346,7 +410,7 @@ async function installFromLocalPath(localPath, targetDir, options) {
|
|
|
346
410
|
}
|
|
347
411
|
}
|
|
348
412
|
|
|
349
|
-
async function installFromGitUrl(gitUrl, targetDir, options) {
|
|
413
|
+
async function installFromGitUrl(gitUrl, targetDir, options, subPath = '') {
|
|
350
414
|
// Derive cache key from URL
|
|
351
415
|
const cacheKey = gitUrl
|
|
352
416
|
.replace(/^git@/, '')
|
|
@@ -355,8 +419,12 @@ async function installFromGitUrl(gitUrl, targetDir, options) {
|
|
|
355
419
|
.replace(/[:/]/g, '-');
|
|
356
420
|
|
|
357
421
|
const cachePath = path.join(CACHE_DIR, cacheKey);
|
|
422
|
+
const sourcePath = subPath ? path.join(cachePath, subPath) : cachePath;
|
|
358
423
|
|
|
359
424
|
console.log(chalk.blue(`📦 Installing from: ${gitUrl}`));
|
|
425
|
+
if (subPath) {
|
|
426
|
+
console.log(chalk.gray(` Subpath: ${subPath}`));
|
|
427
|
+
}
|
|
360
428
|
|
|
361
429
|
// Check if cache is valid (non-empty)
|
|
362
430
|
const cacheValid = fs.existsSync(cachePath) && fs.readdirSync(cachePath).length > 0;
|
|
@@ -387,7 +455,7 @@ async function installFromGitUrl(gitUrl, targetDir, options) {
|
|
|
387
455
|
}
|
|
388
456
|
}
|
|
389
457
|
|
|
390
|
-
const skills = await findSkills(
|
|
458
|
+
const skills = await findSkills(sourcePath);
|
|
391
459
|
if (skills.length === 0) throw new Error('No skills found in repository');
|
|
392
460
|
|
|
393
461
|
console.log(chalk.green(` Found ${skills.length} skill(s)`));
|
|
@@ -620,7 +688,7 @@ async function installFromLockfile(targetDir, projectRoot) {
|
|
|
620
688
|
console.log(chalk.green(`\n✅ All skills installed`));
|
|
621
689
|
}
|
|
622
690
|
|
|
623
|
-
async function findSkills(basePath) {
|
|
691
|
+
export async function findSkills(basePath) {
|
|
624
692
|
const skills = [];
|
|
625
693
|
|
|
626
694
|
async function scan(dir, relativePath = '') {
|
package/src/commands/list.js
CHANGED
|
@@ -1,149 +1,91 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { LockfileManager } from '../lockfile.js';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import * as fs from 'fs';
|
|
5
|
-
import * as os from 'os';
|
|
1
|
+
import { SkillLoader } from '../skill-loader.js';
|
|
6
2
|
import chalk from 'chalk';
|
|
7
3
|
|
|
8
|
-
/** Scan all standard skill directories and return unique skill names */
|
|
9
|
-
function scanAllSkillDirs(projectRoot) {
|
|
10
|
-
const dirs = [
|
|
11
|
-
path.join(projectRoot, '.agent', 'skills'),
|
|
12
|
-
path.join(projectRoot, '.claude', 'skills'),
|
|
13
|
-
path.join(os.homedir(), '.agent', 'skills'),
|
|
14
|
-
path.join(os.homedir(), '.claude', 'skills'),
|
|
15
|
-
path.join(projectRoot, 'skills'), // legacy
|
|
16
|
-
];
|
|
17
|
-
const seen = new Set();
|
|
18
|
-
const skills = [];
|
|
19
|
-
for (const dir of dirs) {
|
|
20
|
-
if (!fs.existsSync(dir)) continue;
|
|
21
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
22
|
-
if (entry.isDirectory() && !seen.has(entry.name)) {
|
|
23
|
-
seen.add(entry.name);
|
|
24
|
-
const skillMd = path.join(dir, entry.name, 'SKILL.md');
|
|
25
|
-
let description = '', version = '';
|
|
26
|
-
if (fs.existsSync(skillMd)) {
|
|
27
|
-
const content = fs.readFileSync(skillMd, 'utf-8');
|
|
28
|
-
description = (content.match(/description:\s*(.+)/) || [])[1]?.trim() || '';
|
|
29
|
-
version = (content.match(/version:\s*(.+)/) || [])[1]?.trim() || '';
|
|
30
|
-
}
|
|
31
|
-
skills.push({ name: entry.name, dir, description, version });
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return skills;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
4
|
export function listCommand(program) {
|
|
39
5
|
program
|
|
40
6
|
.command('list')
|
|
41
|
-
.
|
|
42
|
-
.
|
|
43
|
-
.option('-
|
|
7
|
+
.alias('ls')
|
|
8
|
+
.description('List installed skills')
|
|
9
|
+
.option('-a, --agent <agent>', 'Filter by agent (joycode, claude-code, cursor, etc.)')
|
|
10
|
+
.option('-g, --global', 'Show only global (user-level) skills')
|
|
11
|
+
.option('-p, --project', 'Show only project-level skills')
|
|
44
12
|
.option('-s, --search <query>', 'Search skills by name or description')
|
|
45
|
-
.option('
|
|
46
|
-
.option('-l, --local', 'Show only local skills')
|
|
47
|
-
.option('-r, --registry <name>', 'Show skills from specific registry')
|
|
13
|
+
.option('--stats', 'Show statistics')
|
|
48
14
|
.action(async (options) => {
|
|
49
15
|
const projectRoot = process.cwd();
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
console.log(chalk.blue('Available Skills:'));
|
|
53
|
-
console.log(chalk.gray('================='));
|
|
16
|
+
const loader = new SkillLoader(projectRoot);
|
|
54
17
|
|
|
55
18
|
try {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
console.log(chalk.blue('\nInstalled Skills:'));
|
|
65
|
-
let filtered = localSkills;
|
|
66
|
-
if (options.search) {
|
|
67
|
-
const q = options.search.toLowerCase();
|
|
68
|
-
filtered = filtered.filter(s => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q));
|
|
69
|
-
}
|
|
70
|
-
if (filtered.length > 0) {
|
|
71
|
-
filtered.forEach(skill => {
|
|
72
|
-
const lock = installedSkills[skill.name];
|
|
73
|
-
const ver = lock ? chalk.gray(`v${lock.version}`) : (skill.version ? chalk.gray(`v${skill.version}`) : '');
|
|
74
|
-
const src = lock ? chalk.gray(`[${lock.source}]`) : '';
|
|
75
|
-
console.log(` ✅ ${chalk.bold(skill.name)} ${ver} ${src}`);
|
|
76
|
-
if (skill.description) console.log(` ${chalk.gray(skill.description)}`);
|
|
77
|
-
});
|
|
78
|
-
} else {
|
|
79
|
-
console.log(' No matching skills found.');
|
|
80
|
-
}
|
|
81
|
-
} else {
|
|
82
|
-
console.log(chalk.yellow('\n No skills installed.'));
|
|
83
|
-
console.log(chalk.gray(' Run: joySkills install <skill>'));
|
|
84
|
-
}
|
|
19
|
+
// 加载 skills
|
|
20
|
+
let skills = [];
|
|
21
|
+
if (options.global) {
|
|
22
|
+
skills = loader.loadGlobalSkills(options.agent);
|
|
23
|
+
} else if (options.project) {
|
|
24
|
+
skills = loader.loadProjectSkills(options.agent);
|
|
25
|
+
} else {
|
|
26
|
+
skills = loader.loadAllSkills(options.agent);
|
|
85
27
|
}
|
|
86
28
|
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
29
|
+
// 搜索过滤
|
|
30
|
+
if (options.search) {
|
|
31
|
+
const q = options.search.toLowerCase();
|
|
32
|
+
skills = skills.filter(s =>
|
|
33
|
+
s.name.toLowerCase().includes(q) ||
|
|
34
|
+
s.description.toLowerCase().includes(q)
|
|
35
|
+
);
|
|
36
|
+
}
|
|
93
37
|
|
|
94
|
-
|
|
38
|
+
// 统计模式
|
|
39
|
+
if (options.stats) {
|
|
40
|
+
const stats = loader.getStats(options.agent);
|
|
41
|
+
console.log(chalk.blue('\nSkill Statistics:'));
|
|
42
|
+
console.log(chalk.gray('=================='));
|
|
43
|
+
console.log(` Total: ${stats.total}`);
|
|
44
|
+
console.log(` Project-level: ${stats.project}`);
|
|
45
|
+
console.log(` Global: ${stats.global}`);
|
|
46
|
+
console.log(` Agents: ${stats.agents.join(', ')}`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
95
49
|
|
|
96
|
-
|
|
50
|
+
// 显示 skills
|
|
51
|
+
console.log(chalk.blue('\nInstalled Skills:'));
|
|
52
|
+
console.log(chalk.gray('=================='));
|
|
97
53
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
54
|
+
if (skills.length === 0) {
|
|
55
|
+
console.log(chalk.yellow(' No skills found.'));
|
|
56
|
+
console.log(chalk.gray(' Run: joySkills install <skill>'));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
102
59
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
60
|
+
// 按 scope 分组显示
|
|
61
|
+
const projectSkills = skills.filter(s => s.scope === 'project');
|
|
62
|
+
const globalSkills = skills.filter(s => s.scope === 'global');
|
|
106
63
|
|
|
107
|
-
|
|
108
|
-
|
|
64
|
+
if (projectSkills.length > 0 && !options.global) {
|
|
65
|
+
console.log(chalk.cyan('\n 📁 Project-level:'));
|
|
66
|
+
projectSkills.forEach(skill => {
|
|
67
|
+
const scopeLabel = skill.agent === 'joycode' ? '' : chalk.gray(`[${skill.agent}]`);
|
|
68
|
+
console.log(` ✅ ${chalk.bold(skill.name)} ${chalk.gray(`v${skill.version}`)} ${scopeLabel}`);
|
|
69
|
+
if (skill.description) {
|
|
70
|
+
console.log(` ${chalk.gray(skill.description)}`);
|
|
109
71
|
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
110
74
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const recommendedVersion = recommended ? recommended.version : 'N/A';
|
|
119
|
-
|
|
120
|
-
console.log(` ${isInstalled} ${skill.name || skill.id} (${skill.id})`);
|
|
121
|
-
if (options.allVersions) {
|
|
122
|
-
skill.versions.forEach(v => {
|
|
123
|
-
const marker = v.recommended ? '⭐' : ' ';
|
|
124
|
-
const state = v.state === 'approved' ? '✓' : v.state;
|
|
125
|
-
console.log(` ${marker} v${v.version} [${state}]`);
|
|
126
|
-
});
|
|
127
|
-
} else {
|
|
128
|
-
console.log(` Recommended: v${recommendedVersion}`);
|
|
129
|
-
if (installedVersion) {
|
|
130
|
-
console.log(` Installed: v${installedVersion}`);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
if (skill.description) {
|
|
134
|
-
console.log(` ${skill.description}`);
|
|
135
|
-
}
|
|
136
|
-
console.log('');
|
|
137
|
-
});
|
|
138
|
-
} else {
|
|
139
|
-
console.log(' No matching registry skills found.');
|
|
75
|
+
if (globalSkills.length > 0 && !options.project) {
|
|
76
|
+
console.log(chalk.cyan('\n 🏠 Global (user-level):'));
|
|
77
|
+
globalSkills.forEach(skill => {
|
|
78
|
+
const scopeLabel = skill.agent === 'joycode' ? '' : chalk.gray(`[${skill.agent}]`);
|
|
79
|
+
console.log(` ✅ ${chalk.bold(skill.name)} ${chalk.gray(`v${skill.version}`)} ${scopeLabel}`);
|
|
80
|
+
if (skill.description) {
|
|
81
|
+
console.log(` ${chalk.gray(skill.description)}`);
|
|
140
82
|
}
|
|
141
|
-
}
|
|
142
|
-
console.error('Error loading registry:', error.message);
|
|
143
|
-
}
|
|
83
|
+
});
|
|
144
84
|
}
|
|
85
|
+
|
|
86
|
+
console.log(chalk.gray(`\n Total: ${skills.length} skill(s)`));
|
|
145
87
|
} catch (error) {
|
|
146
|
-
console.error('Error listing skills:', error);
|
|
88
|
+
console.error(chalk.red('Error listing skills:'), error.message);
|
|
147
89
|
}
|
|
148
90
|
});
|
|
149
91
|
}
|
package/src/commands/remove.js
CHANGED
|
@@ -5,12 +5,21 @@ import * as os from 'os';
|
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
|
|
7
7
|
function findSkillOnDisk(skillName, projectRoot) {
|
|
8
|
+
// v2.0: Support all agent directories, joycode first
|
|
8
9
|
const dirs = [
|
|
10
|
+
// Project-level
|
|
11
|
+
path.join(projectRoot, '.joycode', 'skills'),
|
|
9
12
|
path.join(projectRoot, '.agent', 'skills'),
|
|
10
13
|
path.join(projectRoot, '.claude', 'skills'),
|
|
14
|
+
path.join(projectRoot, '.cursor', 'skills'),
|
|
15
|
+
path.join(projectRoot, '.qoder', 'skills'),
|
|
16
|
+
path.join(projectRoot, 'skills'),
|
|
17
|
+
// Global
|
|
18
|
+
path.join(os.homedir(), '.joycode', 'skills'),
|
|
11
19
|
path.join(os.homedir(), '.agent', 'skills'),
|
|
12
20
|
path.join(os.homedir(), '.claude', 'skills'),
|
|
13
|
-
path.join(
|
|
21
|
+
path.join(os.homedir(), '.cursor', 'skills'),
|
|
22
|
+
path.join(os.homedir(), '.qoder', 'skills'),
|
|
14
23
|
];
|
|
15
24
|
for (const d of dirs) {
|
|
16
25
|
const p = path.join(d, skillName);
|
package/src/commands/sync.js
CHANGED
|
@@ -20,12 +20,18 @@ export function syncCommand(program) {
|
|
|
20
20
|
|
|
21
21
|
console.log(chalk.blue('🔍 Scanning skills...'));
|
|
22
22
|
|
|
23
|
-
// Scan
|
|
23
|
+
// Scan project-level skill directories only (v2.0)
|
|
24
|
+
// AGENTS.md should only reference project-level skills
|
|
24
25
|
const skillDirs = [
|
|
26
|
+
// JoyCode (default, highest priority)
|
|
27
|
+
{ path: path.join(projectRoot, '.joycode', 'skills'), label: 'joycode' },
|
|
28
|
+
// Universal (multi-agent compatible)
|
|
25
29
|
{ path: path.join(projectRoot, '.agent', 'skills'), label: 'universal' },
|
|
26
|
-
|
|
27
|
-
{ path: path.join(
|
|
28
|
-
|
|
30
|
+
// Claude Code
|
|
31
|
+
{ path: path.join(projectRoot, '.claude', 'skills'), label: 'claude' },
|
|
32
|
+
// Other agents
|
|
33
|
+
{ path: path.join(projectRoot, '.cursor', 'skills'), label: 'cursor' },
|
|
34
|
+
{ path: path.join(projectRoot, '.qoder', 'skills'), label: 'qoder' },
|
|
29
35
|
// legacy
|
|
30
36
|
{ path: path.join(projectRoot, 'skills'), label: 'legacy' },
|
|
31
37
|
];
|