six-degrees 0.2.0 → 0.3.0-beta.1

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 (478) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +10 -0
  3. package/.next/standalone/.next/build-manifest.json +3 -3
  4. package/.next/standalone/.next/prerender-manifest.json +39 -11
  5. package/.next/standalone/.next/required-server-files.json +4 -1
  6. package/.next/standalone/.next/routes-manifest.json +60 -0
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.rsc +11 -11
  18. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +11 -11
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +9 -9
  20. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/api/admin-delete/route.js +3 -2
  22. package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/admin-update/route.js +3 -2
  24. package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/bridges/route.js +3 -2
  26. package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/bulk-import/route.js +3 -2
  28. package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/company-scores/legacy/route/app-paths-manifest.json +3 -0
  30. package/.next/standalone/.next/server/app/api/company-scores/legacy/route/build-manifest.json +12 -0
  31. package/.next/standalone/.next/server/app/api/company-scores/legacy/route/server-reference-manifest.json +4 -0
  32. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js +9 -0
  33. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.map +5 -0
  34. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -0
  35. package/.next/standalone/.next/server/app/api/company-scores/legacy/route_client-reference-manifest.js +3 -0
  36. package/.next/standalone/.next/server/app/api/company-scores/route.js +3 -2
  37. package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/connections/route.js +3 -2
  39. package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/data/export/route/app-paths-manifest.json +3 -0
  41. package/.next/standalone/.next/server/app/api/data/export/route/build-manifest.json +12 -0
  42. package/.next/standalone/.next/server/app/api/data/export/route/server-reference-manifest.json +4 -0
  43. package/.next/standalone/.next/server/app/api/data/export/route.js +7 -0
  44. package/.next/standalone/.next/server/app/api/data/export/route.js.map +5 -0
  45. package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -0
  46. package/.next/standalone/.next/server/app/api/data/export/route_client-reference-manifest.js +3 -0
  47. package/.next/standalone/.next/server/app/api/data/import/route/app-paths-manifest.json +3 -0
  48. package/.next/standalone/.next/server/app/api/data/import/route/build-manifest.json +12 -0
  49. package/.next/standalone/.next/server/app/api/data/import/route/server-reference-manifest.json +4 -0
  50. package/.next/standalone/.next/server/app/api/data/import/route.js +8 -0
  51. package/.next/standalone/.next/server/app/api/data/import/route.js.map +5 -0
  52. package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -0
  53. package/.next/standalone/.next/server/app/api/data/import/route_client-reference-manifest.js +3 -0
  54. package/.next/standalone/.next/server/app/api/data/restart/route/app-paths-manifest.json +3 -0
  55. package/.next/standalone/.next/server/app/api/data/restart/route/build-manifest.json +12 -0
  56. package/.next/standalone/.next/server/app/api/data/restart/route/server-reference-manifest.json +4 -0
  57. package/.next/standalone/.next/server/app/api/data/restart/route.js +8 -0
  58. package/.next/standalone/.next/server/app/api/data/restart/route.js.map +5 -0
  59. package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -0
  60. package/.next/standalone/.next/server/app/api/data/restart/route_client-reference-manifest.js +3 -0
  61. package/.next/standalone/.next/server/app/api/data/reveal/route/app-paths-manifest.json +3 -0
  62. package/.next/standalone/.next/server/app/api/data/reveal/route/build-manifest.json +12 -0
  63. package/.next/standalone/.next/server/app/api/data/reveal/route/server-reference-manifest.json +4 -0
  64. package/.next/standalone/.next/server/app/api/data/reveal/route.js +7 -0
  65. package/.next/standalone/.next/server/app/api/data/reveal/route.js.map +5 -0
  66. package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -0
  67. package/.next/standalone/.next/server/app/api/data/reveal/route_client-reference-manifest.js +3 -0
  68. package/.next/standalone/.next/server/app/api/data/route/app-paths-manifest.json +3 -0
  69. package/.next/standalone/.next/server/app/api/data/route/build-manifest.json +12 -0
  70. package/.next/standalone/.next/server/app/api/data/route/server-reference-manifest.json +4 -0
  71. package/.next/standalone/.next/server/app/api/data/route.js +7 -0
  72. package/.next/standalone/.next/server/app/api/data/route.js.map +5 -0
  73. package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -0
  74. package/.next/standalone/.next/server/app/api/data/route_client-reference-manifest.js +3 -0
  75. package/.next/standalone/.next/server/app/api/delete-cluster/route.js +3 -2
  76. package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
  77. package/.next/standalone/.next/server/app/api/ingest/route.js +2 -2
  78. package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
  79. package/.next/standalone/.next/server/app/api/network/route.js +3 -2
  80. package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
  81. package/.next/standalone/.next/server/app/api/notifications/route.js +3 -2
  82. package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  83. package/.next/standalone/.next/server/app/api/outreach/route.js +3 -2
  84. package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
  85. package/.next/standalone/.next/server/app/api/queue/route.js +3 -2
  86. package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
  87. package/.next/standalone/.next/server/app/api/scraper/route.js +3 -2
  88. package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
  89. package/.next/standalone/.next/server/app/api/settings/route/app-paths-manifest.json +3 -0
  90. package/.next/standalone/.next/server/app/api/settings/route/build-manifest.json +12 -0
  91. package/.next/standalone/.next/server/app/api/settings/route/server-reference-manifest.json +4 -0
  92. package/.next/standalone/.next/server/app/api/settings/route.js +9 -0
  93. package/.next/standalone/.next/server/app/api/settings/route.js.map +5 -0
  94. package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -0
  95. package/.next/standalone/.next/server/app/api/settings/route_client-reference-manifest.js +3 -0
  96. package/.next/standalone/.next/server/app/api/settings/sector-preview/route/app-paths-manifest.json +3 -0
  97. package/.next/standalone/.next/server/app/api/settings/sector-preview/route/build-manifest.json +12 -0
  98. package/.next/standalone/.next/server/app/api/settings/sector-preview/route/server-reference-manifest.json +4 -0
  99. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js +8 -0
  100. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.map +5 -0
  101. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -0
  102. package/.next/standalone/.next/server/app/api/settings/sector-preview/route_client-reference-manifest.js +3 -0
  103. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route/app-paths-manifest.json +3 -0
  104. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route/build-manifest.json +12 -0
  105. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route/server-reference-manifest.json +4 -0
  106. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js +8 -0
  107. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.map +5 -0
  108. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -0
  109. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route_client-reference-manifest.js +3 -0
  110. package/.next/standalone/.next/server/app/api/setup-profile/route.js +3 -2
  111. package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
  112. package/.next/standalone/.next/server/app/api/unlock/route.js +3 -2
  113. package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
  114. package/.next/standalone/.next/server/app/api/update/route.js +1 -1
  115. package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  116. package/.next/standalone/.next/server/app/api/update-images/route.js +3 -2
  117. package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
  118. package/.next/standalone/.next/server/app/api/users/route.js +3 -2
  119. package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
  120. package/.next/standalone/.next/server/app/avatars/[file]/route.js +2 -1
  121. package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
  122. package/.next/standalone/.next/server/app/favicon.ico/route.js +2 -1
  123. package/.next/standalone/.next/server/app/favicon.ico/route.js.nft.json +1 -1
  124. package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
  125. package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
  126. package/.next/standalone/.next/server/app/import.html +1 -1
  127. package/.next/standalone/.next/server/app/import.rsc +13 -13
  128. package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +13 -13
  129. package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +1 -1
  130. package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +11 -11
  131. package/.next/standalone/.next/server/app/index.html +1 -1
  132. package/.next/standalone/.next/server/app/index.rsc +13 -13
  133. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +11 -11
  134. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +13 -13
  135. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  136. package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
  137. package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
  138. package/.next/standalone/.next/server/app/launch.html +2 -2
  139. package/.next/standalone/.next/server/app/launch.rsc +13 -13
  140. package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +13 -13
  141. package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +1 -1
  142. package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +11 -11
  143. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  144. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  145. package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
  146. package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
  147. package/.next/standalone/.next/server/app/paths.html +1 -1
  148. package/.next/standalone/.next/server/app/paths.rsc +13 -13
  149. package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +13 -13
  150. package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +1 -1
  151. package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +11 -11
  152. package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
  153. package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
  154. package/.next/standalone/.next/server/app/profile.html +1 -1
  155. package/.next/standalone/.next/server/app/profile.rsc +13 -13
  156. package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +13 -13
  157. package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +1 -1
  158. package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +11 -11
  159. package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
  160. package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
  161. package/.next/standalone/.next/server/app/queue.html +1 -1
  162. package/.next/standalone/.next/server/app/queue.rsc +13 -13
  163. package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +13 -13
  164. package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +1 -1
  165. package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +11 -11
  166. package/.next/standalone/.next/server/app/settings/page/app-paths-manifest.json +3 -0
  167. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +20 -0
  168. package/.next/standalone/.next/server/app/settings/page/next-font-manifest.json +11 -0
  169. package/.next/standalone/.next/server/app/settings/page/react-loadable-manifest.json +1 -0
  170. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -0
  171. package/.next/standalone/.next/server/app/settings/page.js +13 -0
  172. package/.next/standalone/.next/server/app/settings/page.js.map +5 -0
  173. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -0
  174. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +3 -0
  175. package/.next/standalone/.next/server/app/settings.html +1 -0
  176. package/.next/standalone/.next/server/app/settings.meta +26 -0
  177. package/.next/standalone/.next/server/app/settings.rsc +24 -0
  178. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +24 -0
  179. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +4 -0
  180. package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +28 -0
  181. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  182. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  183. package/.next/standalone/.next/server/app/setup.html +1 -1
  184. package/.next/standalone/.next/server/app/setup.rsc +13 -13
  185. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +13 -13
  186. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +1 -1
  187. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +11 -11
  188. package/.next/standalone/.next/server/app-paths-manifest.json +10 -0
  189. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-rnla2._.js +3 -0
  190. package/.next/standalone/.next/server/chunks/[root-of-the-server]__00la_y0._.js +3 -0
  191. package/.next/standalone/.next/server/chunks/[root-of-the-server]__07t47cm._.js +3 -0
  192. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cj5jqc._.js +3 -0
  193. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e0fei3._.js +3 -0
  194. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0i5hdu4._.js +3 -0
  195. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljcioj._.js +3 -0
  196. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljnyho._.js +3 -0
  197. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o5-tcg._.js +3 -0
  198. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qrnous._.js +3 -0
  199. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +3 -0
  200. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0uag-k5._.js +8 -0
  201. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0um1uey._.js +3 -0
  202. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wmexuc._.js +3 -0
  203. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xu34sz._.js +3 -0
  204. package/.next/standalone/.next/server/chunks/[root-of-the-server]__133knx1._.js +3 -0
  205. package/.next/standalone/.next/server/chunks/[root-of-the-server]__14ou7j5._.js +3 -0
  206. package/.next/standalone/.next/server/chunks/[root-of-the-server]__15-17hl._.js +3 -0
  207. package/.next/standalone/.next/server/chunks/[root-of-the-server]__16iu0hv._.js +3 -0
  208. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19b7mig._.js +3 -0
  209. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bbcwub._.js +8 -0
  210. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1eris29._.js +3 -0
  211. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1gjo_cq._.js +3 -0
  212. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +3 -0
  213. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qcm08_._.js +3 -0
  214. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rphsdk._.js +3 -0
  215. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1t3rugi._.js +3 -0
  216. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1w25q0m._.js +3 -0
  217. package/.next/standalone/.next/server/chunks/_072jl0y._.js +3 -0
  218. package/.next/standalone/.next/server/chunks/_0lw8_i0._.js +3 -0
  219. package/.next/standalone/.next/server/chunks/_1_fynia._.js +21 -3
  220. package/.next/standalone/.next/server/chunks/_1fn30_l._.js +3 -0
  221. package/.next/standalone/.next/server/chunks/_1itd1o_._.js +2 -1
  222. package/.next/standalone/.next/server/chunks/_1iwkh8t._.js +1 -1
  223. package/.next/standalone/.next/server/chunks/_1mxn_lb._.js +3 -0
  224. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_company-scores_legacy_route_actions_1ncspu_.js +3 -0
  225. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_export_route_actions_1lkimty.js +3 -0
  226. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_import_route_actions_1eji0kd.js +3 -0
  227. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_restart_route_actions_00_-u6o.js +3 -0
  228. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_reveal_route_actions_0-moqtn.js +3 -0
  229. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_route_actions_0j0w5ep.js +3 -0
  230. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_settings_route_actions_1bpm9ac.js +3 -0
  231. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_settings_sector-preview_route_actions_0w79454.js +3 -0
  232. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_settings_sector-suggestions_route_actions_1eoqugp.js +3 -0
  233. package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +155 -0
  234. package/.next/standalone/.next/server/chunks/{lib_rpc_0mc73lk.js → lib_rpc_00tx2sm.js} +1 -1
  235. package/.next/standalone/.next/server/chunks/lib_rpc_02-qa4d.js +3 -0
  236. package/.next/standalone/.next/server/chunks/lib_rpc_02ndsg2.js +3 -0
  237. package/.next/standalone/.next/server/chunks/lib_rpc_08q5vp6.js +3 -0
  238. package/.next/standalone/.next/server/chunks/lib_rpc_0_8i3ft.js +3 -0
  239. package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +5 -2
  240. package/.next/standalone/.next/server/chunks/lib_rpc_0huvw-3.js +3 -0
  241. package/.next/standalone/.next/server/chunks/lib_rpc_0ip2vdh.js +3 -0
  242. package/.next/standalone/.next/server/chunks/lib_rpc_0jzisgh.js +3 -0
  243. package/.next/standalone/.next/server/chunks/lib_rpc_0yi9xvd.js +3 -0
  244. package/.next/standalone/.next/server/chunks/lib_rpc_1jyhd0o.js +3 -0
  245. package/.next/standalone/.next/server/chunks/lib_rpc_1kn59-i.js +3 -0
  246. package/.next/standalone/.next/server/chunks/lib_rpc_1q2jkza.js +3 -0
  247. package/.next/standalone/.next/server/chunks/lib_rpc_1vpm1sk.js +3 -0
  248. package/.next/standalone/.next/server/chunks/lib_rpc_1x6p_31.js +3 -0
  249. package/.next/standalone/.next/server/chunks/node_modules_next_1zc5q0a._.js +158 -0
  250. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0mocm6d._.js → [root-of-the-server]__0an6blh._.js} +3 -3
  251. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h1_f_2._.js +1 -1
  252. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m0255u._.js +3 -0
  253. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0bipr5s._.js → [root-of-the-server]__1z1j3ek._.js} +2 -2
  254. package/.next/standalone/.next/server/chunks/ssr/_07u8byv._.js +3 -0
  255. package/.next/standalone/.next/server/chunks/ssr/_08r_mlf._.js +3 -0
  256. package/.next/standalone/.next/server/chunks/ssr/{_0ggabjc._.js → _0942v8n._.js} +1 -1
  257. package/.next/standalone/.next/server/chunks/ssr/{_1djpk8t._.js → _0hl_t7r._.js} +2 -2
  258. package/.next/standalone/.next/server/chunks/ssr/_0tb83dw._.js +132 -0
  259. package/.next/standalone/.next/server/chunks/ssr/_1-87bs1._.js +3 -0
  260. package/.next/standalone/.next/server/chunks/ssr/_17gui6a._.js +1 -1
  261. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_settings_page_actions_11x4dq5.js +3 -0
  262. package/.next/standalone/.next/server/chunks/ssr/app_import_page_1fmu6_l.js +1 -1
  263. package/.next/standalone/.next/server/chunks/ssr/app_launch_page_19t0dyd.js +2 -2
  264. package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +4 -4
  265. package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
  266. package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +5 -5
  267. package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +3 -0
  268. package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +2 -2
  269. package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +1 -1
  270. package/.next/standalone/.next/server/chunks/ssr/node_modules_1f8t_rp._.js +23 -0
  271. package/.next/standalone/.next/server/chunks/ssr/node_modules_1nkgb45._.js +2 -2
  272. package/.next/standalone/.next/server/chunks/ssr/{node_modules_01xbj_9._.js → node_modules_1wax83z._.js} +3 -3
  273. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_00nap8h._.js → node_modules_next_dist_1bunoni._.js} +2 -2
  274. package/.next/standalone/.next/server/edge/chunks/[root-of-the-server]__0pco7a6._.js +2 -2
  275. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  276. package/.next/standalone/.next/server/middleware-manifest.json +15 -7
  277. package/.next/standalone/.next/server/next-font-manifest.js +1 -1
  278. package/.next/standalone/.next/server/next-font-manifest.json +4 -0
  279. package/.next/standalone/.next/server/pages/404.html +1 -1
  280. package/.next/standalone/.next/server/pages/500.html +1 -1
  281. package/.next/standalone/.next/server/prefetch-hints.json +14 -0
  282. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  283. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  284. package/.next/standalone/.next/static/chunks/06x3jiiz96m5t.js +1 -0
  285. package/.next/{static/chunks/34at5d2ifl62i.js → standalone/.next/static/chunks/0ngx9s6-zgshe.js} +5 -5
  286. package/.next/standalone/.next/static/chunks/0q-_khndfxubv.js +2 -0
  287. package/.next/standalone/.next/static/chunks/1c79veqedvubt.js +1 -0
  288. package/.next/standalone/.next/static/chunks/1chp3nx45meg5.js +44 -0
  289. package/.next/standalone/.next/static/chunks/1irqj0lf-fu3d.js +1 -0
  290. package/.next/standalone/.next/static/chunks/1uadqylrod5_v.js +1 -0
  291. package/.next/standalone/.next/static/chunks/23u0zclgwpc4d.js +1 -0
  292. package/.next/standalone/.next/static/chunks/289ktj97di6zg.js +1 -0
  293. package/.next/standalone/.next/static/chunks/2b3lmf8e6uj8m.js +16 -0
  294. package/.next/standalone/.next/static/chunks/2jz311xl1pp_8.js +1 -0
  295. package/.next/standalone/.next/static/chunks/2ot61qsv6_0eq.js +1 -0
  296. package/.next/standalone/.next/static/chunks/3npq5yd7c4dmn.js +2 -0
  297. package/.next/standalone/.next/static/frfHFnRYTA0Wjrw8oZJtY/_clientMiddlewareManifest.js +14 -0
  298. package/.next/standalone/AGENTS.md +10 -0
  299. package/.next/standalone/CHANGELOG.md +325 -0
  300. package/.next/standalone/CONTRIBUTING.md +14 -2
  301. package/.next/standalone/README.md +181 -53
  302. package/.next/standalone/SECURITY.md +120 -10
  303. package/.next/standalone/app/api/company-scores/legacy/route.js +32 -0
  304. package/.next/standalone/app/api/company-scores/route.js +20 -11
  305. package/.next/standalone/app/api/data/export/route.js +49 -0
  306. package/.next/standalone/app/api/data/import/route.js +83 -0
  307. package/.next/standalone/app/api/data/restart/route.js +37 -0
  308. package/.next/standalone/app/api/data/reveal/route.js +14 -0
  309. package/.next/standalone/app/api/data/route.js +39 -0
  310. package/.next/standalone/app/api/ingest/route.js +15 -47
  311. package/.next/standalone/app/api/scraper/route.js +268 -128
  312. package/.next/standalone/app/api/settings/route.js +58 -0
  313. package/.next/standalone/app/api/settings/sector-preview/route.js +38 -0
  314. package/.next/standalone/app/api/settings/sector-suggestions/route.js +20 -0
  315. package/.next/standalone/app/api/update/route.js +197 -24
  316. package/.next/standalone/app/avatars/[file]/route.js +4 -2
  317. package/.next/standalone/app/components/CompanyScores.js +28 -7
  318. package/.next/standalone/app/components/EmptyState.js +8 -0
  319. package/.next/standalone/app/components/ForceGraph.js +40 -36
  320. package/.next/standalone/app/components/LegacyScoresCard.js +129 -0
  321. package/.next/standalone/app/components/Sidebar.js +19 -18
  322. package/.next/standalone/app/components/UpdatePanel.js +317 -99
  323. package/.next/standalone/app/components/UserProvider.js +4 -3
  324. package/.next/standalone/app/components/settings/DataSection.js +437 -0
  325. package/.next/standalone/app/components/settings/SectorSection.js +445 -0
  326. package/.next/standalone/app/components/ui.js +62 -0
  327. package/.next/standalone/app/import/page.js +3 -3
  328. package/.next/standalone/app/launch/page.js +30 -20
  329. package/.next/standalone/app/layout.js +2 -2
  330. package/.next/standalone/app/page.js +15 -2
  331. package/.next/standalone/app/paths/page.js +13 -5
  332. package/.next/standalone/app/profile/page.js +41 -186
  333. package/.next/standalone/app/queue/page.js +8 -2
  334. package/.next/standalone/app/settings/page.js +96 -0
  335. package/.next/standalone/app/setup/page.js +35 -19
  336. package/.next/standalone/bin/args.mjs +44 -0
  337. package/.next/standalone/bin/six-degrees.mjs +16 -17
  338. package/.next/standalone/install.sh +1 -1
  339. package/.next/standalone/lib/companies.js +44 -119
  340. package/.next/standalone/lib/csv.js +61 -27
  341. package/.next/standalone/lib/data-export.js +170 -0
  342. package/.next/standalone/lib/data-folder.js +238 -0
  343. package/.next/standalone/lib/data-import.js +878 -0
  344. package/.next/standalone/lib/db-client.js +48 -3
  345. package/.next/standalone/lib/durable.js +73 -0
  346. package/.next/standalone/lib/gate.js +90 -1
  347. package/.next/standalone/lib/ingest.js +40 -0
  348. package/.next/standalone/lib/legacy-offer.js +185 -0
  349. package/.next/standalone/lib/legacy-scores.js +77 -0
  350. package/.next/standalone/lib/linkedin-limits.js +124 -1
  351. package/.next/standalone/lib/release.js +5 -2
  352. package/.next/standalone/lib/rpc.js +121 -13
  353. package/.next/standalone/lib/sample-stats.js +16 -0
  354. package/.next/standalone/lib/scan-progress.js +13 -0
  355. package/.next/standalone/lib/scan-state.js +53 -0
  356. package/.next/standalone/lib/scanner-python.js +685 -0
  357. package/.next/standalone/lib/scanner-setup.js +104 -0
  358. package/.next/standalone/lib/scoring.js +747 -138
  359. package/.next/standalone/lib/scraper-client.js +10 -6
  360. package/.next/standalone/lib/sector-directory.js +1532 -0
  361. package/.next/standalone/lib/sector-focus.js +163 -0
  362. package/.next/standalone/lib/sector-labels.js +81 -0
  363. package/.next/standalone/lib/settings-effects.js +59 -0
  364. package/.next/standalone/lib/settings.js +83 -0
  365. package/.next/standalone/lib/updater-job.js +838 -0
  366. package/.next/standalone/lib/updater.js +494 -0
  367. package/.next/standalone/middleware.js +25 -28
  368. package/.next/standalone/next.config.mjs +19 -3
  369. package/.next/standalone/package-lock.json +103 -9
  370. package/.next/standalone/package.json +12 -11
  371. package/.next/standalone/public/demo-data.json +1 -1
  372. package/.next/standalone/scripts/apply-update.sh +482 -0
  373. package/.next/standalone/scripts/build-app.mjs +251 -9
  374. package/.next/standalone/scripts/gen-synthetic.mjs +16 -1
  375. package/.next/standalone/scripts/readme_buttons.py +89 -0
  376. package/.next/standalone/scripts/requirements.txt +106 -4
  377. package/.next/standalone/scripts/screenshots.py +2 -0
  378. package/.next/standalone/scripts/test-release-server.mjs +143 -0
  379. package/.next/standalone/server.js +1 -1
  380. package/.next/static/chunks/06x3jiiz96m5t.js +1 -0
  381. package/.next/{standalone/.next/static/chunks/34at5d2ifl62i.js → static/chunks/0ngx9s6-zgshe.js} +5 -5
  382. package/.next/static/chunks/0q-_khndfxubv.js +2 -0
  383. package/.next/static/chunks/1c79veqedvubt.js +1 -0
  384. package/.next/static/chunks/1chp3nx45meg5.js +44 -0
  385. package/.next/static/chunks/1irqj0lf-fu3d.js +1 -0
  386. package/.next/static/chunks/1uadqylrod5_v.js +1 -0
  387. package/.next/static/chunks/23u0zclgwpc4d.js +1 -0
  388. package/.next/static/chunks/289ktj97di6zg.js +1 -0
  389. package/.next/static/chunks/2b3lmf8e6uj8m.js +16 -0
  390. package/.next/static/chunks/2jz311xl1pp_8.js +1 -0
  391. package/.next/static/chunks/2ot61qsv6_0eq.js +1 -0
  392. package/.next/static/chunks/3npq5yd7c4dmn.js +2 -0
  393. package/.next/static/frfHFnRYTA0Wjrw8oZJtY/_clientMiddlewareManifest.js +14 -0
  394. package/CHANGELOG.md +325 -0
  395. package/README.md +181 -53
  396. package/SECURITY.md +120 -10
  397. package/bin/args.mjs +44 -0
  398. package/bin/six-degrees.mjs +16 -17
  399. package/lib/companies.js +44 -119
  400. package/lib/csv.js +61 -27
  401. package/lib/data-export.js +170 -0
  402. package/lib/data-folder.js +238 -0
  403. package/lib/data-import.js +878 -0
  404. package/lib/db-client.js +48 -3
  405. package/lib/durable.js +73 -0
  406. package/lib/gate.js +90 -1
  407. package/lib/ingest.js +40 -0
  408. package/lib/legacy-offer.js +185 -0
  409. package/lib/legacy-scores.js +77 -0
  410. package/lib/linkedin-limits.js +124 -1
  411. package/lib/release.js +5 -2
  412. package/lib/rpc.js +121 -13
  413. package/lib/sample-stats.js +16 -0
  414. package/lib/scan-progress.js +13 -0
  415. package/lib/scan-state.js +53 -0
  416. package/lib/scanner-python.js +685 -0
  417. package/lib/scanner-setup.js +104 -0
  418. package/lib/scoring.js +747 -138
  419. package/lib/scraper-client.js +10 -6
  420. package/lib/sector-directory.js +1532 -0
  421. package/lib/sector-focus.js +163 -0
  422. package/lib/sector-labels.js +81 -0
  423. package/lib/settings-effects.js +59 -0
  424. package/lib/settings.js +83 -0
  425. package/lib/updater-job.js +838 -0
  426. package/lib/updater.js +494 -0
  427. package/package.json +12 -11
  428. package/public/demo-data.json +1 -1
  429. package/scripts/requirements.txt +106 -4
  430. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jrmmj-._.js +0 -150
  431. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qx548g._.js +0 -151
  432. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1an3leo._.js +0 -150
  433. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1m8zedk._.js +0 -155
  434. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1zor0ys._.js +0 -3
  435. package/.next/standalone/.next/server/chunks/_0_rp-01._.js +0 -3
  436. package/.next/standalone/.next/server/chunks/_0hzdk79._.js +0 -3
  437. package/.next/standalone/.next/server/chunks/_0ow7u5r._.js +0 -3
  438. package/.next/standalone/.next/server/chunks/_0t0zc2d._.js +0 -3
  439. package/.next/standalone/.next/server/chunks/_0wa631d._.js +0 -3
  440. package/.next/standalone/.next/server/chunks/_13ccnao._.js +0 -3
  441. package/.next/standalone/.next/server/chunks/_13es063._.js +0 -3
  442. package/.next/standalone/.next/server/chunks/_142872j._.js +0 -3
  443. package/.next/standalone/.next/server/chunks/_18wsb_a._.js +0 -8
  444. package/.next/standalone/.next/server/chunks/_1e-4h5h._.js +0 -3
  445. package/.next/standalone/.next/server/chunks/_1gvvd7n._.js +0 -158
  446. package/.next/standalone/.next/server/chunks/_1moda66._.js +0 -3
  447. package/.next/standalone/.next/server/chunks/_1p6ghzv._.js +0 -3
  448. package/.next/standalone/.next/server/chunks/_1xmmi_v._.js +0 -3
  449. package/.next/standalone/.next/server/chunks/_1z-yrlf._.js +0 -3
  450. package/.next/standalone/.next/server/chunks/ssr/_0bf3x4q._.js +0 -3
  451. package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +0 -4
  452. package/.next/standalone/.next/static/N9wokeIiC9rUIGmzBhj6T/_clientMiddlewareManifest.js +0 -6
  453. package/.next/standalone/.next/static/chunks/1odow92u6wxz0.js +0 -1
  454. package/.next/standalone/.next/static/chunks/1wb_is4wsdm-u.js +0 -1
  455. package/.next/standalone/.next/static/chunks/1zmpcmngg5c7-.js +0 -2
  456. package/.next/standalone/.next/static/chunks/220-ne82aus0c.js +0 -44
  457. package/.next/standalone/.next/static/chunks/2rrf5kzx2fhin.js +0 -2
  458. package/.next/standalone/.next/static/chunks/2s_k9x4d0gi9h.js +0 -16
  459. package/.next/standalone/.next/static/chunks/3hsi90cejq2dg.js +0 -1
  460. package/.next/standalone/.next/static/chunks/3pfyuezyqd9ob.js +0 -1
  461. package/.next/standalone/.next/static/chunks/3ts-_4i6bfkh2.js +0 -1
  462. package/.next/standalone/scripts/score-connections.sql +0 -80
  463. package/.next/standalone/scripts/score_new_connections.sql +0 -77
  464. package/.next/static/N9wokeIiC9rUIGmzBhj6T/_clientMiddlewareManifest.js +0 -6
  465. package/.next/static/chunks/1odow92u6wxz0.js +0 -1
  466. package/.next/static/chunks/1wb_is4wsdm-u.js +0 -1
  467. package/.next/static/chunks/1zmpcmngg5c7-.js +0 -2
  468. package/.next/static/chunks/220-ne82aus0c.js +0 -44
  469. package/.next/static/chunks/2rrf5kzx2fhin.js +0 -2
  470. package/.next/static/chunks/2s_k9x4d0gi9h.js +0 -16
  471. package/.next/static/chunks/3hsi90cejq2dg.js +0 -1
  472. package/.next/static/chunks/3pfyuezyqd9ob.js +0 -1
  473. package/.next/static/chunks/3ts-_4i6bfkh2.js +0 -1
  474. package/scripts/score_new_connections.sql +0 -77
  475. /package/.next/standalone/.next/static/{N9wokeIiC9rUIGmzBhj6T → frfHFnRYTA0Wjrw8oZJtY}/_buildManifest.js +0 -0
  476. /package/.next/standalone/.next/static/{N9wokeIiC9rUIGmzBhj6T → frfHFnRYTA0Wjrw8oZJtY}/_ssgManifest.js +0 -0
  477. /package/.next/static/{N9wokeIiC9rUIGmzBhj6T → frfHFnRYTA0Wjrw8oZJtY}/_buildManifest.js +0 -0
  478. /package/.next/static/{N9wokeIiC9rUIGmzBhj6T → frfHFnRYTA0Wjrw8oZJtY}/_ssgManifest.js +0 -0
