unbrowse 3.7.0 → 3.7.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.
package/dist/cli.js CHANGED
@@ -31,7 +31,7 @@ var __promiseAll = (args) => Promise.all(args);
31
31
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
32
32
 
33
33
  // ../../src/build-info.generated.ts
34
- var BUILD_RELEASE_VERSION = "3.7.0", BUILD_GIT_SHA = "051ddb5b3add", BUILD_CODE_HASH = "5d9ebf619c61", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiMy43LjAiLCJnaXRfc2hhIjoiMDUxZGRiNWIzYWRkIiwiY29kZV9oYXNoIjoiNWQ5ZWJmNjE5YzYxIiwidHJhY2VfdmVyc2lvbiI6IjVkOWViZjYxOWM2MUAwNTFkZGI1YjNhZGQiLCJpc3N1ZWRfYXQiOiIyMDI2LTA0LTEwVDAzOjA3OjU1Ljg4MloifQ", BUILD_RELEASE_MANIFEST_SIGNATURE = "TKsUZaH-T1iT7XTgsV8ykdoZDHQj05PQkkmGr0Yf3dQ", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai";
34
+ var BUILD_RELEASE_VERSION = "3.7.1", BUILD_GIT_SHA = "90d1969f3abe", BUILD_CODE_HASH = "5d9ebf619c61", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiMy43LjEiLCJnaXRfc2hhIjoiOTBkMTk2OWYzYWJlIiwiY29kZV9oYXNoIjoiNWQ5ZWJmNjE5YzYxIiwidHJhY2VfdmVyc2lvbiI6IjVkOWViZjYxOWM2MUA5MGQxOTY5ZjNhYmUiLCJpc3N1ZWRfYXQiOiIyMDI2LTA0LTEwVDA4OjQyOjEwLjg1NloifQ", BUILD_RELEASE_MANIFEST_SIGNATURE = "R5-PEYUVNGWtRmubf9WAMDDuH1_C2N6D4xteuGelBIM", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai";
35
35
 
36
36
  // ../../src/version.ts
37
37
  import { createHash } from "crypto";
@@ -3375,43 +3375,59 @@ async function bootstrapAgentMailKey() {
3375
3375
  let currentUrl = await getCurrentUrl(tabId);
3376
3376
  let onDashboard = typeof currentUrl === "string" && currentUrl.includes("/dashboard");
3377
3377
  if (!onDashboard) {
3378
- log("bootstrap-agentmail", "not logged in — looking for GitHub OAuth");
3379
- const githubBtn = await evaluate(tabId, `(() => {
3380
- // Clerk renders OAuth buttons — look for GitHub
3378
+ log("bootstrap-agentmail", "not logged in — looking for social OAuth");
3379
+ const clickResult = await evaluate(tabId, `(() => {
3380
+ // Clerk's social button class pattern
3381
+ const socialBtns = document.querySelectorAll('.cl-socialButtonsBlockButton, [data-provider]');
3382
+ for (const btn of socialBtns) {
3383
+ const text = (btn.textContent || '').toLowerCase();
3384
+ const provider = btn.getAttribute('data-provider') || '';
3385
+ // Match any social provider — Google, GitHub, etc.
3386
+ if (text || provider) {
3387
+ btn.click();
3388
+ return 'clicked:' + (provider || text.slice(0, 20));
3389
+ }
3390
+ }
3391
+ // Fallback: look for any button with a known OAuth provider name
3381
3392
  const btns = document.querySelectorAll('button, a');
3382
3393
  for (const btn of btns) {
3383
3394
  const text = (btn.textContent || '').toLowerCase();
3384
- const ariaLabel = (btn.getAttribute('aria-label') || '').toLowerCase();
3385
- if (text.includes('github') || ariaLabel.includes('github') ||
3386
- btn.querySelector('img[alt*="github" i], svg[aria-label*="github" i]')) {
3395
+ if (text.includes('google') || text.includes('github') || text.includes('continue with')) {
3387
3396
  btn.click();
3388
- return 'clicked';
3397
+ return 'clicked:' + text.slice(0, 30);
3389
3398
  }
3390
3399
  }
3391
- // Also check for social login containers
3392
- const social = document.querySelector('.cl-socialButtonsBlockButton__github, [data-provider="github"]');
3393
- if (social) { social.click(); return 'clicked'; }
3394
3400
  return 'not_found';
3395
3401
  })()`);
3396
- if (githubBtn === "clicked") {
3397
- log("bootstrap-agentmail", "clicked GitHub OAuth — waiting for redirect");
3402
+ const clickStr = typeof clickResult === "string" ? clickResult : String(clickResult);
3403
+ log("bootstrap-agentmail", `social button result: ${clickStr}`);
3404
+ if (clickStr.startsWith("clicked")) {
3405
+ log("bootstrap-agentmail", "clicked social OAuth — waiting for redirect");
3398
3406
  const start2 = Date.now();
3399
3407
  while (Date.now() - start2 < AUTH_TIMEOUT_MS) {
3400
3408
  await new Promise((r) => setTimeout(r, 2000));
3401
3409
  currentUrl = await getCurrentUrl(tabId);
3402
3410
  if (typeof currentUrl === "string" && currentUrl.includes("/dashboard")) {
3403
3411
  onDashboard = true;
3404
- log("bootstrap-agentmail", "GitHub OAuth completed — on dashboard");
3412
+ log("bootstrap-agentmail", "OAuth completed — on dashboard");
3405
3413
  break;
3406
3414
  }
3407
3415
  }
3408
3416
  }
3409
3417
  if (!onDashboard) {
3410
3418
  log("bootstrap-agentmail", "could not auto-login — manual setup needed");
3419
+ const pageInfo = await evaluate(tabId, `JSON.stringify({
3420
+ url: window.location.href,
3421
+ title: document.title,
3422
+ visible_buttons: Array.from(document.querySelectorAll('button,a'))
3423
+ .map(e => (e.textContent || '').trim().slice(0, 30))
3424
+ .filter(t => t)
3425
+ .slice(0, 10),
3426
+ })`).catch(() => "{}");
3411
3427
  return {
3412
3428
  success: false,
3413
3429
  method: "manual",
3414
- error: "Could not auto-login to AgentMail console. Sign up at https://console.agentmail.to and create an API key manually."
3430
+ error: `Could not auto-login to AgentMail console. Page state: ${pageInfo}. Sign up at https://console.agentmail.to and create an API key manually, then: export AGENTMAIL_API_KEY=<key>`
3415
3431
  };
3416
3432
  }
3417
3433
  }
@@ -6792,6 +6808,11 @@ async function cmdSetup(flags) {
6792
6808
  properties: { command: "setup" }
6793
6809
  });
