vantuz 3.4.2 → 3.5.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.
Files changed (70) hide show
  1. package/LICENSE +45 -45
  2. package/admin-keygen.js +51 -0
  3. package/cli.js +685 -585
  4. package/config.js +733 -733
  5. package/core/agent-loop.js +190 -190
  6. package/core/ai-provider.js +298 -261
  7. package/core/automation.js +523 -523
  8. package/core/brand-analyst.js +101 -0
  9. package/core/channels.js +167 -167
  10. package/core/dashboard.js +230 -230
  11. package/core/database.js +135 -37
  12. package/core/eia-monitor.js +3 -1
  13. package/core/engine.js +648 -636
  14. package/core/gateway.js +447 -447
  15. package/core/learning.js +214 -214
  16. package/core/license.js +113 -0
  17. package/core/marketplace-adapter.js +168 -168
  18. package/core/memory.js +190 -190
  19. package/core/migrations/001-initial-schema.sql +1 -1
  20. package/core/queue.js +120 -120
  21. package/core/self-healer.js +314 -314
  22. package/core/unified-product.js +214 -214
  23. package/core/vision-service.js +113 -113
  24. package/index.js +217 -174
  25. package/modules/crm/sentiment-crm.js +231 -231
  26. package/modules/healer/listing-healer.js +201 -201
  27. package/modules/oracle/predictor.js +214 -214
  28. package/modules/researcher/agent.js +169 -169
  29. package/modules/team/agents/base.js +92 -92
  30. package/modules/team/agents/dev.js +33 -33
  31. package/modules/team/agents/josh.js +40 -40
  32. package/modules/team/agents/marketing.js +33 -33
  33. package/modules/team/agents/milo.js +36 -36
  34. package/modules/team/index.js +78 -78
  35. package/modules/team/shared-memory.js +87 -87
  36. package/modules/war-room/competitor-tracker.js +250 -250
  37. package/modules/war-room/pricing-engine.js +308 -308
  38. package/nodes/warehouse.js +238 -238
  39. package/onboard.js +1 -1
  40. package/package.json +7 -5
  41. package/platforms/pttavm.js +14 -14
  42. package/plugins/vantuz/index.js +528 -528
  43. package/plugins/vantuz/memory/hippocampus.js +465 -465
  44. package/plugins/vantuz/package.json +20 -20
  45. package/plugins/vantuz/platforms/_template.js +118 -118
  46. package/plugins/vantuz/platforms/amazon.js +236 -236
  47. package/plugins/vantuz/platforms/ciceksepeti.js +166 -166
  48. package/plugins/vantuz/platforms/hepsiburada.js +180 -180
  49. package/plugins/vantuz/platforms/index.js +165 -165
  50. package/plugins/vantuz/platforms/n11.js +229 -229
  51. package/plugins/vantuz/platforms/pazarama.js +154 -154
  52. package/plugins/vantuz/platforms/pttavm.js +127 -127
  53. package/plugins/vantuz/platforms/trendyol.js +326 -326
  54. package/plugins/vantuz/services/alerts.js +253 -253
  55. package/plugins/vantuz/services/license.js +34 -34
  56. package/plugins/vantuz/services/scheduler.js +232 -232
  57. package/plugins/vantuz/tools/analytics.js +152 -152
  58. package/plugins/vantuz/tools/crossborder.js +187 -187
  59. package/plugins/vantuz/tools/nl-parser.js +211 -211
  60. package/plugins/vantuz/tools/product.js +110 -110
  61. package/plugins/vantuz/tools/quick-report.js +175 -175
  62. package/plugins/vantuz/tools/repricer.js +314 -314
  63. package/plugins/vantuz/tools/sentiment.js +115 -115
  64. package/plugins/vantuz/tools/vision.js +257 -257
  65. package/private.pem +28 -0
  66. package/public.pem +9 -0
  67. package/server/app.js +260 -260
  68. package/server/public/index.html +514 -514
  69. package/start.bat +33 -33
  70. package/vantuz.sqlite +0 -0
