wield-mcp 0.1.1 → 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.
@@ -1 +1 @@
1
- {"build":"0.1.13+20261107213428.da3a"}
1
+ {"build":"0.1.15+20261107220223.8c74"}
@@ -3061,6 +3061,14 @@
3061
3061
  if (res.error.code === "login_required") return false;
3062
3062
  return null;
3063
3063
  }
3064
+ function pageOf(base) {
3065
+ try {
3066
+ const u = new URL(base);
3067
+ return { origin: u.origin, host: u.host };
3068
+ } catch {
3069
+ return { origin: "", host: "" };
3070
+ }
3071
+ }
3064
3072
  var Run = class {
3065
3073
  constructor(recipe, adapters, opts) {
3066
3074
  this.recipe = recipe;
@@ -3130,12 +3138,12 @@
3130
3138
  this.staticChecks();
3131
3139
  await this.resolveBase();
3132
3140
  const params = prepareParams(this.recipe.params, rawParams);
3133
- const ctx = { params, vars: {}, steps: {}, now: this.opts.now ? this.opts.now() : /* @__PURE__ */ new Date() };
3141
+ const ctx = { params, vars: {}, steps: {}, now: this.opts.now ? this.opts.now() : /* @__PURE__ */ new Date(), page: pageOf(this.base) };
3134
3142
  await this.derive(ctx);
3135
3143
  if (this.recipe.login_check && (this.recipe.requires_login || loginOnly)) await this.loginCheck(ctx);
3136
3144
  await this.runSteps(ctx);
3137
3145
  this.currentStep = void 0;
3138
- let output = this.q({ params: ctx.params, steps: ctx.steps, now: localIsoNow(ctx.now) }, this.recipe.output.value, "output");
3146
+ let output = this.q({ params: ctx.params, steps: ctx.steps, now: localIsoNow(ctx.now), page: ctx.page }, this.recipe.output.value, "output");
3139
3147
  output = redactDeep(normalizeValue(output), this.secrets);
3140
3148
  const duration_ms = Date.now() - this.t0;
3141
3149
  this.emit("done", "info", `ok in ${duration_ms} ms (${this.requests} requests)`);
@@ -3413,7 +3421,7 @@
3413
3421
  let summary = this.recipe.task;
3414
3422
  if (this.recipe.confirm?.summary) {
3415
3423
  try {
3416
- summary = toText(renderString(this.recipe.confirm.summary, { params: ctx.params, steps: ctx.steps, now: ctx.now }));
3424
+ summary = toText(renderString(this.recipe.confirm.summary, { params: ctx.params, steps: ctx.steps, now: ctx.now, page: ctx.page }));
3417
3425
  } catch {
3418
3426
  summary = this.recipe.confirm.summary;
3419
3427
  }
@@ -3754,7 +3762,7 @@
3754
3762
  var enc = new TextEncoder();
3755
3763
 
3756
3764
  // apps/extension/src/shared/types.ts
3757
- var BUILD_ID = "0.1.13+20261107213428.da3a";
3765
+ var BUILD_ID = "0.1.15+20261107220223.8c74";
3758
3766
  var RELOAD_HINT = "Reload the Wield extension once: open chrome://extensions (vivaldi://extensions in Vivaldi) and click the reload arrow on the Wield card, then try again.";
3759
3767
  var LIMITS2 = {
3760
3768
  steps: 12,
@@ -3,7 +3,7 @@
3
3
  "name": "Wield",
4
4
  "short_name": "Wield",
5
5
  "description": "Lets your AI agent use websites through your own browser and logins. Your passwords never leave your device.",
6
- "version": "0.1.13",
6
+ "version": "0.1.15",
7
7
  "minimum_chrome_version": "137",
8
8
  "permissions": [
9
9
  "nativeMessaging",
@@ -12,13 +12,16 @@
12
12
  "storage"
13
13
  ],
14
14
  "host_permissions": [
15
+ "https://en.zalando.de/*",
15
16
  "https://energie.check24.de/*",
16
17
  "https://fahrplan.oebb.at/*",
18
+ "https://regiojet.cz/*",
17
19
  "https://shop.flixbus.de/*",
18
20
  "https://suchen.mobile.de/*",
19
21
  "https://www.11880.com/*",
20
22
  "https://www.2dehands.be/*",
21
23
  "https://www.airbnb.com/*",
24
+ "https://www.allrecipes.com/*",
22
25
  "https://www.alternate.de/*",
23
26
  "https://www.amazon.ca/*",
24
27
  "https://www.amazon.co.uk/*",
@@ -56,6 +59,7 @@
56
59
  "https://www.eventim.de/*",
57
60
  "https://www.finn.no/*",
58
61
  "https://www.gelbeseiten.de/*",
62
+ "https://www.giallozafferano.it/*",
59
63
  "https://www.gumtree.com/*",
60
64
  "https://www.hugendubel.de/*",
61
65
  "https://www.idealo.de/*",
@@ -70,10 +74,13 @@
70
74
  "https://www.kijiji.ca/*",
71
75
  "https://www.kleinanzeigen.de/*",
72
76
  "https://www.lefrecce.it/*",
77
+ "https://www.lidl.de/*",
73
78
  "https://www.lieferando.de/*",
79
+ "https://www.local.ch/*",
74
80
  "https://www.marktplaats.nl/*",
75
81
  "https://www.marmiton.org/*",
76
82
  "https://www.mvg.de/*",
83
+ "https://www.mydealz.de/*",
77
84
  "https://www.ns.nl/*",
78
85
  "https://www.olx.pl/*",
79
86
  "https://www.onthemarket.com/*",
@@ -84,6 +91,7 @@
84
91
  "https://www.reed.co.uk/*",
85
92
  "https://www.rightmove.co.uk/*",
86
93
  "https://www.ryanair.com/*",
94
+ "https://www.spareroom.co.uk/*",
87
95
  "https://www.stepstone.de/*",
88
96
  "https://www.thomann.de/*",
89
97
  "https://www.thuisbezorgd.nl/*",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema": "https://wield.run/schema/recipe/v1.json",
3
3
  "id": "chefkoch.de/search-recipes",
4
- "version": 1,
4
+ "version": 2,
5
5
  "site": {
6
6
  "id": "chefkoch.de",
7
7
  "name": "Chefkoch",
@@ -209,10 +209,10 @@
209
209
  "method": "browser-inspect",
210
210
  "recorded_at": "2026-11-07T12:27:00+01:00",
211
211
  "recorded_by": "C4",
212
- "notes": "spikes/chefkoch.md. Public JSON search gateway used by chefkoch.de; CORS allows https://www.chefkoch.de (with credentials) and no cookies or tokens are needed, so credentials omit. Non-null campaign = brand-sponsored recipe; isPlus = paywalled. difficulty 1/2/3 = simpel/normal/pfiffig."
212
+ "notes": "spikes/chefkoch.md. Public JSON search gateway used by chefkoch.de; CORS allows https://www.chefkoch.de (with credentials) and no cookies or tokens are needed, so credentials omit. Non-null campaign = brand-sponsored recipe; isPlus = paywalled. difficulty 1/2/3 = simpel/normal/pfiffig. v2 (2026-11-07 22:38): tos.notes only; requests and output unchanged."
213
213
  },
214
214
  "tos": {
215
215
  "reviewed": false,
216
- "notes": "Chefkoch's terms restrict automated use of its content. One user-initiated search at a time from the user's browser (1 request); recipe texts stay on chefkoch.de via the link."
216
+ "notes": "Chefkoch's robots.txt disallows AI training crawlers (ClaudeBot, anthropic-ai, Claude-Web). Chefkoch's terms restrict automated use of its content. One user-initiated search at a time from the user's browser (1 request); recipe texts stay on chefkoch.de via the link."
217
217
  }
218
218
  }
@@ -114,12 +114,14 @@ var REGISTRY_KEYS = {
114
114
  // packages/recipe/src/features.ts
115
115
  var INTERPRETER_FEATURES = [
116
116
  /** extract.html / html_items fields with parse: "json" (and path): per-element JSON attributes (CHANGES 2026-11-07 C1). */
117
- "html_json"
117
+ "html_json",
118
+ /** page.host / page.origin in templates and context expressions: the language host the run landed on (CHANGES 22:39 C4). */
119
+ "page_context"
118
120
  ];
119
121
 
120
122
  // apps/extension/src/shared/types.ts
121
123
  var PROTOCOL_VERSION = 1;
122
- var BUILD_ID = "0.1.13+20261107213428.da3a";
124
+ var BUILD_ID = "0.1.15+20261107220223.8c74";
123
125
  var RELOAD_HINT = "Reload the Wield extension once: open chrome://extensions (vivaldi://extensions in Vivaldi) and click the reload arrow on the Wield card, then try again.";
124
126
  var LIMITS2 = {
125
127
  steps: 12,
@@ -794,9 +796,14 @@ chrome.tabs.onUpdated.addListener((tabId, info) => {
794
796
  function originPattern(origin) {
795
797
  return `${origin}/*`;
796
798
  }
797
- async function tabsForOrigin(origin) {
798
- const tabs = await chrome.tabs.query({ url: originPattern(origin) });
799
- return tabs.filter((t) => t.id !== void 0 && t.url && new URL(t.url).origin === origin);
799
+ function siteOrigins(origin, localeHosts = []) {
800
+ const port = new URL(origin).port;
801
+ return [origin, ...localeHosts.map((h) => `https://${h}${port ? `:${port}` : ""}`)];
802
+ }
803
+ async function tabsForOrigin(origin, localeHosts = []) {
804
+ const origins = siteOrigins(origin, localeHosts);
805
+ const tabs = await chrome.tabs.query({ url: origins.map(originPattern) });
806
+ return tabs.filter((t) => t.id !== void 0 && t.url && origins.includes(new URL(t.url).origin));
800
807
  }
801
808
  function waitComplete(tabId, timeoutMs) {
802
809
  return new Promise((resolve, reject) => {
@@ -839,11 +846,12 @@ async function acquireTab(origin, signal, startPath, opts = {}) {
839
846
  const url = startUrl(origin, startPath);
840
847
  const owned = await ownedTabs();
841
848
  const stale = await staleTabs();
842
- const candidates = (await tabsForOrigin(origin)).filter((t) => !t.discarded && !stale.has(t.id)).sort((a, b) => score(b, owned) - score(a, owned));
849
+ const accepted = siteOrigins(origin, opts.localeHosts);
850
+ const candidates = (await tabsForOrigin(origin, opts.localeHosts)).filter((t) => !t.discarded && !stale.has(t.id)).sort((a, b) => score(b, owned) - score(a, owned));
843
851
  const pick = opts.forceNew ? void 0 : candidates[0];
844
852
  if (pick?.id !== void 0) {
845
853
  if (pick.status !== "complete") await waitComplete(pick.id, LIMITS2.tabLoadMs);
846
- return { tabId: pick.id, opened: false, owned: owned.has(pick.id) };
854
+ return { tabId: pick.id, opened: false, owned: owned.has(pick.id), ...pick.url ? { host: new URL(pick.url).host } : {} };
847
855
  }
848
856
  let tab;
849
857
  try {
@@ -859,11 +867,11 @@ async function acquireTab(origin, signal, startPath, opts = {}) {
859
867
  });
860
868
  const done = await waitComplete(tabId, LIMITS2.tabLoadMs);
861
869
  const finalOrigin = done.url ? new URL(done.url).origin : "";
862
- if (finalOrigin !== origin) {
870
+ if (!accepted.includes(finalOrigin)) {
863
871
  throw new ExecError("tab_error", `${origin} redirected the tab to ${finalOrigin || "a site Wield has no access to"}; the recipe's site.origin needs updating`);
864
872
  }
865
873
  if (!signal.aborted) await sleep2(LIMITS2.tabSettleMs);
866
- return { tabId, opened: true, owned: true };
874
+ return { tabId, opened: true, owned: true, ...done.url ? { host: new URL(done.url).host } : {} };
867
875
  }
868
876
  function score(t, owned) {
869
877
  let s = 0;
@@ -923,6 +931,12 @@ function checkRecipe(r) {
923
931
  const site = recipe.site;
924
932
  if (!site || !isAllowedOrigin(site.origin)) bad("site.origin must be https://host");
925
933
  if (!Array.isArray(site.origins) || site.origins.length === 0 || !site.origins.every(isAllowedOrigin)) bad("site.origins must list https origins");
934
+ if (site.locale_hosts !== void 0) {
935
+ if (!Array.isArray(site.locale_hosts) || site.locale_hosts.length > 20) bad("site.locale_hosts must be a list of up to 20 host names");
936
+ for (const h of site.locale_hosts) {
937
+ if (typeof h !== "string" || !/^[a-z0-9-]+(\.[a-z0-9-]+)+$/.test(h) || !(h === site.id || h.endsWith(`.${site.id}`))) bad(`site.locale_hosts: "${String(h)}" must be an exact host name under ${site.id}`);
938
+ }
939
+ }
926
940
  if (!Array.isArray(recipe.steps) || recipe.steps.length < 1) bad("recipe has no steps");
927
941
  if (recipe.steps.length > LIMITS2.steps) throw new ExecError("limit_exceeded", `recipe has ${recipe.steps.length} steps; the limit is ${LIMITS2.steps}`);
928
942
  for (const s of [...recipe.steps, ...recipe.login_check ? [recipe.login_check] : []]) {
@@ -1078,13 +1092,13 @@ function execute(opts) {
1078
1092
  if (tabId === void 0) {
1079
1093
  await ensureHostAccess(recipe, opts.run_id, budget, abort.signal, opts.emit);
1080
1094
  const t0 = Date.now();
1081
- const tab = await acquireTab(recipe.site.origin, abort.signal, recipe.site.start_path, { forceNew: opts.forceNewTab });
1095
+ const tab = await acquireTab(recipe.site.origin, abort.signal, recipe.site.start_path, { forceNew: opts.forceNewTab, localeHosts: recipe.site.locale_hosts ?? [] });
1082
1096
  tabId = tab.tabId;
1083
1097
  if (opts.meta) {
1084
1098
  opts.meta.owned = tab.owned;
1085
1099
  if (tab.opened) opts.meta.openedTabId = tab.tabId;
1086
1100
  }
1087
- const host = new URL(recipe.site.origin).host;
1101
+ const host = tab.host ?? new URL(recipe.site.origin).host;
1088
1102
  opts.emit({ level: "info", kind: "tab", msg: tab.opened ? `opened background tab ${host} (${Date.now() - t0} ms)` : `using open tab ${host}` });
1089
1103
  }
1090
1104
  if (opts.meta) opts.meta.tabId = tabId;
package/dist/index.js CHANGED
@@ -27797,7 +27797,7 @@ function makeCatalog(cfg, http) {
27797
27797
  }
27798
27798
 
27799
27799
  // src/server.ts
27800
- var VERSION = "0.1.1";
27800
+ var VERSION = "0.1.2";
27801
27801
  var OFFLINE_HINT = "Open Chrome with the Wield extension installed";
27802
27802
  function offlineHint(home) {
27803
27803
  if (!existsSync7(paths(home).bridgeLauncher)) {
@@ -28116,11 +28116,17 @@ function createWieldServer(d) {
28116
28116
  const missing = await missingFeatures(recipe);
28117
28117
  if (missing.length) {
28118
28118
  let older;
28119
- if (version2 === void 0 && !fallback && lastGood && lastGood !== recipe.version) {
28120
- try {
28121
- const o = await d.source.recipe(recipe_id, lastGood);
28122
- if (!(await missingFeatures(o.recipe)).length) older = o;
28123
- } catch {
28119
+ if (version2 === void 0 && !fallback) {
28120
+ const candidates = [.../* @__PURE__ */ new Set([lastGood, recipe.version - 1, recipe.version - 2])].filter((v) => typeof v === "number" && v >= 1 && v < recipe.version);
28121
+ for (const v of candidates) {
28122
+ try {
28123
+ const o = await d.source.recipe(recipe_id, v);
28124
+ if (!(await missingFeatures(o.recipe)).length) {
28125
+ older = o;
28126
+ break;
28127
+ }
28128
+ } catch {
28129
+ }
28124
28130
  }
28125
28131
  }
28126
28132
  if (!older) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wield-mcp",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "mcpName": "run.wield/wield",
5
5
  "description": "Websites as tools for your AI agent (MCP). Runs signed recipes in your own browser with your own session: your logins never leave your computer.",
6
6
  "keywords": [