sootsim 0.1.303 → 0.1.304

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 (151) hide show
  1. package/dist-cli/bin.js +5 -5
  2. package/dist-cli/chunks/{agent-NKFJH2RJ.js → agent-EK355OQM.js} +2 -2
  3. package/dist-cli/chunks/{agent-wrapper-WEA3TGWF.js → agent-wrapper-LCJJWHZE.js} +2 -2
  4. package/dist-cli/chunks/app-fonts-LIPU2DLT.js +4 -0
  5. package/dist-cli/chunks/{assert-ZG65AUNK.js → assert-OASKCPBX.js} +2 -2
  6. package/dist-cli/chunks/{auth-RMFBZQIW.js → auth-XYYKD33F.js} +2 -2
  7. package/dist-cli/chunks/{beta-RR6TCDCJ.js → beta-TOL55XPM.js} +2 -2
  8. package/dist-cli/chunks/{camera-JWOAFE6U.js → camera-PHKVEQHR.js} +2 -2
  9. package/dist-cli/chunks/{chunk-AWE4ZKR3.js → chunk-2MEUZOSD.js} +1 -1
  10. package/dist-cli/chunks/{chunk-HKWQLGKK.js → chunk-2RLFUM3C.js} +3 -3
  11. package/dist-cli/chunks/{chunk-TDM4IQS3.js → chunk-3UUHIHA6.js} +2 -2
  12. package/dist-cli/chunks/{chunk-U7GJF4CQ.js → chunk-5NJTX5OP.js} +2 -2
  13. package/dist-cli/chunks/{chunk-GVC4ZNVM.js → chunk-76OLOD2G.js} +2 -2
  14. package/dist-cli/chunks/{chunk-LK63GSPB.js → chunk-7RFXZXKH.js} +1 -1
  15. package/dist-cli/chunks/{chunk-NRPNLTJI.js → chunk-A4UYZTV7.js} +2 -2
  16. package/dist-cli/chunks/{chunk-YOV3CQIH.js → chunk-ABHNOM2C.js} +1 -1
  17. package/dist-cli/chunks/{chunk-WI5GVQ6M.js → chunk-AJYA6R3N.js} +2 -2
  18. package/dist-cli/chunks/{chunk-YPZDXYHV.js → chunk-BJOT2LL6.js} +2 -2
  19. package/dist-cli/chunks/{chunk-MLX2UIH7.js → chunk-BPRMA4IC.js} +2 -2
  20. package/dist-cli/chunks/{chunk-CVRPU5KX.js → chunk-CMYVGJBQ.js} +1 -1
  21. package/dist-cli/chunks/{chunk-BYCMWKNX.js → chunk-ENXLKDQ6.js} +3 -3
  22. package/dist-cli/chunks/{chunk-G4B2PTUA.js → chunk-EXW6TH3O.js} +2 -2
  23. package/dist-cli/chunks/{chunk-APBN2B6C.js → chunk-FNI75AD7.js} +1 -1
  24. package/dist-cli/chunks/{chunk-4F7VUXKY.js → chunk-G4R7CPCY.js} +2 -2
  25. package/dist-cli/chunks/{chunk-H3U7LAZG.js → chunk-GF3LDVWF.js} +2 -2
  26. package/dist-cli/chunks/{chunk-QR4A5JV3.js → chunk-GMILMS7P.js} +2 -2
  27. package/dist-cli/chunks/{chunk-JZZIA5BR.js → chunk-HFA5ZACE.js} +1 -1
  28. package/dist-cli/chunks/{chunk-3YYCMGOR.js → chunk-HFKQUS4I.js} +3 -3
  29. package/dist-cli/chunks/{chunk-CQEK4J75.js → chunk-INBBOCU4.js} +2 -2
  30. package/dist-cli/chunks/chunk-ITDTSNN2.js +4 -0
  31. package/dist-cli/chunks/{chunk-VGNEIUJ7.js → chunk-IWRRIQDJ.js} +2 -2
  32. package/dist-cli/chunks/{chunk-3I647BPF.js → chunk-JDKWIB6V.js} +1 -1
  33. package/dist-cli/chunks/{chunk-MLHEWBGL.js → chunk-LRXSLMX4.js} +1 -1
  34. package/dist-cli/chunks/{chunk-UJIGVLKD.js → chunk-M3ETYVNL.js} +1 -1
  35. package/dist-cli/chunks/{chunk-FLLDNYUC.js → chunk-MSYPDBFG.js} +2 -2
  36. package/dist-cli/chunks/{chunk-ATRV3MGB.js → chunk-NXYKNKNL.js} +2 -2
  37. package/dist-cli/chunks/{chunk-FQZQKJZ5.js → chunk-OSH6526H.js} +1 -1
  38. package/dist-cli/chunks/{chunk-VLLTUTO5.js → chunk-QWQHSUZC.js} +2 -2
  39. package/dist-cli/chunks/chunk-RJWQ3VAD.js +14 -0
  40. package/dist-cli/chunks/{chunk-XAHT6BRW.js → chunk-RQJDIDJ3.js} +1 -1
  41. package/dist-cli/chunks/{chunk-657GXEAT.js → chunk-RT7TUHH7.js} +2 -2
  42. package/dist-cli/chunks/{chunk-LIK6FJNR.js → chunk-RYQUO6LT.js} +1 -1
  43. package/dist-cli/chunks/{chunk-2LMSJNAH.js → chunk-SN6ARNCL.js} +2 -2
  44. package/dist-cli/chunks/{chunk-SSGGUPY7.js → chunk-SVFN66PF.js} +2 -2
  45. package/dist-cli/chunks/{chunk-ZJ6EL5FU.js → chunk-UB3OWUVK.js} +1 -1
  46. package/dist-cli/chunks/{chunk-5NHCBHYZ.js → chunk-V6JDDX4B.js} +2 -2
  47. package/dist-cli/chunks/{chunk-M4SIQQDE.js → chunk-VUR5QXVX.js} +2 -2
  48. package/dist-cli/chunks/{chunk-TFWWL3HI.js → chunk-W3CSYOSQ.js} +1 -1
  49. package/dist-cli/chunks/{chunk-6Q5PHS4F.js → chunk-WNDYTKLC.js} +1 -1
  50. package/dist-cli/chunks/{chunk-TL6D2L7C.js → chunk-XCVSB47R.js} +2 -2
  51. package/dist-cli/chunks/{chunk-BQLQCPXE.js → chunk-XHYSASAX.js} +1 -1
  52. package/dist-cli/chunks/{chunk-DTQ4XGJP.js → chunk-XXS4TCOO.js} +3 -3
  53. package/dist-cli/chunks/{chunk-OSCWNNYR.js → chunk-Y5YM7XHU.js} +1 -1
  54. package/dist-cli/chunks/{chunk-AXDVKOGM.js → chunk-YAV6BH3A.js} +2 -2
  55. package/dist-cli/chunks/{chunk-XE2DUF5Z.js → chunk-YXHO2OUL.js} +1 -1
  56. package/dist-cli/chunks/{chunk-ZED5FLNC.js → chunk-ZDRI2LUG.js} +1 -1
  57. package/dist-cli/chunks/{chunk-BDZ7GDAA.js → chunk-ZONISYZ5.js} +1 -1
  58. package/dist-cli/chunks/{cleanup-MTGLBGPQ.js → cleanup-GCTEGJMP.js} +2 -2
  59. package/dist-cli/chunks/{cli-version-KF6SJ5QC.js → cli-version-G72HUH4E.js} +2 -2
  60. package/dist-cli/chunks/{compat-NIROXTXH.js → compat-IC4JHGBH.js} +2 -2
  61. package/dist-cli/chunks/{config-GVI5ADMM.js → config-C65MZFU3.js} +2 -2
  62. package/dist-cli/chunks/{control-RLM3WBEH.js → control-RWLYBKQR.js} +2 -2
  63. package/dist-cli/chunks/daemon-D77E5G6Z.js +4 -0
  64. package/dist-cli/chunks/{debug-QLQ6VFDL.js → debug-LEL32PDN.js} +3 -3
  65. package/dist-cli/chunks/{desktop-4KAE6XGD.js → desktop-RDSISJH2.js} +3 -3
  66. package/dist-cli/chunks/{detox-XW4L3NAA.js → detox-SCCWUIKK.js} +2 -2
  67. package/dist-cli/chunks/{device-IMVFXWBB.js → device-MTDEKDUK.js} +2 -2
  68. package/dist-cli/chunks/{diagnose-JTFFK6SX.js → diagnose-UZJCKLPZ.js} +2 -2
  69. package/dist-cli/chunks/disk-cleanup-HLWRVTVW.js +4 -0
  70. package/dist-cli/chunks/drivers-Q7GMKTBM.js +4 -0
  71. package/dist-cli/chunks/{film-BTQI2ZPO.js → film-YBE544PG.js} +3 -3
  72. package/dist-cli/chunks/flow-5VOVAVZN.js +4 -0
  73. package/dist-cli/chunks/help-2PESJ7V2.js +4 -0
  74. package/dist-cli/chunks/{hidden-runtime-alias-5Y3QDZ2L.js → hidden-runtime-alias-KJYCJLSR.js} +2 -2
  75. package/dist-cli/chunks/{home-paths-K2P3TAAX.js → home-paths-NSHWNEGK.js} +2 -2
  76. package/dist-cli/chunks/inspect-OIA3Q5MD.js +4 -0
  77. package/dist-cli/chunks/install-desktop-7KLACQUW.js +4 -0
  78. package/dist-cli/chunks/{login-DIHEERZB.js → login-AEVUU72R.js} +4 -4
  79. package/dist-cli/chunks/{logout-HXABB5BG.js → logout-72XHF6J2.js} +2 -2
  80. package/dist-cli/chunks/{maestro-LF67TXN2.js → maestro-OXSKQJ6B.js} +3 -3
  81. package/dist-cli/chunks/{maestro-generate-XU7IAT4W.js → maestro-generate-AKZFORVJ.js} +3 -3
  82. package/dist-cli/chunks/{mode-GJRNBJWL.js → mode-U6KRG2PE.js} +2 -2
  83. package/dist-cli/chunks/{optional-demo-registry-SXOSQMWZ.js → optional-demo-registry-L3VIWBI2.js} +2 -2
  84. package/dist-cli/chunks/{perf-IZY2XMUE.js → perf-KF2BJZIN.js} +2 -2
  85. package/dist-cli/chunks/{permissions-RLZXUFQU.js → permissions-TRJG4QAI.js} +2 -2
  86. package/dist-cli/chunks/{record-OBWLYFSP.js → record-TICJYZOA.js} +3 -3
  87. package/dist-cli/chunks/{report-issue-DU7TRHJK.js → report-issue-OQPLBQMJ.js} +2 -2
  88. package/dist-cli/chunks/runtime-HDMLWNXW.js +4 -0
  89. package/dist-cli/chunks/{screenshot-command-5IF4BSJ4.js → screenshot-command-XLERKHVW.js} +3 -3
  90. package/dist-cli/chunks/{screenshots-capture-OFYWMTVR.js → screenshots-capture-B6OORSD4.js} +2 -2
  91. package/dist-cli/chunks/{serve-DUES2MUY.js → serve-5M7MIPWF.js} +2 -2
  92. package/dist-cli/chunks/{setup-7JBMUE6Q.js → setup-HJZDC4PK.js} +2 -2
  93. package/dist-cli/chunks/{skills-J5HVAM75.js → skills-RXHLMGGL.js} +2 -2
  94. package/dist-cli/chunks/{storage-EPTR6RW5.js → storage-MMPD2XBS.js} +2 -2
  95. package/dist-cli/chunks/store-APKY72ZY.js +4 -0
  96. package/dist-cli/chunks/telemetry-W3YRHE67.js +4 -0
  97. package/dist-cli/chunks/{timeline-QR2474MZ.js → timeline-6PF5BZ37.js} +2 -2
  98. package/dist-cli/chunks/{upgrade-MQL27HMQ.js → upgrade-QSMIK3RC.js} +2 -2
  99. package/dist-cli/chunks/upload-HQEOYILQ.js +4 -0
  100. package/dist-cli/chunks/{version-ILGJD7EH.js → version-WLTFXEDC.js} +2 -2
  101. package/dist-cli/chunks/{web-NEPM3SZU.js → web-DI73LQ7N.js} +2 -2
  102. package/dist-cli/chunks/{what-happened-KGOUXBW4.js → what-happened-4SAMGZWR.js} +2 -2
  103. package/dist-lib/agent-daemon-client.cjs +1 -1
  104. package/dist-lib/agent-events.cjs +1 -1
  105. package/dist-lib/agent-identity.cjs +1 -1
  106. package/dist-lib/agent-sessions.cjs +1 -1
  107. package/dist-lib/attached-projects.cjs +1 -1
  108. package/dist-lib/auth/shared-session.cjs +1 -1
  109. package/dist-lib/backend-origin.cjs +1 -1
  110. package/dist-lib/beta.cjs +1 -1
  111. package/dist-lib/beta.mjs +1 -1
  112. package/dist-lib/bridge-constants.cjs +1 -1
  113. package/dist-lib/cli-constants.cjs +1 -1
  114. package/dist-lib/config.cjs +1 -1
  115. package/dist-lib/detox/index.cjs +1 -1
  116. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  117. package/dist-lib/home-paths.cjs +1 -1
  118. package/dist-lib/host/bridge-host.cjs +17 -8
  119. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  120. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  121. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  122. package/dist-lib/host/websocket-proxy.cjs +1 -1
  123. package/dist-lib/index.cjs +1 -1
  124. package/dist-lib/jump-to-source-babel.cjs +1 -1
  125. package/dist-lib/menu.cjs +1 -1
  126. package/dist-lib/menu.mjs +1 -1
  127. package/dist-lib/metro.cjs +1 -1
  128. package/dist-lib/profiles.cjs +1 -1
  129. package/dist-lib/render-mode.cjs +1 -1
  130. package/dist-lib/scripts/dev-server-scanner.cjs +58 -3
  131. package/dist-lib/sdk.cjs +1 -1
  132. package/dist-lib/sdk.mjs +1 -1
  133. package/dist-lib/skills.cjs +21 -11
  134. package/dist-lib/vite.cjs +1 -1
  135. package/package.json +1 -1
  136. package/scripts/dev-server-scanner.ts +95 -9
  137. package/skills/sootsim-setup/SKILL.md +23 -11
  138. package/dist-cli/chunks/app-fonts-L5JXOKIF.js +0 -4
  139. package/dist-cli/chunks/chunk-E3POCZJT.js +0 -4
  140. package/dist-cli/chunks/chunk-YWAV3FQJ.js +0 -12
  141. package/dist-cli/chunks/daemon-O2R2GEWR.js +0 -4
  142. package/dist-cli/chunks/disk-cleanup-3HBGT6XT.js +0 -4
  143. package/dist-cli/chunks/drivers-BHLLX7OX.js +0 -4
  144. package/dist-cli/chunks/flow-C7PWEH4C.js +0 -4
  145. package/dist-cli/chunks/help-LOC2M6FR.js +0 -4
  146. package/dist-cli/chunks/inspect-JH64MAHP.js +0 -4
  147. package/dist-cli/chunks/install-desktop-C6KE3OM5.js +0 -4
  148. package/dist-cli/chunks/runtime-E7SIB4P3.js +0 -4
  149. package/dist-cli/chunks/store-W3YDQEZQ.js +0 -4
  150. package/dist-cli/chunks/telemetry-WXVIVQ4Y.js +0 -4
  151. package/dist-cli/chunks/upload-6IA2EKEH.js +0 -4
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.303 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.304 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -39,12 +39,15 @@ __export(dev_server_scanner_exports, {
39
39
  __shouldReuseScannerCacheEntry: () => __shouldReuseScannerCacheEntry,
40
40
  discoverListeningPorts: () => discoverListeningPorts,
41
41
  discoverListeningProcesses: () => discoverListeningProcesses,
42
+ pidFromNetstat: () => pidFromNetstat,
42
43
  probePort: () => probePort,
44
+ resolveListeningPid: () => resolveListeningPid,
43
45
  resolveProcessCwd: () => resolveProcessCwd,
44
46
  scanDevServers: () => scanDevServers
45
47
  });
