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.
- package/README.md +94 -11
- package/bin/natureco.js +470 -10
- package/package.json +10 -6
- package/src/commands/admin-rpc.js +219 -0
- package/src/commands/agent.js +89 -0
- package/src/commands/approvals.js +53 -0
- package/src/commands/backup.js +124 -0
- package/src/commands/bonjour.js +167 -0
- package/src/commands/capability.js +64 -0
- package/src/commands/channels.js +94 -4
- package/src/commands/chat.js +11 -25
- package/src/commands/clickclack.js +130 -0
- package/src/commands/commitments.js +32 -0
- package/src/commands/completion.js +76 -0
- package/src/commands/config.js +111 -68
- package/src/commands/configure.js +93 -0
- package/src/commands/crestodian.js +92 -0
- package/src/commands/daemon.js +60 -0
- package/src/commands/device-pair.js +248 -0
- package/src/commands/devices.js +110 -0
- package/src/commands/directory.js +47 -0
- package/src/commands/dns.js +58 -0
- package/src/commands/docs.js +43 -0
- package/src/commands/doctor.js +121 -16
- package/src/commands/exec-policy.js +71 -0
- package/src/commands/gateway-server.js +1175 -30
- package/src/commands/gateway.js +11 -20
- package/src/commands/health.js +18 -0
- package/src/commands/help.js +6 -0
- package/src/commands/imessage.js +169 -0
- package/src/commands/infer.js +73 -0
- package/src/commands/irc.js +119 -0
- package/src/commands/mattermost.js +164 -0
- package/src/commands/memory-cmd.js +134 -1
- package/src/commands/message.js +30 -4
- package/src/commands/migrate.js +213 -2
- package/src/commands/models.js +584 -216
- package/src/commands/node.js +98 -0
- package/src/commands/nodes.js +106 -0
- package/src/commands/oc-path.js +200 -0
- package/src/commands/onboard.js +70 -0
- package/src/commands/open-prose.js +67 -0
- package/src/commands/plugins.js +415 -172
- package/src/commands/policy.js +176 -0
- package/src/commands/proxy.js +155 -0
- package/src/commands/qr.js +28 -0
- package/src/commands/sandbox.js +125 -0
- package/src/commands/secrets.js +118 -0
- package/src/commands/security.js +149 -1
- package/src/commands/setup.js +114 -10
- package/src/commands/signal.js +495 -0
- package/src/commands/skills.js +20 -29
- package/src/commands/sms.js +168 -0
- package/src/commands/system.js +53 -0
- package/src/commands/tasks.js +328 -79
- package/src/commands/terminal.js +21 -0
- package/src/commands/thread-ownership.js +157 -0
- package/src/commands/transcripts.js +72 -0
- package/src/commands/voice.js +82 -0
- package/src/commands/vydra.js +98 -0
- package/src/commands/webhooks.js +79 -0
- package/src/commands/whatsapp.js +7 -21
- package/src/commands/workboard.js +207 -0
- package/src/tools/audio_understanding.js +154 -0
- package/src/tools/bash.js +63 -29
- package/src/tools/browser.js +112 -0
- package/src/tools/canvas.js +104 -0
- package/src/tools/document_extract.js +84 -0
- package/src/tools/duckduckgo.js +54 -0
- package/src/tools/exa_search.js +66 -0
- package/src/tools/firecrawl.js +104 -0
- package/src/tools/image_generation.js +99 -0
- package/src/tools/llm_task.js +118 -0
- package/src/tools/media_understanding.js +128 -0
- package/src/tools/music_generation.js +113 -0
- package/src/tools/parallel_search.js +77 -0
- package/src/tools/phone_control.js +80 -0
- package/src/tools/phone_control_enhanced.js +184 -0
- package/src/tools/searxng.js +61 -0
- package/src/tools/speech_to_text.js +135 -0
- package/src/tools/text_to_speech.js +105 -0
- package/src/tools/thread_ownership.js +88 -0
- package/src/tools/video_generation.js +72 -0
- package/src/tools/web_readability.js +104 -0
- package/src/utils/api.js +3 -20
- package/src/utils/approvals.js +297 -0
- package/src/utils/background.js +223 -66
- package/src/utils/baileys.js +21 -0
- package/src/utils/config.js +141 -10
- package/src/utils/errors.js +148 -0
- package/src/utils/inquirer-wrapper.js +1 -2
- package/src/utils/memory.js +200 -0
- package/src/utils/path-utils.js +13 -13
- package/src/utils/plugin-registry.js +238 -0
- package/src/utils/secrets.js +177 -0
- package/src/utils/skills.js +10 -23
package/src/utils/config.js
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const os = require('os');
|
|
4
|
+
const crypto = require('crypto');
|
|
5
|
+
const { ConfigParseError, ConfigMutationConflictError, ConfigValidationError, handleError } = require('./errors');
|
|
6
|
+
|
|
7
|
+
let json5;
|
|
8
|
+
try {
|
|
9
|
+
json5 = require('json5');
|
|
10
|
+
} catch {
|
|
11
|
+
json5 = null;
|
|
12
|
+
}
|
|
4
13
|
|
|
5
14
|
const CONFIG_DIR = path.join(os.homedir(), '.natureco');
|
|
6
15
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
16
|
+
const CONFIG_BACKUP_DIR = path.join(CONFIG_DIR, 'backups');
|
|
17
|
+
const MAX_BACKUPS = 10;
|
|
18
|
+
|
|
19
|
+
let _configCache = null;
|
|
20
|
+
let _configHash = null;
|
|
7
21
|
|
|
8
|
-
// --profile flag desteği: ~/.natureco-<profile>/
|
|
9
22
|
function getProfileDir() {
|
|
10
23
|
const profileArg = process.argv.find(a => a.startsWith('--profile='));
|
|
11
24
|
const profileIdx = process.argv.indexOf('--profile');
|
|
@@ -27,50 +40,134 @@ function ensureConfigDir() {
|
|
|
27
40
|
}
|
|
28
41
|
}
|
|
29
42
|
|
|
30
|
-
function
|
|
43
|
+
function computeHash(data) {
|
|
44
|
+
return crypto.createHash('sha256').update(JSON.stringify(data)).digest('hex');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function createBackup() {
|
|
48
|
+
if (!fs.existsSync(ACTIVE_CONFIG_FILE)) return;
|
|
31
49
|
ensureConfigDir();
|
|
32
|
-
fs.
|
|
50
|
+
if (!fs.existsSync(CONFIG_BACKUP_DIR)) {
|
|
51
|
+
fs.mkdirSync(CONFIG_BACKUP_DIR, { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
54
|
+
const backupFile = path.join(CONFIG_BACKUP_DIR, `config-${timestamp}.json`);
|
|
55
|
+
fs.copyFileSync(ACTIVE_CONFIG_FILE, backupFile);
|
|
56
|
+
const backups = fs.readdirSync(CONFIG_BACKUP_DIR)
|
|
57
|
+
.filter(f => f.startsWith('config-') && f.endsWith('.json'))
|
|
58
|
+
.sort()
|
|
59
|
+
.reverse();
|
|
60
|
+
if (backups.length > MAX_BACKUPS) {
|
|
61
|
+
backups.slice(MAX_BACKUPS).forEach(f => {
|
|
62
|
+
try { fs.unlinkSync(path.join(CONFIG_BACKUP_DIR, f)); } catch {}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parseConfigContent(content) {
|
|
68
|
+
if (!content || !content.trim()) return {};
|
|
69
|
+
const trimmed = content.trim();
|
|
70
|
+
if (trimmed.startsWith('{')) {
|
|
71
|
+
if (json5) {
|
|
72
|
+
try { return json5.parse(trimmed); } catch {}
|
|
73
|
+
}
|
|
74
|
+
return JSON.parse(trimmed);
|
|
75
|
+
}
|
|
76
|
+
return JSON.parse(trimmed);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function validateConfig(data) {
|
|
80
|
+
if (data === null || data === undefined) throw new ConfigValidationError('Config cannot be null', { field: 'root' });
|
|
81
|
+
if (typeof data !== 'object' || Array.isArray(data)) throw new ConfigValidationError('Config must be a JSON object', { field: 'root' });
|
|
82
|
+
if (data.apiKey !== undefined && typeof data.apiKey !== 'string') throw new ConfigValidationError('apiKey must be a string', { field: 'apiKey' });
|
|
83
|
+
if (data.providerUrl !== undefined && typeof data.providerUrl !== 'string') throw new ConfigValidationError('providerUrl must be a string', { field: 'providerUrl' });
|
|
84
|
+
if (data.providerModel !== undefined && typeof data.providerModel !== 'string') throw new ConfigValidationError('providerModel must be a string', { field: 'providerModel' });
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function saveConfig(data, options = {}) {
|
|
89
|
+
const { skipBackup = false, skipValidation = false } = options;
|
|
90
|
+
ensureConfigDir();
|
|
91
|
+
if (!skipValidation) validateConfig(data);
|
|
92
|
+
if (!skipBackup) createBackup();
|
|
93
|
+
const content = JSON.stringify(data, null, 2);
|
|
94
|
+
fs.writeFileSync(ACTIVE_CONFIG_FILE, content, 'utf8');
|
|
95
|
+
_configCache = data;
|
|
96
|
+
_configHash = computeHash(data);
|
|
33
97
|
}
|
|
34
98
|
|
|
35
|
-
function loadConfig() {
|
|
99
|
+
function loadConfig(options = {}) {
|
|
100
|
+
const { useCache = true, skipValidation = false } = options;
|
|
101
|
+
if (useCache && _configCache) return _configCache;
|
|
36
102
|
if (!fs.existsSync(ACTIVE_CONFIG_FILE)) {
|
|
103
|
+
_configCache = null;
|
|
104
|
+
_configHash = null;
|
|
37
105
|
return null;
|
|
38
106
|
}
|
|
39
107
|
try {
|
|
40
108
|
const content = fs.readFileSync(ACTIVE_CONFIG_FILE, 'utf8');
|
|
41
|
-
|
|
109
|
+
const data = parseConfigContent(content);
|
|
110
|
+
if (!skipValidation) validateConfig(data);
|
|
111
|
+
_configCache = data;
|
|
112
|
+
_configHash = computeHash(data);
|
|
113
|
+
return data;
|
|
42
114
|
} catch (err) {
|
|
115
|
+
_configCache = null;
|
|
116
|
+
_configHash = null;
|
|
117
|
+
if (err instanceof ConfigValidationError || err instanceof ConfigParseError) throw err;
|
|
43
118
|
return null;
|
|
44
119
|
}
|
|
45
120
|
}
|
|
46
121
|
|
|
122
|
+
function loadConfigWithRetry(maxRetries = 3) {
|
|
123
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
124
|
+
try {
|
|
125
|
+
return loadConfig({ useCache: false });
|
|
126
|
+
} catch (err) {
|
|
127
|
+
if (i === maxRetries - 1) throw err;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
47
133
|
function deleteConfig() {
|
|
48
134
|
if (fs.existsSync(ACTIVE_CONFIG_FILE)) {
|
|
135
|
+
createBackup();
|
|
49
136
|
fs.unlinkSync(ACTIVE_CONFIG_FILE);
|
|
50
137
|
}
|
|
138
|
+
_configCache = null;
|
|
139
|
+
_configHash = null;
|
|
51
140
|
}
|
|
52
141
|
|
|
53
142
|
function getApiKey() {
|
|
54
143
|
const config = loadConfig();
|
|
55
|
-
return config?.apiKey
|
|
144
|
+
return config?.apiKey ?? null;
|
|
56
145
|
}
|
|
57
146
|
|
|
58
147
|
function saveApiKey(apiKey) {
|
|
59
|
-
const config = loadConfig()
|
|
148
|
+
const config = loadConfig() ?? {};
|
|
60
149
|
config.apiKey = apiKey;
|
|
61
150
|
saveConfig(config);
|
|
62
151
|
}
|
|
63
152
|
|
|
64
153
|
function getConfig() {
|
|
65
|
-
|
|
154
|
+
try {
|
|
155
|
+
return loadConfig() ?? {};
|
|
156
|
+
} catch {
|
|
157
|
+
return {};
|
|
158
|
+
}
|
|
66
159
|
}
|
|
67
160
|
|
|
68
161
|
function getAllConfig() {
|
|
69
|
-
|
|
162
|
+
try {
|
|
163
|
+
return loadConfig() ?? {};
|
|
164
|
+
} catch {
|
|
165
|
+
return {};
|
|
166
|
+
}
|
|
70
167
|
}
|
|
71
168
|
|
|
72
169
|
function setConfigValue(key, value) {
|
|
73
|
-
const config = loadConfig()
|
|
170
|
+
const config = loadConfig() ?? {};
|
|
74
171
|
const keys = key.split('.');
|
|
75
172
|
let current = config;
|
|
76
173
|
for (let i = 0; i < keys.length - 1; i++) {
|
|
@@ -83,15 +180,49 @@ function setConfigValue(key, value) {
|
|
|
83
180
|
saveConfig(config);
|
|
84
181
|
}
|
|
85
182
|
|
|
183
|
+
function getConfigHash() {
|
|
184
|
+
return _configHash;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function listBackups() {
|
|
188
|
+
if (!fs.existsSync(CONFIG_BACKUP_DIR)) return [];
|
|
189
|
+
return fs.readdirSync(CONFIG_BACKUP_DIR)
|
|
190
|
+
.filter(f => f.startsWith('config-') && f.endsWith('.json'))
|
|
191
|
+
.sort()
|
|
192
|
+
.reverse();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function restoreConfig(backupFile) {
|
|
196
|
+
const backupPath = path.isAbsolute(backupFile)
|
|
197
|
+
? backupFile
|
|
198
|
+
: path.join(CONFIG_BACKUP_DIR, backupFile);
|
|
199
|
+
if (!fs.existsSync(backupPath)) {
|
|
200
|
+
throw new ConfigValidationError(`Yedek dosyası bulunamadı: ${backupPath}`, { field: 'backupFile' });
|
|
201
|
+
}
|
|
202
|
+
const content = fs.readFileSync(backupPath, 'utf8');
|
|
203
|
+
const data = parseConfigContent(content);
|
|
204
|
+
validateConfig(data);
|
|
205
|
+
createBackup();
|
|
206
|
+
fs.writeFileSync(ACTIVE_CONFIG_FILE, JSON.stringify(data, null, 2), 'utf8');
|
|
207
|
+
_configCache = data;
|
|
208
|
+
_configHash = computeHash(data);
|
|
209
|
+
return { path: backupPath, timestamp: path.basename(backupPath).replace(/^config-|\.json$/g, '') };
|
|
210
|
+
}
|
|
211
|
+
|
|
86
212
|
module.exports = {
|
|
87
213
|
saveConfig,
|
|
88
214
|
loadConfig,
|
|
215
|
+
loadConfigWithRetry,
|
|
89
216
|
deleteConfig,
|
|
90
217
|
getApiKey,
|
|
91
218
|
saveApiKey,
|
|
92
219
|
getConfig,
|
|
93
220
|
getAllConfig,
|
|
94
221
|
setConfigValue,
|
|
222
|
+
getConfigHash,
|
|
223
|
+
listBackups,
|
|
224
|
+
restoreConfig,
|
|
95
225
|
CONFIG_FILE: ACTIVE_CONFIG_FILE,
|
|
96
226
|
CONFIG_DIR: ACTIVE_CONFIG_DIR,
|
|
227
|
+
CONFIG_BACKUP_DIR,
|
|
97
228
|
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
class NatureCoError extends Error {
|
|
4
|
+
constructor(message, options = {}) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = this.constructor.name;
|
|
7
|
+
this.cause = options.cause || null;
|
|
8
|
+
this.exitCode = options.exitCode || 1;
|
|
9
|
+
if (Error.captureStackTrace) {
|
|
10
|
+
Error.captureStackTrace(this, this.constructor);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class ConfigError extends NatureCoError {
|
|
16
|
+
constructor(message, options = {}) {
|
|
17
|
+
super(message, options);
|
|
18
|
+
this.configPath = options.configPath || null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
class ConfigParseError extends ConfigError {
|
|
23
|
+
constructor(message, options = {}) {
|
|
24
|
+
super(`Config parse error: ${message}`, options);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
class ConfigMutationConflictError extends ConfigError {
|
|
29
|
+
constructor(message, options = {}) {
|
|
30
|
+
super(`Config conflict: ${message}`, options);
|
|
31
|
+
this.currentHash = options.currentHash || null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
class ConfigValidationError extends ConfigError {
|
|
36
|
+
constructor(message, options = {}) {
|
|
37
|
+
super(`Config validation error: ${message}`, options);
|
|
38
|
+
this.field = options.field || null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class ApiError extends NatureCoError {
|
|
43
|
+
constructor(message, options = {}) {
|
|
44
|
+
super(message, options);
|
|
45
|
+
this.statusCode = options.statusCode || null;
|
|
46
|
+
this.provider = options.provider || null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
class ProviderError extends ApiError {
|
|
51
|
+
constructor(message, options = {}) {
|
|
52
|
+
super(`Provider error (${options.provider || 'unknown'}): ${message}`, options);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
class AuthenticationError extends ApiError {
|
|
57
|
+
constructor(message, options = {}) {
|
|
58
|
+
super(`Authentication error: ${message}`, options);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
class ToolError extends NatureCoError {
|
|
63
|
+
constructor(message, options = {}) {
|
|
64
|
+
super(message, options);
|
|
65
|
+
this.toolName = options.toolName || null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
class ToolInputError extends ToolError {
|
|
70
|
+
constructor(message, options = {}) {
|
|
71
|
+
super(`Invalid tool input: ${message}`, options);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
class ToolExecutionError extends ToolError {
|
|
76
|
+
constructor(message, options = {}) {
|
|
77
|
+
super(`Tool execution failed: ${message}`, options);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
class ChannelError extends NatureCoError {
|
|
82
|
+
constructor(message, options = {}) {
|
|
83
|
+
super(message, options);
|
|
84
|
+
this.channel = options.channel || null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
class GatewayError extends NatureCoError {
|
|
89
|
+
constructor(message, options = {}) {
|
|
90
|
+
super(message, options);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
class PluginError extends NatureCoError {
|
|
95
|
+
constructor(message, options = {}) {
|
|
96
|
+
super(message, options);
|
|
97
|
+
this.plugin = options.plugin || null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
class SkillError extends NatureCoError {
|
|
102
|
+
constructor(message, options = {}) {
|
|
103
|
+
super(message, options);
|
|
104
|
+
this.skill = options.skill || null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
class MigrationError extends NatureCoError {
|
|
109
|
+
constructor(message, options = {}) {
|
|
110
|
+
super(message, options);
|
|
111
|
+
this.step = options.step || null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function handleError(err, options = {}) {
|
|
116
|
+
const { prefix = '', exit = true, log = true } = options;
|
|
117
|
+
|
|
118
|
+
if (log) {
|
|
119
|
+
const message = err instanceof NatureCoError
|
|
120
|
+
? `${prefix}${err.message}`
|
|
121
|
+
: `${prefix}${err.message || 'An unknown error occurred'}`;
|
|
122
|
+
console.log(chalk.red(`\n${message}\n`));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (exit) {
|
|
126
|
+
process.exit(err instanceof NatureCoError ? err.exitCode : 1);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = {
|
|
131
|
+
NatureCoError,
|
|
132
|
+
ConfigError,
|
|
133
|
+
ConfigParseError,
|
|
134
|
+
ConfigMutationConflictError,
|
|
135
|
+
ConfigValidationError,
|
|
136
|
+
ApiError,
|
|
137
|
+
ProviderError,
|
|
138
|
+
AuthenticationError,
|
|
139
|
+
ToolError,
|
|
140
|
+
ToolInputError,
|
|
141
|
+
ToolExecutionError,
|
|
142
|
+
ChannelError,
|
|
143
|
+
GatewayError,
|
|
144
|
+
PluginError,
|
|
145
|
+
SkillError,
|
|
146
|
+
MigrationError,
|
|
147
|
+
handleError,
|
|
148
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { select, input, password, confirm } = require('@inquirer/prompts');
|
|
1
|
+
const { select, input, password, confirm, checkbox } = require('@inquirer/prompts');
|
|
2
2
|
|
|
3
3
|
module.exports = {
|
|
4
4
|
async prompt(questions) {
|
|
@@ -14,7 +14,6 @@ module.exports = {
|
|
|
14
14
|
} else if (q.type === 'password') {
|
|
15
15
|
results[q.name] = await password({ message: q.message, mask: q.mask });
|
|
16
16
|
} else if (q.type === 'checkbox') {
|
|
17
|
-
const { checkbox } = require('@inquirer/prompts');
|
|
18
17
|
results[q.name] = await checkbox({
|
|
19
18
|
message: q.message,
|
|
20
19
|
choices: q.choices.map(c =>
|
package/src/utils/memory.js
CHANGED
|
@@ -282,11 +282,211 @@ function clearMemory(botId) {
|
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
+
// ── Memory Wiki (structured pages) ──────────────────────────────────────────────
|
|
286
|
+
|
|
287
|
+
const MEMORY_WIKI_DIR = path.join(CONFIG_DIR, 'memory-wiki');
|
|
288
|
+
|
|
289
|
+
function ensureWikiDir() {
|
|
290
|
+
if (!fs.existsSync(MEMORY_WIKI_DIR)) {
|
|
291
|
+
fs.mkdirSync(MEMORY_WIKI_DIR, { recursive: true });
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function getWikiPage(slug) {
|
|
296
|
+
ensureWikiDir();
|
|
297
|
+
const file = path.join(MEMORY_WIKI_DIR, `${slug.replace(/[^a-z0-9_-]/gi, '_')}.json`);
|
|
298
|
+
if (!fs.existsSync(file)) return null;
|
|
299
|
+
try { return JSON.parse(fs.readFileSync(file, 'utf-8')); } catch { return null; }
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function saveWikiPage(slug, content) {
|
|
303
|
+
ensureWikiDir();
|
|
304
|
+
const file = path.join(MEMORY_WIKI_DIR, `${slug.replace(/[^a-z0-9_-]/gi, '_')}.json`);
|
|
305
|
+
fs.writeFileSync(file, JSON.stringify({ slug, content, updatedAt: new Date().toISOString() }, null, 2));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function listWikiPages() {
|
|
309
|
+
ensureWikiDir();
|
|
310
|
+
return fs.readdirSync(MEMORY_WIKI_DIR)
|
|
311
|
+
.filter(f => f.endsWith('.json'))
|
|
312
|
+
.map(f => {
|
|
313
|
+
try {
|
|
314
|
+
const data = JSON.parse(fs.readFileSync(path.join(MEMORY_WIKI_DIR, f), 'utf-8'));
|
|
315
|
+
return { slug: data.slug || f.replace('.json', ''), content: data.content || '', updatedAt: data.updatedAt };
|
|
316
|
+
} catch { return null; }
|
|
317
|
+
})
|
|
318
|
+
.filter(Boolean);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function searchWikiPages(query) {
|
|
322
|
+
const pages = listWikiPages();
|
|
323
|
+
const q = query.toLowerCase();
|
|
324
|
+
return pages.filter(p =>
|
|
325
|
+
p.slug.toLowerCase().includes(q) ||
|
|
326
|
+
p.content.toLowerCase().includes(q)
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ── Semantic Memory Search ─────────────────────────────────────────────────────
|
|
331
|
+
|
|
332
|
+
function semanticSearchMemory(query, limit = 5) {
|
|
333
|
+
const results = [];
|
|
334
|
+
const q = query.toLowerCase();
|
|
335
|
+
|
|
336
|
+
if (!fs.existsSync(MEMORY_DIR)) return results;
|
|
337
|
+
|
|
338
|
+
const files = fs.readdirSync(MEMORY_DIR).filter(f => f.endsWith('.json'));
|
|
339
|
+
for (const file of files) {
|
|
340
|
+
const botId = file.replace('.json', '');
|
|
341
|
+
const mem = loadMemory(botId);
|
|
342
|
+
|
|
343
|
+
// Score each fact by relevance
|
|
344
|
+
const scored = (mem.facts || [])
|
|
345
|
+
.map(f => {
|
|
346
|
+
const val = typeof f === 'string' ? f : f.value || '';
|
|
347
|
+
const words = q.split(/\s+/).filter(Boolean);
|
|
348
|
+
const matches = words.filter(w => val.toLowerCase().includes(w)).length;
|
|
349
|
+
const score = words.length > 0 ? matches / words.length : 0;
|
|
350
|
+
const boost = typeof f === 'object' ? (f.score || 5) / 10 : 0.5;
|
|
351
|
+
return { bot: mem.botName || botId, value: val, score: score * 0.7 + boost * 0.3 };
|
|
352
|
+
})
|
|
353
|
+
.filter(f => f.score > 0.1)
|
|
354
|
+
.sort((a, b) => b.score - a.score)
|
|
355
|
+
.slice(0, limit);
|
|
356
|
+
|
|
357
|
+
results.push(...scored);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return results.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ── Memory Categories ──────────────────────────────────────────────────────────
|
|
364
|
+
|
|
365
|
+
const MEMORY_CATEGORIES = {
|
|
366
|
+
personal: { label: 'Kişisel Bilgiler', icon: '👤' },
|
|
367
|
+
work: { label: 'İş/Okul', icon: '💼' },
|
|
368
|
+
preferences: { label: 'Tercihler', icon: '⭐' },
|
|
369
|
+
health: { label: 'Sağlık', icon: '❤️' },
|
|
370
|
+
social: { label: 'Sosyal', icon: '👥' },
|
|
371
|
+
goals: { label: 'Hedefler', icon: '🎯' },
|
|
372
|
+
projects: { label: 'Projeler', icon: '📁' },
|
|
373
|
+
general: { label: 'Genel', icon: '📝' }
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
function addMemoryEntryWithCategory(botId, key, value, category = 'general') {
|
|
377
|
+
const memory = loadMemory(botId);
|
|
378
|
+
const now = new Date().toISOString();
|
|
379
|
+
const validCategory = MEMORY_CATEGORIES[category] ? category : 'general';
|
|
380
|
+
|
|
381
|
+
if (key === 'name') { memory.name = value; }
|
|
382
|
+
else if (key === 'nickname') { memory.nickname = value; }
|
|
383
|
+
else if (key === 'botName') { memory.botName = value; }
|
|
384
|
+
else if (key === 'preference') {
|
|
385
|
+
const existing = memory.preferences.find(p => p.value.toLowerCase() === value.toLowerCase());
|
|
386
|
+
if (existing) {
|
|
387
|
+
existing.score = Math.min(existing.score + 1, 10);
|
|
388
|
+
existing.updatedAt = now;
|
|
389
|
+
} else {
|
|
390
|
+
memory.preferences.push({ value, score: 5, updatedAt: now, category: validCategory });
|
|
391
|
+
}
|
|
392
|
+
} else {
|
|
393
|
+
const existing = memory.facts.find(f => f.value.toLowerCase() === value.toLowerCase());
|
|
394
|
+
if (existing) {
|
|
395
|
+
existing.score = Math.min(existing.score + 1, 10);
|
|
396
|
+
existing.updatedAt = now;
|
|
397
|
+
if (!existing.category) existing.category = validCategory;
|
|
398
|
+
} else {
|
|
399
|
+
memory.facts.push({ value, score: 5, updatedAt: now, category: validCategory });
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Decay old entries
|
|
404
|
+
const sixMonthsAgo = new Date();
|
|
405
|
+
sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6);
|
|
406
|
+
|
|
407
|
+
memory.preferences = memory.preferences.map(p => {
|
|
408
|
+
const age = new Date(p.updatedAt);
|
|
409
|
+
if (age < sixMonthsAgo) p.score = Math.max(p.score - 1, 1);
|
|
410
|
+
return p;
|
|
411
|
+
}).filter(p => p.score > 0);
|
|
412
|
+
|
|
413
|
+
memory.facts = memory.facts.map(f => {
|
|
414
|
+
const age = new Date(f.updatedAt);
|
|
415
|
+
if (age < sixMonthsAgo) f.score = Math.max(f.score - 1, 1);
|
|
416
|
+
return f;
|
|
417
|
+
}).filter(f => f.score > 0);
|
|
418
|
+
|
|
419
|
+
saveMemory(botId, memory);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// ── Memory Import/Export ───────────────────────────────────────────────────────
|
|
423
|
+
|
|
424
|
+
function exportMemory(botId) {
|
|
425
|
+
return loadMemory(botId);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function importMemory(botId, data) {
|
|
429
|
+
const current = loadMemory(botId);
|
|
430
|
+
const merged = {
|
|
431
|
+
name: data.name || current.name,
|
|
432
|
+
nickname: data.nickname || current.nickname,
|
|
433
|
+
botName: data.botName || current.botName,
|
|
434
|
+
preferences: [...(current.preferences || []), ...(data.preferences || [])],
|
|
435
|
+
facts: [...(current.facts || []), ...(data.facts || [])],
|
|
436
|
+
lastSeen: current.lastSeen
|
|
437
|
+
};
|
|
438
|
+
saveMemory(botId, merged);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// ── Active Memory (pre-prompt injection) ──────────────────────────────────────
|
|
442
|
+
|
|
443
|
+
function getActiveMemoryPrompt(botId, messageContext = '') {
|
|
444
|
+
const memory = loadMemory(botId);
|
|
445
|
+
const facts = memory.facts || [];
|
|
446
|
+
const prefs = memory.preferences || [];
|
|
447
|
+
|
|
448
|
+
if (facts.length === 0 && prefs.length === 0 && !memory.name) return '';
|
|
449
|
+
|
|
450
|
+
// Score facts by relevance to current context
|
|
451
|
+
const contextWords = messageContext.toLowerCase().split(/\s+/).filter(w => w.length > 3);
|
|
452
|
+
const scored = facts
|
|
453
|
+
.map(f => {
|
|
454
|
+
let relevance = 0;
|
|
455
|
+
if (contextWords.length > 0) {
|
|
456
|
+
const val = (f.value || '').toLowerCase();
|
|
457
|
+
relevance = contextWords.filter(w => val.includes(w)).length / contextWords.length;
|
|
458
|
+
}
|
|
459
|
+
return { ...f, relevance };
|
|
460
|
+
})
|
|
461
|
+
.sort((a, b) => (b.score || 0) + b.relevance - (a.score || 0) - a.relevance)
|
|
462
|
+
.slice(0, 5);
|
|
463
|
+
|
|
464
|
+
const parts = [];
|
|
465
|
+
if (memory.name) parts.push(`👤 ${memory.name}`);
|
|
466
|
+
if (memory.nickname) parts.push(`🏷 ${memory.nickname}`);
|
|
467
|
+
if (scored.length > 0) {
|
|
468
|
+
parts.push('📌 ' + scored.map(f => f.value).join(' | '));
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return parts.length > 0 ? `<active_memory>\n${parts.join('\n')}\n</active_memory>` : '';
|
|
472
|
+
}
|
|
473
|
+
|
|
285
474
|
module.exports = {
|
|
286
475
|
loadMemory,
|
|
287
476
|
saveMemory,
|
|
288
477
|
addMemoryEntry,
|
|
478
|
+
addMemoryEntryWithCategory,
|
|
289
479
|
extractMemoryFromMessage,
|
|
290
480
|
getMemoryPrompt,
|
|
481
|
+
getActiveMemoryPrompt,
|
|
291
482
|
clearMemory,
|
|
483
|
+
semanticSearchMemory,
|
|
484
|
+
exportMemory,
|
|
485
|
+
importMemory,
|
|
486
|
+
// Wiki
|
|
487
|
+
getWikiPage,
|
|
488
|
+
saveWikiPage,
|
|
489
|
+
listWikiPages,
|
|
490
|
+
searchWikiPages,
|
|
491
|
+
MEMORY_CATEGORIES,
|
|
292
492
|
};
|
package/src/utils/path-utils.js
CHANGED
|
@@ -3,19 +3,19 @@ const os = require('os');
|
|
|
3
3
|
function normalizeWindowsPaths(str) {
|
|
4
4
|
let result = str.replace(/\\/g, '/');
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
6
|
+
const homeDir = os.homedir().replace(/\\/g, '/');
|
|
7
|
+
|
|
8
|
+
// Replace any Windows user profile path with actual homedir
|
|
9
|
+
result = result.replace(/[A-Za-z]:\/Users\/[^/]+\//g, `${homeDir}/`);
|
|
10
|
+
|
|
11
|
+
// Replace bare drive-letter references pointing to .openclaw
|
|
12
|
+
result = result.replace(/[A-Za-z]:\/\.openclaw\//g, `${homeDir}/.natureco/`);
|
|
13
|
+
|
|
14
|
+
// Migrate .openclaw paths to .natureco
|
|
15
|
+
result = result.replace(/\.openclaw\//g, '.natureco/');
|
|
16
|
+
|
|
17
|
+
// Normalize mixed path separators
|
|
18
|
+
result = result.replace(/workspace\/scripts\\/g, 'workspace/scripts/');
|
|
19
19
|
|
|
20
20
|
return result;
|
|
21
21
|
}
|