unbrowse 2.8.3 → 2.8.5

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
@@ -624,14 +624,6 @@ async function executeInPageFetch(tabId, url, method, headers, body) {
624
624
  return { status: 0, data: result };
625
625
  }
626
626
  }
627
- async function health() {
628
- try {
629
- const result = await kuriGet("/health");
630
- return { ok: result?.ok === true || result?.status === "ok", tabs: result?.tabs };
631
- } catch {
632
- return { ok: false };
633
- }
634
- }
635
627
  async function action(tabId, actionType, ref, value) {
636
628
  const params = { tab_id: tabId, action: actionType, ref };
637
629
  if (value !== undefined)
@@ -6342,259 +6334,6 @@ var init_drift = __esm(() => {
6342
6334
  init_transform();
6343
6335
  });
6344
6336
 
6345
- // ../../src/transform/schema-hints.ts
6346
- function buildIntentProfile(intent) {
6347
- const text = intent?.toLowerCase() ?? "";
6348
- const profile = {
6349
- preferredPaths: [],
6350
- discouragedPaths: [],
6351
- preferredFields: [],
6352
- discouragedFields: [],
6353
- wantsStructuredRecords: /\b(search|list|find|get|fetch|timeline|feed|trending)\b/.test(text)
6354
- };
6355
- if (/\b(repo|repos|repository|repositories|code|projects?)\b/.test(text)) {
6356
- profile.preferredPaths.push("repositories", "repos", "results", "items", "data");
6357
- profile.preferredFields.push("full_name", "name", "description", "stargazers_count", "stars", "language", "owner", "url");
6358
- profile.discouragedPaths.push("accounts", "users", "hashtags", "topics");
6359
- }
6360
- if (/\b(post|posts|tweet|tweets|status|statuses|timeline|feed|thread|threads)\b/.test(text)) {
6361
- profile.preferredPaths.push("statuses", "posts", "tweets", "timeline", "entries", "results");
6362
- profile.preferredFields.push("content", "text", "body", "created_at", "url", "account", "username", "replies_count", "reblogs_count", "favourites_count");
6363
- profile.discouragedPaths.push("accounts", "users", "people", "profiles", "hashtags");
6364
- }
6365
- if (/\b(person|people|user|users|profile|profiles|member|members|account|accounts)\b/.test(text)) {
6366
- profile.preferredPaths.push("people", "users", "accounts", "profiles", "included", "elements");
6367
- profile.preferredFields.push("name", "headline", "title", "public_identifier", "username", "handle", "url");
6368
- profile.discouragedPaths.push("hashtags", "statuses", "posts");
6369
- }
6370
- if (/\b(trend|trending|topic|topics)\b/.test(text)) {
6371
- profile.preferredPaths.push("trends", "topics", "timeline", "entries", "results", "data");
6372
- profile.preferredFields.push("name", "query", "topic", "post_count", "tweet_volume", "url");
6373
- profile.discouragedPaths.push("accounts", "users");
6374
- }
6375
- return profile;
6376
- }
6377
- function findArrayCandidates(schema, path5, depth, results) {
6378
- if (schema.type === "array" && schema.items) {
6379
- const items = schema.items;
6380
- if (items.type === "object" && items.properties) {
6381
- const fieldCount = Object.keys(items.properties).length;
6382
- results.push({ path: path5 ? `${path5}[]` : "[]", itemSchema: items, fieldCount, depth });
6383
- for (const [key, prop] of Object.entries(items.properties)) {
6384
- const childPath = path5 ? `${path5}[].${key}` : `[].${key}`;
6385
- findArrayCandidates(prop, childPath, depth + 1, results);
6386
- }
6387
- return;
6388
- }
6389
- if (items.type === "array") {
6390
- findArrayCandidates(items, path5 ? `${path5}[]` : "[]", depth + 1, results);
6391
- }
6392
- return;
6393
- }
6394
- if (schema.type === "object" && schema.properties) {
6395
- for (const [key, prop] of Object.entries(schema.properties)) {
6396
- const childPath = path5 ? `${path5}.${key}` : key;
6397
- findArrayCandidates(prop, childPath, depth + 1, results);
6398
- }
6399
- }
6400
- }
6401
- function scoreField(name, schema) {
6402
- let score = 0;
6403
- const lower = name.toLowerCase();
6404
- if (/^(id|name|title|label|slug)$/i.test(name))
6405
- score += 10;
6406
- if (/^(url|link|href|uri)$/i.test(name))
6407
- score += 8;
6408
- if (/^(description|text|content|body|summary|bio)$/i.test(name))
6409
- score += 7;
6410
- if (/^(email|username|handle|screen.?name)$/i.test(name))
6411
- score += 7;
6412
- if (/date|time|created|updated|start|end/i.test(lower))
6413
- score += 5;
6414
- if (/count|total|price|amount|score|rating|likes|views|followers/i.test(lower))
6415
- score += 5;
6416
- if (/status|state|type|category|kind|tag/i.test(lower))
6417
- score += 4;
6418
- if (/city|address|location|lat|lng|geo|place|venue/i.test(lower))
6419
- score += 4;
6420
- if (/image|photo|avatar|thumbnail|cover|logo|icon/i.test(lower))
6421
- score += 3;
6422
- if (schema.type === "string" || schema.type === "integer" || schema.type === "number" || schema.type === "boolean") {
6423
- score += 2;
6424
- }
6425
- if (/urn|tracking|internal|hash|token|cursor|pagination|__/i.test(lower))
6426
- score -= 5;
6427
- if (name.startsWith("$") || name.startsWith("_"))
6428
- score -= 3;
6429
- return score;
6430
- }
6431
- function selectBestArray(candidates, intent) {
6432
- if (candidates.length === 0)
6433
- return null;
6434
- const profile = buildIntentProfile(intent);
6435
- const scored = candidates.map((c) => {
6436
- let score = c.fieldCount * 2;
6437
- if (c.depth >= 1 && c.depth <= 3)
6438
- score += 5;
6439
- if (c.depth === 0)
6440
- score += 2;
6441
- const pathLower = c.path.toLowerCase();
6442
- if (/data|results|items|entries|elements|records|list|feed|posts|events|users/i.test(pathLower)) {
6443
- score += 8;
6444
- }
6445
- if (/included|nodes|edges/i.test(pathLower))
6446
- score += 6;
6447
- for (const token of profile.preferredPaths) {
6448
- if (pathLower.includes(token))
6449
- score += 14;
6450
- }
6451
- for (const token of profile.discouragedPaths) {
6452
- if (pathLower.includes(token))
6453
- score -= 18;
6454
- }
6455
- const fieldNames = Object.keys(c.itemSchema.properties ?? {}).map((name) => name.toLowerCase());
6456
- for (const token of profile.preferredFields) {
6457
- if (fieldNames.includes(token.toLowerCase()))
6458
- score += 7;
6459
- }
6460
- for (const token of profile.discouragedFields) {
6461
- if (fieldNames.includes(token.toLowerCase()))
6462
- score -= 8;
6463
- }
6464
- if (profile.wantsStructuredRecords && c.fieldCount < 3)
6465
- score -= 12;
6466
- if (fieldNames.length > 0 && fieldNames.every((name) => /^(link|title|label|text|value)$/i.test(name))) {
6467
- score -= 16;
6468
- }
6469
- if (c.fieldCount < 3)
6470
- score -= 5;
6471
- return { candidate: c, score };
6472
- });
6473
- scored.sort((a, b) => b.score - a.score);
6474
- return scored[0]?.candidate ?? null;
6475
- }
6476
- function schemaToTree(schema, maxDepth = 3) {
6477
- const tree = {};
6478
- function walk(s, path5, depth) {
6479
- if (depth > maxDepth)
6480
- return;
6481
- if (s.type === "object" && s.properties) {
6482
- for (const [key, prop] of Object.entries(s.properties)) {
6483
- const childPath = path5 ? `${path5}.${key}` : key;
6484
- if (prop.type === "array" && prop.items) {
6485
- if (prop.items.type === "object" && prop.items.properties) {
6486
- const count = Object.keys(prop.items.properties).length;
6487
- tree[`${childPath}[]`] = `array<object> (${count} fields)`;
6488
- walk(prop.items, `${childPath}[]`, depth + 1);
6489
- } else {
6490
- tree[`${childPath}[]`] = `array<${prop.items.type}>`;
6491
- }
6492
- } else if (prop.type === "object" && prop.properties) {
6493
- const count = Object.keys(prop.properties).length;
6494
- tree[childPath] = `object (${count} fields)`;
6495
- walk(prop, childPath, depth + 1);
6496
- } else {
6497
- tree[childPath] = prop.type;
6498
- }
6499
- }
6500
- }
6501
- }
6502
- if (schema.type === "array" && schema.items) {
6503
- tree["[]"] = `array<${schema.items.type}>`;
6504
- if (schema.items.type === "object") {
6505
- walk(schema.items, "[]", 1);
6506
- }
6507
- } else {
6508
- walk(schema, "", 0);
6509
- }
6510
- return tree;
6511
- }
6512
- function generateExtractionHints(schema, intent) {
6513
- if (schema.type !== "object" && schema.type !== "array")
6514
- return null;
6515
- const profile = buildIntentProfile(intent);
6516
- if (schema.type === "object" && schema.properties) {
6517
- for (const token of profile.preferredPaths) {
6518
- const prop = schema.properties[token];
6519
- if (prop?.type === "array" && (!prop.items || prop.items.type !== "object")) {
6520
- return finalize({
6521
- path: `${token}[]`,
6522
- fields: [],
6523
- item_field_count: 0,
6524
- confidence: "medium"
6525
- }, schema);
6526
- }
6527
- }
6528
- }
6529
- const candidates = [];
6530
- findArrayCandidates(schema, "", 0, candidates);
6531
- if (candidates.length === 0) {
6532
- if (schema.type === "object" && schema.properties) {
6533
- const propCount = Object.keys(schema.properties).length;
6534
- if (propCount <= 5)
6535
- return null;
6536
- }
6537
- if (schema.type === "object" && schema.properties) {
6538
- const fields = Object.entries(schema.properties).map(([name, prop]) => ({ name, score: scoreField(name, prop) })).filter((f) => f.score > 0).sort((a, b) => b.score - a.score).slice(0, 8).map((f) => f.name);
6539
- if (fields.length >= 2) {
6540
- return finalize({ path: "", fields, item_field_count: Object.keys(schema.properties).length, confidence: "low" }, schema);
6541
- }
6542
- }
6543
- return null;
6544
- }
6545
- const best = selectBestArray(candidates, intent);
6546
- if (!best)
6547
- return null;
6548
- const itemProps = best.itemSchema.properties ?? {};
6549
- const scoredFields = Object.entries(itemProps).map(([name, prop]) => ({ name, score: scoreField(name, prop), type: prop.type })).sort((a, b) => b.score - a.score);
6550
- const topFields = scoredFields.filter((f) => f.score > 0).slice(0, 10).map((f) => f.name);
6551
- if (intent) {
6552
- const intentWords = intent.toLowerCase().split(/\s+/);
6553
- for (const field of scoredFields) {
6554
- if (topFields.includes(field.name))
6555
- continue;
6556
- const fieldLower = field.name.toLowerCase();
6557
- if (intentWords.some((w) => fieldLower.includes(w) || w.includes(fieldLower))) {
6558
- topFields.push(field.name);
6559
- }
6560
- }
6561
- }
6562
- if (topFields.length < 2) {
6563
- const primitiveFields = scoredFields.filter((f) => f.type === "string" || f.type === "integer" || f.type === "number").slice(0, 5).map((f) => f.name);
6564
- if (primitiveFields.length < 2)
6565
- return null;
6566
- return finalize({
6567
- path: best.path,
6568
- fields: primitiveFields,
6569
- item_field_count: best.fieldCount,
6570
- confidence: "low"
6571
- }, schema);
6572
- }
6573
- const confidence = best.fieldCount >= 5 ? "high" : best.fieldCount >= 3 ? "medium" : "low";
6574
- return finalize({
6575
- path: best.path,
6576
- fields: topFields,
6577
- item_field_count: best.fieldCount,
6578
- confidence
6579
- }, schema);
6580
- }
6581
- function finalize(hint, schema) {
6582
- hint.cli_args = hintsToCliArgs(hint);
6583
- hint.schema_tree = schemaToTree(schema, 2);
6584
- return hint;
6585
- }
6586
- function hintsToCliArgs(hints) {
6587
- const parts = [];
6588
- if (hints.path) {
6589
- parts.push(`--path "${hints.path}"`);
6590
- }
6591
- if (hints.fields.length > 0) {
6592
- parts.push(`--extract "${hints.fields.join(",")}"`);
6593
- }
6594
- parts.push("--limit 10");
6595
- return parts.join(" ");
6596
- }
6597
-
6598
6337
  // ../../src/execution/retry.ts
6599
6338
  async function withRetry(fn, isRetryable, opts) {
6600
6339
  const maxRetries = opts?.maxRetries ?? MAX_RETRIES;
@@ -9760,7 +9499,7 @@ function sanitizeNavigationQueryParams(url) {
9760
9499
  return out;
9761
9500
  }
9762
9501
  function restoreTemplatePlaceholderEncoding(url) {
9763
- return url.replace(/%7B/gi, "{").replace(/%7D/gi, "}");
9502
+ return url.replace(/%7B(\w+)%7D/gi, "{$1}");
9764
9503
  }
9765
9504
  function compactSchemaSample(value, depth = 0) {
9766
9505
  if (depth >= 4)
@@ -10807,6 +10546,15 @@ async function tryHttpFetch(url, authHeaders, cookies) {
10807
10546
  return null;
10808
10547
  }
10809
10548
  }
10549
+ function flattenExtracted(data) {
10550
+ if (!Array.isArray(data))
10551
+ return data;
10552
+ const first = data[0];
10553
+ if (first && typeof first === "object" && "type" in first && "data" in first && "relevance_score" in first) {
10554
+ return data.reduce((best, cur) => (cur.relevance_score ?? 0) > (best.relevance_score ?? 0) ? cur : best).data;
10555
+ }
10556
+ return data;
10557
+ }
10810
10558
  async function executeDomExtractionEndpoint(endpoint, url, intent, authHeaders, cookies) {
10811
10559
  const ssrResult = await tryHttpFetch(url, authHeaders, cookies);
10812
10560
  if (ssrResult) {
@@ -10818,16 +10566,7 @@ async function executeDomExtractionEndpoint(endpoint, url, intent, authHeaders,
10818
10566
  if (ssrSemantic.verdict !== "fail") {
10819
10567
  console.log(`[ssr-fast] hit — extracted via HTTP fetch`);
10820
10568
  return {
10821
- data: {
10822
- data: ssrExtracted.data,
10823
- _extraction: {
10824
- method: ssrExtracted.extraction_method,
10825
- confidence: ssrExtracted.confidence,
10826
- source: "ssr-fast",
10827
- final_url: ssrResult.final_url,
10828
- ...ssrExtracted.selector ? { selector: ssrExtracted.selector } : {}
10829
- }
10830
- },
10569
+ data: flattenExtracted(ssrExtracted.data),
10831
10570
  status: 200,
10832
10571
  trace_id: nanoid5()
10833
10572
  };
@@ -10865,16 +10604,7 @@ async function executeDomExtractionEndpoint(endpoint, url, intent, authHeaders,
10865
10604
  };
10866
10605
  }
10867
10606
  return {
10868
- data: {
10869
- data: extracted.data,
10870
- _extraction: {
10871
- method: extracted.extraction_method,
10872
- confidence: extracted.confidence,
10873
- source: "rendered-dom",
10874
- final_url: captured.final_url,
10875
- ...extracted.selector ? { selector: extracted.selector } : {}
10876
- }
10877
- },
10607
+ data: flattenExtracted(extracted.data),
10878
10608
  status: 200,
10879
10609
  trace_id: nanoid5()
10880
10610
  };
@@ -10933,9 +10663,7 @@ async function executeEndpoint(skill, endpoint, params = {}, projection, options
10933
10663
  }
10934
10664
  return {
10935
10665
  trace: trace2,
10936
- result: resultData2,
10937
- ...endpoint.response_schema ? { response_schema: endpoint.response_schema } : {},
10938
- ...endpoint.response_schema ? { extraction_hints: generateExtractionHints(endpoint.response_schema, skill.intent_signature) ?? undefined } : {}
10666
+ result: resultData2
10939
10667
  };
10940
10668
  } catch (err) {
10941
10669
  const trace2 = stampTrace({
@@ -11183,6 +10911,7 @@ async function executeEndpoint(skill, endpoint, params = {}, projection, options
11183
10911
  let last = { data: null, status: 0 };
11184
10912
  for (const replayUrl of replayUrls) {
11185
10913
  const replayHeaders = buildStructuredReplayHeaders(url, replayUrl, headers);
10914
+ log("exec", `server-fetch: ${endpoint.method} ${replayUrl.substring(0, 80)} csrf=${replayHeaders["x-csrf-token"]?.substring(0, 10)}... cookies=${replayHeaders["cookie"]?.length ?? 0}chars`);
11186
10915
  const res = await fetch(replayUrl, {
11187
10916
  method: endpoint.method,
11188
10917
  headers: replayHeaders,
@@ -11260,7 +10989,8 @@ async function executeEndpoint(skill, endpoint, params = {}, projection, options
11260
10989
  try {
11261
10990
  result = await serverFetch();
11262
10991
  if (result.status >= 200 && result.status < 400) {
11263
- if (shouldFallbackToBrowserReplay(result.data, endpoint, options?.intent ?? skill.intent_signature, options?.contextUrl)) {
10992
+ const isApiEndpoint = /\/(api|graphql)\b/i.test(endpoint.url_template) || /\.(json)(\?|$)/.test(endpoint.url_template);
10993
+ if (!isApiEndpoint && shouldFallbackToBrowserReplay(result.data, endpoint, options?.intent ?? skill.intent_signature, options?.contextUrl)) {
11264
10994
  result = await withRetry(browserCall, (r) => isRetryableStatus(r.status));
11265
10995
  strategy = "browser";
11266
10996
  } else {
@@ -11453,12 +11183,9 @@ async function executeEndpoint(skill, endpoint, params = {}, projection, options
11453
11183
  } else if (trace.success) {
11454
11184
  resultData = projectResultForIntent(data, effectiveIntent);
11455
11185
  }
11456
- const rawResultShape = resultData === data;
11457
11186
  return {
11458
11187
  trace,
11459
- result: resultData,
11460
- ...endpoint.response_schema && rawResultShape ? { response_schema: endpoint.response_schema } : {},
11461
- ...endpoint.response_schema && rawResultShape ? { extraction_hints: generateExtractionHints(endpoint.response_schema, effectiveIntent) ?? undefined } : {}
11188
+ result: resultData
11462
11189
  };
11463
11190
  }
11464
11191
  function templatizeQueryParams(url) {
@@ -13274,14 +13001,12 @@ function cacheResolvedSkill(cacheKey, skill, endpointId) {
13274
13001
  });
13275
13002
  persistRouteCache();
13276
13003
  }
13277
- function promoteResultSnapshot(cacheKey, skill, endpointId, result, trace, response_schema, extraction_hints) {
13004
+ function promoteResultSnapshot(cacheKey, skill, endpointId, result, trace) {
13278
13005
  routeResultCache.set(cacheKey, {
13279
13006
  skill,
13280
13007
  endpointId,
13281
13008
  result,
13282
13009
  trace,
13283
- response_schema,
13284
- extraction_hints,
13285
13010
  expires: Date.now() + ROUTE_CACHE_TTL
13286
13011
  });
13287
13012
  }
@@ -13299,9 +13024,7 @@ function buildCachedResultResponse(cached, source, timing) {
13299
13024
  },
13300
13025
  source,
13301
13026
  skill: cached.skill,
13302
- timing,
13303
- response_schema: cached.response_schema,
13304
- extraction_hints: cached.extraction_hints
13027
+ timing
13305
13028
  };
13306
13029
  }
13307
13030
  function invalidateResolveCacheEntries(cacheKeys, domainKeys = []) {
@@ -14349,7 +14072,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
14349
14072
  routeResultCache.delete(k);
14350
14073
  }
14351
14074
  }
14352
- function finalize2(source, result2, skillId, skill, trace2) {
14075
+ function finalize(source, result2, skillId, skill, trace2) {
14353
14076
  timing.total_ms = Date.now() - t0;
14354
14077
  timing.source = source;
14355
14078
  timing.skill_id = skillId;
@@ -14505,7 +14228,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
14505
14228
  trace: deferTrace,
14506
14229
  source,
14507
14230
  skill: resolvedSkill,
14508
- timing: finalize2(source, null, resolvedSkill.skill_id, resolvedSkill, deferTrace)
14231
+ timing: finalize(source, null, resolvedSkill.skill_id, resolvedSkill, deferTrace)
14509
14232
  };
14510
14233
  }
14511
14234
  function missingTemplateParams(endpoint, boundParams) {
@@ -14835,7 +14558,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
14835
14558
  skill_id: skill.skill_id,
14836
14559
  selected_endpoint_id: candidate.endpoint.endpoint_id
14837
14560
  });
14838
- promoteResultSnapshot(cacheKey, skill, candidate.endpoint.endpoint_id, execOut.result, execOut.trace, execOut.response_schema, execOut.extraction_hints);
14561
+ promoteResultSnapshot(cacheKey, skill, candidate.endpoint.endpoint_id, execOut.result, execOut.trace);
14839
14562
  try {
14840
14563
  const endpointSeq = decisionTrace.autoexec_attempts.map((a) => a.endpoint_id);
14841
14564
  storeExecutionTrace({
@@ -14892,7 +14615,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
14892
14615
  trace: execOut.trace,
14893
14616
  source,
14894
14617
  skill,
14895
- timing: finalize2(source, null, skill.skill_id, skill, execOut.trace)
14618
+ timing: finalize(source, null, skill.skill_id, skill, execOut.trace)
14896
14619
  };
14897
14620
  }
14898
14621
  }
@@ -14913,9 +14636,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
14913
14636
  trace: execOut.trace,
14914
14637
  source,
14915
14638
  skill,
14916
- timing: finalize2(source, execOut.result, skill.skill_id, skill, execOut.trace),
14917
- response_schema: execOut.response_schema,
14918
- extraction_hints: execOut.extraction_hints
14639
+ timing: finalize(source, execOut.result, skill.skill_id, skill, execOut.trace)
14919
14640
  };
14920
14641
  }
14921
14642
  decisionTrace.autoexec_attempts.push({
@@ -14979,7 +14700,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
14979
14700
  skill_id: cachedResult.skill.skill_id,
14980
14701
  selected_endpoint_id: cachedResult.endpointId ?? cachedResult.trace.endpoint_id
14981
14702
  });
14982
- return buildCachedResultResponse(cachedResult, "marketplace", finalize2("route-cache", cachedResult.result, cachedResult.skill.skill_id, cachedResult.skill, cachedResult.trace));
14703
+ return buildCachedResultResponse(cachedResult, "marketplace", finalize("route-cache", cachedResult.result, cachedResult.skill.skill_id, cachedResult.skill, cachedResult.trace));
14983
14704
  }
14984
14705
  }
14985
14706
  }
@@ -15093,15 +14814,13 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15093
14814
  timing.execute_ms = Date.now() - te02;
15094
14815
  if (execOut.trace.success && isAcceptableIntentResult(execOut.result, queryIntent)) {
15095
14816
  timing.cache_hit = true;
15096
- promoteResultSnapshot(cacheKey, skill, params.endpoint_id ?? cached.entry.endpointId, execOut.result, execOut.trace, execOut.response_schema, execOut.extraction_hints);
14817
+ promoteResultSnapshot(cacheKey, skill, params.endpoint_id ?? cached.entry.endpointId, execOut.result, execOut.trace);
15097
14818
  return {
15098
14819
  result: execOut.result,
15099
14820
  trace: execOut.trace,
15100
14821
  source: "marketplace",
15101
14822
  skill,
15102
- timing: finalize2("route-cache", execOut.result, cached.entry.skillId, skill, execOut.trace),
15103
- response_schema: execOut.response_schema,
15104
- extraction_hints: execOut.extraction_hints
14823
+ timing: finalize("route-cache", execOut.result, cached.entry.skillId, skill, execOut.trace)
15105
14824
  };
15106
14825
  }
15107
14826
  } catch {
@@ -15208,15 +14927,13 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15208
14927
  ])));
15209
14928
  timing.execute_ms = Date.now() - te02;
15210
14929
  cacheResolvedSkill(cacheKey, winner.candidate.skill, winner.trace.endpoint_id);
15211
- promoteResultSnapshot(cacheKey, winner.candidate.skill, winner.trace.endpoint_id, winner.result, winner.trace, winner.response_schema, winner.extraction_hints);
14930
+ promoteResultSnapshot(cacheKey, winner.candidate.skill, winner.trace.endpoint_id, winner.result, winner.trace);
15212
14931
  return {
15213
14932
  result: winner.result,
15214
14933
  trace: winner.trace,
15215
14934
  source: "marketplace",
15216
14935
  skill: winner.candidate.skill,
15217
- timing: finalize2("marketplace", winner.result, winner.candidate.skill.skill_id, winner.candidate.skill, winner.trace),
15218
- response_schema: winner.response_schema,
15219
- extraction_hints: winner.extraction_hints
14936
+ timing: finalize("marketplace", winner.result, winner.candidate.skill.skill_id, winner.candidate.skill, winner.trace)
15220
14937
  };
15221
14938
  } catch (err) {
15222
14939
  console.log(`[race] all candidates failed after ${Date.now() - te02}ms: ${err.message}`);
@@ -15253,7 +14970,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15253
14970
  completed_at: new Date().toISOString(),
15254
14971
  success: true
15255
14972
  };
15256
- const t = finalize2("direct-fetch", data, "direct-fetch", undefined, trace2);
14973
+ const t = finalize("direct-fetch", data, "direct-fetch", undefined, trace2);
15257
14974
  console.log(`[direct-fetch] ${context.url} returned JSON directly — skipping browser`);
15258
14975
  return {
15259
14976
  result: data,
@@ -15285,7 +15002,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15285
15002
  success: true,
15286
15003
  network_events: firstPassResult.interceptedEntries
15287
15004
  };
15288
- const t = finalize2("first-pass", firstPassResult.result, firstPassResult.miniSkill.skill_id, firstPassResult.miniSkill, trace2);
15005
+ const t = finalize("first-pass", firstPassResult.result, firstPassResult.miniSkill.skill_id, firstPassResult.miniSkill, trace2);
15289
15006
  return {
15290
15007
  result: firstPassResult.result,
15291
15008
  trace: trace2,
@@ -15321,7 +15038,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15321
15038
  completed_at: fpNow,
15322
15039
  success: true
15323
15040
  };
15324
- const t = finalize2("browse-session", null, "browse-session", undefined, trace2);
15041
+ const t = finalize("browse-session", null, "browse-session", undefined, trace2);
15325
15042
  return {
15326
15043
  result: {
15327
15044
  status: "browse_session_open",
@@ -15359,15 +15076,13 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15359
15076
  if (agentChoseEndpoint) {
15360
15077
  const execOut = await executeSkill(domainHit.skill, { ...params, endpoint_id: params.endpoint_id ?? domainHit.endpointId, ...queryIntent !== intent ? { intent: queryIntent } : {} }, projection, { ...options, intent: queryIntent, contextUrl: context?.url });
15361
15078
  if (execOut.trace.success && isAcceptableIntentResult(execOut.result, queryIntent)) {
15362
- promoteResultSnapshot(cacheKey, domainHit.skill, params.endpoint_id ?? domainHit.endpointId, execOut.result, execOut.trace, execOut.response_schema, execOut.extraction_hints);
15079
+ promoteResultSnapshot(cacheKey, domainHit.skill, params.endpoint_id ?? domainHit.endpointId, execOut.result, execOut.trace);
15363
15080
  return {
15364
15081
  result: execOut.result,
15365
15082
  trace: execOut.trace,
15366
15083
  source: "marketplace",
15367
15084
  skill: domainHit.skill,
15368
- timing: finalize2("marketplace", execOut.result, domainHit.skill.skill_id, domainHit.skill, execOut.trace),
15369
- response_schema: execOut.response_schema,
15370
- extraction_hints: execOut.extraction_hints
15085
+ timing: finalize("marketplace", execOut.result, domainHit.skill.skill_id, domainHit.skill, execOut.trace)
15371
15086
  };
15372
15087
  }
15373
15088
  invalidateResolveCacheEntries([cacheKey], requestedDomainCacheKey ? [requestedDomainCacheKey] : []);
@@ -15403,7 +15118,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15403
15118
  trace,
15404
15119
  source: "live-capture",
15405
15120
  skill: await getOrCreateBrowserCaptureSkill(),
15406
- timing: finalize2("live-capture", result, undefined, undefined, trace)
15121
+ timing: finalize("live-capture", result, undefined, undefined, trace)
15407
15122
  };
15408
15123
  }
15409
15124
  if (learned_skill) {
@@ -15422,7 +15137,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15422
15137
  trace,
15423
15138
  source: "live-capture",
15424
15139
  skill: await getOrCreateBrowserCaptureSkill(),
15425
- timing: finalize2("live-capture", result, undefined, undefined, trace)
15140
+ timing: finalize("live-capture", result, undefined, undefined, trace)
15426
15141
  };
15427
15142
  }
15428
15143
  }
