six-degrees 0.4.11 → 0.5.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 (375) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +9 -9
  4. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  5. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  6. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  10. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  11. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  12. package/.next/standalone/.next/server/app/_not-found.rsc +12 -12
  13. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
  14. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +11 -11
  15. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  16. package/.next/standalone/.next/server/app/api/admin-delete/route.js +1 -1
  17. package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
  18. package/.next/standalone/.next/server/app/api/admin-update/route.js +1 -1
  19. package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
  20. package/.next/standalone/.next/server/app/api/bridges/route.js +1 -1
  21. package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/bulk-import/route.js +1 -1
  23. package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js +1 -1
  25. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/company-scores/route.js +1 -1
  27. package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/connections/route.js +1 -1
  29. package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/delete-cluster/route.js +1 -1
  36. package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/ingest/route.js +1 -1
  38. package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/network/route.js +1 -1
  40. package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/api/notifications/route.js +1 -1
  42. package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/api/outreach/route.js +1 -1
  44. package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/api/queue/route.js +1 -1
  46. package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
  47. package/.next/standalone/.next/server/app/api/scraper/route.js +3 -2
  48. package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/api/settings/route.js +1 -1
  50. package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
  51. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js +1 -1
  52. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js +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/social/crm/route.js.nft.json +1 -1
  58. package/.next/standalone/.next/server/app/api/social/messages/route.js.nft.json +1 -1
  59. package/.next/standalone/.next/server/app/api/social/route.js +3 -3
  60. package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -1
  61. package/.next/standalone/.next/server/app/api/unlock/route.js +1 -1
  62. package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
  63. package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  64. package/.next/standalone/.next/server/app/api/update-images/route.js +1 -1
  65. package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/api/users/route.js +1 -1
  67. package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
  68. package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
  69. package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
  71. package/.next/standalone/.next/server/app/import.html +1 -1
  72. package/.next/standalone/.next/server/app/import.rsc +14 -14
  73. package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +14 -14
  74. package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +1 -1
  75. package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +13 -13
  76. package/.next/standalone/.next/server/app/index.html +1 -1
  77. package/.next/standalone/.next/server/app/index.rsc +14 -14
  78. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
  79. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
  80. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  81. package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
  82. package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
  83. package/.next/standalone/.next/server/app/launch.html +2 -2
  84. package/.next/standalone/.next/server/app/launch.rsc +14 -14
  85. package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +14 -14
  86. package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +1 -1
  87. package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +13 -13
  88. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  89. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  90. package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
  91. package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
  92. package/.next/standalone/.next/server/app/paths.html +1 -1
  93. package/.next/standalone/.next/server/app/paths.rsc +14 -14
  94. package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +14 -14
  95. package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +1 -1
  96. package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +13 -13
  97. package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
  98. package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
  99. package/.next/standalone/.next/server/app/profile.html +1 -1
  100. package/.next/standalone/.next/server/app/profile.rsc +14 -14
  101. package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +14 -14
  102. package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +1 -1
  103. package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +13 -13
  104. package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
  105. package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
  106. package/.next/standalone/.next/server/app/queue.html +1 -1
  107. package/.next/standalone/.next/server/app/queue.rsc +14 -14
  108. package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +14 -14
  109. package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +1 -1
  110. package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +13 -13
  111. package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
  112. package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
  113. package/.next/standalone/.next/server/app/scores.html +1 -1
  114. package/.next/standalone/.next/server/app/scores.rsc +14 -14
  115. package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +14 -14
  116. package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +1 -1
  117. package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +13 -13
  118. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  119. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  120. package/.next/standalone/.next/server/app/settings.html +1 -1
  121. package/.next/standalone/.next/server/app/settings.rsc +14 -14
  122. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +14 -14
  123. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  124. package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +13 -13
  125. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  126. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  127. package/.next/standalone/.next/server/app/setup.html +1 -1
  128. package/.next/standalone/.next/server/app/setup.rsc +14 -14
  129. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +14 -14
  130. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +1 -1
  131. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +13 -13
  132. package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -1
  133. package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
  134. package/.next/standalone/.next/server/app/social.html +1 -1
  135. package/.next/standalone/.next/server/app/social.rsc +14 -14
  136. package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +14 -14
  137. package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +1 -1
  138. package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +13 -13
  139. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0bat8nx._.js → [root-of-the-server]__13dx4sd._.js} +2 -2
  140. package/.next/standalone/.next/server/chunks/[root-of-the-server]__15wc9e-._.js +3 -0
  141. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +1 -1
  142. package/.next/standalone/.next/server/chunks/[root-of-the-server]__20rwfdw._.js +8 -0
  143. package/.next/standalone/.next/server/chunks/_072jl0y._.js +1 -1
  144. package/.next/standalone/.next/server/chunks/_1_fynia._.js +7 -7
  145. package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
  146. package/.next/standalone/.next/server/chunks/_1iwkh8t._.js +1 -1
  147. package/.next/standalone/.next/server/chunks/lib_0851sk0._.js +8 -0
  148. package/.next/standalone/.next/server/chunks/lib_1q7p2ro._.js +8 -0
  149. package/.next/standalone/.next/server/chunks/lib_1r6l86l._.js +8 -0
  150. package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +8 -8
  151. package/.next/standalone/.next/server/chunks/lib_rpc_04s8mzr.js +3 -0
  152. package/.next/standalone/.next/server/chunks/lib_rpc_053z7as.js +3 -0
  153. package/.next/standalone/.next/server/chunks/lib_rpc_0b_-2te.js +3 -0
  154. package/.next/standalone/.next/server/chunks/lib_rpc_0dhbxdm.js +3 -0
  155. package/.next/standalone/.next/server/chunks/lib_rpc_0dxlk31.js +3 -0
  156. package/.next/standalone/.next/server/chunks/lib_rpc_0f8b-qc.js +3 -0
  157. package/.next/standalone/.next/server/chunks/lib_rpc_0pzie7_.js +3 -0
  158. package/.next/standalone/.next/server/chunks/lib_rpc_0r76fq_.js +3 -0
  159. package/.next/standalone/.next/server/chunks/lib_rpc_0ut7pjp.js +3 -0
  160. package/.next/standalone/.next/server/chunks/lib_rpc_15zyqaz.js +3 -0
  161. package/.next/standalone/.next/server/chunks/lib_rpc_18zpjtq.js +3 -0
  162. package/.next/standalone/.next/server/chunks/lib_rpc_1a8xkiz.js +3 -0
  163. package/.next/standalone/.next/server/chunks/lib_rpc_1e_55xo.js +3 -0
  164. package/.next/standalone/.next/server/chunks/lib_rpc_1oxfd1_.js +3 -0
  165. package/.next/standalone/.next/server/chunks/lib_rpc_1s0lod0.js +3 -0
  166. package/.next/standalone/.next/server/chunks/lib_scoring_1-e4ur0.js +1 -1
  167. package/.next/standalone/.next/server/chunks/ssr/_01kacen._.js +3 -0
  168. package/.next/standalone/.next/server/chunks/ssr/_03l7vge._.js +3 -0
  169. package/.next/standalone/.next/server/chunks/ssr/{_1ap1tu_._.js → _0gtv9jb._.js} +2 -2
  170. package/.next/standalone/.next/server/chunks/ssr/_0jdkqgi._.js +3 -0
  171. package/.next/standalone/.next/server/chunks/ssr/_0xi-fbs._.js +3 -0
  172. package/.next/standalone/.next/server/chunks/ssr/_0xmwmfv._.js +3 -0
  173. package/.next/standalone/.next/server/chunks/ssr/_1-b9hj7._.js +9 -0
  174. package/.next/standalone/.next/server/chunks/ssr/_112ewem._.js +3 -0
  175. package/.next/standalone/.next/server/chunks/ssr/_16ab63q._.js +3 -0
  176. package/.next/standalone/.next/server/chunks/ssr/_1fxm0-s._.js +3 -0
  177. package/.next/standalone/.next/server/chunks/ssr/_1ra1d3r._.js +9 -0
  178. package/.next/standalone/.next/server/chunks/ssr/app_141w189._.js +3 -0
  179. package/.next/standalone/.next/server/chunks/ssr/app_components_ui_0pdmz2z.js +3 -0
  180. package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +22 -10
  181. package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +2 -2
  182. package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
  183. package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +6 -5
  184. package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +1 -1
  185. package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +19 -3
  186. package/.next/standalone/.next/server/chunks/ssr/app_social_page_0h7zxda.js +1 -2
  187. package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +1 -1
  188. package/.next/standalone/.next/server/chunks/ssr/lib_13rapwj._.js +3 -0
  189. package/.next/standalone/.next/server/chunks/ssr/lib_galaxy-lab_108_lu-.js +1 -1
  190. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  191. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  192. package/.next/standalone/.next/server/pages/404.html +1 -1
  193. package/.next/standalone/.next/server/pages/500.html +1 -1
  194. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  195. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  196. package/.next/standalone/.next/static/chunks/03bziijw5srol.js +1 -0
  197. package/.next/standalone/.next/static/chunks/0bgq8wmopl_ig.js +7 -0
  198. package/.next/standalone/.next/static/chunks/0ezus6p2eb-vg.js +1 -0
  199. package/.next/standalone/.next/static/chunks/0g_fy2034bp0x.js +34 -0
  200. package/.next/standalone/.next/static/chunks/0q5dru4q6gxl5.js +22 -0
  201. package/.next/standalone/.next/static/chunks/0w-byiwb2zax8.js +2 -0
  202. package/.next/standalone/.next/static/chunks/1g7jcovdejc9-.js +1 -0
  203. package/.next/standalone/.next/static/chunks/1i51yylxdzht1.js +1 -0
  204. package/.next/standalone/.next/static/chunks/1zf8swaasjf1e.js +43 -0
  205. package/.next/standalone/.next/static/chunks/2rqtpccruavf2.js +1 -0
  206. package/.next/standalone/.next/static/chunks/2wgz9ietkqqe6.js +1 -0
  207. package/.next/standalone/.next/static/chunks/30x3oq_st7a5r.js +1 -0
  208. package/.next/standalone/.next/static/chunks/355ryio1b7sm1.js +1 -0
  209. package/.next/standalone/.next/static/chunks/3ap5hgma7n0o7.js +1 -0
  210. package/.next/standalone/.next/static/chunks/3fci6qi39-1n5.js +1 -0
  211. package/.next/standalone/.next/static/chunks/{24tvdun1cb3g1.js → 3sowbvbaj1s6n.js} +1 -1
  212. package/.next/standalone/CHANGELOG.md +187 -0
  213. package/.next/standalone/app/api/ingest/route.js +40 -1
  214. package/.next/standalone/app/api/scraper/route.js +7 -1
  215. package/.next/standalone/app/api/settings/sector-preview/route.js +2 -1
  216. package/.next/standalone/app/components/AppTabs.js +59 -0
  217. package/.next/standalone/app/components/ChainView.js +303 -89
  218. package/.next/standalone/app/components/CirclePanel.js +162 -0
  219. package/.next/standalone/app/components/CompanyScores.js +2 -2
  220. package/.next/standalone/app/components/FieldStep.js +4 -4
  221. package/.next/standalone/app/components/FilterPanel.js +121 -183
  222. package/.next/standalone/app/components/ForceGraph.js +41 -3
  223. package/.next/standalone/app/components/GalaxyLab.js +26 -31
  224. package/.next/standalone/app/components/LegacyScoresCard.js +2 -2
  225. package/.next/standalone/app/components/MapControls.js +154 -0
  226. package/.next/standalone/app/components/NoteDetail.js +118 -0
  227. package/.next/standalone/app/components/PathsAnalyzer.js +147 -110
  228. package/.next/standalone/app/components/PeopleMap.js +308 -0
  229. package/.next/standalone/app/components/ScanRadar.js +129 -0
  230. package/.next/standalone/app/components/ScanStatusBar.js +71 -27
  231. package/.next/standalone/app/components/SeparationView.js +626 -118
  232. package/.next/standalone/app/components/Sidebar.js +28 -2
  233. package/.next/standalone/app/components/settings/NetworkHealthSection.js +1 -1
  234. package/.next/standalone/app/components/settings/SectorSection.js +2 -2
  235. package/.next/standalone/app/components/settings/TierSection.js +1 -1
  236. package/.next/standalone/app/components/settings/TitleSection.js +149 -0
  237. package/.next/standalone/app/components/views.js +15 -14
  238. package/.next/standalone/app/page.js +89 -156
  239. package/.next/standalone/app/paths/page.js +39 -27
  240. package/.next/standalone/app/profile/page.js +33 -5
  241. package/.next/standalone/app/queue/page.js +56 -34
  242. package/.next/standalone/app/scores/page.js +16 -66
  243. package/.next/standalone/app/settings/page.js +27 -13
  244. package/.next/standalone/app/setup/page.js +298 -205
  245. package/.next/standalone/app/social/SocialHub.js +374 -0
  246. package/.next/standalone/app/social/page.js +10 -362
  247. package/.next/standalone/lib/chain-layout.js +105 -35
  248. package/.next/standalone/lib/degrees.js +0 -6
  249. package/.next/standalone/lib/dot-rings.js +25 -0
  250. package/.next/standalone/lib/galaxy-lab.js +65 -6
  251. package/.next/standalone/lib/ingest.js +2 -0
  252. package/.next/standalone/lib/island.js +40 -0
  253. package/.next/standalone/lib/linkedin-limits.js +6 -2
  254. package/.next/standalone/lib/map-heat.js +20 -0
  255. package/.next/standalone/lib/network-health.js +1 -1
  256. package/.next/standalone/lib/notifications.js +121 -0
  257. package/.next/standalone/lib/people-map.js +82 -0
  258. package/.next/standalone/lib/rarity.js +45 -3
  259. package/.next/standalone/lib/reach.js +7 -6
  260. package/.next/standalone/lib/rpc.js +15 -4
  261. package/.next/standalone/lib/scan-pace.js +46 -0
  262. package/.next/standalone/lib/scoring.js +39 -8
  263. package/.next/standalone/lib/scraper-client.js +19 -6
  264. package/.next/standalone/lib/sector-focus.js +4 -4
  265. package/.next/standalone/lib/separation.js +137 -10
  266. package/.next/standalone/lib/settings-effects.js +20 -11
  267. package/.next/standalone/lib/settings.js +3 -0
  268. package/.next/standalone/lib/tier-grid.js +99 -0
  269. package/.next/standalone/lib/title-ranking.js +83 -0
  270. package/.next/standalone/package-lock.json +2 -2
  271. package/.next/standalone/package.json +1 -1
  272. package/.next/standalone/scripts/scrape.py +51 -13
  273. package/.next/static/chunks/03bziijw5srol.js +1 -0
  274. package/.next/static/chunks/0bgq8wmopl_ig.js +7 -0
  275. package/.next/static/chunks/0ezus6p2eb-vg.js +1 -0
  276. package/.next/static/chunks/0g_fy2034bp0x.js +34 -0
  277. package/.next/static/chunks/0q5dru4q6gxl5.js +22 -0
  278. package/.next/static/chunks/0w-byiwb2zax8.js +2 -0
  279. package/.next/static/chunks/1g7jcovdejc9-.js +1 -0
  280. package/.next/static/chunks/1i51yylxdzht1.js +1 -0
  281. package/.next/static/chunks/1zf8swaasjf1e.js +43 -0
  282. package/.next/static/chunks/2rqtpccruavf2.js +1 -0
  283. package/.next/static/chunks/2wgz9ietkqqe6.js +1 -0
  284. package/.next/static/chunks/30x3oq_st7a5r.js +1 -0
  285. package/.next/static/chunks/355ryio1b7sm1.js +1 -0
  286. package/.next/static/chunks/3ap5hgma7n0o7.js +1 -0
  287. package/.next/static/chunks/3fci6qi39-1n5.js +1 -0
  288. package/.next/static/chunks/{24tvdun1cb3g1.js → 3sowbvbaj1s6n.js} +1 -1
  289. package/CHANGELOG.md +187 -0
  290. package/lib/chain-layout.js +105 -35
  291. package/lib/degrees.js +0 -6
  292. package/lib/dot-rings.js +25 -0
  293. package/lib/galaxy-lab.js +65 -6
  294. package/lib/ingest.js +2 -0
  295. package/lib/island.js +40 -0
  296. package/lib/linkedin-limits.js +6 -2
  297. package/lib/map-heat.js +20 -0
  298. package/lib/network-health.js +1 -1
  299. package/lib/notifications.js +121 -0
  300. package/lib/people-map.js +82 -0
  301. package/lib/rarity.js +45 -3
  302. package/lib/reach.js +7 -6
  303. package/lib/rpc.js +15 -4
  304. package/lib/scan-pace.js +46 -0
  305. package/lib/scoring.js +39 -8
  306. package/lib/scraper-client.js +19 -6
  307. package/lib/sector-focus.js +4 -4
  308. package/lib/separation.js +137 -10
  309. package/lib/settings-effects.js +20 -11
  310. package/lib/settings.js +3 -0
  311. package/lib/tier-grid.js +99 -0
  312. package/lib/title-ranking.js +83 -0
  313. package/package.json +1 -1
  314. package/scripts/scrape.py +51 -13
  315. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17hjtzl._.js +0 -8
  316. package/.next/standalone/.next/server/chunks/[root-of-the-server]__20tmem7._.js +0 -3
  317. package/.next/standalone/.next/server/chunks/lib_rpc_03i8xef.js +0 -3
  318. package/.next/standalone/.next/server/chunks/lib_rpc_05gt5ya.js +0 -3
  319. package/.next/standalone/.next/server/chunks/lib_rpc_08fc3rp.js +0 -3
  320. package/.next/standalone/.next/server/chunks/lib_rpc_08r2t_q.js +0 -3
  321. package/.next/standalone/.next/server/chunks/lib_rpc_0btpv1t.js +0 -3
  322. package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +0 -8
  323. package/.next/standalone/.next/server/chunks/lib_rpc_0cm2ev3.js +0 -3
  324. package/.next/standalone/.next/server/chunks/lib_rpc_0iovswz.js +0 -3
  325. package/.next/standalone/.next/server/chunks/lib_rpc_0is8flp.js +0 -3
  326. package/.next/standalone/.next/server/chunks/lib_rpc_0v8ajtd.js +0 -3
  327. package/.next/standalone/.next/server/chunks/lib_rpc_0xgmvvt.js +0 -3
  328. package/.next/standalone/.next/server/chunks/lib_rpc_144mzbf.js +0 -3
  329. package/.next/standalone/.next/server/chunks/lib_rpc_1bggi0n.js +0 -3
  330. package/.next/standalone/.next/server/chunks/lib_rpc_1dkcu9f.js +0 -3
  331. package/.next/standalone/.next/server/chunks/lib_rpc_1o0jb6w.js +0 -3
  332. package/.next/standalone/.next/server/chunks/ssr/_0-85h6g._.js +0 -3
  333. package/.next/standalone/.next/server/chunks/ssr/_08cfj2b._.js +0 -3
  334. package/.next/standalone/.next/server/chunks/ssr/_0c_y09g._.js +0 -3
  335. package/.next/standalone/.next/server/chunks/ssr/_0j4-lc3._.js +0 -3
  336. package/.next/standalone/.next/server/chunks/ssr/_0l8cxmo._.js +0 -3
  337. package/.next/standalone/.next/server/chunks/ssr/_0xkur4p._.js +0 -3
  338. package/.next/standalone/.next/server/chunks/ssr/_14iv_zy._.js +0 -3
  339. package/.next/standalone/.next/server/chunks/ssr/_15x6q32._.js +0 -3
  340. package/.next/standalone/.next/server/chunks/ssr/_212xw8q._.js +0 -3
  341. package/.next/standalone/.next/server/chunks/ssr/app_components_19c2d9c._.js +0 -3
  342. package/.next/standalone/.next/server/chunks/ssr/app_scores_page_09wepvv.js +0 -3
  343. package/.next/standalone/.next/static/chunks/0a5iadnmt9u9k.js +0 -1
  344. package/.next/standalone/.next/static/chunks/15sjx07xhpn6p.js +0 -1
  345. package/.next/standalone/.next/static/chunks/160wjb73ddwqd.js +0 -6
  346. package/.next/standalone/.next/static/chunks/1a74dky22dxd6.js +0 -1
  347. package/.next/standalone/.next/static/chunks/1c_n0973t07l0.js +0 -2
  348. package/.next/standalone/.next/static/chunks/1e1n0kkz-psdb.js +0 -1
  349. package/.next/standalone/.next/static/chunks/236f0jtp68j_h.js +0 -1
  350. package/.next/standalone/.next/static/chunks/297e1z34yi--z.js +0 -1
  351. package/.next/standalone/.next/static/chunks/2_ak6qjxb6oa5.js +0 -1
  352. package/.next/standalone/.next/static/chunks/2kfhc58df2xyo.js +0 -1
  353. package/.next/standalone/.next/static/chunks/39hr8d-b2ibtw.js +0 -31
  354. package/.next/standalone/.next/static/chunks/3ui4d-uqkqj5d.js +0 -2
  355. package/.next/standalone/.next/static/chunks/3zw8bv7-mc4n6.js +0 -33
  356. package/.next/standalone/app/components/OrbitGraph.js +0 -737
  357. package/.next/static/chunks/0a5iadnmt9u9k.js +0 -1
  358. package/.next/static/chunks/15sjx07xhpn6p.js +0 -1
  359. package/.next/static/chunks/160wjb73ddwqd.js +0 -6
  360. package/.next/static/chunks/1a74dky22dxd6.js +0 -1
  361. package/.next/static/chunks/1c_n0973t07l0.js +0 -2
  362. package/.next/static/chunks/1e1n0kkz-psdb.js +0 -1
  363. package/.next/static/chunks/236f0jtp68j_h.js +0 -1
  364. package/.next/static/chunks/297e1z34yi--z.js +0 -1
  365. package/.next/static/chunks/2_ak6qjxb6oa5.js +0 -1
  366. package/.next/static/chunks/2kfhc58df2xyo.js +0 -1
  367. package/.next/static/chunks/39hr8d-b2ibtw.js +0 -31
  368. package/.next/static/chunks/3ui4d-uqkqj5d.js +0 -2
  369. package/.next/static/chunks/3zw8bv7-mc4n6.js +0 -33
  370. /package/.next/standalone/.next/static/{jqE2D3zWqSGz5su948Azf → I-cj79yE2IZ8taX2dZGp3}/_buildManifest.js +0 -0
  371. /package/.next/standalone/.next/static/{jqE2D3zWqSGz5su948Azf → I-cj79yE2IZ8taX2dZGp3}/_clientMiddlewareManifest.js +0 -0
  372. /package/.next/standalone/.next/static/{jqE2D3zWqSGz5su948Azf → I-cj79yE2IZ8taX2dZGp3}/_ssgManifest.js +0 -0
  373. /package/.next/static/{jqE2D3zWqSGz5su948Azf → I-cj79yE2IZ8taX2dZGp3}/_buildManifest.js +0 -0
  374. /package/.next/static/{jqE2D3zWqSGz5su948Azf → I-cj79yE2IZ8taX2dZGp3}/_clientMiddlewareManifest.js +0 -0
  375. /package/.next/static/{jqE2D3zWqSGz5su948Azf → I-cj79yE2IZ8taX2dZGp3}/_ssgManifest.js +0 -0
