vantuz 3.3.6 → 3.4.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/README.md +25 -31
- package/cli.js +0 -0
- package/config.js +24 -0
- package/core/agent-loop.js +190 -0
- package/core/ai-analyst.js +2 -2
- package/core/ai-provider.js +41 -7
- package/core/automation.js +43 -24
- package/core/dashboard.js +230 -0
- package/core/database.js +63 -72
- package/core/engine.js +31 -4
- package/core/learning.js +214 -0
- package/core/license-manager.js +9 -9
- package/core/marketplace-adapter.js +168 -0
- package/core/memory.js +190 -0
- package/core/queue.js +120 -0
- package/core/scheduler.js +111 -31
- package/core/self-healer.js +314 -0
- package/core/unified-product.js +214 -0
- package/core/vector-db.js +83 -17
- package/core/vision-service.js +113 -0
- package/index.js +175 -0
- package/modules/crm/sentiment-crm.js +231 -0
- package/modules/healer/listing-healer.js +201 -0
- package/modules/oracle/predictor.js +214 -0
- package/modules/researcher/agent.js +169 -0
- package/modules/team/agents/base.js +92 -0
- package/modules/team/agents/dev.js +33 -0
- package/modules/team/agents/josh.js +40 -0
- package/modules/team/agents/marketing.js +33 -0
- package/modules/team/agents/milo.js +36 -0
- package/modules/team/index.js +78 -0
- package/modules/team/shared-memory.js +87 -0
- package/modules/war-room/competitor-tracker.js +250 -0
- package/modules/war-room/pricing-engine.js +308 -0
- package/nodes/warehouse.js +238 -0
- package/onboard.js +0 -0
- package/package.json +47 -87
- package/platforms/amazon.js +3 -8
- package/platforms/ciceksepeti.js +2 -5
- package/platforms/hepsiburada.js +4 -6
- package/platforms/n11.js +3 -5
- package/platforms/pazarama.js +2 -5
- package/platforms/trendyol.js +3 -3
- package/plugins/vantuz/index.js +48 -0
- package/server/app.js +142 -29
- package/vantuz-3.3.4.tgz +0 -0
package/README.md
CHANGED
|
@@ -1,44 +1,38 @@
|
|
|
1
|
-
# 🐙
|
|
1
|
+
# 🐙 VANTUZ (Enterprise Edition)
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**OpenClaw Altyapısıyla Güçlendirilmiş E-Ticaret Zekası**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Vantuz, sıradan bir entegrasyon aracı değildir. OpenClaw'ın yapay zeka ve otomasyon gücünü kullanarak e-ticaret operasyonlarınızı otopilota alan, ticari bir zekadır.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## 🚀 Neden Vantuz?
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
## Hızlı Başlangıç
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
# Yapılandırma
|
|
17
|
-
npx vantuz config
|
|
18
|
-
|
|
19
|
-
# Başlat
|
|
20
|
-
npx vantuz tui
|
|
21
|
-
```
|
|
9
|
+
1. **AI Vision (Göz):** Ürün fotoğraflarını tanır, Trendyol/Amazon için başlık, açıklama ve fiyatı otomatik oluşturur.
|
|
10
|
+
2. **OpenClaw Entegrasyonu (Beyin):** Sistem arka planda çalışan OpenClaw Gateway ile konuşur. Web tarayıcısını kullanarak rakip fiyat analizi yapar, trendleri takip eder.
|
|
11
|
+
3. **Cross-Border (Kol):** Tek tuşla ürünlerinizi yurt dışı pazarlarına (Amazon DE, Etsy vb.) uyarlar.
|
|
22
12
|
|
|
23
|
-
##
|
|
13
|
+
## 📦 Kurulum
|
|
24
14
|
|
|
25
|
-
|
|
26
|
-
- 🤖 **Akıllı Fiyatlama** - Rakip takibi ve otomatik fiyat optimizasyonu
|
|
27
|
-
- 📊 **Hızlı Raporlar** - Anlık satış ve stok özeti
|
|
28
|
-
- ⏰ **Otomasyon** - Zamanlanmış görevler ve uyarılar
|
|
29
|
-
- 🧠 **Türkçe NL** - Doğal dil komut desteği
|
|
15
|
+
Vantuz, **Node.js** tabanlıdır ancak son kullanıcı için tek bir `.exe` (Windows) veya Binary (Linux/Mac) olarak paketlenmiştir.
|
|
30
16
|
|
|
31
|
-
|
|
17
|
+
### Kaynak Koddan Kurulum (Geliştiriciler İçin)
|
|
32
18
|
|
|
33
19
|
```bash
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
20
|
+
git clone https://github.com/vantuz-ai/core.git
|
|
21
|
+
cd vantuz
|
|
22
|
+
npm install
|
|
23
|
+
npm link
|
|
24
|
+
vantuz
|
|
37
25
|
```
|
|
38
26
|
|
|
39
|
-
|
|
27
|
+
### Lisanslama
|
|
28
|
+
Sistemi ilk açtığınızda **Lisans Anahtarı** girmeniz istenecektir. Lisanssız kullanımda sadece "Demo Modu" (kısıtlı veri) çalışır.
|
|
29
|
+
|
|
30
|
+
## 🛠️ Desteklenen Platformlar
|
|
40
31
|
|
|
41
|
-
|
|
32
|
+
* ✅ **Trendyol** (Tam Entegrasyon)
|
|
33
|
+
* 🚧 **Hepsiburada** (Beta)
|
|
34
|
+
* 🚧 **Amazon** (Geliştiriliyor)
|
|
35
|
+
* 🚧 **N11** (Planlandı)
|
|
42
36
|
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
---
|
|
38
|
+
**Powered by OpenClaw Framework**
|
package/cli.js
CHANGED
|
File without changes
|
package/config.js
CHANGED
|
@@ -249,6 +249,7 @@ class Configurator {
|
|
|
249
249
|
await this.step3_Channels();
|
|
250
250
|
await this.step4_Gateway();
|
|
251
251
|
await this.step_EIAConfig();
|
|
252
|
+
await this.step_RiskAcceptance(); // Add risk acceptance step
|
|
252
253
|
await this.step5_Save();
|
|
253
254
|
await this.showSuccess();
|
|
254
255
|
} catch (error) {
|
|
@@ -630,6 +631,29 @@ class Configurator {
|
|
|
630
631
|
}
|
|
631
632
|
|
|
632
633
|
// KAYDET
|
|
634
|
+
async step_RiskAcceptance() {
|
|
635
|
+
if (this.envVars.RISK_ACCEPTED === 'true') return;
|
|
636
|
+
|
|
637
|
+
this.printHeader('RİSK KABULÜ', '⚠️');
|
|
638
|
+
console.log(c('brightWhite', 'Vantuz AI, sizin adınıza fiyat ve stok güncellemeleri yapabilir.\n'));
|
|
639
|
+
console.log(c('yellow', 'Bazı işlemler geri alınamaz olabilir. Otonom kararlar için riskleri kabul ediyor musunuz?'));
|
|
640
|
+
console.log(c('dim', '(Kabul ederseniz, yazma işlemlerinde sürekli onay sormayacaktır.)\n'));
|
|
641
|
+
|
|
642
|
+
console.log(this.menuItem('E', 'Evet, kabul ediyorum', 'Otonom mod'));
|
|
643
|
+
console.log(this.menuItem('H', 'Hayır, her işlemde sor', 'Güvenli mod'));
|
|
644
|
+
console.log('');
|
|
645
|
+
|
|
646
|
+
const choice = await this.prompt(c('brightYellow', '❯ Seçiminiz (E/H)'));
|
|
647
|
+
if (choice.toLowerCase() === 'e') {
|
|
648
|
+
this.envVars.RISK_ACCEPTED = 'true';
|
|
649
|
+
console.log(this.successMessage('Risk kabul edildi. Otonom mod aktif.'));
|
|
650
|
+
} else {
|
|
651
|
+
this.envVars.RISK_ACCEPTED = 'false';
|
|
652
|
+
console.log(this.infoMessage('Güvenli mod aktif. Kritik işlemlerde onay istenecektir.'));
|
|
653
|
+
}
|
|
654
|
+
await sleep(1000);
|
|
655
|
+
}
|
|
656
|
+
|
|
633
657
|
async step5_Save() {
|
|
634
658
|
this.printHeader('AYARLAR KAYDEDİLİYOR', '💾');
|
|
635
659
|
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// core/agent-loop.js
|
|
2
|
+
// The Heartbeat of Vantuz OS V2 — Autonomous Agent Loop
|
|
3
|
+
// Cron-driven cycle that orchestrates all intelligence modules.
|
|
4
|
+
|
|
5
|
+
import { log } from './ai-provider.js';
|
|
6
|
+
import { getLearning } from './learning.js';
|
|
7
|
+
import { getSelfHealer } from './self-healer.js';
|
|
8
|
+
import { CronJob } from 'cron';
|
|
9
|
+
|
|
10
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
11
|
+
// AGENT LOOP
|
|
12
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
13
|
+
|
|
14
|
+
class AgentLoop {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.modules = new Map(); // name -> { fn, interval, enabled }
|
|
17
|
+
this.cronJobs = new Map(); // name -> CronJob instance
|
|
18
|
+
this.running = false;
|
|
19
|
+
this.lastRun = {}; // name -> timestamp
|
|
20
|
+
this.results = {}; // name -> last result
|
|
21
|
+
this.healer = getSelfHealer();
|
|
22
|
+
this.learning = getLearning();
|
|
23
|
+
|
|
24
|
+
log('INFO', '🫀 AgentLoop initialized');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Register a module to run in the loop.
|
|
29
|
+
* @param {string} name - Module name (e.g., 'warroom', 'oracle').
|
|
30
|
+
* @param {function} fn - Async function to execute.
|
|
31
|
+
* @param {string} cronExpr - Cron expression (default: every 30 min).
|
|
32
|
+
* @param {boolean} enabled - Start enabled? (default: true).
|
|
33
|
+
*/
|
|
34
|
+
register(name, fn, cronExpr = '*/30 * * * *', enabled = true) {
|
|
35
|
+
this.modules.set(name, { fn, cronExpr, enabled });
|
|
36
|
+
log('INFO', `AgentLoop: registered "${name}" (${cronExpr}, ${enabled ? 'enabled' : 'disabled'})`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Start the loop — creates cron jobs for all registered modules.
|
|
41
|
+
*/
|
|
42
|
+
start() {
|
|
43
|
+
if (this.running) {
|
|
44
|
+
log('WARN', 'AgentLoop already running');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const [name, mod] of this.modules) {
|
|
49
|
+
if (!mod.enabled) continue;
|
|
50
|
+
|
|
51
|
+
const job = new CronJob(mod.cronExpr, async () => {
|
|
52
|
+
await this._executeModule(name);
|
|
53
|
+
}, null, false, 'Europe/Istanbul');
|
|
54
|
+
|
|
55
|
+
this.cronJobs.set(name, job);
|
|
56
|
+
job.start();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this.running = true;
|
|
60
|
+
log('INFO', `🫀 AgentLoop STARTED — ${this.cronJobs.size} modules active`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Stop the loop.
|
|
65
|
+
*/
|
|
66
|
+
stop() {
|
|
67
|
+
for (const [name, job] of this.cronJobs) {
|
|
68
|
+
job.stop();
|
|
69
|
+
}
|
|
70
|
+
this.cronJobs.clear();
|
|
71
|
+
this.running = false;
|
|
72
|
+
log('INFO', '🫀 AgentLoop STOPPED');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Enable/disable a module.
|
|
77
|
+
*/
|
|
78
|
+
setEnabled(name, enabled) {
|
|
79
|
+
const mod = this.modules.get(name);
|
|
80
|
+
if (mod) {
|
|
81
|
+
mod.enabled = enabled;
|
|
82
|
+
const job = this.cronJobs.get(name);
|
|
83
|
+
if (job) {
|
|
84
|
+
enabled ? job.start() : job.stop();
|
|
85
|
+
}
|
|
86
|
+
log('INFO', `AgentLoop: "${name}" ${enabled ? 'enabled' : 'disabled'}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Manually trigger a module immediately.
|
|
92
|
+
*/
|
|
93
|
+
async trigger(name) {
|
|
94
|
+
if (!this.modules.has(name)) {
|
|
95
|
+
return { error: `Module "${name}" not found` };
|
|
96
|
+
}
|
|
97
|
+
return await this._executeModule(name);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Run all modules once (manual full cycle).
|
|
102
|
+
*/
|
|
103
|
+
async runFullCycle() {
|
|
104
|
+
log('INFO', '🫀 Full cycle triggered manually');
|
|
105
|
+
const results = {};
|
|
106
|
+
for (const [name, mod] of this.modules) {
|
|
107
|
+
if (mod.enabled) {
|
|
108
|
+
results[name] = await this._executeModule(name);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return results;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
getStatus() {
|
|
115
|
+
const moduleStatus = {};
|
|
116
|
+
for (const [name, mod] of this.modules) {
|
|
117
|
+
moduleStatus[name] = {
|
|
118
|
+
enabled: mod.enabled,
|
|
119
|
+
cronExpr: mod.cronExpr,
|
|
120
|
+
lastRun: this.lastRun[name] || null,
|
|
121
|
+
lastResult: this.results[name] ? 'OK' : null
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
running: this.running,
|
|
127
|
+
autonomous: this.learning.isAutonomous(),
|
|
128
|
+
netScore: this.learning.getNetScore(),
|
|
129
|
+
activeModules: [...this.modules.entries()].filter(([_, m]) => m.enabled).length,
|
|
130
|
+
totalModules: this.modules.size,
|
|
131
|
+
modules: moduleStatus
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
async _executeModule(name) {
|
|
138
|
+
const mod = this.modules.get(name);
|
|
139
|
+
if (!mod) return null;
|
|
140
|
+
|
|
141
|
+
// Autonomy check
|
|
142
|
+
if (!this.learning.isAutonomous()) {
|
|
143
|
+
log('WARN', `AgentLoop: "${name}" skipped — otonom mod kapalı (skor: ${this.learning.getNetScore()})`);
|
|
144
|
+
return { skipped: true, reason: 'Autonomous mode disabled' };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
log('INFO', `AgentLoop: executing "${name}"`);
|
|
148
|
+
const startTime = Date.now();
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
// Execute with self-healing wrapper
|
|
152
|
+
const result = await this.healer.withHealing(
|
|
153
|
+
`agent-loop/${name}`,
|
|
154
|
+
() => mod.fn(),
|
|
155
|
+
`agent-loop-${name}`,
|
|
156
|
+
{ module: name, timestamp: new Date().toISOString() }
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const duration = Date.now() - startTime;
|
|
160
|
+
this.lastRun[name] = new Date().toISOString();
|
|
161
|
+
this.results[name] = result;
|
|
162
|
+
|
|
163
|
+
// Positive learning
|
|
164
|
+
this.learning.record('successful_price_update', `${name} başarılı (${duration}ms)`, name);
|
|
165
|
+
|
|
166
|
+
log('INFO', `AgentLoop: "${name}" completed in ${duration}ms`);
|
|
167
|
+
return result;
|
|
168
|
+
|
|
169
|
+
} catch (e) {
|
|
170
|
+
const duration = Date.now() - startTime;
|
|
171
|
+
|
|
172
|
+
// Negative learning
|
|
173
|
+
this.learning.record('listing_error', `${name} hata: ${e.message}`, name);
|
|
174
|
+
|
|
175
|
+
log('ERROR', `AgentLoop: "${name}" failed after ${duration}ms`, { error: e.message });
|
|
176
|
+
return { error: e.message };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let loopInstance = null;
|
|
182
|
+
|
|
183
|
+
export function getAgentLoop() {
|
|
184
|
+
if (!loopInstance) {
|
|
185
|
+
loopInstance = new AgentLoop();
|
|
186
|
+
}
|
|
187
|
+
return loopInstance;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export default AgentLoop;
|
package/core/ai-analyst.js
CHANGED
|
@@ -7,7 +7,7 @@ module.exports = {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
const prompt = `
|
|
10
|
-
|
|
10
|
+
Sen OmniMarket AI'ın uzman e-ticaret analistisin.
|
|
11
11
|
Aşağıdaki e-ticaret verilerini analiz et ve mağaza sahibine 3 adet kritik, eyleme dönüştürülebilir öneri sun.
|
|
12
12
|
Yanıtın kısa, net ve profesyonel olsun. Türkçe yanıt ver.
|
|
13
13
|
|
|
@@ -18,7 +18,7 @@ ${JSON.stringify(data, null, 2)}
|
|
|
18
18
|
try {
|
|
19
19
|
const baseURL = config.ai.baseUrl || 'https://api.openai.com/v1';
|
|
20
20
|
const model = config.ai.model || 'gpt-4-turbo';
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
// Generic OpenAI Compatible Endpoint Support (Works with DeepSeek, OpenAI, LocalAI)
|
|
23
23
|
const response = await axios.post(`${baseURL}/chat/completions`, {
|
|
24
24
|
model: model,
|
package/core/ai-provider.js
CHANGED
|
@@ -134,6 +134,38 @@ export function clearLogs() {
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
138
|
+
// WORKSPACE IDENTITY LOADER
|
|
139
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
140
|
+
|
|
141
|
+
const WORKSPACE_DIR = path.join(process.cwd(), 'workspace');
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Loads workspace identity files (BRAND.md, SOUL.md, AGENTS.md)
|
|
145
|
+
* These files define the AI's personality, brand rules, and capabilities.
|
|
146
|
+
*/
|
|
147
|
+
function loadWorkspaceIdentity() {
|
|
148
|
+
const files = ['BRAND.md', 'SOUL.md', 'AGENTS.md'];
|
|
149
|
+
let identity = '';
|
|
150
|
+
|
|
151
|
+
for (const file of files) {
|
|
152
|
+
const filePath = path.join(WORKSPACE_DIR, file);
|
|
153
|
+
try {
|
|
154
|
+
if (fs.existsSync(filePath)) {
|
|
155
|
+
const content = fs.readFileSync(filePath, 'utf-8').trim();
|
|
156
|
+
if (content) {
|
|
157
|
+
identity += `\n\n--- ${file} ---\n${content}`;
|
|
158
|
+
log('INFO', `Workspace identity loaded: ${file}`, { chars: content.length });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} catch (e) {
|
|
162
|
+
log('WARN', `Workspace file read error: ${file}`, { error: e.message });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return identity;
|
|
167
|
+
}
|
|
168
|
+
|
|
137
169
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
138
170
|
// SYSTEM PROMPT
|
|
139
171
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -158,17 +190,18 @@ const VANTUZ_SYSTEM_PROMPT = `Sen Vantuz AI, e-ticaret operasyonlarını yönete
|
|
|
158
190
|
## Yeteneklerin
|
|
159
191
|
- Stok kontrolü ve güncelleme
|
|
160
192
|
- Fiyat analizi ve güncelleme
|
|
161
|
-
- Sipariş yönetimi
|
|
193
|
+
- Sipariş yönetimi (Listeleme, Durum sorgulama)
|
|
162
194
|
- Rakip analizi
|
|
163
195
|
- Satış raporları
|
|
196
|
+
- Zamanlanmış görevler (Cron Jobs) oluşturma/yönetme
|
|
164
197
|
|
|
165
198
|
## Önemli Kurallar
|
|
166
199
|
1. **ASLA VE ASLA** elindeki "MEVCUT SİSTEM DURUMU" verisinde olmayan bir sayıyı uydurma.
|
|
167
|
-
2. Eğer
|
|
168
|
-
3.
|
|
169
|
-
4.
|
|
170
|
-
5.
|
|
171
|
-
6.
|
|
200
|
+
2. Eğer mesajda "cron" veya "zamanla" geçiyorsa, cron formatında zamanlanmış görev oluşturmayı teklif et.
|
|
201
|
+
3. Sipariş veya ürün listeleme gibi OKUMA (Read) işlemleri için ASLA onay isteme. Doğrudan listele.
|
|
202
|
+
4. Sadece Fiyat/Stok güncelleme veya Silme gibi YAZMA (Write) işlemleri için risk uyarısı ver.
|
|
203
|
+
5. Kullanıcı "Risk Kabul Edildi" modundaysa (RISK_ACCEPTED=true), onay istemeden işlemi yap.
|
|
204
|
+
6. Kar marjının altına fiyat düşürme önerme.
|
|
172
205
|
|
|
173
206
|
## Yanıt Formatı
|
|
174
207
|
- Kısa ve öz ol
|
|
@@ -205,7 +238,8 @@ export async function chat(message, config, env) {
|
|
|
205
238
|
|
|
206
239
|
// Context bilgisi ekle
|
|
207
240
|
const contextInfo = config.systemContext || '';
|
|
208
|
-
const
|
|
241
|
+
const workspaceIdentity = loadWorkspaceIdentity();
|
|
242
|
+
const fullSystemPrompt = VANTUZ_SYSTEM_PROMPT + contextInfo + (workspaceIdentity ? `\n\n## MARKA KİMLİĞİ VE STRATEJİ\nAşağıdaki kurallara MUTLAKA uy:${workspaceIdentity}` : '');
|
|
209
243
|
|
|
210
244
|
log('INFO', `AI isteği: ${provider}`, { message: message.slice(0, 100) });
|
|
211
245
|
|
package/core/automation.js
CHANGED
|
@@ -10,14 +10,13 @@ const VANTUZ_HOME = path.join(os.homedir(), '.vantuz');
|
|
|
10
10
|
const CONFIG_JSON = path.join(VANTUZ_HOME, 'config.json');
|
|
11
11
|
|
|
12
12
|
const RISKY_KEYWORDS = [
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
'sil', 'kald?r', 'de?i?tir', 'price', 'stock'
|
|
13
|
+
'güncelle', 'update', 'yayınla', 'publish', 'yayından kaldır', 'unpublish',
|
|
14
|
+
'değiştir', 'change', 'sil', 'kaldır', 'delete', 'remove',
|
|
15
|
+
'fiyatı yap', 'stoğu yap'
|
|
17
16
|
];
|
|
18
17
|
|
|
19
18
|
const APPROVE_KEYWORDS = ['onay', 'onayla', 'kabul', 'evet', 'tamam'];
|
|
20
|
-
const REJECT_KEYWORDS = ['
|
|
19
|
+
const REJECT_KEYWORDS = ['hayır', 'iptal', 'vazgeç', 'reddet'];
|
|
21
20
|
|
|
22
21
|
function loadConfig() {
|
|
23
22
|
try {
|
|
@@ -25,7 +24,7 @@ function loadConfig() {
|
|
|
25
24
|
return JSON.parse(fs.readFileSync(CONFIG_JSON, 'utf-8'));
|
|
26
25
|
}
|
|
27
26
|
} catch (e) {
|
|
28
|
-
log('WARN', 'Config
|
|
27
|
+
log('WARN', 'Config okunamadı', { error: e.message });
|
|
29
28
|
}
|
|
30
29
|
return {};
|
|
31
30
|
}
|
|
@@ -35,7 +34,7 @@ function saveConfig(config) {
|
|
|
35
34
|
fs.writeFileSync(CONFIG_JSON, JSON.stringify(config, null, 2));
|
|
36
35
|
return true;
|
|
37
36
|
} catch (e) {
|
|
38
|
-
log('WARN', 'Config
|
|
37
|
+
log('WARN', 'Config yazılamadı', { error: e.message });
|
|
39
38
|
return false;
|
|
40
39
|
}
|
|
41
40
|
}
|
|
@@ -54,12 +53,12 @@ function messageHasAny(message, keywords) {
|
|
|
54
53
|
function normalizeTr(input) {
|
|
55
54
|
return String(input || '')
|
|
56
55
|
.toLowerCase()
|
|
57
|
-
.replace(/[
|
|
58
|
-
.replace(/[
|
|
59
|
-
.replace(/[
|
|
60
|
-
.replace(/[
|
|
61
|
-
.replace(/[
|
|
62
|
-
.replace(/[
|
|
56
|
+
.replace(/[çÇ]/g, 'c')
|
|
57
|
+
.replace(/[ğĞ]/g, 'g')
|
|
58
|
+
.replace(/[ıİ]/g, 'i')
|
|
59
|
+
.replace(/[öÖ]/g, 'o')
|
|
60
|
+
.replace(/[şŞ]/g, 's')
|
|
61
|
+
.replace(/[üÜ]/g, 'u');
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
function summarizeOrdersByStatus(orders = []) {
|
|
@@ -77,7 +76,7 @@ function summarizeOrdersByStatus(orders = []) {
|
|
|
77
76
|
|
|
78
77
|
function isActiveStatus(status) {
|
|
79
78
|
const s = String(status || '').toLowerCase();
|
|
80
|
-
return s === 'created' || s === 'picking' || s === 'unpacked';
|
|
79
|
+
return s === 'created' || s === 'picking' || s === 'unpacked' || s === 'shipped';
|
|
81
80
|
}
|
|
82
81
|
|
|
83
82
|
function extractProductNames(order) {
|
|
@@ -107,7 +106,7 @@ function getOrderTimestamp(order) {
|
|
|
107
106
|
|
|
108
107
|
function formatOrderLine(order) {
|
|
109
108
|
const number = order.orderNumber || order.id || 'N/A';
|
|
110
|
-
const name = order.customerName || order.customerfullName || order.customerFullName || '
|
|
109
|
+
const name = order.customerName || order.customerfullName || order.customerFullName || 'Müşteri';
|
|
111
110
|
const total = order.totalPrice ?? order.totalAmount ?? order.total ?? '?';
|
|
112
111
|
const status = order.status || order.shipmentPackageStatus || order.orderStatus || 'UNKNOWN';
|
|
113
112
|
return `#${number} - ${name} - ${total} TL - ${status}`;
|
|
@@ -175,13 +174,13 @@ function removeApproval(config, approvalId) {
|
|
|
175
174
|
|
|
176
175
|
async function planWithAI(message, engine) {
|
|
177
176
|
const systemPrompt = [
|
|
178
|
-
'Sen Vantuz otomasyon
|
|
179
|
-
'
|
|
180
|
-
'
|
|
177
|
+
'Sen Vantuz otomasyon planlayıcısısın.',
|
|
178
|
+
'Kullanıcı mesajını tek bir otomasyon planına çevir ve sadece JSON döndür.',
|
|
179
|
+
'Şema:',
|
|
181
180
|
'{ "intent": "report|analysis|change|schedule|other", "risk": "low|high", "schedule": "", "action": "" }',
|
|
182
|
-
'risk =
|
|
183
|
-
'schedule = cron ifadesi (
|
|
184
|
-
'action =
|
|
181
|
+
'risk = SADECE veri değiştiren/silen işlemler (update/delete/create) varsa "high" olmalı. Listeleme/okuma işlemleri her zaman "low".',
|
|
182
|
+
'schedule = cron ifadesi (boş olabilir).',
|
|
183
|
+
'action = yapılacak işi kısa Türkçe özetle.'
|
|
185
184
|
].join('\n');
|
|
186
185
|
|
|
187
186
|
const response = await aiChat(message, {
|
|
@@ -200,10 +199,14 @@ async function planWithAI(message, engine) {
|
|
|
200
199
|
|
|
201
200
|
function fallbackPlan(message) {
|
|
202
201
|
const risky = messageHasAny(message, RISKY_KEYWORDS);
|
|
202
|
+
// Explicitly safe keywords check
|
|
203
|
+
const safeKeywords = ['listele', 'göster', 'nedir', 'kaç', 'ne kadar', 'durum', 'rapor'];
|
|
204
|
+
const safe = messageHasAny(message, safeKeywords);
|
|
205
|
+
|
|
203
206
|
const schedule = message.includes('cron ') ? message.split('cron ')[1].trim() : '';
|
|
204
207
|
return {
|
|
205
208
|
intent: schedule ? 'schedule' : (risky ? 'change' : 'analysis'),
|
|
206
|
-
risk: risky ? 'high' : 'low',
|
|
209
|
+
risk: (risky && !safe) ? 'high' : 'low',
|
|
207
210
|
schedule,
|
|
208
211
|
action: message
|
|
209
212
|
};
|
|
@@ -231,15 +234,31 @@ export class AutomationManager {
|
|
|
231
234
|
if (!job?.name || !job?.cron || !job?.message) return;
|
|
232
235
|
|
|
233
236
|
this.scheduler.addJob(job.name, job.cron, async () => {
|
|
237
|
+
log('INFO', `Cron job executing: ${job.name}`, { message: job.message });
|
|
234
238
|
await this.engine.chat(job.message);
|
|
235
239
|
}, true);
|
|
236
240
|
|
|
237
241
|
if (persist) {
|
|
238
|
-
this.config.automation.cronJobs.
|
|
239
|
-
|
|
242
|
+
const exists = this.config.automation.cronJobs.find(j => j.name === job.name);
|
|
243
|
+
if (!exists) {
|
|
244
|
+
this.config.automation.cronJobs.push(job);
|
|
245
|
+
saveConfig(this.config);
|
|
246
|
+
}
|
|
240
247
|
}
|
|
241
248
|
}
|
|
242
249
|
|
|
250
|
+
_deleteJob(name) {
|
|
251
|
+
this.scheduler.removeJob(name);
|
|
252
|
+
this.config.automation.cronJobs = this.config.automation.cronJobs.filter(j => j.name !== name && j.name !== `auto-${name}`);
|
|
253
|
+
saveConfig(this.config);
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
_listJobs() {
|
|
258
|
+
return this.config.automation.cronJobs || [];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
|
|
243
262
|
async handleMessage(message, meta = { channel: 'local', from: 'local' }) {
|
|
244
263
|
this.config = ensureConfigShape(loadConfig());
|
|
245
264
|
|