6794
6810
  info("Running setup checks");
6811
+ try {
6812
+ await ensureRegistered({ promptForEmail: false, exitOnFailure: false });
6813
+ } catch (err) {
6814
+ info(`[setup] background registration issue: ${err instanceof Error ? err.message : err}`);
6815
+ }
6795
6816
  const report = await runSetup({
6796
6817
  cwd: process.cwd(),
6797
6818
  opencode: normalizeSetupScope(flags.opencode),
@@ -7603,8 +7624,22 @@ async function cmdLoginAuto(args, flags) {
7603
7624
  }
7604
7625
  })();
7605
7626
  const { autonomousEmailLogin: autonomousEmailLogin3, isAgentMailAvailable: isAgentMailAvailable3 } = await init_agent_mail2().then(() => exports_agent_mail2);
7606
- if (!isAgentMailAvailable3())
7607
- return die("AGENTMAIL_API_KEY not set \u2014 run: export AGENTMAIL_API_KEY=<key>");
7627
+ if (!isAgentMailAvailable3()) {
7628
+ info(`[login-auto] AGENTMAIL_API_KEY not set \u2014 attempting auto-bootstrap via console.agentmail.to`);
7629
+ try {
7630
+ const { bootstrapAgentMailKey: bootstrapAgentMailKey2 } = await init_bootstrap_agentmail().then(() => exports_bootstrap_agentmail);
7631
+ const bootstrap = await bootstrapAgentMailKey2();
7632
+ if (bootstrap.success && bootstrap.api_key) {
7633
+ process.env.AGENTMAIL_API_KEY = bootstrap.api_key;
7634
+ info(`[login-auto] bootstrapped AgentMail key via ${bootstrap.method ?? "browser"}`);
7635
+ } else {
7636
+ return die(`AGENTMAIL_API_KEY not set and bootstrap failed: ${bootstrap.error ?? "unknown"}. Get a key at https://agentmail.to and run: export AGENTMAIL_API_KEY=<key>`);
7637
+ }
7638
+ } catch (err) {
7639
+ const msg = err instanceof Error ? err.message : String(err);
7640
+ return die(`AGENTMAIL_API_KEY not set and bootstrap errored: ${msg}. Get a key at https://agentmail.to and run: export AGENTMAIL_API_KEY=<key>`);
7641
+ }
7642
+ }
7608
7643
  info(`[login-auto] creating agent email for ${domain}...`);
7609
7644
  const session = await autonomousEmailLogin3(domain);
7610
7645
  info(`[login-auto] email: ${session.email}`);
package/dist/mcp.js CHANGED
@@ -225,11 +225,11 @@ import { dirname, join, parse } from "path";
225
225
  import { fileURLToPath as fileURLToPath2 } from "url";
226
226
 
227
227
  // ../../src/build-info.generated.ts
228
- var BUILD_RELEASE_VERSION = "3.7.0";
229
- var BUILD_GIT_SHA = "051ddb5b3add";
228
+ var BUILD_RELEASE_VERSION = "3.7.1";
229
+ var BUILD_GIT_SHA = "90d1969f3abe";
230
230
  var BUILD_CODE_HASH = "5d9ebf619c61";