@@ -15512,7 +15227,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15512
15227
  trace: rejectedTrace,
15513
15228
  source: "live-capture",
15514
15229
  skill: captureSkill,
15515
- timing: finalize2("live-capture", result, undefined, undefined, rejectedTrace)
15230
+ timing: finalize("live-capture", result, undefined, undefined, rejectedTrace)
15516
15231
  };
15517
15232
  }
15518
15233
  if (learned_skill && learnedSkillUsable) {
@@ -15546,7 +15261,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15546
15261
  trace,
15547
15262
  source: "live-capture",
15548
15263
  skill: captureSkill,
15549
- timing: finalize2("live-capture", result, undefined, undefined, trace)
15264
+ timing: finalize("live-capture", result, undefined, undefined, trace)
15550
15265
  };
15551
15266
  }
15552
15267
  const hasNonDomApiEndpoints = !!learned_skill?.endpoints?.some((ep) => !ep.dom_extraction && ep.method !== "WS");
@@ -15560,7 +15275,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15560
15275
  trace,
15561
15276
  source: directExtractionSource === "html-embedded" ? "live-capture" : "dom-fallback",
15562
15277
  skill: learned_skill,
15563
- timing: finalize2(directExtractionSource === "html-embedded" ? "live-capture" : "dom-fallback", result, learned_skill.skill_id, learned_skill, trace)
15278
+ timing: finalize(directExtractionSource === "html-embedded" ? "live-capture" : "dom-fallback", result, learned_skill.skill_id, learned_skill, trace)
15564
15279
  };
