cdp-edge 1.18.3 → 1.20.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 +18 -1
- package/contracts/agent-versions.json +364 -0
- package/dist/commands/install.js +1 -1
- package/dist/commands/setup.js +326 -111
- package/extracted-skill/tracking-events-generator/agents/server-tracking.md +5 -5
- package/package.json +7 -2
- package/server-edge-tracker/index.js +780 -0
- package/server-edge-tracker/migrate-v7.sql +64 -0
- package/server-edge-tracker/modules/db.js +531 -0
- package/server-edge-tracker/modules/dispatch/ga4.js +65 -0
- package/server-edge-tracker/modules/dispatch/meta.js +119 -0
- package/server-edge-tracker/modules/dispatch/platforms.js +237 -0
- package/server-edge-tracker/modules/dispatch/tiktok.js +100 -0
- package/server-edge-tracker/modules/dispatch/whatsapp.js +233 -0
- package/server-edge-tracker/modules/intelligence.js +321 -0
- package/server-edge-tracker/modules/ml/bidding.js +245 -0
- package/server-edge-tracker/modules/ml/fraud.js +301 -0
- package/server-edge-tracker/modules/ml/logistic.js +195 -0
- package/server-edge-tracker/modules/ml/ltv.js +420 -0
- package/server-edge-tracker/modules/ml/matchquality.js +176 -0
- package/server-edge-tracker/modules/ml/segmentation.js +316 -0
- package/server-edge-tracker/modules/utils.js +89 -0
- package/server-edge-tracker/schema-indexes.sql +67 -0
- package/server-edge-tracker/wrangler.toml +2 -0
package/dist/commands/setup.js
CHANGED
|
@@ -1,138 +1,353 @@
|
|
|
1
|
-
|
|
2
|
-
* Setup Wizard
|
|
1
|
+
/**
|
|
2
|
+
* CDP Edge Setup Wizard — Guiado e não-técnico
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Coleta os dados do projeto do cliente e gera:
|
|
5
|
+
* • Comandos wrangler secret put prontos para copiar
|
|
6
|
+
* • URLs de webhook com domínio preenchido
|
|
7
|
+
* • Checklist de próximos passos para o agente
|
|
5
8
|
*/
|
|
6
9
|
|
|
7
10
|
import inquirer from 'inquirer';
|
|
8
11
|
import chalk from 'chalk';
|
|
9
12
|
import ora from 'ora';
|
|
13
|
+
import { writeFileSync } from 'fs';
|
|
14
|
+
import { join } from 'path';
|
|
10
15
|
|
|
11
16
|
function printBanner() {
|
|
12
17
|
console.log('');
|
|
13
|
-
console.log(chalk.
|
|
14
|
-
console.log('');
|
|
15
|
-
console.log(chalk.cyan('
|
|
16
|
-
console.log(chalk.cyan('
|
|
17
|
-
console.log(chalk.cyan('██║ ██║ ██║██████╔╝ █████╗ ██║ ██║██║ ███╗█████╗ '));
|
|
18
|
-
console.log(chalk.cyan('██║ ██║ ██║██╔═══╝ ██╔══╝ ██║ ██║██║ ██║██╔══╝ '));
|
|
19
|
-
console.log(chalk.cyan('╚██████╗██████╔╝██║ ███████╗██████╔╝╚██████╔╝███████╗'));
|
|
20
|
-
console.log(chalk.cyan(' ╚═════╝╚═════╝ ╚═╝ ╚══════╝╚═════╝ ╚═════╝╚══════╝'));
|
|
21
|
-
console.log('');
|
|
22
|
-
console.log(chalk.gray(' Customer Data Platform on the Edge · Global Edge Tracking · v2.0.2'));
|
|
23
|
-
console.log('');
|
|
24
|
-
console.log(chalk.gray('═'.repeat(68)));
|
|
18
|
+
console.log(chalk.cyan(' ╔═══════════════════════════════════════╗'));
|
|
19
|
+
console.log(chalk.cyan(' ║') + chalk.white.bold(' CDP Edge — Setup Wizard ') + chalk.cyan('║'));
|
|
20
|
+
console.log(chalk.cyan(' ║') + chalk.gray(' Configuração Guiada de Tracking ') + chalk.cyan('║'));
|
|
21
|
+
console.log(chalk.cyan(' ╚═══════════════════════════════════════╝'));
|
|
25
22
|
console.log('');
|
|
26
23
|
}
|
|
27
24
|
|
|
25
|
+
function sep(label = '') {
|
|
26
|
+
if (label) {
|
|
27
|
+
console.log('\n' + chalk.gray('── ') + chalk.cyan.bold(label) + chalk.gray(' ' + '─'.repeat(Math.max(0, 40 - label.length))));
|
|
28
|
+
} else {
|
|
29
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
28
33
|
export async function runSetupWizard(dir = '.') {
|
|
29
34
|
printBanner();
|
|
30
35
|
|
|
31
|
-
|
|
36
|
+
console.log(chalk.cyan(' Vou te fazer algumas perguntas simples.'));
|
|
37
|
+
console.log(chalk.cyan(' Não precisa saber programar — é só preencher os dados.\n'));
|
|
38
|
+
console.log(chalk.gray(' Ao final, vou gerar todos os comandos prontos para instalar.\n'));
|
|
32
39
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
console.log(chalk.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
|
|
40
|
+
// ── PASSO 1: Dados do projeto ──────────────────────────────────────────────
|
|
41
|
+
sep('1. Dados do Projeto');
|
|
36
42
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
const projeto = await inquirer.prompt([
|
|
44
|
+
{
|
|
45
|
+
type: 'input',
|
|
46
|
+
name: 'domain',
|
|
47
|
+
message: 'Qual o domínio do site? (ex: meusite.com.br)',
|
|
48
|
+
validate: v => v.trim() ? true : 'Domínio é obrigatório',
|
|
49
|
+
filter: v => v.trim().replace(/^https?:\/\//, '').replace(/\/$/, ''),
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: 'input',
|
|
53
|
+
name: 'projectName',
|
|
54
|
+
message: 'Nome do projeto (para identificação):',
|
|
55
|
+
default: answers => answers.domain.split('.')[0],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
type: 'list',
|
|
59
|
+
name: 'productType',
|
|
60
|
+
message: 'Que tipo de produto você vende?',
|
|
61
|
+
choices: [
|
|
62
|
+
{ name: 'Infoproduto / Curso Online', value: 'infoproduto' },
|
|
63
|
+
{ name: 'E-commerce físico', value: 'ecommerce' },
|
|
64
|
+
{ name: 'SaaS / Assinatura', value: 'saas' },
|
|
65
|
+
{ name: 'Serviço / Consultoria', value: 'servico' },
|
|
66
|
+
{ name: 'Lead Generation (captação)', value: 'leadgen' },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
]);
|
|
44
70
|
|
|
45
|
-
//
|
|
71
|
+
// ── PASSO 2: Plataforma de vendas ──────────────────────────────────────────
|
|
72
|
+
sep('2. Plataforma de Vendas / Checkout');
|
|
46
73
|
|
|
47
|
-
const
|
|
74
|
+
const { plataformas } = await inquirer.prompt([
|
|
48
75
|
{
|
|
49
|
-
type:
|
|
50
|
-
name:
|
|
51
|
-
message: '
|
|
76
|
+
type: 'checkbox',
|
|
77
|
+
name: 'plataformas',
|
|
78
|
+
message: 'Quais plataformas de checkout você usa? (espaço para marcar)',
|
|
52
79
|
choices: [
|
|
53
|
-
{ name: '
|
|
54
|
-
{ name: '
|
|
55
|
-
{ name: '
|
|
56
|
-
|
|
57
|
-
|
|
80
|
+
{ name: 'Hotmart', value: 'hotmart', checked: false },
|
|
81
|
+
{ name: 'Kiwify', value: 'kiwify', checked: false },
|
|
82
|
+
{ name: 'Ticto', value: 'ticto', checked: false },
|
|
83
|
+
{ name: 'Eduzz', value: 'eduzz', checked: false },
|
|
84
|
+
{ name: 'Stripe', value: 'stripe', checked: false },
|
|
85
|
+
{ name: 'Nenhuma (apenas formulário de captação)', value: 'none', checked: false },
|
|
86
|
+
],
|
|
87
|
+
validate: v => v.length > 0 ? true : 'Selecione pelo menos uma opção',
|
|
88
|
+
},
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
// ── PASSO 3: Plataformas de anúncios ───────────────────────────────────────
|
|
92
|
+
sep('3. Plataformas de Anúncios');
|
|
93
|
+
|
|
94
|
+
const { adsPlataformas } = await inquirer.prompt([
|
|
95
|
+
{
|
|
96
|
+
type: 'checkbox',
|
|
97
|
+
name: 'adsPlataformas',
|
|
98
|
+
message: 'Onde você faz anúncios? (espaço para marcar)',
|
|
99
|
+
choices: [
|
|
100
|
+
{ name: 'Meta Ads (Facebook / Instagram)', value: 'meta', checked: true },
|
|
101
|
+
{ name: 'Google Ads / YouTube', value: 'google', checked: false },
|
|
102
|
+
{ name: 'TikTok Ads', value: 'tiktok', checked: false },
|
|
103
|
+
{ name: 'Pinterest Ads', value: 'pinterest', checked: false },
|
|
104
|
+
{ name: 'LinkedIn Ads', value: 'linkedin', checked: false },
|
|
105
|
+
],
|
|
106
|
+
validate: v => v.length > 0 ? true : 'Selecione pelo menos uma plataforma',
|
|
107
|
+
},
|
|
58
108
|
]);
|
|
59
109
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
110
|
+
// ── PASSO 4: Credenciais Meta ──────────────────────────────────────────────
|
|
111
|
+
const creds = {};
|
|
112
|
+
|
|
113
|
+
if (adsPlataformas.includes('meta')) {
|
|
114
|
+
sep('4a. Credenciais — Meta Ads');
|
|
115
|
+
console.log(chalk.gray(' Onde encontrar: Meta Events Manager → Configurações → Pixel\n'));
|
|
116
|
+
|
|
117
|
+
const meta = await inquirer.prompt([
|
|
118
|
+
{
|
|
119
|
+
type: 'input',
|
|
120
|
+
name: 'pixelId',
|
|
121
|
+
message: 'Meta Pixel ID:',
|
|
122
|
+
validate: v => v.trim() ? true : 'Pixel ID é obrigatório',
|
|
123
|
+
filter: v => v.trim(),
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
type: 'password',
|
|
127
|
+
name: 'accessToken',
|
|
128
|
+
message: 'Meta Access Token (CAPI):',
|
|
129
|
+
mask: '*',
|
|
130
|
+
validate: v => v.trim() ? true : 'Access Token é obrigatório',
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
type: 'input',
|
|
134
|
+
name: 'testCode',
|
|
135
|
+
message: 'Meta Test Event Code (opcional, para homologação):',
|
|
136
|
+
default: '',
|
|
137
|
+
filter: v => v.trim(),
|
|
138
|
+
},
|
|
139
|
+
]);
|
|
140
|
+
creds.meta = meta;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (adsPlataformas.includes('google')) {
|
|
144
|
+
sep('4b. Credenciais — Google Analytics 4');
|
|
145
|
+
console.log(chalk.gray(' Onde encontrar: GA4 → Admin → Data Streams → Measurement Protocol\n'));
|
|
146
|
+
|
|
147
|
+
const google = await inquirer.prompt([
|
|
148
|
+
{
|
|
149
|
+
type: 'input',
|
|
150
|
+
name: 'measurementId',
|
|
151
|
+
message: 'GA4 Measurement ID (G-XXXXXXXX):',
|
|
152
|
+
validate: v => /^G-[A-Z0-9]+$/i.test(v.trim()) ? true : 'Formato: G-XXXXXXXX',
|
|
153
|
+
filter: v => v.trim().toUpperCase(),
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
type: 'password',
|
|
157
|
+
name: 'apiSecret',
|
|
158
|
+
message: 'GA4 API Secret:',
|
|
159
|
+
mask: '*',
|
|
160
|
+
validate: v => v.trim() ? true : 'API Secret é obrigatório',
|
|
161
|
+
},
|
|
162
|
+
]);
|
|
163
|
+
creds.google = google;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (adsPlataformas.includes('tiktok')) {
|
|
167
|
+
sep('4c. Credenciais — TikTok');
|
|
168
|
+
console.log(chalk.gray(' Onde encontrar: TikTok Events Manager → Pixel → Detalhes\n'));
|
|
169
|
+
|
|
170
|
+
const tiktok = await inquirer.prompt([
|
|
171
|
+
{
|
|
172
|
+
type: 'input',
|
|
173
|
+
name: 'pixelId',
|
|
174
|
+
message: 'TikTok Pixel ID:',
|
|
175
|
+
validate: v => v.trim() ? true : 'Pixel ID é obrigatório',
|
|
176
|
+
filter: v => v.trim(),
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
type: 'password',
|
|
180
|
+
name: 'accessToken',
|
|
181
|
+
message: 'TikTok Access Token:',
|
|
182
|
+
mask: '*',
|
|
183
|
+
validate: v => v.trim() ? true : 'Access Token é obrigatório',
|
|
184
|
+
},
|
|
185
|
+
]);
|
|
186
|
+
creds.tiktok = tiktok;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ── PASSO 5: Alertas WhatsApp ──────────────────────────────────────────────
|
|
190
|
+
sep('5. Alertas WhatsApp (opcional)');
|
|
191
|
+
console.log(chalk.gray(' Receba alertas de erros e match quality direto no WhatsApp.\n'));
|
|
192
|
+
|
|
193
|
+
const { wantsWhatsapp } = await inquirer.prompt([
|
|
194
|
+
{
|
|
195
|
+
type: 'confirm',
|
|
196
|
+
name: 'wantsWhatsapp',
|
|
197
|
+
message: 'Deseja ativar alertas via WhatsApp?',
|
|
198
|
+
default: true,
|
|
199
|
+
},
|
|
200
|
+
]);
|
|
201
|
+
|
|
202
|
+
let whatsapp = {};
|
|
203
|
+
if (wantsWhatsapp) {
|
|
204
|
+
console.log(chalk.gray('\n Como configurar: acesse callmebot.com e adicione o bot no WhatsApp.'));
|
|
205
|
+
console.log(chalk.gray(' Envie "I allow callmebot to send me messages" para +34 644 35 78 48\n'));
|
|
206
|
+
|
|
207
|
+
whatsapp = await inquirer.prompt([
|
|
208
|
+
{
|
|
209
|
+
type: 'input',
|
|
210
|
+
name: 'phone',
|
|
211
|
+
message: 'Seu número com DDI (ex: +5511999999999):',
|
|
212
|
+
validate: v => /^\+\d{10,15}$/.test(v.trim()) ? true : 'Formato: +5511999999999',
|
|
213
|
+
filter: v => v.trim(),
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
type: 'password',
|
|
217
|
+
name: 'apiKey',
|
|
218
|
+
message: 'CallMeBot API Key:',
|
|
219
|
+
mask: '*',
|
|
220
|
+
validate: v => v.trim() ? true : 'API Key é obrigatória',
|
|
221
|
+
},
|
|
222
|
+
]);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ── GERAÇÃO DOS OUTPUTS ────────────────────────────────────────────────────
|
|
226
|
+
const spinner = ora('Gerando configurações...').start();
|
|
227
|
+
await sleep(600);
|
|
228
|
+
spinner.succeed('Configurações geradas!');
|
|
229
|
+
|
|
230
|
+
const domain = projeto.domain;
|
|
231
|
+
|
|
232
|
+
// Monta lista de secrets
|
|
233
|
+
const secrets = [];
|
|
234
|
+
|
|
235
|
+
if (creds.meta) {
|
|
236
|
+
secrets.push({ key: 'META_PIXEL_ID', value: creds.meta.pixelId, platform: 'Meta' });
|
|
237
|
+
secrets.push({ key: 'META_ACCESS_TOKEN', value: creds.meta.accessToken, platform: 'Meta' });
|
|
238
|
+
if (creds.meta.testCode) {
|
|
239
|
+
secrets.push({ key: 'META_TEST_CODE', value: creds.meta.testCode, platform: 'Meta (homologação)' });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (creds.google) {
|
|
244
|
+
secrets.push({ key: 'GA4_MEASUREMENT_ID', value: creds.google.measurementId, platform: 'Google' });
|
|
245
|
+
secrets.push({ key: 'GA4_API_SECRET', value: creds.google.apiSecret, platform: 'Google' });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (creds.tiktok) {
|
|
249
|
+
secrets.push({ key: 'TIKTOK_PIXEL_ID', value: creds.tiktok.pixelId, platform: 'TikTok' });
|
|
250
|
+
secrets.push({ key: 'TIKTOK_ACCESS_TOKEN', value: creds.tiktok.accessToken, platform: 'TikTok' });
|
|
135
251
|
}
|
|
252
|
+
|
|
253
|
+
if (whatsapp.phone) {
|
|
254
|
+
secrets.push({ key: 'CALLMEBOT_PHONE', value: whatsapp.phone, platform: 'WhatsApp' });
|
|
255
|
+
secrets.push({ key: 'CALLMEBOT_API_KEY', value: whatsapp.apiKey, platform: 'WhatsApp' });
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
secrets.push({ key: 'SITE_DOMAIN', value: domain, platform: 'Worker' });
|
|
259
|
+
|
|
260
|
+
// Webhooks
|
|
261
|
+
const webhookUrls = {};
|
|
262
|
+
const webhookSecrets = [];
|
|
263
|
+
if (plataformas.includes('hotmart')) {
|
|
264
|
+
webhookUrls.hotmart = `https://${domain}/webhook/hotmart`;
|
|
265
|
+
webhookSecrets.push({ key: 'WEBHOOK_SECRET_HOTMART', platform: 'Hotmart', note: 'Gere uma senha forte qualquer' });
|
|
266
|
+
}
|
|
267
|
+
if (plataformas.includes('kiwify')) {
|
|
268
|
+
webhookUrls.kiwify = `https://${domain}/webhook/kiwify`;
|
|
269
|
+
webhookSecrets.push({ key: 'WEBHOOK_SECRET_KIWIFY', platform: 'Kiwify', note: 'Gere uma senha forte qualquer' });
|
|
270
|
+
}
|
|
271
|
+
if (plataformas.includes('ticto')) {
|
|
272
|
+
webhookUrls.ticto = `https://${domain}/webhook/ticto`;
|
|
273
|
+
webhookSecrets.push({ key: 'WEBHOOK_SECRET_TICTO', platform: 'Ticto', note: 'Chave HMAC configurada no painel Ticto' });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ── EXIBIR RESULTADO ───────────────────────────────────────────────────────
|
|
277
|
+
console.log('\n');
|
|
278
|
+
sep('CONFIGURAÇÃO PRONTA');
|
|
279
|
+
|
|
280
|
+
console.log(chalk.green.bold('\n Projeto: ') + chalk.white(projeto.projectName));
|
|
281
|
+
console.log(chalk.green.bold(' Domínio: ') + chalk.white(domain));
|
|
282
|
+
console.log(chalk.green.bold(' Produto: ') + chalk.white(projeto.productType));
|
|
283
|
+
console.log(chalk.green.bold(' Ads: ') + chalk.white(adsPlataformas.join(', ')));
|
|
284
|
+
|
|
285
|
+
// Comandos de secrets
|
|
286
|
+
sep('Comandos para configurar os secrets');
|
|
287
|
+
console.log(chalk.gray(' Execute dentro da pasta server-edge-tracker:\n'));
|
|
288
|
+
|
|
289
|
+
for (const s of secrets) {
|
|
290
|
+
const masked = s.value.length > 4 ? s.value.slice(0, 4) + '*'.repeat(Math.min(s.value.length - 4, 8)) : '****';
|
|
291
|
+
console.log(chalk.cyan(` wrangler secret put ${s.key}`));
|
|
292
|
+
console.log(chalk.gray(` # ${s.platform} — valor: ${masked}\n`));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (webhookSecrets.length > 0) {
|
|
296
|
+
console.log(chalk.yellow(' # Secrets de webhook (gere senhas e configure abaixo + no painel da plataforma):'));
|
|
297
|
+
for (const ws of webhookSecrets) {
|
|
298
|
+
console.log(chalk.cyan(` wrangler secret put ${ws.key}`));
|
|
299
|
+
console.log(chalk.gray(` # ${ws.platform}: ${ws.note}\n`));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// URLs de webhook
|
|
304
|
+
if (Object.keys(webhookUrls).length > 0) {
|
|
305
|
+
sep('URLs de Webhook para configurar nas plataformas');
|
|
306
|
+
for (const [plat, url] of Object.entries(webhookUrls)) {
|
|
307
|
+
console.log(chalk.green.bold(` ${plat.charAt(0).toUpperCase() + plat.slice(1)}:`));
|
|
308
|
+
console.log(chalk.white(` ${url}\n`));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Checklist
|
|
313
|
+
sep('Próximos passos');
|
|
314
|
+
const steps = [
|
|
315
|
+
'Executar todos os wrangler secret put acima',
|
|
316
|
+
'Executar as migrations D1 (schema.sql → migrate-v7.sql)',
|
|
317
|
+
'Executar: wrangler deploy',
|
|
318
|
+
...(Object.keys(webhookUrls).length > 0 ? ['Configurar as URLs de webhook nas plataformas de venda'] : []),
|
|
319
|
+
'Configurar Worker Route no Cloudflare: ' + domain + '/*',
|
|
320
|
+
'Testar o endpoint: https://' + domain + '/health',
|
|
321
|
+
...(adsPlataformas.includes('meta') && creds.meta?.testCode ? ['Testar eventos no Meta Events Manager com o Test Event Code'] : []),
|
|
322
|
+
];
|
|
323
|
+
|
|
324
|
+
steps.forEach((step, i) => {
|
|
325
|
+
console.log(chalk.cyan(` [${i + 1}] `) + chalk.white(step));
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// Salvar config (sem tokens reais)
|
|
329
|
+
const configOutput = {
|
|
330
|
+
generatedAt: new Date().toISOString(),
|
|
331
|
+
domain,
|
|
332
|
+
projectName: projeto.projectName,
|
|
333
|
+
productType: projeto.productType,
|
|
334
|
+
adsPlataformas,
|
|
335
|
+
checkoutPlataformas: plataformas,
|
|
336
|
+
webhookUrls,
|
|
337
|
+
secretKeys: secrets.map(s => s.key),
|
|
338
|
+
whatsappAlertsEnabled: !!whatsapp.phone,
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const outputPath = join(dir, 'cdp-edge-setup.json');
|
|
342
|
+
try {
|
|
343
|
+
writeFileSync(outputPath, JSON.stringify(configOutput, null, 2), 'utf8');
|
|
344
|
+
console.log(chalk.gray(`\n Configuração salva em: ${outputPath} (sem tokens)\n`));
|
|
345
|
+
} catch {
|
|
346
|
+
// não crítico se não conseguir salvar
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
sep();
|
|
350
|
+
console.log(chalk.green.bold('\n Setup concluído! Passe os comandos acima para o agente instalar.\n'));
|
|
136
351
|
}
|
|
137
352
|
|
|
138
353
|
function sleep(ms) {
|
|
@@ -526,7 +526,7 @@ async function syncIdentity(DB, body) {
|
|
|
526
526
|
const fp = body.fingerprint || null;
|
|
527
527
|
if (!fp || !DB) return body;
|
|
528
528
|
|
|
529
|
-
const existing = await DB.prepare(
|
|
529
|
+
const existing = await env.DB.prepare(
|
|
530
530
|
'SELECT * FROM identity_graph WHERE fingerprint = ?'
|
|
531
531
|
).bind(fp).first();
|
|
532
532
|
|
|
@@ -541,7 +541,7 @@ async function syncIdentity(DB, body) {
|
|
|
541
541
|
visit_count: (existing.visit_count || 1) + 1
|
|
542
542
|
};
|
|
543
543
|
} else {
|
|
544
|
-
await DB.prepare(`
|
|
544
|
+
await env.DB.prepare(`
|
|
545
545
|
INSERT OR IGNORE INTO identity_graph (fingerprint, fbp, fbc, ga_client_id, external_id, ttclid, first_utm)
|
|
546
546
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
547
547
|
`).bind(fp, body.fbp, body.fbc, body.ga_client_id, body.external_id, body.ttclid, JSON.stringify(body.utm || {})).run();
|
|
@@ -635,7 +635,7 @@ function buildCookieHeader(visitor, umbrellaDomain) {
|
|
|
635
635
|
async function logBehavioralEvent(DB, body, visitor, engagementScore) {
|
|
636
636
|
if (!DB) return;
|
|
637
637
|
|
|
638
|
-
await DB.prepare(`
|
|
638
|
+
await env.DB.prepare(`
|
|
639
639
|
INSERT OR REPLACE INTO behavioral_events (
|
|
640
640
|
event_id, user_id, session_id,
|
|
641
641
|
engagement_score, time_level, scroll_score, click_score, video_score, hover_score, intention_level,
|
|
@@ -972,7 +972,7 @@ CREATE INDEX IF NOT EXISTS idx_retry_scheduled ON retry_queue(scheduled_at, stat
|
|
|
972
972
|
async function logEventSuccess(DB, platform, eventId) {
|
|
973
973
|
if (!DB) return;
|
|
974
974
|
|
|
975
|
-
await DB.prepare(`
|
|
975
|
+
await env.DB.prepare(`
|
|
976
976
|
UPDATE events_log
|
|
977
977
|
SET status = 'success',
|
|
978
978
|
retry_count = 0,
|
|
@@ -988,7 +988,7 @@ async function logEventFailure(DB, platform, eventId, errorMessage, retryCount)
|
|
|
988
988
|
const maxRetries = 3;
|
|
989
989
|
const isFinalFailure = retryCount >= maxRetries;
|
|
990
990
|
|
|
991
|
-
await DB.prepare(`
|
|
991
|
+
await env.DB.prepare(`
|
|
992
992
|
UPDATE events_log
|
|
993
993
|
SET status = ?,
|
|
994
994
|
retry_count = ?,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cdp-edge",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.20.0",
|
|
4
4
|
"description": "CDP Edge - Quantum Tracking - Sistema multi-agente para tracking digital Cloudflare Native (Workers + D1)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -28,7 +28,12 @@
|
|
|
28
28
|
"test:unit:hash": "node tests/unit/hashing.test.js",
|
|
29
29
|
"test:unit:dedup": "node tests/unit/deduplication.test.js",
|
|
30
30
|
"test:unit:payload": "node tests/unit/payload-validation.test.js",
|
|
31
|
-
"test:all": "npm run test:unit"
|
|
31
|
+
"test:all": "npm run test:unit",
|
|
32
|
+
"test:integration": "cd tests/integration && npx vitest run",
|
|
33
|
+
"agents:check": "node scripts/validate-agents.js",
|
|
34
|
+
"agents:sync": "node scripts/sync-agents.js",
|
|
35
|
+
"agents:sync:list": "node scripts/sync-agents.js --list",
|
|
36
|
+
"agents:sync:all": "node scripts/sync-agents.js --apply-all"
|
|
32
37
|
},
|
|
33
38
|
"keywords": [
|
|
34
39
|
"pixel",
|