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
@@ -13,6 +13,9 @@
13
13
  // and the scanner also keeps any two a minute apart. Searches did too, at 373 in
14
14
  // 24 hours: the page asks before a risky search budget (lib/search-risk.js).
15
15
  //
16
+ // Auto's connection requests (lib/auto-connect.js) are a third list in the same
17
+ // record, "invites", with caps of their own: 15 in any 24 hours, 80 in any 7 days.
18
+ //
16
19
  // They belong to the LinkedIn account, not to a profile in the app. TRAPS §35.
17
20
 
18
21
  import {
@@ -22,12 +25,14 @@ import path from 'node:path';
22
25
  import { durable } from './durable.js';
23
26
  import { PACES, DEFAULT_PACE } from './scan-pace.js';
24
27
  import { countAfter } from './usage.js';
28
+ import { INVITE_CAPS } from './auto-connect.js';
25
29
 
26
30
  export const DEFAULT_LIMITS = { daily: 50, monthly: 250, profiles: 50, pace: DEFAULT_PACE };
27
31
  export const DAILY_CHOICES = [25, 50, 100, 200, 500];
28
32
  export const MONTHLY_CHOICES = [100, 250, 500, 1000, 0]; // 0 = no monthly cap (Premium)
29
33
  export const PROFILE_CHOICES = [10, 25, 50, 100]; // profile views a day; never unlimited
30
34
  const DAY = 24 * 3600;
35
+ const WEEK = 7 * DAY;
31
36
  const PACIFIC = 'America/Los_Angeles';
32
37
 
33
38
  function readJson(file) {
@@ -102,17 +107,16 @@ export function writeLimits(dir, { daily, monthly, profiles, pace } = {}) {
102
107
  }
103
108
 
104
109
  /**
105
- * The scanner's record as two lists of times (epoch seconds), or null when it
106
- * can't be read. No record at all is nothing done yet.
110
+ * The scanner's record as three lists of times (epoch seconds): searches,
111
+ * profile views and Auto's connection requests. null when it can't be read. No
112
+ * record at all is nothing done yet.
107
113
  */
108
114
  function readActivity(dir) {
109
115
  const file = path.join(dir, 'linkedin-activity.json');
110
116
  const data = existsSync(file) ? readJson(file) : { searches: [], profiles: [] };
111
117
  if (!data || typeof data !== 'object') return null;
112
- return {
113
- searches: (Array.isArray(data.searches) ? data.searches : []).map(Number),
114
- profiles: (Array.isArray(data.profiles) ? data.profiles : []).map(Number),
115
- };
118
+ const times = (v) => (Array.isArray(v) ? v : []).map(Number);
119
+ return { searches: times(data.searches), profiles: times(data.profiles), invites: times(data.invites) };
116
120
  }
117
121
 
118
122
  /**
@@ -123,14 +127,19 @@ function readActivity(dir) {
123
127
  */
124
128
  export function usage(dir, now = Date.now()) {
125
129
  const rec = readActivity(dir);
126
- if (!rec) return { searchesToday: Infinity, searchesMonth: 0, profilesToday: Infinity, unreadable: true };
127
- const { searches: s, profiles: p } = rec;
130
+ if (!rec) {
131
+ return { searchesToday: Infinity, searchesMonth: 0, profilesToday: Infinity, invitesToday: Infinity, invitesWeek: Infinity, unreadable: true };
132
+ }
133
+ const { searches: s, profiles: p, invites: i } = rec;
128
134
  const dayAgo = now / 1000 - DAY;
129
135
  const month0 = monthStartPacific(now) / 1000;
130
136
  return {
131
137
  searchesToday: s.filter((t) => t > dayAgo).length,
132
138
  searchesMonth: s.filter((t) => t >= month0).length,
133
139
  profilesToday: p.filter((t) => t > dayAgo).length,
140
+ // Auto's connection requests (lib/auto-connect.js), against their own caps.
141
+ invitesToday: i.filter((t) => t > dayAgo).length,
142
+ invitesWeek: i.filter((t) => t > now / 1000 - WEEK).length,
134
143
  };
135
144
  }
136
145
 
@@ -177,12 +186,18 @@ export function linkedinState(dir, now = Date.now()) {
177
186
  const limits = readLimits(dir);
178
187
  const use = usage(dir, now);
179
188
  const left = (limit, used) => (limit ? Math.max(0, limit - used) : null);
189
+ // When Auto may send again once a cap is reached: the request that stops
190
+ // counting soonest frees the next (freesAt below).
191
+ const invites = use.unreadable ? null : readActivity(dir)?.invites || [];
180
192
  return {
181
193
  ...use,
182
194
  limits,
183
195
  leftToday: left(limits.daily, use.searchesToday),
184
196
  leftMonth: left(limits.monthly, use.searchesMonth),
185
197
  profilesLeftToday: left(limits.profiles, use.profilesToday),
198
+ inviteCaps: INVITE_CAPS,
199
+ invitesFreeAt: invites ? freesAt(invites, now, INVITE_CAPS.day) : null,
200
+ invitesWeekFreeAt: invites ? freesAt(invites, now, INVITE_CAPS.week, WEEK) : null,
186
201
  monthResets: nextMonthStartPacific(now),
187
202
  cooldown: readCooldown(dir, now),
188
203
  };
@@ -325,6 +340,9 @@ export function linkedinUsage(dir, now = Date.now()) {
325
340
  now,
326
341
  searchesToday: rec ? state.searchesToday : null,
327
342
  profilesToday: rec ? state.profilesToday : null,
343
+ // Auto's connection requests: the last 24 hours and the last 7 days, against INVITE_CAPS.
344
+ invitesToday: rec ? state.invitesToday : null,
345
+ invitesWeek: rec ? state.invitesWeek : null,
328
346
  searchesMonth: rec ? state.searchesMonth : null,
329
347
  leftMonth: rec ? state.leftMonth : null,
330
348
  searchesLastHour: known((r) => countWithin(r.searches, now, 3600)),
@@ -363,7 +381,7 @@ const isTime = (v) => typeof v === 'number' && Number.isFinite(v) && v >= 0;
363
381
  export function budgetFileProblem(name, data) {
364
382
  if (!isObject(data)) return 'it is not a JSON object';
365
383
  if (name === ACTIVITY) {
366
- if (!isTimeList(data.searches) || !isTimeList(data.profiles)) return 'its record of searches is not a list of times';
384
+ if (!isTimeList(data.searches) || !isTimeList(data.profiles) || !isTimeList(data.invites)) return 'its record of searches is not a list of times';
367
385
  } else if (name === COOLDOWN) {
368
386
  if (!isTime(data.until)) return 'its pause has no end time';
369
387
  if (data.reason !== undefined && typeof data.reason !== 'string') return 'its pause has a reason that is not text';
@@ -415,7 +433,8 @@ const hasFile = (p) => {
415
433
  /**
416
434
  * Merge the budget files in `from` (an import's, already checked with
417
435
  * budgetFileProblem) into the data folder `dir`:
418
- * linkedin-activity.json both computers' searches and profile views (mergeTimes)
436
+ * linkedin-activity.json both computers' searches, profile views and Auto's
437
+ * connection requests (mergeTimes)
419
438
  * linkedin-cooldown.json whichever pause ends later
420
439
  * scan-limits.json this computer's, if it has one; the copy's otherwise.
421
440
  * The limits are a choice someone made here, and an
@@ -437,10 +456,16 @@ export function mergeBudgetFiles({ dir, from }) {
437
456
  const mine = hasFile(here(ACTIVITY)) ? readJson(here(ACTIVITY)) : {};
438
457
  const searches = isObject(mine) ? readableTimes(mine.searches) : null;
439
458
  const profiles = isObject(mine) ? readableTimes(mine.profiles) : null;
440
- if (searches && profiles) {
459
+ const invites = isObject(mine) ? readableTimes(mine.invites) : null;
460
+ if (searches && profiles && invites) {
461
+ // Auto's requests too, or a copy brought in would forget the ones sent here
462
+ // (and Auto's caps would start again from nothing). Written only when
463
+ // either side has any, so a record from before Auto keeps its old layout.
464
+ const sent = mergeTimes(invites, activity.invites);
441
465
  durable.writeJsonDurably(here(ACTIVITY), {
442
466
  searches: mergeTimes(searches, activity.searches),
443
467
  profiles: mergeTimes(profiles, activity.profiles),
468
+ ...(sent.length ? { invites: sent } : {}),
444
469
  });
445
470
  }
446
471
  }
@@ -96,6 +96,16 @@ export async function markRequested(row, { bridgeId } = {}) {
96
96
  }
97
97
  }
98
98
 
99
+ /**
100
+ * A request the app has already saved, shown everywhere at once with nothing
101
+ * posted: Auto's, which the server marks itself once LinkedIn shows it pending
102
+ * (app/api/scraper/route.js, lib/requests.js).
103
+ */
104
+ export function sawRequested(row) {
105
+ mine.set(keyFor(row), true);
106
+ publish();
107
+ }
108
+
99
109
  /** Take a request back, everywhere. */
100
110
  export async function undoRequest(row) {
101
111
  const key = keyFor(row);
@@ -0,0 +1,124 @@
1
+ // Requests you've sent, kept per PERSON: the server side of
2
+ // lib/requests-client.js.
3
+ //
4
+ // A person has one row per bridge whose circle holds them (the table's key is
5
+ // profile + bridge + user), and a request used to mark only the copy that was
6
+ // clicked. The other copies still offered "Add", the Pending count counted
7
+ // copies, and every click awarded XP again. Now a request marks every copy of
8
+ // the person, in both columns, the bridge you asked through is kept (so once
9
+ // they accept, their card and circle say who introduced you: lib/promote.js
10
+ // keeps it), XP comes once per person, and Undo takes back all of it.
11
+ //
12
+ // Two callers: POST /api/outreach (a Connect pressed on a page), and the
13
+ // scanner's route once Auto has sent a request and LinkedIn showed it pending
14
+ // (app/api/scraper/route.js; Blake, 2026-10-03). Both go through markSent here,
15
+ // so a request sent by Auto is the same request everywhere as one sent by hand.
16
+
17
+ import { db as supabase } from './db.js';
18
+ import { resolveProfile } from './profile.js';
19
+
20
+ // XP rewards by tier
21
+ export const XP_SEND = { S: 25, A: 15, B: 10, C: 5, D: 2 };
22
+ export const XP_ACCEPT = { S: 100, A: 60, B: 35, C: 15, D: 5 };
23
+ const TIER_ORDER = ['S', 'A', 'B', 'C', 'D'];
24
+
25
+ export async function awardXP(userId, amount) {
26
+ if (!userId || !amount) return;
27
+ // Upsert: create if not exists, change if exists. Never below zero: an Undo
28
+ // takes back what a send gave, including sends from before sends gave any.
29
+ const { data: existing } = await supabase.from('user_stats').select('xp').eq('id', userId).single();
30
+ if (existing) {
31
+ await supabase.from('user_stats').update({ xp: Math.max(0, (existing.xp || 0) + amount) }).eq('id', userId);
32
+ } else if (amount > 0) {
33
+ await supabase.from('user_stats').insert([{ id: userId, xp: amount, level: 1 }]).catch(() => {});
34
+ }
35
+ }
36
+
37
+ /** The profile a request belongs to: the one sent, else this machine's. */
38
+ export function whose(userId) {
39
+ if (userId) return String(userId);
40
+ try { return resolveProfile({ create: false })?.id || null; } catch { return null; }
41
+ }
42
+
43
+ /** Every copy of one person: by the clicked row's profile URL, or the URL given. */
44
+ export function copiesOf(db, { connectionId, profileUrl, userId }) {
45
+ let url = profileUrl ? String(profileUrl) : null;
46
+ let user = userId ? String(userId) : null;
47
+ let clicked = null;
48
+ if (connectionId != null) {
49
+ clicked = db.prepare('SELECT id, profile_url, user_id FROM linkedin_connections WHERE id = ?').get(String(connectionId)) || null;
50
+ if (clicked) {
51
+ url = clicked.profile_url || url;
52
+ user = user || clicked.user_id || null;
53
+ }
54
+ }
55
+ const cols = 'id, degree, tier, outreach_status, unlock_status, unlocked_from_bridge_id';
56
+ let rows = [];
57
+ if (url) {
58
+ rows = user
59
+ ? db.prepare(`SELECT ${cols} FROM linkedin_connections WHERE profile_url = ? AND user_id = ?`).all(url, user)
60
+ : db.prepare(`SELECT ${cols} FROM linkedin_connections WHERE profile_url = ?`).all(url);
61
+ } else if (clicked) {
62
+ rows = db.prepare(`SELECT ${cols} FROM linkedin_connections WHERE id = ?`).all(clicked.id);
63
+ }
64
+ return { url, user, rows };
65
+ }
66
+
67
+ export const inList = (ids) => ids.map(() => '?').join(', ');
68
+ export const bestTier = (rows) => TIER_ORDER.find((t) => rows.some((r) => r.tier === t)) || 'D';
69
+
70
+ /**
71
+ * Mark a request to this person as sent, on every copy of them, through
72
+ * `bridgeId` (one of your own connections) when given: the rows only, at once.
73
+ * Returns { saved, xp, already, user }: `saved` is how many rows (0 when nobody
74
+ * is on file, as for the sample network, which lives in the browser), `xp` what
75
+ * the send is worth (once per person), for markSent or the caller to award.
76
+ */
77
+ export function markSentRows(db, { connectionId, profileUrl, userId, bridgeId } = {}) {
78
+ const { user, rows } = copiesOf(db, { connectionId, profileUrl, userId });
79
+ if (!rows.length) return { saved: 0, xp: 0, already: false, user };
80
+ const ids = rows.map((r) => r.id);
81
+ const now = new Date().toISOString();
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
+ return { saved: ids.length, xp, already, user };
99
+ }
100
+
101
+ /** markSentRows, and its XP awarded: { saved, xp, already }. */
102
+ export async function markSent(db, request = {}) {
103
+ const { saved, xp, already, user } = markSentRows(db, request);
104
+ if (saved) await awardXP(whose(user), xp);
105
+ return { saved, xp, already };
106
+ }
107
+
108
+ /**
109
+ * Where this person stands for a new request, over every copy of them:
110
+ * { connected, requested, accepted }. `connected`: a copy of them is one of
111
+ * your own connections; `requested`: a request is out (sent or pending);
112
+ * `accepted`: one was accepted (or their path is unlocked).
113
+ */
114
+ export function requestStanding(db, { profileUrl, userId }) {
115
+ if (!profileUrl) return { connected: false, requested: false, accepted: false };
116
+ const rows = userId
117
+ ? db.prepare('SELECT degree, outreach_status, unlock_status FROM linkedin_connections WHERE profile_url = ? AND user_id = ?').all(String(profileUrl), String(userId))
118
+ : db.prepare('SELECT degree, outreach_status, unlock_status FROM linkedin_connections WHERE profile_url = ?').all(String(profileUrl));
119
+ return {
120
+ connected: rows.some((r) => r.degree === 1),
121
+ requested: rows.some((r) => r.outreach_status === 'sent' || r.unlock_status === 'pending'),
122
+ accepted: rows.some((r) => r.outreach_status === 'accepted' || r.unlock_status === 'unlocked'),
123
+ };
124
+ }
@@ -41,6 +41,7 @@ export function runningNow(job) {
41
41
  case 'setup': return 'The scanner is being set up';
42
42
  case 'login': return 'The LinkedIn sign-in window is open';
43
43
  case 'photos': return 'Profile photos are being saved';
44
+ case 'connect': return 'A connection request is being sent';
44
45
  default: return 'A scan is running';
45
46
  }
46
47
  }
@@ -1,8 +1,9 @@
1
1
  // What the Scan page's first step says and offers, from the checks GET
2
2
  // /api/scraper reports (app/api/scraper/route.js, lib/scanner-python.js),
3
- // whether it asks for your field once your connections are in (askForField),
4
- // and two answers the server gives that step: is Google Chrome here
5
- // (chromeInstalled), and are you signed in to LinkedIn (signedInFrom).
3
+ // its optional item on a Mac about macOS's App Management question
4
+ // (appManagementStep), whether it asks for your field once your connections
5
+ // are in (askForField), and two answers the server gives that step: is Google
6
+ // Chrome here (chromeInstalled), and are you signed in to LinkedIn (signedInFrom).
6
7
  // Plain data in, plain data out: the page draws it, and
7
8
  // tests/scanner-setup.test.mjs checks every state without a browser. No Node
8
9
  // imports here; the page runs in the browser.
@@ -178,6 +179,84 @@ function stepOnly(status) {
178
179
  };
179
180
  }
180
181
 
182
+ // ── macOS's App Management question ─────────────────────────────────────────
183
+ // The scanner starts Google Chrome, and Chrome's own updater may then try to
184
+ // update /Applications/Google Chrome.app. macOS puts that down to the app that
185
+ // started Chrome and asks whether Six Degrees may manage apps (System Settings
186
+ // → Privacy & Security → App Management). Saying no is harmless: Chrome updates
187
+ // itself the next time you open it, and scanning works either way. Now that
188
+ // the Mac app is signed, a yes lasts across updates (unsigned copies lost it
189
+ // every update). "shouldnt we have in the onboarding for scan … a button where
190
+ // the user is basically brought to the app management and enables it like
191
+ // Flow does" (Blake, 2026-10-04). So step 1 offers the pane, once, and never
192
+ // waits for it: the app can't read whether it was allowed, and it's optional.
193
+
194
+ // The pane's address, as macOS 26 has it (checked on 26.5.1; macOS 15 knows it
195
+ // too), and as macOS 13 and 14 have it. desktop/lib.mjs SETTINGS_URLS lets
196
+ // exactly these two out of the Mac app; tests/desktop.test.mjs keeps them equal.
197
+ export const APP_MANAGEMENT_URL = 'x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_AppBundles';
198
+ export const APP_MANAGEMENT_URL_OLDER = 'x-apple.systempreferences:com.apple.preference.security?Privacy_AppBundles';
199
+ export const APP_MANAGEMENT_URLS = Object.freeze([APP_MANAGEMENT_URL, APP_MANAGEMENT_URL_OLDER]);
200
+ export const APP_MANAGEMENT_TITLE = 'Let Six Degrees open Chrome without macOS asking';
201
+ export const APP_MANAGEMENT_WHERE = 'System Settings → Privacy & Security → App Management';
202
+
203
+ /** The Scan page's App Management item, once answered: 'done' or 'skipped' (null: not yet). */
204
+ export const APP_MANAGEMENT_SETTING = {
205
+ default: null,
206
+ parse(value) {
207
+ if (value === null || value === 'done' || value === 'skipped') return value;
208
+ throw new Error('The App Management answer is done, skipped or null.');
209
+ },
210
+ };
211
+
212
+ /**
213
+ * macOS's major version from the Darwin kernel's (os.release() on the
214
+ * server): Darwin 25 is macOS 26, when the numbers jumped to the year, and 20
215
+ * to 24 are macOS 11 to 15. null off a Mac, or when it can't be read.
216
+ */
217
+ export function macosVersion(platform, release) {
218
+ if (platform !== 'darwin') return null;
219
+ const darwin = Number.parseInt(String(release ?? ''), 10);
220
+ if (!Number.isInteger(darwin) || darwin < 1) return null;
221
+ if (darwin >= 25) return darwin + 1;
222
+ if (darwin >= 20) return darwin - 9;
223
+ return 10; // Catalina and before were all 10.x
224
+ }
225
+
226
+ /**
227
+ * Step 1's optional item about App Management: { title, text, href, where }
228
+ * to show it, false not to, null while that isn't known yet (the page leaves
229
+ * it out until it is, as with askForField).
230
+ *
231
+ * Only on a Mac (`status.checks.mac`: { version, app }, null elsewhere and
232
+ * from an older server), and from macOS 13, which brought App Management.
233
+ * Never once answered, Done or Skip (`settings.appManagement`), and not when
234
+ * settings couldn't be read, since the answer couldn't be kept either. The
235
+ * button opens the pane for that macOS (an unknown version gets the newest
236
+ * address), and `where` says where it is by hand.
237
+ *
238
+ * In the Mac app macOS asks about Six Degrees. Started with npx, Chrome's
239
+ * starter is the app Six Degrees was started from (Terminal, for example), and
240
+ * that is the one macOS names.
241
+ */
242
+ export function appManagementStep(status, settings) {
243
+ if (!status) return null;
244
+ const mac = status.checks?.mac;
245
+ if (!mac || (mac.version != null && mac.version < 13)) return false;
246
+ if (settings === undefined) return null;
247
+ if (!settings || settings.appManagement) return false;
248
+ const asked = mac.app ? 'Six Degrees' : 'the app you started Six Degrees from (Terminal, for example)';
249
+ const allow = mac.app ? 'Allow Six Degrees' : 'Allow that app';
250
+ return {
251
+ title: APP_MANAGEMENT_TITLE,
252
+ text: `When the scanner starts Google Chrome, macOS may ask whether ${asked} can manage apps. `
253
+ + 'That’s Chrome updating itself while the scanner uses it. '
254
+ + `${allow} once in App Management and macOS won’t ask again. Scanning works either way.`,
255
+ href: mac.version != null && mac.version < 15 ? APP_MANAGEMENT_URL_OLDER : APP_MANAGEMENT_URL,
256
+ where: APP_MANAGEMENT_WHERE,
257
+ };
258
+ }
259
+
181
260
  /**
182
261
  * Whether the Scan page asks for your field (app/components/FieldStep.js):
183
262
  * once, when your own connections are in (step 3) and before who they know is
@@ -39,7 +39,13 @@ export async function startScrape(action, name, extra = {}) {
39
39
  body: JSON.stringify(scanRequest(action, { ...(name ? { name } : {}), ...extra })),
40
40
  });
41
41
  const d = await r.json().catch(() => ({}));
42
- if (!r.ok) throw new Error(d.error || 'Could not start the scanner.');
42
+ if (!r.ok) {
43
+ // The answer's flags travel with the error (needsAutoAcceptance, say), so a
44
+ // button can tell a question to ask from a reason to show.
45
+ const err = new Error(d.error || 'Could not start the scanner.');
46
+ err.details = d;
47
+ throw err;
48
+ }
43
49
  return d;
44
50
  }
45
51
 
@@ -55,12 +61,13 @@ export async function resumePoint(id) {
55
61
  }
56
62
 
57
63
  /** Nothing noted: before the first answer, and for the sample or a CSV. */
58
- export const NO_SCAN_NOTES = Object.freeze({ skips: [], read: [] });
64
+ export const NO_SCAN_NOTES = Object.freeze({ skips: [], read: [], lists: {} });
59
65
 
60
66
  /**
61
67
  * What the scanner noted, for lib/reach.js: `skips`, the hidden lists
62
- * ([{ profileUrl, … }]), and `read`, the profile URLs of every list it has
63
- * read. Never throws: with no answer (the app restarting, say) nothing is
68
+ * ([{ profileUrl, … }]), `read`, the profile URLs of every list it has
69
+ * read, and `lists`, how far each was read ({ url: { pages, more, total } }).
70
+ * Never throws: with no answer (the app restarting, say) nothing is
64
71
  * noted, so a hidden list shows as ready rather than anyone going missing.
65
72
  */
66
73
  export async function loadScanNotes() {
@@ -68,7 +75,11 @@ export async function loadScanNotes() {
68
75
  const r = await fetch('/api/scraper?reach=1');
69
76
  if (!r.ok) return NO_SCAN_NOTES;
70
77
  const d = await r.json();
71
- return { skips: Array.isArray(d.skips) ? d.skips : [], read: Array.isArray(d.read) ? d.read : [] };
78
+ // `lists` is how far each list was read, for the rings round a dot
79
+ // (lib/reach.js scanBars). Dropped here until 10/3, every scanned
80
+ // connection drew 2 of 5 bars, "partly read", even with the whole list in.
81
+ const lists = d.lists && typeof d.lists === 'object' && !Array.isArray(d.lists) ? d.lists : {};
82
+ return { skips: Array.isArray(d.skips) ? d.skips : [], read: Array.isArray(d.read) ? d.read : [], lists };
72
83
  } catch {
73
84
  return NO_SCAN_NOTES;
74
85
  }
@@ -314,6 +325,8 @@ export function busyReason(job = now) {
314
325
  case 'full':
315
326
  case 'refresh':
316
327
  return 'Your own connections are being scanned';
328
+ case 'connect':
329
+ return who ? `A request to ${who} is being sent` : 'A connection request is being sent';
317
330
  default:
318
331
  return runningNow(job.action);
319
332
  }
@@ -14,8 +14,10 @@ import { SECTOR_FOCUS_SETTING, FIELD_ASKED_SETTING } from './sector-focus.js';
14
14
  import { TIER_SCALE_SETTING } from './scoring.js';
15
15
  import { TITLE_RANKING_SETTING } from './title-ranking.js';
16
16
  import { SCAN_RISK_SETTING } from './scan-risk.js';
17
+ import { AUTO_ACCEPTED_SETTING } from './auto-connect.js';
17
18
  import { THEME_SETTING } from './themes.js';
18
19
  import { GALAXY_LAYOUTS_SETTING } from './galaxy-layouts.js';
20
+ import { APP_MANAGEMENT_SETTING } from './scanner-setup.js';
19
21
 
20
22
  export const SETTINGS = {
21
23
  // Scores → Your sector: industries whose companies count for more (lib/sector-focus.js).
@@ -28,6 +30,10 @@ export const SETTINGS = {
28
30
  titleRanking: TITLE_RANKING_SETTING,
29
31
  // The Scan page's "I understand" before the first scan: when it was given (lib/scan-risk.js).
30
32
  scanRiskAccepted: SCAN_RISK_SETTING,
33
+ // The Scan page's optional App Management item on a Mac: Done or Skip, so it's offered once (lib/scanner-setup.js).
34
+ appManagement: APP_MANAGEMENT_SETTING,
35
+ // Auto's one-time question before its first connection request: when it was answered yes (lib/auto-connect.js).
36
+ autoConnectAccepted: AUTO_ACCEPTED_SETTING,
31
37
  // Settings → Appearance: the look (lib/themes.js). Here so it travels with a
32
38
  // backup or a restore; the browser keeps a copy to paint with first (lib/theme-store.js).
33
39
  theme: THEME_SETTING,
@@ -193,3 +193,8 @@ export function hoursText([from, to] = AUTO.hours) {
193
193
  const h = (n) => `${n % 12 || 12} ${n < 12 ? 'AM' : 'PM'}`;
194
194
  return `${h(from)} to ${h(to)}`;
195
195
  }
196
+
197
+ // Auto's connection requests have caps of their own, in the same record
198
+ // (lib/auto-connect.js, scripts/scrape.py INVITE_DAY_CAP and INVITE_WEEK_CAP):
199
+ // the usage page shows them beside the searches and profile views.
200
+ export { INVITE_CAPS, REPORTED_WEEKLY_INVITES } from './auto-connect.js';
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "six-degrees",
3
- "version": "0.7.0",
3
+ "version": "1.0.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "six-degrees",
9
- "version": "0.7.0",
9
+ "version": "1.0.0",
10
10
  "license": "MIT",
11
11
  "os": [
12
12
  "darwin",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "six-degrees",
3
- "version": "0.7.0",
3
+ "version": "1.0.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",