natureco-cli 2.23.4 → 2.23.6

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.
@@ -1,109 +1,170 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const os = require('os');
4
-
5
- function getSessionsDir(botId) {
6
- return path.join(os.homedir(), '.natureco', 'history', botId, 'sessions');
7
- }
8
-
9
- function ensureSessionsDir(botId) {
10
- const dir = getSessionsDir(botId);
11
- if (!fs.existsSync(dir)) {
12
- fs.mkdirSync(dir, { recursive: true });
13
- }
14
- }
15
-
16
- function createSession(botId, botName) {
17
- ensureSessionsDir(botId);
18
-
19
- const sessionId = Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
20
- const session = {
21
- id: sessionId,
22
- botId,
23
- botName,
24
- createdAt: new Date().toISOString(),
25
- messages: [],
26
- };
27
-
28
- saveSession(botId, session);
29
- return session;
30
- }
31
-
32
- function saveSession(botId, session) {
33
- ensureSessionsDir(botId);
34
- const sessionFile = path.join(getSessionsDir(botId), `${session.id}.json`);
35
- fs.writeFileSync(sessionFile, JSON.stringify(session, null, 2), 'utf-8');
36
- }
37
-
38
- function loadSession(botId, sessionId) {
39
- const sessionFile = path.join(getSessionsDir(botId), `${sessionId}.json`);
40
- if (!fs.existsSync(sessionFile)) {
41
- return null;
42
- }
43
- try {
44
- const data = fs.readFileSync(sessionFile, 'utf-8');
45
- return JSON.parse(data);
46
- } catch {
47
- return null;
48
- }
49
- }
50
-
51
- function getLatestSession(botId) {
52
- ensureSessionsDir(botId);
53
- const sessionsDir = getSessionsDir(botId);
54
- const files = fs.readdirSync(sessionsDir).filter(f => f.endsWith('.json'));
55
-
56
- if (files.length === 0) return null;
57
-
58
- // Sort by modification time
59
- const sorted = files
60
- .map(f => ({
61
- file: f,
62
- mtime: fs.statSync(path.join(sessionsDir, f)).mtime.getTime(),
63
- }))
64
- .sort((a, b) => b.mtime - a.mtime);
65
-
66
- const latestFile = sorted[0].file;
67
- const sessionId = path.basename(latestFile, '.json');
68
- return loadSession(botId, sessionId);
69
- }
70
-
71
- function listSessions(botId) {
72
- ensureSessionsDir(botId);
73
- const sessionsDir = getSessionsDir(botId);
74
- const files = fs.readdirSync(sessionsDir).filter(f => f.endsWith('.json'));
75
-
76
- const sessions = files.map(f => {
77
- const sessionId = path.basename(f, '.json');
78
- const session = loadSession(botId, sessionId);
79
- return session;
80
- }).filter(Boolean);
81
-
82
- // Sort by creation time
83
- sessions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
84
-
85
- return sessions;
86
- }
87
-
88
- function addMessageToSession(botId, sessionId, userMessage, botReply) {
89
- const session = loadSession(botId, sessionId);
90
- if (!session) return false;
91
-
92
- session.messages.push({
93
- user: userMessage,
94
- bot: botReply,
95
- timestamp: new Date().toISOString(),
96
- });
97
-
98
- saveSession(botId, session);
99
- return true;
100
- }
101
-
102
- module.exports = {
103
- createSession,
104
- saveSession,
105
- loadSession,
106
- getLatestSession,
107
- listSessions,
108
- addMessageToSession,
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
+ };
@@ -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
- const PROJECT_SKILLS_DIR = path.join(process.cwd(), '.natureco', 'skills');
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
- if (fs.existsSync(PROJECT_SKILLS_DIR)) {
124
- const projectDirs = fs.readdirSync(PROJECT_SKILLS_DIR, { withFileTypes: true })
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(PROJECT_SKILLS_DIR, dir.name, 'SKILL.md');
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(PROJECT_SKILLS_DIR, dir.name),
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, project, or builtin)
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(PROJECT_SKILLS_DIR, slug), label: 'project' },
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) {
@@ -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}${params.path ? ' — ' + params.path : params.command ? ' — ' + params.command : ''}`;
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(params.command || ''));
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(params.path), 'utf-8'); } catch {}
108
- showDiff(oldContent, params.content || '', params.path);
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(params.command)}\n`));
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' ? `✏️ ${params.path} dosyası değiştirilecek` : '⚠️ Bu komut çalıştırılacak'}. Onaylıyor musun?`),
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(params);
131
+ const result = await tool.execute(safeParams);
131
132
  stopSpinner(spinner, label, result.success !== false);
132
133
 
133
134
  // İstatistik güncelle