natureco-cli 5.37.0 → 5.39.0
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 +29 -0
- package/package.json +1 -1
- package/src/tools/agentic-runner.js +8 -2
- package/src/tools/code_execution.js +38 -26
- package/src/tools/git.js +67 -20
- package/src/tools/grep_search.js +60 -48
- package/src/tools/social_open.js +11 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to NatureCo CLI will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [5.39.0] - 2026-07-08 — "CROSS-PLATFORM: grep_search + social_open Windows'ta kırıktı, düzeltildi"
|
|
6
|
+
|
|
7
|
+
Platform-uyumluluk denetimi: 90 aracın 18'i platform-özel. Çekirdek chat/code araçlarının Windows VE macOS'ta çalışması hedeflendi. 2 çekirdek/computer-use aracı saf Windows'ta kırıktı.
|
|
8
|
+
|
|
9
|
+
### 🐛 Cross-platform düzeltmeler
|
|
10
|
+
- **grep_search saf Windows'ta tamamen kırıktı (ÇEKİRDEK araç)**: komut tespiti `spawn('which', ...)` + fallback `spawn('grep', ...)` kullanıyordu — `which` ve `grep` Windows'ta YOK (Git Bash olmadan). Ripgrep kurulu değilse arama hiç çalışmıyordu (bu makinede E2E'de çalışması yalnızca Git Bash kurulu olduğu içindi). Artık: (a) komut tespiti `rg --version` ile (which/where farkını bypass eder), (b) fallback **saf Node.js** dizin-tarama + regex (hiçbir Unix komutu gerekmez, node_modules/.git atlar, glob filtre, ikili-dosya atlama, 2MB sınırı). ripgrep varsa yine hız için kullanılır.
|
|
11
|
+
- **social_open Windows/Linux'ta hard-red**: `if (!IS_MAC) return {error:"sadece macOS"}` → tamamen reddediyordu. Artık platformlar arası URL açma: macOS `open`, Windows `cmd /c start`, Linux `xdg-open`.
|
|
12
|
+
|
|
13
|
+
### ✓ Doğrulanan cross-platform durum (denetim)
|
|
14
|
+
- **Zaten cross-platform**: list_dir/read_file/write_file/edit_file (saf Node fs), http_request (Node http), git (v5.38 execFileSync, git PATH'te standart), code_execution (v5.38 py/python/node fallback), web_search/duckduckgo (Node http).
|
|
15
|
+
- **bash**: `shell:true` = platform-native shell (Windows cmd.exe, *nix sh) — doğru; agent OS'u sysMsg'den bilir (`İşletim sistemi: <platform>`) → platform-uygun komut seçer.
|
|
16
|
+
- **Tasarım gereği macOS-only (8 araç)**: mac_app_open/quit, mac_notify, macos_screenshot, notes/reminder/calendar_add, mac_alarm — osascript tabanlı; Windows'ta net "sadece macOS" mesajı (kullanıcının Mac'inde çalışır).
|
|
17
|
+
|
|
18
|
+
Doğrulama: grep_search Node fallback E2E (tool:node, rg'siz çalıştı); 527 test yeşil (6 yeni: grep-search Node fallback — dizin atlama, glob, maxResults, case-insensitive, geçersiz-regex), ESLint temiz.
|
|
19
|
+
|
|
20
|
+
## [5.38.0] - 2026-07-08 — "FİNAL DOĞRULAMA: git/code_execution/http_request düzeltmeleri + git enjeksiyon açığı kapatıldı"
|
|
21
|
+
|
|
22
|
+
Final doğrulama re-run'inde (agent yolu E2E) 3 fonksiyonel sorun + 1 güvenlik açığı bulundu ve düzeltildi.
|
|
23
|
+
|
|
24
|
+
### 🔒 Güvenlik açığı (kapatıldı)
|
|
25
|
+
- **git args komut enjeksiyonu + remote-yazma bypass (KRİTİK)**: `git` özel aracı `execSync('git log ' + args)` ile STRING komut kuruyordu → `args` içindeki `;`, `&&`, `$()`, backtick shell'de çalışıyordu (ör. `args: "-n1; rm -rf /"`). Ayrıca bu araç, agentic-runner'daki `git remote add`/`git push` bloklarını GÖRMÜYORDU (özel araç, bash guard'ından geçmez) → remote-yazma bypass. Artık `execFileSync` (shell:false) + tırnak-farkındalıklı tokenizer → metakarakterler işlem görmez; `remote add/set-url/remove/rename` engelli (salt-okunur remote serbest). POC: `-n1; echo PWNED` → git argüman sanıp reddetti, echo hiç çalışmadı.
|
|
26
|
+
|
|
27
|
+
### 🐛 Düzeltmeler
|
|
28
|
+
- **git "Unknown operation"**: araç yalnızca `operation` param'i kabul ediyordu; ajanlar `args:"log -n2"` gibi gönderince tanımıyor, bash'e sapıyordu. Artık esnek giriş (operation/args/command'dan parse), `cwd` param'i onurlandırma, env-tabanlı repo bulma (`NATURECO_PROJECT_DIR`/`INIT_CWD`/`PWD` — makineden bağımsız), +5 salt-okunur operasyon (show/remote/tag/describe/rev-parse). E2E: tek çağrıda `git:done`.
|
|
29
|
+
- **code_execution Python bulamıyor**: sabit `python3` Windows'ta App-execution-alias tuzağına düşüyordu. Artık aday-deneme (`py`/`python`/`python3` — *nix'te `python3`/`python`), node için garantili `process.execPath`; yorumlayıcı yoksa net "Python kurulu değil" mesajı. E2E: `12! = 479001600` doğru.
|
|
30
|
+
- **http_request `[object Object]`**: araç JSON gövdeyi PARSED NESNE döndürüyor, `buildFeedback` `String(obj)` yapınca `[object Object]` oluyordu → model değeri okuyamıyordu. Artık nesne alanlar JSON'a serileştirilir. E2E: nodejs/node stargazers_count doğru okundu.
|
|
31
|
+
|
|
32
|
+
Doğrulama: grep/git/http/code_execution ajan üzerinden E2E (hepsi `done`); gerçek PTY 9/9; komut denetimi 23/23; güvenlik 20/20 yıkıcı + 7/7 hassas-dosya + git enjeksiyonu POC kapalı. **521 test yeşil** (12 yeni regresyon: git-tool + buildFeedback nesne-body), ESLint temiz.
|
|
33
|
+
|
|
5
34
|
## [5.37.0] - 2026-07-05 — "GÜVENLİK: 2 açık kapatıldı (inline-eval bypass + hassas dosya erişimi)"
|
|
6
35
|
|
|
7
36
|
Sistematik guvenlik taramasi (POC'larla). 20 yikici komuttan 19'u zaten engelliydi; 2 gercek acik bulundu ve kapatildi.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "natureco-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.39.0",
|
|
4
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
5
|
"bin": {
|
|
6
6
|
"natureco": "bin/natureco.js"
|
|
@@ -278,7 +278,13 @@ function buildFeedback(norm, res) {
|
|
|
278
278
|
let body = '';
|
|
279
279
|
const first = ['content', 'output', 'stdout', 'text', 'transcript', 'body', 'reply', 'answer', 'summary', 'url', 'path'];
|
|
280
280
|
const jsonish = ['results', 'items', 'matches', 'data', 'events', 'entries', 'files'];
|
|
281
|
-
|
|
281
|
+
// v5.38: body/data gibi alanlar nesne olabilir ( or. http_request JSON govdesi) —
|
|
282
|
+
// String(obj) "[object Object]" verir ve model degeri okuyamaz; nesneyse JSON'a cevir.
|
|
283
|
+
for (const k of first) {
|
|
284
|
+
if (res[k] == null) continue;
|
|
285
|
+
const v = typeof res[k] === 'object' ? JSON.stringify(res[k]) : String(res[k]);
|
|
286
|
+
if (v.trim() && v !== '[object Object]') { body = v.slice(0, 2500); break; }
|
|
287
|
+
}
|
|
282
288
|
if (!body) for (const k of jsonish) { if (res[k] != null) { body = JSON.stringify(res[k]).slice(0, 2000); break; } }
|
|
283
289
|
if (!body) {
|
|
284
290
|
// hicbir bilinen alan yok → sonucu oldugu gibi ver (success/error disi anlamli alanlar)
|
|
@@ -436,4 +442,4 @@ async function runAgentic({ callModel, systemPrompt, historyMessages, task, tool
|
|
|
436
442
|
return { records: allRecords, reply: finalReply, iterations };
|
|
437
443
|
}
|
|
438
444
|
|
|
439
|
-
module.exports = { parseAgenticCalls, stripProtocolTokens, executeCall, runAgentic, expandHome, makeStreamFilter, makeSanitizeStream, agentExecAllowed, TOOL_ALIASES, DEFAULT_ALLOWED };
|
|
445
|
+
module.exports = { parseAgenticCalls, stripProtocolTokens, executeCall, runAgentic, expandHome, makeStreamFilter, makeSanitizeStream, agentExecAllowed, buildFeedback, TOOL_ALIASES, DEFAULT_ALLOWED };
|
|
@@ -33,38 +33,50 @@ async function runCode({ code, language = 'auto', timeoutMs = 30000, cwd = null
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
// v5.38: Yorumlayici adaylari — platformlar arasi saglam.
|
|
37
|
+
// node icin process.execPath her zaman mevcut; python icin Windows'ta py/python
|
|
38
|
+
// (python3 App-execution-alias tuzagina duser), *nix'te python3/python.
|
|
39
|
+
const isWin = process.platform === 'win32';
|
|
40
|
+
let candidates;
|
|
41
|
+
if (language === 'python') { candidates = isWin ? ['py', 'python', 'python3'] : ['python3', 'python']; args = ['-c', code]; }
|
|
42
|
+
else if (language === 'node') { candidates = [process.execPath]; args = ['-e', code]; }
|
|
43
|
+
else if (language === 'bash' || language === 'shell') { candidates = isWin ? ['bash'] : ['bash', 'sh']; args = ['-c', code]; }
|
|
39
44
|
else return { success: false, error: `Desteklenmeyen dil: ${language}` };
|
|
40
45
|
|
|
41
|
-
|
|
42
|
-
const proc = spawn(cmd, args, {
|
|
43
|
-
cwd: cwd || os.homedir(),
|
|
44
|
-
timeout: timeoutMs,
|
|
45
|
-
env: { ...process.env, FORCE_COLOR: '0' },
|
|
46
|
-
});
|
|
46
|
+
const truncated = (s) => s.length > 8000 ? s.slice(0, 8000) + '\n... (kesildi, ' + (s.length - 8000) + ' karakter daha)' : s;
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
let stderr = '';
|
|
48
|
+
const spawnOnce = (bin) => new Promise((resolve) => {
|
|
49
|
+
let stdout = '', stderr = '', proc;
|
|
50
|
+
try {
|
|
51
|
+
proc = spawn(bin, args, { cwd: cwd || os.homedir(), timeout: timeoutMs, env: { ...process.env, FORCE_COLOR: '0' } });
|
|
52
|
+
} catch (e) { return resolve({ notFound: true, err: e.message }); }
|
|
50
53
|
proc.stdout.on('data', d => stdout += d.toString());
|
|
51
54
|
proc.stderr.on('data', d => stderr += d.toString());
|
|
52
|
-
|
|
53
|
-
proc.on('close', (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
exitCode: code,
|
|
59
|
-
stdout: truncated(stdout).trim(),
|
|
60
|
-
stderr: truncated(stderr).trim(),
|
|
61
|
-
output: truncated(stdout + (stderr ? '\n[STDERR]: ' + stderr : '')).trim(),
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
proc.on('error', (e) => {
|
|
65
|
-
resolve({ success: false, error: e.message, language });
|
|
66
|
-
});
|
|
55
|
+
proc.on('error', (e) => resolve({ notFound: /ENOENT/i.test(e.message), err: e.message }));
|
|
56
|
+
proc.on('close', (exitCode) => resolve({
|
|
57
|
+
// 9009 (win) / 127 (*nix) veya alias mesaji = yorumlayici yok → sonraki adaya gec
|
|
58
|
+
notFound: exitCode === 9009 || exitCode === 127 || /not found|not recognized|install from the Microsoft Store/i.test(stderr),
|
|
59
|
+
exitCode, stdout, stderr,
|
|
60
|
+
}));
|
|
67
61
|
});
|
|
62
|
+
|
|
63
|
+
let r = { notFound: true };
|
|
64
|
+
for (const bin of candidates) {
|
|
65
|
+
r = await spawnOnce(bin);
|
|
66
|
+
if (!r.notFound) break; // yorumlayici bulundu (basarili ya da kod hatasi) — bunu kullan
|
|
67
|
+
}
|
|
68
|
+
if (r.notFound) {
|
|
69
|
+
const nice = language === 'python' ? 'Python bu sistemde kurulu degil.' : `${language} yorumlayicisi bulunamadi.`;
|
|
70
|
+
return { success: false, language, error: `${nice} (denenen: ${candidates.join(', ')})` };
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
success: r.exitCode === 0,
|
|
74
|
+
language,
|
|
75
|
+
exitCode: r.exitCode,
|
|
76
|
+
stdout: truncated(r.stdout).trim(),
|
|
77
|
+
stderr: truncated(r.stderr).trim(),
|
|
78
|
+
output: truncated(r.stdout + (r.stderr ? '\n[STDERR]: ' + r.stderr : '')).trim(),
|
|
79
|
+
};
|
|
68
80
|
}
|
|
69
81
|
|
|
70
82
|
module.exports = {
|
package/src/tools/git.js
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { execFileSync } = require('child_process');
|
|
2
|
+
|
|
3
|
+
// v5.38 GUVENLIK: args'i shell'e sokmadan token'lara ayir. execFileSync shell:false ile
|
|
4
|
+
// calisir; boylece ";", "&&", "|", "$()", backtick gibi metakarakterler ISLEM GORMEZ —
|
|
5
|
+
// komut enjeksiyonu imkansiz. (Eski execSync `git log ${args}` enjeksiyona aciktir.)
|
|
6
|
+
function tokenizeArgs(s) {
|
|
7
|
+
if (!s) return [];
|
|
8
|
+
const out = [];
|
|
9
|
+
const re = /"([^"]*)"|'([^']*)'|(\S+)/g;
|
|
10
|
+
let m;
|
|
11
|
+
while ((m = re.exec(String(s))) !== null) out.push(m[1] ?? m[2] ?? m[3]);
|
|
12
|
+
return out;
|
|
13
|
+
}
|
|
2
14
|
|
|
3
15
|
module.exports = {
|
|
4
16
|
name: 'git',
|
|
@@ -17,27 +29,56 @@ module.exports = {
|
|
|
17
29
|
required: ['operation']
|
|
18
30
|
},
|
|
19
31
|
|
|
20
|
-
execute({ operation, args = '', message = '' }) {
|
|
32
|
+
execute({ operation, args = '', message = '', command = '', cwd: userCwd = '' }) {
|
|
21
33
|
// v5.6.22: Git repo otomatik bul - this baglami kayboldugu icin module-level helper kullan
|
|
22
|
-
|
|
34
|
+
// v5.38: ajan bir cwd verdiyse ve orada .git varsa onu kullan; yoksa oradan yukari tara.
|
|
35
|
+
const cwd = findGitRepo(userCwd);
|
|
36
|
+
// v5.38: Esnek giris — ajanlar farkli formatlarda gonderebilir (operation yerine args/command
|
|
37
|
+
// icinde "log -n 2" gibi). operation yoksa ilk token'i operation kabul et, gerisi args olur.
|
|
38
|
+
if (!operation && (command || args)) {
|
|
39
|
+
const raw = String(command || args).trim().replace(/^git\s+/i, '');
|
|
40
|
+
const m = raw.match(/^([a-z][a-z-]*)\s*([\s\S]*)$/i);
|
|
41
|
+
if (m) { operation = m[1].toLowerCase(); if (m[2].trim()) args = m[2].trim(); }
|
|
42
|
+
}
|
|
43
|
+
operation = String(operation || '').toLowerCase();
|
|
44
|
+
const tok = tokenizeArgs(args);
|
|
23
45
|
try {
|
|
24
|
-
let
|
|
46
|
+
let gitArgs;
|
|
25
47
|
switch (operation) {
|
|
26
|
-
case 'status':
|
|
27
|
-
case 'diff':
|
|
28
|
-
case 'log':
|
|
29
|
-
case '
|
|
30
|
-
case '
|
|
31
|
-
case '
|
|
32
|
-
|
|
48
|
+
case 'status': gitArgs = ['status', '--short']; break;
|
|
49
|
+
case 'diff': gitArgs = ['diff', ...(tok.length ? tok : ['HEAD'])]; break;
|
|
50
|
+
case 'log': gitArgs = ['log', '--oneline', ...(tok.length ? tok : ['-10'])]; break;
|
|
51
|
+
case 'branch':
|
|
52
|
+
case 'branches': gitArgs = ['branch', '-a']; break;
|
|
53
|
+
case 'add': gitArgs = ['add', ...(tok.length ? tok : ['.'])]; break;
|
|
54
|
+
case 'commit': gitArgs = ['commit', '-m', String(message || '')]; break;
|
|
55
|
+
// v5.38: yaygin salt-okunur alt-komutlar (ajan bunlari da isteyebiliyor)
|
|
56
|
+
case 'show': gitArgs = ['show', ...(tok.length ? tok : ['HEAD'])]; break;
|
|
57
|
+
case 'remote': {
|
|
58
|
+
// GUVENLIK: remote yazma islemleri (add/set-url/remove/rename) engelli — push
|
|
59
|
+
// zeminini ve exec-allowlist bypass'ini kapatir. Sadece salt-okunur.
|
|
60
|
+
const sub = (tok[0] || '').toLowerCase();
|
|
61
|
+
if (['add', 'set-url', 'remove', 'rm', 'rename', 'prune', 'set-head', 'set-branches'].includes(sub)) {
|
|
62
|
+
return { success: false, error: `git remote "${sub}" engellendi (guvenlik: remote yazma yasak)` };
|
|
63
|
+
}
|
|
64
|
+
gitArgs = ['remote', ...(tok.length ? tok : ['-v'])]; break;
|
|
65
|
+
}
|
|
66
|
+
case 'tag': gitArgs = ['tag', ...tok]; break;
|
|
67
|
+
case 'describe': gitArgs = ['describe', ...(tok.length ? tok : ['--tags', '--always'])]; break;
|
|
68
|
+
case 'rev-parse': gitArgs = ['rev-parse', ...(tok.length ? tok : ['HEAD'])]; break;
|
|
69
|
+
default: return { success: false, error: `Bilinmeyen git islemi: "${operation}". Gecerli: status, diff, log, branch, add, commit, show, remote, tag, describe, rev-parse` };
|
|
33
70
|
}
|
|
34
|
-
|
|
35
|
-
|
|
71
|
+
// shell:false (execFile default) → args ne olursa olsun shell yorumlamaz.
|
|
72
|
+
const output = execFileSync('git', gitArgs, { cwd, stdio: 'pipe', encoding: 'utf8' });
|
|
73
|
+
return { success: true, output: (output || '').trim() };
|
|
36
74
|
} catch (err) {
|
|
37
75
|
return { success: false, error: err.stderr?.toString() || err.message };
|
|
38
76
|
}
|
|
39
77
|
},
|
|
40
78
|
|
|
79
|
+
// v5.38: test icin — shell'e gitmeden token'lara ayirma davranisini dogrulamak icin
|
|
80
|
+
_tokenizeArgs: tokenizeArgs,
|
|
81
|
+
|
|
41
82
|
/**
|
|
42
83
|
* v5.6.22: Git repo bul - cwd'de yoksa ~/Projects ve parent dizinleri tara
|
|
43
84
|
*/
|
|
@@ -93,24 +134,30 @@ module.exports = {
|
|
|
93
134
|
/**
|
|
94
135
|
* Git repo bul - cwd'de yoksa ~/Projects ve parent dizinleri tara
|
|
95
136
|
*/
|
|
96
|
-
function findGitRepo() {
|
|
137
|
+
function findGitRepo(startDir = '') {
|
|
97
138
|
const fs = require('fs');
|
|
98
139
|
const path = require('path');
|
|
99
140
|
const os = require('os');
|
|
100
141
|
|
|
101
|
-
|
|
102
|
-
|
|
142
|
+
// v5.38: ajanin verdigi cwd oncelikli — orada .git varsa onu, yoksa oradan yukari tara.
|
|
143
|
+
const seed = startDir && fs.existsSync(startDir) ? startDir : process.cwd();
|
|
144
|
+
if (fs.existsSync(path.join(seed, '.git'))) {
|
|
145
|
+
return seed;
|
|
103
146
|
}
|
|
104
147
|
|
|
105
148
|
const home = os.homedir();
|
|
106
149
|
const candidates = [
|
|
150
|
+
// v5.38: makineden bagimsiz — env ipuclari once (repo dizini disindan calisilsa bile bulunur)
|
|
151
|
+
process.env.NATURECO_PROJECT_DIR,
|
|
152
|
+
process.env.INIT_CWD,
|
|
153
|
+
process.env.PWD,
|
|
107
154
|
path.join(home, 'Projects', 'natureco-cli'),
|
|
108
155
|
path.join(home, 'Projects'),
|
|
109
156
|
path.join(home, 'projects'),
|
|
110
157
|
path.join(home, 'code'),
|
|
111
158
|
path.join(home, 'dev'),
|
|
112
159
|
path.join(home, 'src'),
|
|
113
|
-
];
|
|
160
|
+
].filter(Boolean);
|
|
114
161
|
|
|
115
162
|
for (const dir of candidates) {
|
|
116
163
|
try {
|
|
@@ -120,8 +167,8 @@ function findGitRepo() {
|
|
|
120
167
|
} catch {}
|
|
121
168
|
}
|
|
122
169
|
|
|
123
|
-
let current =
|
|
124
|
-
for (let i = 0; i <
|
|
170
|
+
let current = seed;
|
|
171
|
+
for (let i = 0; i < 6; i++) {
|
|
125
172
|
const parent = path.dirname(current);
|
|
126
173
|
if (parent === current) break;
|
|
127
174
|
try {
|
|
@@ -132,5 +179,5 @@ function findGitRepo() {
|
|
|
132
179
|
current = parent;
|
|
133
180
|
}
|
|
134
181
|
|
|
135
|
-
return
|
|
182
|
+
return seed;
|
|
136
183
|
}
|
package/src/tools/grep_search.js
CHANGED
|
@@ -36,23 +36,75 @@ async function grepSearch({ pattern, path: searchPath = null, caseSensitive = fa
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
// ripgrep varsa onu kullan, yoksa fallback
|
|
39
|
+
// ripgrep varsa onu kullan (hizli), yoksa SAF NODE fallback.
|
|
40
|
+
// v5.39: eski fallback `grep` komutuydu → Windows'ta yok (Git Bash gerekir).
|
|
41
|
+
// Artik hicbir Unix komutuna bagimli degil: rg opsiyonel hizlandirma.
|
|
40
42
|
const useRipgrep = await checkCommand('rg');
|
|
41
43
|
|
|
42
44
|
if (useRipgrep) {
|
|
43
45
|
return await grepWithRipgrep(pattern, cwd, caseSensitive, includePattern, maxResults);
|
|
44
46
|
}
|
|
45
|
-
return
|
|
47
|
+
return grepWithNode(pattern, cwd, caseSensitive, includePattern, maxResults);
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
async function checkCommand(cmd) {
|
|
51
|
+
// v5.39: `which`/`where` platform-farkini bypass et — komutu dogrudan --version ile
|
|
52
|
+
// dene. PATH'te varsa 0 doner. (which Windows'ta yok, where *nix'te yok.)
|
|
49
53
|
return new Promise((resolve) => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
let done = false;
|
|
55
|
+
const fin = (v) => { if (!done) { done = true; resolve(v); } };
|
|
56
|
+
try {
|
|
57
|
+
const proc = spawn(cmd, ['--version'], { stdio: 'ignore' });
|
|
58
|
+
proc.on('close', code => fin(code === 0));
|
|
59
|
+
proc.on('error', () => fin(false));
|
|
60
|
+
} catch { fin(false); }
|
|
53
61
|
});
|
|
54
62
|
}
|
|
55
63
|
|
|
64
|
+
// v5.39: SAF NODE icerik aramasi — platformdan ve harici komuttan bagimsiz.
|
|
65
|
+
function grepWithNode(pattern, cwd, caseSensitive, includePattern, maxResults) {
|
|
66
|
+
const results = [];
|
|
67
|
+
let re;
|
|
68
|
+
const flags = caseSensitive ? 'g' : 'gi';
|
|
69
|
+
try { re = new RegExp(pattern, flags); }
|
|
70
|
+
catch { re = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), flags); }
|
|
71
|
+
let globRe = null;
|
|
72
|
+
if (includePattern) {
|
|
73
|
+
const esc = includePattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.');
|
|
74
|
+
globRe = new RegExp('^' + esc + '$', 'i');
|
|
75
|
+
}
|
|
76
|
+
const IGNORE = new Set(['node_modules', '.git', 'dist', 'build', '.next', 'coverage', '.cache', '.turbo']);
|
|
77
|
+
const walk = (dir) => {
|
|
78
|
+
if (results.length >= maxResults) return;
|
|
79
|
+
let entries;
|
|
80
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
81
|
+
for (const e of entries) {
|
|
82
|
+
if (results.length >= maxResults) break;
|
|
83
|
+
const full = path.join(dir, e.name);
|
|
84
|
+
if (e.isDirectory()) { if (!IGNORE.has(e.name)) walk(full); continue; }
|
|
85
|
+
if (!e.isFile()) continue;
|
|
86
|
+
if (globRe && !globRe.test(e.name)) continue;
|
|
87
|
+
let content;
|
|
88
|
+
try {
|
|
89
|
+
if (fs.statSync(full).size > 2 * 1024 * 1024) continue; // 2MB üstü atla
|
|
90
|
+
content = fs.readFileSync(full, 'utf8');
|
|
91
|
+
} catch { continue; }
|
|
92
|
+
if (content.includes('\x00')) continue; // ikili dosya (null byte)
|
|
93
|
+
const lines = content.split(/\r?\n/);
|
|
94
|
+
for (let i = 0; i < lines.length; i++) {
|
|
95
|
+
re.lastIndex = 0;
|
|
96
|
+
if (re.test(lines[i])) {
|
|
97
|
+
results.push({ file: full, line: i + 1, text: lines[i].trim().slice(0, 300) });
|
|
98
|
+
if (results.length >= maxResults) break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const start = (fs.existsSync(cwd) && fs.statSync(cwd).isFile()) ? path.dirname(cwd) : cwd;
|
|
104
|
+
walk(start);
|
|
105
|
+
return { success: true, pattern, tool: 'node', count: results.length, results };
|
|
106
|
+
}
|
|
107
|
+
|
|
56
108
|
async function grepWithRipgrep(pattern, cwd, caseSensitive, includePattern, maxResults) {
|
|
57
109
|
return new Promise((resolve) => {
|
|
58
110
|
const args = [
|
|
@@ -125,49 +177,6 @@ async function grepWithRipgrep(pattern, cwd, caseSensitive, includePattern, maxR
|
|
|
125
177
|
});
|
|
126
178
|
}
|
|
127
179
|
|
|
128
|
-
async function grepWithFallback(pattern, cwd, caseSensitive, maxResults) {
|
|
129
|
-
return new Promise((resolve) => {
|
|
130
|
-
const args = ['-r', '-n', caseSensitive ? '' : '-i'];
|
|
131
|
-
if (process.platform === 'darwin') args.push('-E');
|
|
132
|
-
else args.push('-E');
|
|
133
|
-
args.push(pattern);
|
|
134
|
-
args.push(cwd);
|
|
135
|
-
|
|
136
|
-
const proc = spawn('grep', args);
|
|
137
|
-
let stdout = '';
|
|
138
|
-
let stderr = '';
|
|
139
|
-
// v5.6.32: Memory taşmasını önle - max 5MB output
|
|
140
|
-
let stdoutBytes = 0;
|
|
141
|
-
const MAX_OUTPUT = 5 * 1024 * 1024; // 5MB
|
|
142
|
-
let truncated = false;
|
|
143
|
-
const addStdout = (d) => {
|
|
144
|
-
if (truncated) return;
|
|
145
|
-
stdoutBytes += d.length;
|
|
146
|
-
if (stdoutBytes > MAX_OUTPUT) {
|
|
147
|
-
truncated = true;
|
|
148
|
-
stdout += '\n[OUTPUT TRUNCATED - exceeded 5MB limit]';
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
stdout += d.toString();
|
|
152
|
-
};
|
|
153
|
-
proc.stdout.on('data', addStdout);
|
|
154
|
-
|
|
155
|
-
proc.on('close', (code) => {
|
|
156
|
-
const results = [];
|
|
157
|
-
const lines = stdout.split('\n').filter(Boolean);
|
|
158
|
-
for (const line of lines) {
|
|
159
|
-
if (results.length >= maxResults) break;
|
|
160
|
-
const match = line.match(/^(.+?):(\d+):(.*)$/);
|
|
161
|
-
if (match) {
|
|
162
|
-
results.push({ file: match[1], line: parseInt(match[2]), text: match[3] });
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
resolve({ success: true, pattern, tool: 'grep', count: results.length, results });
|
|
166
|
-
});
|
|
167
|
-
proc.on('error', (e) => resolve({ success: false, error: e.message }));
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
180
|
module.exports = {
|
|
172
181
|
name: 'grep_search',
|
|
173
182
|
description: 'Dosya içeriklerinde pattern ara (ripgrep veya grep). Örn: pattern="TODO", path="~/projects".',
|
|
@@ -185,4 +194,7 @@ module.exports = {
|
|
|
185
194
|
async execute(params) {
|
|
186
195
|
return await grepSearch(params);
|
|
187
196
|
},
|
|
197
|
+
|
|
198
|
+
// v5.39: test icin — saf Node fallback'i rg'den bagimsiz dogrulamak icin
|
|
199
|
+
_grepWithNode: grepWithNode,
|
|
188
200
|
};
|
package/src/tools/social_open.js
CHANGED
|
@@ -124,9 +124,14 @@ function detectPlatform(input) {
|
|
|
124
124
|
};
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
// v5.39: platformlar arası URL açma — macOS `open`, Windows `start`, Linux `xdg-open`.
|
|
128
|
+
function openUrlProc(url, browserApp) {
|
|
129
|
+
if (IS_MAC) return spawn("open", browserApp ? ["-a", browserApp, url] : [url]);
|
|
130
|
+
if (process.platform === "win32") return spawn("cmd", ["/c", "start", "", url], { windowsHide: true });
|
|
131
|
+
return spawn("xdg-open", [url]); // linux + diğer *nix
|
|
132
|
+
}
|
|
129
133
|
|
|
134
|
+
async function socialOpen(params) {
|
|
130
135
|
const { query, platform, username } = params;
|
|
131
136
|
|
|
132
137
|
if (!query && !platform && !username) {
|
|
@@ -146,20 +151,19 @@ async function socialOpen(params) {
|
|
|
146
151
|
note = detected.note;
|
|
147
152
|
}
|
|
148
153
|
|
|
149
|
-
const browser = getOpenBrowser();
|
|
154
|
+
const browser = IS_MAC ? getOpenBrowser() : null; // pgrep sadece macOS
|
|
150
155
|
return new Promise((resolve) => {
|
|
151
|
-
const
|
|
152
|
-
const proc = spawn("open", args);
|
|
156
|
+
const proc = openUrlProc(url, browser);
|
|
153
157
|
proc.on("close", (code) => {
|
|
154
158
|
if (code === 0) {
|
|
155
159
|
resolve({
|
|
156
160
|
success: true,
|
|
157
161
|
message: browser
|
|
158
162
|
? `${browser}'da yeni sekmede açıldı`
|
|
159
|
-
: "
|
|
163
|
+
: "Varsayılan tarayıcıda açıldı",
|
|
160
164
|
platform: platformName,
|
|
161
165
|
url,
|
|
162
|
-
browser: browser || "
|
|
166
|
+
browser: browser || "default",
|
|
163
167
|
...(note ? { note } : {}),
|
|
164
168
|
});
|
|
165
169
|
} else {
|