natureco-cli 2.23.32 → 4.4.1

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.
Files changed (174) hide show
  1. package/AUDIT.md +178 -0
  2. package/CHANGELOG.md +422 -0
  3. package/DEPLOY_v2.0.0.md +400 -0
  4. package/README.md +159 -1
  5. package/bin/natureco.js +170 -8
  6. package/package.json +43 -11
  7. package/skills/code-review/SKILL.md +0 -0
  8. package/skills/summarize/SKILL.md +0 -0
  9. package/skills/translate/SKILL.md +0 -0
  10. package/src/commands/acp.js +0 -0
  11. package/src/commands/admin-rpc.js +0 -0
  12. package/src/commands/agent.js +0 -0
  13. package/src/commands/agents.js +0 -0
  14. package/src/commands/approvals.js +0 -0
  15. package/src/commands/ask.js +0 -0
  16. package/src/commands/audit.js +209 -0
  17. package/src/commands/backup.js +0 -0
  18. package/src/commands/bonjour.js +0 -0
  19. package/src/commands/bots.js +0 -0
  20. package/src/commands/browser.js +0 -0
  21. package/src/commands/capability.js +0 -0
  22. package/src/commands/channels.js +0 -0
  23. package/src/commands/chat.js +0 -0
  24. package/src/commands/clawbot.js +0 -0
  25. package/src/commands/clickclack.js +0 -0
  26. package/src/commands/code.js +0 -0
  27. package/src/commands/commands.js +0 -0
  28. package/src/commands/commitments.js +0 -0
  29. package/src/commands/completion.js +0 -0
  30. package/src/commands/config.js +0 -0
  31. package/src/commands/configure.js +0 -0
  32. package/src/commands/cost.js +210 -0
  33. package/src/commands/crestodian.js +0 -0
  34. package/src/commands/cron.js +0 -0
  35. package/src/commands/daemon.js +0 -0
  36. package/src/commands/dashboard.js +126 -45
  37. package/src/commands/device-pair.js +0 -0
  38. package/src/commands/devices.js +0 -0
  39. package/src/commands/directory.js +0 -0
  40. package/src/commands/discord.js +0 -0
  41. package/src/commands/dns.js +0 -0
  42. package/src/commands/docs.js +0 -0
  43. package/src/commands/doctor.js +134 -15
  44. package/src/commands/exec-policy.js +0 -0
  45. package/src/commands/gateway-server.js +0 -0
  46. package/src/commands/gateway.js +0 -0
  47. package/src/commands/git.js +0 -0
  48. package/src/commands/health.js +0 -0
  49. package/src/commands/help.js +0 -0
  50. package/src/commands/hooks.js +0 -0
  51. package/src/commands/imessage.js +0 -0
  52. package/src/commands/infer.js +0 -0
  53. package/src/commands/init.js +0 -0
  54. package/src/commands/irc.js +0 -0
  55. package/src/commands/login.js +0 -0
  56. package/src/commands/logout.js +0 -0
  57. package/src/commands/logs.js +0 -0
  58. package/src/commands/mattermost.js +0 -0
  59. package/src/commands/mcp.js +0 -0
  60. package/src/commands/medium.js +206 -0
  61. package/src/commands/memory-cmd.js +0 -0
  62. package/src/commands/memory.js +0 -0
  63. package/src/commands/message.js +0 -0
  64. package/src/commands/migrate.js +0 -0
  65. package/src/commands/models.js +0 -0
  66. package/src/commands/naturehub.js +156 -0
  67. package/src/commands/node.js +0 -0
  68. package/src/commands/nodes.js +0 -0
  69. package/src/commands/oc-path.js +0 -0
  70. package/src/commands/onboard.js +0 -0
  71. package/src/commands/open-prose.js +0 -0
  72. package/src/commands/pairing.js +0 -0
  73. package/src/commands/path.js +0 -0
  74. package/src/commands/plugins.js +0 -0
  75. package/src/commands/policy.js +0 -0
  76. package/src/commands/proxy.js +0 -0
  77. package/src/commands/qr.js +0 -0
  78. package/src/commands/reset.js +0 -0
  79. package/src/commands/run.js +0 -0
  80. package/src/commands/sandbox.js +0 -0
  81. package/src/commands/secrets.js +0 -0
  82. package/src/commands/security.js +0 -0
  83. package/src/commands/seo.js +268 -0
  84. package/src/commands/sessions.js +0 -0
  85. package/src/commands/setup.js +13 -6
  86. package/src/commands/signal.js +0 -0
  87. package/src/commands/skills.js +82 -1
  88. package/src/commands/slack.js +0 -0
  89. package/src/commands/sms.js +0 -0
  90. package/src/commands/status.js +0 -0
  91. package/src/commands/system.js +0 -0
  92. package/src/commands/tasks.js +0 -0
  93. package/src/commands/team.js +171 -0
  94. package/src/commands/telegram.js +0 -0
  95. package/src/commands/terminal.js +0 -0
  96. package/src/commands/thread-ownership.js +0 -0
  97. package/src/commands/transcripts.js +0 -0
  98. package/src/commands/tui.js +0 -0
  99. package/src/commands/ultrareview.js +0 -0
  100. package/src/commands/uninstall.js +0 -0
  101. package/src/commands/update.js +0 -0
  102. package/src/commands/voice.js +0 -0
  103. package/src/commands/vydra.js +0 -0
  104. package/src/commands/web-fetch.js +0 -0
  105. package/src/commands/webhooks.js +0 -0
  106. package/src/commands/whatsapp.js +0 -0
  107. package/src/commands/wiki.js +0 -0
  108. package/src/commands/workboard.js +0 -0
  109. package/src/commands/xp.js +194 -0
  110. package/src/tools/audio_understanding.js +0 -0
  111. package/src/tools/bash.js +0 -0
  112. package/src/tools/browser.js +0 -0
  113. package/src/tools/canvas.js +0 -0
  114. package/src/tools/document_extract.js +0 -0
  115. package/src/tools/duckduckgo.js +0 -0
  116. package/src/tools/exa_search.js +0 -0
  117. package/src/tools/filesystem.js +0 -0
  118. package/src/tools/firecrawl.js +0 -0
  119. package/src/tools/git.js +0 -0
  120. package/src/tools/http.js +0 -0
  121. package/src/tools/image_generation.js +0 -0
  122. package/src/tools/list_dir.js +0 -0
  123. package/src/tools/llm_task.js +43 -11
  124. package/src/tools/media_understanding.js +0 -0
  125. package/src/tools/music_generation.js +0 -0
  126. package/src/tools/parallel_search.js +0 -0
  127. package/src/tools/phone_control.js +0 -0
  128. package/src/tools/phone_control_enhanced.js +0 -0
  129. package/src/tools/read_file.js +0 -0
  130. package/src/tools/searxng.js +0 -0
  131. package/src/tools/speech_to_text.js +0 -0
  132. package/src/tools/text_to_speech.js +0 -0
  133. package/src/tools/thread_ownership.js +0 -0
  134. package/src/tools/video_generation.js +0 -0
  135. package/src/tools/web_readability.js +0 -0
  136. package/src/tools/web_search.js +0 -0
  137. package/src/tools/write_file.js +0 -0
  138. package/src/utils/agents-md.js +0 -0
  139. package/src/utils/agents.js +0 -0
  140. package/src/utils/api.js +5 -1
  141. package/src/utils/approvals.js +0 -0
  142. package/src/utils/audit.js +199 -0
  143. package/src/utils/background.js +0 -0
  144. package/src/utils/baileys.js +0 -0
  145. package/src/utils/branding.js +136 -0
  146. package/src/utils/commands.js +0 -0
  147. package/src/utils/config.js +0 -0
  148. package/src/utils/cost-tracker.js +360 -0
  149. package/src/utils/cron.js +0 -0
  150. package/src/utils/dashboard-server.js +284 -0
  151. package/src/utils/errors.js +0 -0
  152. package/src/utils/format.js +7 -10
  153. package/src/utils/gateway-ws.js +0 -0
  154. package/src/utils/headless.js +0 -0
  155. package/src/utils/history.js +0 -0
  156. package/src/utils/hooks.js +0 -0
  157. package/src/utils/inquirer-wrapper.js +0 -0
  158. package/src/utils/mcp-client.js +0 -0
  159. package/src/utils/mcp.js +0 -0
  160. package/src/utils/memory.js +0 -0
  161. package/src/utils/parallel-tools.js +0 -0
  162. package/src/utils/path-utils.js +0 -0
  163. package/src/utils/pattern-detector.js +314 -0
  164. package/src/utils/plugin-registry.js +0 -0
  165. package/src/utils/secret-scanner.js +204 -0
  166. package/src/utils/secrets.js +0 -0
  167. package/src/utils/sessions.js +0 -0
  168. package/src/utils/skills.js +0 -0
  169. package/src/utils/sub-agent.js +6 -0
  170. package/src/utils/token-budget.js +0 -0
  171. package/src/utils/tool-adapter.js +0 -0
  172. package/src/utils/tool-runner.js +0 -0
  173. package/src/utils/tui.js +750 -0
  174. package/src/utils/web-fetch.js +0 -0
