natureco-cli 2.23.28 → 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 (96) hide show
  1. package/README.md +94 -11
  2. package/bin/natureco.js +470 -10
  3. package/package.json +10 -6
  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/channels.js +94 -4
  11. package/src/commands/chat.js +11 -25
  12. package/src/commands/clickclack.js +130 -0
  13. package/src/commands/commitments.js +32 -0
  14. package/src/commands/completion.js +76 -0
  15. package/src/commands/config.js +111 -68
  16. package/src/commands/configure.js +93 -0
  17. package/src/commands/crestodian.js +92 -0
  18. package/src/commands/daemon.js +60 -0
  19. package/src/commands/device-pair.js +248 -0
  20. package/src/commands/devices.js +110 -0
  21. package/src/commands/directory.js +47 -0
  22. package/src/commands/dns.js +58 -0
  23. package/src/commands/docs.js +43 -0
  24. package/src/commands/doctor.js +121 -16
  25. package/src/commands/exec-policy.js +71 -0
  26. package/src/commands/gateway-server.js +1175 -30
  27. package/src/commands/gateway.js +11 -20
  28. package/src/commands/health.js +18 -0
  29. package/src/commands/help.js +6 -0
  30. package/src/commands/imessage.js +169 -0
  31. package/src/commands/infer.js +73 -0
  32. package/src/commands/irc.js +119 -0
  33. package/src/commands/mattermost.js +164 -0
  34. package/src/commands/memory-cmd.js +134 -1
  35. package/src/commands/message.js +30 -4
  36. package/src/commands/migrate.js +213 -2
  37. package/src/commands/models.js +584 -216
  38. package/src/commands/node.js +98 -0
  39. package/src/commands/nodes.js +106 -0
  40. package/src/commands/oc-path.js +200 -0
  41. package/src/commands/onboard.js +70 -0
  42. package/src/commands/open-prose.js +67 -0
  43. package/src/commands/plugins.js +415 -172
  44. package/src/commands/policy.js +176 -0
  45. package/src/commands/proxy.js +155 -0
  46. package/src/commands/qr.js +28 -0
  47. package/src/commands/sandbox.js +125 -0
  48. package/src/commands/secrets.js +118 -0
  49. package/src/commands/security.js +149 -1
  50. package/src/commands/setup.js +114 -10
  51. package/src/commands/signal.js +495 -0
  52. package/src/commands/skills.js +20 -29
  53. package/src/commands/sms.js +168 -0
  54. package/src/commands/system.js +53 -0
  55. package/src/commands/tasks.js +328 -79
  56. package/src/commands/terminal.js +21 -0
  57. package/src/commands/thread-ownership.js +157 -0
  58. package/src/commands/transcripts.js +72 -0
  59. package/src/commands/voice.js +82 -0
  60. package/src/commands/vydra.js +98 -0
  61. package/src/commands/webhooks.js +79 -0
  62. package/src/commands/whatsapp.js +7 -21
  63. package/src/commands/workboard.js +207 -0
  64. package/src/tools/audio_understanding.js +154 -0
  65. package/src/tools/bash.js +63 -29
  66. package/src/tools/browser.js +112 -0
  67. package/src/tools/canvas.js +104 -0
  68. package/src/tools/document_extract.js +84 -0
  69. package/src/tools/duckduckgo.js +54 -0
  70. package/src/tools/exa_search.js +66 -0
  71. package/src/tools/firecrawl.js +104 -0
  72. package/src/tools/image_generation.js +99 -0
  73. package/src/tools/llm_task.js +118 -0
  74. package/src/tools/media_understanding.js +128 -0
  75. package/src/tools/music_generation.js +113 -0
  76. package/src/tools/parallel_search.js +77 -0
  77. package/src/tools/phone_control.js +80 -0
  78. package/src/tools/phone_control_enhanced.js +184 -0
  79. package/src/tools/searxng.js +61 -0
  80. package/src/tools/speech_to_text.js +135 -0
  81. package/src/tools/text_to_speech.js +105 -0
  82. package/src/tools/thread_ownership.js +88 -0
  83. package/src/tools/video_generation.js +72 -0
  84. package/src/tools/web_readability.js +104 -0
  85. package/src/utils/api.js +3 -20
  86. package/src/utils/approvals.js +297 -0
  87. package/src/utils/background.js +223 -66
  88. package/src/utils/baileys.js +21 -0
  89. package/src/utils/config.js +141 -10
  90. package/src/utils/errors.js +148 -0
  91. package/src/utils/inquirer-wrapper.js +1 -2
  92. package/src/utils/memory.js +200 -0
  93. package/src/utils/path-utils.js +13 -13
  94. package/src/utils/plugin-registry.js +238 -0
  95. package/src/utils/secrets.js +177 -0
  96. package/src/utils/skills.js +10 -23