package/lib/separation.js CHANGED
@@ -86,11 +86,6 @@ export function comparePower(a, b) {
86
86
  }
87
87
  const NO_ROUTE = { id: null, bridge: null };
88
88
 
89
- /** "Ways in" order: most ways in first, then the power order. The rank shown stays the power rank. */
90
- export function compareWaysIn(a, b) {
91
- return b.waysIn - a.waysIn || comparePower(a, b);
92
- }
93
-
94
89
  /**
95
90
  * @param {object[]} degree2 2nd-degree rows (one per bridge per person)
96
91
  * @param {object[]} connections 1st-degree rows, the bridges
@@ -236,6 +231,10 @@ export function dotRadius(waysIn) {
236
231
 
237
232
  export const MIN_BRIDGE_GAP = 18;
238
233
  const TOP_PAD = 16;
234
+ /** Grey dots for mutual connections in circles not scanned yet, at most this many drawn. */
235
+ export const GHOST_MAX = 60;
236
+ const GHOST_LABEL = 40; // room under the last connection for the line that counts them
237
+ const PILL_W_LAYOUT = 236; // SeparationView's PILL_W: the connection pills' width as cards
239
238
 
240
239
  /**
241
240
  * The summit map: You → the bridges → the top of the list, every route drawn.
@@ -249,10 +248,13 @@ const TOP_PAD = 16;
249
248
  *
250
249
  * @param {object[]} top the first K people of whatever the list shows
251
250
  * @param {number} width map width in px
251
+ * @param {{ cards?: boolean }} opts cards: people drawn as cards and
252
+ * connections as pills (Blake, 2026-10-02), which need more room per row
252
253
  * @returns {{ width, height, you, people, bridges, links, spokes }}
253
254
  */
254
- export function summitLayout(top = [], width = 800, isMobile = false) {
255
- const gap = isMobile ? 36 : 26;
255
+ export function summitLayout(top = [], width = 800, isMobile = false, { cards = false } = {}) {
256
+ const gap = cards ? 50 : isMobile ? 36 : 26;
257
+ const bridgeGap = cards ? 36 : MIN_BRIDGE_GAP;
256
258
  const youX = 20;
257
259
  const bx = Math.round(width * 0.42);
258
260
  const px = Math.round(width * 0.58);
@@ -262,7 +264,7 @@ export function summitLayout(top = [], width = 800, isMobile = false) {
262
264
  p,
263
265
  x: px,
264
266
  y: TOP_PAD + i * gap,
265
- r: dotRadius(p.waysIn),
267
+ r: cards ? 0 : dotRadius(p.waysIn),
266
268
  label: fit(`#${p.rank}${p.tied ? '=' : ''} ${p.person.name || ''}`, Math.floor((width - px - 60) / 6.2)),
267
269
  }));
268
270
 
@@ -288,11 +290,16 @@ export function summitLayout(top = [], width = 800, isMobile = false) {
288
290
 
289
291
  const y0 = TOP_PAD;
290
292
  const y1 = people.length ? people[people.length - 1].y : TOP_PAD;
291
- const step = bridges.length > 1 ? Math.max(MIN_BRIDGE_GAP, (y1 - y0) / (bridges.length - 1)) : 0;
293
+ // Each connection sits level with the people they lead to (the middle of
294
+ // them), moved no more than it takes to keep `bridgeGap` from the next one,
295
+ // so a connection with one person sits on that person's row and the line
296
+ // between them runs straight (Blake, 2026-10-02: "the lines should line up
297
+ // with every box").
298
+ const ys = stackColumn(bridges.map((b) => b.bary), bridgeGap, y0);
292
299
  const nameBudget = Math.max(4, Math.floor((bx - 10 - (youX + 22)) / 6));
293
300
  bridges.forEach((b, j) => {
294
301
  b.x = bx;
295
- b.y = bridges.length === 1 ? (y0 + y1) / 2 : y0 + j * step;
302
+ b.y = ys[j];
296
303
  b.r = 5;
297
304
  if (b.unresolved) b.label = fit(`can't be named (${b.count})`, nameBudget);
298
305
  else {
@@ -302,6 +309,15 @@ export function summitLayout(top = [], width = 800, isMobile = false) {
302
309
  delete b.ys;
303
310
  });
304
311
  const nodeFor = new Map(bridges.map((b) => [b.unresolved ? UNRESOLVED : b.id, b]));
312
+ // What each connection is to the people on the map: the only door to some,
313
+ // the best way in to others, or only another way in.
314
+ for (const b of bridges) { b.onlyFor = 0; b.bestFor = 0; }
315
+ for (const pp of people) {
316
+ const first = pp.p.routes[0];
317
+ const bestNode = first ? nodeFor.get(first.bridge ? first.id : UNRESOLVED) : null;
318
+ if (bestNode) bestNode.bestFor++;
319
+ if (pp.p.waysIn === 1 && bestNode) bestNode.onlyFor++;
320
+ }
305
321
 
306
322
  // Routes, per person: routes[0] is the top-scored bridge. Two unnamed routes
307
323
  // for one person are one line to the one grey node.
@@ -326,3 +342,114 @@ export function summitLayout(top = [], width = 800, isMobile = false) {
326
342
  const spokes = bridges.map((b) => ({ id: b.id, x1: you.x, y1: you.y, x2: b.x, y2: b.y }));
327
343
  return { width, height, you, people, bridges, links, spokes };
328
344
  }
345
+
346
+ /**
347
+ * Heights as near the ones asked for as they can be while staying `gap`
348
+ * apart, in the order given (already sorted), never above `top`: neighbours
349
+ * that would overlap move apart by the same amount each way.
350
+ */
351
+ export function stackColumn(wanted = [], gap = MIN_BRIDGE_GAP, top = 0) {
352
+ const blocks = [];
353
+ wanted.forEach((y, k) => {
354
+ blocks.push({ sum: y - k * gap, count: 1 });
355
+ while (blocks.length > 1 && blocks[blocks.length - 2].sum / blocks[blocks.length - 2].count > blocks[blocks.length - 1].sum / blocks[blocks.length - 1].count) {
356
+ const last = blocks.pop();
357
+ blocks[blocks.length - 1].sum += last.sum;
358
+ blocks[blocks.length - 1].count += last.count;
359
+ }
360
+ });
361
+ const out = blocks.flatMap((b) => new Array(b.count).fill(b.sum / b.count)).map((q, k) => q + k * gap);
362
+ const lift = out.length && out[0] < top ? top - out[0] : 0;
363
+ return out.map((y) => y + lift);
364
+ }
365
+
366
+ /**
367
+ * Who the map aims at with the slider all the way to easy: whoever you share the
368
+ * most mutual connections with, scanned or not (Blake, 2026-10-02: "if we have
369
+ * mutuals but not scanned thats still a high probability … i dont want to hinder
370
+ * the algorithm since we havent scanned that person"). `mutualsOf` is LinkedIn's
371
+ * count where a scan saved one, never less than the ways in the app has drawn
372
+ * (lib/rarity.js). Then the most drawn, then the stronger person. Narrowing the
373
+ * tiers first (S only, say) keeps it to the people worth it.
374
+ */
375
+ export function mostMutuals(people = [], mutualsOf = () => 0, limit = 7) {
376
+ return [...people].sort((a, b) => (Math.max(mutualsOf(b) || 0, b.waysIn) - Math.max(mutualsOf(a) || 0, a.waysIn))
377
+ || b.waysIn - a.waysIn
378
+ || comparePower(a, b)).slice(0, limit);
379
+ }
380
+
381
+ /**
382
+ * How many people the map draws with the slider at `at` (lib/rarity.js): all
383
+ * `k` from the rare end to the middle, then fewer and fewer, down to the one
384
+ * person you're aiming at with the slider all the way to easy. Blake,
385
+ * 2026-10-02: at the rare end "it will show that one door"; at the other "all
386
+ * the connections going into that one person".
387
+ */
388
+ export function mapCount(at, k = 10) {
389
+ const v = Math.max(0, Math.min(100, Number(at) || 0));
390
+ if (v <= 50) return k;
391
+ return Math.max(1, Math.round((k * (100 - v)) / 50));
392
+ }
393
+
394
+ /**
395
+ * The map for one person: You → every connection of yours who leads to them →
396
+ * them, with rings round them. Routes the map can't name share one grey node,
397
+ * as in summitLayout, and it comes back in summitLayout's shape (`people` holds
398
+ * the one person, marked `big`), so one map draws both and can glide from one
399
+ * to the other. Pure, so it is tested without a browser.
400
+ *
401
+ * @returns {{ width, height, you, target, people, bridges, links, spokes }}
402
+ */
403
+ export function convergeLayout(p, width = 800, isMobile = false, { cards = false, mutuals = 0 } = {}) {
404
+ const gap = cards ? 46 : isMobile ? 40 : 34;
405
+ const youX = 20;
406
+ const bx = Math.round(width * (isMobile ? 0.44 : 0.36));
407
+ const tx = Math.round(width * (isMobile ? 0.8 : cards ? 0.6 : 0.62));
408
+ const nodes = [];
409
+ let unnamed = null;
410
+ for (const r of p?.routes || []) {
411
+ if (r.bridge) nodes.push({ id: r.id, bridge: r.bridge, unresolved: false, count: 1 });
412
+ else if (unnamed) unnamed.count++;
413
+ else nodes.push((unnamed = { id: null, bridge: null, unresolved: true, count: 1 }));
414
+ }
415
+ // Mutual connections the app can't draw yet: they're in circles you haven't
416
+ // scanned. Grey dots under the connections who do lead there, each with a faint
417
+ // line in, so the count reads at a glance (GHOST_MAX at most, then "+N").
418
+ const unscanned = Math.max(0, Math.floor(Number(mutuals) || 0) - (p?.waysIn || 0));
419
+ const ghostCount = Math.min(GHOST_MAX, unscanned);
420
+ const ghostStep = cards ? 12 : 11;
421
+ const ghostW = cards ? PILL_W_LAYOUT - 16 : isMobile ? 96 : 120;
422
+ const perRow = Math.max(1, Math.floor(ghostW / ghostStep) + 1);
423
+ const ghostRows = Math.ceil(ghostCount / perRow);
424
+ const ghostH = ghostCount ? GHOST_LABEL + ghostRows * ghostStep : 0;
425
+ const block = Math.max(0, nodes.length - 1) * gap + ghostH;
426
+ const height = Math.max(isMobile ? 190 : cards ? 250 : 220, TOP_PAD * 2 + block + 48);
427
+ const mid = height / 2;
428
+ const top = mid - block / 2;
429
+ const nameBudget = Math.max(4, Math.floor((bx - 10 - (youX + 22)) / 6));
430
+ const bridges = nodes.map((n, j) => {
431
+ const full = n.unresolved ? `can't be named (${n.count})` : n.bridge.name || '';
432
+ return {
433
+ ...n, x: bx, y: top + j * gap, r: 6,
434
+ primary: j === 0 && !n.unresolved,
435
+ label: full.length <= nameBudget ? full : fit(n.unresolved ? full : shortName(full), nameBudget),
436
+ };
437
+ });
438
+ const target = { x: tx, y: mid, r: cards ? 0 : 17, rings: cards ? [] : [32, 50, 70].filter((r) => r <= mid - 4) };
439
+ const links = bridges.map((b) => ({
440
+ key: p?.key, bridgeId: b.id, primary: b.primary, unresolved: b.unresolved, tier: b.bridge?.tier ?? null,
441
+ x1: b.x, y1: b.y, x2: target.x, y2: target.y,
442
+ }));
443
+ const you = { x: youX, y: mid, r: 14 };
444
+ const spokes = bridges.map((b) => ({ id: b.id, x1: you.x, y1: you.y, x2: b.x, y2: b.y }));
445
+ const people = p ? [{ key: p.key, p, x: target.x, y: target.y, r: target.r, rings: target.rings, big: true, label: '' }] : [];
446
+ const gy = top + Math.max(0, nodes.length - 1) * gap + GHOST_LABEL;
447
+ const gx = cards ? bx - PILL_W_LAYOUT + 8 : bx - ghostW;
448
+ const ghosts = Array.from({ length: ghostCount }, (_, i) => ({ x: gx + (i % perRow) * ghostStep, y: gy + Math.floor(i / perRow) * ghostStep }));
449
+ // How bright the person is: the share of your mutual connections with them that the map can draw.
450
+ const drawn = p?.waysIn ? p.waysIn / (p.waysIn + unscanned) : 1;
451
+ return {
452
+ width, height, you, target, people, bridges, links, spokes,
453
+ ghosts, unscanned, ghostLabel: ghostCount ? { x: gx, y: gy - 13 } : null, drawn,
454
+ };
455
+ }
@@ -5,28 +5,37 @@
5
5
 
6
6
  import { rescoreAll } from './rpc.js';
7
7
  import { sameFocus, NO_FOCUS, tierMoves } from './sector-focus.js';
8
+ import { rankingFingerprint } from './title-ranking.js';
8
9
 
9
10
  // Each entry: `changed(before, after)` says whether a change matters (by
10
11
  // default, any change to the stored value), and `run(db, after, before)` does
11
12
  // the work and returns what to tell the page.
13
+ // Rescore everyone and count who changed tier, from the stored tiers before and after.
14
+ function rescoreCountingMoves(db) {
15
+ const rows = () => db.prepare('SELECT id, degree, profile_url, tier FROM linkedin_connections').all();
16
+ try {
17
+ const before = new Map(rows().map((r) => [r.id, r.tier]));
18
+ const { scored } = rescoreAll();
19
+ const { people, up, down } = tierMoves(rows(), (r) => before.get(r.id), (r) => r.tier, { examples: 0 });
20
+ return { scored, people, moved: up + down, up, down };
21
+ } catch (err) {
22
+ throw new Error(`Saved, but rescoring your network failed (${err.message}). It will try again the next time the map loads.`);
23
+ }
24
+ }
25
+
12
26
  const EFFECTS = {
27
+ // Title points are part of everyone's score, so a new ranking rescores everyone, as a new tier scale does.
28
+ titleRanking: {
29
+ changed: (before, after) => rankingFingerprint(before) !== rankingFingerprint(after),
30
+ run: (db) => rescoreCountingMoves(db),
31
+ },
13
32
  tierScale: {
14
33
  // Tiers are graded when everyone is scored, so a new scale rescores
15
34
  // everyone. The answer counts who changed tier, from the stored tiers
16
35
  // before and after, with the sector focus's own counter (tierMoves): a
17
36
  // person reachable through two bridges is two rows but one person, counted
18
37
  // once at their closest degree.
19
- run(db) {
20
- const rows = () => db.prepare('SELECT id, degree, profile_url, tier FROM linkedin_connections').all();
21
- try {
22
- const before = new Map(rows().map((r) => [r.id, r.tier]));
23
- const { scored } = rescoreAll();
24
- const { people, up, down } = tierMoves(rows(), (r) => before.get(r.id), (r) => r.tier, { examples: 0 });
25
- return { scored, people, moved: up + down, up, down };
26
- } catch (err) {
27
- throw new Error(`Saved, but rescoring your network failed (${err.message}). It will try again the next time the map loads.`);
28
- }
29
- },
38
+ run: (db) => rescoreCountingMoves(db),
30
39
  },
31
40
  sectorFocus: {
32
41
  // Only a change to what scoring uses: a strength with no sectors picked
package/lib/settings.js CHANGED
@@ -12,6 +12,7 @@
12
12
 
13
13
  import { SECTOR_FOCUS_SETTING, FIELD_ASKED_SETTING } from './sector-focus.js';
14
14
  import { TIER_SCALE_SETTING } from './scoring.js';
15
+ import { TITLE_RANKING_SETTING } from './title-ranking.js';
15
16
 
16
17
  export const SETTINGS = {
17
18
  // Scores → Your sector: industries whose companies count for more (lib/sector-focus.js).
@@ -20,6 +21,8 @@ export const SETTINGS = {
20
21
  fieldAsked: FIELD_ASKED_SETTING,
21
22
  // Scores → Tiers: graded on your network's curve, or on the fixed scale (lib/scoring.js).
22
23
  tierScale: TIER_SCALE_SETTING,
24
+ // Scores → Titles: how much each kind of title counts, and roles you're looking for (lib/title-ranking.js).
25
+ titleRanking: TITLE_RANKING_SETTING,
23
26
  };
24
27
 
25
28
  const KEY = 'settings';
@@ -0,0 +1,99 @@
1
+ // The Filters panel's grid: five tiers down, six degrees across, the same in
2
+ // Network Circle and in Degrees.
3
+ //
4
+ // Blake, 2026-10-02: "keeping the tiers as is but enlarging them and having 6
5
+ // dots to let the user tap on to toggle or not if they want specific degrees
6
+ // showing for whatever tier … it will be universal for both".
7
+ //
8
+ // A grid is { hidden: [tiers switched off], degrees: { S: [1, 2], … } }: a tier
9
+ // row switches on and off as it always did, and remembers which degrees it
10
+ // shows while it's off. Counts are { S: { 1: n, 2: n, … }, … }: how many people
11
+ // there are at each tier and degree, so a dot with nobody behind it can't be
12
+ // tapped, and the last people on screen can't be switched off.
13
+
14
+ export const TIERS = ['S', 'A', 'B', 'C', 'D'];
15
+ export const GRID_DEGREES = [1, 2, 3, 4, 5, 6];
16
+
17
+ const tierOf = (tier) => (TIERS.includes(tier) ? tier : 'D'); // no tier: with D, as the views draw it
18
+
19
+ export const makeGrid = (degrees = GRID_DEGREES) => ({
20
+ hidden: [],
21
+ degrees: Object.fromEntries(TIERS.map((t) => [t, degrees.slice()])),
22
+ });
23
+
24
+ /** Network Circle starts with your own connections; you pick the rest. */
25
+ export const NETWORK_GRID = Object.freeze(makeGrid([1]));
26
+ /** Degrees starts with everything it has. */
27
+ export const DEGREES_GRID = Object.freeze(makeGrid());
28
+
29
+ /** Is someone of this tier, at this degree, on screen? */
30
+ export function shows(grid, tier, degree) {
31
+ const t = tierOf(tier);
32
+ return !grid.hidden.includes(t) && grid.degrees[t].includes(degree);
33
+ }
34
+
35
+ const countAt = (counts, tier, degree) => counts?.[tier]?.[degree] || 0;
36
+
37
+ /** How many people one tier is showing. */
38
+ export function showingIn(grid, counts, tier) {
39
+ if (grid.hidden.includes(tier)) return 0;
40
+ return grid.degrees[tier].reduce((n, d) => n + countAt(counts, tier, d), 0);
41
+ }
42
+
43
+ /** How many people the whole grid is showing. */
44
+ export const showing = (grid, counts) => TIERS.reduce((n, t) => n + showingIn(grid, counts, t), 0);
45
+
46
+ // A change that would leave nobody on screen isn't made.
47
+ const keep = (grid, next, counts) => (counts && showing(next, counts) === 0 ? grid : next);
48
+
49
+ /** One dot: that degree, for that tier. Tapping a dot in a tier that's off switches the tier on with it. */
50
+ export function toggleCell(grid, tier, degree, counts) {
51
+ const has = grid.degrees[tier].includes(degree);
52
+ const off = grid.hidden.includes(tier);
53
+ const degrees = off || !has
54
+ ? [...new Set([...grid.degrees[tier], degree])].sort((a, b) => a - b)
55
+ : grid.degrees[tier].filter((d) => d !== degree);
56
+ return keep(grid, {
57
+ hidden: off ? grid.hidden.filter((t) => t !== tier) : grid.hidden,
58
+ degrees: { ...grid.degrees, [tier]: degrees },
59
+ }, counts);
60
+ }
61
+
62
+ /** A whole tier, off or back on with the degrees it had. */
63
+ export function toggleTier(grid, tier, counts) {
64
+ if (!grid.hidden.includes(tier)) return keep(grid, { ...grid, hidden: [...grid.hidden, tier] }, counts);
65
+ // Back on with nothing to show would look broken: give it what the others show.
66
+ const own = grid.degrees[tier];
67
+ const others = [...new Set(TIERS.flatMap((t) => (t === tier ? [] : grid.degrees[t])))].sort((a, b) => a - b);
68
+ return {
69
+ hidden: grid.hidden.filter((t) => t !== tier),
70
+ degrees: own.length ? grid.degrees : { ...grid.degrees, [tier]: others.length ? others : [1] },
71
+ };
72
+ }
73
+
74
+ /** A whole degree: off if every tier with people there is showing it, otherwise on for all of them. */
75
+ export function toggleDegree(grid, degree, counts) {
76
+ const tiers = TIERS.filter((t) => !grid.hidden.includes(t) && (!counts || countAt(counts, t, degree) > 0));
77
+ if (!tiers.length) return grid;
78
+ const allOn = tiers.every((t) => grid.degrees[t].includes(degree));
79
+ const degrees = { ...grid.degrees };
80
+ for (const t of tiers) {
81
+ degrees[t] = allOn ? degrees[t].filter((d) => d !== degree) : [...new Set([...degrees[t], degree])].sort((a, b) => a - b);
82
+ }
83
+ return keep(grid, { ...grid, degrees }, counts);
84
+ }
85
+
86
+ /** Every tier back on, each with the degrees it had. */
87
+ export const showAllTiers = (grid) => (grid.hidden.length ? { ...grid, hidden: [] } : grid);
88
+
89
+ /** People by tier and degree, for the dots: `byDegree` is { 1: rows, 2: rows, … }. */
90
+ export function gridCounts(byDegree = {}) {
91
+ const counts = Object.fromEntries(TIERS.map((t) => [t, {}]));
92
+ for (const [degree, rows] of Object.entries(byDegree)) {
93
+ for (const row of rows || []) {
94
+ const t = tierOf(row?.tier);
95
+ counts[t][degree] = (counts[t][degree] || 0) + 1;
96
+ }
97
+ }
98
+ return counts;
99
+ }
@@ -0,0 +1,83 @@
1
+ // Settings → Scores → Titles: how much each kind of title counts, your way
2
+ // (Blake, 2026-10-02: "let the user change the rankings of titles as well …
3
+ // incase they value certain people or is using this for outreaching new
4
+ // founders", and people who "find more in that role" for outbound or hiring).
5
+ //
6
+ // Two parts, both in title points (the 0–10 a title is worth before the
7
+ // company weighs it, lib/scoring.js LEVELS):
8
+ // points a level's points, yours: Owner / Entrepreneur at 10, say, to put
9
+ // founders of small companies level with CEOs
10
+ // roles words in a title, and what anyone whose title has them counts:
11
+ // "account executive" at 10 puts every AE you can reach in S
12
+ // Nothing set scores exactly as before. The model applies it (scorePerson);
13
+ // this file is the setting and its presets.
14
+
15
+ import { LEVELS } from './scoring.js';
16
+
17
+ /** The ladder shown in Settings, top to bottom (the main kinds of title). */
18
+ export const RANKED_LEVELS = ['csuite', 'owner', 'vp', 'director', 'manager', 'senior', 'ic', 'unknown', 'intern', 'student'];
19
+ export const NO_RANKING = Object.freeze({ points: Object.freeze({}), roles: Object.freeze([]) });
20
+ export const MAX_ROLES = 20;
21
+ const MAX_ROLE_TEXT = 60;
22
+
23
+ const clampPoints = (v) => {
24
+ const n = Number(v);
25
+ if (!Number.isFinite(n) || n < 0 || n > 10) throw new Error('Title points go from 0 to 10.');
26
+ return Math.round(n * 2) / 2;
27
+ };
28
+
29
+ export const TITLE_RANKING_SETTING = {
30
+ default: NO_RANKING,
31
+ parse(value) {
32
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('A title ranking is { points, roles }.');
33
+ const points = {};
34
+ for (const [key, v] of Object.entries(value.points || {})) {
35
+ if (!Object.hasOwn(LEVELS, key)) throw new Error(`There is no kind of title called '${key}'.`);
36
+ const p = clampPoints(v);
37
+ if (p !== LEVELS[key].points) points[key] = p; // the default needn't be stored
38
+ }
39
+ const roles = [];
40
+ const seen = new Set();
41
+ for (const r of Array.isArray(value.roles) ? value.roles : []) {
42
+ const text = String(r?.text ?? '').trim().toLowerCase().replace(/\s+/g, ' ');
43
+ if (text.length < 2) throw new Error('A role needs at least two letters.');
44
+ if (text.length > MAX_ROLE_TEXT) throw new Error(`Keep a role under ${MAX_ROLE_TEXT} letters.`);
45
+ if (seen.has(text)) continue;
46
+ seen.add(text);
47
+ roles.push({ text, points: clampPoints(r.points) });
48
+ }
49
+ if (roles.length > MAX_ROLES) throw new Error(`Up to ${MAX_ROLES} roles.`);
50
+ return { points, roles };
51
+ },
52
+ };
53
+
54
+ /** The same ranking always gives the same text, so the stored scores can say which they were made with. */
55
+ export function rankingFingerprint(r = NO_RANKING) {
56
+ const points = Object.keys(r.points || {}).sort().map((k) => `${k}=${r.points[k]}`).join(',');
57
+ const roles = (r.roles || []).map((x) => `${x.text}=${x.points}`).sort().join(',');
58
+ return points || roles ? `${points}|${roles}` : 'none';
59
+ }
60
+
61
+ export const isDefaultRanking = (r) => rankingFingerprint(r) === 'none';
62
+
63
+ /** Starting points for the common reasons to change it. Each replaces the whole ranking; you can adjust from there. */
64
+ export const RANKING_PRESETS = [
65
+ { key: 'default', label: 'As the app ranks them', ranking: NO_RANKING, note: 'C-suite and founders first, then VPs, directors, managers.' },
66
+ {
67
+ key: 'founders', label: 'Founders first',
68
+ note: 'Owners and entrepreneurs count like CEOs, and anyone who says founder counts as much: for meeting founders, or selling to them.',
69
+ ranking: { points: { owner: 10 }, roles: [{ text: 'founder', points: 10 }, { text: 'co-founder', points: 10 }] },
70
+ },
71
+ {
72
+ key: 'investors', label: 'Investors',
73
+ note: 'Anyone who invests for a living rises to the top: for raising money.',
74
+ ranking: { points: {}, roles: ['investor', 'venture', 'angel', 'general partner', 'managing partner', 'principal at', 'vc'].map((text) => ({ text, points: 10 })) },
75
+ },
76
+ {
77
+ key: 'role', label: 'A role I’m looking for',
78
+ note: 'For outbound or hiring: add the role below, and everyone in it rises, whatever their level.',
79
+ ranking: null, // keeps what you have and opens the role box
80
+ },
81
+ ];
82
+
83
+ export { titleHas } from './scoring.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "six-degrees",
3
- "version": "0.4.11",
3
+ "version": "0.5.0",
4
4
  "description": "Map your professional network as a galaxy — leverage tiers, bridges into new circles, and the shortest path to anyone. Runs locally on your own data.",
5
5
  "license": "MIT",
6
6
  "author": "Blake Burford",
package/scripts/scrape.py CHANGED
@@ -91,7 +91,7 @@ def _assert_local_target():
91
91
  if host in ("localhost", "127.0.0.1", "::1", "0.0.0.0"):
92
92
  return
93
93
  if os.getenv("ALLOW_REMOTE_PUSH") == "1":
94
- print(f"WARNING: pushing scraped data to remote host {host}")
94
+ print(f"WARNING: pushing scanned data to remote host {host}")
95
95
  return
96
96
  raise SystemExit(
97
97
  f"Refusing to push scraped data to '{APP_URL}'.\n"
@@ -370,7 +370,7 @@ def _print_sign_in_banner(say):
370
370
  print(" inside automated browsers, which is why that window comes up")
371
371
  print(" greyed out and does nothing.")
372
372
  print()
373
- print(" Nothing is scraped until you are signed in, and the scrape starts")
373
+ print(" Nothing is scanned until you are signed in, and the scan starts")
374
374
  print(" by itself the moment you are. Take as long as you need.")
375
375
  print(" Close the browser window to cancel.")
376
376
  print(" ==================================================================")
@@ -387,11 +387,11 @@ def _wait_for_sign_in(page, context, timeout_s, say):
387
387
  # not: signing in legitimately opens and closes tabs.
388
388
  try:
389
389
  if not [pg for pg in context.pages if not pg.is_closed()]:
390
- print("\n Browser closed — nothing was scraped.")
390
+ print("\n Browser closed — nothing was scanned.")
391
391
  say("Browser closed before sign-in.")
392
392
  return False
393
393
  except Exception:
394
- print("\n Browser closed — nothing was scraped.")
394
+ print("\n Browser closed — nothing was scanned.")
395
395
  return False
396
396
 
397
397
  # The cookie belongs to the whole browser, so it is found no matter which
@@ -493,7 +493,7 @@ def resolve_active_user():
493
493
  for u in users:
494
494
  if (u.get("name") or "").strip().lower() == wanted:
495
495
  _active_user_id = u["id"]
496
- print(f" Scraping into profile: {u['name']}")
496
+ print(f" Scanning into profile: {u['name']}")
497
497
  return _active_user_id
498
498
  raise SystemExit(f"No profile named '{os.getenv('SIX_DEGREES_USER')}' in the app.")
499
499
 
@@ -510,7 +510,7 @@ def resolve_active_user():
510
510
  raise SystemExit(f"The app at {APP_URL} did not say which profile to use.")
511
511
 
512
512
  _active_user_id = me["id"]
513
- print(f" Scraping into profile: {me.get('name')}")
513
+ print(f" Scanning into profile: {me.get('name')}")
514
514
  return _active_user_id
515
515
 
516
516
 
@@ -732,7 +732,7 @@ def push_connections(connections, degree=1, bridge_id=None, user_id=None, on_sav
732
732
  if p["profile_url"] in d1_urls:
733
733
  # Unlock via API route
734
734
  requests.post(f"{APP_URL}/api/unlock", json={"connectionId": p["id"]}, headers=app_headers())
735
- print(f" *** PATH UNLOCKED: {p['name']}! Run --bridge \"{p['name']}\" to scrape their network ***")
735
+ print(f" *** PATH UNLOCKED: {p['name']}! Run --bridge \"{p['name']}\" to scan their network ***")
736
736
 
737
737
  return inserted
738
738
 
@@ -893,8 +893,8 @@ def scrape_full(headless=False):
893
893
  if searches_left()[0] <= 0:
894
894
  raise BudgetReached(0, searches_left()[1])
895
895
 
896
- print("\n=== Full Account Setup Scrape ===\n")
897
- print("This scrapes ALL connections via the search page (not the connections page).")
896
+ print("\n=== Full account scan ===\n")
897
+ print("This scans ALL connections via the search page (not the connections page).")
898
898
  print("Takes 3-5 minutes for ~500 connections.\n")
899
899
 
900
900
  with sync_playwright() as p:
@@ -1286,6 +1286,18 @@ SAVE_EVERY_PAGES = 10 # save as a long read goes, so a stop loses li
1286
1286
  # randomised: the point is fewer searches an hour, not looking like a person.
1287
1287
  PAGE_PAUSE = 20 # seconds before each next page of results
1288
1288
  CHUNK_COOLDOWN = 60 # extra seconds after every SAVE_EVERY_PAGES pages
1289
+ # The Scan page's speed (Blake, 2026-09-30 and 10-03: "slow, medium and fast").
1290
+ # Fast is exactly the pacing above and nothing is ever faster; Medium and Slow
1291
+ # only add waiting. Speed changes how many searches an hour, never how many a
1292
+ # day: the daily budget stays the volume cap. Read from scan-limits.json
1293
+ # ("pace") at the start of every run (apply_pace). lib/scan-pace.js holds the
1294
+ # same numbers for the page, and tests/scan-pace.test.mjs checks they agree.
1295
+ SCAN_PACES = {
1296
+ "fast": {"page_pause": 20, "chunk_cooldown": 60, "profile_gap": 60},
1297
+ "medium": {"page_pause": 45, "chunk_cooldown": 180, "profile_gap": 90},
1298
+ "slow": {"page_pause": 90, "chunk_cooldown": 300, "profile_gap": 120},
1299
+ }
1300
+ DEFAULT_PACE = "fast"
1289
1301
  LEGACY_PAGES_READ = 10 # how far every read before 0.1.6 went, at most
1290
1302
 
1291
1303
  # Experimental all-day pacing for Auto-Bridge (--experimental; item 44, Graph
@@ -1561,6 +1573,27 @@ def _profile_wait(data, now):
1561
1573
  return 0
1562
1574
 
1563
1575
 
1576
+ def scan_pace():
1577
+ """The speed saved on the Scan page: "fast", "medium" or "slow"."""
1578
+ try:
1579
+ name = json.loads((_home() / "scan-limits.json").read_text()).get("pace")
1580
+ except Exception:
1581
+ name = None
1582
+ return name if name in SCAN_PACES else DEFAULT_PACE
1583
+
1584
+
1585
+ def apply_pace(name=None):
1586
+ """Set this run's waits from the chosen speed. Never below Fast's."""
1587
+ global PAGE_PAUSE, CHUNK_COOLDOWN, PROFILE_GAP
1588
+ name = name if name in SCAN_PACES else scan_pace()
1589
+ pace = SCAN_PACES[name]
1590
+ fast = SCAN_PACES["fast"]
1591
+ PAGE_PAUSE = max(fast["page_pause"], pace["page_pause"])
1592
+ CHUNK_COOLDOWN = max(fast["chunk_cooldown"], pace["chunk_cooldown"])
1593
+ PROFILE_GAP = max(fast["profile_gap"], pace["profile_gap"])
1594
+ return name
1595
+
1596
+
1564
1597
  def search_limits():
1565
1598
  try:
1566
1599
  data = json.loads((_home() / "scan-limits.json").read_text())
@@ -3256,7 +3289,7 @@ def scrape_bridge(bridge_name, headless=False, max_pages=LINKEDIN_MAX_PAGES, dee
3256
3289
  if not urn and profiles_left() <= 0:
3257
3290
  raise BudgetReached(0, "profiles")
3258
3291
 
3259
- print(f"\n=== {'Carrying on with' if start_page > 1 else 'Scraping'} connections of {bridge_name} ===\n")
3292
+ print(f"\n=== {'Carrying on with' if start_page > 1 else 'Scanning'} connections of {bridge_name} ===\n")
3260
3293
 
3261
3294
  read = []
3262
3295
 
@@ -3368,7 +3401,7 @@ def scrape_company(company_name, headless=False, log_fn=None):
3368
3401
  if log_fn:
3369
3402
  log_fn(msg)
3370
3403
 
3371
- log(f"Scraping employees at: {company_name}")
3404
+ log(f"Scanning employees at: {company_name}")
3372
3405
 
3373
3406
  # A company scan is people searches too: the same cooldown and budget.
3374
3407
  cd = read_cooldown()
@@ -4045,7 +4078,7 @@ def auto_bridge_all(headless=False, log_fn=None, retry_private=False, max_bridge
4045
4078
 
4046
4079
  def rescrape_bridge(bridge_name, headless=False, max_pages=LINKEDIN_MAX_PAGES):
4047
4080
  """Delete all existing cluster data for a bridge and re-scrape from scratch."""
4048
- print(f"\n=== Re-scraping {bridge_name} (delete + fresh scrape) ===\n")
4081
+ print(f"\n=== Rescanning {bridge_name} (delete + fresh scan) ===\n")
4049
4082
 
4050
4083
  # Check before deleting anything: a cooldown or an empty budget would leave
4051
4084
  # their circle deleted and not read again.
@@ -4349,11 +4382,16 @@ Examples:
4349
4382
  EXPERIMENT["on"] = True
4350
4383
  print("Experimental Auto-Bridge: all-day pacing, and LinkedIn's own data read beside the page text.")
4351
4384
  args.max_pages = max(1, min(LINKEDIN_MAX_PAGES, args.max_pages))
4385
+ # The Scan page's speed. Signing in searches nothing, so it isn't said there.
4386
+ _pace = apply_pace()
4387
+ if not args.login:
4388
+ print(f"Speed: {_pace.capitalize()}. {PAGE_PAUSE}s before each page of results, {CHUNK_COOLDOWN}s more after "
4389
+ f"every {SAVE_EVERY_PAGES}, and profiles at least {PROFILE_GAP}s apart.")
4352
4390
 
4353
4391
  # A failed save ends the run with its reason, not a traceback (TRAPS §32).
4354
4392
  def _say_why(exc_type, exc, tb):
4355
4393
  if issubclass(exc_type, SaveFailed):
4356
- print(f"\n The app could not save what was scraped: {exc}\n", file=sys.stderr)
4394
+ print(f"\n The app could not save what was scanned: {exc}\n", file=sys.stderr)
4357
4395
  elif issubclass(exc_type, TryLater):
4358
4396
  print(f"\n The photos weren't saved: {exc}.\n", file=sys.stderr)
4359
4397
  elif issubclass(exc_type, NotSignedIn):
@@ -1,8 +0,0 @@
1
- module.exports=[70406,(e,t,r)=>{t.exports=e.x("next/dist/compiled/@opentelemetry/api",()=>require("next/dist/compiled/@opentelemetry/api"))},18622,(e,t,r)=>{t.exports=e.x("next/dist/compiled/next-server/app-page-turbo.runtime.prod.js",()=>require("next/dist/compiled/next-server/app-page-turbo.runtime.prod.js"))},56704,(e,t,r)=>{t.exports=e.x("next/dist/server/app-render/work-async-storage.external.js",()=>require("next/dist/server/app-render/work-async-storage.external.js"))},32319,(e,t,r)=>{t.exports=e.x("next/dist/server/app-render/work-unit-async-storage.external.js",()=>require("next/dist/server/app-render/work-unit-async-storage.external.js"))},59043,(e,t,r)=>{t.exports=e.x("next/dist/server/runtime-reacts.external.js",()=>require("next/dist/server/runtime-reacts.external.js"))},93695,(e,t,r)=>{t.exports=e.x("next/dist/shared/lib/no-fallback-error.external.js",()=>require("next/dist/shared/lib/no-fallback-error.external.js"))},74533,(e,t,r)=>{t.exports=e.x("node:child_process",()=>require("node:child_process"))},66680,(e,t,r)=>{t.exports=e.x("node:crypto",()=>require("node:crypto"))},2157,(e,t,r)=>{t.exports=e.x("node:fs",()=>require("node:fs"))},12714,(e,t,r)=>{t.exports=e.x("node:fs/promises",()=>require("node:fs/promises"))},60526,(e,t,r)=>{t.exports=e.x("node:os",()=>require("node:os"))},50227,(e,t,r)=>{t.exports=e.x("node:path",()=>require("node:path"))},81111,(e,t,r)=>{t.exports=e.x("node:stream",()=>require("node:stream"))},10960,e=>{"use strict";var t=e.i(2157),r=e.i(50227),n=e.i(60526);function s(){for(let e of[process.env.SIX_DEGREES_ROOT,process.cwd(),r.default.join(process.cwd(),".."),r.default.join(process.cwd(),"..","..")].filter(Boolean))if((0,t.existsSync)(r.default.join(e,"scripts","scrape.py")))return e;return null}e.s(["dataDir",0,function(){return process.env.SIX_DEGREES_HOME||r.default.join(n.default.homedir(),".six-degrees")},"isGitCheckout",0,function(){let e=s();return!!(e&&(0,t.existsSync)(r.default.join(e,".git")))},"projectRoot",0,s])},88602,e=>{"use strict";var t=e.i(88431);e.s(["networkCounts",0,function(e){let r=Object.fromEntries((0,t.getDb)().prepare("SELECT degree, COUNT(*) AS n FROM linkedin_connections WHERE user_id = ? GROUP BY degree").all(e).map(e=>[e.degree,e.n]));return{first:r[1]||0,second:r[2]||0,third:r[3]||0}},"resolveProfile",0,function({create:e=!0}={}){let[r]=(0,t.getDb)().prepare(`
2
- SELECT u.*,
3
- (SELECT COUNT(*) FROM linkedin_connections c WHERE c.user_id = u.id) AS connections
4
- FROM users u
5
- ORDER BY connections DESC, u.created_at ASC, u.rowid ASC
6
- `).all().map(e=>(0,t.decodeRow)("users",e));if(r)return r;if(!e)return null;let n=(0,t.newId)();return(0,t.getDb)().prepare("INSERT INTO users (id, name) VALUES (?, ?)").run(n,"You"),{...(0,t.decodeRow)("users",(0,t.getDb)().prepare("SELECT * FROM users WHERE id = ?").get(n)),connections:0}}])},12506,1348,e=>{"use strict";e.s(["compareVersions",0,function(e,t){let r=e=>String(e||"").replace(/^v/i,"").split(/[-+]/)[0].split(".").map(e=>Number.parseInt(e,10)||0),n=r(e),s=r(t);for(let e=0;e<Math.max(n.length,s.length);e++){let t=(n[e]||0)-(s[e]||0);if(0!==t)return t>0?1:-1}return 0},"installKind",0,function(e=process.env,t=""){return["mac-app","source","npm"].includes(e.SIX_DEGREES_INSTALL)?e.SIX_DEGREES_INSTALL:/\.app\/Contents\/Resources/.test(t||"")?"mac-app":"npm"},"repoSlug",0,function(e){let t=("string"==typeof e?e:e?.url||"").match(/github\.com[/:]([^/]+)\/([^/.#?]+)/);return t?`${t[1]}/${t[2]}`:null},"updateCommand",0,function(e,t,{dataDir:r}={}){if("mac-app"===e)return`curl -fsSL https://raw.githubusercontent.com/${t}/main/install.sh | bash`;if("source"===e)return"git pull && npm ci && npm run build && npm run start:packaged";let n=r&&`'${String(r).replace(/'/g,"'\\''")}'`;return n?`npx six-degrees@latest --data-dir ${n}`:"npx six-degrees@latest"}],12506);let t=Symbol.for("six-degrees.scan-state");function r(){let e=globalThis[t];return e?.running!==!0?null:"string"==typeof e.action&&e.action?e.action:"scan"}function n(e){switch(e){case"install":return"The scanner's packages are being installed";case"setup":return"The scanner is being set up";case"login":return"The LinkedIn sign-in window is open";case"photos":return"Profile photos are being saved";default:return"A scan is running"}}e.s(["busyRefusal",0,function(e,t=r()){return t?`${n(t)}. ${e}`:null},"registerScanState",0,function(e){return globalThis[t]||(globalThis[t]=e),globalThis[t]},"runningNow",0,n,"scannerJob",0,r],1348)},1746,e=>{"use strict";e.s(["keyFor",0,function(e){return String(e?.profile_url||"").split(/[?#]/)[0].replace(/\/+$/,"")||`id:${e?.id}`}])},98199,e=>{"use strict";var t=e.i(50227),r=e.i(12506);let n="Six Degrees.app",s="SHA256SUMS",i=Object.freeze({apiBase:"https://api.github.com",downloadBase:"https://github.com"}),o=(e,t)=>({refusal:{code:e,message:t}}),a={arm64:"Apple Silicon",x64:"Intel"},l={0x1000007:"x64",0x100000c:"arm64"};e.s(["APP_BUNDLE_NAME",0,n,"BUNDLE_ID",0,"com.blakeburford.sixdegrees","CHIP_LABEL",0,a,"GITHUB",0,i,"UPDATE_HANDOFF_EXIT_CODE",0,76,"chipOf",0,function({sysctlArm64:e,processArch:t}={}){return"1"===String(e??"").trim()||"arm64"===t?"arm64":"x64"},"digestsMatch",0,function(e,t){let r=String(e||"").toLowerCase(),n=String(t||"").toLowerCase();return/^[0-9a-f]{64}$/.test(r)&&r===n},"foreignAppProcesses",0,function(e,{bundles:t=[],uid:r}){let n=t.filter(Boolean).map(e=>`${e}/Contents/`);if(!n.length||!Number.isInteger(r))return[];let s=[];for(let t of String(e||"").split("\n")){let e=t.match(/^\s*(\d+)\s+(\d+)\s+(.+)$/);e&&Number(e[1])!==r&&n.some(t=>e[3].startsWith(t))&&s.push(Number(e[2]))}return s},"helperArgs",0,function({target:e,staged:t,keepDir:r,statusFile:n,from:s,to:i,pids:o=[],waitSeconds:a=30,logFile:l,work:u,relaunch:d=[],confirmFile:p,confirmWaitSeconds:c=600}){let f=["--target",e,"--staged",t,"--keep",r,"--status",n,"--from",s,"--to",i,"--wait",String(a)];for(let e of o)Number.isInteger(e)&&e>1&&f.push("--pid",String(e));return p&&f.push("--confirm",p,"--confirm-wait",String(c)),l&&f.push("--log",l),u&&f.push("--work",u),[...f,"--",...d]},"installerTarget",0,function({applicationsWritable:e=!0,home:r=""}={}){return e?t.default.join("/Applications",n):t.default.join(r,"Applications",n)},"lastUpdateReport",0,function(e,{runningVersion:t,now:s=Date.now(),maxAgeDays:i=7}={}){if(!e||"object"!=typeof e||"string"!=typeof e.to)return null;let o=Date.parse(e.at);if(!Number.isFinite(o)||s-o>864e5*i)return null;let a=e.to,l="string"==typeof e.reason&&e.reason.trim()?e.reason.trim().replace(/\.$/,""):null,u={at:new Date(o).toISOString(),from:e.from||null,to:a,previous:"string"==typeof e.previous?e.previous:null},d=t?(0,r.compareVersions)(t,a):null,p=`The update to ${a} didn't finish${l?`: ${l}`:""}.`;switch(e.outcome){case"installed":case"started":if(0===d)return{...u,tone:"ok",text:`Updated to ${a}.`};if(d>0)return null;if("installed"===e.outcome)return{...u,tone:"bad",text:`Version ${a} was installed, but this copy is ${t}. If you have two copies of Six Degrees, open the one in Applications.`};return{...u,tone:"bad",text:`The update to ${a} didn't finish, and it didn't say why.${e.log?` Its log is ${e.log}.`:""}`};case"not-applied":if(null!==d&&d>=0)return null;return{...u,tone:"bad",text:`${p} Nothing was changed.`};case"rolled-back":if(null!==d&&d>=0)return null;return{...u,tone:"bad",text:`${p} Your previous version was put back.`};case"failed":if(null!==d&&d>=0)return null;return{...u,tone:"bad",text:`${p}${e.previous?` Your previous version is safe, in a hidden folder: ${e.previous}. To use it again, rename it to ${n} (in Finder, Shift-Command-. shows hidden files). Or install the newest version: Check for updates, below.`:""}`};default:return null}},"leftoversBeside",0,function(e,t){let r=`.${t}.`,n=[];for(let t of e){if("string"!=typeof t||!t.startsWith(r))continue;let e=t.slice(r.length);if("incoming"===e){n.push({name:t,kind:"incoming",pid:null});continue}let s=e.match(/^(previous|failed)-(\d+)(?:-\d+)?$/);s&&n.push({name:t,kind:s[1],pid:Number(s[2])})}return n},"machoArchs",0,function(e){let t=new Set;if(!e||e.length<8)return t;let r=e=>{l[e>>>0]&&t.add(l[e>>>0])},n=e.readUInt32LE(0);if(0xfeedfacf===n||0xfeedface===n)return r(e.readUInt32LE(4)),t;let s=e.readUInt32BE(0);if(0xcafebabe===s||0xcafebabf===s){let n=e.readUInt32BE(4),i=0xcafebabf===s?32:20;if(n<1||n>16)return t;for(let t=0;t<n;t++){let n=8+t*i;if(n+4>e.length)break;r(e.readUInt32BE(n))}}return t},"parseSha256Sums",0,function(e){let t=new Map;for(let r of String(e||"").split("\n")){let e=r.replace(/\r$/,"").match(/^([0-9a-fA-F]{64}) [ *](.+)$/);if(!e)continue;let[,n,s]=e,i=n.toLowerCase();t.has(s)&&t.get(s)!==i?t.set(s,null):t.set(s,i)}return t},"planFromRelease",0,function(e,{currentVersion:t,chip:n,slug:l,downloadBase:u=i.downloadBase}){if(!e||"object"!=typeof e||!e.tag_name)return o("no-release","GitHub did not describe a release.");let d=String(e.tag_name),p=d.replace(/^v/i,"");if(e.draft||e.prerelease||p.includes("-"))return o("prerelease",`Version ${p} is a test version, so it isn't installed from here.`);if(!/^\d+(\.\d+){1,3}$/.test(p))return o("bad-version",`GitHub's newest release has a version this app doesn't understand (${d}).`);if(0>=(0,r.compareVersions)(p,t))return o("not-newer",`You already have ${t}, and the newest version is ${p}.`);let c=Array.isArray(e.assets)?e.assets:[],f=e=>c.find(t=>t&&t.name===e)||null,m=f(`Six-Degrees-${p}-${n}.dmg`);if(!m)return o("no-download",`Version ${p} has no download for this Mac (${a[n]||n}).`);let g=f(s);if(!g)return o("no-checksums",`Version ${p} was published without its checksum file (${s}), so its download can't be checked.`);let h=`${u}/${l}/releases/download/${encodeURIComponent(d)}/`;for(let e of[m,g])if(e.browser_download_url!==h+encodeURIComponent(e.name))return o("unexpected-address",`GitHub listed ${e.name} at an unexpected address, so it wasn't downloaded.`);let x=Number(m.size)>0?Number(m.size):null;return x&&x>0x40000000?o("too-large",`The download for version ${p} is unexpectedly large, so it wasn't downloaded.`):{plan:{version:p,tag:d,notes:"string"==typeof e.html_url?e.html_url:null,dmg:{name:m.name,url:m.browser_download_url,size:x},sums:{name:g.name,url:g.browser_download_url}}}},"relaunchArgs",0,function({dataDir:e}={}){let r=["--after-update"];return e&&r.push("--data-dir",t.default.resolve(e)),r},"releaseSource",0,function(e={}){let t=e?.SIX_DEGREES_TEST_RELEASES;if(!t)return{...i,test:!1};let r=null;try{r=new URL(String(t))}catch{}return!r||"http:"!==r.protocol||"127.0.0.1"!==r.hostname||""===r.port||"/"!==r.pathname||r.search||r.hash||r.username||r.password?{...i,test:!1}:{apiBase:r.origin,downloadBase:r.origin,test:!0}},"stagingPath",0,function(e){return t.default.join(t.default.dirname(e),`.${t.default.basename(e)}.incoming`)},"terminalFallback",0,function({bundle:e,refusalCode:t=null,installsTo:r,dataDir:n=null}){if("data-inside-app"===t||"database-elsewhere"===t||"not-owner"===t&&e===r)return null;let s=n?{dataDir:n}:{};return e&&e===r?{mode:"replace",...s}:{mode:"elsewhere",installsTo:r,...s}},"versionAtLeast",0,function(e,t){return(0,r.compareVersions)(e,t)>=0}])}];
7
-
8
- //# sourceMappingURL=%5Broot-of-the-server%5D__17hjtzl._.js.map