package/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ <p align="center">
2
+ <a href="https://blakeb056.github.io/six-degrees/"><img src="desktop/icon/icon.svg" width="128" height="128" alt="Six Degrees website"></a>
3
+ </p>
4
+
1
5
  <h1 align="center">Six Degrees</h1>
2
6
 
3
7
  <p align="center">
@@ -5,36 +9,21 @@
5
9
  and find the shortest path to someone you haven't met.</em>
6
10
  </p>
7
11
 
8
- <p align="center">
9
- <a href="https://github.com/blakeb056/six-degrees/releases/latest/download/Six-Degrees-Mac-Apple-Silicon.dmg"><img src="https://img.shields.io/badge/Download-Apple%20Silicon%20(M1%20or%20newer)-2ea44f?style=for-the-badge&logo=apple&logoColor=white" alt="Download for a Mac with Apple Silicon (M1 or newer)"></a>
10
- &nbsp;
11
- <a href="https://github.com/blakeb056/six-degrees/releases/latest/download/Six-Degrees-Mac-Intel.dmg"><img src="https://img.shields.io/badge/Download-Intel%20Mac-555555?style=for-the-badge&logo=apple&logoColor=white" alt="Download for a Mac with an Intel processor"></a>
12
- </p>
12
+ ## Install it
13
13
 
