joyskills-cli 0.2.6 → 0.2.7

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.
@@ -1,8 +1,39 @@
1
- import { LocalManager } from '../local.js';
2
1
  import { RegistryManager } from '../registry.js';
3
2
  import { LockfileManager } from '../lockfile.js';
4
3
  import * as path from 'path';
5
4
  import * as fs from 'fs';
5
+ import * as os from 'os';
6
+ import chalk from 'chalk';
7
+
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
+ }
6
37
 
7
38
  export function listCommand(program) {
8
39
  program
@@ -16,42 +47,40 @@ export function listCommand(program) {
16
47
  .option('-r, --registry <name>', 'Show skills from specific registry')
17
48
  .action(async (options) => {
18
49
  const projectRoot = process.cwd();
19
- const localManager = new LocalManager(projectRoot);
20
50
  const lockfileManager = new LockfileManager(projectRoot);
21
51
 
22
- console.log('Available Skills:');
23
- console.log('=================');
52
+ console.log(chalk.blue('Available Skills:'));
53
+ console.log(chalk.gray('================='));
24
54
 
25
55
  try {
26
- // Load lockfile to know what's installed
27
56
  await lockfileManager.load();
28
57
  const installedSkills = lockfileManager.lockData.skills;
29
58
 
30
- // List local skills
31
- const localSkills = localManager.listSkills();
59
+ // Scan all skill directories
60
+ const localSkills = scanAllSkillDirs(projectRoot);
32
61
 
33
62
  if (!options.registry) {
34
- // Show local skills if requested or if local-only mode
35
- if (options.local || localSkills.length > 0) {
36
- console.log('\nLocal Skills:');
37
- const filteredLocalSkills = localSkills.filter(skill => {
38
- if (options.installed && !installedSkills[skill]) return false;
39
- if (options.search) {
40
- const query = options.search.toLowerCase();
41
- return skill.toLowerCase().includes(query);
42
- }
43
- return true;
44
- });
45
-
46
- if (filteredLocalSkills.length > 0) {
47
- filteredLocalSkills.forEach(skill => {
48
- const isInstalled = installedSkills[skill] ? '✅' : ' ';
49
- const version = installedSkills[skill] ? `v${installedSkills[skill].version}` : '';
50
- console.log(` ${isInstalled} ${skill} ${version}`);
63
+ if (localSkills.length > 0) {
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)}`);
51
77
  });
52
78
  } else {
53
- console.log(' No matching local skills found.');
79
+ console.log(' No matching skills found.');
54
80
  }
81
+ } else {
82
+ console.log(chalk.yellow('\n No skills installed.'));
83
+ console.log(chalk.gray(' Run: joySkills install <skill>'));
55
84
  }
56
85
  }
57
86
 
@@ -0,0 +1,102 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+ import { checkbox, confirm } from '@inquirer/prompts';
5
+ import chalk from 'chalk';
6
+ import { LockfileManager } from '../lockfile.js';
7
+
8
+ export function manageCommand(program) {
9
+ program
10
+ .command('manage')
11
+ .description('Interactively manage (remove) installed skills')
12
+ .option('-g, --global', 'Manage global ~/.claude/skills')
13
+ .option('--universal', 'Manage .agent/skills/')
14
+ .option('-y, --yes', 'Skip confirmation')
15
+ .action(async (options) => {
16
+ try {
17
+ const projectRoot = process.cwd();
18
+
19
+ // Determine which dirs to scan
20
+ const dirsToScan = options.global
21
+ ? [path.join(os.homedir(), '.claude', 'skills')]
22
+ : options.universal
23
+ ? [path.join(projectRoot, '.agent', 'skills')]
24
+ : [
25
+ path.join(projectRoot, '.agent', 'skills'),
26
+ path.join(projectRoot, '.claude', 'skills'),
27
+ ];
28
+
29
+ // Collect skills from all relevant dirs
30
+ const seen = new Set();
31
+ const skills = [];
32
+ for (const skillsDir of dirsToScan) {
33
+ if (!fs.existsSync(skillsDir)) continue;
34
+ for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
35
+ if (!entry.isDirectory() || seen.has(entry.name)) continue;
36
+ seen.add(entry.name);
37
+ const skillPath = path.join(skillsDir, entry.name);
38
+ const skillMd = path.join(skillPath, 'SKILL.md');
39
+ let description = '';
40
+ if (fs.existsSync(skillMd)) {
41
+ const content = fs.readFileSync(skillMd, 'utf-8');
42
+ description = (content.match(/description:\s*(.+)/) || [])[1]?.trim() || '';
43
+ }
44
+ skills.push({ name: entry.name, path: skillPath, description });
45
+ }
46
+ }
47
+
48
+ if (skills.length === 0) {
49
+ console.log(chalk.yellow('⚠️ No skills installed'));
50
+ return;
51
+ }
52
+
53
+ let toRemove;
54
+ if (options.yes) {
55
+ // -y: remove all without interaction
56
+ toRemove = skills;
57
+ console.log(chalk.blue(`\n📦 Removing all ${skills.length} skill(s) (--yes mode):`));
58
+ for (const s of toRemove) console.log(` - ${s.name}`);
59
+ } else {
60
+ console.log(chalk.blue(`\n📦 Installed skills:`));
61
+
62
+ const choices = skills.map(s => ({
63
+ name: s.description ? `${s.name} — ${chalk.gray(s.description)}` : s.name,
64
+ value: s,
65
+ checked: false
66
+ }));
67
+
68
+ toRemove = await checkbox({
69
+ message: 'Select skills to remove (space to select, enter to confirm):',
70
+ choices
71
+ });
72
+
73
+ if (toRemove.length === 0) {
74
+ console.log(chalk.gray(' Nothing selected'));
75
+ return;
76
+ }
77
+
78
+ console.log(chalk.yellow(`\nAbout to remove ${toRemove.length} skill(s):`));
79
+ for (const s of toRemove) console.log(` - ${s.name}`);
80
+ const ok = await confirm({ message: 'Continue?', default: false });
81
+ if (!ok) { console.log(chalk.gray(' Cancelled')); return; }
82
+ }
83
+
84
+ // Remove
85
+ const lockfileManager = new LockfileManager(projectRoot);
86
+ await lockfileManager.load();
87
+
88
+ for (const skill of toRemove) {
89
+ fs.rmSync(skill.path, { recursive: true, force: true });
90
+ lockfileManager.removeSkill(skill.name);
91
+ console.log(chalk.green(` ✓ Removed ${skill.name}`));
92
+ }
93
+
94
+ await lockfileManager.save();
95
+ console.log(chalk.green(`\n✅ Removed ${toRemove.length} skill(s)`));
96
+
97
+ } catch (error) {
98
+ console.error(chalk.red(`❌ Manage failed: ${error.message}`));
99
+ process.exit(1);
100
+ }
101
+ });
102
+ }
@@ -1,127 +1,70 @@
1
- #!/usr/bin/env node
2
-
3
- import fs from 'fs';
4
- import path from 'path';
5
-
6
- /**
7
- * joySkills read 命令
8
- *
9
- * 功能:
10
- * 读取指定 skill 的完整内容(YAML frontmatter + Markdown)
11
- *
12
- * 设计原则:
13
- * - 100% 兼容 OpenSkills read 的输出格式
14
- * - 路径查找优先级与 openskills sync 一致
15
- * - 供 AI 编辑器解析使用
16
- *
17
- * 路径查找优先级:
18
- * 1. 项目级 .agent/skills(--universal)
19
- * 2. 全局 ~/.agent/skills
20
- * 3. 项目级 .claude/skills(--project)
21
- * 4. 全局 ~/.claude/skills(--global)
22
- */
23
-
24
- export async function readCommand(skillName) {
25
- if (!skillName) {
26
- console.error('❌ 错误:请指定要读取的 skill 名称');
27
- console.log('\n用法: joySkills read <skill-name>\n');
28
- console.log('示例: joySkills read rc-onboarding\n');
29
- process.exit(1);
30
- }
31
-
32
- try {
33
- // 查找 skill 路径
34
- const skillPath = findSkillPath(skillName);
35
-
36
- if (!skillPath) {
37
- console.error(`❌ 找不到 skill: ${skillName}`);
38
- console.log('\n💡 提示:');
39
- console.log(` 1. 检查 skill 名称是否正确`);
40
- console.log(` 2. 执行 joySkills list 查看已安装的 skills\n`);
41
- process.exit(1);
42
- }
43
-
44
- // 读取 SKILL.md
45
- const skillMdPath = path.join(skillPath, 'SKILL.md');
46
- if (!fs.existsSync(skillMdPath)) {
47
- console.error(`❌ ${skillName} 缺少 SKILL.md 文件`);
48
- process.exit(1);
49
- }
50
-
51
- const content = fs.readFileSync(skillMdPath, 'utf-8');
52
-
53
- // 输出到 stdout(供 AI 编辑器解析)
54
- console.log(content);
55
-
56
- } catch (error) {
57
- console.error('❌ 读取失败:', error.message);
58
- process.exit(1);
59
- }
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+ import chalk from 'chalk';
5
+
6
+ export function readCommand(program) {
7
+ program
8
+ .command('read <skills>')
9
+ .description('Load and print skill content (comma-separated names supported)')
10
+ .option('-g, --global', 'Read from global ~/.claude/skills')
11
+ .option('--universal', 'Read from .agent/skills/')
12
+ .action(async (skillsInput, options) => {
13
+ try {
14
+ const projectRoot = process.cwd();
15
+
16
+ // Build search paths in priority order (openskill compatible)
17
+ const searchPaths = [
18
+ path.join(projectRoot, '.agent', 'skills'),
19
+ path.join(os.homedir(), '.agent', 'skills'),
20
+ path.join(projectRoot, '.claude', 'skills'),
21
+ path.join(os.homedir(), '.claude', 'skills'),
22
+ // legacy
23
+ path.join(projectRoot, 'skills'),
24
+ ];
25
+
26
+ // Support comma-separated skill names
27
+ const skillNames = skillsInput.split(',').map(s => s.trim()).filter(Boolean);
28
+
29
+ for (const skillName of skillNames) {
30
+ const content = findSkillContent(skillName, searchPaths);
31
+
32
+ if (!content) {
33
+ console.error(chalk.red(`❌ Skill not found: ${skillName}`));
34
+ console.error(chalk.gray(` Searched in: ${searchPaths.filter(p => fs.existsSync(p)).join(', ')}`));
35
+ continue;
36
+ }
37
+
38
+ // Print skill content (for agent consumption)
39
+ if (skillNames.length > 1) {
40
+ console.log(chalk.blue(`\n── ${skillName} ──────────────────────────────────`));
41
+ }
42
+ console.log(content);
43
+ }
44
+
45
+ } catch (error) {
46
+ console.error(chalk.red(`❌ Read failed: ${error.message}`));
47
+ process.exit(1);
48
+ }
49
+ });
60
50
  }
61
51
 
62
- /**
63
- * 查找 skill 路径(按优先级)
64
- *
65
- * 优先级与 OpenSkills sync 一致:
66
- * 1. 项目 .agent > 全局 .agent
67
- * 2. 项目 .claude > 全局 .claude
68
- */
69
- function findSkillPath(skillName) {
70
- const homeDir = process.env.HOME || process.env.USERPROFILE;
71
- const cwd = process.cwd();
72
-
73
- const searchPaths = [
74
- // 1. 项目级 .agent/skills(joySkills 默认 / OpenSkills --universal)
75
- path.join(cwd, '.agent', 'skills', skillName),
76
-
77
- // 2. 全局 ~/.agent/skills(joySkills --global)
78
- path.join(homeDir, '.agent', 'skills', skillName),
79
-
80
- // 3. 项目级 .claude/skills(OpenSkills --project)
81
- path.join(cwd, '.claude', 'skills', skillName),
82
-
83
- // 4. 全局 ~/.claude/skills(OpenSkills --global)
84
- path.join(homeDir, '.claude', 'skills', skillName),
85
- ];
86
-
52
+ function findSkillContent(skillName, searchPaths) {
87
53
  for (const searchPath of searchPaths) {
88
- if (fs.existsSync(searchPath)) {
89
- return searchPath;
90
- }
91
- }
92
-
93
- return null;
94
- }
95
-
96
- /**
97
- * 列出所有可读取的 skills(辅助命令)
98
- */
99
- export function listReadableSkills() {
100
- const homeDir = process.env.HOME || process.env.USERPROFILE;
101
- const cwd = process.cwd();
54
+ if (!fs.existsSync(searchPath)) continue;
102
55
 
103
- const searchDirs = [
104
- path.join(cwd, '.agent', 'skills'),
105
- path.join(homeDir, '.agent', 'skills'),
106
- path.join(cwd, '.claude', 'skills'),
107
- path.join(homeDir, '.claude', 'skills'),
108
- ];
56
+ const skillDir = path.join(searchPath, skillName);
57
+ const skillMd = path.join(skillDir, 'SKILL.md');
109
58
 
110
- const skills = new Set();
111
-
112
- for (const dir of searchDirs) {
113
- if (!fs.existsSync(dir)) continue;
114
-
115
- const entries = fs.readdirSync(dir, { withFileTypes: true });
116
- for (const entry of entries) {
117
- if (!entry.isDirectory()) continue;
59
+ if (fs.existsSync(skillMd)) {
60
+ return fs.readFileSync(skillMd, 'utf-8');
61
+ }
118
62
 
119
- const skillMdPath = path.join(dir, entry.name, 'SKILL.md');
120
- if (fs.existsSync(skillMdPath)) {
121
- skills.add(entry.name);
122
- }
63
+ // Also check if skillName is a direct SKILL.md path
64
+ const directMd = path.join(searchPath, `${skillName}.md`);
65
+ if (fs.existsSync(directMd)) {
66
+ return fs.readFileSync(directMd, 'utf-8');
123
67
  }
124
68
  }
125
-
126
- return Array.from(skills).sort();
69
+ return null;
127
70
  }
@@ -1,6 +1,23 @@
1
- import { Command } from 'commander';
2
- import { LocalManager } from '../local.js';
3
1
  import { LockfileManager } from '../lockfile.js';
2
+ import * as path from 'path';
3
+ import * as fs from 'fs';
4
+ import * as os from 'os';
5
+ import chalk from 'chalk';
6
+
7
+ function findSkillOnDisk(skillName, projectRoot) {
8
+ const dirs = [
9
+ path.join(projectRoot, '.agent', 'skills'),
10
+ path.join(projectRoot, '.claude', 'skills'),
11
+ path.join(os.homedir(), '.agent', 'skills'),
12
+ path.join(os.homedir(), '.claude', 'skills'),
13
+ path.join(projectRoot, 'skills'),
14
+ ];
15
+ for (const d of dirs) {
16
+ const p = path.join(d, skillName);
17
+ if (fs.existsSync(p)) return p;
18
+ }
19
+ return null;
20
+ }
4
21
 
5
22
  export function removeCommand(program) {
6
23
  program
@@ -8,35 +25,26 @@ export function removeCommand(program) {
8
25
  .description('Remove a skill')
9
26
  .action(async (skillName) => {
10
27
  const projectRoot = process.cwd();
11
- const localManager = new LocalManager(projectRoot);
12
28
  const lockfileManager = new LockfileManager(projectRoot);
13
29
 
14
- console.log(`Removing skill: ${skillName}`);
30
+ console.log(chalk.blue(`Removing skill: ${skillName}`));
15
31
 
16
32
  try {
17
- // Load existing lockfile if exists
18
33
  await lockfileManager.load();
19
34
 
20
- // Check if skill exists locally
21
- if (!localManager.hasSkill(skillName)) {
22
- console.log(`Skill ${skillName} does not exist locally.`);
35
+ const skillPath = findSkillOnDisk(skillName, projectRoot);
36
+ if (!skillPath) {
37
+ console.log(chalk.yellow(`Skill ${skillName} does not exist.`));
23
38
  return;
24
39
  }
25
40
 
26
- // Remove the skill
27
- const removed = localManager.removeSkill(skillName);
28
-
29
- if (removed) {
30
- // Update lockfile
31
- lockfileManager.removeSkill(skillName);
32
- await lockfileManager.save();
41
+ fs.rmSync(skillPath, { recursive: true, force: true });
42
+ lockfileManager.removeSkill(skillName);
43
+ await lockfileManager.save();
33
44
 
34
- console.log(`✅ Successfully removed ${skillName}`);
35
- } else {
36
- console.log(`❌ Failed to remove ${skillName}`);
37
- }
45
+ console.log(chalk.green(`✅ Successfully removed ${skillName}`));
38
46
  } catch (error) {
39
- console.error(`❌ Failed to remove ${skillName}:`, error);
47
+ console.error(chalk.red(`❌ Failed to remove ${skillName}:`), error.message);
40
48
  }
41
49
  });
42
50
  }
@@ -1,8 +1,20 @@
1
1
  import { LockfileManager } from '../lockfile.js';
2
2
  import { RegistryManager } from '../registry.js';
3
- import { LocalManager } from '../local.js';
4
3
  import * as path from 'path';
5
4
  import * as fs from 'fs';
5
+ import * as os from 'os';
6
+ import chalk from 'chalk';
7
+
8
+ function skillExistsOnDisk(skillName, projectRoot) {
9
+ const dirs = [
10
+ path.join(projectRoot, '.agent', 'skills'),
11
+ path.join(projectRoot, '.claude', 'skills'),
12
+ path.join(os.homedir(), '.agent', 'skills'),
13
+ path.join(os.homedir(), '.claude', 'skills'),
14
+ path.join(projectRoot, 'skills'),
15
+ ];
16
+ return dirs.some(d => fs.existsSync(path.join(d, skillName)));
17
+ }
6
18
 
7
19
  export function statusCommand(program) {
8
20
  program
@@ -11,10 +23,9 @@ export function statusCommand(program) {
11
23
  .action(async () => {
12
24
  const projectRoot = process.cwd();
13
25
  const lockfileManager = new LockfileManager(projectRoot);
14
- const localManager = new LocalManager(projectRoot);
15
26
 
16
- console.log('Skill Status Report:');
17
- console.log('====================');
27
+ console.log(chalk.blue('Skill Status Report:'));
28
+ console.log(chalk.gray('===================='));
18
29
 
19
30
  try {
20
31
  await lockfileManager.load();
@@ -23,7 +34,7 @@ export function statusCommand(program) {
23
34
  const skillIds = Object.keys(installedSkills);
24
35
 
25
36
  if (skillIds.length === 0) {
26
- console.log('No skills installed.');
37
+ console.log(chalk.yellow('No skills installed.'));
27
38
  return;
28
39
  }
29
40
 
@@ -40,17 +51,17 @@ export function statusCommand(program) {
40
51
  }
41
52
  }
42
53
 
43
- console.log(`\nInstalled Skills (${skillIds.length} total):`);
44
- console.log('------------------------------');
54
+ console.log(chalk.blue(`\nInstalled Skills (${skillIds.length} total):`));
55
+ console.log(chalk.gray(''.repeat(50)));
45
56
 
46
57
  for (const skillId of skillIds) {
47
58
  const skillEntry = installedSkills[skillId];
48
59
 
49
- let status = '✅ Installed';
60
+ let status = chalk.green('✅ Installed');
50
61
  let details = '';
51
62
 
52
- // Check if skill exists locally
53
- const hasLocal = localManager.hasSkill(skillId);
63
+ // Check if skill exists on disk (check all standard dirs)
64
+ const hasLocal = skillExistsOnDisk(skillId, projectRoot);
54
65
 
55
66
  // Check registry status
56
67
  if (registryManager) {
@@ -59,56 +70,35 @@ export function statusCommand(program) {
59
70
  const version = registryManager.getVersion(skillId, skillEntry.version);
60
71
  if (version) {
61
72
  if (version.state === 'deprecated') {
62
- status = '⚠️ Deprecated';
73
+ status = chalk.yellow('⚠️ Deprecated');
63
74
  details = ' (upgrade recommended)';
64
75
  } else if (version.state === 'draft' || version.state === 'pending_review') {
65
- status = '❌ Not Approved';
76
+ status = chalk.red('❌ Not Approved');
66
77
  details = ` (${version.state})`;
67
78
  }
68
-
69
- // Check for newer versions
70
79
  const latest = registryManager.getLatestVersion(skillId);
71
80
  if (latest && latest.version !== skillEntry.version) {
72
- details += ` - newer version v${latest.version} available`;
81
+ details += chalk.gray(` v${latest.version} available`);
73
82
  }
74
83
  }
75
- } else {
76
- status = '❓ Not in Registry';
77
- details = ' (local/external skill)';
78
84
  }
79
85
  }
80
86
 
81
87
  if (!hasLocal) {
82
- status = '⚠️ Missing Local Files';
83
- details = ' (files may have been deleted)';
88
+ status = chalk.red('Missing Files');
89
+ details = ' (files deleted, run joySkills install to restore)';
84
90
  }
85
91
 
86
- console.log(`${skillId} v${skillEntry.version}: ${status}${details}`);
92
+ const src = skillEntry.source ? chalk.gray(` [${skillEntry.source}]`) : '';
93
+ console.log(` ${status} ${chalk.bold(skillId)} v${skillEntry.version}${src}${details}`);
87
94
  }
88
95
 
89
- // Show registry info
90
96
  if (registryManager) {
91
- console.log('\nRegistry Status:');
92
- console.log('---------------');
93
- console.log(`Registry loaded from: ${registryPath}`);
94
- console.log(`Total skills in registry: ${registryManager.getAllSkills().length}`);
95
- } else {
96
- console.log('\nRegistry Status:');
97
- console.log('---------------');
98
- console.log('No registry found. Using local skills only.');
97
+ console.log(chalk.blue('\nRegistry:'));
98
+ console.log(` Loaded: ${chalk.gray(registryPath)}`);
99
+ console.log(` Skills: ${chalk.blue(registryManager.getAllSkills().length)}`);
99
100
  }
100
101
 
101
- // Show local skills
102
- const localSkills = localManager.listSkills();
103
- const localOnlySkills = localSkills.filter(skillId => !installedSkills[skillId]);
104
-
105
- if (localOnlySkills.length > 0) {
106
- console.log(`\nLocal Skills (${localOnlySkills.length} not installed):`);
107
- console.log('-------------------------');
108
- for (const skillId of localOnlySkills) {
109
- console.log(`${skillId} (local only)`);
110
- }
111
- }
112
102
  } catch (error) {
113
103
  console.error('Error checking status:', error);
114
104
  }