pop-pay 0.2.0 → 0.3.1

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,135 @@
1
+ document.addEventListener('DOMContentLoaded', () => {
2
+ const todaySpendingEl = document.getElementById('today-spending');
3
+ const remainingBudgetEl = document.getElementById('remaining-budget');
4
+ const utilizationPctEl = document.getElementById('utilization-pct');
5
+ const utilizationFillEl = document.getElementById('utilization-fill');
6
+ const sealsBody = document.getElementById('seals-body');
7
+ const rejectedBody = document.getElementById('rejected-body');
8
+ const refreshBtn = document.getElementById('refresh-btn');
9
+ const maxBudgetInput = document.getElementById('max-daily-budget');
10
+ const saveSettingsBtn = document.getElementById('save-settings');
11
+
12
+ let sealsData = [];
13
+
14
+ const formatCurrency = (amount) => {
15
+ return new Intl.NumberFormat('en-US', {
16
+ style: 'currency',
17
+ currency: 'USD',
18
+ }).format(amount);
19
+ };
20
+
21
+ const fetchData = async () => {
22
+ try {
23
+ const [budgetRes, sealsRes, rejectedRes] = await Promise.all([
24
+ fetch('/api/budget/today'),
25
+ fetch('/api/seals'),
26
+ fetch('/api/seals?status=rejected')
27
+ ]);
28
+
29
+ const budget = await budgetRes.json();
30
+ sealsData = await sealsRes.json();
31
+ const rejected = await rejectedRes.json();
32
+
33
+ updateBudget(budget);
34
+ renderSeals(sealsData);
35
+ renderRejected(rejected);
36
+ } catch (error) {
37
+ console.error('Failed to fetch dashboard data:', error);
38
+ }
39
+ };
40
+
41
+ const updateBudget = (data) => {
42
+ const { spent, max, remaining } = data;
43
+ todaySpendingEl.textContent = formatCurrency(spent);
44
+ remainingBudgetEl.textContent = formatCurrency(remaining);
45
+
46
+ const utilization = max > 0 ? (spent / max) * 100 : 0;
47
+ utilizationPctEl.textContent = `${utilization.toFixed(1)}%`;
48
+ utilizationFillEl.style.width = `${Math.min(utilization, 100)}%`;
49
+
50
+ if (utilization >= 90) {
51
+ utilizationFillEl.style.backgroundColor = 'var(--danger-red)';
52
+ } else if (utilization >= 70) {
53
+ utilizationFillEl.style.backgroundColor = 'var(--warning-amber)';
54
+ } else {
55
+ utilizationFillEl.style.backgroundColor = 'var(--accent-green)';
56
+ }
57
+
58
+ maxBudgetInput.value = max;
59
+ };
60
+
61
+ const renderSeals = (seals) => {
62
+ sealsBody.innerHTML = '';
63
+ seals.forEach(seal => {
64
+ const row = document.createElement('tr');
65
+ row.innerHTML = `
66
+ <td>${seal.seal_id}</td>
67
+ <td>${formatCurrency(seal.amount)}</td>
68
+ <td>${seal.vendor}</td>
69
+ <td style="color: ${getStatusColor(seal.status)}">${seal.status}</td>
70
+ <td>${seal.masked_card || 'N/A'}</td>
71
+ <td>${new Date(seal.timestamp).toLocaleString()}</td>
72
+ `;
73
+ sealsBody.appendChild(row);
74
+ });
75
+ };
76
+
77
+ const renderRejected = (rejected) => {
78
+ rejectedBody.innerHTML = '';
79
+ rejected.forEach(seal => {
80
+ const row = document.createElement('tr');
81
+ row.innerHTML = `
82
+ <td>${seal.seal_id}</td>
83
+ <td>${formatCurrency(seal.amount)}</td>
84
+ <td>${seal.vendor}</td>
85
+ <td>${seal.status}</td>
86
+ <td>${new Date(seal.timestamp).toLocaleString()}</td>
87
+ `;
88
+ rejectedBody.appendChild(row);
89
+ });
90
+ };
91
+
92
+ const getStatusColor = (status) => {
93
+ switch (status.toLowerCase()) {
94
+ case 'issued': return 'var(--accent-green)';
95
+ case 'used': return 'var(--text-secondary)';
96
+ case 'rejected': return 'var(--danger-red)';
97
+ default: return 'var(--text-primary)';
98
+ }
99
+ };
100
+
101
+ const saveSettings = async () => {
102
+ const maxBudget = parseFloat(maxBudgetInput.value);
103
+ if (isNaN(maxBudget)) return;
104
+
105
+ try {
106
+ await fetch('/api/settings/max_daily_budget', {
107
+ method: 'PUT',
108
+ headers: { 'Content-Type': 'application/json' },
109
+ body: JSON.stringify({ value: maxBudget.toString() })
110
+ });
111
+ fetchData();
112
+ } catch (error) {
113
+ console.error('Failed to save settings:', error);
114
+ }
115
+ };
116
+
117
+ // Sorting logic
118
+ document.querySelectorAll('#seals-table th[data-sort]').forEach(th => {
119
+ th.addEventListener('click', () => {
120
+ const prop = th.dataset.sort;
121
+ const sorted = [...sealsData].sort((a, b) => {
122
+ if (a[prop] < b[prop]) return -1;
123
+ if (a[prop] > b[prop]) return 1;
124
+ return 0;
125
+ });
126
+ renderSeals(sorted);
127
+ });
128
+ });
129
+
130
+ refreshBtn.addEventListener('click', fetchData);
131
+ saveSettingsBtn.addEventListener('click', saveSettings);
132
+
133
+ // Initial load
134
+ fetchData();
135
+ });
@@ -0,0 +1,89 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>The Vault - Pop Pay Dashboard</title>
7
+ <link rel="stylesheet" href="dashboard.css">
8
+ </head>
9
+ <body>
10
+ <header>
11
+ <div class="container">
12
+ <h1>THE VAULT <span class="subtitle">// POP PAY TERMINAL</span></h1>
13
+ <button id="refresh-btn" class="btn">REFRESH_DATA</button>
14
+ </div>
15
+ </header>
16
+
17
+ <main class="container">
18
+ <section class="metrics">
19
+ <div class="card">
20
+ <h3>TODAY_SPENDING</h3>
21
+ <p id="today-spending" class="value">$0.00</p>
22
+ </div>
23
+ <div class="card">
24
+ <h3>REMAINING_BUDGET</h3>
25
+ <p id="remaining-budget" class="value">$0.00</p>
26
+ </div>
27
+ <div class="card">
28
+ <h3>UTILIZATION</h3>
29
+ <p id="utilization-pct" class="value">0%</p>
30
+ <div class="progress-bar">
31
+ <div id="utilization-fill" class="progress-fill"></div>
32
+ </div>
33
+ </div>
34
+ </section>
35
+
36
+ <section class="settings card">
37
+ <h3>BUDGET_SETTINGS</h3>
38
+ <div class="input-group">
39
+ <label for="max-daily-budget">MAX_DAILY_BUDGET ($):</label>
40
+ <input type="number" id="max-daily-budget" value="500" step="10">
41
+ <button id="save-settings" class="btn btn-small">UPDATE</button>
42
+ </div>
43
+ </section>
44
+
45
+ <section class="seals">
46
+ <h3>ISSUED_SEALS</h3>
47
+ <div class="table-container">
48
+ <table id="seals-table">
49
+ <thead>
50
+ <tr>
51
+ <th data-sort="seal_id">SEAL_ID</th>
52
+ <th data-sort="amount">AMOUNT</th>
53
+ <th data-sort="vendor">VENDOR</th>
54
+ <th data-sort="status">STATUS</th>
55
+ <th data-sort="masked_card">CARD_MASK</th>
56
+ <th data-sort="timestamp">TIMESTAMP</th>
57
+ </tr>
58
+ </thead>
59
+ <tbody id="seals-body">
60
+ <!-- Data injected here -->
61
+ </tbody>
62
+ </table>
63
+ </div>
64
+ </section>
65
+
66
+ <section class="rejected">
67
+ <h3>REJECTION_LOG</h3>
68
+ <div class="table-container">
69
+ <table id="rejected-table">
70
+ <thead>
71
+ <tr>
72
+ <th>SEAL_ID</th>
73
+ <th>AMOUNT</th>
74
+ <th>VENDOR</th>
75
+ <th>REASON</th>
76
+ <th>TIMESTAMP</th>
77
+ </tr>
78
+ </thead>
79
+ <tbody id="rejected-body">
80
+ <!-- Data injected here -->
81
+ </tbody>
82
+ </table>
83
+ </div>
84
+ </section>
85
+ </main>
86
+
87
+ <script src="dashboard.js"></script>
88
+ </body>
89
+ </html>
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli-dashboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-dashboard.d.ts","sourceRoot":"","sources":["../src/cli-dashboard.ts"],"names":[],"mappings":""}
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const dashboard_js_1 = require("./dashboard.js");
5
+ function parseArgs() {
6
+ const args = process.argv.slice(2);
7
+ const options = {
8
+ port: 3210,
9
+ dbPath: "pop_state.db"
10
+ };
11
+ for (let i = 0; i < args.length; i++) {
12
+ if (args[i] === "--port" && args[i + 1]) {
13
+ options.port = parseInt(args[i + 1], 10);
14
+ i++;
15
+ }
16
+ else if (args[i] === "--db" && args[i + 1]) {
17
+ options.dbPath = args[i + 1];
18
+ i++;
19
+ }
20
+ }
21
+ return options;
22
+ }
23
+ const options = parseArgs();
24
+ (0, dashboard_js_1.main)(options).catch(err => {
25
+ console.error("Failed to start dashboard:", err);
26
+ process.exit(1);
27
+ });
28
+ //# sourceMappingURL=cli-dashboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-dashboard.js","sourceRoot":"","sources":["../src/cli-dashboard.ts"],"names":[],"mappings":";;;AACA,iDAAsC;AAEtC,SAAS,SAAS;IAChB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG;QACd,IAAI,EAAE,IAAI;QACV,MAAM,EAAE,cAAc;KACvB,CAAC;IAEF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACxC,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACzC,CAAC,EAAE,CAAC;QACN,CAAC;aAAM,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAC7C,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC7B,CAAC,EAAE,CAAC;QACN,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,OAAO,GAAG,SAAS,EAAE,CAAC;AAC5B,IAAA,mBAAI,EAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;IACxB,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,GAAG,CAAC,CAAC;IACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * pop-pay CLI dispatcher.
4
+ * Routes subcommands to the appropriate module.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=cli-main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-main.d.ts","sourceRoot":"","sources":["../src/cli-main.ts"],"names":[],"mappings":";AAEA;;;GAGG"}
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * pop-pay CLI dispatcher.
5
+ * Routes subcommands to the appropriate module.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ const node_fs_1 = require("node:fs");
9
+ const node_path_1 = require("node:path");
10
+ function getVersion() {
11
+ try {
12
+ const pkgPath = (0, node_path_1.join)(__dirname, "..", "package.json");
13
+ const pkg = JSON.parse((0, node_fs_1.readFileSync)(pkgPath, "utf8"));
14
+ return pkg.version;
15
+ }
16
+ catch {
17
+ return "unknown";
18
+ }
19
+ }
20
+ function showHelp() {
21
+ console.log(`pop-pay v${getVersion()} — Semantic Payment Guardrail for AI Agents
22
+
23
+ Usage: pop-pay <command> [options]
24
+
25
+ Commands:
26
+ launch-mcp Start the MCP server (stdio transport)
27
+ launch Launch Chrome with CDP remote debugging
28
+ init-vault Initialize the encrypted credential vault
29
+ unlock Unlock the vault for the current session
30
+ dashboard Start the monitoring dashboard
31
+
32
+ Options:
33
+ -v, --version Show version
34
+ -h, --help Show this help message`);
35
+ }
36
+ async function main() {
37
+ const subcommand = process.argv[2];
38
+ if (!subcommand || subcommand === "--help" || subcommand === "-h") {
39
+ showHelp();
40
+ return;
41
+ }
42
+ if (subcommand === "--version" || subcommand === "-v") {
43
+ console.log(getVersion());
44
+ return;
45
+ }
46
+ switch (subcommand) {
47
+ case "launch-mcp":
48
+ process.argv.splice(2, 1);
49
+ await import("./mcp-server.js");
50
+ break;
51
+ case "launch":
52
+ case "pop-launch":
53
+ process.argv.splice(2, 1);
54
+ await import("./cli.js");
55
+ break;
56
+ case "init-vault":
57
+ case "pop-init-vault":
58
+ process.argv.splice(2, 1);
59
+ await import("./cli-vault.js");
60
+ break;
61
+ case "unlock":
62
+ case "pop-unlock":
63
+ // Keep "unlock" in argv — cli-vault.ts detects it via process.argv.includes("unlock")
64
+ process.argv[2] = "unlock";
65
+ await import("./cli-vault.js");
66
+ break;
67
+ case "dashboard":
68
+ process.argv.splice(2, 1);
69
+ await import("./cli-dashboard.js");
70
+ break;
71
+ default:
72
+ console.error(`Unknown command: ${subcommand}\n`);
73
+ showHelp();
74
+ process.exit(1);
75
+ }
76
+ }
77
+ main().catch((err) => {
78
+ console.error("pop-pay:", err.message ?? err);
79
+ process.exit(1);
80
+ });
81
+ //# sourceMappingURL=cli-main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-main.js","sourceRoot":"","sources":["../src/cli-main.ts"],"names":[],"mappings":";;AAEA;;;GAGG;;AAEH,qCAAuC;AACvC,yCAAiC;AAEjC,SAAS,UAAU;IACjB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAA,gBAAI,EAAC,SAAS,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;QACtD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAA,sBAAY,EAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;QACtD,OAAO,GAAG,CAAC,OAAO,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,QAAQ;IACf,OAAO,CAAC,GAAG,CAAC,YAAY,UAAU,EAAE;;;;;;;;;;;;;yCAaG,CAAC,CAAC;AAC3C,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAEnC,IAAI,CAAC,UAAU,IAAI,UAAU,KAAK,QAAQ,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;QAClE,QAAQ,EAAE,CAAC;QACX,OAAO;IACT,CAAC;IAED,IAAI,UAAU,KAAK,WAAW,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;QACtD,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;QAC1B,OAAO;IACT,CAAC;IAED,QAAQ,UAAU,EAAE,CAAC;QACnB,KAAK,YAAY;YACf,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAC1B,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;YAChC,MAAM;QAER,KAAK,QAAQ,CAAC;QACd,KAAK,YAAY;YACf,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAC1B,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;YACzB,MAAM;QAER,KAAK,YAAY,CAAC;QAClB,KAAK,gBAAgB;YACnB,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAC1B,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;YAC/B,MAAM;QAER,KAAK,QAAQ,CAAC;QACd,KAAK,YAAY;YACf,sFAAsF;YACtF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC;YAC3B,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;YAC/B,MAAM;QAER,KAAK,WAAW;YACd,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAC1B,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;YACnC,MAAM;QAER;YACE,OAAO,CAAC,KAAK,CAAC,oBAAoB,UAAU,IAAI,CAAC,CAAC;YAClD,QAAQ,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,UAAU,EAAE,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,CAAC;IAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,9 @@
1
+ import http from "node:http";
2
+ export interface DashboardOptions {
3
+ port: number;
4
+ dbPath: string;
5
+ }
6
+ export declare function main(options: DashboardOptions & {
7
+ skipOpen?: boolean;
8
+ }): Promise<http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>>;
9
+ //# sourceMappingURL=dashboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAM7B,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,IAAI,CAAC,OAAO,EAAE,gBAAgB,GAAG;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,iFA0H5E"}
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.main = main;
7
+ const node_http_1 = __importDefault(require("node:http"));
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const node_child_process_1 = require("node:child_process");
11
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
12
+ async function main(options) {
13
+ const { port, dbPath, skipOpen } = options;
14
+ const db = new better_sqlite3_1.default(dbPath);
15
+ // Initialize tables
16
+ db.exec(`
17
+ CREATE TABLE IF NOT EXISTS daily_budget (
18
+ date TEXT PRIMARY KEY,
19
+ spent_amount REAL
20
+ )
21
+ `);
22
+ db.exec(`
23
+ CREATE TABLE IF NOT EXISTS issued_seals (
24
+ seal_id TEXT PRIMARY KEY,
25
+ amount REAL,
26
+ vendor TEXT,
27
+ status TEXT,
28
+ masked_card TEXT,
29
+ expiration_date TEXT,
30
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
31
+ )
32
+ `);
33
+ db.exec(`
34
+ CREATE TABLE IF NOT EXISTS dashboard_settings (
35
+ key TEXT PRIMARY KEY,
36
+ value TEXT
37
+ )
38
+ `);
39
+ const server = node_http_1.default.createServer((req, res) => {
40
+ const { method, url } = req;
41
+ const pathname = url ? new URL(url, `http://localhost:${port}`).pathname : "";
42
+ // Static File Serving
43
+ if (method === "GET" && (pathname === "/" || pathname.startsWith("/dashboard") || !pathname.startsWith("/api"))) {
44
+ let filePath = pathname === "/" ? "/index.html" : pathname;
45
+ if (filePath.startsWith("/dashboard/")) {
46
+ filePath = filePath.replace("/dashboard/", "/");
47
+ }
48
+ const fullPath = node_path_1.default.join(process.cwd(), "dashboard", filePath);
49
+ if (node_fs_1.default.existsSync(fullPath) && node_fs_1.default.statSync(fullPath).isFile()) {
50
+ const ext = node_path_1.default.extname(fullPath);
51
+ const mimeTypes = {
52
+ ".html": "text/html",
53
+ ".js": "application/javascript",
54
+ ".css": "text/css",
55
+ ".png": "image/png",
56
+ ".jpg": "image/jpeg",
57
+ };
58
+ res.writeHead(200, { "Content-Type": mimeTypes[ext] || "text/plain" });
59
+ node_fs_1.default.createReadStream(fullPath).pipe(res);
60
+ return;
61
+ }
62
+ }
63
+ // API Routes
64
+ if (method === "GET" && pathname === "/api/budget/today") {
65
+ const today = new Date().toISOString().slice(0, 10);
66
+ const spentRow = db.prepare("SELECT spent_amount FROM daily_budget WHERE date = ?").get(today);
67
+ const spent = spentRow?.spent_amount ?? 0;
68
+ const maxRow = db.prepare("SELECT value FROM dashboard_settings WHERE key = 'max_daily_budget'").get();
69
+ const max = maxRow ? parseFloat(maxRow.value) : 500;
70
+ res.writeHead(200, { "Content-Type": "application/json" });
71
+ res.end(JSON.stringify({ spent, max, remaining: max - spent }));
72
+ return;
73
+ }
74
+ if (method === "GET" && pathname === "/api/seals") {
75
+ const searchParams = new URL(url, `http://localhost:${port}`).searchParams;
76
+ const statusFilter = searchParams.get("status");
77
+ let seals;
78
+ if (statusFilter) {
79
+ seals = db.prepare("SELECT * FROM issued_seals WHERE LOWER(status) = LOWER(?) ORDER BY timestamp DESC").all(statusFilter);
80
+ }
81
+ else {
82
+ seals = db.prepare("SELECT * FROM issued_seals ORDER BY timestamp DESC").all();
83
+ }
84
+ res.writeHead(200, { "Content-Type": "application/json" });
85
+ res.end(JSON.stringify(seals));
86
+ return;
87
+ }
88
+ if (method === "PUT" && pathname.startsWith("/api/settings/")) {
89
+ const key = pathname.replace("/api/settings/", "");
90
+ let body = "";
91
+ req.on("data", chunk => body += chunk);
92
+ req.on("end", () => {
93
+ try {
94
+ const { value } = JSON.parse(body);
95
+ db.prepare("INSERT INTO dashboard_settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = ?")
96
+ .run(key, value.toString(), value.toString());
97
+ res.writeHead(200, { "Content-Type": "application/json" });
98
+ res.end(JSON.stringify({ key, value }));
99
+ }
100
+ catch (e) {
101
+ res.writeHead(400);
102
+ res.end("Invalid JSON");
103
+ }
104
+ });
105
+ return;
106
+ }
107
+ res.writeHead(404);
108
+ res.end("Not Found");
109
+ });
110
+ return new Promise((resolve) => {
111
+ server.listen(port, () => {
112
+ const url = `http://localhost:${port}`;
113
+ if (!skipOpen) {
114
+ console.log(`Dashboard running at ${url}`);
115
+ const start = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
116
+ (0, node_child_process_1.exec)(`${start} ${url}`);
117
+ }
118
+ resolve(server);
119
+ });
120
+ });
121
+ }
122
+ //# sourceMappingURL=dashboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"injector.d.ts","sourceRoot":"","sources":["../../src/engine/injector.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"injector.d.ts","sourceRoot":"","sources":["../../src/engine/injector.ts"],"names":[],"mappings":"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"}
@@ -852,19 +852,69 @@ class PopBrowserInjector {
852
852
  else
853
853
  failed.push(`${name} (value='${value}')`);
854
854
  };
