natureco-cli 5.1.1 → 5.2.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.
- package/package.json +1 -1
- package/src/tools/cron_create.js +32 -2
- package/src/tools/delegate_task.js +22 -0
- package/src/tools/document_extract.js +2 -1
- package/src/tools/filesystem.js +7 -7
- package/src/tools/list_dir.js +127 -127
- package/src/tools/mac_alarm.js +19 -7
- package/src/tools/macos_screenshot.js +87 -0
- package/src/tools/read_file.js +75 -75
- package/src/utils/paths.js +66 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "natureco-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.0",
|
|
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"
|
package/src/tools/cron_create.js
CHANGED
|
@@ -78,11 +78,41 @@ async function createCron({ name, schedule, command, description = "" }) {
|
|
|
78
78
|
}
|
|
79
79
|
} catch {}
|
|
80
80
|
|
|
81
|
+
// v5.2.0: Gercek macOS crontab'a ekle (kullanici crontab, sudo gerekmez)
|
|
82
|
+
const marker = `# natureco:${name}`;
|
|
83
|
+
let crontabUpdated = false;
|
|
84
|
+
let crontabError = null;
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const { execSync } = require("child_process");
|
|
88
|
+
// Mevcut crontab'i oku
|
|
89
|
+
let existing = "";
|
|
90
|
+
try { existing = execSync("crontab -l 2>/dev/null", { encoding: "utf8" }); }
|
|
91
|
+
catch { existing = ""; }
|
|
92
|
+
|
|
93
|
+
// Ayni isimde var mi kontrol
|
|
94
|
+
if (existing.includes(marker)) {
|
|
95
|
+
crontabUpdated = false;
|
|
96
|
+
} else {
|
|
97
|
+
const newLine = `${resolvedSchedule} ${command} ${marker}`;
|
|
98
|
+
const updated = existing + (existing.endsWith("\n") || existing === "" ? "" : "\n") + newLine + "\n";
|
|
99
|
+
// Yeni crontab yukle (heredoc yerine - ile stdin)
|
|
100
|
+
execSync("crontab -", { input: updated, encoding: "utf8" });
|
|
101
|
+
crontabUpdated = true;
|
|
102
|
+
}
|
|
103
|
+
} catch (e) {
|
|
104
|
+
crontabError = e.message;
|
|
105
|
+
}
|
|
106
|
+
|
|
81
107
|
return {
|
|
82
108
|
success: true,
|
|
83
109
|
cron: newCron,
|
|
84
|
-
message:
|
|
85
|
-
|
|
110
|
+
message: crontabUpdated
|
|
111
|
+
? `Cron olusturuldu VE macOS crontab'a eklendi: ${name} (${resolvedSchedule})`
|
|
112
|
+
: `Cron olusturuldu: ${name} (${resolvedSchedule})${crontabError ? ` - crontab eklenemedi: ${crontabError}` : ""}`,
|
|
113
|
+
crontabUpdated,
|
|
114
|
+
crontabError,
|
|
115
|
+
schedule: resolvedSchedule,
|
|
86
116
|
};
|
|
87
117
|
}
|
|
88
118
|
|
|
@@ -9,9 +9,31 @@ const { spawn } = require("child_process");
|
|
|
9
9
|
const path = require("path");
|
|
10
10
|
const os = require("os");
|
|
11
11
|
|
|
12
|
+
// v5.2.0: Agent alias mapping (Parton'un testinden — "review" diye bir agent yok)
|
|
13
|
+
const AGENT_ALIASES = {
|
|
14
|
+
"review": "general", // eskiden review diye bir vardi, simdi general
|
|
15
|
+
"analyze": "explore",
|
|
16
|
+
"scan": "security",
|
|
17
|
+
"audit": "security",
|
|
18
|
+
"check": "general",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const VALID_AGENTS = ["explore", "general", "seo", "content", "security", "debugger", "translator"];
|
|
22
|
+
|
|
12
23
|
async function delegate({ task, agent = "general", timeoutMs = 60000 }) {
|
|
13
24
|
if (!task) return { success: false, error: "task gerekli" };
|
|
14
25
|
|
|
26
|
+
// Alias cozumu
|
|
27
|
+
if (AGENT_ALIASES[agent]) {
|
|
28
|
+
agent = AGENT_ALIASES[agent];
|
|
29
|
+
}
|
|
30
|
+
if (!VALID_AGENTS.includes(agent)) {
|
|
31
|
+
return {
|
|
32
|
+
success: false,
|
|
33
|
+
error: `Gecersiz agent: ${agent}. Var olan agent tipleri: ${VALID_AGENTS.join(", ")}. Alias'lar: ${Object.keys(AGENT_ALIASES).join(", ")}`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
15
37
|
// Bu basitlestirilmis versiyon: kendi agent sistemi yerine
|
|
16
38
|
// natureco'nun REPL'ini sub-process olarak baslatip gorev verir
|
|
17
39
|
return new Promise((resolve) => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
|
+
const { expandPath } = require('../utils/paths');
|
|
2
3
|
const path = require('path');
|
|
3
4
|
const { execSync } = require('child_process');
|
|
4
5
|
|
|
@@ -16,7 +17,7 @@ module.exports = {
|
|
|
16
17
|
|
|
17
18
|
async execute(params) {
|
|
18
19
|
try {
|
|
19
|
-
const filePath = path.resolve(params.path
|
|
20
|
+
const filePath = path.resolve(expandPath(params.path));
|
|
20
21
|
const maxChars = params.maxChars || 50000;
|
|
21
22
|
|
|
22
23
|
if (!fs.existsSync(filePath)) {
|
package/src/tools/filesystem.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const listDir = require('./list_dir');
|
|
2
|
-
|
|
3
|
-
module.exports = {
|
|
4
|
-
...listDir,
|
|
5
|
-
name: 'filesystem',
|
|
6
|
-
description: 'List files and directories',
|
|
7
|
-
};
|
|
1
|
+
const listDir = require('./list_dir');
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
...listDir,
|
|
5
|
+
name: 'filesystem',
|
|
6
|
+
description: 'List files and directories',
|
|
7
|
+
};
|
package/src/tools/list_dir.js
CHANGED
|
@@ -1,127 +1,127 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* List Directory Tool
|
|
7
|
-
* Lists files and directories in a given path
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
module.exports = {
|
|
11
|
-
name: 'list_dir',
|
|
12
|
-
description: 'List files and directories in a given path. Returns file names, sizes, and types.',
|
|
13
|
-
inputSchema: {
|
|
14
|
-
type: 'object',
|
|
15
|
-
properties: {
|
|
16
|
-
path: {
|
|
17
|
-
type: 'string',
|
|
18
|
-
description: 'Directory path to list (relative or absolute). Use "." for current directory.'
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
required: ['path']
|
|
22
|
-
},
|
|
23
|
-
|
|
24
|
-
async execute(params) {
|
|
25
|
-
try {
|
|
26
|
-
// Expand ~ to home directory
|
|
27
|
-
let dirPath = params.path || '.';
|
|
28
|
-
dirPath = dirPath.replace(/^~/, os.homedir());
|
|
29
|
-
|
|
30
|
-
// Fix /home path - replace with actual home directory (Unix only)
|
|
31
|
-
// Handles: /home, home, /home/Documents, /home/anything
|
|
32
|
-
if (process.platform !== 'win32') {
|
|
33
|
-
if (dirPath === '/home' || dirPath === 'home' || dirPath.startsWith('/home/')) {
|
|
34
|
-
dirPath = dirPath.replace(/^\/home/, os.homedir());
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const absolutePath = path.resolve(dirPath);
|
|
39
|
-
|
|
40
|
-
// Check if directory exists
|
|
41
|
-
if (!fs.existsSync(absolutePath)) {
|
|
42
|
-
return {
|
|
43
|
-
success: false,
|
|
44
|
-
error: `Directory not found: ${params.path}`
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Check if it's a directory
|
|
49
|
-
const stats = fs.statSync(absolutePath);
|
|
50
|
-
if (!stats.isDirectory()) {
|
|
51
|
-
return {
|
|
52
|
-
success: false,
|
|
53
|
-
error: `Not a directory: ${params.path}`
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Read directory
|
|
58
|
-
const entries = fs.readdirSync(absolutePath, { withFileTypes: true });
|
|
59
|
-
|
|
60
|
-
// Format entries
|
|
61
|
-
const items = entries.map(entry => {
|
|
62
|
-
const itemPath = path.join(absolutePath, entry.name);
|
|
63
|
-
let size = 0;
|
|
64
|
-
let type = 'unknown';
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
const itemStats = fs.statSync(itemPath);
|
|
68
|
-
size = itemStats.size;
|
|
69
|
-
|
|
70
|
-
if (entry.isDirectory()) {
|
|
71
|
-
type = 'directory';
|
|
72
|
-
} else if (entry.isFile()) {
|
|
73
|
-
type = 'file';
|
|
74
|
-
} else if (entry.isSymbolicLink()) {
|
|
75
|
-
type = 'symlink';
|
|
76
|
-
}
|
|
77
|
-
} catch (err) {
|
|
78
|
-
// Ignore stat errors
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
name: entry.name,
|
|
83
|
-
type: type,
|
|
84
|
-
size: size
|
|
85
|
-
};
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// Sort: directories first, then files
|
|
89
|
-
items.sort((a, b) => {
|
|
90
|
-
if (a.type === 'directory' && b.type !== 'directory') return -1;
|
|
91
|
-
if (a.type !== 'directory' && b.type === 'directory') return 1;
|
|
92
|
-
return a.name.localeCompare(b.name);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
// Format output
|
|
96
|
-
let output = `Directory: ${absolutePath}\n`;
|
|
97
|
-
output += `Total items: ${items.length}\n\n`;
|
|
98
|
-
|
|
99
|
-
items.forEach(item => {
|
|
100
|
-
const typeIcon = item.type === 'directory' ? '📁' : '📄';
|
|
101
|
-
const sizeStr = item.type === 'file' ? ` (${formatSize(item.size)})` : '';
|
|
102
|
-
output += `${typeIcon} ${item.name}${sizeStr}\n`;
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
success: true,
|
|
107
|
-
output: output,
|
|
108
|
-
items: items,
|
|
109
|
-
path: absolutePath
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
} catch (error) {
|
|
113
|
-
return {
|
|
114
|
-
success: false,
|
|
115
|
-
error: error.message
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
function formatSize(bytes) {
|
|
122
|
-
if (bytes === 0) return '0 B';
|
|
123
|
-
const k = 1024;
|
|
124
|
-
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
125
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
126
|
-
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
|
127
|
-
}
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* List Directory Tool
|
|
7
|
+
* Lists files and directories in a given path
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
name: 'list_dir',
|
|
12
|
+
description: 'List files and directories in a given path. Returns file names, sizes, and types.',
|
|
13
|
+
inputSchema: {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
path: {
|
|
17
|
+
type: 'string',
|
|
18
|
+
description: 'Directory path to list (relative or absolute). Use "." for current directory.'
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
required: ['path']
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
async execute(params) {
|
|
25
|
+
try {
|
|
26
|
+
// Expand ~ to home directory
|
|
27
|
+
let dirPath = params.path || '.';
|
|
28
|
+
dirPath = dirPath.replace(/^~/, os.homedir());
|
|
29
|
+
|
|
30
|
+
// Fix /home path - replace with actual home directory (Unix only)
|
|
31
|
+
// Handles: /home, home, /home/Documents, /home/anything
|
|
32
|
+
if (process.platform !== 'win32') {
|
|
33
|
+
if (dirPath === '/home' || dirPath === 'home' || dirPath.startsWith('/home/')) {
|
|
34
|
+
dirPath = dirPath.replace(/^\/home/, os.homedir());
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const absolutePath = path.resolve(dirPath);
|
|
39
|
+
|
|
40
|
+
// Check if directory exists
|
|
41
|
+
if (!fs.existsSync(absolutePath)) {
|
|
42
|
+
return {
|
|
43
|
+
success: false,
|
|
44
|
+
error: `Directory not found: ${params.path}`
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Check if it's a directory
|
|
49
|
+
const stats = fs.statSync(absolutePath);
|
|
50
|
+
if (!stats.isDirectory()) {
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
error: `Not a directory: ${params.path}`
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Read directory
|
|
58
|
+
const entries = fs.readdirSync(absolutePath, { withFileTypes: true });
|
|
59
|
+
|
|
60
|
+
// Format entries
|
|
61
|
+
const items = entries.map(entry => {
|
|
62
|
+
const itemPath = path.join(absolutePath, entry.name);
|
|
63
|
+
let size = 0;
|
|
64
|
+
let type = 'unknown';
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const itemStats = fs.statSync(itemPath);
|
|
68
|
+
size = itemStats.size;
|
|
69
|
+
|
|
70
|
+
if (entry.isDirectory()) {
|
|
71
|
+
type = 'directory';
|
|
72
|
+
} else if (entry.isFile()) {
|
|
73
|
+
type = 'file';
|
|
74
|
+
} else if (entry.isSymbolicLink()) {
|
|
75
|
+
type = 'symlink';
|
|
76
|
+
}
|
|
77
|
+
} catch (err) {
|
|
78
|
+
// Ignore stat errors
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
name: entry.name,
|
|
83
|
+
type: type,
|
|
84
|
+
size: size
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Sort: directories first, then files
|
|
89
|
+
items.sort((a, b) => {
|
|
90
|
+
if (a.type === 'directory' && b.type !== 'directory') return -1;
|
|
91
|
+
if (a.type !== 'directory' && b.type === 'directory') return 1;
|
|
92
|
+
return a.name.localeCompare(b.name);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Format output
|
|
96
|
+
let output = `Directory: ${absolutePath}\n`;
|
|
97
|
+
output += `Total items: ${items.length}\n\n`;
|
|
98
|
+
|
|
99
|
+
items.forEach(item => {
|
|
100
|
+
const typeIcon = item.type === 'directory' ? '📁' : '📄';
|
|
101
|
+
const sizeStr = item.type === 'file' ? ` (${formatSize(item.size)})` : '';
|
|
102
|
+
output += `${typeIcon} ${item.name}${sizeStr}\n`;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
success: true,
|
|
107
|
+
output: output,
|
|
108
|
+
items: items,
|
|
109
|
+
path: absolutePath
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
} catch (error) {
|
|
113
|
+
return {
|
|
114
|
+
success: false,
|
|
115
|
+
error: error.message
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
function formatSize(bytes) {
|
|
122
|
+
if (bytes === 0) return '0 B';
|
|
123
|
+
const k = 1024;
|
|
124
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
125
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
126
|
+
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
|
127
|
+
}
|
package/src/tools/mac_alarm.js
CHANGED
|
@@ -90,26 +90,38 @@ async function setAlarm({ time, label = "Alarm", calendarName = "Calendar" }) {
|
|
|
90
90
|
|
|
91
91
|
// AppleScript ile macOS Calendar'a all-day etkinlik olarak alarm ekle
|
|
92
92
|
// (Reminder ile ayni sonuc, daha guvenilir cunku Calendar otomasyon izni Reminders'dan once verilir)
|
|
93
|
-
|
|
93
|
+
// Dual: Calendar event (gorunurluk) + Reminders (bildirim + alarm)
|
|
94
|
+
// Once Calendar'a basit etkinlik (alarm'siz, ama gorunur), sonra Reminders alarm
|
|
95
|
+
const calendarScript = `
|
|
94
96
|
tell application "Calendar"
|
|
95
97
|
set targetCal to first calendar whose writable is true
|
|
96
98
|
set startDate to date "${parsed.formattedDate} ${parsed.formattedTime}"
|
|
97
99
|
set endDate to startDate + (1 * minutes)
|
|
98
100
|
set newEvent to make new event at end of events of targetCal with properties {summary:"⏰ ${label.replace(/"/g, "'")} - NatureCo", start date:startDate, end date:endDate, allday event:false}
|
|
99
|
-
-- Alarm ekle: 0 dakika once = tam zamanda
|
|
100
|
-
set the number of sound alarms of newEvent to 1
|
|
101
|
-
make new sound alarm at end of sound alarms of newEvent with properties {trigger interval:-0}
|
|
102
101
|
save
|
|
103
102
|
return id of newEvent
|
|
104
103
|
end tell
|
|
105
104
|
`;
|
|
106
105
|
|
|
106
|
+
// Reminders'a bildirim + sesli alarm
|
|
107
|
+
const reminderScript = `
|
|
108
|
+
tell application "Reminders"
|
|
109
|
+
set targetList to default list
|
|
110
|
+
set startDate to date "${parsed.formattedDate} ${parsed.formattedTime}"
|
|
111
|
+
set newReminder to make new reminder at end of targetList with properties {name:"⏰ ${label.replace(/"/g, "'")} - NatureCo", body:"NatureCo CLI tarafindan ${parsed.humanReadable} icin kuruldu", due date:startDate}
|
|
112
|
+
save
|
|
113
|
+
return id of newReminder
|
|
114
|
+
end tell
|
|
115
|
+
`;
|
|
116
|
+
|
|
107
117
|
try {
|
|
108
|
-
const
|
|
118
|
+
const calId = await runAppleScript(calendarScript);
|
|
119
|
+
const remId = await runAppleScript(reminderScript);
|
|
109
120
|
return {
|
|
110
121
|
success: true,
|
|
111
|
-
eventId,
|
|
112
|
-
|
|
122
|
+
eventId: remId,
|
|
123
|
+
calendarEventId: calId,
|
|
124
|
+
message: `⏰ Alarm kuruldu: ${parsed.humanReadable} — "${label}" (Calendar + Reminders)`,
|
|
113
125
|
calendar: calendarName,
|
|
114
126
|
targetTime: parsed.humanReadable,
|
|
115
127
|
targetTimestamp: parsed.timestamp,
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* macos_screenshot - macOS native screenshot (v5.2.0)
|
|
3
|
+
*
|
|
4
|
+
* Playwright'a gerek kalmadan screencapture komutu ile
|
|
5
|
+
* ekran goruntusu alir. Sonra base64 olarak doner.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { spawn } = require("child_process");
|
|
9
|
+
const os = require("os");
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
const path = require("path");
|
|
12
|
+
|
|
13
|
+
const IS_MAC = os.platform() === "darwin";
|
|
14
|
+
|
|
15
|
+
async function captureScreen({ outputPath = null, region = "full" } = {}) {
|
|
16
|
+
if (!IS_MAC) return { success: false, error: "Sadece macOS" };
|
|
17
|
+
|
|
18
|
+
const tmpFile = outputPath || path.join(os.tmpdir(), `screenshot-${Date.now()}.png`);
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
// screencapture -x: ses yok, -t png: png format
|
|
21
|
+
const proc = spawn("screencapture", ["-x", "-t", "png", tmpFile]);
|
|
22
|
+
proc.on("close", (code) => {
|
|
23
|
+
if (code === 0 && fs.existsSync(tmpFile)) {
|
|
24
|
+
const buffer = fs.readFileSync(tmpFile);
|
|
25
|
+
const stats = fs.statSync(tmpFile);
|
|
26
|
+
resolve({
|
|
27
|
+
success: true,
|
|
28
|
+
path: tmpFile,
|
|
29
|
+
size: stats.size,
|
|
30
|
+
mime: "image/png",
|
|
31
|
+
base64: buffer.toString("base64"),
|
|
32
|
+
message: `Screenshot kaydedildi: ${tmpFile}`,
|
|
33
|
+
});
|
|
34
|
+
} else {
|
|
35
|
+
resolve({ success: false, error: `screencapture exit ${code}` });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
proc.on("error", (e) => resolve({ success: false, error: e.message }));
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function captureWindow({ windowTitle = null, outputPath = null } = {}) {
|
|
43
|
+
if (!IS_MAC) return { success: false, error: "Sadece macOS" };
|
|
44
|
+
|
|
45
|
+
const tmpFile = outputPath || path.join(os.tmpdir(), `screenshot-${Date.now()}.png`);
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
// screencapture -l <windowId> veya -w (interaktif, fare ile pencere seç)
|
|
48
|
+
const args = ["-x", "-t", "png", "-o"]; // -o: shadow yok
|
|
49
|
+
if (windowTitle) args.push("-l", String(windowTitle));
|
|
50
|
+
args.push(tmpFile);
|
|
51
|
+
|
|
52
|
+
const proc = spawn("screencapture", args);
|
|
53
|
+
proc.on("close", (code) => {
|
|
54
|
+
if (code === 0 && fs.existsSync(tmpFile)) {
|
|
55
|
+
const buffer = fs.readFileSync(tmpFile);
|
|
56
|
+
const stats = fs.statSync(tmpFile);
|
|
57
|
+
resolve({
|
|
58
|
+
success: true,
|
|
59
|
+
path: tmpFile,
|
|
60
|
+
size: stats.size,
|
|
61
|
+
mime: "image/png",
|
|
62
|
+
base64: buffer.toString("base64"),
|
|
63
|
+
});
|
|
64
|
+
} else {
|
|
65
|
+
resolve({ success: false, error: `screencapture exit ${code}` });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
proc.on("error", (e) => resolve({ success: false, error: e.message }));
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
name: "macos_screenshot",
|
|
74
|
+
description: "macOS native ekran goruntusu al (Playwright gerekmez). screencapture komutunu kullanir.",
|
|
75
|
+
inputSchema: {
|
|
76
|
+
type: "object",
|
|
77
|
+
properties: {
|
|
78
|
+
outputPath: { type: "string", description: "Kayit yolu (default: /tmp/screenshot-<ts>.png)" },
|
|
79
|
+
region: { type: "string", description: "full/window (default: full)", enum: ["full", "window"] },
|
|
80
|
+
windowTitle: { type: "string", description: "Pencere basligi (sadece region=window)" },
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
async execute(params) {
|
|
84
|
+
if (params.region === "window") return captureWindow(params);
|
|
85
|
+
return captureScreen(params);
|
|
86
|
+
},
|
|
87
|
+
};
|
package/src/tools/read_file.js
CHANGED
|
@@ -1,75 +1,75 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
|
|
5
|
-
module.exports = {
|
|
6
|
-
name: 'read_file',
|
|
7
|
-
description: 'Read content from a file',
|
|
8
|
-
inputSchema: {
|
|
9
|
-
type: 'object',
|
|
10
|
-
properties: {
|
|
11
|
-
path: {
|
|
12
|
-
type: 'string',
|
|
13
|
-
description: 'File path to read'
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
required: ['path']
|
|
17
|
-
},
|
|
18
|
-
|
|
19
|
-
async execute(params) {
|
|
20
|
-
try {
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
filePath = path
|
|
24
|
-
|
|
25
|
-
if (!fs.existsSync(filePath)) {
|
|
26
|
-
return {
|
|
27
|
-
success: false,
|
|
28
|
-
error: 'File does not exist'
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const stats = fs.statSync(filePath);
|
|
33
|
-
|
|
34
|
-
if (!stats.isFile()) {
|
|
35
|
-
return {
|
|
36
|
-
success: false,
|
|
37
|
-
error: 'Path is not a file'
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Check file size - if > 1MB, read only first 50KB
|
|
42
|
-
if (stats.size > 1024 * 1024) {
|
|
43
|
-
const fd = fs.openSync(filePath, 'r');
|
|
44
|
-
const buf = Buffer.alloc(50000);
|
|
45
|
-
fs.readSync(fd, buf, 0, 50000, 0);
|
|
46
|
-
fs.closeSync(fd);
|
|
47
|
-
|
|
48
|
-
const content = '[Büyük dosya - ilk ~50KB gösteriliyor]\n' + buf.toString('utf8');
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
success: true,
|
|
52
|
-
path: filePath,
|
|
53
|
-
content,
|
|
54
|
-
size: stats.size,
|
|
55
|
-
truncated: true
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
success: true,
|
|
63
|
-
path: filePath,
|
|
64
|
-
content,
|
|
65
|
-
size: stats.size,
|
|
66
|
-
truncated: false
|
|
67
|
-
};
|
|
68
|
-
} catch (error) {
|
|
69
|
-
return {
|
|
70
|
-
success: false,
|
|
71
|
-
error: error.message
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
};
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
name: 'read_file',
|
|
7
|
+
description: 'Read content from a file',
|
|
8
|
+
inputSchema: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
path: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
description: 'File path to read'
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
required: ['path']
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
async execute(params) {
|
|
20
|
+
try {
|
|
21
|
+
// v5.2.0: Robust path expansion (~, ~/, relative)
|
|
22
|
+
const { expandPath } = require('../utils/paths');
|
|
23
|
+
const filePath = expandPath(params.path);
|
|
24
|
+
|
|
25
|
+
if (!fs.existsSync(filePath)) {
|
|
26
|
+
return {
|
|
27
|
+
success: false,
|
|
28
|
+
error: 'File does not exist'
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const stats = fs.statSync(filePath);
|
|
33
|
+
|
|
34
|
+
if (!stats.isFile()) {
|
|
35
|
+
return {
|
|
36
|
+
success: false,
|
|
37
|
+
error: 'Path is not a file'
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Check file size - if > 1MB, read only first 50KB
|
|
42
|
+
if (stats.size > 1024 * 1024) {
|
|
43
|
+
const fd = fs.openSync(filePath, 'r');
|
|
44
|
+
const buf = Buffer.alloc(50000);
|
|
45
|
+
fs.readSync(fd, buf, 0, 50000, 0);
|
|
46
|
+
fs.closeSync(fd);
|
|
47
|
+
|
|
48
|
+
const content = '[Büyük dosya - ilk ~50KB gösteriliyor]\n' + buf.toString('utf8');
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
success: true,
|
|
52
|
+
path: filePath,
|
|
53
|
+
content,
|
|
54
|
+
size: stats.size,
|
|
55
|
+
truncated: true
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
success: true,
|
|
63
|
+
path: filePath,
|
|
64
|
+
content,
|
|
65
|
+
size: stats.size,
|
|
66
|
+
truncated: false
|
|
67
|
+
};
|
|
68
|
+
} catch (error) {
|
|
69
|
+
return {
|
|
70
|
+
success: false,
|
|
71
|
+
error: error.message
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* paths.js — Path helper utilities (v5.2.0)
|
|
3
|
+
*
|
|
4
|
+
* Tum tool'larda ~/Desktop/test.txt gibi path'leri dogru handle etmek icin.
|
|
5
|
+
* Parton'un gercek testinde "File does not exist" bug'i duzeltildi.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const os = require("os");
|
|
9
|
+
const path = require("path");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Path'i normalize et ve ~/ expansion yap.
|
|
13
|
+
* Mac/Windows/Linux'ta calisir.
|
|
14
|
+
*
|
|
15
|
+
* Ornek:
|
|
16
|
+
* expandPath("~/Desktop/test.txt") -> "/Users/gencay/Desktop/test.txt"
|
|
17
|
+
* expandPath("~") -> "/Users/gencay"
|
|
18
|
+
* expandPath("/tmp/x") -> "/tmp/x"
|
|
19
|
+
* expandPath("Downloads/x.txt") -> "<cwd>/Downloads/x.txt"
|
|
20
|
+
*/
|
|
21
|
+
function expandPath(inputPath) {
|
|
22
|
+
if (!inputPath) return inputPath;
|
|
23
|
+
let p = String(inputPath);
|
|
24
|
+
|
|
25
|
+
// ~ veya ~/foo -> home + foo
|
|
26
|
+
if (p === "~") return os.homedir();
|
|
27
|
+
if (p.startsWith("~/")) {
|
|
28
|
+
return path.join(os.homedir(), p.slice(2));
|
|
29
|
+
}
|
|
30
|
+
if (p.startsWith("~")) {
|
|
31
|
+
return path.join(os.homedir(), p.slice(1));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Zaten absolut ise dokunma
|
|
35
|
+
if (path.isAbsolute(p)) return p;
|
|
36
|
+
|
|
37
|
+
// Relative ise cwd'ye gore resolve
|
|
38
|
+
return path.resolve(process.cwd(), p);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isValidPath(p) {
|
|
42
|
+
if (!p) return false;
|
|
43
|
+
if (typeof p !== "string") return false;
|
|
44
|
+
if (p.includes("\0")) return false; // null byte
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Path icin shell-safe quote (Parton'un "Downloads/adsiz klasor" gibi yollari icin)
|
|
50
|
+
* shlex.quote kullaniyor — universal
|
|
51
|
+
*/
|
|
52
|
+
function quotePath(p) {
|
|
53
|
+
if (!p) return p;
|
|
54
|
+
const { execSync } = require("child_process");
|
|
55
|
+
// shlex.quote Python'dan cagiriliyor cunku Node'da yok
|
|
56
|
+
// Veya: bash -c 'echo $1' ile quote edilmis halini al
|
|
57
|
+
try {
|
|
58
|
+
// macOS/Unix: shlex.quote'a benzer sekilde single-quote wrap
|
|
59
|
+
if (/^[\w\-\.\/=]+$/.test(p)) return p; // guvenli karakterler
|
|
60
|
+
return `'${p.replace(/'/g, "'\\''")}'`;
|
|
61
|
+
} catch {
|
|
62
|
+
return p;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = { expandPath, isValidPath, quotePath };
|