unbrowse 6.6.0-preview.0 → 6.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -31,7 +31,7 @@ var __promiseAll = (args) => Promise.all(args);
31
31
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
32
32
 
33
33
  // ../../src/build-info.generated.ts
34
- var BUILD_RELEASE_VERSION = "6.6.0-preview.0", BUILD_GIT_SHA = "e74cd1481aa5", BUILD_CODE_HASH = "5d9ebf619c61", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiNi42LjAtcHJldmlldy4wIiwiZ2l0X3NoYSI6ImU3NGNkMTQ4MWFhNSIsImNvZGVfaGFzaCI6IjVkOWViZjYxOWM2MSIsInRyYWNlX3ZlcnNpb24iOiI1ZDllYmY2MTljNjFAZTc0Y2QxNDgxYWE1IiwiaXNzdWVkX2F0IjoiMjAyNi0wNS0wNFQwNTo1MTo1Mi4yNTNaIn0", BUILD_RELEASE_MANIFEST_SIGNATURE = "_hrkIsHrYUpg_q-N3P2xraayyLNVbgdyB8-8cRupt6U", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai", BUILD_DEFAULT_PROFILE = "";
34
+ var BUILD_RELEASE_VERSION = "6.6.0", BUILD_GIT_SHA = "7de260727d86", BUILD_CODE_HASH = "5d9ebf619c61", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiNi42LjAiLCJnaXRfc2hhIjoiN2RlMjYwNzI3ZDg2IiwiY29kZV9oYXNoIjoiNWQ5ZWJmNjE5YzYxIiwidHJhY2VfdmVyc2lvbiI6IjVkOWViZjYxOWM2MUA3ZGUyNjA3MjdkODYiLCJpc3N1ZWRfYXQiOiIyMDI2LTA1LTA0VDA2OjM2OjA0LjY1NVoifQ", BUILD_RELEASE_MANIFEST_SIGNATURE = "ZP-8gEii8oLih47bn-ZtfHZzY88cRU-K5rRbAl3oaSA", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai", BUILD_DEFAULT_PROFILE = "";
35
35
 
36
36
  // ../../src/version.ts
37
37
  import { createHash } from "crypto";
