six-degrees 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (427) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +1 -0
  3. package/.next/standalone/.next/build-manifest.json +3 -3
  4. package/.next/standalone/.next/prerender-manifest.json +35 -7
  5. package/.next/standalone/.next/required-server-files.json +6 -1
  6. package/.next/standalone/.next/routes-manifest.json +18 -1
  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 +11 -11
  18. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +11 -11
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +10 -10
  20. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  21. package/.next/standalone/.next/server/app/api/admin-delete/route.js +1 -1
  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 +1 -1
  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 +1 -1
  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 +1 -1
  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.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/connections/route.js +1 -1
  32. package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/delete-cluster/route.js +1 -1
  39. package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/ingest/route.js +1 -1
  41. package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/network/route.js +1 -1
  43. package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/notifications/route.js +1 -1
  45. package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/api/outreach/route.js +3 -3
  47. package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/api/queue/route.js +1 -1
  49. package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/api/scraper/route.js +1 -1
  51. package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
  54. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
  55. package/.next/standalone/.next/server/app/api/setup-profile/route.js +1 -1
  56. package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/api/unlock/route.js +1 -1
  58. package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
  59. package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  60. package/.next/standalone/.next/server/app/api/update-images/route.js +1 -1
  61. package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
  62. package/.next/standalone/.next/server/app/api/users/route.js +1 -1
  63. package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
  64. package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/app/import.html +1 -1
  68. package/.next/standalone/.next/server/app/import.rsc +13 -13
  69. package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +13 -13
  70. package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
  71. package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +12 -12
  72. package/.next/standalone/.next/server/app/index.html +1 -1
  73. package/.next/standalone/.next/server/app/index.rsc +13 -13
  74. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +12 -12
  75. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +13 -13
  76. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  77. package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
  78. package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
  79. package/.next/standalone/.next/server/app/launch.html +2 -2
  80. package/.next/standalone/.next/server/app/launch.rsc +13 -13
  81. package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +13 -13
  82. package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
  83. package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +12 -12
  84. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  85. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  86. package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
  87. package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
  88. package/.next/standalone/.next/server/app/paths.html +1 -1
  89. package/.next/standalone/.next/server/app/paths.rsc +13 -13
  90. package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +13 -13
  91. package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
  92. package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +12 -12
  93. package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
  94. package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
  95. package/.next/standalone/.next/server/app/profile.html +1 -1
  96. package/.next/standalone/.next/server/app/profile.rsc +13 -13
  97. package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +13 -13
  98. package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
  99. package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +12 -12
  100. package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
  101. package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
  102. package/.next/standalone/.next/server/app/queue.html +1 -1
  103. package/.next/standalone/.next/server/app/queue.rsc +13 -13
  104. package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +13 -13
  105. package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
  106. package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +12 -12
  107. package/.next/standalone/.next/server/app/scores/page/app-paths-manifest.json +3 -0
  108. package/.next/standalone/.next/server/app/scores/page/build-manifest.json +20 -0
  109. package/.next/standalone/.next/server/app/scores/page/next-font-manifest.json +11 -0
  110. package/.next/standalone/.next/server/app/scores/page/react-loadable-manifest.json +1 -0
  111. package/.next/standalone/.next/server/app/scores/page/server-reference-manifest.json +4 -0
  112. package/.next/standalone/.next/server/app/scores/page.js +13 -0
  113. package/.next/standalone/.next/server/app/scores/page.js.map +5 -0
  114. package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -0
  115. package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +3 -0
  116. package/.next/standalone/.next/server/app/scores.html +1 -0
  117. package/.next/standalone/.next/server/app/scores.meta +26 -0
  118. package/.next/standalone/.next/server/app/scores.rsc +24 -0
  119. package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +24 -0
  120. package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +4 -0
  121. package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +28 -0
  122. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  123. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  124. package/.next/standalone/.next/server/app/settings.html +1 -1
  125. package/.next/standalone/.next/server/app/settings.rsc +13 -13
  126. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +13 -13
  127. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  128. package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +12 -12
  129. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  130. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  131. package/.next/standalone/.next/server/app/setup.html +1 -1
  132. package/.next/standalone/.next/server/app/setup.rsc +13 -13
  133. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +13 -13
  134. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
  135. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +12 -12
  136. package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
  137. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-rnla2._.js +1 -1
  138. package/.next/standalone/.next/server/chunks/[root-of-the-server]__00la_y0._.js +1 -1
  139. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0i5hdu4._.js → [root-of-the-server]__03ydxxh._.js} +2 -2
  140. package/.next/standalone/.next/server/chunks/[root-of-the-server]__07t47cm._.js +1 -1
  141. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0bat8nx._.js +8 -0
  142. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cj5jqc._.js +1 -1
  143. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e0fei3._.js +1 -1
  144. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljcioj._.js +1 -1
  145. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljnyho._.js +1 -1
  146. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o5-tcg._.js +1 -1
  147. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qrnous._.js +1 -1
  148. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +1 -1
  149. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0uag-k5._.js +1 -1
  150. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0um1uey._.js +1 -1
  151. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wmexuc._.js +1 -1
  152. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xu34sz._.js +1 -1
  153. package/.next/standalone/.next/server/chunks/[root-of-the-server]__133knx1._.js +1 -1
  154. package/.next/standalone/.next/server/chunks/[root-of-the-server]__14ou7j5._.js +1 -1
  155. package/.next/standalone/.next/server/chunks/[root-of-the-server]__15-17hl._.js +1 -1
  156. package/.next/standalone/.next/server/chunks/[root-of-the-server]__16iu0hv._.js +1 -1
  157. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19b7mig._.js +1 -1
  158. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bbcwub._.js +2 -2
  159. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1eris29._.js +1 -1
  160. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1gjo_cq._.js +1 -1
  161. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +1 -1
  162. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qcm08_._.js +1 -1
  163. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rphsdk._.js +1 -1
  164. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1w25q0m._.js +1 -1
  165. package/.next/standalone/.next/server/chunks/_02twlca._.js +10 -0
  166. package/.next/standalone/.next/server/chunks/_072jl0y._.js +1 -1
  167. package/.next/standalone/.next/server/chunks/_0lw8_i0._.js +1 -1
  168. package/.next/standalone/.next/server/chunks/_1_fynia._.js +8 -6
  169. package/.next/standalone/.next/server/chunks/_1fn30_l._.js +1 -1
  170. package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
  171. package/.next/standalone/.next/server/chunks/_1iwkh8t._.js +1 -1
  172. package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +13 -8
  173. package/.next/standalone/.next/server/chunks/{lib_rpc_02ndsg2.js → lib_rpc_03i8xef.js} +1 -1
  174. package/.next/standalone/.next/server/chunks/{lib_rpc_08q5vp6.js → lib_rpc_08fc3rp.js} +1 -1
  175. package/.next/standalone/.next/server/chunks/{lib_rpc_00tx2sm.js → lib_rpc_08r2t_q.js} +1 -1
  176. package/.next/standalone/.next/server/chunks/{lib_rpc_02-qa4d.js → lib_rpc_0btpv1t.js} +1 -1
  177. package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +5 -5
  178. package/.next/standalone/.next/server/chunks/lib_rpc_0cm2ev3.js +3 -0
  179. package/.next/standalone/.next/server/chunks/lib_rpc_0iovswz.js +3 -0
  180. package/.next/standalone/.next/server/chunks/lib_rpc_0is8flp.js +3 -0
  181. package/.next/standalone/.next/server/chunks/lib_rpc_0v8ajtd.js +3 -0
  182. package/.next/standalone/.next/server/chunks/lib_rpc_0xgmvvt.js +3 -0
  183. package/.next/standalone/.next/server/chunks/lib_rpc_144mzbf.js +3 -0
  184. package/.next/standalone/.next/server/chunks/lib_rpc_1bggi0n.js +3 -0
  185. package/.next/standalone/.next/server/chunks/lib_rpc_1dkcu9f.js +3 -0
  186. package/.next/standalone/.next/server/chunks/lib_rpc_1o0jb6w.js +3 -0
  187. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0an6blh._.js → [root-of-the-server]__0zzcbqg._.js} +2 -2
  188. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1194jps._.js +3 -0
  189. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1z1j3ek._.js → [root-of-the-server]__19ip1qq._.js} +2 -2
  190. package/.next/standalone/.next/server/chunks/ssr/_01f38ty._.js +3 -0
  191. package/.next/standalone/.next/server/chunks/ssr/_06w_gl4._.js +3 -0
  192. package/.next/standalone/.next/server/chunks/ssr/_0hw_qnj._.js +3 -0
  193. package/.next/standalone/.next/server/chunks/ssr/_13kzrwv._.js +3 -0
  194. package/.next/standalone/.next/server/chunks/ssr/_15xuwkv._.js +3 -0
  195. package/.next/standalone/.next/server/chunks/ssr/_1cyxocj._.js +3 -0
  196. package/.next/standalone/.next/server/chunks/ssr/_1gpbbw9._.js +3 -0
  197. package/.next/standalone/.next/server/chunks/ssr/_1hgd5dp._.js +3 -0
  198. package/.next/standalone/.next/server/chunks/ssr/_1vpfyd-._.js +3 -0
  199. package/.next/standalone/.next/server/chunks/ssr/_20w06i-._.js +3 -0
  200. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_scores_page_actions_0x-w58n.js +3 -0
  201. package/.next/standalone/.next/server/chunks/ssr/app_components_settings_SectorPicker_1c-29qw.js +3 -0
  202. package/.next/standalone/.next/server/chunks/ssr/app_launch_page_19t0dyd.js +1 -1
  203. package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +11 -9
  204. package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +4 -0
  205. package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
  206. package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +5 -5
  207. package/.next/standalone/.next/server/chunks/ssr/app_scores_page_09wepvv.js +3 -0
  208. package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +1 -1
  209. package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +2 -2
  210. package/.next/standalone/.next/server/chunks/ssr/lib_1bn38gs._.js +3 -0
  211. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_0x3i8za._.js +131 -0
  212. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_1bunoni._.js → node_modules_next_dist_1v8aef8._.js} +2 -2
  213. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_client_components_server-async-storage_0t1dato.js +3 -0
  214. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  215. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  216. package/.next/standalone/.next/server/next-font-manifest.js +1 -1
  217. package/.next/standalone/.next/server/next-font-manifest.json +4 -0
  218. package/.next/standalone/.next/server/pages/404.html +1 -1
  219. package/.next/standalone/.next/server/pages/500.html +1 -1
  220. package/.next/standalone/.next/server/prefetch-hints.json +14 -0
  221. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  222. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  223. package/.next/standalone/.next/static/chunks/0ax_9zzboyv6d.js +44 -0
  224. package/.next/standalone/.next/static/chunks/0cuuo2jwc9h94.js +1 -0
  225. package/.next/standalone/.next/static/chunks/0l1568qff0efj.js +1 -0
  226. package/.next/standalone/.next/static/chunks/0muwt9bykzr-1.js +1 -0
  227. package/.next/standalone/.next/static/chunks/0w76iraw7ij3k.js +18 -0
  228. package/.next/standalone/.next/static/chunks/135n87ydxjrra.js +2 -0
  229. package/.next/standalone/.next/static/chunks/18xu0fbgo_qz-.js +1 -0
  230. package/.next/standalone/.next/static/chunks/28bu26ausqzmn.js +1 -0
  231. package/.next/standalone/.next/static/chunks/{40nvbgfn7-072.css → 2p6o90z0vgcwr.css} +1 -1
  232. package/.next/standalone/.next/static/chunks/2pnbm1z6j7xab.js +1 -0
  233. package/.next/standalone/.next/static/chunks/2xjho0lroiwlq.js +1 -0
  234. package/.next/standalone/.next/static/chunks/3e8zg6ctvvb8p.js +33 -0
  235. package/.next/standalone/.next/static/chunks/3qh78g2aqhprd.js +2 -0
  236. package/.next/standalone/CHANGELOG.md +286 -0
  237. package/.next/standalone/CONTRIBUTING.md +15 -4
  238. package/.next/standalone/README.md +50 -29
  239. package/.next/standalone/SECURITY.md +10 -0
  240. package/.next/standalone/app/api/company-scores/legacy/route.js +1 -1
  241. package/.next/standalone/app/api/company-scores/route.js +1 -1
  242. package/.next/standalone/app/api/ingest/route.js +23 -2
  243. package/.next/standalone/app/api/outreach/route.js +107 -44
  244. package/.next/standalone/app/api/scraper/route.js +146 -17
  245. package/.next/standalone/app/api/settings/sector-preview/route.js +3 -2
  246. package/.next/standalone/app/api/settings/sector-suggestions/route.js +1 -1
  247. package/.next/standalone/app/api/update-images/route.js +41 -7
  248. package/.next/standalone/app/avatars/[file]/route.js +6 -1
  249. package/.next/standalone/app/components/Avatar.js +5 -2
  250. package/.next/standalone/app/components/ChainView.js +659 -469
  251. package/.next/standalone/app/components/CompanyScores.js +16 -10
  252. package/.next/standalone/app/components/EmptyState.js +1 -1
  253. package/.next/standalone/app/components/FieldStep.js +94 -0
  254. package/.next/standalone/app/components/ForceGraph.js +183 -768
  255. package/.next/standalone/app/components/GridView.js +9 -7
  256. package/.next/standalone/app/components/LegacyScoresCard.js +1 -1
  257. package/.next/standalone/app/components/LinkedInLimits.js +22 -7
  258. package/.next/standalone/app/components/ListView.js +6 -4
  259. package/.next/standalone/app/components/MappingProgress.js +5 -6
  260. package/.next/standalone/app/components/OnboardingGate.js +1 -1
  261. package/.next/standalone/app/components/OrbitGraph.js +33 -1
  262. package/.next/standalone/app/components/OutlinkQuest.js +5 -4
  263. package/.next/standalone/app/components/PathsAnalyzer.js +1 -1
  264. package/.next/standalone/app/components/RingsView.js +9 -8
  265. package/.next/standalone/app/components/SeparationView.js +96 -14
  266. package/.next/standalone/app/components/Sidebar.js +366 -287
  267. package/.next/standalone/app/components/TheirCircle.js +300 -0
  268. package/.next/standalone/app/components/settings/SectorPicker.js +202 -0
  269. package/.next/standalone/app/components/settings/SectorSection.js +21 -195
  270. package/.next/standalone/app/components/settings/TierSection.js +99 -0
  271. package/.next/standalone/app/components/useRequests.js +12 -0
  272. package/.next/standalone/app/components/useScanner.js +13 -0
  273. package/.next/standalone/app/components/views.js +3 -5
  274. package/.next/standalone/app/globals.css +7 -0
  275. package/.next/standalone/app/launch/page.js +6 -3
  276. package/.next/standalone/app/page.js +183 -74
  277. package/.next/standalone/app/paths/page.js +20 -14
  278. package/.next/standalone/app/profile/page.js +3 -3
  279. package/.next/standalone/app/queue/page.js +56 -60
  280. package/.next/standalone/app/scores/page.js +72 -0
  281. package/.next/standalone/app/settings/page.js +15 -3
  282. package/.next/standalone/app/setup/page.js +423 -260
  283. package/.next/standalone/db/schema.js +2 -1
  284. package/.next/standalone/eslint.config.mjs +3 -3
  285. package/.next/standalone/install.sh +3 -3
  286. package/.next/standalone/lib/chain-layout.js +139 -0
  287. package/.next/standalone/lib/circle.js +169 -0
  288. package/.next/standalone/lib/companies.js +1 -1
  289. package/.next/standalone/lib/csv.js +21 -10
  290. package/.next/standalone/lib/data-export.js +2 -1
  291. package/.next/standalone/lib/data-folder.js +4 -2
  292. package/.next/standalone/lib/db-client.js +1 -0
  293. package/.next/standalone/lib/galaxy.js +12 -0
  294. package/.next/standalone/lib/ingest.js +27 -8
  295. package/.next/standalone/lib/legacy-offer.js +7 -6
  296. package/.next/standalone/lib/legacy-scores.js +2 -2
  297. package/.next/standalone/lib/linkedin-limits.js +30 -8
  298. package/.next/standalone/lib/photos.js +47 -0
  299. package/.next/standalone/lib/quest.js +8 -3
  300. package/.next/standalone/lib/rarity.js +80 -0
  301. package/.next/standalone/lib/reach.js +113 -0
  302. package/.next/standalone/lib/requests-client.js +113 -0
  303. package/.next/standalone/lib/rpc.js +26 -15
  304. package/.next/standalone/lib/scan-state.js +1 -0
  305. package/.next/standalone/lib/scanner-setup.js +20 -1
  306. package/.next/standalone/lib/score-guess.js +60 -0
  307. package/.next/standalone/lib/scoring.js +1761 -69
  308. package/.next/standalone/lib/scraper-client.js +235 -17
  309. package/.next/standalone/lib/sector-directory.js +1 -1
  310. package/.next/standalone/lib/sector-focus.js +17 -3
  311. package/.next/standalone/lib/separation.js +4 -2
  312. package/.next/standalone/lib/settings-client.js +21 -0
  313. package/.next/standalone/lib/settings-effects.js +19 -1
  314. package/.next/standalone/lib/settings.js +7 -2
  315. package/.next/standalone/next.config.mjs +25 -3
  316. package/.next/standalone/package-lock.json +2 -2
  317. package/.next/standalone/package.json +2 -2
  318. package/.next/standalone/public/demo-data.json +1 -1
  319. package/.next/standalone/scripts/build-app.mjs +11 -2
  320. package/.next/standalone/scripts/check-node.mjs +1 -1
  321. package/.next/standalone/scripts/gen-synthetic.mjs +1 -1
  322. package/.next/standalone/scripts/image_store.py +77 -21
  323. package/.next/standalone/scripts/scrape.py +305 -29
  324. package/.next/standalone/server.js +1 -1
  325. package/.next/static/chunks/0ax_9zzboyv6d.js +44 -0
  326. package/.next/static/chunks/0cuuo2jwc9h94.js +1 -0
  327. package/.next/static/chunks/0l1568qff0efj.js +1 -0
  328. package/.next/static/chunks/0muwt9bykzr-1.js +1 -0
  329. package/.next/static/chunks/0w76iraw7ij3k.js +18 -0
  330. package/.next/static/chunks/135n87ydxjrra.js +2 -0
  331. package/.next/static/chunks/18xu0fbgo_qz-.js +1 -0
  332. package/.next/static/chunks/28bu26ausqzmn.js +1 -0
  333. package/.next/static/chunks/{40nvbgfn7-072.css → 2p6o90z0vgcwr.css} +1 -1
  334. package/.next/static/chunks/2pnbm1z6j7xab.js +1 -0
  335. package/.next/static/chunks/2xjho0lroiwlq.js +1 -0
  336. package/.next/static/chunks/3e8zg6ctvvb8p.js +33 -0
  337. package/.next/static/chunks/3qh78g2aqhprd.js +2 -0
  338. package/CHANGELOG.md +286 -0
  339. package/README.md +50 -29
  340. package/SECURITY.md +10 -0
  341. package/db/schema.js +2 -1
  342. package/lib/chain-layout.js +139 -0
  343. package/lib/circle.js +169 -0
  344. package/lib/companies.js +1 -1
  345. package/lib/csv.js +21 -10
  346. package/lib/data-export.js +2 -1
  347. package/lib/data-folder.js +4 -2
  348. package/lib/db-client.js +1 -0
  349. package/lib/galaxy.js +12 -0
  350. package/lib/ingest.js +27 -8
  351. package/lib/legacy-offer.js +7 -6
  352. package/lib/legacy-scores.js +2 -2
  353. package/lib/linkedin-limits.js +30 -8
  354. package/lib/photos.js +47 -0
  355. package/lib/quest.js +8 -3
  356. package/lib/rarity.js +80 -0
  357. package/lib/reach.js +113 -0
  358. package/lib/requests-client.js +113 -0
  359. package/lib/rpc.js +26 -15
  360. package/lib/scan-state.js +1 -0
  361. package/lib/scanner-setup.js +20 -1
  362. package/lib/score-guess.js +60 -0
  363. package/lib/scoring.js +1761 -69
  364. package/lib/scraper-client.js +235 -17
  365. package/lib/sector-directory.js +1 -1
  366. package/lib/sector-focus.js +17 -3
  367. package/lib/separation.js +4 -2
  368. package/lib/settings-client.js +21 -0
  369. package/lib/settings-effects.js +19 -1
  370. package/lib/settings.js +7 -2
  371. package/package.json +2 -2
  372. package/public/demo-data.json +1 -1
  373. package/scripts/check-node.mjs +1 -1
  374. package/scripts/image_store.py +77 -21
  375. package/scripts/scrape.py +305 -29
  376. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1t3rugi._.js +0 -3
  377. package/.next/standalone/.next/server/chunks/lib_rpc_0_8i3ft.js +0 -3
  378. package/.next/standalone/.next/server/chunks/lib_rpc_0huvw-3.js +0 -3
  379. package/.next/standalone/.next/server/chunks/lib_rpc_0ip2vdh.js +0 -3
  380. package/.next/standalone/.next/server/chunks/lib_rpc_0jzisgh.js +0 -3
  381. package/.next/standalone/.next/server/chunks/lib_rpc_0yi9xvd.js +0 -3
  382. package/.next/standalone/.next/server/chunks/lib_rpc_1jyhd0o.js +0 -3
  383. package/.next/standalone/.next/server/chunks/lib_rpc_1kn59-i.js +0 -3
  384. package/.next/standalone/.next/server/chunks/lib_rpc_1q2jkza.js +0 -3
  385. package/.next/standalone/.next/server/chunks/lib_rpc_1vpm1sk.js +0 -3
  386. package/.next/standalone/.next/server/chunks/lib_rpc_1x6p_31.js +0 -3
  387. package/.next/standalone/.next/server/chunks/ssr/_07u8byv._.js +0 -3
  388. package/.next/standalone/.next/server/chunks/ssr/_08r_mlf._.js +0 -3
  389. package/.next/standalone/.next/server/chunks/ssr/_0942v8n._.js +0 -3
  390. package/.next/standalone/.next/server/chunks/ssr/_0hl_t7r._.js +0 -3
  391. package/.next/standalone/.next/server/chunks/ssr/_0tb83dw._.js +0 -132
  392. package/.next/standalone/.next/server/chunks/ssr/_1-87bs1._.js +0 -3
  393. package/.next/standalone/.next/server/chunks/ssr/_17gui6a._.js +0 -3
  394. package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +0 -3
  395. package/.next/standalone/.next/server/chunks/ssr/node_modules_16vay6l._.js +0 -3
  396. package/.next/standalone/.next/server/chunks/ssr/node_modules_1kciwn4._.js +0 -3
  397. package/.next/standalone/.next/static/chunks/06x3jiiz96m5t.js +0 -1
  398. package/.next/standalone/.next/static/chunks/08k1crid-05dt.js +0 -1
  399. package/.next/standalone/.next/static/chunks/0ngx9s6-zgshe.js +0 -33
  400. package/.next/standalone/.next/static/chunks/0q-_khndfxubv.js +0 -2
  401. package/.next/standalone/.next/static/chunks/1c79veqedvubt.js +0 -1
  402. package/.next/standalone/.next/static/chunks/1chp3nx45meg5.js +0 -44
  403. package/.next/standalone/.next/static/chunks/1irqj0lf-fu3d.js +0 -1
  404. package/.next/standalone/.next/static/chunks/1uadqylrod5_v.js +0 -1
  405. package/.next/standalone/.next/static/chunks/23u0zclgwpc4d.js +0 -1
  406. package/.next/standalone/.next/static/chunks/289ktj97di6zg.js +0 -1
  407. package/.next/standalone/.next/static/chunks/2b3lmf8e6uj8m.js +0 -16
  408. package/.next/standalone/.next/static/chunks/3npq5yd7c4dmn.js +0 -2
  409. package/.next/standalone/app/components/BridgeRing.js +0 -753
  410. package/.next/static/chunks/06x3jiiz96m5t.js +0 -1
  411. package/.next/static/chunks/08k1crid-05dt.js +0 -1
  412. package/.next/static/chunks/0ngx9s6-zgshe.js +0 -33
  413. package/.next/static/chunks/0q-_khndfxubv.js +0 -2
  414. package/.next/static/chunks/1c79veqedvubt.js +0 -1
  415. package/.next/static/chunks/1chp3nx45meg5.js +0 -44
  416. package/.next/static/chunks/1irqj0lf-fu3d.js +0 -1
  417. package/.next/static/chunks/1uadqylrod5_v.js +0 -1
  418. package/.next/static/chunks/23u0zclgwpc4d.js +0 -1
  419. package/.next/static/chunks/289ktj97di6zg.js +0 -1
  420. package/.next/static/chunks/2b3lmf8e6uj8m.js +0 -16
  421. package/.next/static/chunks/3npq5yd7c4dmn.js +0 -2
  422. /package/.next/standalone/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_buildManifest.js +0 -0
  423. /package/.next/standalone/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_clientMiddlewareManifest.js +0 -0
  424. /package/.next/standalone/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_ssgManifest.js +0 -0
  425. /package/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_buildManifest.js +0 -0
  426. /package/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_clientMiddlewareManifest.js +0 -0
  427. /package/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_ssgManifest.js +0 -0
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- 6 Degrees LinkedIn Scraper
3
+ Six Degrees LinkedIn scanner
4
4
 
