natureco-cli 5.9.7 → 5.12.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.
@@ -0,0 +1,73 @@
1
+ const fs = require('fs');
2
+ const crypto = require('crypto');
3
+
4
+ const FILE_STATES = new Map();
5
+
6
+ function hashFile(filePath) {
7
+ try {
8
+ const content = fs.readFileSync(filePath);
9
+ return crypto.createHash('sha256').update(content).digest('hex');
10
+ } catch { return null; }
11
+ }
12
+
13
+ async function fileState(params) {
14
+ const { action, file, path: filePath } = params;
15
+ const targetFile = file || filePath;
16
+ if (!targetFile) return { success: false, error: 'file gerekli' };
17
+
18
+ if (action === 'track') {
19
+ if (!fs.existsSync(targetFile)) return { success: false, error: 'Dosya bulunamadi: ' + targetFile };
20
+ const hash = hashFile(targetFile);
21
+ FILE_STATES.set(targetFile, { hash, mtime: fs.statSync(targetFile).mtimeMs, trackedAt: Date.now() });
22
+ return { success: true, file: targetFile, hash, status: 'tracking' };
23
+ }
24
+
25
+ if (action === 'check') {
26
+ if (!FILE_STATES.has(targetFile)) return { success: false, error: 'Dosya takip edilmiyor. Once track kullanin.' };
27
+ const prev = FILE_STATES.get(targetFile);
28
+ if (!fs.existsSync(targetFile)) return { success: true, file: targetFile, status: 'deleted', previous: prev };
29
+ const current = { hash: hashFile(targetFile), mtime: fs.statSync(targetFile).mtimeMs };
30
+ const changed = current.hash !== prev.hash;
31
+ FILE_STATES.set(targetFile, { ...current, trackedAt: prev.trackedAt });
32
+ return { success: true, file: targetFile, changed, status: changed ? 'modified' : 'unchanged', previous: prev.hash, current: current.hash };
33
+ }
34
+
35
+ if (action === 'list') {
36
+ const entries = [];
37
+ for (const [f, state] of FILE_STATES) {
38
+ const exists = fs.existsSync(f);
39
+ entries.push({ file: f, trackedAt: new Date(state.trackedAt).toISOString(), exists, hash: exists ? hashFile(f) : null });
40
+ }
41
+ return { success: true, tracked: entries };
42
+ }
43
+
44
+ if (action === 'untrack') {
45
+ FILE_STATES.delete(targetFile);
46
+ return { success: true, message: targetFile + ' takipten cikarildi' };
47
+ }
48
+
49
+ if (action === 'diff') {
50
+ if (!FILE_STATES.has(targetFile)) return { success: false, error: 'Dosya takip edilmiyor' };
51
+ if (!fs.existsSync(targetFile)) return { success: false, error: 'Dosya bulunamadi' };
52
+ const prev = FILE_STATES.get(targetFile);
53
+ const current = hashFile(targetFile);
54
+ return { success: true, file: targetFile, changed: current !== prev.hash, oldHash: prev.hash, newHash: current };
55
+ }
56
+
57
+ return { success: false, error: 'Gecersiz action: ' + action };
58
+ }
59
+
60
+ module.exports = {
61
+ name: 'file_state',
62
+ description: 'Dosya degisiklik takibi: track/check/list/untrack/diff. Hash bazli degisim tespiti.',
63
+ inputSchema: {
64
+ type: 'object',
65
+ properties: {
66
+ action: { type: 'string', description: 'track, check, list, untrack, diff', enum: ['track', 'check', 'list', 'untrack', 'diff'] },
67
+ file: { type: 'string', description: 'Dosya yolu' },
68
+ path: { type: 'string', description: 'Alternatif dosya yolu (file ile ayni)' },
69
+ },
70
+ required: ['action'],
71
+ },
72
+ async execute(params) { return await fileState(params); },
73
+ };
@@ -0,0 +1,71 @@
1
+ const { execSync } = require('child_process');
2
+ const os = require('os');
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+
6
+ const IS_MAC = os.platform() === 'darwin';
7
+
8
+ async function googleMeet(params) {
9
+ const { action, meetingUrl, title, durationMinutes, email } = params;
10
+
11
+ if (action === 'create') {
12
+ if (!IS_MAC) return { success: false, error: 'Google Meet olusturma su an sadece macOS\'ta destekleniyor (Calendar entegrasyonu ile)' };
13
+ try {
14
+ const script = `
15
+ tell application "Calendar"
16
+ set newEvent to make new event at end of calendar 1 with properties {summary:"${(title || 'NatureCo Meet').replace(/"/g, '\\"')}", start date:(current date), end date:((current date) + ${(durationMinutes || 30) * 60})}
17
+ set URL of newEvent to "https://meet.google.com/new"
18
+ return "https://meet.google.com/new"
19
+ end tell
20
+ `;
21
+ const result = execSync('osascript -e \'' + script.replace(/'/g, "'\\''") + '\'', { timeout: 10000 }).toString().trim();
22
+ return { success: true, meetingUrl: result, title: title || 'NatureCo Meet', message: 'Toplanti olusturuldu. URL: ' + result };
23
+ } catch (e) {
24
+ return { success: false, error: 'Calendar ile meet olusturulamadi: ' + e.message };
25
+ }
26
+ }
27
+
28
+ if (action === 'open') {
29
+ if (!meetingUrl) return { success: false, error: 'meetingUrl gerekli' };
30
+ try {
31
+ if (IS_MAC) {
32
+ execSync('open "' + meetingUrl + '"', { timeout: 5000 });
33
+ } else if (os.platform() === 'win32') {
34
+ execSync('start "" "' + meetingUrl + '"', { timeout: 5000 });
35
+ } else {
36
+ execSync('xdg-open "' + meetingUrl + '"', { timeout: 5000 });
37
+ }
38
+ return { success: true, meetingUrl, message: 'Meet acildi: ' + meetingUrl };
39
+ } catch (e) {
40
+ return { success: false, error: 'Meet acilamadi: ' + e.message };
41
+ }
42
+ }
43
+
44
+ if (action === 'info') {
45
+ return {
46
+ success: true,
47
+ note: 'Google Meet bot ozelligi icin Chrome Extension veya Puppeteer/Playwright ile otomasyon gerekli. Su an: create (Calendar) ve open (browser) destekleniyor.',
48
+ supportedActions: ['create', 'open'],
49
+ requirements: 'create: macOS + Calendar izni. open: her platform.',
50
+ };
51
+ }
52
+
53
+ return { success: false, error: 'Gecersiz action: ' + action + ' (create, open, info)' };
54
+ }
55
+
56
+ module.exports = {
57
+ name: 'google_meet',
58
+ description: 'Google Meet toplantisi: create (Calendar ile), open (browser ile), info.',
59
+ inputSchema: {
60
+ type: 'object',
61
+ properties: {
62
+ action: { type: 'string', description: 'create, open, info', enum: ['create', 'open', 'info'] },
63
+ meetingUrl: { type: 'string', description: '(open) Meet URL' },
64
+ title: { type: 'string', description: '(create) Toplanti basligi' },
65
+ durationMinutes: { type: 'number', description: '(create) Sure (dakika, default: 30)' },
66
+ email: { type: 'string', description: 'Davet edilecek email' },
67
+ },
68
+ required: ['action'],
69
+ },
70
+ async execute(params) { return await googleMeet(params); },
71
+ };
@@ -0,0 +1,84 @@
1
+ const https = require('https');
2
+
3
+ async function homeassistant(params) {
4
+ const { action, entityId, domain, service, data, state, attributes } = params;
5
+
6
+ const baseUrl = process.env.HASS_URL || process.env.HOME_ASSISTANT_URL;
7
+ const token = process.env.HASS_TOKEN || process.env.HOME_ASSISTANT_TOKEN;
8
+
9
+ if (!baseUrl || !token) {
10
+ return { success: false, error: 'HASS_URL ve HASS_TOKEN ortam degiskenleri gerekli (orn: http://homeassistant.local:8123)' };
11
+ }
12
+
13
+ const apiUrl = baseUrl.replace(/\/+$/, '') + '/api';
14
+
15
+ function hassApi(method, path, body) {
16
+ return new Promise((resolve) => {
17
+ const url = apiUrl + path;
18
+ const opts = { method, headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, timeout: 10000 };
19
+ const req = https.request(url, opts, (res) => {
20
+ let data = '';
21
+ res.on('data', c => data += c);
22
+ res.on('end', () => {
23
+ try { resolve({ status: res.statusCode, data: JSON.parse(data) }); } catch { resolve({ status: res.statusCode, data }); }
24
+ });
25
+ });
26
+ req.on('error', (e) => resolve({ status: 0, error: e.message }));
27
+ if (body) req.write(JSON.stringify(body));
28
+ req.end();
29
+ });
30
+ }
31
+
32
+ if (action === 'get_states') {
33
+ const res = await hassApi('GET', '/states');
34
+ if (res.status !== 200) return { success: false, error: 'Home Assistant API: ' + (res.data?.message || res.status) };
35
+ const entities = (Array.isArray(res.data) ? res.data : []).map(e => ({
36
+ entityId: e.entity_id, state: e.state,
37
+ friendlyName: e.attributes?.friendly_name,
38
+ lastChanged: e.last_changed,
39
+ }));
40
+ return { success: true, count: entities.length, entities };
41
+ }
42
+
43
+ if (action === 'get_state') {
44
+ if (!entityId) return { success: false, error: 'entityId gerekli' };
45
+ const res = await hassApi('GET', '/states/' + entityId);
46
+ if (res.status !== 200) return { success: false, error: 'Entity bulunamadi: ' + entityId };
47
+ return { success: true, entityId, state: res.data.state, attributes: res.data.attributes, lastChanged: res.data.last_changed };
48
+ }
49
+
50
+ if (action === 'call_service') {
51
+ if (!domain || !service) return { success: false, error: 'domain ve service gerekli' };
52
+ const res = await hassApi('POST', '/services/' + domain + '/' + service, data || {});
53
+ return { success: res.status === 200, domain, service, status: res.status, result: res.data, entityId };
54
+ }
55
+
56
+ if (action === 'set_state') {
57
+ if (!entityId) return { success: false, error: 'entityId gerekli' };
58
+ const body = { state: state || 'on' };
59
+ if (attributes) body.attributes = attributes;
60
+ const res = await hassApi('POST', '/states/' + entityId, body);
61
+ return { success: res.status === 200, entityId, state: res.data?.state, attributes: res.data?.attributes };
62
+ }
63
+
64
+ return { success: false, error: 'Gecersiz action: ' + action + ' (get_states, get_state, call_service, set_state)' };
65
+ }
66
+
67
+ module.exports = {
68
+ name: 'homeassistant',
69
+ description: 'Home Assistant akilli ev kontrolu: get_states/get_state/call_service/set_state. HASS_URL ve HASS_TOKEN ortam degiskenleri gerekli.',
70
+ inputSchema: {
71
+ type: 'object',
72
+ properties: {
73
+ action: { type: 'string', description: 'get_states, get_state, call_service, set_state', enum: ['get_states', 'get_state', 'call_service', 'set_state'] },
74
+ entityId: { type: 'string', description: 'Entity ID (orn: light.living_room)' },
75
+ domain: { type: 'string', description: '(call_service) Domain (orn: light, switch, climate)' },
76
+ service: { type: 'string', description: '(call_service) Service (orn: turn_on, turn_off)' },
77
+ data: { type: 'object', description: '(call_service) Servis verisi' },
78
+ state: { type: 'string', description: '(set_state) Yeni state' },
79
+ attributes: { type: 'object', description: '(set_state) Opsiyonel attribute' },
80
+ },
81
+ required: ['action'],
82
+ },
83
+ async execute(params) { return await homeassistant(params); },
84
+ };
@@ -0,0 +1,103 @@
1
+ const https = require('https');
2
+
3
+ async function microsoftGraph(params) {
4
+ const { action, endpoint, method, body, userId, messageId, eventId, fileId, query } = params;
5
+
6
+ const accessToken = process.env.MS_GRAPH_TOKEN || process.env.MICROSOFT_GRAPH_TOKEN;
7
+
8
+ if (!accessToken) {
9
+ return { success: false, error: 'MS_GRAPH_TOKEN ortam degiskeni gerekli. (az login + erisim tokeni alinmalidir)' };
10
+ }
11
+
12
+ function graphApi(method, path, body) {
13
+ return new Promise((resolve) => {
14
+ const url = 'https://graph.microsoft.com/v1.0' + path;
15
+ const opts = { method, headers: { 'Authorization': 'Bearer ' + accessToken, 'Content-Type': 'application/json' }, timeout: 15000 };
16
+ const req = https.request(url, opts, (res) => {
17
+ let data = '';
18
+ res.on('data', c => data += c);
19
+ res.on('end', () => {
20
+ try { resolve({ status: res.statusCode, data: JSON.parse(data) }); } catch { resolve({ status: res.statusCode, data }); }
21
+ });
22
+ });
23
+ req.on('error', (e) => resolve({ status: 0, error: e.message }));
24
+ req.on('timeout', () => { req.destroy(); resolve({ status: 0, error: 'Timeout' }); });
25
+ if (body) req.write(JSON.stringify(body));
26
+ req.end();
27
+ });
28
+ }
29
+
30
+ if (action === 'send_email') {
31
+ if (!params.to || !params.subject || !params.body) {
32
+ return { success: false, error: 'to, subject, body gerekli' };
33
+ }
34
+ const emailBody = {
35
+ message: {
36
+ subject: params.subject,
37
+ body: { contentType: 'Text', content: params.body },
38
+ toRecipients: [{ emailAddress: { address: params.to } }],
39
+ },
40
+ };
41
+ if (params.cc) emailBody.message.ccRecipients = [{ emailAddress: { address: params.cc } }];
42
+ const res = await graphApi('POST', '/me/sendMail', emailBody);
43
+ return { success: res.status === 202, message: 'Email gonderildi: ' + params.to, status: res.status, error: res.data?.error?.message };
44
+ }
45
+
46
+ if (action === 'list_emails') {
47
+ const top = params.top || 10;
48
+ const res = await graphApi('GET', `/me/messages?$top=${top}&$select=subject,from,receivedDateTime,isRead`);
49
+ if (res.status !== 200) return { success: false, error: 'Graph API: ' + (res.data?.error?.message || res.status) };
50
+ const emails = (res.data.value || []).map(m => ({
51
+ id: m.id, subject: m.subject, from: m.from?.emailAddress?.address, receivedAt: m.receivedDateTime, isRead: m.isRead,
52
+ }));
53
+ return { success: true, count: emails.length, emails };
54
+ }
55
+
56
+ if (action === 'list_calendar_events') {
57
+ const top = params.top || 10;
58
+ const res = await graphApi('GET', `/me/events?$top=${top}&$select=subject,start,end,location`);
59
+ if (res.status !== 200) return { success: false, error: 'Graph API: ' + (res.data?.error?.message || res.status) };
60
+ const events = (res.data.value || []).map(e => ({
61
+ id: e.id, subject: e.subject, start: e.start?.dateTime, end: e.end?.dateTime, location: e.location?.displayName,
62
+ }));
63
+ return { success: true, count: events.length, events };
64
+ }
65
+
66
+ if (action === 'list_files') {
67
+ const res = await graphApi('GET', '/me/drive/root/children?$select=name,size,lastModifiedDateTime,folder');
68
+ if (res.status !== 200) return { success: false, error: 'Graph API: ' + (res.data?.error?.message || res.status) };
69
+ const files = (res.data.value || []).map(f => ({
70
+ id: f.id, name: f.name, size: f.size, lastModified: f.lastModifiedDateTime, isFolder: !!f.folder,
71
+ }));
72
+ return { success: true, count: files.length, files };
73
+ }
74
+
75
+ if (action === 'custom') {
76
+ if (!endpoint) return { success: false, error: 'endpoint gerekli (orn: /me/contacts)' };
77
+ const res = await graphApi(method || 'GET', endpoint, body);
78
+ return { success: res.status < 400, status: res.status, data: res.data, error: res.data?.error?.message };
79
+ }
80
+
81
+ return { success: false, error: 'Gecersiz action: ' + action + ' (send_email, list_emails, list_calendar_events, list_files, custom)' };
82
+ }
83
+
84
+ module.exports = {
85
+ name: 'microsoft_graph',
86
+ description: 'Microsoft Graph API (Office 365): email gonder/Liste, takvim, dosyalar. MS_GRAPH_TOKEN ortam degiskeni gerekli.',
87
+ inputSchema: {
88
+ type: 'object',
89
+ properties: {
90
+ action: { type: 'string', description: 'send_email, list_emails, list_calendar_events, list_files, custom', enum: ['send_email', 'list_emails', 'list_calendar_events', 'list_files', 'custom'] },
91
+ to: { type: 'string', description: '(send_email) Alici email' },
92
+ subject: { type: 'string', description: '(send_email) Konu' },
93
+ body: { type: 'string', description: '(send_email) Icerik' },
94
+ cc: { type: 'string', description: '(send_email) CC' },
95
+ top: { type: 'number', description: 'Maksimum sonuc sayisi' },
96
+ endpoint: { type: 'string', description: '(custom) Ozel Graph API endpoint (orn: /me/contacts)' },
97
+ method: { type: 'string', description: '(custom) HTTP method (default: GET)' },
98
+ query: { type: 'string', description: 'Arama sorgusu' },
99
+ },
100
+ required: ['action'],
101
+ },
102
+ async execute(params) { return await microsoftGraph(params); },
103
+ };
@@ -0,0 +1,50 @@
1
+ const PATTERNS = [
2
+ { name: 'email', regex: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g, replacement: '[EMAIL]' },
3
+ { name: 'phone', regex: /\b(\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{2,4}\b/g, replacement: '[PHONE]' },
4
+ { name: 'ssn', regex: /\b\d{3}-\d{2}-\d{4}\b/g, replacement: '[SSN]' },
5
+ { name: 'credit_card', regex: /\b(?:\d{4}[-\s]?){3}\d{4}\b/g, replacement: '[CREDIT_CARD]' },
6
+ { name: 'ip', regex: /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, replacement: '[IP]' },
7
+ { name: 'api_key', regex: /\b(sk[-_][a-zA-Z0-9]{20,}|api[-_]key[-_][a-zA-Z0-9]{16,}|[Aa][Kk][Ii][Aa][-\w]{20,})\b/g, replacement: '[API_KEY]' },
8
+ { name: 'token', regex: /\b(ghp_|gho_|ghu_|ghs_|ghr_)[a-zA-Z0-9]{36,}\b/g, replacement: '[TOKEN]' },
9
+ { name: 'aws_key', regex: /\b(AKIA[0-9A-Z]{16})\b/g, replacement: '[AWS_KEY]' },
10
+ ];
11
+
12
+ async function piiRedact(params) {
13
+ const { text, mode = 'mask', preserveTypes } = params;
14
+ if (!text) return { success: false, error: 'text gerekli' };
15
+
16
+ const preserve = new Set((preserveTypes || []).map(s => s.toLowerCase()));
17
+ let redacted = text;
18
+ const findings = [];
19
+
20
+ for (const pattern of PATTERNS) {
21
+ if (preserve.has(pattern.name)) continue;
22
+ let match;
23
+ pattern.regex.lastIndex = 0;
24
+ while ((match = pattern.regex.exec(redacted)) !== null) {
25
+ findings.push({ type: pattern.name, index: match.index, length: match[0].length });
26
+ }
27
+ if (mode === 'mask') {
28
+ redacted = redacted.replace(pattern.regex, pattern.replacement);
29
+ } else if (mode === 'partial') {
30
+ redacted = redacted.replace(pattern.regex, (m) => m.slice(0, 3) + '*'.repeat(Math.max(0, m.length - 6)) + m.slice(-3));
31
+ }
32
+ }
33
+
34
+ return { success: true, redacted, findings: findings.length > 0 ? findings : undefined, totalFindings: findings.length };
35
+ }
36
+
37
+ module.exports = {
38
+ name: 'pii_redact',
39
+ description: 'PII/gizli veri maskeleme: email, telefon, SSN, kredi karti, IP, API key, token, AWS key.',
40
+ inputSchema: {
41
+ type: 'object',
42
+ properties: {
43
+ text: { type: 'string', description: 'Redakte edilecek metin' },
44
+ mode: { type: 'string', description: 'mask (tamamen gizle) veya partial (ilk/son 3 karakter gorunur)', enum: ['mask', 'partial'] },
45
+ preserveTypes: { type: 'array', description: 'Korunacak PII tipleri (orn: ["email","phone"])', items: { type: 'string' } },
46
+ },
47
+ required: ['text'],
48
+ },
49
+ async execute(params) { return await piiRedact(params); },
50
+ };
@@ -0,0 +1,39 @@
1
+ async function sendMessage(params) {
2
+ const { to, message, platform, subject } = params;
3
+ if (!to || !message) return { success: false, error: 'to ve message gerekli' };
4
+
5
+ const platform_lc = (platform || '').toLowerCase();
6
+
7
+ if (platform_lc === 'terminal' || platform_lc === 'console' || !platform) {
8
+ return { success: true, platform: 'terminal', to, message, delivered: true, note: 'Mesaj terminale yazdirildi (dis ortama gonderilmedi)' };
9
+ }
10
+
11
+ if (platform_lc === 'email') {
12
+ if (!process.env.EMAIL_HOST || !process.env.EMAIL_USER) {
13
+ return { success: false, error: 'Email icin EMAIL_HOST ve EMAIL_USER ortam degiskenleri gerekli' };
14
+ }
15
+ return { success: true, platform: 'email', to, subject: subject || 'NatureCo Mesaj', message, delivered: 'pending', note: 'Email gonderme ayarlanmadi (smtp yapilandirmasi gerekli)' };
16
+ }
17
+
18
+ if (platform_lc === 'webhook') {
19
+ return { success: true, platform: 'webhook', to, message, delivered: 'pending', note: 'Webhook entegrasyonu icin gateway yapilandirmasi gerekli' };
20
+ }
21
+
22
+ return { success: false, error: 'Desteklenmeyen platform: ' + platform + ' (terminal, email, webhook)' };
23
+ }
24
+
25
+ module.exports = {
26
+ name: 'send_message',
27
+ description: 'Platformlar arasi mesaj gonderimi: terminal, email, webhook (diger platformlar icin gateway yapilandirmasi gerekli).',
28
+ inputSchema: {
29
+ type: 'object',
30
+ properties: {
31
+ to: { type: 'string', description: 'Alici (email adresi, kullanici adi, kanal)' },
32
+ message: { type: 'string', description: 'Mesaj icerigi' },
33
+ platform: { type: 'string', description: 'Hedef platform: terminal, email, webhook', enum: ['terminal', 'email', 'webhook'] },
34
+ subject: { type: 'string', description: 'Opsiyonel: konu (email icin)' },
35
+ },
36
+ required: ['to', 'message'],
37
+ },
38
+ async execute(params) { return await sendMessage(params); },
39
+ };
@@ -0,0 +1,62 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const SESSIONS_DIR = path.join(process.env.HOME || process.env.USERPROFILE || __dirname, '.natureco', 'sessions');
5
+
6
+ function getAllMessages() {
7
+ const messages = [];
8
+ if (!fs.existsSync(SESSIONS_DIR)) return messages;
9
+ const files = fs.readdirSync(SESSIONS_DIR).filter(f => f.endsWith('.json'));
10
+ for (const file of files) {
11
+ try {
12
+ const data = JSON.parse(fs.readFileSync(path.join(SESSIONS_DIR, file), 'utf8'));
13
+ const sessionName = file.replace('.json', '');
14
+ if (Array.isArray(data.messages)) {
15
+ for (const msg of data.messages) {
16
+ messages.push({ session: sessionName, role: msg.role, content: msg.content, timestamp: msg.timestamp || data.savedAt });
17
+ }
18
+ } else if (Array.isArray(data)) {
19
+ for (const msg of data) {
20
+ messages.push({ session: sessionName, role: msg.role, content: msg.content, timestamp: msg.timestamp });
21
+ }
22
+ }
23
+ } catch {}
24
+ }
25
+ return messages;
26
+ }
27
+
28
+ async function sessionSearch(params) {
29
+ const { query, session, limit = 10 } = params;
30
+ if (!query) return { success: false, error: 'query gerekli' };
31
+
32
+ const allMessages = getAllMessages();
33
+ const filtered = session ? allMessages.filter(m => m.session === session) : allMessages;
34
+ const q = query.toLowerCase();
35
+
36
+ const results = filtered
37
+ .filter(m => m.content && m.content.toLowerCase().includes(q))
38
+ .slice(0, limit)
39
+ .map(m => ({
40
+ session: m.session,
41
+ role: m.role,
42
+ snippet: m.content.length > 200 ? m.content.slice(0, 200) + '...' : m.content,
43
+ timestamp: m.timestamp,
44
+ }));
45
+
46
+ return { success: true, query, totalMatches: results.length, results, searchedSessions: session ? [session] : undefined };
47
+ }
48
+
49
+ module.exports = {
50
+ name: 'session_search',
51
+ description: 'Gecmis oturumlarda metin aramasi: tum oturumlar veya belirli bir oturum icinde ara.',
52
+ inputSchema: {
53
+ type: 'object',
54
+ properties: {
55
+ query: { type: 'string', description: 'Aranacak metin' },
56
+ session: { type: 'string', description: 'Opsiyonel: belirli bir oturum (dosya adi)' },
57
+ limit: { type: 'number', description: 'Maksimum sonuc sayisi (default: 10)' },
58
+ },
59
+ required: ['query'],
60
+ },
61
+ async execute(params) { return await sessionSearch(params); },
62
+ };
@@ -0,0 +1,93 @@
1
+ const https = require('https');
2
+
3
+ async function spotify(params) {
4
+ const { action, query, trackId, deviceId, volumePercent, clientId, clientSecret } = params;
5
+
6
+ const cid = clientId || process.env.SPOTIFY_CLIENT_ID;
7
+ const csecret = clientSecret || process.env.SPOTIFY_CLIENT_SECRET;
8
+
9
+ if (!cid || !csecret) {
10
+ return { success: false, error: 'SPOTIFY_CLIENT_ID ve SPOTIFY_CLIENT_SECRET ortam degiskenleri gerekli' };
11
+ }
12
+
13
+ async function getToken() {
14
+ const auth = Buffer.from(cid + ':' + csecret).toString('base64');
15
+ return new Promise((resolve) => {
16
+ const data = 'grant_type=client_credentials';
17
+ const req = https.request('https://accounts.spotify.com/api/token', {
18
+ method: 'POST',
19
+ headers: { 'Authorization': 'Basic ' + auth, 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(data) },
20
+ timeout: 10000,
21
+ }, (res) => {
22
+ let body = '';
23
+ res.on('data', c => body += c);
24
+ res.on('end', () => {
25
+ try { resolve(JSON.parse(body).access_token); } catch { resolve(null); }
26
+ });
27
+ });
28
+ req.on('error', () => resolve(null));
29
+ req.write(data);
30
+ req.end();
31
+ });
32
+ }
33
+
34
+ function spotifyApi(method, path, token, body) {
35
+ return new Promise((resolve) => {
36
+ const opts = { hostname: 'api.spotify.com', path, method, headers: { 'Authorization': 'Bearer ' + token }, timeout: 10000 };
37
+ const req = https.request(opts, (res) => {
38
+ let data = '';
39
+ res.on('data', c => data += c);
40
+ res.on('end', () => {
41
+ try { resolve({ status: res.statusCode, data: JSON.parse(data) }); } catch { resolve({ status: res.statusCode, data }); }
42
+ });
43
+ });
44
+ req.on('error', (e) => resolve({ status: 0, error: e.message }));
45
+ if (body) req.write(JSON.stringify(body));
46
+ req.end();
47
+ });
48
+ }
49
+
50
+ const token = await getToken();
51
+ if (!token) return { success: false, error: 'Spotify token alinamadi' };
52
+
53
+ if (action === 'search') {
54
+ if (!query) return { success: false, error: 'query gerekli' };
55
+ const res = await spotifyApi('GET', '/v1/search?q=' + encodeURIComponent(query) + '&type=track,album,artist&limit=10', token);
56
+ if (res.status !== 200) return { success: false, error: 'Spotify API: ' + (res.data?.error?.message || res.status) };
57
+ return { success: true, query, tracks: res.data.tracks?.items?.map(t => ({ id: t.id, name: t.name, artist: t.artists?.map(a => a.name).join(', '), album: t.album?.name, url: t.external_urls?.spotify })) || [],
58
+ albums: res.data.albums?.items?.map(a => ({ id: a.id, name: a.name, artist: a.artists?.map(a => a.name).join(', ') })) || [] };
59
+ }
60
+
61
+ if (action === 'get_track') {
62
+ if (!trackId) return { success: false, error: 'trackId gerekli' };
63
+ const res = await spotifyApi('GET', '/v1/tracks/' + trackId, token);
64
+ if (res.status !== 200) return { success: false, error: 'Spotify API: ' + (res.data?.error?.message || res.status) };
65
+ const t = res.data;
66
+ return { success: true, track: { id: t.id, name: t.name, artist: t.artists?.map(a => a.name).join(', '), album: t.album?.name, duration: t.duration_ms, url: t.external_urls?.spotify, preview: t.preview_url } };
67
+ }
68
+
69
+ if (action === 'play' || action === 'pause' || action === 'next' || action === 'previous') {
70
+ return { success: true, action, note: 'Playback kontrolu Spotify Premium + cihaz auth gerektirir. Token tipi "client_credentials" yetmez. "authorization_code" flow gerekli.' };
71
+ }
72
+
73
+ return { success: false, error: 'Gecersiz action: ' + action + ' (search, get_track, play, pause, next, previous)' };
74
+ }
75
+
76
+ module.exports = {
77
+ name: 'spotify',
78
+ description: 'Spotify arama ve bilgi alma. SPOTIFY_CLIENT_ID ve SPOTIFY_CLIENT_SECRET ortam degiskenleri gerekli.',
79
+ inputSchema: {
80
+ type: 'object',
81
+ properties: {
82
+ action: { type: 'string', description: 'search, get_track, play, pause, next, previous', enum: ['search', 'get_track', 'play', 'pause', 'next', 'previous'] },
83
+ query: { type: 'string', description: '(search) Arama sorgusu' },
84
+ trackId: { type: 'string', description: '(get_track) Spotify track ID' },
85
+ deviceId: { type: 'string', description: '(play/pause) Cihaz ID' },
86
+ volumePercent: { type: 'number', description: 'Ses seviyesi (0-100)' },
87
+ clientId: { type: 'string', description: 'Spotify Client ID (default: SPOTIFY_CLIENT_ID env)' },
88
+ clientSecret: { type: 'string', description: 'Spotify Client Secret (default: SPOTIFY_CLIENT_SECRET env)' },
89
+ },
90
+ required: ['action'],
91
+ },
92
+ async execute(params) { return await spotify(params); },
93
+ };
@@ -0,0 +1,65 @@
1
+ const https = require('https');
2
+ const http = require('http');
3
+
4
+ const BLOCKED_DOMAINS = new Set([
5
+ 'malware.test', 'phishing.test', 'evil.com', 'malicious.com',
6
+ 'hackers.org', 'pwned.com', 'ransomware.test',
7
+ ]);
8
+
9
+ async function urlSafety(params) {
10
+ const { url, checkType = 'basic' } = params;
11
+ if (!url) return { success: false, error: 'url gerekli' };
12
+
13
+ const issues = [];
14
+ let parsed;
15
+ try {
16
+ parsed = new URL(url);
17
+ } catch {
18
+ return { success: false, error: 'Gecersiz URL', safe: false };
19
+ }
20
+
21
+ if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
22
+ issues.push('Desteklenmeyen protokol: ' + parsed.protocol);
23
+ }
24
+
25
+ const domain = parsed.hostname.toLowerCase();
26
+ if (BLOCKED_DOMAINS.has(domain)) {
27
+ issues.push('Bilinen tehlikeli domain: ' + domain);
28
+ }
29
+
30
+ if (checkType === 'full' || checkType === 'resolve') {
31
+ try {
32
+ const code = await new Promise((resolve) => {
33
+ const req = (parsed.protocol === 'https:' ? https : http).get(url, { timeout: 5000 }, (res) => {
34
+ resolve(res.statusCode);
35
+ res.resume();
36
+ });
37
+ req.on('error', () => resolve(0));
38
+ req.on('timeout', () => { req.destroy(); resolve(0); });
39
+ });
40
+ if (code >= 400) issues.push('HTTP ' + code + ' dondu');
41
+ } catch { issues.push('Baglanti hatasi'); }
42
+ }
43
+
44
+ return {
45
+ safe: issues.length === 0,
46
+ issues: issues.length > 0 ? issues : undefined,
47
+ domain,
48
+ protocol: parsed.protocol,
49
+ url: parsed.href,
50
+ };
51
+ }
52
+
53
+ module.exports = {
54
+ name: 'url_safety',
55
+ description: 'URL guvenlik taramasi: domain kara liste, HTTP durum kodu kontrolu.',
56
+ inputSchema: {
57
+ type: 'object',
58
+ properties: {
59
+ url: { type: 'string', description: 'Kontrol edilecek URL' },
60
+ checkType: { type: 'string', description: 'basic (domain kontrol) veya full (domain + HTTP resolve)', enum: ['basic', 'full'] },
61
+ },
62
+ required: ['url'],
63
+ },
64
+ async execute(params) { return await urlSafety(params); },
65
+ };