855
- await tryFill(exports.FIRST_NAME_SELECTORS, info.firstName, "first_name");
856
- await tryFill(exports.LAST_NAME_SELECTORS, info.lastName, "last_name");
855
+ // Fill ORDER matters: input fields first, then select dropdowns last.
856
+ // Reason: filling inputs can trigger framework re-renders (React, Zoho)
857
+ // which reset previously selected dropdowns. Selects go last to survive.
858
+ const fieldConfigs = [
859
+ { selectors: exports.FIRST_NAME_SELECTORS, value: info.firstName, name: "first_name" },
860
+ { selectors: exports.LAST_NAME_SELECTORS, value: info.lastName, name: "last_name" },
861
+ { selectors: exports.STREET_SELECTORS, value: info.street, name: "street" },
862
+ { selectors: exports.CITY_SELECTORS, value: info.city, name: "city" },
863
+ { selectors: exports.STATE_SELECTORS, value: state, name: "state" },
864
+ { selectors: exports.COUNTRY_SELECTORS, value: info.country, name: "country" },
865
+ { selectors: exports.ZIP_SELECTORS, value: info.zip, name: "zip" },
866
+ { selectors: exports.EMAIL_SELECTORS, value: info.email, name: "email" },
867
+ ];
857
868
  // Full name fallback