14
- <p align="center"><sub>Free Mac app · macOS 13.5 or later · <a href="#install-it">how to install</a> · <a href="https://github.com/blakeb056/six-degrees/releases/latest">what's new</a></sub></p>
14
+ <p align="center"><strong>Click the button for your Mac to download the app.</strong></p>
15
15
 
16
16
  <p align="center">
17
- <img src="docs/img/app-window.png" alt="Six Degrees on a Mac: your network drawn as circles around you, the most powerful people closest" width="100%">
17
+ <a href="https://github.com/blakeb056/six-degrees/releases/latest/download/Six-Degrees-Mac-Apple-Silicon.dmg"><img src="docs/img/download-apple-silicon.png" width="346" alt="Download Six Degrees for a Mac with Apple Silicon (M1 or newer)"></a>
18
+ <a href="https://github.com/blakeb056/six-degrees/releases/latest/download/Six-Degrees-Mac-Intel.dmg"><img src="docs/img/download-intel.png" width="346" alt="Download Six Degrees for a Mac with an Intel chip"></a>
18
19
  </p>
19
20
 
20
- <p align="center"><sub>Every person shown in this README is invented — see <a href="scripts/gen-synthetic.mjs"><code>gen-synthetic.mjs</code></a>.</sub></p>
21
-
22
- ---
23
-
24
- Your network has a shape, and you can't see it. Six Degrees draws it: everyone you
25
- know placed on rings by how much they can open up for you, the handful of people
26
- whose own circles reach the furthest, and the shortest chain from you to a stranger
27
- worth meeting.
28
-
29
- It's a Mac app. It runs on your computer, against your own data, with no account and
30
- no server. (Inside the app the logo reads *6 Degrees*; in Applications it's **Six
31
- Degrees**.)
32
-
33
- ## Install it
21
+ <p align="center"><sub>Free Mac app · macOS 13.5 or later · <a href="https://blakeb056.github.io/six-degrees/">website</a> · <a href="https://github.com/blakeb056/six-degrees/releases/latest">what's new</a></sub></p>
34
22
 