5
5
  Drives Chrome with a persistent profile. You log into LinkedIn by hand once;
6
6
  the session is reused on every later run.
@@ -37,7 +37,7 @@ from urllib.parse import parse_qs, urlparse
37
37
 
38
38
  # Local helper: download + compress avatars into permanent WebP files so they
39
39
  # don't break every ~3 weeks when LinkedIn's signed CDN URLs expire.
40
- from image_store import localize_images
40
+ from image_store import TryLater, localize_images, store_avatar
41
41
 
42
42
  # --- Config ---
43
43
  def _load_env_local():
@@ -592,10 +592,14 @@ class LinkedInPushedBack(Exception):
592
592
 
593
593
 
594
594
  class BudgetReached(Exception):
595
- """Today's or this month's search budget is used. Raised after saving."""
595
+ """Today's or this month's search budget, or today's profile views, is used.
596
+
597
+ Raised after saving. `kind` is "daily", "monthly" or "profiles"; the
598
+ profile cap is checked before a profile opens, so for it `found` is 0.
599
+ """
596
600
 
597
601
  def __init__(self, found=0, kind="daily"):
598
- super().__init__(f"the {kind} search budget")
602
+ super().__init__("today's profile views" if kind == "profiles" else f"the {kind} search budget")
599
603
  self.found = found