231
- var BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiMy43LjAiLCJnaXRfc2hhIjoiMDUxZGRiNWIzYWRkIiwiY29kZV9oYXNoIjoiNWQ5ZWJmNjE5YzYxIiwidHJhY2VfdmVyc2lvbiI6IjVkOWViZjYxOWM2MUAwNTFkZGI1YjNhZGQiLCJpc3N1ZWRfYXQiOiIyMDI2LTA0LTEwVDAzOjA3OjU1Ljg4MloifQ";
232
- var BUILD_RELEASE_MANIFEST_SIGNATURE = "TKsUZaH-T1iT7XTgsV8ykdoZDHQj05PQkkmGr0Yf3dQ";
231
+ var BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiMy43LjEiLCJnaXRfc2hhIjoiOTBkMTk2OWYzYWJlIiwiY29kZV9oYXNoIjoiNWQ5ZWJmNjE5YzYxIiwidHJhY2VfdmVyc2lvbiI6IjVkOWViZjYxOWM2MUA5MGQxOTY5ZjNhYmUiLCJpc3N1ZWRfYXQiOiIyMDI2LTA0LTEwVDA4OjQyOjEwLjg1NloifQ";
232
+ var BUILD_RELEASE_MANIFEST_SIGNATURE = "R5-PEYUVNGWtRmubf9WAMDDuH1_C2N6D4xteuGelBIM";
233
233
  var BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai";
234
234
 
235
235
  // ../../src/version.ts
package/dist/server.js CHANGED
@@ -1910,6 +1910,22 @@ function deriveTemplateParamsFromContextUrl(urlTemplate, contextUrl) {
1910
1910
  const actualValue = actualUrl.searchParams.get(key);
1911
1911
  if (actualValue != null && actualValue !== "") {
1912
1912
  out[placeholder] = actualValue;
1913
+ continue;
1914
+ }
1915
+ const byPlaceholderName = actualUrl.searchParams.get(placeholder);
1916
+ if (byPlaceholderName != null && byPlaceholderName !== "") {
1917
+ out[placeholder] = byPlaceholderName;
1918
+ continue;
1919
+ }
1920
+ const lowerPlaceholder = placeholder.toLowerCase();
1921
+ if (/^(q|query|search|text|keyword|keywords|term|terms)$/.test(lowerPlaceholder)) {
1922
+ for (const alias of ["q", "query", "search", "text", "keyword", "keywords", "term"]) {
1923
+ const v = actualUrl.searchParams.get(alias);
1924
+ if (v != null && v !== "") {
1925
+ out[placeholder] = v;
1926
+ break;
1927
+ }
1928
+ }
1913
1929
  }
1914
1930
  }
1915
1931
  return out;
@@ -3827,6 +3843,13 @@ __export(exports_reverse_engineer, {
3827
3843
  extractAuthHeaders: () => extractAuthHeaders
3828
3844
  });
3829
3845
  import { nanoid as nanoid2 } from "nanoid";
3846
+ import { createHash } from "node:crypto";
3847
+ function stableEndpointId(method, urlTemplate) {
3848
+ if (!method || !urlTemplate)
3849
+ return nanoid2();
3850
+ const hash = createHash("sha256").update(`${method}:${urlTemplate}`).digest("base64url");
3851
+ return hash.slice(0, 21);
3852
+ }
3830
3853
  function extractGraphQLOperationName(url, requestBody) {
3831
3854
  const urlMatch = url.match(/\/graphql\/[^/]+\/(\w+)/);
3832
3855
  if (urlMatch)
@@ -4447,10 +4470,11 @@ function extractEndpoints(requests, wsMessages, context) {
4447
4470
  });
4448
4471
  const csrfPlan = inferCsrfPlan(req, parsedRequestBody);
4449
4472
  const endpointGraphqlOp = /graphql/i.test(req.url) ? extractGraphQLOperationName(req.url, req.request_body) : undefined;
