sophhub 0.1.0
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/bin/sophhub.js +21 -0
- package/package.json +32 -0
- package/skills/VERSIONS.md +27 -0
- package/skills/builtin/clawhub/SKILL.md +77 -0
- package/skills/builtin/flight-booking/SKILL.md +288 -0
- package/skills/builtin/flight-booking/scripts/flight_booking.py +1232 -0
- package/skills/builtin/inventory-management/SKILL.md +241 -0
- package/skills/builtin/inventory-management/scripts/inventory.py +1844 -0
- package/skills/builtin/schedule-reminder/SKILL.md +619 -0
- package/skills/builtin/schedule-reminder/schedule_template.md +68 -0
- package/skills/builtin/schedule-reminder/scripts/append_event.py +204 -0
- package/skills/builtin/schedule-reminder/scripts/create_reminders.sh +163 -0
- package/skills/builtin/schedule-reminder/scripts/daily_activate.sh +175 -0
- package/skills/builtin/schedule-reminder/scripts/parse_schedule.py +704 -0
- package/skills/builtin/schedule-reminder/scripts/setup.sh +242 -0
- package/skills/builtin/schedule-reminder//347/224/250/346/210/267/346/214/207/345/215/227.md +311 -0
- package/skills/builtin/skill-creator/SKILL.md +370 -0
- package/skills/builtin/skill-creator/license.txt +202 -0
- package/skills/builtin/skill-creator/scripts/init_skill.py +378 -0
- package/skills/builtin/skill-creator/scripts/package_skill.py +111 -0
- package/skills/builtin/skill-creator/scripts/quick_validate.py +101 -0
- package/skills/builtin/sophnet-customer-management/SKILL.md +271 -0
- package/skills/builtin/sophnet-customer-management/pyproject.toml +15 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/__init__.py +2 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/__main__.py +5 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/cli.py +67 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/commands/__init__.py +2 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/commands/customer.py +60 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/commands/export_file.py +18 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/commands/import_file.py +15 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/commands/reminder.py +26 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/commands/schema.py +28 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_cli/config.py +54 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/__init__.py +2 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/exporter.py +85 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/models.py +84 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/normalizer.py +144 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/parser.py +241 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/query.py +109 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/reminder.py +121 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/repository.py +397 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/schema.py +106 -0
- package/skills/builtin/sophnet-customer-management/src/customer_mgmt_core/service.py +565 -0
- package/skills/builtin/sophnet-customer-management/uv.lock +48 -0
- package/skills/builtin/sophnet-customized-marketing/SKILL.md +144 -0
- package/skills/builtin/sophnet-customized-marketing/playbooks/campaign-planning.md +187 -0
- package/skills/builtin/sophnet-customized-marketing/playbooks/content-generation.md +124 -0
- package/skills/builtin/sophnet-customized-marketing/playbooks/marketing-calendar.md +59 -0
- package/skills/builtin/sophnet-customized-marketing/playbooks/multi-channel-bundle.md +94 -0
- package/skills/builtin/sophnet-customized-marketing/playbooks/poster-generation.md +182 -0
- package/skills/builtin/sophnet-customized-marketing/playbooks/style-profile-workflow.md +103 -0
- package/skills/builtin/sophnet-customized-marketing/pyproject.toml +9 -0
- package/skills/builtin/sophnet-customized-marketing/references/campaign-mechanics.md +168 -0
- package/skills/builtin/sophnet-customized-marketing/references/content-safety.md +26 -0
- package/skills/builtin/sophnet-customized-marketing/references/marketing-date-checklist.md +99 -0
- package/skills/builtin/sophnet-customized-marketing/references/platform-writing-guidelines.md +88 -0
- package/skills/builtin/sophnet-customized-marketing/references/quality-checklist.md +44 -0
- package/skills/builtin/sophnet-customized-marketing/scripts/generate_poster.py +585 -0
- package/skills/builtin/sophnet-customized-marketing/scripts/style_profile.py +215 -0
- package/skills/builtin/sophnet-face-search/SKILL.md +115 -0
- package/skills/builtin/sophnet-face-search/pyproject.toml +11 -0
- package/skills/builtin/sophnet-face-search/scripts/face_search.py +336 -0
- package/skills/builtin/sophnet-face-search/uv.lock +508 -0
- package/skills/builtin/sophnet-image-edit/SKILL.md +140 -0
- package/skills/builtin/sophnet-image-edit/pyproject.toml +9 -0
- package/skills/builtin/sophnet-image-edit/scripts/edit_and_preview.sh +68 -0
- package/skills/builtin/sophnet-image-edit/scripts/edit_image.py +279 -0
- package/skills/builtin/sophnet-image-edit/uv.lock +234 -0
- package/skills/builtin/sophnet-image-generate/SKILL.md +62 -0
- package/skills/builtin/sophnet-image-generate/pyproject.toml +9 -0
- package/skills/builtin/sophnet-image-generate/scripts/generate_image.py +156 -0
- package/skills/builtin/sophnet-image-generate/uv.lock +234 -0
- package/skills/builtin/sophnet-image-ocr/SKILL.md +167 -0
- package/skills/builtin/sophnet-image-ocr/pyproject.toml +13 -0
- package/skills/builtin/sophnet-image-ocr/scripts/ocr.py +226 -0
- package/skills/builtin/sophnet-image-ocr/uv.lock +234 -0
- package/skills/builtin/sophnet-infinite-talk/SKILL.md +140 -0
- package/skills/builtin/sophnet-infinite-talk/pyproject.toml +9 -0
- package/skills/builtin/sophnet-infinite-talk/scripts/gen.py +172 -0
- package/skills/builtin/sophnet-oss/SKILL.md +109 -0
- package/skills/builtin/sophnet-oss/pyproject.toml +8 -0
- package/skills/builtin/sophnet-oss/scripts/upload_file.py +43 -0
- package/skills/builtin/sophnet-qa-install/SKILL.md +210 -0
- package/skills/builtin/sophnet-qa-install/pyproject.toml +6 -0
- package/skills/builtin/sophnet-qa-install/scripts/backup_md.py +35 -0
- package/skills/builtin/sophnet-qa-install/scripts/check_installed.py +143 -0
- package/skills/builtin/sophnet-qa-install/scripts/update_config.py +142 -0
- package/skills/builtin/sophnet-qa-install/scripts/update_md.py +73 -0
- package/skills/builtin/sophnet-training-install/SKILL.md +211 -0
- package/skills/builtin/sophnet-training-install/pyproject.toml +6 -0
- package/skills/builtin/sophnet-training-install/scripts/backup_md.py +35 -0
- package/skills/builtin/sophnet-training-install/scripts/check_installed.py +144 -0
- package/skills/builtin/sophnet-training-install/scripts/update_config.py +142 -0
- package/skills/builtin/sophnet-training-install/scripts/update_md.py +73 -0
- package/skills/builtin/sophnet-tts/SKILL.md +79 -0
- package/skills/builtin/sophnet-tts/pyproject.toml +9 -0
- package/skills/builtin/sophnet-tts/scripts/gen_tts.py +130 -0
- package/skills/builtin/sophnet-video-generate/SKILL.md +116 -0
- package/skills/builtin/sophnet-video-generate/scripts/gen_video.py +304 -0
- package/skills/builtin/video-understand/SKILL.md +79 -0
- package/skills/builtin/video-understand/scripts/video_understand.py +204 -0
- package/skills/builtin/weather/SKILL.md +112 -0
- package/skills/builtin/web-scraper/SKILL.md +101 -0
- package/skills/builtin/web-scraper/scripts/scrape.py +270 -0
- package/skills/builtin/website-builder/SKILL.md +266 -0
- package/skills/builtin/website-builder/scripts/deploy_site.sh +46 -0
- package/skills/store/didi-ride/SKILL.md +309 -0
- package/skills/store/didi-ride/_meta.json +6 -0
- package/skills/store/didi-ride/assets/PREFERENCE.md +58 -0
- package/skills/store/didi-ride/package.json +15 -0
- package/skills/store/didi-ride/references/api_references.md +171 -0
- package/skills/store/didi-ride/references/error_handling.md +68 -0
- package/skills/store/didi-ride/references/setup.md +73 -0
- package/skills/store/didi-ride/references/workflow.md +150 -0
- package/skills/store/flyai/SKILL.md +119 -0
- package/skills/store/flyai/references/fliggy-fast-search.md +53 -0
- package/skills/store/flyai/references/search-flight.md +89 -0
- package/skills/store/flyai/references/search-hotels.md +57 -0
- package/skills/store/flyai/references/search-poi.md +49 -0
- package/src/commands/download.js +103 -0
- package/src/commands/list.js +67 -0
- package/src/utils/config.js +24 -0
- package/src/utils/gitlab.js +67 -0
- package/src/utils/paths.js +19 -0
- package/src/utils/versions.js +38 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import readline from 'node:readline/promises';
|
|
5
|
+
import { findSkill } from '../utils/versions.js';
|
|
6
|
+
import { getSkillDir } from '../utils/paths.js';
|
|
7
|
+
import { loadConfig } from '../utils/config.js';
|
|
8
|
+
import { downloadFromGitlab } from '../utils/gitlab.js';
|
|
9
|
+
|
|
10
|
+
async function confirmOverwrite(destDir) {
|
|
11
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
12
|
+
try {
|
|
13
|
+
const answer = await rl.question(
|
|
14
|
+
chalk.yellow(` "${destDir}" already exists. Overwrite? [y/N] `),
|
|
15
|
+
);
|
|
16
|
+
return answer.trim().toLowerCase() === 'y';
|
|
17
|
+
} finally {
|
|
18
|
+
rl.close();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function downloadFromBuiltin(type, skillName, outputDir) {
|
|
23
|
+
const srcDir = getSkillDir(type, skillName);
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
await fs.access(srcDir);
|
|
27
|
+
} catch {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`Skill directory not found in package: ${srcDir}\n` +
|
|
30
|
+
'The bundled skills may be outdated. Try --source gitlab for the latest version.',
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const destDir = path.join(outputDir, skillName);
|
|
35
|
+
await fs.cp(srcDir, destDir, { recursive: true });
|
|
36
|
+
return destDir;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function registerDownloadCommand(program) {
|
|
40
|
+
program
|
|
41
|
+
.command('download')
|
|
42
|
+
.description('Download a skill to local directory')
|
|
43
|
+
.argument('<skill-name>', 'Name of the skill to download')
|
|
44
|
+
.option('-o, --output <dir>', 'Target directory (default: current directory)')
|
|
45
|
+
.option('-s, --source <source>', 'Data source: builtin (default) or gitlab')
|
|
46
|
+
.action(async (skillName, opts) => {
|
|
47
|
+
try {
|
|
48
|
+
const config = await loadConfig();
|
|
49
|
+
const source = opts.source || config.defaultSource || 'builtin';
|
|
50
|
+
const outputDir = path.resolve(opts.output || config.defaultOutput || '.');
|
|
51
|
+
|
|
52
|
+
if (source !== 'builtin' && source !== 'gitlab') {
|
|
53
|
+
console.error(chalk.red(`Invalid source "${source}". Use "builtin" or "gitlab".`));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const skill = await findSkill(skillName);
|
|
58
|
+
if (!skill) {
|
|
59
|
+
console.error(chalk.red(`Skill "${skillName}" not found.`));
|
|
60
|
+
console.error(chalk.gray('Run "sophhub list" to see all available skills.'));
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const destDir = path.join(outputDir, skillName);
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
await fs.access(destDir);
|
|
68
|
+
const ok = await confirmOverwrite(destDir);
|
|
69
|
+
if (!ok) {
|
|
70
|
+
console.log(chalk.gray('Cancelled.'));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
await fs.rm(destDir, { recursive: true, force: true });
|
|
74
|
+
} catch {
|
|
75
|
+
// dest does not exist, proceed
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
79
|
+
|
|
80
|
+
console.log();
|
|
81
|
+
console.log(chalk.gray(` Downloading ${skillName} from ${source}...`));
|
|
82
|
+
|
|
83
|
+
let result;
|
|
84
|
+
if (source === 'gitlab') {
|
|
85
|
+
const repoUrl = config.gitlabRepo;
|
|
86
|
+
result = await downloadFromGitlab(repoUrl, skill.type, skillName, outputDir);
|
|
87
|
+
} else {
|
|
88
|
+
result = await downloadFromBuiltin(skill.type, skillName, outputDir);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.log();
|
|
92
|
+
console.log(chalk.green(' ✓ Download complete'));
|
|
93
|
+
console.log(chalk.gray(` Skill: ${skill.name}`));
|
|
94
|
+
console.log(chalk.gray(` Version: ${skill.version}`));
|
|
95
|
+
console.log(chalk.gray(` Source: ${source}`));
|
|
96
|
+
console.log(chalk.gray(` Path: ${result}`));
|
|
97
|
+
console.log();
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.error(chalk.red(`\n Failed to download: ${err.message}\n`));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import Table from 'cli-table3';
|
|
3
|
+
import { parseVersions } from '../utils/versions.js';
|
|
4
|
+
|
|
5
|
+
export function registerListCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('list')
|
|
8
|
+
.description('List all available skills')
|
|
9
|
+
.option('-t, --type <type>', 'Filter by type: builtin or store')
|
|
10
|
+
.option('--json', 'Output as JSON')
|
|
11
|
+
.action(async (opts) => {
|
|
12
|
+
try {
|
|
13
|
+
let skills = await parseVersions();
|
|
14
|
+
|
|
15
|
+
if (opts.type) {
|
|
16
|
+
const t = opts.type.toLowerCase();
|
|
17
|
+
if (t !== 'builtin' && t !== 'store') {
|
|
18
|
+
console.error(chalk.red(`Invalid type "${opts.type}". Use "builtin" or "store".`));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
skills = skills.filter((s) => s.type === t);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (skills.length === 0) {
|
|
25
|
+
if (opts.json) {
|
|
26
|
+
console.log('[]');
|
|
27
|
+
} else {
|
|
28
|
+
console.log(chalk.yellow('No skills found.'));
|
|
29
|
+
}
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (opts.json) {
|
|
34
|
+
console.log(JSON.stringify(skills, null, 2));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const table = new Table({
|
|
39
|
+
head: [
|
|
40
|
+
chalk.cyan('Skill'),
|
|
41
|
+
chalk.cyan('Type'),
|
|
42
|
+
chalk.cyan('Version'),
|
|
43
|
+
chalk.cyan('Updated At'),
|
|
44
|
+
],
|
|
45
|
+
style: { head: [], border: [] },
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
for (const s of skills) {
|
|
49
|
+
table.push([
|
|
50
|
+
s.name,
|
|
51
|
+
s.type === 'builtin' ? chalk.blue(s.type) : chalk.green(s.type),
|
|
52
|
+
s.version,
|
|
53
|
+
s.updatedAt,
|
|
54
|
+
]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.log();
|
|
58
|
+
console.log(table.toString());
|
|
59
|
+
console.log();
|
|
60
|
+
console.log(chalk.gray(` Total: ${skills.length} skills`));
|
|
61
|
+
console.log();
|
|
62
|
+
} catch (err) {
|
|
63
|
+
console.error(chalk.red(`Failed to list skills: ${err.message}`));
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
|
|
5
|
+
const CONFIG_PATH = path.join(os.homedir(), '.sophhubrc.json');
|
|
6
|
+
|
|
7
|
+
const DEFAULTS = {
|
|
8
|
+
defaultOutput: '.',
|
|
9
|
+
defaultSource: 'builtin',
|
|
10
|
+
gitlabRepo: 'git@gitlab.sophgo.vip:llm-open-platform/sophclaw-skills.git',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export async function loadConfig() {
|
|
14
|
+
try {
|
|
15
|
+
const raw = await fs.readFile(CONFIG_PATH, 'utf-8');
|
|
16
|
+
return { ...DEFAULTS, ...JSON.parse(raw) };
|
|
17
|
+
} catch {
|
|
18
|
+
return { ...DEFAULTS };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function saveConfig(config) {
|
|
23
|
+
await fs.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
24
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Download a skill directory from GitLab using `git archive --remote`.
|
|
11
|
+
* Falls back to sparse-checkout if archive is not supported by the server.
|
|
12
|
+
*/
|
|
13
|
+
export async function downloadFromGitlab(repoUrl, type, skillName, outputDir) {
|
|
14
|
+
const destDir = path.join(outputDir, skillName);
|
|
15
|
+
const subPath = `${type}/${skillName}`;
|
|
16
|
+
|
|
17
|
+
// Try git archive first (most efficient, single-command)
|
|
18
|
+
try {
|
|
19
|
+
await fs.mkdir(destDir, { recursive: true });
|
|
20
|
+
const { stdout } = await execFileAsync(
|
|
21
|
+
'git',
|
|
22
|
+
['archive', '--remote', repoUrl, 'HEAD', subPath],
|
|
23
|
+
{ maxBuffer: 50 * 1024 * 1024 },
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
// pipe tar output through extraction
|
|
27
|
+
const tar = execFileAsync('tar', ['-x', '-C', outputDir], {
|
|
28
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
29
|
+
});
|
|
30
|
+
tar.child.stdin.write(stdout);
|
|
31
|
+
tar.child.stdin.end();
|
|
32
|
+
await tar;
|
|
33
|
+
|
|
34
|
+
// git archive extracts to {type}/{skillName}/, move to {outputDir}/{skillName}/
|
|
35
|
+
const extracted = path.join(outputDir, type, skillName);
|
|
36
|
+
if (extracted !== destDir) {
|
|
37
|
+
await fs.cp(extracted, destDir, { recursive: true });
|
|
38
|
+
await fs.rm(path.join(outputDir, type), { recursive: true, force: true });
|
|
39
|
+
}
|
|
40
|
+
return destDir;
|
|
41
|
+
} catch {
|
|
42
|
+
// archive --remote not supported, fall back to sparse checkout
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return downloadViaSparseCheckout(repoUrl, subPath, destDir);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function downloadViaSparseCheckout(repoUrl, subPath, destDir) {
|
|
49
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'sophhub-'));
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
await execFileAsync(
|
|
53
|
+
'git',
|
|
54
|
+
['clone', '--filter=blob:none', '--sparse', '--depth=1', repoUrl, tmpDir],
|
|
55
|
+
{ timeout: 120_000 },
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
await execFileAsync('git', ['sparse-checkout', 'set', subPath], { cwd: tmpDir });
|
|
59
|
+
|
|
60
|
+
const src = path.join(tmpDir, subPath);
|
|
61
|
+
await fs.cp(src, destDir, { recursive: true });
|
|
62
|
+
|
|
63
|
+
return destDir;
|
|
64
|
+
} finally {
|
|
65
|
+
await fs.rm(tmpDir, { recursive: true, force: true }).catch(() => {});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
5
|
+
const __dirname = path.dirname(__filename);
|
|
6
|
+
|
|
7
|
+
export const PACKAGE_ROOT = path.resolve(__dirname, '../..');
|
|
8
|
+
|
|
9
|
+
export function getSkillsDir() {
|
|
10
|
+
return path.join(PACKAGE_ROOT, 'skills');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function getVersionsPath() {
|
|
14
|
+
return path.join(getSkillsDir(), 'VERSIONS.md');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getSkillDir(type, skillName) {
|
|
18
|
+
return path.join(getSkillsDir(), type, skillName);
|
|
19
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import { getVersionsPath } from './paths.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Parse VERSIONS.md markdown table into an array of skill objects.
|
|
6
|
+
* Expected format:
|
|
7
|
+
* | Skill | Type | Version | Updated At |
|
|
8
|
+
* | --- | --- | --- | --- |
|
|
9
|
+
* | weather | builtin | 1.0.0 | 2026-04-08 |
|
|
10
|
+
*/
|
|
11
|
+
export async function parseVersions() {
|
|
12
|
+
const content = await fs.readFile(getVersionsPath(), 'utf-8');
|
|
13
|
+
const lines = content.split('\n').filter((l) => l.trim().startsWith('|'));
|
|
14
|
+
|
|
15
|
+
// skip header row and separator row (first two | lines)
|
|
16
|
+
const dataLines = lines.slice(2);
|
|
17
|
+
|
|
18
|
+
return dataLines
|
|
19
|
+
.map((line) => {
|
|
20
|
+
const cells = line
|
|
21
|
+
.split('|')
|
|
22
|
+
.map((c) => c.trim())
|
|
23
|
+
.filter(Boolean);
|
|
24
|
+
if (cells.length < 4) return null;
|
|
25
|
+
return {
|
|
26
|
+
name: cells[0],
|
|
27
|
+
type: cells[1],
|
|
28
|
+
version: cells[2],
|
|
29
|
+
updatedAt: cells[3],
|
|
30
|
+
};
|
|
31
|
+
})
|
|
32
|
+
.filter(Boolean);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function findSkill(skillName) {
|
|
36
|
+
const skills = await parseVersions();
|
|
37
|
+
return skills.find((s) => s.name === skillName) || null;
|
|
38
|
+
}
|