858
869
  if (info.firstName || info.lastName) {
859
870
  const fullName = [info.firstName, info.lastName].filter(Boolean).join(" ");
860
- await tryFill(exports.FULL_NAME_SELECTORS, fullName, "full_name");
861
- }
862
- await tryFill(exports.STREET_SELECTORS, info.street, "street");
863
- await tryFill(exports.CITY_SELECTORS, info.city, "city");
864
- await tryFill(exports.STATE_SELECTORS, state, "state");
865
- await tryFill(exports.COUNTRY_SELECTORS, info.country, "country");
866
- await tryFill(exports.ZIP_SELECTORS, info.zip, "zip");
867
- await tryFill(exports.EMAIL_SELECTORS, info.email, "email");
871
+ fieldConfigs.push({ selectors: exports.FULL_NAME_SELECTORS, value: fullName, name: "full_name" });
872
+ }
873
+ // Detect tagName for each non-empty field
874
+ const detections = [];
875
+ for (const config of fieldConfigs) {
876
+ if (!config.value) {
877
+ skipped.push(config.name);
878
+ continue;
879
+ }
880
+ let tagName = null;
881
+ try {
882
+ const allSelector = config.selectors.join(", ");
883
+ const { result } = await client.send("Runtime.evaluate", {
884
+ expression: `
885
+ (function() {
886
+ const el = document.querySelector(${JSON.stringify(allSelector)});
887
+ return el ? el.tagName.toLowerCase() : null;
888
+ })()
889
+ `,
890
+ returnByValue: true,
891
+ });
892
+ tagName = result?.value || null;
893
+ }
894
+ catch {
895
+ // Detection failed — treat as input (non-select)
896
+ }
897
+ detections.push({ ...config, tagName });
898
+ }
899
+ const doFill = async (d) => {
900
+ const ok = await this.fillBillingField(client, d.selectors, d.value, d.name);
901
+ if (ok)
902
+ filled.push(d.name);
903
+ else
904
+ failed.push(`${d.name} (value='${d.value}')`);
905
+ };
906
+ // Round 1: fill all non-select fields (inputs, textareas, etc.)
907
+ for (const d of detections) {
908
+ if (d.tagName !== "select") {
909
+ await doFill(d);
910
+ }
911
+ }
912
+ // Round 2: fill all select dropdowns (survive re-renders)
913
+ for (const d of detections) {
914
+ if (d.tagName === "select") {
915
+ await doFill(d);
916
+ }
917
+ }
868
918
  // Phone: country code dropdown first, then number
869
919
  let ccFilled = false;
870
920
  if (info.phoneCountryCode) {