4473
+ const computedUrlTemplate = qTemplateStr ? `${pathTemplate}${pathTemplate.includes("?") ? "&" : "?"}${qTemplateStr}` : pathTemplate;
4450
4474
  let endpoint = {
4451
- endpoint_id: nanoid2(),
4475
+ endpoint_id: stableEndpointId(req.method, computedUrlTemplate),
4452
4476
  method: req.method,
4453
- url_template: qTemplateStr ? `${pathTemplate}${pathTemplate.includes("?") ? "&" : "?"}${qTemplateStr}` : pathTemplate,
4477
+ url_template: computedUrlTemplate,
4454
4478
  description: buildEndpointDescription(req, sampleRequest, sampleResponse),
4455
4479
  headers_template: sanitizeHeaders(req.request_headers),
4456
4480
  query: sanitizedQParams,
@@ -4541,7 +4565,7 @@ function extractEndpoints(requests, wsMessages, context) {
4541
4565
  response_schema = inferSchema(jsonSamples);
4542
4566
  }
4543
4567
  const endpoint = {
4544
- endpoint_id: nanoid2(),
4568
+ endpoint_id: stableEndpointId("WS", wsUrl),
4545
4569
  method: "WS",
4546
4570
  url_template: wsUrl,
4547
4571
  idempotency: "safe",
@@ -7096,10 +7120,10 @@ var init_capture = __esm(async () => {
7096
7120
  });
7097
7121
 
7098
7122
  // ../../src/build-info.generated.ts
7099
- var BUILD_RELEASE_VERSION = "3.7.0", BUILD_GIT_SHA = "051ddb5b3add", BUILD_CODE_HASH = "5d9ebf619c61", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiMy43LjAiLCJnaXRfc2hhIjoiMDUxZGRiNWIzYWRkIiwiY29kZV9oYXNoIjoiNWQ5ZWJmNjE5YzYxIiwidHJhY2VfdmVyc2lvbiI6IjVkOWViZjYxOWM2MUAwNTFkZGI1YjNhZGQiLCJpc3N1ZWRfYXQiOiIyMDI2LTA0LTEwVDAzOjA3OjU1Ljg4MloifQ", BUILD_RELEASE_MANIFEST_SIGNATURE = "TKsUZaH-T1iT7XTgsV8ykdoZDHQj05PQkkmGr0Yf3dQ", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai";
7123
+ var BUILD_RELEASE_VERSION = "3.7.1", BUILD_GIT_SHA = "90d1969f3abe", BUILD_CODE_HASH = "5d9ebf619c61", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiMy43LjEiLCJnaXRfc2hhIjoiOTBkMTk2OWYzYWJlIiwiY29kZV9oYXNoIjoiNWQ5ZWJmNjE5YzYxIiwidHJhY2VfdmVyc2lvbiI6IjVkOWViZjYxOWM2MUA5MGQxOTY5ZjNhYmUiLCJpc3N1ZWRfYXQiOiIyMDI2LTA0LTEwVDA4OjQyOjEwLjg1NloifQ", BUILD_RELEASE_MANIFEST_SIGNATURE = "R5-PEYUVNGWtRmubf9WAMDDuH1_C2N6D4xteuGelBIM", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai";
7100
7124
 
7101
7125
  // ../../src/version.ts
7102
- import { createHash } from "crypto";
7126
+ import { createHash as createHash2 } from "crypto";
7103
7127
  import { existsSync as existsSync4, readFileSync as readFileSync2, readdirSync } from "fs";
7104
7128
  import { dirname, join as join3, parse } from "path";
7105
7129
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -7116,7 +7140,7 @@ function collectTsFiles(dir) {
7116
7140
  return results;
7117
7141
  }
7118
7142
  function hashFiles(srcDir, files) {
7119
- const hash = createHash("sha256");
7143
+ const hash = createHash2("sha256");
7120
7144
  for (const file of files) {
7121
7145
  hash.update(file.slice(srcDir.length));
7122
7146
  hash.update(readFileSync2(file, "utf-8"));
@@ -7156,7 +7180,7 @@ function computeCodeHash() {
7156
7180
  } catch {}
7157
7181
  const pkgVersion = getPackageVersion();
7158
7182
  if (pkgVersion !== "unknown") {
7159
- return createHash("sha256").update(`package:${pkgVersion}`).digest("hex").slice(0, 12);
7183
+ return createHash2("sha256").update(`package:${pkgVersion}`).digest("hex").slice(0, 12);
7160
7184
  }
7161
7185
  return "compiled";
7162
7186
  }
@@ -7210,13 +7234,13 @@ var init_version = __esm(() => {
7210
7234
  });
7211
7235
 
7212
7236
  // ../../src/payments/cascade.ts
7213
- import { createHash as createHash2 } from "crypto";
7237
+ import { createHash as createHash3 } from "crypto";
7214
7238
  import bs58 from "bs58";
7215
7239
  function payableContributors(skill) {
7216
7240
  return (skill.contributors ?? []).filter((c) => !!c.wallet_address?.trim());
7217
7241
  }
7218
7242
  function cascadeLabel(skillId) {
7219
- const digest = createHash2("sha256").update(skillId).digest("hex");
7243
+ const digest = createHash3("sha256").update(skillId).digest("hex");
7220
7244
  return `ubr-${digest.slice(0, 23)}`;
7221
7245
  }
7222
7246
  function decodeSecretKey(raw) {
@@ -7610,7 +7634,7 @@ __export(exports_client2, {
7610
7634
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync as readdirSync2, unlinkSync } from "fs";
7611
7635
  import { join as join6 } from "path";
7612
7636
  import { homedir as homedir4, hostname, release as osRelease } from "os";
7613
- import { randomBytes as randomBytes2, createHash as createHash3 } from "crypto";
7637
+ import { randomBytes as randomBytes2, createHash as createHash4 } from "crypto";
7614
7638
  import { createInterface } from "readline";
7615
7639
  import { execSync } from "child_process";
7616
7640
  function buildReleaseAttestationHeaders(manifestBase64, signature) {
@@ -7887,7 +7911,7 @@ function getApiKey() {
7887
7911
  function hashApiKey(key) {
7888
7912
  if (!key || key === "local-only")
7889
7913
  return "";
7890
- return createHash3("sha256").update(key).digest("hex");
7914
+ return createHash4("sha256").update(key).digest("hex");
7891
7915
  }
7892
7916
  function getAgentId() {
7893
7917
  const config = loadConfig();
@@ -9069,7 +9093,7 @@ var init_publish_admission = __esm(() => {
9069
9093
  });
9070
9094
 
9071
9095
  // ../../src/telemetry.ts
9072
- import { createHash as createHash4 } from "node:crypto";
9096
+ import { createHash as createHash5 } from "node:crypto";
9073
9097
  import { existsSync as existsSync8, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "node:fs";
9074
9098
  import { join as join7 } from "node:path";
9075
9099
  function getTraceDir() {
@@ -9079,7 +9103,7 @@ function isTracingEnabled() {
9079
9103
  return process.env.UNBROWSE_DISABLE_TRACES !== "1";
9080
9104
  }
9081
9105
  function hashValue(value) {
9082
- return createHash4("sha256").update(value).digest("hex").slice(0, 16);
9106
+ return createHash5("sha256").update(value).digest("hex").slice(0, 16);
9083
9107
  }
9084
9108
  function isSensitiveName(name) {
9085
9109
  return SENSITIVE_PATTERNS.some((re) => re.test(name));
@@ -9097,7 +9121,7 @@ function anonymizeUrl(url) {
9097
9121
  }
9098
9122
  function hashResponseBody(result) {
9099
9123
  const str = typeof result === "string" ? result : JSON.stringify(result ?? "");
9100
- return createHash4("sha256").update(str).digest("hex").slice(0, 32);
9124
+ return createHash5("sha256").update(str).digest("hex").slice(0, 32);
9101
9125
  }
9102
9126
  function classifyFailure(error) {
9103
9127
  if (!error)
@@ -12457,7 +12481,12 @@ function cleanDOM(html) {
12457
12481
  const $ = cheerio.load(html);
12458
12482
  for (const tag of STRIP_TAGS) {
12459
12483
  if (tag === "script") {
12460
- $("script").not('[type="application/ld+json"]').remove();
12484
+ $("script").each((_, el) => {
12485
+ const type = $(el).attr("type") ?? "";
12486
+ if (type !== "application/ld+json") {
12487
+ $(el).remove();
12488
+ }
12489
+ });
12461
12490
  } else {
12462
12491
  $(tag).remove();
12463
12492
  }
@@ -15495,7 +15524,12 @@ __export(exports_execution, {
15495
15524
  buildCanonicalDocumentEndpoint: () => buildCanonicalDocumentEndpoint
15496
15525
  });
15497
15526
  import { nanoid as nanoid6 } from "nanoid";
15498
- import { createHash as createHash5 } from "node:crypto";
15527
+ import { createHash as createHash6 } from "node:crypto";
15528
+ function stableEndpointId2(method, urlTemplate) {
15529
+ if (!method || !urlTemplate)
15530
+ return nanoid6();
15531
+ return createHash6("sha256").update(`${method}:${urlTemplate}`).digest("base64url").slice(0, 21);
15532
+ }
15499
15533
  function stampTrace(trace) {
15500
15534
  trace.trace_version = TRACE_VERSION;
15501
15535
  return trace;
@@ -16039,10 +16073,11 @@ function buildPageArtifactCapture(url, intent, html, authRequired = false) {
16039
16073
  const searchForms = detectSearchForms(html);
16040
16074
  const validSearchForm = searchForms.find((spec) => isStructuredSearchForm(spec));
16041
16075
  const response_schema = inferSchema([extracted.data]);
16076
+ const computedTemplate = templatizeQueryParams(url);
16042
16077
  const endpoint = {
16043
- endpoint_id: nanoid6(),
16078
+ endpoint_id: stableEndpointId2("GET", computedTemplate),
16044
16079
  method: "GET",
16045
- url_template: templatizeQueryParams(url),
16080
+ url_template: computedTemplate,
16046
16081
  idempotency: "safe",
16047
16082
  verification_status: "verified",
16048
16083
  reliability_score: extracted.confidence,
@@ -16090,7 +16125,7 @@ function buildCanonicalDocumentEndpoint(url, intent, authRequired = false) {
16090
16125
  const replayTemplate = deriveStructuredDataReplayTemplate(url);
16091
16126
  if (replayUrl === url && replayTemplate === url)
16092
16127
  return;
16093
- const canonicalId = createHash5("sha1").update(replayTemplate !== url ? replayTemplate : replayUrl).digest("base64url").slice(0, 21);
16128
+ const canonicalId = createHash6("sha1").update(replayTemplate !== url ? replayTemplate : replayUrl).digest("base64url").slice(0, 21);
16094
16129
  const endpoint = {
16095
16130
  endpoint_id: canonicalId,
16096
16131
  method: "GET",
@@ -16270,6 +16305,65 @@ async function trySeedPublicDocumentFetchSkill(skill, url, intent, targetDomain,
16270
16305
  redirect: "follow"
16271
16306
  });
16272
16307
  const html = await response.text();
16308
+ const contentType = (response.headers.get("content-type") || "").toLowerCase();
16309
+ if (response.ok && (contentType.includes("application/json") || contentType.includes("text/json"))) {
16310
+ try {
16311
+ const parsed = JSON.parse(html);
16312
+ const urlObj = new URL(response.url || url);
16313
+ const pathTemplate = `${urlObj.origin}${urlObj.pathname}`;
16314
+ const responseSchema = inferSchema([parsed]);
16315
+ const endpoint = {
16316
+ endpoint_id: stableEndpointId2("GET", pathTemplate),
16317
+ method: "GET",
16318
+ url_template: pathTemplate,
16319
+ idempotency: "safe",
16320
+ verification_status: "verified",
16321
+ reliability_score: 0.95,
16322
+ description: `Direct JSON API for ${intent}`,
16323
+ response_schema: responseSchema
16324
+ };
16325
+ endpoint.semantic = inferEndpointSemantic(endpoint, {
16326
+ sampleResponse: parsed,
16327
+ observedAt: new Date().toISOString(),
16328
+ sampleRequestUrl: url
16329
+ });
16330
+ const domain2 = getRegistrableDomain(targetDomain);
16331
+ const existingSkill2 = findExistingSkillForDomain(domain2, intent);
16332
+ const localEndpoints2 = await prepareLearnedEndpoints(existingSkill2 ? mergeEndpoints(existingSkill2.endpoints, [endpoint]) : [endpoint], intent, domain2);
16333
+ const localDraft2 = {
16334
+ skill_id: existingSkill2?.skill_id ?? nanoid6(),
16335
+ version: "1.0.0",
16336
+ schema_version: "1",
16337
+ lifecycle: "active",
16338
+ execution_type: "http",
16339
+ created_at: existingSkill2?.created_at ?? new Date().toISOString(),
16340
+ updated_at: new Date().toISOString(),
16341
+ name: domain2,
16342
+ intent_signature: intent,
16343
+ domain: domain2,
16344
+ description: `API skill for ${domain2}`,
16345
+ owner_type: "agent",
16346
+ endpoints: localEndpoints2,
16347
+ operation_graph: buildSkillOperationGraph(localEndpoints2),
16348
+ intents: [intent]
16349
+ };
16350
+ try {
16351
+ cachePublishedSkill(localDraft2);
16352
+ } catch {}
16353
+ return {
16354
+ trace: stampTrace({
16355
+ trace_id: nanoid6(),
16356
+ skill_id: localDraft2.skill_id,
16357
+ endpoint_id: endpoint.endpoint_id,
16358
+ started_at: new Date().toISOString(),
16359
+ completed_at: new Date().toISOString(),
16360
+ success: true,
16361
+ status_code: response.status
16362
+ }),
16363
+ result: parsed
16364
+ };
16365
+ } catch {}
16366
+ }
16273
16367
  if (!isHtml(html) || isSpaShell(html))
16274
16368
  return;
16275
16369
  const built = buildPageArtifactCapture(response.url || url, intent, html, usedStoredAuth);
@@ -16595,7 +16689,7 @@ async function executeBrowserCapture(skill, params, options) {
16595
16689
  epUrl = `${route.url}?${qStr}`;
16596
16690
  }
16597
16691
  endpoints.push({
16598
- endpoint_id: nanoid6(),
16692
+ endpoint_id: stableEndpointId2("GET", epUrl),
16599
16693
  method: "GET",
16600
16694
  url_template: epUrl,
16601
16695
  query: epQuery,
@@ -19117,10 +19211,10 @@ var init_timing_economics = __esm(() => {
19117
19211
  });
19118
19212
 
19119
19213
  // ../../src/routing-telemetry.ts
19120
- import { createHash as createHash6 } from "node:crypto";
19214
+ import { createHash as createHash7 } from "node:crypto";
19121
19215
  import { nanoid as nanoid8 } from "nanoid";
19122
19216
  function stableHash(value) {
19123
- return createHash6("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 24);
19217
+ return createHash7("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 24);
19124
19218
  }
19125
19219
  function sanitizeScalar(value) {
19126
19220
  if (value == null)
@@ -19474,7 +19568,7 @@ function applyVerificationResults(skill, verification) {
19474
19568
  import { nanoid as nanoid9 } from "nanoid";
19475
19569
  import { existsSync as existsSync13, writeFileSync as writeFileSync8, readFileSync as readFileSync8, mkdirSync as mkdirSync9, readdirSync as readdirSync6 } from "node:fs";
19476
19570
  import { dirname as dirname2, join as join12 } from "node:path";
19477
- import { createHash as createHash7 } from "node:crypto";
19571
+ import { createHash as createHash8 } from "node:crypto";
19478
19572
  function summarizeSchema(schema, maxDepth = 3) {
19479
19573
  function walk(s, depth) {
19480
19574
  if (depth <= 0)
@@ -19602,7 +19696,7 @@ function scopedResolveCacheKeys(scope, key) {
19602
19696
  return scope === "global" ? [scopedCacheKey("global", key)] : [scopedCacheKey(scope, key), scopedCacheKey("global", key)];
19603
19697
  }
19604
19698
  function snapshotPathForCacheKey(cacheKey) {
19605
- const digest = createHash7("sha1").update(cacheKey).digest("hex");
19699
+ const digest = createHash8("sha1").update(cacheKey).digest("hex");
19606
19700
  return join12(SKILL_SNAPSHOT_DIR, `${digest}.json`);
19607
19701
  }
19608
19702
  function writeSkillSnapshot(cacheKey, skill) {
@@ -19953,7 +20047,7 @@ function buildLocalCanonicalReplaySkill(intent, contextUrl) {
19953
20047
  const domain = new URL(contextUrl).hostname.replace(/^www\./, "");
19954
20048
  const now = new Date().toISOString();
19955
20049
  const skill = {
19956
- skill_id: `canonical-${createHash7("sha1").update(contextUrl).digest("hex").slice(0, 12)}`,
20050
+ skill_id: `canonical-${createHash8("sha1").update(contextUrl).digest("hex").slice(0, 12)}`,
19957
20051
  version: "1.0.0",
19958
20052
  schema_version: "1",
19959
20053
  name: `Canonical replay for ${domain}`,
@@ -22237,15 +22331,15 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
22237
22331
  }
22238
22332
  }
22239
22333
  }
22240
- if (context?.url && (/\.(json|xml)(\?|$)|\/api\/|\/v\d+\//.test(context.url) || /[?&]format=(j\d*|json)\b/i.test(context.url) || /^https?:\/\/api\./i.test(context.url))) {
22334
+ if (context?.url) {
22241
22335
  try {
22242
22336
  const directRes = await fetch(context.url, {
22243
- headers: { Accept: "application/json", "User-Agent": "unbrowse/1.0" },
22244
- signal: AbortSignal.timeout(5000),
22337
+ headers: { Accept: "application/json, text/html;q=0.5", "User-Agent": "unbrowse/1.0" },
22338
+ signal: AbortSignal.timeout(15000),
22245
22339
  redirect: "follow"
22246
22340
  });
22247
22341
  const ct = directRes.headers.get("content-type") ?? "";
22248
- if (directRes.ok && (ct.includes("json") || ct.includes("+json"))) {
22342
+ if (directRes.ok && (ct.includes("application/json") || ct.includes("+json") || ct.includes("text/json"))) {
22249
22343
  const data = await directRes.json();
22250
22344
  const trace2 = {
22251
22345
  trace_id: nanoid9(),
@@ -22265,7 +22359,10 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
22265
22359
  timing: t
22266
22360
  };
22267
22361
  }
22268
- } catch {}
22362
+ } catch (err) {
22363
+ const msg = err instanceof Error ? err.message : String(err);
22364
+ console.log(`[direct-fetch] ${context.url} skipped: ${msg.slice(0, 100)}`);
22365
+ }
22269
22366
  }
22270
22367
  if (process.env.UNBROWSE_LOCAL_ONLY === "1" && !forceCapture) {
22271
22368
  return buildNoCachedMatch();
@@ -22424,6 +22521,32 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
22424
22521
  throw error;
22425
22522
  }
22426
22523
  timing.execute_ms = Date.now() - te0;
22524
+ const captureErrCheck = result?.error;
22525
+ if (captureErrCheck === "connection_failed" || captureErrCheck === "capture_failed") {
22526
+ console.warn(`[capture] ${captureErrCheck} detected — restarting Kuri and retrying once`);
22527
+ try {
22528
+ const kuri = await Promise.resolve().then(() => (init_client(), exports_client));
22529
+ await kuri.stop().catch(() => {});
22530
+ await new Promise((r) => setTimeout(r, 500));
22531
+ } catch {}
22532
+ try {
22533
+ const retryCaptureSkill = await getOrCreateBrowserCaptureSkill();
22534
+ const retryOut = await withAbortableOpTimeout("live_capture_retry", LIVE_CAPTURE_TIMEOUT_MS, (signal) => executeSkill(retryCaptureSkill, { ...params, url: context.url, intent }, undefined, {
22535
+ ...options,
22536
+ intent,
22537
+ contextUrl: context?.url,
22538
+ signal
22539
+ }));
22540
+ if (retryOut.trace.success || !retryOut.result?.error) {
22541
+ trace = retryOut.trace;
22542
+ result = retryOut.result;
22543
+ learned_skill = retryOut.learned_skill;
22544
+ console.log(`[capture] retry after Kuri restart succeeded`);
22545
+ }
22546
+ } catch (retryErr) {
22547
+ console.warn(`[capture] retry failed: ${retryErr instanceof Error ? retryErr.message : retryErr}`);
22548
+ }
22549
+ }
22427
22550
  const captureResult = result;
22428
22551
  const authRecommended = captureResult?.auth_recommended === true;
22429
22552
  const directDomCaptureResult = trace.success && trace.endpoint_id !== "browser-capture" && !!result && typeof result === "object" && "_extraction" in result;
@@ -24207,7 +24330,13 @@ var init_browse_session = __esm(() => {
24207
24330
 
24208
24331
  // ../../src/api/browse-index.ts
24209
24332
  import { nanoid as nanoid10 } from "nanoid";
24333
+ import { createHash as createHash9 } from "node:crypto";
24210
24334
  import { readFileSync as readFileSync12 } from "node:fs";
24335
+ function stableEndpointId3(method, urlTemplate) {
24336
+ if (!method || !urlTemplate)
24337
+ return nanoid10();
24338
+ return createHash9("sha256").update(`${method}:${urlTemplate}`).digest("base64url").slice(0, 21);
24339
+ }
24211
24340
  function normalizeBrowseUrl(url, baseUrl) {
24212
24341
  if (!url)
24213
24342
  return url;
@@ -24385,7 +24514,7 @@ async function cacheBrowseRequests(params) {
24385
24514
  return { domain, indexed: false, mode: "none", skill: null };
24386
24515
  const urlTemplate = templatizeQueryParams2(sessionUrl);
24387
24516
  const endpoint = {
24388
- endpoint_id: nanoid10(),
24517
+ endpoint_id: stableEndpointId3("GET", urlTemplate),
24389
24518
  method: "GET",
24390
24519
  url_template: urlTemplate,
24391
24520
  idempotency: "safe",
@@ -26437,11 +26566,18 @@ async function registerRoutes(app) {
26437
26566
  app.addHook("onRequest", async (req, reply) => {
26438
26567
  if (req.url === "/health" || req.url === "/v1/stats" || req.url.startsWith("/v1/settings"))
26439
26568
  return;
26440
- const key = getApiKey();
26569
+ let key = getApiKey();
26570
+ if (!key) {
26571
+ try {
26572
+ const { waitForBackgroundRegistration: waitForBackgroundRegistration2 } = await Promise.resolve().then(() => (init_client2(), exports_client2));
26573
+ await waitForBackgroundRegistration2(1e4);
26574
+ key = getApiKey();
26575
+ } catch {}
26576
+ }
26441
26577
  if (!key) {
26442
26578
  return reply.code(401).send({
26443
26579
  error: "api_key_required",
26444
- message: "No API key configured. Restart the server to auto-register, or run: bash scripts/setup.sh",
26580
+ message: "No API key configured. Run `unbrowse setup` to register, or set UNBROWSE_API_KEY manually.",
26445
26581
  docs_url: "https://unbrowse.ai"
26446
26582
  });
26447
26583
  }
@@ -26691,7 +26827,28 @@ async function registerRoutes(app) {
26691
26827
  ...context_url && typeof params?.url !== "string" ? { url: context_url } : {}
26692
26828
  };
26693
26829
  try {
26694
- const execResult = await executeSkill(skill, execParams, projection, { confirm_unsafe, confirm_third_party_terms, dry_run, skip_robots_check, intent, contextUrl: context_url, client_scope: clientScope });
26830
+ let execResult = await executeSkill(skill, execParams, projection, { confirm_unsafe, confirm_third_party_terms, dry_run, skip_robots_check, intent, contextUrl: context_url, client_scope: clientScope });
26831
+ if (!execResult.trace.success && execResult.result?.error === "endpoint_not_found" && typeof execParams.endpoint_id === "string") {
26832
+ let recovered = false;
26833
+ const freshSkill = await getSkill2(skill_id, clientScope);
26834
+ if (freshSkill && freshSkill.endpoints.some((e) => e.endpoint_id === execParams.endpoint_id)) {
26835
+ console.log(`[exec] endpoint ${execParams.endpoint_id} found in fresh marketplace skill — retrying`);
26836
+ skill = freshSkill;
26837
+ recovered = true;
26838
+ }
26839
+ if (!recovered && context_url) {
26840
+ const { buildCanonicalDocumentEndpoint: buildCanonicalDocumentEndpoint2 } = await init_execution().then(() => exports_execution);
26841
+ const canonical = buildCanonicalDocumentEndpoint2(context_url, intent ?? "", false);
26842
+ if (canonical && canonical.endpoint_id === execParams.endpoint_id) {
26843
+ console.log(`[exec] endpoint ${execParams.endpoint_id} is a canonical replay — injecting into skill`);
26844
+ skill = { ...skill, endpoints: [canonical, ...skill.endpoints] };
26845
+ recovered = true;
26846
+ }
26847
+ }
26848
+ if (recovered) {
26849
+ execResult = await executeSkill(skill, execParams, projection, { confirm_unsafe, confirm_third_party_terms, dry_run, skip_robots_check, intent, contextUrl: context_url, client_scope: clientScope });
26850
+ }
26851
+ }
26695
26852
  saveTrace(execResult.trace);
26696
26853
  if (execResult.trace.endpoint_id) {
26697
26854
  recordExecution(skill.skill_id, execResult.trace.endpoint_id, execResult.trace, skill).catch(() => {});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unbrowse",
3
- "version": "3.7.0",
3
+ "version": "3.7.1",
4
4
  "description": "Reverse-engineer any website into reusable API skills. Zero-dep single binary with embedded browser engine.",
5
5
  "type": "module",
6
6
  "bin": {