natureco-cli 1.0.1 → 1.0.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 +52 -1
- package/package.json +5 -5
- package/src/commands/chat.js +211 -9
- package/src/commands/commands.js +74 -0
- package/src/commands/cron.js +383 -0
- package/src/commands/git.js +229 -0
- package/src/commands/help.js +28 -4
- package/src/commands/hooks.js +87 -0
- package/src/commands/sessions.js +119 -0
- package/src/commands/setup.js +150 -134
- package/src/commands/skills.js +85 -1
- package/src/commands/tasks.js +79 -0
- package/src/utils/api.js +9 -0
- package/src/utils/background.js +66 -0
- package/src/utils/commands.js +77 -0
- package/src/utils/cron.js +126 -0
- package/src/utils/hooks.js +154 -0
- package/src/utils/memory.js +160 -0
- package/src/utils/sessions.js +109 -0
- package/src/utils/skills.js +41 -5
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const { loadBackgroundTasks, getBackgroundTask } = require('../utils/background');
|
|
3
|
+
|
|
4
|
+
async function tasks(action, ...args) {
|
|
5
|
+
if (!action || action === 'list') {
|
|
6
|
+
return listTasks();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (action === 'show') {
|
|
10
|
+
const id = args[0];
|
|
11
|
+
if (!id) {
|
|
12
|
+
console.log(chalk.red('\n❌ Task ID required\n'));
|
|
13
|
+
console.log(chalk.gray('Usage: natureco tasks show <id>\n'));
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
return showTask(id);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
console.log(chalk.red(`\n❌ Unknown action: ${action}\n`));
|
|
20
|
+
console.log(chalk.gray('Available actions: list, show\n'));
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function listTasks() {
|
|
25
|
+
const tasks = loadBackgroundTasks();
|
|
26
|
+
|
|
27
|
+
if (tasks.length === 0) {
|
|
28
|
+
console.log(chalk.gray('\nNo background tasks found.\n'));
|
|
29
|
+
console.log(chalk.gray('Create one by pressing Ctrl+B in chat.\n'));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log(chalk.yellow('\nBackground Tasks:\n'));
|
|
34
|
+
|
|
35
|
+
tasks.forEach(task => {
|
|
36
|
+
const statusColor = task.status === 'completed' ? chalk.green : task.status === 'failed' ? chalk.red : chalk.yellow;
|
|
37
|
+
const status = statusColor(task.status);
|
|
38
|
+
|
|
39
|
+
console.log(` ${chalk.cyan(task.id)} ${status}`);
|
|
40
|
+
console.log(chalk.white(` ${task.message.slice(0, 60)}...`));
|
|
41
|
+
console.log(chalk.gray(` Bot: ${task.botName} | Created: ${new Date(task.createdAt).toLocaleString()}`));
|
|
42
|
+
console.log('');
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function showTask(id) {
|
|
47
|
+
const task = getBackgroundTask(id);
|
|
48
|
+
|
|
49
|
+
if (!task) {
|
|
50
|
+
console.log(chalk.red(`\n❌ Task not found: ${id}\n`));
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log(chalk.yellow(`\nTask: ${task.id}\n`));
|
|
55
|
+
console.log(chalk.gray(`Status: ${task.status}`));
|
|
56
|
+
console.log(chalk.gray(`Bot: ${task.botName}`));
|
|
57
|
+
console.log(chalk.gray(`Created: ${new Date(task.createdAt).toLocaleString()}`));
|
|
58
|
+
|
|
59
|
+
if (task.completedAt) {
|
|
60
|
+
console.log(chalk.gray(`Completed: ${new Date(task.completedAt).toLocaleString()}`));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log(chalk.cyan('\nMessage:'));
|
|
64
|
+
console.log(chalk.white(task.message));
|
|
65
|
+
|
|
66
|
+
if (task.result) {
|
|
67
|
+
console.log(chalk.cyan('\nResult:'));
|
|
68
|
+
console.log(chalk.white(task.result));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (task.error) {
|
|
72
|
+
console.log(chalk.red('\nError:'));
|
|
73
|
+
console.log(chalk.white(task.error));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log('');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = tasks;
|
package/src/utils/api.js
CHANGED
|
@@ -25,6 +25,9 @@ async function getBots(apiKey) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
async function sendMessage(apiKey, botId, message, conversationId = null, skillPrompts = '') {
|
|
28
|
+
const { getConfig } = require('./config');
|
|
29
|
+
const config = getConfig();
|
|
30
|
+
|
|
28
31
|
const body = {
|
|
29
32
|
agent_id: botId,
|
|
30
33
|
message,
|
|
@@ -38,6 +41,12 @@ async function sendMessage(apiKey, botId, message, conversationId = null, skillP
|
|
|
38
41
|
body.system_prompt = skillPrompts;
|
|
39
42
|
}
|
|
40
43
|
|
|
44
|
+
// Custom AI provider varsa ekle
|
|
45
|
+
if (config.aiProvider && config.aiApiKey) {
|
|
46
|
+
body.custom_provider = config.aiProvider;
|
|
47
|
+
body.custom_api_key = config.aiApiKey;
|
|
48
|
+
}
|
|
49
|
+
|
|
41
50
|
return request('/api/agent/chat', {
|
|
42
51
|
method: 'POST',
|
|
43
52
|
headers: {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
const BACKGROUND_TASKS_FILE = path.join(os.homedir(), '.natureco', 'background-tasks.json');
|
|
6
|
+
|
|
7
|
+
function ensureBackgroundDir() {
|
|
8
|
+
const dir = path.dirname(BACKGROUND_TASKS_FILE);
|
|
9
|
+
if (!fs.existsSync(dir)) {
|
|
10
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function loadBackgroundTasks() {
|
|
15
|
+
ensureBackgroundDir();
|
|
16
|
+
if (!fs.existsSync(BACKGROUND_TASKS_FILE)) {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const data = fs.readFileSync(BACKGROUND_TASKS_FILE, 'utf-8');
|
|
21
|
+
return JSON.parse(data);
|
|
22
|
+
} catch {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function saveBackgroundTasks(tasks) {
|
|
28
|
+
ensureBackgroundDir();
|
|
29
|
+
fs.writeFileSync(BACKGROUND_TASKS_FILE, JSON.stringify(tasks, null, 2), 'utf-8');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function addBackgroundTask(task) {
|
|
33
|
+
const tasks = loadBackgroundTasks();
|
|
34
|
+
const id = Date.now().toString(36) + Math.random().toString(36).slice(2);
|
|
35
|
+
const newTask = {
|
|
36
|
+
id,
|
|
37
|
+
...task,
|
|
38
|
+
status: 'pending',
|
|
39
|
+
createdAt: new Date().toISOString(),
|
|
40
|
+
};
|
|
41
|
+
tasks.push(newTask);
|
|
42
|
+
saveBackgroundTasks(tasks);
|
|
43
|
+
return newTask;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function updateBackgroundTask(id, updates) {
|
|
47
|
+
const tasks = loadBackgroundTasks();
|
|
48
|
+
const index = tasks.findIndex(t => t.id === id);
|
|
49
|
+
if (index === -1) return false;
|
|
50
|
+
tasks[index] = { ...tasks[index], ...updates };
|
|
51
|
+
saveBackgroundTasks(tasks);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getBackgroundTask(id) {
|
|
56
|
+
const tasks = loadBackgroundTasks();
|
|
57
|
+
return tasks.find(t => t.id === id);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
loadBackgroundTasks,
|
|
62
|
+
saveBackgroundTasks,
|
|
63
|
+
addBackgroundTask,
|
|
64
|
+
updateBackgroundTask,
|
|
65
|
+
getBackgroundTask,
|
|
66
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
const USER_COMMANDS_DIR = path.join(os.homedir(), '.natureco', 'commands');
|
|
6
|
+
const PROJECT_COMMANDS_DIR = path.join(process.cwd(), '.natureco', 'commands');
|
|
7
|
+
|
|
8
|
+
function ensureCommandsDirs() {
|
|
9
|
+
if (!fs.existsSync(USER_COMMANDS_DIR)) {
|
|
10
|
+
fs.mkdirSync(USER_COMMANDS_DIR, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
if (!fs.existsSync(PROJECT_COMMANDS_DIR)) {
|
|
13
|
+
fs.mkdirSync(PROJECT_COMMANDS_DIR, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getCommands() {
|
|
18
|
+
ensureCommandsDirs();
|
|
19
|
+
|
|
20
|
+
const commands = [];
|
|
21
|
+
|
|
22
|
+
// User commands
|
|
23
|
+
if (fs.existsSync(USER_COMMANDS_DIR)) {
|
|
24
|
+
const userFiles = fs.readdirSync(USER_COMMANDS_DIR).filter(f => f.endsWith('.md'));
|
|
25
|
+
userFiles.forEach(file => {
|
|
26
|
+
const name = path.basename(file, '.md');
|
|
27
|
+
const content = fs.readFileSync(path.join(USER_COMMANDS_DIR, file), 'utf-8');
|
|
28
|
+
commands.push({ name, content, source: 'user' });
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Project commands
|
|
33
|
+
if (fs.existsSync(PROJECT_COMMANDS_DIR)) {
|
|
34
|
+
const projectFiles = fs.readdirSync(PROJECT_COMMANDS_DIR).filter(f => f.endsWith('.md'));
|
|
35
|
+
projectFiles.forEach(file => {
|
|
36
|
+
const name = path.basename(file, '.md');
|
|
37
|
+
const content = fs.readFileSync(path.join(PROJECT_COMMANDS_DIR, file), 'utf-8');
|
|
38
|
+
commands.push({ name, content, source: 'project' });
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return commands;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getCommandContent(commandName) {
|
|
46
|
+
const commands = getCommands();
|
|
47
|
+
const command = commands.find(c => c.name === commandName);
|
|
48
|
+
return command ? command.content : null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function createCommand(name, scope = 'project') {
|
|
52
|
+
ensureCommandsDirs();
|
|
53
|
+
|
|
54
|
+
const dir = scope === 'user' ? USER_COMMANDS_DIR : PROJECT_COMMANDS_DIR;
|
|
55
|
+
const filePath = path.join(dir, `${name}.md`);
|
|
56
|
+
|
|
57
|
+
if (fs.existsSync(filePath)) {
|
|
58
|
+
throw new Error(`Command "${name}" already exists`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const template = `# ${name} Command
|
|
62
|
+
|
|
63
|
+
Write your custom instruction here. This will be added to the system prompt when you use /${name} in chat.
|
|
64
|
+
|
|
65
|
+
Example:
|
|
66
|
+
Review this code for security vulnerabilities, performance issues, and suggest improvements.
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
fs.writeFileSync(filePath, template, 'utf-8');
|
|
70
|
+
return filePath;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = {
|
|
74
|
+
getCommands,
|
|
75
|
+
getCommandContent,
|
|
76
|
+
createCommand,
|
|
77
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const cron = require('node-cron');
|
|
5
|
+
|
|
6
|
+
const CRON_FILE = path.join(os.homedir(), '.natureco', 'cron.json');
|
|
7
|
+
const CRON_LOGS_DIR = path.join(os.homedir(), '.natureco', 'cron-logs');
|
|
8
|
+
|
|
9
|
+
function ensureCronDirs() {
|
|
10
|
+
const dir = path.dirname(CRON_FILE);
|
|
11
|
+
if (!fs.existsSync(dir)) {
|
|
12
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
if (!fs.existsSync(CRON_LOGS_DIR)) {
|
|
15
|
+
fs.mkdirSync(CRON_LOGS_DIR, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function loadCronJobs() {
|
|
20
|
+
ensureCronDirs();
|
|
21
|
+
if (!fs.existsSync(CRON_FILE)) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const data = fs.readFileSync(CRON_FILE, 'utf-8');
|
|
26
|
+
return JSON.parse(data);
|
|
27
|
+
} catch {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function saveCronJobs(jobs) {
|
|
33
|
+
ensureCronDirs();
|
|
34
|
+
fs.writeFileSync(CRON_FILE, JSON.stringify(jobs, null, 2), 'utf-8');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function addCronJob(job) {
|
|
38
|
+
const jobs = loadCronJobs();
|
|
39
|
+
const id = Date.now().toString(36) + Math.random().toString(36).slice(2);
|
|
40
|
+
const newJob = {
|
|
41
|
+
id,
|
|
42
|
+
...job,
|
|
43
|
+
enabled: true,
|
|
44
|
+
createdAt: new Date().toISOString(),
|
|
45
|
+
};
|
|
46
|
+
jobs.push(newJob);
|
|
47
|
+
saveCronJobs(jobs);
|
|
48
|
+
return newJob;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function removeCronJob(id) {
|
|
52
|
+
const jobs = loadCronJobs();
|
|
53
|
+
const filtered = jobs.filter(j => j.id !== id);
|
|
54
|
+
saveCronJobs(filtered);
|
|
55
|
+
return filtered.length < jobs.length;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function updateCronJob(id, updates) {
|
|
59
|
+
const jobs = loadCronJobs();
|
|
60
|
+
const index = jobs.findIndex(j => j.id === id);
|
|
61
|
+
if (index === -1) return false;
|
|
62
|
+
jobs[index] = { ...jobs[index], ...updates };
|
|
63
|
+
saveCronJobs(jobs);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getCronJob(id) {
|
|
68
|
+
const jobs = loadCronJobs();
|
|
69
|
+
return jobs.find(j => j.id === id);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function parseCronSchedule(schedule) {
|
|
73
|
+
// Basit zamanlama formatlarını cron expression'a çevir
|
|
74
|
+
if (schedule.startsWith('every ')) {
|
|
75
|
+
const parts = schedule.split(' ');
|
|
76
|
+
if (parts[2] === 'hours') {
|
|
77
|
+
const hours = parseInt(parts[1]);
|
|
78
|
+
return `0 */${hours} * * *`;
|
|
79
|
+
}
|
|
80
|
+
if (parts[2] === 'minutes') {
|
|
81
|
+
const minutes = parseInt(parts[1]);
|
|
82
|
+
return `*/${minutes} * * * *`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (schedule.startsWith('daily at ')) {
|
|
87
|
+
const time = schedule.replace('daily at ', '');
|
|
88
|
+
const [hour, minute = '0'] = time.split(':');
|
|
89
|
+
return `${minute} ${hour} * * *`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Zaten cron expression ise olduğu gibi döndür
|
|
93
|
+
return schedule;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function validateCronExpression(expression) {
|
|
97
|
+
return cron.validate(expression);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function logCronOutput(jobId, output) {
|
|
101
|
+
const logFile = path.join(CRON_LOGS_DIR, `${jobId}.log`);
|
|
102
|
+
const timestamp = new Date().toISOString();
|
|
103
|
+
const logEntry = `[${timestamp}]\n${output}\n\n`;
|
|
104
|
+
fs.appendFileSync(logFile, logEntry, 'utf-8');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function getCronLog(jobId) {
|
|
108
|
+
const logFile = path.join(CRON_LOGS_DIR, `${jobId}.log`);
|
|
109
|
+
if (!fs.existsSync(logFile)) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
return fs.readFileSync(logFile, 'utf-8');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = {
|
|
116
|
+
loadCronJobs,
|
|
117
|
+
saveCronJobs,
|
|
118
|
+
addCronJob,
|
|
119
|
+
removeCronJob,
|
|
120
|
+
updateCronJob,
|
|
121
|
+
getCronJob,
|
|
122
|
+
parseCronSchedule,
|
|
123
|
+
validateCronExpression,
|
|
124
|
+
logCronOutput,
|
|
125
|
+
getCronLog,
|
|
126
|
+
};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
const USER_HOOKS_DIR = path.join(os.homedir(), '.natureco', 'hooks');
|
|
6
|
+
const PROJECT_HOOKS_DIR = path.join(process.cwd(), '.natureco', 'hooks');
|
|
7
|
+
|
|
8
|
+
function ensureHooksDirs() {
|
|
9
|
+
if (!fs.existsSync(USER_HOOKS_DIR)) {
|
|
10
|
+
fs.mkdirSync(USER_HOOKS_DIR, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
if (!fs.existsSync(PROJECT_HOOKS_DIR)) {
|
|
13
|
+
fs.mkdirSync(PROJECT_HOOKS_DIR, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getHooks(type) {
|
|
18
|
+
ensureHooksDirs();
|
|
19
|
+
|
|
20
|
+
const hooks = [];
|
|
21
|
+
|
|
22
|
+
// User hooks
|
|
23
|
+
if (fs.existsSync(USER_HOOKS_DIR)) {
|
|
24
|
+
const userFiles = fs.readdirSync(USER_HOOKS_DIR).filter(f => f.endsWith('.js') && f.startsWith(type + '-'));
|
|
25
|
+
userFiles.forEach(file => {
|
|
26
|
+
const hookPath = path.join(USER_HOOKS_DIR, file);
|
|
27
|
+
hooks.push({ path: hookPath, source: 'user', name: file });
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Project hooks
|
|
32
|
+
if (fs.existsSync(PROJECT_HOOKS_DIR)) {
|
|
33
|
+
const projectFiles = fs.readdirSync(PROJECT_HOOKS_DIR).filter(f => f.endsWith('.js') && f.startsWith(type + '-'));
|
|
34
|
+
projectFiles.forEach(file => {
|
|
35
|
+
const hookPath = path.join(PROJECT_HOOKS_DIR, file);
|
|
36
|
+
hooks.push({ path: hookPath, source: 'project', name: file });
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return hooks;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getAllHooks() {
|
|
44
|
+
ensureHooksDirs();
|
|
45
|
+
|
|
46
|
+
const hooks = [];
|
|
47
|
+
|
|
48
|
+
// User hooks
|
|
49
|
+
if (fs.existsSync(USER_HOOKS_DIR)) {
|
|
50
|
+
const userFiles = fs.readdirSync(USER_HOOKS_DIR).filter(f => f.endsWith('.js'));
|
|
51
|
+
userFiles.forEach(file => {
|
|
52
|
+
const hookPath = path.join(USER_HOOKS_DIR, file);
|
|
53
|
+
const type = file.split('-')[0];
|
|
54
|
+
hooks.push({ path: hookPath, source: 'user', name: file, type });
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Project hooks
|
|
59
|
+
if (fs.existsSync(PROJECT_HOOKS_DIR)) {
|
|
60
|
+
const projectFiles = fs.readdirSync(PROJECT_HOOKS_DIR).filter(f => f.endsWith('.js'));
|
|
61
|
+
projectFiles.forEach(file => {
|
|
62
|
+
const hookPath = path.join(PROJECT_HOOKS_DIR, file);
|
|
63
|
+
const type = file.split('-')[0];
|
|
64
|
+
hooks.push({ path: hookPath, source: 'project', name: file, type });
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return hooks;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function runHooks(type, data, context = {}) {
|
|
72
|
+
const hooks = getHooks(type);
|
|
73
|
+
let result = data;
|
|
74
|
+
|
|
75
|
+
for (const hook of hooks) {
|
|
76
|
+
try {
|
|
77
|
+
// Clear require cache to reload hook
|
|
78
|
+
delete require.cache[require.resolve(hook.path)];
|
|
79
|
+
const hookFn = require(hook.path);
|
|
80
|
+
|
|
81
|
+
if (typeof hookFn === 'function') {
|
|
82
|
+
result = await hookFn(result, context);
|
|
83
|
+
}
|
|
84
|
+
} catch (err) {
|
|
85
|
+
console.error(`Hook error (${hook.name}):`, err.message);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function createHook(type, scope = 'project') {
|
|
93
|
+
ensureHooksDirs();
|
|
94
|
+
|
|
95
|
+
const dir = scope === 'user' ? USER_HOOKS_DIR : PROJECT_HOOKS_DIR;
|
|
96
|
+
const timestamp = Date.now().toString(36);
|
|
97
|
+
const fileName = `${type}-${timestamp}.js`;
|
|
98
|
+
const filePath = path.join(dir, fileName);
|
|
99
|
+
|
|
100
|
+
const templates = {
|
|
101
|
+
'pre-message': `module.exports = async function(message, context) {
|
|
102
|
+
// This hook runs before sending a message to the bot
|
|
103
|
+
// You can modify the message or log it
|
|
104
|
+
|
|
105
|
+
console.log('[Hook] Sending message:', message);
|
|
106
|
+
|
|
107
|
+
// Return the (possibly modified) message
|
|
108
|
+
return message;
|
|
109
|
+
};
|
|
110
|
+
`,
|
|
111
|
+
'post-message': `module.exports = async function(reply, context) {
|
|
112
|
+
// This hook runs after receiving a reply from the bot
|
|
113
|
+
// You can modify the reply or log it
|
|
114
|
+
|
|
115
|
+
console.log('[Hook] Received reply:', reply);
|
|
116
|
+
|
|
117
|
+
// Return the (possibly modified) reply
|
|
118
|
+
return reply;
|
|
119
|
+
};
|
|
120
|
+
`,
|
|
121
|
+
'on-start': `module.exports = async function(data, context) {
|
|
122
|
+
// This hook runs when chat starts
|
|
123
|
+
|
|
124
|
+
console.log('[Hook] Chat started with bot:', context.botName);
|
|
125
|
+
|
|
126
|
+
return data;
|
|
127
|
+
};
|
|
128
|
+
`,
|
|
129
|
+
'on-exit': `module.exports = async function(data, context) {
|
|
130
|
+
// This hook runs when chat exits
|
|
131
|
+
|
|
132
|
+
console.log('[Hook] Chat ended');
|
|
133
|
+
|
|
134
|
+
return data;
|
|
135
|
+
};
|
|
136
|
+
`,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const template = templates[type] || `module.exports = async function(data, context) {
|
|
140
|
+
// Custom hook
|
|
141
|
+
return data;
|
|
142
|
+
};
|
|
143
|
+
`;
|
|
144
|
+
|
|
145
|
+
fs.writeFileSync(filePath, template, 'utf-8');
|
|
146
|
+
return filePath;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = {
|
|
150
|
+
getHooks,
|
|
151
|
+
getAllHooks,
|
|
152
|
+
runHooks,
|
|
153
|
+
createHook,
|
|
154
|
+
};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { CONFIG_DIR } = require('./config');
|
|
4
|
+
|
|
5
|
+
const MEMORY_DIR = path.join(CONFIG_DIR, 'memory');
|
|
6
|
+
|
|
7
|
+
// Ensure memory directory exists
|
|
8
|
+
function ensureMemoryDir() {
|
|
9
|
+
if (!fs.existsSync(MEMORY_DIR)) {
|
|
10
|
+
fs.mkdirSync(MEMORY_DIR, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Get memory file path for a bot
|
|
15
|
+
function getMemoryFilePath(botId) {
|
|
16
|
+
ensureMemoryDir();
|
|
17
|
+
return path.join(MEMORY_DIR, `${botId}.json`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Load memory for a bot
|
|
21
|
+
function loadMemory(botId) {
|
|
22
|
+
const filePath = getMemoryFilePath(botId);
|
|
23
|
+
|
|
24
|
+
if (!fs.existsSync(filePath)) {
|
|
25
|
+
return {
|
|
26
|
+
name: null,
|
|
27
|
+
preferences: [],
|
|
28
|
+
facts: [],
|
|
29
|
+
lastSeen: null,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
35
|
+
return JSON.parse(content);
|
|
36
|
+
} catch {
|
|
37
|
+
return {
|
|
38
|
+
name: null,
|
|
39
|
+
preferences: [],
|
|
40
|
+
facts: [],
|
|
41
|
+
lastSeen: null,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Save memory for a bot
|
|
47
|
+
function saveMemory(botId, memory) {
|
|
48
|
+
const filePath = getMemoryFilePath(botId);
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
memory.lastSeen = new Date().toISOString();
|
|
52
|
+
fs.writeFileSync(filePath, JSON.stringify(memory, null, 2), 'utf8');
|
|
53
|
+
} catch (err) {
|
|
54
|
+
// Silently fail - memory is not critical
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Add memory entry
|
|
59
|
+
function addMemoryEntry(botId, key, value) {
|
|
60
|
+
const memory = loadMemory(botId);
|
|
61
|
+
|
|
62
|
+
if (key === 'name') {
|
|
63
|
+
memory.name = value;
|
|
64
|
+
} else if (key === 'preference') {
|
|
65
|
+
if (!memory.preferences.includes(value)) {
|
|
66
|
+
memory.preferences.push(value);
|
|
67
|
+
}
|
|
68
|
+
} else if (key === 'fact') {
|
|
69
|
+
if (!memory.facts.includes(value)) {
|
|
70
|
+
memory.facts.push(value);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
saveMemory(botId, memory);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Extract memory from message (simple keyword extraction)
|
|
78
|
+
function extractMemoryFromMessage(message) {
|
|
79
|
+
const extracted = [];
|
|
80
|
+
|
|
81
|
+
// Name patterns: "adım X", "ben X'im", "ismim X"
|
|
82
|
+
const namePatterns = [
|
|
83
|
+
/ad[ıi]m\s+([A-ZÇĞİÖŞÜ][a-zçğıöşü]+)/i,
|
|
84
|
+
/ben\s+([A-ZÇĞİÖŞÜ][a-zçğıöşü]+)['']?[ıi]?m/i,
|
|
85
|
+
/[iı]smim\s+([A-ZÇĞİÖŞÜ][a-zçğıöşü]+)/i,
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
for (const pattern of namePatterns) {
|
|
89
|
+
const match = message.match(pattern);
|
|
90
|
+
if (match) {
|
|
91
|
+
extracted.push({ key: 'name', value: match[1] });
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Location patterns: "X'te yaşıyorum", "X'de yaşıyorum"
|
|
97
|
+
const locationPatterns = [
|
|
98
|
+
/([A-ZÇĞİÖŞÜ][a-zçğıöşü]+)['']?[td]e\s+ya[şs][ıi]yorum/i,
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
for (const pattern of locationPatterns) {
|
|
102
|
+
const match = message.match(pattern);
|
|
103
|
+
if (match) {
|
|
104
|
+
extracted.push({ key: 'fact', value: `${match[1]}'de yaşıyor` });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Profession patterns: "ben X'im", "X olarak çalışıyorum"
|
|
109
|
+
const professionKeywords = ['yazılımcı', 'developer', 'öğrenci', 'öğretmen', 'mühendis', 'doktor'];
|
|
110
|
+
for (const keyword of professionKeywords) {
|
|
111
|
+
if (message.toLowerCase().includes(keyword)) {
|
|
112
|
+
extracted.push({ key: 'fact', value: keyword.charAt(0).toUpperCase() + keyword.slice(1) });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return extracted;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Get memory as system prompt
|
|
120
|
+
function getMemoryPrompt(botId) {
|
|
121
|
+
const memory = loadMemory(botId);
|
|
122
|
+
|
|
123
|
+
if (!memory.name && memory.preferences.length === 0 && memory.facts.length === 0) {
|
|
124
|
+
return '';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const parts = ['## Kullanıcı Hafızası'];
|
|
128
|
+
|
|
129
|
+
if (memory.name) {
|
|
130
|
+
parts.push(`İsim: ${memory.name}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (memory.preferences.length > 0) {
|
|
134
|
+
parts.push(`Tercihler: ${memory.preferences.join(', ')}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (memory.facts.length > 0) {
|
|
138
|
+
parts.push(`Bilgiler: ${memory.facts.join(', ')}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return parts.join('\n');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Clear memory for a bot
|
|
145
|
+
function clearMemory(botId) {
|
|
146
|
+
const filePath = getMemoryFilePath(botId);
|
|
147
|
+
|
|
148
|
+
if (fs.existsSync(filePath)) {
|
|
149
|
+
fs.unlinkSync(filePath);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = {
|
|
154
|
+
loadMemory,
|
|
155
|
+
saveMemory,
|
|
156
|
+
addMemoryEntry,
|
|
157
|
+
extractMemoryFromMessage,
|
|
158
|
+
getMemoryPrompt,
|
|
159
|
+
clearMemory,
|
|
160
|
+
};
|