six-degrees 0.2.1 → 0.3.0-beta.1

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 (470) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +10 -0
  3. package/.next/standalone/.next/build-manifest.json +3 -3
  4. package/.next/standalone/.next/prerender-manifest.json +39 -11
  5. package/.next/standalone/.next/required-server-files.json +4 -1
  6. package/.next/standalone/.next/routes-manifest.json +60 -0
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.rsc +10 -10
  18. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +10 -10
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +9 -9
  20. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/api/admin-delete/route.js +3 -2
  22. package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/admin-update/route.js +3 -2
  24. package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/bridges/route.js +3 -2
  26. package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/bulk-import/route.js +3 -2
  28. package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/company-scores/legacy/route/app-paths-manifest.json +3 -0
  30. package/.next/standalone/.next/server/app/api/company-scores/legacy/route/build-manifest.json +12 -0
  31. package/.next/standalone/.next/server/app/api/company-scores/legacy/route/server-reference-manifest.json +4 -0
  32. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js +9 -0
  33. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.map +5 -0
  34. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -0
  35. package/.next/standalone/.next/server/app/api/company-scores/legacy/route_client-reference-manifest.js +3 -0
  36. package/.next/standalone/.next/server/app/api/company-scores/route.js +3 -2
  37. package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/connections/route.js +3 -2
  39. package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/data/export/route/app-paths-manifest.json +3 -0
  41. package/.next/standalone/.next/server/app/api/data/export/route/build-manifest.json +12 -0
  42. package/.next/standalone/.next/server/app/api/data/export/route/server-reference-manifest.json +4 -0
  43. package/.next/standalone/.next/server/app/api/data/export/route.js +7 -0
  44. package/.next/standalone/.next/server/app/api/data/export/route.js.map +5 -0
  45. package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -0
  46. package/.next/standalone/.next/server/app/api/data/export/route_client-reference-manifest.js +3 -0
  47. package/.next/standalone/.next/server/app/api/data/import/route/app-paths-manifest.json +3 -0
  48. package/.next/standalone/.next/server/app/api/data/import/route/build-manifest.json +12 -0
  49. package/.next/standalone/.next/server/app/api/data/import/route/server-reference-manifest.json +4 -0
  50. package/.next/standalone/.next/server/app/api/data/import/route.js +8 -0
  51. package/.next/standalone/.next/server/app/api/data/import/route.js.map +5 -0
  52. package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -0
  53. package/.next/standalone/.next/server/app/api/data/import/route_client-reference-manifest.js +3 -0
  54. package/.next/standalone/.next/server/app/api/data/restart/route/app-paths-manifest.json +3 -0
  55. package/.next/standalone/.next/server/app/api/data/restart/route/build-manifest.json +12 -0
  56. package/.next/standalone/.next/server/app/api/data/restart/route/server-reference-manifest.json +4 -0
  57. package/.next/standalone/.next/server/app/api/data/restart/route.js +8 -0
  58. package/.next/standalone/.next/server/app/api/data/restart/route.js.map +5 -0
  59. package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -0
  60. package/.next/standalone/.next/server/app/api/data/restart/route_client-reference-manifest.js +3 -0
  61. package/.next/standalone/.next/server/app/api/data/reveal/route/app-paths-manifest.json +3 -0
  62. package/.next/standalone/.next/server/app/api/data/reveal/route/build-manifest.json +12 -0
  63. package/.next/standalone/.next/server/app/api/data/reveal/route/server-reference-manifest.json +4 -0
  64. package/.next/standalone/.next/server/app/api/data/reveal/route.js +7 -0
  65. package/.next/standalone/.next/server/app/api/data/reveal/route.js.map +5 -0
  66. package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -0
  67. package/.next/standalone/.next/server/app/api/data/reveal/route_client-reference-manifest.js +3 -0
  68. package/.next/standalone/.next/server/app/api/data/route/app-paths-manifest.json +3 -0
  69. package/.next/standalone/.next/server/app/api/data/route/build-manifest.json +12 -0
  70. package/.next/standalone/.next/server/app/api/data/route/server-reference-manifest.json +4 -0
  71. package/.next/standalone/.next/server/app/api/data/route.js +7 -0
  72. package/.next/standalone/.next/server/app/api/data/route.js.map +5 -0
  73. package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -0
  74. package/.next/standalone/.next/server/app/api/data/route_client-reference-manifest.js +3 -0
  75. package/.next/standalone/.next/server/app/api/delete-cluster/route.js +3 -2
  76. package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
  77. package/.next/standalone/.next/server/app/api/ingest/route.js +2 -2
  78. package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
  79. package/.next/standalone/.next/server/app/api/network/route.js +3 -2
  80. package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
  81. package/.next/standalone/.next/server/app/api/notifications/route.js +3 -2
  82. package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  83. package/.next/standalone/.next/server/app/api/outreach/route.js +3 -2
  84. package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
  85. package/.next/standalone/.next/server/app/api/queue/route.js +3 -2
  86. package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
  87. package/.next/standalone/.next/server/app/api/scraper/route.js +3 -2
  88. package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
  89. package/.next/standalone/.next/server/app/api/settings/route/app-paths-manifest.json +3 -0
  90. package/.next/standalone/.next/server/app/api/settings/route/build-manifest.json +12 -0
  91. package/.next/standalone/.next/server/app/api/settings/route/server-reference-manifest.json +4 -0
  92. package/.next/standalone/.next/server/app/api/settings/route.js +9 -0
  93. package/.next/standalone/.next/server/app/api/settings/route.js.map +5 -0
  94. package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -0
  95. package/.next/standalone/.next/server/app/api/settings/route_client-reference-manifest.js +3 -0
  96. package/.next/standalone/.next/server/app/api/settings/sector-preview/route/app-paths-manifest.json +3 -0
  97. package/.next/standalone/.next/server/app/api/settings/sector-preview/route/build-manifest.json +12 -0
  98. package/.next/standalone/.next/server/app/api/settings/sector-preview/route/server-reference-manifest.json +4 -0
  99. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js +8 -0
  100. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.map +5 -0
  101. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -0
  102. package/.next/standalone/.next/server/app/api/settings/sector-preview/route_client-reference-manifest.js +3 -0
  103. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route/app-paths-manifest.json +3 -0
  104. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route/build-manifest.json +12 -0
  105. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route/server-reference-manifest.json +4 -0
  106. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js +8 -0
  107. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.map +5 -0
  108. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -0
  109. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route_client-reference-manifest.js +3 -0
  110. package/.next/standalone/.next/server/app/api/setup-profile/route.js +3 -2
  111. package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
  112. package/.next/standalone/.next/server/app/api/unlock/route.js +3 -2
  113. package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
  114. package/.next/standalone/.next/server/app/api/update/route.js +1 -1
  115. package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  116. package/.next/standalone/.next/server/app/api/update-images/route.js +3 -2
  117. package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
  118. package/.next/standalone/.next/server/app/api/users/route.js +3 -2
  119. package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
  120. package/.next/standalone/.next/server/app/avatars/[file]/route.js +2 -1
  121. package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
  122. package/.next/standalone/.next/server/app/favicon.ico/route.js +2 -1
  123. package/.next/standalone/.next/server/app/favicon.ico/route.js.nft.json +1 -1
  124. package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
  125. package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
  126. package/.next/standalone/.next/server/app/import.html +1 -1
  127. package/.next/standalone/.next/server/app/import.rsc +12 -12
  128. package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +12 -12
  129. package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +1 -1
  130. package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +11 -11
  131. package/.next/standalone/.next/server/app/index.html +1 -1
  132. package/.next/standalone/.next/server/app/index.rsc +12 -12
  133. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +11 -11
  134. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +12 -12
  135. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  136. package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
  137. package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
  138. package/.next/standalone/.next/server/app/launch.html +2 -2
  139. package/.next/standalone/.next/server/app/launch.rsc +12 -12
  140. package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +12 -12
  141. package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +1 -1
  142. package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +11 -11
  143. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  144. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  145. package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
  146. package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
  147. package/.next/standalone/.next/server/app/paths.html +1 -1
  148. package/.next/standalone/.next/server/app/paths.rsc +12 -12
  149. package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +12 -12
  150. package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +1 -1
  151. package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +11 -11
  152. package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
  153. package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
  154. package/.next/standalone/.next/server/app/profile.html +1 -1
  155. package/.next/standalone/.next/server/app/profile.rsc +12 -12
  156. package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +12 -12
  157. package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +1 -1
  158. package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +11 -11
  159. package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
  160. package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
  161. package/.next/standalone/.next/server/app/queue.html +1 -1
  162. package/.next/standalone/.next/server/app/queue.rsc +12 -12
  163. package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +12 -12
  164. package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +1 -1
  165. package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +11 -11
  166. package/.next/standalone/.next/server/app/settings/page/app-paths-manifest.json +3 -0
  167. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +20 -0
  168. package/.next/standalone/.next/server/app/settings/page/next-font-manifest.json +11 -0
  169. package/.next/standalone/.next/server/app/settings/page/react-loadable-manifest.json +1 -0
  170. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -0
  171. package/.next/standalone/.next/server/app/settings/page.js +13 -0
  172. package/.next/standalone/.next/server/app/settings/page.js.map +5 -0
  173. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -0
  174. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +3 -0
  175. package/.next/standalone/.next/server/app/settings.html +1 -0
  176. package/.next/standalone/.next/server/app/settings.meta +26 -0
  177. package/.next/standalone/.next/server/app/settings.rsc +24 -0
  178. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +24 -0
  179. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +4 -0
  180. package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +28 -0
  181. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  182. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  183. package/.next/standalone/.next/server/app/setup.html +1 -1
  184. package/.next/standalone/.next/server/app/setup.rsc +12 -12
  185. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +12 -12
  186. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +1 -1
  187. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +11 -11
  188. package/.next/standalone/.next/server/app-paths-manifest.json +10 -0
  189. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-rnla2._.js +3 -0
  190. package/.next/standalone/.next/server/chunks/[root-of-the-server]__00la_y0._.js +3 -0
  191. package/.next/standalone/.next/server/chunks/[root-of-the-server]__07t47cm._.js +3 -0
  192. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cj5jqc._.js +3 -0
  193. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e0fei3._.js +3 -0
  194. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0i5hdu4._.js +3 -0
  195. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljcioj._.js +3 -0
  196. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljnyho._.js +3 -0
  197. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o5-tcg._.js +3 -0
  198. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qrnous._.js +3 -0
  199. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +3 -0
  200. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0uag-k5._.js +8 -0
  201. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0um1uey._.js +3 -0
  202. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wmexuc._.js +3 -0
  203. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xu34sz._.js +3 -0
  204. package/.next/standalone/.next/server/chunks/[root-of-the-server]__133knx1._.js +3 -0
  205. package/.next/standalone/.next/server/chunks/[root-of-the-server]__14ou7j5._.js +3 -0
  206. package/.next/standalone/.next/server/chunks/[root-of-the-server]__15-17hl._.js +3 -0
  207. package/.next/standalone/.next/server/chunks/[root-of-the-server]__16iu0hv._.js +3 -0
  208. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19b7mig._.js +3 -0
  209. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bbcwub._.js +8 -0
  210. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1eris29._.js +3 -0
  211. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1gjo_cq._.js +3 -0
  212. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +3 -0
  213. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qcm08_._.js +3 -0
  214. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rphsdk._.js +3 -0
  215. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1t3rugi._.js +3 -0
  216. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1w25q0m._.js +3 -0
  217. package/.next/standalone/.next/server/chunks/_072jl0y._.js +3 -0
  218. package/.next/standalone/.next/server/chunks/_0lw8_i0._.js +3 -0
  219. package/.next/standalone/.next/server/chunks/_1_fynia._.js +21 -3
  220. package/.next/standalone/.next/server/chunks/_1fn30_l._.js +3 -0
  221. package/.next/standalone/.next/server/chunks/_1itd1o_._.js +2 -1
  222. package/.next/standalone/.next/server/chunks/_1iwkh8t._.js +1 -1
  223. package/.next/standalone/.next/server/chunks/_1mxn_lb._.js +3 -0
  224. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_company-scores_legacy_route_actions_1ncspu_.js +3 -0
  225. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_export_route_actions_1lkimty.js +3 -0
  226. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_import_route_actions_1eji0kd.js +3 -0
  227. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_restart_route_actions_00_-u6o.js +3 -0
  228. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_reveal_route_actions_0-moqtn.js +3 -0
  229. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_route_actions_0j0w5ep.js +3 -0
  230. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_settings_route_actions_1bpm9ac.js +3 -0
  231. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_settings_sector-preview_route_actions_0w79454.js +3 -0
  232. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_settings_sector-suggestions_route_actions_1eoqugp.js +3 -0
  233. package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +155 -0
  234. package/.next/standalone/.next/server/chunks/{lib_rpc_0mc73lk.js → lib_rpc_00tx2sm.js} +1 -1
  235. package/.next/standalone/.next/server/chunks/lib_rpc_02-qa4d.js +3 -0
  236. package/.next/standalone/.next/server/chunks/lib_rpc_02ndsg2.js +3 -0
  237. package/.next/standalone/.next/server/chunks/lib_rpc_08q5vp6.js +3 -0
  238. package/.next/standalone/.next/server/chunks/lib_rpc_0_8i3ft.js +3 -0
  239. package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +5 -2
  240. package/.next/standalone/.next/server/chunks/lib_rpc_0huvw-3.js +3 -0
  241. package/.next/standalone/.next/server/chunks/lib_rpc_0ip2vdh.js +3 -0
  242. package/.next/standalone/.next/server/chunks/lib_rpc_0jzisgh.js +3 -0
  243. package/.next/standalone/.next/server/chunks/lib_rpc_0yi9xvd.js +3 -0
  244. package/.next/standalone/.next/server/chunks/lib_rpc_1jyhd0o.js +3 -0
  245. package/.next/standalone/.next/server/chunks/lib_rpc_1kn59-i.js +3 -0
  246. package/.next/standalone/.next/server/chunks/lib_rpc_1q2jkza.js +3 -0
  247. package/.next/standalone/.next/server/chunks/lib_rpc_1vpm1sk.js +3 -0
  248. package/.next/standalone/.next/server/chunks/lib_rpc_1x6p_31.js +3 -0
  249. package/.next/standalone/.next/server/chunks/node_modules_next_1zc5q0a._.js +158 -0
  250. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0mocm6d._.js → [root-of-the-server]__0an6blh._.js} +3 -3
  251. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m0255u._.js +3 -0
  252. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0bipr5s._.js → [root-of-the-server]__1z1j3ek._.js} +2 -2
  253. package/.next/standalone/.next/server/chunks/ssr/_07u8byv._.js +3 -0
  254. package/.next/standalone/.next/server/chunks/ssr/_08r_mlf._.js +3 -0
  255. package/.next/standalone/.next/server/chunks/ssr/{_0ggabjc._.js → _0942v8n._.js} +1 -1
  256. package/.next/standalone/.next/server/chunks/ssr/{_1djpk8t._.js → _0hl_t7r._.js} +2 -2
  257. package/.next/standalone/.next/server/chunks/ssr/_0tb83dw._.js +132 -0
  258. package/.next/standalone/.next/server/chunks/ssr/_1-87bs1._.js +3 -0
  259. package/.next/standalone/.next/server/chunks/ssr/_17gui6a._.js +1 -1
  260. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_settings_page_actions_11x4dq5.js +3 -0
  261. package/.next/standalone/.next/server/chunks/ssr/app_launch_page_19t0dyd.js +2 -2
  262. package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +4 -4
  263. package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
  264. package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +5 -5
  265. package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +3 -0
  266. package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +2 -2
  267. package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +1 -1
  268. package/.next/standalone/.next/server/chunks/ssr/node_modules_1f8t_rp._.js +23 -0
  269. package/.next/standalone/.next/server/chunks/ssr/node_modules_1nkgb45._.js +2 -2
  270. package/.next/standalone/.next/server/chunks/ssr/{node_modules_01xbj_9._.js → node_modules_1wax83z._.js} +3 -3
  271. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_00nap8h._.js → node_modules_next_dist_1bunoni._.js} +2 -2
  272. package/.next/standalone/.next/server/edge/chunks/[root-of-the-server]__0pco7a6._.js +2 -2
  273. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  274. package/.next/standalone/.next/server/middleware-manifest.json +15 -7
  275. package/.next/standalone/.next/server/next-font-manifest.js +1 -1
  276. package/.next/standalone/.next/server/next-font-manifest.json +4 -0
  277. package/.next/standalone/.next/server/pages/404.html +1 -1
  278. package/.next/standalone/.next/server/pages/500.html +1 -1
  279. package/.next/standalone/.next/server/prefetch-hints.json +14 -0
  280. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  281. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  282. package/.next/standalone/.next/static/chunks/06x3jiiz96m5t.js +1 -0
  283. package/.next/standalone/.next/static/chunks/{34at5d2ifl62i.js → 0ngx9s6-zgshe.js} +5 -5
  284. package/.next/standalone/.next/static/chunks/0q-_khndfxubv.js +2 -0
  285. package/.next/standalone/.next/static/chunks/1c79veqedvubt.js +1 -0
  286. package/.next/standalone/.next/static/chunks/1chp3nx45meg5.js +44 -0
  287. package/.next/standalone/.next/static/chunks/1irqj0lf-fu3d.js +1 -0
  288. package/.next/standalone/.next/static/chunks/1uadqylrod5_v.js +1 -0
  289. package/.next/standalone/.next/static/chunks/23u0zclgwpc4d.js +1 -0
  290. package/.next/standalone/.next/static/chunks/289ktj97di6zg.js +1 -0
  291. package/.next/standalone/.next/static/chunks/2b3lmf8e6uj8m.js +16 -0
  292. package/.next/standalone/.next/static/chunks/2jz311xl1pp_8.js +1 -0
  293. package/.next/standalone/.next/static/chunks/3npq5yd7c4dmn.js +2 -0
  294. package/.next/standalone/.next/static/frfHFnRYTA0Wjrw8oZJtY/_clientMiddlewareManifest.js +14 -0
  295. package/.next/standalone/CHANGELOG.md +284 -0
  296. package/.next/standalone/CONTRIBUTING.md +5 -2
  297. package/.next/standalone/README.md +138 -30
  298. package/.next/standalone/SECURITY.md +117 -10
  299. package/.next/standalone/app/api/company-scores/legacy/route.js +32 -0
  300. package/.next/standalone/app/api/company-scores/route.js +20 -11
  301. package/.next/standalone/app/api/data/export/route.js +49 -0
  302. package/.next/standalone/app/api/data/import/route.js +83 -0
  303. package/.next/standalone/app/api/data/restart/route.js +37 -0
  304. package/.next/standalone/app/api/data/reveal/route.js +14 -0
  305. package/.next/standalone/app/api/data/route.js +39 -0
  306. package/.next/standalone/app/api/ingest/route.js +15 -47
  307. package/.next/standalone/app/api/scraper/route.js +264 -125
  308. package/.next/standalone/app/api/settings/route.js +58 -0
  309. package/.next/standalone/app/api/settings/sector-preview/route.js +38 -0
  310. package/.next/standalone/app/api/settings/sector-suggestions/route.js +20 -0
  311. package/.next/standalone/app/api/update/route.js +188 -23
  312. package/.next/standalone/app/avatars/[file]/route.js +4 -2
  313. package/.next/standalone/app/components/CompanyScores.js +28 -7
  314. package/.next/standalone/app/components/EmptyState.js +8 -0
  315. package/.next/standalone/app/components/ForceGraph.js +39 -35
  316. package/.next/standalone/app/components/LegacyScoresCard.js +129 -0
  317. package/.next/standalone/app/components/Sidebar.js +11 -10
  318. package/.next/standalone/app/components/UpdatePanel.js +306 -119
  319. package/.next/standalone/app/components/UserProvider.js +4 -3
  320. package/.next/standalone/app/components/settings/DataSection.js +437 -0
  321. package/.next/standalone/app/components/settings/SectorSection.js +445 -0
  322. package/.next/standalone/app/components/ui.js +62 -0
  323. package/.next/standalone/app/launch/page.js +30 -20
  324. package/.next/standalone/app/page.js +13 -0
  325. package/.next/standalone/app/paths/page.js +12 -4
  326. package/.next/standalone/app/profile/page.js +41 -8
  327. package/.next/standalone/app/queue/page.js +8 -2
  328. package/.next/standalone/app/settings/page.js +96 -0
  329. package/.next/standalone/app/setup/page.js +32 -16
  330. package/.next/standalone/bin/args.mjs +44 -0
  331. package/.next/standalone/bin/six-degrees.mjs +9 -15
  332. package/.next/standalone/lib/companies.js +44 -119
  333. package/.next/standalone/lib/csv.js +8 -0
  334. package/.next/standalone/lib/data-export.js +170 -0
  335. package/.next/standalone/lib/data-folder.js +238 -0
  336. package/.next/standalone/lib/data-import.js +878 -0
  337. package/.next/standalone/lib/db-client.js +48 -3
  338. package/.next/standalone/lib/durable.js +73 -0
  339. package/.next/standalone/lib/gate.js +90 -1
  340. package/.next/standalone/lib/ingest.js +40 -0
  341. package/.next/standalone/lib/legacy-offer.js +185 -0
  342. package/.next/standalone/lib/legacy-scores.js +77 -0
  343. package/.next/standalone/lib/linkedin-limits.js +124 -1
  344. package/.next/standalone/lib/rpc.js +121 -13
  345. package/.next/standalone/lib/sample-stats.js +16 -0
  346. package/.next/standalone/lib/scan-progress.js +13 -0
  347. package/.next/standalone/lib/scan-state.js +53 -0
  348. package/.next/standalone/lib/scanner-python.js +685 -0
  349. package/.next/standalone/lib/scanner-setup.js +104 -0
  350. package/.next/standalone/lib/scoring.js +747 -138
  351. package/.next/standalone/lib/scraper-client.js +9 -5
  352. package/.next/standalone/lib/sector-directory.js +1532 -0
  353. package/.next/standalone/lib/sector-focus.js +163 -0
  354. package/.next/standalone/lib/sector-labels.js +81 -0
  355. package/.next/standalone/lib/settings-effects.js +59 -0
  356. package/.next/standalone/lib/settings.js +83 -0
  357. package/.next/standalone/lib/updater-job.js +838 -0
  358. package/.next/standalone/lib/updater.js +494 -0
  359. package/.next/standalone/middleware.js +25 -28
  360. package/.next/standalone/next.config.mjs +19 -3
  361. package/.next/standalone/package-lock.json +2 -2
  362. package/.next/standalone/package.json +1 -2
  363. package/.next/standalone/scripts/apply-update.sh +482 -0
  364. package/.next/standalone/scripts/build-app.mjs +251 -9
  365. package/.next/standalone/scripts/requirements.txt +106 -4
  366. package/.next/standalone/scripts/test-release-server.mjs +143 -0
  367. package/.next/standalone/server.js +1 -1
  368. package/.next/static/chunks/06x3jiiz96m5t.js +1 -0
  369. package/.next/static/chunks/{34at5d2ifl62i.js → 0ngx9s6-zgshe.js} +5 -5
  370. package/.next/static/chunks/0q-_khndfxubv.js +2 -0
  371. package/.next/static/chunks/1c79veqedvubt.js +1 -0
  372. package/.next/static/chunks/1chp3nx45meg5.js +44 -0
  373. package/.next/static/chunks/1irqj0lf-fu3d.js +1 -0
  374. package/.next/static/chunks/1uadqylrod5_v.js +1 -0
  375. package/.next/static/chunks/23u0zclgwpc4d.js +1 -0
  376. package/.next/static/chunks/289ktj97di6zg.js +1 -0
  377. package/.next/static/chunks/2b3lmf8e6uj8m.js +16 -0
  378. package/.next/static/chunks/2jz311xl1pp_8.js +1 -0
  379. package/.next/static/chunks/3npq5yd7c4dmn.js +2 -0
  380. package/.next/static/frfHFnRYTA0Wjrw8oZJtY/_clientMiddlewareManifest.js +14 -0
  381. package/CHANGELOG.md +284 -0
  382. package/README.md +138 -30
  383. package/SECURITY.md +117 -10
  384. package/bin/args.mjs +44 -0
  385. package/bin/six-degrees.mjs +9 -15
  386. package/lib/companies.js +44 -119
  387. package/lib/csv.js +8 -0
  388. package/lib/data-export.js +170 -0
  389. package/lib/data-folder.js +238 -0
  390. package/lib/data-import.js +878 -0
  391. package/lib/db-client.js +48 -3
  392. package/lib/durable.js +73 -0
  393. package/lib/gate.js +90 -1
  394. package/lib/ingest.js +40 -0
  395. package/lib/legacy-offer.js +185 -0
  396. package/lib/legacy-scores.js +77 -0
  397. package/lib/linkedin-limits.js +124 -1
  398. package/lib/rpc.js +121 -13
  399. package/lib/sample-stats.js +16 -0
  400. package/lib/scan-progress.js +13 -0
  401. package/lib/scan-state.js +53 -0
  402. package/lib/scanner-python.js +685 -0
  403. package/lib/scanner-setup.js +104 -0
  404. package/lib/scoring.js +747 -138
  405. package/lib/scraper-client.js +9 -5
  406. package/lib/sector-directory.js +1532 -0
  407. package/lib/sector-focus.js +163 -0
  408. package/lib/sector-labels.js +81 -0
  409. package/lib/settings-effects.js +59 -0
  410. package/lib/settings.js +83 -0
  411. package/lib/updater-job.js +838 -0
  412. package/lib/updater.js +494 -0
  413. package/package.json +1 -2
  414. package/scripts/requirements.txt +106 -4
  415. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jrmmj-._.js +0 -150
  416. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qx548g._.js +0 -151
  417. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1an3leo._.js +0 -150
  418. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1m8zedk._.js +0 -155
  419. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1zor0ys._.js +0 -3
  420. package/.next/standalone/.next/server/chunks/_0_rp-01._.js +0 -3
  421. package/.next/standalone/.next/server/chunks/_0hzdk79._.js +0 -3
  422. package/.next/standalone/.next/server/chunks/_0ow7u5r._.js +0 -3
  423. package/.next/standalone/.next/server/chunks/_0t0zc2d._.js +0 -3
  424. package/.next/standalone/.next/server/chunks/_0wa631d._.js +0 -3
  425. package/.next/standalone/.next/server/chunks/_13ccnao._.js +0 -3
  426. package/.next/standalone/.next/server/chunks/_13es063._.js +0 -3
  427. package/.next/standalone/.next/server/chunks/_142872j._.js +0 -3
  428. package/.next/standalone/.next/server/chunks/_18wsb_a._.js +0 -8
  429. package/.next/standalone/.next/server/chunks/_1e-4h5h._.js +0 -3
  430. package/.next/standalone/.next/server/chunks/_1gvvd7n._.js +0 -158
  431. package/.next/standalone/.next/server/chunks/_1moda66._.js +0 -3
  432. package/.next/standalone/.next/server/chunks/_1p6ghzv._.js +0 -3
  433. package/.next/standalone/.next/server/chunks/_1xmmi_v._.js +0 -3
  434. package/.next/standalone/.next/server/chunks/_1z-yrlf._.js +0 -3
  435. package/.next/standalone/.next/server/chunks/ssr/_0bf3x4q._.js +0 -3
  436. package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +0 -4
  437. package/.next/standalone/.next/static/ZGTiDrZ5NHGETgGdlhssu/_clientMiddlewareManifest.js +0 -6
  438. package/.next/standalone/.next/static/chunks/0jk64vo17avsb.js +0 -1
  439. package/.next/standalone/.next/static/chunks/0s65dsrthylo6.js +0 -2
  440. package/.next/standalone/.next/static/chunks/0uyfdhi8dek85.js +0 -16
  441. package/.next/standalone/.next/static/chunks/220-ne82aus0c.js +0 -44
  442. package/.next/standalone/.next/static/chunks/229haorf0f19b.js +0 -2
  443. package/.next/standalone/.next/static/chunks/2g54uafnwuakc.js +0 -1
  444. package/.next/standalone/.next/static/chunks/2spwqs6b6dx13.js +0 -1
  445. package/.next/standalone/.next/static/chunks/38dlepuubiox0.js +0 -1
  446. package/.next/standalone/scripts/score-connections.sql +0 -80
  447. package/.next/standalone/scripts/score_new_connections.sql +0 -77
  448. package/.next/standalone/site/app.js +0 -84
  449. package/.next/standalone/site/index.html +0 -221
  450. package/.next/standalone/site/style.css +0 -155
  451. package/.next/standalone/site/video/install-mac-poster.jpg +0 -0
  452. package/.next/standalone/site/video/install-mac.mp4 +0 -0
  453. package/.next/standalone/site/video/linux-poster.jpg +0 -0
  454. package/.next/standalone/site/video/linux.mp4 +0 -0
  455. package/.next/standalone/site/video/scan-poster.jpg +0 -0
  456. package/.next/standalone/site/video/scan.mp4 +0 -0
  457. package/.next/static/ZGTiDrZ5NHGETgGdlhssu/_clientMiddlewareManifest.js +0 -6
  458. package/.next/static/chunks/0jk64vo17avsb.js +0 -1
  459. package/.next/static/chunks/0s65dsrthylo6.js +0 -2
  460. package/.next/static/chunks/0uyfdhi8dek85.js +0 -16
  461. package/.next/static/chunks/220-ne82aus0c.js +0 -44
  462. package/.next/static/chunks/229haorf0f19b.js +0 -2
  463. package/.next/static/chunks/2g54uafnwuakc.js +0 -1
  464. package/.next/static/chunks/2spwqs6b6dx13.js +0 -1
  465. package/.next/static/chunks/38dlepuubiox0.js +0 -1
  466. package/scripts/score_new_connections.sql +0 -77
  467. /package/.next/standalone/.next/static/{ZGTiDrZ5NHGETgGdlhssu → frfHFnRYTA0Wjrw8oZJtY}/_buildManifest.js +0 -0
  468. /package/.next/standalone/.next/static/{ZGTiDrZ5NHGETgGdlhssu → frfHFnRYTA0Wjrw8oZJtY}/_ssgManifest.js +0 -0
  469. /package/.next/static/{ZGTiDrZ5NHGETgGdlhssu → frfHFnRYTA0Wjrw8oZJtY}/_buildManifest.js +0 -0
  470. /package/.next/static/{ZGTiDrZ5NHGETgGdlhssu → frfHFnRYTA0Wjrw8oZJtY}/_ssgManifest.js +0 -0