@@ -0,0 +1,207 @@
1
+ const chalk = require('chalk');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const os = require('os');
5
+ const { getConfig, saveConfig } = require('../utils/config');
6
+
7
+ const BOARD_FILE = path.join(os.homedir(), '.natureco', 'data', 'workboard.json');
8
+
9
+ function loadBoard() {
10
+ try {
11
+ if (fs.existsSync(BOARD_FILE)) {
12
+ return JSON.parse(fs.readFileSync(BOARD_FILE, 'utf8'));
13
+ }
14
+ } catch {}
15
+ return { columns: ['todo', 'in-progress', 'done'], items: [] };
16
+ }
17
+
18
+ function saveBoard(board) {
19
+ const dir = path.dirname(BOARD_FILE);
20
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
21
+ fs.writeFileSync(BOARD_FILE, JSON.stringify(board, null, 2));
22
+ }
23
+
24
+ function workboard(args) {
25
+ const [action, ...params] = args || [];
26
+
27
+ if (!action || action === 'show') return showBoard();
28
+ if (action === 'add') return addItem(params.join(' '));
29
+ if (action === 'move') return moveItem(params[0], params[1]);
30
+ if (action === 'remove') return removeItem(params[0]);
31
+ if (action === 'clear') return clearBoard();
32
+ if (action === 'columns') return manageColumns(params);
33
+ if (action === 'export') return exportBoard();
34
+ if (action === 'import') return importBoard(params[0]);
35
+
36
+ console.log(chalk.red(`\n ❌ Bilinmeyen komut: ${action}\n`));
37
+ console.log(chalk.gray(' Kullanım: natureco workboard [show|add|move|remove|clear|columns|export|import]\n'));
38
+ process.exit(1);
39
+ }
40
+
41
+ function showBoard() {
42
+ const board = loadBoard();
43
+ const config = getConfig();
44
+ const wbConfig = config.workboard || {};
45
+ const columns = wbConfig.columns || board.columns;
46
+
47
+ console.log(chalk.cyan('\n 📋 Work Board\n'));
48
+ console.log(chalk.gray(' ' + '─'.repeat(64)));
49
+
50
+ for (const col of columns) {
51
+ const items = board.items.filter(i => i.column === col || (!i.column && col === columns[0]));
52
+ const colLabel = col === 'todo' ? '📋 To Do' : col === 'in-progress' ? '🔄 In Progress' : col === 'done' ? '✅ Done' : col;
53
+ console.log(chalk.white(`\n ${colLabel} (${items.length})`));
54
+
55
+ if (items.length === 0) {
56
+ console.log(chalk.gray(' (empty)'));
57
+ } else {
58
+ for (const item of items) {
59
+ const id = item.id || '';
60
+ const text = item.text || '';
61
+ const priority = item.priority || '';
62
+ const prioTag = priority === 'high' ? chalk.red('‼️') : priority === 'medium' ? chalk.yellow('❗') : priority === 'low' ? chalk.gray('📌') : '';
63
+ console.log(` ${chalk.gray(`[${id}]`)} ${prioTag} ${chalk.white(text.substring(0, 60))}${text.length > 60 ? '…' : ''}`);
64
+ }
65
+ }
66
+ }
67
+
68
+ console.log(chalk.gray('\n ' + '─'.repeat(64)));
69
+ console.log(chalk.gray(` ${board.items.length} total items across ${columns.length} columns`));
70
+ console.log(chalk.gray('\n Commands:'));
71
+ console.log(chalk.cyan(' add <text>') + chalk.gray(' Add item to To Do'));
72
+ console.log(chalk.cyan(' move <id> <column>') + chalk.gray(' Move item to column'));
73
+ console.log(chalk.cyan(' remove <id>') + chalk.gray(' Remove item'));
74
+ console.log(chalk.cyan(' columns list/set') + chalk.gray(' Manage columns'));
75
+ console.log(chalk.cyan(' export') + chalk.gray(' Export board as JSON'));
76
+ console.log(chalk.cyan(' import <file>') + chalk.gray(' Import board from JSON'));
77
+ console.log(chalk.cyan(' clear') + chalk.gray(' Clear all items'));
78
+ console.log();
79
+ }
80
+
81
+ function addItem(text) {
82
+ if (!text) {
83
+ console.log(chalk.red('\n ❌ Text gerekli\n'));
84
+ console.log(chalk.cyan(' natureco workboard add "Task description"\n'));
85
+ process.exit(1);
86
+ }
87
+
88
+ const board = loadBoard();
89
+ const id = Date.now().toString(36) + Math.random().toString(36).slice(2, 5);
90
+ board.items.push({
91
+ id,
92
+ text,
93
+ column: board.columns[0] || 'todo',
94
+ priority: 'medium',
95
+ created: new Date().toISOString()
96
+ });
97
+ saveBoard(board);
98
+ console.log(chalk.green(`\n ✅ Added: ${chalk.white(text)} ${chalk.gray(`[${id}]`)}\n`));
99
+ }
100
+
101
+ function moveItem(id, column) {
102
+ if (!id || !column) {
103
+ console.log(chalk.red('\n ❌ id ve column gerekli\n'));
104
+ console.log(chalk.cyan(' natureco workboard move <id> done\n'));
105
+ process.exit(1);
106
+ }
107
+
108
+ const board = loadBoard();
109
+ const item = board.items.find(i => i.id === id);
110
+ if (!item) {
111
+ console.log(chalk.red(`\n ❌ Item bulunamadı: ${id}\n`));
112
+ process.exit(1);
113
+ }
114
+
115
+ item.column = column;
116
+ item.updated = new Date().toISOString();
117
+ saveBoard(board);
118
+ console.log(chalk.green(`\n ✅ Moved "${item.text.substring(0, 40)}" → ${column}\n`));
119
+ }
120
+
121
+ function removeItem(id) {
122
+ if (!id) {
123
+ console.log(chalk.red('\n ❌ id gerekli\n'));
124
+ process.exit(1);
125
+ }
126
+
127
+ const board = loadBoard();
128
+ const idx = board.items.findIndex(i => i.id === id);
129
+ if (idx === -1) {
130
+ console.log(chalk.red(`\n ❌ Item bulunamadı: ${id}\n`));
131
+ process.exit(1);
132
+ }
133
+
134
+ const removed = board.items.splice(idx, 1)[0];
135
+ saveBoard(board);
136
+ console.log(chalk.green(`\n ✅ Removed: ${removed.text.substring(0, 40)}\n`));
137
+ }
138
+
139
+ function clearBoard() {
140
+ const board = loadBoard();
141
+ board.items = [];
142
+ saveBoard(board);
143
+ console.log(chalk.gray('\n 🗑️ Board temizlendi\n'));
144
+ }
145
+
146
+ function manageColumns(params) {
147
+ const config = getConfig();
148
+ if (!config.workboard) config.workboard = {};
149
+
150
+ if (params[0] === 'set') {
151
+ const cols = params.slice(1);
152
+ if (cols.length === 0) {
153
+ console.log(chalk.red('\n ❌ En az bir kolon gerekli\n'));
154
+ process.exit(1);
155
+ }
156
+ config.workboard.columns = cols;
157
+ saveConfig(config);
158
+ console.log(chalk.green(`\n ✅ Columns set: ${cols.join(', ')}\n`));
159
+ return;
160
+ }
161
+
162
+ const board = loadBoard();
163
+ const columns = config.workboard.columns || board.columns;
164
+ console.log(chalk.cyan('\n 📋 Columns\n'));
165
+ for (const col of columns) {
166
+ const count = board.items.filter(i => i.column === col).length;
167
+ console.log(` ${chalk.white(col)} ${chalk.gray(`(${count} items)`)}`);
168
+ }
169
+ console.log(chalk.gray('\n Set columns:'));
170
+ console.log(chalk.cyan(' natureco workboard columns set todo in-progress review done'));
171
+ console.log();
172
+ }
173
+
174
+ function exportBoard() {
175
+ const board = loadBoard();
176
+ console.log(chalk.cyan('\n 📋 Board Export\n'));
177
+ console.log(JSON.stringify(board, null, 2));
178
+ console.log();
179
+ }
180
+
181
+ function importBoard(filePath) {
182
+ if (!filePath) {
183
+ console.log(chalk.red('\n ❌ Dosya gerekli\n'));
184
+ process.exit(1);
185
+ }
186
+
187
+ if (!fs.existsSync(filePath)) {
188
+ console.log(chalk.red(`\n ❌ Dosya bulunamadı: ${filePath}\n`));
189
+ process.exit(1);
190
+ }
191
+
192
+ try {
193
+ const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
194
+ if (!data.items || !Array.isArray(data.items)) {
195
+ console.log(chalk.red('\n ❌ Geçersiz board JSON (items array gerekli)\n'));
196
+ process.exit(1);
197
+ }
198
+
199
+ saveBoard(data);
200
+ console.log(chalk.green(`\n ✅ Board imported: ${data.items.length} items\n`));
201
+ } catch (err) {
202
+ console.log(chalk.red(`\n ❌ Import hatası: ${err.message}\n`));
203
+ process.exit(1);
204
+ }
205
+ }
206
+
207
+ module.exports = workboard;
@@ -0,0 +1,154 @@
1
+ const { getConfig } = require('../utils/config');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ async function transcribeWithWhisper(audioPath, apiKey) {
6
+ const formData = new FormData();
7
+ const file = await fs.promises.readFile(audioPath);
8
+ const blob = new Blob([file]);
9
+ formData.append('file', blob, path.basename(audioPath));
10
+ formData.append('model', 'whisper-1');
11
+
12
+ const response = await fetch('https://api.openai.com/v1/audio/transcriptions', {
13
+ method: 'POST',
14
+ headers: { 'Authorization': `Bearer ${apiKey}` },
15
+ body: formData
16
+ });
17
+
18
+ if (!response.ok) throw new Error(`Whisper error ${response.status}: ${await response.text()}`);
19
+ const data = await response.json();
20
+ return data.text;
21
+ }
22
+
23
+ async function transcribeWithDeepgram(audioPath, apiKey) {
24
+ const file = await fs.promises.readFile(audioPath);
25
+ const response = await fetch('https://api.deepgram.com/v1/listen', {
26
+ method: 'POST',
27
+ headers: {
28
+ 'Authorization': `Token ${apiKey}`,
29
+ 'Content-Type': 'audio/wav'
30
+ },
31
+ body: file
32
+ });
33
+
34
+ if (!response.ok) throw new Error(`Deepgram error ${response.status}: ${await response.text()}`);
35
+ const data = await response.json();
36
+ return data.results?.channels?.[0]?.alternatives?.[0]?.transcript || '';
37
+ }
38
+
39
+ async function analyzeAudio(text, provider, apiKey, analysisPrompt) {
40
+ const baseUrl = provider === 'openai' ? 'https://api.openai.com/v1' : `https://api.${provider}.com/v1`;
41
+
42
+ const prompt = analysisPrompt || 'Analyze this audio transcript. Identify speakers, extract key points, detect sentiment, and summarize.';
43
+
44
+ const response = await fetch(`${baseUrl}/chat/completions`, {
45
+ method: 'POST',
46
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
47
+ body: JSON.stringify({
48
+ model: provider === 'openai' ? 'gpt-4o' : 'claude-3-haiku-20240307',
49
+ messages: [
50
+ { role: 'system', content: prompt },
51
+ { role: 'user', content: `Transcript:\n\n${text}` }
52
+ ],
53
+ max_tokens: 4096
54
+ })
55
+ });
56
+
57
+ if (!response.ok) throw new Error(`Analysis error ${response.status}: ${await response.text()}`);
58
+ return (await response.json()).choices?.[0]?.message?.content || '';
59
+ }
60
+
61
+ module.exports = {
62
+ name: 'audio_understanding',
63
+ description: 'Analyze audio files — transcribe speech and extract insights using AI (OpenAI Whisper + LLM, Deepgram)',
64
+ inputSchema: {
65
+ type: 'object',
66
+ properties: {
67
+ audioPath: { type: 'string', description: 'Path to audio file (mp3, wav, m4a, ogg)' },
68
+ audioUrl: { type: 'string', description: 'URL to audio file (alternative to audioPath)' },
69
+ action: { type: 'string', description: 'Action: transcribe, analyze, full', enum: ['transcribe', 'analyze', 'full'], default: 'full' },
70
+ transcriptionProvider: { type: 'string', description: 'Transcription provider: openai, deepgram (default: openai)', enum: ['openai', 'deepgram'] },
71
+ analysisProvider: { type: 'string', description: 'Analysis provider: openai, anthropic (default: openai)', enum: ['openai', 'anthropic'] },
72
+ analysisPrompt: { type: 'string', description: 'Custom analysis prompt' },
73
+ language: { type: 'string', description: 'Language code (e.g. tr, en, de)' }
74
+ },
75
+ required: ['action']
76
+ },
77
+
78
+ async execute(params) {
79
+ try {
80
+ const config = getConfig();
81
+ let audioPath = params.audioPath;
82
+
83
+ if (params.audioUrl && !audioPath) {
84
+ const tmpDir = require('os').tmpdir();
85
+ const tmpFile = path.join(tmpDir, `audio_${Date.now()}.tmp`);
86
+ const response = await fetch(params.audioUrl);
87
+ if (!response.ok) throw new Error(`Failed to download audio: ${response.status}`);
88
+ const buffer = Buffer.from(await response.arrayBuffer());
89
+ await fs.promises.writeFile(tmpFile, buffer);
90
+ audioPath = tmpFile;
91
+ }
92
+
93
+ if (!audioPath) {
94
+ return { success: false, error: 'audioPath veya audioUrl gerekli' };
95
+ }
96
+
97
+ if (!fs.existsSync(audioPath)) {
98
+ return { success: false, error: `Dosya bulunamadı: ${audioPath}` };
99
+ }
100
+
101
+ const transcribeProv = params.transcriptionProvider || 'openai';
102
+ let transcribeKey;
103
+
104
+ if (transcribeProv === 'openai') {
105
+ transcribeKey = config.openaiApiKey || process.env.OPENAI_API_KEY;
106
+ } else if (transcribeProv === 'deepgram') {
107
+ transcribeKey = config.deepgramApiKey || process.env.DEEPGRAM_API_KEY;
108
+ }
109
+
110
+ if (!transcribeKey) {
111
+ return { success: false, error: `${transcribeProv} API key gerekli` };
112
+ }
113
+
114
+ const result = { success: true, action: params.action || 'full' };
115
+
116
+ if (params.action === 'transcribe' || params.action === 'full' || !params.action) {
117
+ const transcript = transcribeProv === 'deepgram'
118
+ ? await transcribeWithDeepgram(audioPath, transcribeKey)
119
+ : await transcribeWithWhisper(audioPath, transcribeKey);
120
+
121
+ result.transcript = transcript;
122
+ result.transcriptionProvider = transcribeProv;
123
+ }
124
+
125
+ if (params.action === 'analyze' || params.action === 'full') {
126
+ const transcriptForAnalysis = result.transcript || (transcribeProv === 'deepgram'
127
+ ? await transcribeWithDeepgram(audioPath, transcribeKey)
128
+ : await transcribeWithWhisper(audioPath, transcribeKey));
129
+
130
+ const analysisProv = params.analysisProvider || 'openai';
131
+ const analysisApiKey = analysisProv === 'openai'
132
+ ? (config.openaiApiKey || process.env.OPENAI_API_KEY)
133
+ : (config.anthropicApiKey || process.env.ANTHROPIC_API_KEY);
134
+
135
+ if (!analysisApiKey) {
136
+ return { success: false, error: `${analysisProv} API key gerekli` };
137
+ }
138
+
139
+ const analysis = await analyzeAudio(transcriptForAnalysis, analysisProv, analysisApiKey, params.analysisPrompt);
140
+ result.analysis = analysis;
141
+ result.analysisProvider = analysisProv;
142
+ result.transcript = transcriptForAnalysis;
143
+ }
144
+
145
+ if (audioPath !== params.audioPath && audioPath) {
146
+ await fs.promises.unlink(audioPath).catch(() => {});
147
+ }
148
+
149
+ return result;
150
+ } catch (error) {
151
+ return { success: false, error: error.message };
152
+ }
153
+ }
154
+ };
package/src/tools/bash.js CHANGED
@@ -1,5 +1,7 @@
1
1
  const { execSync } = require('child_process');
