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
@@ -1,6 +1,7 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { existsSync, readFileSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs';
3
3
  import path from 'node:path';
4
+ import { release as osRelease } from 'node:os';
4
5
  import { projectRoot, dataDir } from '../../../lib/paths';
5
6
  import { resolveProfile, networkCounts } from '../../../lib/profile';
6
7
  import { scanProgress, mappingNow } from '../../../lib/scan-progress';
@@ -9,13 +10,16 @@ import { pausedList, readProgress, readUnclear } from '../../../lib/paused';
9
10
  import { getDb, backUpDailyIfDue } from '../../../lib/db-client';
10
11
  import { db as notesDb } from '../../../lib/db';
11
12
  import { scanDoneNotification } from '../../../lib/notifications';
12
- import { registerScanState } from '../../../lib/scan-state';
13
+ import { registerScanState, runningNow } from '../../../lib/scan-state';
13
14
  import { pendingImport } from '../../../lib/data-import';
14
15
  import { waitingPhotoCount } from '../../../lib/photos';
15
16
  import { readSettings } from '../../../lib/settings';
16
17
  import { riskAccepted, touchesLinkedIn, RISK_REFUSAL } from '../../../lib/scan-risk';
17
18
  import { reachIndex, circleState } from '../../../lib/reach';
18
- import { chromeInstalled, signedInFrom, SIGNED_IN_FILE, CHROME_REFUSAL } from '../../../lib/scanner-setup';
19
+ import { chromeInstalled, signedInFrom, macosVersion, SIGNED_IN_FILE, CHROME_REFUSAL } from '../../../lib/scanner-setup';
20
+ import { installKind } from '../../../lib/release';
21
+ import { AUTO_REFUSAL, connectResultIn, connectMarks, inviteRefusal, personRefusal } from '../../../lib/auto-connect';
22
+ import { markSentRows, awardXP, whose, requestStanding } from '../../../lib/requests';
19
23
  import {
20
24
  pythonLooker, thisHostKey, scannerCommand, installSteps, downloadedPython, downloadVerified,
21
25
  placeDownloadedPython, sweepSetupLeftovers, megabytes, SETUP_WORK_PREFIX, ScannerSetupError,
@@ -51,6 +55,8 @@ const state = registerScanState({
51
55
  failure: null, // the last lines of stderr from a run that failed — its reason
52
56
  pages: 0, // pages the running scan has read: the dots along the header's line (app/components/ScanTrail.js)
53
57
  found: [], // how many people each of those pages found: the Scan page's cluster (app/components/ScanRadar.js)
58
+ invitee: null, // Auto's person: { id, name, profileUrl, userId, bridgeId }, looked up here (connectTarget)
59
+ outcome: null, // how Auto's request went: the scanner's "Connect result: …" line (lib/auto-connect.js)
54
60
  });
55
61
 
56
62
  // A page read, as scripts/scrape.py prints it: " Page 3... " as a circle or a
@@ -122,6 +128,9 @@ function push(line) {
122
128
  }
123
129
  const got = t.match(PAGE_FOUND);
124
130
  if (got && state.found.length) state.found[state.found.length - 1] = Number(got[1]);
131
+ // Auto's one line saying how the request went (scrape.py main, --connect).
132
+ const result = state.action === 'connect' && connectResultIn(t);
133
+ if (result) state.outcome = result;
125
134
  }
126
135
  if (state.log.length > MAX_LOG) state.log.splice(0, state.log.length - MAX_LOG);
127
136
  }
@@ -378,6 +387,12 @@ async function status() {
378
387
  signedIn: signedIn(),
379
388
  // The one-time "I understand" before the first scan (lib/scan-risk.js).
380
389
  riskAccepted: scanRisk(),
390
+ // Step 1's App Management item (lib/scanner-setup.js appManagementStep):
391
+ // which macOS, so its button opens the pane where that version keeps it,
392
+ // and whether this is the Mac app, the name macOS asks about. null off a Mac.
393
+ mac: process.platform === 'darwin'
394
+ ? { version: macosVersion(process.platform, osRelease()), app: installKind(process.env, projectRoot()) === 'mac-app' }
395
+ : null,
381
396
  },
382
397
  network,
383
398
  photosWaiting,
