homeskill 1.0.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/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # HomeSkill CLI
2
+
3
+ > The Skill to manage Skills šŸš€
4
+
5
+ **HomeSkill** is the official package manager for Claude Skills (MCP). It allows you to search, install, manage, and share skills for your AI agents directly from your terminal.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/homeskill.svg)](https://www.npmjs.com/package/homeskill)
8
+ [![HomeSkill](https://img.shields.io/badge/Website-homeskill.org-7038F1)](https://homeskill.org)
9
+
10
+ ## ✨ Features
11
+
12
+ - **Store**: Access hundreds of verified skills from the HomeSkill registry.
13
+ - **Easy Install**: `homeskill install <name>` - no more manual zip handling.
14
+ - **Management**: List and update your skills effortlessly.
15
+ - **Publish**: Share your own skills with the community via `homeskill upload`.
16
+ - **Secure**: All skills are reviewed and verified.
17
+
18
+ ## šŸ“¦ Installation
19
+
20
+ ```bash
21
+ npm install -g homeskill
22
+ ```
23
+
24
+ ## šŸš€ Usage
25
+
26
+ ### Search Skills
27
+ Find the perfect tool for your agent:
28
+ ```bash
29
+ homeskill search "pdf"
30
+ homeskill search "web scraper"
31
+ ```
32
+
33
+ ### Install a Skill
34
+ Download and configure a skill in seconds:
35
+ ```bash
36
+ homeskill install "google-search"
37
+ ```
38
+
39
+ ### List Installed Skills
40
+ See what you have installed locally:
41
+ ```bash
42
+ homeskill list
43
+ ```
44
+
45
+ ### Publish Your Skill
46
+ Created a new MCP server? Share it with the world!
47
+ 1. Go to your skill directory
48
+ 2. Run:
49
+ ```bash
50
+ homeskill login
51
+ homeskill upload my-skill-name
52
+ ```
53
+
54
+ ## 🌐 Links
55
+
56
+ - **Website**: [homeskill.org](https://homeskill.org)
57
+ - **Documentation**: [docs.homeskill.org](https://homeskill.org/docs)
58
+ - **GitHub**: [github.com/Start-Z-Labs/homeskill-web](https://github.com/Start-Z-Labs/homeskill-web)
59
+
60
+ ## šŸ“„ License
61
+
62
+ MIT Ā© [HomeSkill Team](https://homeskill.org)
package/dist/index.js ADDED
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const open_1 = __importDefault(require("open"));
10
+ const uuid_1 = require("uuid");
11
+ const api_1 = require("./lib/api");
12
+ const manager_1 = require("./lib/manager");
13
+ const program = new commander_1.Command();
14
+ program
15
+ .name('homeskill')
16
+ .description('The Skill to manage Skills')
17
+ .version('1.0.0')
18
+ .hook('preAction', () => {
19
+ console.log(chalk_1.default.bold.hex('#7038F1')('šŸš€ HomeSkill CLI') + chalk_1.default.gray(' - The Skill to manage Skills (https://homeskill.org)\n'));
20
+ });
21
+ // --- Auth Commands ---
22
+ program
23
+ .command('login')
24
+ .description('Authenticate with HomeSkill')
25
+ .action(async () => {
26
+ const requestId = (0, uuid_1.v4)();
27
+ const webBase = process.env.HOMESKILL_WEB_BASE || "https://homeskill.org";
28
+ const authUrl = `${webBase}/#/auth/cli?request_id=${requestId}`;
29
+ console.log(chalk_1.default.blue(`šŸ”Œ Connecting to HomeSkill login...`));
30
+ console.log(`\nšŸ”‘ Please authenticate in your browser:`);
31
+ console.log(chalk_1.default.underline(authUrl));
32
+ console.log(`\n(Press Ctrl+C to cancel)`);
33
+ try {
34
+ await (0, open_1.default)(authUrl);
35
+ }
36
+ catch (e) {
37
+ console.log(chalk_1.default.yellow("Could not open browser automatically. Please copy the link above."));
38
+ }
39
+ console.log(`\nWaiting for confirmation...`);
40
+ // Poll loop
41
+ const start = Date.now();
42
+ const TIMEOUT = 300 * 1000; // 5 min
43
+ const sleep = (ms) => new Promise(r => setTimeout(r, ms));
44
+ while (Date.now() - start < TIMEOUT) {
45
+ try {
46
+ const res = await api_1.client.pollAuth(requestId);
47
+ if (res && res.status === 'approved') {
48
+ await api_1.client.saveCredentials(res.access_token, res.refresh_token, res.user_id);
49
+ console.log(chalk_1.default.green(`\nāœ… Login Successful!`));
50
+ return;
51
+ }
52
+ else if (res && res.status === 'expired') {
53
+ console.log(chalk_1.default.red(`\nāŒ Login session expired.`));
54
+ process.exit(1);
55
+ }
56
+ }
57
+ catch (e) {
58
+ // 410 Gone, etc
59
+ if (e.response && e.response.status === 410) {
60
+ console.log(chalk_1.default.red(`\nāŒ Login session expired.`));
61
+ process.exit(1);
62
+ }
63
+ }
64
+ process.stdout.write(chalk_1.default.gray("."));
65
+ await sleep(2000);
66
+ }
67
+ console.log(chalk_1.default.red(`\nāŒ Login timed out.`));
68
+ process.exit(1);
69
+ });
70
+ // --- Skill Commands ---
71
+ program
72
+ .command('search <query>')
73
+ .description('Search for skills')
74
+ .option('-t, --tag <tag>', 'Filter by tag')
75
+ .option('-l, --limit <number>', 'Limit results', '10')
76
+ .action(async (query, options) => {
77
+ try {
78
+ console.log(chalk_1.default.blue(`Searching for skills matching: ${query}...`));
79
+ const skills = await api_1.client.searchSkills(query, options.tag, parseInt(options.limit));
80
+ console.log(chalk_1.default.green(`Found ${skills.length} skills:`));
81
+ skills.forEach(skill => {
82
+ console.log(chalk_1.default.bold(`\n${skill.name}`) + ` (v${skill.latest_version})` + chalk_1.default.gray(` by ${skill.author}`));
83
+ console.log(` ${skill.short_description}`);
84
+ const rating = skill.avg_rating ? `⭐ ${skill.avg_rating.toFixed(1)}` : 'No ratings';
85
+ console.log(chalk_1.default.yellow(` ${rating} | šŸ“„ ${skill.installs_count || 0} installs`));
86
+ console.log(chalk_1.default.gray(` ID: ${skill.id}`));
87
+ });
88
+ }
89
+ catch (e) {
90
+ console.error(chalk_1.default.red(`Search failed: ${e.message}`));
91
+ }
92
+ });
93
+ program
94
+ .command('install <skillId>')
95
+ .description('Install a skill')
96
+ .option('-f, --force', 'Overwrite existing skill')
97
+ .option('-n, --name <name>', 'Rename skill locally')
98
+ .action(async (skillId, options) => {
99
+ try {
100
+ console.log(chalk_1.default.blue(`Installing skill: ${skillId}...`));
101
+ const skill = await manager_1.manager.installSkill(skillId, options.name, options.force);
102
+ console.log(chalk_1.default.green(`\nSuccessfully installed ${skill.name} v${skill.version}!`));
103
+ console.log(`Location: ${skill.path}`);
104
+ }
105
+ catch (e) {
106
+ console.error(chalk_1.default.red(`Installation failed: ${e.message}`));
107
+ }
108
+ });
109
+ program
110
+ .command('list')
111
+ .description('List installed skills')
112
+ .action(async () => {
113
+ try {
114
+ const skills = await manager_1.manager.listLocalSkills();
115
+ console.log(chalk_1.default.blue(`Found ${skills.length} installed skills:`));
116
+ skills.forEach(skill => {
117
+ console.log(chalk_1.default.bold(`\n${skill.name}`) + ` (v${skill.version})`);
118
+ console.log(` ${skill.description}`);
119
+ console.log(chalk_1.default.gray(` Path: ${skill.path}`));
120
+ });
121
+ }
122
+ catch (e) {
123
+ console.error(chalk_1.default.red(`List failed: ${e.message}`));
124
+ }
125
+ });
126
+ program
127
+ .command('upload <skillName>')
128
+ .description('Upload a local skill to HomeSkill')
129
+ .option('--user <user>', 'User identifier (email/username)')
130
+ .action(async (skillName, options) => {
131
+ try {
132
+ console.log(chalk_1.default.blue(`Packaging skill: ${skillName}...`));
133
+ const { buffer, info } = await manager_1.manager.packageSkill(skillName);
134
+ console.log(` Name: ${info.name}`);
135
+ console.log(` Version: ${info.version}`);
136
+ console.log(` Size: ${(buffer.length / 1024).toFixed(2)} KB`);
137
+ const user = options.user || process.env.HOMESKILL_USER_ID || "unknown";
138
+ console.log(chalk_1.default.blue(`Uploading as ${user}...`));
139
+ const res = await api_1.client.uploadSkill(buffer, user, `${skillName}.zip`);
140
+ if (res.pr_url) {
141
+ console.log(chalk_1.default.green(`\nāœ… Upload successful! GitHub PR created:`));
142
+ console.log(chalk_1.default.bold(res.pr_url));
143
+ console.log(`\nReview the PR to have your skill published.`);
144
+ }
145
+ else {
146
+ console.log(chalk_1.default.green(`\nāœ… Upload successful!`));
147
+ console.log(res);
148
+ }
149
+ }
150
+ catch (e) {
151
+ console.error(chalk_1.default.red(`Upload failed: ${e.message || e}`));
152
+ if (e.response) {
153
+ console.error(chalk_1.default.red(`Response: ${JSON.stringify(e.response.data)}`));
154
+ }
155
+ }
156
+ });
157
+ program.parse(process.argv);
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.client = exports.HomeSkillClient = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const os_1 = __importDefault(require("os"));
11
+ const form_data_1 = __importDefault(require("form-data"));
12
+ const DEFAULT_API_BASE = "https://homeskill-backend-42697595806.us-central1.run.app";
13
+ const DEFAULT_SUPABASE_URL = "https://kfaursfbsvyqtmhyrgqm.supabase.co";
14
+ const DEFAULT_SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtmYXVyc2Zic3Z5cXRtaHlyZ3FtIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjQ3NTM1OTcsImV4cCI6MjA4MDMyOTU5N30.qfpjo3rrXlXmx1hcFYBWCoYjc_WzKahUEfJNpgB6eBs";
15
+ class HomeSkillClient {
16
+ apiBase;
17
+ apiToken = null;
18
+ refreshToken = null;
19
+ credPath;
20
+ constructor(apiBase) {
21
+ this.apiBase = (apiBase || process.env.HOMESKILL_API_BASE || DEFAULT_API_BASE).replace(/\/$/, "");
22
+ this.credPath = path_1.default.join(os_1.default.homedir(), ".homeskill", "credentials");
23
+ this.loadCredentials();
24
+ // Override with env var if present
25
+ if (process.env.HOMESKILL_API_TOKEN) {
26
+ this.apiToken = process.env.HOMESKILL_API_TOKEN;
27
+ }
28
+ }
29
+ loadCredentials() {
30
+ try {
31
+ if (fs_extra_1.default.existsSync(this.credPath)) {
32
+ const data = fs_extra_1.default.readJsonSync(this.credPath);
33
+ this.apiToken = data.access_token;
34
+ this.refreshToken = data.refresh_token;
35
+ }
36
+ }
37
+ catch (e) {
38
+ // Ignore
39
+ }
40
+ }
41
+ async saveCredentials(accessToken, refreshToken, userId) {
42
+ try {
43
+ await fs_extra_1.default.ensureDir(path_1.default.dirname(this.credPath));
44
+ const payload = {
45
+ access_token: accessToken,
46
+ refresh_token: refreshToken || this.refreshToken,
47
+ user_id: userId,
48
+ updated_at: Date.now() / 1000
49
+ };
50
+ await fs_extra_1.default.writeJson(this.credPath, payload, { spaces: 2 });
51
+ await fs_extra_1.default.chmod(this.credPath, 0o600);
52
+ this.apiToken = accessToken;
53
+ if (refreshToken)
54
+ this.refreshToken = refreshToken;
55
+ }
56
+ catch (e) {
57
+ // Ignore
58
+ }
59
+ }
60
+ async refreshAccessToken() {
61
+ if (!this.refreshToken)
62
+ return false;
63
+ const supabaseUrl = process.env.SUPABASE_URL || process.env.VITE_SUPABASE_URL || DEFAULT_SUPABASE_URL;
64
+ const supabaseAnon = process.env.SUPABASE_ANON_KEY || process.env.VITE_SUPABASE_ANON_KEY || DEFAULT_SUPABASE_ANON_KEY;
65
+ try {
66
+ const resp = await axios_1.default.post(`${supabaseUrl}/auth/v1/token?grant_type=refresh_token`, {
67
+ refresh_token: this.refreshToken
68
+ }, {
69
+ headers: {
70
+ apikey: supabaseAnon,
71
+ Authorization: `Bearer ${supabaseAnon}`,
72
+ 'Content-Type': 'application/json'
73
+ }
74
+ });
75
+ if (resp.data && resp.data.access_token) {
76
+ await this.saveCredentials(resp.data.access_token, resp.data.refresh_token, resp.data.user?.id);
77
+ return true;
78
+ }
79
+ }
80
+ catch (e) {
81
+ return false;
82
+ }
83
+ return false;
84
+ }
85
+ async request(method, endpoint, data, params, headers = {}, retry = true) {
86
+ const url = `${this.apiBase}${endpoint}`;
87
+ const reqHeaders = {
88
+ ...headers,
89
+ 'Accept': 'application/json',
90
+ 'User-Agent': 'HomeSkill-CLI/1.0',
91
+ };
92
+ if (this.apiToken && !reqHeaders['Authorization']) {
93
+ reqHeaders['Authorization'] = `Bearer ${this.apiToken}`;
94
+ }
95
+ try {
96
+ const response = await (0, axios_1.default)({
97
+ method,
98
+ url,
99
+ data,
100
+ params,
101
+ headers: reqHeaders,
102
+ responseType: endpoint.includes('download') ? 'arraybuffer' : 'json'
103
+ });
104
+ return response.data;
105
+ }
106
+ catch (error) {
107
+ if (retry && error.response && error.response.status === 401) {
108
+ const refreshed = await this.refreshAccessToken();
109
+ if (refreshed) {
110
+ // Retry with new token
111
+ return this.request(method, endpoint, data, params, headers, false);
112
+ }
113
+ }
114
+ throw error;
115
+ }
116
+ }
117
+ async searchSkills(query, tag, limit = 50) {
118
+ const params = { query, limit };
119
+ if (tag)
120
+ params.tag = tag;
121
+ const res = await this.request('GET', '/api/skills', null, params);
122
+ // Handle different response structures based on Python client experiences
123
+ let skillsData = [];
124
+ if (Array.isArray(res)) {
125
+ skillsData = res;
126
+ }
127
+ else {
128
+ skillsData = res.skills || res.data || res.results || [];
129
+ }
130
+ return skillsData.map((s) => this.mapSkillInfo(s));
131
+ }
132
+ async getSkillInfo(skillId) {
133
+ const res = await this.request('GET', `/api/skills/${encodeURIComponent(skillId)}`);
134
+ return this.mapSkillInfo(res);
135
+ }
136
+ async downloadSkill(skillId) {
137
+ const res = await this.request('POST', `/api/skills/${encodeURIComponent(skillId)}/download`);
138
+ const downloadUrl = res.download_url;
139
+ if (!downloadUrl)
140
+ throw new Error("No download URL returned");
141
+ const dlRes = await axios_1.default.get(downloadUrl, { responseType: 'arraybuffer' });
142
+ return Buffer.from(dlRes.data);
143
+ }
144
+ async uploadSkill(zipData, userIdentifier, filename = "skill.zip") {
145
+ const form = new form_data_1.default();
146
+ form.append('user_identifier', userIdentifier);
147
+ form.append('zip_file', zipData, { filename, contentType: 'application/zip' });
148
+ // form-data headers need to be passed correctly
149
+ const headers = form.getHeaders();
150
+ return this.request('POST', '/api/skills/upload', form, null, headers);
151
+ }
152
+ async pollAuth(requestId) {
153
+ // Polls the auth endpoint
154
+ // Python client logic: POST /api/auth/cli/poll { request_id }
155
+ // Returns 200 { status: 'approved', access_token... }
156
+ // or 410 if expired
157
+ // or other code if pending
158
+ // NOTE: This call might 404/410/etc, so we handle it outside or rely on axios throw
159
+ return this.request('POST', '/api/auth/cli/poll', { request_id: requestId });
160
+ }
161
+ mapSkillInfo(data) {
162
+ return {
163
+ id: data.id || data._id || "",
164
+ name: data.name || "",
165
+ short_description: data.short_description || data.long_description || "",
166
+ latest_version: data.latest_version || data.version || "0.0.0",
167
+ author: data.author || "unknown",
168
+ last_updated: data.last_updated || data.updated_at || data.createdAt || "",
169
+ download_url: data.download_url,
170
+ avg_rating: data.avg_rating,
171
+ installs_count: data.installs_count,
172
+ tags: data.tags
173
+ };
174
+ }
175
+ }
176
+ exports.HomeSkillClient = HomeSkillClient;
177
+ exports.client = new HomeSkillClient();
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.manager = exports.SkillManager = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const os_1 = __importDefault(require("os"));
10
+ const adm_zip_1 = __importDefault(require("adm-zip"));
11
+ const api_js_1 = require("./api.js");
12
+ const SKILLS_DIR_NAME = 'skills';
13
+ const METADATA_FILE = 'metadata.json';
14
+ class SkillManager {
15
+ skillsDir;
16
+ constructor() {
17
+ this.skillsDir = path_1.default.join(os_1.default.homedir(), '.claude', SKILLS_DIR_NAME);
18
+ }
19
+ ensureSkillsDir() {
20
+ fs_extra_1.default.ensureDirSync(this.skillsDir);
21
+ }
22
+ async listLocalSkills() {
23
+ if (!fs_extra_1.default.existsSync(this.skillsDir))
24
+ return [];
25
+ const skills = [];
26
+ const items = await fs_extra_1.default.readdir(this.skillsDir);
27
+ for (const item of items) {
28
+ if (item.startsWith('.'))
29
+ continue;
30
+ const itemPath = path_1.default.join(this.skillsDir, item);
31
+ const stat = await fs_extra_1.default.stat(itemPath);
32
+ if (stat.isDirectory()) {
33
+ const info = await this.readSkillInfo(itemPath);
34
+ if (info) {
35
+ skills.push(info);
36
+ }
37
+ else {
38
+ skills.push({
39
+ name: item,
40
+ path: itemPath,
41
+ version: 'unknown',
42
+ description: '(No metadata)',
43
+ author: 'unknown'
44
+ });
45
+ }
46
+ }
47
+ }
48
+ return skills;
49
+ }
50
+ async readSkillInfo(skillPath) {
51
+ // 1. Try metadata.json
52
+ const metadataPath = path_1.default.join(skillPath, METADATA_FILE);
53
+ if (fs_extra_1.default.existsSync(metadataPath)) {
54
+ try {
55
+ const data = await fs_extra_1.default.readJson(metadataPath);
56
+ return {
57
+ name: data.name || path_1.default.basename(skillPath),
58
+ path: skillPath,
59
+ version: data.version || '0.0.0',
60
+ description: data.description || '',
61
+ author: data.author || 'unknown',
62
+ skill_id: data.skill_id
63
+ };
64
+ }
65
+ catch (e) { }
66
+ }
67
+ // 2. Try skill.md frontmatter
68
+ const skillMdPath = path_1.default.join(skillPath, 'skill.md');
69
+ if (fs_extra_1.default.existsSync(skillMdPath)) {
70
+ // Basic frontmatter parsing
71
+ const content = await fs_extra_1.default.readFile(skillMdPath, 'utf-8');
72
+ if (content.startsWith('---')) {
73
+ const parts = content.split('---');
74
+ if (parts.length >= 3) {
75
+ const frontmatter = parts[1];
76
+ const data = {};
77
+ frontmatter.split('\n').forEach(line => {
78
+ const [key, ...vals] = line.split(':');
79
+ if (key && vals.length > 0) {
80
+ data[key.trim()] = vals.join(':').trim().replace(/['"]/g, '');
81
+ }
82
+ });
83
+ if (data.name) {
84
+ return {
85
+ name: data.name,
86
+ path: skillPath,
87
+ version: data.version || '0.0.0',
88
+ description: data.description || '',
89
+ author: data.author || 'unknown',
90
+ skill_id: data.skill_id
91
+ };
92
+ }
93
+ }
94
+ }
95
+ }
96
+ return null;
97
+ }
98
+ getSkillPath(skillName) {
99
+ return path_1.default.join(this.skillsDir, skillName);
100
+ }
101
+ async installSkill(skillId, name, force = false) {
102
+ const info = await api_js_1.client.getSkillInfo(skillId);
103
+ const targetName = name || info.name;
104
+ const targetPath = path_1.default.join(this.skillsDir, targetName);
105
+ if (fs_extra_1.default.existsSync(targetPath)) {
106
+ if (!force) {
107
+ throw new Error(`Skill '${targetName}' already exists. Use -f to overwrite.`);
108
+ }
109
+ // Backup
110
+ const backupPath = path_1.default.join(path_1.default.dirname(targetPath), `${targetName}-backup-${Date.now()}`);
111
+ await fs_extra_1.default.move(targetPath, backupPath);
112
+ }
113
+ // Download
114
+ const zipBuffer = await api_js_1.client.downloadSkill(skillId);
115
+ // Extract
116
+ const zip = new adm_zip_1.default(zipBuffer);
117
+ const tmpDir = path_1.default.join(os_1.default.tmpdir(), `homeskill-${Date.now()}`);
118
+ zip.extractAllTo(tmpDir, true);
119
+ // Identify nested folder
120
+ const files = await fs_extra_1.default.readdir(tmpDir);
121
+ let sourceDir = tmpDir;
122
+ // Smart detection: if single folder, assume it's the wrapper
123
+ if (files.length === 1) {
124
+ const possibleDir = path_1.default.join(tmpDir, files[0]);
125
+ if ((await fs_extra_1.default.stat(possibleDir)).isDirectory()) {
126
+ sourceDir = possibleDir;
127
+ }
128
+ }
129
+ // Move to target
130
+ await fs_extra_1.default.move(sourceDir, targetPath, { overwrite: true });
131
+ // Write metadata
132
+ const metadata = {
133
+ name: info.name,
134
+ version: info.latest_version,
135
+ description: info.short_description,
136
+ author: info.author,
137
+ skill_id: info.id,
138
+ installed_at: new Date().toISOString(),
139
+ source: 'homeskill'
140
+ };
141
+ await fs_extra_1.default.writeJson(path_1.default.join(targetPath, METADATA_FILE), metadata, { spaces: 2 });
142
+ // Cleanup
143
+ if (sourceDir !== tmpDir) {
144
+ await fs_extra_1.default.remove(tmpDir).catch(() => { });
145
+ }
146
+ return {
147
+ ...metadata,
148
+ path: targetPath
149
+ };
150
+ }
151
+ async packageSkill(skillName) {
152
+ const skillPath = this.getSkillPath(skillName);
153
+ if (!fs_extra_1.default.existsSync(skillPath)) {
154
+ throw new Error(`Skill '${skillName}' not found`);
155
+ }
156
+ const info = await this.readSkillInfo(skillPath);
157
+ if (!info) {
158
+ throw new Error(`Skill metadata not found in '${skillName}'`);
159
+ }
160
+ // Create zip
161
+ const zip = new adm_zip_1.default();
162
+ // Add local folder recursively, filtering out hidden files
163
+ const addFolder = (dir, zipPath) => {
164
+ const files = fs_extra_1.default.readdirSync(dir);
165
+ for (const file of files) {
166
+ if (file.startsWith('.') || file === '__pycache__' || file === 'node_modules')
167
+ continue;
168
+ const fullPath = path_1.default.join(dir, file);
169
+ const stat = fs_extra_1.default.statSync(fullPath);
170
+ if (stat.isDirectory()) {
171
+ addFolder(fullPath, path_1.default.join(zipPath, file));
172
+ }
173
+ else {
174
+ zip.addLocalFile(fullPath, zipPath);
175
+ }
176
+ }
177
+ };
178
+ addFolder(skillPath, "");
179
+ return {
180
+ buffer: zip.toBuffer(),
181
+ info
182
+ };
183
+ }
184
+ }
185
+ exports.SkillManager = SkillManager;
186
+ exports.manager = new SkillManager();
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "homeskill",
3
+ "version": "1.0.0",
4
+ "description": "The Skill to manage Skills. Search, Install, and Manage Claude Skills with ease.",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "homeskill": "dist/index.js"
8
+ },
9
+ "type": "commonjs",
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "prepublishOnly": "npm run build",
13
+ "start": "node dist/index.js",
14
+ "dev": "ts-node src/index.js"
15
+ },
16
+ "keywords": [
17
+ "homeskill",
18
+ "mcp",
19
+ "claude",
20
+ "level",
21
+ "anthropic",
22
+ "agent",
23
+ "skills",
24
+ "model context protocol"
25
+ ],
26
+ "author": "HomeSkill Team <support@homeskill.org> (https://homeskill.org)",
27
+ "license": "MIT",
28
+ "homepage": "https://homeskill.org",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/Start-Z-Labs/homeskill-web.git"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/Start-Z-Labs/homeskill-web/issues"
35
+ },
36
+ "engines": {
37
+ "node": ">=18.0.0"
38
+ },
39
+ "dependencies": {
40
+ "@types/uuid": "^11.0.0",
41
+ "adm-zip": "^0.5.10",
42
+ "axios": "^1.6.0",
43
+ "chalk": "^4.1.2",
44
+ "commander": "^12.0.0",
45
+ "form-data": "^4.0.5",
46
+ "fs-extra": "^11.2.0",
47
+ "open": "^11.0.0",
48
+ "uuid": "^13.0.0"
49
+ },
50
+ "devDependencies": {
51
+ "@types/adm-zip": "^0.5.5",
52
+ "@types/fs-extra": "^11.0.0",
53
+ "@types/node": "^20.11.0",
54
+ "ts-node": "^10.9.2",
55
+ "typescript": "^5.3.3"
56
+ }
57
+ }