natureco-cli 1.0.1 → 1.0.5

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,87 @@
1
+ const inquirer = require('inquirer');
2
+ const chalk = require('chalk');
3
+ const { getAllHooks, createHook } = require('../utils/hooks');
4
+
5
+ async function hooks(action, ...args) {
6
+ if (!action || action === 'list') {
7
+ return listHooks();
8
+ }
9
+
10
+ if (action === 'create') {
11
+ const type = args[0];
12
+ if (!type) {
13
+ console.log(chalk.red('\n❌ Hook type required\n'));
14
+ console.log(chalk.gray('Available types: pre-message, post-message, on-start, on-exit\n'));
15
+ console.log(chalk.gray('Usage: natureco hooks create <type>\n'));
16
+ process.exit(1);
17
+ }
18
+ return createHookInteractive(type);
19
+ }
20
+
21
+ console.log(chalk.red(`\n❌ Unknown action: ${action}\n`));
22
+ console.log(chalk.gray('Available actions: list, create\n'));
23
+ process.exit(1);
24
+ }
25
+
26
+ function listHooks() {
27
+ const hooks = getAllHooks();
28
+
29
+ if (hooks.length === 0) {
30
+ console.log(chalk.gray('\nNo hooks found.\n'));
31
+ console.log(chalk.gray('Create one with: natureco hooks create <type>\n'));
32
+ return;
33
+ }
34
+
35
+ console.log(chalk.yellow('\nHooks:\n'));
36
+
37
+ const grouped = {};
38
+ hooks.forEach(hook => {
39
+ if (!grouped[hook.type]) grouped[hook.type] = [];
40
+ grouped[hook.type].push(hook);
41
+ });
42
+
43
+ Object.keys(grouped).forEach(type => {
44
+ console.log(chalk.cyan(` ${type}:`));
45
+ grouped[type].forEach(hook => {
46
+ const sourceLabel = hook.source === 'user' ? chalk.blue('[global]') : chalk.green('[project]');
47
+ console.log(` ${sourceLabel} ${chalk.white(hook.name)}`);
48
+ });
49
+ console.log('');
50
+ });
51
+ }
52
+
53
+ async function createHookInteractive(type) {
54
+ const validTypes = ['pre-message', 'post-message', 'on-start', 'on-exit'];
55
+
56
+ if (!validTypes.includes(type)) {
57
+ console.log(chalk.red(`\n❌ Invalid hook type: ${type}\n`));
58
+ console.log(chalk.gray('Available types: pre-message, post-message, on-start, on-exit\n'));
59
+ process.exit(1);
60
+ }
61
+
62
+ process.stdin.resume();
63
+
64
+ const answers = await inquirer.prompt([
65
+ {
66
+ type: 'list',
67
+ name: 'scope',
68
+ message: 'Hook scope:',
69
+ choices: [
70
+ { name: 'Project (only this project)', value: 'project' },
71
+ { name: 'Global (all projects)', value: 'user' },
72
+ ],
73
+ default: 'project',
74
+ },
75
+ ]);
76
+
77
+ try {
78
+ const filePath = createHook(type, answers.scope);
79
+ console.log(chalk.green(`\n✅ Hook created: ${filePath}\n`));
80
+ console.log(chalk.gray('Edit the file to customize the hook behavior.\n'));
81
+ } catch (err) {
82
+ console.log(chalk.red(`\n❌ Error: ${err.message}\n`));
83
+ process.exit(1);
84
+ }
85
+ }
86
+
87
+ module.exports = hooks;
@@ -0,0 +1,119 @@
1
+ const chalk = require('chalk');
2
+ const { listSessions, loadSession } = require('../utils/sessions');
3
+ const { getApiKey } = require('../utils/config');
4
+ const { getBots } = require('../utils/api');
5
+
6
+ async function sessions(action, ...args) {
7
+ if (!action || action === 'list') {
8
+ return listSessionsCommand();
9
+ }
10
+
11
+ if (action === 'show') {
12
+ const sessionId = args[0];
13
+ if (!sessionId) {
14
+ console.log(chalk.red('\n❌ Session ID required\n'));
15
+ console.log(chalk.gray('Usage: natureco sessions show <id>\n'));
16
+ process.exit(1);
17
+ }
18
+ return showSession(sessionId);
19
+ }
20
+
21
+ console.log(chalk.red(`\n❌ Unknown action: ${action}\n`));
22
+ console.log(chalk.gray('Available actions: list, show\n'));
23
+ process.exit(1);
24
+ }
25
+
26
+ async function listSessionsCommand() {
27
+ const apiKey = getApiKey();
28
+
29
+ if (!apiKey) {
30
+ console.log(chalk.red('\n❌ Not logged in. Run "natureco login" first.\n'));
31
+ process.exit(1);
32
+ }
33
+
34
+ let botList;
35
+ try {
36
+ botList = await getBots(apiKey);
37
+ } catch (err) {
38
+ console.log(chalk.red(`\n❌ Error: ${err.message}\n`));
39
+ process.exit(1);
40
+ }
41
+
42
+ if (!botList || !botList.bots || botList.bots.length === 0) {
43
+ console.log(chalk.gray('No bots found.\n'));
44
+ return;
45
+ }
46
+
47
+ console.log(chalk.yellow('\nChat Sessions:\n'));
48
+
49
+ let foundAny = false;
50
+
51
+ botList.bots.forEach(bot => {
52
+ const sessions = listSessions(bot.id);
53
+ if (sessions.length > 0) {
54
+ foundAny = true;
55
+ console.log(chalk.cyan(` ${bot.name}:`));
56
+ sessions.forEach(session => {
57
+ const date = new Date(session.createdAt).toLocaleString();
58
+ const messageCount = session.messages.length;
59
+ console.log(` ${chalk.white(session.id)} - ${chalk.gray(date)} - ${chalk.gray(messageCount + ' messages')}`);
60
+ });
61
+ console.log('');
62
+ }
63
+ });
64
+
65
+ if (!foundAny) {
66
+ console.log(chalk.gray(' No sessions found.\n'));
67
+ }
68
+ }
69
+
70
+ async function showSession(sessionId) {
71
+ const apiKey = getApiKey();
72
+
73
+ if (!apiKey) {
74
+ console.log(chalk.red('\n❌ Not logged in. Run "natureco login" first.\n'));
75
+ process.exit(1);
76
+ }
77
+
78
+ let botList;
79
+ try {
80
+ botList = await getBots(apiKey);
81
+ } catch (err) {
82
+ console.log(chalk.red(`\n❌ Error: ${err.message}\n`));
83
+ process.exit(1);
84
+ }
85
+
86
+ if (!botList || !botList.bots || botList.bots.length === 0) {
87
+ console.log(chalk.gray('No bots found.\n'));
88
+ return;
89
+ }
90
+
91
+ // Find session across all bots
92
+ let session = null;
93
+ let botName = null;
94
+
95
+ for (const bot of botList.bots) {
96
+ session = loadSession(bot.id, sessionId);
97
+ if (session) {
98
+ botName = bot.name;
99
+ break;
100
+ }
101
+ }
102
+
103
+ if (!session) {
104
+ console.log(chalk.red(`\n❌ Session not found: ${sessionId}\n`));
105
+ process.exit(1);
106
+ }
107
+
108
+ console.log(chalk.yellow(`\nSession: ${session.id}\n`));
109
+ console.log(chalk.gray(`Bot: ${botName}`));
110
+ console.log(chalk.gray(`Created: ${new Date(session.createdAt).toLocaleString()}`));
111
+ console.log(chalk.gray(`Messages: ${session.messages.length}\n`));
112
+
113
+ session.messages.forEach((msg, i) => {
114
+ console.log(chalk.cyan(`[${i + 1}] siz › ${msg.user}`));
115
+ console.log(chalk.green(` bot › ${msg.bot}\n`));
116
+ });
117
+ }
118
+
119
+ module.exports = sessions;
@@ -34,183 +34,199 @@ async function setup() {
34
34
  console.log(chalk.cyan('✦ Varsayılan ayarlar yükleniyor...'));
35
35
  console.log(chalk.green(' ✓ Ayarlar hazır\n'));
36
36
 
37
- // API key sor
37
+ // AI sağlayıcı seç
38
38
  process.stdin.resume();
39
- const { apiKey } = await inquirer.prompt([
40
- {
41
- type: 'input',
42
- name: 'apiKey',
43
- message: 'API key girin:',
44
- validate: async (input) => {
45
- if (!input || input.trim().length === 0) {
46
- return 'API key gerekli';
47
- }
48
-
49
- const trimmed = input.trim();
50
- if (!trimmed.startsWith('nc_') && !trimmed.startsWith('nco_')) {
51
- return 'API key nc_ veya nco_ ile başlamalı';
52
- }
53
-
54
- // API key'i validate et
55
- console.log(chalk.yellow('\n ⏳ API key doğrulanıyor...'));
56
- try {
57
- const response = await fetch('https://api.natureco.me/api/v1/bots', {
58
- headers: {
59
- 'Authorization': `Bearer ${trimmed}`,
60
- 'Content-Type': 'application/json',
61
- },
62
- });
63
-
64
- if (!response.ok) {
65
- return 'Geçersiz API key';
66
- }
67
-
68
- const data = await response.json();
69
- if (!data.bots || data.bots.length === 0) {
70
- return 'Bu API key ile bot bulunamadı';
71
- }
72
-
73
- console.log(chalk.green(' ✓ API key doğrulandı\n'));
74
- return true;
75
- } catch (err) {
76
- return `API hatası: ${err.message}`;
77
- }
78
- },
79
- },
80
- ]);
81
-
82
- const trimmedApiKey = apiKey.trim();
83
-
84
- // Botları çek
85
- console.log(chalk.yellow('⏳ Botlar yükleniyor...\n'));
86
- let botList;
87
- try {
88
- botList = await getBots(trimmedApiKey);
89
- } catch (err) {
90
- console.log(chalk.red(`\n❌ Hata: ${err.message}\n`));
91
- process.exit(1);
92
- }
93
-
94
- if (!botList || !botList.bots || botList.bots.length === 0) {
95
- console.log(chalk.red('❌ Bot bulunamadı.\n'));
96
- process.exit(1);
97
- }
98
-
99
- // Bot seç
100
- process.stdin.resume();
101
- const { selectedBotId } = await inquirer.prompt([
39
+ const { aiProvider } = await inquirer.prompt([
102
40
  {
103
41
  type: 'list',
104
- name: 'selectedBotId',
105
- message: 'Varsayılan bot seçin:',
106
- choices: botList.bots.map(b => ({
107
- name: b.name,
108
- value: b.id,
109
- })),
42
+ name: 'aiProvider',
43
+ message: 'AI sağlayıcı seçin:',
44
+ choices: [
45
+ { name: 'NatureCo (önerilen)', value: 'natureco' },
46
+ { name: 'OpenAI', value: 'openai' },
47
+ { name: 'Anthropic Claude', value: 'anthropic' },
48
+ { name: 'Groq', value: 'groq' },
49
+ { name: 'Google Gemini', value: 'gemini' },
50
+ ],
51
+ default: 'natureco',
110
52
  },
111
53
  ]);
112
54
 
113
- const selectedBot = botList.bots.find(b => b.id === selectedBotId);
55
+ let aiApiKey = null;
56
+ let naturecoApiKey = null;
114
57
 
115
- // Telegram bağlantısı
116
- process.stdin.resume();
117
- const { connectTelegram } = await inquirer.prompt([
118
- {
119
- type: 'confirm',
120
- name: 'connectTelegram',
121
- message: 'Telegram bağlamak ister misiniz?',
122
- default: false,
123
- },
124
- ]);
58
+ // NatureCo dışı sağlayıcı seçildiyse key sor
59
+ if (aiProvider !== 'natureco') {
60
+ const providerInfo = {
61
+ openai: { name: 'OpenAI', prefix: 'sk-', example: 'sk-...' },
62
+ anthropic: { name: 'Anthropic', prefix: 'sk-ant-', example: 'sk-ant-...' },
63
+ groq: { name: 'Groq', prefix: 'gsk_', example: 'gsk_...' },
64
+ gemini: { name: 'Google Gemini', prefix: 'AIza', example: 'AIza...' },
65
+ };
125
66
 
126
- let telegramConnected = false;
127
- let telegramSkipped = false;
67
+ const info = providerInfo[aiProvider];
128
68
 
129
- if (connectTelegram) {
130
69
  process.stdin.resume();
131
- const { telegramToken } = await inquirer.prompt([
70
+ const { customApiKey } = await inquirer.prompt([
132
71
  {
133
72
  type: 'input',
134
- name: 'telegramToken',
135
- message: 'Telegram Bot Token girin (BotFather\'dan alınır):',
73
+ name: 'customApiKey',
74
+ message: `${info.name} API key girin (${info.example}):`,
136
75
  validate: (input) => {
137
76
  if (!input || input.trim().length === 0) {
138
- return 'Token gerekli';
77
+ return 'API key gerekli';
78
+ }
79
+ if (!input.trim().startsWith(info.prefix)) {
80
+ return `API key ${info.prefix} ile başlamalı`;
139
81
  }
140
82
  return true;
141
83
  },
142
84
  },
143
85
  ]);
144
86
 
87
+ aiApiKey = customApiKey.trim();
88
+
89
+ // NatureCo API key de sor (opsiyonel)
90
+ process.stdin.resume();
91
+ const { wantNaturecoKey } = await inquirer.prompt([
92
+ {
93
+ type: 'confirm',
94
+ name: 'wantNaturecoKey',
95
+ message: 'NatureCo API key de girmek ister misiniz? (Bot yönetimi için)',
96
+ default: false,
97
+ },
98
+ ]);
99
+
100
+ if (wantNaturecoKey) {
101
+ process.stdin.resume();
102
+ const { ncKey } = await inquirer.prompt([
103
+ {
104
+ type: 'input',
105
+ name: 'ncKey',
106
+ message: 'NatureCo API key:',
107
+ validate: (input) => {
108
+ const trimmed = input.trim();
109
+ if (trimmed.length === 0) return true; // Opsiyonel
110
+ if (!trimmed.startsWith('nc_') && !trimmed.startsWith('nco_')) {
111
+ return 'API key nc_ veya nco_ ile başlamalı';
112
+ }
113
+ return true;
114
+ },
115
+ },
116
+ ]);
117
+ if (ncKey.trim().length > 0) {
118
+ naturecoApiKey = ncKey.trim();
119
+ }
120
+ }
121
+ } else {
122
+ // NatureCo seçildiyse normal akış
145
123
  process.stdin.resume();
146
- const { telegramUserId } = await inquirer.prompt([
124
+ const { apiKey } = await inquirer.prompt([
147
125
  {
148
126
  type: 'input',
149
- name: 'telegramUserId',
150
- message: 'Telegram kullanıcı ID\'niz nedir? (t.me/userinfobot ile öğrenebilirsiniz):',
151
- validate: (val) => val.trim() !== '' || 'Kullanıcı ID boş olamaz',
127
+ name: 'apiKey',
128
+ message: 'NatureCo API key girin:',
129
+ validate: async (input) => {
130
+ if (!input || input.trim().length === 0) {
131
+ return 'API key gerekli';
132
+ }
133
+
134
+ const trimmed = input.trim();
135
+ if (!trimmed.startsWith('nc_') && !trimmed.startsWith('nco_')) {
136
+ return 'API key nc_ veya nco_ ile başlamalı';
137
+ }
138
+
139
+ console.log(chalk.yellow('\n ⏳ API key doğrulanıyor...'));
140
+ try {
141
+ const response = await fetch('https://api.natureco.me/api/v1/bots', {
142
+ headers: {
143
+ 'Authorization': `Bearer ${trimmed}`,
144
+ 'Content-Type': 'application/json',
145
+ },
146
+ });
147
+
148
+ if (!response.ok) {
149
+ return 'Geçersiz API key';
150
+ }
151
+
152
+ const data = await response.json();
153
+ if (!data.bots || data.bots.length === 0) {
154
+ return 'Bu API key ile bot bulunamadı';
155
+ }
156
+
157
+ console.log(chalk.green(' ✓ API key doğrulandı\n'));
158
+ return true;
159
+ } catch (err) {
160
+ return `API hatası: ${err.message}`;
161
+ }
162
+ },
152
163
  },
153
164
  ]);
154
165
 
155
- console.log(chalk.yellow('\n⏳ Telegram bağlanıyor...\n'));
166
+ naturecoApiKey = apiKey.trim();
167
+ }
168
+
169
+ // Bot seçimi (sadece NatureCo key varsa)
170
+ let selectedBot = null;
171
+ let selectedBotId = null;
156
172
 
173
+ if (naturecoApiKey) {
174
+ console.log(chalk.yellow('⏳ Botlar yükleniyor...\n'));
157
175
  try {
158
- const response = await fetch('https://api.natureco.me/api/agent/telegram/connect', {
159
- method: 'POST',
160
- headers: {
161
- 'Authorization': `Bearer ${trimmedApiKey}`,
162
- 'Content-Type': 'application/json',
163
- },
164
- body: JSON.stringify({
165
- agent_id: selectedBotId,
166
- telegram_bot_token: telegramToken.trim(),
167
- telegram_user_id: telegramUserId.trim(),
168
- }),
169
- });
170
-
171
- if (response.ok) {
172
- console.log(chalk.green('✅ Telegram bağlandı\n'));
173
- telegramConnected = true;
174
-
175
- // Config'e telegramUserId ekle
176
- config.telegramUserId = telegramUserId.trim();
177
- } else {
178
- const errorText = await response.text();
179
- console.log(chalk.red(`❌ Telegram bağlanamadı: ${errorText}\n`));
176
+ const botList = await getBots(naturecoApiKey);
177
+
178
+ if (botList && botList.bots && botList.bots.length > 0) {
179
+ process.stdin.resume();
180
+ const { botId } = await inquirer.prompt([
181
+ {
182
+ type: 'list',
183
+ name: 'botId',
184
+ message: 'Varsayılan bot seçin:',
185
+ choices: botList.bots.map(b => ({ name: b.name, value: b.id })),
186
+ },
187
+ ]);
188
+
189
+ selectedBotId = botId;
190
+ selectedBot = botList.bots.find(b => b.id === botId);
180
191
  }
181
192
  } catch (err) {
182
- console.log(chalk.red(`❌ Telegram bağlanamadı: ${err.message}\n`));
193
+ console.log(chalk.yellow(`⚠️ Bot listesi alınamadı: ${err.message}\n`));
183
194
  }
184
- } else {
185
- telegramSkipped = true;
186
195
  }
187
196
 
188
197
  // Config kaydet
189
198
  const config = {
190
- apiKey: trimmedApiKey,
191
- defaultBot: selectedBot.name,
192
- defaultBotId: selectedBot.id,
193
- skills: {
194
- enabled: true,
195
- list: [],
196
- },
197
- mcpServers: {},
198
199
  setupCompleted: true,
199
200
  setupDate: new Date().toISOString(),
201
+ skills: { enabled: true, list: [] },
202
+ mcpServers: {},
200
203
  };
201
204
 
205
+ if (aiProvider !== 'natureco') {
206
+ config.aiProvider = aiProvider;
207
+ config.aiApiKey = aiApiKey;
208
+ }
209
+
210
+ if (naturecoApiKey) {
211
+ config.apiKey = naturecoApiKey;
212
+ }
213
+
214
+ if (selectedBot) {
215
+ config.defaultBot = selectedBot.name;
216
+ config.defaultBotId = selectedBotId;
217
+ }
218
+
202
219
  saveConfig(config);
203
220
 
204
221
  // Kurulum tamamlandı
205
- console.log(chalk.green.bold('✅ Kurulum tamamlandı!\n'));
206
- console.log(chalk.cyan('Aktif bot:'), chalk.white(selectedBot.name));
222
+ console.log(chalk.green.bold('\n✅ Kurulum tamamlandı!\n'));
207
223
 
208
- if (telegramConnected) {
209
- console.log(chalk.cyan('Telegram:'), chalk.green('✅ Bağlı'));
210
- } else if (telegramSkipped) {
211
- console.log(chalk.cyan('Telegram:'), chalk.gray('⏭ Atlandı'));
212
- } else {
213
- console.log(chalk.cyan('Telegram:'), chalk.red('❌ Bağlanamadı'));
224
+ if (aiProvider !== 'natureco') {
225
+ console.log(chalk.cyan('AI Sağlayıcı:'), chalk.white(aiProvider));
226
+ }
227
+
228
+ if (selectedBot) {
229
+ console.log(chalk.cyan('Varsayılan Bot:'), chalk.white(selectedBot.name));
214
230
  }
215
231
 
216
232
  console.log(chalk.cyan('Config:'), chalk.white(CONFIG_FILE));
@@ -1,6 +1,6 @@
1
1
  const chalk = require('chalk');
2
2
  const inquirer = require('inquirer');
3
- const { getSkills, installSkill, removeSkill, updateAllSkills, createSkillTemplate } = require('../utils/skills');
3
+ const { getSkills, installSkill, removeSkill, updateAllSkills, createSkillTemplate, getPopularSkills } = require('../utils/skills');
4
4
 
5
5
  async function skills(args) {
6
6
  const [action, ...params] = args;
@@ -51,6 +51,17 @@ async function skills(args) {
51
51
  return;
52
52
  }
53
53
 
54
+ if (action === 'search') {
55
+ const query = params.join(' ');
56
+ await searchSkillsCommand(query);
57
+ return;
58
+ }
59
+
60
+ if (action === 'browse') {
61
+ await browseSkillsCommand();
62
+ return;
63
+ }
64
+
54
65
  console.log(chalk.red(`\n❌ Geçersiz action: ${action}\n`));
55
66
  console.log(chalk.gray('Kullanım: natureco skills [list|install|remove|update|create]\n'));
56
67
  process.exit(1);
@@ -158,4 +169,77 @@ async function createSkillCommand(name) {
158
169
  }
159
170
  }
160
171
 
172
+ async function searchSkillsCommand(query) {
173
+ const popularSkills = getPopularSkills();
174
+
175
+ if (!query || query.trim().length === 0) {
176
+ console.log(chalk.yellow('\nPopüler Skill\'ler:\n'));
177
+ popularSkills.forEach(skill => {
178
+ console.log(chalk.cyan(` ${skill.name.padEnd(15)}`), chalk.gray(skill.description));
179
+ });
180
+ console.log('');
181
+ console.log(chalk.gray('Kurmak için: '), chalk.cyan('natureco skills install <slug>'));
182
+ console.log(chalk.gray('Örnek: '), chalk.cyan('natureco skills install github'));
183
+ console.log(chalk.gray('ClawHub: '), chalk.cyan('natureco skills install clawhub:github'));
184
+ console.log('');
185
+ return;
186
+ }
187
+
188
+ // Search in popular skills
189
+ const results = popularSkills.filter(skill =>
190
+ skill.name.toLowerCase().includes(query.toLowerCase()) ||
191
+ skill.description.toLowerCase().includes(query.toLowerCase()) ||
192
+ skill.slug.toLowerCase().includes(query.toLowerCase())
193
+ );
194
+
195
+ if (results.length === 0) {
196
+ console.log(chalk.yellow(`\n"${query}" için sonuç bulunamadı.\n`));
197
+ return;
198
+ }
199
+
200
+ console.log(chalk.yellow(`\n"${query}" için ${results.length} sonuç:\n`));
201
+ results.forEach(skill => {
202
+ console.log(chalk.cyan(` ${skill.name.padEnd(15)}`), chalk.gray(skill.description));
203
+ console.log(chalk.gray(` Kurmak için: natureco skills install ${skill.slug}`));
204
+ });
205
+ console.log('');
206
+ }
207
+
208
+ async function browseSkillsCommand() {
209
+ const popularSkills = getPopularSkills();
210
+
211
+ console.log(chalk.green.bold('\n╭─ Popüler Skill\'ler ─╮\n'));
212
+
213
+ process.stdin.resume();
214
+ const { selectedSkills } = await inquirer.prompt([
215
+ {
216
+ type: 'checkbox',
217
+ name: 'selectedSkills',
218
+ message: 'Kurmak istediğiniz skill\'leri seçin:',
219
+ choices: popularSkills.map(skill => ({
220
+ name: `${skill.name} - ${skill.description}`,
221
+ value: skill.slug,
222
+ })),
223
+ },
224
+ ]);
225
+
226
+ if (selectedSkills.length === 0) {
227
+ console.log(chalk.gray('\nHiçbir skill seçilmedi.\n'));
228
+ return;
229
+ }
230
+
231
+ console.log(chalk.yellow(`\n⏳ ${selectedSkills.length} skill kuruluyor...\n`));
232
+
233
+ for (const slug of selectedSkills) {
234
+ try {
235
+ await installSkill(slug);
236
+ console.log(chalk.green(`✅ ${slug} kuruldu`));
237
+ } catch (err) {
238
+ console.log(chalk.red(`❌ ${slug} kurulamadı: ${err.message}`));
239
+ }
240
+ }
241
+
242
+ console.log(chalk.green('\n✅ Kurulum tamamlandı!\n'));
243
+ }
244
+
161
245
  module.exports = skills;