@clawcard/cli 2.1.1 → 2.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/extension/background.js +35 -44
- package/extension/manifest.json +2 -2
- package/package.json +1 -1
- package/src/commands/agent.js +85 -42
- package/src/commands/setup.js +0 -85
package/extension/background.js
CHANGED
|
@@ -1,28 +1,23 @@
|
|
|
1
1
|
// ClawCard Pay — Background Service Worker
|
|
2
|
-
//
|
|
2
|
+
// Polls localhost:7891 for pending fill commands from the CLI
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
|
|
4
|
+
const POLL_URL = "http://localhost:7891/pending";
|
|
5
|
+
const RESULT_URL = "http://localhost:7891/result";
|
|
6
|
+
const POLL_INTERVAL = 500;
|
|
6
7
|
|
|
7
|
-
function
|
|
8
|
+
async function poll() {
|
|
8
9
|
try {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (message.action === "fill") {
|
|
13
|
-
fillActiveTab(message);
|
|
10
|
+
const res = await fetch(POLL_URL);
|
|
11
|
+
if (res.ok) {
|
|
12
|
+
const message = await res.json();
|
|
13
|
+
if (message && message.action === "fill") {
|
|
14
|
+
await fillActiveTab(message);
|
|
14
15
|
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
nativePort = null;
|
|
19
|
-
// Reconnect after delay
|
|
20
|
-
setTimeout(connectNative, 3000);
|
|
21
|
-
});
|
|
22
|
-
} catch (e) {
|
|
23
|
-
// Native host not available — retry later
|
|
24
|
-
setTimeout(connectNative, 5000);
|
|
16
|
+
}
|
|
17
|
+
} catch {
|
|
18
|
+
// Server not running — that's fine, CLI hasn't started a pay command
|
|
25
19
|
}
|
|
20
|
+
setTimeout(poll, POLL_INTERVAL);
|
|
26
21
|
}
|
|
27
22
|
|
|
28
23
|
async function fillActiveTab(message) {
|
|
@@ -33,7 +28,7 @@ async function fillActiveTab(message) {
|
|
|
33
28
|
});
|
|
34
29
|
|
|
35
30
|
if (!tab) {
|
|
36
|
-
|
|
31
|
+
await sendResult({ success: false, error: "No active tab" });
|
|
37
32
|
return;
|
|
38
33
|
}
|
|
39
34
|
|
|
@@ -41,59 +36,55 @@ async function fillActiveTab(message) {
|
|
|
41
36
|
try {
|
|
42
37
|
const response = await chrome.tabs.sendMessage(tab.id, message);
|
|
43
38
|
if (response && response.filled && response.filled.length > 0) {
|
|
44
|
-
|
|
39
|
+
await sendResult(response);
|
|
45
40
|
return;
|
|
46
41
|
}
|
|
47
42
|
} catch {
|
|
48
|
-
// Main frame didn't have
|
|
43
|
+
// Main frame didn't have fields — try subframes
|
|
49
44
|
}
|
|
50
45
|
|
|
51
|
-
// Try all subframes
|
|
46
|
+
// Try all subframes (where Stripe iframes live)
|
|
52
47
|
const frames = await chrome.webNavigation.getAllFrames({ tabId: tab.id });
|
|
53
48
|
if (!frames || frames.length === 0) {
|
|
54
|
-
|
|
55
|
-
success: false,
|
|
56
|
-
error: "No frames found on the current page",
|
|
57
|
-
});
|
|
49
|
+
await sendResult({ success: false, error: "No frames found" });
|
|
58
50
|
return;
|
|
59
51
|
}
|
|
60
52
|
|
|
61
53
|
for (const frame of frames) {
|
|
62
|
-
if (frame.frameId === 0) continue;
|
|
54
|
+
if (frame.frameId === 0) continue;
|
|
63
55
|
try {
|
|
64
56
|
const response = await chrome.tabs.sendMessage(tab.id, message, {
|
|
65
57
|
frameId: frame.frameId,
|
|
66
58
|
});
|
|
67
59
|
if (response && response.filled && response.filled.length > 0) {
|
|
68
|
-
|
|
60
|
+
await sendResult(response);
|
|
69
61
|
return;
|
|
70
62
|
}
|
|
71
63
|
} catch {
|
|
72
|
-
// This frame doesn't have our content script
|
|
64
|
+
// This frame doesn't have our content script — continue
|
|
73
65
|
}
|
|
74
66
|
}
|
|
75
67
|
|
|
76
|
-
|
|
68
|
+
await sendResult({
|
|
77
69
|
success: false,
|
|
78
70
|
error: "No payment form fields found on the current page",
|
|
79
71
|
});
|
|
80
72
|
} catch (err) {
|
|
81
|
-
|
|
82
|
-
success: false,
|
|
83
|
-
error: err.message || "Unknown error",
|
|
84
|
-
});
|
|
73
|
+
await sendResult({ success: false, error: err.message || "Unknown error" });
|
|
85
74
|
}
|
|
86
75
|
}
|
|
87
76
|
|
|
88
|
-
function
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
77
|
+
async function sendResult(result) {
|
|
78
|
+
try {
|
|
79
|
+
await fetch(RESULT_URL, {
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers: { "Content-Type": "application/json" },
|
|
82
|
+
body: JSON.stringify(result),
|
|
83
|
+
});
|
|
84
|
+
} catch {
|
|
85
|
+
// CLI server might have timed out
|
|
95
86
|
}
|
|
96
87
|
}
|
|
97
88
|
|
|
98
|
-
// Start
|
|
99
|
-
|
|
89
|
+
// Start polling
|
|
90
|
+
poll();
|
package/extension/manifest.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "ClawCard Pay",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.1",
|
|
5
5
|
"description": "Fills payment form fields for ClawCard agents",
|
|
6
|
-
"permissions": ["
|
|
6
|
+
"permissions": ["webNavigation"],
|
|
7
7
|
"host_permissions": ["<all_urls>"],
|
|
8
8
|
"background": {
|
|
9
9
|
"service_worker": "background.js"
|
package/package.json
CHANGED
package/src/commands/agent.js
CHANGED
|
@@ -239,14 +239,7 @@ export async function agentActivityCmd(options) {
|
|
|
239
239
|
|
|
240
240
|
// ── Pay (fill payment form via browser extension) ──
|
|
241
241
|
export async function agentPayCmd(options) {
|
|
242
|
-
const
|
|
243
|
-
await import("fs");
|
|
244
|
-
const { join } = await import("path");
|
|
245
|
-
const { homedir } = await import("os");
|
|
246
|
-
|
|
247
|
-
const clawcardDir = join(homedir(), ".clawcard");
|
|
248
|
-
const pendingPath = join(clawcardDir, "pending-pay.json");
|
|
249
|
-
const responsePath = join(clawcardDir, "pay-response.json");
|
|
242
|
+
const http = await import("http");
|
|
250
243
|
|
|
251
244
|
// Get card details
|
|
252
245
|
const agentId = await getAgentId();
|
|
@@ -266,45 +259,95 @@ export async function agentPayCmd(options) {
|
|
|
266
259
|
return;
|
|
267
260
|
}
|
|
268
261
|
|
|
269
|
-
// Format expiry as MM/YY
|
|
270
262
|
const exp = `${String(card.exp_month).padStart(2, "0")}/${String(card.exp_year).slice(-2)}`;
|
|
263
|
+
const fillData = { action: "fill", pan: card.pan, exp, cvc: card.cvv };
|
|
271
264
|
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if (
|
|
293
|
-
|
|
265
|
+
// Start localhost HTTP server — extension polls this
|
|
266
|
+
const PORT = 7891;
|
|
267
|
+
|
|
268
|
+
const result = await new Promise((resolve) => {
|
|
269
|
+
let settled = false;
|
|
270
|
+
|
|
271
|
+
const server = http.createServer((req, res) => {
|
|
272
|
+
// CORS headers for extension
|
|
273
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
274
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
275
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
276
|
+
|
|
277
|
+
if (req.method === "OPTIONS") {
|
|
278
|
+
res.writeHead(204);
|
|
279
|
+
res.end();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Extension polls this for pending fill commands
|
|
284
|
+
if (req.method === "GET" && req.url === "/pending") {
|
|
285
|
+
if (!settled) {
|
|
286
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
287
|
+
res.end(JSON.stringify(fillData));
|
|
294
288
|
} else {
|
|
295
|
-
|
|
289
|
+
res.writeHead(204);
|
|
290
|
+
res.end();
|
|
296
291
|
}
|
|
297
292
|
return;
|
|
298
|
-
} catch {
|
|
299
|
-
// File being written — retry
|
|
300
293
|
}
|
|
301
|
-
}
|
|
302
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
303
|
-
}
|
|
304
294
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
295
|
+
// Extension posts fill result here
|
|
296
|
+
if (req.method === "POST" && req.url === "/result") {
|
|
297
|
+
let body = "";
|
|
298
|
+
req.on("data", (chunk) => (body += chunk));
|
|
299
|
+
req.on("end", () => {
|
|
300
|
+
res.writeHead(200);
|
|
301
|
+
res.end("ok");
|
|
302
|
+
if (!settled) {
|
|
303
|
+
settled = true;
|
|
304
|
+
try {
|
|
305
|
+
resolve(JSON.parse(body));
|
|
306
|
+
} catch {
|
|
307
|
+
resolve({ success: false, error: "Invalid response from extension" });
|
|
308
|
+
}
|
|
309
|
+
server.close();
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
res.writeHead(404);
|
|
316
|
+
res.end();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
server.listen(PORT, "127.0.0.1", () => {
|
|
320
|
+
// Server started — extension will pick up the fill command
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
server.on("error", (err) => {
|
|
324
|
+
if (!settled) {
|
|
325
|
+
settled = true;
|
|
326
|
+
resolve({
|
|
327
|
+
success: false,
|
|
328
|
+
error: `Could not start pay server on port ${PORT}: ${err.message}`,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Timeout after 10 seconds
|
|
334
|
+
setTimeout(() => {
|
|
335
|
+
if (!settled) {
|
|
336
|
+
settled = true;
|
|
337
|
+
server.close();
|
|
338
|
+
resolve({
|
|
339
|
+
success: false,
|
|
340
|
+
error:
|
|
341
|
+
"ClawCard extension not responding. Make sure it's loaded in Chrome: chrome://extensions → Load Unpacked → ~/.clawcard/extension/",
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}, 10000);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
if (options.json) return output(result, true);
|
|
348
|
+
if (result.success) {
|
|
349
|
+
console.log(` Payment fields filled: ${result.filled.join(", ")}`);
|
|
350
|
+
} else {
|
|
351
|
+
console.log(` Error: ${result.error}`);
|
|
352
|
+
}
|
|
310
353
|
}
|
package/src/commands/setup.js
CHANGED
|
@@ -164,91 +164,6 @@ export async function setupCommand() {
|
|
|
164
164
|
|
|
165
165
|
copyDir(extensionSource, extensionDest);
|
|
166
166
|
|
|
167
|
-
// Create native host wrapper script (Chrome requires an executable, not a .js file)
|
|
168
|
-
const nativeHostScript = join(home, ".clawcard", "native-host.js");
|
|
169
|
-
const nativeHostWrapper = join(home, ".clawcard", "native-host.sh");
|
|
170
|
-
const nativeHostSource = join(__dirname, "..", "native-host.js");
|
|
171
|
-
|
|
172
|
-
// Copy native host JS
|
|
173
|
-
copyFileSync(nativeHostSource, nativeHostScript);
|
|
174
|
-
|
|
175
|
-
// Create shell wrapper
|
|
176
|
-
const nodePath = process.execPath;
|
|
177
|
-
writeFileSync(
|
|
178
|
-
nativeHostWrapper,
|
|
179
|
-
`#!/bin/bash\nexec "${nodePath}" "${nativeHostScript}"\n`
|
|
180
|
-
);
|
|
181
|
-
// Make wrapper executable
|
|
182
|
-
const { chmodSync } = await import("fs");
|
|
183
|
-
chmodSync(nativeHostWrapper, "755");
|
|
184
|
-
|
|
185
|
-
// Detect extension ID from Chrome (or use broad matching with key)
|
|
186
|
-
// Read the extension ID from the installed manifest
|
|
187
|
-
const extensionManifestPath = join(extensionDest, "manifest.json");
|
|
188
|
-
const extensionManifest = JSON.parse(readFileSync(extensionManifestPath, "utf-8"));
|
|
189
|
-
|
|
190
|
-
// If no key in manifest, we need to accept any extension ID
|
|
191
|
-
// Chrome requires exact IDs — we'll add a stable key to the manifest
|
|
192
|
-
// For now, prompt user for their extension ID
|
|
193
|
-
let extensionId = "PLACEHOLDER";
|
|
194
|
-
try {
|
|
195
|
-
// Try to read from a saved config
|
|
196
|
-
const savedConfig = JSON.parse(readFileSync(join(home, ".clawcard", "extension-id.txt"), "utf-8"));
|
|
197
|
-
extensionId = savedConfig;
|
|
198
|
-
} catch {}
|
|
199
|
-
|
|
200
|
-
if (extensionId === "PLACEHOLDER") {
|
|
201
|
-
p.log.info("After loading the extension in Chrome, find its ID on chrome://extensions");
|
|
202
|
-
const idInput = await p.text({
|
|
203
|
-
message: "Enter the extension ID from chrome://extensions",
|
|
204
|
-
placeholder: "abcdefghijklmnopqrstuvwxyz123456",
|
|
205
|
-
validate: (v) => {
|
|
206
|
-
if (!v || v.length < 10) return "Paste the full extension ID from Chrome";
|
|
207
|
-
},
|
|
208
|
-
});
|
|
209
|
-
if (!p.isCancel(idInput)) {
|
|
210
|
-
extensionId = idInput;
|
|
211
|
-
writeFileSync(join(home, ".clawcard", "extension-id.txt"), JSON.stringify(extensionId));
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Register native messaging host
|
|
216
|
-
const platform = process.platform;
|
|
217
|
-
let nativeHostDir;
|
|
218
|
-
|
|
219
|
-
if (platform === "darwin") {
|
|
220
|
-
nativeHostDir = join(
|
|
221
|
-
home,
|
|
222
|
-
"Library",
|
|
223
|
-
"Application Support",
|
|
224
|
-
"Google",
|
|
225
|
-
"Chrome",
|
|
226
|
-
"NativeMessagingHosts"
|
|
227
|
-
);
|
|
228
|
-
} else {
|
|
229
|
-
nativeHostDir = join(
|
|
230
|
-
home,
|
|
231
|
-
".config",
|
|
232
|
-
"google-chrome",
|
|
233
|
-
"NativeMessagingHosts"
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
mkdirSync(nativeHostDir, { recursive: true });
|
|
238
|
-
|
|
239
|
-
const nativeHostManifest = {
|
|
240
|
-
name: "com.clawcard.pay",
|
|
241
|
-
description: "ClawCard payment form filler",
|
|
242
|
-
path: nativeHostWrapper,
|
|
243
|
-
type: "stdio",
|
|
244
|
-
allowed_origins: [`chrome-extension://${extensionId}/`],
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
writeFileSync(
|
|
248
|
-
join(nativeHostDir, "com.clawcard.pay.json"),
|
|
249
|
-
JSON.stringify(nativeHostManifest, null, 2)
|
|
250
|
-
);
|
|
251
|
-
|
|
252
167
|
s4.stop("Extension installed!");
|
|
253
168
|
|
|
254
169
|
p.note(
|