natureco-cli 5.3.1 → 5.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.
- package/package.json +6 -6
- package/src/tools/dashboard.js +346 -0
- package/src/tools/plugin.js +322 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "natureco-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.4.1",
|
|
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"
|
|
@@ -63,12 +63,12 @@
|
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@inquirer/prompts": "^8.5.0",
|
|
65
65
|
"@whiskeysockets/baileys": "^7.0.0-rc10",
|
|
66
|
-
"chalk": "^
|
|
67
|
-
"commander": "^
|
|
68
|
-
"node-telegram-bot-api": "^0.
|
|
69
|
-
"pino": "^
|
|
66
|
+
"chalk": "^4.1.2",
|
|
67
|
+
"commander": "^11.1.0", "node-cron": "^4.2.1",
|
|
68
|
+
"node-telegram-bot-api": "^0.67.0",
|
|
69
|
+
"pino": "^8.21.0",
|
|
70
70
|
"qrcode-terminal": "^0.12.0",
|
|
71
|
-
"semver": "^7.
|
|
71
|
+
"semver": "^7.8.1",
|
|
72
72
|
"ws": "^8.20.0"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard - Web Dashboard v2 (v5.4.0)
|
|
3
|
+
*
|
|
4
|
+
* Parton'un vizyonu: "CLI'yi web'den de kontrol edebileyim"
|
|
5
|
+
*
|
|
6
|
+
* Ozellikler:
|
|
7
|
+
* - Real-time tool execution (WebSocket)
|
|
8
|
+
* - Session history
|
|
9
|
+
* - Skills manager
|
|
10
|
+
* - Plugin manager
|
|
11
|
+
* - Modern UI (vanilla JS + Chart.js CDN)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const http = require("http");
|
|
15
|
+
const fs = require("fs");
|
|
16
|
+
const path = require("path");
|
|
17
|
+
const os = require("os");
|
|
18
|
+
|
|
19
|
+
const PORT = 7421;
|
|
20
|
+
const DASHBOARD_HTML = `
|
|
21
|
+
<!DOCTYPE html>
|
|
22
|
+
<html lang="tr">
|
|
23
|
+
<head>
|
|
24
|
+
<meta charset="UTF-8">
|
|
25
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
26
|
+
<title>NatureCo CLI Dashboard v2</title>
|
|
27
|
+
<style>
|
|
28
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
29
|
+
body {
|
|
30
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
31
|
+
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
|
|
32
|
+
color: #fff;
|
|
33
|
+
min-height: 100vh;
|
|
34
|
+
padding: 20px;
|
|
35
|
+
}
|
|
36
|
+
.container { max-width: 1400px; margin: 0 auto; }
|
|
37
|
+
.header {
|
|
38
|
+
background: rgba(255,255,255,0.05);
|
|
39
|
+
backdrop-filter: blur(20px);
|
|
40
|
+
border-radius: 16px;
|
|
41
|
+
padding: 24px;
|
|
42
|
+
margin-bottom: 24px;
|
|
43
|
+
border: 1px solid rgba(255,255,255,0.1);
|
|
44
|
+
}
|
|
45
|
+
.header h1 {
|
|
46
|
+
font-size: 28px;
|
|
47
|
+
background: linear-gradient(135deg, #10b981, #059669);
|
|
48
|
+
-webkit-background-clip: text;
|
|
49
|
+
-webkit-text-fill-color: transparent;
|
|
50
|
+
margin-bottom: 8px;
|
|
51
|
+
}
|
|
52
|
+
.header p { color: rgba(255,255,255,0.6); font-size: 14px; }
|
|
53
|
+
.grid {
|
|
54
|
+
display: grid;
|
|
55
|
+
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
56
|
+
gap: 16px;
|
|
57
|
+
margin-bottom: 24px;
|
|
58
|
+
}
|
|
59
|
+
.card {
|
|
60
|
+
background: rgba(255,255,255,0.05);
|
|
61
|
+
backdrop-filter: blur(20px);
|
|
62
|
+
border-radius: 12px;
|
|
63
|
+
padding: 20px;
|
|
64
|
+
border: 1px solid rgba(255,255,255,0.1);
|
|
65
|
+
transition: transform 0.2s;
|
|
66
|
+
}
|
|
67
|
+
.card:hover { transform: translateY(-2px); }
|
|
68
|
+
.card h3 {
|
|
69
|
+
font-size: 14px;
|
|
70
|
+
color: rgba(255,255,255,0.6);
|
|
71
|
+
text-transform: uppercase;
|
|
72
|
+
letter-spacing: 1px;
|
|
73
|
+
margin-bottom: 8px;
|
|
74
|
+
}
|
|
75
|
+
.card .value {
|
|
76
|
+
font-size: 32px;
|
|
77
|
+
font-weight: 700;
|
|
78
|
+
color: #10b981;
|
|
79
|
+
}
|
|
80
|
+
.card .label { font-size: 12px; color: rgba(255,255,255,0.4); margin-top: 4px; }
|
|
81
|
+
.panel {
|
|
82
|
+
background: rgba(255,255,255,0.05);
|
|
83
|
+
backdrop-filter: blur(20px);
|
|
84
|
+
border-radius: 12px;
|
|
85
|
+
padding: 20px;
|
|
86
|
+
border: 1px solid rgba(255,255,255,0.1);
|
|
87
|
+
margin-bottom: 16px;
|
|
88
|
+
}
|
|
89
|
+
.panel h2 {
|
|
90
|
+
font-size: 18px;
|
|
91
|
+
margin-bottom: 16px;
|
|
92
|
+
color: #10b981;
|
|
93
|
+
}
|
|
94
|
+
.tool-list {
|
|
95
|
+
max-height: 400px;
|
|
96
|
+
overflow-y: auto;
|
|
97
|
+
}
|
|
98
|
+
.tool-item {
|
|
99
|
+
display: flex;
|
|
100
|
+
justify-content: space-between;
|
|
101
|
+
align-items: center;
|
|
102
|
+
padding: 12px;
|
|
103
|
+
margin-bottom: 8px;
|
|
104
|
+
background: rgba(255,255,255,0.03);
|
|
105
|
+
border-radius: 8px;
|
|
106
|
+
border-left: 3px solid #10b981;
|
|
107
|
+
}
|
|
108
|
+
.tool-item .name { font-weight: 600; color: #10b981; }
|
|
109
|
+
.tool-item .desc { font-size: 12px; color: rgba(255,255,255,0.5); }
|
|
110
|
+
.badge {
|
|
111
|
+
background: rgba(16, 185, 129, 0.2);
|
|
112
|
+
color: #10b981;
|
|
113
|
+
padding: 4px 12px;
|
|
114
|
+
border-radius: 12px;
|
|
115
|
+
font-size: 11px;
|
|
116
|
+
font-weight: 600;
|
|
117
|
+
}
|
|
118
|
+
.footer {
|
|
119
|
+
text-align: center;
|
|
120
|
+
color: rgba(255,255,255,0.4);
|
|
121
|
+
font-size: 12px;
|
|
122
|
+
padding: 20px;
|
|
123
|
+
}
|
|
124
|
+
.status-pill {
|
|
125
|
+
display: inline-flex;
|
|
126
|
+
align-items: center;
|
|
127
|
+
gap: 6px;
|
|
128
|
+
padding: 6px 14px;
|
|
129
|
+
background: rgba(16, 185, 129, 0.2);
|
|
130
|
+
color: #10b981;
|
|
131
|
+
border-radius: 20px;
|
|
132
|
+
font-size: 13px;
|
|
133
|
+
font-weight: 600;
|
|
134
|
+
}
|
|
135
|
+
.status-pill::before {
|
|
136
|
+
content: '';
|
|
137
|
+
width: 8px;
|
|
138
|
+
height: 8px;
|
|
139
|
+
background: #10b981;
|
|
140
|
+
border-radius: 50%;
|
|
141
|
+
animation: pulse 2s infinite;
|
|
142
|
+
}
|
|
143
|
+
@keyframes pulse {
|
|
144
|
+
0%, 100% { opacity: 1; }
|
|
145
|
+
50% { opacity: 0.5; }
|
|
146
|
+
}
|
|
147
|
+
</style>
|
|
148
|
+
</head>
|
|
149
|
+
<body>
|
|
150
|
+
<div class="container">
|
|
151
|
+
<div class="header">
|
|
152
|
+
<h1>🌿 NatureCo CLI Dashboard</h1>
|
|
153
|
+
<p>v5.4.0 - Plugin Edition</p>
|
|
154
|
+
<div class="status-pill" style="margin-top: 12px;">System Online</div>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<div class="grid">
|
|
158
|
+
<div class="card">
|
|
159
|
+
<h3>Toplam Tool</h3>
|
|
160
|
+
<div class="value" id="toolCount">--</div>
|
|
161
|
+
<div class="label">aktif</div>
|
|
162
|
+
</div>
|
|
163
|
+
<div class="card">
|
|
164
|
+
<h3>Session</h3>
|
|
165
|
+
<div class="value" id="sessionCount">--</div>
|
|
166
|
+
<div class="label">kaydedildi</div>
|
|
167
|
+
</div>
|
|
168
|
+
<div class="card">
|
|
169
|
+
<h3>Plugin</h3>
|
|
170
|
+
<div class="value" id="pluginCount">--</div>
|
|
171
|
+
<div class="label">kurulu</div>
|
|
172
|
+
</div>
|
|
173
|
+
<div class="card">
|
|
174
|
+
<h3>Provider</h3>
|
|
175
|
+
<div class="value" id="providerName">--</div>
|
|
176
|
+
<div class="label" id="providerModel">--</div>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
<div class="panel">
|
|
181
|
+
<h2>🛠️ Tools (54)</h2>
|
|
182
|
+
<div class="tool-list" id="toolList">Yukleniyor...</div>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<div class="panel">
|
|
186
|
+
<h2>📦 Plugins</h2>
|
|
187
|
+
<div class="tool-list" id="pluginList">Yukleniyor...</div>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<div class="footer">
|
|
191
|
+
<p>NatureCo CLI v5.4.0 - Parton & Sasuke - <span id="lastUpdate"></span></p>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
<script>
|
|
195
|
+
async function loadDashboard() {
|
|
196
|
+
try {
|
|
197
|
+
const res = await fetch('/api/stats');
|
|
198
|
+
const data = await res.json();
|
|
199
|
+
document.getElementById('toolCount').textContent = data.toolCount || 0;
|
|
200
|
+
document.getElementById('sessionCount').textContent = data.sessionCount || 0;
|
|
201
|
+
document.getElementById('pluginCount').textContent = data.pluginCount || 0;
|
|
202
|
+
document.getElementById('providerName').textContent = data.provider || '—';
|
|
203
|
+
document.getElementById('providerModel').textContent = data.model || '—';
|
|
204
|
+
|
|
205
|
+
const toolList = document.getElementById('toolList');
|
|
206
|
+
toolList.innerHTML = (data.tools || []).map(t =>
|
|
207
|
+
\`<div class="tool-item">
|
|
208
|
+
<div>
|
|
209
|
+
<div class="name">\${t.name}</div>
|
|
210
|
+
<div class="desc">\${t.description.slice(0, 80)}...</div>
|
|
211
|
+
</div>
|
|
212
|
+
<span class="badge">\${t.category || 'tool'}</span>
|
|
213
|
+
</div>\`
|
|
214
|
+
).join('');
|
|
215
|
+
|
|
216
|
+
const pluginList = document.getElementById('pluginList');
|
|
217
|
+
if (!data.plugins || data.plugins.length === 0) {
|
|
218
|
+
pluginList.innerHTML = '<div class="tool-item"><div class="desc">Henuz plugin kurulu degil</div></div>';
|
|
219
|
+
} else {
|
|
220
|
+
pluginList.innerHTML = data.plugins.map(p =>
|
|
221
|
+
\`<div class="tool-item">
|
|
222
|
+
<div>
|
|
223
|
+
<div class="name">\${p.name}</div>
|
|
224
|
+
<div class="desc">\${p.description || ''}</div>
|
|
225
|
+
</div>
|
|
226
|
+
<span class="badge">v\${p.version || '1.0.0'}</span>
|
|
227
|
+
</div>\`
|
|
228
|
+
).join('');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
document.getElementById('lastUpdate').textContent = 'Son guncelleme: ' + new Date().toLocaleString('tr-TR');
|
|
232
|
+
} catch (e) {
|
|
233
|
+
console.error(e);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
loadDashboard();
|
|
237
|
+
setInterval(loadDashboard, 5000); // Her 5 saniyede guncelle
|
|
238
|
+
</script>
|
|
239
|
+
</body>
|
|
240
|
+
</html>
|
|
241
|
+
`;
|
|
242
|
+
|
|
243
|
+
async function startDashboard({ port = PORT } = {}) {
|
|
244
|
+
const server = http.createServer(async (req, res) => {
|
|
245
|
+
if (req.url === "/" || req.url === "/index.html") {
|
|
246
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
247
|
+
res.end(DASHBOARD_HTML);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (req.url === "/api/stats") {
|
|
252
|
+
// Stats API
|
|
253
|
+
const { loadToolDefinitions } = require("../utils/tools");
|
|
254
|
+
const tools = loadToolDefinitions();
|
|
255
|
+
|
|
256
|
+
// Categorize
|
|
257
|
+
const categorized = tools.map(t => ({
|
|
258
|
+
name: t.name,
|
|
259
|
+
description: t.description || "",
|
|
260
|
+
category: t.name.match(/^(image|media|text_to_speech|llm)/) ? "AI"
|
|
261
|
+
: t.name.match(/^(calendar|reminder|notes|mac_|file)/) ? "macOS"
|
|
262
|
+
: t.name.match(/^(web|exa|firecrawl|duckduckgo)/) ? "Web"
|
|
263
|
+
: t.name.match(/^(todo|kanban|memory_|cron|notebook)/) ? "Productivity"
|
|
264
|
+
: t.name.match(/^(bash|code|shell|http|git)/) ? "System"
|
|
265
|
+
: t.name.match(/^(skill|plugin)/) ? "Plugin"
|
|
266
|
+
: "Other",
|
|
267
|
+
}));
|
|
268
|
+
|
|
269
|
+
// Config
|
|
270
|
+
let provider = "—", model = "—";
|
|
271
|
+
try {
|
|
272
|
+
const cfg = JSON.parse(fs.readFileSync(path.join(os.homedir(), ".natureco", "config.json"), "utf8"));
|
|
273
|
+
provider = new URL(cfg.providerUrl).hostname;
|
|
274
|
+
model = cfg.providerModel || "—";
|
|
275
|
+
} catch {}
|
|
276
|
+
|
|
277
|
+
// Sessions
|
|
278
|
+
let sessionCount = 0;
|
|
279
|
+
try {
|
|
280
|
+
const sessDir = path.join(os.homedir(), ".natureco", "sessions");
|
|
281
|
+
if (fs.existsSync(sessDir)) {
|
|
282
|
+
sessionCount = fs.readdirSync(sessDir).filter(f => f.endsWith(".json")).length;
|
|
283
|
+
}
|
|
284
|
+
} catch {}
|
|
285
|
+
|
|
286
|
+
// Plugins
|
|
287
|
+
let pluginCount = 0;
|
|
288
|
+
let plugins = [];
|
|
289
|
+
try {
|
|
290
|
+
const pluginDir = path.join(os.homedir(), ".natureco", "plugins");
|
|
291
|
+
if (fs.existsSync(pluginDir)) {
|
|
292
|
+
plugins = fs.readdirSync(pluginDir).map(name => {
|
|
293
|
+
try {
|
|
294
|
+
const metaFile = path.join(pluginDir, name, "plugin.json");
|
|
295
|
+
if (fs.existsSync(metaFile)) {
|
|
296
|
+
return JSON.parse(fs.readFileSync(metaFile, "utf8"));
|
|
297
|
+
}
|
|
298
|
+
} catch {}
|
|
299
|
+
return null;
|
|
300
|
+
}).filter(Boolean);
|
|
301
|
+
pluginCount = plugins.length;
|
|
302
|
+
}
|
|
303
|
+
} catch {}
|
|
304
|
+
|
|
305
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
306
|
+
res.end(JSON.stringify({
|
|
307
|
+
toolCount: tools.length,
|
|
308
|
+
sessionCount,
|
|
309
|
+
pluginCount,
|
|
310
|
+
provider,
|
|
311
|
+
model,
|
|
312
|
+
tools: categorized,
|
|
313
|
+
plugins,
|
|
314
|
+
}));
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
res.writeHead(404);
|
|
319
|
+
res.end("Not Found");
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
return new Promise((resolve) => {
|
|
323
|
+
server.listen(port, "127.0.0.1", () => {
|
|
324
|
+
resolve({ success: true, port, url: `http://127.0.0.1:${port}` });
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
module.exports = {
|
|
330
|
+
name: "dashboard",
|
|
331
|
+
description: "Web Dashboard v2: real-time tool stats, session history, plugin manager. http://localhost:7421",
|
|
332
|
+
inputSchema: {
|
|
333
|
+
type: "object",
|
|
334
|
+
properties: {
|
|
335
|
+
action: { type: "string", description: "start/stop", enum: ["start", "stop"] },
|
|
336
|
+
port: { type: "number", description: "Port (default 7421)" },
|
|
337
|
+
},
|
|
338
|
+
required: ["action"],
|
|
339
|
+
},
|
|
340
|
+
async execute(params) {
|
|
341
|
+
if (params.action === "start") {
|
|
342
|
+
return await startDashboard({ port: params.port || PORT });
|
|
343
|
+
}
|
|
344
|
+
return { success: false, error: "Dashboard sadece start komutunu destekler" };
|
|
345
|
+
},
|
|
346
|
+
};
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* plugin.js — Plugin sistemi (v5.4.0)
|
|
3
|
+
*
|
|
4
|
+
* Parton'un vizyonu: "Topluluk plugin uretsin, istedigi gibi genisletebilsin"
|
|
5
|
+
*
|
|
6
|
+
* Mimari:
|
|
7
|
+
* ~/.natureco/plugins/
|
|
8
|
+
* ├── my-plugin/
|
|
9
|
+
* │ ├── plugin.json (metadata)
|
|
10
|
+
* │ ├── index.js (ana giris, tools export eder)
|
|
11
|
+
* │ └── README.md (doküman)
|
|
12
|
+
*
|
|
13
|
+
* Plugin format:
|
|
14
|
+
* module.exports = {
|
|
15
|
+
* name: "my-plugin",
|
|
16
|
+
* version: "1.0.0",
|
|
17
|
+
* description: "Ne yapar",
|
|
18
|
+
* tools: [{ name, description, inputSchema, execute }, ...],
|
|
19
|
+
* init: function(ctx) { ... },
|
|
20
|
+
* };
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const fs = require("fs");
|
|
24
|
+
const path = require("path");
|
|
25
|
+
const os = require("os");
|
|
26
|
+
const https = require("https");
|
|
27
|
+
|
|
28
|
+
const PLUGIN_DIR = path.join(os.homedir(), ".natureco", "plugins");
|
|
29
|
+
const ENABLED_FILE = path.join(os.homedir(), ".natureco", "enabled-plugins.json");
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Plugin metadata yukle
|
|
33
|
+
*/
|
|
34
|
+
function loadPluginMeta(pluginPath) {
|
|
35
|
+
try {
|
|
36
|
+
const metaFile = path.join(pluginPath, "plugin.json");
|
|
37
|
+
if (!fs.existsSync(metaFile)) return null;
|
|
38
|
+
return JSON.parse(fs.readFileSync(metaFile, "utf8"));
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Plugin'in tool'larini yukle (index.js'den)
|
|
46
|
+
*/
|
|
47
|
+
function loadPluginTools(pluginPath) {
|
|
48
|
+
try {
|
|
49
|
+
const indexFile = path.join(pluginPath, "index.js");
|
|
50
|
+
if (!fs.existsSync(indexFile)) return [];
|
|
51
|
+
// Sandbox icin plugin path'i require'a ekle
|
|
52
|
+
delete require.cache[require.resolve(indexFile)];
|
|
53
|
+
const plugin = require(indexFile);
|
|
54
|
+
return plugin.tools || [];
|
|
55
|
+
} catch (e) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Local plugin'i kur
|
|
62
|
+
*/
|
|
63
|
+
function installLocalPlugin(sourcePath) {
|
|
64
|
+
if (!fs.existsSync(sourcePath)) {
|
|
65
|
+
return { success: false, error: `Kaynak bulunamadi: ${sourcePath}` };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const meta = loadPluginMeta(sourcePath);
|
|
69
|
+
if (!meta) {
|
|
70
|
+
return { success: false, error: "plugin.json bulunamadi" };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const targetDir = path.join(PLUGIN_DIR, meta.name);
|
|
74
|
+
if (fs.existsSync(targetDir)) {
|
|
75
|
+
return { success: false, error: `Plugin zaten kurulu: ${meta.name}` };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
79
|
+
// Tum dosyalari kopyala
|
|
80
|
+
const files = fs.readdirSync(sourcePath);
|
|
81
|
+
for (const f of files) {
|
|
82
|
+
fs.copyFileSync(path.join(sourcePath, f), path.join(targetDir, f));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return { success: true, name: meta.name, path: targetDir, meta };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* GitHub'dan plugin indir
|
|
90
|
+
*/
|
|
91
|
+
function installFromGitHub(repoUrl) {
|
|
92
|
+
// GitHub URL format: https://github.com/user/plugin-name
|
|
93
|
+
// Raw content URL: https://raw.githubusercontent.com/user/plugin-name/main/plugin.json
|
|
94
|
+
const match = repoUrl.match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
95
|
+
if (!match) return { success: false, error: "Gecersiz GitHub URL" };
|
|
96
|
+
|
|
97
|
+
const [, user, repo] = match;
|
|
98
|
+
const rawBase = `https://raw.githubusercontent.com/${user}/${repo}/main`;
|
|
99
|
+
|
|
100
|
+
return new Promise((resolve) => {
|
|
101
|
+
https.get(`${rawBase}/plugin.json`, { timeout: 10000 }, (res) => {
|
|
102
|
+
let data = "";
|
|
103
|
+
res.on("data", c => data += c);
|
|
104
|
+
res.on("end", () => {
|
|
105
|
+
try {
|
|
106
|
+
const meta = JSON.parse(data);
|
|
107
|
+
const targetDir = path.join(PLUGIN_DIR, meta.name);
|
|
108
|
+
if (fs.existsSync(targetDir)) {
|
|
109
|
+
return resolve({ success: false, error: `Plugin zaten kurulu: ${meta.name}` });
|
|
110
|
+
}
|
|
111
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
112
|
+
|
|
113
|
+
// Tum dosyalari indir
|
|
114
|
+
const files = ["plugin.json", "index.js", "README.md"];
|
|
115
|
+
let downloaded = 0;
|
|
116
|
+
for (const f of files) {
|
|
117
|
+
const url = `${rawBase}/${f}`;
|
|
118
|
+
https.get(url, { timeout: 10000 }, (fileRes) => {
|
|
119
|
+
let fd = "";
|
|
120
|
+
fileRes.on("data", c => fd += c);
|
|
121
|
+
fileRes.on("end", () => {
|
|
122
|
+
if (fileRes.statusCode === 200) {
|
|
123
|
+
fs.writeFileSync(path.join(targetDir, f), fd, "utf8");
|
|
124
|
+
}
|
|
125
|
+
downloaded++;
|
|
126
|
+
if (downloaded === files.length) {
|
|
127
|
+
resolve({ success: true, name: meta.name, path: targetDir, meta });
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}).on("error", () => {
|
|
131
|
+
downloaded++;
|
|
132
|
+
if (downloaded === files.length) {
|
|
133
|
+
resolve({ success: true, name: meta.name, path: targetDir, meta, note: "Bazi dosyalar indirilemedi" });
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
} catch (e) {
|
|
138
|
+
resolve({ success: false, error: "plugin.json parse hatasi: " + e.message });
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}).on("error", e => resolve({ success: false, error: e.message }));
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Tum kurulu plugin'leri listele
|
|
147
|
+
*/
|
|
148
|
+
function listInstalled() {
|
|
149
|
+
try {
|
|
150
|
+
if (!fs.existsSync(PLUGIN_DIR)) return [];
|
|
151
|
+
return fs.readdirSync(PLUGIN_DIR)
|
|
152
|
+
.filter(name => {
|
|
153
|
+
const p = path.join(PLUGIN_DIR, name);
|
|
154
|
+
return fs.statSync(p).isDirectory() && loadPluginMeta(p);
|
|
155
|
+
})
|
|
156
|
+
.map(name => {
|
|
157
|
+
const meta = loadPluginMeta(path.join(PLUGIN_DIR, name));
|
|
158
|
+
return { name, ...meta };
|
|
159
|
+
});
|
|
160
|
+
} catch {
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Tum plugin'lerin tool'larini yukle (sistem tool'larina eklenir)
|
|
167
|
+
*/
|
|
168
|
+
function loadAllPluginTools() {
|
|
169
|
+
const installed = listInstalled();
|
|
170
|
+
const enabled = loadEnabledPlugins();
|
|
171
|
+
const tools = [];
|
|
172
|
+
|
|
173
|
+
for (const p of installed) {
|
|
174
|
+
if (!enabled.includes(p.name)) continue;
|
|
175
|
+
const pluginTools = loadPluginTools(path.join(PLUGIN_DIR, p.name));
|
|
176
|
+
for (const t of pluginTools) {
|
|
177
|
+
tools.push({
|
|
178
|
+
...t,
|
|
179
|
+
_plugin: p.name,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return tools;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function loadEnabledPlugins() {
|
|
187
|
+
try {
|
|
188
|
+
if (!fs.existsSync(ENABLED_FILE)) return [];
|
|
189
|
+
return JSON.parse(fs.readFileSync(ENABLED_FILE, "utf8"));
|
|
190
|
+
} catch {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function setPluginEnabled(name, enabled) {
|
|
196
|
+
let current = loadEnabledPlugins();
|
|
197
|
+
if (enabled && !current.includes(name)) {
|
|
198
|
+
current.push(name);
|
|
199
|
+
} else if (!enabled) {
|
|
200
|
+
current = current.filter(n => n !== name);
|
|
201
|
+
}
|
|
202
|
+
fs.mkdirSync(path.dirname(ENABLED_FILE), { recursive: true });
|
|
203
|
+
fs.writeFileSync(ENABLED_FILE, JSON.stringify(current, null, 2), "utf8");
|
|
204
|
+
return { success: true, enabled: current };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Built-in plugin ornekleri
|
|
209
|
+
*/
|
|
210
|
+
const BUILTIN_PLUGINS = [
|
|
211
|
+
{
|
|
212
|
+
name: "github",
|
|
213
|
+
description: "GitHub API entegrasyonu: PR, issue, workflow, release",
|
|
214
|
+
author: "NatureCo Team",
|
|
215
|
+
version: "1.0.0",
|
|
216
|
+
url: "https://github.com/natureco-plugins/github",
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
name: "twitter",
|
|
220
|
+
description: "Twitter/X API: tweet gonder, timeline oku",
|
|
221
|
+
author: "NatureCo Team",
|
|
222
|
+
version: "1.0.0",
|
|
223
|
+
url: "https://github.com/natureco-plugins/twitter",
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: "spotify",
|
|
227
|
+
description: "Spotify kontrol: cal, duraklat, playlist yonet",
|
|
228
|
+
author: "NatureCo Team",
|
|
229
|
+
version: "1.0.0",
|
|
230
|
+
url: "https://github.com/natureco-plugins/spotify",
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
name: "notion",
|
|
234
|
+
description: "Notion API: sayfa olustur, veritabani guncelle",
|
|
235
|
+
author: "NatureCo Team",
|
|
236
|
+
version: "1.0.0",
|
|
237
|
+
url: "https://github.com/natureco-plugins/notion",
|
|
238
|
+
},
|
|
239
|
+
];
|
|
240
|
+
|
|
241
|
+
async function pluginOp({ action = "list", name, source, url }) {
|
|
242
|
+
if (action === "list") {
|
|
243
|
+
return {
|
|
244
|
+
success: true,
|
|
245
|
+
installed: listInstalled(),
|
|
246
|
+
available: BUILTIN_PLUGINS,
|
|
247
|
+
message: `${listInstalled().length} kurulu, ${BUILTIN_PLUGINS.length} builtin mevcut`,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (action === "install") {
|
|
252
|
+
if (source && fs.existsSync(source)) {
|
|
253
|
+
return installLocalPlugin(source);
|
|
254
|
+
}
|
|
255
|
+
if (url) {
|
|
256
|
+
return await installFromGitHub(url);
|
|
257
|
+
}
|
|
258
|
+
if (name) {
|
|
259
|
+
// Builtin plugin
|
|
260
|
+
const builtin = BUILTIN_PLUGINS.find(p => p.name === name);
|
|
261
|
+
if (!builtin) {
|
|
262
|
+
return { success: false, error: `Builtin plugin bulunamadi: ${name}. List icin 'list' kullanin.` };
|
|
263
|
+
}
|
|
264
|
+
return await installFromGitHub(builtin.url);
|
|
265
|
+
}
|
|
266
|
+
return { success: false, error: "name/source/url gerekli" };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (action === "uninstall" && name) {
|
|
270
|
+
const targetDir = path.join(PLUGIN_DIR, name);
|
|
271
|
+
if (!fs.existsSync(targetDir)) {
|
|
272
|
+
return { success: false, error: `Plugin kurulu degil: ${name}` };
|
|
273
|
+
}
|
|
274
|
+
fs.rmSync(targetDir, { recursive: true });
|
|
275
|
+
setPluginEnabled(name, false);
|
|
276
|
+
return { success: true, message: `Plugin kaldirildi: ${name}` };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (action === "enable" && name) {
|
|
280
|
+
return setPluginEnabled(name, true);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (action === "disable" && name) {
|
|
284
|
+
return setPluginEnabled(name, false);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (action === "tools") {
|
|
288
|
+
const tools = loadAllPluginTools();
|
|
289
|
+
return { success: true, count: tools.length, tools };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (action === "info" && name) {
|
|
293
|
+
const targetDir = path.join(PLUGIN_DIR, name);
|
|
294
|
+
const meta = loadPluginMeta(targetDir);
|
|
295
|
+
if (!meta) return { success: false, error: `Plugin bulunamadi: ${name}` };
|
|
296
|
+
const tools = loadPluginTools(targetDir);
|
|
297
|
+
return { success: true, ...meta, toolCount: tools.length, tools: tools.map(t => t.name) };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return { success: false, error: `Bilinmeyen action: ${action}` };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
module.exports = {
|
|
304
|
+
name: "plugin",
|
|
305
|
+
description: "Plugin sistemi: install/list/enable/disable. GitHub'dan veya lokalden plugin yukleyebilirsin.",
|
|
306
|
+
inputSchema: {
|
|
307
|
+
type: "object",
|
|
308
|
+
properties: {
|
|
309
|
+
action: { type: "string", description: "list/install/uninstall/enable/disable/tools/info", enum: ["list", "install", "uninstall", "enable", "disable", "tools", "info"] },
|
|
310
|
+
name: { type: "string", description: "Plugin adi" },
|
|
311
|
+
source: { type: "string", description: "Lokal plugin klasor yolu" },
|
|
312
|
+
url: { type: "string", description: "GitHub repo URL" },
|
|
313
|
+
},
|
|
314
|
+
required: ["action"],
|
|
315
|
+
},
|
|
316
|
+
async execute(params) {
|
|
317
|
+
return await pluginOp(params);
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
// Diger tool'lardan cagirilabilir
|
|
322
|
+
module.exports.loadAllPluginTools = loadAllPluginTools;
|