600
604
  self.kind = kind
601
605
 
@@ -779,6 +783,90 @@ def push_company(people, company_name):
779
783
  return inserted
780
784
 
781
785
 
786
+ # Failures in a row that say nothing about a link (offline, LinkedIn's image
787
+ # server busy or down) before Save photos stops: the rest would fail the same way.
788
+ PHOTO_TRIES_IN_A_ROW = 3
789
+
790
+
791
+ def save_waiting_photos(say_none=False):
792
+ """Save the photos still kept as links, once each. Returns how many were saved.
793
+
794
+ The app shows only photos saved on this computer, never LinkedIn's link, so
795
+ browsing never goes online. Older versions stored the link until a scan
796
+ replaced it, and a copy of a network made by one carries them too; those
797
+ people show initials until this runs, at the end of a scan or from Save
798
+ photos on the Scan page (--save-photos). Each link is tried until there's an
799
+ answer: saved, it becomes the file's path; a definite no (expired, not
800
+ LinkedIn's, not a picture, or the same picture as someone else's), the app
801
+ forgets it. A photo already saved for that person is used without fetching.
802
+
803
+ A failure that says nothing about the link (TryLater: offline, LinkedIn's
804
+ image server busy or down) keeps it for next time. Forgetting on those wiped
805
+ good links whenever this ran offline, and a person's photo then came back
806
+ only by scanning them again (TRAPS §7). A few in a row, or nothing but
807
+ those, raise TryLater once the answers there were are recorded, so the
808
+ run ends with the reason.
809
+ """
810
+ try:
811
+ resp = requests.get(f"{APP_URL}/api/update-images", headers=app_headers(json_body=False), timeout=30)
812
+ except requests.RequestException as e:
813
+ raise SaveFailed(f"the app could not be reached ({e})")
814
+ if resp.status_code != 200:
815
+ raise SaveFailed(f"the app answered {resp.status_code}: {resp.text[:160]}")
816
+ waiting = resp.json().get("waiting", [])
817
+ if not waiting:
818
+ if say_none:
819
+ print("No photos are waiting to be saved.")
820
+ return 0
821
+
822
+ people = len({w.get("profileUrl") for w in waiting})
823
+ print(f"\nSaving {people} profile photo{'s' if people != 1 else ''} kept as links to LinkedIn...")
824
+ saved, forget, kept = [], [], set()
825
+ in_a_row, why = 0, ""
826
+ for w in waiting:
827
+ if stop_requested() or in_a_row >= PHOTO_TRIES_IN_A_ROW:
828
+ break
829
+ purl, iurl = w.get("profileUrl"), w.get("imageUrl")
830
+ if not purl or not iurl:
831
+ continue
832
+ try:
833
+ local = store_avatar(iurl, purl, overwrite=False)
834
+ except TryLater as e:
835
+ print(f" Not saved this time: {purl} — {e}")
836
+ kept.add(purl)
837
+ in_a_row, why = in_a_row + 1, str(e)
838
+ continue
839
+ in_a_row = 0
840
+ if local:
841
+ saved.append({"profileUrl": purl, "imageUrl": local})
842
+ else:
843
+ forget.append({"profileUrl": purl, "imageUrl": iurl})
844
+
845
+ # What was answered is recorded even after a stop, so it isn't tried again.
846
+ for i in range(0, max(len(saved), len(forget)), 100):
847
+ r = requests.post(
848
+ f"{APP_URL}/api/update-images",
849
+ headers=app_headers(),
850
+ json={"images": saved[i:i+100], "forget": forget[i:i+100]},
851
+ timeout=60,
852
+ )
853
+ if r.status_code != 200:
854
+ raise SaveFailed(f"the app answered {r.status_code}: {r.text[:160]}")
855
+
856
+ done = len({x["profileUrl"] for x in saved})
857
+ lost = len({x["profileUrl"] for x in forget} - {x["profileUrl"] for x in saved})
858
+ later = len(kept - {x["profileUrl"] for x in saved})
859
+ print(f" Saved {done} photo{'s' if done != 1 else ''} to this computer"
860
+ + (f"; {lost} couldn't be (an expired link, a link that isn't LinkedIn's or isn't a picture, "
861
+ "or the same picture as someone else's), so those people show initials" if lost else "")
862
+ + (f"; {later} couldn't be fetched this time, and their links are kept to try again" if later else "")
863
+ + ".")
864
+ if not stop_requested() and (in_a_row >= PHOTO_TRIES_IN_A_ROW or (kept and not saved and not forget)):
865
+ raise TryLater(f"{why}. Nothing was forgotten: the photos still waiting are tried again "
866
+ "with Save photos, and at the end of your next scan")
867
+ return done
868
+
869
+
782
870
  def scrape_full(headless=False):
