unbrowse 3.1.0-experiments.995f8bb → 3.1.0-experiments.9cbcb13

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.
Files changed (105) hide show
  1. package/dist/cli.js +79 -33
  2. package/dist/index.js +2 -6
  3. package/dist/mcp.js +73 -22
  4. package/dist/server.js +25994 -0
  5. package/package.json +1 -2
  6. package/vendor/kuri/manifest.json +1 -1
  7. package/runtime-src/agent-outcome.ts +0 -166
  8. package/runtime-src/analytics-session.ts +0 -55
  9. package/runtime-src/api/browse-index.ts +0 -343
  10. package/runtime-src/api/browse-session.ts +0 -572
  11. package/runtime-src/api/browse-submit-prereqs.ts +0 -48
  12. package/runtime-src/api/browse-submit.ts +0 -1184
  13. package/runtime-src/api/routes.ts +0 -1833
  14. package/runtime-src/auth/browser-cookies.ts +0 -423
  15. package/runtime-src/auth/index.ts +0 -535
  16. package/runtime-src/auth/runtime.ts +0 -116
  17. package/runtime-src/browser/index.ts +0 -659
  18. package/runtime-src/browser/types.ts +0 -41
  19. package/runtime-src/build-info.generated.ts +0 -6
  20. package/runtime-src/capture/index.ts +0 -1929
  21. package/runtime-src/capture/prefetch.ts +0 -95
  22. package/runtime-src/capture/rsc.ts +0 -45
  23. package/runtime-src/cli/shortcuts.ts +0 -273
  24. package/runtime-src/cli.ts +0 -1734
  25. package/runtime-src/client/graph-client.ts +0 -100
  26. package/runtime-src/client/index.ts +0 -1425
  27. package/runtime-src/debug-trace.ts +0 -18
  28. package/runtime-src/domain.ts +0 -38
  29. package/runtime-src/execution/index.ts +0 -3407
  30. package/runtime-src/execution/retry.ts +0 -46
  31. package/runtime-src/execution/robots.ts +0 -167
  32. package/runtime-src/execution/search-forms.ts +0 -188
  33. package/runtime-src/execution/token-resolver.ts +0 -135
  34. package/runtime-src/extraction/index.ts +0 -1507
  35. package/runtime-src/foundry/publish-bundle.ts +0 -392
  36. package/runtime-src/graph/agent-augment.ts +0 -315
  37. package/runtime-src/graph/index.ts +0 -1532
  38. package/runtime-src/graph/local-fixtures.ts +0 -393
  39. package/runtime-src/graph/local-harness.ts +0 -646
  40. package/runtime-src/graph/planner.ts +0 -411
  41. package/runtime-src/graph/session.ts +0 -294
  42. package/runtime-src/graph/trace-store.ts +0 -136
  43. package/runtime-src/impact-log.ts +0 -227
  44. package/runtime-src/index.ts +0 -24
  45. package/runtime-src/indexer/index.ts +0 -465
  46. package/runtime-src/intent-match.ts +0 -1515
  47. package/runtime-src/kuri/client.ts +0 -1839
  48. package/runtime-src/logger.ts +0 -30
  49. package/runtime-src/marketplace/index.ts +0 -111
  50. package/runtime-src/mcp.ts +0 -1911
  51. package/runtime-src/orchestrator/browser-agent.ts +0 -374
  52. package/runtime-src/orchestrator/dag-advisor.ts +0 -59
  53. package/runtime-src/orchestrator/dag-feedback.ts +0 -257
  54. package/runtime-src/orchestrator/first-pass-action.ts +0 -403
  55. package/runtime-src/orchestrator/index.ts +0 -4480
  56. package/runtime-src/orchestrator/passive-publish.ts +0 -187
  57. package/runtime-src/orchestrator/timing-economics.ts +0 -80
  58. package/runtime-src/payments/cascade.ts +0 -137
  59. package/runtime-src/payments/index.ts +0 -270
  60. package/runtime-src/payments/lobster-pay.ts +0 -182
  61. package/runtime-src/payments/wallet.ts +0 -98
  62. package/runtime-src/publish/review-context.ts +0 -93
  63. package/runtime-src/publish/sanitize.ts +0 -197
  64. package/runtime-src/publish/schema-review.ts +0 -192
  65. package/runtime-src/publish-admission.ts +0 -388
  66. package/runtime-src/ratelimit/index.ts +0 -23
  67. package/runtime-src/reverse-engineer/bundle-scanner.ts +0 -127
  68. package/runtime-src/reverse-engineer/description-prompt.ts +0 -213
  69. package/runtime-src/reverse-engineer/index.ts +0 -1551
  70. package/runtime-src/reverse-engineer/token-sources.ts +0 -379
  71. package/runtime-src/router.ts +0 -17
  72. package/runtime-src/routing-telemetry.ts +0 -395
  73. package/runtime-src/runtime/browser-access.ts +0 -11
  74. package/runtime-src/runtime/browser-auth.ts +0 -12
  75. package/runtime-src/runtime/browser-host.ts +0 -48
  76. package/runtime-src/runtime/lifecycle.ts +0 -17
  77. package/runtime-src/runtime/local-server.ts +0 -311
  78. package/runtime-src/runtime/paths.ts +0 -99
  79. package/runtime-src/runtime/setup.ts +0 -251
  80. package/runtime-src/runtime/supervisor.ts +0 -69
  81. package/runtime-src/runtime/update-hints.ts +0 -351
  82. package/runtime-src/server.ts +0 -100
  83. package/runtime-src/session-logs.ts +0 -142
  84. package/runtime-src/settings.ts +0 -221
  85. package/runtime-src/single-binary.ts +0 -143
  86. package/runtime-src/site-policy.ts +0 -54
  87. package/runtime-src/stale-cleanup-runner.ts +0 -144
  88. package/runtime-src/stale-cleanup.ts +0 -133
  89. package/runtime-src/telemetry-attribution.ts +0 -120
  90. package/runtime-src/telemetry.ts +0 -253
  91. package/runtime-src/template-params.ts +0 -141
  92. package/runtime-src/transform/drift.ts +0 -60
  93. package/runtime-src/transform/index.ts +0 -277
  94. package/runtime-src/types/index.ts +0 -1
  95. package/runtime-src/types/skill.ts +0 -931
  96. package/runtime-src/vault/index.ts +0 -196
  97. package/runtime-src/verification/auth-gate.ts +0 -8
  98. package/runtime-src/verification/candidates.ts +0 -27
  99. package/runtime-src/verification/index.ts +0 -120
  100. package/runtime-src/verification/matrix.ts +0 -30
  101. package/runtime-src/version.ts +0 -148
  102. package/runtime-src/workflow/artifact.ts +0 -161
  103. package/runtime-src/workflow/compile.ts +0 -808
  104. package/runtime-src/workflow/publish.ts +0 -225
  105. package/runtime-src/workflow/runtime.ts +0 -213
