@mahe_pkm/buzl-capi 0.1.2
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/Buzl_GoogleAppsScript_Template.gs +948 -0
- package/CHANGELOG.md +83 -0
- package/LICENSE +21 -0
- package/README.md +253 -0
- package/assets/js/buzl-tracking.js +390 -0
- package/bin/cli.js +155 -0
- package/package.json +54 -0
- package/src/cli/terminal.js +388 -0
- package/src/core/injector.js +321 -0
- package/src/core/rollback.js +281 -0
- package/src/core/scanner.js +506 -0
- package/src/core/tester.js +357 -0
- package/src/gui/public/app.js +942 -0
- package/src/gui/public/index.html +634 -0
- package/src/gui/public/style.css +1130 -0
- package/src/gui/server.js +232 -0
- package/src/index.js +47 -0
- package/src/templates/GoogleAppsScript.gs +948 -0
- package/src/templates/buzl-tracking.js +475 -0
- package/src/templates/gtm-meta-snippets.js +149 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* =========================================================================
|
|
3
|
+
* BUZL UNIVERSAL TRACKING & FORM DISPATCHER RUNTIME
|
|
4
|
+
* Headless SDK for GTM, Meta Pixel/CAPI, Google Sheets, & Zoho CRM
|
|
5
|
+
* =========================================================================
|
|
6
|
+
*/
|
|
7
|
+
(function (root, factory) {
|
|
8
|
+
if (typeof define === 'function' && define.amd) {
|
|
9
|
+
define([], factory);
|
|
10
|
+
} else if (typeof module === 'object' && module.exports) {
|
|
11
|
+
module.exports = factory();
|
|
12
|
+
} else {
|
|
13
|
+
root.BuzlTracker = factory();
|
|
14
|
+
}
|
|
15
|
+
}(typeof self !== 'undefined' ? self : this, function () {
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
var VERSION = '1.0.0';
|
|
19
|
+
|
|
20
|
+
function win() { return typeof window !== 'undefined' ? window : {}; }
|
|
21
|
+
function doc() { return typeof document !== 'undefined' ? document : {}; }
|
|
22
|
+
function nav() { return typeof navigator !== 'undefined' ? navigator : {}; }
|
|
23
|
+
|
|
24
|
+
function getCookie(name) {
|
|
25
|
+
try {
|
|
26
|
+
var m = doc().cookie.match(new RegExp('(?:^|; )' + name.replace(/([.$?*|{}()\[\]\\\/+^])/g, '\\$1') + '=([^;]*)'));
|
|
27
|
+
return m ? decodeURIComponent(m[1]) : '';
|
|
28
|
+
} catch (e) { return ''; }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function sessionGet(key) {
|
|
32
|
+
try { return (win().sessionStorage && win().sessionStorage.getItem(key)) || ''; }
|
|
33
|
+
catch (e) { return ''; }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function sessionSet(key, val) {
|
|
37
|
+
try { if (win().sessionStorage) win().sessionStorage.setItem(key, val); }
|
|
38
|
+
catch (e) {}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getFbclid() {
|
|
42
|
+
try {
|
|
43
|
+
return new URLSearchParams(win().location.search).get('fbclid') || sessionGet('fbclid') || '';
|
|
44
|
+
} catch (e) { return ''; }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getFbc() {
|
|
48
|
+
var fbc = getCookie('_fbc');
|
|
49
|
+
if (fbc) return fbc;
|
|
50
|
+
var fbclid = getFbclid();
|
|
51
|
+
return fbclid ? 'fb.1.' + Date.now() + '.' + fbclid : '';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getFbp() {
|
|
55
|
+
return getCookie('_fbp') || '';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getDomainLabel() {
|
|
59
|
+
try {
|
|
60
|
+
var host = (win().location.hostname || '').trim().toLowerCase();
|
|
61
|
+
host = host.replace(/^www\./, '');
|
|
62
|
+
host = host.replace(/[^a-z0-9_-]/g, '-').slice(0, 63);
|
|
63
|
+
return host || 'website';
|
|
64
|
+
} catch (e) {
|
|
65
|
+
return 'website';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function generateLeadId() {
|
|
70
|
+
var uuid = '';
|
|
71
|
+
try {
|
|
72
|
+
if (win().crypto && win().crypto.randomUUID) {
|
|
73
|
+
uuid = win().crypto.randomUUID();
|
|
74
|
+
} else if (win().crypto && win().crypto.getRandomValues) {
|
|
75
|
+
var b = win().crypto.getRandomValues(new Uint8Array(16));
|
|
76
|
+
b[6] = (b[6] & 0x0f) | 0x40;
|
|
77
|
+
b[8] = (b[8] & 0x3f) | 0x80;
|
|
78
|
+
var h = [];
|
|
79
|
+
for (var i = 0; i < 16; i++) h.push((b[i] + 0x100).toString(16).slice(1));
|
|
80
|
+
uuid = h[0] + h[1] + h[2] + h[3] + '-' + h[4] + h[5] + '-' + h[6] + h[7] + '-' + h[8] + h[9] + '-' + h[10] + h[11] + h[12] + h[13] + h[14] + h[15];
|
|
81
|
+
}
|
|
82
|
+
} catch (e) {}
|
|
83
|
+
|
|
84
|
+
if (!uuid) {
|
|
85
|
+
uuid = Date.now().toString(36) + '-' + Math.random().toString(36).substring(2, 10);
|
|
86
|
+
}
|
|
87
|
+
return getDomainLabel() + '-' + uuid;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function captureUtm() {
|
|
91
|
+
try {
|
|
92
|
+
var params = new URLSearchParams(win().location.search);
|
|
93
|
+
var keys = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid'];
|
|
94
|
+
keys.forEach(function (k) {
|
|
95
|
+
var v = params.get(k);
|
|
96
|
+
if (v) sessionSet(k, v);
|
|
97
|
+
});
|
|
98
|
+
} catch (e) {}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getUtm() {
|
|
102
|
+
return {
|
|
103
|
+
source: sessionGet('utm_source'),
|
|
104
|
+
medium: sessionGet('utm_medium'),
|
|
105
|
+
campaign: sessionGet('utm_campaign'),
|
|
106
|
+
term: sessionGet('utm_term'),
|
|
107
|
+
content: sessionGet('utm_content'),
|
|
108
|
+
fbclid: sessionGet('fbclid'),
|
|
109
|
+
gclid: sessionGet('gclid')
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function createTracker(userConfig) {
|
|
114
|
+
var cfg = userConfig || {};
|
|
115
|
+
var googleSheetUrl = cfg.googleSheetUrl || '';
|
|
116
|
+
var gtmEvent = cfg.gtmEvent || 'lead_form_submitted';
|
|
117
|
+
var safetyTimeoutMs = typeof cfg.safetyTimeoutMs === 'number' ? cfg.safetyTimeoutMs : 800;
|
|
118
|
+
var zoho = cfg.zoho || {};
|
|
119
|
+
var buzlCapi = cfg.buzlCapi || {};
|
|
120
|
+
var whatsapp = cfg.whatsapp || {};
|
|
121
|
+
|
|
122
|
+
captureUtm();
|
|
123
|
+
|
|
124
|
+
/* 1. Push to GTM dataLayer */
|
|
125
|
+
function pushGTM(leadId, payload) {
|
|
126
|
+
if (!cfg.enableGTM) return;
|
|
127
|
+
if (!win().dataLayer) win().dataLayer = [];
|
|
128
|
+
try {
|
|
129
|
+
win().dataLayer.push({
|
|
130
|
+
event: gtmEvent,
|
|
131
|
+
leadId: leadId,
|
|
132
|
+
contact: payload.contact,
|
|
133
|
+
utm: payload.utm,
|
|
134
|
+
pageUrl: win().location.href
|
|
135
|
+
});
|
|
136
|
+
} catch (e) {
|
|
137
|
+
console.warn('[BuzlTracker] GTM push error', e);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* 2. Track with Meta Pixel */
|
|
142
|
+
function trackMeta(leadId, payload) {
|
|
143
|
+
if (!cfg.enableMeta || typeof win().fbq !== 'function') return;
|
|
144
|
+
try {
|
|
145
|
+
var customData = {
|
|
146
|
+
content_name: payload.source || 'Lead Form',
|
|
147
|
+
buzl_lead_id: leadId,
|
|
148
|
+
utm_source: payload.utm.source,
|
|
149
|
+
utm_medium: payload.utm.medium,
|
|
150
|
+
utm_campaign: payload.utm.campaign
|
|
151
|
+
};
|
|
152
|
+
win().fbq('trackCustom', 'formSubmitted', customData, { eventID: leadId });
|
|
153
|
+
if (cfg.trackMetaLeadEvent) {
|
|
154
|
+
win().fbq('track', 'Lead', customData, { eventID: leadId });
|
|
155
|
+
}
|
|
156
|
+
} catch (e) {
|
|
157
|
+
console.warn('[BuzlTracker] Meta track error', e);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* 3. Sync to Google Sheets */
|
|
162
|
+
function syncToGoogleSheet(leadId, payload) {
|
|
163
|
+
if (!googleSheetUrl) return Promise.resolve();
|
|
164
|
+
return new Promise(function (resolve) {
|
|
165
|
+
var sheetPayload = {
|
|
166
|
+
timestamp: new Date().toISOString(),
|
|
167
|
+
leadId: leadId,
|
|
168
|
+
name: payload.contact.name || '',
|
|
169
|
+
phone: payload.contact.phone || '',
|
|
170
|
+
email: payload.contact.email || '',
|
|
171
|
+
location: payload.contact.location || cfg.siteLocation || '',
|
|
172
|
+
siteLocation: cfg.siteLocation || '',
|
|
173
|
+
source: payload.source || 'Website Form',
|
|
174
|
+
utm: payload.utm,
|
|
175
|
+
fbclid: getFbclid(),
|
|
176
|
+
fbc: getFbc(),
|
|
177
|
+
fbp: getFbp(),
|
|
178
|
+
eventSourceUrl: win().location.href,
|
|
179
|
+
userAgent: nav().userAgent || '',
|
|
180
|
+
rawFields: payload.rawFields || {}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// Attach dynamic fields directly for top-level access
|
|
184
|
+
if (payload.rawFields) {
|
|
185
|
+
for (var rk in payload.rawFields) {
|
|
186
|
+
if (sheetPayload[rk] === undefined) {
|
|
187
|
+
sheetPayload[rk] = payload.rawFields[rk];
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
win().fetch(googleSheetUrl, {
|
|
194
|
+
method: 'POST',
|
|
195
|
+
mode: 'no-cors',
|
|
196
|
+
headers: { 'Content-Type': 'application/json' },
|
|
197
|
+
body: JSON.stringify(sheetPayload),
|
|
198
|
+
keepalive: true
|
|
199
|
+
}).then(function () { resolve({ status: 'sent' }); })
|
|
200
|
+
.catch(function () { resolve({ status: 'error' }); });
|
|
201
|
+
} catch (e) {
|
|
202
|
+
resolve({ status: 'exception' });
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/* 4. Sync to Zoho CRM Web-to-Lead */
|
|
208
|
+
function syncToZoho(payload) {
|
|
209
|
+
if (!zoho.endpoint || !zoho.xnQsjsdp) return Promise.resolve();
|
|
210
|
+
return new Promise(function (resolve) {
|
|
211
|
+
try {
|
|
212
|
+
var fd = new FormData();
|
|
213
|
+
fd.append('xnQsjsdp', zoho.xnQsjsdp);
|
|
214
|
+
if (zoho.xmIwtLD) fd.append('xmIwtLD', zoho.xmIwtLD);
|
|
215
|
+
fd.append('actionType', zoho.actionType || 'TGVhZHM=');
|
|
216
|
+
|
|
217
|
+
var fMap = zoho.fields || {};
|
|
218
|
+
var nameField = fMap.lastName || 'Last Name';
|
|
219
|
+
var phoneField = fMap.phone || 'Phone';
|
|
220
|
+
var emailField = fMap.email || 'Email';
|
|
221
|
+
var locationField = fMap.location || 'City';
|
|
222
|
+
var sourceField = fMap.source || 'Lead Source';
|
|
223
|
+
|
|
224
|
+
if (payload.contact.name) fd.append(nameField, payload.contact.name);
|
|
225
|
+
if (payload.contact.phone) fd.append(phoneField, payload.contact.phone);
|
|
226
|
+
if (payload.contact.email) fd.append(emailField, payload.contact.email);
|
|
227
|
+
if (payload.contact.location) fd.append(locationField, payload.contact.location);
|
|
228
|
+
if (payload.source) fd.append(sourceField, payload.source);
|
|
229
|
+
|
|
230
|
+
if (nav().sendBeacon && nav().sendBeacon(zoho.endpoint, fd)) {
|
|
231
|
+
resolve({ status: 'beacon_sent' });
|
|
232
|
+
} else {
|
|
233
|
+
win().fetch(zoho.endpoint, { method: 'POST', body: fd, mode: 'no-cors', keepalive: true })
|
|
234
|
+
.then(function () { resolve({ status: 'sent' }); })
|
|
235
|
+
.catch(function () { resolve({ status: 'error' }); });
|
|
236
|
+
}
|
|
237
|
+
} catch (e) {
|
|
238
|
+
resolve({ status: 'exception' });
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* 5. Sync to Server-Side Buzl CAPI */
|
|
244
|
+
function syncToBuzlCAPI(leadId, payload) {
|
|
245
|
+
if (!buzlCapi.endpoint || !buzlCapi.authUser) return Promise.resolve();
|
|
246
|
+
return new Promise(function (resolve) {
|
|
247
|
+
try {
|
|
248
|
+
var capiData = {
|
|
249
|
+
leadId: leadId,
|
|
250
|
+
domain: getDomainLabel(),
|
|
251
|
+
eventName: 'Lead',
|
|
252
|
+
eventTime: Math.floor(Date.now() / 1000),
|
|
253
|
+
actionSource: 'website',
|
|
254
|
+
eventSourceUrl: win().location.href,
|
|
255
|
+
contact: payload.contact,
|
|
256
|
+
source: payload.source,
|
|
257
|
+
fbc: getFbc(),
|
|
258
|
+
fbp: getFbp(),
|
|
259
|
+
fbclid: getFbclid(),
|
|
260
|
+
userAgent: nav().userAgent || '',
|
|
261
|
+
utm: payload.utm
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
win().fetch(buzlCapi.endpoint, {
|
|
265
|
+
method: 'POST',
|
|
266
|
+
headers: {
|
|
267
|
+
'Content-Type': 'application/json',
|
|
268
|
+
'Authorization': 'Basic ' + win().btoa(buzlCapi.authUser + ':' + (buzlCapi.authPass || ''))
|
|
269
|
+
},
|
|
270
|
+
body: JSON.stringify(capiData),
|
|
271
|
+
keepalive: true
|
|
272
|
+
}).then(function () { resolve({ status: 'capi_sent' }); })
|
|
273
|
+
.catch(function () { resolve({ status: 'capi_error' }); });
|
|
274
|
+
} catch (e) {
|
|
275
|
+
resolve({ status: 'capi_exception' });
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/* Extract contact inputs from a Form element */
|
|
281
|
+
function extractFormFields(form) {
|
|
282
|
+
var data = {};
|
|
283
|
+
var elements = form.elements || [];
|
|
284
|
+
for (var i = 0; i < elements.length; i++) {
|
|
285
|
+
var el = elements[i];
|
|
286
|
+
if (!el.name || el.type === 'submit' || el.type === 'button') continue;
|
|
287
|
+
data[el.name] = el.value;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Semantic inference
|
|
291
|
+
var name = data.name || data.fullName || data.bizName || data['first-name'] || data['Last Name'] || '';
|
|
292
|
+
var phone = data.phone || data.mobile || data.tel || data.bizPhone || data['Phone'] || '';
|
|
293
|
+
var email = data.email || data.mail || data['Email'] || '';
|
|
294
|
+
var location = data.location || data.city || data.bizLocation || data['City'] || cfg.siteLocation || '';
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
contact: { name: name, phone: phone, email: email, location: location },
|
|
298
|
+
rawFields: data
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/* Main Submit Handler */
|
|
303
|
+
function submitLead(opts) {
|
|
304
|
+
opts = opts || {};
|
|
305
|
+
var leadId = generateLeadId();
|
|
306
|
+
var utm = getUtm();
|
|
307
|
+
var contact = opts.contact || {};
|
|
308
|
+
var source = opts.source || 'Website Form';
|
|
309
|
+
var payload = {
|
|
310
|
+
leadId: leadId,
|
|
311
|
+
contact: contact,
|
|
312
|
+
source: source,
|
|
313
|
+
utm: utm,
|
|
314
|
+
rawFields: opts.rawFields || {}
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// 1. GTM & Meta client triggers
|
|
318
|
+
pushGTM(leadId, payload);
|
|
319
|
+
trackMeta(leadId, payload);
|
|
320
|
+
|
|
321
|
+
// 2. Race async endpoints against safety timeout
|
|
322
|
+
var safetyTimer = new Promise(function (res) { setTimeout(res, safetyTimeoutMs); });
|
|
323
|
+
var networkPromises = Promise.all([
|
|
324
|
+
syncToGoogleSheet(leadId, payload),
|
|
325
|
+
syncToZoho(payload),
|
|
326
|
+
syncToBuzlCAPI(leadId, payload)
|
|
327
|
+
]);
|
|
328
|
+
|
|
329
|
+
return Promise.race([networkPromises, safetyTimer]).then(function () {
|
|
330
|
+
// Redirection or follow-up
|
|
331
|
+
if (whatsapp.number && opts.redirect !== false) {
|
|
332
|
+
var textTmpl = whatsapp.template || 'Hi, I submitted an inquiry from {name} in {location}.';
|
|
333
|
+
var msg = textTmpl
|
|
334
|
+
.replace(/{name}/g, contact.name || 'my business')
|
|
335
|
+
.replace(/{location}/g, contact.location || '')
|
|
336
|
+
.replace(/{phone}/g, contact.phone || '');
|
|
337
|
+
var waUrl = 'https://wa.me/' + whatsapp.number + '?text=' + encodeURIComponent(msg);
|
|
338
|
+
win().location.href = waUrl;
|
|
339
|
+
}
|
|
340
|
+
return { leadId: leadId, success: true };
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/* Auto-bind to forms */
|
|
345
|
+
function autoBindForms() {
|
|
346
|
+
var forms = doc().querySelectorAll('form[data-buzl-track], form:not([data-buzl-ignore])');
|
|
347
|
+
forms.forEach(function (f) {
|
|
348
|
+
if (f._buzlBound) return;
|
|
349
|
+
f._buzlBound = true;
|
|
350
|
+
f.addEventListener('submit', function (e) {
|
|
351
|
+
// If form already validated or native
|
|
352
|
+
var extracted = extractFormFields(f);
|
|
353
|
+
var formSource = f.getAttribute('data-buzl-source') || f.getAttribute('id') || 'Form Submission';
|
|
354
|
+
submitLead({
|
|
355
|
+
contact: extracted.contact,
|
|
356
|
+
rawFields: extracted.rawFields,
|
|
357
|
+
source: formSource,
|
|
358
|
+
redirect: f.getAttribute('data-buzl-no-redirect') !== 'true'
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (doc().readyState === 'loading') {
|
|
365
|
+
doc().addEventListener('DOMContentLoaded', autoBindForms);
|
|
366
|
+
} else {
|
|
367
|
+
autoBindForms();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
version: VERSION,
|
|
372
|
+
submitLead: submitLead,
|
|
373
|
+
getUtm: getUtm,
|
|
374
|
+
getLeadId: generateLeadId,
|
|
375
|
+
autoBindForms: autoBindForms
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Global auto-init if config object is defined on window
|
|
380
|
+
var Tracker = {
|
|
381
|
+
version: VERSION,
|
|
382
|
+
init: createTracker
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
if (typeof win().__BUZL_CONFIG__ !== 'undefined') {
|
|
386
|
+
win().buzl = Tracker.init(win().__BUZL_CONFIG__);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return Tracker;
|
|
390
|
+
}));
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ============================================================================
|
|
5
|
+
* BUZL TRACKER & CAPI CLI
|
|
6
|
+
* Primary binary entry point for `npx buzl-tracker` and `npx buzl-capi`
|
|
7
|
+
*
|
|
8
|
+
* Copyright (c) 2026 Buzl Digital Solutions
|
|
9
|
+
* Licensed under the MIT License
|
|
10
|
+
* ============================================================================
|
|
11
|
+
*
|
|
12
|
+
* CLI Architecture:
|
|
13
|
+
* - Positional path argument: target project folder (defaults to process.cwd())
|
|
14
|
+
* - Flags:
|
|
15
|
+
* --gui, -g Start zero-dependency Web GUI daemon on port 3333
|
|
16
|
+
* --backup, -b [name] Create a point-in-time snapshot backup
|
|
17
|
+
* --list-backups List all saved backups for target project
|
|
18
|
+
* --restore, -r [name] Restore specified backup or latest snapshot
|
|
19
|
+
* --uninstall, -u Cleanly remove all injected tracking tags from HTML
|
|
20
|
+
* --help, -h Display detailed command reference & usage manual
|
|
21
|
+
*
|
|
22
|
+
* Flow:
|
|
23
|
+
* - If `--gui` is specified: Starts HTTP server and opens browser dashboard.
|
|
24
|
+
* - Otherwise: Launches interactive ANSI terminal wizard with step-by-step prompts.
|
|
25
|
+
* ============================================================================
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const path = require('path');
|
|
29
|
+
const pkg = require('../package.json');
|
|
30
|
+
const { runTerminalWizard } = require('../src/cli/terminal');
|
|
31
|
+
const { startGuiServer } = require('../src/gui/server');
|
|
32
|
+
const { restoreBackup, restoreLatestBackup, listBackups, manualBackup } = require('../src/core/rollback');
|
|
33
|
+
const { removeTracking } = require('../src/core/injector');
|
|
34
|
+
const { scanProject } = require('../src/core/scanner');
|
|
35
|
+
|
|
36
|
+
// Extract CLI arguments
|
|
37
|
+
const args = process.argv.slice(2);
|
|
38
|
+
|
|
39
|
+
// Check for explicit directory argument, or fallback to current working directory
|
|
40
|
+
const positionalArgs = args.filter(a => !a.startsWith('-'));
|
|
41
|
+
const rootDir = positionalArgs.length > 0 ? path.resolve(positionalArgs[0]) : process.cwd();
|
|
42
|
+
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// COMMAND: --help / -h (Display Help Manual)
|
|
45
|
+
// ============================================================================
|
|
46
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
47
|
+
console.log(`
|
|
48
|
+
⚡ BUZL TRACKING & FORM DISPATCHER CLI (v${pkg.version})
|
|
49
|
+
|
|
50
|
+
USAGE:
|
|
51
|
+
$ npx buzl-tracker [dir] Launch interactive terminal wizard
|
|
52
|
+
$ npx buzl-tracker [dir] --gui Launch local web GUI on http://localhost:3333
|
|
53
|
+
$ npx buzl-tracker [dir] --backup [name] Create a named snapshot backup
|
|
54
|
+
$ npx buzl-tracker [dir] --list-backups List all saved backups on disk
|
|
55
|
+
$ npx buzl-tracker [dir] --restore [name] Restore a specific backup or latest snapshot
|
|
56
|
+
$ npx buzl-tracker [dir] --uninstall Cleanly remove all tracking tags from site
|
|
57
|
+
$ npx buzl-tracker --help Show this help reference manual
|
|
58
|
+
|
|
59
|
+
ARGUMENTS:
|
|
60
|
+
[dir] Target website directory (defaults to current working directory)
|
|
61
|
+
|
|
62
|
+
OPTIONS:
|
|
63
|
+
-g, --gui Launch interactive browser GUI dashboard (port 3333)
|
|
64
|
+
-b, --backup [name] Create an immutable timestamped backup before modifications
|
|
65
|
+
-r, --restore [name] Revert HTML files to a previous snapshot or 'latest'
|
|
66
|
+
-u, --uninstall Strip GTM, Meta Pixel, runtime scripts, and form hooks
|
|
67
|
+
-h, --help Display usage guide and command summary
|
|
68
|
+
|
|
69
|
+
KEY CAPABILITIES:
|
|
70
|
+
✔ Multi-Platform Tracking: Google Tag Manager (GTM), Meta Pixel & CAPI, Google Sheets CRM, Zoho CRM
|
|
71
|
+
✔ Multi-Page Site Scanner: Resolves relative script paths across any nested subdirectory depth
|
|
72
|
+
✔ Universal WhatsApp Auto-Fetch: Discovers numbers from buttons & forms and builds pre-filled routing
|
|
73
|
+
✔ Google Sheets Multi-Tab CRM Engine: Forward layout (Handled By & Comments next to Lead Stage), 12-hour dates,
|
|
74
|
+
Buzl Navy Blue (#1E4E9E) headers, full-row conditional colors, and auto-pruning empty team tabs
|
|
75
|
+
✔ 100% Zero-Risk Rollback: Automated snapshot backups prior to every file injection
|
|
76
|
+
`);
|
|
77
|
+
process.exit(0);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ============================================================================
|
|
81
|
+
// COMMAND: --list-backups (Inspect Saved Snapshots)
|
|
82
|
+
// ============================================================================
|
|
83
|
+
if (args.includes('--list-backups')) {
|
|
84
|
+
const backups = listBackups(rootDir);
|
|
85
|
+
console.log(`\n💾 Saved Backups on disk for: ${rootDir}`);
|
|
86
|
+
if (backups.length === 0) {
|
|
87
|
+
console.log(' No backups found.');
|
|
88
|
+
} else {
|
|
89
|
+
backups.forEach((b, i) => {
|
|
90
|
+
console.log(` [${i + 1}] "${b.name}" (${b.dirName}) - ${b.timestamp} [${b.filesCount} file(s)]`);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
console.log('');
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ============================================================================
|
|
98
|
+
// COMMAND: --backup / -b (Create Snapshot)
|
|
99
|
+
// ============================================================================
|
|
100
|
+
const backupIdx = args.findIndex(a => a === '--backup' || a === '-b');
|
|
101
|
+
if (backupIdx !== -1) {
|
|
102
|
+
const customName = args[backupIdx + 1] && !args[backupIdx + 1].startsWith('-') ? args[backupIdx + 1] : '';
|
|
103
|
+
console.log(`💾 Creating snapshot backup ${customName ? `"${customName}"` : ''}...`);
|
|
104
|
+
const result = manualBackup(rootDir, customName);
|
|
105
|
+
if (result.success) {
|
|
106
|
+
console.log(`✔ ${result.message}`);
|
|
107
|
+
} else {
|
|
108
|
+
console.error(`✖ ${result.message}`);
|
|
109
|
+
}
|
|
110
|
+
process.exit(result.success ? 0 : 1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ============================================================================
|
|
114
|
+
// COMMAND: --restore / --rollback / -r (Revert Site to Snapshot)
|
|
115
|
+
// ============================================================================
|
|
116
|
+
const restoreIdx = args.findIndex(a => a === '--restore' || a === '--rollback' || a === '-r');
|
|
117
|
+
if (restoreIdx !== -1) {
|
|
118
|
+
const targetName = args[restoreIdx + 1] && !args[restoreIdx + 1].startsWith('-') ? args[restoreIdx + 1] : 'latest';
|
|
119
|
+
console.log(`↺ Attempting to restore backup "${targetName}"...`);
|
|
120
|
+
const result = restoreBackup(rootDir, targetName);
|
|
121
|
+
if (result.success) {
|
|
122
|
+
console.log(`✔ ${result.message}`);
|
|
123
|
+
} else {
|
|
124
|
+
console.error(`✖ ${result.message}`);
|
|
125
|
+
}
|
|
126
|
+
process.exit(result.success ? 0 : 1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ============================================================================
|
|
130
|
+
// COMMAND: --uninstall / -u (Cleanly Strip All Injected Tracking)
|
|
131
|
+
// ============================================================================
|
|
132
|
+
if (args.includes('--uninstall') || args.includes('-u')) {
|
|
133
|
+
console.log('🧹 Cleanly removing all injected tracking from HTML files...');
|
|
134
|
+
const scan = scanProject(rootDir);
|
|
135
|
+
const htmlFilePaths = scan.files.map(f => f.filePath);
|
|
136
|
+
const result = removeTracking(rootDir, htmlFilePaths);
|
|
137
|
+
if (result.success) {
|
|
138
|
+
console.log(`✔ ${result.message}`);
|
|
139
|
+
} else {
|
|
140
|
+
console.error(`✖ ${result.message}`);
|
|
141
|
+
}
|
|
142
|
+
process.exit(result.success ? 0 : 1);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ============================================================================
|
|
146
|
+
// MODE: GUI Server vs Interactive Terminal Wizard
|
|
147
|
+
// ============================================================================
|
|
148
|
+
if (args.includes('--gui') || args.includes('-g')) {
|
|
149
|
+
// Start local web GUI server on port 3333
|
|
150
|
+
console.log(`⚡ Launching Buzl Tracker Web GUI for: ${rootDir}`);
|
|
151
|
+
startGuiServer(rootDir);
|
|
152
|
+
} else {
|
|
153
|
+
// Start interactive terminal wizard with step-by-step CLI prompts
|
|
154
|
+
runTerminalWizard(rootDir);
|
|
155
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mahe_pkm/buzl-capi",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "All-in-one CLI & GUI for automated GTM, Meta Pixel/CAPI, Google Sheets Form Sync, and Zoho CRM injection with self-testing.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"buzl-capi": "bin/cli.js",
|
|
8
|
+
"buzl-tracker": "bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/mahe-pkm/Buzl_CAPI.git"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/mahe-pkm/Buzl_CAPI/issues"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/mahe-pkm/Buzl_CAPI#readme",
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"start": "node bin/cli.js",
|
|
23
|
+
"gui": "node bin/cli.js --gui",
|
|
24
|
+
"test": "node tests/injector.test.js && node tests/multipage.test.js && node tests/whatsapp-autofetch.test.js && node tests/gas-template.test.js"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"bin",
|
|
28
|
+
"src",
|
|
29
|
+
"assets",
|
|
30
|
+
"Buzl_GoogleAppsScript_Template.gs",
|
|
31
|
+
"README.md",
|
|
32
|
+
"CHANGELOG.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
35
|
+
"keywords": [
|
|
36
|
+
"buzl",
|
|
37
|
+
"gtm",
|
|
38
|
+
"meta-pixel",
|
|
39
|
+
"meta-capi",
|
|
40
|
+
"google-sheets",
|
|
41
|
+
"google-apps-script",
|
|
42
|
+
"zoho-crm",
|
|
43
|
+
"form-sync",
|
|
44
|
+
"landing-page",
|
|
45
|
+
"lead-capture",
|
|
46
|
+
"crm-engine",
|
|
47
|
+
"conversion-tracking"
|
|
48
|
+
],
|
|
49
|
+
"author": "Buzl Digital Solutions",
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=16.0.0"
|
|
53
|
+
}
|
|
54
|
+
}
|