783
871
  """Full first-time scrape: uses search page to get ALL connections with images.
784
872
  Paginates through /search/results/people/?network=["F"] (10 results per page).
@@ -1266,16 +1354,24 @@ def note_unclear(profile_url, clear=False):
1266
1354
  # day's or the month's budget is used. A cooldown lock is set when LinkedIn
1267
1355
  # pushes back, and nothing searches until it lifts.
1268
1356
  #
1357
+ # Profile views have a cap of their own, because they are what LinkedIn
1358
+ # restricted an account for (TRAPS §16). Every circle scan that opens someone's
1359
+ # profile takes one, and any two opens are at least PROFILE_GAP apart, timed
1360
+ # from the record, so starting scan after scan can't open profiles back to back.
1361
+ #
1269
1362
  # These belong to the LinkedIn account (the one Chrome profile), not to a
1270
1363
  # profile in the app: three app profiles must not triple the budget. The app's
1271
1364
  # Scan page reads and edits the same files (lib/linkedin-limits.js).
1272
1365
  #
1273
1366
  # linkedin-activity.json {"searches": [epoch s, ...], "profiles": [...]}
1274
- # scan-limits.json {"daily": 50, "monthly": 250} 0 = no cap
1367
+ # scan-limits.json {"daily": 50, "monthly": 250, "profiles": 50}
1368
+ # 0 = no cap on searches; profile views always have one
1275
1369
  # linkedin-cooldown.json {"until": epoch s, "reason": "...", "set_at": ...}
1276
1370
  # ---------------------------------------------------------------------------
1277
1371
  DEFAULT_DAILY_SEARCHES = 50
1278
1372
  DEFAULT_MONTHLY_SEARCHES = 250
1373
+ DEFAULT_DAILY_PROFILES = 50
1374
+ PROFILE_GAP = 60 # seconds between any two profile opens, whatever opened them
1279
1375
  DAY_SECONDS = 24 * 3600
1280
1376
  PACIFIC = "America/Los_Angeles"
1281
1377
 
@@ -1370,7 +1466,8 @@ def _read_activity():
1370
1466
  except OSError:
1371
1467
  pass
1372
1468
  print(" (the record of LinkedIn searches couldn't be read; counting today as used)")
1373
- fresh = {"searches": [now] * max(1, search_limits()["daily"] or 1), "profiles": []}
1469
+ lim = search_limits()
1470
+ fresh = {"searches": [now] * max(1, lim["daily"] or 1), "profiles": [now] * lim["profiles"]}
1374
1471
  try:
1375
1472
  _write_json_atomic(path, fresh)
1376
1473
  except Exception:
@@ -1380,17 +1477,43 @@ def _read_activity():
1380
1477
 
1381
1478
  def charge_linkedin(kind="searches", n=1):
1382
1479
  """Write down n searches (or profile views) made just now."""
1383
- now = time.time()
1480
+ with _Locked("linkedin-activity"):
1481
+ _charge(_read_activity(), kind, n, time.time())
1482
+
1483
+
1484
+ def _charge(data, kind, n, now):
1485
+ """Add n of `kind` at `now` to the record and write it. The caller holds the lock."""
1384
1486
  keep_from = month_start_pacific(now) - 32 * DAY_SECONDS
1487
+ data[kind] = [t for t in data.get(kind, []) if t >= keep_from] + [now] * n
1488
+ other = "profiles" if kind == "searches" else "searches"
1489
+ data[other] = [t for t in data.get(other, []) if t >= keep_from]
1490
+ try:
1491
+ _write_json_atomic(_home() / "linkedin-activity.json", data)
1492
+ except Exception as exc:
1493
+ print(f" (could not write down LinkedIn activity: {exc})")
1494
+
1495
+
1496
+ def take_profile_view(now=None):
1497
+ """Write down one profile view, if the cap and the gap allow one now.
1498
+
1499
+ Returns 0 once it is written down and a profile may open, the whole
1500
+ seconds still to wait when the last view was under PROFILE_GAP ago, or
1501
+ None when the last 24 hours' views are used. The check and the write are
1502
+ one step under the lock, so two runs can't both take the last view.
1503
+ """
1385
1504
  with _Locked("linkedin-activity"):
1505
+ now = now if now is not None else time.time()
1386
1506
  data = _read_activity()
1387
- data[kind] = [t for t in data.get(kind, []) if t >= keep_from] + [now] * n
1388
- other = "profiles" if kind == "searches" else "searches"
1389
- data[other] = [t for t in data.get(other, []) if t >= keep_from]
1390
- try:
1391
- _write_json_atomic(_home() / "linkedin-activity.json", data)
1392
- except Exception as exc:
1393
- print(f" (could not write down LinkedIn activity: {exc})")
1507
+ views = [t for t in data["profiles"] if t > now - DAY_SECONDS]
1508
+ if len(views) >= search_limits()["profiles"]:
1509
+ return None
1510
+ # A time ahead of this clock (another computer's, imported) is left
1511
+ # out of the gap: waiting for it could never end.
1512
+ last = max((t for t in views if t <= now), default=None)
1513
+ if last is not None and now - last < PROFILE_GAP:
1514
+ return int(last + PROFILE_GAP - now) + 1
1515
+ _charge(data, "profiles", 1, now)
1516
+ return 0
1394
1517
 
1395
1518
 
1396
1519
  def search_limits():
@@ -1405,7 +1528,9 @@ def search_limits():
1405
1528
  except (TypeError, ValueError):
1406
1529
  return default
1407
1530
  return {"daily": num(data.get("daily"), DEFAULT_DAILY_SEARCHES),
1408
- "monthly": num(data.get("monthly"), DEFAULT_MONTHLY_SEARCHES)}
1531
+ "monthly": num(data.get("monthly"), DEFAULT_MONTHLY_SEARCHES),
1532
+ # 0 is no cap for searches, never for profile views.
1533
+ "profiles": num(data.get("profiles"), DEFAULT_DAILY_PROFILES) or DEFAULT_DAILY_PROFILES}
1409
1534
 
1410
1535
 
1411
1536
  def linkedin_usage(now=None):
@@ -1427,6 +1552,11 @@ def searches_left(now=None):
1427
1552
  return (left_day, "daily") if left_day <= left_month else (left_month, "monthly")
1428
1553
 
1429
1554
 
1555
+ def profiles_left(now=None):
1556
+ """How many more profiles may be opened: the cap less the last 24 hours' views."""
1557
+ return max(0, search_limits()["profiles"] - linkedin_usage(now)["profiles_today"])
1558
+
1559
+
1430
1560
  def _when(ts):