46
48
  module.exports = __toCommonJS(dev_server_scanner_exports);
47
49
  var import_child_process = require("child_process");
50
+ var import_promises2 = require("fs/promises");
48
51
  var import_http = __toESM(require("http"), 1);
49
52
  var import_net = __toESM(require("net"), 1);
50
53
  var import_util = require("util");
@@ -576,15 +579,65 @@ async function discoverListeningPorts(excludePorts = []) {
576
579
  const processes = await discoverListeningProcesses(excludePorts);
577
580
  return processes.map((p) => p.port);
578
581
  }
582
+ function pidFromNetstat(stdout, port) {
583
+ for (const line of stdout.split("\n")) {
584
+ if (!line.includes("LISTEN")) continue;
585
+ const parts = line.trim().split(/\s+/);
586
+ if (!parts[3]?.endsWith(`.${port}`)) continue;
587
+ for (const token of parts) {
588
+ const owner = token.match(/^\S+:(\d+)$/);
589
+ if (owner) {
590
+ const pid = Number(owner[1]);
591
+ if (pid > 0) return pid;
592
+ }
593
+ }
594
+ }
595
+ return null;
596
+ }
597
+ async function resolveListeningPid(port) {
598
+ if (!Number.isInteger(port) || port <= 0) return null;
599
+ try {
600
+ const { stdout } = await execP("netstat -anv -p tcp 2>/dev/null", {
601
+ encoding: "utf8",
602
+ timeout: 8e3,
603
+ maxBuffer: 16 * 1024 * 1024
604
+ });
605
+ const pid = pidFromNetstat(stdout, port);
606
+ if (pid !== null) return pid;
607
+ } catch {
608
+ }
609
+ try {
610
+ const { stdout } = await execP(`ss -tlnp 2>/dev/null`, {
611
+ encoding: "utf8",
612
+ timeout: 8e3,
613
+ maxBuffer: 16 * 1024 * 1024
614
+ });
615
+ for (const line of stdout.split("\n")) {
616
+ if (!new RegExp(`:${port}\\s`).test(line)) continue;
617
+ const pid = Number(line.match(/pid=(\d+)/)?.[1]);
618
+ if (pid > 0) return pid;
619
+ }
620
+ } catch {
621
+ }
622
+ return null;
623
+ }
579
624
  var cwdByPid = /* @__PURE__ */ new Map();