15565
15280
  queuePassivePublishIfExecuted(learned_skill, direct, parityBaseline);
15566
15281
  return direct;
@@ -15570,7 +15285,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15570
15285
  trace,
15571
15286
  source: "dom-fallback",
15572
15287
  skill: captureSkill,
15573
- timing: finalize2("dom-fallback", result, undefined, undefined, trace)
15288
+ timing: finalize("dom-fallback", result, undefined, undefined, trace)
15574
15289
  };
15575
15290
  }
15576
15291
  if (!learned_skill) {
@@ -15579,7 +15294,7 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15579
15294
  trace,
15580
15295
  source: "live-capture",
15581
15296
  skill: captureSkill,
15582
- timing: finalize2("live-capture", result, undefined, undefined, trace)
15297
+ timing: finalize("live-capture", result, undefined, undefined, trace)
15583
15298
  };
15584
15299
  }
15585
15300
  if (agentChoseEndpoint && learned_skill) {
@@ -15598,22 +15313,18 @@ async function resolveAndExecute(intent, params = {}, context, projection, optio
15598
15313
  trace: execOut.trace,
15599
15314
  source: "live-capture",
15600
15315
  skill: learned_skill,
15601
- timing: finalize2("live-capture", execOut.result, learned_skill.skill_id, learned_skill, execOut.trace),
15602
- response_schema: execOut.response_schema,
15603
- extraction_hints: execOut.extraction_hints
15316
+ timing: finalize("live-capture", execOut.result, learned_skill.skill_id, learned_skill, execOut.trace)
15604
15317
  }, parityBaseline);