@@ -529,8 +544,48 @@ const ACTIONS = {
529
544
  // 1,000 conversations at most), for the Social tab's "Read my whole history".
530
545
  'messages-full': { flag: '--messages --full-history', label: 'Reading your whole messages history' },
531
546
  photos: { flag: '--save-photos', label: 'Saving profile photos to this computer' },
547
+ // Auto (Blake, 2026-10-03): one connection request, without a note, to one
548
+ // person pressed on a card. By id, looked up here (connectTarget); the URL
549
+ // and name that reach the command line come from the database, never the page.
550
+ connect: { flag: '--connect', label: 'Sending a connection request to' },
532
551
  };
533
552
 
553
+ /**
554
+ * Auto's person, by id: { person: { id, name, profileUrl, userId, bridgeId } },
555
+ * or why not: { error, status }. Anyone in your network on this computer with a
556
+ * LinkedIn profile who isn't already one of your connections (no copy of them
557
+ * at degree 1) and has no request out. The URL is checked like every other
558
+ * that reaches a command line.
559
+ */
560
+ function connectTarget(id) {
561
+ try {
562
+ const me = resolveProfile({ create: false });
563
+ const db = getDb();
564
+ const row = me && db.prepare(
565
+ 'SELECT id, name, degree, profile_url, source_connection_id FROM linkedin_connections WHERE id = ? AND user_id = ?',
566
+ ).get(String(id ?? ''), me.id);
567
+ if (!row) return personRefusal(null);
568
+ const why = personRefusal(row, requestStanding(db, { profileUrl: row.profile_url, userId: me.id }));
569
+ if (why) return why;
570
+ const profileUrl = cleanProfileUrl(row.profile_url);
571
+ if (!profileUrl) return { status: 400, error: `${row.name || 'Their'} LinkedIn profile address on file can’t be used.` };
572
+ return {
573
+ person: {
574
+ id: row.id, name: cleanName(row.name) || null, profileUrl, userId: me.id,
575
+ // Who you'd be asking through: the connection whose circle they're in.
576
+ bridgeId: row.degree === 2 ? row.source_connection_id ?? null : null,
577
+ },
578
+ };
579
+ } catch {
580
+ return personRefusal(null);
581
+ }
582
+ }
583
+
584
+ /** Auto's one-time yes (lib/auto-connect.js), given on the page that first offers it. */
585
+ function autoAccepted() {
586
+ try { return Boolean(readSettings(getDb()).autoConnectAccepted); } catch { return false; }
587
+ }
588
+
534
589
  /** Only a plain linkedin.com/in/ profile URL becomes an argument. */