@@ -0,0 +1,210 @@
1
+ /**
2
+ * natureco cost — Maliyet takibi ve bütçe yönetimi (Phase 4)
3
+ *
4
+ * Kullanım:
5
+ * natureco cost Bugünün maliyetini göster
6
+ * natureco cost today Bugünkü maliyet
7
+ * natureco cost week Bu haftaki maliyet
8
+ * natureco cost month Bu ayki maliyet
9
+ * natureco cost all Tüm zamanlar
10
+ * natureco cost budget Bütçe durumu ve uyarılar
11
+ * natureco cost set <key> <val> Bütçe ayarla (dailyLimit 5, monthlyLimit 100, warnAt 0.75)
12
+ * natureco cost model <prompt> Bir prompt için önerilen modeli göster
13
+ * natureco cost prices Tüm fiyatları listele
14
+ */
15
+
16
+ const chalk = require('chalk');
17
+ const tui = require('../utils/tui');
18
+ const cost = require('../utils/cost-tracker');
19
+ const audit = require('../utils/audit');
20
+
21
+ function showPeriod(period) {
22
+ const data = cost.totalForPeriod(period);
23
+ const icon = { today: '📅', week: '📆', month: '🗓️', all: '♾️ ' }[period] || '💰';
24
+ console.log(tui.styled(`\n ${icon} Maliyet Raporu · ${period.toUpperCase()}`, { color: tui.PALETTE.primary, bold: true }));
25
+ console.log(tui.styled(' ' + '─'.repeat(56), { color: tui.PALETTE.border }));
26
+
27
+ // Üst metrik kartı
28
+ const cardWidth = 54;
29
+ const cardLines = [];
30
+ cardLines.push(tui.styled(' ╭' + '─'.repeat(cardWidth) + '╮', { color: tui.PALETTE.border }));
31
+ cardLines.push(tui.styled(' │', { color: tui.PALETTE.border }) + ' ' + tui.C.muted('Toplam maliyet:') + ' ' + tui.styled(cost.formatUSD(data.totalCost).padStart(12), { color: tui.PALETTE.primary, bold: true }) + ' ' + tui.styled('│', { color: tui.PALETTE.border }));
32
+ cardLines.push(tui.styled(' │', { color: tui.PALETTE.border }) + ' ' + tui.C.muted('Toplam token:') + ' ' + tui.C.text((data.totalInput + data.totalOutput).toLocaleString().padStart(8) + ` (${data.totalInput.toLocaleString()} in / ${data.totalOutput.toLocaleString()} out)`) + ' ' + tui.styled('│', { color: tui.PALETTE.border }));
33
+ cardLines.push(tui.styled(' │', { color: tui.PALETTE.border }) + ' ' + tui.C.muted('Çağrı sayısı:') + ' ' + tui.C.text(String(data.entries).padStart(8)) + ' ' + tui.styled('│', { color: tui.PALETTE.border }));
34
+ cardLines.push(tui.styled(' ╰' + '─'.repeat(cardWidth) + '╯', { color: tui.PALETTE.border }));
35
+ console.log(cardLines.join('\n'));
36
+
37
+ if (Object.keys(data.byProvider).length > 0) {
38
+ console.log('\n' + tui.styled(' 💵 Provider Bazlı Maliyet', { color: tui.PALETTE.secondary, bold: true }));
39
+ const providerRows = Object.entries(data.byProvider)
40
+ .sort((a, b) => b[1] - a[1])
41
+ .map(([provider, amount]) => ({
42
+ provider,
43
+ amount: cost.formatUSD(amount),
44
+ pct: tui.progressBar(amount / Math.max(0.01, data.totalCost), 1, { width: 20, showPercent: false }),
45
+ }));
46
+ console.log('\n' + tui.table(providerRows, [
47
+ { key: 'provider', label: 'Provider', minWidth: 12 },
48
+ { key: 'amount', label: 'Maliyet', minWidth: 10, render: r => tui.C.brand(r.amount) },
49
+ { key: 'pct', label: 'Dağılım', minWidth: 20, render: r => tui.styled(r.pct, { color: tui.PALETTE.primary }) },
50
+ ], { borderStyle: 'round', zebra: true }));
51
+ }
52
+
53
+ if (Object.keys(data.byModel).length > 0) {
54
+ console.log('\n' + tui.styled(' 🤖 Model Bazlı Maliyet (Top 5)', { color: tui.PALETTE.accent, bold: true }));
55
+ const sortedModels = Object.entries(data.byModel).sort((a, b) => b[1] - a[1]).slice(0, 5);
56
+ const modelRows = sortedModels.map(([model, amount]) => ({
57
+ model,
58
+ amount: cost.formatUSD(amount),
59
+ }));
60
+ console.log('\n' + tui.table(modelRows, [
61
+ { key: 'model', label: 'Model', minWidth: 40, render: r => tui.C.muted(r.model) },
62
+ { key: 'amount', label: 'Maliyet', minWidth: 10, render: r => tui.C.brand(r.amount) },
63
+ ], { borderStyle: 'round', zebra: true }));
64
+ }
65
+ console.log('');
66
+ }
67
+
68
+ function showBudget() {
69
+ const status = cost.checkBudget();
70
+ const budget = cost.loadBudget();
71
+
72
+ console.log('\n' + tui.styled(' 🛡️ Bütçe Durumu', { color: tui.PALETTE.primary, bold: true }));
73
+ console.log(tui.styled(' ' + '─'.repeat(56), { color: tui.PALETTE.border }));
74
+
75
+ // Günlük
76
+ const dailyBar = tui.progressBar(status.daily.usage, 1, {
77
+ width: 25, showPercent: true,
78
+ fillChar: status.daily.exceeded ? '▓' : status.daily.warning ? '▒' : '█',
79
+ });
80
+ const dailyColor = status.daily.exceeded ? tui.PALETTE.danger : status.daily.warning ? tui.PALETTE.warning : tui.PALETTE.success;
81
+ console.log(`\n ${tui.C.muted('Günlük limit')} ${tui.C.brand(cost.formatUSD(budget.dailyLimit).padStart(10))} ${tui.styled(dailyBar, { color: dailyColor })}`);
82
+ if (status.daily.exceeded) console.log(' ' + tui.styled('⚠️ Günlük limit aşıldı!', { color: tui.PALETTE.danger, bold: true }));
83
+ else if (status.daily.warning) console.log(' ' + tui.styled(`⚠️ %${(budget.warnAt * 100)} eşiğine yaklaşıldı`, { color: tui.PALETTE.warning }));
84
+
85
+ console.log('');
86
+
87
+ // Aylık
88
+ const monthlyBar = tui.progressBar(status.monthly.usage, 1, {
89
+ width: 25, showPercent: true,
90
+ fillChar: status.monthly.exceeded ? '▓' : status.monthly.warning ? '▒' : '█',
91
+ });
92
+ const monthlyColor = status.monthly.exceeded ? tui.PALETTE.danger : status.monthly.warning ? tui.PALETTE.warning : tui.PALETTE.success;
93
+ console.log(` ${tui.C.muted('Aylık limit')} ${tui.C.brand(cost.formatUSD(budget.monthlyLimit).padStart(10))} ${tui.styled(monthlyBar, { color: monthlyColor })}`);
94
+ if (status.monthly.exceeded) console.log(' ' + tui.styled('⚠️ Aylık limit aşıldı!', { color: tui.PALETTE.danger, bold: true }));
95
+ else if (status.monthly.warning) console.log(' ' + tui.styled(`⚠️ %${(budget.warnAt * 100)} eşiğine yaklaşıldı`, { color: tui.PALETTE.warning }));
96
+
97
+ if (status.shouldDowngrade) {
98
+ console.log('\n' + tui.styled(' ⬇️ Otomatik downgrade önerilir — basit soruları ucuz modele yönlendir.', { color: tui.PALETTE.warning, bold: true }));
99
+ }
100
+
101
+ console.log('\n' + tui.C.muted(' Ayarlamak için: ') + tui.C.brand('natureco cost set <key> <value>'));
102
+ console.log('');
103
+ }
104
+
105
+ function setBudget(args) {
106
+ const [key, value] = args;
107
+ if (!key || value === undefined) {
108
+ console.log(chalk.red('\n Kullanım: natureco cost set <key> <value>\n'));
109
+ console.log(chalk.gray(' Anahtarlar: dailyLimit, monthlyLimit, warnAt, downgradeAt'));
110
+ console.log(chalk.gray(' Örnek: natureco cost set dailyLimit 3.00'));
111
+ console.log('');
112
+ return;
113
+ }
114
+ const budget = cost.loadBudget();
115
+ const num = parseFloat(value);
116
+ if (isNaN(num) && key !== 'preset') {
117
+ console.log(chalk.red(`\n Geçersiz sayı: ${value}\n`));
118
+ return;
119
+ }
120
+ budget[key] = num;
121
+ cost.saveBudget(budget);
122
+ console.log(chalk.green(`\n ✓ ${key} = ${value}\n`));
123
+ audit.log(audit.ACTIONS.CONFIG_CHANGE, { source: 'cost', key, value });
124
+ }
125
+
126
+ function suggestModel(args) {
127
+ const prompt = args.join(' ');
128
+ if (!prompt) {
129
+ console.log(chalk.red('\n Kullanım: natureco cost model "<prompt>"\n'));
130
+ return;
131
+ }
132
+ const suggestion = cost.suggestModel(prompt);
133
+ if (!suggestion) return;
134
+
135
+ console.log(chalk.bold('\n 🎯 Model Önerisi\n'));
136
+ console.log(chalk.gray(' Prompt karmaşıklığı: ') + chalk.cyan(suggestion.complexity));
137
+ console.log(chalk.gray(` ${cost.ROUTING[suggestion.complexity].description}\n`));
138
+ console.log(chalk.gray(' Önerilen model: ') + chalk.bold(`${suggestion.provider}:${suggestion.model}`));
139
+ console.log(chalk.gray(' Neden: ') + chalk.gray(suggestion.reason));
140
+
141
+ const pricing = cost.getPricing(suggestion.provider, suggestion.model);
142
+ console.log(chalk.gray(' Fiyat: ') + chalk.cyan(`${cost.formatUSD(pricing.input)} in / ${cost.formatUSD(pricing.output)} out (per 1M)`));
143
+
144
+ if (suggestion.optimal) {
145
+ console.log(chalk.green('\n ✓ Mevcut model zaten optimal.\n'));
146
+ } else {
147
+ console.log(chalk.yellow('\n ⚠️ Mevcut model optimal değil. Değiştirmek için:'));
148
+ console.log(chalk.cyan(` natureco config set providerUrl <url>`));
149
+ console.log(chalk.cyan(` natureco config set providerModel ${suggestion.model}`));
150
+ console.log('');
151
+ }
152
+ }
153
+
154
+ function showPrices() {
155
+ console.log(chalk.bold('\n 💵 Model Fiyatları (USD / 1M token)\n'));
156
+ console.log(chalk.gray(' ' + '─'.repeat(60)));
157
+
158
+ // Provider'lara göre grupla
159
+ const grouped = {};
160
+ for (const [key, price] of Object.entries(cost.PRICING)) {
161
+ const [provider] = key.split(':');
162
+ if (!grouped[provider]) grouped[provider] = [];
163
+ grouped[provider].push({ model: key.slice(provider.length + 1), ...price });
164
+ }
165
+
166
+ for (const [provider, models] of Object.entries(grouped)) {
167
+ console.log(chalk.bold(`\n ${provider.toUpperCase()}`));
168
+ for (const m of models) {
169
+ const inPrice = m.input === 0 ? chalk.green('ücretsiz') : cost.formatUSD(m.input);
170
+ const outPrice = m.output === 0 ? chalk.green('ücretsiz') : cost.formatUSD(m.output);
171
+ console.log(` ${m.model.padEnd(40)} ${inPrice.padStart(10)} in ${outPrice.padStart(10)} out`);
172
+ }
173
+ }
174
+ console.log('');
175
+ }
176
+
177
+ function cost_cmd(args) {
178
+ const [action, ...params] = args || [];
179
+
180
+ if (!action || action === 'today') {
181
+ showPeriod('today');
182
+ return;
183
+ }
184
+
185
+ if (action === 'week') { showPeriod('week'); return; }
186
+ if (action === 'month') { showPeriod('month'); return; }
187
+ if (action === 'all') { showPeriod('all'); return; }
188
+
189
+ if (action === 'budget') { showBudget(); return; }
190
+
191
+ if (action === 'set') { setBudget(params); return; }
192
+
193
+ if (action === 'model') { suggestModel(params); return; }
194
+
195
+ if (action === 'prices') { showPrices(); return; }
196
+
197
+ // Yardım
198
+ console.log(chalk.yellow('\n Kullanım:'));
199
+ console.log(chalk.gray(' natureco cost Bugünün maliyeti'));
200
+ console.log(chalk.gray(' natureco cost week Bu hafta'));
201
+ console.log(chalk.gray(' natureco cost month Bu ay'));
202
+ console.log(chalk.gray(' natureco cost all Tüm zamanlar'));
203
+ console.log(chalk.gray(' natureco cost budget Bütçe durumu'));
204
+ console.log(chalk.gray(' natureco cost set <k> <v> Bütçe ayarla'));
205
+ console.log(chalk.gray(' natureco cost model "<p>" Model önerisi'));
206
+ console.log(chalk.gray(' natureco cost prices Fiyat listesi'));
207
+ console.log('');
208
+ }
209
+
210
+ module.exports = cost_cmd;
File without changes
File without changes
File without changes
@@ -1,69 +1,150 @@
1
+ /**
2
+ * natureco dashboard — Local web dashboard (Phase 5)
3
+ *
4
+ * Kullanım:
5
+ * natureco dashboard Dashboard'u başlat ve tarayıcıda aç
6
+ * natureco dashboard start Sadece başlat (arka plan)
7
+ * natureco dashboard status Çalışıyor mu kontrol et
8
+ * natureco dashboard url URL'i göster
9
+ * natureco dashboard stop Durdur (PID file'dan)
10
+ *
11
+ * Phase 5 — Vanilla JS, framework yok, port 7421
12
+ */
13
+
1
14
  const chalk = require('chalk');