15605
15318
  }
15606
15319
  if (execOut.trace.success && isAcceptableIntentResult(execOut.result, queryIntent)) {
15607
- promoteResultSnapshot(cacheKey, learned_skill, execOut.trace.endpoint_id, execOut.result, execOut.trace, execOut.response_schema, execOut.extraction_hints);
15320
+ promoteResultSnapshot(cacheKey, learned_skill, execOut.trace.endpoint_id, execOut.result, execOut.trace);
15608
15321
  }
15609
15322
  return {
15610
15323
  result: execOut.result,
15611
15324
  trace: execOut.trace,
15612
15325
  source: "live-capture",
15613
15326
  skill: learned_skill,
15614
- timing: finalize2("live-capture", execOut.result, learned_skill.skill_id, learned_skill, execOut.trace),
15615
- response_schema: execOut.response_schema,
15616
- extraction_hints: execOut.extraction_hints
15327
+ timing: finalize("live-capture", execOut.result, learned_skill.skill_id, learned_skill, execOut.trace)
15617
15328
  };
15618
15329
  }
15619
15330
  const deferred = await buildDeferralWithAutoExec(learned_skill, "live-capture", authRecommended ? {
@@ -16699,7 +16410,19 @@ async function registerRoutes(app) {
16699
16410
  app.get("/v1/skills/:skill_id", async (req, reply) => {
16700
16411
  const clientScope = clientScopeFor(req);
16701
16412
  const { skill_id } = req.params;
16702
- const skill = getRecentLocalSkill(skill_id, clientScope) ?? await getSkill2(skill_id, clientScope);
16413
+ let skill = getRecentLocalSkill(skill_id, clientScope);
16414
+ if (!skill) {
16415
+ for (const [, entry] of domainSkillCache) {
16416
+ if (entry.skillId === skill_id && entry.localSkillPath) {
16417
+ try {
16418
+ skill = JSON.parse(__require("fs").readFileSync(entry.localSkillPath, "utf-8"));
16419
+ } catch {}
16420
+ break;
16421
+ }
16422
+ }
16423
+ }
16424
+ if (!skill)
16425
+ skill = await getSkill2(skill_id, clientScope);
16703
16426
  if (!skill)
16704
16427
  return reply.code(404).send({ error: "Skill not found" });
16705
16428
  return reply.send(skill);
@@ -16741,7 +16464,20 @@ async function registerRoutes(app) {
16741
16464
  const clientScope = clientScopeFor(req);
16742
16465
  const { skill_id } = req.params;
16743
16466
  const { params, projection, confirm_unsafe, dry_run, intent, context_url } = req.body;
16744
- const skill = getRecentLocalSkill(skill_id, clientScope) ?? await getSkill2(skill_id, clientScope);
16467
+ let skill = getRecentLocalSkill(skill_id, clientScope);
16468
+ if (!skill) {
16469
+ const { findExistingSkillForDomain: findLocal } = await Promise.resolve().then(() => (init_client2(), exports_client2));
16470
+ for (const [, entry] of domainSkillCache) {
16471
+ if (entry.skillId === skill_id && entry.localSkillPath) {
16472
+ try {
16473
+ skill = JSON.parse(__require("fs").readFileSync(entry.localSkillPath, "utf-8"));
16474
+ } catch {}
16475
+ break;
16476
+ }
16477
+ }
16478
+ }
16479
+ if (!skill)
16480
+ skill = await getSkill2(skill_id, clientScope);
16745
16481
  if (!skill)
16746
16482
  return reply.code(404).send({ error: "Skill not found" });
16747
16483
  const execParams = {
@@ -17248,13 +16984,6 @@ async function startUnbrowseServer(options = {}) {
17248
16984
  try {
17249
16985
  execSync3("pkill -f chrome-headless-shell", { stdio: "ignore" });
17250
16986
  } catch {}
17251
- try {
17252
- await start();
17253
- const h = await health();
17254
- console.log(`[startup] Kuri ready — ${h.tabs ?? 0} tabs`);
17255
- } catch (err) {
17256
- console.warn(`[startup] WARNING: Kuri not available. Capture will start it on demand. ${err instanceof Error ? err.message : err}`);
17257
- }
17258
16987
  await ensureRegistered2();
17259
16988
  const app = Fastify({ logger: options.logger ?? true });
17260
16989
  await app.register(cors, { origin: true });
@@ -17285,7 +17014,6 @@ var init_server = __esm(async () => {
17285
17014
  init_verification();
17286
17015
  init_client2();
17287
17016
  init_capture();
17288
- init_client();
17289
17017
  await init_routes();
17290
17018
  });
17291
17019
 
@@ -18281,184 +18009,6 @@ function normalizeSetupScope(value) {
18281
18009
  return normalized;
18282
18010
  return "auto";
18283
18011
  }
18284
- function buildEntityIndex(items) {
18285
- const index = new Map;
18286
- for (const item of items) {
18287
- if (item != null && typeof item === "object") {
18288
- const urn = item.entityUrn;
18289
- if (typeof urn === "string")
18290
- index.set(urn, item);
18291
- }
18292
- }
18293
- return index;
18294
- }
18295
- function detectEntityIndex(data) {
18296
- if (data == null || typeof data !== "object")
18297
- return null;
18298
- let best = null;
18299
- const check = (arr) => {
18300
- if (arr.length < 2)
18301
- return;
18302
- const sample = arr.slice(0, 10);
18303
- const withUrn = sample.filter((i) => i != null && typeof i === "object" && typeof i.entityUrn === "string").length;
18304
- if (withUrn >= sample.length * 0.5 && (!best || arr.length > best.length)) {
18305
- best = arr;
18306
- }
18307
- };
18308
- const obj = data;
18309
- for (const val of Object.values(obj)) {
18310
- if (Array.isArray(val)) {
18311
- check(val);
18312
- } else if (val != null && typeof val === "object" && !Array.isArray(val)) {
18313
- for (const nested of Object.values(val)) {
18314
- if (Array.isArray(nested))
18315
- check(nested);
18316
- }
18317
- }
18318
- }
18319
- return best ? buildEntityIndex(best) : null;
18320
- }
18321
- function resolvePath2(obj, path9, entityIndex) {
18322
- if (!path9 || obj == null)
18323
- return obj;
18324
- const segments = path9.split(".");
18325
- let cur = obj;
18326
- for (let i = 0;i < segments.length; i++) {
18327
- if (cur == null)
18328
- return;
18329
- const seg = segments[i];
18330
- if (seg.endsWith("[]")) {
18331
- const key = seg.slice(0, -2);
18332
- const arr = key ? cur[key] : cur;
18333
- if (!Array.isArray(arr))
18334
- return;
18335
- const remaining = segments.slice(i + 1).join(".");
18336
- if (!remaining)
18337
- return arr;
18338
- return arr.flatMap((item) => {
18339
- const v = resolvePath2(item, remaining, entityIndex);
18340
- return v === undefined ? [] : Array.isArray(v) ? v : [v];
18341
- });
18342
- }
18343
- const indexMatch = seg.match(/^(.+?)\[(\d+)\]$/);
18344
- if (indexMatch) {
18345
- const key = indexMatch[1];
18346
- const idx = parseInt(indexMatch[2], 10);
18347
- const arr = key ? cur[key] : cur;
18348
- if (!Array.isArray(arr) || idx >= arr.length)
18349
- return;
18350
- cur = arr[idx];
18351
- continue;
18352
- }
18353
- const rec = cur;
18354
- let val = rec[seg];
18355
- if (val == null && entityIndex) {
18356
- const ref = rec[`*${seg}`];
18357
- if (typeof ref === "string") {
18358
- val = entityIndex.get(ref);
18359
- }
18360
- }
18361
- cur = val;
18362
- }
18363
- return cur;
18364
- }
18365
- function extractFields(data, fields, entityIndex) {
18366
- if (data == null)
18367
- return data;
18368
- function mapItem(item) {
18369
- const out = {};
18370
- for (const f of fields) {
18371
- const colonIdx = f.indexOf(":");
18372
- const alias = colonIdx >= 0 ? f.slice(0, colonIdx) : f.split(".").pop();
18373
- const path9 = colonIdx >= 0 ? f.slice(colonIdx + 1) : f;
18374
- const resolved = resolvePath2(item, path9, entityIndex ?? undefined) ?? [];
18375
- out[alias] = Array.isArray(resolved) ? resolved.length === 0 ? null : resolved.length === 1 ? resolved[0] : resolved : resolved;
18376
- }
18377
- return out;
18378
- }
18379
- function hasValue(v) {
18380
- if (v == null)
18381
- return false;
18382
- if (Array.isArray(v))
18383
- return v.length > 0;
18384
- return true;
18385
- }
18386
- if (Array.isArray(data)) {
18387
- return data.map(mapItem).filter((row) => Object.values(row).some(hasValue));
18388
- }
18389
- return mapItem(data);
18390
- }
18391
- function hasMeaningfulValue(value) {
18392
- if (value == null)
18393
- return false;
18394
- if (typeof value === "string")
18395
- return value.trim().length > 0;
18396
- if (typeof value === "number" || typeof value === "boolean")
18397
- return true;
18398
- if (Array.isArray(value))
18399
- return value.some((item) => hasMeaningfulValue(item));
18400
- if (typeof value === "object")
18401
- return Object.values(value).some((item) => hasMeaningfulValue(item));
18402
- return false;
18403
- }
18404
- function isPlainRecord(value) {
18405
- return value != null && typeof value === "object" && !Array.isArray(value);
18406
- }
18407
- function isScalarLike(value) {
18408
- if (value == null)
18409
- return false;
18410
- if (typeof value === "string")
18411
- return value.trim().length > 0;
18412
- if (typeof value === "number" || typeof value === "boolean")
18413
- return true;
18414
- if (Array.isArray(value)) {
18415
- return value.length > 0 && value.every((item) => item == null || typeof item === "string" || typeof item === "number" || typeof item === "boolean");
18416
- }
18417
- return false;
18418
- }
18419
- function looksStructuredForDirectOutput(value) {
18420
- if (Array.isArray(value)) {
18421
- const sample = value.filter(isPlainRecord).slice(0, 3);
18422
- if (sample.length === 0)
18423
- return false;
18424
- const simpleRows = sample.filter((row) => {
18425
- const keys2 = Object.keys(row);
18426
- const scalarFields2 = Object.values(row).filter(isScalarLike).length;
18427
- return keys2.length > 0 && keys2.length <= 20 && scalarFields2 >= 2;
18428
- });
18429
- return simpleRows.length >= Math.ceil(sample.length / 2);
18430
- }
18431
- if (!isPlainRecord(value))
18432
- return false;
18433
- const keys = Object.keys(value);
18434
- if (keys.length === 0 || keys.length > 20)
18435
- return false;
18436
- const scalarFields = Object.values(value).filter(isScalarLike).length;
18437
- return scalarFields >= 2;
18438
- }
18439
- function applyTransforms(result, flags) {
18440
- let data = result;
18441
- const entityIndex = detectEntityIndex(result);
18442
- const pathFlag = flags.path;
18443
- if (pathFlag) {
18444
- data = resolvePath2(data, pathFlag, entityIndex);
18445
- if (data === undefined) {
18446
- process.stderr.write(`[unbrowse] warning: --path "${pathFlag}" resolved to undefined. Check path against response structure.
18447
- `);
18448
- return [];
18449
- }
18450
- }
18451
- const extractFlag = flags.extract;
18452
- if (extractFlag) {
18453
- const fields = extractFlag.split(",").map((f) => f.trim());
18454
- data = extractFields(data, fields, entityIndex);
18455
- }
18456
- const limitFlag = flags.limit;
18457
- if (limitFlag && Array.isArray(data)) {
18458
- data = data.slice(0, Number(limitFlag));
18459
- }
18460
- return data;
18461
- }
18462
18012
  function slimTrace(obj) {
18463
18013
  const trace = obj.trace;
18464
18014
  const out = {
@@ -18474,67 +18024,14 @@ function slimTrace(obj) {
18474
18024
  };
18475
18025
  if ("result" in obj)
18476
18026
  out.result = obj.result;
18027
+ if (obj.available_endpoints)
18028
+ out.available_endpoints = obj.available_endpoints;
18029
+ if (obj.source)
18030
+ out.source = obj.source;
18031
+ if (obj.skill)
18032
+ out.skill = obj.skill;
18477
18033
  return out;
18478
18034
  }
18479
- function wrapWithHints(obj) {
18480
- const hints = obj.extraction_hints;
18481
- if (!hints)
18482
- return obj;
18483
- const resultStr = JSON.stringify(obj.result ?? "");
18484
- if (resultStr.length < 2000)
18485
- return obj;
18486
- const trace = obj.trace;
18487
- return {
18488
- trace: trace ? {
18489
- trace_id: trace.trace_id,
18490
- skill_id: trace.skill_id,
18491
- endpoint_id: trace.endpoint_id,
18492
- success: trace.success,
18493
- status_code: trace.status_code
18494
- } : undefined,
18495
- _response_too_large: `${resultStr.length} bytes \u2014 use extraction flags below to get structured data`,
18496
- extraction_hints: hints
18497
- };
18498
- }
18499
- function schemaOnly(obj) {
18500
- const trace = obj.trace;
18501
- return {
18502
- trace: trace ? { trace_id: trace.trace_id, skill_id: trace.skill_id, endpoint_id: trace.endpoint_id, success: trace.success } : undefined,
18503
- extraction_hints: obj.extraction_hints ?? null,
18504
- response_schema: obj.response_schema ?? null
18505
- };
18506
- }
18507
- function autoExtractOrWrap(obj) {
18508
- const hints = obj.extraction_hints;
18509
- const resultStr = JSON.stringify(obj.result ?? "");
18510
- if (resultStr.length < 2000)
18511
- return obj;
18512
- if (looksStructuredForDirectOutput(obj.result)) {
18513
- return slimTrace({ ...obj, extraction_hints: undefined, response_schema: undefined });
18514
- }
18515
- if (!hints)
18516
- return obj;
18517
- if (hints.confidence === "high") {
18518
- const syntheticFlags = {};
18519
- if (hints.path)
18520
- syntheticFlags.path = hints.path;
18521
- if (hints.fields.length > 0)
18522
- syntheticFlags.extract = hints.fields.join(",");
18523
- syntheticFlags.limit = "20";
18524
- const extracted = applyTransforms(obj.result, syntheticFlags);
18525
- if (!hasMeaningfulValue(extracted))
18526
- return wrapWithHints(obj);
18527
- const slimmed = slimTrace({ ...obj, result: extracted });
18528
- slimmed._auto_extracted = {
18529
- applied: hints.cli_args,
18530
- confidence: hints.confidence,
18531
- all_fields: hints.schema_tree,
18532
- note: "Auto-extracted using response_schema. Add/remove fields with --extract, change array with --path, or use --raw for full response."
18533
- };
18534
- return slimmed;
18535
- }
18536
- return wrapWithHints(obj);
18537
- }
18538
18035
  async function cmdHealth(flags) {
18539
18036
  output(await api3("GET", "/health"), !!flags.pretty);
18540
18037
  }
@@ -18546,6 +18043,8 @@ async function cmdResolve(flags) {
18546
18043
  const url = flags.url;
18547
18044
  const domain = flags.domain;
18548
18045
  const explicitEndpointId = flags["endpoint-id"];
18046
+ const autoExecute = !!flags.execute;
18047
+ const extraParams = flags.params ? JSON.parse(flags.params) : {};
18549
18048
  if (url) {
18550
18049
  body.params = { url };
18551
18050
  body.context = { url };
@@ -18557,14 +18056,19 @@ async function cmdResolve(flags) {
18557
18056
  body.params = { ...body.params ?? {}, endpoint_id: explicitEndpointId };
18558
18057
  }
18559
18058
  if (flags.params) {
18560
- body.params = { ...body.params ?? {}, ...JSON.parse(flags.params) };
18059
+ body.params = { ...body.params ?? {}, ...extraParams };
18561
18060
  }
18562
18061
  if (flags["dry-run"])
18563
18062
  body.dry_run = true;
18564
18063
  if (flags["force-capture"])
18565
18064
  body.force_capture = true;
18566
- const hasTransforms = !!(flags.path || flags.extract);
18567
18065
  body.projection = { raw: true };
18066
+ function execBody(endpointId) {
18067
+ return { params: { endpoint_id: endpointId, ...extraParams }, intent, projection: { raw: true } };
18068
+ }
18069
+ function resolveSkillId() {
18070
+ return result.skill?.skill_id ?? result.skill_id;
18071
+ }
18568
18072
  const startedAt = Date.now();
18569
18073
  let result = await withPendingNotice(api3("POST", "/v1/intent/resolve", body), "Still working. First-time capture/indexing for a site can take 20-80s. Waiting is usually better than falling back.");
18570
18074
  const resultError = result.result?.error ?? result.error;
@@ -18582,14 +18086,18 @@ async function cmdResolve(flags) {
18582
18086
  }
18583
18087
  }
18584
18088
  if (explicitEndpointId && result.available_endpoints) {
18585
- const skillId = result.skill?.skill_id ?? result.skill_id;
18089
+ const skillId = resolveSkillId();
18586
18090
  if (skillId) {
18587
- const execBody = {
18588
- params: { endpoint_id: explicitEndpointId, ...flags.params ? JSON.parse(flags.params) : {} },
18589
- intent,
18590
- projection: { raw: true }
18591
- };
18592
- result = await withPendingNotice(api3("POST", `/v1/skills/${skillId}/execute`, execBody), "Executing selected endpoint...");
18091
+ result = await withPendingNotice(api3("POST", `/v1/skills/${skillId}/execute`, execBody(explicitEndpointId)), "Executing selected endpoint...");
18092
+ }
18093
+ }
18094
+ if (autoExecute && result.available_endpoints && !result.result) {
18095
+ const endpoints = result.available_endpoints;
18096
+ const skillId = resolveSkillId();
18097
+ if (skillId && endpoints.length > 0) {
18098
+ const bestEndpoint = endpoints[0];
18099
+ info(`Auto-executing endpoint: ${bestEndpoint.description ?? bestEndpoint.endpoint_id}`);
18100
+ result = await withPendingNotice(api3("POST", `/v1/skills/${skillId}/execute`, execBody(bestEndpoint.endpoint_id)), "Executing best endpoint...");
18593
18101
  }
18594
18102
  }
18595
18103
  const resultObj = result.result;
@@ -18606,13 +18114,7 @@ async function cmdResolve(flags) {
18606
18114
  if (Date.now() - startedAt > 3000 && result.source === "live-capture") {
18607
18115
  info("Live capture finished. Future runs against this site should be much faster.");
18608
18116
  }
18609
- if (flags.schema) {
18610
- output(schemaOnly(result), !!flags.pretty);
18611
- return;
18612
- }
18613
- if (hasTransforms && result.result != null) {
18614
- result = slimTrace({ ...result, result: applyTransforms(result.result, flags) });
18615
- }
18117
+ result = slimTrace(result);
18616
18118
  const skill = result.skill;
18617
18119
  const trace = result.trace;
18618
18120
  if (skill?.skill_id && trace) {
@@ -18641,16 +18143,9 @@ async function cmdExecute(flags) {
18641
18143
  body.dry_run = true;
18642
18144
  if (flags["confirm-unsafe"])
18643
18145
  body.confirm_unsafe = true;
18644
- const hasTransforms = !!(flags.path || flags.extract);
18645
18146
  body.projection = { raw: true };
18646
18147
  let result = await withPendingNotice(api3("POST", `/v1/skills/${skillId}/execute`, body), "Still working. This endpoint may require browser replay or first-time auth/capture setup.");
18647
- if (flags.schema) {
18648
- output(schemaOnly(result), !!flags.pretty);
18649
- return;
18650
- }
18651
- if (hasTransforms && result.result != null) {
18652
- result = slimTrace({ ...result, result: applyTransforms(result.result, flags) });
18653
- }
18148
+ result = slimTrace(result);
18654
18149
  output(result, !!flags.pretty);
18655
18150
  }
18656
18151
  async function cmdFeedback(flags) {
@@ -18745,7 +18240,7 @@ var CLI_REFERENCE = {
18745
18240
  commands: [
18746
18241
  { name: "health", usage: "", desc: "Server health check" },
18747
18242
  { name: "setup", usage: "[--opencode auto|global|project|off] [--no-start]", desc: "Bootstrap browser deps + Open Code command" },
18748
- { name: "resolve", usage: '--intent "..." --url "..." [opts]', desc: "Resolve intent \u2192 search/capture/execute" },
18243
+ { name: "resolve", usage: '--intent "..." --url "..." [opts]', desc: "Resolve intent \u2192 find skill + execute" },
18749
18244
  { name: "execute", usage: "--skill ID --endpoint ID [opts]", desc: "Execute a specific endpoint" },
18750
18245
  { name: "feedback", usage: "--skill ID --endpoint ID --rating N", desc: "Submit feedback (mandatory after resolve)" },
18751
18246
  { name: "login", usage: '--url "..."', desc: "Interactive browser login" },
@@ -18773,15 +18268,11 @@ var CLI_REFERENCE = {
18773
18268
  globalFlags: [
18774
18269
  { flag: "--pretty", desc: "Indented JSON output" },
18775
18270
  { flag: "--no-auto-start", desc: "Don't auto-start server" },
18776
- { flag: "--raw", desc: "Return raw response data (skip server-side projection)" },
18777
18271
  { flag: "--skip-browser", desc: "setup: skip browser-engine install" },
18778
18272
  { flag: "--opencode auto|global|project|off", desc: "setup: install /unbrowse command for Open Code" }
18779
18273
  ],
18780
18274
  resolveExecuteFlags: [
18781
- { flag: "--schema", desc: "Show response schema + extraction hints only (no data)" },
18782
- { flag: '--path "data.items[]"', desc: "Drill into result before extract/output" },
18783
- { flag: '--extract "field1,alias:deep.path.to.val"', desc: "Pick specific fields (no piping needed)" },
18784
- { flag: "--limit N", desc: "Cap array output to N items" },
18275
+ { flag: "--execute", desc: "Auto-pick best endpoint and return data (resolve only)" },
18785
18276
  { flag: "--endpoint-id ID", desc: "Pick a specific endpoint" },
18786
18277
  { flag: "--dry-run", desc: "Preview mutations" },
18787
18278
  { flag: "--force-capture", desc: "Bypass caches, re-capture" },
@@ -18789,10 +18280,9 @@ var CLI_REFERENCE = {
18789
18280
  ],
18790
18281
  examples: [
18791
18282
  "unbrowse setup",
18283
+ 'unbrowse resolve --intent "top stories" --url "https://news.ycombinator.com" --execute',
18792
18284
  'unbrowse resolve --intent "get timeline" --url "https://x.com"',
18793
18285
  "unbrowse execute --skill abc --endpoint def --pretty",
18794
- 'unbrowse execute --skill abc --endpoint def --extract "user,text,likes" --limit 10',
18795
- 'unbrowse execute --skill abc --endpoint def --path "data.included[]" --extract "name:actor.name,text:commentary.text" --limit 20',
18796
18286
  "unbrowse feedback --skill abc --endpoint def --rating 5"
18797
18287
  ]
18798
18288
  };
@@ -18913,9 +18403,7 @@ async function cmdSiteTask(pack, taskName, flags) {
18913
18403
  body.dry_run = true;
18914
18404
  if (flags["force-capture"])
18915
18405
  body.force_capture = true;
18916
- const hasTransforms = !!(flags.path || flags.extract);
18917
- if (flags.raw || hasTransforms)
18918
- body.projection = { raw: true };
18406
+ body.projection = { raw: true };
18919
18407
  const startedAt = Date.now();
18920
18408
  let result = await withPendingNotice(api3("POST", "/v1/intent/resolve", body), "Still working. First-time capture/indexing for a site can take 20-80s.");
18921
18409
  if (result && typeof result === "object" && result.error === "auth_required") {
@@ -18924,15 +18412,7 @@ async function cmdSiteTask(pack, taskName, flags) {
18924
18412
  output({ ...result, _deps: { ...deps2, requires: ["login"] }, _next: [`unbrowse ${pack.site} login`] }, !!flags.pretty);
18925
18413
  process.exit(2);
18926
18414
  }
18927
- if (flags.schema) {
18928
- output(schemaOnly(result), !!flags.pretty);
18929
- return;
18930
- }
18931
- if (hasTransforms && result.result != null) {
18932
- result = slimTrace({ ...result, result: applyTransforms(result.result, flags) });
18933
- } else if (!flags.raw && result.result != null) {
18934
- result = autoExtractOrWrap(result);
18935
- }
18415
+ result = slimTrace(result);
18936
18416
  const deps = buildDepsMetadata(pack, taskName);
18937
18417
  result._deps = deps;
18938
18418
  result._shortcut = `${pack.site} ${taskName}`;
@@ -18964,14 +18444,9 @@ async function cmdSiteBatch(pack, batchArg, flags) {
18964
18444
  };
18965
18445
  if (flags["force-capture"])
18966
18446
  body.force_capture = true;
18967
- const hasTransforms = !!(flags.path || flags.extract);
18968
- if (flags.raw || hasTransforms)
18969
- body.projection = { raw: true };
18970
- let res = await api3("POST", "/v1/intent/resolve", body);
18971
- if (!flags.raw && res.result != null) {
18972
- res = autoExtractOrWrap(res);
18973
- }
18974
- return { task, result: res };
18447
+ body.projection = { raw: true };
18448
+ const res = await api3("POST", "/v1/intent/resolve", body);
18449
+ return { task, result: slimTrace(res) };
18975
18450
  });
18976
18451
  const waveResult = await Promise.all(promises);
18977
18452
  waveResults.push({
@@ -19072,6 +18547,54 @@ async function cmdForward() {
19072
18547
  async function cmdClose() {
19073
18548
  output(await api3("POST", "/v1/browse/close"), false);
19074
18549
  }
18550
+ async function cmdConnectChrome() {
18551
+ const { execSync: execSync4, spawn: spawnProc } = __require("child_process");
18552
+ try {
18553
+ const res = await fetch("http://127.0.0.1:9222/json/version", { signal: AbortSignal.timeout(1000) });
18554
+ if (res.ok) {
18555
+ const data = await res.json();
18556
+ if (!data["User-Agent"]?.includes("Headless")) {
18557
+ console.log("Your Chrome is already connected with CDP on port 9222.");
18558
+ console.log("Browse commands will use your real browser with all your sessions.");
18559
+ return;
18560
+ }
18561
+ }
18562
+ } catch {}
18563
+ try {
18564
+ execSync4("pkill -f kuri/chrome-profile", { stdio: "ignore" });
18565
+ } catch {}
18566
+ console.log("Quitting Chrome to relaunch with remote debugging...");
18567
+ if (process.platform === "darwin") {
18568
+ try {
18569
+ execSync4('osascript -e "quit app \\"Google Chrome\\""', { stdio: "ignore", timeout: 5000 });
18570
+ } catch {}
18571
+ } else {
18572
+ try {
18573
+ execSync4("pkill -f chrome", { stdio: "ignore" });
18574
+ } catch {}
18575
+ }
18576
+ await new Promise((r) => setTimeout(r, 2000));
18577
+ console.log("Launching Chrome with remote debugging on port 9222...");
18578
+ if (process.platform === "darwin") {
18579
+ spawnProc("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", ["--remote-debugging-port=9222", "--no-first-run", "--no-default-browser-check"], { stdio: "ignore", detached: true }).unref();
18580
+ } else {
18581
+ spawnProc("google-chrome", ["--remote-debugging-port=9222"], { stdio: "ignore", detached: true }).unref();
18582
+ }
18583
+ const deadline = Date.now() + 15000;
18584
+ while (Date.now() < deadline) {
18585
+ try {
18586
+ const res = await fetch("http://127.0.0.1:9222/json/version", { signal: AbortSignal.timeout(500) });
18587
+ if (res.ok) {
18588
+ console.log("Connected. Your real Chrome is now available for browse commands.");
18589
+ console.log("All your logged-in sessions (LinkedIn, X, etc.) will work.");
18590
+ console.log('Run: unbrowse go "https://linkedin.com/feed/"');
18591
+ return;
18592
+ }
18593
+ } catch {}
18594
+ await new Promise((r) => setTimeout(r, 500));
18595
+ }
18596
+ console.error("Could not connect to Chrome. Make sure all Chrome windows are closed and try again.");
18597
+ }
19075
18598
  async function main() {
19076
18599
  const { command, args, flags } = parseArgs(process.argv);
19077
18600
  const noAutoStart = !!flags["no-auto-start"];
@@ -19093,6 +18616,8 @@ async function main() {
19093
18616
  return cmdRestart(flags);
19094
18617
  if (command === "upgrade" || command === "update")
19095
18618
  return cmdUpgrade(flags);
18619
+ if (command === "connect-chrome")
18620
+ return cmdConnectChrome();
19096
18621
  const KNOWN_COMMANDS = new Set([
19097
18622
  "health",
19098
18623
  "setup",
@@ -19126,7 +18651,8 @@ async function main() {
19126
18651
  "eval",
19127
18652
  "back",
19128
18653
  "forward",
19129
- "close"
18654
+ "close",
18655
+ "connect-chrome"
19130
18656
  ]);
19131
18657
  if (!KNOWN_COMMANDS.has(command)) {
19132
18658
  const pack = findSitePack(command);
@@ -19202,6 +18728,8 @@ async function main() {
19202
18728
  return cmdForward();
19203
18729
  case "close":
19204
18730
  return cmdClose();
18731
+ case "connect-chrome":
18732
+ return cmdConnectChrome();
19205
18733
  default:
19206
18734
  info(`Unknown command: ${command}`);
19207
18735
  printHelp();