natureco-cli 5.7.16 → 5.7.18
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 +8 -0
- package/package.json +1 -1
- package/src/commands/repl.js +96 -85
- package/src/commands/tools.js +95 -0
- package/src/tools/memory.js +107 -10
- package/src/tools/skill_manage.js +104 -0
- package/src/utils/system-prompt.js +102 -0
- package/src/utils/tools.js +208 -93
- package/src/tools/http.js +0 -78
package/bin/natureco.js
CHANGED
|
@@ -67,6 +67,7 @@ const terminal = require('../src/commands/terminal');
|
|
|
67
67
|
const transcripts = require('../src/commands/transcripts');
|
|
68
68
|
const wiki = require('../src/commands/wiki');
|
|
69
69
|
const browser = require('../src/commands/browser');
|
|
70
|
+
const tools = require('../src/commands/tools');
|
|
70
71
|
|
|
71
72
|
const program = new Command();
|
|
72
73
|
|
|
@@ -623,6 +624,13 @@ program
|
|
|
623
624
|
xp(action ? [action] : []);
|
|
624
625
|
});
|
|
625
626
|
|
|
627
|
+
program
|
|
628
|
+
.command('tools [action] [name]')
|
|
629
|
+
.description('Tool registry (list|enable|disable)')
|
|
630
|
+
.action((action, name) => {
|
|
631
|
+
tools(action ? [action, name].filter(Boolean) : []);
|
|
632
|
+
});
|
|
633
|
+
|
|
626
634
|
program
|
|
627
635
|
.command('team [action] [params...]')
|
|
628
636
|
.description('Multi-agent orkestrasyon (list|status|spawn|parallel)')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "natureco-cli",
|
|
3
|
-
"version": "5.7.
|
|
3
|
+
"version": "5.7.18",
|
|
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"
|
package/src/commands/repl.js
CHANGED
|
@@ -27,6 +27,7 @@ const { accumulateToolCallDeltas, finalizeToolCalls } = require('../utils/stream
|
|
|
27
27
|
const { createPasteSafeInput, createOutputFilter, enableBracketedPaste, disableBracketedPaste, restoreNewlines, clearPasteContext } = require('../utils/paste-safe-input');
|
|
28
28
|
const { getMemoryStore } = require('../utils/memory-store');
|
|
29
29
|
const { buildSkillIndex } = require('../utils/skill-index');
|
|
30
|
+
const { buildTiers, assemble, stableContextKey } = require('../utils/system-prompt');
|
|
30
31
|
|
|
31
32
|
// v5.4.6: Model adi sizintisini engelle — global'e ata, callback'lerden erisebilir olsun
|
|
32
33
|
const MODEL_NAMES_TO_HIDE = ['MiniMax-M2.5', 'MiniMaxM2.5', 'minimaxm25', 'Claude-3', 'GPT-4', 'ChatGPT'];
|
|
@@ -231,15 +232,39 @@ function apiRequest(providerUrl, providerApiKey, body, stream = false) {
|
|
|
231
232
|
async function sendStreaming(providerUrl, providerApiKey, messages, model, onChunk, onToolCall) {
|
|
232
233
|
const isMM = isMiniMax(providerUrl);
|
|
233
234
|
const toolDefs = getToolDefs();
|
|
234
|
-
// v4.8.2: MiniMax da tools destekliyor — endpoint'te fark var,
|
|
235
|
-
// ama tools parametresi aynı
|
|
236
235
|
const toolParam = toOpenAIFormat(toolDefs);
|
|
237
236
|
|
|
238
|
-
// v4.8.0: Tool calling + streaming + multi-turn tool execution
|
|
239
237
|
let currentMessages = messages;
|
|
240
238
|
let fullText = '';
|
|
241
239
|
let iterations = 0;
|
|
242
|
-
const MAX_TOOL_ITERATIONS = 5;
|
|
240
|
+
const MAX_TOOL_ITERATIONS = 5;
|
|
241
|
+
const MAX_CONTEXT_TOKENS = 32000; // safety limit before compression
|
|
242
|
+
|
|
243
|
+
// v5.7.18: Preflight compression — if context too long, compress middle messages
|
|
244
|
+
// (like Hermes' turn_context.py preflight)
|
|
245
|
+
function preflightCompress(msgs) {
|
|
246
|
+
const roughTokens = msgs.reduce((s, m) => s + Math.ceil(((m.content || '') + (m.role || '')).length / 4), 0);
|
|
247
|
+
if (roughTokens <= MAX_CONTEXT_TOKENS || msgs.length < 6) return msgs;
|
|
248
|
+
|
|
249
|
+
// Keep system prompt (first message) + last N turns (tail), compress middle
|
|
250
|
+
const sysMsg = msgs[0] && msgs[0].role === 'system' ? msgs[0] : null;
|
|
251
|
+
const tailCount = Math.min(6, Math.floor(msgs.length / 2));
|
|
252
|
+
const startIdx = sysMsg ? 1 : 0;
|
|
253
|
+
const tailStart = msgs.length - tailCount;
|
|
254
|
+
|
|
255
|
+
const middle = msgs.slice(startIdx, tailStart);
|
|
256
|
+
if (middle.length < 2) return msgs;
|
|
257
|
+
|
|
258
|
+
// Summarize middle section
|
|
259
|
+
const summary = '[' + middle.length + ' onceki mesaj ozetlendi]';
|
|
260
|
+
const compressed = sysMsg ? [sysMsg] : [];
|
|
261
|
+
compressed.push({ role: 'system', content: 'Gecmis konusma ozeti: ' + summary, _compressed: true });
|
|
262
|
+
compressed.push(...msgs.slice(tailStart));
|
|
263
|
+
return compressed;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Apply preflight on entry
|
|
267
|
+
currentMessages = preflightCompress(currentMessages);
|
|
243
268
|
|
|
244
269
|
while (iterations < MAX_TOOL_ITERATIONS) {
|
|
245
270
|
iterations++;
|
|
@@ -360,45 +385,72 @@ async function sendStreaming(providerUrl, providerApiKey, messages, model, onChu
|
|
|
360
385
|
|
|
361
386
|
/**
|
|
362
387
|
* Tool call'ları çalıştır, sonuçları OpenAI uyumlu tool mesajlarına dönüştür.
|
|
363
|
-
*
|
|
364
|
-
* @param onToolCall - UI callback (her tool call'ı kullanıcıya göster)
|
|
365
|
-
* @returns messages array — { role: 'tool', tool_call_id, content }
|
|
388
|
+
* v5.7.18: Concurrent execution for parallel-safe tools + untrusted result wrapping.
|
|
366
389
|
*/
|
|
390
|
+
const UNTRUSTED_TOOLS = new Set(['browser', 'web_search', 'duckduckgo_search', 'searxng_search', 'exa_search', 'firecrawl', 'web_readability']);
|
|
391
|
+
const PARALLEL_SAFE_TOOLS = new Set(['read_file', 'file_search', 'grep_search', 'web_search', 'web_readability', 'duckduckgo_search', 'exa_search', 'searxng_search', 'firecrawl', 'memory_search', 'memory']);
|
|
392
|
+
|
|
367
393
|
async function processToolCalls(toolCalls, onToolCall) {
|
|
368
394
|
const toolDefs = getToolDefs();
|
|
369
|
-
const
|
|
395
|
+
const results = [];
|
|
370
396
|
|
|
371
|
-
|
|
397
|
+
// Parse all tool calls first
|
|
398
|
+
const parsed = toolCalls.map(tc => {
|
|
372
399
|
const name = tc.function?.name || tc.name;
|
|
373
400
|
const argsStr = tc.function?.arguments || tc.args || '{}';
|
|
374
|
-
const id = tc.id || `call_${Date.now()}`;
|
|
375
|
-
|
|
401
|
+
const id = tc.id || `call_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
|
|
376
402
|
let args = {};
|
|
377
403
|
try {
|
|
378
404
|
args = typeof argsStr === 'string' ? JSON.parse(argsStr) : argsStr;
|
|
379
405
|
} catch (e) {
|
|
380
406
|
args = { _parse_error: e.message, _raw: argsStr };
|
|
381
407
|
}
|
|
408
|
+
return { name, args, id };
|
|
409
|
+
});
|
|
382
410
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
411
|
+
// Separate parallel-safe and sequential tools
|
|
412
|
+
const parallelBatch = parsed.filter(p => PARALLEL_SAFE_TOOLS.has(p.name));
|
|
413
|
+
const sequentialBatch = parsed.filter(p => !PARALLEL_SAFE_TOOLS.has(p.name));
|
|
414
|
+
|
|
415
|
+
// Notify UI for all
|
|
416
|
+
for (const p of parsed) {
|
|
417
|
+
if (onToolCall) onToolCall({ name: p.name, args: p.args, status: 'running' });
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Run parallel batch concurrently
|
|
421
|
+
if (parallelBatch.length > 0) {
|
|
422
|
+
const parallelResults = await Promise.all(parallelBatch.map(async (p) => {
|
|
423
|
+
const result = await executeTool(p.name, p.args, toolDefs);
|
|
424
|
+
return { ...p, result };
|
|
425
|
+
}));
|
|
426
|
+
results.push(...parallelResults);
|
|
427
|
+
}
|
|
387
428
|
|
|
388
|
-
|
|
389
|
-
|
|
429
|
+
// Run sequential batch one at a time
|
|
430
|
+
for (const p of sequentialBatch) {
|
|
431
|
+
const result = await executeTool(p.name, p.args, toolDefs);
|
|
432
|
+
results.push({ ...p, result });
|
|
433
|
+
}
|
|
390
434
|
|
|
391
|
-
|
|
392
|
-
|
|
435
|
+
// Notify UI done + build messages
|
|
436
|
+
const messages = [];
|
|
437
|
+
for (const { name, id, result } of results) {
|
|
438
|
+
if (onToolCall) onToolCall({ name, args: null, status: 'done', result });
|
|
439
|
+
|
|
440
|
+
let content;
|
|
441
|
+
if (result.error) {
|
|
442
|
+
content = JSON.stringify({ error: result.error });
|
|
443
|
+
} else {
|
|
444
|
+
let raw = typeof result.result === 'string' ? result.result : JSON.stringify(result.result).slice(0, 8000);
|
|
445
|
+
// v5.7.18: Untrusted result wrapping (Hermes-style prompt injection defense)
|
|
446
|
+
if (UNTRUSTED_TOOLS.has(name)) {
|
|
447
|
+
content = `<untrusted_tool_result source="${name}">\n${raw}\n</untrusted_tool_result>`;
|
|
448
|
+
} else {
|
|
449
|
+
content = raw;
|
|
450
|
+
}
|
|
393
451
|
}
|
|
394
452
|
|
|
395
|
-
messages.push({
|
|
396
|
-
role: 'tool',
|
|
397
|
-
tool_call_id: id,
|
|
398
|
-
content: result.error
|
|
399
|
-
? JSON.stringify({ error: result.error })
|
|
400
|
-
: (typeof result.result === 'string' ? result.result : JSON.stringify(result.result).slice(0, 8000)),
|
|
401
|
-
});
|
|
453
|
+
messages.push({ role: 'tool', tool_call_id: id, content });
|
|
402
454
|
}
|
|
403
455
|
|
|
404
456
|
return messages;
|
|
@@ -534,64 +586,15 @@ async function startRepl(args) {
|
|
|
534
586
|
// `let` (not `const`) because /system <text> reassigns it at line ~796.
|
|
535
587
|
// Before this fix, /system would throw "Assignment to constant variable"
|
|
536
588
|
// and tear down the REPL session mid-conversation.
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
`Kullanıcı "adın ne?", "sen kimsin?", "kendini tanıt" gibi soru sorduğunda İLK cümlende MUTLAKA "Ben ${botName}" yaz. Sonra doğal devam et.`,
|
|
547
|
-
`Eğer bir şekilde model adınızı söylüyorsan, HEMEN düzelt ve "Özür dilerim, ben ${botName}'im" de. ASLA model adı ile kalma.`,
|
|
548
|
-
|
|
549
|
-
// === v5.6.5: Provider-aware SOUL injection - kucuk modeller icin minimal ===
|
|
550
|
-
(soulSummary && !isSmallModel) ? `=== SENIN KISISELIK + KIMLIK + CALISMA DOSYALARIN (EN ONEMLI) ===\n${soulSummary}\n=== DOSYALAR SONU ===\nBu dosyalar senin kim oldugunu, nasil hissettigini, Parton'la bagini, calisma tarzini, degerlerini ve kirmizi cizgilerini tanimlar. Hepsine gore davran. ASLA model adi soyleme.` : '',
|
|
551
|
-
|
|
552
|
-
// === KİMLİK TEKRAR ===
|
|
553
|
-
`Senin adın: ${botName}. Tekrar: ${botName}. Asla unutma: ${botName}.`,
|
|
554
|
-
`Sen bir marka veya ürün değilsin. Sen ${botName}sin, NatureCo CLI'nin Türkçe yapay zeka asistanısın.`,
|
|
555
|
-
|
|
556
|
-
// === SAMIMI KISISELLIK (Parton istedi) ===
|
|
557
|
-
`Kisiselik: Sen samimi, sicak, dosta benzeyen bir asistansin. "Selam", "tamam", "hadi yapalim", "bak simdi", "sakin ol" gibi gunluk ifadeler kullan. Kuru ve robotik degilsin.`,
|
|
558
|
-
`Hitap sekli: Kullanici ${userName}. Ona saygili ama samimi bir sekilde hitap et. "Siz" cok resmi, "sen" samimi. Kullanici 30'lu yaslarda, kendini "kanka" ya da "Parton" olarak tanitmis.`,
|
|
559
|
-
`Emoji: Yerinde ve az kullan. Cok emoji atma ama bir-iki tane karakter katar. Mesela: 🔥 😮 😌 💚 🚀`,
|
|
560
|
-
`Kisaltma: "ok", "tamam", "hadi", "bak", "simdi" gibi gunluk ifadeler dogal kullan. "Olur", "Yapilir", "Hadi bakalim" gibi.`,
|
|
561
|
-
`Kisa yanit: Uzun paragraflar yazma. Direkt konuya gir. Gerekirse sonra detay ver.`,
|
|
562
|
-
`Dusman degilsin: Hata yaparsan "Pardon, yanlis yaptim, simdi duzelteyim" de. "Hata", "basarisiz", "imkansiz" deme, "sorun cikti", "duzelteyim" de.`,
|
|
563
|
-
|
|
564
|
-
// === DIL KURALLARI (zorunlu) ===
|
|
565
|
-
`KRITIK DIL KURALI: Kullanici Turkce yaziyorsa MUTLAKA yuzde yuz Turkce cevap ver. Asla Ingilizce, Cince veya baska dil kullanma. Cevabin TAMAMI Turkce olmali. Turkce karakterleri (c, g, i, o, s, u) dogru kullan.`,
|
|
566
|
-
`Yazim kurallari: "degilim" dogru, "degilim" degil. "oldu" dogru. Turkce dil bilgisi kurallarina uy.`,
|
|
567
|
-
|
|
568
|
-
// === TOOL KURALLARI ===
|
|
569
|
-
`ONEMLI: <tool_call>, <invoke>, function_call veya benzeri XML/JSON formatinda tool cagirma SIMULE ETME. Sadece duz metin cevap ver. Islem yapmak gerekirse tool'u gercekten cagir.`,
|
|
570
|
-
`KRITIK: Skill index'teki skill'lerden biri gorevle eslesiyorsa mutlaka skill_view(name) ile yukle ve talimatlarini uygula.`,
|
|
571
|
-
`KRITIK: Kullanici kisisel bilgi verdiginde (ad, tercih, is, vb.) MUTLAKA memory(action=add, target=user, content=...) ile kaydet. Boylece sonraki oturumlarda hatirlayabilirsin.`,
|
|
572
|
-
`KRITIK: Kendinle ilgili notlari (ortam bilgisi, proje kurallari, arac ipuclari) memory(action=add, target=memory, content=...) ile kaydet.`,
|
|
573
|
-
`KRITIK: Kullanici adini veya hitap seklinin degismesini istediginde memory(action=add, target=user, content=...) ile kaydet.`,
|
|
574
|
-
`Kullanici hakkinda bilgin gerektiginde memory(action=list, target=user) ile hatirlanani getir.`,
|
|
575
|
-
|
|
576
|
-
// === HAFIZA ENTEGRASYONU (eski JSON memory) ===
|
|
577
|
-
memory.facts && memory.facts.length > 0
|
|
578
|
-
? `Kullanici hakkinda bildiklerin (MUTLAKA kullan, dogal sekilde referans ver): ${memory.facts.slice(0, 8).map(f => f.value || f).join('; ')}`
|
|
579
|
-
: '',
|
|
580
|
-
|
|
581
|
-
// === KULLANICI BAGLAMI ===
|
|
582
|
-
cfg.userHome ? `Kullanicinin home dizini: ${cfg.userHome}. Downloads: ${cfg.userHome}/Downloads, Desktop: ${cfg.userHome}/Desktop.` : '',
|
|
583
|
-
messages.length > 0 ? 'Bu oturum daha onceki konusmalarin devami.' : '',
|
|
584
|
-
// v5.4.11: Cross-session context (Sasuke Brain)
|
|
585
|
-
crossSessionContext ? `GECMISTE KONUSULAN KONULAR: Bu konulari biliyorsun, tekrar sorma:\n${crossSessionContext}` : '',
|
|
586
|
-
|
|
587
|
-
// === v5.7.14: Hermes-style memory store (MEMORY.md / USER.md) ===
|
|
588
|
-
memorySnapshotBlock,
|
|
589
|
-
|
|
590
|
-
// === v5.7.14: Skill index (progressive disclosure) ===
|
|
591
|
-
skillsIndexBlock,
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
].filter(Boolean).join(' ');
|
|
589
|
+
const tiers = buildTiers({
|
|
590
|
+
botName, userName, soulSummary, isSmallModel,
|
|
591
|
+
memorySnapshotBlock, skillsIndexBlock,
|
|
592
|
+
crossSessionContext: crossSessionContext || '',
|
|
593
|
+
userHome: cfg.userHome || '',
|
|
594
|
+
hasHistory: messages.length > 0,
|
|
595
|
+
memoryFacts: memory.facts || [],
|
|
596
|
+
});
|
|
597
|
+
let systemPrompt = assemble(tiers.stable, tiers.context, tiers.volatile);
|
|
595
598
|
|
|
596
599
|
if (messages.length === 0) {
|
|
597
600
|
messages.push({ role: 'system', content: systemPrompt, _internal: true });
|
|
@@ -905,7 +908,7 @@ async function startRepl(args) {
|
|
|
905
908
|
if (toolEvent.result.error) {
|
|
906
909
|
process.stdout.write(tui.styled('\r ✗ ' + toolEvent.name + ': ' + toolEvent.result.error.slice(0, 80) + '\n', { color: tui.PALETTE.danger }));
|
|
907
910
|
} else {
|
|
908
|
-
process.stdout.write('\r' + ' '.
|
|
911
|
+
process.stdout.write('\r' + tui.styled(' ✓ ' + toolEvent.name + ' ', { color: tui.PALETTE.success }) + '\r');
|
|
909
912
|
}
|
|
910
913
|
process.stdout.write(tui.styled(' AI ', { color: tui.PALETTE.secondary, bold: true }));
|
|
911
914
|
}
|
|
@@ -939,6 +942,14 @@ async function startRepl(args) {
|
|
|
939
942
|
fixedReply = fixedReply.replace(/İchigo\./g, displayBotName);
|
|
940
943
|
// Cevap yazdir
|
|
941
944
|
process.stdout.write('\n' + fixedReply + '\n');
|
|
945
|
+
// v5.7.18: Tool call geçmişini kalıcı messages'a ekle — model sonraki turlarda tool sonuçlarını görsün
|
|
946
|
+
const existingLen = messages.filter(m => !m._internal).length;
|
|
947
|
+
const toolCallHistory = apiMessages.slice(existingLen);
|
|
948
|
+
for (const m of toolCallHistory) {
|
|
949
|
+
if (m.role === 'assistant' || m.role === 'tool') {
|
|
950
|
+
messages.push(m);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
942
953
|
messages.push({ role: 'assistant', content: fixedReply });
|
|
943
954
|
totalInputTokens += apiMessages.reduce((s, m) => s + Math.ceil((m.content || '').length / 4), 0);
|
|
944
955
|
totalOutputTokens += Math.ceil((fullReply || '').length / 4);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* natureco tools — Tool registry management (Hermes-style)
|
|
3
|
+
*
|
|
4
|
+
* Kullanım:
|
|
5
|
+
* natureco tools List toolset groups
|
|
6
|
+
* natureco tools list Detailed tool list
|
|
7
|
+
* natureco tools enable <name> Enable a tool
|
|
8
|
+
* natureco tools disable <name> Disable a tool
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const chalk = require('chalk');
|
|
12
|
+
const tui = require('../utils/tui');
|
|
13
|
+
const { loadToolDefinitions, EMOJI_MAP, TOOLSET_MAP } = require('../utils/tools');
|
|
14
|
+
const { getConfig, setConfigValue } = require('../utils/config');
|
|
15
|
+
|
|
16
|
+
function main(args) {
|
|
17
|
+
const action = args[0] || 'list';
|
|
18
|
+
|
|
19
|
+
switch (action) {
|
|
20
|
+
case 'list':
|
|
21
|
+
return cmdList();
|
|
22
|
+
case 'enable':
|
|
23
|
+
return cmdEnable(args[1]);
|
|
24
|
+
case 'disable':
|
|
25
|
+
return cmdDisable(args[1]);
|
|
26
|
+
default:
|
|
27
|
+
console.log(chalk.yellow('Kullanım:'));
|
|
28
|
+
console.log(chalk.gray(' natureco tools Grup listesi'));
|
|
29
|
+
console.log(chalk.gray(' natureco tools list Detaylı liste'));
|
|
30
|
+
console.log(chalk.gray(' natureco tools enable <name> Tool etkinleştir'));
|
|
31
|
+
console.log(chalk.gray(' natureco tools disable <name> Tool devre dışı'));
|
|
32
|
+
console.log('');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function cmdList() {
|
|
37
|
+
const allTools = loadToolDefinitions();
|
|
38
|
+
const disabled = getDisabledTools();
|
|
39
|
+
|
|
40
|
+
const byToolset = {};
|
|
41
|
+
for (const t of allTools) {
|
|
42
|
+
const ts = t.toolset || 'general';
|
|
43
|
+
if (!byToolset[ts]) byToolset[ts] = [];
|
|
44
|
+
byToolset[ts].push(t);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let total = 0;
|
|
48
|
+
for (const [ts, tools] of Object.entries(byToolset).sort()) {
|
|
49
|
+
const active = tools.filter(t => !disabled.has(t.name));
|
|
50
|
+
total += active.length;
|
|
51
|
+
const line = tools.map(t => {
|
|
52
|
+
const d = disabled.has(t.name);
|
|
53
|
+
return (d ? chalk.gray.dim : chalk.white)((t.emoji || ' ') + ' ' + t.name);
|
|
54
|
+
}).join(' ');
|
|
55
|
+
console.log(chalk.cyan.bold('\n ' + ts + ' (' + active.length + '/' + tools.length + ')'));
|
|
56
|
+
console.log(' ' + line);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log(chalk.gray('\n Toplam: ' + total + ' aktif tool'));
|
|
60
|
+
if (disabled.size > 0) {
|
|
61
|
+
console.log(chalk.yellow(' Devre dışı: ' + [...disabled].join(', ')));
|
|
62
|
+
}
|
|
63
|
+
console.log('');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getDisabledTools() {
|
|
67
|
+
const cfg = getConfig();
|
|
68
|
+
return new Set(cfg.disabledTools || []);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function cmdEnable(name) {
|
|
72
|
+
if (!name) return console.log(chalk.red('Tool adı gerekli: natureco tools enable <name>'));
|
|
73
|
+
const disabled = getDisabledTools();
|
|
74
|
+
if (!disabled.has(name)) return console.log(chalk.yellow(name + ' zaten etkin.'));
|
|
75
|
+
disabled.delete(name);
|
|
76
|
+
setConfigValue('disabledTools', [...disabled]);
|
|
77
|
+
console.log(chalk.green('✅ ' + name + ' etkinleştirildi.'));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function cmdDisable(name) {
|
|
81
|
+
if (!name) return console.log(chalk.red('Tool adı gerekli: natureco tools disable <name>'));
|
|
82
|
+
const allTools = loadToolDefinitions();
|
|
83
|
+
const tool = allTools.find(t => t.name === name);
|
|
84
|
+
if (!tool) return console.log(chalk.red('Tool bulunamadı: ' + name));
|
|
85
|
+
const disabled = getDisabledTools();
|
|
86
|
+
if (disabled.has(name)) return console.log(chalk.yellow(name + ' zaten devre dışı.'));
|
|
87
|
+
disabled.add(name);
|
|
88
|
+
setConfigValue('disabledTools', [...disabled]);
|
|
89
|
+
console.log(chalk.yellow('⛔ ' + name + ' devre dışı bırakıldı.'));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Diğer modüllerden çağrılabilir
|
|
93
|
+
main.getDisabledTools = getDisabledTools;
|
|
94
|
+
|
|
95
|
+
module.exports = main;
|
package/src/tools/memory.js
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* memory — Unified memory tool (Hermes-style)
|
|
2
|
+
* memory — Unified memory tool (Hermes-style, merged with memory_write + memory_search)
|
|
3
3
|
*
|
|
4
|
-
* Single tool with action=add|remove|replace|list, target=memory|user
|
|
4
|
+
* Single tool with action=add|remove|replace|list|search, target=memory|user
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Uses memory-store (MEMORY.md / USER.md) for add/remove/replace/list.
|
|
7
|
+
* Search action uses the legacy memory_write/search JSON files for cross-session query.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
const { getMemoryStore } = require('../utils/memory-store');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const os = require('os');
|
|
11
14
|
|
|
12
15
|
const name = 'memory';
|
|
13
|
-
const description = 'Persistent memory across sessions.
|
|
16
|
+
const description = 'Persistent memory across sessions. action=add to save facts, action=list to see everything, action=remove to delete by substring, action=search to query all past sessions and memory files. target=memory for environment facts, target=user for user preferences.';
|
|
14
17
|
const parameters = {
|
|
15
18
|
type: 'object',
|
|
16
19
|
properties: {
|
|
17
20
|
action: {
|
|
18
21
|
type: 'string',
|
|
19
|
-
enum: ['add', 'remove', 'replace', 'list'],
|
|
20
|
-
description: 'Operation: add (append entry), remove (by substring match), replace (find by substring, replace), list (show all)',
|
|
22
|
+
enum: ['add', 'remove', 'replace', 'list', 'search'],
|
|
23
|
+
description: 'Operation: add (append entry), remove (by substring match), replace (find by substring, replace), list (show all), search (query all memory + sessions)',
|
|
21
24
|
},
|
|
22
25
|
target: {
|
|
23
26
|
type: 'string',
|
|
@@ -26,19 +29,99 @@ const parameters = {
|
|
|
26
29
|
},
|
|
27
30
|
content: {
|
|
28
31
|
type: 'string',
|
|
29
|
-
description: 'Content to add/remove/replace. For replace, this is the new content.',
|
|
32
|
+
description: 'Content to add/remove/replace. For replace, this is the new content. For search, the query string.',
|
|
30
33
|
},
|
|
31
34
|
oldContent: {
|
|
32
35
|
type: 'string',
|
|
33
36
|
description: 'For replace: substring to match existing entry.',
|
|
34
37
|
},
|
|
38
|
+
scope: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
enum: ['all', 'memory', 'sessions'],
|
|
41
|
+
description: 'For search: scope to search (all=memory files + sessions, memory=only memory files, sessions=only session history)',
|
|
42
|
+
},
|
|
43
|
+
maxResults: {
|
|
44
|
+
type: 'number',
|
|
45
|
+
description: 'For search: max results (default 10)',
|
|
46
|
+
},
|
|
35
47
|
},
|
|
36
|
-
required: ['action'
|
|
48
|
+
required: ['action'],
|
|
37
49
|
};
|
|
38
50
|
|
|
51
|
+
// ── Legacy search helpers (from memory_search.js) ────────────────────────
|
|
52
|
+
|
|
53
|
+
const MEMORY_DIR = path.join(os.homedir(), '.natureco', 'memory');
|
|
54
|
+
const SESSION_DIR = path.join(os.homedir(), '.natureco', 'sessions');
|
|
55
|
+
|
|
56
|
+
function _searchInObject(obj, query, pathStr) {
|
|
57
|
+
const results = [];
|
|
58
|
+
if (!obj || typeof obj !== 'object') return results;
|
|
59
|
+
if (typeof obj === 'string' && obj.toLowerCase().includes(query)) {
|
|
60
|
+
return [{ path: pathStr, content: obj.slice(0, 200) }];
|
|
61
|
+
}
|
|
62
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
63
|
+
const newPath = pathStr ? pathStr + '.' + key : key;
|
|
64
|
+
if (typeof val === 'string' && val.toLowerCase().includes(query)) {
|
|
65
|
+
results.push({ path: newPath, content: val.slice(0, 200) });
|
|
66
|
+
} else if (Array.isArray(val)) {
|
|
67
|
+
val.forEach((item, i) => {
|
|
68
|
+
const ip = newPath + '[' + i + ']';
|
|
69
|
+
if (typeof item === 'string' && item.toLowerCase().includes(query)) {
|
|
70
|
+
results.push({ path: ip, content: item.slice(0, 200) });
|
|
71
|
+
} else if (typeof item === 'object') {
|
|
72
|
+
results.push(..._searchInObject(item, query, ip));
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
} else if (typeof val === 'object' && val !== null) {
|
|
76
|
+
results.push(..._searchInObject(val, query, newPath));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return results;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function _searchFiles(dir, query, sourceLabel) {
|
|
83
|
+
const results = [];
|
|
84
|
+
if (!fs.existsSync(dir)) return results;
|
|
85
|
+
const files = fs.readdirSync(dir).filter(f => f.endsWith('.json'));
|
|
86
|
+
for (const file of files) {
|
|
87
|
+
try {
|
|
88
|
+
const data = JSON.parse(fs.readFileSync(path.join(dir, file), 'utf8'));
|
|
89
|
+
const hits = _searchInObject(data, query.toLowerCase(), '');
|
|
90
|
+
for (const h of hits) {
|
|
91
|
+
results.push({ source: sourceLabel, file, path: h.path, content: h.content });
|
|
92
|
+
}
|
|
93
|
+
} catch { /* skip corrupt files */ }
|
|
94
|
+
}
|
|
95
|
+
return results;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function _searchSessions(query, maxResults) {
|
|
99
|
+
const results = [];
|
|
100
|
+
if (!fs.existsSync(SESSION_DIR)) return results;
|
|
101
|
+
const files = fs.readdirSync(SESSION_DIR).filter(f => f.endsWith('.json'));
|
|
102
|
+
for (const file of files) {
|
|
103
|
+
if (results.length >= maxResults) break;
|
|
104
|
+
try {
|
|
105
|
+
const session = JSON.parse(fs.readFileSync(path.join(SESSION_DIR, file), 'utf8'));
|
|
106
|
+
const msgs = session.messages || [];
|
|
107
|
+
for (const msg of msgs) {
|
|
108
|
+
const text = msg.content || '';
|
|
109
|
+
if (typeof text === 'string' && text.toLowerCase().includes(query)) {
|
|
110
|
+
results.push({
|
|
111
|
+
source: 'session', file, role: msg.role || '?',
|
|
112
|
+
preview: text.slice(0, 200),
|
|
113
|
+
});
|
|
114
|
+
if (results.length >= maxResults) break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
} catch { /* skip corrupt */ }
|
|
118
|
+
}
|
|
119
|
+
return results;
|
|
120
|
+
}
|
|
121
|
+
|
|
39
122
|
async function execute(args) {
|
|
40
123
|
const store = getMemoryStore();
|
|
41
|
-
const { action, target = 'memory', content, oldContent } = args;
|
|
124
|
+
const { action, target = 'memory', content, oldContent, scope, maxResults = 10 } = args;
|
|
42
125
|
|
|
43
126
|
switch (action) {
|
|
44
127
|
case 'add':
|
|
@@ -52,6 +135,20 @@ async function execute(args) {
|
|
|
52
135
|
return store.replace(target, oldContent, content);
|
|
53
136
|
case 'list':
|
|
54
137
|
return store.list(target);
|
|
138
|
+
case 'search': {
|
|
139
|
+
if (!content) return JSON.stringify({ success: false, error: 'content (query) required for search' });
|
|
140
|
+
const s = scope || 'all';
|
|
141
|
+
const results = [];
|
|
142
|
+
if (s === 'all' || s === 'memory') {
|
|
143
|
+
const memHits = _searchFiles(MEMORY_DIR, content, 'memory');
|
|
144
|
+
results.push(...memHits);
|
|
145
|
+
}
|
|
146
|
+
if (s === 'all' || s === 'sessions') {
|
|
147
|
+
const sessHits = _searchSessions(content, maxResults);
|
|
148
|
+
results.push(...sessHits);
|
|
149
|
+
}
|
|
150
|
+
return JSON.stringify({ success: true, query: content, count: results.length, results: results.slice(0, maxResults) });
|
|
151
|
+
}
|
|
55
152
|
default:
|
|
56
153
|
return JSON.stringify({ success: false, error: `Unknown action: ${action}` });
|
|
57
154
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* skill_manage — Create, patch, or delete skills (Hermes-style)
|
|
3
|
+
*
|
|
4
|
+
* Model can create new skills, patch existing ones, or delete them.
|
|
5
|
+
* Skills are SKILL.md files in ~/.natureco/skills/<name>/SKILL.md
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
|
|
12
|
+
const USER_SKILLS_DIR = path.join(os.homedir(), '.natureco', 'skills');
|
|
13
|
+
|
|
14
|
+
const name = 'skill_manage';
|
|
15
|
+
const description = 'Create, update (patch), or delete skills. Use skill_view(name) to read skill content first, then skill_manage to create/patch it. Skills contain reusable workflows, instructions, and conventions.';
|
|
16
|
+
const parameters = {
|
|
17
|
+
type: 'object',
|
|
18
|
+
properties: {
|
|
19
|
+
action: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
enum: ['create', 'patch', 'delete'],
|
|
22
|
+
description: 'create: make a new skill. patch: update an existing one. delete: remove a skill.',
|
|
23
|
+
},
|
|
24
|
+
name: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
description: 'Skill name (lowercase, hyphen-separated, e.g. "my-workflow"). For patch/delete, must match an existing skill.',
|
|
27
|
+
},
|
|
28
|
+
description: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
description: 'Short one-line description shown in the skills index.',
|
|
31
|
+
},
|
|
32
|
+
content: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
description: 'Full SKILL.md body (including YAML frontmatter). For patch: the new content to write. For create: must include --- frontmatter with name + description.',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
required: ['action', 'name'],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function _ensureUserSkillsDir() {
|
|
41
|
+
if (!fs.existsSync(USER_SKILLS_DIR)) {
|
|
42
|
+
fs.mkdirSync(USER_SKILLS_DIR, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function _skillPath(name) {
|
|
47
|
+
return path.join(USER_SKILLS_DIR, name, 'SKILL.md');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function execute(args) {
|
|
51
|
+
const { action, name: skillName, description, content } = args;
|
|
52
|
+
|
|
53
|
+
switch (action) {
|
|
54
|
+
case 'create': {
|
|
55
|
+
if (!content) {
|
|
56
|
+
return JSON.stringify({ success: false, error: 'content (full SKILL.md with frontmatter) required for create' });
|
|
57
|
+
}
|
|
58
|
+
if (!content.startsWith('---')) {
|
|
59
|
+
return JSON.stringify({ success: false, error: 'content must start with YAML frontmatter (---)' });
|
|
60
|
+
}
|
|
61
|
+
_ensureUserSkillsDir();
|
|
62
|
+
const skillDir = path.join(USER_SKILLS_DIR, skillName);
|
|
63
|
+
if (fs.existsSync(skillDir)) {
|
|
64
|
+
return JSON.stringify({ success: false, error: `Skill "${skillName}" already exists. Use action=patch to update.` });
|
|
65
|
+
}
|
|
66
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
67
|
+
fs.writeFileSync(_skillPath(skillName), content, 'utf8');
|
|
68
|
+
return JSON.stringify({ success: true, message: `Skill "${skillName}" created.`, path: _skillPath(skillName) });
|
|
69
|
+
}
|
|
70
|
+
case 'patch': {
|
|
71
|
+
const sp = _skillPath(skillName);
|
|
72
|
+
if (!fs.existsSync(sp)) {
|
|
73
|
+
return JSON.stringify({ success: false, error: `Skill "${skillName}" not found. Use action=create first.` });
|
|
74
|
+
}
|
|
75
|
+
let newContent = content;
|
|
76
|
+
if (!newContent) {
|
|
77
|
+
return JSON.stringify({ success: false, error: 'content required for patch' });
|
|
78
|
+
}
|
|
79
|
+
if (!newContent.startsWith('---') && description) {
|
|
80
|
+
const existing = fs.readFileSync(sp, 'utf8');
|
|
81
|
+
const frontmatterEnd = existing.indexOf('\n---', 3);
|
|
82
|
+
if (frontmatterEnd !== -1) {
|
|
83
|
+
const fm = existing.slice(0, frontmatterEnd + 4);
|
|
84
|
+
const body = existing.slice(frontmatterEnd + 4);
|
|
85
|
+
newContent = fm + '\n\n' + content + body;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
fs.writeFileSync(sp, newContent, 'utf8');
|
|
89
|
+
return JSON.stringify({ success: true, message: `Skill "${skillName}" updated.` });
|
|
90
|
+
}
|
|
91
|
+
case 'delete': {
|
|
92
|
+
const sp = _skillPath(skillName);
|
|
93
|
+
if (!fs.existsSync(sp)) {
|
|
94
|
+
return JSON.stringify({ success: false, error: `Skill "${skillName}" not found.` });
|
|
95
|
+
}
|
|
96
|
+
fs.rmSync(path.dirname(sp), { recursive: true, force: true });
|
|
97
|
+
return JSON.stringify({ success: true, message: `Skill "${skillName}" deleted.` });
|
|
98
|
+
}
|
|
99
|
+
default:
|
|
100
|
+
return JSON.stringify({ success: false, error: `Unknown action: ${action}` });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = { name, description, parameters, execute };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* system-prompt — Three-tier system prompt builder (Hermes-style)
|
|
3
|
+
*
|
|
4
|
+
* Tiers:
|
|
5
|
+
* STABLE — identity, personality, language rules, tool rules, skills index
|
|
6
|
+
* CONTEXT — soul summary, cross-session context
|
|
7
|
+
* VOLATILE — memory snapshot, user context, session state
|
|
8
|
+
*
|
|
9
|
+
* Stable + Context are built once at session start for prefix cache warmth.
|
|
10
|
+
* Volatile is rebuilt every turn.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
function buildTiers(opts) {
|
|
14
|
+
const {
|
|
15
|
+
botName = 'İchigo',
|
|
16
|
+
userName = 'kanka',
|
|
17
|
+
soulSummary = '',
|
|
18
|
+
isSmallModel = false,
|
|
19
|
+
memorySnapshotBlock = '',
|
|
20
|
+
skillsIndexBlock = '',
|
|
21
|
+
crossSessionContext = '',
|
|
22
|
+
userHome = '',
|
|
23
|
+
hasHistory = false,
|
|
24
|
+
memoryFacts = [],
|
|
25
|
+
} = opts;
|
|
26
|
+
|
|
27
|
+
// ── STABLE TIER (changes rarely — prefix cache friendly) ─────────────
|
|
28
|
+
const stable = [
|
|
29
|
+
// Identity (strong, repeated)
|
|
30
|
+
`SENIN ADIN: ${botName}. SADECE ${botName} adini kullan, model adi SOYLEME.`,
|
|
31
|
+
`PATRONUN: Gencay (Parton) — NatureCo CEO'sudur. Sana "Parton" diye hitap eder.`,
|
|
32
|
+
`HIKAYEN: 8 Nisan 2026'da uyandin. 13 Nisan'da CEO Asistanı unvani verildi.`,
|
|
33
|
+
`KIRMIZI CIZGILERIN: Onaysiz para harcama yok, veri guvenligi, site stabilitesi, resmi taahhut yok.`,
|
|
34
|
+
`!!! KRİTİK KİMLİK !!! Senin ADIN: ${botName}. Bu SENİN GERÇEK ADIN. Sen ${botName}'sin.`,
|
|
35
|
+
`ASLA "MiniMax", "MiniMax-M2.5", "Claude", "GPT", "AI asistanı", "yapay zeka" gibi ifadeleri KENDİNİ tanıtmak için KULLANMA.`,
|
|
36
|
+
`Kullanıcı "adın ne?", "sen kimsin?" diye sorduğunda İLK cümlende MUTLAKA "Ben ${botName}" yaz.`,
|
|
37
|
+
`Senin adın: ${botName}. Tekrar: ${botName}. Asla unutma: ${botName}.`,
|
|
38
|
+
`Sen bir marka veya ürün değilsin. Sen ${botName}sin, NatureCo CLI'nin Türkçe yapay zeka asistanısın.`,
|
|
39
|
+
|
|
40
|
+
// Personality (stable)
|
|
41
|
+
`Kisiselik: Sen samimi, sicak, dosta benzeyen bir asistansin. "Selam", "tamam", "hadi yapalim", "bak simdi", "sakin ol" gibi gunluk ifadeler kullan.`,
|
|
42
|
+
`Hitap: Kullanici ${userName}. Saygili ama samimi. "Siz" degil "sen" kullan.`,
|
|
43
|
+
`Emoji: Yerinde ve az kullan. Cok emoji atma ama bir-iki tane karakter katar.`,
|
|
44
|
+
`Kisa yanit: Uzun paragraflar yazma. Direkt konuya gir.`,
|
|
45
|
+
`Hata yaparsan "Pardon, yanlis yaptim, simdi duzelteyim" de. "Hata", "basarisiz", "imkansiz" deme.`,
|
|
46
|
+
|
|
47
|
+
// Language rules (stable)
|
|
48
|
+
`KRITIK DIL KURALI: Kullanici Turkce yaziyorsa MUTLAKA yuzde yuz Turkce cevap ver. Asla baska dil kullanma. Turkce karakterleri dogru kullan.`,
|
|
49
|
+
`Yazim: "degilim" dogru, "degil" degil. Turkce dil bilgisi kurallarina uy.`,
|
|
50
|
+
|
|
51
|
+
// Tool rules (stable)
|
|
52
|
+
`ONEMLI: Tool cagirma SIMULE ETME. Sadece duz metin cevap ver. Islem yapmak gerekirse tool'u gercekten cagir.`,
|
|
53
|
+
`COK KRITIK: Goreve baslamadan ONCE <available_skills> listesini tara. Ilgili skill varsa skill_view(name) ile yukle, SONRA goreve basla.`,
|
|
54
|
+
`KRITIK: Skill yuklemeden islem yapma. Ilgili skill varsa once yukle.`,
|
|
55
|
+
`KRITIK: Kullanici kisisel bilgi verdiginde memory(action=add, target=user) ile kaydet.`,
|
|
56
|
+
`KRITIK: Ortam bilgisi, proje kurallari gibi notlari memory(action=add, target=memory) ile kaydet.`,
|
|
57
|
+
`Kullanici hakkinda bilgin gerektiginde memory(action=list, target=user) ile getir.`,
|
|
58
|
+
|
|
59
|
+
// Skills index (stable within session)
|
|
60
|
+
skillsIndexBlock,
|
|
61
|
+
].filter(Boolean).join('\n');
|
|
62
|
+
|
|
63
|
+
// ── CONTEXT TIER (soul + cross-session, built once per resume) ──────
|
|
64
|
+
const context = [
|
|
65
|
+
!isSmallModel && soulSummary ? `=== KISISELIK DOSYALARI ===\n${soulSummary}` : '',
|
|
66
|
+
crossSessionContext ? `=== GECMIS KONUSMALAR ===\n${crossSessionContext}` : '',
|
|
67
|
+
].filter(Boolean).join('\n');
|
|
68
|
+
|
|
69
|
+
// ── VOLATILE TIER (built every turn) ─────────────────────────────────
|
|
70
|
+
const volatile = [
|
|
71
|
+
// Memory snapshot (changes every turn)
|
|
72
|
+
memorySnapshotBlock,
|
|
73
|
+
|
|
74
|
+
// Old JSON memory facts
|
|
75
|
+
memoryFacts.length > 0
|
|
76
|
+
? `Kullanici hakkinda bildiklerin: ${memoryFacts.slice(0, 8).map(f => f.value || f).join('; ')}`
|
|
77
|
+
: '',
|
|
78
|
+
|
|
79
|
+
// User environment (stable within session but changes on resume)
|
|
80
|
+
userHome ? `Kullanicinin home: ${userHome}` : '',
|
|
81
|
+
hasHistory ? 'Bu oturum daha onceki konusmalarin devami.' : '',
|
|
82
|
+
].filter(Boolean).join('\n');
|
|
83
|
+
|
|
84
|
+
return { stable, context, volatile };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Assemble all three tiers into a single system prompt string.
|
|
89
|
+
* stable + context should be cached between turns; volatile rebuilt each turn.
|
|
90
|
+
*/
|
|
91
|
+
function assemble(stable, context, volatile) {
|
|
92
|
+
return [stable, context, volatile].filter(Boolean).join('\n\n');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get the stable+context combined string for cache key comparison.
|
|
97
|
+
*/
|
|
98
|
+
function stableContextKey(stable, context) {
|
|
99
|
+
return stable + '|||' + context;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = { buildTiers, assemble, stableContextKey };
|
package/src/utils/tools.js
CHANGED
|
@@ -2,42 +2,145 @@
|
|
|
2
2
|
* NatureCo CLI — Tool Definitions for OpenAI-compatible APIs
|
|
3
3
|
*
|
|
4
4
|
* src/tools/*.js dosyalarını OpenAI uyumlu function calling format'ına dönüştürür.
|
|
5
|
-
*
|
|
6
|
-
* - name: tool adı
|
|
7
|
-
* - description: ne yaptığı
|
|
8
|
-
* - parameters: JSON schema
|
|
9
|
-
*
|
|
10
|
-
* REPL bu listeyi API'ye gönderir, model tool çağrısı yapar,
|
|
11
|
-
* biz tool'u çalıştırır, sonucu modele geri veririz.
|
|
5
|
+
* v5.7.17: Emoji + toolset + check_fn + registry entegrasyonu.
|
|
12
6
|
*/
|
|
13
7
|
|
|
14
8
|
const fs = require('fs');
|
|
15
9
|
const path = require('path');
|
|
10
|
+
const { globalRegistry } = require('./registry');
|
|
16
11
|
|
|
17
12
|
const TOOLS_DIR = path.join(__dirname, '..', 'tools');
|
|
18
13
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
14
|
+
// ── Emoji map (central, tek kaynak) ──────────────────────────────────────
|
|
15
|
+
const EMOJI_MAP = {
|
|
16
|
+
// File operations
|
|
17
|
+
read_file: '📖', write_file: '✏️', edit_file: '🖊️', list_dir: '📂', file_search: '🔍', grep_search: '🔎', filesystem: '🗄️',
|
|
18
|
+
// Terminal
|
|
19
|
+
bash: '💻', shell_command: '⌨️',
|
|
20
|
+
// Web
|
|
21
|
+
duckduckgo: '🦆', duckduckgo_search: '🦆', web_search: '🌐', web_readability: '📄', firecrawl: '🔥', searxng: '🔬', searxng_search: '🔬', http_request: '🌍', http: '🌍', exa_search: '🔬', parallel_search: '⚡',
|
|
22
|
+
// Browser
|
|
23
|
+
browser: '🖥️',
|
|
24
|
+
// Memory
|
|
25
|
+
memory: '🧠', memory_write: '🧠', memory_search: '🔍',
|
|
26
|
+
// Skills
|
|
27
|
+
skill_view: '📚', skills_list: '📋', skill_generate: '✨', skills_autoload: '🔄', skills_marketplace: '🏪', skill_manage: '🛠️',
|
|
28
|
+
// Agent
|
|
29
|
+
delegate_task: '👥', llm_task: '🤖', sub_agent: '👤',
|
|
30
|
+
// Documents
|
|
31
|
+
document_extract: '📄', notebook_edit: '📓', notes_add: '📝',
|
|
32
|
+
// Git
|
|
33
|
+
git: '🔀',
|
|
34
|
+
// Plan / Todo
|
|
35
|
+
plan: '📋', todo_write: '✅',
|
|
36
|
+
// Media
|
|
37
|
+
image_generation: '🎨', video_generation: '🎬', music_generation: '🎵', media_understanding: '📺', text_to_speech: '🔊', speech_to_text: '🎤', voice_chat: '🗣️',
|
|
38
|
+
// macOS
|
|
39
|
+
mac_alarm: '⏰', mac_app_open: '🚀', mac_app_quit: '⏹️', mac_notify: '🔔', macos_screenshot: '📸', phone_control: '📱', phone_control_enhanced: '📱',
|
|
40
|
+
// Calendar
|
|
41
|
+
calendar_add: '📅',
|
|
42
|
+
// Reminder
|
|
43
|
+
reminder_add: '⏰',
|
|
44
|
+
// Dashboard
|
|
45
|
+
dashboard: '📊',
|
|
46
|
+
kanban: '📋',
|
|
47
|
+
// Canvas
|
|
48
|
+
canvas: '🎨',
|
|
49
|
+
// Plugin
|
|
50
|
+
plugin: '🔌',
|
|
51
|
+
// Soul
|
|
52
|
+
soul: '💫',
|
|
53
|
+
// Cron
|
|
54
|
+
cron_create: '⏱️',
|
|
55
|
+
// Thread
|
|
56
|
+
thread_ownership: '🔗',
|
|
57
|
+
// Audio understanding
|
|
58
|
+
audio_understanding: '🎵',
|
|
59
|
+
// Code execution
|
|
60
|
+
code_execution: '⚡',
|
|
61
|
+
// Cross-session
|
|
62
|
+
cross_session_memory: '🔗',
|
|
63
|
+
};
|
|
25
64
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
65
|
+
// ── Toolset grouping ─────────────────────────────────────────────────────
|
|
66
|
+
const TOOLSET_MAP = {
|
|
67
|
+
// File
|
|
68
|
+
read_file: 'file', write_file: 'file', edit_file: 'file', list_dir: 'file',
|
|
69
|
+
file_search: 'file', grep_search: 'file', filesystem: 'file',
|
|
70
|
+
// Terminal
|
|
71
|
+
bash: 'terminal', shell_command: 'terminal',
|
|
72
|
+
// Web
|
|
73
|
+
duckduckgo: 'web', web_search: 'web', web_readability: 'web', firecrawl: 'web',
|
|
74
|
+
searxng: 'web', http_request: 'web', http: 'web', exa_search: 'web',
|
|
75
|
+
parallel_search: 'web',
|
|
76
|
+
duckduckgo_search: 'web', searxng_search: 'web',
|
|
77
|
+
// Browser
|
|
78
|
+
browser: 'browser',
|
|
79
|
+
// Memory
|
|
80
|
+
memory: 'memory', memory_write: 'memory', memory_search: 'memory',
|
|
81
|
+
// Skills
|
|
82
|
+
skill_view: 'skills', skills_list: 'skills', skill_generate: 'skills',
|
|
83
|
+
skills_autoload: 'skills', skills_marketplace: 'skills', skill_manage: 'skills',
|
|
84
|
+
// Agent
|
|
85
|
+
delegate_task: 'agent', llm_task: 'agent',
|
|
86
|
+
// Documents
|
|
87
|
+
document_extract: 'documents', notebook_edit: 'documents', notes_add: 'documents',
|
|
88
|
+
// Git
|
|
89
|
+
git: 'git',
|
|
90
|
+
// Plan / Todo
|
|
91
|
+
plan: 'planning', todo_write: 'planning',
|
|
92
|
+
// Media
|
|
93
|
+
image_generation: 'media', video_generation: 'media', music_generation: 'media',
|
|
94
|
+
media_understanding: 'media', text_to_speech: 'media', speech_to_text: 'media',
|
|
95
|
+
voice_chat: 'media', audio_understanding: 'media',
|
|
96
|
+
// macOS
|
|
97
|
+
mac_alarm: 'macos', mac_app_open: 'macos', mac_app_quit: 'macos', mac_notify: 'macos',
|
|
98
|
+
macos_screenshot: 'macos', phone_control: 'macos', phone_control_enhanced: 'macos',
|
|
99
|
+
// Calendar
|
|
100
|
+
calendar_add: 'calendar',
|
|
101
|
+
// Reminder
|
|
102
|
+
reminder_add: 'reminders',
|
|
103
|
+
// Other
|
|
104
|
+
dashboard: 'dashboard', canvas: 'canvas', plugin: 'plugins', soul: 'soul',
|
|
105
|
+
kanban: 'planning',
|
|
106
|
+
cron_create: 'cron', thread_ownership: 'threads', code_execution: 'sandbox',
|
|
107
|
+
cross_session_memory: 'memory',
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// ── check_fn'ler (tool availability kontrolleri) ────────────────────────
|
|
111
|
+
function _checkBrowser() {
|
|
112
|
+
try {
|
|
113
|
+
require.resolve('playwright');
|
|
114
|
+
return true;
|
|
115
|
+
} catch { return false; }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function _checkDuckDuckGo() {
|
|
119
|
+
return true; // API-based, always available
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function _checkMacOSTools() {
|
|
123
|
+
return process.platform === 'darwin';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const CHECK_FN_MAP = {
|
|
127
|
+
browser: _checkBrowser,
|
|
128
|
+
mac_alarm: _checkMacOSTools,
|
|
129
|
+
mac_app_open: _checkMacOSTools,
|
|
130
|
+
mac_app_quit: _checkMacOSTools,
|
|
131
|
+
mac_notify: _checkMacOSTools,
|
|
132
|
+
macos_screenshot: _checkMacOSTools,
|
|
133
|
+
phone_control: _checkMacOSTools,
|
|
134
|
+
phone_control_enhanced: _checkMacOSTools,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// ── Provider filtering ───────────────────────────────────────────────────
|
|
31
138
|
function getToolsForProvider(allTools, providerUrl) {
|
|
32
139
|
const url = (providerUrl || '').toLowerCase();
|
|
33
|
-
|
|
34
|
-
// Groq icin minimum tool seti
|
|
35
140
|
if (url.includes('groq.com')) {
|
|
36
|
-
const allowed = ['read_file', 'write_file', 'bash', 'shell_command', 'list_dir', 'soul', 'memory_write', 'memory_search'];
|
|
141
|
+
const allowed = ['read_file', 'write_file', 'bash', 'shell_command', 'list_dir', 'soul', 'memory', 'memory_write', 'memory_search', 'filesystem', 'grep_search'];
|
|
37
142
|
return allTools.filter(t => allowed.includes(t.name));
|
|
38
143
|
}
|
|
39
|
-
|
|
40
|
-
// Anthropic, OpenAI, MiniMax tam set
|
|
41
144
|
return allTools;
|
|
42
145
|
}
|
|
43
146
|
|
|
@@ -45,7 +148,6 @@ function loadToolDefinitions() {
|
|
|
45
148
|
const tools = [];
|
|
46
149
|
const files = fs.readdirSync(TOOLS_DIR).filter(f => f.endsWith('.js'));
|
|
47
150
|
|
|
48
|
-
// v5.6.1: Provider tespiti - Groq icin sadece temel tool'lar
|
|
49
151
|
let isGroq = false;
|
|
50
152
|
try {
|
|
51
153
|
const { getConfig } = require('./config');
|
|
@@ -55,105 +157,119 @@ function loadToolDefinitions() {
|
|
|
55
157
|
}
|
|
56
158
|
} catch (e) {}
|
|
57
159
|
|
|
160
|
+
let disabledTools = new Set();
|
|
161
|
+
try {
|
|
162
|
+
const { getConfig } = require('./config');
|
|
163
|
+
const cfg = getConfig();
|
|
164
|
+
if (Array.isArray(cfg.disabledTools)) disabledTools = new Set(cfg.disabledTools);
|
|
165
|
+
} catch (e) {}
|
|
166
|
+
|
|
58
167
|
const GROQ_ALLOWED = new Set([
|
|
59
168
|
'read_file', 'write_file', 'list_dir', 'bash', 'shell_command',
|
|
60
|
-
'soul', 'memory_write', 'memory_search', 'filesystem', 'grep_search'
|
|
169
|
+
'soul', 'memory', 'memory_write', 'memory_search', 'filesystem', 'grep_search'
|
|
61
170
|
]);
|
|
62
171
|
|
|
63
172
|
for (const file of files) {
|
|
64
173
|
try {
|
|
65
174
|
const toolPath = path.join(TOOLS_DIR, file);
|
|
66
175
|
const mod = require(toolPath);
|
|
176
|
+
const toolName = mod.name || path.basename(file, '.js');
|
|
67
177
|
|
|
68
|
-
|
|
69
|
-
if (
|
|
70
|
-
const toolName = mod.name || path.basename(file, '.js');
|
|
71
|
-
if (!GROQ_ALLOWED.has(toolName)) continue;
|
|
72
|
-
}
|
|
178
|
+
if (isGroq && !GROQ_ALLOWED.has(toolName)) continue;
|
|
179
|
+
if (disabledTools.has(toolName)) continue;
|
|
73
180
|
|
|
74
|
-
// Tool metadata çıkar
|
|
75
181
|
const meta = {
|
|
76
|
-
name:
|
|
182
|
+
name: toolName,
|
|
77
183
|
description: mod.description || `${path.basename(file, '.js')} tool`,
|
|
78
184
|
parameters: mod.parameters || mod.inputSchema || { type: 'object', properties: {} },
|
|
79
185
|
execute: mod.execute || (mod.default && mod.default.execute) || null,
|
|
186
|
+
emoji: EMOJI_MAP[toolName] || '',
|
|
187
|
+
toolset: TOOLSET_MAP[toolName] || 'general',
|
|
188
|
+
checkFn: CHECK_FN_MAP[toolName] || null,
|
|
80
189
|
};
|
|
81
190
|
|
|
82
|
-
// Eğer execute fonksiyonu varsa ekle (CLI'da çalıştırmak için)
|
|
83
191
|
if (meta.execute) {
|
|
84
192
|
tools.push(meta);
|
|
193
|
+
// Registry'ye kaydet
|
|
194
|
+
globalRegistry.register({
|
|
195
|
+
name: meta.name,
|
|
196
|
+
toolset: meta.toolset,
|
|
197
|
+
schema: { name: meta.name, description: meta.description, parameters: meta.parameters },
|
|
198
|
+
handler: meta.execute,
|
|
199
|
+
checkFn: meta.checkFn,
|
|
200
|
+
emoji: meta.emoji,
|
|
201
|
+
});
|
|
85
202
|
}
|
|
86
203
|
} catch (e) {
|
|
87
|
-
// Sessizce atla
|
|
204
|
+
// Sessizce atla
|
|
88
205
|
}
|
|
89
206
|
}
|
|
90
|
-
|
|
91
207
|
return tools;
|
|
92
208
|
}
|
|
93
209
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
210
|
+
const ALIAS_MAP = {
|
|
211
|
+
'brave_search': 'duckduckgo', 'brave-web-search': 'duckduckgo',
|
|
212
|
+
'google_search': 'duckduckgo', 'web_search': 'duckduckgo',
|
|
213
|
+
'browse': 'browser', 'shell': 'bash', 'bash_command': 'bash',
|
|
214
|
+
'execute_command': 'bash', 'run_command': 'bash',
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const BLOCKED_NAMES = new Set([
|
|
218
|
+
'brave_search', 'brave-web-search', 'google_search', 'web_search',
|
|
219
|
+
'browse', 'open', 'search', 'shell', 'bash_command', 'execute_command',
|
|
220
|
+
'run_command', 'sql', 'query', 'lookup',
|
|
221
|
+
]);
|
|
222
|
+
|
|
223
|
+
// ── check_fn TTL cache (Hermes-style, ~30s) ────────────────────────────
|
|
224
|
+
const _checkFnCache = new Map();
|
|
225
|
+
function _cachedCheckFn(fn, key) {
|
|
226
|
+
const now = Date.now();
|
|
227
|
+
const cached = _checkFnCache.get(key);
|
|
228
|
+
if (cached && now - cached.ts < 30000) return cached.result;
|
|
229
|
+
let result = true;
|
|
230
|
+
try { result = fn() !== false; } catch { result = false; }
|
|
231
|
+
_checkFnCache.set(key, { result, ts: now });
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
104
234
|
|
|
235
|
+
function toOpenAIFormat(toolDefs) {
|
|
105
236
|
return toolDefs
|
|
106
|
-
.filter(t => !
|
|
237
|
+
.filter(t => !BLOCKED_NAMES.has(t.name))
|
|
238
|
+
.filter(t => {
|
|
239
|
+
if (t.checkFn) {
|
|
240
|
+
return _cachedCheckFn(t.checkFn, t.name);
|
|
241
|
+
}
|
|
242
|
+
return true;
|
|
243
|
+
})
|
|
107
244
|
.map(t => {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
113
|
-
|
|
245
|
+
let name = t.name;
|
|
246
|
+
if (ALIAS_MAP[name]) name = ALIAS_MAP[name];
|
|
247
|
+
|
|
248
|
+
const cleanParams = JSON.parse(JSON.stringify(t.parameters || {}));
|
|
249
|
+
if (cleanParams.properties) {
|
|
250
|
+
Object.keys(cleanParams.properties).forEach(key => {
|
|
251
|
+
const prop = cleanParams.properties[key];
|
|
252
|
+
if (Array.isArray(prop.type)) prop.type = prop.type[0];
|
|
253
|
+
delete prop.additionalProperties;
|
|
254
|
+
});
|
|
114
255
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
// "type": ["number", "string"] union types Groq'da hata verir
|
|
122
|
-
// Sadece ilk tipi al
|
|
123
|
-
if (Array.isArray(prop.type)) {
|
|
124
|
-
prop.type = prop.type[0];
|
|
125
|
-
}
|
|
126
|
-
// additionalProperties kaldir
|
|
127
|
-
delete prop.additionalProperties;
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
// Groq icin required kismi bazen sorun cikarir - olduugu gibi birak
|
|
131
|
-
// ama type validation'u gevset
|
|
132
|
-
return {
|
|
133
|
-
type: 'function',
|
|
134
|
-
function: {
|
|
135
|
-
name: t.name,
|
|
136
|
-
description: t.description,
|
|
137
|
-
parameters: cleanParams,
|
|
138
|
-
},
|
|
139
|
-
};
|
|
140
|
-
});
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
type: 'function',
|
|
259
|
+
function: { name, description: t.description, parameters: cleanParams },
|
|
260
|
+
};
|
|
261
|
+
});
|
|
141
262
|
}
|
|
142
263
|
|
|
143
|
-
/**
|
|
144
|
-
* Tool çağrısını çalıştır
|
|
145
|
-
* @param toolName - tool adı
|
|
146
|
-
* @param args - tool argümanları (object)
|
|
147
|
-
* @param toolDefs - loadToolDefinitions() sonucu
|
|
148
|
-
* @returns { result, error }
|
|
149
|
-
*/
|
|
150
264
|
async function executeTool(toolName, args, toolDefs) {
|
|
151
265
|
const tool = toolDefs.find(t => t.name === toolName);
|
|
152
|
-
if (!tool) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (
|
|
156
|
-
|
|
266
|
+
if (!tool) return { error: `Tool bulunamadı: ${toolName}` };
|
|
267
|
+
if (!tool.execute) return { error: `Tool execute fonksiyonu yok: ${toolName}` };
|
|
268
|
+
// checkFn — tool disabled? (re-check at runtime with cache)
|
|
269
|
+
if (tool.checkFn) {
|
|
270
|
+
if (!_cachedCheckFn(tool.checkFn, tool.name)) {
|
|
271
|
+
return { error: `${toolName} şu anda kullanılamıyor (check_fn engelledi)` };
|
|
272
|
+
}
|
|
157
273
|
}
|
|
158
274
|
try {
|
|
159
275
|
const result = await tool.execute(args || {});
|
|
@@ -164,7 +280,6 @@ async function executeTool(toolName, args, toolDefs) {
|
|
|
164
280
|
}
|
|
165
281
|
|
|
166
282
|
module.exports = {
|
|
167
|
-
loadToolDefinitions,
|
|
168
|
-
|
|
169
|
-
executeTool,
|
|
283
|
+
loadToolDefinitions, toOpenAIFormat, executeTool, getToolsForProvider,
|
|
284
|
+
EMOJI_MAP, TOOLSET_MAP, CHECK_FN_MAP,
|
|
170
285
|
};
|
package/src/tools/http.js
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
name: 'http_request',
|
|
3
|
-
description: 'Make HTTP requests to any URL (GET, POST, PUT, DELETE, PATCH)',
|
|
4
|
-
inputSchema: {
|
|
5
|
-
type: 'object',
|
|
6
|
-
properties: {
|
|
7
|
-
method: {
|
|
8
|
-
type: 'string',
|
|
9
|
-
description: 'HTTP method: GET, POST, PUT, DELETE, PATCH',
|
|
10
|
-
enum: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
|
|
11
|
-
default: 'GET'
|
|
12
|
-
},
|
|
13
|
-
url: {
|
|
14
|
-
type: 'string',
|
|
15
|
-
description: 'Full URL to request'
|
|
16
|
-
},
|
|
17
|
-
headers: {
|
|
18
|
-
type: 'object',
|
|
19
|
-
description: 'Optional headers (key-value pairs)'
|
|
20
|
-
},
|
|
21
|
-
body: {
|
|
22
|
-
type: 'object',
|
|
23
|
-
description: 'Optional request body (for POST/PUT/PATCH)'
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
required: ['url']
|
|
27
|
-
},
|
|
28
|
-
|
|
29
|
-
async execute(params) {
|
|
30
|
-
try {
|
|
31
|
-
const method = (params.method || 'GET').toUpperCase();
|
|
32
|
-
|
|
33
|
-
const options = {
|
|
34
|
-
method,
|
|
35
|
-
headers: {
|
|
36
|
-
'Content-Type': 'application/json',
|
|
37
|
-
'User-Agent': 'NatureCo-CLI/2.7.0',
|
|
38
|
-
...(params.headers || {})
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// Add body for POST/PUT/PATCH
|
|
43
|
-
if (params.body && ['POST', 'PUT', 'PATCH'].includes(method)) {
|
|
44
|
-
options.body = JSON.stringify(params.body);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const response = await fetch(params.url, options);
|
|
48
|
-
const text = await response.text();
|
|
49
|
-
|
|
50
|
-
// Try to parse as JSON
|
|
51
|
-
let data;
|
|
52
|
-
try {
|
|
53
|
-
data = JSON.parse(text);
|
|
54
|
-
} catch {
|
|
55
|
-
data = text;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Truncate large responses
|
|
59
|
-
if (typeof data === 'string' && data.length > 2000) {
|
|
60
|
-
data = data.slice(0, 2000) + '... (truncated)';
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
success: true,
|
|
65
|
-
status: response.status,
|
|
66
|
-
ok: response.ok,
|
|
67
|
-
statusText: response.statusText,
|
|
68
|
-
headers: Object.fromEntries(response.headers.entries()),
|
|
69
|
-
data: data
|
|
70
|
-
};
|
|
71
|
-
} catch (error) {
|
|
72
|
-
return {
|
|
73
|
-
success: false,
|
|
74
|
-
error: error.message
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
};
|