35
23
  **Which download?** Apple menu → **About This Mac**. If it says "Chip: Apple M…", take
36
24
  **Apple Silicon**. If it says "Processor: …Intel…", take **Intel**. It needs **macOS 13.5
37
- (Ventura) or later**, and it's about 185 MB.
25
+ (Ventura) or later**, and it's about 220 MB. Everything the scanner needs except Google
26
+ Chrome is inside, Python included, so there's nothing else to install.
38
27
 
39
28
  **The first time you open it**, macOS stops it, because the app isn't signed with a
40
29
  paid Apple certificate yet:
@@ -45,8 +34,8 @@ paid Apple certificate yet:
45
34
  malware. Close that message with **Done** (on macOS 13 or 14 it may say **OK** or
46
35
  **Cancel**). Anything except *Move to Trash*.
47
36
  3. Open **System Settings → Privacy & Security**, scroll down to *"Six Degrees" was
48
- blocked*, and click **Open Anyway**. Confirm with **Open** (and your password if
49
- asked).
37
+ blocked*, and click **Open Anyway**. Confirm with **Open Anyway** again (on macOS
38
+ 13 or 14 the button says **Open**), and enter your password if asked.
50
39
 
51
40
  That's once per download; after that it opens like any app.
52
41
 
@@ -61,22 +50,48 @@ It downloads the right version for your Mac, checks the file is exactly the one
61
50
  published here, puts **Six Degrees** in Applications and opens it.
