@staff0rd/assist 0.305.4 → 0.306.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 CHANGED
@@ -224,6 +224,7 @@ The first backlog command in a repository that still has a local `.assist/backlo
224
224
  - `assist sql mutate "<sql>" [connection]` - Execute a mutating SQL statement and print rows affected (rejects non-mutating statements like pure SELECTs)
225
225
  - `assist sql tables [connection]` - List tables in the connected database (via INFORMATION_SCHEMA.TABLES)
226
226
  - `assist sql columns <table> [connection]` - List columns for a table (use `schema.table` for non-default schema; via INFORMATION_SCHEMA.COLUMNS)
227
+ - `assist netcap [-p, --port <port>]` - Start a local receiver that captures browser network traffic to a JSONL file (default `~/.assist/netcap/capture.jsonl`), paired with the netcap browser extension. Sends permissive CORS, logs each ping/capture live, and runs until Ctrl-C (default port `8723`). See [netcap browser extension](#netcap-browser-extension)
227
228
  - `assist screenshot <process>` - Capture a screenshot of a running application window (e.g. `assist screenshot notepad`). Output directory is configurable via `screenshot.outputDir` (default `./screenshots`)
228
229
  - `assist handover save --summary <s>` - Save a session handover note to the backlog DB (content read from stdin), scoped by the repo's git origin. Appends a new row each call; `--summary` is the one-line description shown when recalling
229
230
  - `assist handover list` - List unrecalled handovers for this repo, most recent first, one per line as tab-separated `id`, ISO-8601 created timestamp, and one-line summary. Prints nothing when none are pending
@@ -273,3 +274,14 @@ When iterating on assist itself: web server changes only need the `assist sessio
273
274
 
274
275
  When `commit.pull` is enabled in config, `assist draft`, `assist bug`, `assist refine`, `assist next`, and `assist backlog run` run `git pull --ff-only` before doing anything else; if the pull fails the command aborts. `assist next` pulls once per invocation, not per item in its loop.
275
276
 
