@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.
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Lightweight Local Web GUI Server
3
+ * Zero dependencies — built purely with Node.js built-ins
4
+ */
5
+ const http = require('http');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const { exec } = require('child_process');
9
+ const { scanProject } = require('../core/scanner');
10
+ const { applyInjection, removeTracking, removeService } = require('../core/injector');
11
+ const { runVerification, testDispatch, testIndividualForm } = require('../core/tester');
12
+ const { restoreBackup, restoreLatestBackup, deleteBackup, listBackups, manualBackup } = require('../core/rollback');
13
+
14
+ const PUBLIC_DIR = path.join(__dirname, 'public');
15
+
16
+ const MIME_TYPES = {
17
+ '.html': 'text/html',
18
+ '.css': 'text/css',
19
+ '.js': 'application/javascript',
20
+ '.json': 'application/json',
21
+ '.svg': 'image/svg+xml'
22
+ };
23
+
24
+ function openBrowser(url) {
25
+ const start = process.platform === 'darwin' ? 'open' :
26
+ process.platform === 'win32' ? 'start ""' : 'xdg-open';
27
+ exec(`${start} "${url}"`, (err) => {
28
+ if (err) {
29
+ console.log(`Could not automatically open browser. Please open: ${url}`);
30
+ }
31
+ });
32
+ }
33
+
34
+ function parseJsonBody(req) {
35
+ return new Promise((resolve, reject) => {
36
+ let body = '';
37
+ req.on('data', chunk => { body += chunk.toString(); });
38
+ req.on('end', () => {
39
+ try {
40
+ resolve(body ? JSON.parse(body) : {});
41
+ } catch (e) {
42
+ reject(e);
43
+ }
44
+ });
45
+ });
46
+ }
47
+
48
+ function startGuiServer(rootDir, port = 3333) {
49
+ const server = http.createServer(async (req, res) => {
50
+ const parsedUrl = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
51
+ const pathname = parsedUrl.pathname;
52
+
53
+ // Set CORS headers
54
+ res.setHeader('Access-Control-Allow-Origin', '*');
55
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
56
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
57
+
58
+ if (req.method === 'OPTIONS') {
59
+ res.writeHead(204);
60
+ res.end();
61
+ return;
62
+ }
63
+
64
+ try {
65
+ // 1. API: Scan project
66
+ if (pathname === '/api/scan' && req.method === 'GET') {
67
+ const scan = scanProject(rootDir);
68
+ res.writeHead(200, { 'Content-Type': 'application/json' });
69
+ res.end(JSON.stringify(scan));
70
+ return;
71
+ }
72
+
73
+ // 1.5. API: Live State of Site
74
+ if (pathname === '/api/live-state' && req.method === 'GET') {
75
+ const scan = scanProject(rootDir);
76
+ res.writeHead(200, { 'Content-Type': 'application/json' });
77
+ res.end(JSON.stringify(scan.liveState));
78
+ return;
79
+ }
80
+
81
+ // 2. API: Get Apps Script Code
82
+ if (pathname === '/api/apps-script' && req.method === 'GET') {
83
+ const gsPath = path.join(__dirname, '..', 'templates', 'GoogleAppsScript.gs');
84
+ const code = fs.readFileSync(gsPath, 'utf8');
85
+ res.writeHead(200, { 'Content-Type': 'application/json' });
86
+ res.end(JSON.stringify({ code }));
87
+ return;
88
+ }
89
+
90
+ // 3. API: Inject & Run Self-Tests
91
+ if (pathname === '/api/inject' && req.method === 'POST') {
92
+ const config = await parseJsonBody(req);
93
+ const scan = scanProject(rootDir);
94
+ const htmlFilePaths = scan.files.map(f => f.filePath);
95
+
96
+ if (htmlFilePaths.length === 0) {
97
+ res.writeHead(400, { 'Content-Type': 'application/json' });
98
+ res.end(JSON.stringify({ success: false, message: 'No HTML files found to inject.' }));
99
+ return;
100
+ }
101
+
102
+ const injectResult = applyInjection(rootDir, htmlFilePaths, config);
103
+ const testReport = await runVerification(rootDir, htmlFilePaths, config);
104
+
105
+ res.writeHead(200, { 'Content-Type': 'application/json' });
106
+ res.end(JSON.stringify({
107
+ success: true,
108
+ injectResult,
109
+ testReport
110
+ }));
111
+ return;
112
+ }
113
+
114
+ // 4. API: Clean Uninstall / Remove All Tracking
115
+ if (pathname === '/api/remove-tracking' && req.method === 'POST') {
116
+ const scan = scanProject(rootDir);
117
+ const htmlFilePaths = scan.files.map(f => f.filePath);
118
+ const result = removeTracking(rootDir, htmlFilePaths);
119
+ res.writeHead(200, { 'Content-Type': 'application/json' });
120
+ res.end(JSON.stringify(result));
121
+ return;
122
+ }
123
+
124
+ // 4.5. API: Selectively Remove Single Tracking Service
125
+ if (pathname === '/api/remove-service' && req.method === 'POST') {
126
+ const body = await parseJsonBody(req);
127
+ const result = removeService(rootDir, body.service || '');
128
+ res.writeHead(200, { 'Content-Type': 'application/json' });
129
+ res.end(JSON.stringify(result));
130
+ return;
131
+ }
132
+
133
+ // 5. API: List Named Backups
134
+ if (pathname === '/api/backups' && req.method === 'GET') {
135
+ const backups = listBackups(rootDir);
136
+ res.writeHead(200, { 'Content-Type': 'application/json' });
137
+ res.end(JSON.stringify({ backups }));
138
+ return;
139
+ }
140
+
141
+ // 6. API: Create Named Backup
142
+ if (pathname === '/api/backup' && req.method === 'POST') {
143
+ const body = await parseJsonBody(req);
144
+ const backupResult = manualBackup(rootDir, body.name || '');
145
+ res.writeHead(200, { 'Content-Type': 'application/json' });
146
+ res.end(JSON.stringify(backupResult));
147
+ return;
148
+ }
149
+
150
+ // 7. API: Restore Backup (Specific or Latest)
151
+ if (pathname === '/api/restore' && req.method === 'POST') {
152
+ const body = await parseJsonBody(req);
153
+ const result = restoreBackup(rootDir, body.backupDirName || 'latest');
154
+ res.writeHead(200, { 'Content-Type': 'application/json' });
155
+ res.end(JSON.stringify(result));
156
+ return;
157
+ }
158
+
159
+ // 8. API: Delete Specific Backup
160
+ if (pathname === '/api/delete-backup' && req.method === 'POST') {
161
+ const body = await parseJsonBody(req);
162
+ const result = deleteBackup(rootDir, body.backupDirName);
163
+ res.writeHead(200, { 'Content-Type': 'application/json' });
164
+ res.end(JSON.stringify(result));
165
+ return;
166
+ }
167
+
168
+ // 9. API: Test Individual Form Submission
169
+ if (pathname === '/api/test-form' && req.method === 'POST') {
170
+ const body = await parseJsonBody(req);
171
+ const scan = scanProject(rootDir);
172
+ const existingConfig = (scan.files.find(f => f.existingConfig) || {}).existingConfig || {};
173
+ const mergedConfig = Object.assign({}, existingConfig, body.config || {});
174
+ const results = await testIndividualForm(rootDir, body.formId, body.formFields || {}, mergedConfig, { pagePath: body.pagePath });
175
+ res.writeHead(200, { 'Content-Type': 'application/json' });
176
+ res.end(JSON.stringify({ success: true, results }));
177
+ return;
178
+ }
179
+
180
+ // 10. API: Live Test Lead Submit (Global)
181
+ if (pathname === '/api/test-submit' && req.method === 'POST') {
182
+ const body = await parseJsonBody(req);
183
+ const scan = scanProject(rootDir);
184
+ const existingConfig = (scan.files.find(f => f.existingConfig) || {}).existingConfig || {};
185
+ const mergedConfig = Object.assign({}, existingConfig, body.config || {});
186
+ const results = await testDispatch(rootDir, mergedConfig, body.lead || {});
187
+ res.writeHead(200, { 'Content-Type': 'application/json' });
188
+ res.end(JSON.stringify({ success: true, results }));
189
+ return;
190
+ }
191
+
192
+ // 5. Static Files
193
+ let filePath = path.join(PUBLIC_DIR, pathname === '/' ? 'index.html' : pathname);
194
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
195
+ const ext = path.extname(filePath);
196
+ const contentType = MIME_TYPES[ext] || 'text/plain';
197
+ res.writeHead(200, { 'Content-Type': contentType });
198
+ fs.createReadStream(filePath).pipe(res);
199
+ return;
200
+ }
201
+
202
+ // Not found
203
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
204
+ res.end('Not Found');
205
+
206
+ } catch (err) {
207
+ console.error('[GUI Server Error]', err);
208
+ res.writeHead(500, { 'Content-Type': 'application/json' });
209
+ res.end(JSON.stringify({ error: err.message }));
210
+ }
211
+ });
212
+
213
+ server.listen(port, () => {
214
+ const localUrl = `http://localhost:${port}`;
215
+ console.log(`\n⚡ Buzl Tracker GUI is running at: ${localUrl}`);
216
+ console.log('Press Ctrl+C to stop the server.\n');
217
+ openBrowser(localUrl);
218
+ });
219
+
220
+ server.on('error', (err) => {
221
+ if (err.code === 'EADDRINUSE') {
222
+ console.log(`Port ${port} is in use, trying ${port + 1}...`);
223
+ startGuiServer(rootDir, port + 1);
224
+ } else {
225
+ console.error('Server error:', err);
226
+ }
227
+ });
228
+
229
+ return server;
230
+ }
231
+
232
+ module.exports = { startGuiServer };
package/src/index.js ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * ============================================================================
3
+ * BUZL TRACKER & CAPI SDK
4
+ * Programmatic Node.js API for automated analytics & CRM injection
5
+ *
6
+ * Copyright (c) 2026 Buzl Digital Solutions
7
+ * Licensed under the MIT License
8
+ * ============================================================================
9
+ */
10
+
11
+ const { scanProject, findHtmlFiles, analyzeHtmlFile } = require('./core/scanner');
12
+ const { injectHtml, applyInjection, removeTracking, removeService } = require('./core/injector');
13
+ const { runVerification, pingUrl, testDispatch, testIndividualForm } = require('./core/tester');
14
+ const { createBackup, restoreBackup, restoreLatestBackup, deleteBackup, listBackups, manualBackup } = require('./core/rollback');
15
+ const { startGuiServer } = require('./gui/server');
16
+ const { runTerminalWizard } = require('./cli/terminal');
17
+
18
+ module.exports = {
19
+ // Scanner module: multi-page HTML parsing, live state detection, and form discovery
20
+ scanProject,
21
+ findHtmlFiles,
22
+ analyzeHtmlFile,
23
+
24
+ // Injector module: AST-safe HTML injection and selective service removal
25
+ injectHtml,
26
+ applyInjection,
27
+ removeTracking,
28
+ removeService,
29
+
30
+ // Tester module: tag verification, dispatch testing, and webhook reachability pings
31
+ runVerification,
32
+ pingUrl,
33
+ testDispatch,
34
+ testIndividualForm,
35
+
36
+ // Rollback module: snapshot creation, named backups, and zero-risk point-in-time restores
37
+ createBackup,
38
+ restoreBackup,
39
+ restoreLatestBackup,
40
+ deleteBackup,
41
+ listBackups,
42
+ manualBackup,
43
+
44
+ // User interfaces: web GUI server and terminal wizard
45
+ startGuiServer,
46
+ runTerminalWizard
47
+ };