62
51
  [Read the script](install.sh) first if you like.
63
52
 
64
- **Updating.** Choose *Six Degrees → Check for Updates…*, scroll to **Updates** at the
65
- bottom of the page that opens, and click **Check for updates**. Nothing checks by
66
- itself. If there's a newer version, it gives you the Terminal line to paste, which
67
- replaces the app even while it's running. Downloading the new `.dmg` works too; you'll
68
- repeat step 3 once for it.
53
+ **Updating.** Choose *Six Degrees → Check for Updates…* (or open **Settings** with the ⚙
54
+ button or ⌘,). It opens the Updates section and checks. Nothing checks by
55
+ itself. If there's a newer version, click **Install and restart**: the app downloads it
56
+ from GitHub, checks it against the release's published checksums and checks that its code
57
+ signature is intact, closes, puts the new version in its place and opens it again. If the
58
+ new version can't be put in place, won't open, or closes before its first page appears,
59
+ your old one is put back and opened. Once the new one has started, the version you had is
60
+ kept, zipped, in `~/Library/Caches/Six Degrees` until the next update: if the new one
61
+ misbehaves later, unzip it and drag the app into Applications to go back. When the app
62
+ can't update itself (for example, it's running from the disk image), it says why and what
63
+ to do, usually with a Terminal line to paste and what that line will do. Copies of 0.2.1
64
+ and older don't have the button yet, so they take the Terminal line once more.
65
+ Downloading the new `.dmg` works too; you'll repeat step 3 once for it.
69
66
 
70
67
  Your network isn't stored in the app. It's in a hidden folder in your home folder,
71
68
  `.six-degrees` (open it any time with *Help → Show the Data Folder*), so updates never
72
69
  touch it. Each new version also copies your data into its `backups` folder before it
73
- first opens it.
70
+ first opens it. To take it to another computer, see
71
+ [Moving to a new computer](#moving-to-a-new-computer).
72
+
73
+ **Linux:** install **Node 22.13 or later** (from nodejs.org or nvm; Ubuntu's own
74
+ `nodejs` package is too old), then run:
74
75
 
75
- **Linux:** run it from source (below); it's tested on Ubuntu.
76
- **Windows:** not yet. A Windows app is planned; nothing is released.
76
+ ```bash
77
+ npx six-degrees
78
+ ```
79
+
80
+ It opens http://127.0.0.1:6363 in your browser (`--no-open` on a machine without a
81
+ desktop), keeps your data in `~/.six-degrees` (it prints the folder when it starts;
82
+ `--data-dir` puts it elsewhere), and stops with Ctrl-C. To update, stop it and run
83
+ `npx six-degrees@latest`; **Settings → Updates** gives the same
84
+ line. Scanning LinkedIn also needs Google Chrome (not Chromium), and Python: your own 3.10
85
+ to 3.14 if you have it (on Ubuntu with `python3-venv`), or, if not, the Scan page's **Set
86
+ up the scanner** button downloads a private copy into your data folder (24–33 MB, from
87
+ GitHub, checked against a checksum built into Six Degrees). It's tested on Ubuntu, and
88
+ works on a Mac too if you'd rather not install the app.
89
+
90
+ **Windows:** not yet. There's no Windows app, and `npx six-degrees` doesn't run on
91
+ Windows yet; npm says so if you try.
77
92
 
78
93
  <details>
79
- <summary>Run it from source (Linux, or a Mac without the app)</summary>
94
+ <summary>Run it from source (contributors)</summary>
80
95
 
81
96
  Needs **Node 22.13 or later** and git.
82
97
 
@@ -90,9 +105,27 @@ npm run start:packaged # opens http://127.0.0.1:6363 in your browser
90
105
 
91
106
  To update later: stop it (Ctrl-C), then
92
107
  `git pull && npm ci && npm run build && npm run start:packaged`.
93
- Scanning LinkedIn also needs Python 3.9+ and Google Chrome (see below).
108
+ Scanning LinkedIn also needs Google Chrome, and Python 3.10 to 3.14 or the Scan page's
109
+ **Set up the scanner** (see below).
94
110
  </details>
95
111
 
112
+ ## What it is
113
+
114
+ <p align="center">
115
+ <img src="docs/img/app-window.png" alt="Six Degrees on a Mac: your network drawn as circles around you, the most powerful people closest" width="100%">
116
+ </p>
117
+
118
+ <p align="center"><sub>Every person shown in this README is invented — see <a href="scripts/gen-synthetic.mjs"><code>gen-synthetic.mjs</code></a>.</sub></p>
119
+
120
+ Your network has a shape, and you can't see it. Six Degrees draws it: everyone you
121
+ know placed on rings by how much they can open up for you, the handful of people
122
+ whose own circles reach the furthest, and the shortest chain from you to a stranger
123
+ worth meeting.
124
+
125
+ It's a Mac app (or `npx six-degrees` on Linux). It runs on your computer, against your own data, with no account and
126
+ no server. (Inside the app the logo reads *6 Degrees*; in Applications it's **Six
127
+ Degrees**.)
128
+
96
129
  ## Before you scan LinkedIn
97
130
 
98
131
  > [!WARNING]
@@ -108,7 +141,7 @@ Scanning LinkedIn also needs Python 3.9+ and Google Chrome (see below).
108
141
  > stops at the first sign of push-back. The risk is still yours.
109
142
  >
110
143
  > **The CSV import and the sample network carry no LinkedIn risk at all.** Details:
111
- > [`docs/SCRAPING.md`](docs/SCRAPING.md).
144
+ > [how scanning works and what it risks](docs/SCRAPING.md).
112
145
 
113
146
  ## First run
114
147
 
@@ -116,9 +149,10 @@ The app opens on a welcome screen with three ways in, and asks nothing about you
116
149
 
117
150
  - **Scan my LinkedIn**: a guided page that ticks each step off as it goes. It sets up
118
151
  the scanner in one click, you sign into LinkedIn yourself in a Chrome window, and then
119
- it scans. Needs Google Chrome (in Applications) and Python 3.9+; the page checks that
120
- both are installed and says what's missing. The app marks this *Recommended* because
121
- it's the only way to Degrees and Outlink. Read the warning above first.
152
+ it scans. Needs Google Chrome. The Mac app brings its own Python; with `npx six-degrees`
153
+ the page uses yours, or sets up a private one with a click. It checks and says what's
154
+ missing. The app marks this *Recommended* because it's the only way to Degrees and
155
+ Outlink. Read the warning above first.
122
156
  - **Import my LinkedIn CSV**: LinkedIn's official export, read on your machine. On
123
157
  LinkedIn: **Settings & Privacy → Data privacy → Get a copy of your data →
124
158
  Connections**. LinkedIn emails a link in about ten minutes; unzip it and drop
@@ -128,7 +162,7 @@ The app opens on a welcome screen with three ways in, and asks nothing about you
128
162
 
129
163
  | | Your scan | Your `Connections.csv` | Sample network |
130
164
  |---|---|---|---|
131
- | Setup | Python + Chrome, once | ~10 min (LinkedIn emails the file) | none |
165
+ | Setup | Chrome (Python comes with the Mac app) | ~10 min (LinkedIn emails the file) | none |
132
166
  | Network Circle, Paths | ✅ | ✅ | ✅ |