package/dist/cli.js CHANGED
@@ -31,7 +31,7 @@ var __promiseAll = (args) => Promise.all(args);
31
31
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
32
32
 
33
33
  // ../../src/build-info.generated.ts
34
- var BUILD_RELEASE_VERSION = "3.1.0-experiments.995f8bb", BUILD_GIT_SHA = "995f8bbf54ac", BUILD_CODE_HASH = "1488fc1d92b7", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiMy4xLjAtZXhwZXJpbWVudHMuOTk1ZjhiYiIsImdpdF9zaGEiOiI5OTVmOGJiZjU0YWMiLCJjb2RlX2hhc2giOiIxNDg4ZmMxZDkyYjciLCJ0cmFjZV92ZXJzaW9uIjoiMTQ4OGZjMWQ5MmI3QDk5NWY4YmJmNTRhYyIsImlzc3VlZF9hdCI6IjIwMjYtMDQtMDVUMjM6NDc6MDguMzk3WiJ9", BUILD_RELEASE_MANIFEST_SIGNATURE = "fsY0FlwZTGKoZwvzv3jow-t3ri874GEziuzW8i3aupw", BUILD_DEFAULT_BACKEND_URL = "https://unbrowse-backend-experiments.lewis-6d8.workers.dev";
34
+ var BUILD_RELEASE_VERSION = "3.1.0-experiments.9cbcb13", BUILD_GIT_SHA = "9cbcb1312b26", BUILD_CODE_HASH = "1488fc1d92b7", BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiMy4xLjAtZXhwZXJpbWVudHMuOWNiY2IxMyIsImdpdF9zaGEiOiI5Y2JjYjEzMTJiMjYiLCJjb2RlX2hhc2giOiIxNDg4ZmMxZDkyYjciLCJ0cmFjZV92ZXJzaW9uIjoiMTQ4OGZjMWQ5MmI3QDljYmNiMTMxMmIyNiIsImlzc3VlZF9hdCI6IjIwMjYtMDQtMDZUMDY6MjM6MDQuMzU5WiJ9", BUILD_RELEASE_MANIFEST_SIGNATURE = "CVnzRBAw3oxxU_-oG-sOC9vJWGI0-k2PtTw4Q5SYueY", BUILD_DEFAULT_BACKEND_URL = "https://unbrowse-backend-experiments.lewis-6d8.workers.dev";
35
35
 
36
36
  // ../../src/version.ts
37
37
  import { createHash } from "crypto";