277
+
278
+ ## netcap browser extension
279
+
280
+ `assist netcap` only runs the receiver; the browser side is a raw Manifest V3 extension (no build step) under `netcap-extension/`. A MAIN-world content script patches `fetch`/`XMLHttpRequest` to capture `{url, method, status, requestBody, responseBody, timestamp}`, relays each entry to the background service worker (`window.postMessage` → `chrome.runtime.sendMessage`), and the background worker POSTs it to the receiver. Forwarding happens in the background context, so the page's CSP (`connect-src`) never blocks it — the limitation that killed the earlier console-paste approach.
281
+
282
+ 1. Run `assist netcap` — it prints the receiver URL, the capture file path, and the absolute path to the extension directory to load. The receiver host/port is baked into the extension's `background.js` at this point. Under WSL (where the browser runs on the Windows host, cannot load from a WSL path, and cannot reach the receiver on `localhost` because WSL2 localhost forwarding is unreliable) it copies the extension to `C:\tools\netcap-extension`, targets the WSL VM's IP, and prints that Windows path instead. Re-run `assist netcap` after a reboot (the WSL IP can change) and reload the extension.
283
+ 2. Load the unpacked extension:
284
+ - **Firefox**: open `about:debugging#/runtime/this-firefox` → **Load Temporary Add-on…** → pick `manifest.json` inside the printed extension directory. (Requires Firefox 128+ for MAIN-world content scripts.)
285
+ - **Chrome**: open `chrome://extensions`, enable **Developer mode**, click **Load unpacked**, and select the extension directory.
286
+ 3. On load the background worker pings the receiver; `ping from extension` appears in the `assist netcap` log, confirming browser→server connectivity.
287
+ 4. Browse a site (e.g. LinkedIn activity); matching requests append to the capture file live and survive page refreshes. The extension targets `http://127.0.0.1:8723`; if you change `--port`, edit `RECEIVER` in `netcap-extension/background.js`.
@@ -0,0 +1,22 @@
1
+ // Background service worker: the only context that talks to the receiver. It
2
+ // runs outside any page, so the page's CSP does not restrict its fetches. On
3
+ // startup it pings the receiver (the connectivity spike) and thereafter
4
+ // forwards every captured entry relayed from the content scripts.
5
+ const RECEIVER = "http://127.0.0.1:8723/";
6
+
7
+ function ping() {
8
+ fetch(`${RECEIVER}ping`, { method: "GET" }).catch(() => {});
9
+ }
10
+
11
+ ping();
12
+ chrome.runtime.onInstalled?.addListener(ping);
13
+ chrome.runtime.onStartup?.addListener(ping);
14
+
15
+ chrome.runtime.onMessage.addListener((message) => {
16
+ if (!message || message.type !== "netcap-entry") return;
17
+ fetch(RECEIVER, {
18
+ method: "POST",
19
+ headers: { "Content-Type": "application/json" },
20
+ body: JSON.stringify(message.entry),
21
+ }).catch(() => {});
22
+ });
@@ -0,0 +1,94 @@
1
+ // MAIN-world content script: runs in the page's own JS context so it can patch
2
+ // the page's fetch/XMLHttpRequest before page code uses them. Captured entries
3
+ // are handed to the isolated-world relay via window.postMessage; this script
4
+ // never talks to the receiver itself (the background worker does, outside CSP).
5
+ (() => {
6
+ const SOURCE = "assist-netcap";
7
+
8
+ function post(entry) {
9
+ try {
10
+ window.postMessage({ source: SOURCE, entry }, "*");
11
+ } catch {
12
+ // ignore: structured-clone failures on exotic bodies
13
+ }
14
+ }
15
+
16
+ function bodyToString(body) {
17
+ return typeof body === "string" ? body : null;
18
+ }
19
+
20
+ const originalFetch = window.fetch;
21
+ if (typeof originalFetch === "function") {
22
+ window.fetch = function (...args) {
23
+ const input = args[0];
24
+ const init = args[1] || {};
25
+ const url =
26
+ typeof input === "string" || input instanceof URL
27
+ ? String(input)
28
+ : input && input.url
29
+ ? input.url
30
+ : "";
31
+ const method = (
32
+ init.method ||
33
+ (input && input.method) ||
34
+ "GET"
35
+ ).toUpperCase();
36
+ const requestBody = bodyToString(init.body);
37
+ const result = originalFetch.apply(this, args);
38
+ result
39
+ .then((response) => {
40
+ response
41
+ .clone()
42
+ .text()
43
+ .then((responseBody) => {
44
+ post({
45
+ url,
46
+ method,
47
+ status: response.status,
48
+ requestBody,
49
+ responseBody,
50
+ timestamp: Date.now(),
51
+ });
52
+ })
53
+ .catch(() => {});
54
+ })
55
+ .catch(() => {});
56
+ return result;
57
+ };
58
+ }
59
+
60
+ const OriginalXHR = window.XMLHttpRequest;
61
+ if (OriginalXHR) {
62
+ const open = OriginalXHR.prototype.open;
63
+ const send = OriginalXHR.prototype.send;
64
+ OriginalXHR.prototype.open = function (method, url, ...rest) {
65
+ this.__netcap = { method: String(method).toUpperCase(), url: String(url) };
66
+ return open.call(this, method, url, ...rest);
67
+ };
68
+ OriginalXHR.prototype.send = function (body) {
69
+ const meta = this.__netcap;
70
+ if (meta) {
71
+ meta.requestBody = bodyToString(body);
72
+ this.addEventListener("loadend", () => {
73
+ let responseBody = "";
74
+ try {
75
+ if (this.responseType === "" || this.responseType === "text") {
76
+ responseBody = this.responseText;
77
+ }
78
+ } catch {
79
+ // ignore: responseText unavailable for this responseType
80
+ }
81
+ post({
82
+ url: meta.url,
83
+ method: meta.method,
84
+ status: this.status,
85
+ requestBody: meta.requestBody ?? null,
86
+ responseBody,
87
+ timestamp: Date.now(),
88
+ });
89
+ });
90
+ }
91
+ return send.call(this, body);
92
+ };
93
+ }
94
+ })();
@@ -0,0 +1,25 @@
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "assist netcap",
4
+ "version": "1.0.0",
5
+ "description": "Captures fetch/XHR traffic and forwards it to the local assist netcap receiver.",
6
+ "host_permissions": ["http://*/*"],
7
+ "background": {
8
+ "service_worker": "background.js",
9
+ "scripts": ["background.js"]
10
+ },
11
+ "content_scripts": [
12
+ {
13
+ "matches": ["<all_urls>"],
14
+ "js": ["interceptor.js"],
15
+ "run_at": "document_start",
16
+ "world": "MAIN"
17
+ },
18
+ {
19
+ "matches": ["<all_urls>"],
20
+ "js": ["relay.js"],
21
+ "run_at": "document_start",
22
+ "world": "ISOLATED"
23
+ }
24
+ ]
25
+ }
@@ -0,0 +1,14 @@
1
+ // Isolated-world content script: bridges the MAIN-world interceptor's
2
+ // window.postMessage events to the background service worker via runtime
3
+ // messaging. The background worker, not the page, posts to the receiver, so the
4
+ // page's CSP (connect-src) never applies.
5
+ window.addEventListener("message", (event) => {
6
+ if (event.source !== window) return;
7
+ const data = event.data;
8
+ if (!data || data.source !== "assist-netcap" || !data.entry) return;
9
+ try {
10
+ chrome.runtime.sendMessage({ type: "netcap-entry", entry: data.entry });
11
+ } catch {
12
+ // ignore: extension context invalidated (e.g. after reload)
13
+ }
14
+ });