natureco-cli 5.0.0 → 5.1.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/bin/natureco.js CHANGED
@@ -221,13 +221,19 @@ program
221
221
 
222
222
  program
223
223
  .command('code [file]')
224
- .description('Agentic coding modu — dosya oku, değiştir, komut çalıştır')
224
+ .description('Agentic coding modu v5 Claude Code alternative (47 tools, TUI, auto tool selection)')
225
225
  .option('--dir <path>', 'çalışma dizini', process.cwd())
226
226
  .option('--no-stream', 'streaming devre dışı')
227
227
  .option('--dry-run', 'değişiklikleri göster ama uygulama')
228
+ .option('--legacy', 'v2.23 eski code agent kullan')
228
229
  .action((file, options) => {
229
- const codeCmd = require('../src/commands/code');
230
- codeCmd(file, options);
230
+ if (options.legacy) {
231
+ const codeCmd = require('../src/commands/code');
232
+ codeCmd(file, options);
233
+ } else {
234
+ const codeV5 = require('../src/commands/code_v5');
235
+ codeV5(options.dir || (file ? path.dirname(path.resolve(file)) : null));
236
+ }
231
237
  });
232
238
 
233
239
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "natureco-cli",
3
- "version": "5.0.0",
3
+ "version": "5.1.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"
@@ -0,0 +1,279 @@
1
+ /**
2
+ * natureco code v5 — Claude Code alternatif (v5.0)
3
+ *
4
+ * TUI engine + 47 tool + auto tool selection ile modern code agent.
5
+ * v2.23'ün code.js'inin yerini alir.
6
+ *
7
+ * Ozellikler:
8
+ * - TUI welcome card (round border, status pill)
9
+ * - 47 tool otomatik yuklu (OpenAI function calling)
10
+ * - Multi-turn tool execution (max 10 iter)
11
+ * - Auto-context: dosya/klasor tarama (proje baglami)
12
+ * - Token tracking
13
+ * - Approval prompt (write_file, bash, dangerous komutlar)
14
+ */
15
+
16
+ const fs = require("fs");
17
+ const path = require("path");
18
+ const os = require("os");
19
+ const readline = require("readline");
20
+ const chalk = require("chalk");
21
+ const tui = require("../utils/tui");
22
+ const { getConfig } = require("../utils/config");
23
+ const { loadToolDefinitions, executeTool } = require("../utils/tools");
24
+
25
+ const IS_MAC = os.platform() === "darwin";
26
+ const MAX_ITERATIONS = 10;
27
+
28
+ function isMiniMax(url) {
29
+ return url && (url.includes("minimax.io") || url.includes("minimaxi.com"));
30
+ }
31
+
32
+ async function apiRequest(url, key, body) {
33
+ const https = require("https");
34
+ return new Promise((resolve, reject) => {
35
+ const isMM = isMiniMax(url);
36
+ const endpoint = isMM
37
+ ? url.replace(/\/+$/, "") + "/v1/text/chatcompletion_v2"
38
+ : url.replace(/\/+$/, "") + "/chat/completions";
39
+ const data = JSON.stringify(body);
40
+ const req = https.request(endpoint, {
41
+ method: "POST",
42
+ headers: { "Authorization": "Bearer " + key, "Content-Type": "application/json" },
43
+ timeout: 60000,
44
+ }, res => {
45
+ let body = "";
46
+ res.on("data", c => body += c);
47
+ res.on("end", () => {
48
+ if (res.statusCode === 200) {
49
+ try { resolve(JSON.parse(body)); } catch (e) { reject(new Error("Parse hatasi")); }
50
+ } else reject(new Error("HTTP " + res.statusCode + ": " + body.slice(0, 200)));
51
+ });
52
+ });
53
+ req.on("error", reject);
54
+ req.on("timeout", () => req.destroy() && reject(new Error("Timeout")));
55
+ req.write(data);
56
+ req.end();
57
+ });
58
+ }
59
+
60
+ async function sendMessageWithTools(providerUrl, providerKey, model, messages, toolDefs) {
61
+ const isMM = isMiniMax(providerUrl);
62
+ const tools = toolDefs.map(t => ({
63
+ type: "function",
64
+ function: { name: t.name, description: t.description, parameters: t.parameters },
65
+ }));
66
+ const body = {
67
+ model,
68
+ messages,
69
+ temperature: 0.3,
70
+ max_tokens: 4096,
71
+ stream: false,
72
+ };
73
+ if (tools.length > 0) {
74
+ body.tools = tools;
75
+ body.tool_choice = "auto";
76
+ }
77
+ const res = await apiRequest(providerUrl, providerKey, body);
78
+ return res.choices?.[0]?.message || {};
79
+ }
80
+
81
+ function confirm(prompt) {
82
+ return new Promise(resolve => {
83
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
84
+ rl.question(chalk.yellow("\n ⚠ " + prompt + "\n Devam edilsin mi? (Y/n) "), ans => {
85
+ rl.close();
86
+ resolve(!ans || ans.toLowerCase().startsWith("y"));
87
+ });
88
+ });
89
+ }
90
+
91
+ function printToolCall(name, args, result) {
92
+ const argsStr = JSON.stringify(args).slice(0, 100);
93
+ console.log("\n " + tui.styled(" 🔧 Tool: " + name, { color: tui.PALETTE.accent, bold: true }));
94
+ console.log(" " + tui.styled(" Args: " + argsStr, { color: tui.PALETTE.muted }));
95
+ if (result) {
96
+ if (result.error) {
97
+ console.log(" " + tui.styled(" ✗ Hata: " + result.error.slice(0, 200), { color: tui.PALETTE.danger }));
98
+ } else if (result.success !== false) {
99
+ const out = typeof result === "string" ? result.slice(0, 200) : JSON.stringify(result).slice(0, 200);
100
+ console.log(" " + tui.styled(" ✓ Sonuç: " + out, { color: tui.PALETTE.success }));
101
+ }
102
+ }
103
+ }
104
+
105
+ function scanProject(cwd) {
106
+ try {
107
+ const files = fs.readdirSync(cwd, { withFileTypes: true });
108
+ const summary = {
109
+ totalFiles: 0,
110
+ dirs: [],
111
+ configFiles: [],
112
+ readme: null,
113
+ };
114
+ for (const f of files) {
115
+ if (f.name.startsWith(".") || f.name === "node_modules") continue;
116
+ if (f.isDirectory()) summary.dirs.push(f.name);
117
+ else {
118
+ summary.totalFiles++;
119
+ if (f.name === "package.json" || f.name === "Cargo.toml" || f.name === "go.mod" || f.name.endsWith(".lock")) {
120
+ summary.configFiles.push(f.name);
121
+ }
122
+ if (f.name.toLowerCase() === "readme.md") summary.readme = f.name;
123
+ }
124
+ }
125
+ return summary;
126
+ } catch {
127
+ return null;
128
+ }
129
+ }
130
+
131
+ async function codeV5(targetPath) {
132
+ const config = getConfig();
133
+ if (!config.providerUrl || !config.providerApiKey) {
134
+ console.log(tui.C.red("\n ❌ Provider ayarlı değil. Önce: natureco setup\n"));
135
+ return;
136
+ }
137
+
138
+ const cwd = targetPath ? path.resolve(targetPath) : process.cwd();
139
+ const projectCtx = scanProject(cwd);
140
+ const toolDefs = loadToolDefinitions();
141
+
142
+ // Header / Welcome
143
+ console.log("");
144
+ console.log(tui.styled(" ╭" + "─".repeat(58) + "╮", { color: tui.PALETTE.primary }));
145
+ console.log(tui.styled(" │", { color: tui.PALETTE.primary }) + " ⚡ " + tui.styled("NatureCo Code Agent v5", { color: tui.PALETTE.primary, bold: true }) + tui.C.muted(" Claude Code alternative") + "".padEnd(8) + tui.styled(" │", { color: tui.PALETTE.primary }));
146
+ console.log(tui.styled(" │", { color: tui.PALETTE.primary }) + tui.C.muted(" Proje: ") + tui.styled(cwd.padEnd(47), { color: tui.PALETTE.text }) + tui.styled(" │", { color: tui.PALETTE.primary }));
147
+ console.log(tui.styled(" │", { color: tui.PALETTE.primary }) + tui.C.muted(" Model: ") + tui.styled((config.providerModel || "—").padEnd(47), { color: tui.PALETTE.text }) + tui.styled(" │", { color: tui.PALETTE.primary }));
148
+ console.log(tui.styled(" │", { color: tui.PALETTE.primary }) + tui.C.muted(" Tools: ") + tui.styled(String(toolDefs.length).padEnd(47), { color: tui.PALETTE.success, bold: true }) + tui.styled(" │", { color: tui.PALETTE.primary }));
149
+ console.log(tui.styled(" ╰" + "─".repeat(58) + "╯", { color: tui.PALETTE.primary }));
150
+
151
+ // Project context
152
+ if (projectCtx) {
153
+ console.log("\n " + tui.C.muted("📂 Proje bağlamı:"));
154
+ if (projectCtx.readme) console.log(" " + tui.C.muted("• README: ") + tui.C.text(projectCtx.readme));
155
+ if (projectCtx.configFiles.length) {
156
+ console.log(" " + tui.C.muted("• Config: ") + tui.C.text(projectCtx.configFiles.join(", ")));
157
+ }
158
+ if (projectCtx.dirs.length) {
159
+ console.log(" " + tui.C.muted("• Klasörler: ") + tui.C.text(projectCtx.dirs.slice(0, 8).join(", ")));
160
+ }
161
+ console.log(" " + tui.C.muted("• Toplam dosya: ") + tui.C.text(String(projectCtx.totalFiles)));
162
+ }
163
+
164
+ console.log("\n " + tui.C.muted("Komutlar:"));
165
+ console.log(" " + tui.C.amber("/summary") + tui.C.muted(" — Oturum özeti"));
166
+ console.log(" " + tui.C.amber("/done") + tui.C.muted(" — Özet + çıkış"));
167
+ console.log(" " + tui.C.amber("Ctrl+C") + tui.C.muted(" — Çıkış"));
168
+ console.log("");
169
+
170
+ // System prompt
171
+ const systemPrompt = [
172
+ "Sen NatureCo Code Agent v5'sin — Claude Code alternatifi bir terminal AI asistanısın.",
173
+ "47 tool'un var: dosya okuma/yazma, bash, Python, web search, macOS native, image generation, TTS, vs.",
174
+ "Kullanıcının isteğini analiz et, uygun tool'ları kullan, sonuçları özetle.",
175
+ "Türkçe cevap ver.",
176
+ "Kullanıcıdan onay almadan dosya yazma veya tehlikeli komut çalıştırma.",
177
+ ].join(" ");
178
+
179
+ let messages = [{ role: "system", content: systemPrompt }];
180
+ let totalIn = 0, totalOut = 0;
181
+ let filesChanged = 0, commandsRun = 0;
182
+ const startTime = Date.now();
183
+
184
+ // Input loop
185
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
186
+ process.stdout.write("\n " + tui.styled("You ", { color: tui.PALETTE.primary, bold: true }));
187
+
188
+ const ask = () => {
189
+ rl.question("", async (input) => {
190
+ input = input.trim();
191
+ if (!input) { process.stdout.write(" " + tui.styled("You ", { color: tui.PALETTE.primary, bold: true })); return ask(); }
192
+ if (input === "/summary" || input === "/done") {
193
+ printSummary(filesChanged, commandsRun, messages.length - 1, startTime);
194
+ rl.close();
195
+ return;
196
+ }
197
+ if (input === "exit" || input === "quit") { rl.close(); return; }
198
+
199
+ messages.push({ role: "user", content: input });
200
+ process.stdout.write("\n " + tui.styled("AI ", { color: tui.PALETTE.secondary, bold: true }));
201
+
202
+ // Multi-turn tool loop
203
+ let iter = 0;
204
+ while (iter < MAX_ITERATIONS) {
205
+ iter++;
206
+ try {
207
+ const reply = await sendMessageWithTools(
208
+ config.providerUrl, config.providerApiKey, config.providerModel,
209
+ messages, toolDefs
210
+ );
211
+ if (reply.content) {
212
+ process.stdout.write(reply.content);
213
+ messages.push({ role: "assistant", content: reply.content });
214
+ totalOut += Math.ceil(reply.content.length / 4);
215
+ }
216
+
217
+ if (reply.tool_calls && reply.tool_calls.length > 0) {
218
+ // Approval for dangerous tools
219
+ let approved = true;
220
+ const dangerous = ["bash", "write_file", "edit_file", "mac_app_quit"];
221
+ for (const tc of reply.tool_calls) {
222
+ if (dangerous.includes(tc.function.name)) {
223
+ process.stdout.write("\n");
224
+ approved = await confirm(tc.function.name + " çalıştırılsın mı?");
225
+ if (!approved) break;
226
+ }
227
+ }
228
+ if (!approved) {
229
+ console.log("\n " + tui.C.yellow("⚠ Kullanıcı onayı iptal etti, tool çalıştırılmadı."));
230
+ break;
231
+ }
232
+
233
+ messages.push({ role: "assistant", content: reply.content || null, tool_calls: reply.tool_calls });
234
+ for (const tc of reply.tool_calls) {
235
+ const name = tc.function.name;
236
+ let args = {};
237
+ try { args = JSON.parse(tc.function.arguments || "{}"); } catch {}
238
+ printToolCall(name, args);
239
+ if (name === "bash" || name === "shell_command") commandsRun++;
240
+ if (name === "write_file" || name === "edit_file") filesChanged++;
241
+ const result = await executeTool(name, args, toolDefs);
242
+ printToolCall(name, args, result);
243
+ const out = result.error
244
+ ? "ERROR: " + result.error
245
+ : (typeof result.result === "string" ? result.result : JSON.stringify(result.result));
246
+ messages.push({ role: "tool", tool_call_id: tc.id, content: (out || "(empty)").slice(0, 8000) });
247
+ totalIn += Math.ceil(out.length / 4);
248
+ }
249
+ process.stdout.write("\n " + tui.styled("AI ", { color: tui.PALETTE.secondary, bold: true }));
250
+ continue;
251
+ }
252
+ break;
253
+ } catch (e) {
254
+ console.log("\n " + tui.C.red("❌ " + e.message));
255
+ break;
256
+ }
257
+ }
258
+
259
+ totalIn += Math.ceil(input.length / 4);
260
+ process.stdout.write("\n\n " + tui.styled("You ", { color: tui.PALETTE.primary, bold: true }));
261
+ ask();
262
+ });
263
+ };
264
+ ask();
265
+ }
266
+
267
+ function printSummary(files, cmds, msgs, startTime) {
268
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
269
+ const min = Math.floor(elapsed / 60);
270
+ const sec = elapsed % 60;
271
+ console.log("\n " + tui.styled("─── Session Özeti ───", { color: tui.PALETTE.primary, bold: true }));
272
+ console.log(" " + tui.C.green(" ✓ " + files + " dosya değiştirildi"));
273
+ console.log(" " + tui.C.green(" ✓ " + cmds + " komut çalıştırıldı"));
274
+ console.log(" " + tui.C.amber(" ✓ " + msgs + " mesaj değişimi"));
275
+ console.log(" " + tui.C.muted(" ⏱ " + min + "dk " + sec + "sn"));
276
+ console.log("");
277
+ }
278
+
279
+ module.exports = codeV5;
@@ -21,11 +21,18 @@ async function searchFiles({ pattern, basePath = null, maxResults = 100 }) {
21
21
  const results = [];
22
22
 
23
23
  // Basit glob regex donusumu (glob deseni)
24
+ // **/ ve ** pattern'lerini placeholder yap (once kalsin),
25
+ // sonra escape et, en son placeholder'lari replace et.
26
+ // Glob -> regex: **/* is once, ** is sonra, sonra *, ?
24
27
  const regexPattern = pattern
25
- .replace(/[.+^${}()|[\]\\]/g, "\\$&")
26
- .replace(/\\\*\\\*/g, ".*")
27
- .replace(/\\\*/g, "[^/]*")
28
- .replace(/\\\?/g, "[^/]");
28
+ .replace(/\*\*\//g, '@@DSLASH@@') // **/ -> placeholder
29
+ .replace(/\*\*/g, '@@DSTAR@@') // ** -> placeholder
30
+ .replace(/\?/g, '@@QMARK@@') // ? -> placeholder
31
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&') // diger ozel karakterleri escape
32
+ .replace(/@@DSLASH@@/g, '(?:.*\\/)?') // **/ -> (?:.*\/)? (sifir veya daha fazla dizin)
33
+ .replace(/@@DSTAR@@/g, '.*') // ** -> .*
34
+ .replace(/@@QMARK@@/g, '[^/]') // ? -> [^/]
35
+ .replace(/\*/g, '[^/]*');
29
36
 
30
37
  const regex = new RegExp("^" + regexPattern + "$");
31
38
 
@@ -0,0 +1,146 @@
1
+ /**
2
+ * skill_generate - Self-generating skill (v5.1.0)
3
+ *
4
+ * Parton'un vizyonu: "Ihtiyaca gore skill yoksa kendi uretsin"
5
+ *
6
+ * Akis:
7
+ * 1. Kullanici bir istek yapar (ornek: "tum pdf'leri birlestir")
8
+ * 2. Mevcut tool/skill'ler yoksa bu tool cagirilir
9
+ * 3. LLM'a skill taslagi uretmesi icin istek gonderilir
10
+ * 4. SKILL.md dosyasi yazilir, kaydedilir, hemen kullanilir
11
+ */
12
+
13
+ const fs = require("fs");
14
+ const path = require("path");
15
+ const os = require("os");
16
+ const https = require("https");
17
+
18
+ const SKILLS_DIR = path.join(os.homedir(), ".natureco", "skills");
19
+
20
+ function getConfig() {
21
+ try {
22
+ return JSON.parse(fs.readFileSync(path.join(os.homedir(), ".natureco", "config.json"), "utf8"));
23
+ } catch { return {}; }
24
+ }
25
+
26
+ function isMiniMax(url) {
27
+ return url && (url.includes("minimax.io") || url.includes("minimaxi.com"));
28
+ }
29
+
30
+ function apiRequest(url, key, body) {
31
+ return new Promise((resolve, reject) => {
32
+ const isMM = isMiniMax(url);
33
+ const endpoint = isMM
34
+ ? url.replace(/\/$/, "") + "/v1/text/chatcompletion_v2"
35
+ : url.replace(/\/$/, "") + "/chat/completions";
36
+ const req = https.request(endpoint, {
37
+ method: "POST",
38
+ headers: { "Authorization": "Bearer " + key, "Content-Type": "application/json" },
39
+ timeout: 30000,
40
+ }, res => {
41
+ let data = "";
42
+ res.on("data", c => data += c);
43
+ res.on("end", () => {
44
+ if (res.statusCode === 200) {
45
+ try { resolve(JSON.parse(data)); } catch (e) { reject(new Error("Parse hatasi")); }
46
+ } else reject(new Error("HTTP " + res.statusCode + ": " + data.slice(0, 200)));
47
+ });
48
+ });
49
+ req.on("error", reject);
50
+ req.on("timeout", () => req.destroy() && reject(new Error("Timeout")));
51
+ req.write(JSON.stringify(body));
52
+ req.end();
53
+ });
54
+ }
55
+
56
+ function slugify(text) {
57
+ return (text || "")
58
+ .toLowerCase()
59
+ .replace(/[^a-z0-9]+/g, "-")
60
+ .replace(/^-+|-+$/g, "")
61
+ .slice(0, 40) || "custom-skill";
62
+ }
63
+
64
+ async function generateSkillInstructions(taskDescription, skillName) {
65
+ const cfg = getConfig();
66
+ if (!cfg.providerUrl || !cfg.providerApiKey) {
67
+ throw new Error("Provider ayarli degil. Once: natureco setup");
68
+ }
69
+ const promptText = "Sen NatureCo CLI icin SKILL talimatlari yazarsin. Asagidaki gorev icin System Prompt'a eklenecek Turkce talimat yaz:\n\nGOREV: " + taskDescription + "\n\nKURALLAR:\n- Turkce, kisa, oz, madde madde yaz\n- 5-10 satir, dogrudan uygulanabilir\n- Hangi araclarin kullanilacagi (dosya/shell/web) acikca belirt\n- Ornek bir kucuk kullanim ornegi ekle\n- Yanlis ciktilari nasil onlenmesi gerektigini yaz\n\nDirekt SKILL.md icerigini yaz, baska aciklama yapma.";
70
+
71
+ const body = {
72
+ model: cfg.providerModel || "MiniMax-M2.5",
73
+ messages: [
74
+ { role: "system", content: "Sen bir CLI skill talimat yazarsin. Kisa ve net Turkce yaz. Markdown kullanma, duz metin yaz." },
75
+ { role: "user", content: promptText },
76
+ ],
77
+ temperature: 0.5,
78
+ max_tokens: 1500,
79
+ };
80
+ const res = await apiRequest(cfg.providerUrl, cfg.providerApiKey, body);
81
+ const content = res.choices && res.choices[0] && res.choices[0].message ? (res.choices[0].message.content || "") : "";
82
+ return content.trim();
83
+ }
84
+
85
+ async function generateSkill(params) {
86
+ const taskDescription = params.taskDescription;
87
+ const requestedName = params.skillName;
88
+ if (!taskDescription) return { success: false, error: "taskDescription gerekli" };
89
+
90
+ const skillName = requestedName || slugify(taskDescription);
91
+ const skillDir = path.join(SKILLS_DIR, skillName);
92
+
93
+ if (fs.existsSync(skillDir)) {
94
+ return { success: true, message: "Skill zaten mevcut", path: skillDir, skillName: skillName, alreadyExisted: true };
95
+ }
96
+
97
+ let skillContent;
98
+ try {
99
+ skillContent = await generateSkillInstructions(taskDescription, skillName);
100
+ } catch (e) {
101
+ return { success: false, error: "Skill talimati uretilemedi: " + e.message };
102
+ }
103
+
104
+ if (!skillContent || skillContent.length < 50) {
105
+ return { success: false, error: "LLM bos icerik uretti" };
106
+ }
107
+
108
+ fs.mkdirSync(skillDir, { recursive: true });
109
+ fs.writeFileSync(path.join(skillDir, "SKILL.md"), skillContent, "utf8");
110
+ const meta = {
111
+ name: skillName,
112
+ description: "Otomatik uretildi: " + taskDescription.slice(0, 100),
113
+ author: "skill_generate (auto)",
114
+ version: "1.0.0",
115
+ tags: ["auto-generated", "user-created"],
116
+ autoGenerated: true,
117
+ taskDescription: taskDescription,
118
+ createdAt: new Date().toISOString(),
119
+ };
120
+ fs.writeFileSync(path.join(skillDir, "metadata.json"), JSON.stringify(meta, null, 2), "utf8");
121
+
122
+ return {
123
+ success: true,
124
+ message: "Yeni skill olusturuldu ve hemen kullanilabilir!",
125
+ skillName: skillName,
126
+ path: skillDir,
127
+ content: skillContent.slice(0, 300),
128
+ note: "REPL'i yeniden acarsaniz yeni skill yuklenecek. /skills list ile test edebilirsiniz.",
129
+ };
130
+ }
131
+
132
+ module.exports = {
133
+ name: "skill_generate",
134
+ description: "Olmayan bir ozellik icin LLM ile yeni bir skill talimati uretir, kaydeder ve hemen kullanima sunar. 'PDF birlestir', 'JSON duzenle' gibi gorevler icin.",
135
+ inputSchema: {
136
+ type: "object",
137
+ properties: {
138
+ taskDescription: { type: "string", description: "Skill'in ne yapacagi (ornek: 'PDF dosyalarini tek dosyada birlestir')" },
139
+ skillName: { type: "string", description: "Ozel skill adi (opsiyonel, otomatik uretilir)" },
140
+ },
141
+ required: ["taskDescription"],
142
+ },
143
+ async execute(params) {
144
+ return await generateSkill(params);
145
+ },
146
+ };