2
2
  const os = require('os');
3
+ const chalk = require('chalk');
4
+ const { isDangerousCommand, checkCommand, isSafeCommand } = require('../utils/approvals');
3
5
 
4
6
  module.exports = {
5
7
  name: 'bash',
@@ -17,43 +19,75 @@ module.exports = {
17
19
 
18
20
  async execute(params) {
19
21
  try {
20
- // Security: Block dangerous commands
21
- const DANGEROUS_COMMANDS = ['kill', 'rm -rf', 'sudo rm', 'pkill', 'killall', 'shutdown', 'reboot', 'format'];
22
- const isDangerous = DANGEROUS_COMMANDS.some(cmd => params.command.includes(cmd));
23
-
24
- if (isDangerous) {
22
+ if (!params.command || !params.command.trim()) {
23
+ return { success: false, error: 'No command provided' };
24
+ }
25
+
26
+ const command = params.command;
27
+
28
+ // Skip approval checks for safe commands
29
+ if (isSafeCommand(command)) {
30
+ return runCommand(command);
31
+ }
32
+
33
+ // Check command against approvals policy
34
+ const approval = await checkCommand(command, { agentId: 'default' });
35
+
36
+ if (!approval.allowed) {
37
+ if (approval.reason === 'denied-by-policy') {
38
+ return {
39
+ success: false,
40
+ error: 'Command execution is denied by security policy. Use "natureco security allowlist add" to allow specific commands.',
41
+ };
42
+ }
43
+ if (approval.reason === 'not-in-allowlist') {
44
+ return {
45
+ success: false,
46
+ error: 'Command is not in the allowlist. Use "natureco security allowlist add" to add it, or set a less restrictive policy.',
47
+ };
48
+ }
49
+ return { success: false, error: `Command rejected: ${approval.reason}` };
50
+ }
51
+
52
+ // Check for dangerous commands that weren't already user-approved
53
+ if (isDangerousCommand(command)) {
54
+ console.log(chalk.yellow('\n ⚠️ Potentially dangerous command'));
55
+ console.log(chalk.gray(' ') + command);
56
+ console.log('');
25
57
  return {
26
58
  success: false,
27
- error: 'Bu komut güvenlik nedeniyle engellendi. Kullanıcıdan onay alınması gerekiyor.'
59
+ error: 'Dangerous command blocked. Run manually after review.',
28
60
  };
29
61
  }
30
-
31
- // Replace /home with actual home directory (Unix only)
32
- // Handles: /home, /home/Documents, /home/anything
33
- let command = params.command;
34
- if (process.platform !== 'win32') {
35
- command = command.replace(/\/home(\/[^\s]*)?/g, (match, subpath) => {
36
- return os.homedir() + (subpath || '');
37
- });
38
- }
39
-
40
- const output = execSync(command, {
41
- encoding: 'utf-8',
42
- maxBuffer: 10 * 1024 * 1024, // 10MB
43
- timeout: 30000, // 30 seconds
44
- shell: true // Use shell for command execution
45
- });
46
-
47
- return {
48
- success: true,
49
- output: output.trim()
50
- };
62
+
63
+ return runCommand(approval.editedCommand || command);
51
64
  } catch (error) {
52
65
  return {
53
66
  success: false,
54
- error: error.message,
55
- stderr: error.stderr?.toString() || ''
67
+ error: error?.message ?? 'Unknown error',
68
+ stderr: error?.stderr?.toString() ?? '',
56
69
  };
57
70
  }
58
71
  }
59
72
  };
73
+
74
+ function runCommand(command) {
75
+ let cmd = command;
76
+ if (process.platform !== 'win32') {
77
+ cmd = cmd.replace(/\/home(\/[^\s]*)?/g, (match, subpath) => {
78
+ return os.homedir() + (subpath || '');
79
+ });
80
+ }
81
+
82
+ const output = execSync(cmd, {
83
+ encoding: 'utf-8',
84
+ maxBuffer: 10 * 1024 * 1024,
85
+ timeout: 30000,
86
+ shell: true,
87
+ });
88
+
89
+ return {
90
+ success: true,
91
+ output: output.trim(),
92
+ };
93
+ }
@@ -0,0 +1,112 @@
1
+ const { getConfig } = require('../utils/config');
2
+
3
+ async function tryPlaywright(action, params) {
4
+ try {
5
+ const { chromium } = require('playwright');
6
+ const browser = await chromium.launch({ headless: true });
7
+ try {
8
+ const page = await browser.newPage();
9
+
10
+ if (action === 'open') {
11
+ await page.goto(params.url, { waitUntil: 'networkidle', timeout: 30000 });
12
+ const title = await page.title();
13
+ const content = await page.evaluate(() => document.body.innerText);
14
+ return { success: true, action, title, url: params.url, content: content.substring(0, 10000) };
15
+ }
16
+
17
+ if (action === 'screenshot') {
18
+ await page.goto(params.url, { waitUntil: 'networkidle', timeout: 30000 });
19
+ const screenshot = await page.screenshot({ type: 'png', fullPage: params.fullPage !== false });
20
+ return { success: true, action, url: params.url, screenshot: screenshot.toString('base64'), format: 'png' };
21
+ }
22
+
23
+ if (action === 'evaluate') {
24
+ await page.goto(params.url, { waitUntil: 'networkidle', timeout: 30000 });
25
+ const result = await page.evaluate(params.script);
26
+ return { success: true, action, url: params.url, result: JSON.stringify(result) };
27
+ }
28
+
29
+ if (action === 'html') {
30
+ await page.goto(params.url, { waitUntil: 'networkidle', timeout: 30000 });
31
+ const html = await page.content();
32
+ return { success: true, action, url: params.url, html: html.substring(0, 50000) };
33
+ }
34
+
35
+ return { success: false, error: `Unknown browser action: ${action}` };
36
+ } finally {
37
+ await browser.close();
38
+ }
39
+ } catch (err) {
40
+ if (err.code === 'MODULE_NOT_FOUND' || err.message?.includes('Cannot find module')) {
41
+ return { success: false, error: 'Playwright kurulu değil. Yüklemek için: npm install playwright', fallback: true };
42
+ }
43
+ return { success: false, error: err.message };
44
+ }
45
+ }
46
+
47
+ async function httpFallback(url) {
48
+ try {
49
+ const https = require('https');
50
+ const http = require('http');
51
+ const transport = url.startsWith('https') ? https : http;
52
+
53
+ return new Promise((resolve) => {
54
+ const req = transport.get(url, { headers: { 'User-Agent': 'NatureCo-CLI/2.0' }, timeout: 15000 }, (res) => {
55
+ let data = '';
56
+ res.on('data', (chunk) => { data += chunk; });
57
+ res.on('end', () => {
58
+ const title = data.match(/<title>([^<]*)<\/title>/i)?.[1]?.trim() || '';
59
+ const text = data.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
60
+ resolve({ success: true, action: 'open', title, url, content: text.substring(0, 10000), mode: 'http-fallback' });
61
+ });
62
+ });
63
+ req.on('error', (err) => resolve({ success: false, error: err.message }));
64
+ req.on('timeout', () => { req.destroy(); resolve({ success: false, error: 'timeout' }); });
65
+ });
66
+ } catch (err) {
67
+ return { success: false, error: err.message };
68
+ }
69
+ }
70
+
71
+ module.exports = {
72
+ name: 'browser',
73
+ description: 'Headless browser automation — open URLs, take screenshots, evaluate JS, get HTML. Uses Playwright if available, falls back to HTTP.',
74
+ inputSchema: {
75
+ type: 'object',
76
+ properties: {
77
+ action: { type: 'string', description: 'Action: open, screenshot, evaluate, html', enum: ['open', 'screenshot', 'evaluate', 'html'] },
78
+ url: { type: 'string', description: 'URL to navigate to' },
79
+ script: { type: 'string', description: 'JavaScript to evaluate (for evaluate action)' },
80
+ fullPage: { type: 'boolean', description: 'Full page screenshot (default: true)' }
81
+ },
82
+ required: ['action', 'url']
83
+ },
84
+
85
+ async execute(params) {
86
+ if (params.action === 'open') {
87
+ const pw = await tryPlaywright('open', params);
88
+ if (pw.fallback) return httpFallback(params.url);
89
+ return pw;
90
+ }
91
+
92
+ if (params.action === 'screenshot') {
93
+ const pw = await tryPlaywright('screenshot', params);
94
+ if (pw.fallback) return { success: false, error: 'Screenshot için Playwright gerekli. Kur: npm install playwright' };
95
+ return pw;
96
+ }
97
+
98
+ if (params.action === 'evaluate') {
99
+ const pw = await tryPlaywright('evaluate', params);
100
+ if (pw.fallback) return { success: false, error: 'JS evaluate için Playwright gerekli. Kur: npm install playwright' };
101
+ return pw;
102
+ }
103
+
104
+ if (params.action === 'html') {
105
+ const pw = await tryPlaywright('html', params);
106
+ if (pw.fallback) return httpFallback(params.url);
107
+ return pw;
108
+ }
109
+
110
+ return { success: false, error: `Unknown action: ${params.action}` };
111
+ }
112
+ };
@@ -0,0 +1,104 @@
1
+ module.exports = {
2
+ name: 'canvas',
3
+ description: 'Create and display rich content: tables, charts, formatted text, and structured data',
4
+ inputSchema: {
5
+ type: 'object',
6
+ properties: {
7
+ type: { type: 'string', description: 'Canvas type: table, markdown, code, data, separator, heading', enum: ['table', 'markdown', 'code', 'data', 'separator', 'heading'] },
8
+ title: { type: 'string', description: 'Section title' },
9
+ content: { type: 'string', description: 'Text content for markdown/code types' },
10
+ headers: { type: 'array', items: { type: 'string' }, description: 'Column headers (for table type)' },
11
+ rows: { type: 'array', items: { type: 'array', items: { type: 'string' } }, description: 'Table rows (for table type)' },
12
+ data: { type: 'object', description: 'Structured data to display (for data type)' },
13
+ language: { type: 'string', description: 'Code language (for code type)' }
14
+ },
15
+ required: ['type']
16
+ },
17
+
18
+ async execute(params) {
19
+ try {
20
+ const chalk = require('chalk');
21
+ const w = process.stdout.columns || 80;
22
+ const line = chalk.gray('─'.repeat(w));
23
+ const thin = chalk.gray('─'.repeat(40));
24
+
25
+ let output = ['', line];
26
+
27
+ if (params.title) {
28
+ output.push(chalk.bold.cyan(` ${params.title}`));
29
+ output.push(thin);
30
+ }
31
+
32
+ if (params.type === 'heading') {
33
+ output = ['', '', chalk.bold.green(` ${'█'.repeat(4)} ${params.content}`), ''];
34
+ output.push(line);
35
+ }
36
+
37
+ if (params.type === 'separator') {
38
+ output = ['', line, ''];
39
+ }
40
+
41
+ if (params.type === 'markdown') {
42
+ const lines = (params.content || '').split('\n');
43
+ for (const lineText of lines) {
44
+ output.push(lineText.startsWith('#')
45
+ ? chalk.bold.cyan(` ${lineText}`)
46
+ : lineText.startsWith('-') || lineText.startsWith('*')
47
+ ? chalk.white(` ${lineText}`)
48
+ : lineText.startsWith('>')
49
+ ? chalk.gray(` ${lineText}`)
50
+ : chalk.white(` ${lineText}`));
51
+ }
52
+ }
53
+
54
+ if (params.type === 'code') {
55
+ const lang = params.language || 'text';
56
+ output.push(chalk.gray(` ── ${lang} ──`));
57
+ const codeLines = (params.content || '').split('\n');
58
+ for (const lineText of codeLines) {
59
+ output.push(chalk.yellow(` │ ${lineText}`));
60
+ }
61
+ output.push(chalk.gray(' ' + '─'.repeat(40)));
62
+ }
63
+
64
+ if (params.type === 'table') {
65
+ const headers = params.headers || [];
66
+ const rows = params.rows || [];
67
+
68
+ if (headers.length > 0) {
69
+ const headerLine = headers.map(h => chalk.bold.cyan(h.padEnd(20))).join(' │ ');
70
+ output.push(` ${headerLine}`);
71
+ output.push(chalk.gray(` ${'─'.repeat(Math.max(40, headers.length * 22))}`));
72
+ }
73
+
74
+ for (const row of rows) {
75
+ const rowLine = row.map((cell, i) => {
76
+ const width = headers[i] ? headers[i].length : 20;
77
+ return (cell || '').padEnd(Math.max(width, 20));
78
+ }).join(' │ ');
79
+ output.push(` ${rowLine}`);
80
+ }
81
+ }
82
+
83
+ if (params.type === 'data') {
84
+ const d = params.data || {};
85
+ for (const [key, value] of Object.entries(d)) {
86
+ const val = typeof value === 'object' ? JSON.stringify(value) : String(value);
87
+ output.push(chalk.white(` ${key}`) + chalk.gray(': ') + chalk.cyan(val.length > 60 ? val.slice(0, 60) + '...' : val));
88
+ }
89
+ }
90
+
91
+ output.push(line);
92
+ output.push('');
93
+
94
+ return {
95
+ success: true,
96
+ type: params.type,
97
+ output: output.join('\n'),
98
+ rendered: true
99
+ };
100
+ } catch (error) {
101
+ return { success: false, error: error.message };
102
+ }
103
+ }
104
+ };