natureco-cli 2.23.28 → 2.23.29

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.
@@ -3,18 +3,128 @@ const fs = require('fs');
3
3
  const path = require('path');
4
4
  const os = require('os');
5
5
  const { getConfig, CONFIG_FILE, CONFIG_DIR } = require('../utils/config');
6
+ const { listSecretRefs, resolveSecretRef, coerceSecretRef } = require('../utils/secrets');
7
+ const { NatureCoError, handleError } = require('../utils/errors');
6
8
 
7
9
  async function security(args) {
8
10
  const [action, ...params] = (args || []);
9
11
 
10
12
  if (!action || action === 'audit') return audit(args);
11
13
 
14
+ if (action === 'allowlist') return allowlistAction(args);
15
+
16
+ if (action === 'policy') return policyAction(args);
17
+
18
+ if (action === 'secrets') return scanSecrets(args);
19
+
12
20
  console.log(chalk.red(`\n ❌ Bilinmeyen komut: ${action}\n`));
13
- console.log(chalk.gray(' Kullanım: natureco security [audit]\n'));
21
+ console.log(chalk.gray(' Kullanım: natureco security [audit|allowlist|policy|secrets]\n'));
14
22
  process.exit(1);
15
23
  }
16
24
 
25
+ async function allowlistAction(args) {
26
+ const { loadApprovals, addAllowlistEntry, listAllowlist, removeAllowlistEntry } = require('../utils/approvals');
27
+ const sub = args[1];
28
+
29
+ if (!sub || sub === 'list') {
30
+ const entries = listAllowlist('default');
31
+ console.log('');
32
+ console.log(chalk.cyan.bold('\n Exec Allowlist\n'));
33
+ if (entries.length === 0) {
34
+ console.log(chalk.gray(' No entries. Add with: natureco security allowlist add "<command>"'));
35
+ } else {
36
+ entries.forEach((e, i) => {
37
+ console.log(chalk.gray(` ${i + 1}. `) + chalk.white(e.lastUsedCommand || e.pattern));
38
+ console.log(chalk.gray(` id: ${e.id} · source: ${e.source || 'manual'}`));
39
+ });
40
+ }
41
+ console.log('');
42
+ return;
43
+ }
44
+
45
+ if (sub === 'add' && args[2]) {
46
+ addAllowlistEntry('default', args.slice(2).join(' '));
47
+ console.log(chalk.green('\n ✓ Command added to allowlist\n'));
48
+ return;
49
+ }
50
+
51
+ if (sub === 'remove' && args[2]) {
52
+ const removed = removeAllowlistEntry('default', args[2]);
53
+ if (removed) {
54
+ console.log(chalk.green('\n ✓ Entry removed from allowlist\n'));
55
+ } else {
56
+ console.log(chalk.yellow('\n ⚠️ Entry not found\n'));
57
+ }
58
+ return;
59
+ }
60
+
61
+ console.log(chalk.gray('\n Kullanım:'));
62
+ console.log(chalk.gray(' natureco security allowlist list'));
63
+ console.log(chalk.gray(' natureco security allowlist add "<command>"'));
64
+ console.log(chalk.gray(' natureco security allowlist remove <id>\n'));
65
+ }
66
+
67
+ async function policyAction(args) {
68
+ const { setSecurityPolicy } = require('../utils/approvals');
69
+ const sub = args[1];
70
+
71
+ if (sub === 'set' && args[2]) {
72
+ const value = args[2];
73
+ if (['deny', 'allowlist', 'full'].includes(value)) {
74
+ setSecurityPolicy('default', { security: value });
75
+ console.log(chalk.green(`\n ✓ Security policy set to: ${value}\n`));
76
+ } else {
77
+ console.log(chalk.red(`\n ❌ Invalid policy: ${value}. Use: deny, allowlist, full\n`));
78
+ }
79
+ return;
80
+ }
81
+
82
+ console.log(chalk.gray('\n Kullanım:'));
83
+ console.log(chalk.gray(' natureco security policy set deny|allowlist|full'));
84
+ console.log(chalk.gray('\n Policy levels:'));
85
+ console.log(chalk.gray(' deny - Block all command execution'));
86
+ console.log(chalk.gray(' allowlist - Only allowlisted commands'));
87
+ console.log(chalk.gray(' full - Allow all commands (default)\n'));
88
+ }
89
+
90
+ async function scanSecrets(args) {
91
+ const config = getConfig();
92
+ const refs = listSecretRefs(config);
93
+ const fix = args.includes('--fix');
94
+
95
+ console.log('');
96
+ console.log(chalk.cyan.bold('\n Secret Reference Scan\n'));
97
+
98
+ if (refs.length === 0) {
99
+ console.log(chalk.green(' ✓ No secret references found in config\n'));
100
+ return;
101
+ }
102
+
103
+ let resolved = 0;
104
+ let unresolved = 0;
105
+
106
+ for (const ref of refs) {
107
+ try {
108
+ const value = resolveSecretRef(ref.ref, { throwOnMissing: true });
109
+ console.log(chalk.green(` ✓ ${ref.path} → resolved (${ref.ref.source}:${ref.ref.id})`));
110
+ resolved++;
111
+ } catch {
112
+ console.log(chalk.yellow(` ⚠️ ${ref.path} → ${ref.ref.source}:${ref.ref.id} (unresolved)`));
113
+ unresolved++;
114
+ }
115
+ }
116
+
117
+ console.log('');
118
+ console.log(chalk.gray(` ${resolved} resolved · ${unresolved} unresolved`));
119
+
120
+ if (unresolved > 0) {
121
+ console.log(chalk.gray('\n Set environment variables or use .env file to resolve secrets.\n'));
122
+ }
123
+ console.log('');
124
+ }
125
+
17
126
  async function audit(args) {
127
+ const { DANGEROUS_PATTERNS, loadApprovals } = require('../utils/approvals');
18
128
  const fix = args.includes('--fix');
19
129
  const json = args.includes('--json');
20
130
 
@@ -90,6 +200,44 @@ async function audit(args) {
90
200
  }
91
201
  }
