natureco-cli 2.23.4 → 2.23.5
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/bin/natureco.js +36 -15
- package/package.json +6 -8
- package/src/commands/chat.js +34 -4
- package/src/commands/code.js +45 -23
- package/src/commands/doctor.js +2 -2
- package/src/commands/gateway.js +1 -2
- package/src/commands/help.js +1 -1
- package/src/commands/migrate.js +5 -44
- package/src/commands/sessions.js +70 -183
- package/src/commands/telegram.js +1 -2
- package/src/commands/ultrareview.js +0 -7
- package/src/commands/update.js +2 -17
- package/src/commands/whatsapp.js +1 -2
- package/src/tools/bash.js +6 -4
- package/src/tools/filesystem.js +2 -62
- package/src/tools/git.js +39 -0
- package/src/tools/list_dir.js +5 -3
- package/src/utils/agents.js +7 -4
- package/src/utils/api.js +185 -11
- package/src/utils/commands.js +12 -7
- package/src/utils/config.js +2 -2
- package/src/utils/gateway-ws.js +73 -63
- package/src/utils/hooks.js +16 -11
- package/src/utils/mcp.js +6 -2
- package/src/utils/path-utils.js +23 -0
- package/src/utils/sessions.js +170 -109
- package/src/utils/skills.js +25 -8
- package/src/utils/tool-runner.js +8 -7
package/src/utils/sessions.js
CHANGED
|
@@ -1,109 +1,170 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
};
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
// ── Legacy botId-based sessions ─────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
const HISTORY_DIR = path.join(os.homedir(), '.natureco', 'history');
|
|
8
|
+
|
|
9
|
+
function getSessionsDir(botId) {
|
|
10
|
+
return path.join(HISTORY_DIR, botId, 'sessions');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function ensureSessionsDir(botId) {
|
|
14
|
+
const dir = getSessionsDir(botId);
|
|
15
|
+
if (!fs.existsSync(dir)) {
|
|
16
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createSession(botId, botName) {
|
|
21
|
+
ensureSessionsDir(botId);
|
|
22
|
+
|
|
23
|
+
const sessionId = Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
|
|
24
|
+
const session = {
|
|
25
|
+
id: sessionId,
|
|
26
|
+
botId,
|
|
27
|
+
botName,
|
|
28
|
+
createdAt: new Date().toISOString(),
|
|
29
|
+
messages: [],
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
_saveBotSession(botId, session);
|
|
33
|
+
return session;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function _saveBotSession(botId, session) {
|
|
37
|
+
ensureSessionsDir(botId);
|
|
38
|
+
const sessionFile = path.join(getSessionsDir(botId), `${session.id}.json`);
|
|
39
|
+
fs.writeFileSync(sessionFile, JSON.stringify(session, null, 2), 'utf-8');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function loadSession(botId, sessionId) {
|
|
43
|
+
const sessionFile = path.join(getSessionsDir(botId), `${sessionId}.json`);
|
|
44
|
+
if (!fs.existsSync(sessionFile)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const data = fs.readFileSync(sessionFile, 'utf-8');
|
|
49
|
+
return JSON.parse(data);
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getLatestSession(botId) {
|
|
56
|
+
ensureSessionsDir(botId);
|
|
57
|
+
const sessionsDir = getSessionsDir(botId);
|
|
58
|
+
const files = fs.readdirSync(sessionsDir).filter(f => f.endsWith('.json'));
|
|
59
|
+
|
|
60
|
+
if (files.length === 0) return null;
|
|
61
|
+
|
|
62
|
+
const sorted = files
|
|
63
|
+
.map(f => ({
|
|
64
|
+
file: f,
|
|
65
|
+
mtime: fs.statSync(path.join(sessionsDir, f)).mtime.getTime(),
|
|
66
|
+
}))
|
|
67
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
68
|
+
|
|
69
|
+
const latestFile = sorted[0].file;
|
|
70
|
+
const sessionId = path.basename(latestFile, '.json');
|
|
71
|
+
return loadSession(botId, sessionId);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function listSessionsForBot(botId) {
|
|
75
|
+
ensureSessionsDir(botId);
|
|
76
|
+
const sessionsDir = getSessionsDir(botId);
|
|
77
|
+
const files = fs.readdirSync(sessionsDir).filter(f => f.endsWith('.json'));
|
|
78
|
+
|
|
79
|
+
const sessions = files.map(f => {
|
|
80
|
+
const sessionId = path.basename(f, '.json');
|
|
81
|
+
return loadSession(botId, sessionId);
|
|
82
|
+
}).filter(Boolean);
|
|
83
|
+
|
|
84
|
+
sessions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
85
|
+
|
|
86
|
+
return sessions;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function addMessageToSession(botId, sessionId, userMessage, botReply) {
|
|
90
|
+
const session = loadSession(botId, sessionId);
|
|
91
|
+
if (!session) return false;
|
|
92
|
+
|
|
93
|
+
session.messages.push({
|
|
94
|
+
user: userMessage,
|
|
95
|
+
bot: botReply,
|
|
96
|
+
timestamp: new Date().toISOString(),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
_saveBotSession(botId, session);
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── Command-based sessions ──────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
const SESSIONS_DIR = path.join(os.homedir(), '.natureco', 'sessions');
|
|
106
|
+
|
|
107
|
+
function saveSession(commandName, messages, metadata = {}) {
|
|
108
|
+
if (!fs.existsSync(SESSIONS_DIR)) {
|
|
109
|
+
fs.mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
110
|
+
}
|
|
111
|
+
const id = Date.now().toString(36);
|
|
112
|
+
const filename = path.join(SESSIONS_DIR, `${commandName}-${id}.json`);
|
|
113
|
+
fs.writeFileSync(filename, JSON.stringify({
|
|
114
|
+
id, commandName, messages, metadata,
|
|
115
|
+
savedAt: new Date().toISOString()
|
|
116
|
+
}, null, 2));
|
|
117
|
+
return filename;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function loadLastSession(commandName) {
|
|
121
|
+
if (!fs.existsSync(SESSIONS_DIR)) return null;
|
|
122
|
+
const files = fs.readdirSync(SESSIONS_DIR)
|
|
123
|
+
.filter(f => f.startsWith(commandName + '-') && f.endsWith('.json'))
|
|
124
|
+
.sort()
|
|
125
|
+
.reverse();
|
|
126
|
+
if (!files.length) return null;
|
|
127
|
+
return JSON.parse(
|
|
128
|
+
fs.readFileSync(path.join(SESSIONS_DIR, files[0]), 'utf8')
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function listSessions(commandName) {
|
|
133
|
+
if (!fs.existsSync(SESSIONS_DIR)) return [];
|
|
134
|
+
return fs.readdirSync(SESSIONS_DIR)
|
|
135
|
+
.filter(f => !commandName || f.startsWith(commandName + '-'))
|
|
136
|
+
.map(f => {
|
|
137
|
+
const data = JSON.parse(
|
|
138
|
+
fs.readFileSync(path.join(SESSIONS_DIR, f), 'utf8')
|
|
139
|
+
);
|
|
140
|
+
return {
|
|
141
|
+
id: data.id,
|
|
142
|
+
commandName: data.commandName,
|
|
143
|
+
savedAt: data.savedAt,
|
|
144
|
+
messageCount: data.messages.length,
|
|
145
|
+
preview: data.messages
|
|
146
|
+
.find(m => m.role === 'user')?.content?.slice(0, 60)
|
|
147
|
+
};
|
|
148
|
+
})
|
|
149
|
+
.sort((a, b) => b.savedAt.localeCompare(a.savedAt));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function deleteSession(id) {
|
|
153
|
+
if (!fs.existsSync(SESSIONS_DIR)) return false;
|
|
154
|
+
const files = fs.readdirSync(SESSIONS_DIR)
|
|
155
|
+
.filter(f => f.includes(id));
|
|
156
|
+
files.forEach(f => fs.unlinkSync(path.join(SESSIONS_DIR, f)));
|
|
157
|
+
return files.length > 0;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = {
|
|
161
|
+
createSession,
|
|
162
|
+
loadSession,
|
|
163
|
+
getLatestSession,
|
|
164
|
+
listSessionsForBot,
|
|
165
|
+
addMessageToSession,
|
|
166
|
+
saveSession,
|
|
167
|
+
loadLastSession,
|
|
168
|
+
listSessions,
|
|
169
|
+
deleteSession,
|
|
170
|
+
};
|
package/src/utils/skills.js
CHANGED
|
@@ -6,7 +6,20 @@ const { CONFIG_DIR } = require('./config');
|
|
|
6
6
|
|
|
7
7
|
const BUILTIN_SKILLS_DIR = path.join(__dirname, '..', '..', 'skills');
|
|
8
8
|
const USER_SKILLS_DIR = path.join(CONFIG_DIR, 'skills');
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
function getProjectSkillsDir() {
|
|
11
|
+
return path.join(process.cwd(), '.natureco', 'skills');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Scan built-in skill slugs at module load time
|
|
15
|
+
let BUILTIN_SKILL_SLUGS = [];
|
|
16
|
+
try {
|
|
17
|
+
if (fs.existsSync(BUILTIN_SKILLS_DIR)) {
|
|
18
|
+
BUILTIN_SKILL_SLUGS = fs.readdirSync(BUILTIN_SKILLS_DIR, { withFileTypes: true })
|
|
19
|
+
.filter(d => d.isDirectory())
|
|
20
|
+
.map(d => d.name);
|
|
21
|
+
}
|
|
22
|
+
} catch {}
|
|
10
23
|
|
|
11
24
|
// Skill metadata parser
|
|
12
25
|
function parseSkillMetadata(content) {
|
|
@@ -120,12 +133,13 @@ function getSkills() {
|
|
|
120
133
|
}
|
|
121
134
|
|
|
122
135
|
// Project skills
|
|
123
|
-
|
|
124
|
-
|
|
136
|
+
const projectSkillsDir = getProjectSkillsDir();
|
|
137
|
+
if (fs.existsSync(projectSkillsDir)) {
|
|
138
|
+
const projectDirs = fs.readdirSync(projectSkillsDir, { withFileTypes: true })
|
|
125
139
|
.filter(d => d.isDirectory());
|
|
126
140
|
|
|
127
141
|
for (const dir of projectDirs) {
|
|
128
|
-
const skillFile = path.join(
|
|
142
|
+
const skillFile = path.join(projectSkillsDir, dir.name, 'SKILL.md');
|
|
129
143
|
if (fs.existsSync(skillFile)) {
|
|
130
144
|
const content = fs.readFileSync(skillFile, 'utf8');
|
|
131
145
|
const metadata = parseSkillMetadata(content);
|
|
@@ -134,7 +148,7 @@ function getSkills() {
|
|
|
134
148
|
...metadata,
|
|
135
149
|
slug: dir.name,
|
|
136
150
|
source: 'project',
|
|
137
|
-
path: path.join(
|
|
151
|
+
path: path.join(projectSkillsDir, dir.name),
|
|
138
152
|
});
|
|
139
153
|
}
|
|
140
154
|
}
|
|
@@ -202,12 +216,15 @@ async function installSkill(slug) {
|
|
|
202
216
|
}
|
|
203
217
|
}
|
|
204
218
|
|
|
205
|
-
// Remove skill (from user
|
|
219
|
+
// Remove skill (from user or project — built-in skills cannot be removed)
|
|
206
220
|
function removeSkill(slug) {
|
|
221
|
+
if (BUILTIN_SKILL_SLUGS.includes(slug)) {
|
|
222
|
+
throw new Error(`Built-in skill "${slug}" silinemez`);
|
|
223
|
+
}
|
|
224
|
+
|
|
207
225
|
const candidates = [
|
|
208
226
|
{ path: path.join(USER_SKILLS_DIR, slug), label: 'user' },
|
|
209
|
-
{ path: path.join(
|
|
210
|
-
{ path: path.join(BUILTIN_SKILLS_DIR, slug), label: 'builtin' },
|
|
227
|
+
{ path: path.join(getProjectSkillsDir(), slug), label: 'project' },
|
|
211
228
|
];
|
|
212
229
|
|
|
213
230
|
for (const { path: skillPath, label } of candidates) {
|
package/src/utils/tool-runner.js
CHANGED
|
@@ -84,6 +84,7 @@ function getToolDefinitions() {
|
|
|
84
84
|
|
|
85
85
|
// ── Execute a single tool ─────────────────────────────────────────────────────
|
|
86
86
|
async function executeTool(toolName, params, opts = {}) {
|
|
87
|
+
const safeParams = params ?? {};
|
|
87
88
|
const tools = loadTools();
|
|
88
89
|
const tool = tools[toolName];
|
|
89
90
|
const agentMode = opts.agentMode || false;
|
|
@@ -92,28 +93,28 @@ async function executeTool(toolName, params, opts = {}) {
|
|
|
92
93
|
return { success: false, error: `Tool '${toolName}' not found` };
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
const label = `${toolName}${
|
|
96
|
+
const label = `${toolName}${safeParams.path ? ' — ' + safeParams.path : safeParams.command ? ' — ' + safeParams.command : ''}`;
|
|
96
97
|
|
|
97
98
|
// ── Onay mekanizması (write_file ve tehlikeli bash) ───────────────────────
|
|
98
99
|
if (agentMode) {
|
|
99
100
|
const needsConfirm =
|
|
100
101
|
toolName === 'write_file' ||
|
|
101
|
-
(toolName === 'bash' && /\b(rm|mv|cp|chmod|chown|dd|mkfs|truncate)\b/.test(
|
|
102
|
+
(toolName === 'bash' && /\b(rm|mv|cp|chmod|chown|dd|mkfs|truncate)\b/.test(safeParams.command || ''));
|
|
102
103
|
|
|
103
104
|
if (needsConfirm) {
|
|
104
105
|
if (toolName === 'write_file') {
|
|
105
106
|
// Diff göster
|
|
106
107
|
let oldContent = '';
|
|
107
|
-
try { oldContent = fs.readFileSync(path.resolve(
|
|
108
|
-
showDiff(oldContent,
|
|
108
|
+
try { oldContent = fs.readFileSync(path.resolve(safeParams.path), 'utf-8'); } catch {}
|
|
109
|
+
showDiff(oldContent, safeParams.content || '', safeParams.path);
|
|
109
110
|
} else {
|
|
110
|
-
console.log(chalk.yellow(`\n 🖥️ Komut: ${chalk.white(
|
|
111
|
+
console.log(chalk.yellow(`\n 🖥️ Komut: ${chalk.white(safeParams.command)}\n`));
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
const { confirm } = await inquirer.prompt([{
|
|
114
115
|
type: 'confirm',
|
|
115
116
|
name: 'confirm',
|
|
116
|
-
message: chalk.yellow(` ${toolName === 'write_file' ? `✏️ ${
|
|
117
|
+
message: chalk.yellow(` ${toolName === 'write_file' ? `✏️ ${safeParams.path} dosyası değiştirilecek` : '⚠️ Bu komut çalıştırılacak'}. Onaylıyor musun?`),
|
|
117
118
|
default: true,
|
|
118
119
|
}]);
|
|
119
120
|
|
|
@@ -127,7 +128,7 @@ async function executeTool(toolName, params, opts = {}) {
|
|
|
127
128
|
// ── Spinner ile çalıştır ──────────────────────────────────────────────────
|
|
128
129
|
const spinner = startSpinner(label);
|
|
129
130
|
try {
|
|
130
|
-
const result = await tool.execute(
|
|
131
|
+
const result = await tool.execute(safeParams);
|
|
131
132
|
stopSpinner(spinner, label, result.success !== false);
|
|
132
133
|
|
|
133
134
|
// İstatistik güncelle
|