natureco-cli 2.23.29 → 2.23.30

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 (71) hide show
  1. package/README.md +94 -11
  2. package/bin/natureco.js +402 -4
  3. package/package.json +1 -1
  4. package/src/commands/admin-rpc.js +219 -0
  5. package/src/commands/agent.js +89 -0
  6. package/src/commands/approvals.js +53 -0
  7. package/src/commands/backup.js +124 -0
  8. package/src/commands/bonjour.js +167 -0
  9. package/src/commands/capability.js +64 -0
  10. package/src/commands/clickclack.js +130 -0
  11. package/src/commands/commitments.js +32 -0
  12. package/src/commands/completion.js +76 -0
  13. package/src/commands/configure.js +93 -0
  14. package/src/commands/crestodian.js +92 -0
  15. package/src/commands/daemon.js +60 -0
  16. package/src/commands/device-pair.js +248 -0
  17. package/src/commands/devices.js +110 -0
  18. package/src/commands/directory.js +47 -0
  19. package/src/commands/dns.js +58 -0
  20. package/src/commands/docs.js +43 -0
  21. package/src/commands/exec-policy.js +71 -0
  22. package/src/commands/gateway-server.js +1155 -24
  23. package/src/commands/health.js +18 -0
  24. package/src/commands/imessage.js +128 -14
  25. package/src/commands/infer.js +73 -0
  26. package/src/commands/irc.js +64 -15
  27. package/src/commands/mattermost.js +114 -12
  28. package/src/commands/memory-cmd.js +134 -1
  29. package/src/commands/message.js +9 -3
  30. package/src/commands/migrate.js +213 -2
  31. package/src/commands/node.js +98 -0
  32. package/src/commands/nodes.js +106 -0
  33. package/src/commands/oc-path.js +200 -0
  34. package/src/commands/onboard.js +70 -0
  35. package/src/commands/open-prose.js +67 -0
  36. package/src/commands/policy.js +176 -0
  37. package/src/commands/proxy.js +155 -0
  38. package/src/commands/qr.js +28 -0
  39. package/src/commands/sandbox.js +125 -0
  40. package/src/commands/secrets.js +118 -0
  41. package/src/commands/setup.js +113 -7
  42. package/src/commands/signal.js +447 -18
  43. package/src/commands/sms.js +123 -19
  44. package/src/commands/system.js +53 -0
  45. package/src/commands/terminal.js +21 -0
  46. package/src/commands/thread-ownership.js +157 -0
  47. package/src/commands/transcripts.js +72 -0
  48. package/src/commands/voice.js +82 -0
  49. package/src/commands/vydra.js +98 -0
  50. package/src/commands/workboard.js +207 -0
  51. package/src/tools/audio_understanding.js +154 -0
  52. package/src/tools/browser.js +112 -0
  53. package/src/tools/canvas.js +104 -0
  54. package/src/tools/document_extract.js +84 -0
  55. package/src/tools/duckduckgo.js +54 -0
  56. package/src/tools/exa_search.js +66 -0
  57. package/src/tools/firecrawl.js +104 -0
  58. package/src/tools/image_generation.js +99 -0
  59. package/src/tools/llm_task.js +118 -0
  60. package/src/tools/media_understanding.js +128 -0
  61. package/src/tools/music_generation.js +113 -0
  62. package/src/tools/parallel_search.js +77 -0
  63. package/src/tools/phone_control.js +80 -0
  64. package/src/tools/phone_control_enhanced.js +184 -0
  65. package/src/tools/searxng.js +61 -0
  66. package/src/tools/speech_to_text.js +135 -0
  67. package/src/tools/text_to_speech.js +105 -0
  68. package/src/tools/thread_ownership.js +88 -0
  69. package/src/tools/video_generation.js +72 -0
  70. package/src/tools/web_readability.js +104 -0
  71. package/src/utils/memory.js +200 -0