92
202
 
203
+ // 7. Secret refs in config
204
+ const secretRefs = listSecretRefs(config);
205
+ if (secretRefs.length > 0) {
206
+ let unresolvedCount = 0;
207
+ for (const ref of secretRefs) {
208
+ try {
209
+ resolveSecretRef(ref.ref, { throwOnMissing: true });
210
+ } catch {
211
+ unresolvedCount++;
212
+ }
213
+ }
214
+ if (unresolvedCount > 0) {
215
+ warnings.push({ id: 'unresolved-secrets', msg: `${unresolvedCount}/${secretRefs.length} secret reference(s) unresolved` });
216
+ } else {
217
+ ok.push('Secret references resolvable');
218
+ }
219
+ }
220
+
221
+ // 8. Exec approvals policy
222
+ const approvals = loadApprovals();
223
+ if (approvals.defaults?.security === 'deny') {
224
+ warnings.push({ id: 'deny-policy', msg: 'Exec approvals: all commands denied' });
225
+ } else if (approvals.defaults?.security === 'allowlist') {
226
+ const entries = Object.values(approvals.agents || {}).reduce((a, ag) => a + (ag.allowlist?.length || 0), 0);
227
+ ok.push(`Exec approvals: allowlist mode (${entries} entries)`);
228
+ } else {
229
+ ok.push('Exec approvals: full mode');
230
+ }
231
+
232
+ // 9. Config backup status
233
+ const { listBackups } = require('../utils/config');
234
+ const backups = listBackups();
235
+ if (backups.length > 0) {
236
+ ok.push(`Config backups: ${backups.length} available`);
237
+ } else {
238
+ warnings.push({ id: 'no-backups', msg: 'No config backups found. Backups are created automatically on config changes.' });
239
+ }
240
+
93
241
  if (json) {
94
242
  console.log(JSON.stringify({ ok, warnings: warnings.map(w => w.msg), issues: issues.map(i => i.msg) }, null, 2));
95
243
  return;
@@ -454,9 +454,7 @@ async function setup() {
454
454
  language: lang,
455
455
  providerUrl,
456
456
  providerApiKey: providerApiKey.trim(),
457
- providerModel,
458
- apiKey: providerApiKey.trim(), // login bypass — chat doğrudan çalışsın
459
- botName: safeBotName,
457
+ botName: safeBotName,
460
458
  userName: userName.trim(),
461
459
  debug: existingConfig.debug || false,
462
460
  skills: existingConfig.skills || { enabled: true, list: [] },
@@ -0,0 +1,66 @@
1
+ const chalk = require('chalk');
2
+ const inquirer = require('../utils/inquirer-wrapper');
3
+ const { getConfig, saveConfig } = require('../utils/config');
4
+
5
+ async function signal(action) {
6
+ if (!action || action === 'connect') return connectSignal();
7
+ if (action === 'disconnect') return disconnectSignal();
8
+ if (action === 'status') return statusSignal();
9
+ console.log(chalk.red('\n❌ Unknown action\n'));
10
+ console.log(chalk.gray('Available actions: connect, disconnect, status\n'));
11
+ process.exit(1);
12
+ }
13
+
14
+ async function connectSignal() {
15
+ const config = getConfig();
16
+ if (!config.providerUrl) {
17
+ console.log(chalk.red('\n❌ Setup yapılmamış. Önce "natureco setup" çalıştırın.\n'));
18
+ process.exit(1);
19
+ }
20
+ console.log(chalk.yellow('\n⏳ Signal bağlantısı hazırlanıyor...\n'));
21
+ console.log(chalk.gray('Signal, signal-cli REST API üzerinden bağlanır.'));
22
+ console.log(chalk.gray('Önce signal-cli kurulu olmalı: https://github.com/AsamK/signal-cli\n'));
23
+ const answers = await inquirer.prompt([
24
+ { type: 'input', name: 'httpUrl', message: 'signal-cli REST API URL (örn: http://localhost:8080):', validate: v => v.trim() ? true : 'URL boş olamaz' },
25
+ { type: 'input', name: 'account', message: 'Signal hesap numarası (+905551234567):', validate: v => v.trim() ? true : 'Numara boş olamaz' },
26
+ { type: 'list', name: 'dmPolicy', message: 'DM politikası:', choices: [{ name: 'Pairing (önerilen)', value: 'pairing' }, { name: 'Allowlist', value: 'allowlist' }, { name: 'Open', value: 'open' }, { name: 'Disabled', value: 'disabled' }], default: 'pairing' },
27
+ ]);
28
+ const botId = `signal_${Date.now()}`;
29
+ config.signalHttpUrl = answers.httpUrl.trim();
30
+ config.signalAccount = answers.account.trim();
31
+ config.signalDmPolicy = answers.dmPolicy;
32
+ config.signalBotId = botId;
33
+ saveConfig(config);
34
+ console.log(chalk.green('\n✅ Signal bağlantısı kaydedildi!\n'));
35
+ console.log(chalk.cyan('Bot ID:'), chalk.white(botId));
36
+ console.log(chalk.cyan('API URL:'), chalk.white(answers.httpUrl.trim()));
37
+ console.log(chalk.cyan('Hesap:'), chalk.white(answers.account.trim()));
38
+ console.log(chalk.gray('\nNot: signal-cli\'nin çalıştığından emin olun.'));
39
+ console.log(chalk.gray('Gateway ile başlatmak için: natureco gateway start\n'));
40
+ }
41
+
42
+ async function disconnectSignal() {
43
+ const config = getConfig();
44
+ if (!config.signalBotId) { console.log(chalk.gray('\n⚠️ No Signal connection found\n')); return; }
45
+ const { confirm } = await inquirer.prompt([{ type: 'confirm', name: 'confirm', message: 'Signal bağlantısını kaldırmak istediğinize emin misiniz?', default: false }]);
46
+ if (!confirm) { console.log(chalk.gray('\nCancelled\n')); return; }
47
+ delete config.signalHttpUrl;
48
+ delete config.signalAccount;
49
+ delete config.signalDmPolicy;
50
+ delete config.signalBotId;
51
+ saveConfig(config);
52
+ console.log(chalk.green('\n✅ Signal disconnected\n'));
53
+ }
54
+
55
+ function statusSignal() {
56
+ const config = getConfig();
57
+ if (!config.signalBotId) { console.log(chalk.gray('\n⚠️ Signal not connected\n')); console.log(chalk.gray('Connect with: natureco signal connect\n')); return; }
58
+ console.log(chalk.green('\n✅ Signal connected\n'));
59
+ console.log(chalk.cyan('Bot ID:'), chalk.white(config.signalBotId));
60
+ console.log(chalk.cyan('API URL:'), chalk.white(config.signalHttpUrl));
61
+ console.log(chalk.cyan('Hesap:'), chalk.white(config.signalAccount));
62
+ console.log(chalk.cyan('DM Politikası:'), chalk.white(config.signalDmPolicy || 'pairing'));
63
+ console.log(chalk.gray('\nDisconnect with: natureco signal disconnect\n'));
64
+ }
65
+
66
+ module.exports = signal;
@@ -1,6 +1,7 @@
1
1
  const chalk = require('chalk');
2
2
  const inquirer = require('../utils/inquirer-wrapper');
3
3
  const { getSkills, installSkill, removeSkill, updateAllSkills, createSkillTemplate, getPopularSkills } = require('../utils/skills');
4
+ const { NatureCoError, SkillError, handleError } = require('../utils/errors');
4
5
 
5
6
  async function skills(args) {
6
7
  const [action, ...params] = args;
@@ -167,11 +168,15 @@ async function createSkillCommand(name) {
167
168
 
168
169
  async function searchSkillsCommand(query) {
169
170
  if (!query || query.trim().length === 0) {
170
- const popularSkills = await getPopularSkills();
171
- console.log(chalk.yellow('\nPopüler Skill\'ler:\n'));
172
- popularSkills.forEach(skill => {
173
- console.log(chalk.cyan(` ${skill.name.padEnd(15)}`), chalk.gray(skill.description));
174
- });
171
+ try {
172
+ const popularSkills = await getPopularSkills();
173
+ console.log(chalk.yellow('\nPopüler Skill\'ler:\n'));
174
+ popularSkills.forEach(skill => {
175
+ console.log(chalk.cyan(` ${skill.name.padEnd(15)}`), chalk.gray(skill.description));
176
+ });
177
+ } catch (err) {
178
+ console.log(chalk.red(`\n ❌ Popüler skill'ler alınamadı: ${err.message}\n`));
179
+ }
175
180
  console.log('');
176
181
  console.log(chalk.gray('Kurmak için: '), chalk.cyan('natureco skills install <slug>'));
177
182
  console.log(chalk.gray('Örnek: '), chalk.cyan('natureco skills install github'));
@@ -188,14 +193,14 @@ async function searchSkillsCommand(query) {
188
193
  const response = await fetch(searchUrl);
189
194
 
190
195
  if (!response.ok) {
191
- throw new Error('ClawHub API\'ye erişilemedi');
196
+ throw new SkillError('ClawHub API\'ye erişilemedi', 'fetch', `https://clawhub.ai/api/skills?q=${encodeURIComponent(query)}`);
192
197
  }
193
198
 
194
199
  let data;
195
200
  try {
196
201
  data = await response.json();
197
202
  } catch {
198
- throw new Error('ClawHub geçersiz yanıt döndü (JSON bekleniyordu)');
203
+ throw new SkillError('ClawHub geçersiz yanıt döndü (JSON bekleniyordu)', 'parse', searchUrl);
199
204
  }
200
205
  const results = data.skills || [];
201
206
 
@@ -211,32 +216,18 @@ async function searchSkillsCommand(query) {
211
216
  });
212
217
  console.log('');
213
218
  } catch (err) {
214
- // Fallback to local popular skills search
215
- console.log(chalk.gray('ClawHub\'a bağlanılamadı, yerel aramada...\n'));
216
-
217
- const popularSkills = await getPopularSkills();
218
- const results = popularSkills.filter(skill =>
219
- skill.name.toLowerCase().includes(query.toLowerCase()) ||
220
- skill.description.toLowerCase().includes(query.toLowerCase()) ||
221
- skill.slug.toLowerCase().includes(query.toLowerCase())
222
- );
223
-
224
- if (results.length === 0) {
225
- console.log(chalk.yellow(`"${query}" için sonuç bulunamadı.\n`));
226
- return;
227
- }
228
-
229
- console.log(chalk.yellow(`"${query}" için ${results.length} sonuç:\n`));
230
- results.forEach(skill => {
231
- console.log(chalk.cyan(` ${skill.name.padEnd(15)}`), chalk.gray(skill.description));
232
- console.log(chalk.gray(` Kurmak için: natureco skills install ${skill.slug}`));
233
- });
234
- console.log('');
219
+ console.log(chalk.red(`\n ❌ Arama başarısız: ${err.message}\n`));
235
220
  }
236
221
  }
237
222
 
238
223
  async function browseSkillsCommand() {
239
- const popularSkills = await getPopularSkills();
224
+ let popularSkills;
225
+ try {
226
+ popularSkills = await getPopularSkills();
227
+ } catch (err) {
228
+ console.log(chalk.red(`\n ❌ Popüler skill'ler alınamadı: ${err.message}\n`));
229
+ return;
230
+ }
240
231
 
241
232
  console.log(chalk.green.bold('\n╭─ Popüler Skill\'ler ─╮\n'));
242
233
 
@@ -0,0 +1,64 @@
1
+ const chalk = require('chalk');
2
+ const inquirer = require('../utils/inquirer-wrapper');
3
+ const { getConfig, saveConfig } = require('../utils/config');
4
+
5
+ async function sms(action) {
6
+ if (!action || action === 'connect') return connectSms();
7
+ if (action === 'disconnect') return disconnectSms();
8
+ if (action === 'status') return statusSms();
9
+ console.log(chalk.red('\n❌ Unknown action\n'));
10
+ console.log(chalk.gray('Available actions: connect, disconnect, status\n'));
11
+ process.exit(1);
12
+ }
13
+
14
+ async function connectSms() {
15
+ const config = getConfig();
16
+ if (!config.providerUrl) { console.log(chalk.red('\n❌ Setup yapılmamış. Önce "natureco setup" çalıştırın.\n')); process.exit(1); }
17
+ console.log(chalk.yellow('\n⏳ SMS (Twilio) bağlantısı hazırlanıyor...\n'));
18
+ console.log(chalk.gray('Twilio hesabı gereklidir: https://twilio.com'));
19
+ console.log(chalk.gray('Account SID ve Auth Token\'ı Twilio Console\'dan alın.\n'));
20
+ const answers = await inquirer.prompt([
21
+ { type: 'input', name: 'accountSid', message: 'Twilio Account SID:', validate: v => v.trim() ? true : 'Gerekli' },
22
+ { type: 'password', name: 'authToken', message: 'Twilio Auth Token:', validate: v => v.trim() ? true : 'Gerekli' },
23
+ { type: 'input', name: 'fromNumber', message: 'Twilio telefon numarası (E.164, örn: +15551234567):', validate: v => v.trim() ? true : 'Gerekli' },
24
+ { type: 'input', name: 'publicWebhookUrl', message: 'Webhook URL\'si (opsiyonel, ngrok vb.):' },
25
+ { type: 'list', name: 'dmPolicy', message: 'DM politikası:', choices: [{ name: 'Allowlist (önerilen)', value: 'allowlist' }, { name: 'Pairing', value: 'pairing' }, { name: 'Open', value: 'open' }, { name: 'Disabled', value: 'disabled' }], default: 'allowlist' },
26
+ ]);
27
+ const botId = `sms_${Date.now()}`;
28
+ config.smsAccountSid = answers.accountSid.trim();
29
+ config.smsAuthToken = answers.authToken.trim();
30
+ config.smsFromNumber = answers.fromNumber.trim();
31
+ config.smsPublicWebhookUrl = answers.publicWebhookUrl.trim() || '';
32
+ config.smsDmPolicy = answers.dmPolicy;
33
+ config.smsBotId = botId;
34
+ saveConfig(config);
35
+ console.log(chalk.green('\n✅ SMS (Twilio) bağlantısı kaydedildi!\n'));
36
+ console.log(chalk.cyan('Bot ID:'), chalk.white(botId));
37
+ console.log(chalk.cyan('Account SID:'), chalk.white(answers.accountSid.slice(0, 20) + '...'));
38
+ console.log(chalk.cyan('Numara:'), chalk.white(answers.fromNumber.trim()));
39
+ console.log(chalk.gray('\nNot: SMS webhook\'u için Twilio Console\'da Webhook URL\'si ayarlayın.'));
40
+ console.log(chalk.gray('Gateway ile başlatmak için: natureco gateway start\n'));
41
+ }
42
+
43
+ async function disconnectSms() {
44
+ const config = getConfig();
45
+ if (!config.smsBotId) { console.log(chalk.gray('\n⚠️ No SMS connection found\n')); return; }
46
+ const { confirm } = await inquirer.prompt([{ type: 'confirm', name: 'confirm', message: 'SMS bağlantısını kaldırmak istediğinize emin misiniz?', default: false }]);
47
+ if (!confirm) { console.log(chalk.gray('\nCancelled\n')); return; }
48
+ delete config.smsAccountSid; delete config.smsAuthToken; delete config.smsFromNumber;
49
+ delete config.smsPublicWebhookUrl; delete config.smsDmPolicy; delete config.smsBotId;
50
+ saveConfig(config);
51
+ console.log(chalk.green('\n✅ SMS disconnected\n'));
52
+ }
53
+
54
+ function statusSms() {
55
+ const config = getConfig();
56
+ if (!config.smsBotId) { console.log(chalk.gray('\n⚠️ SMS not connected\n')); console.log(chalk.gray('Connect with: natureco sms connect\n')); return; }
57
+ console.log(chalk.green('\n✅ SMS (Twilio) connected\n'));
58
+ console.log(chalk.cyan('Bot ID:'), chalk.white(config.smsBotId));
59
+ console.log(chalk.cyan('Account SID:'), chalk.white((config.smsAccountSid || '').slice(0, 20) + '...'));
60
+ console.log(chalk.cyan('Numara:'), chalk.white(config.smsFromNumber));
61
+ console.log(chalk.gray('\nDisconnect with: natureco sms disconnect\n'));
62
+ }
63
+
64
+ module.exports = sms;