@@ -1,250 +1,250 @@
1
- // modules/war-room/competitor-tracker.js
2
- // Competitor Price Tracker for Vantuz OS V2
3
- // Monitors rival prices with POLITENESS MODE to avoid IP bans.
4
-
5
- import fs from 'fs';
6
- import path from 'path';
7
- import os from 'os';
8
- import { log } from '../../core/ai-provider.js';
9
-
10
- const HISTORY_FILE = path.join(os.homedir(), '.vantuz', 'memory', 'competitor-history.json');
11
- const MAX_HISTORY = 1000;
12
-
13
- // ═══════════════════════════════════════════════════════════════════════════
14
- // POLITENESS MODE — Anti-Ban Protection
15
- // ═══════════════════════════════════════════════════════════════════════════
16
-
17
- function randomDelay(minMs = 2000, maxMs = 5000) {
18
- const delay = Math.floor(Math.random() * (maxMs - minMs + 1)) + minMs;
19
- return new Promise(resolve => setTimeout(resolve, delay));
20
- }
21
-
22
- const RATE_LIMITS = new Map(); // platform -> { count, resetAt }
23
- const MAX_REQUESTS_PER_HOUR = 120;
24
-
25
- function checkRateLimit(platform) {
26
- const now = Date.now();
27
- let limit = RATE_LIMITS.get(platform);
28
-
29
- if (!limit || now > limit.resetAt) {
30
- limit = { count: 0, resetAt: now + 3600000 }; // 1 hour window
31
- RATE_LIMITS.set(platform, limit);
32
- }
33
-
34
- if (limit.count >= MAX_REQUESTS_PER_HOUR) {
35
- log('WARN', `Rate limit reached for ${platform}`, {
36
- count: limit.count,
37
- resetIn: Math.round((limit.resetAt - now) / 60000) + ' min'
38
- });
39
- return false;
40
- }
41
-
42
- limit.count++;
43
- return true;
44
- }
45
-
46
- // ═══════════════════════════════════════════════════════════════════════════
47
- // HISTORY PERSISTENCE
48
- // ═══════════════════════════════════════════════════════════════════════════
49
-
50
- function loadHistory() {
51
- try {
52
- if (fs.existsSync(HISTORY_FILE)) {
53
- return JSON.parse(fs.readFileSync(HISTORY_FILE, 'utf-8'));
54
- }
55
- } catch (e) { /* ignore */ }
56
- return {};
57
- }
58
-
59
- function saveHistory(history) {
60
- const dir = path.dirname(HISTORY_FILE);
61
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
62
- const tmp = HISTORY_FILE + '.tmp';
63
- fs.writeFileSync(tmp, JSON.stringify(history, null, 2), 'utf-8');
64
- fs.renameSync(tmp, HISTORY_FILE);
65
- }
66
-
67
- // ═══════════════════════════════════════════════════════════════════════════
68
- // COMPETITOR TRACKER
69
- // ═══════════════════════════════════════════════════════════════════════════
70
-
71
- class CompetitorTracker {
72
- constructor() {
73
- this.history = loadHistory(); // barcode -> [{timestamp, platform, competitors[]}]
74
- this.adapters = null; // Set by init()
75
- log('INFO', 'CompetitorTracker initialized', {
76
- trackedProducts: Object.keys(this.history).length
77
- });
78
- }
79
-
80
- /**
81
- * Set adapter registry reference.
82
- */
83
- setAdapters(adapterRegistry) {
84
- this.adapters = adapterRegistry;
85
- }
86
-
87
- /**
88
- * Track competitor prices for a single product.
89
- * @param {string} barcode
90
- * @param {string} platform - Platform to check (default: all active).
91
- * @returns {{ competitors: array, alert: string|null }}
92
- */
93
- async trackProduct(barcode, platform = null) {
94
- const competitors = [];
95
- const platforms = platform
96
- ? [platform]
97
- : (this.adapters?.getActive() || []).map(a => a.name);
98
-
99
- for (const pName of platforms) {
100
- // Rate limit check
101
- if (!checkRateLimit(pName)) {
102
- log('WARN', `Skipping ${pName} for ${barcode}: rate limited`);
103
- continue;
104
- }
105
-
106
- const adapter = this.adapters?.get(pName);
107
- if (!adapter || typeof adapter.getCompetitorPrices !== 'function') {
108
- continue;
109
- }
110
-
111
- try {
112
- // POLITENESS MODE: Random delay before each request
113
- await randomDelay(2000, 5000);
114
-
115
- const result = await adapter.getCompetitorPrices(barcode);
116
- if (result?.success && result.data) {
117
- for (const comp of result.data) {
118
- competitors.push({
119
- platform: pName,
120
- seller: comp.seller || comp.merchantName || 'unknown',
121
- price: parseFloat(comp.price || comp.salePrice || 0),
122
- stock: comp.stock ?? comp.hasStock ?? null,
123
- rating: comp.rating || null,
124
- fetchedAt: new Date().toISOString()
125
- });
126
- }
127
- }
128
- } catch (e) {
129
- log('ERROR', `Competitor fetch failed: ${pName}/${barcode}`, { error: e.message });
130
- }
131
- }
132
-
133
- // Store in history
134
- this._recordHistory(barcode, competitors);
135
-
136
- // Detect alerts
137
- const alert = this._detectAlerts(barcode, competitors);
138
-
139
- return { barcode, competitors, alert };
140
- }
141
-
142
- /**
143
- * Track multiple products in batch (respects rate limits).
144
- * @param {string[]} barcodes
145
- */
146
- async trackBatch(barcodes) {
147
- const results = [];
148
-
149
- for (const barcode of barcodes) {
150
- const result = await this.trackProduct(barcode);
151
- results.push(result);
152
-
153
- // Extra delay between products for politeness
154
- await randomDelay(1000, 3000);
155
- }
156
-
157
- log('INFO', `Batch tracking complete`, {
158
- products: barcodes.length,
159
- totalCompetitors: results.reduce((s, r) => s + r.competitors.length, 0)
160
- });
161
-
162
- return results;
163
- }
164
-
165
- /**
166
- * Get price history for a product.
167
- */
168
- getHistory(barcode) {
169
- return this.history[barcode] || [];
170
- }
171
-
172
- /**
173
- * Get competitor summary: cheapest, average, count.
174
- */
175
- getSummary(barcode) {
176
- const entries = this.history[barcode] || [];
177
- if (entries.length === 0) return null;
178
-
179
- const latest = entries[entries.length - 1];
180
- const prices = latest.competitors.map(c => c.price).filter(p => p > 0);
181
-
182
- return {
183
- barcode,
184
- sellerCount: latest.competitors.length,
185
- cheapest: prices.length > 0 ? Math.min(...prices) : null,
186
- average: prices.length > 0 ? prices.reduce((a, b) => a + b, 0) / prices.length : null,
187
- mostExpensive: prices.length > 0 ? Math.max(...prices) : null,
188
- lastChecked: latest.timestamp
189
- };
190
- }
191
-
192
- // ─────────────────────────────────────────────────────────────────────
193
- // PRIVATE
194
- // ─────────────────────────────────────────────────────────────────────
195
-
196
- _recordHistory(barcode, competitors) {
197
- if (!this.history[barcode]) {
198
- this.history[barcode] = [];
199
- }
200
-
201
- this.history[barcode].push({
202
- timestamp: new Date().toISOString(),
203
- competitors
204
- });
205
-
206
- // Keep only recent entries per product
207
- if (this.history[barcode].length > 50) {
208
- this.history[barcode] = this.history[barcode].slice(-50);
209
- }
210
-
211
- // Limit total history size
212
- const total = Object.values(this.history).reduce((s, arr) => s + arr.length, 0);
213
- if (total > MAX_HISTORY) {
214
- // Remove oldest product histories
215
- const sorted = Object.entries(this.history)
216
- .sort((a, b) => a[1].length - b[1].length);
217
- delete this.history[sorted[0][0]];
218
- }
219
-
220
- saveHistory(this.history);
221
- }
222
-
223
- _detectAlerts(barcode, competitors) {
224
- if (competitors.length === 0) return null;
225
-
226
- const prices = competitors.map(c => c.price).filter(p => p > 0);
227
- if (prices.length === 0) return null;
228
-
229
- const cheapest = Math.min(...prices);
230
- const lowStockSellers = competitors.filter(c => c.stock !== null && c.stock < 5);
231
-
232
- // Alert: all competitors low stock → opportunity!
233
- if (lowStockSellers.length > 0 && lowStockSellers.length >= competitors.length * 0.7) {
234
- return `🔥 FIRSAT: ${barcode} — Rakiplerin %${Math.round(lowStockSellers.length / competitors.length * 100)}'i düşük stokta. Fiyat artırma fırsatı!`;
235
- }
236
-
237
- return null;
238
- }
239
- }
240
-
241
- let trackerInstance = null;
242
-
243
- export function getCompetitorTracker() {
244
- if (!trackerInstance) {
245
- trackerInstance = new CompetitorTracker();
246
- }
247
- return trackerInstance;
248
- }
249
-
250
- export default CompetitorTracker;
1
+ // modules/war-room/competitor-tracker.js
2
+ // Competitor Price Tracker for Vantuz OS V2
3
+ // Monitors rival prices with POLITENESS MODE to avoid IP bans.
4
+
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import os from 'os';
8
+ import { log } from '../../core/ai-provider.js';
9
+
10
+ const HISTORY_FILE = path.join(os.homedir(), '.vantuz', 'memory', 'competitor-history.json');
11
+ const MAX_HISTORY = 1000;
12
+
13
+ // ═══════════════════════════════════════════════════════════════════════════
14
+ // POLITENESS MODE — Anti-Ban Protection
15
+ // ═══════════════════════════════════════════════════════════════════════════
16
+
17
+ function randomDelay(minMs = 2000, maxMs = 5000) {
18
+ const delay = Math.floor(Math.random() * (maxMs - minMs + 1)) + minMs;
19
+ return new Promise(resolve => setTimeout(resolve, delay));
20
+ }
21
+
22
+ const RATE_LIMITS = new Map(); // platform -> { count, resetAt }
23
+ const MAX_REQUESTS_PER_HOUR = 120;
24
+
25
+ function checkRateLimit(platform) {
26
+ const now = Date.now();
27
+ let limit = RATE_LIMITS.get(platform);
28
+
29
+ if (!limit || now > limit.resetAt) {
30
+ limit = { count: 0, resetAt: now + 3600000 }; // 1 hour window
31
+ RATE_LIMITS.set(platform, limit);
32
+ }
33
+
34
+ if (limit.count >= MAX_REQUESTS_PER_HOUR) {
35
+ log('WARN', `Rate limit reached for ${platform}`, {
36
+ count: limit.count,
37
+ resetIn: Math.round((limit.resetAt - now) / 60000) + ' min'
38
+ });
39
+ return false;
40
+ }
41
+
42
+ limit.count++;
43
+ return true;
44
+ }
45
+
46
+ // ═══════════════════════════════════════════════════════════════════════════
47
+ // HISTORY PERSISTENCE
48
+ // ═══════════════════════════════════════════════════════════════════════════
49
+
50
+ function loadHistory() {
51
+ try {
52
+ if (fs.existsSync(HISTORY_FILE)) {
53
+ return JSON.parse(fs.readFileSync(HISTORY_FILE, 'utf-8'));
54
+ }
55
+ } catch (e) { /* ignore */ }
56
+ return {};
57
+ }
58
+
59
+ function saveHistory(history) {
60
+ const dir = path.dirname(HISTORY_FILE);
61
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
62
+ const tmp = HISTORY_FILE + '.tmp';
63
+ fs.writeFileSync(tmp, JSON.stringify(history, null, 2), 'utf-8');
64
+ fs.renameSync(tmp, HISTORY_FILE);
65
+ }
66
+
67
+ // ═══════════════════════════════════════════════════════════════════════════
68
+ // COMPETITOR TRACKER
69
+ // ═══════════════════════════════════════════════════════════════════════════
70
+
71
+ class CompetitorTracker {
72
+ constructor() {
73
+ this.history = loadHistory(); // barcode -> [{timestamp, platform, competitors[]}]
74
+ this.adapters = null; // Set by init()
75
+ log('INFO', 'CompetitorTracker initialized', {
76
+ trackedProducts: Object.keys(this.history).length
77
+ });
78
+ }
79
+
80
+ /**
81
+ * Set adapter registry reference.
82
+ */
83
+ setAdapters(adapterRegistry) {
84
+ this.adapters = adapterRegistry;
85
+ }
86
+
87
+ /**
88
+ * Track competitor prices for a single product.
89
+ * @param {string} barcode
90
+ * @param {string} platform - Platform to check (default: all active).
91
+ * @returns {{ competitors: array, alert: string|null }}
92
+ */
93
+ async trackProduct(barcode, platform = null) {
94
+ const competitors = [];
95
+ const platforms = platform
96
+ ? [platform]
97
+ : (this.adapters?.getActive() || []).map(a => a.name);
98
+
99
+ for (const pName of platforms) {
100
+ // Rate limit check
101
+ if (!checkRateLimit(pName)) {
102
+ log('WARN', `Skipping ${pName} for ${barcode}: rate limited`);
103
+ continue;
104
+ }
105
+
106
+ const adapter = this.adapters?.get(pName);
107
+ if (!adapter || typeof adapter.getCompetitorPrices !== 'function') {
108
+ continue;
109
+ }
110
+
111
+ try {
112
+ // POLITENESS MODE: Random delay before each request
113
+ await randomDelay(2000, 5000);
114
+
115
+ const result = await adapter.getCompetitorPrices(barcode);
116
+ if (result?.success && result.data) {
117
+ for (const comp of result.data) {
118
+ competitors.push({
119
+ platform: pName,
120
+ seller: comp.seller || comp.merchantName || 'unknown',
121
+ price: parseFloat(comp.price || comp.salePrice || 0),
122
+ stock: comp.stock ?? comp.hasStock ?? null,
123
+ rating: comp.rating || null,
124
+ fetchedAt: new Date().toISOString()
125
+ });
126
+ }
127
+ }
128
+ } catch (e) {
129
+ log('ERROR', `Competitor fetch failed: ${pName}/${barcode}`, { error: e.message });
130
+ }
131
+ }
132
+
133
+ // Store in history
134
+ this._recordHistory(barcode, competitors);
135
+
136
+ // Detect alerts
137
+ const alert = this._detectAlerts(barcode, competitors);
138
+
139
+ return { barcode, competitors, alert };
140
+ }
141
+
142
+ /**
143
+ * Track multiple products in batch (respects rate limits).
144
+ * @param {string[]} barcodes
145
+ */
146
+ async trackBatch(barcodes) {
147
+ const results = [];
148
+
149
+ for (const barcode of barcodes) {
150
+ const result = await this.trackProduct(barcode);
151
+ results.push(result);
152
+
153
+ // Extra delay between products for politeness
154
+ await randomDelay(1000, 3000);
155
+ }
156
+
157
+ log('INFO', `Batch tracking complete`, {
158
+ products: barcodes.length,
159
+ totalCompetitors: results.reduce((s, r) => s + r.competitors.length, 0)
160
+ });
161
+
162
+ return results;
163
+ }
164
+
165
+ /**
166
+ * Get price history for a product.
167
+ */
168
+ getHistory(barcode) {
169
+ return this.history[barcode] || [];
170
+ }
171
+
172
+ /**
173
+ * Get competitor summary: cheapest, average, count.
174
+ */
175
+ getSummary(barcode) {
176
+ const entries = this.history[barcode] || [];
177
+ if (entries.length === 0) return null;
178
+
179
+ const latest = entries[entries.length - 1];
180
+ const prices = latest.competitors.map(c => c.price).filter(p => p > 0);
181
+
182
+ return {
183
+ barcode,
184
+ sellerCount: latest.competitors.length,
185
+ cheapest: prices.length > 0 ? Math.min(...prices) : null,
186
+ average: prices.length > 0 ? prices.reduce((a, b) => a + b, 0) / prices.length : null,
187
+ mostExpensive: prices.length > 0 ? Math.max(...prices) : null,
188
+ lastChecked: latest.timestamp
189
+ };
190
+ }
191
+
192
+ // ─────────────────────────────────────────────────────────────────────
193
+ // PRIVATE
194
+ // ─────────────────────────────────────────────────────────────────────
195
+
196
+ _recordHistory(barcode, competitors) {
197
+ if (!this.history[barcode]) {
198
+ this.history[barcode] = [];
199
+ }
200
+
201
+ this.history[barcode].push({
202
+ timestamp: new Date().toISOString(),
203
+ competitors
204
+ });
205
+
206
+ // Keep only recent entries per product
207
+ if (this.history[barcode].length > 50) {
208
+ this.history[barcode] = this.history[barcode].slice(-50);
209
+ }
210
+
211
+ // Limit total history size
212
+ const total = Object.values(this.history).reduce((s, arr) => s + arr.length, 0);
213
+ if (total > MAX_HISTORY) {
214
+ // Remove oldest product histories
215
+ const sorted = Object.entries(this.history)
216
+ .sort((a, b) => a[1].length - b[1].length);
217
+ delete this.history[sorted[0][0]];
218
+ }
219
+
220
+ saveHistory(this.history);
221
+ }
222
+
223
+ _detectAlerts(barcode, competitors) {
224
+ if (competitors.length === 0) return null;
225
+
226
+ const prices = competitors.map(c => c.price).filter(p => p > 0);
227
+ if (prices.length === 0) return null;
228
+
229
+ const cheapest = Math.min(...prices);
230
+ const lowStockSellers = competitors.filter(c => c.stock !== null && c.stock < 5);
231
+
232
+ // Alert: all competitors low stock → opportunity!
233
+ if (lowStockSellers.length > 0 && lowStockSellers.length >= competitors.length * 0.7) {
234
+ return `🔥 FIRSAT: ${barcode} — Rakiplerin %${Math.round(lowStockSellers.length / competitors.length * 100)}'i düşük stokta. Fiyat artırma fırsatı!`;
235
+ }
236
+
237
+ return null;
238
+ }
239
+ }
240
+
241
+ let trackerInstance = null;
242
+
243
+ export function getCompetitorTracker() {
244
+ if (!trackerInstance) {
245
+ trackerInstance = new CompetitorTracker();
246
+ }
247
+ return trackerInstance;
248
+ }
249
+
250
+ export default CompetitorTracker;