580
625
  async function resolveProcessCwd(pid) {
581
626
  if (pid <= 0) return null;
582
627
  const cached2 = cwdByPid.get(pid);
583
628
  if (cached2) return cached2;
584
629
  try {
585
- const { stdout } = await execP(`lsof -p ${pid} -a -d cwd -Fn 2>/dev/null`, {
630
+ const cwd = await (0, import_promises2.readlink)(`/proc/${pid}/cwd`);
631
+ if (cwd) {
632
+ cwdByPid.set(pid, cwd);
633
+ return cwd;
634
+ }
635
+ } catch {
636
+ }
637
+ try {
638
+ const { stdout } = await execP(`lsof -b -w -p ${pid} -a -d cwd -Fn 2>/dev/null`, {
586
639
  encoding: "utf8",
587
- timeout: 1500
640
+ timeout: 8e3
588
641
  });
589
642
  for (const line of stdout.split("\n")) {
590
643
  if (line.startsWith("n") && line.length > 1) {
@@ -923,7 +976,9 @@ async function scanDevServers(opts = {}) {
923
976
  __shouldReuseScannerCacheEntry,
924
977
  discoverListeningPorts,
925
978
  discoverListeningProcesses,
979
+ pidFromNetstat,
926
980
  probePort,
981
+ resolveListeningPid,
927
982
  resolveProcessCwd,
928
983
  scanDevServers
929
984
  });
package/dist-lib/sdk.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.303 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.304 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
package/dist-lib/sdk.mjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.303 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.304 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // cli/commands/inspect/core.ts
4
4
  function resolveMaxMsFlag(args, fallbackMs) {
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.303 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.304 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -11386,9 +11386,17 @@ async function resolveProcessCwd(pid) {
11386
11386
  const cached4 = cwdByPid.get(pid);
11387
11387
  if (cached4) return cached4;
11388
11388
  try {
11389
- const { stdout } = await execP(`lsof -p ${pid} -a -d cwd -Fn 2>/dev/null`, {
11389
+ const cwd = await (0, import_promises2.readlink)(`/proc/${pid}/cwd`);
11390
+ if (cwd) {
11391
+ cwdByPid.set(pid, cwd);
11392
+ return cwd;
11393
+ }
11394
+ } catch {
11395
+ }
11396
+ try {
11397
+ const { stdout } = await execP(`lsof -b -w -p ${pid} -a -d cwd -Fn 2>/dev/null`, {
11390
11398
  encoding: "utf8",
11391
- timeout: 1500
11399
+ timeout: 8e3
11392
11400
  });
11393
11401
  for (const line of stdout.split("\n")) {
11394
11402
  if (line.startsWith("n") && line.length > 1) {
@@ -11695,11 +11703,12 @@ async function scanDevServers(opts = {}) {
11695
11703
  }
11696
11704
  return results.filter((r) => !isSootSimSelfServer(r));
11697
11705
  }
11698
- var import_child_process, import_http, import_net, import_util, execP, TIMEOUT_MS, MANIFEST_TIMEOUT_MS, TCP_GATE_MS, FALLBACK_PORTS, cwdByPid, ENRICH_TIMEOUT_MS, enrichmentCache, enrichmentInFlight, knownNonPatched, knownNonExpo, knownOne, portCache, NEGATIVE_CACHE_TTL_MS, WEAK_RESULT_CACHE_TTL_MS;
11706
+ var import_child_process, import_promises2, import_http, import_net, import_util, execP, TIMEOUT_MS, MANIFEST_TIMEOUT_MS, TCP_GATE_MS, FALLBACK_PORTS, cwdByPid, ENRICH_TIMEOUT_MS, enrichmentCache, enrichmentInFlight, knownNonPatched, knownNonExpo, knownOne, portCache, NEGATIVE_CACHE_TTL_MS, WEAK_RESULT_CACHE_TTL_MS;
11699
11707
  var init_dev_server_scanner = __esm({
11700
11708
  "scripts/dev-server-scanner.ts"() {
11701
11709
  "use strict";
11702
11710
  import_child_process = require("child_process");
11711
+ import_promises2 = require("fs/promises");
11703
11712
  import_http = __toESM(require("http"), 1);
11704
11713
  import_net = __toESM(require("net"), 1);
11705
11714
  import_util = require("util");
@@ -12355,7 +12364,7 @@ async function downloadToFile(url, destPath) {
12355
12364
  import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(destPath), { recursive: true });
12356
12365
  const tmp = `${destPath}.partial`;
12357
12366
  try {
12358
- await (0, import_promises2.pipeline)(
12367
+ await (0, import_promises3.pipeline)(
12359
12368
  import_node_stream.Readable.fromWeb(res.body),
12360
12369
  import_node_fs4.default.createWriteStream(tmp)
12361
12370
  );
@@ -12389,7 +12398,7 @@ function extractTarball(tarPath, destDir) {
12389
12398
  });
12390
12399
  });
12391
12400
  }
12392
- var import_node_child_process2, import_node_crypto, import_node_fs4, import_node_os2, import_node_path4, import_node_stream, import_promises2, DEFAULT_LOCAL_RUNTIME_RETENTION_COUNT;
12401
+ var import_node_child_process2, import_node_crypto, import_node_fs4, import_node_os2, import_node_path4, import_node_stream, import_promises3, DEFAULT_LOCAL_RUNTIME_RETENTION_COUNT;
12393
12402
  var init_src = __esm({
12394
12403
  "../contrast-runtime-delivery/src/index.ts"() {
12395
12404
  "use strict";
@@ -12399,7 +12408,7 @@ var init_src = __esm({
12399
12408
  import_node_os2 = __toESM(require("node:os"), 1);
12400
12409
  import_node_path4 = __toESM(require("node:path"), 1);
12401
12410
  import_node_stream = require("node:stream");
12402
- import_promises2 = require("node:stream/promises");
12411
+ import_promises3 = require("node:stream/promises");
12403
12412
  init_product();
12404
12413
  init_product();
12405
12414
  DEFAULT_LOCAL_RUNTIME_RETENTION_COUNT = 2;
@@ -14069,7 +14078,7 @@ async function handleFetchProxyRequest(req, res) {
14069
14078
  const switchToGet = (status === 301 || status === 302) && method === "POST" || status === 303 && method !== "GET" && method !== "HEAD";
14070
14079
  const redirectUrl = new URL(location, targetUrl2);
14071
14080
  proxyRes.resume();
14072
- await (0, import_promises3.finished)(proxyRes);
14081
+ await (0, import_promises4.finished)(proxyRes);
14073
14082
  await proxy(
14074
14083
  redirectUrl,
14075
14084
  switchToGet ? "GET" : method,
@@ -14094,7 +14103,7 @@ async function handleFetchProxyRequest(req, res) {
14094
14103
  );
14095
14104
  }
14096
14105
  res.statusCode = status;
14097
- await (0, import_promises3.pipeline)(proxyRes, res);
14106
+ await (0, import_promises4.pipeline)(proxyRes, res);
14098
14107
  };
14099
14108
  try {
14100
14109
  await proxy(upstreamUrl, originalMethod, body, 0);
@@ -14177,13 +14186,13 @@ function handleAppApiRequest(req, res) {
14177
14186
  req.pipe(proxyReq);
14178
14187
  return true;
14179
14188
  }
14180
- var import_http2, import_https, import_promises3, STRIP_FETCH_PROXY_HEADERS, FETCH_PROXY_CORS_HEADERS, APP_API_HEADER_REWRITES;
14189
+ var import_http2, import_https, import_promises4, STRIP_FETCH_PROXY_HEADERS, FETCH_PROXY_CORS_HEADERS, APP_API_HEADER_REWRITES;
14181
14190
  var init_fetch_proxy_handler = __esm({
14182
14191
  "src/host/fetch-proxy-handler.ts"() {
14183
14192
  "use strict";
14184
14193
  import_http2 = __toESM(require("http"), 1);
14185
14194
  import_https = __toESM(require("https"), 1);
14186
- import_promises3 = require("stream/promises");
14195
+ import_promises4 = require("stream/promises");
14187
14196
  init_backend_origin();
14188
14197
  init_fetch_proxy_overrides();
14189
14198
  STRIP_FETCH_PROXY_HEADERS = /* @__PURE__ */ new Set([
@@ -18636,6 +18645,7 @@ var init_app_fonts = __esm({
18636
18645
  "use strict";
18637
18646
  import_node_module = require("node:module");
18638
18647
  import_config_plugin_fonts = require("sootsim-engine/engine/config-plugin-fonts");
18648
+ init_dev_server_scanner();
18639
18649
  init_app_project();
18640
18650
  require2 = (0, import_node_module.createRequire)(__sootsim_import_meta_url);
18641
18651
  }
package/dist-lib/vite.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.303 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.304 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sootsim",
3
- "version": "0.1.303",
3
+ "version": "0.1.304",
4
4
  "description": "sootsim CLI + vite/metro plugins + skills registry. bridge client for driving the proprietary sootsim-engine over WebSocket.",
5
5
  "author": "Tamagui LLC",
6
6
  "license": "MIT",
@@ -14,6 +14,7 @@
14
14
  // pid changes. before any HTTP, a TCP connect gate skips unreachable ports.
15
15
 
16
16
  import { exec } from 'child_process'
17
+ import { readlink } from 'fs/promises'
17
18
  import http from 'http'
18
19
  import net from 'net'
19
20
  import { promisify } from 'util'
@@ -222,27 +223,112 @@ export async function discoverListeningPorts(
222
223
  return processes.map((p) => p.port)
223
224
  }
224
225
 
225
- // per-pid cwd cache. lsof is fairly cheap but we call it per discovered
226
- // server per scan (every 5s in the tray), so caching the result until the
227
- // pid goes away avoids redundant forks.
226
+ /** parse `netstat -anv -p tcp` output for the pid listening on one port.
227
+ *
228
+ * macOS lays a LISTEN row out as
229
+ * tcp4 0 0 127.0.0.1.8090 *.* LISTEN 0 0 131072 131072 node:97330 ...
230
+ * so the local address is the 4th column with a DOT before the port (which is
231
+ * what keeps port 8137 from matching a row for 18137), and the owner is the
232
+ * only token shaped "<name>:<pid>".
233
+ *
234
+ * exported because this column layout is the fragile part and a child process
235
+ * cannot exercise it under bun's test runner, which hands back empty stdout. */
236
+ export function pidFromNetstat(stdout: string, port: number): number | null {
237
+ for (const line of stdout.split('\n')) {
238
+ if (!line.includes('LISTEN')) continue
239
+ const parts = line.trim().split(/\s+/)
240
+ if (!parts[3]?.endsWith(`.${port}`)) continue
241
+ for (const token of parts) {
242
+ const owner = token.match(/^\S+:(\d+)$/)
243
+ if (owner) {
244
+ const pid = Number(owner[1])
245
+ if (pid > 0) return pid
246
+ }
247
+ }
248
+ }
249
+ return null
250
+ }
251
+
252
+ /** resolve the pid of the process listening on one local port.
253
+ *
254
+ * `lsof` walks every descriptor of every process, which measures 2-4s on a
255
+ * loaded machine — longer than any timeout worth giving it, so
256
+ * `discoverListeningProcesses` above degrades to its pid-less port list
257
+ * exactly when the machine is busy. netstat (macOS/BSD) and ss (linux) read
258
+ * the kernel socket table instead and answer in well under a second, and both
259
+ * carry the owning pid. use this whenever the question is "who owns this one
260
+ * port"; `discoverListeningProcesses` answers the different question of what
261
+ * is listening at all. */
262
+ export async function resolveListeningPid(port: number): Promise<number | null> {
263
+ if (!Number.isInteger(port) || port <= 0) return null
264
+
265
+ // macOS netstat: "tcp4 0 0 127.0.0.1.8090 *.* LISTEN ... node:97330 ..."
266
+ // the local address uses a dot before the port, and the owner column is the
267
+ // only token shaped "<name>:<pid>".
268
+ try {
269
+ const { stdout } = await execP('netstat -anv -p tcp 2>/dev/null', {
270
+ encoding: 'utf8',
271
+ timeout: 8000,
272
+ maxBuffer: 16 * 1024 * 1024,
273
+ })
274
+ const pid = pidFromNetstat(stdout, port)
275
+ if (pid !== null) return pid
276
+ } catch {}
277
+
278
+ // linux ss: 'LISTEN 0 511 0.0.0.0:8081 0.0.0.0:* users:(("node",pid=1234,fd=5))'
279
+ try {
280
+ const { stdout } = await execP(`ss -tlnp 2>/dev/null`, {
281
+ encoding: 'utf8',
282
+ timeout: 8000,
283
+ maxBuffer: 16 * 1024 * 1024,
284
+ })
285
+ for (const line of stdout.split('\n')) {
286
+ if (!new RegExp(`:${port}\\s`).test(line)) continue
287
+ const pid = Number(line.match(/pid=(\d+)/)?.[1])
288
+ if (pid > 0) return pid
289
+ }
290
+ } catch {}
291
+
292
+ return null
293
+ }
294
+
295
+ // per-pid cwd cache. lsof is the only way to read another process's cwd on
296
+ // macOS and it is NOT cheap — measured at 3-8s for a single pid on a machine
297
+ // running a few dozen node processes — so caching the result until the pid
298
+ // goes away is what keeps the 5s tray scan viable.
228
299
  const cwdByPid = new Map<number, string>()
229
300
 
230
- /** resolve the current working directory of a pid using `lsof -d cwd`.
231
- * macOS + linux both accept this. returns null for pid 0 (unknown owner)
232
- * and whenever lsof is unavailable or the fd isn't present.
233
- * async — sync exec blocked CrBrowserMain at ~600ms on macOS. */
301
+ /** resolve the current working directory of a pid. returns null for pid 0
302
+ * (unknown owner) and whenever neither mechanism below can see the process.
303
+ * async — sync exec blocked CrBrowserMain at ~600ms on macOS.
304
+ *
305
+ * `-b -w` skips the blocking stat calls lsof otherwise makes against every
306
+ * mount, which roughly halves it. the timeout is generous because the answer
307
+ * is cached per pid: too tight a limit silently returns null on exactly the
308
+ * busy machines where auto-attaching a project matters most. */
234
309
  export async function resolveProcessCwd(pid: number): Promise<string | null> {
235
310
  if (pid <= 0) return null
236
311
  const cached = cwdByPid.get(pid)
237
312
  if (cached) return cached
313
+
314
+ // linux publishes the cwd as a symlink, which costs one readlink and no
315
+ // subprocess at all. macOS has no /proc and has to pay for lsof below.
316
+ try {
317
+ const cwd = await readlink(`/proc/${pid}/cwd`)
318
+ if (cwd) {
319
+ cwdByPid.set(pid, cwd)
320
+ return cwd
321
+ }
322
+ } catch {}
323
+
238
324
  try {
239
325
  // -Fn → machine-readable, one field per line. lines look like:
240
326
  // p<pid>
241
327
  // fcwd
242
328
  // n<absolute path>
243
- const { stdout } = await execP(`lsof -p ${pid} -a -d cwd -Fn 2>/dev/null`, {
329
+ const { stdout } = await execP(`lsof -b -w -p ${pid} -a -d cwd -Fn 2>/dev/null`, {
244
330
  encoding: 'utf8',
245
- timeout: 1500,
331
+ timeout: 8000,
246
332
  })
247
333
  for (const line of stdout.split('\n')) {
248
334
  if (line.startsWith('n') && line.length > 1) {
@@ -23,16 +23,25 @@ debugging or `/sootsim-test` for writing tests.
23
23
  - **an empty `sootsim list` is not "broken sootsim"** — it usually means the
24
24
  daemon is down, the dev server port is wrong, or the runtime is missing.
25
25
  walk the recovery checklist before reinstalling.
26
- - **one sim per investigation. `--new` is an escape hatch, not the normal
27
- invocation.** plain `sootsim open <port>` loads the target into the sim you
28
- already have, `sootsim reload` reloads it in place, and `sootsim close`
29
- disposes it when you are done. reach for `--new` only when the current sim is
30
- held by another CLI, and close it afterwards. passing `--new` on every command
31
- leaves a trail of abandoned browser tabs on whatever machine you are running
32
- on, which is somebody's actual desktop.
26
+ - **one sim for your whole session. switch APPS by navigating that sim, never by
27
+ opening another.** `sootsim open <port> --sim <id>` resolves that exact sim and
28
+ navigates it in place — no new tab, no new browser tree. that is the supported
29
+ way to move between apps and ports:
30
+
31
+ ```sh
32
+ sootsim open 8086 --sim <id> # one app, in your sim
33
+ sootsim open 8090 --sim <id> # a different app, same tab
34
+ sootsim reload --sim <id> # reload whatever it is showing
35
+ sootsim close <id> # dispose it when you are done
36
+ ```
37
+
38
+ every `--new` spawns another browser tree, and each abandoned tab holds a
39
+ renderer process for the life of the browser. these land in somebody's actual
40
+ desktop Chrome. a run that opened one per app left 14 renderers against 2 live
41
+ sims on a shared machine and the owner noticed before we did.
33
42
  - **sharing a machine with other agents? open ONE sim with `--new`, pin it, and
34
- pass `--sim <id>` from then on.** this is the case `--new` exists for. an
35
- unpinned command has no sim of its own, so it falls back to whatever sim is
43
+ pass `--sim <id>` from then on.** that single `--new` is the only one you get.
44
+ an unpinned command has no sim of its own, so it falls back to whatever sim is
36
45
  currently PRIMARY — and that can be another agent's live sim, mid-run. a plain
37
46
  `sootsim open <port>` from an unpinned CLI will happily retarget somebody
38
47
  else's sim to your port and take over their session; the warning it prints is
@@ -47,8 +56,11 @@ debugging or `/sootsim-test` for writing tests.
47
56
  sootsim describe --sim <id> # name it on every command after that
48
57
  ```
49
58
 
50
- still close it when you are done. the reason not to pass `--new` on every
51
- command is unchanged: each one spawns another browser tree.
59
+ `--new`, `--profile`, and `--ephemeral` are rejected outright when combined
60
+ with `--sim`, so if a command needs one of them you are starting a second sim
61
+ whether you meant to or not. if yours died, run `sootsim list --all` and adopt
62
+ an open unlocked sim with `sootsim use <id>` before you consider opening one.
63
+ always close yours when you finish.
52
64
  - **never retry-loop `sootsim open` against a wedged or slow stack.** a
53
65
  connect-timeout ("timed out waiting for opened sim to connect") means STOP and
54
66
  diagnose — not retry. each retried open spawns a new browser tree (a
@@ -1,4 +0,0 @@
1
- /*! sootsim v0.1.303 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import { createRequire as __sootsimCreateRequire } from 'node:module'
3
- var require = __sootsimCreateRequire(import.meta.url)
4
- import{d as p}from"./chunk-E3POCZJT.js";import"./chunk-XAHT6BRW.js";import"./chunk-BQLQCPXE.js";import{existsSync as h,mkdirSync as d,readFileSync as y,writeFileSync as g}from"node:fs";import{join as m}from"node:path";import{gzipSync as w}from"node:zlib";function f(t,e){let o=t.indexOf(`--${e}`);if(o!==-1&&o+1<t.length)return t[o+1];let i=t.find(r=>r.startsWith(`--${e}=`));return i?i.slice(e.length+3):void 0}async function S(t){let e=f(t,"wire")||"",o=f(t,"out-dir"),i=f(t,"manifest");if((!o||!i)&&(console.error(" usage: sootsim app-fonts stage --wire <wire> --out-dir <dir> --manifest <path>"),process.exit(1)),!e.trim()){console.log("no app fonts declared \u2014 nothing to stage");return}d(o,{recursive:!0});let r=await p(e,{onStaged:({url:n,byteLength:s})=>console.log(` staged app font: ${n} (${(s/1024).toFixed(1)} KiB)`),onError:(n,s)=>console.error(` warning: failed to fetch app font ${n}: ${s instanceof Error?s.message:s}`)}),a=[];for(let n of r){let s=w(n.bytes);g(m(o,n.urlhash),s),a.push({url:n.url,urlhash:n.urlhash,contentType:n.contentType,encoding:"gzip",sizeBytes:s.length,rawBytes:n.bytes.byteLength})}let c=[];if(h(i))try{let n=JSON.parse(y(i,"utf8"));Array.isArray(n)&&(c=n)}catch{}let u=new Set(c.map(n=>n.urlhash)),l=[...c,...a.filter(n=>!u.has(n.urlhash))];g(i,JSON.stringify(l)),console.log(`staged ${a.length} app font(s); manifest now has ${l.length} file(s)`)}async function E(t){let e=t[0];if(e==="stage"){await S(t.slice(1));return}console.error(` unknown app-fonts subcommand: ${e??"(none)"}`),console.error(" available: stage"),process.exit(1)}export{E as runAppFonts};
@@ -1,4 +0,0 @@
1
- /*! sootsim v0.1.303 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import { createRequire as __sootsimCreateRequire } from 'node:module'
3
- var require = __sootsimCreateRequire(import.meta.url)
4
- import{a as S}from"./chunk-XAHT6BRW.js";import{createHash as W}from"node:crypto";import w from"node:fs";import{createRequire as v}from"node:module";import c from"node:path";var D=/\.(ttf|otf|woff|woff2)$/i;function _(t,n,o){if(!t||typeof t!="object")return;let r=t.fonts;if(Array.isArray(r))for(let s of r)typeof s=="string"&&o.push(s);let i=t[n];if(!i||typeof i!="object")return;let e=i.fonts;if(Array.isArray(e))for(let s of e){if(typeof s=="string"){o.push(s);continue}if(!s||typeof s!="object")continue;let l=s.fontDefinitions;if(Array.isArray(l))for(let m of l){if(!m||typeof m!="object")continue;let h=m.path;typeof h=="string"&&o.push(h)}}}function $(t,n){if(!Array.isArray(t))return[];let o=[];for(let r of t){let i=Array.isArray(r)?r[0]:r;if(typeof i!="string"||!/(^|\/)expo-font$/.test(i))continue;let e=Array.isArray(r)?r[1]??{}:{};_(e,n,o)}return o}function N(t){return t.slice(t.lastIndexOf("/")+1).replace(D,"")}function T(t,n){let o=n.replace(/\/+$/,""),r=new Set,i=[];for(let e of t){let s=e.replace(/^\.\//,"").replace(/^\/+/,"");!s||r.has(s)||(r.add(s),D.test(s)&&i.push({family:N(s),url:`${o}/assets/${s}`}))}return i}var O=v(import.meta.url);function E(t){try{let n=new URL(t),o=n.hostname.replace(/^\[|\]$/g,"").toLowerCase();if(o==="localhost"||o.endsWith(".localhost")||o==="0.0.0.0"||o==="::1"||/^127(?:\.\d{1,3}){3}$/.test(o))return n.origin}catch{}return null}function q(t){try{return w.statSync(c.join(t,"package.json")).isFile()}catch{return!1}}function Q(t){let n=E(t.bundleUrl);if(!n)return[];let o=t.repoDir||process.env.CONTRAST_REPO_DIR,r=o?c.resolve(o):process.cwd();if(!o&&!q(r))return[];let i=S(r,{trustStart:t.exactProjectDir}),e=i?.appDir??null;if(!e)return[];let s=(t.platform||process.env.CONTRAST_PLATFORM||"ios").toLowerCase(),l=null;if(o||i)try{let f=O.resolve("@expo/config",{paths:[e,r]}),g=O(f);typeof g.getConfig=="function"&&(l=g.getConfig)}catch{}let m=[];if(l)try{let f=l(e,{skipSDKVersionRequirement:!0});if(f?.exp&&typeof f.exp=="object"){let g=f.exp.plugins;Array.isArray(g)&&(m=g)}}catch{}let h=$(m,s==="android"?"android":"ios").slice(0,256),I=Date.now(),P=0,A=(f,g)=>{let d=[{dir:f,depth:0}],p=0;for(;p<d.length&&P<64&&h.length<256&&Date.now()-I<50;){let a=d[p++];if(!a)continue;P++;let y;try{y=w.readdirSync(a.dir,{withFileTypes:!0})}catch{continue}for(let u of y){if(u.isSymbolicLink())continue;let x=c.join(a.dir,u.name);if(u.isDirectory()){a.depth<4&&d.length<64&&!u.name.startsWith(".")&&d.push({dir:x,depth:a.depth+1});continue}if(!u.isFile()||!/\.(ttf|otf|woff|woff2)$/i.test(u.name))continue;let j=c.relative(f,x).split(c.sep).join("/");if(h.push(c.posix.join(g,j)),h.length>=256)break}}};try{let f=JSON.parse(w.readFileSync(c.join(e,"package.json"),"utf8")),g=new Set;for(let p of["dependencies","devDependencies","optionalDependencies"]){let a=Reflect.get(f,p);if(!(!a||typeof a!="object"))for(let y of Object.keys(a))g.add(y)}let d=[...g].filter(p=>p==="react-native-vector-icons"||p.startsWith("@react-native-vector-icons/"));if(d.length>0){let p=Reflect.get(f,"reactNativeVectorIcons"),a=p&&typeof p=="object"?Reflect.get(p,"fontDir"):null,y=typeof a=="string"?a:"rnvi-fonts",u=c.resolve(e,y),x=c.relative(e,u);!x.startsWith("..")&&!c.isAbsolute(x)&&A(u,x.split(c.sep).join("/"));let j=v(c.join(e,"package.json"));for(let F of d)try{let b=j.resolve(F),L=F.split("/").join(c.sep),R=`${c.sep}node_modules${c.sep}${L}${c.sep}`,k=b.lastIndexOf(R);if(k===-1)continue;let U=b.slice(0,k+R.length-1),C=F==="react-native-vector-icons"?"Fonts":"fonts";A(c.join(U,C),c.posix.join("node_modules",F,C))}catch{}}}catch{}try{let f=c.join(e,"react-native.config.js");if(w.statSync(f).isFile()){let d=v(c.join(e,"package.json"))(f),p=d&&typeof d=="object"?Reflect.get(d,"assets"):null;if(Array.isArray(p))for(let a of p){if(typeof a!="string")continue;let y=c.resolve(e,a),u=c.relative(e,y);u.startsWith("..")||c.isAbsolute(u)||A(y,u.split(c.sep).join("/"))}}}catch{}return T(h,n)}async function Y(t,n){let o=E(t);if(!o)return null;let r=new AbortController,i=setTimeout(()=>r.abort(),n?.timeoutMs??1500);try{let e=await fetch(`${o}/`,{headers:{"expo-platform":"ios",Accept:"application/json"},signal:r.signal});if(!e.ok)return null;let l=(await e.json())?.extra?.expoClient?._internal?.projectRoot;return typeof l=="string"&&l.length>0?l:null}catch{return null}finally{clearTimeout(i)}}function Z(t){return t.map(n=>`${encodeURIComponent(n.url)}::${encodeURIComponent(n.family)}`).join(",")}function M(t){let n=[];for(let o of t.split(",")){let r=o.trim();if(!r)continue;let i=r.indexOf("::"),e=i===-1?r:r.slice(0,i);try{n.push(decodeURIComponent(e))}catch{n.push(e)}}return n.filter(Boolean)}function G(t){let n=(t.split("?")[0].split(".").pop()||"").toLowerCase();return n==="ttf"?"font/ttf":n==="otf"?"font/otf":n==="woff"?"font/woff":n==="woff2"?"font/woff2":"application/octet-stream"}async function tt(t,n){let o=[],r=new Set;for(let i of M(t)){let e=W("sha256").update(i).digest("hex");if(!r.has(e)){r.add(e);try{let s=await fetch(i);if(!s.ok){n?.onError?.(i,new Error(`${s.status} ${s.statusText}`));continue}let l=new Uint8Array(await s.arrayBuffer());o.push({url:i,urlhash:e,contentType:G(i),bytes:l}),n?.onStaged?.({url:i,byteLength:l.byteLength})}catch(s){n?.onError?.(i,s)}}}return o}export{Q as a,Y as b,Z as c,tt as d};