@@ -1,5 +1,5 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { existsSync, readFileSync } from 'node:fs';
2
+ import { existsSync, readFileSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { projectRoot, dataDir } from '../../../lib/paths';
5
5
  import { resolveProfile, networkCounts } from '../../../lib/profile';
@@ -7,6 +7,12 @@ import { scanProgress } from '../../../lib/scan-progress';
7
7
  import { linkedinState, writeLimits, liftCooldown } from '../../../lib/linkedin-limits';
8
8
  import { pausedList, readProgress, readUnclear } from '../../../lib/paused';
9
9
  import { getDb } from '../../../lib/db-client';
10
+ import { registerScanState } from '../../../lib/scan-state';
11
+ import { pendingImport } from '../../../lib/data-import';
12
+ import {
13
+ pythonLooker, thisHostKey, scannerCommand, installSteps, downloadedPython, downloadVerified,
14
+ placeDownloadedPython, sweepSetupLeftovers, megabytes, SETUP_WORK_PREFIX, ScannerSetupError,
15
+ } from '../../../lib/scanner-python';
10
16
 
11
17
  // The app runs the scraper itself.
12
18
  //
@@ -22,17 +28,19 @@ import { getDb } from '../../../lib/db-client';
22
28
 
23
29
  const MAX_LOG = 500;
24
30
 
25
- const state = {
31
+ // Registered in lib/scan-state.js so other routes can see whether a job runs.
32
+ const state = registerScanState({
26
33
  running: false,
27
34
  action: null,
28
35
  log: [],
29
36
  startedAt: null,
30
37
  exitCode: null,
31
38
  child: null,
39
+ abort: null, // a step that runs inside this server (the Python download): its AbortController
32
40
  stopping: false,
33
41
  stderrTail: [],
34
42
  failure: null, // the last lines of stderr from a run that failed — its reason
35
- };
43
+ });
36
44
 
37
45
  /** Stop the running job without orphaning the browser it opened.
38
46
  *
@@ -40,9 +48,16 @@ const state = {
40
48
  * signals Chrome too — killing only the Python process would leave a browser
41
49
  * window open with a live LinkedIn session in it. SIGTERM first, because the
42
50
  * scraper catches it and closes the browser itself; SIGKILL only if that is
43
- * ignored.
51
+ * ignored. A step running inside this server (the Python download) stops at
52
+ * once, and nothing it fetched is kept.
44
53
  */
45
54
  function stopChild() {
55
+ if (state.abort) {
56
+ state.stopping = true;
57
+ push('Stopping…');
58
+ state.abort.abort(new ScannerSetupError('Stopped.'));
59
+ return;
60
+ }
46
61
  const child = state.child;
47
62
  if (!child) return;
48
63
  state.stopping = true;
@@ -69,68 +84,25 @@ function push(line) {
69
84
  if (state.log.length > MAX_LOG) state.log.splice(0, state.log.length - MAX_LOG);
70
85
  }
71
86
 
72
- /** Run something short and tell me only whether it worked. */
73
- function probe(cmd, args, timeoutMs = 6000) {
74
- return new Promise((resolve) => {
75
- let done = false;
76
- const finish = (ok) => { if (!done) { done = true; resolve(ok); } };
77
- try {
78
- const c = spawn(cmd, args, { stdio: 'ignore' });
79
- const t = setTimeout(() => { try { c.kill(); } catch {} finish(false); }, timeoutMs);
80
- c.on('error', () => { clearTimeout(t); finish(false); });
81
- c.on('close', (code) => { clearTimeout(t); finish(code === 0); });
82
- } catch {
83
- finish(false);
84
- }
85
- });
86
- }
87
-
88
- // Load the compiled parts, not just the package names. `import PIL` succeeds even
89
- // when its C extension is built for the other chip; `from PIL import Image` is
90
- // what fails. The old check said "installed" and the scan then died on import —
91
- // which is how a Rosetta-launched app showed up (TRAPS §30).
92
- const IMPORTS = 'import requests; from PIL import Image; from playwright.sync_api import sync_playwright';
93
-
94
- /** The interpreter we install into and run from: our own, inside the data dir.
95
- *
96
- * Two traps make "just use python3" wrong, and between them they are why a
97
- * hand-run install could appear to succeed and still leave nothing working:
98
- *
99
- * 1. `python3` on PATH is often Homebrew's, while the interpreter that already
100
- * has Playwright is /usr/bin/python3. Installing into one and running the
101
- * other looks identical to a broken install.
102
- * 2. Homebrew and system Pythons are "externally managed" (PEP 668) and refuse
103
- * `pip install` outright, with an error most people read as a dead end.
104
- *
105
- * A virtualenv in the data directory has neither problem, is thrown away with
106
- * the rest of the folder, and never touches the machine's Python.
107
- */
108
- function venvPython() {
109
- const base = path.join(dataDir(), 'venv');
110
- return process.platform === 'win32'
111
- ? path.join(base, 'Scripts', 'python.exe')
112
- : path.join(base, 'bin', 'python');
113
- }
114
-
115
- /** A Python that can actually run the scraper right now, or null. */
116
- async function findUsablePython() {
117
- const venv = venvPython();
118
- if (existsSync(venv) && await probe(venv, ['-c', IMPORTS], 8000)) return venv;
119
- // Respect an existing working install rather than forcing a venv on someone
120
- // who already did this by hand.
121
- for (const c of ['/usr/bin/python3', 'python3', 'python']) {
122
- if (await probe(c, ['-c', IMPORTS], 8000)) return c;
123
- }
124
- return null;
125
- }
126
-
127
- /** Any Python at all — used to build the venv. */
128
- async function findAnyPython() {
129
- for (const c of ['python3', '/usr/bin/python3', 'python']) {
130
- if (await probe(c, ['--version'], 4000)) return c;
131
- }
132
- return null;
133
- }
87
+ // Which Python runs the scanner is decided in lib/scanner-python.js
88
+ // (choosePython): the Mac app's own first (SIX_DEGREES_PYTHON, from the app),
89
+ // then the scanner's environment in the data folder, then a Python on this
90
+ // computer that already has the packages.
91
+ //
92
+ // The environment is what Install builds, never the machine's own Python, for
93
+ // two reasons that between them are why a hand-run install could appear to
94
+ // succeed and still leave nothing working (TRAPS §14):
95
+ //
96
+ // 1. `python3` on PATH is often Homebrew's, while the interpreter that already
97
+ // has Playwright is /usr/bin/python3. Installing into one and running the
98
+ // other looks identical to a broken install.
99
+ // 2. Homebrew and system Pythons are "externally managed" (PEP 668) and refuse
100
+ // `pip install` outright, with an error most people read as a dead end.
101
+ //
102
+ // A virtualenv in the data directory has neither problem, is thrown away with
103
+ // the rest of the folder, and never touches the machine's Python. When the
104
+ // computer has no Python to build it from, "Set up the scanner" first downloads
105
+ // a standalone one into the data folder (a click, never by itself).
134
106
 
135
107
  /** Who the scraper found to be private, so the app can report honestly.
136
108
  *
@@ -155,17 +127,47 @@ function bridgeSkips() {
155
127
  }
156
128
 
157
129
  // The machine checks (Python, Chrome, signed in) spawn processes, so they are
158
- // cached for a few seconds. Everything about the running job — its log, how far
159
- // it has got — and the counts are read fresh on every call: the page polls
160
- // every 1.5 s, and a progress bar that moves every 4 s looks stuck.
130
+ // cached for a few seconds, and callers that arrive while a look is under way
131
+ // share it rather than each starting their own Pythons. Everything about the
132
+ // running job — its log, how far it has got — and the counts are read fresh on
133
+ // every call: the page polls every 1.5 s, and a progress bar that moves every
134
+ // 4 s looks stuck.
161
135
  let cached = { at: 0, value: null };
136
+ let looking = null;
137
+ let lookGeneration = 0;
162
138
 
163
- async function machineChecks() {
164
- if (Date.now() - cached.at < 4000 && cached.value) return cached.value;
139
+ /** Forget the last look, after something that changes its answer (a job starting or ending). */
140
+ function forgetChecks() {
141
+ lookGeneration++;
142
+ cached = { at: 0, value: null };
143
+ looking = null;
144
+ }
145
+
146
+ // The Python the app ships can't change while this server runs, so once it has
147
+ // been seen to work it isn't started again to ask: each look costs a Python
148
+ // start, and the page asks every 1.5 seconds. Once it has failed it isn't
149
+ // started again at all until the server restarts (it used to be asked again
150
+ // every minute), so a Python macOS refuses to run can't keep bringing macOS's
151
+ // alert back; the page says what happened instead. (lib/scanner-python.js
152
+ // pythonLooker.) Kept once per server process, like the scan state
153
+ // (lib/scan-state.js): a second copy of this module (the bundler's, or a reload
154
+ // while developing) must not start a failed Python afresh.
155
+ const LOOKER = Symbol.for('six-degrees.python-looker');
156
+ const lookForPython = (globalThis[LOOKER] ??= pythonLooker());
157
+
158
+ let host;
159
+ function hostOnce() {
160
+ if (host === undefined) host = thisHostKey();
161
+ return host;
162
+ }
165
163
 
164
+ async function look() {
166
165
  const root = projectRoot();
167
- const usable = await findUsablePython();
168
- const anyPython = usable || await findAnyPython();
166
+ const python = await lookForPython(process.env.SIX_DEGREES_PYTHON || '', {
167
+ app: process.env.SIX_DEGREES_APP || '',
168
+ dataDir: dataDir(),
169
+ host: hostOnce(),
170
+ });
169
171
 
170
172
  // Where Playwright's "chrome" channel looks for Google Chrome (Chromium doesn't count).
171
173
  const chrome =
@@ -175,9 +177,22 @@ async function machineChecks() {
175
177
 
176
178
  const signedIn = existsSync(path.join(dataDir(), 'chrome-profile', 'Default', 'Cookies'));
177
179
 
178
- const value = { root, usable, anyPython, chrome, signedIn };
179
- cached = { at: Date.now(), value };
180
- return value;
180
+ return { root, python, chrome, signedIn };
181
+ }
182
+
183
+ function machineChecks() {
184
+ if (Date.now() - cached.at < 4000 && cached.value) return Promise.resolve(cached.value);
185
+ if (looking) return looking;
186
+ const generation = lookGeneration;
187
+ const current = look().then((value) => {
188
+ // A job that started or ended meanwhile makes this look stale: don't keep it.
189
+ if (generation === lookGeneration) cached = { at: Date.now(), value };
190
+ return value;
191
+ }).finally(() => {
192
+ if (looking === current) looking = null;
193
+ });
194
+ looking = current;
195
+ return current;
181
196
  }
182
197
 
183
198
  /** People whose list was only partly read, for the Scan page's Paused list. */
@@ -211,13 +226,27 @@ async function status() {
211
226
  if (me) network = networkCounts(me.id);
212
227
  } catch {}
213
228
 
229
+ const py = m.python;
214
230
  return {
215
- ready: Boolean(m.root && m.usable && m.chrome),
231
+ ready: Boolean(m.root && py.run && m.chrome),
216
232
  checks: {
217
233
  scriptsFound: Boolean(m.root),
218
- python: Boolean(m.anyPython),
219
- pythonPath: m.usable || m.anyPython,
220
- dependencies: Boolean(m.usable),
234
+ // A Python that runs the scanner, or one Install can build its environment from.
235
+ python: Boolean(py.run || py.base),
236
+ pythonPath: py.run?.path || py.base?.path || null,
237
+ pythonVersion: py.run?.version || py.base?.version || null,
238
+ // Where the one that runs it comes from: 'bundled' (the Mac app's own),
239
+ // 'custom' (named in SIX_DEGREES_PYTHON), 'venv' or 'system'.
240
+ pythonSource: py.run?.source || null,
241
+ dependencies: Boolean(py.run),
242
+ // The named Python, when it didn't work: { source, problem }.
243
+ ownPython: py.own,
244
+ // What Install would build the scanner's environment from: { source: 'system'|'downloaded', version }.
245
+ installFrom: py.base ? { source: py.base.source, version: py.base.version } : null,
246
+ // A Python this computer has that won't do, when there is no other: { version, venv }.
247
+ systemPython: py.systemFound,
248
+ // What "Set up the scanner" would download, when there is nothing to install from.
249
+ download: py.download ? { version: py.download.version, size: py.download.size, from: 'github.com' } : null,
221
250
  chrome: m.chrome,
222
251
  signedIn: m.signedIn,
223
252
  },
@@ -243,7 +272,10 @@ export async function GET() {
243
272
  // so a name that begins with "-" can never be read as a flag of its own — and
244
273
  // spawn takes an array, so there is no shell for it to escape into either.
245
274
  const ACTIONS = {
246
- install: { label: 'Installing the scraper’s Python packages' },
275
+ install: { label: 'Installing the scanner’s Python packages' },
276
+ // Only when this computer has no Python to install into: downloads the one
277
+ // pinned for it (lib/scanner-python.js), then installs as above.
278
+ setup: { label: 'Setting up the scanner' },
247
279
  login: { flag: '--login', label: 'Opening LinkedIn so you can sign in' },
248
280
  full: { flag: '--full', label: 'Scanning your whole network' },
249
281
  refresh: { flag: '--refresh', label: 'Checking for new connections' },
@@ -278,6 +310,86 @@ function cleanName(raw) {
278
310
  return name;
279
311
  }
280
312
 
313
+ /**
314
+ * Install, and "Set up the scanner", as steps: { plan, cleanup }, or why not:
315
+ * { error, status }. `found` is choosePython()'s answer. Every step is a fixed
316
+ * command, or a `run` done inside this server; nothing comes from the request.
317
+ *
318
+ * install build the scanner's environment from `found.base` (this
319
+ * computer's Python, or the one a setup downloaded), install the
320
+ * pinned packages into it, and check that they load there, so a pip
321
+ * that put them elsewhere fails with its reason, not "Finished"
322
+ * setup when there is nothing to build from: download the standalone
323
+ * Python pinned for this computer, check its SHA-256, unpack it into
324
+ * the data folder, then install as above. The download is checked
325
+ * before anything is unpacked: downloadVerified only names the file
326
+ * once it matches, and deletes it otherwise.
327
+ */
328
+ function setupPlan(action, found, { root, data, say }) {
329
+ if (found.run) {
330
+ return {
331
+ status: 409,
332
+ error: found.run.source === 'bundled'
333
+ ? 'The scanner is already set up: its Python comes with the app.'
334
+ : 'The scanner is already set up.',
335
+ };
336
+ }
337
+ const base = found.base;
338
+ if (action === 'install' && !base) {
339
+ return found.download
340
+ ? { status: 409, error: 'This computer has no Python the scanner can use. Use Set up the scanner instead.' }
341
+ : { status: 500, error: 'No Python 3.10 to 3.14 is installed, or none is on this app’s PATH.' };
342
+ }
343
+ if (action === 'setup' && base?.source === 'system') {
344
+ return { status: 409, error: `This computer already has Python ${base.version}, so nothing needs downloading. Use Install instead.` };
345
+ }
346
+ if (action === 'setup' && !base && !found.download) {
347
+ return { status: 409, error: 'There is no Python download for this computer. Install Python 3.10 to 3.14, then reload.' };
348
+ }
349
+
350
+ // The environment, the pinned packages without the pip settings that would
351
+ // put them elsewhere, and a check that they load there
352
+ // (lib/scanner-python.js installSteps).
353
+ const requirements = path.join(root, 'scripts', 'requirements.txt');
354
+ const install = (from) => installSteps({ base: from, dataDir: data, requirements });
355
+ // A setup that already got as far as the download (it was stopped, or the
356
+ // packages failed) carries on from its Python rather than fetching it again.
357
+ if (base) return { plan: install(base), cleanup: null };
358
+
359
+ const dl = found.download;
360
+ mkdirSync(data, { recursive: true });
361
+ sweepSetupLeftovers(data);
362
+ const work = mkdtempSync(path.join(data, SETUP_WORK_PREFIX));
363
+ const archive = path.join(work, dl.file);
364
+ let tenths = -1;
365
+ return {
366
+ plan: [
367
+ {
368
+ note: `Downloading Python ${dl.version} from GitHub (${megabytes(dl.size)} MB)`,
369
+ run: async ({ signal }) => {
370
+ await downloadVerified(dl.url, archive, {
371
+ sha256: dl.sha256,
372
+ size: dl.size,
373
+ signal,
374
+ onProgress: (got, total) => {
375
+ const t = Math.floor((got / total) * 10);
376
+ if (t > tenths) {
377
+ tenths = t;
378
+ say(`Downloading Python ${dl.version}: ${megabytes(got)} of ${megabytes(total)} MB`);
379
+ }
380
+ },
381
+ });
382
+ say('Its checksum matches the one Six Degrees has for it.');
383
+ },
384
+ },
385
+ { cmd: 'tar', args: ['-xzf', archive, '-C', work], note: 'Unpacking it' },
386
+ { run: () => placeDownloadedPython(work, data, work) },
387
+ ...install({ path: downloadedPython(data), source: 'downloaded' }),
388
+ ],
389
+ cleanup: () => rmSync(work, { recursive: true, force: true }),
390
+ };
391
+ }
392
+
281
393
  export async function POST(request) {
282
394
  let body = {};
283
395
  try { body = await request.json(); } catch {}
@@ -342,6 +454,12 @@ export async function POST(request) {
342
454
  // The 1st-degree scans aren't searches, but they open LinkedIn with automation too.
343
455
  const searches = spec.searches || action.startsWith('auto-bridge')
344
456
  || ['bridge', 'rescrape', 'company', 'full', 'refresh'].includes(action);
457
+ // A staged import replaces the network at the next start, so anything scanned
458
+ // now would land in the copy that is about to be set aside. Setting the
459
+ // scanner up touches no network data.
460
+ if (!['install', 'setup', 'login'].includes(action) && pendingImport(dataDir())) {
461
+ return Response.json({ error: 'An import is waiting to finish. Restart Six Degrees first (Settings → Your data), then scan.' }, { status: 409 });
462
+ }
345
463
  const cooldown = linkedinState(dataDir()).cooldown;
346
464
  if (searches && cooldown) {
347
465
  return Response.json({ error: `Scanning is paused until ${new Date(cooldown.until).toLocaleString()} — ${cooldown.reason}.`, cooldown }, { status: 409 });
@@ -354,53 +472,47 @@ export async function POST(request) {
354
472
  if (!root) {
355
473
  return Response.json({ error: 'Could not find scripts/scrape.py next to the app.' }, { status: 500 });
356
474
  }
357
- let python;
358
- if (action === 'install') {
359
- python = await findAnyPython();
360
- if (!python) {
361
- return Response.json({ error: 'Python 3 is not installed, or not on this app’s PATH.' }, { status: 500 });
362
- }
363
- } else {
364
- python = await findUsablePython();
365
- if (!python) {
366
- return Response.json({ error: 'The scraper’s packages are not installed yet. Do step 1 first.' }, { status: 409 });
367
- }
368
- }
369
475
 
370
- const venvDir = path.join(dataDir(), 'venv');
371
- const reqs = path.join(root, 'scripts', 'requirements.txt');
476
+ // Which Python, looked at afresh: what the page saw may be seconds old.
477
+ forgetChecks();
478
+ const found = (await machineChecks()).python;
479
+ if (state.running) {
480
+ return Response.json({ error: 'Something is already running.', action: state.action }, { status: 409 });
481
+ }
372
482
 
373
483
  // Installing is several commands, so it runs as a small sequential plan
374
484
  // rather than a shell string — nothing here is ever concatenated from input.
375
- const plan = action === 'install'
376
- ? [
377
- { cmd: python, args: ['-m', 'venv', venvDir], note: 'Creating a private Python environment' },
378
- { cmd: venvPython(), args: ['-m', 'pip', 'install', '--upgrade', 'pip'], note: 'Updating pip', tolerant: true },
379
- { cmd: venvPython(), args: ['-m', 'pip', 'install', '-r', reqs], note: 'Installing Playwright, requests and Pillow' },
380
- ]
381
- : [
382
- {
383
- cmd: python,
384
- args: [
385
- path.join(root, 'scripts', 'scrape.py'),
386
- // `--flag=value` is one token on purpose: a name beginning with
387
- // "-" can then never be read as a flag of its own.
388
- ...(name ? [`${spec.flag}=${name}`] : profileUrl ? [`${spec.flag}=${profileUrl}`] : spec.flag.split(' ')),
389
- ...(maxBridges && (action.startsWith('auto-bridge') || action === 'resume-all') ? [`--max-bridges=${maxBridges}`] : []),
390
- ...(tiers.length && action.startsWith('auto-bridge') ? [`--tiers=${tiers.join(',')}`] : []),
391
- ...(action.startsWith('auto-bridge') ? [`--order=${order}`] : []),
392
- // Resuming always reads to the end: a remembered "10 pages" would
393
- // otherwise leave everyone paused at page 11 and do nothing.
394
- ...(readsCircles ? [`--max-pages=${action.startsWith('resume') ? 100 : maxPages}`] : []),
395
- ...(deeper ? ['--deeper'] : []),
396
- ],
397
- },
398
- ];
485
+ let plan;
486
+ let cleanup = null;
487
+ if (action === 'install' || action === 'setup') {
488
+ const made = setupPlan(action, found, { root, data: dataDir(), say: push });
489
+ if (made.error) return Response.json({ error: made.error }, { status: made.status });
490
+ ({ plan, cleanup } = made);
491
+ } else {
492
+ if (!found.run) {
493
+ return Response.json({ error: 'The scanner’s packages are not installed yet. Do step 1 first.' }, { status: 409 });
494
+ }
495
+ // The app's own Python runs isolated from this user's Python settings; any
496
+ // Python runs it without writing bytecode beside scrape.py, which in the Mac
497
+ // app is inside the signed app (lib/scanner-python.js scannerCommand).
498
+ plan = [scannerCommand(found.run, path.join(root, 'scripts', 'scrape.py'), [
499
+ // `--flag=value` is one token on purpose: a name beginning with
500
+ // "-" can then never be read as a flag of its own.
501
+ ...(name ? [`${spec.flag}=${name}`] : profileUrl ? [`${spec.flag}=${profileUrl}`] : spec.flag.split(' ')),
502
+ ...(maxBridges && (action.startsWith('auto-bridge') || action === 'resume-all') ? [`--max-bridges=${maxBridges}`] : []),
503
+ ...(tiers.length && action.startsWith('auto-bridge') ? [`--tiers=${tiers.join(',')}`] : []),
504
+ ...(action.startsWith('auto-bridge') ? [`--order=${order}`] : []),
505
+ // Resuming always reads to the end: a remembered "10 pages" would
506
+ // otherwise leave everyone paused at page 11 and do nothing.
507
+ ...(readsCircles ? [`--max-pages=${action.startsWith('resume') ? 100 : maxPages}`] : []),
508
+ ...(deeper ? ['--deeper'] : []),
509
+ ])];
510
+ }
399
511
 
400
512
  // The scraper writes back through this very app, so point it at the port we
401
513
  // are actually being served on rather than guessing 3000.
402
- const host = request.headers.get('host') || '127.0.0.1:3000';
403
- const port = host.includes(':') ? host.split(':').pop() : '80';
514
+ const hostHeader = request.headers.get('host') || '127.0.0.1:3000';
515
+ const port = hostHeader.includes(':') ? hostHeader.split(':').pop() : '80';
404
516
 
405
517
  state.running = true;
406
518
  state.stopping = false;
@@ -410,7 +522,7 @@ export async function POST(request) {
410
522
  state.log = [spec.label + (name ? ` ${name}…` : '…')];
411
523
  state.stderrTail = [];
412
524
  state.failure = null;
413
- cached = { at: 0, value: null };
525
+ forgetChecks();
414
526
 
415
527
  // Name the profile outright. The scraper would otherwise ask the app which one
416
528
  // to use; passing it means the page and the scrape cannot disagree.
@@ -446,8 +558,13 @@ export async function POST(request) {
446
558
  state.stopping = false;
447
559
  state.running = false;
448
560
  state.child = null;
561
+ state.abort = null;
449
562
  state.exitCode = code;
450
- cached = { at: 0, value: null };
563
+ // A setup's private folder (the download and what was unpacked from it).
564
+ if (cleanup) {
565
+ try { cleanup(); } catch { /* swept at the next setup (sweepSetupLeftovers) */ }
566
+ }
567
+ forgetChecks();
451
568
  }
452
569
 
453
570
  function runStep(i) {
@@ -455,10 +572,32 @@ export async function POST(request) {
455
572
  const step = plan[i];
456
573
  if (step.note) push(step.note + '…');
457
574
 
575
+ // A step done inside this server: the download, and moving what it unpacked
576
+ // into place. Stop aborts it (stopChild).
577
+ if (step.run) {
578
+ const ctrl = new AbortController();
579
+ state.abort = ctrl;
580
+ Promise.resolve()
581
+ .then(() => step.run({ signal: ctrl.signal }))
582
+ .then(() => {
583
+ state.abort = null;
584
+ if (state.stopping) return finish(0);
585
+ runStep(i + 1);
586
+ }, (err) => {
587
+ state.abort = null;
588
+ if (state.stopping) return finish(0);
589
+ // Kept where finish() looks for a failure's reason, for the page's red box.
590
+ state.stderrTail.push(err instanceof ScannerSetupError ? err.message : `Could not finish: ${err.message}`);
591
+ finish(1);
592
+ });
593
+ return;
594
+ }
595
+
458
596
  let child;
459
597
  try {
460
- // Its own process group, so cancelling reaches the browser as well.
461
- child = spawn(step.cmd, step.args, { cwd: root, env: childEnv, detached: true });
598
+ // Its own process group, so cancelling reaches the browser as well. A
599
+ // step's env() makes its own environment from this one.
600
+ child = spawn(step.cmd, step.args, { cwd: root, env: step.env ? step.env(childEnv) : childEnv, detached: true });
462
601
  } catch (err) {
463
602
  push(`Could not start: ${err.message}`);
464
603
  return finish(-1);
@@ -0,0 +1,58 @@
1
+ import path from 'node:path';
2
+ import { homedir } from 'node:os';
3
+ import { getDb } from '../../../lib/db-client';
4
+ import { readSettings, writeSettings, SettingsError } from '../../../lib/settings';
5
+ import { afterSettingsChange } from '../../../lib/settings-effects';
6
+ import { dataDir, projectRoot, isGitCheckout } from '../../../lib/paths';
7
+ import { installKind } from '../../../lib/release';
8
+ import { APP_VERSION } from '../../../lib/app-version';
9
+
10
+ // The Settings page's data: what the user has chosen, plus plain facts about
11
+ // this copy (version, how it was installed, where its data lives). Reading is
12
+ // local only; saving is a POST, so the cross-site guard in middleware.js
13
+ // applies to it like every other write.
14
+
15
+ function about() {
16
+ const dir = path.resolve(dataDir());
17
+ return {
18
+ version: APP_VERSION,
19
+ // A git checkout is its own kind here: it updates with git, not a release.
20
+ kind: isGitCheckout() ? 'git' : installKind(process.env, projectRoot()),
21
+ dataDir: dir,
22
+ customDataDir: dir !== path.join(homedir(), '.six-degrees'),
23
+ };
24
+ }
25
+
26
+ export async function GET() {
27
+ try {
28
+ return Response.json({ settings: readSettings(getDb()), about: about() });
29
+ } catch (err) {
30
+ return Response.json({ error: err.message }, { status: 500 });
31
+ }
32
+ }
33
+
34
+ export async function POST(request) {
35
+ let body;
36
+ try {
37
+ body = await request.json();
38
+ } catch {
39
+ return Response.json({ error: 'Send the settings as JSON.' }, { status: 400 });
40
+ }
41
+ let before, settings;
42
+ try {
43
+ before = readSettings(getDb());
44
+ settings = writeSettings(getDb(), body?.settings);
45
+ } catch (err) {
46
+ return Response.json({ error: err.message }, { status: err instanceof SettingsError ? 400 : 500 });
47
+ }
48
+ // What the change sets in motion (lib/settings-effects.js): a new sector
49
+ // focus rescores everyone. The save has landed either way, so a failure
50
+ // here is reported with the saved settings (and whatever work did finish)
51
+ // rather than hidden.
52
+ try {
53
+ const effects = afterSettingsChange(getDb(), before, settings);
54
+ return Response.json(effects ? { settings, effects } : { settings });
55
+ } catch (err) {
56
+ return Response.json({ settings, ...(err.effects ? { effects: err.effects } : {}), error: err.message }, { status: 500 });
57
+ }
58
+ }
@@ -0,0 +1,38 @@
1
+ import { getDb } from '../../../../lib/db-client';
2
+ import { companyOverrides, readForScoring, scoringRows, sectorFocusOf } from '../../../../lib/rpc';
3
+ import { parseSectorFocus, previewSectorFocus } from '../../../../lib/sector-focus';
4
+
5
+ // Settings → Your sector, before you save: what a sector focus would change,
6
+ // against the one saved now. It reads the network once and scores it twice in
7
+ // memory, with the inputs rescoreAll() uses (readForScoring: each company's
8
+ // industry and its sectors from the directory), and writes nothing. A save
9
+ // counts its changes with the same function (rescoreAll's compareWith), so the
10
+ // two agree. A POST because it takes a body; the cross-site guard in
11
+ // middleware.js covers it like every write.
12
+
13
+ export async function POST(request) {
14
+ let body;
15
+ try {
16
+ body = await request.json();
17
+ } catch {
18
+ return Response.json({ error: 'Send the sector focus as JSON.' }, { status: 400 });
19
+ }
20
+ let to;
21
+ try {
22
+ to = parseSectorFocus(body?.sectorFocus);
23
+ } catch (err) {
24
+ return Response.json({ error: err.message }, { status: 400 });
25
+ }
26
+ try {
27
+ const db = getDb();
28
+ const rows = scoringRows(db, { withPeople: true });
29
+ return Response.json(previewSectorFocus(rows, {
30
+ overrides: companyOverrides(db),
31
+ read: readForScoring(rows),
32
+ from: sectorFocusOf(db),
33
+ to,
34
+ }));
35
+ } catch (err) {
36
+ return Response.json({ error: err.message }, { status: 500 });
37
+ }
38
+ }
@@ -0,0 +1,20 @@
1
+ import { getDb } from '../../../../lib/db-client';
2
+ import { readForScoring, scoringRows } from '../../../../lib/rpc';
3
+ import { suggestSectors } from '../../../../lib/sector-directory';
4
+
5
+ // Settings → Your sector: "Suggested from your network". The directory's
6
+ // sectors most of your scanned people work in, counted here from the rows
7
+ // with the same read rescoring uses, so a suggestion's companies are the ones
8
+ // picking it would lean. Its own read-only GET, not part of GET
9
+ // /api/settings: that one is also what the profile page loads, and this reads
10
+ // every row. A CSV import or the sample network isn't in the database, so it
11
+ // isn't counted (the page says so).
12
+
13
+ export async function GET() {
14
+ try {
15
+ const rows = scoringRows(getDb());
16
+ return Response.json({ scored: rows.length, suggestions: suggestSectors(rows, readForScoring(rows)) });
17
+ } catch (err) {
18
+ return Response.json({ error: err.message }, { status: 500 });
19
+ }
20
+ }