2
- const http = require('http');
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const os = require('os');
3
18
  const net = require('net');
19
+ const { exec } = require('child_process');
20
+ const dashboardServer = require('../utils/dashboard-server');
4
21
 
5
- const DASHBOARD_URL = 'http://127.0.0.1:18789';
22
+ const DASHBOARD_PORT = dashboardServer.PORT;
23
+ const DASHBOARD_URL = `http://${dashboardServer.HOST}:${DASHBOARD_PORT}`;
24
+ const PID_FILE = path.join(os.homedir(), '.natureco', 'dashboard.pid');
6
25
 
7
- function dashboard(params) {
26
+ function isPortInUse(port, host = '127.0.0.1') {
27
+ return new Promise((resolve) => {
28
+ const socket = new net.Socket();
29
+ socket.setTimeout(1000);
30
+ socket.on('connect', () => { socket.destroy(); resolve(true); });
31
+ socket.on('timeout', () => { socket.destroy(); resolve(false); });
32
+ socket.on('error', () => resolve(false));
33
+ socket.connect(port, host);
34
+ });
35
+ }
36
+
37
+ function getRunningPid() {
8
38
  try {
9
- const [action] = params || [];
39
+ if (fs.existsSync(PID_FILE)) {
40
+ const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim(), 10);
41
+ // Process çalışıyor mu?
42
+ try { process.kill(pid, 0); return pid; } catch { return null; }
43
+ }
44
+ } catch {}
45
+ return null;
46
+ }
10
47
 
