unbrowse 2.8.4 → 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);
@@ -17261,13 +16984,6 @@ async function startUnbrowseServer(options = {}) {
17261
16984
  try {
17262
16985
  execSync3("pkill -f chrome-headless-shell", { stdio: "ignore" });
17263
16986
  } catch {}
17264
- try {
17265
- await start();
17266
- const h = await health();
17267
- console.log(`[startup] Kuri ready — ${h.tabs ?? 0} tabs`);
17268
- } catch (err) {
17269
- console.warn(`[startup] WARNING: Kuri not available. Capture will start it on demand. ${err instanceof Error ? err.message : err}`);
17270
- }
17271
16987
  await ensureRegistered2();
17272
16988
  const app = Fastify({ logger: options.logger ?? true });
17273
16989
  await app.register(cors, { origin: true });
@@ -17298,7 +17014,6 @@ var init_server = __esm(async () => {
17298
17014
  init_verification();
17299
17015
  init_client2();
17300
17016
  init_capture();
17301
- init_client();
17302
17017
  await init_routes();
17303
17018
  });
17304
17019
 
@@ -18294,184 +18009,6 @@ function normalizeSetupScope(value) {
18294
18009
  return normalized;
18295
18010
  return "auto";
18296
18011
  }
18297
- function buildEntityIndex(items) {
18298
- const index = new Map;
18299
- for (const item of items) {
18300
- if (item != null && typeof item === "object") {
18301
- const urn = item.entityUrn;
18302
- if (typeof urn === "string")
18303
- index.set(urn, item);
18304
- }
18305
- }
18306
- return index;
18307
- }
18308
- function detectEntityIndex(data) {
18309
- if (data == null || typeof data !== "object")
18310
- return null;
18311
- let best = null;
18312
- const check = (arr) => {
18313
- if (arr.length < 2)
18314
- return;
18315
- const sample = arr.slice(0, 10);
18316
- const withUrn = sample.filter((i) => i != null && typeof i === "object" && typeof i.entityUrn === "string").length;
18317
- if (withUrn >= sample.length * 0.5 && (!best || arr.length > best.length)) {
18318
- best = arr;
18319
- }
18320
- };
18321
- const obj = data;
18322
- for (const val of Object.values(obj)) {
18323
- if (Array.isArray(val)) {
18324
- check(val);
18325
- } else if (val != null && typeof val === "object" && !Array.isArray(val)) {
18326
- for (const nested of Object.values(val)) {
18327
- if (Array.isArray(nested))
18328
- check(nested);
18329
- }
18330
- }
18331
- }
18332
- return best ? buildEntityIndex(best) : null;
18333
- }
18334
- function resolvePath2(obj, path9, entityIndex) {
18335
- if (!path9 || obj == null)
18336
- return obj;
18337
- const segments = path9.split(".");
18338
- let cur = obj;
18339
- for (let i = 0;i < segments.length; i++) {
18340
- if (cur == null)
18341
- return;
18342
- const seg = segments[i];
18343
- if (seg.endsWith("[]")) {
18344
- const key = seg.slice(0, -2);
18345
- const arr = key ? cur[key] : cur;
18346
- if (!Array.isArray(arr))
18347
- return;
18348
- const remaining = segments.slice(i + 1).join(".");
18349
- if (!remaining)
18350
- return arr;
18351
- return arr.flatMap((item) => {
18352
- const v = resolvePath2(item, remaining, entityIndex);
18353
- return v === undefined ? [] : Array.isArray(v) ? v : [v];
18354
- });
18355
- }
18356
- const indexMatch = seg.match(/^(.+?)\[(\d+)\]$/);
18357
- if (indexMatch) {
18358
- const key = indexMatch[1];
18359
- const idx = parseInt(indexMatch[2], 10);
18360
- const arr = key ? cur[key] : cur;
18361
- if (!Array.isArray(arr) || idx >= arr.length)
18362
- return;
18363
- cur = arr[idx];
18364
- continue;
18365
- }
18366
- const rec = cur;
18367
- let val = rec[seg];
18368
- if (val == null && entityIndex) {
18369
- const ref = rec[`*${seg}`];
18370
- if (typeof ref === "string") {
18371
- val = entityIndex.get(ref);
18372
- }
18373
- }
18374
- cur = val;
18375
- }
18376
- return cur;
18377
- }
18378
- function extractFields(data, fields, entityIndex) {
18379
- if (data == null)
18380
- return data;
18381
- function mapItem(item) {
18382
- const out = {};
18383
- for (const f of fields) {
18384
- const colonIdx = f.indexOf(":");
18385
- const alias = colonIdx >= 0 ? f.slice(0, colonIdx) : f.split(".").pop();
18386
- const path9 = colonIdx >= 0 ? f.slice(colonIdx + 1) : f;
18387
- const resolved = resolvePath2(item, path9, entityIndex ?? undefined) ?? [];
18388
- out[alias] = Array.isArray(resolved) ? resolved.length === 0 ? null : resolved.length === 1 ? resolved[0] : resolved : resolved;
18389
- }
18390
- return out;
18391
- }
18392
- function hasValue(v) {
18393
- if (v == null)
18394
- return false;
18395
- if (Array.isArray(v))
18396
- return v.length > 0;
18397
- return true;
18398
- }
18399
- if (Array.isArray(data)) {
18400
- return data.map(mapItem).filter((row) => Object.values(row).some(hasValue));
18401
- }
18402
- return mapItem(data);
18403
- }
18404
- function hasMeaningfulValue(value) {
18405
- if (value == null)
18406
- return false;
18407
- if (typeof value === "string")
18408
- return value.trim().length > 0;
18409
- if (typeof value === "number" || typeof value === "boolean")
18410
- return true;
18411
- if (Array.isArray(value))
18412
- return value.some((item) => hasMeaningfulValue(item));
18413
- if (typeof value === "object")
18414
- return Object.values(value).some((item) => hasMeaningfulValue(item));
18415
- return false;
18416
- }
18417
- function isPlainRecord(value) {
18418
- return value != null && typeof value === "object" && !Array.isArray(value);
18419
- }
18420
- function isScalarLike(value) {
18421
- if (value == null)
18422
- return false;
18423
- if (typeof value === "string")
18424
- return value.trim().length > 0;
18425
- if (typeof value === "number" || typeof value === "boolean")
18426
- return true;
18427
- if (Array.isArray(value)) {
18428
- return value.length > 0 && value.every((item) => item == null || typeof item === "string" || typeof item === "number" || typeof item === "boolean");
18429
- }
18430
- return false;
18431
- }
18432
- function looksStructuredForDirectOutput(value) {
18433
- if (Array.isArray(value)) {
18434
- const sample = value.filter(isPlainRecord).slice(0, 3);
18435
- if (sample.length === 0)
18436
- return false;
18437
- const simpleRows = sample.filter((row) => {
18438
- const keys2 = Object.keys(row);
18439
- const scalarFields2 = Object.values(row).filter(isScalarLike).length;
18440
- return keys2.length > 0 && keys2.length <= 20 && scalarFields2 >= 2;
18441
- });
18442
- return simpleRows.length >= Math.ceil(sample.length / 2);
18443
- }
18444
- if (!isPlainRecord(value))
18445
- return false;
18446
- const keys = Object.keys(value);
18447
- if (keys.length === 0 || keys.length > 20)
18448
- return false;
18449
- const scalarFields = Object.values(value).filter(isScalarLike).length;
18450
- return scalarFields >= 2;
18451
- }
18452
- function applyTransforms(result, flags) {
18453
- let data = result;
18454
- const entityIndex = detectEntityIndex(result);
18455
- const pathFlag = flags.path;
18456
- if (pathFlag) {
18457
- data = resolvePath2(data, pathFlag, entityIndex);
18458
- if (data === undefined) {
18459
- process.stderr.write(`[unbrowse] warning: --path "${pathFlag}" resolved to undefined. Check path against response structure.
18460
- `);
18461
- return [];
18462
- }
18463
- }
18464
- const extractFlag = flags.extract;
18465
- if (extractFlag) {
18466
- const fields = extractFlag.split(",").map((f) => f.trim());
18467
- data = extractFields(data, fields, entityIndex);
18468
- }
18469
- const limitFlag = flags.limit;
18470
- if (limitFlag && Array.isArray(data)) {
18471
- data = data.slice(0, Number(limitFlag));
18472
- }
18473
- return data;
18474
- }
18475
18012
  function slimTrace(obj) {
18476
18013
  const trace = obj.trace;
18477
18014
  const out = {
@@ -18487,67 +18024,14 @@ function slimTrace(obj) {
18487
18024
  };
18488
18025
  if ("result" in obj)
18489
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;
18490
18033
  return out;
18491
18034
  }
18492
- function wrapWithHints(obj) {
18493
- const hints = obj.extraction_hints;
18494
- if (!hints)
18495
- return obj;
18496
- const resultStr = JSON.stringify(obj.result ?? "");
18497
- if (resultStr.length < 2000)
18498
- return obj;
18499
- const trace = obj.trace;
18500
- return {
18501
- trace: trace ? {
18502
- trace_id: trace.trace_id,
18503
- skill_id: trace.skill_id,
18504
- endpoint_id: trace.endpoint_id,
18505
- success: trace.success,
18506
- status_code: trace.status_code
18507
- } : undefined,
18508
- _response_too_large: `${resultStr.length} bytes \u2014 use extraction flags below to get structured data`,
18509
- extraction_hints: hints
18510
- };
18511
- }
18512
- function schemaOnly(obj) {
18513
- const trace = obj.trace;
18514
- return {
18515
- trace: trace ? { trace_id: trace.trace_id, skill_id: trace.skill_id, endpoint_id: trace.endpoint_id, success: trace.success } : undefined,
18516
- extraction_hints: obj.extraction_hints ?? null,
18517
- response_schema: obj.response_schema ?? null
18518
- };
18519
- }
18520
- function autoExtractOrWrap(obj) {
18521
- const hints = obj.extraction_hints;
18522
- const resultStr = JSON.stringify(obj.result ?? "");
18523
- if (resultStr.length < 2000)
18524
- return obj;
18525
- if (looksStructuredForDirectOutput(obj.result)) {
18526
- return slimTrace({ ...obj, extraction_hints: undefined, response_schema: undefined });
18527
- }
18528
- if (!hints)
18529
- return obj;
18530
- if (hints.confidence === "high") {
18531
- const syntheticFlags = {};
18532
- if (hints.path)
18533
- syntheticFlags.path = hints.path;
18534
- if (hints.fields.length > 0)
18535
- syntheticFlags.extract = hints.fields.join(",");
18536
- syntheticFlags.limit = "20";
18537
- const extracted = applyTransforms(obj.result, syntheticFlags);
18538
- if (!hasMeaningfulValue(extracted))
18539
- return wrapWithHints(obj);
18540
- const slimmed = slimTrace({ ...obj, result: extracted });
18541
- slimmed._auto_extracted = {
18542
- applied: hints.cli_args,
18543
- confidence: hints.confidence,
18544
- all_fields: hints.schema_tree,
18545
- note: "Auto-extracted using response_schema. Add/remove fields with --extract, change array with --path, or use --raw for full response."
18546
- };
18547
- return slimmed;
18548
- }
18549
- return wrapWithHints(obj);
18550
- }
18551
18035
  async function cmdHealth(flags) {
18552
18036
  output(await api3("GET", "/health"), !!flags.pretty);
18553
18037
  }
@@ -18559,6 +18043,8 @@ async function cmdResolve(flags) {
18559
18043
  const url = flags.url;
18560
18044
  const domain = flags.domain;
18561
18045
  const explicitEndpointId = flags["endpoint-id"];
18046
+ const autoExecute = !!flags.execute;
18047
+ const extraParams = flags.params ? JSON.parse(flags.params) : {};
18562
18048
  if (url) {
18563
18049
  body.params = { url };
18564
18050
  body.context = { url };
@@ -18570,14 +18056,19 @@ async function cmdResolve(flags) {
18570
18056
  body.params = { ...body.params ?? {}, endpoint_id: explicitEndpointId };
18571
18057
  }
18572
18058
  if (flags.params) {
18573
- body.params = { ...body.params ?? {}, ...JSON.parse(flags.params) };
18059
+ body.params = { ...body.params ?? {}, ...extraParams };
18574
18060
  }
18575
18061
  if (flags["dry-run"])
18576
18062
  body.dry_run = true;
18577
18063
  if (flags["force-capture"])
18578
18064
  body.force_capture = true;
18579
- const hasTransforms = !!(flags.path || flags.extract);
18580
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
+ }
18581
18072
  const startedAt = Date.now();
18582
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.");
18583
18074
  const resultError = result.result?.error ?? result.error;
@@ -18595,14 +18086,18 @@ async function cmdResolve(flags) {
18595
18086
  }
18596
18087
  }
18597
18088
  if (explicitEndpointId && result.available_endpoints) {
18598
- const skillId = result.skill?.skill_id ?? result.skill_id;
18089
+ const skillId = resolveSkillId();
18599
18090
  if (skillId) {
18600
- const execBody = {
18601
- params: { endpoint_id: explicitEndpointId, ...flags.params ? JSON.parse(flags.params) : {} },
18602
- intent,
18603
- projection: { raw: true }
18604
- };
18605
- 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...");
18606
18101
  }
18607
18102
  }
18608
18103
  const resultObj = result.result;
@@ -18619,13 +18114,7 @@ async function cmdResolve(flags) {
18619
18114
  if (Date.now() - startedAt > 3000 && result.source === "live-capture") {
18620
18115
  info("Live capture finished. Future runs against this site should be much faster.");
18621
18116
  }
18622
- if (flags.schema) {
18623
- output(schemaOnly(result), !!flags.pretty);
18624
- return;
18625
- }
18626
- if (hasTransforms && result.result != null) {
18627
- result = slimTrace({ ...result, result: applyTransforms(result.result, flags) });
18628
- }
18117
+ result = slimTrace(result);
18629
18118
  const skill = result.skill;
18630
18119
  const trace = result.trace;
18631
18120
  if (skill?.skill_id && trace) {
@@ -18654,16 +18143,9 @@ async function cmdExecute(flags) {
18654
18143
  body.dry_run = true;
18655
18144
  if (flags["confirm-unsafe"])
18656
18145
  body.confirm_unsafe = true;
18657
- const hasTransforms = !!(flags.path || flags.extract);
18658
18146
  body.projection = { raw: true };
18659
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.");
18660
- if (flags.schema) {
18661
- output(schemaOnly(result), !!flags.pretty);
18662
- return;
18663
- }
18664
- if (hasTransforms && result.result != null) {
18665
- result = slimTrace({ ...result, result: applyTransforms(result.result, flags) });
18666
- }
18148
+ result = slimTrace(result);
18667
18149
  output(result, !!flags.pretty);
18668
18150
  }
18669
18151
  async function cmdFeedback(flags) {
@@ -18758,7 +18240,7 @@ var CLI_REFERENCE = {
18758
18240
  commands: [
18759
18241
  { name: "health", usage: "", desc: "Server health check" },
18760
18242
  { name: "setup", usage: "[--opencode auto|global|project|off] [--no-start]", desc: "Bootstrap browser deps + Open Code command" },
18761
- { 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" },
18762
18244
  { name: "execute", usage: "--skill ID --endpoint ID [opts]", desc: "Execute a specific endpoint" },
18763
18245
  { name: "feedback", usage: "--skill ID --endpoint ID --rating N", desc: "Submit feedback (mandatory after resolve)" },
18764
18246
  { name: "login", usage: '--url "..."', desc: "Interactive browser login" },
@@ -18786,15 +18268,11 @@ var CLI_REFERENCE = {
18786
18268
  globalFlags: [
18787
18269
  { flag: "--pretty", desc: "Indented JSON output" },
18788
18270
  { flag: "--no-auto-start", desc: "Don't auto-start server" },
18789
- { flag: "--raw", desc: "Return raw response data (skip server-side projection)" },
18790
18271
  { flag: "--skip-browser", desc: "setup: skip browser-engine install" },
18791
18272
  { flag: "--opencode auto|global|project|off", desc: "setup: install /unbrowse command for Open Code" }
18792
18273
  ],
18793
18274
  resolveExecuteFlags: [
18794
- { flag: "--schema", desc: "Show response schema + extraction hints only (no data)" },
18795
- { flag: '--path "data.items[]"', desc: "Drill into result before extract/output" },
18796
- { flag: '--extract "field1,alias:deep.path.to.val"', desc: "Pick specific fields (no piping needed)" },
18797
- { flag: "--limit N", desc: "Cap array output to N items" },
18275
+ { flag: "--execute", desc: "Auto-pick best endpoint and return data (resolve only)" },
18798
18276
  { flag: "--endpoint-id ID", desc: "Pick a specific endpoint" },
18799
18277
  { flag: "--dry-run", desc: "Preview mutations" },
18800
18278
  { flag: "--force-capture", desc: "Bypass caches, re-capture" },
@@ -18802,10 +18280,9 @@ var CLI_REFERENCE = {
18802
18280
  ],
18803
18281
  examples: [
18804
18282
  "unbrowse setup",
18283
+ 'unbrowse resolve --intent "top stories" --url "https://news.ycombinator.com" --execute',
18805
18284
  'unbrowse resolve --intent "get timeline" --url "https://x.com"',
18806
18285
  "unbrowse execute --skill abc --endpoint def --pretty",
18807
- 'unbrowse execute --skill abc --endpoint def --extract "user,text,likes" --limit 10',
18808
- 'unbrowse execute --skill abc --endpoint def --path "data.included[]" --extract "name:actor.name,text:commentary.text" --limit 20',
18809
18286
  "unbrowse feedback --skill abc --endpoint def --rating 5"
18810
18287
  ]
18811
18288
  };
@@ -18926,9 +18403,7 @@ async function cmdSiteTask(pack, taskName, flags) {
18926
18403
  body.dry_run = true;
18927
18404
  if (flags["force-capture"])
18928
18405
  body.force_capture = true;
18929
- const hasTransforms = !!(flags.path || flags.extract);
18930
- if (flags.raw || hasTransforms)
18931
- body.projection = { raw: true };
18406
+ body.projection = { raw: true };
18932
18407
  const startedAt = Date.now();
18933
18408
  let result = await withPendingNotice(api3("POST", "/v1/intent/resolve", body), "Still working. First-time capture/indexing for a site can take 20-80s.");
18934
18409
  if (result && typeof result === "object" && result.error === "auth_required") {
@@ -18937,15 +18412,7 @@ async function cmdSiteTask(pack, taskName, flags) {
18937
18412
  output({ ...result, _deps: { ...deps2, requires: ["login"] }, _next: [`unbrowse ${pack.site} login`] }, !!flags.pretty);
18938
18413
  process.exit(2);
18939
18414
  }
18940
- if (flags.schema) {
18941
- output(schemaOnly(result), !!flags.pretty);
18942
- return;
18943
- }
18944
- if (hasTransforms && result.result != null) {
18945
- result = slimTrace({ ...result, result: applyTransforms(result.result, flags) });
18946
- } else if (!flags.raw && result.result != null) {
18947
- result = autoExtractOrWrap(result);
18948
- }
18415
+ result = slimTrace(result);
18949
18416
  const deps = buildDepsMetadata(pack, taskName);
18950
18417
  result._deps = deps;
18951
18418
  result._shortcut = `${pack.site} ${taskName}`;
@@ -18977,14 +18444,9 @@ async function cmdSiteBatch(pack, batchArg, flags) {
18977
18444
  };
18978
18445
  if (flags["force-capture"])
18979
18446
  body.force_capture = true;
18980
- const hasTransforms = !!(flags.path || flags.extract);
18981
- if (flags.raw || hasTransforms)
18982
- body.projection = { raw: true };
18983
- let res = await api3("POST", "/v1/intent/resolve", body);
18984
- if (!flags.raw && res.result != null) {
18985
- res = autoExtractOrWrap(res);
18986
- }
18987
- 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) };
18988
18450
  });
18989
18451
  const waveResult = await Promise.all(promises);
18990
18452
  waveResults.push({
@@ -19085,6 +18547,54 @@ async function cmdForward() {
19085
18547
  async function cmdClose() {
19086
18548
  output(await api3("POST", "/v1/browse/close"), false);
19087
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
+ }
19088
18598
  async function main() {
19089
18599
  const { command, args, flags } = parseArgs(process.argv);
19090
18600
  const noAutoStart = !!flags["no-auto-start"];
@@ -19106,6 +18616,8 @@ async function main() {
19106
18616
  return cmdRestart(flags);
19107
18617
  if (command === "upgrade" || command === "update")
19108
18618
  return cmdUpgrade(flags);
18619
+ if (command === "connect-chrome")
18620
+ return cmdConnectChrome();
19109
18621
  const KNOWN_COMMANDS = new Set([
19110
18622
  "health",
19111
18623
  "setup",
@@ -19139,7 +18651,8 @@ async function main() {
19139
18651
  "eval",
19140
18652
  "back",
19141
18653
  "forward",
19142
- "close"
18654
+ "close",
18655
+ "connect-chrome"
19143
18656
  ]);
19144
18657
  if (!KNOWN_COMMANDS.has(command)) {
19145
18658
  const pack = findSitePack(command);
@@ -19215,6 +18728,8 @@ async function main() {
19215
18728
  return cmdForward();
19216
18729
  case "close":
19217
18730
  return cmdClose();
18731
+ case "connect-chrome":
18732
+ return cmdConnectChrome();
19218
18733
  default:
19219
18734
  info(`Unknown command: ${command}`);
19220
18735
  printHelp();