@@ -0,0 +1,88 @@
1
+ const { getConfig, saveConfig } = require('../utils/config');
2
+
3
+ module.exports = {
4
+ name: 'thread_ownership',
5
+ description: 'Manage message thread ownership — assign threads to specific agents/bots',
6
+ inputSchema: {
7
+ type: 'object',
8
+ properties: {
9
+ action: { type: 'string', description: 'Action: assign, release, status, list', enum: ['assign', 'release', 'status', 'list'] },
10
+ threadId: { type: 'string', description: 'Thread/channel/conversation ID' },
11
+ agentName: { type: 'string', description: 'Agent name to assign (for assign action)' },
12
+ channel: { type: 'string', description: 'Channel type: telegram, whatsapp, signal, irc, mattermost, discord, slack' }
13
+ },
14
+ required: ['action']
15
+ },
16
+
17
+ async execute(params) {
18
+ try {
19
+ const config = getConfig();
20
+ const ownership = config.threadOwnership || {};
21
+
22
+ if (params.action === 'list') {
23
+ const entries = Object.entries(ownership);
24
+ if (entries.length === 0) {
25
+ return { success: true, action: 'list', message: 'Atanmış thread yok.', threads: [] };
26
+ }
27
+ return {
28
+ success: true,
29
+ action: 'list',
30
+ threads: entries.map(([id, agent]) => ({
31
+ threadId: id,
32
+ assignedAgent: agent
33
+ })),
34
+ count: entries.length
35
+ };
36
+ }
37
+
38
+ if (params.action === 'status') {
39
+ if (!params.threadId) {
40
+ return { success: false, error: 'threadId gerekli' };
41
+ }
42
+ const assigned = ownership[params.threadId];
43
+ return {
44
+ success: true,
45
+ action: 'status',
46
+ threadId: params.threadId,
47
+ assignedAgent: assigned || null,
48
+ isAssigned: !!assigned
49
+ };
50
+ }
51
+
52
+ if (params.action === 'assign') {
53
+ if (!params.threadId || !params.agentName) {
54
+ return { success: false, error: 'threadId ve agentName gerekli' };
55
+ }
56
+ ownership[params.threadId] = params.agentName;
57
+ config.threadOwnership = ownership;
58
+ saveConfig(config);
59
+ return {
60
+ success: true,
61
+ action: 'assign',
62
+ threadId: params.threadId,
63
+ agentName: params.agentName,
64
+ message: `Thread ${params.threadId} → ${params.agentName}`
65
+ };
66
+ }
67
+
68
+ if (params.action === 'release') {
69
+ if (!params.threadId) {
70
+ return { success: false, error: 'threadId gerekli' };
71
+ }
72
+ delete ownership[params.threadId];
73
+ config.threadOwnership = ownership;
74
+ saveConfig(config);
75
+ return {
76
+ success: true,
77
+ action: 'release',
78
+ threadId: params.threadId,
79
+ message: `Thread ${params.threadId} serbest bırakıldı`
80
+ };
81
+ }
82
+
83
+ return { success: false, error: `Bilinmeyen aksiyon: ${params.action}` };
84
+ } catch (error) {
85
+ return { success: false, error: error.message };
86
+ }
87
+ }
88
+ };
@@ -0,0 +1,72 @@
1
+ const { getConfig } = require('../utils/config');
2
+
3
+ const PROVIDERS = {
4
+ runway: {
5
+ name: 'RunwayML',
6
+ async generate({ prompt, apiKey, model }) {
7
+ const response = await fetch('https://api.runwayml.com/v1/text_to_video', {
8
+ method: 'POST',
9
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
10
+ body: JSON.stringify({
11
+ model: model || 'gen3a_turbo',
12
+ prompt_text: prompt,
13
+ prompt_image: null,
14
+ duration: 5
15
+ })
16
+ });
17
+ if (!response.ok) throw new Error(`Runway error ${response.status}: ${await response.text()}`);
18
+ const data = await response.json();
19
+ return [{ url: data.video?.url || data.url, id: data.id }];
20
+ }
21
+ }
22
+ };
23
+
24
+ module.exports = {
25
+ name: 'video_generation',
26
+ description: 'Generate videos using AI. Supports RunwayML provider.',
27
+ inputSchema: {
28
+ type: 'object',
29
+ properties: {
30
+ prompt: { type: 'string', description: 'Text description of the video to generate' },
31
+ provider: { type: 'string', description: 'Video provider: runway (default: runway)', enum: ['runway'] },
32
+ model: { type: 'string', description: 'Model override (default: gen3a_turbo)' }
33
+ },
34
+ required: ['prompt']
35
+ },
36
+
37
+ async execute(params) {
38
+ try {
39
+ const config = getConfig();
40
+ const provider = params.provider || 'runway';
41
+
42
+ const providerConfig = PROVIDERS[provider];
43
+ if (!providerConfig) {
44
+ return { success: false, error: `Desteklenmeyen provider: ${provider}` };
45
+ }
46
+
47
+ const apiKey = params.apiKey || config.runwayApiKey || process.env.RUNWAY_API_KEY;
48
+ if (!apiKey) {
49
+ return {
50
+ success: false,
51
+ error: `${providerConfig.name} API key gerekli.\nKur: natureco config set runwayApiKey <key>`
52
+ };
53
+ }
54
+
55
+ const videos = await providerConfig.generate({
56
+ prompt: params.prompt,
57
+ apiKey,
58
+ model: params.model
59
+ });
60
+
61
+ return {
62
+ success: true,
63
+ prompt: params.prompt,
64
+ provider,
65
+ videos,
66
+ count: videos.length
67
+ };
68
+ } catch (error) {
69
+ return { success: false, error: error.message };
70
+ }
71
+ }
72
+ };
@@ -0,0 +1,104 @@
1
+ const { getConfig } = require('../utils/config');
2
+
3
+ module.exports = {
4
+ name: 'web_readability',
5
+ description: 'Extract readable content from any web page (Mozilla Readability algorithm, no API key needed)',
6
+ inputSchema: {
7
+ type: 'object',
8
+ properties: {
9
+ url: { type: 'string', description: 'URL to extract readable content from' },
10
+ maxChars: { type: 'number', description: 'Maximum characters to return (default: 10000)', default: 10000 }
11
+ },
12
+ required: ['url']
13
+ },
14
+
15
+ async execute(params) {
16
+ try {
17
+ const maxChars = params.maxChars || 10000;
18
+
19
+ const response = await fetch(params.url, {
20
+ headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NatureCo-CLI/2.0; +https://natureco.me)' },
21
+ signal: AbortSignal.timeout(15000)
22
+ });
23
+
24
+ if (!response.ok) {
25
+ return { success: false, error: `HTTP ${response.status}: ${response.statusText}` };
26
+ }
27
+
28
+ const html = await response.text();
29
+ const contentType = response.headers.get('content-type') || '';
30
+ const isHtml = contentType.includes('text/html') || contentType.includes('text/plain') || html.trim().startsWith('<');
31
+
32
+ if (!isHtml) {
33
+ return { success: false, error: 'URL does not contain HTML content' };
34
+ }
35
+
36
+ // Extract title
37
+ const titleMatch = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
38
+ const title = titleMatch ? titleMatch[1].trim() : '';
39
+
40
+ // Extract meta description
41
+ const descMatch = html.match(/<meta[^>]+name=["']description["'][^>]+content=["']([^"']*)["']/i);
42
+ const description = descMatch ? descMatch[1] : '';
43
+
44
+ // Strip HTML tags for readable text
45
+ let text = html
46
+ // Remove scripts and styles
47
+ .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, ' ')
48
+ .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, ' ')
49
+ .replace(/<nav[^>]*>[\s\S]*?<\/nav>/gi, ' ')
50
+ .replace(/<footer[^>]*>[\s\S]*?<\/footer>/gi, ' ')
51
+ .replace(/<header[^>]*>[\s\S]*?<\/header>/gi, ' ')
52
+ // Remove all HTML tags
53
+ .replace(/<[^>]+>/g, ' ')
54
+ // Decode HTML entities
55
+ .replace(/&amp;/g, '&')
56
+ .replace(/&lt;/g, '<')
57
+ .replace(/&gt;/g, '>')
58
+ .replace(/&quot;/g, '"')
59
+ .replace(/&#x27;/g, "'")
60
+ .replace(/&#x2F;/g, '/')
61
+ .replace(/&#\d+;/g, ' ')
62
+ // Normalize whitespace
63
+ .replace(/&nbsp;/g, ' ')
64
+ .replace(/\s+/g, ' ')
65
+ .trim();
66
+
67
+ // Extract meaningful paragraphs
68
+ const paragraphs = [];
69
+ const pRegex = /<p[^>]*>([\s\S]*?)<\/p>/gi;
70
+ let pMatch;
71
+ while ((pMatch = pRegex.exec(html)) !== null) {
72
+ const pText = pMatch[1]
73
+ .replace(/<[^>]+>/g, '')
74
+ .replace(/&nbsp;/g, ' ')
75
+ .replace(/\s+/g, ' ')
76
+ .trim();
77
+ if (pText.length > 20) paragraphs.push(pText);
78
+ }
79
+
80
+ // Prefer paragraph extraction, fall back to full text
81
+ const content = paragraphs.length > 0
82
+ ? paragraphs.join('\n\n')
83
+ : text;
84
+
85
+ const truncated = content.length > maxChars
86
+ ? content.slice(0, maxChars) + '...'
87
+ : content;
88
+
89
+ return {
90
+ success: true,
91
+ url: params.url,
92
+ title,
93
+ description,
94
+ content: truncated,
95
+ wordCount: content.split(/\s+/).length,
96
+ totalChars: content.length,
97
+ truncated: content.length > maxChars,
98
+ source: 'readability'
99
+ };
100
+ } catch (error) {
101
+ return { success: false, error: error.message };
102
+ }
103
+ }
104
+ };
@@ -282,11 +282,211 @@ function clearMemory(botId) {
282
282
  }
283
283
  }
284
284
 
285
+ // ── Memory Wiki (structured pages) ──────────────────────────────────────────────
286
+
287
+ const MEMORY_WIKI_DIR = path.join(CONFIG_DIR, 'memory-wiki');
288
+
289
+ function ensureWikiDir() {
290
+ if (!fs.existsSync(MEMORY_WIKI_DIR)) {
291
+ fs.mkdirSync(MEMORY_WIKI_DIR, { recursive: true });
292
+ }
293
+ }
294
+
295
+ function getWikiPage(slug) {
296
+ ensureWikiDir();
297
+ const file = path.join(MEMORY_WIKI_DIR, `${slug.replace(/[^a-z0-9_-]/gi, '_')}.json`);
298
+ if (!fs.existsSync(file)) return null;
299
+ try { return JSON.parse(fs.readFileSync(file, 'utf-8')); } catch { return null; }
300
+ }
301
+
302
+ function saveWikiPage(slug, content) {
303
+ ensureWikiDir();
304
+ const file = path.join(MEMORY_WIKI_DIR, `${slug.replace(/[^a-z0-9_-]/gi, '_')}.json`);
305
+ fs.writeFileSync(file, JSON.stringify({ slug, content, updatedAt: new Date().toISOString() }, null, 2));
306
+ }
307
+
308
+ function listWikiPages() {
309
+ ensureWikiDir();
310
+ return fs.readdirSync(MEMORY_WIKI_DIR)
311
+ .filter(f => f.endsWith('.json'))
312
+ .map(f => {
313
+ try {
314
+ const data = JSON.parse(fs.readFileSync(path.join(MEMORY_WIKI_DIR, f), 'utf-8'));
315
+ return { slug: data.slug || f.replace('.json', ''), content: data.content || '', updatedAt: data.updatedAt };
316
+ } catch { return null; }
317
+ })
318
+ .filter(Boolean);
319
+ }
320
+
321
+ function searchWikiPages(query) {
322
+ const pages = listWikiPages();
323
+ const q = query.toLowerCase();
324
+ return pages.filter(p =>
325
+ p.slug.toLowerCase().includes(q) ||
326
+ p.content.toLowerCase().includes(q)
327
+ );
328
+ }
329
+
330
+ // ── Semantic Memory Search ─────────────────────────────────────────────────────
331
+
332
+ function semanticSearchMemory(query, limit = 5) {
333
+ const results = [];
334
+ const q = query.toLowerCase();
335
+
336
+ if (!fs.existsSync(MEMORY_DIR)) return results;
337
+
338
+ const files = fs.readdirSync(MEMORY_DIR).filter(f => f.endsWith('.json'));
339
+ for (const file of files) {
340
+ const botId = file.replace('.json', '');
341
+ const mem = loadMemory(botId);
342
+
343
+ // Score each fact by relevance
344
+ const scored = (mem.facts || [])
345
+ .map(f => {
346
+ const val = typeof f === 'string' ? f : f.value || '';
347
+ const words = q.split(/\s+/).filter(Boolean);
348
+ const matches = words.filter(w => val.toLowerCase().includes(w)).length;
349
+ const score = words.length > 0 ? matches / words.length : 0;
350
+ const boost = typeof f === 'object' ? (f.score || 5) / 10 : 0.5;
351
+ return { bot: mem.botName || botId, value: val, score: score * 0.7 + boost * 0.3 };
352
+ })
353
+ .filter(f => f.score > 0.1)
354
+ .sort((a, b) => b.score - a.score)
355
+ .slice(0, limit);
356
+
357
+ results.push(...scored);
358
+ }
359
+
360
+ return results.sort((a, b) => b.score - a.score).slice(0, limit);
361
+ }
362
+
363
+ // ── Memory Categories ──────────────────────────────────────────────────────────
364
+
365
+ const MEMORY_CATEGORIES = {
366
+ personal: { label: 'Kişisel Bilgiler', icon: '👤' },
367
+ work: { label: 'İş/Okul', icon: '💼' },
368
+ preferences: { label: 'Tercihler', icon: '⭐' },
369
+ health: { label: 'Sağlık', icon: '❤️' },
370
+ social: { label: 'Sosyal', icon: '👥' },
371
+ goals: { label: 'Hedefler', icon: '🎯' },
372
+ projects: { label: 'Projeler', icon: '📁' },
373
+ general: { label: 'Genel', icon: '📝' }
374
+ };
375
+
376
+ function addMemoryEntryWithCategory(botId, key, value, category = 'general') {
377
+ const memory = loadMemory(botId);
378
+ const now = new Date().toISOString();
379
+ const validCategory = MEMORY_CATEGORIES[category] ? category : 'general';
380
+
381
+ if (key === 'name') { memory.name = value; }
382
+ else if (key === 'nickname') { memory.nickname = value; }
383
+ else if (key === 'botName') { memory.botName = value; }
384
+ else if (key === 'preference') {
385
+ const existing = memory.preferences.find(p => p.value.toLowerCase() === value.toLowerCase());
386
+ if (existing) {
387
+ existing.score = Math.min(existing.score + 1, 10);
388
+ existing.updatedAt = now;
389
+ } else {
390
+ memory.preferences.push({ value, score: 5, updatedAt: now, category: validCategory });
391
+ }
392
+ } else {
393
+ const existing = memory.facts.find(f => f.value.toLowerCase() === value.toLowerCase());
394
+ if (existing) {
395
+ existing.score = Math.min(existing.score + 1, 10);
396
+ existing.updatedAt = now;
397
+ if (!existing.category) existing.category = validCategory;
398
+ } else {
399
+ memory.facts.push({ value, score: 5, updatedAt: now, category: validCategory });
400
+ }
401
+ }
402
+
403
+ // Decay old entries
404
+ const sixMonthsAgo = new Date();
405
+ sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6);
406
+
407
+ memory.preferences = memory.preferences.map(p => {
408
+ const age = new Date(p.updatedAt);
409
+ if (age < sixMonthsAgo) p.score = Math.max(p.score - 1, 1);
410
+ return p;
411
+ }).filter(p => p.score > 0);
412
+
413
+ memory.facts = memory.facts.map(f => {
414
+ const age = new Date(f.updatedAt);
415
+ if (age < sixMonthsAgo) f.score = Math.max(f.score - 1, 1);
416
+ return f;
417
+ }).filter(f => f.score > 0);
418
+
419
+ saveMemory(botId, memory);
420
+ }
421
+
422
+ // ── Memory Import/Export ───────────────────────────────────────────────────────
423
+
424
+ function exportMemory(botId) {
425
+ return loadMemory(botId);
426
+ }
427
+
428
+ function importMemory(botId, data) {
429
+ const current = loadMemory(botId);
430
+ const merged = {
431
+ name: data.name || current.name,
432
+ nickname: data.nickname || current.nickname,
433
+ botName: data.botName || current.botName,
434
+ preferences: [...(current.preferences || []), ...(data.preferences || [])],
435
+ facts: [...(current.facts || []), ...(data.facts || [])],
436
+ lastSeen: current.lastSeen
437
+ };
438
+ saveMemory(botId, merged);
439
+ }
440
+
441
+ // ── Active Memory (pre-prompt injection) ──────────────────────────────────────
442
+
443
+ function getActiveMemoryPrompt(botId, messageContext = '') {
444
+ const memory = loadMemory(botId);
445
+ const facts = memory.facts || [];
446
+ const prefs = memory.preferences || [];
447
+
448
+ if (facts.length === 0 && prefs.length === 0 && !memory.name) return '';
449
+
450
+ // Score facts by relevance to current context
451
+ const contextWords = messageContext.toLowerCase().split(/\s+/).filter(w => w.length > 3);
452
+ const scored = facts
453
+ .map(f => {
454
+ let relevance = 0;
455
+ if (contextWords.length > 0) {
456
+ const val = (f.value || '').toLowerCase();
457
+ relevance = contextWords.filter(w => val.includes(w)).length / contextWords.length;
458
+ }
459
+ return { ...f, relevance };
460
+ })
461
+ .sort((a, b) => (b.score || 0) + b.relevance - (a.score || 0) - a.relevance)
462
+ .slice(0, 5);
463
+
464
+ const parts = [];
465
+ if (memory.name) parts.push(`👤 ${memory.name}`);
466
+ if (memory.nickname) parts.push(`🏷 ${memory.nickname}`);
467
+ if (scored.length > 0) {
468
+ parts.push('📌 ' + scored.map(f => f.value).join(' | '));
469
+ }
470
+
471
+ return parts.length > 0 ? `<active_memory>\n${parts.join('\n')}\n</active_memory>` : '';
472
+ }
473
+
285
474
  module.exports = {
286
475
  loadMemory,
287
476
  saveMemory,
288
477
  addMemoryEntry,
478
+ addMemoryEntryWithCategory,
289
479
  extractMemoryFromMessage,
290
480
  getMemoryPrompt,
481
+ getActiveMemoryPrompt,
291
482
  clearMemory,
483
+ semanticSearchMemory,
484
+ exportMemory,
485
+ importMemory,
486
+ // Wiki
487
+ getWikiPage,
488
+ saveWikiPage,
489
+ listWikiPages,
490
+ searchWikiPages,
491
+ MEMORY_CATEGORIES,
292
492
  };