133
167
  | **Degrees** (people you *haven't* met) | ✅ | — | ✅ |
134
168
  | **Outlink** (getting introduced) | ✅ | — | — |
@@ -188,18 +222,36 @@ tiers: S ≥ 7.5 A ≥ 5.5 B ≥ 4.0 C ≥ 2.5 D < 2.5
188
222
  - **Title (1–10)** comes from someone's current role in their headline, with former
189
223
  roles at 70%; current students are capped.
190
224
  - **Company score (1–10)** is the one you set (Paths → Scores) if there is one. Otherwise
191
- it comes from a list of well-known companies, or from how many of your people work
192
- there.
225
+ it comes from a built-in list of well-known companies, or from how many of your people
226
+ work there. The built-in list is the same for everyone and follows one rule: only
227
+ companies most US professionals would recognise (household names, the largest companies,
228
+ top investment, consulting, law and accounting firms, frontier AI labs, top universities),
229
+ scored 7 to 10. Anything else is estimated from your network. If you scanned with an
230
+ earlier version, Paths → Scores offers once to keep its old scores for the companies in
231
+ your network whose score changed.
232
+ - **Your sector (Settings, optional):** pick up to three sectors you work in, and companies
233
+ in them get +1 ("lean") or +2 ("strong") on that score, once, never above 10 and never on
234
+ a score you set. A pick is one of twelve broad industries or a narrower sector from a
235
+ built-in list of about fifty (Dental, Real Estate, Software & SaaS…). A sector matches by a
236
+ fixed list of words: in the company's name, or in the headlines of at least half of the
237
+ people you know there, where a job every kind of company has (recruiter, accountant,
238
+ software engineer) doesn't count. An industry includes its sectors, and a company's one
239
+ industry when the known list or the company's name gives it, not when only its people's
240
+ job titles do. Settings suggests the sectors your network is in and shows what would
241
+ change before you save.
193
242
  - Multiplying means seniority counts for more at a bigger company. A VP at a company
194
243
  scored 10 gets 9.0; a founder at an unknown company, 6.7.
195
- - **Reach bonus (up to 1.5)**: investor, YC, an audience in the millions. It counts half
196
- at an unknown company, because headlines are self-written.
244
+ - **Reach bonus (up to 1.5)**: investor, YC, an audience in the millions, the top award
245
+ of any field (a Nobel, a Pulitzer, an Oscar, an Olympic medal…). It counts half
246
+ at an unknown company, because headlines are self-written (your sector doesn't change
247
+ that).
197
248
  - **Bridge boost (up to 1)**: someone whose mapped circle is unusually strong.
198
249
 
199
- Scanned and sample people show the working ("VP / Partner / GM (9) · Snap (9/10) · +0.7
250
+ Scanned and sample people show the working ("VP / Partner / GM (9) · Adobe (8/10) · +0.7
200
251
  strong circle"). A CSV import shows the score only, and uses the built-in company list
201
- rather than your own scores. The model is [`lib/scoring.js`](lib/scoring.js), explained
202
- in [`docs/brain/SCORING.md`](docs/brain/SCORING.md).
252
+ rather than your own scores or sectors; the sample network keeps its own. The model is
253
+ [`lib/scoring.js`](lib/scoring.js), explained in
254
+ [`docs/brain/SCORING.md`](docs/brain/SCORING.md).
203
255
 
204
256
  It estimates **network position**, not what anyone is worth as a person.
205
257
 
@@ -212,11 +264,21 @@ It estimates **network position**, not what anyone is worth as a person.
212
264
  - The app contacts only these, and only when you act:
213
265
  - **LinkedIn**, while you scan. Also, until a profile photo has been saved to your
214
266
  computer, the app shows it straight from LinkedIn's image server.
215
- - **PyPI** (the Python package library), once, to download the scanner's add-ons.
267
+ - Only without the Mac app (which carries the scanner's Python and add-ons inside it),
268
+ once, when you set the scanner up: **PyPI** (the Python package library) for the
269
+ scanner's add-ons, and, if the computer has no Python the scanner can use, **GitHub**
270
+ for a copy of Python when you click *Set up the scanner*. Every file is checked
271
+ against a checksum built into Six Degrees.
216
272
  - **GitHub**, only when you click *Check for updates*, to read the newest version
217
- number.
273
+ number. In the Mac app, also when you then click *Install and restart*, to download
274
+ that version and the file of checksums that proves it's the one published.
218
275
 
219
276
  Nothing about you is sent, and nothing checks on its own.
277
+ - A **copy of your network** that you save (Settings → Your data) is a file you keep
278
+ wherever you choose; the app never uploads it. It holds the names, headlines and profile
279
+ links of the people in your network, their photos unless you leave them out, your
280
+ settings, and the scanner's progress and LinkedIn budget. Your LinkedIn sign-in is never
281
+ in it. Keep it private, and delete it once it's imported.
220
282
  - The web framework's own anonymous usage stats are turned off.
221
283
 
222
284
  See [SECURITY.md](SECURITY.md) for the threat model.
@@ -225,15 +287,81 @@ See [SECURITY.md](SECURITY.md) for the threat model.
225
287
 
226
288
  ```
227
289
  .six-degrees/ in your home folder (hidden)
228
- ├── six-degrees.sqlite your network: connections, scores, tiers, queue
229
- ├── backups/ a copy of the database before each new version touches it
290
+ ├── six-degrees.sqlite your network: connections, scores, tiers, queue, settings
291
+ ├── backups/ copies of the database from before each new version, and
292
+ │ before an import, touched it
230
293
  ├── avatars/ profile photos, if you scan
231
294
  ├── chrome-profile/ the scanner's Chrome sign-in, if you scan (see below)
232
- ├── venv/ the scanner's Python add-ons, if you set it up
295
+ ├── venv/ the scanner's Python add-ons, if you set it up without the Mac app
296
+ ├── python/ a private Python for them, if Set up the scanner downloaded one
233
297
  ├── pushback/ what LinkedIn's page said if it ever pushed back
234
298
  └── *.json the scanner's budget, cooldown, progress and skip lists
235
299
  ```
236
300
 
301
+ **Settings → Your data** shows where this folder is (with *Copy the path* and *Show in
302
+ Finder*), what each part takes up, and the backups in it.
303
+
304
+ ### Moving to a new computer
305
+
306
+ 1. On the old computer, open **Settings → Your data** and click **Save a copy of my
307
+ network**. You get one file, `Six Degrees backup <date>.sixdegrees`, with your network,
308
+ your settings, the scanner's progress, skip lists and LinkedIn budget, and the profile
309
+ photos of the people in it (untick them to leave them out; they come back as you scan
310
+ again).
311
+ 2. Move that file to the new computer yourself (a USB stick, AirDrop). It holds other
312
+ people's names and photos, so don't post it or share it.
313
+ 3. On the new computer, install Six Degrees, open **Settings → Your data**, choose the
314
+ file and click **Import**. It's checked first, and nothing changes if it isn't a whole
315
+ and undamaged Six Degrees copy. If that computer already has a network, you're asked to
316
+ confirm that the import replaces it. The two networks are never merged. Your LinkedIn
317
+ search budget is the one thing that is: it belongs to your LinkedIn account, not to a
318
+ computer, so searches made on either computer still count, a pause on scanning set on
319
+ either stays until it ends, and budget limits already set on that computer stay.
320
+ 4. The import finishes the next time Six Degrees starts. The Mac app does that with
321
+ **Restart now**; with `npx six-degrees`, press Ctrl-C and start it again. If another
322
+ copy of Six Degrees has the same folder open (one started from the Terminal, say), the
323
+ import waits, and Settings says so. What was there before is kept in `backups/`
324
+ (`before-import-…`), and those copies are never deleted automatically: see
325
+ [Undo an import](#undo-an-import).
326
+ 5. Sign in to LinkedIn again on the new computer before you scan. Your sign-in never goes
327
+ into a copy. A network opened from a LinkedIn CSV isn't in the copy either: it lives in
328
+ its browser tab, so import the CSV again there.
329
+
330
+ ### Undo an import
331
+
332
+ An import keeps what it replaced in the data folder's `backups/`, named with the time it
333
+ finished (Settings → Your data shows the exact name):
334
+
335
+ - `before-import-<time>.sqlite`: your network as it was.
336
+ - `before-import-<time>-files/`: its profile photos (`avatars/`), the scanner's notes about
337
+ it (`bridge-progress.json`, `bridge-skips.json`, `bridge-unclear.json`), and a copy of
338
+ your LinkedIn budget files as they were.
339
+
340
+ To put it back (there's no button for this yet):
341
+
342
+ 1. Quit Six Degrees (⌘Q in the Mac app, Ctrl-C for `npx six-degrees`). Do the rest while
343
+ it's closed.
344
+ 2. Open the data folder (Settings → Your data → *Show in Finder*, or `~/.six-degrees`).
345
+ Move these out of it, to the Trash or somewhere safe if you might want the imported
346
+ network again: `six-degrees.sqlite`, and `six-degrees.sqlite-wal` and
347
+ `six-degrees.sqlite-shm` if they're there (a `-wal` left beside a different database
348
+ is replayed into it and damages it), then `avatars/` and the three `bridge-*.json`
349
+ files.
350
+ 3. Copy `backups/before-import-<time>.sqlite` into the data folder and rename the copy
351
+ `six-degrees.sqlite`. If `before-import-<time>.sqlite-wal` and `-shm` files are beside
352
+ it, copy them too, renamed to match (`six-degrees.sqlite-wal`,
353
+ `six-degrees.sqlite-shm`).
354
+ 4. Copy `avatars/` and the three `bridge-*.json` files from
355
+ `backups/before-import-<time>-files/` back into the data folder.
356
+ 5. Leave `linkedin-activity.json`, `linkedin-cooldown.json` and `scan-limits.json` as they
357
+ are. They're your LinkedIn account's budget, which the import kept (it only added the
358
+ other computer's searches), so they're right whichever network you use.
359
+ 6. Open Six Degrees again.
360
+
361
+ Don't put the live folder in iCloud Drive or Dropbox to share it between computers
362
+ instead. A sync service that copies the database while it's open can damage it, and it
363
+ would also sync your LinkedIn sign-in.
364
+
237
365
  **To remove everything:** quit the app and drag **Six Degrees** from Applications to the
238
366
  Trash. Then in Finder choose **Go → Go to Folder…** (⇧⌘G), paste `~/.six-degrees` and
239
367
  move that folder to the Trash. Do the same for
@@ -251,8 +379,8 @@ Nothing to configure. Two optional environment variables exist:
251
379
 
252
380
  | Variable | Purpose |
253
381
  |---|---|
254
- | `SIX_DEGREES_HOME` | Where your data lives. Defaults to `~/.six-degrees`. It's read at launch, so it applies when you run from source, not when the Mac app is opened from the Dock or Finder. |
255
- | `ADMIN_TOKEN` | Not needed on your own computer. The app listens only on 127.0.0.1 and isn't built to be exposed: don't put it behind a tunnel or bind it to another address ([SECURITY.md](SECURITY.md)). If it's ever bound elsewhere, the six routes that delete data, run the scanner or update a copy run from source refuse every caller without this token, including the app's own buttons. Everything else, including reading your whole network, stays open. |
382
+ | `SIX_DEGREES_HOME` | Where your data lives. Defaults to `~/.six-degrees`. It's read at launch, so it applies to `npx six-degrees` and source runs (npx also takes `--data-dir`; see `npx six-degrees --help`), not when the Mac app is opened from the Dock or Finder. A relative folder is taken from the folder you run the command in. |
383
+ | `ADMIN_TOKEN` | Not needed on your own computer. The app listens only on 127.0.0.1 and isn't built to be exposed: don't put it behind a tunnel or bind it to another address ([SECURITY.md](SECURITY.md)). If it's ever bound elsewhere, the ten routes that delete, replace or hand over your data, run the scanner, open the data folder or update a copy run from source refuse every caller without this token, including the app's own buttons. Everything else, including reading your whole network, stays open. |
256
384
 
257
385
  ## Contributing
258
386
 
package/SECURITY.md CHANGED
@@ -12,17 +12,90 @@ This app is designed to run **on your own machine, against your own network**.
12
12
 
13
13
  - The web UI binds to localhost. It is not hardened for exposure to the
14
14
  internet, and it has no multi-user authentication or per-user authorization.
15
- - Four API routes (`admin-delete`, `admin-update`, `delete-cluster`,
16
- `setup-profile`) can irreversibly destroy or rewrite data. On localhost they
17
- are allowed without a token — anyone who can reach 127.0.0.1 can already open
18
- the database file directly, so a token there adds friction rather than
19
- safety. Reached from any other host they require `ADMIN_TOKEN` as a bearer,
20
- and refuse to run when it is unset.
15
+ - Ten API routes are gated (`admin-delete`, `admin-update`, `delete-cluster`,
16
+ `setup-profile`, `scraper`, `update`, and Settings → Your data's
17
+ `data/export`, `data/import`, `data/restart`, `data/reveal`). The first four
18
+ can irreversibly destroy or rewrite data, `scraper` starts processes, and
19
+ `update` changes the code that runs next. `data/export` hands over the whole
20
+ network in one file, `data/import` replaces it, `data/restart` stops the
21
+ server, and `data/reveal` starts a process (Finder, or the Linux file
22
+ browser, on the data folder's own path). While the server is bound to
23
+ 127.0.0.1 they need no token — anyone who can reach it can already open the
24
+ database file directly, so a token there adds friction rather than safety. If
25
+ it is bound to any other address, every caller must send `ADMIN_TOKEN` as a
26
+ bearer, including this machine and the app's own buttons, and the routes
27
+ refuse to run when it is unset. Reading the data folder's facts
28
+ (`GET /api/data`: its path and sizes) changes nothing and stays open, like
29
+ every other read.
21
30
  - A localhost-bound service is still reachable by any web page you visit — the
22
31
  binding keeps other machines out, not your own browser. Every mutating API
23
32
  request is therefore refused when the browser reports it came from another
24
- site (`Sec-Fetch-Site`, with `Origin` as a fallback). Command-line callers
25
- such as the scraper send neither header and are unaffected.
33
+ site (`Sec-Fetch-Site`, with `Origin` as a fallback). That includes other
34
+ ports on this same machine: a page from another local app or dev server is
35
+ refused unless its `Origin` is exactly this app's address. Command-line callers
36
+ such as the scanner send neither header and are unaffected.
37
+ - A website can also make its own domain point at `127.0.0.1` (DNS rebinding).
38
+ The browser then treats the app as that site's own origin, which would let
39
+ the page read the network and send writes. While the app is bound to
40
+ loopback, every request to `/api` and `/avatars` must be addressed to
41
+ `127.0.0.1`, `localhost` or `[::1]` (the `Host` header); any other name gets
42
+ a 421.
43
+ - The Mac app's **Install and restart** (behind the `update` route's gate)
44
+ downloads only what GitHub's `releases/latest` names for this repository,
45
+ and only the version the user's check was shown (the server remembers it):
46
+ this Mac's `.dmg` by its exact file name, from that release's own download
47
+ address, never a pre-release and never a version or address sent by the page.
48
+ Nothing replaces the app unless the image matches the release's `SHA256SUMS`
49
+ (a release without one is refused), the app inside passes
50
+ `codesign --verify --deep --strict` (its code signature is intact), is Six
51
+ Degrees (its bundle id) at exactly that version, runs on this macOS and chip,
52
+ and has no link pointing outside itself. The checksums come from the same
53
+ release, so they catch a corrupted, truncated or swapped download, not a
54
+ compromised GitHub account; that is the same trust as the Terminal
55
+ installer. The app is only signed ad hoc, so the signature proves it is
56
+ whole, not who made it. The swap is done by a script shipped inside the app
57
+ (`scripts/apply-update.sh`), started with a clean environment, and it moves
58
+ only the app: the data folder is never read, moved or written. It stops only
59
+ the app's own processes, the ones the server names and those whose
60
+ executable is inside the app, never a program that merely has a file or
61
+ folder of the app open (a Terminal in its folder, an editor). It never
62
+ replaces an app another user of the Mac has open. `SIX_DEGREES_TEST_RELEASES`
63
+ (for tests) is honoured only for a `127.0.0.1` address.
64
+ - **The scanner's Python.** The Mac app carries its own: CPython 3.12.14 from
65
+ python-build-standalone (release 20260814), which the build checks against a
66
+ SHA-256 pinned in `lib/scanner-python.js` before unpacking it, with the
67
+ scanner's packages installed from PyPI when the app is built. Those packages
68
+ are pinned in `scripts/requirements.txt`: an exact version and the SHA-256 of
69
+ every file pip may install, wheels only, so pip refuses any other file and
70
+ nothing is built from source. The Python is signed ad hoc like the rest of the
71
+ app and sealed into its signature, which `codesign --verify --deep --strict`
72
+ checks (in CI, and before the in-app updater uses a download). It runs
73
+ isolated from the user's Python settings (`-E -s`: no `PYTHON*` variable and
74
+ no user site-packages count), so nothing of the user's can stand in for its
75
+ packages, and no scan writes bytecode into the app, on this Python or any
76
+ other.
77
+ - **Set up the scanner** (the Scan page, for `npx six-degrees` and source copies
78
+ on a computer with no Python 3.10–3.14 it can use) downloads only when that
79
+ button is pressed, and only from two places:
80
+ - **GitHub's release download** for python-build-standalone (`github.com`,
81
+ which hands the file over from GitHub's own download server): the one file
82
+ pinned for this operating system and chip (`lib/scanner-python.js`: Linux
83
+ x64 and arm64, macOS Apple Silicon and Intel). Its size and SHA-256 must
84
+ match before it is even named, let alone unpacked; a file that doesn't is
85
+ deleted.
86
+ - **PyPI** (`pypi.org`, `files.pythonhosted.org`) for the scanner's packages,
87
+ every file checked against `scripts/requirements.txt`. (pip follows your
88
+ own pip settings for which index, proxy and certificates to use, if you
89
+ have any; the hashes still have to match. Settings that would install the
90
+ packages somewhere other than `venv/` (`PIP_TARGET`, `PIP_PREFIX`,
91
+ `PIP_ROOT`, `PIP_USER`) are left out, and Install checks that the packages
92
+ load from `venv/` before it says it has finished.)
93
+
94
+ The Python lands in the data folder's `python/`, the packages in `venv/`;
95
+ nothing outside the data folder changes, and neither is ever exported. The
96
+ checksums come from this repository, not from the servers, so a swapped or
97
+ corrupted download is refused; a release that was compromised before its
98
+ checksums were taken would not be.
26
99
  - Values stored in the database are treated as untrusted text and escaped
27
100
  before rendering. Do not reintroduce `innerHTML` (including d3's `.html()`)
28
101
  for anything data-bearing.
@@ -34,12 +107,49 @@ Everything stays local:
34
107
 
35
108
  - A CSV import is parsed in the browser and held for that tab only. It is never
36
109
  uploaded and never written to a database.
37
- - Scraped data and cached avatars are written to your machine and are gitignored.
110
+ - Scanned data and cached avatars are written to your machine and are gitignored.
38
111
  - There is no telemetry, no analytics, and no crash reporting.
39
112
 
40
- If you run the scraper, `chrome-profile/` in the data directory holds a live
113
+ If you run the scanner, `chrome-profile/` in the data directory holds a live
41
114
  logged-in LinkedIn session. Never copy, sync, or commit it.
42
115
 
116
+ ### Moving your data (Settings → Your data)
117
+
118
+ - **An export** is one `.sixdegrees` file the user saves and carries. It is
119
+ built from an allow-list (the database, the photos its rows still point at,
120
+ and the scanner's six progress and budget files), never from the folder minus
121
+ a few things, so `chrome-profile/`, `venv/`, `python/`, `backups/` and
122
+ `pushback/` never travel, and links are never followed out of the folder (a link standing in
123
+ for `avatars/` itself included). The app never uploads it. It holds other
124
+ people's names, headlines and photos, and the page says so.
125
+ - **An import is untrusted input**: the file came from somewhere else. Before
126
+ anything in the data folder changes, it must pass SQLite's own integrity
127
+ check (read with `trusted_schema` off), hold only the app's own ordinary
128
+ tables and indexes (no views, triggers, virtual tables or extra columns),
129
+ come from this version or an older one, match its own manifest, and carry
130
+ only allow-listed files whose names can't leave the data folder, each
131
+ matching its SHA-256. The LinkedIn budget files must also follow the rules
132
+ the app writes them by: limits only from the Scan page's own menu (the
133
+ scanner reads a daily limit of 0 as no limit at all). The network is then
134
+ rebuilt into this version's own schema, so only rows travel, never table
135
+ definitions. It is refused while a scan (or the scanner's setup) runs, and
136
+ replacing a network that has people needs a confirmation that names how many.
137
+ - **The upload never sits in memory.** Next copies the body of every request
138
+ `middleware.js` sees into memory before middleware decides anything, so the
139
+ import route is left out of it and makes the same checks itself
140
+ (`lib/gate.js requestRefusal`: rebinding, cross-site, the gate) before it
141
+ reads a byte. The body is then written to disk as it arrives, at most
142
+ 256 MB, and must match the size the page declared. Every other route keeps
143
+ Next's 10 MB limit.
144
+ - **It is applied at the next start**, never under the open database, and never
145
+ while another process has it open (a second copy of Six Degrees on the same
146
+ folder). What was there is kept first in `backups/before-import-<time>.sqlite`
147
+ (and its photos and files beside it), synced to the disk and checked (SQLite's
148
+ quick check, and every table's row count against the original) before the
149
+ original is replaced; those copies are never deleted automatically. The
150
+ LinkedIn budget files are merged with this computer's, not replaced: they
151
+ belong to the account.
152
+
43
153
  Never commit `public/avatars/` or any exported CSV — they contain real people.
44
154
  `public/demo-data.json` is committed, but only ever as the generated synthetic
45
155
  sample; CI fails the build if it appears without its `"synthetic":true` marker,
package/bin/args.mjs ADDED
@@ -0,0 +1,44 @@
1
+ // The npx launcher's command line, kept apart from bin/six-degrees.mjs (which
2
+ // starts a server the moment it runs) so the tests can read it directly.
3
+
4
+ import { homedir } from 'node:os';
5
+ import path from 'node:path';
6
+
7
+ /**
8
+ * A folder named on the command line, made absolute. The launcher changes into
9
+ * the package's own folder before the server starts, so a relative folder
10
+ * passed on as it is would land inside the npx cache, and be lost when that is
11
+ * cleared. A leading ~ is the home folder: the shell expands `--data-dir ~/x`
12
+ * but not `--data-dir=~/x`, which would otherwise make a folder named "~".
13
+ */
14
+ export function absoluteFolder(value, { cwd = process.cwd(), home = homedir() } = {}) {
15
+ const s = String(value);
16
+ const expanded = s === '~' ? home : s.startsWith('~/') ? path.join(home, s.slice(2)) : s;
17
+ return path.resolve(cwd, expanded);
18
+ }
19
+
20
+ /**
21
+ * @returns {{ port, open, dataDir, help, version, error }}
22
+ * dataDir is absolute (absoluteFolder), or null when not given.
23
+ */
24
+ export function parseArgs(argv, { env = process.env, cwd = process.cwd(), home = homedir() } = {}) {
25
+ const out = { port: Number(env.PORT) || 6363, open: true, dataDir: null };
26
+ for (let i = 0; i < argv.length; i++) {
27
+ const a = String(argv[i]);
28
+ if (a === '--port' || a === '-p') out.port = Number(argv[++i]);
29
+ else if (a === '--no-open') out.open = false;
30
+ else if (a === '--data-dir' || a.startsWith('--data-dir=')) {
31
+ const value = a === '--data-dir' ? argv[++i] : a.slice('--data-dir='.length);
32
+ if (value) out.dataDir = absoluteFolder(value, { cwd, home });
33
+ else out.error = '--data-dir needs a folder, for example: --data-dir ~/six-degrees-copy';
34
+ }
35
+ else if (a === '--help' || a === '-h') out.help = true;
36
+ else if (a === '--version' || a === '-v') out.version = true;
37
+ }
38
+ return out;
39
+ }
40
+
41
+ /** The data folder: --data-dir, else SIX_DEGREES_HOME, else ~/.six-degrees. Always absolute, for the same reason. */
42
+ export function resolveDataDir(args, { env = process.env, cwd = process.cwd(), home = homedir() } = {}) {
43
+ return absoluteFolder(args.dataDir || env.SIX_DEGREES_HOME || path.join(home, '.six-degrees'), { cwd, home });
44
+ }
@@ -10,8 +10,9 @@ import { createServer } from 'node:net';
10
10
  import { spawn } from 'node:child_process';
11
11
  import { fileURLToPath } from 'node:url';
12
12
  import { existsSync, mkdirSync } from 'node:fs';
13
- import { homedir, platform } from 'node:os';
13
+ import { platform } from 'node:os';
14
14
  import path from 'node:path';
15
+ import { parseArgs, resolveDataDir } from './args.mjs';
15
16
 
16
17
  const HERE = path.dirname(fileURLToPath(import.meta.url));
17
18
  const ROOT = path.join(HERE, '..');
@@ -33,19 +34,6 @@ function checkNode() {
33
34
  }
34
35
  }
35
36
 
36
- function parseArgs(argv) {
37
- const out = { port: Number(process.env.PORT) || 6363, open: true };
38
- for (let i = 0; i < argv.length; i++) {
39
- const a = argv[i];
40
- if (a === '--port' || a === '-p') out.port = Number(argv[++i]);
41
- else if (a === '--no-open') out.open = false;
42
- else if (a === '--data-dir') process.env.SIX_DEGREES_HOME = argv[++i];
43
- else if (a === '--help' || a === '-h') out.help = true;
44
- else if (a === '--version' || a === '-v') out.version = true;
45
- }
46
- return out;
47
- }
48
-
49
37
  const HELP = `
50
38
  six-degrees — map your professional network as a galaxy, on your own machine
51
39
 
@@ -84,10 +72,15 @@ function freePort(start, attempts = 20) {
84
72
  function openBrowser(url) {
85
73
  const cmd = platform() === 'darwin' ? 'open' : platform() === 'win32' ? 'start' : 'xdg-open';
86
74
  const args = platform() === 'win32' ? ['', url] : [url];
75
+ // A machine with no desktop (no xdg-open) can't open one; say where to go.
76
+ // spawn reports a missing command as an 'error' event, not a throw.
77
+ const instead = () => console.log(` Open ${url} in your browser.`);
87
78
  try {
88
- spawn(cmd, args, { stdio: 'ignore', detached: true, shell: platform() === 'win32' }).unref();
79
+ const child = spawn(cmd, args, { stdio: 'ignore', detached: true, shell: platform() === 'win32' });
80
+ child.on('error', instead);
81
+ child.unref();
89
82
  } catch {
90
- /* a browser that will not open is not worth failing over */
83
+ instead();
91
84
  }
92
85
  }
93
86
 
@@ -101,9 +94,15 @@ async function main() {
101
94
  return;
102
95
  }
103
96
 
97
+ if (args.error) {
98
+ console.error(`\n ${args.error}\n`);
99
+ process.exit(1);
100
+ }
101
+
104
102
  checkNode();
105
103
 
106
- const dataDir = process.env.SIX_DEGREES_HOME || path.join(homedir(), '.six-degrees');
104
+ // Absolute (bin/args.mjs): the server runs from inside the package, not here.
105
+ const dataDir = resolveDataDir(args);
107
106
  if (!existsSync(dataDir)) mkdirSync(dataDir, { recursive: true });
108
107
 
109
108
  const server = path.join(ROOT, '.next', 'standalone', 'server.js');