11
- if (!action || action === 'open') return cmdOpen();
12
- if (action === 'status') return cmdStatus();
13
- if (action === 'url') return cmdUrl();
48
+ async function cmdStart(openBrowser = true) {
49
+ const inUse = await isPortInUse(DASHBOARD_PORT);
50
+ const existingPid = getRunningPid();
14
51
 
15
- console.log(chalk.red(`\n Unknown dashboard action: ${action}\n`));
16
- console.log(chalk.gray(' Usage: natureco dashboard [open|status|url]\n'));
17
- } catch (err) {
18
- console.log(chalk.red(`\n Dashboard error: ${err.message}\n`));
52
+ if (inUse || existingPid) {
53
+ console.log(chalk.green(`\n Dashboard zaten çalışıyor: ${DASHBOARD_URL}`));
54
+ console.log(chalk.gray(` PID: ${existingPid || 'unknown'}`));
55
+ if (openBrowser) openInBrowser(DASHBOARD_URL);
56
+ return;
19
57
  }
20
- }
21
58
 
22
- function cmdOpen() {
23
- const { exec } = require('child_process');
24
- const platform = process.platform;
59
+ console.log(chalk.cyan(`\n 🌿 NatureCo Dashboard başlatılıyor...\n`));
60
+ const server = dashboardServer.startServer(DASHBOARD_PORT);
25
61
 
26
- console.log(chalk.cyan('\n Opening dashboard at ' + DASHBOARD_URL + '\n'));
62
+ // PID kaydet
63
+ setTimeout(() => {
64
+ try {
65
+ fs.writeFileSync(PID_FILE, String(process.pid), 'utf8');
66
+ } catch {}
67
+ }, 100);
27
68
 
28
- try {
29
- if (platform === 'win32') {
30
- exec('start ' + DASHBOARD_URL);
31
- } else if (platform === 'darwin') {
32
- exec('open ' + DASHBOARD_URL);
33
- } else {
34
- exec('xdg-open ' + DASHBOARD_URL);
35
- }
36
- } catch (e) {
37
- console.log(chalk.yellow(' Could not open browser. Visit: ' + DASHBOARD_URL + '\n'));
69
+ if (openBrowser) {
70
+ setTimeout(() => openInBrowser(DASHBOARD_URL), 500);
38
71
  }
39
- }
40
-
41
- function cmdStatus() {
42
- const socket = new net.Socket();
43
- const timeout = 2000;
44
72
 
45
- socket.setTimeout(timeout);
46
-
47
- socket.on('connect', () => {
48
- socket.destroy();
49
- console.log(chalk.green('\n Dashboard is running at ' + DASHBOARD_URL + '\n'));
73
+ // Graceful shutdown
74
+ process.on('SIGINT', () => {
75
+ console.log(chalk.yellow('\n ⏹ Dashboard durduruluyor...'));
76
+ try { fs.unlinkSync(PID_FILE); } catch {}
77
+ server.close(() => process.exit(0));
50
78
  });
79
+ }
51
80
 
52
- socket.on('timeout', () => {
53
- socket.destroy();
54
- console.log(chalk.yellow('\n Dashboard is not responding at ' + DASHBOARD_URL + '\n'));
55
- });
81
+ function openInBrowser(url) {
82
+ const platform = process.platform;
83
+ try {
84
+ if (platform === 'win32') exec(`start ${url}`);
85
+ else if (platform === 'darwin') exec(`open ${url}`);
86
+ else exec(`xdg-open ${url}`);
87
+ } catch {}
88
+ }
56
89
 
57
- socket.on('error', () => {
58
- socket.destroy();
59
- console.log(chalk.yellow('\n Dashboard is not running at ' + DASHBOARD_URL + '\n'));
60
- });
90
+ async function cmdStatus() {
91
+ const inUse = await isPortInUse(DASHBOARD_PORT);
92
+ const pid = getRunningPid();
61
93
 
62
- socket.connect(18789, '127.0.0.1');
94
+ if (inUse || pid) {
95
+ console.log(chalk.green(`\n ✓ Dashboard çalışıyor`));
96
+ console.log(chalk.gray(` URL: ${DASHBOARD_URL}`));
97
+ if (pid) console.log(chalk.gray(` PID: ${pid}`));
98
+ } else {
99
+ console.log(chalk.yellow(`\n ⏸ Dashboard çalışmıyor`));
100
+ console.log(chalk.gray(` Başlatmak için: natureco dashboard`));
101
+ }
102
+ console.log('');
63
103
  }
64
104
 
65
105
  function cmdUrl() {
66
106
  console.log(DASHBOARD_URL);
67
107
  }
68
108
 
109
+ function cmdStop() {
110
+ const pid = getRunningPid();
111
+ if (!pid) {
112
+ console.log(chalk.yellow('\n Dashboard zaten çalışmıyor.\n'));
113
+ return;
114
+ }
115
+ try {
116
+ process.kill(pid, 'SIGTERM');
117
+ try { fs.unlinkSync(PID_FILE); } catch {}
118
+ console.log(chalk.green(`\n ✓ Dashboard durduruldu (PID ${pid})\n`));
119
+ } catch (e) {
120
+ console.log(chalk.red(`\n ❌ Durdurulamadı: ${e.message}\n`));
121
+ }
122
+ }
123
+
124
+ async function dashboard(params) {
125
+ try {
126
+ // Hem string hem array kabul et (eski/yeni bin uyumluluğu)
127
+ const action = Array.isArray(params) ? params[0] : params;
128
+ const allParams = Array.isArray(params) ? params : [params];
129
+
130
+ if (!action || action === 'open' || action === 'start') {
131
+ await cmdStart(action !== 'start');
132
+ return;
133
+ }
134
+ if (action === 'status') { await cmdStatus(); return; }
135
+ if (action === 'url') { cmdUrl(); return; }
136
+ if (action === 'stop') { cmdStop(); return; }
137
+
138
+ console.log(chalk.yellow('\n Kullanım:'));
139
+ console.log(chalk.gray(' natureco dashboard Başlat ve tarayıcıda aç'));
140
+ console.log(chalk.gray(' natureco dashboard start Sadece başlat'));
141
+ console.log(chalk.gray(' natureco dashboard status Çalışıyor mu?'));
142
+ console.log(chalk.gray(' natureco dashboard stop Durdur'));
143
+ console.log(chalk.gray(' natureco dashboard url URL göster'));
144
+ console.log('');
145
+ } catch (err) {
146
+ console.log(chalk.red(`\n Dashboard error: ${err.message}\n`));
147
+ }
148
+ }
149
+
69
150
  module.exports = dashboard;
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,8 +1,11 @@
1
1
  const chalk = require('chalk');
2
2
  const F = require('../utils/format');
3
+ const tui = require('../utils/tui');
3
4
  const fs = require('fs');
4
5
  const path = require('path');
5
6
  const os = require('os');
7
+ const audit = require('../utils/audit');
8
+ const secrets = require('../utils/secret-scanner');
6
9
 
7
10
  const BASE_DIR = path.join(os.homedir(), '.natureco');
8
11
  const CONFIG_FILE = path.join(BASE_DIR, 'config.json');
@@ -11,8 +14,13 @@ const CHECKS = [
11
14
  { name: 'configExists', label: 'Config file exists' },
12
15
  { name: 'nodeVersion', label: 'Node.js version >= 18' },
13
16
  { name: 'npmPackages', label: 'Required npm packages installed' },
14
- { name: 'diskSpace', label: 'Sufficient disk space' },
17
+ { name: 'diskSpace', label: 'Sufficient disk space (>500 MB)' },
15
18
  { name: 'writePermission', label: 'Write permission on ~/.natureco' },
19
+ { name: 'apiKeyValid', label: 'API key format valid' },
20
+ { name: 'providerReachable', label: 'Provider API reachable' },
21
+ { name: 'dataDirs', label: 'All data directories exist' },
22
+ { name: 'auditLog', label: 'Audit log directory writable' },
23
+ { name: 'secretsClean', label: 'No secrets in current directory' },
16
24
  ];
17
25
 
18
26
  function doctor(params) {
@@ -58,30 +66,53 @@ function cmdCheck(name) {
58
66
  }
59
67
 
60
68
  function cmdRun() {
61
- F.header('System Doctor');
69
+ F.header('System Doctor · Tüm Sistem Kontrolleri', { icon: '🩺' });
62
70
 
63
71
  const rows = [];
64
72
  let passed = 0;
65
73
  let failed = 0;
74
+ const startTime = Date.now();
66
75
 
67
76
  for (const check of CHECKS) {
68
77
  const result = runCheck(check.name);
69
- rows.push([
70
- check.label,
71
- result.pass ? '✓' : '✗',
72
- result.message,
73
- ]);
78
+ rows.push({
79
+ check: check.label,
80
+ status: result.pass,
81
+ message: result.message,
82
+ });
74
83
  if (result.pass) passed++; else failed++;
75
84
  }
76
85
 
77
- F.table(['Check', 'Status', 'Message'], rows);
86
+ // Yeni TUI tablo
87
+ console.log('\n' + tui.table(rows, [
88
+ { key: 'check', label: 'Kontrol', minWidth: 28 },
89
+ {
90
+ key: 'status', label: 'Durum', minWidth: 9,
91
+ render: r => r.status
92
+ ? tui.styled(' ✓ PASS ', { bg: tui.PALETTE.success, color: '#000000', bold: true })
93
+ : tui.styled(' ✗ FAIL ', { bg: tui.PALETTE.danger, color: '#000000', bold: true }),
94
+ },
95
+ { key: 'message', label: 'Mesaj', minWidth: 20 },
96
+ ], { borderStyle: 'round', zebra: true }));
78
97
 
79
98
  const total = passed + failed;
80
- F.meta(`${passed}/${total} checks passed`);
99
+ const duration = Date.now() - startTime;
100
+
101
+ // Özet kartı
102
+ console.log('\n' + tui.box(60, 5, {
103
+ title: 'Özet',
104
+ borderColor: failed > 0 ? tui.PALETTE.warning : tui.PALETTE.success,
105
+ }).split('\n').map((line, i) => {
106
+ if (i === 2) return line.replace(' '.repeat(58), ` ${tui.C.text(`${passed}/${total} kontrol geçti`)} · ${tui.C.muted(duration + 'ms')}`);
107
+ return line;
108
+ }).join('\n'));
81
109
 
82
110
  if (failed > 0) {
83
- F.warning('Some checks failed. Run individual checks for details.');
111
+ console.log('\n' + tui.C.amber(' ⚠️ Bazı kontroller başarısız. Detay için: ') + tui.C.brand('natureco doctor check <name>'));
112
+ } else {
113
+ console.log('\n' + tui.C.green(' ✨ Tüm kontroller geçti! Sistem sağlıklı.'));
84
114
  }
115
+ console.log('');
85
116
  }
86
117
 
87
118
  function runCheck(name) {
@@ -111,15 +142,29 @@ function runCheck(name) {
111
142
  }
112
143
 
113
144
  case 'diskSpace': {
145
+ // Gerçek disk alanı (cross-platform)
114
146
  try {
115
- const stats = require('os').freemem();
116
- const freeGB = stats / 1024 / 1024 / 1024;
147
+ const { execSync } = require('child_process');
148
+ let freeGB = null;
149
+ if (process.platform === 'darwin' || process.platform === 'linux') {
150
+ // df -k ~/.natureco | tail -1 | awk '{print $4}'
151
+ const out = execSync(`df -k ${JSON.stringify(BASE_DIR)} | tail -1`).toString().trim();
152
+ const parts = out.split(/\s+/);
153
+ const freeKB = parseInt(parts[3], 10);
154
+ if (!isNaN(freeKB)) freeGB = freeKB / 1024 / 1024;
155
+ } else if (process.platform === 'win32') {
156
+ const out = execSync(`powershell -NoProfile -Command "(Get-PSDrive ${BASE_DIR[0]}).Free / 1GB"`).toString().trim();
157
+ freeGB = parseFloat(out);
158
+ }
159
+ if (freeGB === null || isNaN(freeGB)) {
160
+ return { pass: true, message: 'Unable to determine disk space' };
161
+ }
117
162
  return {
118
163
  pass: freeGB > 0.5,
119
- message: freeGB > 0.5 ? `${freeGB.toFixed(1)} GB free` : 'Less than 500 MB free',
164
+ message: freeGB > 0.5 ? `${freeGB.toFixed(1)} GB free` : `Sadece ${(freeGB * 1024).toFixed(0)} MB kaldı — gerekli: 500 MB`,
120
165
  };
121
- } catch {
122
- return { pass: true, message: 'Unable to check' };
166
+ } catch (e) {
167
+ return { pass: true, message: 'Unable to check disk space' };
123
168
  }
124
169
  }
125
170
 
@@ -135,6 +180,80 @@ function runCheck(name) {
135
180
  }
136
181
  }
137
182
 
183
+ case 'apiKeyValid': {
184
+ try {
185
+ if (!fs.existsSync(CONFIG_FILE)) return { pass: false, message: 'Config missing — run `natureco setup`' };
186
+ const cfg = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
187
+ const key = cfg.providerApiKey || cfg.apiKey;
188
+ if (!key) return { pass: false, message: 'API key tanımlı değil' };
189
+ if (key.length < 10) return { pass: false, message: 'API key çok kısa — yanlış kopyalanmış olabilir' };
190
+ return { pass: true, message: `Key uzunluğu: ${key.length} karakter` };
191
+ } catch (e) {
192
+ return { pass: false, message: e.message };
193
+ }
194
+ }
195
+
196
+ case 'providerReachable': {
197
+ try {
198
+ if (!fs.existsSync(CONFIG_FILE)) return { pass: false, message: 'Config missing' };
199
+ const cfg = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
200
+ const url = cfg.providerUrl;
201
+ if (!url) return { pass: false, message: 'Provider URL tanımlı değil' };
202
+ // URL format geçerli mi?
203
+ const parsed = new URL(url);
204
+ return { pass: true, message: `URL geçerli: ${parsed.host}` };
205
+ } catch (e) {
206
+ return { pass: false, message: e.message };
207
+ }
208
+ }
209
+
210
+ case 'dataDirs': {
211
+ try {
212
+ if (!fs.existsSync(BASE_DIR)) fs.mkdirSync(BASE_DIR, { recursive: true });
213
+ const REQUIRED = ['sources', 'concepts', 'cache', 'skills', 'memory', 'sessions', 'backups', 'hooks', 'audit'];
214
+ const missing = REQUIRED.filter(d => !fs.existsSync(path.join(BASE_DIR, d)));
215
+ if (missing.length === 0) {
216
+ return { pass: true, message: `${REQUIRED.length} dizin hazır` };
217
+ }
218
+ // Otomatik oluştur
219
+ for (const d of missing) {
220
+ try { fs.mkdirSync(path.join(BASE_DIR, d), { recursive: true }); } catch {}
221
+ }
222
+ return { pass: true, message: `Eksik dizinler oluşturuldu: ${missing.join(', ')}` };
223
+ } catch (e) {
224
+ return { pass: false, message: e.message };
225
+ }
226
+ }
227
+
228
+ case 'auditLog': {
229
+ try {
230
+ // Audit log yazma testi
231
+ audit.logSync(audit.ACTIONS.INFO, { source: 'doctor', check: 'auditLog' });
232
+ const files = audit.listLogFiles();
233
+ return { pass: true, message: `${files.length} log dosyası, en son: ${files[0] || 'yok'}` };
234
+ } catch (e) {
235
+ return { pass: false, message: e.message };
236
+ }
237
+ }
238
+
239
+ case 'secretsClean': {
240
+ try {
241
+ // Mevcut çalışma dizinini tara — secret var mı?
242
+ const findings = secrets.scanDir(process.cwd());
243
+ const critical = findings.filter(f => f.severity === 'critical' || f.severity === 'high');
244
+ if (critical.length === 0) {
245
+ return { pass: true, message: 'Çalışma dizininde secret bulunamadı ✓' };
246
+ }
247
+ const sample = critical.slice(0, 3).map(f => `${f.type}@${path.basename(f.file || '?')}`).join(', ');
248
+ return {
249
+ pass: false,
250
+ message: `${critical.length} potansiyel secret: ${sample}${critical.length > 3 ? '...' : ''}`,
251
+ };
252
+ } catch (e) {
253
+ return { pass: false, message: e.message };
254
+ }
255
+ }
256
+
138
257
  default:
139
258
  return { pass: false, message: 'Unknown check' };
140
259
  }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes