six-degrees 0.7.0 → 1.0.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 (360) 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 +32 -4
  5. package/.next/standalone/.next/routes-manifest.json +6 -0
  6. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  12. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  13. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  14. package/.next/standalone/.next/server/app/_not-found.rsc +16 -16
  15. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +16 -16
  16. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  17. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  18. package/.next/standalone/.next/server/app/api/admin-delete/route.js +1 -1
  19. package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
  20. package/.next/standalone/.next/server/app/api/admin-update/route.js +1 -1
  21. package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/bridges/route.js +1 -1
  23. package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/bulk-import/route.js +1 -1
  25. package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +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/backup/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/data/csv/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/data/restore/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 +4 -4
  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/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 +1 -1
  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 +18 -18
  73. package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +18 -18
  74. package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
  75. package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +16 -16
  76. package/.next/standalone/.next/server/app/index.html +1 -1
  77. package/.next/standalone/.next/server/app/index.rsc +19 -19
  78. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +16 -16
  79. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +19 -19
  80. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  81. package/.next/standalone/.next/server/app/insights/page/app-paths-manifest.json +3 -0
  82. package/.next/standalone/.next/server/app/insights/page/build-manifest.json +20 -0
  83. package/.next/standalone/.next/server/app/insights/page/next-font-manifest.json +11 -0
  84. package/.next/standalone/.next/server/app/insights/page/react-loadable-manifest.json +1 -0
  85. package/.next/standalone/.next/server/app/insights/page/server-reference-manifest.json +4 -0
  86. package/.next/standalone/.next/server/app/insights/page.js +13 -0
  87. package/.next/standalone/.next/server/app/insights/page.js.map +5 -0
  88. package/.next/standalone/.next/server/app/insights/page.js.nft.json +1 -0
  89. package/.next/standalone/.next/server/app/insights/page_client-reference-manifest.js +3 -0
  90. package/.next/standalone/.next/server/app/insights.html +1 -0
  91. package/.next/standalone/.next/server/app/insights.meta +26 -0
  92. package/.next/standalone/.next/server/app/insights.rsc +35 -0
  93. package/.next/standalone/.next/server/app/insights.segments/_full.segment.rsc +35 -0
  94. package/.next/standalone/.next/server/app/insights.segments/_tree.segment.rsc +4 -0
  95. package/.next/standalone/.next/server/app/insights.segments/insights/__PAGE__.segment.rsc +33 -0
  96. package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
  97. package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
  98. package/.next/standalone/.next/server/app/launch.html +2 -2
  99. package/.next/standalone/.next/server/app/launch.rsc +18 -18
  100. package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +18 -18
  101. package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
  102. package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +16 -16
  103. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  104. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  105. package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
  106. package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
  107. package/.next/standalone/.next/server/app/paths.html +1 -1
  108. package/.next/standalone/.next/server/app/paths.rsc +19 -19
  109. package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +19 -19
  110. package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
  111. package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +16 -16
  112. package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
  113. package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
  114. package/.next/standalone/.next/server/app/profile.html +1 -1
  115. package/.next/standalone/.next/server/app/profile.rsc +19 -19
  116. package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +19 -19
  117. package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
  118. package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +16 -16
  119. package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
  120. package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
  121. package/.next/standalone/.next/server/app/queue.html +1 -1
  122. package/.next/standalone/.next/server/app/queue.rsc +19 -19
  123. package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +19 -19
  124. package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
  125. package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +21 -20
  126. package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
  127. package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
  128. package/.next/standalone/.next/server/app/scores.html +1 -1
  129. package/.next/standalone/.next/server/app/scores.rsc +18 -18
  130. package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +18 -18
  131. package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +2 -2
  132. package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +16 -16
  133. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  134. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  135. package/.next/standalone/.next/server/app/settings.html +1 -1
  136. package/.next/standalone/.next/server/app/settings.rsc +19 -19
  137. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +19 -19
  138. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  139. package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +16 -16
  140. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  141. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  142. package/.next/standalone/.next/server/app/setup.html +1 -1
  143. package/.next/standalone/.next/server/app/setup.rsc +19 -19
  144. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +19 -19
  145. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
  146. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +16 -16
  147. package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -1
  148. package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
  149. package/.next/standalone/.next/server/app/social.html +1 -1
  150. package/.next/standalone/.next/server/app/social.rsc +18 -18
  151. package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +18 -18
  152. package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +2 -2
  153. package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +16 -16
  154. package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
  155. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__063s0x7._.js → [root-of-the-server]__0r-0t-9._.js} +1 -1
  156. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ra6xyg._.js +15 -0
  157. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__20rwfdw._.js → [root-of-the-server]__0rcpjad._.js} +5 -2
  158. package/.next/standalone/.next/server/chunks/_1_fynia._.js +7 -7
  159. package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
  160. package/.next/standalone/.next/server/chunks/lib_02iz_9q._.js +2 -2
  161. package/.next/standalone/.next/server/chunks/lib_05agvk3._.js +2 -2
  162. package/.next/standalone/.next/server/chunks/lib_0v84u66._.js +2 -2
  163. package/.next/standalone/.next/server/chunks/lib_db-client_0_dch35.js +9 -9
  164. package/.next/standalone/.next/server/chunks/{lib_rpc_0jnfp2e.js → lib_rpc_00nndai.js} +1 -1
  165. package/.next/standalone/.next/server/chunks/{lib_rpc_0b17r6v.js → lib_rpc_03ibb-x.js} +1 -1
  166. package/.next/standalone/.next/server/chunks/{lib_rpc_0doq0t-.js → lib_rpc_03ri3ji.js} +1 -1
  167. package/.next/standalone/.next/server/chunks/{lib_rpc_0fd2rlw.js → lib_rpc_078ce48.js} +1 -1
  168. package/.next/standalone/.next/server/chunks/lib_rpc_0a69mgr.js +3 -0
  169. package/.next/standalone/.next/server/chunks/{lib_rpc_005abye.js → lib_rpc_0cej3t8.js} +1 -1
  170. package/.next/standalone/.next/server/chunks/lib_rpc_0cmh4g6.js +3 -0
  171. package/.next/standalone/.next/server/chunks/lib_rpc_0nx2_pc.js +3 -0
  172. package/.next/standalone/.next/server/chunks/lib_rpc_0r79-w1.js +3 -0
  173. package/.next/standalone/.next/server/chunks/lib_rpc_0yx8ynd.js +3 -0
  174. package/.next/standalone/.next/server/chunks/lib_rpc_12p-o9-.js +3 -0
  175. package/.next/standalone/.next/server/chunks/lib_rpc_1466dnb.js +3 -0
  176. package/.next/standalone/.next/server/chunks/{lib_rpc_0is4qoc.js → lib_rpc_1hjjjr4.js} +1 -1
  177. package/.next/standalone/.next/server/chunks/lib_rpc_1sryxv6.js +3 -0
  178. package/.next/standalone/.next/server/chunks/lib_rpc_1trn-x5.js +3 -0
  179. package/.next/standalone/.next/server/chunks/lib_rpc_1ygu_mv.js +3 -0
  180. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0egamyq._.js +3 -0
  181. package/.next/standalone/.next/server/chunks/ssr/_04am_h8._.js +3 -0
  182. package/.next/standalone/.next/server/chunks/ssr/_07zb7lu._.js +1 -1
  183. package/.next/standalone/.next/server/chunks/ssr/_0_79idq._.js +1 -1
  184. package/.next/standalone/.next/server/chunks/ssr/_0gds903._.js +3 -0
  185. package/.next/standalone/.next/server/chunks/ssr/_0klqt9z._.js +3 -0
  186. package/.next/standalone/.next/server/chunks/ssr/{_1k9qfsk._.js → _14jak5s._.js} +6 -2
  187. package/.next/standalone/.next/server/chunks/ssr/_14nxd3q._.js +1 -1
  188. package/.next/standalone/.next/server/chunks/ssr/_1cj2yog._.js +3 -0
  189. package/.next/standalone/.next/server/chunks/ssr/_1fm1ih_._.js +1 -1
  190. package/.next/standalone/.next/server/chunks/ssr/_1hdqw0-._.js +7 -0
  191. package/.next/standalone/.next/server/chunks/ssr/_1k2nmbw._.js +1 -1
  192. package/.next/standalone/.next/server/chunks/ssr/{_0gy11ar._.js → _1r62six._.js} +6 -2
  193. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_insights_page_actions_0-0tf75.js +3 -0
  194. package/.next/standalone/.next/server/chunks/ssr/app_components_0uuff77._.js +2 -2
  195. package/.next/standalone/.next/server/chunks/ssr/app_components_1j3q-h5._.js +2 -2
  196. package/.next/standalone/.next/server/chunks/ssr/{_01o3i5y._.js → app_components_settings_SectorPicker_1c-29qw.js} +2 -6
  197. package/.next/standalone/.next/server/chunks/ssr/app_components_ui_0pdmz2z.js +3 -0
  198. package/.next/standalone/.next/server/chunks/ssr/app_insights_page_1d_chja.js +3 -0
  199. package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +14 -14
  200. package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +3 -3
  201. package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +1 -1
  202. package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +4 -4
  203. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  204. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  205. package/.next/standalone/.next/server/next-font-manifest.js +1 -1
  206. package/.next/standalone/.next/server/next-font-manifest.json +4 -0
  207. package/.next/standalone/.next/server/pages/404.html +1 -1
  208. package/.next/standalone/.next/server/pages/500.html +1 -1
  209. package/.next/standalone/.next/server/prefetch-hints.json +14 -0
  210. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  211. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  212. package/.next/standalone/.next/static/chunks/02wnf8dkxegxg.js +34 -0
  213. package/.next/standalone/.next/static/chunks/0hggugbh84m4t.js +21 -0
  214. package/.next/{static/chunks/2eo9pqq9gay3r.js → standalone/.next/static/chunks/0ka4prpk3w9wn.js} +4 -4
  215. package/.next/standalone/.next/static/chunks/0s_xxwp7z88vv.js +1 -0
  216. package/.next/standalone/.next/static/chunks/1-nnl8amfm4yh.js +57 -0
  217. package/.next/{static/chunks/44yn0tcd9dfk3.js → standalone/.next/static/chunks/1bkt_hhjzvbh8.js} +1 -1
  218. package/.next/standalone/.next/static/chunks/1cmphb3kh2ers.css +3 -0
  219. package/.next/{static/chunks/1qbjermbf68xj.js → standalone/.next/static/chunks/1d8yv3k_atka7.js} +1 -1
  220. package/.next/standalone/.next/static/chunks/1ods5h86raivz.js +1 -0
  221. package/.next/standalone/.next/static/chunks/23hc51oa4vkhc.js +5 -0
  222. package/.next/standalone/.next/static/chunks/27n8k9wxlz5-7.js +1 -0
  223. package/.next/standalone/.next/static/chunks/{0p3g5tfostsep.js → 2q9i7pcxcnph1.js} +2 -2
  224. package/.next/{static/chunks/2zf4jmz9l3li2.js → standalone/.next/static/chunks/2w-y83mjlpnse.js} +1 -1
  225. package/.next/standalone/.next/static/chunks/2ws1xf293j-tm.js +5 -0
  226. package/.next/standalone/.next/static/chunks/3cqn-r-y5rrw1.js +5 -0
  227. package/.next/standalone/.next/static/chunks/3opgg1efrx8q6.js +1 -0
  228. package/.next/standalone/.next/static/chunks/3p51fpxxi4d5y.js +34 -0
  229. package/.next/{static/chunks/0an_b6zi1r_qq.js → standalone/.next/static/chunks/3y40z6of3r9py.js} +1 -5
  230. package/.next/standalone/.next/static/chunks/403dpflln8ah5.js +1 -0
  231. package/.next/standalone/CHANGELOG.md +83 -0
  232. package/.next/standalone/README.md +40 -14
  233. package/.next/standalone/SECURITY.md +17 -4
  234. package/.next/standalone/app/api/outreach/route.js +13 -73
  235. package/.next/standalone/app/api/scraper/route.js +106 -5
  236. package/.next/standalone/app/components/AppHeader.js +22 -6
  237. package/.next/standalone/app/components/AppTabs.js +4 -0
  238. package/.next/standalone/app/components/AutoConnect.js +286 -0
  239. package/.next/standalone/app/components/ChainView.js +75 -2
  240. package/.next/standalone/app/components/FilterPanel.js +8 -1
  241. package/.next/standalone/app/components/LinkedInLimits.js +38 -13
  242. package/.next/standalone/app/components/OutlinkQuest.js +32 -11
  243. package/.next/standalone/app/components/ScanStatusBar.js +18 -3
  244. package/.next/standalone/app/components/ScanTrail.js +13 -3
  245. package/.next/standalone/app/components/SeparationView.js +6 -22
  246. package/.next/standalone/app/components/Sidebar.js +147 -38
  247. package/.next/standalone/app/components/insights/Boards.js +275 -0
  248. package/.next/standalone/app/components/insights/PeopleView.js +266 -0
  249. package/.next/standalone/app/components/insights/Rail.js +156 -0
  250. package/.next/standalone/app/components/insights/Report.js +382 -0
  251. package/.next/standalone/app/components/insights/parts.js +239 -0
  252. package/.next/standalone/app/components/settings/UsageSection.js +50 -3
  253. package/.next/standalone/app/globals.css +2 -3
  254. package/.next/standalone/app/insights/page.js +220 -0
  255. package/.next/standalone/app/page.js +1 -17
  256. package/.next/standalone/app/queue/page.js +30 -18
  257. package/.next/standalone/app/setup/page.js +71 -13
  258. package/.next/standalone/install.sh +7 -6
  259. package/.next/standalone/lib/auto-connect.js +145 -0
  260. package/.next/standalone/lib/companies.js +1 -1
  261. package/.next/standalone/lib/in-progress.js +46 -0
  262. package/.next/standalone/lib/insights-board.js +613 -0
  263. package/.next/standalone/lib/linkedin-limits.js +36 -11
  264. package/.next/standalone/lib/requests-client.js +10 -0
  265. package/.next/standalone/lib/requests.js +124 -0
  266. package/.next/standalone/lib/scan-state.js +1 -0
  267. package/.next/standalone/lib/scanner-setup.js +82 -3
  268. package/.next/standalone/lib/scraper-client.js +18 -5
  269. package/.next/standalone/lib/settings.js +6 -0
  270. package/.next/standalone/lib/usage.js +5 -0
  271. package/.next/standalone/package-lock.json +2 -2
  272. package/.next/standalone/package.json +1 -1
  273. package/.next/standalone/scripts/build-app.mjs +167 -11
  274. package/.next/standalone/scripts/build-desktop.mjs +3 -2
  275. package/.next/standalone/scripts/ci-mac-signing.sh +128 -0
  276. package/.next/standalone/scripts/entitlements/electron.plist +23 -0
  277. package/.next/standalone/scripts/entitlements/node.plist +27 -0
  278. package/.next/standalone/scripts/mac-sign.mjs +563 -0
  279. package/.next/standalone/scripts/make-dmg-background.mjs +2 -1
  280. package/.next/standalone/scripts/scrape.py +569 -9
  281. package/.next/standalone/scripts/set-signing-secrets.sh +96 -0
  282. package/.next/static/chunks/02wnf8dkxegxg.js +34 -0
  283. package/.next/static/chunks/0hggugbh84m4t.js +21 -0
  284. package/.next/{standalone/.next/static/chunks/2eo9pqq9gay3r.js → static/chunks/0ka4prpk3w9wn.js} +4 -4
  285. package/.next/static/chunks/0s_xxwp7z88vv.js +1 -0
  286. package/.next/static/chunks/1-nnl8amfm4yh.js +57 -0
  287. package/.next/{standalone/.next/static/chunks/44yn0tcd9dfk3.js → static/chunks/1bkt_hhjzvbh8.js} +1 -1
  288. package/.next/static/chunks/1cmphb3kh2ers.css +3 -0
  289. package/.next/{standalone/.next/static/chunks/1qbjermbf68xj.js → static/chunks/1d8yv3k_atka7.js} +1 -1
  290. package/.next/static/chunks/1ods5h86raivz.js +1 -0
  291. package/.next/static/chunks/23hc51oa4vkhc.js +5 -0
  292. package/.next/static/chunks/27n8k9wxlz5-7.js +1 -0
  293. package/.next/static/chunks/{0p3g5tfostsep.js → 2q9i7pcxcnph1.js} +2 -2
  294. package/.next/{standalone/.next/static/chunks/2zf4jmz9l3li2.js → static/chunks/2w-y83mjlpnse.js} +1 -1
  295. package/.next/static/chunks/2ws1xf293j-tm.js +5 -0
  296. package/.next/static/chunks/3cqn-r-y5rrw1.js +5 -0
  297. package/.next/static/chunks/3opgg1efrx8q6.js +1 -0
  298. package/.next/static/chunks/3p51fpxxi4d5y.js +34 -0
  299. package/.next/{standalone/.next/static/chunks/0an_b6zi1r_qq.js → static/chunks/3y40z6of3r9py.js} +1 -5
  300. package/.next/static/chunks/403dpflln8ah5.js +1 -0
  301. package/CHANGELOG.md +83 -0
  302. package/README.md +40 -14
  303. package/SECURITY.md +17 -4
  304. package/lib/auto-connect.js +145 -0
  305. package/lib/companies.js +1 -1
  306. package/lib/in-progress.js +46 -0
  307. package/lib/insights-board.js +613 -0
  308. package/lib/linkedin-limits.js +36 -11
  309. package/lib/requests-client.js +10 -0
  310. package/lib/requests.js +124 -0
  311. package/lib/scan-state.js +1 -0
  312. package/lib/scanner-setup.js +82 -3
  313. package/lib/scraper-client.js +18 -5
  314. package/lib/settings.js +6 -0
  315. package/lib/usage.js +5 -0
  316. package/package.json +1 -1
  317. package/scripts/scrape.py +569 -9
  318. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_4y8z7._.js +0 -8
  319. package/.next/standalone/.next/server/chunks/_02twlca._.js +0 -10
  320. package/.next/standalone/.next/server/chunks/lib_rpc_0nef_n7.js +0 -3
  321. package/.next/standalone/.next/server/chunks/lib_rpc_0plrzra.js +0 -3
  322. package/.next/standalone/.next/server/chunks/lib_rpc_13a4m0c.js +0 -3
  323. package/.next/standalone/.next/server/chunks/lib_rpc_1if17c-.js +0 -3
  324. package/.next/standalone/.next/server/chunks/lib_rpc_1j-zw7v.js +0 -3
  325. package/.next/standalone/.next/server/chunks/lib_rpc_1sngt-q.js +0 -3
  326. package/.next/standalone/.next/server/chunks/lib_rpc_1v2sgme.js +0 -3
  327. package/.next/standalone/.next/server/chunks/lib_rpc_1vh-tgx.js +0 -3
  328. package/.next/standalone/.next/server/chunks/lib_rpc_1zaubz7.js +0 -3
  329. package/.next/standalone/.next/server/chunks/ssr/_02sband._.js +0 -7
  330. package/.next/standalone/.next/server/chunks/ssr/_189g8lg._.js +0 -3
  331. package/.next/standalone/.next/server/chunks/ssr/_1f_n00p._.js +0 -3
  332. package/.next/standalone/.next/server/chunks/ssr/app_components_0t908t-._.js +0 -3
  333. package/.next/standalone/.next/static/chunks/0e3g0sil79cus.js +0 -21
  334. package/.next/standalone/.next/static/chunks/0zd0t7e60bn4z.js +0 -1
  335. package/.next/standalone/.next/static/chunks/163b-20vwa06i.js +0 -34
  336. package/.next/standalone/.next/static/chunks/1pa-2v1ajvrko.js +0 -1
  337. package/.next/standalone/.next/static/chunks/23_1z47al92nz.js +0 -57
  338. package/.next/standalone/.next/static/chunks/260bomtr214m8.js +0 -34
  339. package/.next/standalone/.next/static/chunks/2xk2ra4piiuod.js +0 -34
  340. package/.next/standalone/.next/static/chunks/3b0-14cxqsprt.js +0 -1
  341. package/.next/standalone/.next/static/chunks/3jarus1_38s2z.js +0 -1
  342. package/.next/standalone/.next/static/chunks/3ssu341hav_sq.css +0 -3
  343. package/.next/standalone/.next/static/chunks/3v1qd3sqhqn0d.js +0 -1
  344. package/.next/static/chunks/0e3g0sil79cus.js +0 -21
  345. package/.next/static/chunks/0zd0t7e60bn4z.js +0 -1
  346. package/.next/static/chunks/163b-20vwa06i.js +0 -34
  347. package/.next/static/chunks/1pa-2v1ajvrko.js +0 -1
  348. package/.next/static/chunks/23_1z47al92nz.js +0 -57
  349. package/.next/static/chunks/260bomtr214m8.js +0 -34
  350. package/.next/static/chunks/2xk2ra4piiuod.js +0 -34
  351. package/.next/static/chunks/3b0-14cxqsprt.js +0 -1
  352. package/.next/static/chunks/3jarus1_38s2z.js +0 -1
  353. package/.next/static/chunks/3ssu341hav_sq.css +0 -3
  354. package/.next/static/chunks/3v1qd3sqhqn0d.js +0 -1
  355. /package/.next/standalone/.next/static/{ZVpLBK-2vpllv2HxOFs2d → mwCGF3OV3CWV4SjUgg5T8}/_buildManifest.js +0 -0
  356. /package/.next/standalone/.next/static/{ZVpLBK-2vpllv2HxOFs2d → mwCGF3OV3CWV4SjUgg5T8}/_clientMiddlewareManifest.js +0 -0
  357. /package/.next/standalone/.next/static/{ZVpLBK-2vpllv2HxOFs2d → mwCGF3OV3CWV4SjUgg5T8}/_ssgManifest.js +0 -0
  358. /package/.next/static/{ZVpLBK-2vpllv2HxOFs2d → mwCGF3OV3CWV4SjUgg5T8}/_buildManifest.js +0 -0
  359. /package/.next/static/{ZVpLBK-2vpllv2HxOFs2d → mwCGF3OV3CWV4SjUgg5T8}/_clientMiddlewareManifest.js +0 -0
  360. /package/.next/static/{ZVpLBK-2vpllv2HxOFs2d → mwCGF3OV3CWV4SjUgg5T8}/_ssgManifest.js +0 -0
@@ -0,0 +1 @@
1
+ (globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,94657,e=>{"use strict";var t=e.i(43476),n=e.i(71645),r=e.i(7837);let o=e=>`${e.name} ${e.was} → ${e.estimated?"estimated":e.now}`,l=e=>({padding:"6px 12px",borderRadius:7,cursor:"pointer",fontSize:12,fontWeight:700,border:e?"none":"1px solid rgba(var(--sd-ink, 255, 255, 255), 0.15)",background:e?"linear-gradient(135deg, #00ff88, #3498DB)":"transparent",color:e?"#000":"var(--sd-fg-2, #cfd8d8)"});function s({onAnswered:e}){var a;let i,c,[d,u]=(0,n.useState)(null),[f,p]=(0,n.useState)(!1),[m,g]=(0,n.useState)(()=>new Set),[h,y]=(0,n.useState)(!1),[b,x]=(0,n.useState)(null);if((0,n.useEffect)(()=>{let e=!1;return(0,r.csvNetworkSource)().then(t=>t||e?null:fetch("/api/company-scores/legacy").then(e=>e.json())).then(t=>{!e&&t?.offer?.companies?.length&&(u(t.offer),g(new Set(t.offer.companies.map(e=>e.name))))}).catch(()=>{}),()=>{e=!0}},[]),!d)return null;let v=async t=>{y(!0),x(null);try{let n=await fetch("/api/company-scores/legacy",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({keep:t})}),r=await n.json();if(!n.ok&&!r.kept)throw Error(r.error||"Could not save your answer.");u(null),e?.({kept:r.kept||[],scored:r.scored||0,error:n.ok?null:r.error})}catch(e){x(e.message)}finally{y(!1)}};return(0,t.jsxs)("section",{"aria-label":"Built-in scores changed",style:{marginBottom:14,padding:14,borderRadius:10,border:"1px solid rgba(255,215,0,0.35)",background:"rgba(255,215,0,0.06)",fontSize:12.5,color:"var(--sd-fg-2, #cfd8d8)",lineHeight:1.6},children:[(0,t.jsx)("b",{style:{color:"var(--sd-fg-1, #fff)"},children:"Built-in scores changed in this version:"})," ",(i=(a=d.companies).slice(0,3).map(o).join(", "),(c=a.length-3)>0?`${i} and ${c} more`:i),"."," ","Keep any of the old ones as your own?",(0,t.jsx)("div",{style:{marginTop:4,color:"var(--sd-fg-3, #8b9a9a)"},children:"The built-in list now holds only companies most professionals would recognise, scored the same for everyone; anything else is estimated from your network. A score you keep is yours, like one you set below, and Auto hands it back."}),f&&(0,t.jsx)("ul",{style:{listStyle:"none",padding:0,margin:"10px 0 0",display:"grid",gap:4},children:d.companies.map(e=>(0,t.jsx)("li",{children:(0,t.jsxs)("label",{style:{display:"flex",gap:8,alignItems:"baseline",cursor:"pointer"},children:[(0,t.jsx)("input",{type:"checkbox",checked:m.has(e.name),disabled:h,onChange:()=>{let t;return t=e.name,g(e=>{let n=new Set(e);return n.has(t)?n.delete(t):n.add(t),n})}}),(0,t.jsxs)("span",{children:[(0,t.jsx)("b",{style:{color:"var(--sd-fg-1, #fff)"},children:e.name})," ",e.was," → ",e.estimated?`estimated (${e.now})`:e.now,(0,t.jsxs)("span",{style:{color:"var(--sd-fg-4, #778)"},children:[` \xb7 ${e.people} ${1===e.people?"person":"people"} there now`,e.names.length>1&&` \xb7 for ${e.names.join(", ")}`]})]})]})},e.name))}),(0,t.jsxs)("div",{style:{display:"flex",gap:8,marginTop:10,flexWrap:"wrap"},children:[f?(0,t.jsxs)(t.Fragment,{children:[(0,t.jsxs)("button",{disabled:h||!m.size,onClick:()=>v([...m]),style:{...l(!0),opacity:h||!m.size?.5:1},children:["Keep ",m.size," ",1===m.size?"score":"scores"]}),(0,t.jsx)("button",{disabled:h,onClick:()=>p(!1),style:l(!1),children:"Back"})]}):(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)("button",{disabled:h,onClick:()=>v(d.companies.map(e=>e.name)),style:l(!0),children:"Keep all"}),(0,t.jsx)("button",{disabled:h,onClick:()=>p(!0),style:l(!1),children:"Choose…"})]}),(0,t.jsx)("button",{disabled:h,onClick:()=>v([]),style:l(!1),children:"No thanks"})]}),b&&(0,t.jsx)("div",{role:"alert",style:{marginTop:8,color:"#e74c3c"},children:b})]})}let a="1px solid rgba(var(--sd-ink, 255, 255, 255), 0.1)",i={yours:{label:"your score",color:"var(--sd-green, #00ff88)"},known:{label:"known list",color:"var(--sd-blue, #3498DB)"},data:{label:"public data",color:"#1abc9c"},network:{label:"estimated: many of your people",color:"var(--sd-orange, #FF6B35)"},default:{label:"unknown company",color:"var(--sd-fg-4, #778)"}};async function c(){let e=await fetch("/api/company-scores"),t=await e.json();if(!e.ok)throw Error(t.error||"Could not load company scores");return t.companies}function d(){let[e,t]=(0,n.useState)(null),[r,o]=(0,n.useState)(null);return(0,n.useEffect)(()=>{let e=!1;return c().then(n=>{e||t(n)}).catch(t=>{e||o(t.message)}),()=>{e=!0}},[]),{companies:e,error:r,setScore:(0,n.useCallback)(async(e,n)=>{let r=await fetch("/api/company-scores",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,score:n})}),o=await r.json();if(!r.ok)throw Error(o.error||"Could not save");return t(await c()),o},[]),reload:(0,n.useCallback)(async()=>t(await c()),[])}}function u({company:e,onSet:r,compact:o}){let[l,s]=(0,n.useState)(!1),i=async t=>{s(!0);try{await r(e.name,"auto"===t?null:Number(t))}finally{s(!1)}};return(0,t.jsxs)("select",{disabled:l,value:"yours"===e.source?String(e.score):"auto",onChange:e=>i(e.target.value),title:"Set this company's score. Auto uses the known list or an estimate.",style:{padding:o?"2px 4px":"4px 6px",borderRadius:6,border:a,background:"var(--sd-card, #11131c)",color:"var(--sd-fg-1, #dfe6e9)",fontSize:12},children:[(0,t.jsxs)("option",{value:"auto",children:["Auto (","yours"===e.source?e.suggested??"est.":e.score,")"]}),[10,9,8,7,6,5,4,3,2,1].map(e=>(0,t.jsx)("option",{value:e,children:e},e))]})}e.s(["ScorePicker",0,u,"default",0,function({onRescored:e,version:r=0}){let{companies:o,error:l,setScore:c,reload:f}=d();(0,n.useEffect)(()=>{r&&f().catch(()=>{})},[r,f]);let[p,m]=(0,n.useState)(""),[g,h]=(0,n.useState)("all"),[y,b]=(0,n.useState)(150),[x,v]=(0,n.useState)(null),w=e=>{v(e),setTimeout(()=>v(null),3500)},S=async(t,n)=>{let r=await c(t,n);w(`${t}: ${null==n?"back to auto":`set to ${n}`} \xb7 rescored ${r.scored} people`),e?.()},k=async({kept:t,scored:n,error:r})=>{if(!t.length)return w("Keeping the new built-in scores.");w(r||`Kept ${t.length} old ${1===t.length?"score":"scores"} as your own \xb7 rescored ${n} people`),await f().catch(()=>{}),e?.()},j=(0,n.useMemo)(()=>{if(!o)return[];let e=p.trim().toLowerCase();return o.filter(t=>(!e||t.name.toLowerCase().includes(e))&&("all"===g||t.source===g))},[o,p,g]);if(l)return(0,t.jsx)("div",{style:{padding:24,color:"#e74c3c"},children:l});if(!o)return(0,t.jsx)("div",{style:{padding:24,color:"var(--sd-fg-4, #778)"},children:"Loading companies…"});let C=e=>o.filter(t=>t.source===e).length;return(0,t.jsxs)("div",{style:{flex:1,overflow:"auto",padding:16},children:[(0,t.jsxs)("div",{style:{maxWidth:980,margin:"0 auto"},children:[(0,t.jsx)(s,{onAnswered:k}),(0,t.jsxs)("div",{style:{padding:14,borderRadius:10,border:a,background:"rgba(var(--sd-ink, 255, 255, 255), 0.03)",fontSize:12.5,color:"var(--sd-fg-2, #cfd8d8)",lineHeight:1.6},children:[(0,t.jsx)("b",{style:{color:"var(--sd-fg-1, #fff)"},children:"How a power score works."})," power = ",(0,t.jsx)("b",{children:"title"})," × ",(0,t.jsx)("b",{children:"company weight"})," + bonus. The title (student 1 … founder or C-suite 10) comes from someone’s roles, with former roles at 70%; an audience of their own counts like a title (1M+ followers like a director). The company weight runs from 0.725 for a company we don’t know (or none found), the middle of the scale, to 1.0 (a company scored 10), so seniority counts for more at a bigger company. The bonus is at most +1.5, for investor, YC, an audience in the millions or the top award of any field, and counts half at an unknown company. A 1st-degree person whose circle is unusually strong gets up to +2 more: up to +1 for a high share of it at A or S, or +1 for every 25 people there. Tiers are graded on your network’s curve unless you choose the fixed scale (",(0,t.jsx)("a",{href:"#tiers",style:{color:"var(--sd-blue, #3498DB)"},children:"Tiers"}),", above): your top 3% are S, the next 12% A, the next 25% B (it only lifts, and never lifts anyone under 4 into S or A). On the fixed scale, S ≥ 7.5 · A ≥ 5.5 · B ≥ 4 · C ≥ 2.5.",(0,t.jsxs)("div",{style:{marginTop:6,color:"var(--sd-fg-3, #8b9a9a)"},children:["So a VP at a 10 scores 9.0, a founder at an unknown company 7.3, a director at an unknown company 5.4, and an intern at a 10 scores 2.0. Set a company’s score below and everyone there is rescored. Companies in the sectors you pick (",(0,t.jsx)("a",{href:"#sector",style:{color:"var(--sd-blue, #3498DB)"},children:"Your sector"}),", above) get +1 or +2; a score you set is never changed."]})]}),(0,t.jsxs)("div",{style:{display:"flex",gap:8,alignItems:"center",flexWrap:"wrap",margin:"14px 0 10px"},children:[(0,t.jsx)("input",{value:p,onChange:e=>m(e.target.value),placeholder:"Find a company",style:{padding:"6px 10px",borderRadius:7,border:a,background:"rgba(var(--sd-ink, 255, 255, 255), 0.05)",color:"var(--sd-fg-1, #fff)",fontSize:13,minWidth:180}}),[["all",`All ${o.length}`],["known",`Known list ${C("known")}`],["data",`Public data ${C("data")}`],["yours",`Yours ${C("yours")}`],["network",`Estimated ${C("network")}`],["default",`Unknown ${C("default")}`]].map(([e,n])=>(0,t.jsx)("button",{onClick:()=>h(e),style:{padding:"5px 10px",borderRadius:7,border:a,cursor:"pointer",fontSize:11.5,fontWeight:600,background:g===e?"rgba(52,152,219,0.25)":"transparent",color:g===e?"var(--sd-fg-1, #cfe6f7)":"var(--sd-fg-3, #8b9a9a)"},children:n},e))]}),(0,t.jsxs)("div",{style:{borderRadius:10,border:a,overflow:"hidden"},children:[(0,t.jsxs)("div",{style:{display:"grid",gridTemplateColumns:"1fr 70px 70px 70px 60px 180px 110px",gap:8,padding:"8px 12px",fontSize:10.5,letterSpacing:.6,color:"var(--sd-fg-3, #8b9a9a)",background:"rgba(var(--sd-ink, 255, 255, 255), 0.04)"},children:[(0,t.jsx)("span",{children:"COMPANY"}),(0,t.jsx)("span",{style:{textAlign:"right"},children:"PEOPLE"}),(0,t.jsx)("span",{style:{textAlign:"right"},children:"YOU KNOW"}),(0,t.jsx)("span",{style:{textAlign:"right"},children:"DIRECTOR+"}),(0,t.jsx)("span",{style:{textAlign:"right"},children:"SCORE"}),(0,t.jsx)("span",{children:"SOURCE"}),(0,t.jsx)("span",{children:"SET"})]}),j.slice(0,y).map(e=>(0,t.jsxs)("div",{style:{display:"grid",gridTemplateColumns:"1fr 70px 70px 70px 60px 180px 110px",gap:8,padding:"7px 12px",alignItems:"center",borderTop:"1px solid rgba(var(--sd-ink, 255, 255, 255), 0.05)",fontSize:12.5},children:[(0,t.jsxs)("span",{style:{display:"flex",alignItems:"center",gap:8,minWidth:0},children:[(0,t.jsx)("span",{title:`${e.industry.label} (inferred)`,style:{width:8,height:8,borderRadius:"50%",background:e.industry.color,flexShrink:0}}),(0,t.jsx)("span",{style:{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:e.name})]}),(0,t.jsx)("span",{style:{textAlign:"right"},children:e.people}),(0,t.jsx)("span",{style:{textAlign:"right",color:e.d1?"var(--sd-green, #00ff88)":"var(--sd-fg-5, #556)"},children:e.d1}),(0,t.jsx)("span",{style:{textAlign:"right",color:e.senior?"var(--sd-gold, #FFD700)":"var(--sd-fg-5, #556)"},children:e.senior}),(0,t.jsx)("b",{style:{textAlign:"right",fontSize:15},children:e.score}),(0,t.jsxs)("span",{style:{fontSize:11,color:i[e.source]?.color},children:[i[e.source]?.label,e.sectorBonus>0&&(0,t.jsxs)("span",{style:{color:"var(--sd-green, #00ff88)"},children:[" + ",e.sectorBonus," your sector"]})]}),(0,t.jsx)(u,{company:e,onSet:S,compact:!0})]},e.name))]}),j.length>y&&(0,t.jsxs)("button",{onClick:()=>b(e=>e+300),style:{marginTop:10,padding:"7px 14px",borderRadius:8,border:a,background:"rgba(var(--sd-ink, 255, 255, 255), 0.05)",color:"var(--sd-fg-1, #dfe6e9)",cursor:"pointer",fontSize:12},children:["Show more (",j.length-y," left)"]})]}),x&&(0,t.jsx)("div",{role:"status",style:{position:"fixed",bottom:24,left:"50%",transform:"translateX(-50%)",padding:"10px 18px",borderRadius:24,background:"linear-gradient(135deg, #00ff88, #3498DB)",color:"#000",fontWeight:800,fontSize:13,zIndex:50},children:x})]})},"useCompanyScores",0,d],94657)},39160,84278,e=>{"use strict";var t=e.i(7214),n=e.i(7837);function r(e){let t=String(e||"").trim();if(!t)return null;let n=t.match(/^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})(?::(\d{2}))?(?:\s*UTC)?$/);if(n)return Date.UTC(+n[1],n[2]-1,+n[3],+n[4],+n[5],+(n[6]||0));if(n=t.match(/^(\d{1,2})\/(\d{1,2})\/(\d{2,4}),?\s+(\d{1,2}):(\d{2})\s*(AM|PM)?$/i)){let e=100>+n[3]?2e3+ +n[3]:+n[3],t=n[4]%12;return"PM"===(n[6]||"").toUpperCase()&&(t+=12),Date.UTC(e,n[1]-1,+n[2],t,+n[5])}if(n=t.match(/^([A-Za-z]{3,9})\s+(\d{4})$/)){let e=Date.parse(`1 ${n[1]} ${n[2]} UTC`);return Number.isNaN(e)?null:e}let r=Date.parse(/\d{4}$/.test(t)?`${t} UTC`:t);return Number.isNaN(r)?null:r}let o=e=>(0,t.keyFor)({profile_url:e});function l(e=[]){let t=new Map;for(let n of e){let e=o(n["sender profile url"]),r=n["conversation id"];r&&e&&(t.has(e)||t.set(e,new Set),t.get(e).add(r))}let n=null;for(let[e,r]of t)(!n||r.size>t.get(n).size)&&(n=e);return n}let s=e=>{let t=f(e);return t?`name:${t}`:null},a=/sponsored/i,i=/in\s?mail/i,c=/linkedin\.com\/(company|showcase|school)\//i,d=e=>/^(yes|true|1)$/i.test(String(e||"").trim());function u(e,t){if(!e||!e.total)return"never";let n=t-e.last;return n<=2592e6||e.recent>=3?"warm":n<=31536e6?"cool":"dormant"}let f=e=>String(e||"").split(",")[0].normalize("NFD").replace(/[̀-ͯ]/g,"").toLowerCase().replace(/[^a-z0-9 ]+/g," ").replace(/\s+/g," ").trim();e.s(["buildConversations",0,function(e=[],{keepText:t=!1}={}){let n=l(e),u=new Map;for(let t of e)n&&o(t["sender profile url"])===n&&t.from&&u.set(t.from,(u.get(t.from)||0)+1);let p=f([...u].sort((e,t)=>t[1]-e[1])[0]?.[0]||""),m=e=>!!e&&(e===n||!!p&&e===`name:${p}`),g={},h=new Map,y=0,b=0;for(let n of e){let e=n["conversation id"],l=r(n.date);if(!e||null==l){b+=1;continue}if(d(n["is message draft"])){y+=1;continue}let u=o(n["sender profile url"])||s(n.from);u&&n.from&&!g[u]&&(g[u]=String(n.from).trim());let f=String(n["recipient profile urls"]||"").split(/[,;\s]+/).filter(Boolean).map(o),p=String(n.to||"").split(",").map(e=>e.trim()).filter(Boolean),x=f.length?f:p.map(s).filter(Boolean);f.length&&f.length===p.length&&f.forEach((e,t)=>{g[e]||(g[e]=p[t])}),f.length||p.forEach(e=>{let t=s(e);t&&!g[t]&&(g[t]=e)}),h.has(e)||h.set(e,{id:e,title:"",everyone:new Set,messages:[],kind:null,folder:"",newest:-1/0});let v=h.get(e);u&&v.everyone.add(u),x.forEach(e=>v.everyone.add(e)),v.kind=v.kind||function(e){let t=String(e.folder||""),n=String(e["conversation title"]||"");return a.test(t)||a.test(n)||c.test(String(e["sender profile url"]||""))?"sponsored":i.test(t)||i.test(n)?"inmail":null}(n),l>=v.newest&&(v.newest=l,v.folder=String(n.folder||"").trim().toLowerCase().slice(0,30));let w={t:l,fromMe:m(u)};t&&(w.text=[n.subject,n.content].map(e=>String(e||"").trim()).filter(Boolean).join("\n\n"),!v.title&&n["conversation title"]&&(v.title=String(n["conversation title"]).trim())),v.messages.push(w)}let x=[];for(let e of h.values()){let n=[...e.everyone].filter(e=>e&&!m(e)).sort();e.messages.sort((e,t)=>e.t-t.t);let r={id:e.id,people:n,group:n.length>1,kind:e.kind,folder:e.folder,messages:e.messages};t&&(r.title=e.title),x.push(r)}for(let e of Object.keys(g))m(e)&&delete g[e];return{self:n,conversations:x,names:g,drafts:y,skipped:b}},"buildInvitations",0,function(e=[],{keepText:t=!1}={}){let n=[];for(let l of e){let e=String(l.direction||"").toUpperCase();if("INCOMING"!==e&&"OUTGOING"!==e)continue;let a="INCOMING"===e?"in":"out",i=String(("in"===a?l.from:l.to)||"").trim(),c=o("in"===a?l.inviterprofileurl:l.inviteeprofileurl),d=(c&&/\/in\//.test(c)?c:null)||s(i);if(!d)continue;let u=r(l["sent at"]),f={id:`${a}|${d}|${u??""}`,dir:a,key:d,name:i.slice(0,120),t:u};t&&String(l.message||"").trim()&&(f.note=String(l.message).trim().slice(0,2e3)),n.push(f)}return n.sort((e,t)=>(t.t||0)-(e.t||0))},"careerChapters",0,function(e=[],t=[],n=Date.now()){let l=e.map(e=>({company:e["company name"]||"",title:e.title||"",from:r(e["started on"]),to:r(e["finished on"])})).filter(e=>e.company&&null!=e.from).sort((e,t)=>e.from-t.from),s=t.map(e=>({url:o(e.url||e.profile_url),t:r(e["connected on"]||e.connected_on)})).filter(e=>null!=e.t);return l.map(e=>{let t=e.to??n,r=s.filter(n=>n.t>=e.from&&n.t<t);return{...e,to:e.to,made:r.length,people:r.map(e=>e.url)}})},"conversationSummary",0,function(e){let t=e.messages[e.messages.length-1];return{id:e.id,people:e.people,group:e.group,kind:e.kind||null,folder:e.folder||"",last:t?t.t:null,lastFromThem:t?!t.fromMe:null,count:e.messages.length,mine:e.messages.filter(e=>e.fromMe).length}},"invitationSplit",0,function(e=[]){let t=0,n=0;for(let r of e){let e=String(r.direction||"").toUpperCase();"INCOMING"===e?t+=1:"OUTGOING"===e&&(n+=1)}return{incoming:t,outgoing:n,total:t+n}},"messageStats",0,function(e=[]){let t=l(e),n=new Map;for(let t of e){let e=o(t["sender profile url"]),l=t["conversation id"];if(!l||!e)continue;let s=String(t["recipient profile urls"]||"").split(/[,;\s]+/).filter(Boolean).map(o),a=r(t.date);if(null==a)continue;n.has(l)||n.set(l,{people:new Set,msgs:[]});let i=n.get(l);i.people.add(e),s.forEach(e=>i.people.add(e)),i.msgs.push({from:e,t:a})}let s=0,a=new Map;for(let e of n.values()){let n=[...e.people].filter(e=>e!==t);if(1!==n.length||!e.people.has(t))continue;let r=n[0],o=a.get(r)||{last:0,lastFromThem:!1,total:0,times:[]};for(let n of e.msgs)o.total+=1,o.times.push(n.t),n.t>=o.last&&(o.last=n.t,o.lastFromThem=n.from!==t),n.t>s&&(s=n.t);a.set(r,o)}for(let e of a.values())e.recent=e.times.filter(e=>e>s-7776e6).length,delete e.times;return{self:t,asOf:s||null,people:a}},"nameKey",0,f,"postingEffect",0,function(e=[],t=[]){let n=e.map(e=>({t:r(e.date),link:e.sharelink||e["share link"]||""})).filter(e=>null!=e.t).sort((e,t)=>e.t-t.t),o=t.map(e=>r(e["connected on"]||e.connected_on)).filter(e=>null!=e).sort((e,t)=>e-t);if(!o.length)return{posts:[],usualWeek:0,afterPosts:0};let l=Math.max(6048e5,o[o.length-1]-o[0]),s=o.length/l*6048e5,a=n.map(e=>({...e,week:o.filter(t=>t>=e.t&&t<e.t+6048e5).length})),i=a.length?a.reduce((e,t)=>e+t.week,0)/a.length:0;return{posts:a,usualWeek:s,afterPosts:i}},"readTable",0,function(e,t){let r=(0,n.parseCsv)(String(e||"")),o=r.findIndex(e=>e.some(e=>e.trim().toLowerCase()===t.toLowerCase()));if(o<0)return[];let l=r[o].map(e=>e.trim().toLowerCase());return r.slice(o+1).filter(e=>e.some(e=>e.trim())).map(e=>Object.fromEntries(l.map((t,n)=>[t,(e[n]??"").trim()])))},"warmthOf",0,u],84278);let p=["gravity","rings","push","pull","distance","orbit","dotSize","lines","sizeBy","names","branch","colourBy","speed","loop"],m=e=>"boolean"==typeof e||"number"==typeof e&&Number.isFinite(e)&&1e3>=Math.abs(e)||"string"==typeof e&&/^[a-z]{1,20}$/i.test(e);function g(e){if(!Array.isArray(e))return[];let t=new Map;for(let n of e){let e="string"==typeof n?.name?n.name.trim().slice(0,40):"";if(!e||!n.settings||"object"!=typeof n.settings||Array.isArray(n.settings))continue;let r={};for(let e of p)Object.hasOwn(n.settings,e)&&m(n.settings[e])&&(r[e]=n.settings[e]);t.set(e,{name:e,settings:r})}return[...t.values()].slice(0,50)}var h=e.i(56899),y=e.i(40827);let b="six-degrees-galaxy-lab",x="six-degrees-galaxy-layouts",v=Object.freeze({on:!0,gravity:0,rings:1,push:15,pull:1,distance:1,dotSize:1,lines:1,sizeBy:"power",orbit:0,names:"key",branch:!1,colourBy:"tier",speed:15,loop:!1,labels:!0,v:2}),w=["gravity","rings","push","pull","distance"],S=Object.freeze({gravity:.8,rings:0,push:6,pull:6,distance:.15,sizeBy:"links"}),k=Object.freeze({rings:{lines:1},orbit:{lines:1},clusters:{lines:3}}),j=Object.freeze({gravity:0,rings:3,push:6,pull:4,distance:.22,sizeBy:"power"}),C=new Set,O=null;function T(){return O||(O=function(){try{let e=JSON.parse(localStorage.getItem(b)||"null");if(!e||"object"!=typeof e)return v;return{...v,...e,...e.v===v.v?{}:{branch:!1},v:v.v,on:!0}}catch{return v}}()),O}function F(e){O=null===e?{...v,labels:T().labels}:{...T(),...e,on:!0};try{localStorage.setItem(b,JSON.stringify(O))}catch{}C.forEach(e=>e())}let A=null,N=new Set;function M(){if(!A){try{A=JSON.parse(localStorage.getItem(x)||"[]")}catch{A=[]}Array.isArray(A)||(A=[])}return A}function $(e,{save:t=!0}={}){A=e;try{localStorage.setItem(x,JSON.stringify(e))}catch{}N.forEach(e=>e()),t&&(0,h.saveSettings)({galaxyLayouts:e})}let B=new Set,E={at:null,playing:!1,min:null,max:null,of:0,find:"",fly:0,found:0,fit:0,social:void 0},z=null;function L(e){let t={...E,...e};Object.keys(e).every(e=>Object.is(t[e],E[e]))||(E=t,B.forEach(e=>e()))}function D(){cancelAnimationFrame(z),z=null,L({playing:!1})}let I=null;function R(e){let n=new Map;for(let[r,o]of Object.entries(e?.people||{}))n.set((0,t.keyFor)({profile_url:r}),{...o});for(let[r,o]of Object.entries(e?.live||{})){let e=(0,t.keyFor)({profile_url:r}),l=n.get(e)||{total:0,recent:0};o.last&&(!l.last||o.last>l.last)&&(l.last=o.last,"boolean"==typeof o.lastFromThem&&(l.lastFromThem=o.lastFromThem)),l.unread=o.unread,l.total=Math.max(l.total||0,1),n.set(e,l)}let r=Math.max(e?.asOf||0,e?.liveAt&&Date.parse(e.liveAt)||0);for(let e of n.values())(e.last||0)>r&&(r=e.last);return{merged:n,asOf:r,chapters:e?.chapters||[],posts:e?.posts?.posts||[]}}let U={1:"#4FC3F7",2:"#BA68C8",3:"#FFB74D"},P=["#FF6B6B","#4ECDC4","#FFD93D","#A29BFE","#00B894","#FD79A8","#74B9FF","#E17055"],K={warm:"#FF7043",cool:"#4FC3F7",dormant:"#8D6E63",never:"#546E7A"},W="#4a5568",_=["#2b1b6b","#6a1b9a","#c2185b","#f4511e","#ffb300","#fff3b0"];function J(e){let t=Math.max(0,Math.min(1,Number(e)||0))*(_.length-1),n=Math.min(_.length-2,Math.floor(t)),r=t-n,o=_[n],l=_[n+1];return`#${[1,3,5].map(e=>Math.round(parseInt(o.slice(e,e+2),16)*(1-r)+parseInt(l.slice(e,e+2),16)*r).toString(16).padStart(2,"0")).join("")}`}function Y(e=[],t=()=>0){let n=[...new Set(e.map(t))].sort((e,t)=>e-t),r=new Map(n.map((e,t)=>[e,n.length>1?t/(n.length-1):1]));return e=>r.get(t(e))??0}let G=e=>String(e||"").trim().toLowerCase();e.s(["CLUSTERS",0,S,"FORCE_KEYS",0,w,"LAB_DEFAULTS",0,v,"LAYOUT_LOOKS",0,k,"ORBIT",0,j,"adoptSavedLayouts",0,function(e){let t=g(M()),{use:n,save:r,changed:o}=(0,y.reconcileSaved)({saved:null==e?null:g(e),cached:t,isDefault:e=>0===e.length,same:(e,t)=>JSON.stringify(e)===JSON.stringify(t)});r&&(0,h.saveSettings)({galaxyLayouts:t}),o&&$(n,{save:!1})},"applyLayout",0,function(e){let t=M().find(t=>t.name===e);t&&F(Object.fromEntries(p.filter(e=>e in t.settings).map(e=>[e,t.settings[e]])))},"bornTimes",0,function(e,t){let n=new Map(e.map(e=>[e.id,e])),r=new Map,o=(e,l=0)=>{if(r.has(e.id))return r.get(e.id);let s=-1/0,a=t.get(e.id);if(1===e.degree||null==a||l>6){let t=e.connected_date?Date.parse(e.connected_date):NaN;s=Number.isNaN(t)?-1/0:t}return null!=a&&n.has(a)&&l<=6&&(s=Math.max(s,o(n.get(a),l+1))),r.set(e.id,s),s};return e.map(e=>o(e))},"chapterAt",0,function(e,t){let n=null;for(let r of e?.chapters||[])Number.isFinite(r.from)&&r.from<=t&&(!n||r.from>n.from)&&(n=r);return n&&(!Number.isFinite(n.to)||n.to>t)?n:null},"clockNow",0,()=>E,"colourScheme",0,function(e,n,r,o){if("degree"===e)return{of:e=>U[Math.min(e.degree||1,3)],legend:[["1st",U[1]],["2nd",U[2]],["3rd+",U[3]]]};if("company"===e){let e=new Map;for(let t of n){let n=G(t.company);if(!n)continue;let r=e.get(n)||{name:String(t.company).trim(),n:0};r.n+=1,e.set(n,r)}let t=[...e.entries()].filter(([,e])=>e.n>1).sort((e,t)=>t[1].n-e[1].n).slice(0,P.length),r=new Map(t.map(([e],t)=>[e,P[t]]));return{of:e=>r.get(G(e.company))||W,legend:[...t.map(([,e],t)=>[e.name,P[t]]),["Other",W]]}}if("heat"===e){let e=function(e=[]){return Y(e,t.score)}(n);return{of:t=>J(e(t)),heat:e,legend:[["Cold",J(.1)],["Warm",J(.45)],["Hot",J(.75)],["Hottest",J(1)]]}}return"warmth"===e&&o?{of:e=>1===(e.degree||1)?K[u(o.merged.get((0,t.keyFor)(e)),o.asOf)]:"#2d3748",legend:[["Warm",K.warm],["Cool",K.cool],["Dormant",K.dormant],["Never messaged",K.never]]}:{of:e=>r[e.tier]||"#666",legend:Object.entries(r)}},"effectiveLab",0,e=>e.on?e:e.labels===v.labels?v:{...v,labels:e.labels},"findMatches",0,function(e,t){let n=String(t||"").trim().toLowerCase();if(n.length<2)return null;let r=new Set;for(let t of e)[t.name,t.company,t.role,t.headline].some(e=>e&&String(e).toLowerCase().includes(n))&&r.add(t.id);return r},"forgetLayout",0,function(e){$(M().filter(t=>t.name!==e))},"heatBy",0,Y,"heatColour",0,J,"labNow",0,T,"layoutOf",0,function(e){let t=t=>Object.entries(t).every(([t,n])=>e[t]===n);return w.every(t=>e[t]===v[t])?"rings":t(S)?"clusters":t(j)?"orbit":null},"layoutsNow",0,M,"loadSocial",0,function(){return I||(I=fetch("/api/social").then(e=>e.ok?e.json():{}).then(e=>{L({social:e.social?R(e.social):null})}).catch(()=>L({social:null}))),I},"mergeSocial",0,R,"milestones",0,function(e){let t=[];for(let n of e?.chapters||[])Number.isFinite(n.from)&&t.push({t:n.from,kind:"job",label:n.company});for(let n of e?.posts||[])Number.isFinite(n.t)&&t.push({t:n.t,kind:"post",label:"You posted"});return t.sort((e,t)=>e.t-t.t)},"pause",0,D,"play",0,function e(){if(null==E.min||null==E.max||E.max<=E.min)return;let t=E.max-E.min,n=1e3*Math.max(1,Number(T().speed)||15),r=null==E.at||E.at>=E.max?E.min:E.at,o=performance.now()-(r-E.min)/t*n;L({at:r,playing:!0});let l=r=>{let s=Math.min(E.max,E.min+(r-o)/n*t);if(L({at:s}),s>=E.max){z=null,L({playing:!1}),T().loop&&setTimeout(()=>{z||E.at!==E.max||(L({at:E.min}),e())},1200);return}z=requestAnimationFrame(l)};cancelAnimationFrame(z),z=requestAnimationFrame(l)},"reachCounts",0,function(e,t){let n=new Map(e.map(e=>[e.id,0]));for(let r of e){let e=t.get(r.id);for(let r=0;null!=e&&r<7;r++)n.set(e,(n.get(e)||0)+1),e=t.get(e)}return n},"saveLayout",0,function(e){let t=String(e||"").trim().slice(0,40);if(!t)return;let n=T(),r=Object.fromEntries(p.map(e=>[e,n[e]]));$([...M().filter(e=>e.name!==t),{name:t,settings:r}])},"setClock",0,L,"setLab",0,F,"stopReplay",0,function(){D(),L({at:null})},"watchClock",0,function(e){return B.add(e),()=>B.delete(e)},"watchLab",0,function(e){C.add(e);let t=t=>{t.key===b&&(O=null,e())};return window.addEventListener("storage",t),()=>{C.delete(e),window.removeEventListener("storage",t)}},"watchLayouts",0,function(e){return N.add(e),()=>N.delete(e)}],39160)}]);
@@ -6,6 +6,89 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.0.0] - 2026-10-04
10
+
11
+ ### Added
12
+ - **On a Mac, the Scan page offers App Management once, so macOS stops asking while you scan.**
13
+ When the scanner starts Google Chrome, Chrome's own updater may try to update Chrome, and macOS
14
+ asks whether Six Degrees can manage apps. A new item under step 1 says what that is and opens
15
+ System Settings → Privacy & Security → App Management with one button (the right pane for macOS
16
+ 13 and 14 too); Done or Skip puts it away for good. It's optional and never holds the step up:
17
+ scanning works either way, and Chrome updates itself the next time you open it. Now that the app
18
+ is signed, allowing it once lasts across updates. The docs page's questions and the README say
19
+ the same.
20
+
21
+ ### Changed
22
+ - **The Mac app is signed by Blake Burford and notarized by Apple: no more Open Anyway.**
23
+ Download it, drag it to Applications and open it like any Mac app; macOS only asks once
24
+ whether to open an app from the internet. Every program inside it (the app, the server, the
25
+ scanner's Python and its parts) carries the signature, and Apple has checked the app and the
26
+ disk image. Updating from inside the app works the same from an unsigned copy (0.8.0 and
27
+ earlier) to a signed one, and from one signed version to the next. The website, the README,
28
+ the release notes and the picture in the disk image's window say so now. From 1.0.0 on, a
29
+ Mac release can't go out unsigned. The Windows app isn't signed yet: SmartScreen still asks
30
+ the first time.
31
+
32
+ ## [0.8.0] - 2026-10-03
33
+
34
+ ### Added
35
+ - **Insights: your network ranked by power.** A new tab between Paths and Outlink. The Power
36
+ Index ranks everyone within two steps of you, each person once, with shared ranks for ties and
37
+ a line saying why a big tie shares its score; filter by degree, tier and rarity, search by name,
38
+ company or who knows them, and from any row open their circle, scan it, or ask (their LinkedIn
39
+ profile opens and the request is marked sent, as on their card). Beside it: Untapped (S and A
40
+ in your 2nd degree with no request out), Kingmakers (the connections with the most S and A
41
+ behind them), Gatekeepers (who your S and A reach hangs on), Hidden giants (S-tier with one way
42
+ in), Company power, How sure (how much of the ranking rests on a guess), and Richest?, which
43
+ says plainly that the app never sees money. Kingmakers, Gatekeepers, Companies, Industries and
44
+ a scrolling Report are views of their own in the notch. Every number says where it came from;
45
+ a CSV import gets the boards that need no circles, and says so for the rest. On your own network every 2nd-degree row offers Auto beside Ask.
46
+ - **Auto: one press sends someone a connection request for you.** Next to every Connect (a person's
47
+ card, and Outlink's lists and next best moves) there's now an Auto button, the bright one. Press it
48
+ and the scanner's Chrome opens their profile, presses Connect and sends the request without a note,
49
+ then shows "Request sent" and marks it pending everywhere in the app. Connect still opens their
50
+ profile for you to do it yourself. One press is one person: never a batch, never on a timer, and it
51
+ waits while a scan runs. It asks once before the first one. When LinkedIn wants their email address
52
+ first, Auto closes that and sends nothing ("Use Connect to add them yourself"); when LinkedIn offers
53
+ a personal note (Premium), it sends without one. It only calls a request sent once their profile
54
+ shows it pending, and says so plainly when it can't tell. It stops at 15 requests in any 24 hours and
55
+ 80 in any 7 days (LinkedIn doesn't publish its limit; about 100 a week is commonly reported), opens
56
+ their profile once like a scan does, holds during a pause after LinkedIn pushed back, and shows in
57
+ Settings → LinkedIn usage as Connection requests (Auto).
58
+
59
+ ### Changed
60
+ - **A scan that stopped partway says so, on the person's card and on the Scan page.** Open someone
61
+ whose circle scan didn't finish and a line by their name says which page it stopped after, with
62
+ Resume right there; their scan box now reads Scan in progress, with Resume from page N as its main
63
+ button, instead of Cluster active. The card's Insights offer Resume for them too, not a scan that
64
+ reads the list again from page 1, and an empty circle in Bridge Chains whose scan stopped partway
65
+ says so, with Resume from page N, instead of saying their list has been read. On the Scan page, everyone stopped partway is in a new In progress
66
+ card right after step 4, with Resume for each person and Resume all, and each name opens their circle
67
+ on the map. It used to be folded away under Fine-tune the scanner.
68
+ - **The tabs sit on the header's line, and the notch hangs from them.** On a computer the row of
69
+ tabs rests on the line under the header, so a page's own buttons in the notch below join onto
70
+ it. The header is the same height on every tab: the Pending count that added a row on Degrees
71
+ and Separation is gone (sent requests are still under Outlink → Pending).
72
+ - **Separation starts with its slider.** The line counting people two steps away and the tier
73
+ strip under it are gone; the search box stays at the top.
74
+ - **The notch comes with the page.** Switching tabs, it waits for the next page to appear instead
75
+ of hanging over a blank screen while it loads.
76
+
77
+ ### Fixed
78
+ - **A company called "Foods" or "Beverages" is in Retail, Consumer & Hospitality.** The word list
79
+ had "food" and "beverage" but not the plurals, so a company like Northwind Foods took its
80
+ industry from its people's headlines instead (tech, on a test network). Scores are worked out
81
+ again once after the update, since a company's industry can change its estimate and your sector.
82
+ - **Scan dots are whole on Paths.** The dots along the header's line while a scan runs were cut in
83
+ half above Paths' left panel; they also stop short of the tabs now that the tabs sit on the line.
84
+ - **The 3rd-degree dot in Degrees and Separation says why it can't be tapped.** It said "No one at
85
+ 3rd degree yet" even with people found by company scans; it now says those are drawn on Network
86
+ Circle.
87
+ - **The ring round a scanned connection's dot shows how much of their list was read.** It showed 2
88
+ of 5 bars, "partly read", for everyone scanned, even when their whole list was in, and the card's
89
+ Insights offered to finish lists that were finished. The app now takes in how far each list was
90
+ read, which the scanner had been noting all along.
91
+
9
92
  ## [0.7.0] - 2026-10-03
10
93
 
11
94
  ### Added
@@ -10,6 +10,10 @@
10
10
  someone you haven't met. Free, open source, and it runs on your own computer.
11
11
  </p>
12
12
 
13
+ <p align="center">
14
+ <strong>1.0:</strong> complete, signed and notarized by Apple, and maintained. New features come slowly.
15
+ </p>
16
+
13
17
  <p align="center">
14
18
  <a href="https://github.com/blakeb056/six-degrees/releases/latest"><img src="https://img.shields.io/github/v/release/blakeb056/six-degrees?label=release&color=ffd700" alt="Latest release"></a>
15
19
  <a href="https://github.com/blakeb056/six-degrees/releases/latest"><img src="https://img.shields.io/github/release-date/blakeb056/six-degrees?label=released&color=3ee08f" alt="Release date"></a>
@@ -71,21 +75,17 @@ The links above always fetch the newest release.</sub>
71
75
  (Ventura) or later**, and it's about 210 MB. Everything the scanner needs except Google
72
76
  Chrome is inside, Python included, so there's nothing else to install.
73
77
 
74
- **The first time you open it**, macOS stops it, because the app isn't signed with a
75
- paid Apple certificate yet:
76
-
77
78
  1. Open the file you downloaded (**Six-Degrees-Mac-….dmg**, in your Downloads folder).
78
79
  In the window that opens, drag **Six Degrees** onto the **Applications** folder.
79
- 2. Open **Six Degrees** from Applications. macOS says it can't check the app for
80
- malware. Close that message with **Done** (on macOS 13 or 14 it may say **OK** or
81
- **Cancel**). Anything except *Move to Trash*.
82
- 3. Open **System Settings → Privacy & Security**, scroll down to *"Six Degrees" was
83
- blocked*, and click **Open Anyway**. Confirm with **Open Anyway** again (on macOS
84
- 13 or 14 the button says **Open**), and enter your password if asked.
80
+ 2. Open **Six Degrees** from Applications. Signed by Blake Burford and notarized by
81
+ Apple: it opens like any Mac app. The first time, macOS asks whether to open an app
82
+ downloaded from the internet: click **Open**.
85
83
 
86
- That's once per download; after that it opens like any app.
84
+ (A copy of 0.8.0 or earlier wasn't signed yet: if macOS says it can't check it for
85
+ malware, close that message, not *Move to Trash*, then **System Settings → Privacy &
86
+ Security → Open Anyway**. Or update it, below.)
87
87
 
88
- **Or install it from Terminal** (no approval step). Press ⌘ Space, type *Terminal* and
88
+ **Or install it from Terminal.** Press ⌘ Space, type *Terminal* and
89
89
  press Return. Paste this line into the window that opens and press Return:
90
90
 
91
91
  ```bash
@@ -108,7 +108,7 @@ misbehaves later, unzip it and drag the app into Applications to go back. When t
108
108
  can't update itself (for example, it's running from the disk image), it says why and what
109
109
  to do, usually with a Terminal line to paste and what that line will do. Copies of 0.2.1
110
110
  and older don't have the button yet, so they take the Terminal line once more.
111
- Downloading the new `.dmg` works too; you'll repeat step 3 once for it.
111
+ Downloading the new `.dmg` and dragging it to Applications works too.
112
112
 
113
113
  Your network isn't stored in the app. It's in a hidden folder in your home folder,
114
114
  `.six-degrees` (open it any time with *Help → Show the Data Folder*), so updates never
@@ -193,6 +193,16 @@ in, and its most powerful people.
193
193
 
194
194
  <img src="docs/img/paths.png" alt="Paths → Map with the company analyzer open: companies grouped by industry, and one company's people by level and ways in" width="100%">
195
195
 
196
+ **Insights.** Everyone within two steps of you in one table, ranked by power, each person
197
+ once: the **Power Index**, with filters for degree, tier and rarity, and Open circle, Scan
198
+ circle or Ask on every row. Beside it, and each a board of its own in the notch:
199
+ **Kingmakers** (your connections with the most S and A people behind them), **Gatekeepers**
200
+ (who your S and A reach hangs on), **Hidden giants** (S-tier with one way in), **Untapped**
201
+ (S and A with no request out), **Company power** and **Industries**. **Report** is the same
202
+ numbers as a page you scroll. Every number says where it came from, and nothing is about
203
+ money: *Richest?* says plainly that the app never sees it. Power ranks reachability, not
204
+ people.
205
+
196
206
  **Scores.** How a power score is worked out, and the three things you can change about
197
207
  it, in one place: your field (the sectors you work in), how tiers are graded (on your
198
208
  network's curve or the fixed scale), and every scanned company's score.
@@ -202,6 +212,15 @@ circle offers its best five people at a time; mark invites as sent to fill the r
202
212
  unlock the next five. It also shows the next best moves, levels and points (from
203
213
  invites sent and people who accepted, never from browsing).
204
214
 
215
+ **Auto** *(needs a scan)*. Next to every Connect (a person's card, Outlink), Auto sends
216
+ that one person a connection request for you, from the scanner's Chrome, without a note.
217
+ One press, one person: never a batch, never on a timer. It asks once before the first one.
218
+ If LinkedIn wants their email address first, Auto closes that and sends nothing (use
219
+ Connect to add them yourself); if it offers a personal note (Premium), Auto sends without
220
+ one. It calls a request sent only once their profile shows it pending. It stops at 15 in
221
+ any 24 hours and 80 in any 7 days, opens their profile once (a profile view), and counts in
222
+ Settings → LinkedIn usage.
223
+
205
224
  **Scan.** The guided scanner. It shows progress, the budget that's left, the cooldown
206
225
  lock, and a **Paused** list you can carry on from.
207
226
 
@@ -287,6 +306,10 @@ Something out of date, or unfair to another tool? [Open an issue](https://github
287
306
  >
288
307
  > The app asks for an explicit "I understand" once, before the first scan.
289
308
  >
309
+ > **The scanner reads; the one thing it ever sends is a connection request, and only when
310
+ > you press Auto** on a person (one each press, without a note, at most 15 a day and 80 a
311
+ > week). Auto asks once before its first request.
312
+ >
290
313
  > Six Degrees is not affiliated with or endorsed by LinkedIn.
291
314
 
292
315
  ## First run
@@ -299,7 +322,9 @@ The app opens on a welcome screen with three ways in, and asks nothing about you
299
322
  in a Chrome window, and then it scans. Needs Google Chrome. The Mac app brings its own
300
323
  Python; with `npx six-degrees` the page uses yours, or sets up a private one with a
301
324
  click. It checks and says what's missing. The app marks this *Recommended* because
302
- it's the only way to Degrees and Outlink. Read the warning above first.
325
+ it's the only way to Degrees and Outlink. Read the warning above first. On a Mac,
326
+ macOS may ask whether Six Degrees can manage apps while you scan: that's Chrome
327
+ updating itself, and scanning works either way (step 1 has a button to allow it once).
303
328
  - **Import my LinkedIn CSV**: LinkedIn's official export, read on your machine. On
304
329
  LinkedIn: **Settings & Privacy → Data privacy → Get a copy of your data →
305
330
  Connections**. LinkedIn emails a link in about ten minutes; unzip it and drop
@@ -395,7 +420,8 @@ It estimates **network position**, not what anyone is worth as a person.
395
420
  data folder as `csv-network.json` (names, positions, companies, profile links and when
396
421
  you connected; never email addresses) until you click × beside *Your CSV*.
397
422
  - The app contacts only these, and only when you act:
398
- - **LinkedIn**, while you scan. Each profile photo is saved on your computer as the
423
+ - **LinkedIn**, while you scan, and when you press **Auto** (one connection request,
424
+ sent from the scanner's Chrome). Each profile photo is saved on your computer as the
399
425
  scan reads it, and the app shows photos only from there, so looking at your network
400
426
  never contacts LinkedIn. (Photos an older version kept as links to LinkedIn show
401
427
  initials until the next scan, or *Save photos* on the Scan page, saves them.)
@@ -56,8 +56,11 @@ This app is designed to run **on your own machine, against your own network**.
56
56
  and has no link pointing outside itself. The checksums come from the same
57
57
  release, so they catch a corrupted, truncated or swapped download, not a
58
58
  compromised GitHub account; that is the same trust as the Terminal
59
- installer. The app is only signed ad hoc, so the signature proves it is
60
- whole, not who made it. The swap is done by a script shipped inside the app
59
+ installer. Releases from the first signed one on are signed with Blake
60
+ Burford's Developer ID and notarized by Apple (docs/brain/DESKTOP.md D4), but
61
+ the updater checks only that the signature is intact, not who made it (0.8.0
62
+ and earlier were signed ad hoc, and have nobody to compare against), so the
63
+ trust is still the release's. The swap is done by a script shipped inside the app
61
64
  (`scripts/apply-update.sh`), started with a clean environment, and it moves
62
65
  only the app: the data folder is never read, moved or written. It stops only
63
66
  the app's own processes, the ones the server names and those whose
@@ -71,8 +74,10 @@ This app is designed to run **on your own machine, against your own network**.
71
74
  scanner's packages installed from PyPI when the app is built. Those packages
72
75
  are pinned in `scripts/requirements.txt`: an exact version and the SHA-256 of
73
76
  every file pip may install, wheels only, so pip refuses any other file and
74
- nothing is built from source. The Python is signed ad hoc like the rest of the
75
- app and sealed into its signature, which `codesign --verify --deep --strict`
77
+ nothing is built from source. The Python is signed like the rest of the app
78
+ (in releases with the Developer ID and the hardened runtime, each program and
79
+ library on its own; ad hoc in a build without the certificate) and sealed into
80
+ the app's signature, which `codesign --verify --deep --strict`
76
81
  checks (in CI, and before the in-app updater uses a download). It runs
77
82
  isolated from the user's Python settings (`-E -s`: no `PYTHON*` variable and
78
83
  no user site-packages count), so nothing of the user's can stand in for its
@@ -129,6 +134,14 @@ Everything stays local:
129
134
  If you run the scanner, `chrome-profile/` in the data directory holds a live
130
135
  logged-in LinkedIn session. Never copy, sync, or commit it.
131
136
 
137
+ The scanner reads LinkedIn. The one thing it ever sends there is a connection request,
138
+ and only when you press **Auto** on a person: one request per press, without a note, at
139
+ most 15 in any 24 hours and 80 in any 7 days. The app looks the person up by the id the
140
+ page sends and passes the scanner their profile URL from the database, never a URL or name
141
+ from the request, and refuses before anything starts during a cooldown, past those caps,
142
+ or before Auto's one-time question has been answered (`app/api/scraper/route.js`,
143
+ `lib/auto-connect.js`).
144
+
132
145
  ### Moving your data (Settings → Your data)
133
146
 
134
147
  - **An export** is one `.sixdegrees` file the user saves and carries. It is
@@ -1,6 +1,7 @@
1
- import { db as supabase } from '../../../lib/db';
2
1
  import { getDb, decodeRow } from '../../../lib/db-client';
3
- import { resolveProfile } from '../../../lib/profile';
2
+ import {
3
+ XP_SEND, XP_ACCEPT, awardXP, whose, copiesOf, inList, bestTier, markSent,
4
+ } from '../../../lib/requests';
4
5
 
5
6
  // Requests you've sent, kept per PERSON.
6
7
  //
@@ -13,56 +14,8 @@ import { resolveProfile } from '../../../lib/profile';
13
14
  // asked through is kept (so once they accept, their card and circle say who
14
15
  // introduced you: lib/promote.js keeps it), XP comes once per person, and Undo
15
16
  // takes back all of it. lib/requests-client.js is the one way the pages call this.
16
-
17
- // XP rewards by tier
18
- const XP_SEND = { S: 25, A: 15, B: 10, C: 5, D: 2 };
19
- const XP_ACCEPT = { S: 100, A: 60, B: 35, C: 15, D: 5 };
20
- const TIER_ORDER = ['S', 'A', 'B', 'C', 'D'];
21
-
22
- async function awardXP(userId, amount) {
23
- if (!userId || !amount) return;
24
- // Upsert: create if not exists, change if exists. Never below zero: an Undo
25
- // takes back what a send gave, including sends from before sends gave any.
26
- const { data: existing } = await supabase.from('user_stats').select('xp').eq('id', userId).single();
27
- if (existing) {
28
- await supabase.from('user_stats').update({ xp: Math.max(0, (existing.xp || 0) + amount) }).eq('id', userId);
29
- } else if (amount > 0) {
30
- await supabase.from('user_stats').insert([{ id: userId, xp: amount, level: 1 }]).catch(() => {});
31
- }
32
- }
33
-
34
- /** The profile a request belongs to: the one sent, else this machine's. */
35
- function whose(userId) {
36
- if (userId) return String(userId);
37
- try { return resolveProfile({ create: false })?.id || null; } catch { return null; }
38
- }
39
-
40
- /** Every copy of one person: by the clicked row's profile URL, or the URL given. */
41
- function copiesOf(db, { connectionId, profileUrl, userId }) {
42
- let url = profileUrl ? String(profileUrl) : null;
43
- let user = userId ? String(userId) : null;
44
- let clicked = null;
45
- if (connectionId != null) {
46
- clicked = db.prepare('SELECT id, profile_url, user_id FROM linkedin_connections WHERE id = ?').get(String(connectionId)) || null;
47
- if (clicked) {
48
- url = clicked.profile_url || url;
49
- user = user || clicked.user_id || null;
50
- }
51
- }
52
- const cols = 'id, degree, tier, outreach_status, unlock_status, unlocked_from_bridge_id';
53
- let rows = [];
54
- if (url) {
55
- rows = user
56
- ? db.prepare(`SELECT ${cols} FROM linkedin_connections WHERE profile_url = ? AND user_id = ?`).all(url, user)
57
- : db.prepare(`SELECT ${cols} FROM linkedin_connections WHERE profile_url = ?`).all(url);
58
- } else if (clicked) {
59
- rows = db.prepare(`SELECT ${cols} FROM linkedin_connections WHERE id = ?`).all(clicked.id);
60
- }
61
- return { url, user, rows };
62
- }
63
-
64
- const inList = (ids) => ids.map(() => '?').join(', ');
65
- const bestTier = (rows) => TIER_ORDER.find((t) => rows.some((r) => r.tier === t)) || 'D';
17
+ // The marking itself is lib/requests.js markSent, which Auto's sends use too
18
+ // (app/api/scraper/route.js).
66
19
 
67
20
  export async function POST(request) {
68
21
  try {
@@ -72,33 +25,20 @@ export async function POST(request) {
72
25
  return Response.json({ error: 'Unknown action' }, { status: 400 });
73
26
  }
74
27
  const db = getDb();
28
+
29
+ if (action === 'mark-sent') {
30
+ const { saved, xp, already } = await markSent(db, { connectionId, profileUrl, userId, bridgeId });
31
+ // Nobody on file (the sample network lives in the browser): nothing to keep.
32
+ if (!saved) return Response.json({ success: true, saved: 0, xp: 0 });
33
+ return Response.json({ success: true, saved, xp, already });
34
+ }
35
+
75
36
  const { user, rows } = copiesOf(db, { connectionId, profileUrl, userId });
76
37
  // Nobody on file (the sample network lives in the browser): nothing to keep.
77
38
  if (!rows.length) return Response.json({ success: true, saved: 0, xp: 0 });
78
39
  const ids = rows.map((r) => r.id);
79
40
  const now = new Date().toISOString();
80
41
 
81
- if (action === 'mark-sent') {
82
- const already = rows.some((r) => r.outreach_status === 'sent' || r.outreach_status === 'accepted');
83
- db.prepare(`UPDATE linkedin_connections SET outreach_status = 'sent', updated_at = ?
84
- WHERE id IN (${inList(ids)}) AND (outreach_status IS NULL OR outreach_status NOT IN ('sent', 'accepted'))`).run(now, ...ids);
85
- db.prepare(`UPDATE linkedin_connections SET unlock_status = 'pending', updated_at = ?
86
- WHERE id IN (${inList(ids)}) AND (unlock_status IS NULL OR unlock_status = 'locked')`).run(now, ...ids);
87
- // Who you asked through: one of your own connections, or nothing.
88
- if (bridgeId != null) {
89
- const bridge = db.prepare(
90
- `SELECT id, name FROM linkedin_connections WHERE id = ? AND degree = 1${user ? ' AND user_id = ?' : ''}`,
91
- ).get(...[String(bridgeId), ...(user ? [user] : [])]);
92
- if (bridge) {
93
- db.prepare(`UPDATE linkedin_connections SET unlocked_from_bridge_id = ?, unlocked_from_name = ?, updated_at = ?
94
- WHERE id IN (${inList(ids)}) AND degree = 2 AND unlocked_from_bridge_id IS NULL`).run(bridge.id, bridge.name, now, ...ids);
95
- }
96
- }
97
- const xp = already ? 0 : XP_SEND[bestTier(rows)] || 2;
98
- await awardXP(whose(user), xp);
99
- return Response.json({ success: true, saved: ids.length, xp, already });
100
- }
101
-
102
42
  if (action === 'mark-accepted') {
103
43
  db.prepare(`UPDATE linkedin_connections SET outreach_status = 'accepted', updated_at = ? WHERE id IN (${inList(ids)})`).run(now, ...ids);
104
44
  return Response.json({ success: true, saved: ids.length, xp: 0, accepts: XP_ACCEPT[bestTier(rows)] || 5 });