535
590
  function cleanProfileUrl(raw) {
536
591
  const url = String(raw ?? '').trim();
@@ -651,6 +706,15 @@ export async function POST(request) {
651
706
  if (!Object.hasOwn(ACTIONS, action)) {
652
707
  return Response.json({ error: `Unknown action '${action}'` }, { status: 400 });
653
708
  }
709
+ // Auto: who, by id, and whether a request can go to them at all, before
710
+ // anything else is asked. Then its own one-time yes (the page asks it).
711
+ let invitee = null;
712
+ if (action === 'connect') {
713
+ const found = connectTarget(body.id);
714
+ if (found.error) return Response.json({ error: found.error }, { status: found.status });
715
+ invitee = found.person;
716
+ if (!autoAccepted()) return Response.json({ error: AUTO_REFUSAL, needsAutoAcceptance: true }, { status: 409 });
717
+ }
654
718
  // Nothing opens LinkedIn before the one-time "I understand" on the Scan page,
655
719
  // whichever button asked (lib/scan-risk.js).
656
720
  if (touchesLinkedIn(action) && !scanRisk()) {
@@ -707,12 +771,14 @@ export async function POST(request) {
707
771
  // never reaches the command line (a name or the URL looked up for it does),
708
772
  // and anything not shaped like one of our ids is dropped.
709
773
  const hintId = typeof body.id === 'string' && /^[\w-]{1,64}$/.test(body.id) ? body.id : null;
710
- const target = name || profileUrl ? { id: hintId, name: name || person?.name || null } : null;
774
+ const target = invitee ? { id: invitee.id, name: invitee.name }
775
+ : name || profileUrl ? { id: hintId, name: name || person?.name || null } : null;
711
776
  // Nothing that searches LinkedIn starts during a cooldown; the scanner checks
712
777
  // too, this just says so before a process is spawned.
713
778
  // The 1st-degree scans aren't searches, but they open LinkedIn with automation too.
779
+ // Auto's request too: a pause after LinkedIn pushed back holds everything.
714
780
  const searches = spec.searches || action.startsWith('auto-bridge')
715
- || ['bridge', 'rescrape', 'company', 'full', 'refresh'].includes(action);
781
+ || ['bridge', 'rescrape', 'company', 'full', 'refresh', 'connect'].includes(action);
716
782
  // A staged import replaces the network at the next start, so anything scanned
717
783
  // now would land in the copy that is about to be set aside. Setting the
718
784
  // scanner up touches no network data.
@@ -723,7 +789,15 @@ export async function POST(request) {
723
789
  if (searches && cooldown) {
724
790
  return Response.json({ error: `Scanning is paused until ${new Date(cooldown.until).toLocaleString()}: ${cooldown.reason}.`, cooldown }, { status: 409 });
725
791
  }
792
+ // Auto's caps (15 in any 24 hours, 80 in any 7 days) and the profile view it
793
+ // takes, said before a process starts; the scanner checks them again.
794
+ if (invitee) {
795
+ const capped = inviteRefusal(linkedinState(dataDir()));
796
+ if (capped) return Response.json({ error: capped }, { status: 409 });
797
+ }
726
798
  if (state.running) {
799
+ // One thing at a time: Auto waits for a scan like any other job.
800
+ if (invitee) return Response.json({ error: `${runningNow(state.action)}. Press Auto again once it has finished.`, action: state.action }, { status: 409 });
727
801
  return Response.json({ error: 'Something is already running.', action: state.action }, { status: 409 });
728
802
  }
729
803
 
@@ -759,7 +833,8 @@ export async function POST(request) {
759
833
  // "-" can then never be read as a flag of its own. Anyone found by
760
834
  // id is --bridge-url, which carries on where their last read stopped
761
835
  // unless told to start at page 1.
762
- ...(name ? [`${spec.flag}=${name}`] : profileUrl ? [`--bridge-url=${profileUrl}`] : spec.flag.split(' ')),
836
+ ...(invitee ? [`--connect=${invitee.profileUrl}`, ...(invitee.name ? [`--connect-name=${invitee.name}`] : [])]
837
+ : name ? [`${spec.flag}=${name}`] : profileUrl ? [`--bridge-url=${profileUrl}`] : spec.flag.split(' ')),
763
838
  ...(profileUrl && action === 'bridge' && !deeper ? ['--from-start'] : []),
764
839
  ...(maxBridges && (action.startsWith('auto-bridge') || action === 'resume-all') ? [`--max-bridges=${maxBridges}`] : []),
765
840
  ...(tiers.length && action.startsWith('auto-bridge') ? [`--tiers=${tiers.join(',')}`] : []),
@@ -793,6 +868,8 @@ export async function POST(request) {
793
868
  state.found = [];
794
869
  state.stderrTail = [];
795
870
  state.failure = null;
871
+ state.invitee = invitee;
872
+ state.outcome = null;
796
873
  forgetChecks();
797
874
 
798
875
  // Name the profile outright. The scraper would otherwise ask the app which one
@@ -810,7 +887,29 @@ export async function POST(request) {
810
887
  SIX_DEGREES_FROM_APP: '1',
811
888
  };
812
889
 
890
+ /**
891
+ * Auto: a request LinkedIn showed as pending (or one already pending there)
892
+ * is marked as sent through the same store as the Connect button
893
+ * (lib/requests.js), before the job is seen to end, so every view that looks
894
+ * next finds it; its XP follows. Anything else marks nothing (TRAPS §7).
895
+ */
896
+ function markAutoSent() {
897
+ const who = state.invitee;
898
+ if (state.action !== 'connect' || !who || who.marked || !connectMarks(state.outcome)) return;
899
+ who.marked = true;
900
+ try {
901
+ const { saved, xp, user } = markSentRows(getDb(), {
902
+ connectionId: who.id, profileUrl: who.profileUrl, userId: who.userId, bridgeId: who.bridgeId,
903
+ });
904
+ if (saved) Promise.resolve(awardXP(whose(user), xp)).catch(() => {});
905
+ push('Marked as sent in Six Degrees.');
906
+ } catch (err) {
907
+ push(`Sent on LinkedIn, but it couldn’t be marked as sent here: ${err.message}`);
908
+ }
909
+ }
910
+
813
911
  function finish(code) {
912
+ markAutoSent();
814
913
  // A failure must say why. stderr is filtered while running because pip and
815
914
  // Playwright are noisy there, and that filter once swallowed the only line
816
915
  // explaining a failed scan, leaving just "exit 1". On a failure, show the
@@ -834,6 +933,8 @@ export async function POST(request) {
834
933
  state.exitCode = code;
835
934
  state.recent = [{
836
935
  action: state.action, target: state.target, startedAt: state.startedAt, exitCode: code, failure: state.failure,
936
+ // How Auto's request went, for the button that started it (lib/auto-connect.js connectOutcome).
937
+ ...(state.action === 'connect' ? { outcome: state.outcome } : {}),
837
938
  }, ...state.recent].slice(0, 5);
838
939
  // A scan that finished leaves a notification ("Scan done: …"); a stop or a failure doesn't.
839
940
  const done = scanDoneNotification({ action: state.action, target: state.target, exitCode: code, stopped, log: state.log.slice(-12), userId: profileId || null });
@@ -10,6 +10,12 @@
10
10
  // The row is a grid of 1fr | auto | 1fr: the two sides share what's left
11
11
  // equally, so the tabs sit in the middle whatever is beside them. A window too
12
12
  // narrow for that lets the sides shrink to fit before anything overlaps.
13
+ //
14
+ // On a computer the tabs sit on the header's bottom line, so the notch that
15
+ // hangs under the line (ScanStatusBar) hangs from the tabs themselves (Blake,
16
+ // 2026-10-03: "lower the section of buttons right to the line so the notch is
17
+ // touching"). They're moved down, not laid out lower, so the header keeps its
18
+ // height and every page under it stays where it was.
13
19
 
14
20
  import { useEffect, useRef, useState, useSyncExternalStore } from 'react';
15
21
  import Link from 'next/link';
@@ -36,6 +42,11 @@ function useIsMobile() {
36
42
  return m;
37
43
  }
38
44
 
45
+ // The header's padding top and bottom, and the gap under the row: the tabs
46
+ // move down by the last two to reach the line.
47
+ const PAD_Y = 20;
48
+ const ROW_GAP = 8;
49
+
39
50
  const TITLE = { fontWeight: 700, margin: 0, whiteSpace: 'nowrap', background: 'linear-gradient(135deg, #FFD700, #9B59B6, #3498DB)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' };
40
51
 
41
52
  /**
@@ -53,22 +64,27 @@ export default function AppHeader({ active, isMobile: mobileProp, csvMode = fals
53
64
  const mobileSeen = useIsMobile();
54
65
  const isMobile = mobileProp ?? mobileSeen;
55
66
  const own = !IS_DEMO && !csvMode; // scanning and notifications are for your own network
67
+ const tabs = (
68
+ <AppTabs active={active} isMobile={isMobile} csvMode={csvMode} onMode={onMode}
69
+ after={own ? <AutoScanButton isMobile={isMobile} /> : null}>
70
+ {chips}
71
+ </AppTabs>
72
+ );
56
73
  return (
57
74
  <header style={{
58
- position: 'relative', padding: isMobile ? '10px 12px' : '20px 30px', borderBottom: '1px solid rgba(var(--sd-ink, 255, 255, 255), 0.1)', flexShrink: 0,
75
+ position: 'relative', padding: isMobile ? '10px 12px' : `${PAD_Y}px 30px`, borderBottom: '1px solid rgba(var(--sd-ink, 255, 255, 255), 0.1)', flexShrink: 0,
59
76
  // Clear, or frosted glass on a glass theme (lib/themes.js).
60
77
  background: 'var(--sd-header)', backdropFilter: 'var(--sd-header-blur)', WebkitBackdropFilter: 'var(--sd-header-blur)',
61
78
  }}>
62
79
  <div style={isMobile
63
80
  ? { display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 6, marginBottom: 4 }
64
- : { display: 'grid', gridTemplateColumns: '1fr auto 1fr', alignItems: 'center', gap: 16, marginBottom: 8 }}>
81
+ : { display: 'grid', gridTemplateColumns: '1fr auto 1fr', alignItems: 'center', gap: 16, marginBottom: ROW_GAP }}>
65
82
  <Link href="/" style={{ textDecoration: 'none', justifySelf: 'start' }}>
66
83
  <Brand style={{ ...TITLE, fontSize: isMobile ? 16 : 28 }}>Six Degrees</Brand>
67
84
  </Link>
68
- <AppTabs active={active} isMobile={isMobile} csvMode={csvMode} onMode={onMode}
69
- after={own ? <AutoScanButton isMobile={isMobile} /> : null}>
70
- {chips}
71
- </AppTabs>
85
+ {isMobile ? tabs : (
86
+ <div style={{ alignSelf: 'end', position: 'relative', top: ROW_GAP + PAD_Y, zIndex: 2 }}>{tabs}</div>
87
+ )}
72
88
  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', justifySelf: 'end', gap: isMobile ? 4 : 8 }}>
73
89
  {/* Settings: also in CSV or sample mode and on phones, so updates stay reachable */}
74
90
  {!IS_DEMO && <Link href="/settings" title="Settings" aria-label="Settings" style={{
@@ -17,6 +17,10 @@ const MODES = [
17
17
  ];
18
18
  const PAGES = [
19
19
  { key: 'paths', href: '/paths', label: 'Paths', color: 'var(--sd-green, #00ff88)', on: 'linear-gradient(135deg, #00ff88, #3498DB)', demo: true },
20
+ // Your network ranked by power, and the boards that read it (Blake, 2026-10-03:
21
+ // "stats and insights … based off their circles"). Purple to gold, its own colour.
22
+ // The sample and a CSV import have something to rank too, so it isn't `own`.
23
+ { key: 'insights', href: '/insights', label: 'Insights', color: 'var(--sd-purple, #c39bd3)', on: 'linear-gradient(135deg, #9B59B6, #FFD700)' },
20
24
  // Social is part of Outlink now (Messages & follow-ups); /social forwards there.
21
25
  { key: 'outlink', href: '/queue', label: 'Outlink', color: 'var(--sd-orange, #FF6B35)', on: 'linear-gradient(135deg, #FF6B35, #FFD700)', own: true },
22
26
  { key: 'scan', href: '/setup', label: 'Scan', color: 'var(--sd-fg-4, #666)', on: 'linear-gradient(135deg, #00ff88, #1abc9c)', own: true },
@@ -0,0 +1,286 @@
1
+ 'use client';
2
+
3
+ // Auto, next to every Connect: one connection request, sent for you from the
4
+ // scanner's Chrome, without a note.
5
+ //
6
+ // Blake, 2026-10-03: "auto add and basically adds the person for them in the
7
+ // card or wherever its available", then "i just want it next to connect button
8
+ // but say auto and makes it the better pick visually". So Auto comes first and
9
+ // wears the look Connect had (the gold-to-orange gradient), and the page's own
10
+ // Connect, which opens their profile for you to connect yourself, steps back to
11
+ // an outline beside it.
12
+ //
13
+ // One press, one person: it starts the scanner's `connect` job
14
+ // (app/api/scraper/route.js, scripts/scrape.py connect_person) and says how it
15
+ // went. It waits its turn like every Scan button (greyed out, with the reason,
16
+ // while anything else runs). Before the first one ever it asks once, and the
17
+ // answer is a setting (lib/auto-connect.js); the server refuses until it's
18
+ // given. A request LinkedIn showed as pending is marked as sent by the server,
19
+ // through the same store as Connect (lib/requests.js), and shown everywhere
20
+ // here a moment after "Request sent" (lib/requests-client.js sawRequested).
21
+
22
+ import { useEffect, useRef, useState } from 'react';
23
+ import { createPortal } from 'react-dom';
24
+ import useScanner from './useScanner';
25
+ import { beginScrape, busyReason } from '../../lib/scraper-client';
26
+ import { sawRequested } from '../../lib/requests-client';
27
+ import { AUTO_EXPLAIN, AUTO_CONFIRM, INVITE_CAPS, connectOutcome } from '../../lib/auto-connect';
28
+
29
+ export const AUTO_GRADIENT = 'linear-gradient(135deg, #FFD700, #FF6B35)';
30
+
31
+ // How long "Request sent" shows before the card turns to its pending state.
32
+ const SENT_SHOWN_MS = 2500;
33
+
34
+ // Whether the one-time question has been answered yes, asked of the app once per page.
35
+ let acceptedHere = false;
36
+
37
+ async function autoAccepted() {
38
+ if (acceptedHere) return true;
39
+ try {
40
+ const r = await fetch('/api/settings', { cache: 'no-store' });
41
+ const d = await r.json();
42
+ acceptedHere = Boolean(d?.settings?.autoConnectAccepted);
43
+ } catch {
44
+ acceptedHere = false; // can't tell: ask, which is harmless
45
+ }
46
+ return acceptedHere;
47
+ }
48
+
49
+ async function saveAccepted() {
50
+ const r = await fetch('/api/settings', {
51
+ method: 'POST',
52
+ headers: { 'Content-Type': 'application/json' },
53
+ body: JSON.stringify({ settings: { autoConnectAccepted: new Date().toISOString() } }),
54
+ });
55
+ if (!r.ok) throw new Error((await r.json().catch(() => ({}))).error || 'Your answer couldn’t be saved.');
56
+ acceptedHere = true;
57
+ }
58
+
59
+ /** Auto's state for one person: press it, the one-time question, how it went. */
60
+ function useAutoConnect(person, onSent) {
61
+ const scan = useScanner();
62
+ const [asking, setAsking] = useState(false);
63
+ const [sending, setSending] = useState(false);
64
+ const [result, setResult] = useState(null); // { ok, text } once a press has an answer
65
+ const ours = useRef(new Set()); // jobs this button started (by startedAt)
66
+ const shownJob = useRef(null);
67
+
68
+ const mine = scan.running && scan.action === 'connect' && Boolean(person?.id) && scan.target?.id === person.id;
69
+ const busy = scan.running && !mine && !sending ? busyReason(scan) : null;
70
+
71
+ const done = (r) => {
72
+ setResult(r);
73
+ if (r.marks) setTimeout(() => { sawRequested(person); onSent?.(person); }, SENT_SHOWN_MS);
74
+ };
75
+
76
+ // A request to this person started elsewhere (another window, or before a
77
+ // reload) that this page watched end: say how it went here too.
78
+ const last = (scan.finished || []).find((j) => j.action === 'connect' && j.target?.id === person?.id);
79
+ useEffect(() => {
80
+ if (!last || ours.current.has(last.startedAt) || shownJob.current === last.startedAt) return;
81
+ shownJob.current = last.startedAt;
82
+ done(connectOutcome(last));
83
+ // eslint-disable-next-line react-hooks/exhaustive-deps
84
+ }, [last?.startedAt]);
85
+
86
+ async function send() {
87
+ setAsking(false);
88
+ setSending(true);
89
+ setResult(null);
90
+ try {
91
+ const { startedAt, ended } = await beginScrape('connect', { id: person.id, name: person.name });
92
+ ours.current.add(startedAt);
93
+ shownJob.current = startedAt;
94
+ done(connectOutcome(await ended));
95
+ } catch (err) {
96
+ if (err.details?.needsAutoAcceptance) {
97
+ acceptedHere = false;
98
+ setAsking(true);
99
+ } else {
100
+ setResult({ ok: false, text: err.message });
101
+ }
102
+ } finally {
103
+ setSending(false);
104
+ }
105
+ }
106
+
107
+ async function press() {
108
+ if (sending || mine || busy) return;
109
+ setResult(null);
110
+ if (await autoAccepted()) send();
111
+ else setAsking(true);
112
+ }
113
+
114
+ async function confirm() {
115
+ try {
116
+ await saveAccepted();
117
+ send();
118
+ } catch (err) {
119
+ setAsking(false);
120
+ setResult({ ok: false, text: err.message });
121
+ }
122
+ }
123
+
124
+ return {
125
+ press, confirm, cancel: () => setAsking(false), dismiss: () => setResult(null),
126
+ asking, sending: sending || mine, busy, result,
127
+ };
128
+ }
129
+
130
+ /** The question before Auto's first request ever. Into <body>: a blurred panel would trap a fixed overlay (TheirCircle.js). */
131
+ function AutoAsk({ onSend, onCancel }) {
132
+ const sendRef = useRef(null);
133
+ useEffect(() => {
134
+ sendRef.current?.focus();
135
+ const onKey = (e) => { if (e.key === 'Escape') onCancel(); };
136
+ window.addEventListener('keydown', onKey);
137
+ return () => window.removeEventListener('keydown', onKey);
138
+ }, [onCancel]);
139
+ return createPortal(
140
+ <div role="dialog" aria-modal="true" aria-label="Auto sends connection requests" onClick={onCancel} style={{
141
+ position: 'fixed', inset: 0, zIndex: 400, background: 'rgba(var(--sd-shade, 0, 0, 0), 0.72)',
142
+ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 16,
143
+ }}>
144
+ <div data-auto-ask onClick={(e) => e.stopPropagation()} style={{
145
+ width: 'min(420px, 100%)', boxSizing: 'border-box', borderRadius: 14, padding: '20px 20px 16px',
146
+ background: 'var(--sd-surface, #12111c)', border: '1px solid rgba(255,215,0,0.28)',
147
+ boxShadow: '0 18px 60px rgba(0,0,0,0.5)', textAlign: 'left',
148
+ }}>
149
+ <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}>
150
+ <span style={{
151
+ padding: '3px 9px', borderRadius: 6, background: AUTO_GRADIENT, color: '#000', fontWeight: 800, fontSize: 12,
152
+ }}>Auto</span>
153
+ <span style={{ fontSize: 15, fontWeight: 750, color: 'var(--sd-fg-1, #fff)' }}>Before the first one</span>
154
+ </div>
155
+ <p style={{ margin: 0, fontSize: 13.5, lineHeight: 1.6, color: 'var(--sd-fg-2, #d6dbe4)' }}>{AUTO_CONFIRM}</p>
156
+ <p style={{ margin: '10px 0 0', fontSize: 12, lineHeight: 1.55, color: 'var(--sd-fg-3, #8b9a9a)' }}>
157
+ It never adds a note, and stops at {INVITE_CAPS.day} in any 24 hours and {INVITE_CAPS.week} in any 7 days.
158
+ This is asked once.
159
+ </p>
160
+ <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 16 }}>
161
+ <button type="button" onClick={onCancel} style={{
162
+ padding: '9px 16px', borderRadius: 8, fontSize: 13, fontWeight: 650, cursor: 'pointer',
163
+ background: 'transparent', color: 'var(--sd-fg-2, #ccc)', border: '1px solid rgba(var(--sd-ink, 255, 255, 255), 0.18)',
164
+ }}>Cancel</button>
165
+ <button ref={sendRef} type="button" onClick={onSend} style={{
166
+ padding: '9px 18px', borderRadius: 8, fontSize: 13, fontWeight: 800, cursor: 'pointer',
167
+ background: AUTO_GRADIENT, color: '#000', border: 'none',
168
+ }}>Send it</button>
169
+ </div>
170
+ </div>
171
+ </div>,
172
+ document.body,
173
+ );
174
+ }
175
+
176
+ // minWidth: room for "Sending…", so the row doesn't jump when it changes.
177
+ const SIZES = {
178
+ card: { padding: '10px 18px', fontSize: 13, radius: 8, minWidth: 104 },
179
+ quest: { padding: '7px 14px', fontSize: 12.5, radius: 8, minWidth: 92 },
180
+ row: { padding: '4px 9px', fontSize: 9.5, radius: 4, minWidth: 0 },
181
+ // Insights' Power Index rows: the size of the table's other buttons (Open circle, Scan circle).
182
+ table: { padding: '6px 11px', fontSize: 12, radius: 7, minWidth: 0 },
183
+ };
184
+
185
+ /** The Auto button itself: the gradient, the better pick. */
186
+ function AutoButton({ auto, size, label = 'Auto' }) {
187
+ const s = SIZES[size] || SIZES.card;
188
+ // Also while "Request sent" shows, before the card turns to its pending state.
189
+ const off = auto.sending || Boolean(auto.busy) || Boolean(auto.result?.marks);
190
+ const title = auto.busy ? `${auto.busy}. Auto waits for it to finish.` : AUTO_EXPLAIN;
191
+ return (
192
+ <button type="button" data-auto-connect onClick={(e) => { e.stopPropagation(); auto.press(); }} disabled={off} title={title}
193
+ aria-label={auto.sending ? 'Sending the request' : `${label}: ${AUTO_EXPLAIN}`}
194
+ style={{
195
+ padding: s.padding, borderRadius: s.radius, fontSize: s.fontSize, fontWeight: 800, border: 'none', minWidth: s.minWidth,
196
+ background: AUTO_GRADIENT, color: '#000', cursor: off ? 'not-allowed' : 'pointer', opacity: auto.busy ? 0.45 : 1,
197
+ whiteSpace: 'nowrap', flexShrink: 0, boxShadow: off ? 'none' : '0 2px 12px rgba(255,140,50,0.28)',
198
+ }}>
199
+ {auto.sending ? 'Sending…' : label}
200
+ </button>
201
+ );
202
+ }
203
+
204
+ const resultColor = (r) => (r.ok ? 'var(--sd-green, #00ff88)' : 'var(--sd-coral, #ff8a7a)');
205
+
206
+ /**
207
+ * Auto and the page's Connect, side by side: Auto first, as the better pick.
208
+ * `connect(secondary)` draws the page's own Connect, told whether it now stands
209
+ * second (an outline) or alone (as it always looked). Without Auto (`canAuto`
210
+ * false: the sample, a CSV, someone with no profile on file) Connect stands
211
+ * alone. `size`: 'card' (the person card: a line under it says what Auto does),
212
+ * 'quest' (Outlink's move cards), 'table' (Insights' rows) or 'row' (a list row: the line is the
213
+ * button's tooltip, and how it went shows in a small note under the buttons).
214
+ */
215
+ export default function ConnectChoice({ person, canAuto = true, size = 'card', connect, onSent, style }) {
216
+ const available = canAuto && Boolean(person?.id) && Boolean(person?.profile_url);
217
+ if (!available) return connect(false);
218
+ // Keyed by person: a card reused for the next person starts fresh, never
219
+ // showing the last one's result as theirs.
220
+ return <WithAuto key={person.id} person={person} size={size} connect={connect} onSent={onSent} style={style} />;
221
+ }
222
+
223
+ function WithAuto({ person, size, connect, onSent, style }) {
224
+ const auto = useAutoConnect(person, onSent);
225
+ const ask = auto.asking && <AutoAsk onSend={auto.confirm} onCancel={auto.cancel} />;
226
+
227
+ if (size === 'row' || size === 'table') {
228
+ return (
229
+ <span style={{ position: 'relative', display: 'inline-flex', gap: 4, alignItems: 'center', flexShrink: 0, ...style }}
230
+ onClick={(e) => e.stopPropagation()}>
231
+ <AutoButton auto={auto} size={size} />
232
+ {connect(true)}
233
+ {auto.result && (
234
+ <span role="status" style={{
235
+ position: 'absolute', top: 'calc(100% + 4px)', right: 0, zIndex: 20, width: 230, boxSizing: 'border-box',
236
+ padding: '7px 24px 7px 9px', borderRadius: 7, fontSize: 11, lineHeight: 1.45, fontWeight: 600, textAlign: 'left',
237
+ color: resultColor(auto.result), background: 'var(--sd-surface, #14131d)',
238
+ border: `1px solid ${auto.result.ok ? 'rgba(0,255,136,0.35)' : 'rgba(255,138,122,0.4)'}`, boxShadow: '0 8px 24px rgba(0,0,0,0.4)',
239
+ }}>
240
+ {auto.result.text}
241
+ <button type="button" aria-label="Close" onClick={auto.dismiss} style={{
242
+ position: 'absolute', top: 3, right: 4, background: 'none', border: 'none', cursor: 'pointer',
243
+ color: 'var(--sd-fg-3, #888)', fontSize: 12, lineHeight: 1,
244
+ }}>×</button>
245
+ </span>
246
+ )}
247
+ {ask}
248
+ </span>
249
+ );
250
+ }
251
+
252
+ const card = size === 'card';
253
+ return (
254
+ <div style={style}>
255
+ <div style={{ display: 'flex', gap: 8, alignItems: 'stretch', flexWrap: 'wrap' }}>
256
+ <AutoButton auto={auto} size={size} />
257
+ {connect(true)}
258
+ </div>
259
+ {card && (
260
+ <div style={{ fontSize: 11, lineHeight: 1.5, color: 'var(--sd-fg-3, #8b9a9a)', marginTop: 8 }}>
261
+ {auto.busy ? `${auto.busy}. Auto waits for it to finish.` : AUTO_EXPLAIN}
262
+ </div>
263
+ )}
264
+ {auto.result && (
265
+ <div role="status" style={{ fontSize: 12, lineHeight: 1.5, fontWeight: 650, marginTop: 8, color: resultColor(auto.result) }}>
266
+ {auto.result.text}
267
+ </div>
268
+ )}
269
+ {ask}
270
+ </div>
271
+ );
272
+ }
273
+
274
+ /**
275
+ * The page's Connect when it stands second to Auto: the same link, as an
276
+ * outline. `color` and `line` keep a page's own colour (Outlink's blue Add).
277
+ */
278
+ export function secondaryLook(size = 'card', { color = 'var(--sd-gold, #FFD700)', line = 'rgba(255,215,0,0.5)' } = {}) {
279
+ const s = SIZES[size] || SIZES.card;
280
+ return {
281
+ padding: s.padding, borderRadius: s.radius, fontSize: s.fontSize, fontWeight: 700,
282
+ background: 'transparent', color, border: `1px solid ${line}`,
283
+ textDecoration: 'none', cursor: 'pointer', whiteSpace: 'nowrap', boxSizing: 'border-box',
284
+ display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
285
+ };
286
+ }