wield-mcp 0.0.0-stage → 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.
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,41 @@
1
+ {
2
+ "type": "object",
3
+ "properties": {
4
+ "org_name": {
5
+ "title": "Organization name",
6
+ "description": "Shown in Wield's popup and errors.",
7
+ "type": "string"
8
+ },
9
+ "allowed_sites": {
10
+ "title": "Allowed sites",
11
+ "description": "Only these sites may be used (e.g. bahn.de or *.doctolib.de). Empty: no restriction.",
12
+ "type": "array",
13
+ "items": {
14
+ "type": "string"
15
+ }
16
+ },
17
+ "writes": {
18
+ "title": "Actions that change something",
19
+ "description": "deny: refuse every recipe with write steps. allow: ask for Allow each time (always).",
20
+ "type": "string",
21
+ "enum": [
22
+ "allow",
23
+ "deny"
24
+ ]
25
+ },
26
+ "telemetry": {
27
+ "title": "Outcome counts",
28
+ "description": "off: wield-mcp must not send outcome counts.",
29
+ "type": "string",
30
+ "enum": [
31
+ "off",
32
+ "allowed"
33
+ ]
34
+ },
35
+ "require_signed": {
36
+ "title": "Require signed recipes",
37
+ "description": "Refuse recipes without a valid wield.run or org signature.",
38
+ "type": "boolean"
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,150 @@
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "Wield",
4
+ "short_name": "Wield",
5
+ "description": "Lets your AI agent use websites through your own browser and logins. Your passwords never leave your device.",
6
+ "version": "0.1.15",
7
+ "minimum_chrome_version": "137",
8
+ "permissions": [
9
+ "nativeMessaging",
10
+ "scripting",
11
+ "activeTab",
12
+ "storage"
13
+ ],
14
+ "host_permissions": [
15
+ "https://en.zalando.de/*",
16
+ "https://energie.check24.de/*",
17
+ "https://fahrplan.oebb.at/*",
18
+ "https://regiojet.cz/*",
19
+ "https://shop.flixbus.de/*",
20
+ "https://suchen.mobile.de/*",
21
+ "https://www.11880.com/*",
22
+ "https://www.2dehands.be/*",
23
+ "https://www.airbnb.com/*",
24
+ "https://www.allrecipes.com/*",
25
+ "https://www.alternate.de/*",
26
+ "https://www.amazon.ca/*",
27
+ "https://www.amazon.co.uk/*",
28
+ "https://www.amazon.com/*",
29
+ "https://www.amazon.de/*",
30
+ "https://www.amazon.es/*",
31
+ "https://www.amazon.fr/*",
32
+ "https://www.amazon.it/*",
33
+ "https://www.amazon.nl/*",
34
+ "https://www.arbeitsagentur.de/*",
35
+ "https://www.autoscout24.at/*",
36
+ "https://www.autoscout24.de/*",
37
+ "https://www.autoscout24.es/*",
38
+ "https://www.autoscout24.fr/*",
39
+ "https://www.autoscout24.it/*",
40
+ "https://www.autoscout24.nl/*",
41
+ "https://www.autotrader.co.uk/*",
42
+ "https://www.bahn.de/*",
43
+ "https://www.bbcgoodfood.com/*",
44
+ "https://www.blocket.se/*",
45
+ "https://www.booking.com/*",
46
+ "https://www.chefkoch.de/*",
47
+ "https://www.dasoertliche.de/*",
48
+ "https://www.dba.dk/*",
49
+ "https://www.doctolib.de/*",
50
+ "https://www.ebay.co.uk/*",
51
+ "https://www.ebay.com/*",
52
+ "https://www.ebay.de/*",
53
+ "https://www.ebay.es/*",
54
+ "https://www.ebay.fr/*",
55
+ "https://www.ebay.it/*",
56
+ "https://www.eventbrite.co.uk/*",
57
+ "https://www.eventbrite.com/*",
58
+ "https://www.eventbrite.de/*",
59
+ "https://www.eventim.de/*",
60
+ "https://www.finn.no/*",
61
+ "https://www.gelbeseiten.de/*",
62
+ "https://www.giallozafferano.it/*",
63
+ "https://www.gumtree.com/*",
64
+ "https://www.hugendubel.de/*",
65
+ "https://www.idealo.de/*",
66
+ "https://www.ikea.com/*",
67
+ "https://www.imdb.com/*",
68
+ "https://www.immobilienscout24.de/*",
69
+ "https://www.immoweb.be/*",
70
+ "https://www.jameda.de/*",
71
+ "https://www.jobs.ch/*",
72
+ "https://www.just-eat.co.uk/*",
73
+ "https://www.karriere.at/*",
74
+ "https://www.kijiji.ca/*",
75
+ "https://www.kleinanzeigen.de/*",
76
+ "https://www.lefrecce.it/*",
77
+ "https://www.lidl.de/*",
78
+ "https://www.lieferando.de/*",
79
+ "https://www.local.ch/*",
80
+ "https://www.marktplaats.nl/*",
81
+ "https://www.marmiton.org/*",
82
+ "https://www.mvg.de/*",
83
+ "https://www.mydealz.de/*",
84
+ "https://www.ns.nl/*",
85
+ "https://www.olx.pl/*",
86
+ "https://www.onthemarket.com/*",
87
+ "https://www.opentable.com/*",
88
+ "https://www.otodom.pl/*",
89
+ "https://www.otto.de/*",
90
+ "https://www.pyszne.pl/*",
91
+ "https://www.reed.co.uk/*",
92
+ "https://www.rightmove.co.uk/*",
93
+ "https://www.ryanair.com/*",
94
+ "https://www.spareroom.co.uk/*",
95
+ "https://www.stepstone.de/*",
96
+ "https://www.thomann.de/*",
97
+ "https://www.thuisbezorgd.nl/*",
98
+ "https://www.ticketmaster.co.uk/*",
99
+ "https://www.ticketmaster.com/*",
100
+ "https://www.ticketmaster.de/*",
101
+ "https://www.ticketmaster.nl/*",
102
+ "https://www.tori.fi/*",
103
+ "https://www.vinted.co.uk/*",
104
+ "https://www.vinted.de/*",
105
+ "https://www.vinted.es/*",
106
+ "https://www.vinted.fr/*",
107
+ "https://www.vinted.it/*",
108
+ "https://www.vinted.nl/*",
109
+ "https://www.vinted.pl/*",
110
+ "https://www.walmart.com/*",
111
+ "https://www.wg-gesucht.de/*",
112
+ "https://www.willhaben.at/*",
113
+ "https://www.youtube.com/*",
114
+ "https://www.zalando.de/*",
115
+ "https://www.zillow.com/*"
116
+ ],
117
+ "optional_host_permissions": [
118
+ "https://*/*"
119
+ ],
120
+ "background": {
121
+ "service_worker": "sw.js",
122
+ "type": "module"
123
+ },
124
+ "action": {
125
+ "default_title": "Wield",
126
+ "default_popup": "popup.html",
127
+ "default_icon": {
128
+ "16": "icons/16.png",
129
+ "32": "icons/32.png"
130
+ }
131
+ },
132
+ "icons": {
133
+ "16": "icons/16.png",
134
+ "32": "icons/32.png",
135
+ "48": "icons/48.png",
136
+ "128": "icons/128.png"
137
+ },
138
+ "externally_connectable": {
139
+ "matches": [
140
+ "https://wield.run/*"
141
+ ]
142
+ },
143
+ "content_security_policy": {
144
+ "extension_pages": "script-src 'self'; object-src 'self'; connect-src 'self'; img-src 'self' data:; font-src 'self'"
145
+ },
146
+ "storage": {
147
+ "managed_schema": "managed_schema.json"
148
+ },
149
+ "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsho7lWZClmnKKB3lw7dV1NbUJKal1ki8eDz0DmNdxaIa/ejaSAi3S+eQ9jsk2vERxt0DwoCGwH4iGrV6DfdhNIm2t7/iGiwfBh5DbzYnA4clv3BkOrPb9No3t5QdAgptW8zYLse/UR89GMrVOUaAob1Jn0uEbQtR2uY1WOb/58fhoDx26qOJSxsFX3G6LgwOUdWZZOSndedrGOR2XMzVomSI9zd3+0ANTupYX7ouwgYEYJ1+ICyBe4bHfmWOreRhennx+X594qxlAOaRqjetcScrD5/npmsLuEwH+2RrlKyEKT0tal8AOQKGLqzRKhGDU1sWKYXSgztY65/Djq0dbwIDAQAB"
150
+ }
@@ -0,0 +1,77 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Wield</title>
6
+ <link rel="stylesheet" href="tokens.css">
7
+ <link rel="stylesheet" href="ui.css">
8
+ <style>
9
+ body { width: 380px; max-height: 590px; overflow-y: auto; }
10
+ </style>
11
+ </head>
12
+ <body>
13
+ <header class="top">
14
+ <span class="brand" aria-label="Wield">
15
+ <svg viewBox="0 0 24 24" aria-hidden="true"><defs><linearGradient id="wm-hd" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#ffd85a"/><stop offset=".5" stop-color="#ff9a24"/><stop offset="1" stop-color="#ff5200"/></linearGradient><linearGradient id="wm-ht" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#ff6a0a"/><stop offset="1" stop-color="currentColor"/></linearGradient></defs><g transform="translate(0 -2.45)"><path d="M7.0 11.6 2.4 16.6 7.0 21.6M17.0 11.6 21.6 16.6 17.0 21.6" fill="none" stroke="currentColor" stroke-width="2.7" stroke-linecap="round" stroke-linejoin="round"/><path d="M11.0 21.6 12.6 10.28" stroke="currentColor" stroke-width="2.3" stroke-linecap="round"/><g transform="translate(12.6 10.28) rotate(8.045051779050041)"><rect x="-1.15" y="-.2" width="2.3" height="2.4" fill="url(#wm-ht)"/></g><g transform="translate(12.908 8.102) rotate(8.05) scale(0.84)"><path d="M-4.6 -1.9 -2.9 -1.9 -2.4 -1.5 1.3 -1.5 4.4 -0.55 4.6 -0.2 4.6 0.2 4.4 0.55 1.3 1.5 -2.4 1.5 -2.9 1.9 -4.6 1.9Z" fill="url(#wm-hd)" stroke="url(#wm-hd)" stroke-width=".35" stroke-linejoin="round"/><path d="M-4.2 -1.25H-2.9M-1.9 -.95H1.0" stroke="#fff1b8" stroke-width=".55" stroke-linecap="round" opacity=".85"/><rect x="-1.4" y="1.35" width="2.8" height=".8" rx=".3" fill="currentColor"/></g><rect x="7.01" y="5.50" width="1.3" height="1.3" fill="#ffc23d"/><rect x="5.41" y="7.20" width=".9" height=".9" fill="#ff8a1f"/></g></svg>
16
+ <b>wield</b>
17
+ </span>
18
+ <span id="bridge" class="badge" role="status">Checking</span>
19
+ </header>
20
+
21
+ <section aria-labelledby="h-exec">
22
+ <h2 class="label" id="h-exec">Executor</h2>
23
+ <p id="exec" class="mono muted" style="margin:0"></p>
24
+ <p id="pace" class="hint num" style="margin:4px 0 0"></p>
25
+ <div id="rebuilt" hidden>
26
+ <p class="hint">Wield was rebuilt on disk. Runs stop until it reloads.</p>
27
+ <button class="btn btn-primary btn-sm" id="reload" type="button" style="margin-top:8px">Reload Wield</button>
28
+ </div>
29
+ <div id="offline" hidden>
30
+ <p class="hint">The bridge is not running. Run <code>npx -y wield-mcp init</code> once, then reconnect. Chrome starts the bridge itself.</p>
31
+ <p class="hint mono" id="bridge-error"></p>
32
+ <button class="btn btn-secondary btn-sm" id="reconnect" type="button" style="margin-top:8px">Reconnect</button>
33
+ </div>
34
+ </section>
35
+
36
+ <section aria-labelledby="h-rec" id="rec-section">
37
+ <h2 class="label" id="h-rec">Teach a site</h2>
38
+ <div id="rec-idle">
39
+ <p class="hint" style="margin:0 0 8px">Do a task once in this tab; Wield turns it into a recipe for your agent. You review everything before it is sent.</p>
40
+ <div class="rec-start">
41
+ <input id="rec-task" class="input" type="text" placeholder="What will you do? e.g. search trains" maxlength="200" aria-label="What will you do?">
42
+ <button class="btn btn-secondary btn-sm" id="rec-go" type="button">Record</button>
43
+ </div>
44
+ <p class="hint" id="rec-msg" role="status"></p>
45
+ </div>
46
+ <div id="rec-active" hidden>
47
+ <p class="mono" style="margin:0 0 8px" id="rec-what"></p>
48
+ <button class="btn btn-secondary btn-sm" id="rec-cancel" type="button">Cancel</button>
49
+ <button class="btn btn-primary btn-sm" id="rec-done" type="button">Done</button>
50
+ </div>
51
+ </section>
52
+
53
+ <!-- The run log first: it is the proof of what left the browser. -->
54
+ <section aria-labelledby="h-runs">
55
+ <h2 class="label" id="h-runs">Recent runs</h2>
56
+ <ul class="rows" id="runs"></ul>
57
+ </section>
58
+
59
+ <section aria-labelledby="h-sites">
60
+ <h2 class="label" id="h-sites">Sites</h2>
61
+ <ul class="rows" id="sites"></ul>
62
+ <details class="sites-all" id="sites-all-wrap">
63
+ <summary>All built-in sites (<span id="sites-all-count">0</span>)</summary>
64
+ <ul class="rows" id="sites-all"></ul>
65
+ </details>
66
+ </section>
67
+
68
+ <section aria-labelledby="h-dev">
69
+ <h2 class="label" id="h-dev">Developer</h2>
70
+ <label class="dev-toggle"><input type="checkbox" id="dev-mode"> Run local recipes</label>
71
+ <p class="hint" style="margin:4px 0 0">Lets programs on this computer run recipes that the registry hasn't signed, such as ones you are writing. Leave it off otherwise. Changes on a site still ask you first.</p>
72
+ </section>
73
+
74
+ <p class="proof">Wield sent nothing but the requests listed here, each from this browser to the site itself. Your passwords and cookies went nowhere else.</p>
75
+ <script src="popup.js"></script>
76
+ </body>
77
+ </html>
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ (() => {
3
+ // apps/extension/src/ui/popup.ts
4
+ var $ = (id) => document.getElementById(id);
5
+ function el(tag, cls, text) {
6
+ const e = document.createElement(tag);
7
+ if (cls) e.className = cls;
8
+ if (text !== void 0) e.textContent = text;
9
+ return e;
10
+ }
11
+ function clock(iso) {
12
+ if (!iso) return "";
13
+ return new Date(iso).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
14
+ }
15
+ function secs(ms) {
16
+ return ms < 1e3 ? `${ms} ms` : `${(ms / 1e3).toFixed(1)} s`;
17
+ }
18
+ function renderBridge(s) {
19
+ const badge = $("bridge");
20
+ const b = s.bridge.state;
21
+ badge.className = `badge ${b === "connected" ? "ok" : b === "connecting" ? "" : "fail"}`;
22
+ badge.textContent = b === "connected" ? "Bridge connected" : b === "connecting" ? "Connecting" : "Bridge offline";
23
+ const since = s.bridge.since ? ` since ${clock(s.bridge.since)}` : "";
24
+ $("exec").title = `build ${s.build}`;
25
+ $("rebuilt").hidden = !s.pending_build;
26
+ $("exec").textContent = `${s.browser} \xB7 extension ${s.version}${b === "connected" ? ` \xB7 connected${since}` : ""}${s.active_runs ? ` \xB7 ${s.active_runs} running` : ""}`;
27
+ $("offline").hidden = b === "connected";
28
+ const pace = Object.entries(s.pace ?? {}).filter(([, v]) => v.last_hour > 0).sort((x, y) => y[1].last_hour - x[1].last_hour);
29
+ $("pace").textContent = pace.length ? `This hour: ${pace.map(([k, v]) => `${k} ${v.last_hour}/${v.per_hour}`).join(" \xB7 ")}` : "";
30
+ $("bridge-error").textContent = s.bridge.last_error ?? "";
31
+ }
32
+ function siteBadge(r) {
33
+ if (r.policy_blocked_by) return ["fail", `Blocked by ${r.policy_blocked_by}`, `Your organization's policy doesn't allow this site.`];
34
+ if (r.paused) return ["warn", "Paused", "Your agent can\u2019t use this site until you resume it."];
35
+ if (r.last_run && !r.last_run.ok && r.last_run.error_code === "site_challenge") return ["warn", "Needs a quick check", `The site asked for a bot check. Open ${r.host} and click once on the page.`];
36
+ if (r.last_run && !r.last_run.ok && r.last_run.error_code === "login_required") return ["warn", "Log in needed", `Log in to ${r.host} in this browser, then try again.`];
37
+ if (r.logged_in === true) return ["ok", "Logged in", null];
38
+ if (r.logged_in === false) return ["warn", "Logged out", null];
39
+ if (r.last_run) return [r.last_run.ok ? "ok" : "fail", r.last_run.ok ? "Working" : r.last_run.error_code?.replace(/_/g, " ") ?? "Failed", null];
40
+ return ["", r.access === "granted" ? "Allowed" : "Ready", null];
41
+ }
42
+ function siteRow(r, compact) {
43
+ const li = el("li", "site");
44
+ li.append(el("span", "name", r.host.replace(/^www\./, "")));
45
+ const [cls, text, hint] = siteBadge(r);
46
+ li.append(el("span", `badge ${cls}`, text));
47
+ if (!compact) {
48
+ const bits = [r.access === "built-in" ? "built in" : r.access === "granted" ? "you allowed it" : "no access", r.tab_open ? "tab open" : null, r.pace?.last_hour ? `${r.pace.last_hour}/${r.pace.per_hour} this hour` : null];
49
+ li.append(el("span", "sub", bits.filter(Boolean).join(" \xB7 ")));
50
+ if (hint) li.append(el("span", "sub site-hint", hint));
51
+ }
52
+ const actions = el("span", "site-actions");
53
+ if (!r.policy_blocked_by) {
54
+ const pause = el("button", "btn btn-ghost btn-xs", r.paused ? "Resume" : "Pause");
55
+ pause.type = "button";
56
+ pause.setAttribute("aria-label", `${r.paused ? "Resume" : "Pause"} ${r.host}`);
57
+ pause.addEventListener("click", async () => {
58
+ await chrome.runtime.sendMessage({ type: "site_pause", host: r.host, paused: !r.paused });
59
+ void refresh();
60
+ });
61
+ actions.append(pause);
62
+ }
63
+ if (r.access === "granted") {
64
+ const revoke = el("button", "btn btn-ghost btn-xs", "Remove access");
65
+ revoke.type = "button";
66
+ revoke.setAttribute("aria-label", `Remove Wield's access to ${r.host}`);
67
+ revoke.addEventListener("click", async () => {
68
+ await chrome.runtime.sendMessage({ type: "site_revoke", origin: r.origin });
69
+ void refresh();
70
+ });
71
+ actions.append(revoke);
72
+ }
73
+ li.append(actions);
74
+ return li;
75
+ }
76
+ function renderSites(rows, policy) {
77
+ const ul = $("sites");
78
+ ul.replaceChildren();
79
+ const active = rows.filter((r) => r.active);
80
+ const rest = rows.filter((r) => !r.active);
81
+ if (policy) {
82
+ const org = policy.orgs.map((o) => `${o.name}${o.expired ? " (expired, still applied)" : ""}`).join(", ");
83
+ const li = el("li", "site site-policy");
84
+ li.append(el("span", "name", `Managed by ${org}`));
85
+ li.append(el("span", "sub", [policy.allowed_sites ? `only ${policy.allowed_sites.flat().join(", ")}` : "all sites", policy.writes === "deny" ? "no actions that change something" : "actions need your Allow"].join(" \xB7 ")));
86
+ ul.append(li);
87
+ }
88
+ if (!active.length) ul.append(el("li", "empty", "No site used yet. Your agent\u2019s runs show up here."));
89
+ for (const r of active) ul.append(siteRow(r, false));
90
+ $("sites-all-count").textContent = String(rest.length);
91
+ const all = $("sites-all");
92
+ all.replaceChildren(...rest.map((r) => siteRow(r, true)));
93
+ $("sites-all-wrap").hidden = rest.length === 0;
94
+ }
95
+ function renderRuns(runs) {
96
+ const ul = $("runs");
97
+ ul.replaceChildren();
98
+ if (!runs.length) {
99
+ ul.append(el("li", "empty", "No runs yet."));
100
+ return;
101
+ }
102
+ for (const r of runs.slice(0, 12)) {
103
+ const li = el("li");
104
+ const head = el("div", "run-head");
105
+ head.append(el("span", "id", r.kind === "status" ? `${r.site_id} \xB7 login check` : `${r.recipe_id} v${r.version}`));
106
+ head.append(el("span", `badge ${r.ok ? "ok" : "fail"}`, r.ok ? "ok" : (r.error_code ?? "failed").replace(/_/g, " ")));
107
+ li.append(head);
108
+ li.append(el("span", "run-meta num", `${secs(r.duration_ms)} \xB7 ${clock(r.started_at)} \xB7 ${r.requests.length} ${r.requests.length === 1 ? "request" : "requests"}`));
109
+ if (r.requests.length) {
110
+ const hosts = [...new Set(r.requests.map((q) => new URL(q.origin).host))];
111
+ const reqs = el("ul", "reqs");
112
+ reqs.setAttribute("aria-label", `Requests sent to ${hosts.join(", ")}`);
113
+ if (hosts.length === 1) reqs.append(el("li", "dest", `to ${hosts[0]}`));
114
+ for (const q of r.requests) {
115
+ const row = el("li");
116
+ row.append(el("span", "", q.method));
117
+ const p = el("span", "path");
118
+ if (hosts.length > 1) p.append(el("span", "host", new URL(q.origin).host));
119
+ q.path.split("/").forEach((seg, i) => {
120
+ if (i > 0) p.append("/", document.createElement("wbr"));
121
+ p.append(seg);
122
+ });
123
+ p.title = `${q.origin}${q.path}`;
124
+ row.append(p);
125
+ row.append(el("span", `st num${q.status >= 400 || q.status === 0 ? " bad" : ""}`, String(q.status || "err")));
126
+ row.append(el("span", "ms num", `${q.ms} ms`));
127
+ reqs.append(row);
128
+ }
129
+ li.append(reqs);
130
+ } else {
131
+ li.append(el("span", "muted", "No requests sent."));
132
+ }
133
+ ul.append(li);
134
+ }
135
+ }
136
+ function renderRecording(s) {
137
+ const r = s.recording;
138
+ $("rec-idle").hidden = !!r;
139
+ $("rec-active").hidden = !r;
140
+ if (r) $("rec-what").textContent = `${r.state === "review" ? "Reviewing" : "Recording"} on ${new URL(r.origin).host} \xB7 ${r.requests} requests`;
141
+ }
142
+ async function refresh() {
143
+ const s = await chrome.runtime.sendMessage({ type: "popup_state" });
144
+ renderRecording(s);
145
+ renderBridge(s);
146
+ renderSites(s.sites_overview ?? [], s.policy ?? null);
147
+ renderRuns(s.runs);
148
+ }
149
+ $("rec-go").addEventListener("click", async () => {
150
+ const msg = $("rec-msg");
151
+ const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
152
+ if (!tab?.id || !tab.url || !/^https:\/\//.test(tab.url)) {
153
+ msg.textContent = "Open the site you want to teach in this tab first (https only).";
154
+ return;
155
+ }
156
+ msg.textContent = "Starting\u2026";
157
+ const r = await chrome.runtime.sendMessage({ type: "record_begin", tabId: tab.id, task: $("rec-task").value });
158
+ if (!r.ok) msg.textContent = r.error ?? "Could not start recording.";
159
+ else window.close();
160
+ });
161
+ $("rec-done").addEventListener("click", async () => {
162
+ await chrome.runtime.sendMessage({ type: "record_stop", how: "done" });
163
+ window.close();
164
+ });
165
+ $("rec-cancel").addEventListener("click", async () => {
166
+ await chrome.runtime.sendMessage({ type: "record_stop", how: "cancel" });
167
+ void refresh();
168
+ });
169
+ $("reload").addEventListener("click", () => {
170
+ void chrome.runtime.sendMessage({ type: "reload_extension" });
171
+ window.close();
172
+ });
173
+ $("reconnect").addEventListener("click", async () => {
174
+ await chrome.runtime.sendMessage({ type: "reconnect" });
175
+ setTimeout(refresh, 1200);
176
+ });
177
+ var devBox = $("dev-mode");
178
+ void chrome.storage.local.get("dev_mode").then((v) => devBox.checked = v.dev_mode === true);
179
+ devBox.addEventListener("change", () => void chrome.storage.local.set({ dev_mode: devBox.checked }));
180
+ chrome.storage.onChanged.addListener(() => void refresh());
181
+ void refresh();
182
+ })();
@@ -0,0 +1,206 @@
1
+ "use strict";
2
+ (() => {
3
+ // apps/extension/src/content/recmain.ts
4
+ (() => {
5
+ const KEY = "__wield_rec__";
6
+ const NAV_KEY = "__wield_rec_nav__";
7
+ const MAX_BODY = 1e6;
8
+ const TEXT = /(json|html|xml|text\/|javascript|x-www-form-urlencoded|graphql|turbo-stream)/i;
9
+ const w = window;
10
+ let nonce = null;
11
+ try {
12
+ nonce = sessionStorage.getItem(KEY);
13
+ } catch {
14
+ return;
15
+ }
16
+ if (!nonce || w.__wieldRecNonce === nonce) return;
17
+ w.__wieldRecNonce = nonce;
18
+ const myNonce = nonce;
19
+ const active = () => {
20
+ try {
21
+ return sessionStorage.getItem(KEY) === myNonce;
22
+ } catch {
23
+ return false;
24
+ }
25
+ };
26
+ let ready = false;
27
+ const queue = [];
28
+ const flush = () => {
29
+ if (!ready) return;
30
+ while (queue.length) window.postMessage({ source: "wield-rec", nonce: myNonce, entry: queue.shift() }, location.origin);
31
+ };
32
+ const post = (entry) => {
33
+ if (!active() || queue.length > 400) return;
34
+ queue.push(entry);
35
+ flush();
36
+ };
37
+ window.addEventListener("message", (e) => {
38
+ const d = e.data;
39
+ if (e.source !== window || !d || d.source !== "wield-rec-ctl" || d.nonce !== myNonce) return;
40
+ if (d.cmd === "ready") {
41
+ ready = true;
42
+ flush();
43
+ }
44
+ });
45
+ window.postMessage({ source: "wield-rec-hello", nonce: myNonce }, location.origin);
46
+ const cap = (s) => typeof s === "string" ? s.length > MAX_BODY ? { body: s.slice(0, MAX_BODY), body_truncated: true } : { body: s } : {};
47
+ const headersOf = (h) => {
48
+ const o = {};
49
+ h.forEach((v, k) => o[k] = v);
50
+ return o;
51
+ };
52
+ const formText = (fd) => {
53
+ const p = new URLSearchParams();
54
+ fd.forEach((v, k) => typeof v === "string" && p.append(k, v));
55
+ return p.toString();
56
+ };
57
+ const bodyText = (b) => {
58
+ if (b == null) return void 0;
59
+ if (typeof b === "string") return b.slice(0, 65536);
60
+ if (b instanceof URLSearchParams) return b.toString().slice(0, 65536);
61
+ if (b instanceof FormData) return formText(b).slice(0, 65536);
62
+ return "[binary body]";
63
+ };
64
+ const sendsCookies = (url, credentials) => {
65
+ try {
66
+ const sameOrigin = new URL(url).origin === location.origin;
67
+ return credentials === "include" || sameOrigin && credentials !== "omit";
68
+ } catch {
69
+ return false;
70
+ }
71
+ };
72
+ const ofetch = window.fetch;
73
+ window.fetch = function(input, init) {
74
+ let method = "GET";
75
+ let url = "";
76
+ let credentials;
77
+ const request_headers = {};
78
+ let post_data;
79
+ let reqBody;
80
+ const started = Date.now();
81
+ try {
82
+ if (input instanceof Request) {
83
+ method = input.method;
84
+ url = input.url;
85
+ credentials = input.credentials;
86
+ input.headers.forEach((v, k) => request_headers[k] = v);
87
+ if (!init?.body && method !== "GET" && method !== "HEAD") reqBody = input.clone().text().catch(() => "");
88
+ } else url = input instanceof URL ? input.href : String(input);
89
+ if (init?.method) method = String(init.method).toUpperCase();
90
+ if (init?.credentials) credentials = init.credentials;
91
+ if (init?.headers) new Headers(init.headers).forEach((v, k) => request_headers[k] = v);
92
+ post_data = bodyText(init?.body);
93
+ url = new URL(url, location.href).href;
94
+ } catch {
95
+ }
96
+ const p = ofetch.apply(this, arguments);
97
+ if (!/^https?:/.test(url) || !active()) return p;
98
+ p.then(
99
+ async (res) => {
100
+ const mime = res.headers.get("content-type") ?? "";
101
+ const len = Number(res.headers.get("content-length") ?? 0);
102
+ let text;
103
+ if (TEXT.test(mime) && !/event-stream/i.test(mime) && len <= 4 * MAX_BODY) text = await res.clone().text().catch(() => void 0);
104
+ if (reqBody) post_data = (await reqBody).slice(0, 65536);
105
+ post({ kind: "fetch", method, url, ...res.url && res.url !== url ? { final_url: res.url } : {}, started, status: res.status, mime, request_headers, ...post_data !== void 0 ? { post_data } : {}, response_headers: headersOf(res.headers), ...cap(text), sends_cookies: sendsCookies(url, credentials) });
106
+ },
107
+ (err) => post({ kind: "fetch", method, url, started, request_headers, failed: String(err?.message ?? err).slice(0, 200) })
108
+ );
109
+ return p;
110
+ };
111
+ const META = /* @__PURE__ */ new WeakMap();
112
+ const XP = XMLHttpRequest.prototype;
113
+ const oOpen = XP.open;
114
+ const oSend = XP.send;
115
+ const oSet = XP.setRequestHeader;
116
+ XP.open = function(method, url) {
117
+ try {
118
+ META.set(this, { method: String(method).toUpperCase(), url: new URL(String(url), location.href).href, headers: {} });
119
+ } catch {
120
+ }
121
+ return oOpen.apply(this, arguments);
122
+ };
123
+ XP.setRequestHeader = function(k, v) {
124
+ const m = META.get(this);
125
+ if (m) m.headers[String(k).toLowerCase()] = String(v);
126
+ return oSet.call(this, k, v);
127
+ };
128
+ XP.send = function(body) {
129
+ const m = META.get(this);
130
+ if (m && /^https?:/.test(m.url) && active()) {
131
+ m.started = Date.now();
132
+ m.post_data = bodyText(body);
133
+ this.addEventListener("loadend", () => {
134
+ let text;
135
+ try {
136
+ if (this.responseType === "" || this.responseType === "text") text = this.responseText;
137
+ else if (this.responseType === "json") text = JSON.stringify(this.response);
138
+ } catch {
139
+ }
140
+ const mime = this.getResponseHeader("content-type") ?? "";
141
+ const response_headers = {};
142
+ for (const line of this.getAllResponseHeaders().trim().split(/[\r\n]+/)) {
143
+ const i = line.indexOf(":");
144
+ if (i > 0) response_headers[line.slice(0, i).trim().toLowerCase()] = line.slice(i + 1).trim();
145
+ }
146
+ post({
147
+ kind: "xhr",
148
+ method: m.method,
149
+ url: m.url,
150
+ started: m.started,
151
+ status: this.status,
152
+ mime,
153
+ request_headers: m.headers,
154
+ ...m.post_data !== void 0 ? { post_data: m.post_data } : {},
155
+ response_headers,
156
+ ...TEXT.test(mime) ? cap(text) : {},
157
+ ...this.status === 0 ? { failed: "network error or aborted" } : {},
158
+ sends_cookies: sendsCookies(m.url, this.withCredentials ? "include" : "same-origin")
159
+ });
160
+ });
161
+ }
162
+ return oSend.call(this, body);
163
+ };
164
+ window.addEventListener("submit", (e) => {
165
+ const form = e.target;
166
+ if (!form || e.defaultPrevented || !active()) return;
167
+ const method = (form.method || "get").toUpperCase();
168
+ if (method !== "POST") return;
169
+ try {
170
+ const fd = new FormData(form, e.submitter ?? void 0);
171
+ for (const el of Array.from(form.elements)) {
172
+ if (el.name && (el.type === "password" || el.getAttribute?.("autocomplete")?.includes("password"))) fd.delete(el.name);
173
+ }
174
+ sessionStorage.setItem(NAV_KEY, JSON.stringify({ method, url: new URL(form.action || location.href, location.href).href, post_data: formText(fd).slice(0, 65536), started: Date.now() }));
175
+ } catch {
176
+ }
177
+ });
178
+ const snapshotDocument = (fromRecordingStart) => {
179
+ const nav = performance.getEntriesByType("navigation")[0];
180
+ let pending = null;
181
+ try {
182
+ pending = JSON.parse(sessionStorage.getItem(NAV_KEY) ?? "null");
183
+ sessionStorage.removeItem(NAV_KEY);
184
+ } catch {
185
+ pending = null;
186
+ }
187
+ const viaForm = pending && Date.now() - pending.started < 6e4 ? pending : null;
188
+ post({
189
+ kind: "document",
190
+ method: viaForm ? viaForm.method : "GET",
191
+ url: location.href,
192
+ started: fromRecordingStart ? Date.now() : Math.round(performance.timeOrigin),
193
+ status: nav?.responseStatus || 200,
194
+ mime: document.contentType || "text/html",
195
+ request_headers: {},
196
+ ...viaForm ? { post_data: viaForm.post_data } : {},
197
+ response_headers: {},
198
+ ...cap(document.documentElement?.outerHTML),
199
+ sends_cookies: true,
200
+ start_page: fromRecordingStart
201
+ });
202
+ };
203
+ if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", () => snapshotDocument(false), { once: true });
204
+ else snapshotDocument(true);
205
+ })();
206
+ })();