1431
1561
  t = datetime.fromtimestamp(ts)
1432
1562
  hour = t.hour % 12 or 12
@@ -1440,6 +1570,14 @@ def _day(ts):
1440
1570
 
1441
1571
  def budget_message(kind):
1442
1572
  lim = search_limits()
1573
+ if kind == "profiles":
1574
+ # The one that stops counting soonest frees the next view.
1575
+ now = time.time()
1576
+ views = sorted(t for t in _read_activity()["profiles"] if t > now - DAY_SECONDS)
1577
+ extra = len(views) - lim["profiles"]
1578
+ free = f" The next one frees up at {_when(views[extra] + DAY_SECONDS)}." if extra >= 0 else ""
1579
+ return (f"Today's profile views ({lim['profiles']}) are used — they count the last 24 hours.{free} "
1580
+ f"No profile was opened and nothing was recorded, so the next run starts with the same person.")
1443
1581
  if kind == "monthly":
1444
1582
  when = _day(next_month_start_pacific())
1445
1583
  return (f"This month's search budget ({lim['monthly']}) is used. It starts again on {when} "
@@ -1751,6 +1889,68 @@ BRIDGE_RESULTS_JS = r"""
1751
1889
  }
1752
1890
  });
1753
1891
 
1892
+ // How many mutual connections each result shares with you: the line
1893
+ // under its card, "Maya Chen and 23 other mutual connections". Read
1894
+ // from the page's text in order, each count going to the result named
1895
+ // above it. A name two results share ("LinkedIn Member") takes none:
1896
+ // no count is better than someone else's. English only; any other
1897
+ // wording reads as no count, never a wrong one.
1898
+ // mutualCount: begin
1899
+ const mutualCount = (text) => {
1900
+ const l = String(text || '').replace(/\s+/g, ' ').trim();
1901
+ if (!/\bmutual connections?$/i.test(l) || l.length > 300) return null;
1902
+ const num = (s) => Number(String(s).replace(/,/g, ''));
1903
+ const names = (s) => s.split(/\s*,\s*|\s+and\s+/).filter(Boolean).length;
1904
+ let m = l.match(/^([\d,]+) mutual connections?$/i);
1905
+ if (m) return num(m[1]);
1906
+ m = l.match(/^(.*?)\s*\band ([\d,]+) others? mutual connections?$/i);
1907
+ if (m) return Math.max(1, names(m[1])) + num(m[2]);
1908
+ if (/\bis a mutual connection$/i.test(l)) return 1;
1909
+ m = l.match(/^(.+?)\s+are mutual connections$/i);
1910
+ if (m) return names(m[1]);
1911
+ return null;
1912
+ };
1913
+ // mutualCount: end
1914
+ const byName = new Map();
1915
+ for (const r of byHref.values()) {
1916
+ if (r.name) byName.set(r.name, byName.has(r.name) ? null : r);
1917
+ }
1918
+ const nameAt = (l) => {
1919
+ if (byName.has(l)) return byName.get(l);
1920
+ for (const [name, r] of byName) {
1921
+ if (l.startsWith(name) && /^\s*[\u2022\u00b7|,(]/.test(l.slice(name.length))) return r;
1922
+ }
1923
+ return undefined;
1924
+ };
1925
+ // The rest of a mutual line when the names are drawn above it, each on
1926
+ // a line of its own ("Maya Chen" / "and 23 other mutual connections").
1927
+ const tailOf = (l) => /^(and [\d,]+ others? mutual connections?|is a mutual connection|are mutual connections)$/i.test(l);
1928
+ let current = null;
1929
+ const give = (n) => {
1930
+ if (current && current.mutualCount == null && Number.isFinite(n) && n >= 1) current.mutualCount = n;
1931
+ };
1932
+ const lines = (root.innerText || '').split('\n').map(clean).filter(Boolean);
1933
+ for (let i = 0; i < lines.length; i++) {
1934
+ const l = lines[i];
1935
+ // Those names are links to your own connections, so they're on the
1936
+ // page as people too, but they are the line, not a new result: read
1937
+ // them with the rest of it, and the count stays with the card it's on.
1938
+ let j = i;
1939
+ while (j < lines.length && j - i < 3 && !tailOf(lines[j]) && nameAt(lines[j]) !== undefined) j++;
1940
+ if (j > i && j < lines.length && tailOf(lines[j])) {
1941
+ give(mutualCount(`${lines.slice(i, j).join(', ')} ${lines[j]}`));
1942
+ i = j;
1943
+ continue;
1944
+ }
1945
+ const n = mutualCount(l);
1946
+ if (n != null) {
1947
+ give(n);
1948
+ continue;
1949
+ }
1950
+ const hit = nameAt(l);
1951
+ if (hit !== undefined) current = hit;
1952
+ }
1953
+
1754
1954
  const results = [...byHref.values()]
1755
1955
  .filter((r) => r.name && r.name.length >= 2)
1756
1956
  .map(({ named, ...r }) => r);
@@ -2046,6 +2246,30 @@ def _page_limit_hint(next_page):
2046
2246
  return f"Run again with --deeper, and no --max-pages, to carry on from page {next_page}."
2047
2247
 
2048
2248
 
2249
+ def _wait_for_profile_view(page):
2250
+ """Take one of today's profile views, first waiting out PROFILE_GAP.
2251
+
2252
+ None once it is taken and the profile may open; "budget" when the last 24
2253
+ hours' views are used; "stopped" when Stop, or a closed window, came
2254
+ during the wait. Nothing else is written down.
2255
+ """
2256
+ def tick(left):
2257
+ if not _window_closed(page): # a closed window sets the stop
2258
+ print(f" {left}s to go", flush=True)
2259
+
2260
+ while True:
2261
+ wait = take_profile_view()
2262
+ if wait is None:
2263
+ return "budget"
2264
+ if not wait:
2265
+ return None
2266
+ print(f" Waiting {wait}s before opening a profile: at least {PROFILE_GAP}s pass "
2267
+ f"between any two.", flush=True)
2268
+ if not interruptible_sleep(wait, on_tick=tick, step=10) or _window_closed(page):
2269
+ print(" Stopped before opening their profile.")
2270
+ return "stopped"
2271
+
2272
+
2049
2273
  def _scrape_one_bridge(page, bridge_name, bridge_id, profile_url, max_pages=LINKEDIN_MAX_PAGES,
2050
2274
  start_page=1, urn=None, on_save=None):
2051
2275
  """
@@ -2060,6 +2284,7 @@ def _scrape_one_bridge(page, bridge_name, bridge_id, profile_url, max_pages=LINK
2060
2284
  urn the id LinkedIn searches their connections by
2061
2285
  found how many people were read in all
2062
2286
  limited LinkedIn's monthly search limit ended the read
2287
+ budget "daily", "monthly" or "profiles": that budget ended it (status "budget")
2063
2288
 
2064
2289
  Timing is calibrated from successful runs:
2065
2290
  - 30s profile render wait (LinkedIn is slow)
@@ -2076,9 +2301,17 @@ def _scrape_one_bridge(page, bridge_name, bridge_id, profile_url, max_pages=LINK
2076
2301
  # one fewer profile view, which is what LinkedIn counts most.
2077
2302
  print(f" Going straight to {bridge_name}'s connections (their search id is known).")
2078
2303
  else:
2079
- # Step 1: Navigate to profile (wait_until="commit" — don't wait for full load)
2304
+ # Step 1: take one of today's profile views, at least PROFILE_GAP after
2305
+ # the last one. With none left nothing is opened or recorded: the batch
2306
+ # ends (BudgetReached, which says why) and the next run tries them again.
2307
+ why = _wait_for_profile_view(page)
2308
+ if why == "budget":
2309
+ reach["budget"] = "profiles"
2310
+ if why:
2311
+ return [], why, reach
2312
+
2313
+ # Navigate to profile (wait_until="commit" — don't wait for full load)
2080
2314
  print(f" Opening {bridge_name}'s profile...")
2081
- charge_linkedin("profiles")
2082
2315
  try:
2083
2316
  page.goto(profile_url, wait_until="commit")
2084
2317
  except Exception as e:
@@ -2437,6 +2670,10 @@ def scrape_bridge(bridge_name, headless=False, max_pages=LINKEDIN_MAX_PAGES, dee
2437
2670
  f"Choose more than {max_pages} pages to read further.")
2438
2671
  return _read([], "finished")
2439
2672
  start_page, urn = nxt, (entry or {}).get("urn")
2673
+ # Without their search id, the read opens their profile: with no profile
2674
+ # views left, stop before a browser opens.
2675
+ if not urn and profiles_left() <= 0:
2676
+ raise BudgetReached(0, "profiles")
2440
2677
 
2441
2678
  print(f"\n=== {'Carrying on with' if start_page > 1 else 'Scraping'} connections of {bridge_name} ===\n")
2442
2679
 
@@ -2506,6 +2743,17 @@ def scrape_bridge(bridge_name, headless=False, max_pages=LINKEDIN_MAX_PAGES, dee
2506
2743
  # link; a profile that didn't render is "unclear" and marks nothing.
2507
2744
  mark_bridge_hidden(profile_url, bridge_name)
2508
2745
  print(" Their connections are not visible any more — keeping what is mapped.")
2746
+ elif status in ("private", "empty") and profile_url and not stop_requested():
2747
+ # A read from page 1 that found their list hidden, or LinkedIn showing no
2748
+ # one in it. Noted here rather than by the batch alone: a one-person scan
2749
+ # (a card's Scan or Rescan, the Degrees panel's Ready to scan) that found
2750
+ # it and said nothing left them "not scanned yet", offered again forever.
2751
+ # A stop that landed meanwhile concludes nothing, as in the batch. TRAPS §15.
2752
+ record_bridge_skip(profile_url, bridge_name,
2753
+ "no visible connections" if status == "private"
2754
+ else "LinkedIn showed no one in their list")
2755
+ print(" Their connections are hidden — noted, and skipped from now on."
2756
+ if status == "private" else " LinkedIn shows no one in their list — noted, and skipped from now on.")
2509
2757
 
2510
2758
  if status == "stopped" or stop_requested():
2511
2759
  return _read(read, "stopped")
@@ -2914,6 +3162,8 @@ def auto_bridge_all(headless=False, log_fn=None, retry_private=False, max_bridge
2914
3162
  use, lim = linkedin_usage(), search_limits()
2915
3163
  log(f"Search budget: {use['searches_today']} of {lim['daily'] or 'no limit'} used today, "
2916
3164
  f"{use['searches_month']} of {lim['monthly'] or 'no limit'} this month")
3165
+ log(f"Profile views: {use['profiles_today']} of {lim['profiles']} in the last 24 hours, "
3166
+ f"at least {PROFILE_GAP}s apart")
2917
3167
 
2918
3168
  # Get all degree-1 connections (filtered by active user)
2919
3169
  D1_LIMIT = 20000
@@ -3095,15 +3345,11 @@ def auto_bridge_all(headless=False, log_fn=None, retry_private=False, max_bridge
3095
3345
  results.append({"name": name, "tier": tier, "found": 0, "status": "private"})
3096
3346
  if from_page > 1:
3097
3347
  log(" Their connections are hidden now — keeping what's mapped")
3098
- elif url:
3099
- record_bridge_skip(url, name, "no visible connections")
3100
- log(" Connections are hidden — noted, and skipped from now on")
3348
+ # From page 1, scrape_bridge has noted them in bridge-skips.json.
3101
3349
  elif status == "empty":
3350
+ # scrape_bridge has noted them in bridge-skips.json, and said so.
3102
3351
  healthy = True
3103
3352
  results.append({"name": name, "tier": tier, "found": 0, "status": "private"})
3104
- if url:
3105
- record_bridge_skip(url, name, "LinkedIn showed no one in their list")
3106
- log(" LinkedIn shows no one in their list — noted, and skipped from now on")
3107
3353
  elif status == "finished":
3108
3354
  healthy = True
3109
3355
  results.append({"name": name, "tier": tier, "found": 0, "status": "finished"})
@@ -3126,9 +3372,12 @@ def auto_bridge_all(headless=False, log_fn=None, retry_private=False, max_bridge
3126
3372
  log("Stopped.")
3127
3373
  raise
3128
3374
  except BudgetReached as exc:
3129
- results.append({"name": name, "tier": tier, "found": exc.found, "status": "done" if exc.found else "unclear"})
3375
+ # The profile cap stops before their profile opens: nothing was
3376
+ # tried, so they aren't counted, and the next run tries them again.
3377
+ if exc.found or exc.kind != "profiles":
3378
+ results.append({"name": name, "tier": tier, "found": exc.found, "status": "done" if exc.found else "unclear"})
3130
3379
  log(" " + budget_message(exc.kind))
3131
- stopped_early = f"the {exc.kind} search budget"
3380
+ stopped_early = str(exc)
3132
3381
  break
3133
3382
  except CoolingDown as exc:
3134
3383
  log(" " + str(exc))
@@ -3224,6 +3473,8 @@ def rescrape_bridge(bridge_name, headless=False, max_pages=LINKEDIN_MAX_PAGES):
3224
3473
  raise CoolingDown(cd)
3225
3474
  if searches_left()[0] <= 0:
3226
3475
  raise BudgetReached(0, searches_left()[1])
3476
+ if profiles_left() <= 0: # a fresh read opens their profile
3477
+ raise BudgetReached(0, "profiles")
3227
3478
 
3228
3479
  print(f"Deleting old cluster data...")
3229
3480
  bridge_id = delete_bridge_cluster(bridge_name)
@@ -3423,7 +3674,7 @@ def run_server(port=5555):
3423
3674
 
3424
3675
  server = HTTPServer(("127.0.0.1", port), ScrapeHandler)
3425
3676
  print(f"\n{'='*50}")
3426
- print(f" 6 Degrees Scraper Server")
3677
+ print(f" Six Degrees scanner server")
3427
3678
  print(f" Running on http://localhost:{port}")
3428
3679
  print(f"{'='*50}")
3429
3680
  print(f"\n Open your visualization and use the Setup page buttons.")
@@ -3438,7 +3689,7 @@ def run_server(port=5555):
3438
3689
 
3439
3690
  if __name__ == "__main__":
3440
3691
  parser = argparse.ArgumentParser(
3441
- description="6 Degrees LinkedIn Scraper",
3692
+ description="Six Degrees LinkedIn scanner",
3442
3693
  formatter_class=argparse.RawDescriptionHelpFormatter,
3443
3694
  epilog="""
3444
3695
  Examples:
@@ -3482,9 +3733,16 @@ Examples:
3482
3733
  help="Carry on with people already mapped, from the page their last read "
3483
3734
  "stopped at. With --auto-bridge: alongside new people. With --bridge: that person.")
3484
3735
  parser.add_argument("--bridge-url", type=str,
3485
- help="Carry on with one person, found by their LinkedIn profile URL (implies --deeper)")
3736
+ help="Carry on with one person, found by their LinkedIn profile URL (implies "
3737
+ "--deeper, unless --from-start)")
3738
+ parser.add_argument("--from-start", action="store_true",
3739
+ help="With --bridge-url: read their list from page 1 instead, as --bridge does. "
3740
+ "The app's Scan buttons use it, since two connections can share a name")
3486
3741
  parser.add_argument("--only-unfinished", action="store_true",
3487
3742
  help="With --auto-bridge: only people whose read was cut short (Resume all)")
3743
+ parser.add_argument("--save-photos", action="store_true",
3744
+ help="Save the profile photos still kept as links to LinkedIn, then exit "
3745
+ "(every scan also does this at its end)")
3488
3746
  parser.add_argument("--headless", action="store_true", help="Run browser in headless mode")
3489
3747
  args = parser.parse_args()
3490
3748
  args.max_pages = max(1, min(LINKEDIN_MAX_PAGES, args.max_pages))
@@ -3493,6 +3751,8 @@ Examples:
3493
3751
  def _say_why(exc_type, exc, tb):
3494
3752
  if issubclass(exc_type, SaveFailed):
3495
3753
  print(f"\n The app could not save what was scraped: {exc}\n", file=sys.stderr)
3754
+ elif issubclass(exc_type, TryLater):
3755
+ print(f"\n The photos weren't saved: {exc}.\n", file=sys.stderr)
3496
3756
  elif issubclass(exc_type, NotSignedIn):
3497
3757
  print("\n LinkedIn isn't signed in, so nothing was read. Sign in with --login, then run it again.\n",
3498
3758
  file=sys.stderr)
@@ -3519,6 +3779,10 @@ Examples:
3519
3779
  if args.login:
3520
3780
  raise SystemExit(0 if open_login_window() else 1)
3521
3781
 
3782
+ if args.save_photos:
3783
+ save_waiting_photos(say_none=True)
3784
+ raise SystemExit(0)
3785
+
3522
3786
  if not args.server:
3523
3787
  resolve_active_user()
3524
3788
 
@@ -3552,7 +3816,10 @@ Examples:
3552
3816
  elif args.rescrape:
3553
3817
  rescrape_bridge(args.rescrape, headless=args.headless, max_pages=args.max_pages)
3554
3818
  elif args.bridge_url:
3555
- scrape_bridge(None, headless=args.headless, max_pages=args.max_pages, deeper=True,
3819
+ # Carries on unless --from-start. A server started before a pull (TRAPS §22)
3820
+ # still sends Resume as a bare --bridge-url, and a read from page 1 would
3821
+ # spend searches on pages already read.
3822
+ scrape_bridge(None, headless=args.headless, max_pages=args.max_pages, deeper=not args.from_start,
3556
3823
  profile_url=args.bridge_url)
3557
3824
  elif args.bridge:
3558
3825
  scrape_bridge(args.bridge, headless=args.headless, max_pages=args.max_pages, deeper=args.deeper)
@@ -3569,6 +3836,15 @@ Examples:
3569
3836
  print("\nNo connections yet — walking your whole connections list.")
3570
3837
  print("This takes a couple of minutes and captures photos.\n")
3571
3838
  scrape_connections(headless=args.headless, full_walk=True)
3839
+
3840
+ # Photos an older version kept as links, saved once the scan's own are
3841
+ # (so a photo it just read wins). Not after a stop, which ends the run
3842
+ # now, and a failure here doesn't undo a scan that worked.
3843
+ if not args.server and not stop_requested():
3844
+ try:
3845
+ save_waiting_photos()
3846
+ except (SaveFailed, TryLater) as e:
3847
+ print(f" The photos kept as links weren't saved this time: {e}")
3572
3848
  except (BudgetReached, CoolingDown) as exc:
3573
3849
  print("\n " + (budget_message(exc.kind) if isinstance(exc, BudgetReached) else str(exc)))
3574
3850
  raise SystemExit(0)
@@ -9,7 +9,7 @@ const currentPort = parseInt(process.env.PORT, 10) || 3000
9
9
  const hostname = process.env.HOSTNAME || '0.0.0.0'
10
10
 
11
11
  let keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT, 10)
12
- const nextConfig = {"env":{},"webpack":null,"typescript":{"ignoreBuildErrors":false},"typedRoutes":false,"distDir":"./.next","cleanDistDir":true,"assetPrefix":"","cacheMaxMemorySize":52428800,"configOrigin":"next.config.mjs","useFileSystemPublicRoutes":true,"generateEtags":true,"pageExtensions":["tsx","ts","jsx","js"],"instrumentationClientInject":[],"poweredByHeader":true,"compress":true,"images":{"deviceSizes":[640,750,828,1080,1200,1920,2048,3840],"imageSizes":[32,48,64,96,128,256,384],"path":"/_next/image","loader":"default","loaderFile":"","domains":[],"disableStaticImages":false,"minimumCacheTTL":14400,"formats":["image/webp"],"maximumRedirects":3,"maximumResponseBody":50000000,"dangerouslyAllowLocalIP":false,"dangerouslyAllowSVG":false,"contentSecurityPolicy":"script-src 'none'; frame-src 'none'; sandbox;","contentDispositionType":"attachment","localPatterns":[{"pathname":"**","search":""}],"remotePatterns":[],"qualities":[75],"unoptimized":false,"customCacheHandler":false},"devIndicators":{"position":"bottom-left"},"onDemandEntries":{"maxInactiveAge":60000,"pagesBufferLength":5},"basePath":"","sassOptions":{},"trailingSlash":false,"i18n":null,"productionBrowserSourceMaps":false,"excludeDefaultMomentLocales":true,"reactProductionProfiling":false,"reactStrictMode":null,"reactMaxHeadersLength":6000,"httpAgentOptions":{"keepAlive":true},"logging":{"serverFunctions":true,"browserToTerminal":"warn"},"compiler":{},"expireTime":31536000,"staticPageGenerationTimeout":60,"output":"standalone","modularizeImports":{"@mui/icons-material":{"transform":"@mui/icons-material/{{member}}"},"lodash":{"transform":"lodash/{{member}}"}},"outputFileTracingRoot":"/home/runner/work/six-degrees/six-degrees","enablePrerenderSourceMaps":true,"cacheComponents":false,"cacheLife":{"default":{"stale":300,"revalidate":900,"expire":4294967294},"seconds":{"stale":30,"revalidate":1,"expire":60},"minutes":{"stale":300,"revalidate":60,"expire":3600},"hours":{"stale":300,"revalidate":3600,"expire":86400},"days":{"stale":300,"revalidate":86400,"expire":604800},"weeks":{"stale":300,"revalidate":604800,"expire":2592000},"max":{"stale":300,"revalidate":2592000,"expire":31536000}},"cacheHandlers":{},"experimental":{"appNewScrollHandler":true,"coldCacheBadge":false,"devValidationWorker":true,"useSkewCookie":false,"cssChunking":true,"multiZoneDraftMode":false,"appNavFailHandling":false,"prerenderEarlyExit":true,"serverMinification":true,"linkNoTouchStart":false,"caseSensitiveRoutes":false,"cachedNavigations":false,"dynamicOnHover":false,"useOffline":false,"varyParams":true,"optimisticRouting":true,"instrumentationClientRouterTransitionEvents":false,"prefetchInlining":{"maxSize":2048,"maxBundleSize":10240},"preloadEntriesOnStart":true,"clientRouterFilter":true,"clientRouterFilterRedirects":false,"fetchCacheKeyPrefix":"","proxyPrefetch":"flexible","optimisticClientCache":true,"manualClientBasePath":false,"cpus":3,"memoryBasedWorkersCount":false,"imgOptConcurrency":null,"imgOptOperationCache":null,"imgOptTimeoutInSeconds":7,"imgOptMaxInputPixels":268402689,"imgOptSequentialRead":null,"isrFlushToDisk":true,"workerThreads":false,"optimizeCss":false,"nextScriptWorkers":false,"scrollRestoration":false,"externalDir":false,"devMemoryThresholdRestart":true,"disableOptimizedLoading":false,"gzipSize":true,"craCompat":false,"esmExternals":true,"fullySpecified":false,"swcTraceProfiling":false,"forceSwcTransforms":false,"requestInsights":false,"largePageDataBytes":128000,"typedEnv":false,"parallelServerCompiles":false,"parallelServerBuildTraces":false,"ppr":false,"authInterrupts":false,"webpackMemoryOptimizations":false,"optimizeServerReact":true,"strictRouteTypes":false,"useTypeScriptCli":true,"removeUncaughtErrorAndRejectionListeners":false,"validateRSCRequestHeaders":true,"staleTimes":{"dynamic":0,"static":300},"reactDebugChannel":true,"serverComponentsHmrCache":true,"serverComponentsHmrCancellation":false,"staticGenerationMaxConcurrency":8,"staticGenerationMinPagesPerWorker":25,"transitionIndicator":false,"gestureTransition":false,"inlineCss":false,"useCache":false,"globalNotFound":false,"browserDebugInfoInTerminal":"warn","lockDistDir":true,"proxyClientMaxBodySize":10485760,"hideLogsAfterAbort":false,"mcpServer":true,"turbopackFileSystemCacheForDev":true,"turbopackFileSystemCacheForBuild":true,"turbopackInferModuleSideEffects":true,"turbopackPluginRuntimeStrategy":"childProcesses","turbopackMemoryEvictionMode":"auto","optimizePackageImports":["lucide-react","date-fns","lodash-es","ramda","antd","react-bootstrap","ahooks","@ant-design/icons","@headlessui/react","@headlessui-float/react","@heroicons/react/20/solid","@heroicons/react/24/solid","@heroicons/react/24/outline","@visx/visx","@tremor/react","rxjs","@mui/material","@mui/icons-material","recharts","react-use","effect","@effect/schema","@effect/platform","@effect/platform-node","@effect/platform-browser","@effect/platform-bun","@effect/sql","@effect/sql-mssql","@effect/sql-mysql2","@effect/sql-pg","@effect/sql-sqlite-node","@effect/sql-sqlite-bun","@effect/sql-sqlite-wasm","@effect/sql-sqlite-react-native","@effect/rpc","@effect/rpc-http","@effect/typeclass","@effect/experimental","@effect/opentelemetry","@material-ui/core","@material-ui/icons","@tabler/icons-react","mui-core","react-icons/ai","react-icons/bi","react-icons/bs","react-icons/cg","react-icons/ci","react-icons/di","react-icons/fa","react-icons/fa6","react-icons/fc","react-icons/fi","react-icons/gi","react-icons/go","react-icons/gr","react-icons/hi","react-icons/hi2","react-icons/im","react-icons/io","react-icons/io5","react-icons/lia","react-icons/lib","react-icons/lu","react-icons/md","react-icons/pi","react-icons/ri","react-icons/rx","react-icons/si","react-icons/sl","react-icons/tb","react-icons/tfi","react-icons/ti","react-icons/vsc","react-icons/wi"],"useCacheTimeout":54,"instantInsights":{"validationLevel":"warning"},"trustHostHeader":false,"isExperimentalCompile":false},"htmlLimitedBots":"[\\w-]+-Google|Google-[\\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight","bundlePagesRouterDependencies":false,"configFileName":"next.config.mjs","outputFileTracingExcludes":{"*":["dist/*.app/**","dist/*.dmg","dist/staging/**","dist/electron-stage/**","docs/**","tests/**",".git",".git/**","*.log","scripts/dmg/**","scripts/pin-python-packages.mjs","desktop/**","node_modules/electron/**","node_modules/@electron/**","site/**"]},"serverExternalPackages":["node:sqlite"],"repoRoot":"/home/runner/work/six-degrees/six-degrees","turbopack":{"root":"/home/runner/work/six-degrees/six-degrees"},"distDirRoot":".next"}
12
+ const nextConfig = {"env":{},"webpack":null,"typescript":{"ignoreBuildErrors":false},"typedRoutes":false,"distDir":"./.next","cleanDistDir":true,"assetPrefix":"","cacheMaxMemorySize":52428800,"configOrigin":"next.config.mjs","useFileSystemPublicRoutes":true,"generateEtags":true,"pageExtensions":["tsx","ts","jsx","js"],"instrumentationClientInject":[],"poweredByHeader":true,"compress":true,"images":{"deviceSizes":[640,750,828,1080,1200,1920,2048,3840],"imageSizes":[32,48,64,96,128,256,384],"path":"/_next/image","loader":"default","loaderFile":"","domains":[],"disableStaticImages":false,"minimumCacheTTL":14400,"formats":["image/webp"],"maximumRedirects":3,"maximumResponseBody":50000000,"dangerouslyAllowLocalIP":false,"dangerouslyAllowSVG":false,"contentSecurityPolicy":"script-src 'none'; frame-src 'none'; sandbox;","contentDispositionType":"attachment","localPatterns":[{"pathname":"**","search":""}],"remotePatterns":[],"qualities":[75],"unoptimized":false,"customCacheHandler":false},"devIndicators":{"position":"bottom-left"},"onDemandEntries":{"maxInactiveAge":60000,"pagesBufferLength":5},"basePath":"","sassOptions":{},"trailingSlash":false,"i18n":null,"productionBrowserSourceMaps":false,"excludeDefaultMomentLocales":true,"reactProductionProfiling":false,"reactStrictMode":null,"reactMaxHeadersLength":6000,"httpAgentOptions":{"keepAlive":true},"logging":{"serverFunctions":true,"browserToTerminal":"warn"},"compiler":{},"expireTime":31536000,"staticPageGenerationTimeout":60,"output":"standalone","modularizeImports":{"@mui/icons-material":{"transform":"@mui/icons-material/{{member}}"},"lodash":{"transform":"lodash/{{member}}"}},"outputFileTracingRoot":"/home/runner/work/six-degrees/six-degrees","enablePrerenderSourceMaps":true,"cacheComponents":false,"cacheLife":{"default":{"stale":300,"revalidate":900,"expire":4294967294},"seconds":{"stale":30,"revalidate":1,"expire":60},"minutes":{"stale":300,"revalidate":60,"expire":3600},"hours":{"stale":300,"revalidate":3600,"expire":86400},"days":{"stale":300,"revalidate":86400,"expire":604800},"weeks":{"stale":300,"revalidate":604800,"expire":2592000},"max":{"stale":300,"revalidate":2592000,"expire":31536000}},"cacheHandlers":{},"experimental":{"appNewScrollHandler":true,"coldCacheBadge":false,"devValidationWorker":true,"useSkewCookie":false,"cssChunking":true,"multiZoneDraftMode":false,"appNavFailHandling":false,"prerenderEarlyExit":true,"serverMinification":true,"linkNoTouchStart":false,"caseSensitiveRoutes":false,"cachedNavigations":false,"dynamicOnHover":false,"useOffline":false,"varyParams":true,"optimisticRouting":true,"instrumentationClientRouterTransitionEvents":false,"prefetchInlining":{"maxSize":2048,"maxBundleSize":10240},"preloadEntriesOnStart":true,"clientRouterFilter":true,"clientRouterFilterRedirects":false,"fetchCacheKeyPrefix":"","proxyPrefetch":"flexible","optimisticClientCache":true,"manualClientBasePath":false,"cpus":3,"memoryBasedWorkersCount":false,"imgOptConcurrency":null,"imgOptOperationCache":null,"imgOptTimeoutInSeconds":7,"imgOptMaxInputPixels":268402689,"imgOptSequentialRead":null,"isrFlushToDisk":true,"workerThreads":false,"optimizeCss":false,"nextScriptWorkers":false,"scrollRestoration":false,"externalDir":false,"devMemoryThresholdRestart":true,"disableOptimizedLoading":false,"gzipSize":true,"craCompat":false,"esmExternals":true,"fullySpecified":false,"swcTraceProfiling":false,"forceSwcTransforms":false,"requestInsights":false,"largePageDataBytes":128000,"typedEnv":false,"parallelServerCompiles":false,"parallelServerBuildTraces":false,"ppr":false,"authInterrupts":false,"webpackMemoryOptimizations":false,"optimizeServerReact":true,"strictRouteTypes":false,"useTypeScriptCli":true,"removeUncaughtErrorAndRejectionListeners":false,"validateRSCRequestHeaders":true,"staleTimes":{"dynamic":0,"static":300},"reactDebugChannel":true,"serverComponentsHmrCache":true,"serverComponentsHmrCancellation":false,"staticGenerationMaxConcurrency":8,"staticGenerationMinPagesPerWorker":25,"transitionIndicator":false,"gestureTransition":false,"inlineCss":false,"useCache":false,"globalNotFound":false,"browserDebugInfoInTerminal":"warn","lockDistDir":true,"proxyClientMaxBodySize":10485760,"hideLogsAfterAbort":false,"mcpServer":true,"turbopackFileSystemCacheForDev":true,"turbopackFileSystemCacheForBuild":true,"turbopackInferModuleSideEffects":true,"turbopackPluginRuntimeStrategy":"childProcesses","turbopackMemoryEvictionMode":"auto","optimizePackageImports":["lucide-react","date-fns","lodash-es","ramda","antd","react-bootstrap","ahooks","@ant-design/icons","@headlessui/react","@headlessui-float/react","@heroicons/react/20/solid","@heroicons/react/24/solid","@heroicons/react/24/outline","@visx/visx","@tremor/react","rxjs","@mui/material","@mui/icons-material","recharts","react-use","effect","@effect/schema","@effect/platform","@effect/platform-node","@effect/platform-browser","@effect/platform-bun","@effect/sql","@effect/sql-mssql","@effect/sql-mysql2","@effect/sql-pg","@effect/sql-sqlite-node","@effect/sql-sqlite-bun","@effect/sql-sqlite-wasm","@effect/sql-sqlite-react-native","@effect/rpc","@effect/rpc-http","@effect/typeclass","@effect/experimental","@effect/opentelemetry","@material-ui/core","@material-ui/icons","@tabler/icons-react","mui-core","react-icons/ai","react-icons/bi","react-icons/bs","react-icons/cg","react-icons/ci","react-icons/di","react-icons/fa","react-icons/fa6","react-icons/fc","react-icons/fi","react-icons/gi","react-icons/go","react-icons/gr","react-icons/hi","react-icons/hi2","react-icons/im","react-icons/io","react-icons/io5","react-icons/lia","react-icons/lib","react-icons/lu","react-icons/md","react-icons/pi","react-icons/ri","react-icons/rx","react-icons/si","react-icons/sl","react-icons/tb","react-icons/tfi","react-icons/ti","react-icons/vsc","react-icons/wi"],"useCacheTimeout":54,"instantInsights":{"validationLevel":"warning"},"trustHostHeader":false,"isExperimentalCompile":false},"htmlLimitedBots":"[\\w-]+-Google|Google-[\\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight","bundlePagesRouterDependencies":false,"configFileName":"next.config.mjs","outputFileTracingExcludes":{"*":["dist/*.app/**","dist/*.dmg","dist/staging/**","dist/electron-stage/**","docs/**","tests/**",".git",".git/**","*.log","scripts/dmg/**","scripts/pin-python-packages.mjs","scripts/check-site-version.mjs","desktop/**","node_modules/electron/**","node_modules/@electron/**","site/**","*.csv","*.sqlite*","*.sixdegrees","chrome-profile/**"]},"serverExternalPackages":["node:sqlite"],"repoRoot":"/home/runner/work/six-degrees/six-degrees","turbopack":{"root":"/home/runner/work/six-degrees/six-degrees"},"distDirRoot":".next"}
13
13
 
14
14
  process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig)
15
15