@@ -5050,7 +5050,25 @@ function parseArgs(argv) {
5050
5050
  } else if (a.startsWith("--")) {
5051
5051
  const key = a.slice(2);
5052
5052
  const next = rest[i + 1];
5053
- const valueExpectedFlags = new Set(["skill", "endpoint", "intent", "url", "domain", "params", "path", "extract", "limit"]);
5053
+ const valueExpectedFlags = new Set([
5054
+ "skill",
5055
+ "endpoint",
5056
+ "intent",
5057
+ "url",
5058
+ "domain",
5059
+ "params",
5060
+ "path",
5061
+ "extract",
5062
+ "limit",
5063
+ "session",
5064
+ "ref",
5065
+ "text",
5066
+ "value",
5067
+ "form-selector",
5068
+ "submit-selector",
5069
+ "wait-for",
5070
+ "timeout-ms"
5071
+ ]);
5054
5072
  if (valueExpectedFlags.has(key)) {
5055
5073
  if (next === undefined)
5056
5074
  die(`--${key} requires a value`);
@@ -5379,14 +5397,29 @@ async function cmdResolve(flags) {
5379
5397
  }, endpointNeedsThirdPartyTermsConfirmation = function(endpoint) {
5380
5398
  return endpoint.requires_third_party_terms_confirmation === true;
5381
5399
  }, resolveSkillId = function() {
5382
- return result.skill?.skill_id ?? result.skill_id;
5400
+ return result.skill?.skill_id ?? result.skill_id ?? result.result?.skill_id;
5401
+ }, resolveAvailableEndpoints = function() {
5402
+ return Array.isArray(result.available_endpoints) ? result.available_endpoints : Array.isArray(result.result?.available_endpoints) ? result.result.available_endpoints : undefined;
5403
+ }, endpointIsSafeToAutoExecute = function(endpoint) {
5404
+ const method = String(endpoint.method ?? "GET").toUpperCase();
5405
+ if (method !== "GET" && method !== "HEAD")
5406
+ return false;
5407
+ if (endpoint.needs_params && Object.keys(extraParams).length === 0)
5408
+ return false;
5409
+ return true;
5383
5410
  };
5384
5411
  const body = { intent };
5385
5412
  const url = flags.url;
5386
5413
  const domain = flags.domain;
5387
- const explicitEndpointId = flags["endpoint-id"];
5388
- const autoExecute = !!flags.execute;
5389
- const extraParams = flags.params ? JSON.parse(flags.params) : {};
5414
+ const endpointFlag = flags["endpoint-id"] ?? flags.endpoint;
5415
+ const explicitEndpointId = typeof endpointFlag === "string" ? endpointFlag : undefined;
5416
+ const noExecute = flags["no-execute"] === true;
5417
+ const autoExecute = !noExecute;
5418
+ const cliKv = flags._params;
5419
+ const extraParams = {
5420
+ ...flags.params ? JSON.parse(flags.params) : {},
5421
+ ...cliKv && Object.keys(cliKv).length > 0 ? cliKv : {}
5422
+ };
5390
5423
  if (url) {
5391
5424
  body.params = { url };
5392
5425
  body.context = { url };
@@ -5429,20 +5462,27 @@ async function cmdResolve(flags) {
5429
5462
  if (loginUrl)
5430
5463
  info(`Authentication required. Run: unbrowse login --url "${loginUrl}"`);
5431
5464
  }
5432
- if (explicitEndpointId && result.available_endpoints) {
5465
+ if (explicitEndpointId && resolveAvailableEndpoints()) {
5433
5466
  const skillId = resolveSkillId();
5434
5467
  if (skillId) {
5435
5468
  result = await withPendingNotice(api2("POST", `/v1/skills/${skillId}/execute`, execBody(explicitEndpointId)), "Executing selected endpoint...");
5436
5469
  }
5437
5470
  }
5438
- if (autoExecute && result.available_endpoints && !result.result) {
5439
- const endpoints = result.available_endpoints;
5471
+ const endpointsForAutoExecute = resolveAvailableEndpoints();
5472
+ if (autoExecute && endpointsForAutoExecute) {
5473
+ const endpoints = endpointsForAutoExecute;
5440
5474
  const skillId = resolveSkillId();
5441
5475
  if (skillId && endpoints.length > 0) {
5442
5476
  const bestEndpoint = endpoints[0];
5443
5477
  if (endpointNeedsThirdPartyTermsConfirmation(bestEndpoint) && !flags["confirm-third-party-terms"]) {
5444
5478
  process.stderr.write(`Skipping auto-execute: endpoint ${bestEndpoint.endpoint_id ?? "?"} ` + `requires explicit third-party terms confirmation. ` + `Re-run with --confirm-third-party-terms to proceed.
5445
5479
  `);
5480
+ } else if (!endpointIsSafeToAutoExecute(bestEndpoint)) {
5481
+ result.next_action = {
5482
+ title: "Execute selected endpoint",
5483
+ command: `unbrowse execute --skill ${skillId} --endpoint ${bestEndpoint.endpoint_id}`,
5484
+ why: "Resolve found a candidate but did not auto-execute because the endpoint is not a safe ready GET."
5485
+ };
5446
5486
  } else {
5447
5487
  info(`Auto-executing endpoint: ${bestEndpoint.description ?? bestEndpoint.endpoint_id}`);
5448
5488
  result = await withPendingNotice(api2("POST", `/v1/skills/${skillId}/execute`, execBody(bestEndpoint.endpoint_id)), "Executing best endpoint...");
@@ -6289,7 +6329,7 @@ var CLI_REFERENCE = {
6289
6329
  { name: "mcp", usage: "[--no-auto-start]", desc: "Run the stdio MCP server" },
6290
6330
  { name: "setup", usage: "[--opencode auto|global|project|off] [--no-start]", desc: "Bootstrap browser deps + Open Code command" },
6291
6331
  { name: "upgrade", usage: "", desc: "Check latest release and print the right upgrade command" },
6292
- { name: "resolve", usage: '--intent "..." [--domain "..."] [--url "..."] [opts]', desc: "Returns ranked shortlist of endpoints for an intent. Pick one and call execute. (Two tool calls is the contract \u2014 autoexec is opt-in via --execute, not the default.)" },
6332
+ { name: "resolve", usage: '--intent "..." [--domain "..."] [--url "..."] [opts]', desc: "Resolve an intent, auto-executing the top safe GET endpoint by default; pass --no-execute for metadata only" },
6293
6333
  { name: "explain", usage: '--intent "..." --url "..." [--top N]', desc: "Emit top-N candidate endpoints + evidence for an LLM judge to pick from (no heuristic verdict \u2014 primitives + agent judgment)" },
6294
6334
  { name: "execute", usage: "--skill ID --endpoint ID [-p key=val ...] [--params '{json}'] [opts]", desc: "Execute a specific endpoint. Pass replay params via repeated -p key=val flags or --params with a JSON object" },
6295
6335
  { name: "feedback", usage: "--skill ID --endpoint ID --rating N", desc: "Submit feedback (mandatory after resolve)" },
@@ -6304,6 +6344,7 @@ var CLI_REFERENCE = {
6304
6344
  { name: "skill", usage: "<id>", desc: "Get skill details" },
6305
6345
  { name: "cleanup-stale", usage: "[--skill ID] [--domain host] [--limit N]", desc: "Verify skills and evict stale cached endpoints" },
6306
6346
  { name: "sessions", usage: '--domain "..." [--limit N]', desc: "Debug session logs" },
6347
+ { name: "inspect", usage: "[--session id] [--all]", desc: "Inspect live browser capture evidence, candidate endpoints, and next actions" },
6307
6348
  { name: "go", usage: "<url> [--session id]", desc: "Open a fresh Kuri browser tab, or reuse explicit --session" },
6308
6349
  { name: "submit", usage: "[--session id] [--form-selector sel] [--submit-selector sel] [--wait-for hint] [--assist-site-state]", desc: "Submit current form. Thin browser-native proxy by default; site-state assist and same-origin rehydrate are explicit opt-ins" },
6309
6350
  { name: "snap", usage: "[--session id] [--filter interactive]", desc: "A11y snapshot with @eN refs" },
@@ -6342,7 +6383,8 @@ var CLI_REFERENCE = {
6342
6383
  { flag: "--opencode auto|global|project|off", desc: "setup: install /unbrowse command for Open Code" }
6343
6384
  ],
6344
6385
  resolveExecuteFlags: [
6345
- { flag: "--execute", desc: "Auto-execute the top trusted endpoint from resolve" },
6386
+ { flag: "--no-execute", desc: "Resolve only; do not auto-execute safe GET endpoints" },
6387
+ { flag: "--execute", desc: "Deprecated no-op alias; safe GET execution is now the default" },
6346
6388
  { flag: "--schema", desc: "Show response schema + extraction hints only (no data)" },
6347
6389
  { flag: '--path "data.items[]"', desc: "Drill into result before extract/output" },
6348
6390
  { flag: '--extract "field1,alias:deep.path.to.val"', desc: "Pick specific fields (no piping needed)" },
@@ -6611,6 +6653,7 @@ async function cmdStatus(flags) {
6611
6653
  const versionInfo = checkServerVersion(BASE_URL, import.meta.url);
6612
6654
  let activeSessions = [];
6613
6655
  let sessionCount = 0;
6656
+ let latestSessionId = null;
6614
6657
  if (healthy) {
6615
6658
  try {
6616
6659
  const res = await fetch(`${BASE_URL}/v1/browse/sessions`, { signal: AbortSignal.timeout(2000) });
@@ -6618,6 +6661,7 @@ async function cmdStatus(flags) {
6618
6661
  const data = await res.json();
6619
6662
  activeSessions = data.sessions ?? [];
6620
6663
  sessionCount = data.count ?? activeSessions.length;
6664
+ latestSessionId = data.latest_session_id ?? null;
6621
6665
  }
6622
6666
  } catch {}
6623
6667
  }
@@ -6626,10 +6670,34 @@ async function cmdStatus(flags) {
6626
6670
  url: BASE_URL,
6627
6671
  ...versionInfo ?? {},
6628
6672
  active_browse_sessions: sessionCount,
6673
+ latest_session_id: latestSessionId,
6629
6674
  sessions: activeSessions,
6630
6675
  chrome_debug_url: process.env.CHROME_DEBUG_URL ?? null
6631
6676
  }, !!flags.pretty);
6632
6677
  }
6678
+ async function cmdInspect(args, flags) {
6679
+ const explicitSession = typeof flags.session === "string" ? flags.session : typeof args[0] === "string" ? args[0] : undefined;
6680
+ const sessions = await api2("GET", "/v1/browse/sessions");
6681
+ if (flags.all) {
6682
+ output(sessions, !!flags.pretty);
6683
+ return;
6684
+ }
6685
+ const latestSessionId = sessions.latest_session_id ?? sessions.sessions?.at(-1)?.session_id;
6686
+ const sessionId = explicitSession ?? latestSessionId;
6687
+ if (!sessionId) {
6688
+ output({
6689
+ error: "no_active_session",
6690
+ message: "No active browse session to inspect.",
6691
+ next_action: {
6692
+ title: "Open a browser session",
6693
+ command: 'unbrowse go "https://example.com" --pretty',
6694
+ why: "Inspection reads live HAR/interceptor evidence from an active Kuri session."
6695
+ }
6696
+ }, !!flags.pretty);
6697
+ return;
6698
+ }
6699
+ output(await api2("GET", `/v1/browse/sessions/${encodeURIComponent(sessionId)}/buffer`), !!flags.pretty);
6700
+ }
6633
6701
  async function cmdRestart(flags) {
6634
6702
  info("Restarting server...");
6635
6703
  await restartServer(BASE_URL, import.meta.url);
@@ -6843,7 +6911,7 @@ async function cmdSnap(flags) {
6843
6911
  }
6844
6912
  }
6845
6913
  async function cmdClick(args, flags) {
6846
- const ref = args[0];
6914
+ const ref = args[0] ?? (typeof flags.ref === "string" ? flags.ref : undefined);
6847
6915
  if (!ref)
6848
6916
  die("Usage: unbrowse click <ref>");
6849
6917
  output(await api2("POST", "/v1/browse/click", {
@@ -6852,10 +6920,10 @@ async function cmdClick(args, flags) {
6852
6920
  }), false);
6853
6921
  }
6854
6922
  async function cmdFill(args, flags) {
6855
- const ref = args[0];
6856
- const value = args.slice(1).join(" ");
6923
+ const ref = args[0] ?? (typeof flags.ref === "string" ? flags.ref : undefined);
6924
+ const value = args.length > 1 ? args.slice(1).join(" ") : typeof flags.text === "string" ? flags.text : typeof flags.value === "string" ? flags.value : "";
6857
6925
  if (!ref || !value)
6858
- die("Usage: unbrowse fill <ref> <value>");
6926
+ die('Usage: unbrowse fill <ref> <value> (also: unbrowse fill --ref e5 --text "hello")');
6859
6927
  output(await api2("POST", "/v1/browse/fill", {
6860
6928
  ref,
6861
6929
  value,
@@ -7290,7 +7358,17 @@ async function cmdConnectChrome() {
7290
7358
  console.error("Could not connect to Chrome. Make sure all Chrome windows are closed and try again.");
7291
7359
  }
7292
7360
  async function main() {
7293
- const { command, args, flags, params: cliParams } = parseArgs(process.argv);
7361
+ const parsed = parseArgs(process.argv);
7362
+ let { command, args, flags } = parsed;
7363
+ const cliParams = parsed.params;
7364
+ if (command === "browse") {
7365
+ const subcommand = args.shift();
7366
+ if (!subcommand || subcommand === "help") {
7367
+ printHelp();
7368
+ process.exit(subcommand === "help" ? 0 : 1);
7369
+ }
7370
+ command = subcommand;
7371
+ }
7294
7372
  if (Object.keys(cliParams).length > 0) {
7295
7373
  flags._params = cliParams;
7296
7374
  }
@@ -7359,6 +7437,7 @@ async function main() {
7359
7437
  "search",
7360
7438
  "sessions",
7361
7439
  "status",
7440
+ "inspect",
7362
7441
  "stop",
7363
7442
  "restart",
7364
7443
  "upgrade",
@@ -7455,6 +7534,8 @@ async function main() {
7455
7534
  return cmdSearch(flags);
7456
7535
  case "sessions":
7457
7536
  return cmdSessions(flags);
7537
+ case "inspect":
7538
+ return cmdInspect(args, flags);
7458
7539
  case "go":
7459
7540
  return cmdGo(args, flags);
7460
7541
  case "submit":
package/dist/mcp.js CHANGED
@@ -226,11 +226,11 @@ import { dirname, join, parse } from "path";
226
226
  import { fileURLToPath as fileURLToPath2 } from "url";
227
227
 
228
228
  // ../../src/build-info.generated.ts
229
- var BUILD_RELEASE_VERSION = "6.6.0-preview.0";
230
- var BUILD_GIT_SHA = "e74cd1481aa5";
229
+ var BUILD_RELEASE_VERSION = "6.6.0";
230
+ var BUILD_GIT_SHA = "7de260727d86";
231
231
  var BUILD_CODE_HASH = "5d9ebf619c61";
232
- var BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiNi42LjAtcHJldmlldy4wIiwiZ2l0X3NoYSI6ImU3NGNkMTQ4MWFhNSIsImNvZGVfaGFzaCI6IjVkOWViZjYxOWM2MSIsInRyYWNlX3ZlcnNpb24iOiI1ZDllYmY2MTljNjFAZTc0Y2QxNDgxYWE1IiwiaXNzdWVkX2F0IjoiMjAyNi0wNS0wNFQwNTo1MTo1Mi4yNTNaIn0";
233
- var BUILD_RELEASE_MANIFEST_SIGNATURE = "_hrkIsHrYUpg_q-N3P2xraayyLNVbgdyB8-8cRupt6U";
232
+ var BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiNi42LjAiLCJnaXRfc2hhIjoiN2RlMjYwNzI3ZDg2IiwiY29kZV9oYXNoIjoiNWQ5ZWJmNjE5YzYxIiwidHJhY2VfdmVyc2lvbiI6IjVkOWViZjYxOWM2MUA3ZGUyNjA3MjdkODYiLCJpc3N1ZWRfYXQiOiIyMDI2LTA1LTA0VDA2OjM2OjA0LjY1NVoifQ";
233
+ var BUILD_RELEASE_MANIFEST_SIGNATURE = "ZP-8gEii8oLih47bn-ZtfHZzY88cRU-K5rRbAl3oaSA";
234
234
  var BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai";
235
235
  var BUILD_DEFAULT_PROFILE = "";
236
236
 
package/dist/server.js CHANGED
@@ -7342,7 +7342,7 @@ var init_capture = __esm(async () => {
7342
7342
  });
7343
7343
 
7344
7344
  // ../../src/build-info.generated.ts
7345
- var BUILD_RELEASE_VERSION = "6.6.0-preview.0", BUILD_GIT_SHA = "e74cd1481aa5", BUILD_CODE_HASH = "5d9ebf619c61", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiNi42LjAtcHJldmlldy4wIiwiZ2l0X3NoYSI6ImU3NGNkMTQ4MWFhNSIsImNvZGVfaGFzaCI6IjVkOWViZjYxOWM2MSIsInRyYWNlX3ZlcnNpb24iOiI1ZDllYmY2MTljNjFAZTc0Y2QxNDgxYWE1IiwiaXNzdWVkX2F0IjoiMjAyNi0wNS0wNFQwNTo1MTo1Mi4yNTNaIn0", BUILD_RELEASE_MANIFEST_SIGNATURE = "_hrkIsHrYUpg_q-N3P2xraayyLNVbgdyB8-8cRupt6U", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai", BUILD_DEFAULT_PROFILE = "";
7345
+ var BUILD_RELEASE_VERSION = "6.6.0", BUILD_GIT_SHA = "7de260727d86", BUILD_CODE_HASH = "5d9ebf619c61", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiNi42LjAiLCJnaXRfc2hhIjoiN2RlMjYwNzI3ZDg2IiwiY29kZV9oYXNoIjoiNWQ5ZWJmNjE5YzYxIiwidHJhY2VfdmVyc2lvbiI6IjVkOWViZjYxOWM2MUA3ZGUyNjA3MjdkODYiLCJpc3N1ZWRfYXQiOiIyMDI2LTA1LTA0VDA2OjM2OjA0LjY1NVoifQ", BUILD_RELEASE_MANIFEST_SIGNATURE = "ZP-8gEii8oLih47bn-ZtfHZzY88cRU-K5rRbAl3oaSA", BUILD_DEFAULT_BACKEND_URL = "https://beta-api.unbrowse.ai", BUILD_DEFAULT_PROFILE = "";
7346
7346
 
7347
7347
  // ../../src/version.ts
7348
7348
  import { createHash as createHash2 } from "crypto";
@@ -25769,9 +25769,7 @@ async function resolveRequestedBrowseSession(sessions, client, requestedSessionI
25769
25769
  const live = await listLiveBrowseSessions(sessions, client);
25770
25770
  if (live.length === 0)
25771
25771
  throw new BrowseSessionError("no_active_session");
25772
- if (live.length > 1)
25773
- throw new BrowseSessionError("session_id_required");
25774
- return live[0];
25772
+ return live[live.length - 1];
25775
25773
  }
25776
25774
  async function withSessionQueue(sessionId, fn) {
25777
25775
  const prev = sessionQueues.get(sessionId) ?? Promise.resolve();
@@ -27921,6 +27919,17 @@ function passiveIndexFromRequests(requests, pageUrl, options = {}) {
27921
27919
  function passiveIndexHar(entries, pageUrl, options = {}) {
27922
27920
  passiveIndexFromRequests(harEntriesToRawRequests(entries), pageUrl, options);
27923
27921
  }
27922
+ function rememberInspectedHarEntries(sessionId, entries) {
27923
+ if (entries.length === 0)
27924
+ return;
27925
+ const existing = inspectedHarEntries.get(sessionId) ?? [];
27926
+ inspectedHarEntries.set(sessionId, [...existing, ...entries]);
27927
+ }
27928
+ function drainInspectedHarEntries(sessionId) {
27929
+ const entries = inspectedHarEntries.get(sessionId) ?? [];
27930
+ inspectedHarEntries.delete(sessionId);
27931
+ return entries;
27932
+ }
27924
27933
  function browseBrokerPorts() {
27925
27934
  return Array.from({ length: BROWSE_BROKER_MAX }, (_, index) => BROWSE_BROKER_BASE_PORT + index);
27926
27935
  }
@@ -28161,9 +28170,23 @@ async function registerRoutes(app) {
28161
28170
  domain: s.domain,
28162
28171
  har_active: s.harActive,
28163
28172
  broker_port: s.brokerPort ?? null,
28164
- streaming_publish_active: streamingWatchers.has(s.sessionId)
28173
+ streaming_publish_active: streamingWatchers.has(s.sessionId),
28174
+ marketplace_publish: (() => {
28175
+ const decision = decideCheckpointPublish(s.domain || profileName(s.url));
28176
+ return {
28177
+ enabled: decision.publishQueued,
28178
+ mode: decision.mode,
28179
+ reason: decision.reason
28180
+ };
28181
+ })(),
28182
+ suggested_commands: [
28183
+ `unbrowse inspect --session ${s.sessionId} --pretty`,
28184
+ `unbrowse resolve --intent "<task>" --url "${s.url}" --pretty`,
28185
+ `unbrowse close --session ${s.sessionId}`
28186
+ ]
28165
28187
  }));
28166
- return reply.send({ sessions, count: sessions.length });
28188
+ const latest = sessions[sessions.length - 1] ?? null;
28189
+ return reply.send({ sessions, count: sessions.length, latest_session_id: latest?.session_id ?? null });
28167
28190
  });
28168
28191
  app.get("/v1/browse/sessions/:id/buffer", async (req, reply) => {
28169
28192
  const { id } = req.params;
@@ -28190,6 +28213,7 @@ async function registerRoutes(app) {
28190
28213
  const broker = brokerForSession(session);
28191
28214
  const stopResult = await broker.harStop(session.tabId);
28192
28215
  harEntries = stopResult.entries ?? [];
28216
+ rememberInspectedHarEntries(session.sessionId, harEntries);
28193
28217
  try {
28194
28218
  await broker.harStart(session.tabId);
28195
28219
  } catch {}
@@ -28197,6 +28221,23 @@ async function registerRoutes(app) {
28197
28221
  harError = err instanceof Error ? err.message : String(err);
28198
28222
  }
28199
28223
  }
28224
+ const rawRequests = [
28225
+ ...Array.isArray(intercepted) ? intercepted : [],
28226
+ ...harEntriesToRawRequests(harEntries, session.url)
28227
+ ];
28228
+ const candidateEndpoints = extractEndpoints(rawRequests, undefined, {
28229
+ pageUrl: session.url,
28230
+ finalUrl: session.url
28231
+ }).slice(0, 10).map((endpoint) => ({
28232
+ endpoint_id: endpoint.endpoint_id,
28233
+ method: endpoint.method,
28234
+ url_template: endpoint.url_template,
28235
+ description: endpoint.description ?? generateLocalDescription(endpoint),
28236
+ reliability_score: endpoint.reliability_score,
28237
+ idempotency: endpoint.idempotency,
28238
+ auth_token_count: endpoint.auth_tokens?.length ?? 0
28239
+ }));
28240
+ const publishDecision = decideCheckpointPublish(session.domain || profileName(session.url));
28200
28241
  return reply.send({
28201
28242
  session: {
28202
28243
  session_id: session.sessionId,
@@ -28204,7 +28245,12 @@ async function registerRoutes(app) {
28204
28245
  url: session.url,
28205
28246
  domain: session.domain,
28206
28247
  har_active: session.harActive,
28207
- streaming_publish_active: streamingWatchers.has(session.sessionId)
28248
+ streaming_publish_active: streamingWatchers.has(session.sessionId),
28249
+ marketplace_publish: {
28250
+ enabled: publishDecision.publishQueued,
28251
+ mode: publishDecision.mode,
28252
+ reason: publishDecision.reason
28253
+ }
28208
28254
  },
28209
28255
  intercepted_requests: intercepted,
28210
28256
  intercepted_count: Array.isArray(intercepted) ? intercepted.length : 0,
@@ -28218,7 +28264,26 @@ async function registerRoutes(app) {
28218
28264
  })),
28219
28265
  har_count: harEntries.length,
28220
28266
  har_error: harError,
28221
- total_captured: (Array.isArray(intercepted) ? intercepted.length : 0) + harEntries.length
28267
+ total_captured: (Array.isArray(intercepted) ? intercepted.length : 0) + harEntries.length,
28268
+ candidate_endpoints: candidateEndpoints,
28269
+ candidate_endpoint_count: candidateEndpoints.length,
28270
+ next_actions: [
28271
+ {
28272
+ title: "Resolve from captured evidence",
28273
+ command: `unbrowse resolve --intent "<task>" --url "${session.url}" --pretty`,
28274
+ why: "Runs the normal resolver after the current in-flight capture has been exposed."
28275
+ },
28276
+ {
28277
+ title: "Checkpoint and publish review material",
28278
+ command: `unbrowse sync --session ${session.sessionId} --pretty`,
28279
+ why: "Keeps the tab open and compiles current browser traffic into local skill evidence."
28280
+ },
28281
+ {
28282
+ title: "Close after judging",
28283
+ command: `unbrowse close --session ${session.sessionId}`,
28284
+ why: "Flushes final capture, saves auth, and stops the live tab."
28285
+ }
28286
+ ]
28222
28287
  });
28223
28288
  });
28224
28289
  app.get("/v1/trace/:trace_id", async (req, reply) => {
@@ -28897,7 +28962,20 @@ async function registerRoutes(app) {
28897
28962
  }
28898
28963
  function sendBrowseSessionError(reply, error) {
28899
28964
  if (error instanceof BrowseSessionError) {
28900
- return reply.code(error.statusCode).send({ error: error.code });
28965
+ const latestSession = Array.from(browseSessions.values()).at(-1);
28966
+ return reply.code(error.statusCode).send({
28967
+ error: error.code,
28968
+ ...latestSession ? { latest_session_id: latestSession.sessionId } : {},
28969
+ next_action: latestSession ? {
28970
+ title: "Retry with latest active session",
28971
+ command: `unbrowse inspect --session ${latestSession.sessionId} --pretty`,
28972
+ why: "A live session exists; inspect it or pass --session to the browser command."
28973
+ } : {
28974
+ title: "Open a browser session",
28975
+ command: 'unbrowse go "<url>" --pretty',
28976
+ why: "This command needs a live Kuri-backed browse session."
28977
+ }
28978
+ });
28901
28979
  }
28902
28980
  if (isRecoverableBrowseFailure(error)) {
28903
28981
  return reply.code(502).send({
@@ -28953,6 +29031,7 @@ async function registerRoutes(app) {
28953
29031
  } catch {}
28954
29032
  } catch {}
28955
29033
  }
29034
+ harEntries = [...drainInspectedHarEntries(session.sessionId), ...harEntries];
28956
29035
  const allRequests = await enrichPassiveCaptureRequests({
28957
29036
  tabId: session.tabId,
28958
29037
  captureUrl: session.url,
@@ -29075,6 +29154,7 @@ async function registerRoutes(app) {
29075
29154
  streamingWatchers.delete(sessionId);
29076
29155
  }
29077
29156
  streamingState.delete(sessionId);
29157
+ inspectedHarEntries.delete(sessionId);
29078
29158
  }
29079
29159
  async function flushBrowseCapture(session, options = {}) {
29080
29160
  let harEntries = [];
@@ -29084,6 +29164,7 @@ async function registerRoutes(app) {
29084
29164
  harEntries = entries;
29085
29165
  } catch {}
29086
29166
  }
29167
+ harEntries = [...drainInspectedHarEntries(session.sessionId), ...harEntries];
29087
29168
  session.harActive = false;
29088
29169
  const allRequests = await enrichPassiveCaptureRequests({
29089
29170
  tabId: session.tabId,
@@ -29300,6 +29381,7 @@ async function registerRoutes(app) {
29300
29381
  streaming_publish_active: streamingWatchers.has(session.sessionId),
29301
29382
  attached_existing_chrome: result.attachedExistingChrome ?? false,
29302
29383
  chrome_debug_url: process.env.CHROME_DEBUG_URL ?? null,
29384
+ inspect_command: `unbrowse inspect --session ${session.sessionId} --pretty`,
29303
29385
  inspect_buffer: `GET ${process.env.UNBROWSE_API_BASE ?? "http://127.0.0.1:6969"}/v1/browse/sessions/${session.sessionId}/buffer`,
29304
29386
  marketplace_publish_enabled: publishDecision.publishQueued,
29305
29387
  marketplace_publish_mode: publishDecision.mode,
@@ -29605,7 +29687,7 @@ function saveTrace(trace) {
29605
29687
  const t = trace;
29606
29688
  writeFileSync13(join19(TRACES_DIR, `${t.trace_id}.json`), JSON.stringify(trace, null, 2));
29607
29689
  }
29608
- var BETA_API_URL, TRACES_DIR, BROWSE_BROKER_MAX, BROWSE_BROKER_BASE_PORT, browseSessions, statsCache = null, STATS_CACHE_TTL;
29690
+ var BETA_API_URL, TRACES_DIR, BROWSE_BROKER_MAX, BROWSE_BROKER_BASE_PORT, browseSessions, inspectedHarEntries, statsCache = null, STATS_CACHE_TTL;
29609
29691
  var init_routes = __esm(async () => {
29610
29692
  init_client();
29611
29693
  init_reverse_engineer();
@@ -29644,6 +29726,7 @@ var init_routes = __esm(async () => {
29644
29726
  BROWSE_BROKER_MAX = Math.max(1, Number(process.env.KURI_MULTI_BROKER_MAX ?? "1"));
29645
29727
  BROWSE_BROKER_BASE_PORT = Number(process.env.KURI_PORT ?? "7700");
29646
29728
  browseSessions = new Map;
29729
+ inspectedHarEntries = new Map;
29647
29730
  STATS_CACHE_TTL = 5 * 60 * 1000;
29648
29731
  });
29649
29732
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unbrowse",
3
- "version": "6.6.0-preview.0",
3
+ "version": "6.6.0",
4
4
  "description": "Reverse-engineer any website into reusable API skills. Zero-dep single binary with embedded browser engine.",
5
5
  "type": "module",
6
6
  "bin": {