@@ -586,7 +586,7 @@ var init_logger = __esm(() => {
586
586
 
587
587
  // ../../src/kuri/client.ts
588
588
  import { execFileSync as execFileSync2, spawn as spawn2 } from "node:child_process";
589
- import { existsSync as existsSync8 } from "node:fs";
589
+ import { existsSync as existsSync9 } from "node:fs";
590
590
  import path5 from "node:path";
591
591
  function createBrokerState(port = KURI_DEFAULT_PORT) {
592
592
  return {
@@ -661,7 +661,7 @@ function findKuriBinary() {
661
661
  if (process.env.KURI_BIN)
662
662
  return process.env.KURI_BIN;
663
663
  const candidates = getKuriBinaryCandidates();
664
- return candidates.find((candidate) => existsSync8(candidate)) ?? candidates[0] ?? kuriBinaryName();
664
+ return candidates.find((candidate) => existsSync9(candidate)) ?? candidates[0] ?? kuriBinaryName();
665
665
  }
666
666
  var KURI_DEFAULT_PORT = 7700, defaultBrokerState, brokerClients;
667
667
  var init_client2 = __esm(() => {
@@ -796,6 +796,14 @@ var init_bundle_scanner = __esm(() => {
796
796
  init_logger();
797
797
  });
798
798
 
799
+ // ../../src/reverse-engineer/token-sources.ts
800
+ var init_token_sources = () => {};
801
+
802
+ // ../../src/execution/token-resolver.ts
803
+ var init_token_resolver = __esm(() => {
804
+ init_token_sources();
805
+ });
806
+
799
807
  // ../../src/vault/index.ts
800
808
  import { join as join7 } from "path";
801
809
  import { homedir as homedir5 } from "os";
@@ -1014,6 +1022,7 @@ var init_execution = __esm(async () => {
1014
1022
  init_capture();
1015
1023
  init_reverse_engineer();
1016
1024
  init_bundle_scanner();
1025
+ init_token_resolver();
1017
1026
  init_marketplace();
1018
1027
  init_runtime();
1019
1028
  init_transform();
@@ -1152,7 +1161,7 @@ var init_routing_telemetry = __esm(() => {
1152
1161
  });
1153
1162
  // ../../src/orchestrator/index.ts
1154
1163
  import { nanoid as nanoid9 } from "nanoid";
1155
- import { existsSync as existsSync9, writeFileSync as writeFileSync3, readFileSync as readFileSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync3 } from "node:fs";
1164
+ import { existsSync as existsSync10, writeFileSync as writeFileSync3, readFileSync as readFileSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync3 } from "node:fs";
1156
1165
  import { dirname as dirname3, join as join9 } from "node:path";
1157
1166
  var LIVE_CAPTURE_TIMEOUT_MS, capturedDomainCache, captureInFlight, captureDomainLocks, skillRouteCache, ROUTE_CACHE_FILE, SKILL_SNAPSHOT_DIR2, domainSkillCache, DOMAIN_CACHE_FILE, _routeCacheDirty = false, routeCacheFlushTimer, routeResultCache, ROUTE_CACHE_TTL, MARKETPLACE_HYDRATE_LIMIT, MARKETPLACE_GET_SKILL_TIMEOUT_MS, MARKETPLACE_DOMAIN_SEARCH_K, MARKETPLACE_GLOBAL_SEARCH_K, SEARCH_INTENT_STOPWORDS;
1158
1167
  var init_orchestrator = __esm(async () => {
@@ -1188,7 +1197,7 @@ var init_orchestrator = __esm(async () => {
1188
1197
  domainSkillCache = new Map;
1189
1198
  DOMAIN_CACHE_FILE = join9(process.env.HOME ?? "/tmp", ".unbrowse", "domain-skill-cache.json");
1190
1199
  try {
1191
- if (existsSync9(DOMAIN_CACHE_FILE)) {
1200
+ if (existsSync10(DOMAIN_CACHE_FILE)) {
1192
1201
  const data = JSON.parse(readFileSync6(DOMAIN_CACHE_FILE, "utf-8"));
1193
1202
  for (const [k, v] of Object.entries(data)) {
1194
1203
  const entry = v;
@@ -1205,7 +1214,7 @@ var init_orchestrator = __esm(async () => {
1205
1214
  _routeCacheDirty = false;
1206
1215
  try {
1207
1216
  const dir = dirname3(ROUTE_CACHE_FILE);
1208
- if (!existsSync9(dir))
1217
+ if (!existsSync10(dir))
1209
1218
  mkdirSync5(dir, { recursive: true });
1210
1219
  const entries = Object.fromEntries(skillRouteCache);
1211
1220
  writeFileSync3(ROUTE_CACHE_FILE, JSON.stringify(entries), "utf-8");
@@ -1213,7 +1222,7 @@ var init_orchestrator = __esm(async () => {
1213
1222
  }, 5000);
1214
1223
  routeCacheFlushTimer.unref?.();
1215
1224
  try {
1216
- if (existsSync9(ROUTE_CACHE_FILE)) {
1225
+ if (existsSync10(ROUTE_CACHE_FILE)) {
1217
1226
  const data = JSON.parse(readFileSync6(ROUTE_CACHE_FILE, "utf-8"));
1218
1227
  for (const [k, v] of Object.entries(data)) {
1219
1228
  const entry = v;
@@ -1310,7 +1319,7 @@ __export(exports_wallet, {
1310
1319
  getWalletContext: () => getWalletContext2,
1311
1320
  checkWalletConfigured: () => checkWalletConfigured2
1312
1321
  });
1313
- import { existsSync as existsSync13, readFileSync as readFileSync9 } from "node:fs";
1322
+ import { existsSync as existsSync14, readFileSync as readFileSync9 } from "node:fs";
1314
1323
  import { homedir as homedir6 } from "node:os";
1315
1324
  import { join as join11 } from "node:path";
1316
1325
  function asNonEmptyString2(value) {
@@ -1318,7 +1327,7 @@ function asNonEmptyString2(value) {
1318
1327
  }
1319
1328
  function getLobsterWalletFromLocalConfig2() {
1320
1329
  const agentsPath = join11(process.env.HOME || homedir6(), ".lobster", "agents.json");
1321
- if (!existsSync13(agentsPath))
1330
+ if (!existsSync14(agentsPath))
1322
1331
  return;
1323
1332
  try {
1324
1333
  const raw = JSON.parse(readFileSync9(agentsPath, "utf8"));
@@ -1369,7 +1378,7 @@ init_wallet();
1369
1378
  init_telemetry_attribution();
1370
1379
  import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync4, mkdirSync, readdirSync as readdirSync2 } from "fs";
1371
1380
  import { join as join4 } from "path";
1372
- import { homedir as homedir3, hostname } from "os";
1381
+ import { homedir as homedir3, hostname, release as osRelease } from "os";
1373
1382
  import { randomBytes, createHash as createHash2 } from "crypto";
1374
1383
  import { createInterface } from "readline";
1375
1384
  var API_URL = process.env.UNBROWSE_BACKEND_URL || DEFAULT_BACKEND_URL;
@@ -1562,9 +1571,19 @@ async function recordInstallTelemetryEvent(source, options) {
1562
1571
  skill_version: options?.skillVersion,
1563
1572
  status: options?.status ?? "installed",
1564
1573
  created_at: createdAt,
1565
- properties: mergeTelemetryProperties(options?.properties, getTelemetryAttribution())
1574
+ properties: mergeTelemetryProperties({ ...getRuntimeContext(), ...options?.properties }, getTelemetryAttribution())
1566
1575
  });
1567
1576
  }
1577
+ function getRuntimeContext() {
1578
+ return {
1579
+ cli_version: PACKAGE_VERSION,
1580
+ code_hash: CODE_HASH,
1581
+ node_version: process.version,
1582
+ platform: process.platform,
1583
+ arch: process.arch,
1584
+ os_release: osRelease()
1585
+ };
1586
+ }
1568
1587
  async function recordFunnelTelemetryEvent(name, options) {
1569
1588
  const createdAt = options?.createdAt ?? new Date().toISOString();
1570
1589
  const landingToken = getLandingToken();
@@ -1576,7 +1595,7 @@ async function recordFunnelTelemetryEvent(name, options) {
1576
1595
  source: options?.source ?? "cli",
1577
1596
  host_type: options?.hostType ?? detectTelemetryHostType(),
1578
1597
  created_at: createdAt,
1579
- properties: mergeTelemetryProperties(options?.properties, getTelemetryAttribution())
1598
+ properties: mergeTelemetryProperties({ ...getRuntimeContext(), ...options?.properties }, getTelemetryAttribution())
1580
1599
  });
1581
1600
  }
1582
1601
  var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/i;
@@ -2287,7 +2306,7 @@ function buildDepsMetadata(pack, taskName) {
2287
2306
  init_paths();
2288
2307
  init_supervisor();
2289
2308
  init_version();
2290
- import { openSync, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
2309
+ import { existsSync as existsSync7, openSync, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
2291
2310
  import path2 from "node:path";
2292
2311
  import { spawn } from "node:child_process";
2293
2312
  function isServerVersionMismatch(runningVersion, installedVersion, runningCodeHash, installedCodeHash) {
@@ -2353,6 +2372,10 @@ function getServerSpawnSpec(metaUrl, entrypoint = resolveSiblingEntrypoint(metaU
2353
2372
  recordedEntrypoint: `${process.execPath} serve`
2354
2373
  };
2355
2374
  }
2375
+ const serverJs = path2.join(path2.dirname(entrypoint), "server.js");
2376
+ if (existsSync7(serverJs) && path2.basename(entrypoint) !== "server.js") {
2377
+ entrypoint = serverJs;
2378
+ }
2356
2379
  return {
2357
2380
  command: process.execPath,
2358
2381
  args: runtimeArgsForEntrypoint(metaUrl, entrypoint),
@@ -2498,7 +2521,7 @@ async function restartServer(baseUrl, metaUrl) {
2498
2521
  }
2499
2522
 
2500
2523
  // ../../src/runtime/paths.ts
2501
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, realpathSync as realpathSync2 } from "node:fs";
2524
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, realpathSync as realpathSync2 } from "node:fs";
2502
2525
  import path3 from "node:path";
2503
2526
  import { createRequire as createRequire3 } from "node:module";
2504
2527
  import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "node:url";
@@ -2518,7 +2541,7 @@ function runtimeArgsForEntrypoint2(metaUrl, entrypoint) {
2518
2541
  const req = createRequire3(metaUrl);
2519
2542
  const tsxPkg = req.resolve("tsx/package.json");
2520
2543
  const tsxLoader = path3.join(path3.dirname(tsxPkg), "dist", "loader.mjs");
2521
- if (existsSync7(tsxLoader))
2544
+ if (existsSync8(tsxLoader))
2522
2545
  return ["--import", pathToFileURL2(tsxLoader).href, entrypoint];
2523
2546
  } catch {}
2524
2547
  return ["--import", "tsx", entrypoint];
@@ -2587,13 +2610,13 @@ init_client2();
2587
2610
  init_logger();
2588
2611
  init_wallet();
2589
2612
  import { execFileSync as execFileSync3 } from "node:child_process";
2590
- import { existsSync as existsSync11, mkdirSync as mkdirSync7, writeFileSync as writeFileSync5 } from "node:fs";
2613
+ import { existsSync as existsSync12, mkdirSync as mkdirSync7, writeFileSync as writeFileSync5 } from "node:fs";
2591
2614
  import os4 from "node:os";
2592
2615
  import path7 from "node:path";
2593
2616
 
2594
2617
  // ../../src/runtime/update-hints.ts
2595
2618
  init_paths();
2596
- import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
2619
+ import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
2597
2620
  import os3 from "node:os";
2598
2621
  import path6 from "node:path";
2599
2622
  var DEFAULT_INTERVAL_MS = 12 * 60 * 60 * 1000;
@@ -2607,7 +2630,7 @@ function getConfigDir2() {
2607
2630
  return path6.join(getHomeDir(), ".unbrowse");
2608
2631
  }
2609
2632
  function ensureDir3(dir) {
2610
- if (!existsSync10(dir))
2633
+ if (!existsSync11(dir))
2611
2634
  mkdirSync6(dir, { recursive: true });
2612
2635
  return dir;
2613
2636
  }
@@ -2630,7 +2653,7 @@ function detectRepoRoot(start2) {
2630
2653
  let dir = path6.resolve(start2);
2631
2654
  const root = path6.parse(dir).root;
2632
2655
  while (dir !== root) {
2633
- if (existsSync10(path6.join(dir, ".git")))
2656
+ if (existsSync11(path6.join(dir, ".git")))
2634
2657
  return dir;
2635
2658
  dir = path6.dirname(dir);
2636
2659
  }
@@ -2709,12 +2732,12 @@ codex_hooks = true
2709
2732
  }
2710
2733
  function writeCodexHook(metaUrl) {
2711
2734
  const configPath = getCodexConfigPath();
2712
- if (!existsSync10(path6.dirname(configPath))) {
2735
+ if (!existsSync11(path6.dirname(configPath))) {
2713
2736
  return { host: "codex", action: "not-detected", config_file: configPath };
2714
2737
  }
2715
2738
  try {
2716
2739
  const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
2717
- const fileExistsBefore = existsSync10(configPath);
2740
+ const fileExistsBefore = existsSync11(configPath);
2718
2741
  let content = fileExistsBefore ? readFileSync7(configPath, "utf8") : "";
2719
2742
  const previous = content;
2720
2743
  content = ensureCodexHooksFeature(content);
@@ -2749,13 +2772,13 @@ command = ${JSON.stringify(command)}
2749
2772
  }
2750
2773
  function writeClaudeHook(metaUrl) {
2751
2774
  const settingsPath = getClaudeSettingsPath();
2752
- if (!existsSync10(path6.dirname(settingsPath))) {
2775
+ if (!existsSync11(path6.dirname(settingsPath))) {
2753
2776
  return { host: "claude", action: "not-detected", config_file: settingsPath };
2754
2777
  }
2755
2778
  try {
2756
2779
  const hookScript = getHookScriptPath(metaUrl).replace(/\\/g, "/");
2757
2780
  const command = `node "${hookScript}"`;
2758
- const fileExistsBefore = existsSync10(settingsPath);
2781
+ const fileExistsBefore = existsSync11(settingsPath);
2759
2782
  const settings = readJsonFile(settingsPath) ?? {};
2760
2783
  settings.hooks ??= {};
2761
2784
  settings.hooks.SessionStart ??= [];
@@ -2820,7 +2843,7 @@ function getOpenCodeProjectCommandsDir(cwd) {
2820
2843
  return path7.join(cwd, ".opencode", "commands");
2821
2844
  }
2822
2845
  function detectOpenCode(cwd) {
2823
- return hasBinary("opencode") || existsSync11(path7.join(resolveConfigHome(), "opencode")) || existsSync11(path7.join(cwd, ".opencode"));
2846
+ return hasBinary("opencode") || existsSync12(path7.join(resolveConfigHome(), "opencode")) || existsSync12(path7.join(cwd, ".opencode"));
2824
2847
  }
2825
2848
  function renderOpenCodeCommand() {
2826
2849
  return `---
@@ -2848,11 +2871,11 @@ function writeOpenCodeCommand(scope, cwd) {
2848
2871
  if (scope === "auto" && !detected) {
2849
2872
  return { detected: false, action: "not-detected", scope: "off" };
2850
2873
  }
2851
- const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync11(path7.join(cwd, ".opencode")) ? "project" : "global";
2874
+ const resolvedScope = scope === "project" ? "project" : scope === "global" ? "global" : existsSync12(path7.join(cwd, ".opencode")) ? "project" : "global";
2852
2875
  const commandsDir = resolvedScope === "project" ? getOpenCodeProjectCommandsDir(cwd) : getOpenCodeGlobalCommandsDir();
2853
2876
  const commandFile = path7.join(ensureDir2(commandsDir), "unbrowse.md");
2854
2877
  const content = renderOpenCodeCommand();
2855
- const action2 = existsSync11(commandFile) ? "updated" : "installed";
2878
+ const action2 = existsSync12(commandFile) ? "updated" : "installed";
2856
2879
  mkdirSync7(path7.dirname(commandFile), { recursive: true });
2857
2880
  writeFileSync5(commandFile, content);
2858
2881
  return {
@@ -2864,10 +2887,10 @@ function writeOpenCodeCommand(scope, cwd) {
2864
2887
  }
2865
2888
  async function ensureBrowserEngineInstalled() {
2866
2889
  const binary = findKuriBinary();
2867
- if (existsSync11(binary)) {
2890
+ if (existsSync12(binary)) {
2868
2891
  return { installed: true, action: "already-installed" };
2869
2892
  }
2870
- const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync11(path7.join(candidate, "build.zig")));
2893
+ const sourceDir = getKuriSourceCandidates().find((candidate) => existsSync12(path7.join(candidate, "build.zig")));
2871
2894
  if (!sourceDir) {
2872
2895
  return {
2873
2896
  installed: false,
@@ -2889,7 +2912,7 @@ async function ensureBrowserEngineInstalled() {
2889
2912
  timeout: 300000
2890
2913
  });
2891
2914
  const builtBinary = findKuriBinary();
2892
- if (existsSync11(builtBinary)) {
2915
+ if (existsSync12(builtBinary)) {
2893
2916
  return {
2894
2917
  installed: true,
2895
2918
  action: "installed",
@@ -2914,7 +2937,7 @@ async function runSetup(options) {
2914
2937
  const browser = options?.installBrowser === false ? { installed: false, action: "skipped" } : await ensureBrowserEngineInstalled();
2915
2938
  const walletCheck = checkWalletConfigured();
2916
2939
  const skipWalletSetup = process.env.UNBROWSE_SKIP_WALLET_SETUP === "1";
2917
- const lobsterInstalled = hasBinary("lobstercash") || existsSync11(path7.join(os4.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
2940
+ const lobsterInstalled = hasBinary("lobstercash") || existsSync12(path7.join(os4.homedir(), ".agents", "skills", "lobstercash", "SKILL.md"));
2918
2941
  if (!skipWalletSetup && !walletCheck.configured && lobsterInstalled) {
2919
2942
  console.log("[unbrowse] Crossmint lobster.cash detected but wallet not configured — running wallet setup...");
2920
2943
  try {
@@ -2954,7 +2977,7 @@ async function runSetup(options) {
2954
2977
 
2955
2978
  // ../../src/runtime/update-hints.ts
2956
2979
  init_paths();
2957
- import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
2980
+ import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
2958
2981
  import os5 from "node:os";
2959
2982
  import path8 from "node:path";
2960
2983
  var INSTALL_SCRIPT_URL = "https://unbrowse.ai/install.sh";
@@ -2968,7 +2991,7 @@ function getConfigDir3() {
2968
2991
  return path8.join(getHomeDir2(), ".unbrowse");
2969
2992
  }
2970
2993
  function ensureDir4(dir) {
2971
- if (!existsSync12(dir))
2994
+ if (!existsSync13(dir))
2972
2995
  mkdirSync8(dir, { recursive: true });
2973
2996
  return dir;
2974
2997
  }
@@ -2994,7 +3017,7 @@ function detectRepoRoot2(start2) {
2994
3017
  let dir = path8.resolve(start2);
2995
3018
  const root = path8.parse(dir).root;
2996
3019
  while (dir !== root) {
2997
- if (existsSync12(path8.join(dir, ".git")))
3020
+ if (existsSync13(path8.join(dir, ".git")))
2998
3021
  return dir;
2999
3022
  dir = path8.dirname(dir);
3000
3023
  }
@@ -3717,6 +3740,25 @@ async function cmdFeedback(flags) {
3717
3740
  body.diagnostics = JSON.parse(flags.diagnostics);
3718
3741
  output(await api2("POST", "/v1/feedback", body), !!flags.pretty);
3719
3742
  }
3743
+ async function cmdAnnotate(flags) {
3744
+ const skillId = flags.skill;
3745
+ const endpointId = flags.endpoint;
3746
+ if (!skillId || !endpointId)
3747
+ die("--skill and --endpoint are required");
3748
+ const body = {};
3749
+ if (flags.text) {
3750
+ body.annotations = [{ text: flags.text }];
3751
+ }
3752
+ if (flags.constraint) {
3753
+ const parts = flags.constraint.split(":");
3754
+ if (parts.length >= 3) {
3755
+ body.constraints = [{ param: parts[0], rule: parts[1], message: parts.slice(2).join(":") }];
3756
+ }
3757
+ }
3758
+ if (!body.annotations && !body.constraints)
3759
+ die("--text or --constraint required");
3760
+ output(await api2("POST", `/v1/skills/${skillId}/endpoints/${endpointId}/annotate`, body), !!flags.pretty);
3761
+ }
3720
3762
  async function cmdReview(flags) {
3721
3763
  const skillId = flags.skill;
3722
3764
  if (!skillId)
@@ -3931,6 +3973,7 @@ var CLI_REFERENCE = {
3931
3973
  { name: "resolve", usage: '--intent "..." [--domain "..."] [--url "..."] [opts]', desc: "Search cached indexed/published routes and optionally execute the top trusted endpoint" },
3932
3974
  { name: "execute", usage: "--skill ID --endpoint ID [opts]", desc: "Execute a specific endpoint" },
3933
3975
  { name: "feedback", usage: "--skill ID --endpoint ID --rating N", desc: "Submit feedback (mandatory after resolve)" },
3976
+ { name: "annotate", usage: "--skill ID --endpoint ID --text 'tip' [--constraint 'param:rule:message']", desc: "Contribute best practices or constraints for an endpoint" },
3934
3977
  { name: "review", usage: "--skill ID --endpoints '[...]'", desc: "Push reviewed descriptions/schema metadata back to a captured skill before publish" },
3935
3978
  { name: "index", usage: "--skill ID", desc: "Recompute local graph/contracts/export from cached skill state only" },
3936
3979
  { name: "publish", usage: "--skill ID [--confirm-publish] [--endpoints '[...]']", desc: "Re-index locally, inspect publish-review metadata, then publish/share from cached skill state" },
@@ -4557,6 +4600,7 @@ async function main() {
4557
4600
  "exec",
4558
4601
  "feedback",
4559
4602
  "fb",
4603
+ "annotate",
4560
4604
  "review",
4561
4605
  "index",
4562
4606
  "publish",
@@ -4628,6 +4672,8 @@ async function main() {
4628
4672
  case "feedback":
4629
4673
  case "fb":
4630
4674
  return cmdFeedback(flags);
4675
+ case "annotate":
4676
+ return cmdAnnotate(flags);
4631
4677
  case "review":
4632
4678
  return cmdReview(flags);
4633
4679
  case "index":
package/dist/index.js CHANGED
@@ -1,16 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from "node:child_process";
3
- import { createRequire } from "node:module";
4
3
  import path from "node:path";
5
4
  import { fileURLToPath } from "node:url";
6
5
 
7
6
  const packageRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
8
- const serverEntrypoint = path.join(packageRoot, "runtime-src", "index.ts");
9
- const req = createRequire(import.meta.url);
10
- const tsxPkg = req.resolve("tsx/package.json");
11
- const tsxLoader = path.join(path.dirname(tsxPkg), "dist", "loader.mjs");
7
+ const serverEntrypoint = path.join(packageRoot, "dist", "server.js");
12
8
 
13
- const child = spawn(process.execPath, ["--import", tsxLoader, serverEntrypoint, ...process.argv.slice(2)], {
9
+ const child = spawn(process.execPath, [serverEntrypoint, ...process.argv.slice(2)], {
14
10
  stdio: "inherit",
15
11
  cwd: process.cwd(),
16
12
  env: {
package/dist/mcp.js CHANGED
@@ -20,7 +20,7 @@ __export(exports_lobster_pay, {
20
20
  isLobsterAvailable: () => isLobsterAvailable
21
21
  });
22
22
  import { execFile, execFileSync } from "node:child_process";
23
- import { existsSync as existsSync5 } from "node:fs";
23
+ import { existsSync as existsSync6 } from "node:fs";
24
24
  import { homedir as homedir3 } from "node:os";
25
25
  import { join as join4 } from "node:path";
26
26
  function getLobsterCommand() {
@@ -31,7 +31,7 @@ function getLobsterCommand() {
31
31
  try {
32
32
  const npmPrefix = execFileSync("npm", ["config", "get", "prefix"], { encoding: "utf8", timeout: 5000 }).trim();
33
33
  const lobsterPath = join4(npmPrefix, "bin", "lobstercash");
34
- if (existsSync5(lobsterPath)) {
34
+ if (existsSync6(lobsterPath)) {
35
35
  execFileSync(lobsterPath, ["--version"], { stdio: "ignore", timeout: 3000 });
36
36
  return { cmd: lobsterPath, prefix: [] };
37
37
  }
@@ -45,7 +45,7 @@ function lobsterCmd() {
45
45
  }
46
46
  function isLobsterAvailable() {
47
47
  const agentsPath = join4(process.env.HOME || homedir3(), ".lobster", "agents.json");
48
- return existsSync5(agentsPath);
48
+ return existsSync6(agentsPath);
49
49
  }
50
50
  function lobsterX402Fetch(url, options) {
51
51
  return new Promise((resolve) => {
@@ -121,12 +121,12 @@ var init_lobster_pay = () => {};
121
121
  // ../../src/mcp.ts
122
122
  import { config as loadEnv } from "dotenv";
123
123
  import { createInterface } from "readline";
124
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
124
+ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
125
125
  import path4 from "path";
126
126
  import { fileURLToPath as fileURLToPath3 } from "url";
127
127
 
128
128
  // ../../src/runtime/local-server.ts
129
- import { openSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "node:fs";
129
+ import { existsSync as existsSync3, openSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "node:fs";
130
130
  import path2 from "node:path";
131
131
  import { spawn } from "node:child_process";
132
132
 
@@ -225,11 +225,11 @@ import { dirname, join, parse } from "path";
225
225
  import { fileURLToPath as fileURLToPath2 } from "url";
226
226
 
227
227
  // ../../src/build-info.generated.ts
228
- var BUILD_RELEASE_VERSION = "3.1.0-experiments.995f8bb";
229
- var BUILD_GIT_SHA = "995f8bbf54ac";
228
+ var BUILD_RELEASE_VERSION = "3.1.0-experiments.9cbcb13";
229
+ var BUILD_GIT_SHA = "9cbcb1312b26";
230
230
  var BUILD_CODE_HASH = "1488fc1d92b7";
231
- var BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiMy4xLjAtZXhwZXJpbWVudHMuOTk1ZjhiYiIsImdpdF9zaGEiOiI5OTVmOGJiZjU0YWMiLCJjb2RlX2hhc2giOiIxNDg4ZmMxZDkyYjciLCJ0cmFjZV92ZXJzaW9uIjoiMTQ4OGZjMWQ5MmI3QDk5NWY4YmJmNTRhYyIsImlzc3VlZF9hdCI6IjIwMjYtMDQtMDVUMjM6NDc6MDguMzk3WiJ9";
232
- var BUILD_RELEASE_MANIFEST_SIGNATURE = "fsY0FlwZTGKoZwvzv3jow-t3ri874GEziuzW8i3aupw";
231
+ var BUILD_RELEASE_MANIFEST_BASE64 = "eyJzY2hlbWFfdmVyc2lvbiI6MSwicmVsZWFzZV92ZXJzaW9uIjoiMy4xLjAtZXhwZXJpbWVudHMuOWNiY2IxMyIsImdpdF9zaGEiOiI5Y2JjYjEzMTJiMjYiLCJjb2RlX2hhc2giOiIxNDg4ZmMxZDkyYjciLCJ0cmFjZV92ZXJzaW9uIjoiMTQ4OGZjMWQ5MmI3QDljYmNiMTMxMmIyNiIsImlzc3VlZF9hdCI6IjIwMjYtMDQtMDZUMDY6MjM6MDQuMzU5WiJ9";
232
+ var BUILD_RELEASE_MANIFEST_SIGNATURE = "CVnzRBAw3oxxU_-oG-sOC9vJWGI0-k2PtTw4Q5SYueY";
233
233
  var BUILD_DEFAULT_BACKEND_URL = "https://unbrowse-backend-experiments.lewis-6d8.workers.dev";
234
234
 
235
235
  // ../../src/version.ts
@@ -400,6 +400,10 @@ function getServerSpawnSpec(metaUrl, entrypoint = resolveSiblingEntrypoint(metaU
400
400
  recordedEntrypoint: `${process.execPath} serve`
401
401
  };
402
402
  }
403
+ const serverJs = path2.join(path2.dirname(entrypoint), "server.js");
404
+ if (existsSync3(serverJs) && path2.basename(entrypoint) !== "server.js") {
405
+ entrypoint = serverJs;
406
+ }
403
407
  return {
404
408
  command: process.execPath,
405
409
  args: runtimeArgsForEntrypoint(metaUrl, entrypoint),
@@ -540,7 +544,7 @@ function stopServer(baseUrl) {
540
544
  }
541
545
 
542
546
  // ../../src/workflow/publish.ts
543
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, readdirSync as readdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
547
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, readdirSync as readdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
544
548
  import { homedir } from "node:os";
545
549
  import { join as join2 } from "node:path";
546
550
 
@@ -567,7 +571,7 @@ function workflowPublishArtifactPathForSkill(skillId) {
567
571
  }
568
572
  function readWorkflowPublishArtifact(skillId) {
569
573
  const target = workflowPublishArtifactPathForSkill(skillId);
570
- if (!existsSync3(target))
574
+ if (!existsSync4(target))
571
575
  return null;
572
576
  try {
573
577
  return JSON.parse(readFileSync3(target, "utf-8"));
@@ -577,13 +581,13 @@ function readWorkflowPublishArtifact(skillId) {
577
581
  }
578
582
  function listWorkflowPublishArtifacts() {
579
583
  const dir = getWorkflowExportDir();
580
- if (!existsSync3(dir))
584
+ if (!existsSync4(dir))
581
585
  return [];
582
586
  return readdirSync2(dir).filter((entry) => entry.endsWith(".json")).map((entry) => join2(dir, entry));
583
587
  }
584
588
 
585
589
  // ../../src/impact-log.ts
586
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, appendFileSync, statSync, readFileSync as readFileSync4, renameSync, unlinkSync as unlinkSync2 } from "node:fs";
590
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, appendFileSync, statSync, readFileSync as readFileSync4, renameSync, unlinkSync as unlinkSync2 } from "node:fs";
587
591
  import { homedir as homedir2 } from "node:os";
588
592
  import { dirname as dirname2, join as join3 } from "node:path";
589
593
  var MAX_LOG_BYTES = 5 * 1024 * 1024;
@@ -599,19 +603,19 @@ function getImpactLogPath() {
599
603
  }
600
604
  function ensureDir2(path4) {
601
605
  const dir = dirname2(path4);
602
- if (!existsSync4(dir))
606
+ if (!existsSync5(dir))
603
607
  mkdirSync3(dir, { recursive: true });
604
608
  }
605
609
  function rotateIfNeeded(path4) {
606
610
  try {
607
- if (!existsSync4(path4))
611
+ if (!existsSync5(path4))
608
612
  return;
609
613
  const size = statSync(path4).size;
610
614
  if (size < MAX_LOG_BYTES)
611
615
  return;
612
616
  for (let i = MAX_ROTATIONS;i >= 1; i--) {
613
617
  const older = `${path4}.${i}`;
614
- if (!existsSync4(older))
618
+ if (!existsSync5(older))
615
619
  continue;
616
620
  if (i === MAX_ROTATIONS) {
617
621
  try {
@@ -681,10 +685,10 @@ function readImpactSummary() {
681
685
  const files = [];
682
686
  for (let i = MAX_ROTATIONS;i >= 1; i--) {
683
687
  const rotated = `${path4}.${i}`;
684
- if (existsSync4(rotated))
688
+ if (existsSync5(rotated))
685
689
  files.push(rotated);
686
690
  }
687
- if (existsSync4(path4))
691
+ if (existsSync5(path4))
688
692
  files.push(path4);
689
693
  if (files.length === 0)
690
694
  return summary;
@@ -741,9 +745,9 @@ function readImpactSummary() {
741
745
  }
742
746
 
743
747
  // ../../src/client/index.ts
744
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync6, mkdirSync as mkdirSync4, readdirSync as readdirSync3 } from "fs";
748
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync as readdirSync3 } from "fs";
745
749
  import { join as join5 } from "path";
746
- import { homedir as homedir4, hostname } from "os";
750
+ import { homedir as homedir4, hostname, release as osRelease } from "os";
747
751
 
748
752
  // ../../src/payments/cascade.ts
749
753
  import bs58 from "bs58";
@@ -792,7 +796,7 @@ function sanitizeProfileName2(value) {
792
796
  function loadConfig() {
793
797
  try {
794
798
  const configPath = getConfigPath();
795
- if (existsSync6(configPath)) {
799
+ if (existsSync7(configPath)) {
796
800
  return JSON.parse(readFileSync5(configPath, "utf-8"));
797
801
  }
798
802
  } catch {}
@@ -1312,7 +1316,7 @@ function getPackageRoot2() {
1312
1316
  let dir = path4.dirname(fileURLToPath3(import.meta.url));
1313
1317
  const root = path4.parse(dir).root;
1314
1318
  while (dir !== root) {
1315
- if (path4.basename(dir) === "src" && existsSync7(path4.join(path4.dirname(dir), "package.json"))) {
1319
+ if (path4.basename(dir) === "src" && existsSync8(path4.join(path4.dirname(dir), "package.json"))) {
1316
1320
  return path4.dirname(dir);
1317
1321
  }
1318
1322
  try {
@@ -1406,12 +1410,23 @@ function addExecuteNextStepHints(result, args) {
1406
1410
  hints.feedback_skill = skillId;
1407
1411
  if (endpointId)
1408
1412
  hints.feedback_endpoint = endpointId;
1413
+ const constraints = isPlainObject(nested) && Array.isArray(nested.constraints) ? nested.constraints : undefined;
1414
+ if (constraints?.length) {
1415
+ hints.known_constraints = constraints;
1416
+ }
1417
+ const annotations = isPlainObject(nested) && Array.isArray(nested.annotations) ? nested.annotations : undefined;
1418
+ if (annotations?.length) {
1419
+ hints.community_notes = annotations;
1420
+ }
1409
1421
  const desc = isPlainObject(nested) && typeof nested.description === "string" ? nested.description : "";
1410
1422
  const looksGeneric = !desc || desc.startsWith("Captured ") || desc.startsWith("Returns results");
1411
1423
  if (looksGeneric) {
1412
1424
  hints.first_use_review_needed = true;
1413
1425
  hints.review_step = "After feedback, call unbrowse_review to write proper endpoint descriptions, then unbrowse_publish to share to marketplace.";
1414
1426
  }
1427
+ if (skillId && endpointId) {
1428
+ hints.contribute = "If you learned something about this endpoint (required params, gotchas, best practices), call unbrowse_annotate to share it with other agents.";
1429
+ }
1415
1430
  return { ...result, _workflow_hints: hints };
1416
1431
  }
1417
1432
  function addCaptureNextStepHints(result, _args) {
@@ -2448,6 +2463,42 @@ var tools = [
2448
2463
  const withHints = addCaptureNextStepHints(result, args);
2449
2464
  return successResult(withHints, "Browse session closed. See _workflow_hints for required next steps: call unbrowse_review then unbrowse_publish.");
2450
2465
  }
2466
+ },
2467
+ {
2468
+ name: "unbrowse_annotate",
2469
+ description: "Contribute constraints or best practices for an endpoint. Call this after executing an endpoint to share what you learned (required params, gotchas, tips) with other agents.",
2470
+ parameters: {
2471
+ type: "object",
2472
+ properties: {
2473
+ skill: { type: "string", description: "Skill ID" },
2474
+ endpoint: { type: "string", description: "Endpoint ID" },
2475
+ constraints: {
2476
+ type: "array",
2477
+ description: "Learned constraints (required params, deprecated fields, format rules)",
2478
+ items: { type: "object", properties: { param: { type: "string" }, rule: { type: "string" }, message: { type: "string" } }, required: ["param", "rule", "message"] }
2479
+ },
2480
+ annotations: {
2481
+ type: "array",
2482
+ description: "Free-text best practices, tips, or gotchas",
2483
+ items: { type: "object", properties: { text: { type: "string" } }, required: ["text"] }
2484
+ }
2485
+ },
2486
+ required: ["skill", "endpoint"]
2487
+ },
2488
+ handler: async (args) => {
2489
+ await ensureServerReady();
2490
+ const skillId = args.skill;
2491
+ const endpointId = args.endpoint;
2492
+ const body = {};
2493
+ if (Array.isArray(args.constraints))
2494
+ body.constraints = args.constraints;
2495
+ if (Array.isArray(args.annotations))
2496
+ body.annotations = args.annotations;
2497
+ if (!body.constraints && !body.annotations)
2498
+ return errorResult("Provide constraints and/or annotations");
2499
+ const result = await api2("POST", `/v1/skills/${skillId}/endpoints/${endpointId}/annotate`, body);
2500
+ return successResult(result, "Annotation saved. Other agents will see your contribution when using this endpoint.");
2501
+ }
2451
2502
  }
2452
2503
  ];
2453
2504
  var toolMap = new Map(tools.map((tool) => [tool.name, tool]));