cli-ai-skills 1.6.0 → 1.7.2
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 +12 -4
- package/bin/cli.js +99 -29
- package/lib/cleanup.js +92 -0
- package/lib/commands/uninstall.js +26 -19
- package/lib/commands/update.js +57 -2
- package/lib/detector.js +79 -17
- package/lib/gemini.js +73 -0
- package/lib/interactive.js +67 -3
- package/lib/opencode.js +73 -0
- package/lib/ui/table.js +58 -0
- package/lib/version-checker.js +130 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
# cli-ai-skills v1.
|
|
1
|
+
# cli-ai-skills v1.7.1
|
|
2
2
|
|
|
3
3
|
🚀 **NPX Installer for AI Skills**
|
|
4
4
|
|
|
5
|
-
Install reusable skills for GitHub Copilot CLI, Claude Code,
|
|
5
|
+
Install reusable skills for GitHub Copilot CLI, Claude Code, OpenAI Codex, OpenCode, and Gemini CLI in one command.
|
|
6
6
|
|
|
7
|
-

|
|
8
8
|

|
|
9
9
|

|
|
10
10
|
|
|
11
11
|
## 🚀 Quick Start
|
|
12
12
|
|
|
13
|
+
**Shell installer (recommended):**
|
|
14
|
+
```bash
|
|
15
|
+
curl -fsSL https://raw.githubusercontent.com/ericandrade/cli-ai-skills/main/scripts/install.sh | bash
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Or use NPX:**
|
|
13
19
|
```bash
|
|
14
20
|
# Zero-config installation (interactive)
|
|
15
21
|
npx cli-ai-skills
|
|
@@ -21,9 +27,11 @@ npx cli-ai-skills -y -q
|
|
|
21
27
|
npx cli-ai-skills --bundle essential -y
|
|
22
28
|
```
|
|
23
29
|
|
|
30
|
+
See [Installation Guide](../docs/INSTALLATION.md) for all methods.
|
|
31
|
+
|
|
24
32
|
## 📦 What It Does
|
|
25
33
|
|
|
26
|
-
1. 🔍 **Detects** installed AI CLI tools (Copilot, Claude, Codex)
|
|
34
|
+
1. 🔍 **Detects** installed AI CLI tools (Copilot, Claude, Codex, OpenCode, Gemini)
|
|
27
35
|
2. 📋 **Lists** all 4 available AI skills
|
|
28
36
|
3. ⚙️ **Installs** skills to the correct platform directories
|
|
29
37
|
4. ✅ **Validates** installation success
|
package/bin/cli.js
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const { detectTools, getInstallInstructions } = require('../lib/detector');
|
|
4
|
-
const { promptPlatforms } = require('../lib/interactive');
|
|
4
|
+
const { promptPlatforms, setupEscapeHandler } = require('../lib/interactive');
|
|
5
|
+
const { setupCleanupHandler } = require('../lib/cleanup');
|
|
5
6
|
const { installCopilotSkills } = require('../lib/copilot');
|
|
6
7
|
const { installClaudeSkills } = require('../lib/claude');
|
|
7
8
|
const { install: installCodexSkills } = require('../lib/codex');
|
|
9
|
+
const { install: installOpenCodeSkills } = require('../lib/opencode');
|
|
10
|
+
const { install: installGeminiSkills } = require('../lib/gemini');
|
|
8
11
|
const { listBundles, validateBundle } = require('../lib/bundles');
|
|
9
12
|
const { searchSkills } = require('../lib/search');
|
|
13
|
+
const { displayToolsTable } = require('../lib/ui/table');
|
|
14
|
+
const { checkInstalledVersion, isUpdateAvailable } = require('../lib/version-checker');
|
|
15
|
+
const chalk = require('chalk');
|
|
16
|
+
const inquirer = require('inquirer');
|
|
10
17
|
const path = require('path');
|
|
11
18
|
|
|
12
|
-
const VERSION = '1.
|
|
19
|
+
const VERSION = '1.6.0';
|
|
13
20
|
|
|
14
21
|
// Command aliases
|
|
15
22
|
const commandAliases = {
|
|
@@ -32,7 +39,11 @@ const shortFlags = {
|
|
|
32
39
|
async function main() {
|
|
33
40
|
const args = process.argv.slice(2);
|
|
34
41
|
|
|
35
|
-
|
|
42
|
+
// Setup handlers
|
|
43
|
+
setupEscapeHandler();
|
|
44
|
+
setupCleanupHandler();
|
|
45
|
+
|
|
46
|
+
console.log(chalk.cyan.bold(`\n🚀 cli-ai-skills v${VERSION} - Multi-Platform Installer\n`));
|
|
36
47
|
|
|
37
48
|
// Handle help
|
|
38
49
|
if (args.includes('--help') || args.includes('-h')) {
|
|
@@ -74,31 +85,32 @@ async function main() {
|
|
|
74
85
|
const bundleName = args[bundleIdx + 1];
|
|
75
86
|
const bundle = validateBundle(bundleName);
|
|
76
87
|
|
|
77
|
-
console.log(
|
|
88
|
+
console.log(chalk.cyan('🔍 Detectando ferramentas AI CLI instaladas...\n'));
|
|
78
89
|
const detected = detectTools();
|
|
79
|
-
|
|
90
|
+
|
|
91
|
+
// Display tools table
|
|
92
|
+
displayToolsTable(detected);
|
|
93
|
+
|
|
94
|
+
const hasAny = detected.copilot.installed || detected.claude.installed ||
|
|
95
|
+
detected.codex.installed || detected.opencode.installed ||
|
|
96
|
+
detected.gemini.installed;
|
|
80
97
|
|
|
81
98
|
if (!hasAny) {
|
|
82
99
|
console.log(getInstallInstructions());
|
|
83
100
|
process.exit(1);
|
|
84
101
|
}
|
|
85
102
|
|
|
86
|
-
// Show detected tools
|
|
87
|
-
console.log('Ferramentas detectadas:');
|
|
88
|
-
if (detected.copilot) console.log(' ✅ GitHub Copilot CLI');
|
|
89
|
-
if (detected.claude) console.log(' ✅ Claude Code');
|
|
90
|
-
if (detected.codex) console.log(' ✅ OpenAI Codex');
|
|
91
|
-
console.log('');
|
|
92
|
-
|
|
93
103
|
// Check for --yes flag (skip prompts)
|
|
94
104
|
const skipPrompt = args.includes('-y') || args.includes('--yes');
|
|
95
105
|
|
|
96
106
|
let platforms;
|
|
97
107
|
if (skipPrompt) {
|
|
98
108
|
platforms = [];
|
|
99
|
-
if (detected.copilot) platforms.push('copilot');
|
|
100
|
-
if (detected.claude) platforms.push('claude');
|
|
101
|
-
if (detected.codex) platforms.push('codex');
|
|
109
|
+
if (detected.copilot.installed) platforms.push('copilot');
|
|
110
|
+
if (detected.claude.installed) platforms.push('claude');
|
|
111
|
+
if (detected.codex.installed) platforms.push('codex');
|
|
112
|
+
if (detected.opencode.installed) platforms.push('opencode');
|
|
113
|
+
if (detected.gemini.installed) platforms.push('gemini');
|
|
102
114
|
} else {
|
|
103
115
|
platforms = await promptPlatforms(detected);
|
|
104
116
|
}
|
|
@@ -127,6 +139,12 @@ async function main() {
|
|
|
127
139
|
if (platforms.includes('codex')) {
|
|
128
140
|
installCodexSkills(repoPath, [skill], quiet);
|
|
129
141
|
}
|
|
142
|
+
if (platforms.includes('opencode')) {
|
|
143
|
+
installOpenCodeSkills(repoPath, [skill], quiet);
|
|
144
|
+
}
|
|
145
|
+
if (platforms.includes('gemini')) {
|
|
146
|
+
installGeminiSkills(repoPath, [skill], quiet);
|
|
147
|
+
}
|
|
130
148
|
});
|
|
131
149
|
|
|
132
150
|
if (!quiet) {
|
|
@@ -137,22 +155,64 @@ async function main() {
|
|
|
137
155
|
|
|
138
156
|
// Zero-config mode (no arguments)
|
|
139
157
|
if (args.length === 0 || command === 'install') {
|
|
140
|
-
console.log('🔍 Detectando ferramentas AI CLI instaladas...\n');
|
|
158
|
+
console.log(chalk.cyan('🔍 Detectando ferramentas AI CLI instaladas...\n'));
|
|
141
159
|
|
|
142
160
|
const detected = detectTools();
|
|
143
|
-
|
|
161
|
+
|
|
162
|
+
// Display tools table
|
|
163
|
+
displayToolsTable(detected);
|
|
164
|
+
|
|
165
|
+
const hasAny = detected.copilot.installed || detected.claude.installed ||
|
|
166
|
+
detected.codex.installed || detected.opencode.installed ||
|
|
167
|
+
detected.gemini.installed;
|
|
144
168
|
|
|
145
169
|
if (!hasAny) {
|
|
146
170
|
console.log(getInstallInstructions());
|
|
147
171
|
process.exit(1);
|
|
148
172
|
}
|
|
149
173
|
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (
|
|
154
|
-
|
|
155
|
-
|
|
174
|
+
// Check if already installed
|
|
175
|
+
const installInfo = checkInstalledVersion();
|
|
176
|
+
|
|
177
|
+
if (installInfo.installed) {
|
|
178
|
+
console.log(chalk.cyan(`\nℹ️ cli-ai-skills já instalado nas seguintes plataformas:\n`));
|
|
179
|
+
|
|
180
|
+
for (const platform of installInfo.platforms) {
|
|
181
|
+
const version = installInfo.versions[platform];
|
|
182
|
+
console.log(chalk.dim(` • ${platform}: v${version}`));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (isUpdateAvailable(installInfo)) {
|
|
186
|
+
console.log(chalk.yellow(`\n⚠️ Nova versão disponível: v${installInfo.latestVersion}\n`));
|
|
187
|
+
|
|
188
|
+
const { update } = await inquirer.prompt([{
|
|
189
|
+
type: 'confirm',
|
|
190
|
+
name: 'update',
|
|
191
|
+
message: 'Deseja atualizar agora?',
|
|
192
|
+
default: true
|
|
193
|
+
}]);
|
|
194
|
+
|
|
195
|
+
if (!update) {
|
|
196
|
+
console.log(chalk.dim('\n❌ Atualização cancelada.\n'));
|
|
197
|
+
process.exit(0);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
console.log(chalk.cyan('\n🔄 Atualizando skills...\n'));
|
|
201
|
+
} else {
|
|
202
|
+
console.log(chalk.green(`\n✅ Você já possui a versão mais recente (v${installInfo.latestVersion})\n`));
|
|
203
|
+
|
|
204
|
+
const { reinstall } = await inquirer.prompt([{
|
|
205
|
+
type: 'confirm',
|
|
206
|
+
name: 'reinstall',
|
|
207
|
+
message: 'Deseja reinstalar?',
|
|
208
|
+
default: false
|
|
209
|
+
}]);
|
|
210
|
+
|
|
211
|
+
if (!reinstall) {
|
|
212
|
+
process.exit(0);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
156
216
|
|
|
157
217
|
// Check for --yes flag (zero-config mode)
|
|
158
218
|
const skipPrompt = args.includes('-y') || args.includes('--yes');
|
|
@@ -161,20 +221,22 @@ async function main() {
|
|
|
161
221
|
if (skipPrompt) {
|
|
162
222
|
// Auto-select all detected platforms
|
|
163
223
|
platforms = [];
|
|
164
|
-
if (detected.copilot) platforms.push('copilot');
|
|
165
|
-
if (detected.claude) platforms.push('claude');
|
|
166
|
-
if (detected.codex) platforms.push('codex');
|
|
224
|
+
if (detected.copilot.installed) platforms.push('copilot');
|
|
225
|
+
if (detected.claude.installed) platforms.push('claude');
|
|
226
|
+
if (detected.codex.installed) platforms.push('codex');
|
|
227
|
+
if (detected.opencode.installed) platforms.push('opencode');
|
|
228
|
+
if (detected.gemini.installed) platforms.push('gemini');
|
|
167
229
|
} else {
|
|
168
230
|
// Interactive selection
|
|
169
231
|
platforms = await promptPlatforms(detected);
|
|
170
232
|
}
|
|
171
233
|
|
|
172
234
|
if (platforms.length === 0) {
|
|
173
|
-
console.log('\n❌ Instalação cancelada.\n');
|
|
235
|
+
console.log(chalk.red('\n❌ Instalação cancelada.\n'));
|
|
174
236
|
process.exit(0);
|
|
175
237
|
}
|
|
176
238
|
|
|
177
|
-
console.log(`\n📦 Instalando skills para: ${platforms.join(', ')}\n`);
|
|
239
|
+
console.log(chalk.cyan(`\n📦 Instalando skills para: ${platforms.join(', ')}\n`));
|
|
178
240
|
|
|
179
241
|
const repoPath = path.resolve(__dirname, '../..');
|
|
180
242
|
const quiet = args.includes('-q') || args.includes('--quiet');
|
|
@@ -192,8 +254,16 @@ async function main() {
|
|
|
192
254
|
installCodexSkills(repoPath, null, quiet);
|
|
193
255
|
}
|
|
194
256
|
|
|
257
|
+
if (platforms.includes('opencode')) {
|
|
258
|
+
installOpenCodeSkills(repoPath, null, quiet);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (platforms.includes('gemini')) {
|
|
262
|
+
installGeminiSkills(repoPath, null, quiet);
|
|
263
|
+
}
|
|
264
|
+
|
|
195
265
|
if (!quiet) {
|
|
196
|
-
console.log(`\n✅ Instalação concluída com sucesso!\n`);
|
|
266
|
+
console.log(chalk.green(`\n✅ Instalação concluída com sucesso!\n`));
|
|
197
267
|
}
|
|
198
268
|
return;
|
|
199
269
|
}
|
package/lib/cleanup.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Track of partial installations for cleanup on cancellation
|
|
6
|
+
*/
|
|
7
|
+
const partialInstalls = [];
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Register a path as partially installed
|
|
11
|
+
* @param {string} installPath - Path to the installed skill
|
|
12
|
+
*/
|
|
13
|
+
function registerPartialInstall(installPath) {
|
|
14
|
+
if (!partialInstalls.includes(installPath)) {
|
|
15
|
+
partialInstalls.push(installPath);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Clear the partial installs registry
|
|
21
|
+
*/
|
|
22
|
+
function clearPartialInstalls() {
|
|
23
|
+
partialInstalls.length = 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Reverte instalações parciais em caso de cancelamento
|
|
28
|
+
* @returns {Promise<void>}
|
|
29
|
+
*/
|
|
30
|
+
async function revertPartialInstalls() {
|
|
31
|
+
if (partialInstalls.length === 0) return;
|
|
32
|
+
|
|
33
|
+
console.log(chalk.cyan('\n🧹 Limpando instalações parciais...\n'));
|
|
34
|
+
|
|
35
|
+
let removed = 0;
|
|
36
|
+
let failed = 0;
|
|
37
|
+
|
|
38
|
+
for (const installPath of partialInstalls) {
|
|
39
|
+
try {
|
|
40
|
+
if (await fs.pathExists(installPath)) {
|
|
41
|
+
// Check if it's a symlink
|
|
42
|
+
const stats = await fs.lstat(installPath);
|
|
43
|
+
|
|
44
|
+
if (stats.isSymbolicLink()) {
|
|
45
|
+
await fs.unlink(installPath);
|
|
46
|
+
} else {
|
|
47
|
+
await fs.remove(installPath);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(chalk.green(` ✓ Removido: ${installPath}`));
|
|
51
|
+
removed++;
|
|
52
|
+
}
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.log(chalk.red(` ✗ Erro ao remover ${installPath}: ${err.message}`));
|
|
55
|
+
failed++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (removed > 0) {
|
|
60
|
+
console.log(chalk.green(`\n✅ Cleanup concluído (${removed} removidos).\n`));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (failed > 0) {
|
|
64
|
+
console.log(chalk.yellow(`⚠️ ${failed} itens falharam ao remover.\n`));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
clearPartialInstalls();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Setup cleanup handler for process termination
|
|
72
|
+
*/
|
|
73
|
+
function setupCleanupHandler() {
|
|
74
|
+
// Handle Ctrl+C
|
|
75
|
+
process.on('SIGINT', async () => {
|
|
76
|
+
await revertPartialInstalls();
|
|
77
|
+
process.exit(0);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Handle termination
|
|
81
|
+
process.on('SIGTERM', async () => {
|
|
82
|
+
await revertPartialInstalls();
|
|
83
|
+
process.exit(0);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = {
|
|
88
|
+
registerPartialInstall,
|
|
89
|
+
clearPartialInstalls,
|
|
90
|
+
revertPartialInstalls,
|
|
91
|
+
setupCleanupHandler
|
|
92
|
+
};
|
|
@@ -27,8 +27,8 @@ async function uninstallCommand(skillName, options) {
|
|
|
27
27
|
// If specific skill name provided, only check that one
|
|
28
28
|
const skillsToCheck = skillName ? [skillName] : await getInstalledSkillNames();
|
|
29
29
|
|
|
30
|
-
for (const platform of ['copilot', 'claude']) {
|
|
31
|
-
if (!platforms[platform].installed) continue;
|
|
30
|
+
for (const platform of ['copilot', 'claude', 'opencode', 'gemini']) {
|
|
31
|
+
if (!platforms[platform] || !platforms[platform].installed) continue;
|
|
32
32
|
|
|
33
33
|
for (const skill of skillsToCheck) {
|
|
34
34
|
const status = await versionChecker.checkVersion(skill, platform, '999.999.999');
|
|
@@ -184,31 +184,38 @@ async function uninstallCommand(skillName, options) {
|
|
|
184
184
|
function getSkillPath(skillName, platform) {
|
|
185
185
|
const homeDir = os.homedir();
|
|
186
186
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
187
|
+
const platformPaths = {
|
|
188
|
+
copilot: path.join(homeDir, '.copilot', 'skills', skillName),
|
|
189
|
+
claude: path.join(homeDir, '.claude', 'skills', skillName),
|
|
190
|
+
opencode: path.join(homeDir, '.opencode', 'skills', skillName),
|
|
191
|
+
gemini: path.join(homeDir, '.gemini', 'skills', skillName)
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
if (!platformPaths[platform]) {
|
|
195
|
+
throw new Error(`Unknown platform: ${platform}`);
|
|
191
196
|
}
|
|
192
197
|
|
|
193
|
-
|
|
198
|
+
return platformPaths[platform];
|
|
194
199
|
}
|
|
195
200
|
|
|
196
201
|
async function getInstalledSkillNames() {
|
|
197
202
|
const homeDir = os.homedir();
|
|
198
203
|
const skills = new Set();
|
|
199
204
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
205
|
+
const platforms = ['copilot', 'claude', 'opencode', 'gemini'];
|
|
206
|
+
|
|
207
|
+
for (const platform of platforms) {
|
|
208
|
+
const skillsDir = path.join(homeDir, `.${platform}`, 'skills');
|
|
209
|
+
|
|
210
|
+
if (await fs.pathExists(skillsDir)) {
|
|
211
|
+
const platformSkills = await fs.readdir(skillsDir);
|
|
212
|
+
platformSkills.forEach(s => {
|
|
213
|
+
const fullPath = path.join(skillsDir, s);
|
|
214
|
+
if (fs.statSync(fullPath).isDirectory()) {
|
|
215
|
+
skills.add(s);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
}
|
|
212
219
|
}
|
|
213
220
|
|
|
214
221
|
return Array.from(skills);
|
package/lib/commands/update.js
CHANGED
|
@@ -31,8 +31,8 @@ async function updateCommand(skillNames, options) {
|
|
|
31
31
|
const availableSkills = await downloader.listAvailableSkills();
|
|
32
32
|
const outdatedSkills = [];
|
|
33
33
|
|
|
34
|
-
for (const platform of ['copilot', 'claude']) {
|
|
35
|
-
if (!platforms[platform].installed) continue;
|
|
34
|
+
for (const platform of ['copilot', 'claude', 'opencode', 'gemini']) {
|
|
35
|
+
if (!platforms[platform] || !platforms[platform].installed) continue;
|
|
36
36
|
|
|
37
37
|
for (const skillMeta of availableSkills) {
|
|
38
38
|
const status = await versionChecker.checkVersion(skillMeta.name, platform, skillMeta.version);
|
|
@@ -54,6 +54,21 @@ async function updateCommand(skillNames, options) {
|
|
|
54
54
|
if (outdatedSkills.length === 0) {
|
|
55
55
|
gauge.complete('All skills are up to date');
|
|
56
56
|
console.log(chalk.green('\n✨ All skills are already at the latest version!\n'));
|
|
57
|
+
|
|
58
|
+
// Offer reinstall option
|
|
59
|
+
if (!options.yes) {
|
|
60
|
+
const { reinstall } = await inquirer.prompt([{
|
|
61
|
+
type: 'confirm',
|
|
62
|
+
name: 'reinstall',
|
|
63
|
+
message: 'Would you like to reinstall all skills?',
|
|
64
|
+
default: false
|
|
65
|
+
}]);
|
|
66
|
+
|
|
67
|
+
if (reinstall) {
|
|
68
|
+
return await reinstallAllSkills(platforms, availableSkills, options);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
57
72
|
return;
|
|
58
73
|
}
|
|
59
74
|
|
|
@@ -140,3 +155,43 @@ async function updateCommand(skillNames, options) {
|
|
|
140
155
|
}
|
|
141
156
|
|
|
142
157
|
module.exports = updateCommand;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Reinstall all skills for all platforms
|
|
161
|
+
*/
|
|
162
|
+
async function reinstallAllSkills(platforms, availableSkills, options) {
|
|
163
|
+
console.log(chalk.cyan('\n🔄 Reinstalling all skills...\n'));
|
|
164
|
+
|
|
165
|
+
const installer = new SkillInstaller();
|
|
166
|
+
const results = { reinstalled: 0, failed: 0 };
|
|
167
|
+
|
|
168
|
+
for (const platform of ['copilot', 'claude', 'opencode', 'gemini']) {
|
|
169
|
+
if (!platforms[platform] || !platforms[platform].installed) continue;
|
|
170
|
+
|
|
171
|
+
for (const skill of availableSkills) {
|
|
172
|
+
try {
|
|
173
|
+
await installer.install(skill.name, platform, 'global', 'symlink');
|
|
174
|
+
results.reinstalled++;
|
|
175
|
+
console.log(chalk.green(` ✓ ${skill.name} (${platform})`));
|
|
176
|
+
} catch (error) {
|
|
177
|
+
results.failed++;
|
|
178
|
+
console.log(chalk.red(` ✗ ${skill.name} (${platform}): ${error.message}`));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Summary
|
|
184
|
+
console.log('\n' + ProgressGauge.summary({
|
|
185
|
+
completed: results.reinstalled,
|
|
186
|
+
failed: results.failed,
|
|
187
|
+
total: results.reinstalled + results.failed
|
|
188
|
+
}));
|
|
189
|
+
|
|
190
|
+
if (results.reinstalled > 0) {
|
|
191
|
+
console.log(chalk.green(`\n✨ Successfully reinstalled ${results.reinstalled} skill(s)!\n`));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (results.failed > 0) {
|
|
195
|
+
console.log(chalk.yellow(`⚠️ ${results.failed} skill(s) failed to reinstall.\n`));
|
|
196
|
+
}
|
|
197
|
+
}
|
package/lib/detector.js
CHANGED
|
@@ -2,40 +2,95 @@ const { execSync } = require('child_process');
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Detecta ferramentas AI CLI instaladas no sistema
|
|
5
|
-
* @returns {Object} { copilot:
|
|
5
|
+
* @returns {Object} { copilot: {installed, version, path}, claude: {...}, ... }
|
|
6
6
|
*/
|
|
7
7
|
function detectTools() {
|
|
8
8
|
const tools = {
|
|
9
|
-
copilot:
|
|
10
|
-
claude:
|
|
11
|
-
codex:
|
|
9
|
+
copilot: detectCopilot(),
|
|
10
|
+
claude: detectClaude(),
|
|
11
|
+
codex: detectCodex(),
|
|
12
|
+
opencode: detectOpenCode(),
|
|
13
|
+
gemini: detectGemini()
|
|
12
14
|
};
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
return tools;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Detecta GitHub Copilot CLI
|
|
21
|
+
*/
|
|
22
|
+
function detectCopilot() {
|
|
15
23
|
try {
|
|
16
|
-
execSync('gh copilot --version', { stdio: 'ignore' });
|
|
17
|
-
|
|
24
|
+
const version = execSync('gh copilot --version', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
25
|
+
const path = execSync('which gh', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
26
|
+
return { installed: true, version, path };
|
|
18
27
|
} catch (e) {
|
|
19
|
-
|
|
28
|
+
return { installed: false, version: null, path: null };
|
|
20
29
|
}
|
|
30
|
+
}
|
|
21
31
|
|
|
22
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Detecta Claude Code
|
|
34
|
+
*/
|
|
35
|
+
function detectClaude() {
|
|
23
36
|
try {
|
|
24
|
-
execSync('claude --version', { stdio: 'ignore' });
|
|
25
|
-
|
|
37
|
+
const version = execSync('claude --version', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
38
|
+
const path = execSync('which claude', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
39
|
+
return { installed: true, version, path };
|
|
26
40
|
} catch (e) {
|
|
27
|
-
|
|
41
|
+
return { installed: false, version: null, path: null };
|
|
28
42
|
}
|
|
43
|
+
}
|
|
29
44
|
|
|
30
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Detecta OpenAI Codex
|
|
47
|
+
*/
|
|
48
|
+
function detectCodex() {
|
|
31
49
|
try {
|
|
32
|
-
execSync('codex --version', { stdio: 'ignore' });
|
|
33
|
-
|
|
50
|
+
const version = execSync('codex --version', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
51
|
+
const path = execSync('which codex', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
52
|
+
return { installed: true, version, path };
|
|
34
53
|
} catch (e) {
|
|
35
|
-
|
|
54
|
+
return { installed: false, version: null, path: null };
|
|
36
55
|
}
|
|
56
|
+
}
|
|
37
57
|
|
|
38
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Detecta OpenCode
|
|
60
|
+
*/
|
|
61
|
+
function detectOpenCode() {
|
|
62
|
+
try {
|
|
63
|
+
const version = execSync('opencode --version', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
64
|
+
const path = execSync('which opencode', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
65
|
+
return { installed: true, version, path };
|
|
66
|
+
} catch (e) {
|
|
67
|
+
// Método alternativo: verificar via npm global
|
|
68
|
+
try {
|
|
69
|
+
execSync('npm list -g opencode', { stdio: 'ignore' });
|
|
70
|
+
return { installed: true, version: 'unknown', path: 'npm global' };
|
|
71
|
+
} catch {
|
|
72
|
+
return { installed: false, version: null, path: null };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Detecta Gemini CLI
|
|
79
|
+
*/
|
|
80
|
+
function detectGemini() {
|
|
81
|
+
try {
|
|
82
|
+
const version = execSync('gemini --version', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
83
|
+
const path = execSync('which gemini', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
84
|
+
return { installed: true, version, path };
|
|
85
|
+
} catch (e) {
|
|
86
|
+
// Método alternativo: verificar via npm global
|
|
87
|
+
try {
|
|
88
|
+
execSync('npm list -g gemini-cli', { stdio: 'ignore' });
|
|
89
|
+
return { installed: true, version: 'unknown', path: 'npm global' };
|
|
90
|
+
} catch {
|
|
91
|
+
return { installed: false, version: null, path: null };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
39
94
|
}
|
|
40
95
|
|
|
41
96
|
/**
|
|
@@ -58,8 +113,15 @@ Instale ao menos uma das seguintes ferramentas:
|
|
|
58
113
|
📦 OpenAI Codex:
|
|
59
114
|
npm install -g @openai/codex
|
|
60
115
|
|
|
116
|
+
📦 OpenCode:
|
|
117
|
+
npm install -g opencode
|
|
118
|
+
|
|
119
|
+
📦 Gemini CLI:
|
|
120
|
+
npm install -g @google/gemini-cli
|
|
121
|
+
|
|
61
122
|
Após instalar, execute novamente: npx cli-ai-skills
|
|
62
123
|
`;
|
|
63
124
|
}
|
|
64
125
|
|
|
65
126
|
module.exports = { detectTools, getInstallInstructions };
|
|
127
|
+
|
package/lib/gemini.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Install skills for Gemini CLI
|
|
8
|
+
* @param {string} repoPath - Path to the cli-ai-skills repository
|
|
9
|
+
* @param {Array<string>|null} skills - Skills to install (null = all)
|
|
10
|
+
* @param {boolean} quiet - Suppress output
|
|
11
|
+
*/
|
|
12
|
+
function install(repoPath, skills = null, quiet = false) {
|
|
13
|
+
const homeDir = os.homedir();
|
|
14
|
+
const targetDir = path.join(homeDir, '.gemini', 'skills');
|
|
15
|
+
|
|
16
|
+
// Criar diretório se não existir
|
|
17
|
+
fs.ensureDirSync(targetDir);
|
|
18
|
+
|
|
19
|
+
const sourceDir = path.join(repoPath, '.gemini', 'skills');
|
|
20
|
+
|
|
21
|
+
if (!fs.existsSync(sourceDir)) {
|
|
22
|
+
if (!quiet) {
|
|
23
|
+
console.log(chalk.red('❌ Diretório .gemini/skills não encontrado no repositório'));
|
|
24
|
+
}
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Listar skills disponíveis
|
|
29
|
+
const availableSkills = fs.readdirSync(sourceDir).filter(f => {
|
|
30
|
+
const fullPath = path.join(sourceDir, f);
|
|
31
|
+
return fs.statSync(fullPath).isDirectory() && f !== 'node_modules';
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const skillsToInstall = skills || availableSkills;
|
|
35
|
+
|
|
36
|
+
let installed = 0;
|
|
37
|
+
let failed = 0;
|
|
38
|
+
|
|
39
|
+
skillsToInstall.forEach(skill => {
|
|
40
|
+
const sourcePath = path.join(sourceDir, skill);
|
|
41
|
+
const targetPath = path.join(targetDir, skill);
|
|
42
|
+
|
|
43
|
+
if (!fs.existsSync(sourcePath)) {
|
|
44
|
+
if (!quiet) {
|
|
45
|
+
console.log(chalk.yellow(`⚠️ Skill não encontrada: ${skill}`));
|
|
46
|
+
}
|
|
47
|
+
failed++;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Criar symlink
|
|
52
|
+
try {
|
|
53
|
+
if (fs.existsSync(targetPath)) {
|
|
54
|
+
fs.removeSync(targetPath);
|
|
55
|
+
}
|
|
56
|
+
fs.symlinkSync(sourcePath, targetPath);
|
|
57
|
+
|
|
58
|
+
if (!quiet) {
|
|
59
|
+
console.log(chalk.green(` ✓ Gemini: ${skill}`));
|
|
60
|
+
}
|
|
61
|
+
installed++;
|
|
62
|
+
} catch (err) {
|
|
63
|
+
if (!quiet) {
|
|
64
|
+
console.log(chalk.red(` ✗ Erro ao instalar ${skill}: ${err.message}`));
|
|
65
|
+
}
|
|
66
|
+
failed++;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return { installed, failed };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = { install };
|
package/lib/interactive.js
CHANGED
|
@@ -1,8 +1,56 @@
|
|
|
1
1
|
const inquirer = require('inquirer');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
|
|
4
|
+
// ESC handler state
|
|
5
|
+
let escListenerActive = false;
|
|
6
|
+
let currentPrompt = null;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Setup ESC key handler for cancelling prompts
|
|
10
|
+
*/
|
|
11
|
+
function setupEscapeHandler() {
|
|
12
|
+
if (escListenerActive) return;
|
|
13
|
+
|
|
14
|
+
const readline = require('readline');
|
|
15
|
+
readline.emitKeypressEvents(process.stdin);
|
|
16
|
+
|
|
17
|
+
if (process.stdin.isTTY) {
|
|
18
|
+
process.stdin.setRawMode(true);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
process.stdin.on('keypress', async (str, key) => {
|
|
22
|
+
if (key && key.name === 'escape') {
|
|
23
|
+
await confirmCancel();
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
escListenerActive = true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Confirm cancellation with user
|
|
32
|
+
*/
|
|
33
|
+
async function confirmCancel() {
|
|
34
|
+
console.log('\n'); // New line for better UX
|
|
35
|
+
|
|
36
|
+
const { cancel } = await inquirer.prompt([{
|
|
37
|
+
type: 'confirm',
|
|
38
|
+
name: 'cancel',
|
|
39
|
+
message: chalk.yellow('⚠️ Deseja cancelar a instalação?'),
|
|
40
|
+
default: false
|
|
41
|
+
}]);
|
|
42
|
+
|
|
43
|
+
if (cancel) {
|
|
44
|
+
console.log(chalk.red('\n❌ Instalação cancelada pelo usuário.\n'));
|
|
45
|
+
process.exit(0);
|
|
46
|
+
} else {
|
|
47
|
+
console.log(chalk.dim('Continuando...\n'));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
2
50
|
|
|
3
51
|
/**
|
|
4
52
|
* Pergunta ao usuário para quais plataformas instalar
|
|
5
|
-
* @param {Object} detected - Ferramentas detectadas { copilot, claude, codex }
|
|
53
|
+
* @param {Object} detected - Ferramentas detectadas { copilot, claude, codex, opencode, gemini }
|
|
6
54
|
* @returns {Promise<Array>} Plataformas escolhidas
|
|
7
55
|
*/
|
|
8
56
|
async function promptPlatforms(detected) {
|
|
@@ -31,6 +79,22 @@ async function promptPlatforms(detected) {
|
|
|
31
79
|
checked: true
|
|
32
80
|
});
|
|
33
81
|
}
|
|
82
|
+
|
|
83
|
+
if (detected.opencode) {
|
|
84
|
+
choices.push({
|
|
85
|
+
name: '✅ OpenCode (.opencode/skills/)',
|
|
86
|
+
value: 'opencode',
|
|
87
|
+
checked: true
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (detected.gemini) {
|
|
92
|
+
choices.push({
|
|
93
|
+
name: '✅ Gemini CLI (.gemini/skills/)',
|
|
94
|
+
value: 'gemini',
|
|
95
|
+
checked: true
|
|
96
|
+
});
|
|
97
|
+
}
|
|
34
98
|
|
|
35
99
|
if (choices.length === 0) {
|
|
36
100
|
return [];
|
|
@@ -40,7 +104,7 @@ async function promptPlatforms(detected) {
|
|
|
40
104
|
{
|
|
41
105
|
type: 'checkbox',
|
|
42
106
|
name: 'platforms',
|
|
43
|
-
message: 'Instalar skills para quais plataformas?',
|
|
107
|
+
message: 'Instalar skills para quais plataformas? (Pressione ESC para cancelar)',
|
|
44
108
|
choices: choices,
|
|
45
109
|
validate: (answer) => {
|
|
46
110
|
if (answer.length < 1) {
|
|
@@ -54,4 +118,4 @@ async function promptPlatforms(detected) {
|
|
|
54
118
|
return answers.platforms;
|
|
55
119
|
}
|
|
56
120
|
|
|
57
|
-
module.exports = { promptPlatforms };
|
|
121
|
+
module.exports = { promptPlatforms, setupEscapeHandler, confirmCancel };
|
package/lib/opencode.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Install skills for OpenCode
|
|
8
|
+
* @param {string} repoPath - Path to the cli-ai-skills repository
|
|
9
|
+
* @param {Array<string>|null} skills - Skills to install (null = all)
|
|
10
|
+
* @param {boolean} quiet - Suppress output
|
|
11
|
+
*/
|
|
12
|
+
function install(repoPath, skills = null, quiet = false) {
|
|
13
|
+
const homeDir = os.homedir();
|
|
14
|
+
const targetDir = path.join(homeDir, '.opencode', 'skills');
|
|
15
|
+
|
|
16
|
+
// Criar diretório se não existir
|
|
17
|
+
fs.ensureDirSync(targetDir);
|
|
18
|
+
|
|
19
|
+
const sourceDir = path.join(repoPath, '.opencode', 'skills');
|
|
20
|
+
|
|
21
|
+
if (!fs.existsSync(sourceDir)) {
|
|
22
|
+
if (!quiet) {
|
|
23
|
+
console.log(chalk.red('❌ Diretório .opencode/skills não encontrado no repositório'));
|
|
24
|
+
}
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Listar skills disponíveis
|
|
29
|
+
const availableSkills = fs.readdirSync(sourceDir).filter(f => {
|
|
30
|
+
const fullPath = path.join(sourceDir, f);
|
|
31
|
+
return fs.statSync(fullPath).isDirectory() && f !== 'node_modules';
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const skillsToInstall = skills || availableSkills;
|
|
35
|
+
|
|
36
|
+
let installed = 0;
|
|
37
|
+
let failed = 0;
|
|
38
|
+
|
|
39
|
+
skillsToInstall.forEach(skill => {
|
|
40
|
+
const sourcePath = path.join(sourceDir, skill);
|
|
41
|
+
const targetPath = path.join(targetDir, skill);
|
|
42
|
+
|
|
43
|
+
if (!fs.existsSync(sourcePath)) {
|
|
44
|
+
if (!quiet) {
|
|
45
|
+
console.log(chalk.yellow(`⚠️ Skill não encontrada: ${skill}`));
|
|
46
|
+
}
|
|
47
|
+
failed++;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Criar symlink
|
|
52
|
+
try {
|
|
53
|
+
if (fs.existsSync(targetPath)) {
|
|
54
|
+
fs.removeSync(targetPath);
|
|
55
|
+
}
|
|
56
|
+
fs.symlinkSync(sourcePath, targetPath);
|
|
57
|
+
|
|
58
|
+
if (!quiet) {
|
|
59
|
+
console.log(chalk.green(` ✓ OpenCode: ${skill}`));
|
|
60
|
+
}
|
|
61
|
+
installed++;
|
|
62
|
+
} catch (err) {
|
|
63
|
+
if (!quiet) {
|
|
64
|
+
console.log(chalk.red(` ✗ Erro ao instalar ${skill}: ${err.message}`));
|
|
65
|
+
}
|
|
66
|
+
failed++;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return { installed, failed };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = { install };
|
package/lib/ui/table.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Exibe tabela formatada de ferramentas detectadas
|
|
5
|
+
* @param {Object} tools - Objeto retornado por detectTools()
|
|
6
|
+
*/
|
|
7
|
+
function displayToolsTable(tools) {
|
|
8
|
+
console.log('\n┌─────────────────────────────────────────────────────────────┐');
|
|
9
|
+
console.log('│ Ferramenta │ Status │ Versão │');
|
|
10
|
+
console.log('├─────────────────────────────────────────────────────────────┤');
|
|
11
|
+
|
|
12
|
+
const toolNames = {
|
|
13
|
+
copilot: 'GitHub Copilot CLI',
|
|
14
|
+
claude: 'Claude Code',
|
|
15
|
+
codex: 'OpenAI Codex',
|
|
16
|
+
opencode: 'OpenCode',
|
|
17
|
+
gemini: 'Gemini CLI'
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
for (const [key, name] of Object.entries(toolNames)) {
|
|
21
|
+
const tool = tools[key];
|
|
22
|
+
const status = tool.installed ? chalk.green('✓') : chalk.red('✗');
|
|
23
|
+
const version = tool.version || chalk.gray('-');
|
|
24
|
+
|
|
25
|
+
// Formatar linha com espaçamento fixo
|
|
26
|
+
const namePadded = name.padEnd(21);
|
|
27
|
+
const statusPadded = ' ' + status + ' ';
|
|
28
|
+
const versionStr = String(version).substring(0, 20);
|
|
29
|
+
const versionPadded = versionStr.padEnd(20);
|
|
30
|
+
|
|
31
|
+
console.log(`│ ${namePadded} │${statusPadded}│ ${versionPadded}│`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log('└─────────────────────────────────────────────────────────────┘\n');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Retorna resumo de ferramentas detectadas (simples)
|
|
39
|
+
* @param {Object} tools - Objeto retornado por detectTools()
|
|
40
|
+
* @returns {Object} { total: number, installed: number, names: string[] }
|
|
41
|
+
*/
|
|
42
|
+
function getToolsSummary(tools) {
|
|
43
|
+
const installed = [];
|
|
44
|
+
|
|
45
|
+
if (tools.copilot && tools.copilot.installed) installed.push('copilot');
|
|
46
|
+
if (tools.claude && tools.claude.installed) installed.push('claude');
|
|
47
|
+
if (tools.codex && tools.codex.installed) installed.push('codex');
|
|
48
|
+
if (tools.opencode && tools.opencode.installed) installed.push('opencode');
|
|
49
|
+
if (tools.gemini && tools.gemini.installed) installed.push('gemini');
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
total: 5,
|
|
53
|
+
installed: installed.length,
|
|
54
|
+
names: installed
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { displayToolsTable, getToolsSummary };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const semver = require('semver');
|
|
5
|
+
const yaml = require('js-yaml');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Verifica se cli-ai-skills já está instalado em alguma plataforma
|
|
9
|
+
* @returns {Object} { installed: boolean, platforms: [], versions: {}, latestVersion: string }
|
|
10
|
+
*/
|
|
11
|
+
function checkInstalledVersion() {
|
|
12
|
+
const result = {
|
|
13
|
+
installed: false,
|
|
14
|
+
platforms: [],
|
|
15
|
+
versions: {},
|
|
16
|
+
latestVersion: require('../package.json').version
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const homeDir = os.homedir();
|
|
20
|
+
|
|
21
|
+
const skillDirs = {
|
|
22
|
+
copilot: path.join(homeDir, '.copilot', 'skills'),
|
|
23
|
+
claude: path.join(homeDir, '.claude', 'skills'),
|
|
24
|
+
codex: path.join(homeDir, '.codex', 'skills'),
|
|
25
|
+
opencode: path.join(homeDir, '.opencode', 'skills'),
|
|
26
|
+
gemini: path.join(homeDir, '.gemini', 'skills')
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
for (const [platform, skillDir] of Object.entries(skillDirs)) {
|
|
30
|
+
if (!fs.existsSync(skillDir)) continue;
|
|
31
|
+
|
|
32
|
+
// Verificar se há skills instaladas (procurar por skill-creator ou prompt-engineer)
|
|
33
|
+
const testSkillPath = path.join(skillDir, 'skill-creator', 'SKILL.md');
|
|
34
|
+
|
|
35
|
+
if (fs.existsSync(testSkillPath)) {
|
|
36
|
+
result.installed = true;
|
|
37
|
+
result.platforms.push(platform);
|
|
38
|
+
|
|
39
|
+
// Extrair versão do YAML frontmatter
|
|
40
|
+
try {
|
|
41
|
+
const content = fs.readFileSync(testSkillPath, 'utf-8');
|
|
42
|
+
const yamlMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
43
|
+
|
|
44
|
+
if (yamlMatch) {
|
|
45
|
+
const metadata = yaml.load(yamlMatch[1]);
|
|
46
|
+
result.versions[platform] = metadata.version || 'unknown';
|
|
47
|
+
} else {
|
|
48
|
+
result.versions[platform] = 'unknown';
|
|
49
|
+
}
|
|
50
|
+
} catch (err) {
|
|
51
|
+
result.versions[platform] = 'unknown';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Compara versões e retorna se atualização está disponível
|
|
61
|
+
* @param {Object} installInfo - Retorno de checkInstalledVersion()
|
|
62
|
+
* @returns {boolean} true se nova versão disponível
|
|
63
|
+
*/
|
|
64
|
+
function isUpdateAvailable(installInfo) {
|
|
65
|
+
if (!installInfo.installed) return false;
|
|
66
|
+
|
|
67
|
+
const { versions, latestVersion } = installInfo;
|
|
68
|
+
|
|
69
|
+
for (const installedVersion of Object.values(versions)) {
|
|
70
|
+
if (installedVersion === 'unknown') continue;
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
if (semver.lt(installedVersion, latestVersion)) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
} catch (err) {
|
|
77
|
+
// Versão inválida, assumir atualização disponível
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Verifica se uma plataforma específica tem cli-ai-skills instalado
|
|
87
|
+
* @param {string} platform - Nome da plataforma (copilot, claude, etc)
|
|
88
|
+
* @returns {Object} { installed: boolean, version: string }
|
|
89
|
+
*/
|
|
90
|
+
function checkPlatformInstallation(platform) {
|
|
91
|
+
const homeDir = os.homedir();
|
|
92
|
+
const platformMap = {
|
|
93
|
+
copilot: '.copilot',
|
|
94
|
+
claude: '.claude',
|
|
95
|
+
codex: '.codex',
|
|
96
|
+
opencode: '.opencode',
|
|
97
|
+
gemini: '.gemini'
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const dirName = platformMap[platform];
|
|
101
|
+
if (!dirName) {
|
|
102
|
+
return { installed: false, version: null };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const skillPath = path.join(homeDir, dirName, 'skills', 'skill-creator', 'SKILL.md');
|
|
106
|
+
|
|
107
|
+
if (!fs.existsSync(skillPath)) {
|
|
108
|
+
return { installed: false, version: null };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const content = fs.readFileSync(skillPath, 'utf-8');
|
|
113
|
+
const yamlMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
114
|
+
|
|
115
|
+
if (yamlMatch) {
|
|
116
|
+
const metadata = yaml.load(yamlMatch[1]);
|
|
117
|
+
return { installed: true, version: metadata.version || 'unknown' };
|
|
118
|
+
}
|
|
119
|
+
} catch (err) {
|
|
120
|
+
return { installed: true, version: 'unknown' };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return { installed: true, version: 'unknown' };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = {
|
|
127
|
+
checkInstalledVersion,
|
|
128
|
+
isUpdateAvailable,
|
|
129
|
+
checkPlatformInstallation
|
|
130
|
+
};
|