natureco-cli 2.23.31 → 4.4.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/AUDIT.md +178 -0
- package/CHANGELOG.md +422 -0
- package/DEPLOY_v2.0.0.md +400 -0
- package/README.md +159 -1
- package/bin/natureco.js +182 -12
- package/package.json +43 -11
- package/skills/code-review/SKILL.md +0 -0
- package/skills/summarize/SKILL.md +0 -0
- package/skills/translate/SKILL.md +0 -0
- package/src/commands/acp.js +0 -0
- package/src/commands/admin-rpc.js +0 -0
- package/src/commands/agent.js +0 -0
- package/src/commands/agents.js +0 -0
- package/src/commands/approvals.js +0 -0
- package/src/commands/ask.js +1 -1
- package/src/commands/audit.js +209 -0
- package/src/commands/backup.js +0 -0
- package/src/commands/bonjour.js +0 -0
- package/src/commands/bots.js +0 -0
- package/src/commands/browser.js +0 -0
- package/src/commands/capability.js +0 -0
- package/src/commands/channels.js +0 -0
- package/src/commands/chat.js +0 -0
- package/src/commands/clawbot.js +0 -0
- package/src/commands/clickclack.js +0 -0
- package/src/commands/code.js +0 -0
- package/src/commands/commands.js +0 -0
- package/src/commands/commitments.js +0 -0
- package/src/commands/completion.js +0 -0
- package/src/commands/config.js +11 -2
- package/src/commands/configure.js +0 -0
- package/src/commands/cost.js +210 -0
- package/src/commands/crestodian.js +0 -0
- package/src/commands/cron.js +0 -0
- package/src/commands/daemon.js +0 -0
- package/src/commands/dashboard.js +126 -45
- package/src/commands/device-pair.js +0 -0
- package/src/commands/devices.js +0 -0
- package/src/commands/directory.js +0 -0
- package/src/commands/discord.js +0 -0
- package/src/commands/dns.js +0 -0
- package/src/commands/docs.js +0 -0
- package/src/commands/doctor.js +134 -15
- package/src/commands/exec-policy.js +0 -0
- package/src/commands/gateway-server.js +0 -0
- package/src/commands/gateway.js +0 -0
- package/src/commands/git.js +0 -0
- package/src/commands/health.js +0 -0
- package/src/commands/help.js +0 -0
- package/src/commands/hooks.js +0 -0
- package/src/commands/imessage.js +0 -0
- package/src/commands/infer.js +0 -0
- package/src/commands/init.js +0 -0
- package/src/commands/irc.js +0 -0
- package/src/commands/login.js +0 -0
- package/src/commands/logout.js +0 -0
- package/src/commands/logs.js +0 -0
- package/src/commands/mattermost.js +0 -0
- package/src/commands/mcp.js +0 -0
- package/src/commands/medium.js +206 -0
- package/src/commands/memory-cmd.js +0 -0
- package/src/commands/memory.js +0 -0
- package/src/commands/message.js +0 -0
- package/src/commands/migrate.js +0 -0
- package/src/commands/models.js +0 -0
- package/src/commands/naturehub.js +156 -0
- package/src/commands/node.js +0 -0
- package/src/commands/nodes.js +1 -1
- package/src/commands/oc-path.js +0 -0
- package/src/commands/onboard.js +0 -0
- package/src/commands/open-prose.js +0 -0
- package/src/commands/pairing.js +0 -0
- package/src/commands/path.js +0 -0
- package/src/commands/plugins.js +0 -0
- package/src/commands/policy.js +0 -0
- package/src/commands/proxy.js +0 -0
- package/src/commands/qr.js +0 -0
- package/src/commands/reset.js +0 -0
- package/src/commands/run.js +0 -0
- package/src/commands/sandbox.js +0 -0
- package/src/commands/secrets.js +0 -0
- package/src/commands/security.js +0 -0
- package/src/commands/seo.js +268 -0
- package/src/commands/sessions.js +0 -0
- package/src/commands/setup.js +131 -4
- package/src/commands/signal.js +0 -0
- package/src/commands/skills.js +82 -1
- package/src/commands/slack.js +0 -0
- package/src/commands/sms.js +0 -0
- package/src/commands/status.js +0 -0
- package/src/commands/system.js +0 -0
- package/src/commands/tasks.js +0 -0
- package/src/commands/team.js +171 -0
- package/src/commands/telegram.js +0 -0
- package/src/commands/terminal.js +0 -0
- package/src/commands/thread-ownership.js +0 -0
- package/src/commands/transcripts.js +0 -0
- package/src/commands/tui.js +0 -0
- package/src/commands/ultrareview.js +0 -0
- package/src/commands/uninstall.js +0 -0
- package/src/commands/update.js +0 -0
- package/src/commands/voice.js +0 -0
- package/src/commands/vydra.js +0 -0
- package/src/commands/web-fetch.js +34 -0
- package/src/commands/webhooks.js +0 -0
- package/src/commands/whatsapp.js +0 -0
- package/src/commands/wiki.js +0 -0
- package/src/commands/workboard.js +0 -0
- package/src/commands/xp.js +194 -0
- package/src/tools/audio_understanding.js +0 -0
- package/src/tools/bash.js +0 -0
- package/src/tools/browser.js +0 -0
- package/src/tools/canvas.js +0 -0
- package/src/tools/document_extract.js +0 -0
- package/src/tools/duckduckgo.js +0 -0
- package/src/tools/exa_search.js +0 -0
- package/src/tools/filesystem.js +0 -0
- package/src/tools/firecrawl.js +0 -0
- package/src/tools/git.js +0 -0
- package/src/tools/http.js +0 -0
- package/src/tools/image_generation.js +0 -0
- package/src/tools/list_dir.js +0 -0
- package/src/tools/llm_task.js +43 -11
- package/src/tools/media_understanding.js +0 -0
- package/src/tools/music_generation.js +0 -0
- package/src/tools/parallel_search.js +0 -0
- package/src/tools/phone_control.js +0 -0
- package/src/tools/phone_control_enhanced.js +0 -0
- package/src/tools/read_file.js +0 -0
- package/src/tools/searxng.js +0 -0
- package/src/tools/speech_to_text.js +0 -0
- package/src/tools/text_to_speech.js +0 -0
- package/src/tools/thread_ownership.js +0 -0
- package/src/tools/video_generation.js +0 -0
- package/src/tools/web_readability.js +0 -0
- package/src/tools/web_search.js +0 -0
- package/src/tools/write_file.js +0 -0
- package/src/utils/agents-md.js +0 -0
- package/src/utils/agents.js +0 -0
- package/src/utils/api.js +6 -2
- package/src/utils/approvals.js +0 -0
- package/src/utils/audit.js +199 -0
- package/src/utils/background.js +0 -0
- package/src/utils/baileys.js +0 -0
- package/src/utils/branding.js +136 -0
- package/src/utils/commands.js +0 -0
- package/src/utils/config.js +0 -0
- package/src/utils/cost-tracker.js +360 -0
- package/src/utils/cron.js +0 -0
- package/src/utils/dashboard-server.js +284 -0
- package/src/utils/errors.js +0 -0
- package/src/utils/format.js +7 -10
- package/src/utils/gateway-ws.js +0 -0
- package/src/utils/headless.js +0 -0
- package/src/utils/history.js +0 -0
- package/src/utils/hooks.js +0 -0
- package/src/utils/inquirer-wrapper.js +0 -0
- package/src/utils/mcp-client.js +0 -0
- package/src/utils/mcp.js +0 -0
- package/src/utils/memory.js +0 -0
- package/src/utils/parallel-tools.js +0 -0
- package/src/utils/path-utils.js +0 -0
- package/src/utils/pattern-detector.js +314 -0
- package/src/utils/plugin-registry.js +0 -0
- package/src/utils/secret-scanner.js +204 -0
- package/src/utils/secrets.js +0 -0
- package/src/utils/sessions.js +0 -0
- package/src/utils/skills.js +0 -0
- package/src/utils/sub-agent.js +6 -0
- package/src/utils/token-budget.js +0 -0
- package/src/utils/tool-adapter.js +0 -0
- package/src/utils/tool-runner.js +0 -0
- package/src/utils/tui.js +750 -0
- package/src/utils/web-fetch.js +0 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NatureCo CLI — Local Dashboard Server (Phase 5)
|
|
3
|
+
*
|
|
4
|
+
* `natureco dashboard` → http://localhost:7421
|
|
5
|
+
* Tüm NatureCo verilerini tek sayfada gösterir:
|
|
6
|
+
* - Sessions (chat geçmişi)
|
|
7
|
+
* - Costs (bugün/hafta/ay)
|
|
8
|
+
* - Skills (yüklü + self-evolving proposals)
|
|
9
|
+
* - Crons (zamanlanmış görevler)
|
|
10
|
+
* - Audit (son 24 saat istatistik)
|
|
11
|
+
* - Patterns (en çok kullanılan tool pattern'leri)
|
|
12
|
+
*
|
|
13
|
+
* Vanilla JS + HTML, framework yok. Node'un built-in http modülü.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const http = require('http');
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const os = require('os');
|
|
20
|
+
const url = require('url');
|
|
21
|
+
|
|
22
|
+
const PORT = 7421;
|
|
23
|
+
const HOST = '127.0.0.1';
|
|
24
|
+
|
|
25
|
+
const NATURECO_DIR = path.join(os.homedir(), '.natureco');
|
|
26
|
+
|
|
27
|
+
function safeReadJSON(filePath) {
|
|
28
|
+
try {
|
|
29
|
+
if (fs.existsSync(filePath)) {
|
|
30
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
31
|
+
}
|
|
32
|
+
} catch (e) { return { error: e.message }; }
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getDashboardData() {
|
|
37
|
+
return {
|
|
38
|
+
timestamp: new Date().toISOString(),
|
|
39
|
+
config: safeReadJSON(path.join(NATURECO_DIR, 'config.json')) || {},
|
|
40
|
+
skills: safeReadJSON(path.join(NATURECO_DIR, 'skills', '_index.json')) || { skills: [] },
|
|
41
|
+
proposals: safeReadJSON(path.join(NATURECO_DIR, 'skill-proposals.json')) || [],
|
|
42
|
+
crons: safeReadJSON(path.join(NATURECO_DIR, 'crons.json')) || [],
|
|
43
|
+
costs: safeReadJSON(path.join(NATURECO_DIR, 'cost-tracking.json')) || { entries: [] },
|
|
44
|
+
patterns: safeReadJSON(path.join(NATURECO_DIR, 'patterns.json')) || { sequences: [] },
|
|
45
|
+
audit: getAuditSummary(),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getAuditSummary() {
|
|
50
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
51
|
+
const auditDir = path.join(NATURECO_DIR, 'audit');
|
|
52
|
+
if (!fs.existsSync(auditDir)) return { today: 0, byAction: {} };
|
|
53
|
+
const file = path.join(auditDir, `audit-${today}.jsonl`);
|
|
54
|
+
if (!fs.existsSync(file)) return { today: 0, byAction: {} };
|
|
55
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
56
|
+
const entries = content.split('\n').filter(Boolean).map(l => {
|
|
57
|
+
try { return JSON.parse(l); } catch { return null; }
|
|
58
|
+
}).filter(Boolean);
|
|
59
|
+
const byAction = {};
|
|
60
|
+
for (const e of entries) byAction[e.action] = (byAction[e.action] || 0) + 1;
|
|
61
|
+
return { today: entries.length, byAction };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const HTML_PAGE = `<!DOCTYPE html>
|
|
65
|
+
<html lang="tr">
|
|
66
|
+
<head>
|
|
67
|
+
<meta charset="utf-8">
|
|
68
|
+
<title>NatureCo Dashboard</title>
|
|
69
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
70
|
+
<style>
|
|
71
|
+
* { box-sizing: border-box; }
|
|
72
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
73
|
+
background: #0f172a; color: #e2e8f0; margin: 0; padding: 0; }
|
|
74
|
+
header { background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
|
|
75
|
+
padding: 24px 32px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); }
|
|
76
|
+
header h1 { margin: 0; font-size: 28px; color: white; }
|
|
77
|
+
header .subtitle { color: rgba(255,255,255,0.85); margin-top: 4px; font-size: 14px; }
|
|
78
|
+
main { max-width: 1200px; margin: 0 auto; padding: 32px; }
|
|
79
|
+
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; }
|
|
80
|
+
.card { background: #1e293b; border-radius: 8px; padding: 20px; border: 1px solid #334155; }
|
|
81
|
+
.card h2 { margin: 0 0 16px; font-size: 16px; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
82
|
+
.metric { font-size: 32px; font-weight: 700; color: #f1f5f9; margin: 4px 0; }
|
|
83
|
+
.metric-label { font-size: 12px; color: #64748b; }
|
|
84
|
+
.bar { height: 8px; background: #334155; border-radius: 4px; overflow: hidden; margin: 4px 0; }
|
|
85
|
+
.bar-fill { height: 100%; background: linear-gradient(90deg, #22c55e, #16a34a); }
|
|
86
|
+
.bar-fill.warn { background: linear-gradient(90deg, #f59e0b, #d97706); }
|
|
87
|
+
.bar-fill.danger { background: linear-gradient(90deg, #ef4444, #dc2626); }
|
|
88
|
+
table { width: 100%; border-collapse: collapse; margin-top: 12px; }
|
|
89
|
+
th, td { text-align: left; padding: 8px; border-bottom: 1px solid #334155; font-size: 13px; }
|
|
90
|
+
th { color: #94a3b8; font-weight: 600; }
|
|
91
|
+
.pill { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 11px; font-weight: 600; }
|
|
92
|
+
.pill-green { background: #064e3b; color: #6ee7b7; }
|
|
93
|
+
.pill-red { background: #7f1d1d; color: #fca5a5; }
|
|
94
|
+
.pill-yellow { background: #78350f; color: #fcd34d; }
|
|
95
|
+
.pill-blue { background: #1e3a8a; color: #93c5fd; }
|
|
96
|
+
.footer { text-align: center; color: #64748b; padding: 32px; font-size: 12px; }
|
|
97
|
+
pre { background: #0f172a; padding: 12px; border-radius: 4px; overflow-x: auto; font-size: 12px; }
|
|
98
|
+
code { color: #6ee7b7; }
|
|
99
|
+
.empty { color: #64748b; font-style: italic; }
|
|
100
|
+
</style>
|
|
101
|
+
</head>
|
|
102
|
+
<body>
|
|
103
|
+
<header>
|
|
104
|
+
<h1>🌿 NatureCo Dashboard</h1>
|
|
105
|
+
<div class="subtitle">Local · <span id="ts">--</span> · <a href="/api" style="color:white">JSON API</a></div>
|
|
106
|
+
</header>
|
|
107
|
+
<main>
|
|
108
|
+
<div class="grid">
|
|
109
|
+
<div class="card">
|
|
110
|
+
<h2>💰 Bugünkü Maliyet</h2>
|
|
111
|
+
<div class="metric" id="cost-today">$0.00</div>
|
|
112
|
+
<div class="metric-label">Günlük limit: <span id="cost-limit">$5.00</span></div>
|
|
113
|
+
<div class="bar"><div class="bar-fill" id="cost-bar" style="width:0%"></div></div>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="card">
|
|
116
|
+
<h2>📦 Yüklü Skill</h2>
|
|
117
|
+
<div class="metric" id="skill-count">0</div>
|
|
118
|
+
<div class="metric-label">Bekleyen proposal: <span id="proposal-count">0</span></div>
|
|
119
|
+
</div>
|
|
120
|
+
<div class="card">
|
|
121
|
+
<h2>⏰ Aktif Cron</h2>
|
|
122
|
+
<div class="metric" id="cron-count">0</div>
|
|
123
|
+
<div class="metric-label">Zamanlanmış görev</div>
|
|
124
|
+
</div>
|
|
125
|
+
<div class="card">
|
|
126
|
+
<h2>📋 Bugünkü Audit</h2>
|
|
127
|
+
<div class="metric" id="audit-count">0</div>
|
|
128
|
+
<div class="metric-label">Toplam kayıt</div>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<div class="card" style="margin-top:16px;">
|
|
133
|
+
<h2>💵 Provider Bazlı Maliyet (Bugün)</h2>
|
|
134
|
+
<table id="cost-table">
|
|
135
|
+
<thead><tr><th>Provider</th><th>Maliyet</th><th>Toplam Token</th><th>Çağrı</th></tr></thead>
|
|
136
|
+
<tbody></tbody>
|
|
137
|
+
</table>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<div class="card" style="margin-top:16px;">
|
|
141
|
+
<h2>🧠 Self-Evolving Skill Proposals</h2>
|
|
142
|
+
<table id="proposals-table">
|
|
143
|
+
<thead><tr><th>Öneri</th><th>Tekrar</th><th>Pattern</th><th>İlk Tespit</th></tr></thead>
|
|
144
|
+
<tbody></tbody>
|
|
145
|
+
</table>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<div class="card" style="margin-top:16px;">
|
|
149
|
+
<h2>📊 Audit İstatistik (Bugün)</h2>
|
|
150
|
+
<table id="audit-table">
|
|
151
|
+
<thead><tr><th>Action</th><th>Sayı</th></tr></thead>
|
|
152
|
+
<tbody></tbody>
|
|
153
|
+
</table>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<div class="card" style="margin-top:16px;">
|
|
157
|
+
<h2>📜 Son Tool Çağrıları</h2>
|
|
158
|
+
<table id="patterns-table">
|
|
159
|
+
<thead><tr><th>Zaman</th><th>Tool</th><th>Args</th></tr></thead>
|
|
160
|
+
<tbody></tbody>
|
|
161
|
+
</table>
|
|
162
|
+
</div>
|
|
163
|
+
</main>
|
|
164
|
+
<div class="footer">
|
|
165
|
+
NatureCo CLI Dashboard · port ${PORT} · <a href="/api" style="color:#22c55e">/api</a> tam JSON için
|
|
166
|
+
</div>
|
|
167
|
+
<script>
|
|
168
|
+
async function load() {
|
|
169
|
+
try {
|
|
170
|
+
const res = await fetch('/api');
|
|
171
|
+
const data = await res.json();
|
|
172
|
+
document.getElementById('ts').textContent = new Date(data.timestamp).toLocaleString();
|
|
173
|
+
|
|
174
|
+
// Costs
|
|
175
|
+
const todayEntries = (data.costs.entries || []).filter(e => {
|
|
176
|
+
const d = new Date(e.ts);
|
|
177
|
+
const now = new Date();
|
|
178
|
+
return d.toDateString() === now.toDateString();
|
|
179
|
+
});
|
|
180
|
+
const todayCost = todayEntries.reduce((s, e) => s + (e.cost || 0), 0);
|
|
181
|
+
document.getElementById('cost-today').textContent = '$' + todayCost.toFixed(4);
|
|
182
|
+
const dailyLimit = 5.00;
|
|
183
|
+
const pct = Math.min(100, (todayCost / dailyLimit) * 100);
|
|
184
|
+
const bar = document.getElementById('cost-bar');
|
|
185
|
+
bar.style.width = pct + '%';
|
|
186
|
+
bar.className = 'bar-fill' + (pct > 90 ? ' danger' : pct > 75 ? ' warn' : '');
|
|
187
|
+
|
|
188
|
+
// Provider breakdown
|
|
189
|
+
const byProvider = {};
|
|
190
|
+
for (const e of todayEntries) {
|
|
191
|
+
if (!byProvider[e.provider]) byProvider[e.provider] = { cost:0, tokens:0, count:0 };
|
|
192
|
+
byProvider[e.provider].cost += e.cost;
|
|
193
|
+
byProvider[e.provider].tokens += (e.input || 0) + (e.output || 0);
|
|
194
|
+
byProvider[e.provider].count++;
|
|
195
|
+
}
|
|
196
|
+
const tbody = document.querySelector('#cost-table tbody');
|
|
197
|
+
tbody.innerHTML = '';
|
|
198
|
+
const sorted = Object.entries(byProvider).sort((a,b) => b[1].cost - a[1].cost);
|
|
199
|
+
if (sorted.length === 0) {
|
|
200
|
+
tbody.innerHTML = '<tr><td colspan="4" class="empty">Henüz maliyet kaydı yok.</td></tr>';
|
|
201
|
+
}
|
|
202
|
+
for (const [prov, info] of sorted) {
|
|
203
|
+
tbody.innerHTML += '<tr><td>' + prov + '</td><td>$' + info.cost.toFixed(4) + '</td><td>' + info.tokens.toLocaleString() + '</td><td>' + info.count + '</td></tr>';
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Skills & proposals
|
|
207
|
+
const skills = (data.skills.skills || []);
|
|
208
|
+
document.getElementById('skill-count').textContent = skills.length;
|
|
209
|
+
const pendingProposals = (data.proposals || []).filter(p => p.status === 'pending');
|
|
210
|
+
document.getElementById('proposal-count').textContent = pendingProposals.length;
|
|
211
|
+
const ptbody = document.querySelector('#proposals-table tbody');
|
|
212
|
+
ptbody.innerHTML = '';
|
|
213
|
+
if (pendingProposals.length === 0) {
|
|
214
|
+
ptbody.innerHTML = '<tr><td colspan="4" class="empty">Bekleyen proposal yok.</td></tr>';
|
|
215
|
+
}
|
|
216
|
+
for (const p of pendingProposals) {
|
|
217
|
+
ptbody.innerHTML += '<tr><td><span class="pill pill-blue">' + (p.suggestedName || '?') + '</span></td><td>' + p.count + 'x</td><td><code>' + (p.pattern || '').slice(0, 60) + '</code></td><td>' + new Date(p.firstSeen).toLocaleString() + '</td></tr>';
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Crons
|
|
221
|
+
const crons = Array.isArray(data.crons) ? data.crons : (data.crons.jobs || []);
|
|
222
|
+
document.getElementById('cron-count').textContent = crons.length;
|
|
223
|
+
|
|
224
|
+
// Audit
|
|
225
|
+
document.getElementById('audit-count').textContent = data.audit.today;
|
|
226
|
+
const atbody = document.querySelector('#audit-table tbody');
|
|
227
|
+
atbody.innerHTML = '';
|
|
228
|
+
const sortedAudit = Object.entries(data.audit.byAction).sort((a,b) => b[1] - a[1]);
|
|
229
|
+
if (sortedAudit.length === 0) {
|
|
230
|
+
atbody.innerHTML = '<tr><td colspan="2" class="empty">Bugün audit kaydı yok.</td></tr>';
|
|
231
|
+
}
|
|
232
|
+
for (const [act, count] of sortedAudit) {
|
|
233
|
+
atbody.innerHTML += '<tr><td><code>' + act + '</code></td><td>' + count + '</td></tr>';
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Recent patterns
|
|
237
|
+
const recent = (data.patterns.sequences || []).slice(-10).reverse();
|
|
238
|
+
const patBody = document.querySelector('#patterns-table tbody');
|
|
239
|
+
patBody.innerHTML = '';
|
|
240
|
+
if (recent.length === 0) {
|
|
241
|
+
patBody.innerHTML = '<tr><td colspan="3" class="empty">Henüz tool çağrısı kaydedilmemiş.</td></tr>';
|
|
242
|
+
}
|
|
243
|
+
for (const r of recent) {
|
|
244
|
+
const args = JSON.stringify(r.call.args || {}).slice(0, 80);
|
|
245
|
+
patBody.innerHTML += '<tr><td>' + new Date(r.ts).toLocaleTimeString() + '</td><td><code>' + (r.call.tool || '?') + '</code></td><td><code>' + args + '</code></td></tr>';
|
|
246
|
+
}
|
|
247
|
+
} catch (e) {
|
|
248
|
+
document.getElementById('ts').textContent = 'Error: ' + e.message;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
load();
|
|
252
|
+
setInterval(load, 5000);
|
|
253
|
+
</script>
|
|
254
|
+
</body>
|
|
255
|
+
</html>`;
|
|
256
|
+
|
|
257
|
+
function startServer(port = PORT) {
|
|
258
|
+
const server = http.createServer((req, res) => {
|
|
259
|
+
const parsed = url.parse(req.url, true);
|
|
260
|
+
if (parsed.pathname === '/api' || parsed.pathname === '/api/') {
|
|
261
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
262
|
+
res.end(JSON.stringify(getDashboardData(), null, 2));
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (parsed.pathname === '/' || parsed.pathname === '/index.html') {
|
|
266
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
267
|
+
res.end(HTML_PAGE);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
res.writeHead(404);
|
|
271
|
+
res.end('Not Found');
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
server.listen(port, HOST, () => {
|
|
275
|
+
console.log(`\n 🌿 NatureCo Dashboard başladı!`);
|
|
276
|
+
console.log(` 📊 http://${HOST}:${port}`);
|
|
277
|
+
console.log(` 🔌 API: http://${HOST}:${port}/api`);
|
|
278
|
+
console.log(` ⏹ Durdurmak için Ctrl+C\n`);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
return server;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
module.exports = { startServer, getDashboardData, PORT, HOST };
|
package/src/utils/errors.js
CHANGED
|
File without changes
|
package/src/utils/format.js
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
|
+
const tui = require('./tui');
|
|
3
|
+
tui.init();
|
|
2
4
|
|
|
3
5
|
const W = () => Math.min(process.stdout.columns || 100, 100);
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const rightPad = padding - leftPad;
|
|
11
|
-
console.log('');
|
|
12
|
-
console.log(line);
|
|
13
|
-
console.log(chalk.dim('│') + ' '.repeat(leftPad) + chalk.bold.cyan(text) + ' '.repeat(rightPad) + chalk.dim('│'));
|
|
14
|
-
console.log(chalk.dim('└' + '─'.repeat(w - 2) + '┘'));
|
|
7
|
+
// Yeni TUI-powered header
|
|
8
|
+
function header(text, options = {}) {
|
|
9
|
+
const { icon = '◈', color = tui.PALETTE.primary } = options;
|
|
10
|
+
console.log('\n' + tui.styled(` ${icon} ${text}`, { color, bold: true }));
|
|
11
|
+
console.log(tui.styled(' ' + '─'.repeat(Math.min(W() - 4, 60)), { color: tui.PALETTE.border }));
|
|
15
12
|
}
|
|
16
13
|
|
|
17
14
|
function section(text) {
|
package/src/utils/gateway-ws.js
CHANGED
|
File without changes
|
package/src/utils/headless.js
CHANGED
|
File without changes
|
package/src/utils/history.js
CHANGED
|
File without changes
|
package/src/utils/hooks.js
CHANGED
|
File without changes
|
|
File without changes
|
package/src/utils/mcp-client.js
CHANGED
|
File without changes
|
package/src/utils/mcp.js
CHANGED
|
File without changes
|
package/src/utils/memory.js
CHANGED
|
File without changes
|
|
File without changes
|
package/src/utils/path-utils.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NatureCo CLI — Self-Evolving Skill Detector (Phase 3)
|
|
3
|
+
*
|
|
4
|
+
* Kullanıcının tool çağrı dizilerini izler, tekrar eden pattern'leri tespit eder
|
|
5
|
+
* ve otomatik olarak yeni bir SKILL.md oluşturmayı önerir.
|
|
6
|
+
*
|
|
7
|
+
* Bu, Hermes Agent'ın "self-evolving skills" özelliğinin NatureCo uyarlamasıdır.
|
|
8
|
+
* OpenClaw'da böyle bir özellik yoktur — kullanıcılar her şeyi manuel tanımlar.
|
|
9
|
+
*
|
|
10
|
+
* Algoritma:
|
|
11
|
+
* 1. Her tool çağrısı normalized olarak loglanır (argümanlar generic hale getirilir)
|
|
12
|
+
* 2. Sliding window ile son N çağrıdan pattern çıkarılır
|
|
13
|
+
* 3. Aynı pattern 3+ kez tekrarlandığında → skill önerisi tetiklenir
|
|
14
|
+
* 4. Kullanıcı kabul ederse SKILL.md template oluşturulur
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const os = require('os');
|
|
20
|
+
const crypto = require('crypto');
|
|
21
|
+
|
|
22
|
+
const PATTERNS_FILE = path.join(os.homedir(), '.natureco', 'patterns.json');
|
|
23
|
+
const PROPOSALS_FILE = path.join(os.homedir(), '.natureco', 'skill-proposals.json');
|
|
24
|
+
const MIN_REPETITIONS = 3; // 3 kez tekrar etmeli
|
|
25
|
+
const WINDOW_SIZE = 5; // Son N tool çağrısına bak
|
|
26
|
+
const COOLDOWN_MS = 24 * 3600 * 1000; // Aynı pattern'i 24 saat içinde tekrar önerme
|
|
27
|
+
|
|
28
|
+
// Hassas argümanları normalize et (gerçek değer yerine tür)
|
|
29
|
+
const NORMALIZE_RULES = [
|
|
30
|
+
// URL'ler
|
|
31
|
+
[/(https?:\/\/)[^\s/$.?#].[^\s]*/g, '$1<url>'],
|
|
32
|
+
// Dosya yolları (uzatmalar korunur)
|
|
33
|
+
[/\/[\w./-]+\.(js|ts|tsx|jsx|json|md|py|go|rs|java|cpp|c|h)\b/g, '<path>'],
|
|
34
|
+
// Sayılar (ID'ler, fiyatlar, vs)
|
|
35
|
+
[/\b\d{3,}\b/g, '<num>'],
|
|
36
|
+
// UUID'ler
|
|
37
|
+
[/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, '<uuid>'],
|
|
38
|
+
// ISO tarihler
|
|
39
|
+
[/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/g, '<iso-date>'],
|
|
40
|
+
// Email
|
|
41
|
+
[/[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, '<email>'],
|
|
42
|
+
// Hex string (40+ karakter)
|
|
43
|
+
[/[0-9a-f]{40,}/gi, '<hex>'],
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Bir tool çağrısını normalize et — aynı tipteki çağrılar eşleşsin.
|
|
48
|
+
*/
|
|
49
|
+
function normalizeCall(call) {
|
|
50
|
+
if (!call || typeof call !== 'object') return null;
|
|
51
|
+
let argsStr = '';
|
|
52
|
+
if (call.args && typeof call.args === 'object') {
|
|
53
|
+
argsStr = JSON.stringify(call.args);
|
|
54
|
+
} else if (typeof call.args === 'string') {
|
|
55
|
+
argsStr = call.args;
|
|
56
|
+
}
|
|
57
|
+
for (const [pattern, replacement] of NORMALIZE_RULES) {
|
|
58
|
+
argsStr = argsStr.replace(pattern, replacement);
|
|
59
|
+
}
|
|
60
|
+
return `${call.tool || call.name || 'unknown'}|${argsStr}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Son N tool çağrısından pattern fingerprint çıkar.
|
|
65
|
+
* Sequence elemanları zaten normalize edilmiş string'ler.
|
|
66
|
+
*/
|
|
67
|
+
function fingerprint(sequence) {
|
|
68
|
+
if (!Array.isArray(sequence) || sequence.length === 0) return '';
|
|
69
|
+
return sequence.join('→');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function fingerprintHash(fp) {
|
|
73
|
+
return crypto.createHash('sha256').update(fp).digest('hex').slice(0, 16);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function loadPatterns() {
|
|
77
|
+
if (!fs.existsSync(PATTERNS_FILE)) {
|
|
78
|
+
return { sequences: [], lastProposalAt: {}, stats: { totalCalls: 0, patternsFound: 0 } };
|
|
79
|
+
}
|
|
80
|
+
try { return JSON.parse(fs.readFileSync(PATTERNS_FILE, 'utf8')); }
|
|
81
|
+
catch { return { sequences: [], lastProposalAt: {}, stats: { totalCalls: 0, patternsFound: 0 } }; }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function savePatterns(data) {
|
|
85
|
+
const dir = path.dirname(PATTERNS_FILE);
|
|
86
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
87
|
+
fs.writeFileSync(PATTERNS_FILE, JSON.stringify(data, null, 2), 'utf8');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function loadProposals() {
|
|
91
|
+
if (!fs.existsSync(PROPOSALS_FILE)) return [];
|
|
92
|
+
try { return JSON.parse(fs.readFileSync(PROPOSALS_FILE, 'utf8')); }
|
|
93
|
+
catch { return []; }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function saveProposals(list) {
|
|
97
|
+
fs.writeFileSync(PROPOSALS_FILE, JSON.stringify(list, null, 2), 'utf8');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Yeni bir tool çağrısı kaydet.
|
|
102
|
+
* Returns: { detected: bool, pattern: object|null, proposal: object|null }
|
|
103
|
+
*/
|
|
104
|
+
function recordCall(call) {
|
|
105
|
+
const data = loadPatterns();
|
|
106
|
+
const norm = normalizeCall(call);
|
|
107
|
+
if (!norm) return { detected: false };
|
|
108
|
+
|
|
109
|
+
// Sequence'den fingerprint
|
|
110
|
+
data.sequences.push({ ts: Date.now(), call, normalized: norm });
|
|
111
|
+
// Maksimum 1000 son çağrı tut
|
|
112
|
+
if (data.sequences.length > 1000) {
|
|
113
|
+
data.sequences = data.sequences.slice(-1000);
|
|
114
|
+
}
|
|
115
|
+
data.stats.totalCalls++;
|
|
116
|
+
|
|
117
|
+
// Tüm sequence'leri grupla
|
|
118
|
+
const fpCounts = {};
|
|
119
|
+
for (let i = 0; i < data.sequences.length; i++) {
|
|
120
|
+
// Her pencere boyutu için fingerprint
|
|
121
|
+
for (let win = 1; win <= Math.min(WINDOW_SIZE, i + 1); win++) {
|
|
122
|
+
const seq = data.sequences.slice(i - win + 1, i + 1).map(s => s.normalized);
|
|
123
|
+
const fp = fingerprint(seq);
|
|
124
|
+
const hash = fingerprintHash(fp);
|
|
125
|
+
if (!fpCounts[hash]) fpCounts[hash] = { fp, count: 0, first: data.sequences[i - win + 1].ts, last: data.sequences[i].ts };
|
|
126
|
+
fpCounts[hash].count++;
|
|
127
|
+
fpCounts[hash].last = data.sequences[i].ts;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// MIN_REPETITIONS kez tekrar eden ve son 24 saat içinde pattern var mı?
|
|
132
|
+
const now = Date.now();
|
|
133
|
+
let detected = null;
|
|
134
|
+
for (const [hash, info] of Object.entries(fpCounts)) {
|
|
135
|
+
if (info.count >= MIN_REPETITIONS && (now - info.last) < 7 * 86400000) {
|
|
136
|
+
// Cooldown kontrolü
|
|
137
|
+
if (data.lastProposalAt[hash] && (now - data.lastProposalAt[hash]) < COOLDOWN_MS) continue;
|
|
138
|
+
// Çok genel pattern'leri atla (tek bir tool çağrısı yetmez)
|
|
139
|
+
if (info.fp.split('→').length < 2) continue;
|
|
140
|
+
detected = { hash, ...info };
|
|
141
|
+
data.lastProposalAt[hash] = now;
|
|
142
|
+
data.stats.patternsFound = (data.stats.patternsFound || 0) + 1;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
savePatterns(data);
|
|
148
|
+
|
|
149
|
+
if (detected) {
|
|
150
|
+
const proposal = createProposal(detected);
|
|
151
|
+
return { detected: true, pattern: detected, proposal };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return { detected: false };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Pattern'den skill önerisi oluştur.
|
|
159
|
+
*/
|
|
160
|
+
function createProposal(pattern) {
|
|
161
|
+
const steps = pattern.fp.split('→');
|
|
162
|
+
const tools = steps.map((s, i) => {
|
|
163
|
+
const [name, args] = s.split('|');
|
|
164
|
+
return { step: i + 1, tool: name, argsTemplate: tryParseArgs(args) };
|
|
165
|
+
});
|
|
166
|
+
// İlk tool'un adını skill adı olarak kullanmak mantıklı
|
|
167
|
+
const skillName = suggestSkillName(tools);
|
|
168
|
+
const proposals = loadProposals();
|
|
169
|
+
const proposal = {
|
|
170
|
+
id: `prop-${Date.now().toString(36)}`,
|
|
171
|
+
hash: pattern.hash,
|
|
172
|
+
suggestedName: skillName,
|
|
173
|
+
pattern: pattern.fp,
|
|
174
|
+
count: pattern.count,
|
|
175
|
+
firstSeen: new Date(pattern.first).toISOString(),
|
|
176
|
+
lastSeen: new Date(pattern.last).toISOString(),
|
|
177
|
+
tools,
|
|
178
|
+
createdAt: new Date().toISOString(),
|
|
179
|
+
status: 'pending',
|
|
180
|
+
};
|
|
181
|
+
proposals.unshift(proposal);
|
|
182
|
+
// Maksimum 50 proposal tut
|
|
183
|
+
saveProposals(proposals.slice(0, 50));
|
|
184
|
+
return proposal;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function tryParseArgs(argsStr) {
|
|
188
|
+
try {
|
|
189
|
+
const obj = JSON.parse(argsStr);
|
|
190
|
+
// Hassas alanları placeholder yap
|
|
191
|
+
return obj;
|
|
192
|
+
} catch {
|
|
193
|
+
return argsStr;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function suggestSkillName(tools) {
|
|
198
|
+
// İlk 2 tool'u birleştir
|
|
199
|
+
const parts = tools.slice(0, 2).map(t => t.tool.replace(/_/g, '-'));
|
|
200
|
+
return parts.join('-to-').slice(0, 40);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Bir proposal'dan gerçek SKILL.md oluştur.
|
|
205
|
+
*/
|
|
206
|
+
function generateSkillMd(proposal) {
|
|
207
|
+
const toolList = proposal.tools.map(t => `- \`${t.tool}\` (step ${t.step})`).join('\n');
|
|
208
|
+
const name = proposal.suggestedName;
|
|
209
|
+
const created = new Date().toISOString().slice(0, 10);
|
|
210
|
+
|
|
211
|
+
return `---
|
|
212
|
+
name: ${name}
|
|
213
|
+
description: Auto-generated skill from repeated tool pattern
|
|
214
|
+
metadata: {"natureco": {"auto_generated": true, "created": "${created}", "uses": ${proposal.count}}, "os": ["darwin", "linux", "win32"]}
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
# ${name} (auto-generated)
|
|
218
|
+
|
|
219
|
+
Bu skill **${proposal.count} kez** tekrar eden bir tool pattern'inden otomatik oluşturuldu.
|
|
220
|
+
|
|
221
|
+
**Tespit edilen adımlar:**
|
|
222
|
+
${toolList}
|
|
223
|
+
|
|
224
|
+
**Pattern:**
|
|
225
|
+
\`\`\`
|
|
226
|
+
${proposal.pattern}
|
|
227
|
+
\`\`\`
|
|
228
|
+
|
|
229
|
+
## Kullanım
|
|
230
|
+
|
|
231
|
+
Bu skill çağrıldığında, yukarıdaki tool dizisi otomatik olarak sırayla çalıştırılır.
|
|
232
|
+
|
|
233
|
+
\`\`\`
|
|
234
|
+
natureco skills run ${name}
|
|
235
|
+
\`\`\`
|
|
236
|
+
|
|
237
|
+
## Detaylar
|
|
238
|
+
|
|
239
|
+
- İlk tespit: ${proposal.firstSeen}
|
|
240
|
+
- Son tespit: ${proposal.lastSeen}
|
|
241
|
+
- Pattern hash: \`${proposal.hash}\`
|
|
242
|
+
|
|
243
|
+
## İyileştirme
|
|
244
|
+
|
|
245
|
+
Bu skill otomatik oluşturuldu. Daha iyi çalışması için:
|
|
246
|
+
1. SKILL.md içeriğini düzenle (description, kullanım notları)
|
|
247
|
+
2. Skill'in artık gerekmediğine karar verirsen: \`natureco skills remove ${name}\`
|
|
248
|
+
3. Yeniden oluşturmak için: \`natureco skills forget ${proposal.hash}\`
|
|
249
|
+
`;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Proposal'ı kabul et ve SKILL.md olarak kaydet.
|
|
254
|
+
*/
|
|
255
|
+
function acceptProposal(proposalId) {
|
|
256
|
+
const proposals = loadProposals();
|
|
257
|
+
const idx = proposals.findIndex(p => p.id === proposalId);
|
|
258
|
+
if (idx === -1) return { success: false, reason: 'Proposal bulunamadı' };
|
|
259
|
+
|
|
260
|
+
const proposal = proposals[idx];
|
|
261
|
+
const userSkillsDir = path.join(os.homedir(), '.natureco', 'skills');
|
|
262
|
+
if (!fs.existsSync(userSkillsDir)) fs.mkdirSync(userSkillsDir, { recursive: true });
|
|
263
|
+
|
|
264
|
+
const skillDir = path.join(userSkillsDir, proposal.suggestedName);
|
|
265
|
+
if (fs.existsSync(skillDir)) {
|
|
266
|
+
return { success: false, reason: `Skill zaten var: ${proposal.suggestedName}` };
|
|
267
|
+
}
|
|
268
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
269
|
+
|
|
270
|
+
const skillMd = generateSkillMd(proposal);
|
|
271
|
+
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), skillMd, 'utf8');
|
|
272
|
+
|
|
273
|
+
proposals[idx].status = 'accepted';
|
|
274
|
+
proposals[idx].acceptedAt = new Date().toISOString();
|
|
275
|
+
saveProposals(proposals);
|
|
276
|
+
|
|
277
|
+
return { success: true, path: path.join(skillDir, 'SKILL.md'), skillName: proposal.suggestedName };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Proposal'ı reddet.
|
|
282
|
+
*/
|
|
283
|
+
function rejectProposal(proposalId, reason = '') {
|
|
284
|
+
const proposals = loadProposals();
|
|
285
|
+
const idx = proposals.findIndex(p => p.id === proposalId);
|
|
286
|
+
if (idx === -1) return { success: false };
|
|
287
|
+
proposals[idx].status = 'rejected';
|
|
288
|
+
proposals[idx].rejectedAt = new Date().toISOString();
|
|
289
|
+
proposals[idx].rejectionReason = reason;
|
|
290
|
+
saveProposals(proposals);
|
|
291
|
+
return { success: true };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Pattern izlemeyi sıfırla.
|
|
296
|
+
*/
|
|
297
|
+
function reset() {
|
|
298
|
+
if (fs.existsSync(PATTERNS_FILE)) fs.unlinkSync(PATTERNS_FILE);
|
|
299
|
+
if (fs.existsSync(PROPOSALS_FILE)) fs.unlinkSync(PROPOSALS_FILE);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
module.exports = {
|
|
303
|
+
normalizeCall,
|
|
304
|
+
fingerprint,
|
|
305
|
+
recordCall,
|
|
306
|
+
loadProposals,
|
|
307
|
+
saveProposals,
|
|
308
|
+
acceptProposal,
|
|
309
|
+
rejectProposal,
|
|
310
|
+
generateSkillMd,
|
|
311
|
+
reset,
|
|
312
|
+
MIN_REPETITIONS,
|
|
313
|
+
WINDOW_SIZE,
|
|
314
|
+
};
|
|
File without changes
|