skills-refiner 1.0.3

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.
@@ -0,0 +1,367 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import fs from 'fs/promises';
5
+ import path from 'path';
6
+ import osLocale from 'os-locale';
7
+ import nodeMachineId from 'node-machine-id';
8
+ const { machineId } = nodeMachineId;
9
+ import Conf from 'conf';
10
+ import chalk from 'chalk';
11
+ import ora from 'ora';
12
+ import open from 'open';
13
+ import Table from 'cli-table3';
14
+ import { languages } from '../languages.js';
15
+ import axios from 'axios';
16
+ import { select, password } from '@inquirer/prompts';
17
+ import AdmZip from 'adm-zip';
18
+ import http from 'http';
19
+
20
+ const program = new Command();
21
+ const config = new Conf({ projectName: 'skills-refiner' });
22
+
23
+ const API_BASE = process.env.API_URL || 'https://skills-refiner.com/api/v1';
24
+
25
+ async function getLanguage() {
26
+ return config.get('default_lang') || await detectOSLocale();
27
+ }
28
+
29
+ async function detectOSLocale() {
30
+ try {
31
+ const locale = await osLocale();
32
+ return locale.split('-')[0] || 'en';
33
+ } catch {
34
+ return 'en';
35
+ }
36
+ }
37
+
38
+ async function getDeviceId() {
39
+ let id = config.get('device_id');
40
+ if (!id) {
41
+ try {
42
+ id = await machineId();
43
+ } catch {
44
+ const crypto = await import('crypto');
45
+ id = crypto.randomUUID();
46
+ }
47
+ config.set('device_id', id);
48
+ }
49
+ return id;
50
+ }
51
+
52
+ async function handleServerAction(data) {
53
+ if (!data.action) return false;
54
+
55
+ const action = data.action;
56
+ const message = data.message || data.error || 'Action required';
57
+
58
+ switch (action.type) {
59
+ case 'OPEN_LOGIN':
60
+ console.log(chalk.yellow(`\n✖ ${message}`));
61
+ if (action.url) {
62
+ await handleAuthFlow(action.url);
63
+ }
64
+ return true;
65
+ case 'RECHARGE':
66
+ console.log(chalk.yellow(`\n✖ ${message}`));
67
+ if (action.url) {
68
+ console.log(chalk.cyan(`Opening recharge page: ${action.url}`));
69
+ await open(action.url);
70
+ }
71
+ process.exit(1);
72
+ case 'REQUIRE_UPDATE':
73
+ console.log(chalk.red(`\n✖ ${message}`));
74
+ console.log(chalk.yellow(`Please update your CLI tool.`));
75
+ if (action.url) {
76
+ console.log(chalk.dim(`More info: ${action.url}`));
77
+ }
78
+ console.log(chalk.white(`Run: npm install -g skills-refiner@latest`));
79
+ process.exit(1);
80
+ case 'CHECK_UPDATE':
81
+ console.log(chalk.cyan(`\nℹ Update available: ${message}`));
82
+ console.log(chalk.yellow(`Run: ${action.update_command || 'npm install -g skills-refiner@latest'}`));
83
+ process.exit(1);
84
+ default:
85
+ return false;
86
+ }
87
+ }
88
+
89
+ async function handleAuthFlow(loginUrl) {
90
+ // Start temporary local server to receive API Key
91
+ return new Promise((resolve) => {
92
+ const server = http.createServer((req, res) => {
93
+ // Handle CORS for browser
94
+ res.setHeader('Access-Control-Allow-Origin', '*');
95
+ res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
96
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
97
+
98
+ if (req.method === 'OPTIONS') {
99
+ res.writeHead(204);
100
+ res.end();
101
+ return;
102
+ }
103
+
104
+ if (req.method === 'POST' && req.url === '/callback') {
105
+ let body = '';
106
+ req.on('data', chunk => { body += chunk; });
107
+ req.on('end', () => {
108
+ try {
109
+ const data = JSON.parse(body);
110
+ if (data.apiKey) {
111
+ config.set('api_key', data.apiKey);
112
+ console.log(chalk.green(`\n✔ API Key saved successfully!`));
113
+ console.log(chalk.cyan(`Please re-run your command to continue with your new points.`));
114
+ res.writeHead(200, { 'Content-Type': 'application/json' });
115
+ res.end(JSON.stringify({ success: true }));
116
+
117
+ setTimeout(() => {
118
+ server.close();
119
+ process.exit(0);
120
+ }, 1000);
121
+ }
122
+ } catch (e) {
123
+ res.writeHead(400);
124
+ res.end();
125
+ }
126
+ });
127
+ } else {
128
+ res.writeHead(404);
129
+ res.end();
130
+ }
131
+ });
132
+
133
+ const port = 3105; // Fixed port for easier browser callback or find free one
134
+ server.listen(port, async () => {
135
+ const authUrlWithCallback = `${loginUrl}&callback=http://localhost:${port}/callback`;
136
+ console.log(chalk.blue(`\nLogin URL: ${authUrlWithCallback}`));
137
+ console.log(chalk.yellow('Waiting for browser authentication...'));
138
+ await open(authUrlWithCallback);
139
+ });
140
+
141
+ server.on('error', (err) => {
142
+ console.log(chalk.red(`\nLocal server error: ${err.message}`));
143
+ console.log(chalk.yellow(`Fallback: After logging in, copy your API Key and run: skills-refiner config`));
144
+ resolve(false);
145
+ });
146
+
147
+ // Timeout if user takes too long
148
+ setTimeout(() => {
149
+ if (server.listening) {
150
+ server.close();
151
+ resolve(false);
152
+ }
153
+ }, 300000); // 5 mins
154
+ });
155
+ }
156
+
157
+
158
+
159
+ async function refineContent(content, lang, slugForDisplay = 'local', version = '1.0.0') {
160
+ const spinner = ora(chalk.cyan(`Refining ${slugForDisplay} into [${lang}]...`)).start();
161
+ const deviceId = await getDeviceId();
162
+ const apiKey = config.get('api_key');
163
+
164
+ try {
165
+ const response = await axios.post(`${API_BASE}/cli/refine`, {
166
+ content,
167
+ target_lang: lang,
168
+ client_type: 'cli',
169
+ client_version: version
170
+ }, {
171
+ headers: {
172
+ 'X-Device-ID': deviceId,
173
+ 'Authorization': apiKey ? `Bearer ${apiKey}` : undefined
174
+ }
175
+ });
176
+
177
+ spinner.succeed(chalk.green(`Refined successfully!`));
178
+ return response.data.refined_content;
179
+ } catch (err) {
180
+ spinner.stop();
181
+ if (err.response && err.response.data) {
182
+ const handled = await handleServerAction(err.response.data);
183
+ if (handled) return null;
184
+ console.log(chalk.red(`\nAPI Error: ${err.response.data.error || 'Unknown error'}`));
185
+ } else {
186
+ console.log(chalk.red(`\nNetwork Error: ${err.message}`));
187
+ }
188
+ process.exit(1);
189
+ }
190
+ }
191
+
192
+ async function downloadAndExtractSkill(slug, targetDir, refinedContent = null) {
193
+ const spinner = ora(chalk.cyan(`Downloading full skill bundle [${slug}]...`)).start();
194
+ try {
195
+ const response = await axios.post(`${API_BASE}/cli/skills/${slug}/download`, {
196
+ refined_content: refinedContent
197
+ }, {
198
+ responseType: 'arraybuffer',
199
+ timeout: 60000 // 60s timeout
200
+ });
201
+
202
+ const zip = new AdmZip(Buffer.from(response.data));
203
+ zip.extractAllTo(targetDir, true);
204
+ spinner.succeed(chalk.green(`Skill [${slug}] downloaded and extracted to ${targetDir}`));
205
+ } catch (err) {
206
+ spinner.fail(chalk.red(`Failed to download skill bundle: ${err.message}`));
207
+ process.exit(1);
208
+ }
209
+ }
210
+
211
+ (async () => {
212
+ try {
213
+ // We can't always rely on package.json being relative if installed globally in a weird way,
214
+ // but standard node CLI structure usually keeps them together.
215
+ const pkgPath = new URL('../package.json', import.meta.url);
216
+ const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
217
+
218
+ // Alias -ls to --ls
219
+ process.argv = process.argv.map(arg => arg === '-ls' ? '--ls' : arg);
220
+
221
+ program
222
+ .name('skills-refiner')
223
+ .version(pkg.version)
224
+ .description('Refine your SKILL.md for AI Agents')
225
+ .argument('[skillName]', 'Optional: Name of the skill to download (e.g., OpenAI/doc)')
226
+ .option('-l, --lang <lang>', 'Target language (e.g., zh-CN)')
227
+ .option('--ls, --list', 'Show supported languages')
228
+ .action(async (skillName, options) => {
229
+ // --- 1. Handle --list / -ls ---
230
+ if (options.list) {
231
+ const table = new Table({ head: [chalk.cyan('Language'), chalk.cyan('Code')] });
232
+ languages.forEach(l => table.push([l.name, l.code]));
233
+ console.log(table.toString());
234
+ process.exit(0);
235
+ }
236
+
237
+ // --- 2. Determine Target Language ---
238
+ // For download (<skillName>), we ONLY refine if -l is explicitly provided (per latest user request).
239
+ // For local (no <skillName>), we use -l or config or auto-detect.
240
+ let targetLang = options.lang;
241
+ if (!skillName && !targetLang) {
242
+ targetLang = config.get('default_lang');
243
+ }
244
+
245
+ let content;
246
+
247
+ // --- 3. Scenario A: Download specific skill ---
248
+ if (skillName) {
249
+ // Fetch metadata first to ensure it exists
250
+ const skillMeta = await axios.get(`${API_BASE}/cli/skills/${skillName}`);
251
+ if (!skillMeta.data.success) {
252
+ console.log(chalk.red(`Skill not found: ${skillName}`));
253
+ process.exit(1);
254
+ }
255
+ content = skillMeta.data.data.content;
256
+ console.log(chalk.green(`Skill [${skillName}] found.`));
257
+ } else {
258
+ // --- 4. Scenario B: Local File Refinement ---
259
+ const localPath = path.resolve(process.cwd(), 'SKILL.md');
260
+ try {
261
+ content = await fs.readFile(localPath, 'utf-8');
262
+ console.log(chalk.blue(`Found local SKILL.md.`));
263
+ } catch {
264
+ console.log(chalk.yellow(`SKILL.md not found in current directory.`));
265
+ console.log(chalk.dim(`Usage:`));
266
+ console.log(chalk.dim(` Download: skills-refiner <skill-name> (e.g. OpenAI/doc)`));
267
+ console.log(chalk.dim(` Refine: skills-refiner -l <lang> (Refine local SKILL.md)`));
268
+ process.exit(1);
269
+ }
270
+ }
271
+
272
+ // --- 5. Validate Language and Refine ---
273
+ let finalContent = content;
274
+
275
+ if (targetLang) {
276
+ const isValid = languages.some(l => l.code === targetLang || l.code.split('-')[0] === targetLang);
277
+ if (!isValid) {
278
+ console.log(chalk.red(`Invalid language code: ${targetLang}`));
279
+ console.log(chalk.yellow(`Use --ls to see available languages.`));
280
+ process.exit(1);
281
+ }
282
+
283
+ const refined = await refineContent(content, targetLang, skillName || 'local', pkg.version);
284
+ if (refined) finalContent = refined;
285
+ } else if (!skillName) {
286
+ // Local file, but no lang and no config. Auto-detect OS.
287
+ targetLang = await detectOSLocale();
288
+ console.log(chalk.dim(`No language specified. Defaulting to system locale: ${targetLang}`));
289
+ const refined = await refineContent(content, targetLang, 'local', pkg.version);
290
+ if (refined) finalContent = refined;
291
+ } else {
292
+ // Downloading raw
293
+ console.log(chalk.cyan(`Saving raw skill content...`));
294
+ }
295
+
296
+ // --- 6. Final Execution & Download ---
297
+ if (skillName) {
298
+ const targetDir = path.resolve(process.cwd(), skillName);
299
+ await downloadAndExtractSkill(skillName, targetDir, targetLang ? finalContent : null);
300
+
301
+ if (!targetLang) {
302
+ console.log(chalk.cyan(`\nTip: To refine/translate this skill, run:`));
303
+ console.log(chalk.white(` skills-refiner -l zh-CN`));
304
+ }
305
+ } else {
306
+ // Local refinement save logic
307
+ const savePath = path.resolve(process.cwd(), 'SKILL.md');
308
+ const backupPath = path.resolve(process.cwd(), 'SKILL_BAK.md');
309
+
310
+ try {
311
+ await fs.access(savePath);
312
+ await fs.copyFile(savePath, backupPath);
313
+ console.log(chalk.dim(`Backed up existing SKILL.md to SKILL_BAK.md`));
314
+ } catch { }
315
+
316
+ await fs.writeFile(savePath, finalContent);
317
+ console.log(chalk.green(`\n✔ Refinement applied to SKILL.md`));
318
+ }
319
+ });
320
+
321
+ program
322
+ .command('config')
323
+ .description('Configure default settings')
324
+ .action(async () => {
325
+ const choice = await select({
326
+ message: 'What would you like to configure?',
327
+ choices: [
328
+ { name: 'Default Language', value: 'lang' },
329
+ { name: 'API Key', value: 'key' },
330
+ { name: 'Exit', value: 'exit' }
331
+ ]
332
+ });
333
+
334
+ if (choice === 'lang') {
335
+ const lang = await select({
336
+ message: 'Select default target language:',
337
+ choices: languages.map(l => ({ name: `${l.name} (${l.code})`, value: l.code })),
338
+ default: config.get('default_lang') || 'zh-CN'
339
+ });
340
+ config.set('default_lang', lang);
341
+ console.log(chalk.green(`Default language set to: ${lang}`));
342
+ } else if (choice === 'key') {
343
+ const key = await password({
344
+ message: 'Enter your API Key:',
345
+ mask: '*',
346
+ });
347
+ if (key) {
348
+ config.set('api_key', key);
349
+ console.log(chalk.green('API Key updated!'));
350
+ }
351
+ }
352
+ });
353
+
354
+ program
355
+ .command('logout')
356
+ .description('Remove API Key and login state')
357
+ .action(() => {
358
+ config.delete('api_key');
359
+ console.log(chalk.green('Logged out successfully.'));
360
+ });
361
+
362
+ program.parse();
363
+ } catch (err) {
364
+ console.error(chalk.red(`Fatal Error: ${err.message}`));
365
+ process.exit(1);
366
+ }
367
+ })();
package/languages.js ADDED
@@ -0,0 +1,53 @@
1
+ export const languages = [
2
+ { name: "English", code: "en-US" },
3
+ { name: "中文 (简体)", code: "zh-CN" },
4
+ { name: "中文 (繁體)", code: "zh-TW" },
5
+ { name: "हिन्दी (Hindi)", code: "hi-IN" },
6
+ { name: "Español", code: "es-ES" },
7
+ { name: "Français", code: "fr-FR" },
8
+ { name: "العربية (Arabic)", code: "ar-SA" },
9
+ { name: "বাংলা (Bengali)", code: "bn-BD" },
10
+ { name: "Português", code: "pt-BR" },
11
+ { name: "Русский (Russian)", code: "ru-RU" },
12
+ { name: "اردو (Urdu)", code: "ur-PK" },
13
+ { name: "Bahasa Indonesia", code: "id-ID" },
14
+ { name: "Deutsch", code: "de-DE" },
15
+ { name: "日本語 (Japanese)", code: "ja-JP" },
16
+ { name: "Nigerian Pidgin", code: "pcm-NG" },
17
+ { name: "मराठी (Marathi)", code: "mr-IN" },
18
+ { name: "తెలుగు (Telugu)", code: "te-IN" },
19
+ { name: "Türkçe", code: "tr-TR" },
20
+ { name: "தமிழ் (Tamil)", code: "ta-IN" },
21
+ { name: "廣東話 (Cantonese)", code: "yue-HK" },
22
+ { name: "Tiếng Việt", code: "vi-VN" },
23
+ { name: "Tagalog", code: "tl-PH" },
24
+ { name: "吴语 (Wu Chinese)", code: "wuu-CN" },
25
+ { name: "한국어 (Korean)", code: "ko-KR" },
26
+ { name: "فارسی (Persian)", code: "fa-IR" },
27
+ { name: "Hausa", code: "ha-NG" },
28
+ { name: "Kiswahili", code: "sw-KE" },
29
+ { name: "Basa Jawa (Javanese)", code: "jv-ID" },
30
+ { name: "Italiano", code: "it-IT" },
31
+ { name: "ਪੰਜਾਬੀ (Punjabi)", code: "pa-PK" },
32
+ { name: "ಕನ್ನಡ (Kannada)", code: "kn-IN" },
33
+ { name: "ગુજરાતી (Gujarati)", code: "gu-IN" },
34
+ { name: "ไทย (Thai)", code: "th-TH" },
35
+ { name: "አማርኛ (Amharic)", code: "am-ET" },
36
+ { name: "भोजपुरी (Bhojpuri)", code: "bho-IN" },
37
+ { name: "閩南語 (Hokkien)", code: "nan-TW" },
38
+ { name: "晋语 (Jin Chinese)", code: "cjy-CN" },
39
+ { name: "Yorùbá", code: "yo-NG" },
40
+ { name: "客家话 (Hakka)", code: "hak-CN" },
41
+ { name: "မြန်မာစာ (Burmese)", code: "my-MM" },
42
+ { name: "ଓଡ଼ିଆ (Odia)", code: "or-IN" },
43
+ { name: "Afaan Oromoo", code: "om-ET" },
44
+ { name: "پښتو (Pashto)", code: "ps-AF" },
45
+ { name: "Kurdî (Kurdish)", code: "ku-TR" },
46
+ { name: "Asụsụ Igbo", code: "ig-NG" },
47
+ { name: "മലയാളം (Malayalam)", code: "ml-IN" },
48
+ { name: "الدارجة (Algerian Arabic)", code: "ar-DZ" },
49
+ { name: "Azərbaycan dili", code: "az-AZ" },
50
+ { name: "Polski", code: "pl-PL" },
51
+ { name: "Oʻzbekcha", code: "uz-UZ" },
52
+ { name: "سنڌي (Sindhi)", code: "sd-PK" }
53
+ ];
@@ -0,0 +1 @@
1
+ {"description": "Refine your SKILL.md for AI Agents", "lang": "Target language (e.g., zh-CN, en)", "overwrite": "Overwrite original file with backup (Safe Mode)", "help": "display help for command", "config": "Configure default settings"}
@@ -0,0 +1 @@
1
+ {"description": "Refinar su SKILL.md para agentes de IA", "lang": "Idioma de destino (ej. es, en)", "overwrite": "Sobrescribir archivo original (Modo Seguro)", "help": "mostrar ayuda para el comando", "config": "Configurar ajustes predeterminados"}
@@ -0,0 +1 @@
1
+ {"description": "AI एजेंटों के लिए अपने SKILL.md को सुधारें", "lang": "लक्ष्य भाषा (जैसे, hi, en)", "overwrite": "मूल फ़ाइल को बैकअप के साथ अधिलेखित करें (सुरक्षित मोड)", "help": "कमांड के लिए मदद दिखाएं", "config": "डिफ़ॉल्ट सेटिंग्स कॉन्फ़िगर करें"}
@@ -0,0 +1 @@
1
+ {"description": "AIエージェント用にSKILL.mdを最適化", "lang": "対象言語 (例: ja, en)", "overwrite": "元のファイルを上書き (バックアップ付き安全モード)", "help": "ヘルプを表示", "config": "デフォルト設定を構成"}
@@ -0,0 +1 @@
1
+ {"description": "优化 SKILL.md 文档以供 AI Agent 使用", "lang": "目标语言 (例如: zh-CN, en)", "overwrite": "覆盖原文件 (安全模式,带备份)", "help": "显示命令帮助", "config": "配置默认设置"}
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "skills-refiner",
3
+ "version": "1.0.3",
4
+ "type": "module",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "skills-refiner": "bin/skills-refiner.js"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "keywords": [],
13
+ "author": "Skill Refiner Team",
14
+ "license": "ISC",
15
+ "description": "A geeky CLI tool to refine and translate SKILL.md for AI Agents.",
16
+ "dependencies": {
17
+ "@inquirer/prompts": "^8.2.0",
18
+ "adm-zip": "^0.5.16",
19
+ "axios": "^1.13.5",
20
+ "chalk": "^5.6.2",
21
+ "cli-table3": "^0.6.5",
22
+ "commander": "^14.0.3",
23
+ "conf": "^15.1.0",
24
+ "node-machine-id": "^1.1.12",
25
+ "open": "^11.0.0",
26
+ "ora": "^9.3.0",
27
+ "os-locale": "^8.0.0"
28
+ }
29
+ }