natureco-cli 5.51.0 → 5.51.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/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  All notable changes to NatureCo CLI will be documented in this file.
4
4
 
5
+ ## [5.51.2] - 2026-07-11 — "account: giriş linki (magic link) desteği"
6
+
7
+ ### Fixed
8
+ - **`natureco account login` OTP akışı, e-posta 6 haneli kod yerine GİRİŞ LİNKİ (magic link) gönderdiğinde de çalışır.** Supabase e-posta şablonu `{{ .Token }}` yerine link gönderiyorsa, gelen linki yapıştırmak yeterli — CLI linkteki `token_hash`+`type`'ı çıkarıp `/verify` ile oturum açar (`verifyLink`). Kod da link de kabul edilir.
9
+
10
+ ## [5.51.1] - 2026-07-10 — "SECURITY: edit_file onay atlaması + kendi-kaynağını-düzenleme kısıtı"
11
+
12
+ ### Security
13
+ - **`edit_file` onay/diff mekanizmasını atlıyordu (kritik).** tool-runner'ın `needsConfirm` kontrolü `write_file` için diff+onay isterken, aynı riski taşıyan hedefli değişiklik aracı `edit_file`'ı kapsamıyordu — SELF.md "kendini onar" protokolü ve Tek Beyin'in kanallara terminal-eşdeğeri araç erişimi ile birleşince, allow-list'teki bir hesaptan (veya prompt injection'dan) gelen mesaj paket kaynak kodunu gözetimsiz değiştirebilirdi. Düzeltmeler:
14
+ 1. `edit_file` onay kapsamına alındı (`needsConfirmation` helper'ı çıkarıldı ve test edilebilir şekilde export edildi); onay ekranında `old_string` → `new_string` hedefli diff'i, dosya yolu ve `replace_all` uyarısı gösterilir.
15
+ 2. **Kendi-kaynağını-düzenleme varsayılan KAPALI** (`src/utils/self-edit-guard.js`): hedef, paket kurulum kökü (veya herhangi bir `node_modules/natureco-cli`) altındaysa `edit_file`/`write_file` reddedilir; bilinçli açma `NATURECO_ALLOW_SELF_EDIT=1` env ya da config `allowSelfEdit: true` ile. Symlink/junction hilesi kapalı (hedef realpath'e çözülür — `~/.natureco/tools` bağlantısı paket içine açılır!). Koruma ARAÇ seviyesinde (v5.43 dersi: allowlist'e güvenme) — hem tool_calls hem agentic yolu kapsar.
16
+ 3. **Kanal kaynaklı çağrılarda koşulsuz red:** `channel-brain` süreci `NATURECO_CHANNEL_ORIGIN=1` işaretler; bayrak/config açık olsa bile mesajlaşma kanalından paket kaynak koduna yazmak HER ZAMAN reddedilir (kanalda interaktif onay gösterilemez). Paket dışı dosyalar (ör. "masaüstüne oyun yap") kanaldan çalışmaya devam eder.
17
+ 4. SELF.md onarma protokolüne yetki notu eklendi (okuma/teşhis her zaman serbest; yazma bilinçli bayrak ister; kanaldan asla).
18
+ - 8 regresyon testi (`test/security/edit-file-approval.test.js`, önce kırmızı→sonra yeşil): onay kapsamı, bayraksız red + dosya değişmedi garantisi, bayrakla açılma, kanal-kaynağında bayrağın yok sayılması, channel-brain'in işareti koyması, paket-dışı dosyaların etkilenmemesi. **638 test yeşil**; canlı E2E: ajan kendi tr-text.js'ini değiştirmeye çalıştı → engellendi, dosya değişmedi, kullanıcıya bilinçli açma yolunu anlattı.
19
+
20
+ ### Added
21
+ - **`natureco account login | logout | whoami` — tek NatureCo hesabı (SSO).** developers.natureco.me API-KEY girişinden (`natureco login`, config.json) AYRIDIR; natureco.me Supabase Auth üstünde kişi kimliği: e-posta+şifre veya e-posta OTP; oturum `~/.natureco/auth.json` (0600), token yenileme. `natureco-sdk` `NatureCoAuth` ile aynı protokol → ekosistem geneli (CLI/terminal/portal) tek hesap. Bağımlılıksız (Supabase REST). Yeni: `src/utils/natureco-account.js`, `src/commands/account.js`.
22
+
5
23
  ## [5.51.0] - 2026-07-10 — "PERF: basit istek 2.698 token — %45 daha ucuz (skill keşfi isteğe bağlı)"
6
24
 
7
25
  ### Changed
package/README.md CHANGED
@@ -576,3 +576,7 @@ MIT © [NatureCo](https://github.com/natureco-official)
576
576
  <p align="center">
577
577
  Made with 🌿 for developers who live in the terminal.
578
578
  </p>
579
+
580
+ ---
581
+
582
+ <sub>Part of the **NatureCo** ecosystem — [natureco.me](https://natureco.me) · NatureCo ekosisteminin parçası</sub>
package/SELF.md CHANGED
@@ -87,6 +87,14 @@
87
87
 
88
88
  ## Kendini onarma protokolü
89
89
 
90
+ > **YETKİ GEREKİR:** Kendi kaynak dosyalarına yazmak varsayılan olarak KAPALIDIR
91
+ > (güvenlik: prompt injection / kanal kaynaklı istekler kodu değiştirememeli).
92
+ > Kullanıcı bilinçli olarak açmalı: `NATURECO_ALLOW_SELF_EDIT=1` env değişkeni
93
+ > ya da config'te `allowSelfEdit: true`. Mesajlaşma kanalından (Telegram vb.)
94
+ > gelen isteklerde bu bayrak açık olsa bile kaynak koda yazmak HER ZAMAN reddedilir
95
+ > — kullanıcıya "bu işlem yalnızca terminalden yapılabilir" de. Okuma/teşhis
96
+ > (read_file, grep_search, node --check) her zaman serbesttir.
97
+
90
98
  1. Belirtiyi netleştir: hangi komut/özellik, hata mesajı ne, `natureco status` çıktısı.
91
99
  2. Bu haritadan ilgili akışı ve dosyayı bul; `read_file` ile oku (önce oku, sonra düzelt).
92
100
  3. `grep_search` ile ilgili fonksiyon/hata metnini kaynakta ara (kurulum kökünde).
package/bin/natureco.js CHANGED
@@ -12,6 +12,7 @@ try { require('../src/utils/update-check').maybeNotify(packageJson.version); } c
12
12
  try { require('../src/utils/builtin-links').ensureBuiltinLinks(); } catch { /* asla komutu bozma */ }
13
13
  const login = require('../src/commands/login');
14
14
  const logout = require('../src/commands/logout');
15
+ const account = require('../src/commands/account');
15
16
  const bots = require('../src/commands/bots');
16
17
  const chat = require('../src/commands/chat');
17
18
  const help = require('../src/commands/help');
@@ -101,6 +102,7 @@ ${chalk.yellow('⚙️ Kurulum & Ayarlar')}
101
102
  ${chalk.cyan('onboard')} Interaktif onboarding (gateway|auth|workspace|channels|skills|health|status)
102
103
  ${chalk.cyan('configure')} Interaktif yapılandırma (gateway|auth|channels|plugins|skills)
103
104
  ${chalk.cyan('login')} API key ile giriş
105
+ ${chalk.cyan('account')} NatureCo hesabı / SSO (login|logout|whoami)
104
106
  ${chalk.cyan('logout')} Çıkış
105
107
  ${chalk.cyan('config')} Ayarlar (get|set|unset|list|file|schema|validate|backups|restore)
106
108
  ${chalk.cyan('doctor')} Sistem teşhis (run|list|check)
@@ -198,6 +200,11 @@ program
198
200
  .description('Login with your NatureCo API key')
199
201
  .action(login);
200
202
 
203
+ program
204
+ .command('account [action]')
205
+ .description('NatureCo hesabı / SSO (login | logout | whoami)')
206
+ .action(account);
207
+
201
208
  program
202
209
  .command('setup [action]')
203
210
  .description('Run initial setup wizard (wizard|config|workspace|dirs|status)')
package/package.json CHANGED
@@ -1,97 +1,97 @@
1
- {
2
- "name": "natureco-cli",
3
- "version": "5.51.0",
4
- "description": "OpenClaw'dan daha güvenli, daha hızlı, daha ucuz AI agent CLI. Multi-agent, self-evolving skills, audit log, maliyet optimizasyonu ve NatureCo platform-native.",
5
- "bin": {
6
- "natureco": "bin/natureco.js"
7
- },
8
- "files": [
9
- "bin/",
10
- "src/",
11
- "scripts/",
12
- "skills/",
13
- "README.md",
14
- "SELF.md",
15
- "CHANGELOG.md",
16
- "AUDIT.md",
17
- "SECURITY_AUDIT_SUMMARY.md",
18
- "DEPLOY_v2.0.0.md"
19
- ],
20
- "scripts": {
21
- "test": "vitest run",
22
- "test:unit": "vitest run",
23
- "test:watch": "vitest",
24
- "test:coverage": "vitest run --coverage",
25
- "lint": "eslint src/ bin/ test/",
26
- "lint:fix": "eslint src/ bin/ test/ --fix",
27
- "lint:errors-only": "eslint src/ bin/ test/ --quiet",
28
- "smoke": "node --check bin/natureco.js && node bin/natureco.js help",
29
- "postinstall": "node scripts/postinstall.js",
30
- "prepublishOnly": "node --check bin/natureco.js && eslint src/ bin/ test/ --quiet && vitest run"
31
- },
32
- "keywords": [
33
- "natureco",
34
- "ai",
35
- "ai-agent",
36
- "bot",
37
- "cli",
38
- "terminal",
39
- "openclaw-alternative",
40
- "claude-code-alternative",
41
- "multi-agent",
42
- "self-evolving-skills",
43
- "audit-log",
44
- "cost-optimization",
45
- "seo",
46
- "türkçe",
47
- "turkish",
48
- "whatsapp-bot",
49
- "telegram-bot",
50
- "discord-bot",
51
- "natureco-me"
52
- ],
53
- "author": {
54
- "name": "Gencay Olgun",
55
- "email": "hello@natureco.me",
56
- "url": "https://natureco.me"
57
- },
58
- "homepage": "https://natureco.me/cli",
59
- "repository": {
60
- "type": "git",
61
- "url": "git+https://github.com/natureco-official/natureco-cli.git"
62
- },
63
- "bugs": {
64
- "url": "https://github.com/natureco-official/natureco-cli/issues"
65
- },
66
- "license": "MIT",
67
- "engines": {
68
- "node": ">=18.0.0"
69
- },
70
- "dependencies": {
71
- "@anthropic-ai/sdk": "^0.105.0",
72
- "@inquirer/prompts": "^8.5.2",
73
- "@slack/web-api": "^7.17.0",
74
- "@whiskeysockets/baileys": "^7.0.0-rc10",
75
- "chalk": "^4.1.2",
76
- "commander": "^11.1.0",
77
- "discord.js": "^14.26.4",
78
- "irc": "^0.5.2",
79
- "mattermost": "^3.4.0",
80
- "node-cron": "^4.2.1",
81
- "node-telegram-bot-api": "^0.67.0",
82
- "openai": "^6.45.0",
83
- "pino": "^8.21.0",
84
- "qrcode": "^1.5.4",
85
- "qrcode-terminal": "^0.12.0",
86
- "semver": "^7.8.1",
87
- "twilio": "^6.0.2",
88
- "ws": "^8.20.0"
89
- },
90
- "devDependencies": {
91
- "@eslint/js": "^10.0.1",
92
- "@vitest/coverage-v8": "^4.1.9",
93
- "eslint": "^10.6.0",
94
- "globals": "^15.15.0",
95
- "vitest": "^4.1.9"
96
- }
97
- }
1
+ {
2
+ "name": "natureco-cli",
3
+ "version": "5.51.2",
4
+ "description": "OpenClaw'dan daha güvenli, daha hızlı, daha ucuz AI agent CLI. Multi-agent, self-evolving skills, audit log, maliyet optimizasyonu ve NatureCo platform-native.",
5
+ "bin": {
6
+ "natureco": "bin/natureco.js"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "src/",
11
+ "scripts/",
12
+ "skills/",
13
+ "README.md",
14
+ "SELF.md",
15
+ "CHANGELOG.md",
16
+ "AUDIT.md",
17
+ "SECURITY_AUDIT_SUMMARY.md",
18
+ "DEPLOY_v2.0.0.md"
19
+ ],
20
+ "scripts": {
21
+ "test": "vitest run",
22
+ "test:unit": "vitest run",
23
+ "test:watch": "vitest",
24
+ "test:coverage": "vitest run --coverage",
25
+ "lint": "eslint src/ bin/ test/",
26
+ "lint:fix": "eslint src/ bin/ test/ --fix",
27
+ "lint:errors-only": "eslint src/ bin/ test/ --quiet",
28
+ "smoke": "node --check bin/natureco.js && node bin/natureco.js help",
29
+ "postinstall": "node scripts/postinstall.js",
30
+ "prepublishOnly": "node --check bin/natureco.js && eslint src/ bin/ test/ --quiet && vitest run"
31
+ },
32
+ "keywords": [
33
+ "natureco",
34
+ "ai",
35
+ "ai-agent",
36
+ "bot",
37
+ "cli",
38
+ "terminal",
39
+ "openclaw-alternative",
40
+ "claude-code-alternative",
41
+ "multi-agent",
42
+ "self-evolving-skills",
43
+ "audit-log",
44
+ "cost-optimization",
45
+ "seo",
46
+ "türkçe",
47
+ "turkish",
48
+ "whatsapp-bot",
49
+ "telegram-bot",
50
+ "discord-bot",
51
+ "natureco-me"
52
+ ],
53
+ "author": {
54
+ "name": "Gencay Olgun",
55
+ "email": "hello@natureco.me",
56
+ "url": "https://natureco.me"
57
+ },
58
+ "homepage": "https://natureco.me/cli",
59
+ "repository": {
60
+ "type": "git",
61
+ "url": "git+https://github.com/natureco-official/natureco-cli.git"
62
+ },
63
+ "bugs": {
64
+ "url": "https://github.com/natureco-official/natureco-cli/issues"
65
+ },
66
+ "license": "MIT",
67
+ "engines": {
68
+ "node": ">=18.0.0"
69
+ },
70
+ "dependencies": {
71
+ "@anthropic-ai/sdk": "^0.105.0",
72
+ "@inquirer/prompts": "^8.5.2",
73
+ "@slack/web-api": "^7.17.0",
74
+ "@whiskeysockets/baileys": "^7.0.0-rc10",
75
+ "chalk": "^4.1.2",
76
+ "commander": "^11.1.0",
77
+ "discord.js": "^14.26.4",
78
+ "irc": "^0.5.2",
79
+ "mattermost": "^3.4.0",
80
+ "node-cron": "^4.2.1",
81
+ "node-telegram-bot-api": "^0.67.0",
82
+ "openai": "^6.45.0",
83
+ "pino": "^8.21.0",
84
+ "qrcode": "^1.5.4",
85
+ "qrcode-terminal": "^0.12.0",
86
+ "semver": "^7.8.1",
87
+ "twilio": "^6.0.2",
88
+ "ws": "^8.20.0"
89
+ },
90
+ "devDependencies": {
91
+ "@eslint/js": "^10.0.1",
92
+ "@vitest/coverage-v8": "^4.1.9",
93
+ "eslint": "^10.6.0",
94
+ "globals": "^15.15.0",
95
+ "vitest": "^4.1.9"
96
+ }
97
+ }
@@ -0,0 +1,106 @@
1
+ const inquirer = require('../utils/inquirer-wrapper');
2
+ const chalk = require('chalk');
3
+ const acc = require('../utils/natureco-account');
4
+
5
+ /**
6
+ * `natureco account [login|logout|whoami]` — tek NatureCo hesabı (SSO).
7
+ * developers.natureco.me API-KEY girişinden (`natureco login`) AYRIDIR:
8
+ * bu, natureco.me hesabınla kişi kimliği; ekosistem geneli paylaşılır.
9
+ */
10
+ async function account(action) {
11
+ const sub = (action || 'whoami').toLowerCase();
12
+ if (sub === 'login' || sub === 'giris' || sub === 'giriş') return doLogin();
13
+ if (sub === 'logout' || sub === 'cikis' || sub === 'çıkış') return doLogout();
14
+ return doWhoami();
15
+ }
16
+
17
+ async function doLogin() {
18
+ console.log('');
19
+ console.log(chalk.green.bold(' (\\_/)'));
20
+ console.log(chalk.green.bold(' (•ᴥ•)'));
21
+ console.log(chalk.green(' />🌿'));
22
+ console.log('');
23
+ console.log(chalk.green.bold(' NatureCo Hesabı — Giriş'));
24
+ console.log(chalk.gray(' natureco.me hesabınla ekosistemin her yerinde tek kimlik.\n'));
25
+ console.log(chalk.gray(' ' + '─'.repeat(48)) + '\n');
26
+
27
+ const { email } = await inquirer.prompt([{
28
+ type: 'input',
29
+ name: 'email',
30
+ message: ' E-posta:',
31
+ validate: (v) => (/.+@.+\..+/.test((v || '').trim()) ? true : 'Geçerli bir e-posta gir'),
32
+ }]);
33
+
34
+ const { method } = await inquirer.prompt([{
35
+ type: 'list',
36
+ name: 'method',
37
+ message: ' Giriş yöntemi:',
38
+ choices: [
39
+ { name: 'Şifre', value: 'password' },
40
+ { name: 'E-postama kod gönder (OTP)', value: 'otp' },
41
+ ],
42
+ }]);
43
+
44
+ try {
45
+ if (method === 'password') {
46
+ const { password } = await inquirer.prompt([{ type: 'password', name: 'password', message: ' Şifre:', mask: '*' }]);
47
+ console.log(chalk.gray('\n Doğrulanıyor...'));
48
+ await acc.loginWithPassword(email.trim(), password);
49
+ } else {
50
+ console.log(chalk.gray('\n Gönderiliyor...'));
51
+ await acc.sendOtp(email.trim());
52
+ console.log(chalk.gray(' ') + chalk.cyan(email.trim()) + chalk.gray(' adresine e-posta gönderildi.'));
53
+ console.log(chalk.gray(' 6 haneli kod geldiyse kodu, giriş linki geldiyse linki yapıştır.'));
54
+ const { token } = await inquirer.prompt([{
55
+ type: 'input', name: 'token', message: ' Kod veya giriş linki:',
56
+ validate: (v) => ((v || '').trim().length >= 6 ? true : 'Kodu ya da linki gir'),
57
+ }]);
58
+ console.log(chalk.gray('\n Doğrulanıyor...'));
59
+ const val = token.trim();
60
+ if (/^https?:\/\//i.test(val) || val.includes('token')) {
61
+ await acc.verifyLink(val);
62
+ } else {
63
+ await acc.verifyOtp(email.trim(), val);
64
+ }
65
+ }
66
+ } catch (err) {
67
+ console.log(chalk.red(`\n ❌ ${err.message || 'Giriş başarısız'}\n`));
68
+ process.exit(1);
69
+ }
70
+
71
+ const me = await acc.whoami();
72
+ console.log(chalk.green('\n ✓ Giriş başarılı!'));
73
+ if (me && me.email) console.log(chalk.gray(' Hoş geldin, ') + chalk.white(me.email));
74
+ console.log(chalk.gray(' Oturum: ~/.natureco/auth.json'));
75
+ console.log('');
76
+ }
77
+
78
+ async function doLogout() {
79
+ if (!acc.isLoggedIn()) {
80
+ console.log(chalk.gray('\n Zaten giriş yapılmamış.\n'));
81
+ return;
82
+ }
83
+ const who = acc.currentEmail();
84
+ acc.logout();
85
+ console.log(chalk.green(`\n ✓ Çıkış yapıldı${who ? ' (' + who + ')' : ''}.\n`));
86
+ }
87
+
88
+ async function doWhoami() {
89
+ if (!acc.isLoggedIn()) {
90
+ console.log(chalk.gray('\n NatureCo hesabına giriş yapılmamış.'));
91
+ console.log(chalk.gray(' Giriş: ') + chalk.cyan('natureco account login') + '\n');
92
+ return;
93
+ }
94
+ console.log(chalk.gray('\n Doğrulanıyor...'));
95
+ const me = await acc.whoami();
96
+ if (!me) {
97
+ console.log(chalk.yellow('\n ⚠ Oturum süresi dolmuş görünüyor. Tekrar giriş yap: ') + chalk.cyan('natureco account login') + '\n');
98
+ return;
99
+ }
100
+ console.log(chalk.green.bold('\n ⬡ NatureCo Hesabı'));
101
+ console.log(chalk.gray(' E-posta: ') + chalk.white(me.email || '-'));
102
+ console.log(chalk.gray(' ID: ') + chalk.gray(me.id || '-'));
103
+ console.log('');
104
+ }
105
+
106
+ module.exports = account;
@@ -50,6 +50,14 @@ async function editFile({ path: filePath, old_string, new_string, replace_all =
50
50
  }
51
51
 
52
52
  const target = _expand(filePath);
53
+
54
+ // GÜVENLİK (v5.51.1): paketin kendi kaynak koduna yazma varsayılan kapalı;
55
+ // kanal kaynaklı çağrılarda koşulsuz red. Bkz. src/utils/self-edit-guard.js
56
+ const guard = require('../utils/self-edit-guard').checkSelfEdit(target);
57
+ if (!guard.allowed) {
58
+ return { success: false, error: guard.error, reason: guard.reason, path: target };
59
+ }
60
+
53
61
  if (!fs.existsSync(target)) {
54
62
  return {
55
63
  success: false,
@@ -27,8 +27,16 @@ module.exports = {
27
27
  rawPath = path.join(require('os').homedir(), rawPath.slice(1));
28
28
  }
29
29
  const filePath = path.resolve(rawPath);
30
+
31
+ // GÜVENLİK (v5.51.1): paketin kendi kaynak koduna yazma varsayılan kapalı;
32
+ // kanal kaynaklı çağrılarda koşulsuz red. Bkz. src/utils/self-edit-guard.js
33
+ const guard = require('../utils/self-edit-guard').checkSelfEdit(filePath);
34
+ if (!guard.allowed) {
35
+ return { success: false, error: guard.error, reason: guard.reason, path: filePath };
36
+ }
37
+
30
38
  const dir = path.dirname(filePath);
31
-
39
+
32
40
  // Create directory if it doesn't exist
33
41
  if (!fs.existsSync(dir)) {
34
42
  fs.mkdirSync(dir, { recursive: true });
@@ -89,6 +89,12 @@ function chunkText(text, maxLen) {
89
89
  * @returns {Promise<string>} bot yaniti ('' donerse gonderilecek bir sey yok)
90
90
  */
91
91
  async function runBrain({ channel, chatKey, text }, deps = {}) {
92
+ // GÜVENLİK (v5.51.1): bu süreçteki araç çağrılarının KANAL kaynaklı olduğunu
93
+ // işaretle — self-edit-guard bunu görünce paket kaynak koduna yazmayı, allow
94
+ // bayrağı açık olsa bile KOŞULSUZ reddeder (kanalda interaktif onay yok).
95
+ // Bilerek geri alınmaz: gateway sürecindeki her çalıştırma kanal kaynaklıdır.
96
+ process.env.NATURECO_CHANNEL_ORIGIN = '1';
97
+
92
98
  const workflow = deps.workflow || require('../tools/workflow');
93
99
  const getConfig = deps.getConfig || require('./config').getConfig;
94
100
  const cfg = getConfig();
@@ -0,0 +1,139 @@
1
+ /**
2
+ * NatureCo Hesabı (SSO) — tek NatureCo hesabı, ekosistem geneli.
3
+ * natureco.me Supabase Auth üstünde, bağımlılıksız (Supabase REST + global fetch).
4
+ * Bu, developers.natureco.me API-KEY girişinden (config.json) AYRIDIR:
5
+ * - API key → bot/otomasyon API'si (config.json)
6
+ * - Hesap → kişi kimliği / SSO (auth.json) ← bu dosya
7
+ * natureco-sdk'daki NatureCoAuth ile aynı protokol (paylaşılan auth.json).
8
+ */
9
+ 'use strict';
10
+
11
+ const fs = require('fs');
12
+ const os = require('os');
13
+ const path = require('path');
14
+
15
+ // natureco.me kimlik projesi — anon key PUBLIC (client'lara gömülür, gizli değil)
16
+ const SUPABASE_URL = process.env.NATURECO_SUPABASE_URL || 'https://mxnlehflfkesasclcldy.supabase.co';
17
+ const SUPABASE_ANON = process.env.NATURECO_SUPABASE_ANON || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im14bmxlaGZsZmtlc2FzY2xjbGR5Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzY2NDA5MzEsImV4cCI6MjA5MjIxNjkzMX0.93aPOg6bVmgFaJvsM5jVZwiX2TTuFIyAzhP6BlhBkGU';
18
+ const AUTH_BASE = `${SUPABASE_URL}/auth/v1`;
19
+
20
+ function authFile() {
21
+ return path.join(os.homedir(), '.natureco', 'auth.json');
22
+ }
23
+
24
+ function loadSession() {
25
+ try { return JSON.parse(fs.readFileSync(authFile(), 'utf8')); } catch (_) { return null; }
26
+ }
27
+
28
+ function saveSession(session) {
29
+ const file = authFile();
30
+ fs.mkdirSync(path.dirname(file), { recursive: true });
31
+ fs.writeFileSync(file, JSON.stringify(session, null, 2));
32
+ try { fs.chmodSync(file, 0o600); } catch (_) {}
33
+ return session;
34
+ }
35
+
36
+ function clearSession() {
37
+ try { fs.unlinkSync(authFile()); } catch (_) {}
38
+ }
39
+
40
+ async function _post(pathname, body, accessToken) {
41
+ const headers = { apikey: SUPABASE_ANON, 'Content-Type': 'application/json' };
42
+ if (accessToken) headers.Authorization = `Bearer ${accessToken}`;
43
+ const res = await fetch(`${AUTH_BASE}${pathname}`, { method: 'POST', headers, body: JSON.stringify(body) });
44
+ const data = await res.json().catch(() => ({}));
45
+ if (!res.ok) {
46
+ const msg = data.error_description || data.msg || data.error || `Auth hatası (${res.status})`;
47
+ const err = new Error(msg); err.statusCode = res.status; throw err;
48
+ }
49
+ return data;
50
+ }
51
+
52
+ function _shape(s) {
53
+ return {
54
+ access_token: s.access_token,
55
+ refresh_token: s.refresh_token,
56
+ token_type: s.token_type || 'bearer',
57
+ expires_at: s.expires_at || (s.expires_in ? Math.floor(Date.now() / 1000) + s.expires_in : null),
58
+ user: s.user ? { id: s.user.id, email: s.user.email } : null,
59
+ };
60
+ }
61
+
62
+ /** E-posta + şifre ile giriş */
63
+ async function loginWithPassword(email, password) {
64
+ return saveSession(_shape(await _post('/token?grant_type=password', { email, password })));
65
+ }
66
+
67
+ /** Şifresiz: e-postaya OTP kodu gönder (mevcut hesap; kayıt açmaz) */
68
+ async function sendOtp(email) {
69
+ await _post('/otp', { email, create_user: false });
70
+ return { sent: true, email };
71
+ }
72
+
73
+ /** OTP kodunu doğrula → oturum */
74
+ async function verifyOtp(email, token) {
75
+ return saveSession(_shape(await _post('/verify', { type: 'email', email, token })));
76
+ }
77
+
78
+ /**
79
+ * E-postadan gelen GİRİŞ LİNKİ'ni doğrula (Supabase şablonu 6 haneli kod yerine
80
+ * magic link gönderdiğinde). Linkteki token_hash + type ile /verify çağrılır.
81
+ */
82
+ async function verifyLink(link) {
83
+ let token_hash, type;
84
+ try {
85
+ const u = new URL(link.trim());
86
+ const q = u.searchParams;
87
+ const frag = new URLSearchParams((u.hash || '').replace(/^#/, ''));
88
+ token_hash = q.get('token_hash') || q.get('token') || frag.get('token_hash') || frag.get('token');
89
+ type = q.get('type') || frag.get('type') || 'magiclink';
90
+ } catch (e) {
91
+ throw new Error('Geçersiz link', { cause: e });
92
+ }
93
+ if (!token_hash) throw new Error("Linkte doğrulama token'ı bulunamadı");
94
+ return saveSession(_shape(await _post('/verify', { type, token_hash })));
95
+ }
96
+
97
+ /** Access token yenile */
98
+ async function refresh() {
99
+ const s = loadSession();
100
+ if (!s || !s.refresh_token) { const e = new Error('Oturum yok'); e.statusCode = 401; throw e; }
101
+ return saveSession(_shape(await _post('/token?grant_type=refresh_token', { refresh_token: s.refresh_token })));
102
+ }
103
+
104
+ /** Geçerli (gerekirse yenilenmiş) access token, yoksa null */
105
+ async function getAccessToken() {
106
+ let s = loadSession();
107
+ if (!s) return null;
108
+ if (s.expires_at && Date.now() / 1000 > s.expires_at - 60) {
109
+ try { s = await refresh(); } catch (_) { return null; }
110
+ }
111
+ return s ? s.access_token : null;
112
+ }
113
+
114
+ /** Giriş yapan kullanıcı ({ id, email, ... }) veya null */
115
+ async function whoami() {
116
+ const token = await getAccessToken();
117
+ if (!token) return null;
118
+ const res = await fetch(`${AUTH_BASE}/user`, { headers: { apikey: SUPABASE_ANON, Authorization: `Bearer ${token}` } });
119
+ if (!res.ok) return null;
120
+ return res.json();
121
+ }
122
+
123
+ function isLoggedIn() {
124
+ const s = loadSession();
125
+ return !!(s && s.access_token);
126
+ }
127
+
128
+ function currentEmail() {
129
+ const s = loadSession();
130
+ return s && s.user ? s.user.email : null;
131
+ }
132
+
133
+ function logout() { clearSession(); }
134
+
135
+ module.exports = {
136
+ loginWithPassword, sendOtp, verifyOtp, verifyLink, refresh,
137
+ getAccessToken, whoami, isLoggedIn, currentEmail, logout,
138
+ SUPABASE_URL,
139
+ };
@@ -0,0 +1,84 @@
1
+ /**
2
+ * self-edit-guard — paketin KENDİ kaynak koduna yazmayı varsayılan olarak kapatır.
3
+ *
4
+ * GÜVENLİK (v5.51.1): SELF.md "kendini onar" protokolü ajana kendi kaynağını
5
+ * düzenlemeyi öğretir; Tek Beyin ise güvenilir mesajlaşma kanallarına terminal
6
+ * eşdeğeri araç erişimi verir. İkisi birleşince, allow-list'teki bir hesaptan
7
+ * (ya da ajanın okuduğu içeriğe gizlenmiş prompt injection'dan) gelen bir mesaj,
8
+ * kurulu paketin kodunu gözetimsiz değiştirebilirdi. Kural:
9
+ *
10
+ * - Hedef paket kurulum kökü altındaysa (veya herhangi bir node_modules/
11
+ * natureco-cli altındaysa) yazma VARSAYILAN OLARAK REDDEDİLİR.
12
+ * - Bilinçli açma: NATURECO_ALLOW_SELF_EDIT=1 env ya da config allowSelfEdit:true.
13
+ * - KANAL KAYNAKLI çağrılarda (NATURECO_CHANNEL_ORIGIN=1; channel-brain koyar)
14
+ * bayrak AÇIK OLSA BİLE koşulsuz red — kanalda interaktif onay gösterilemez.
15
+ *
16
+ * Symlink/junction hilesi kapalıdır: hedef, var olan en derin atası üzerinden
17
+ * realpath'e çözülür (~/.natureco/tools bağlantısı paketin içine açılır!).
18
+ */
19
+
20
+ const fs = require('fs');
21
+ const path = require('path');
22
+
23
+ function packageRoot() {
24
+ try { return fs.realpathSync(path.join(__dirname, '..', '..')); }
25
+ catch { return path.resolve(__dirname, '..', '..'); }
26
+ }
27
+
28
+ // Windows'ta yol karşılaştırması harf-duyarsız olmalı
29
+ function _norm(p) {
30
+ return process.platform === 'win32' ? String(p).toLowerCase() : String(p);
31
+ }
32
+
33
+ // Hedef henüz yoksa bile symlink çözümü yapabilmek için var olan en derin
34
+ // atayı realpath'le, kalan kuyruğu üstüne ekle.
35
+ function realTarget(p) {
36
+ let cur = path.resolve(p);
37
+ const tail = [];
38
+ while (!fs.existsSync(cur)) {
39
+ const parent = path.dirname(cur);
40
+ if (parent === cur) break;
41
+ tail.unshift(path.basename(cur));
42
+ cur = parent;
43
+ }
44
+ try { cur = fs.realpathSync(cur); } catch { /* erişilemedi — çözümsüz devam */ }
45
+ return tail.length ? path.join(cur, ...tail) : cur;
46
+ }
47
+
48
+ function isSelfPath(targetPath) {
49
+ if (!targetPath) return false;
50
+ const real = _norm(realTarget(targetPath));
51
+ const root = _norm(packageRoot());
52
+ if (real === root || real.startsWith(root + path.sep)) return true;
53
+ // Başka bir konumdaki natureco-cli kurulumu da "kendi kaynağı" sayılır
54
+ return /[\\/]node_modules[\\/]natureco-cli([\\/]|$)/i.test(real);
55
+ }
56
+
57
+ /**
58
+ * @returns {{allowed: boolean, error?: string, reason?: string}}
59
+ */
60
+ function checkSelfEdit(targetPath) {
61
+ if (!isSelfPath(targetPath)) return { allowed: true };
62
+
63
+ if (process.env.NATURECO_CHANNEL_ORIGIN === '1') {
64
+ return {
65
+ allowed: false,
66
+ reason: 'self-edit-channel',
67
+ error: 'Güvenlik: natureco-cli kaynak kodu mesajlaşma kanalı üzerinden HİÇBİR KOŞULDA düzenlenemez (interaktif onay gösterilemiyor). Bu işlem yalnızca terminalden, NATURECO_ALLOW_SELF_EDIT=1 ile yapılabilir.',
68
+ };
69
+ }
70
+
71
+ if (process.env.NATURECO_ALLOW_SELF_EDIT === '1') return { allowed: true };
72
+ try {
73
+ const { getConfig } = require('./config');
74
+ if (getConfig().allowSelfEdit === true) return { allowed: true };
75
+ } catch { /* config okunamadı — güvenli tarafta kal (red) */ }
76
+
77
+ return {
78
+ allowed: false,
79
+ reason: 'self-edit-disabled',
80
+ error: 'Güvenlik: hedef, natureco-cli kurulumunun kendi kaynak kodu altında. Kendini-onarma varsayılan olarak kapalıdır; kullanıcı bilinçli olarak açmalı: NATURECO_ALLOW_SELF_EDIT=1 (veya config allowSelfEdit:true). Ayrıntı: paket kökündeki SELF.md.',
81
+ };
82
+ }
83
+
84
+ module.exports = { checkSelfEdit, isSelfPath, _internal: { packageRoot, realTarget } };
@@ -84,6 +84,21 @@ function getToolDefinitions() {
84
84
  }));
85
85
  }
86
86
 
87
+ // ── Onay gerektiren araçlar ───────────────────────────────────────────────────
88
+ // v5.43: shell_command da bash gibi onay tetikler (aksi halde onay/güvenlik
89
+ // akışını atlayan ikinci bir arbitrary-shell yolu olurdu).
90
+ // v5.51.1 GÜVENLİK: edit_file eklendi — write_file diff+onay isterken, aynı riski
91
+ // taşıyan hedefli değişiklik (edit_file) onaysız geçiyordu (SELF.md kendini-onarma
92
+ // + Tek Beyin kanal erişimiyle birleşince kritik).
93
+ function needsConfirmation(toolName, params) {
94
+ const p = params || {};
95
+ return (
96
+ toolName === 'write_file' ||
97
+ toolName === 'edit_file' ||
98
+ ((toolName === 'bash' || toolName === 'shell_command') && /\b(rm|mv|cp|chmod|chown|dd|mkfs|truncate)\b/.test(p.command || ''))
99
+ );
100
+ }
101
+
87
102
  // ── Execute a single tool ─────────────────────────────────────────────────────
88
103
  async function executeTool(toolName, params, opts = {}) {
89
104
  const safeParams = params ?? {};
@@ -97,28 +112,38 @@ async function executeTool(toolName, params, opts = {}) {
97
112
 
98
113
  const label = `${toolName}${safeParams.path ? ' — ' + safeParams.path : safeParams.command ? ' — ' + safeParams.command : ''}`;
99
114
 
100
- // ── Onay mekanizması (write_file ve tehlikeli bash) ───────────────────────
115
+ // ── Onay mekanizması (dosya değiştiren araçlar ve tehlikeli bash) ──────────
101
116
  if (agentMode) {
102
- // v5.43 GÜVENLİK: shell_command da bash gibi onay tetikler (aksi halde onay/güvenlik
103
- // akışını atlayan ikinci bir arbitrary-shell yolu olurdu).
104
- const needsConfirm =
105
- toolName === 'write_file' ||
106
- ((toolName === 'bash' || toolName === 'shell_command') && /\b(rm|mv|cp|chmod|chown|dd|mkfs|truncate)\b/.test(safeParams.command || ''));
107
-
108
- if (needsConfirm) {
117
+ if (needsConfirmation(toolName, safeParams)) {
109
118
  if (toolName === 'write_file') {
110
119
  // Diff göster
111
120
  let oldContent = '';
112
121
  try { oldContent = fs.readFileSync(path.resolve(safeParams.path), 'utf-8'); } catch {}
113
122
  showDiff(oldContent, safeParams.content || '', safeParams.path);
123
+ } else if (toolName === 'edit_file') {
124
+ // v5.51.1 GÜVENLİK: edit_file onay kapsamına alındı — hedefli old→new
125
+ // diff'i göster (write_file'daki diff'in edit_file muadili).
126
+ console.log(chalk.gray(`\n 📄 ${safeParams.path}`));
127
+ for (const line of String(safeParams.old_string || '').split('\n')) {
128
+ console.log(chalk.red(' - ' + line));
129
+ }
130
+ for (const line of String(safeParams.new_string || '').split('\n')) {
131
+ console.log(chalk.green(' + ' + line));
132
+ }
133
+ if (safeParams.replace_all) console.log(chalk.yellow(' (replace_all: TÜM eşleşmeler değişecek)'));
134
+ console.log();
114
135
  } else {
115
136
  console.log(chalk.yellow(`\n 🖥️ Komut: ${chalk.white(safeParams.command)}\n`));
116
137
  }
117
138
 
139
+ const confirmMsg =
140
+ toolName === 'write_file' ? `✏️ ${safeParams.path} dosyası değiştirilecek` :
141
+ toolName === 'edit_file' ? `✏️ ${safeParams.path} dosyasında değişiklik yapılacak` :
142
+ '⚠️ Bu komut çalıştırılacak';
118
143
  const { confirm } = await inquirer.prompt([{
119
144
  type: 'confirm',
120
145
  name: 'confirm',
121
- message: chalk.yellow(` ${toolName === 'write_file' ? `✏️ ${safeParams.path} dosyası değiştirilecek` : '⚠️ Bu komut çalıştırılacak'}. Onaylıyor musun?`),
146
+ message: chalk.yellow(` ${confirmMsg}. Onaylıyor musun?`),
122
147
  default: true,
123
148
  }]);
124
149
 
@@ -137,7 +162,7 @@ async function executeTool(toolName, params, opts = {}) {
137
162
 
138
163
  // İstatistik güncelle
139
164
  if (result.success !== false) {
140
- if (toolName === 'write_file') filesChanged++;
165
+ if (toolName === 'write_file' || toolName === 'edit_file') filesChanged++;
141
166
  if (toolName === 'bash') commandsRun++;
142
167
  }
143
168
 
@@ -185,4 +210,5 @@ module.exports = {
185
210
  executeToolCalls,
186
211
  getSessionStats,
187
212
  resetSessionStats,
213
+ needsConfirmation,
188
214
  };