six-degrees 0.6.0 → 0.7.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 (557) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +3 -0
  3. package/.next/standalone/.next/build-manifest.json +3 -3
  4. package/.next/standalone/.next/prerender-manifest.json +14 -14
  5. package/.next/standalone/.next/routes-manifest.json +18 -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 +1 -1
  12. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  13. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  14. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  15. package/.next/standalone/.next/server/app/_not-found.rsc +26 -18
  16. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +26 -18
  17. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +25 -16
  18. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  19. package/.next/standalone/.next/server/app/api/admin-delete/route.js +2 -2
  20. package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
  21. package/.next/standalone/.next/server/app/api/admin-update/route.js +2 -2
  22. package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/bridges/route.js +2 -2
  24. package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/bulk-import/route.js +2 -2
  26. package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js +3 -3
  28. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/company-scores/route.js +3 -3
  30. package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/connections/route.js +2 -2
  32. package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/data/backup/route/app-paths-manifest.json +3 -0
  34. package/.next/standalone/.next/server/app/api/data/backup/route/build-manifest.json +12 -0
  35. package/.next/standalone/.next/server/app/api/data/backup/route/server-reference-manifest.json +4 -0
  36. package/.next/standalone/.next/server/app/api/data/backup/route.js +7 -0
  37. package/.next/standalone/.next/server/app/api/data/backup/route.js.map +5 -0
  38. package/.next/standalone/.next/server/app/api/data/backup/route.js.nft.json +1 -0
  39. package/.next/standalone/.next/server/app/api/data/backup/route_client-reference-manifest.js +3 -0
  40. package/.next/standalone/.next/server/app/api/data/csv/route/app-paths-manifest.json +3 -0
  41. package/.next/standalone/.next/server/app/api/data/csv/route/build-manifest.json +12 -0
  42. package/.next/standalone/.next/server/app/api/data/csv/route/server-reference-manifest.json +4 -0
  43. package/.next/standalone/.next/server/app/api/data/csv/route.js +8 -0
  44. package/.next/standalone/.next/server/app/api/data/csv/route.js.map +5 -0
  45. package/.next/standalone/.next/server/app/api/data/csv/route.js.nft.json +1 -0
  46. package/.next/standalone/.next/server/app/api/data/csv/route_client-reference-manifest.js +3 -0
  47. package/.next/standalone/.next/server/app/api/data/export/route.js +1 -1
  48. package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/api/data/import/route.js +1 -1
  50. package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
  51. package/.next/standalone/.next/server/app/api/data/restart/route.js +1 -1
  52. package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/api/data/restore/route/app-paths-manifest.json +3 -0
  54. package/.next/standalone/.next/server/app/api/data/restore/route/build-manifest.json +12 -0
  55. package/.next/standalone/.next/server/app/api/data/restore/route/server-reference-manifest.json +4 -0
  56. package/.next/standalone/.next/server/app/api/data/restore/route.js +7 -0
  57. package/.next/standalone/.next/server/app/api/data/restore/route.js.map +5 -0
  58. package/.next/standalone/.next/server/app/api/data/restore/route.js.nft.json +1 -0
  59. package/.next/standalone/.next/server/app/api/data/restore/route_client-reference-manifest.js +3 -0
  60. package/.next/standalone/.next/server/app/api/data/reveal/route.js +1 -1
  61. package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
  62. package/.next/standalone/.next/server/app/api/data/route.js +1 -1
  63. package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
  64. package/.next/standalone/.next/server/app/api/delete-cluster/route.js +2 -2
  65. package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/api/ingest/route.js +2 -2
  67. package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
  68. package/.next/standalone/.next/server/app/api/network/route.js +2 -2
  69. package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/api/notifications/route.js +2 -2
  71. package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  72. package/.next/standalone/.next/server/app/api/outreach/route.js +2 -2
  73. package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
  74. package/.next/standalone/.next/server/app/api/queue/route.js +2 -2
  75. package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
  76. package/.next/standalone/.next/server/app/api/scraper/route.js +4 -2
  77. package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
  78. package/.next/standalone/.next/server/app/api/settings/route.js +3 -3
  79. package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
  80. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js +3 -3
  81. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
  82. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js +3 -3
  83. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
  84. package/.next/standalone/.next/server/app/api/setup-profile/route.js +2 -2
  85. package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
  86. package/.next/standalone/.next/server/app/api/social/crm/route.js +1 -1
  87. package/.next/standalone/.next/server/app/api/social/crm/route.js.nft.json +1 -1
  88. package/.next/standalone/.next/server/app/api/social/messages/route.js +1 -1
  89. package/.next/standalone/.next/server/app/api/social/messages/route.js.nft.json +1 -1
  90. package/.next/standalone/.next/server/app/api/social/route.js +2 -2
  91. package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -1
  92. package/.next/standalone/.next/server/app/api/unlock/route.js +2 -2
  93. package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
  94. package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  95. package/.next/standalone/.next/server/app/api/update-images/route.js +2 -2
  96. package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
  97. package/.next/standalone/.next/server/app/api/users/route.js +2 -2
  98. package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
  99. package/.next/standalone/.next/server/app/avatars/[file]/route.js +1 -1
  100. package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
  101. package/.next/standalone/.next/server/app/import/page.js +1 -1
  102. package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
  103. package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
  104. package/.next/standalone/.next/server/app/import.html +1 -1
  105. package/.next/standalone/.next/server/app/import.rsc +32 -24
  106. package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +32 -24
  107. package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
  108. package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +23 -18
  109. package/.next/standalone/.next/server/app/index.html +1 -1
  110. package/.next/standalone/.next/server/app/index.rsc +29 -22
  111. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +20 -15
  112. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +29 -22
  113. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  114. package/.next/standalone/.next/server/app/launch/page.js +1 -1
  115. package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
  116. package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
  117. package/.next/standalone/.next/server/app/launch.html +2 -2
  118. package/.next/standalone/.next/server/app/launch.rsc +32 -24
  119. package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +32 -24
  120. package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
  121. package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +23 -18
  122. package/.next/standalone/.next/server/app/page.js +1 -1
  123. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  124. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  125. package/.next/standalone/.next/server/app/paths/page.js +1 -1
  126. package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
  127. package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
  128. package/.next/standalone/.next/server/app/paths.html +1 -1
  129. package/.next/standalone/.next/server/app/paths.rsc +32 -24
  130. package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +32 -24
  131. package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
  132. package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +21 -18
  133. package/.next/standalone/.next/server/app/profile/page.js +1 -1
  134. package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
  135. package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
  136. package/.next/standalone/.next/server/app/profile.html +1 -1
  137. package/.next/standalone/.next/server/app/profile.rsc +32 -24
  138. package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +32 -24
  139. package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
  140. package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +23 -18
  141. package/.next/standalone/.next/server/app/queue/page.js +1 -1
  142. package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
  143. package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
  144. package/.next/standalone/.next/server/app/queue.html +1 -1
  145. package/.next/standalone/.next/server/app/queue.rsc +32 -24
  146. package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +32 -24
  147. package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
  148. package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +20 -18
  149. package/.next/standalone/.next/server/app/scores/page.js +1 -1
  150. package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
  151. package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
  152. package/.next/standalone/.next/server/app/scores.html +1 -1
  153. package/.next/standalone/.next/server/app/scores.rsc +32 -24
  154. package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +32 -24
  155. package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +2 -2
  156. package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +23 -18
  157. package/.next/standalone/.next/server/app/settings/page.js +1 -1
  158. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  159. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  160. package/.next/standalone/.next/server/app/settings.html +1 -1
  161. package/.next/standalone/.next/server/app/settings.rsc +32 -24
  162. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +32 -24
  163. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  164. package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +20 -18
  165. package/.next/standalone/.next/server/app/setup/page.js +1 -1
  166. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  167. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  168. package/.next/standalone/.next/server/app/setup.html +1 -1
  169. package/.next/standalone/.next/server/app/setup.rsc +32 -24
  170. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +32 -24
  171. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
  172. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +21 -18
  173. package/.next/standalone/.next/server/app/social/page.js +1 -1
  174. package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -1
  175. package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
  176. package/.next/standalone/.next/server/app/social.html +1 -1
  177. package/.next/standalone/.next/server/app/social.rsc +32 -24
  178. package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +32 -24
  179. package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +2 -2
  180. package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +23 -18
  181. package/.next/standalone/.next/server/app-paths-manifest.json +3 -0
  182. package/.next/standalone/.next/server/chunks/[root-of-the-server]__00la_y0._.js +1 -1
  183. package/.next/standalone/.next/server/chunks/[root-of-the-server]__05_3-22._.js +1 -1
  184. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__15wc9e-._.js → [root-of-the-server]__063s0x7._.js} +2 -2
  185. package/.next/standalone/.next/server/chunks/[root-of-the-server]__066rab1._.js +7 -0
  186. package/.next/standalone/.next/server/chunks/[root-of-the-server]__07t47cm._.js +1 -1
  187. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__13dx4sd._.js → [root-of-the-server]__0_4y8z7._.js} +3 -3
  188. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cj5jqc._.js +1 -1
  189. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e0fei3._.js +1 -1
  190. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0fooke1._.js +7 -0
  191. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljnyho._.js +1 -1
  192. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o5-tcg._.js +1 -1
  193. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qrnous._.js +1 -1
  194. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +1 -1
  195. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t6y79e._.js +7 -0
  196. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wmexuc._.js +1 -1
  197. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0x29muh._.js +7 -0
  198. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xu34sz._.js +1 -1
  199. package/.next/standalone/.next/server/chunks/[root-of-the-server]__133knx1._.js +1 -1
  200. package/.next/standalone/.next/server/chunks/[root-of-the-server]__15-17hl._.js +1 -1
  201. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19b7mig._.js +1 -1
  202. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1a_6x37._.js +8 -0
  203. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1an_8pa._.js +7 -0
  204. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bbcwub._.js +1 -1
  205. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ducpz2._.js +3 -0
  206. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1eris29._.js +1 -1
  207. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1gjo_cq._.js +1 -1
  208. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qcm08_._.js +1 -1
  209. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tir0vc._.js +1 -1
  210. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1w25q0m._.js +1 -1
  211. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1xhyinn._.js +3 -0
  212. package/.next/standalone/.next/server/chunks/[root-of-the-server]__20rwfdw._.js +1 -1
  213. package/.next/standalone/.next/server/chunks/_02twlca._.js +1 -1
  214. package/.next/standalone/.next/server/chunks/_072jl0y._.js +1 -1
  215. package/.next/standalone/.next/server/chunks/_0lw8_i0._.js +1 -1
  216. package/.next/standalone/.next/server/chunks/_0sqqpzb._.js +3 -0
  217. package/.next/standalone/.next/server/chunks/_1_fynia._.js +7 -7
  218. package/.next/standalone/.next/server/chunks/_1fn30_l._.js +1 -1
  219. package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
  220. package/.next/standalone/.next/server/chunks/_1iwkh8t._.js +1 -1
  221. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_backup_route_actions_0zoj04p.js +3 -0
  222. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_csv_route_actions_0l6m7lg.js +3 -0
  223. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_restore_route_actions_0-b05bv.js +3 -0
  224. package/.next/standalone/.next/server/chunks/lib_02iz_9q._.js +4 -0
  225. package/.next/standalone/.next/server/chunks/lib_05agvk3._.js +4 -0
  226. package/.next/standalone/.next/server/chunks/lib_0v84u66._.js +4 -0
  227. package/.next/standalone/.next/server/chunks/lib_db-client_0_dch35.js +174 -0
  228. package/.next/standalone/.next/server/chunks/lib_linkedin-export_12imqxg.js +1 -1
  229. package/.next/standalone/.next/server/chunks/lib_rpc_005abye.js +3 -0
  230. package/.next/standalone/.next/server/chunks/lib_rpc_0b17r6v.js +3 -0
  231. package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +7 -0
  232. package/.next/standalone/.next/server/chunks/lib_rpc_0doq0t-.js +3 -0
  233. package/.next/standalone/.next/server/chunks/lib_rpc_0fd2rlw.js +3 -0
  234. package/.next/standalone/.next/server/chunks/lib_rpc_0is4qoc.js +3 -0
  235. package/.next/standalone/.next/server/chunks/lib_rpc_0jnfp2e.js +3 -0
  236. package/.next/standalone/.next/server/chunks/lib_rpc_0nef_n7.js +3 -0
  237. package/.next/standalone/.next/server/chunks/lib_rpc_0plrzra.js +3 -0
  238. package/.next/standalone/.next/server/chunks/lib_rpc_13a4m0c.js +3 -0
  239. package/.next/standalone/.next/server/chunks/lib_rpc_1if17c-.js +3 -0
  240. package/.next/standalone/.next/server/chunks/lib_rpc_1j-zw7v.js +3 -0
  241. package/.next/standalone/.next/server/chunks/lib_rpc_1sngt-q.js +3 -0
  242. package/.next/standalone/.next/server/chunks/lib_rpc_1v2sgme.js +3 -0
  243. package/.next/standalone/.next/server/chunks/lib_rpc_1vh-tgx.js +3 -0
  244. package/.next/standalone/.next/server/chunks/lib_rpc_1zaubz7.js +3 -0
  245. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qd8pzm._.js +3 -0
  246. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0qdeuyo._.js → [root-of-the-server]__1-012q1._.js} +2 -2
  247. package/.next/standalone/.next/server/chunks/ssr/_01o3i5y._.js +7 -0
  248. package/.next/standalone/.next/server/chunks/ssr/_02sband._.js +7 -0
  249. package/.next/standalone/.next/server/chunks/ssr/_06rgzfh._.js +3 -0
  250. package/.next/standalone/.next/server/chunks/ssr/_07zb7lu._.js +3 -0
  251. package/.next/standalone/.next/server/chunks/ssr/_0_79idq._.js +3 -0
  252. package/.next/standalone/.next/server/chunks/ssr/_0gy11ar._.js +3 -0
  253. package/.next/standalone/.next/server/chunks/ssr/_0hb-dhy._.js +3 -0
  254. package/.next/standalone/.next/server/chunks/ssr/_14nxd3q._.js +3 -0
  255. package/.next/standalone/.next/server/chunks/ssr/_189g8lg._.js +3 -0
  256. package/.next/standalone/.next/server/chunks/ssr/_1f_n00p._.js +1 -1
  257. package/.next/standalone/.next/server/chunks/ssr/_1fm1ih_._.js +2 -2
  258. package/.next/standalone/.next/server/chunks/ssr/_1k2nmbw._.js +2 -2
  259. package/.next/standalone/.next/server/chunks/ssr/_1k9qfsk._.js +3 -0
  260. package/.next/standalone/.next/server/chunks/ssr/app_141w189._.js +1 -1
  261. package/.next/standalone/.next/server/chunks/ssr/app_components_0t908t-._.js +1 -1
  262. package/.next/standalone/.next/server/chunks/ssr/app_components_0uuff77._.js +25 -2
  263. package/.next/standalone/.next/server/chunks/ssr/app_components_1j3q-h5._.js +25 -2
  264. package/.next/standalone/.next/server/chunks/ssr/app_components_CompanyScores_1g0mytz.js +1 -1
  265. package/.next/standalone/.next/server/chunks/ssr/app_import_page_1fmu6_l.js +1 -1
  266. package/.next/standalone/.next/server/chunks/ssr/app_launch_page_19t0dyd.js +1 -1
  267. package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +25 -13
  268. package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +2 -2
  269. package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
  270. package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +11 -11
  271. package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +1 -1
  272. package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +12 -13
  273. package/.next/standalone/.next/server/chunks/ssr/app_social_page_0h7zxda.js +1 -1
  274. package/.next/standalone/.next/server/chunks/ssr/{lib_1c4ieuw._.js → lib_csv_1ttiu_h.js} +2 -2
  275. package/.next/standalone/.next/server/chunks/ssr/lib_galaxy-lab_108_lu-.js +1 -1
  276. package/.next/standalone/.next/server/chunks/ssr/lib_separation_1305zsx.js +3 -0
  277. package/.next/standalone/.next/server/edge/chunks/[root-of-the-server]__0pco7a6._.js +1 -1
  278. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  279. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  280. package/.next/standalone/.next/server/pages/404.html +1 -1
  281. package/.next/standalone/.next/server/pages/500.html +1 -1
  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/03-_esd2x_yk6.js +1 -0
  285. package/.next/standalone/.next/static/chunks/03-demj2iy-0p.js +44 -0
  286. package/.next/standalone/.next/static/chunks/0_1r-3csplxnj.js +1 -0
  287. package/.next/standalone/.next/static/chunks/0an_b6zi1r_qq.js +5 -0
  288. package/.next/standalone/.next/static/chunks/0e3g0sil79cus.js +21 -0
  289. package/.next/standalone/.next/static/chunks/0kscsqnvfq3-r.js +1 -0
  290. package/.next/standalone/.next/static/chunks/0p3g5tfostsep.js +7 -0
  291. package/.next/standalone/.next/static/chunks/0zd0t7e60bn4z.js +1 -0
  292. package/.next/standalone/.next/static/chunks/163b-20vwa06i.js +34 -0
  293. package/.next/standalone/.next/static/chunks/{0ws7zy0w57ufi.js → 1eyajb9ntzlrl.js} +1 -1
  294. package/.next/standalone/.next/static/chunks/1pa-2v1ajvrko.js +1 -0
  295. package/.next/standalone/.next/static/chunks/1qbjermbf68xj.js +1 -0
  296. package/.next/standalone/.next/static/chunks/1z9lb-dbcg4aa.js +1 -0
  297. package/.next/standalone/.next/static/chunks/23_1z47al92nz.js +57 -0
  298. package/.next/standalone/.next/static/chunks/260bomtr214m8.js +34 -0
  299. package/.next/standalone/.next/static/chunks/2eo9pqq9gay3r.js +34 -0
  300. package/.next/standalone/.next/static/chunks/2xk2ra4piiuod.js +34 -0
  301. package/.next/standalone/.next/static/chunks/2z1-q0_vunbql.js +2 -0
  302. package/.next/standalone/.next/static/chunks/2zf4jmz9l3li2.js +1 -0
  303. package/.next/standalone/.next/static/chunks/34g73qhayuevu.js +1 -0
  304. package/.next/standalone/.next/static/chunks/3b0-14cxqsprt.js +1 -0
  305. package/.next/standalone/.next/static/chunks/3jarus1_38s2z.js +1 -0
  306. package/.next/standalone/.next/static/chunks/3ssu341hav_sq.css +3 -0
  307. package/.next/standalone/.next/static/chunks/3v1qd3sqhqn0d.js +1 -0
  308. package/.next/standalone/.next/static/chunks/43pg2sursiypz.js +1 -0
  309. package/.next/standalone/.next/static/chunks/44yn0tcd9dfk3.js +1 -0
  310. package/.next/standalone/CHANGELOG.md +143 -0
  311. package/.next/standalone/README.md +71 -49
  312. package/.next/standalone/SECURITY.md +17 -11
  313. package/.next/standalone/app/api/data/backup/route.js +25 -0
  314. package/.next/standalone/app/api/data/csv/route.js +74 -0
  315. package/.next/standalone/app/api/data/export/route.js +1 -1
  316. package/.next/standalone/app/api/data/restore/route.js +48 -0
  317. package/.next/standalone/app/api/data/reveal/route.js +19 -6
  318. package/.next/standalone/app/api/data/route.js +15 -4
  319. package/.next/standalone/app/api/ingest/route.js +1 -1
  320. package/.next/standalone/app/api/queue/route.js +1 -1
  321. package/.next/standalone/app/api/scraper/route.js +120 -32
  322. package/.next/standalone/app/api/social/crm/route.js +2 -1
  323. package/.next/standalone/app/components/AppHeader.js +20 -16
  324. package/.next/standalone/app/components/AppTabs.js +8 -8
  325. package/.next/standalone/app/components/AutoScanButton.js +2 -2
  326. package/.next/standalone/app/components/Avatar.js +1 -1
  327. package/.next/standalone/app/components/ChainView.js +184 -112
  328. package/.next/standalone/app/components/CirclePanel.js +16 -16
  329. package/.next/standalone/app/components/ClusterSpinner.js +64 -0
  330. package/.next/standalone/app/components/CompanyScores.js +20 -20
  331. package/.next/standalone/app/components/EmptyState.js +39 -18
  332. package/.next/standalone/app/components/FieldStep.js +15 -13
  333. package/.next/standalone/app/components/FilterPanel.js +17 -17
  334. package/.next/standalone/app/components/ForceGraph.js +49 -14
  335. package/.next/standalone/app/components/GalaxyLab.js +33 -33
  336. package/.next/standalone/app/components/GridView.js +18 -18
  337. package/.next/standalone/app/components/LegacyScoresCard.js +10 -11
  338. package/.next/standalone/app/components/LinkedInLimits.js +24 -19
  339. package/.next/standalone/app/components/LiquidGlass.js +99 -0
  340. package/.next/standalone/app/components/ListView.js +20 -20
  341. package/.next/standalone/app/components/MapControls.js +5 -5
  342. package/.next/standalone/app/components/MappingProgress.js +16 -16
  343. package/.next/standalone/app/components/NoteDetail.js +13 -13
  344. package/.next/standalone/app/components/OnboardingGate.js +4 -4
  345. package/.next/standalone/app/components/OutlinkQuest.js +25 -25
  346. package/.next/standalone/app/components/PathsAnalyzer.js +37 -36
  347. package/.next/standalone/app/components/PeopleMap.js +21 -21
  348. package/.next/standalone/app/components/RingsView.js +11 -11
  349. package/.next/standalone/app/components/ScanRadar.js +84 -39
  350. package/.next/standalone/app/components/ScanStatusBar.js +22 -18
  351. package/.next/standalone/app/components/ScanTrail.js +8 -5
  352. package/.next/standalone/app/components/SeparationView.js +168 -104
  353. package/.next/standalone/app/components/Sidebar.js +161 -161
  354. package/.next/standalone/app/components/StaleServerBanner.js +4 -4
  355. package/.next/standalone/app/components/TheirCircle.js +26 -26
  356. package/.next/standalone/app/components/ThemeDefs.js +42 -0
  357. package/.next/standalone/app/components/ThemeLoader.js +20 -1
  358. package/.next/standalone/app/components/UpdatePanel.js +22 -22
  359. package/.next/standalone/app/components/UserProvider.js +22 -15
  360. package/.next/standalone/app/components/settings/AppearanceSection.js +47 -24
  361. package/.next/standalone/app/components/settings/DataSection.js +276 -109
  362. package/.next/standalone/app/components/settings/NetworkHealthSection.js +13 -13
  363. package/.next/standalone/app/components/settings/SectorPicker.js +12 -12
  364. package/.next/standalone/app/components/settings/SectorSection.js +13 -14
  365. package/.next/standalone/app/components/settings/TierSection.js +9 -9
  366. package/.next/standalone/app/components/settings/TitleSection.js +11 -11
  367. package/.next/standalone/app/components/settings/UsageSection.js +313 -0
  368. package/.next/standalone/app/components/ui.js +8 -8
  369. package/.next/standalone/app/globals.css +159 -2
  370. package/.next/standalone/app/import/page.js +94 -38
  371. package/.next/standalone/app/launch/page.js +5 -5
  372. package/.next/standalone/app/layout.js +6 -2
  373. package/.next/standalone/app/page.js +53 -37
  374. package/.next/standalone/app/paths/page.js +63 -63
  375. package/.next/standalone/app/profile/page.js +58 -58
  376. package/.next/standalone/app/queue/page.js +54 -54
  377. package/.next/standalone/app/scores/page.js +2 -2
  378. package/.next/standalone/app/settings/page.js +16 -13
  379. package/.next/standalone/app/setup/page.js +221 -125
  380. package/.next/standalone/app/social/Crm.js +63 -62
  381. package/.next/standalone/app/social/SocialHub.js +22 -22
  382. package/.next/standalone/app/social/Thread.js +7 -7
  383. package/.next/standalone/app/social/page.js +2 -2
  384. package/.next/standalone/lib/backups.js +458 -0
  385. package/.next/standalone/lib/chain-layout.js +32 -0
  386. package/.next/standalone/lib/csv-store.js +131 -0
  387. package/.next/standalone/lib/csv.js +137 -39
  388. package/.next/standalone/lib/data-folder.js +19 -3
  389. package/.next/standalone/lib/data-import.js +5 -2
  390. package/.next/standalone/lib/db-client.js +78 -20
  391. package/.next/standalone/lib/demo.js +5 -4
  392. package/.next/standalone/lib/galaxy-export.js +9 -2
  393. package/.next/standalone/lib/galaxy-lab.js +19 -5
  394. package/.next/standalone/lib/galaxy-layouts.js +43 -0
  395. package/.next/standalone/lib/gate.js +10 -4
  396. package/.next/standalone/lib/ingest.js +1 -1
  397. package/.next/standalone/lib/linkedin-limits.js +212 -11
  398. package/.next/standalone/lib/liquid-glass.js +59 -0
  399. package/.next/standalone/lib/notifications.js +13 -0
  400. package/.next/standalone/lib/scan-pace.js +11 -0
  401. package/.next/standalone/lib/scan-progress.js +20 -0
  402. package/.next/standalone/lib/scan-risk.js +44 -0
  403. package/.next/standalone/lib/scanner-setup.js +97 -15
  404. package/.next/standalone/lib/scraper-client.js +17 -3
  405. package/.next/standalone/lib/search-risk.js +7 -3
  406. package/.next/standalone/lib/settings-client.js +5 -3
  407. package/.next/standalone/lib/settings.js +10 -0
  408. package/.next/standalone/lib/social-store.js +3 -2
  409. package/.next/standalone/lib/synced-setting.js +46 -0
  410. package/.next/standalone/lib/theme-store.js +42 -10
  411. package/.next/standalone/lib/themes.js +111 -21
  412. package/.next/standalone/lib/usage.js +195 -0
  413. package/.next/standalone/package-lock.json +2 -2
  414. package/.next/standalone/package.json +1 -1
  415. package/.next/standalone/scripts/scrape.py +26 -2
  416. package/.next/static/chunks/03-_esd2x_yk6.js +1 -0
  417. package/.next/static/chunks/03-demj2iy-0p.js +44 -0
  418. package/.next/static/chunks/0_1r-3csplxnj.js +1 -0
  419. package/.next/static/chunks/0an_b6zi1r_qq.js +5 -0
  420. package/.next/static/chunks/0e3g0sil79cus.js +21 -0
  421. package/.next/static/chunks/0kscsqnvfq3-r.js +1 -0
  422. package/.next/static/chunks/0p3g5tfostsep.js +7 -0
  423. package/.next/static/chunks/0zd0t7e60bn4z.js +1 -0
  424. package/.next/static/chunks/163b-20vwa06i.js +34 -0
  425. package/.next/static/chunks/{0ws7zy0w57ufi.js → 1eyajb9ntzlrl.js} +1 -1
  426. package/.next/static/chunks/1pa-2v1ajvrko.js +1 -0
  427. package/.next/static/chunks/1qbjermbf68xj.js +1 -0
  428. package/.next/static/chunks/1z9lb-dbcg4aa.js +1 -0
  429. package/.next/static/chunks/23_1z47al92nz.js +57 -0
  430. package/.next/static/chunks/260bomtr214m8.js +34 -0
  431. package/.next/static/chunks/2eo9pqq9gay3r.js +34 -0
  432. package/.next/static/chunks/2xk2ra4piiuod.js +34 -0
  433. package/.next/static/chunks/2z1-q0_vunbql.js +2 -0
  434. package/.next/static/chunks/2zf4jmz9l3li2.js +1 -0
  435. package/.next/static/chunks/34g73qhayuevu.js +1 -0
  436. package/.next/static/chunks/3b0-14cxqsprt.js +1 -0
  437. package/.next/static/chunks/3jarus1_38s2z.js +1 -0
  438. package/.next/static/chunks/3ssu341hav_sq.css +3 -0
  439. package/.next/static/chunks/3v1qd3sqhqn0d.js +1 -0
  440. package/.next/static/chunks/43pg2sursiypz.js +1 -0
  441. package/.next/static/chunks/44yn0tcd9dfk3.js +1 -0
  442. package/CHANGELOG.md +143 -0
  443. package/README.md +71 -49
  444. package/SECURITY.md +17 -11
  445. package/lib/backups.js +458 -0
  446. package/lib/chain-layout.js +32 -0
  447. package/lib/csv-store.js +131 -0
  448. package/lib/csv.js +137 -39
  449. package/lib/data-folder.js +19 -3
  450. package/lib/data-import.js +5 -2
  451. package/lib/db-client.js +78 -20
  452. package/lib/demo.js +5 -4
  453. package/lib/galaxy-export.js +9 -2
  454. package/lib/galaxy-lab.js +19 -5
  455. package/lib/galaxy-layouts.js +43 -0
  456. package/lib/gate.js +10 -4
  457. package/lib/ingest.js +1 -1
  458. package/lib/linkedin-limits.js +212 -11
  459. package/lib/liquid-glass.js +59 -0
  460. package/lib/notifications.js +13 -0
  461. package/lib/scan-pace.js +11 -0
  462. package/lib/scan-progress.js +20 -0
  463. package/lib/scan-risk.js +44 -0
  464. package/lib/scanner-setup.js +97 -15
  465. package/lib/scraper-client.js +17 -3
  466. package/lib/search-risk.js +7 -3
  467. package/lib/settings-client.js +5 -3
  468. package/lib/settings.js +10 -0
  469. package/lib/social-store.js +3 -2
  470. package/lib/synced-setting.js +46 -0
  471. package/lib/theme-store.js +42 -10
  472. package/lib/themes.js +111 -21
  473. package/lib/usage.js +195 -0
  474. package/package.json +1 -1
  475. package/scripts/scrape.py +26 -2
  476. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-rnla2._.js +0 -3
  477. package/.next/standalone/.next/server/chunks/[root-of-the-server]__14ou7j5._.js +0 -3
  478. package/.next/standalone/.next/server/chunks/[root-of-the-server]__16iu0hv._.js +0 -3
  479. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +0 -3
  480. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rphsdk._.js +0 -3
  481. package/.next/standalone/.next/server/chunks/lib_0851sk0._.js +0 -8
  482. package/.next/standalone/.next/server/chunks/lib_1q7p2ro._.js +0 -8
  483. package/.next/standalone/.next/server/chunks/lib_1r6l86l._.js +0 -8
  484. package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +0 -174
  485. package/.next/standalone/.next/server/chunks/lib_rpc_04s8mzr.js +0 -3
  486. package/.next/standalone/.next/server/chunks/lib_rpc_053z7as.js +0 -3
  487. package/.next/standalone/.next/server/chunks/lib_rpc_0b_-2te.js +0 -3
  488. package/.next/standalone/.next/server/chunks/lib_rpc_0dhbxdm.js +0 -3
  489. package/.next/standalone/.next/server/chunks/lib_rpc_0dxlk31.js +0 -3
  490. package/.next/standalone/.next/server/chunks/lib_rpc_0f8b-qc.js +0 -3
  491. package/.next/standalone/.next/server/chunks/lib_rpc_0pzie7_.js +0 -3
  492. package/.next/standalone/.next/server/chunks/lib_rpc_0r76fq_.js +0 -3
  493. package/.next/standalone/.next/server/chunks/lib_rpc_0ut7pjp.js +0 -3
  494. package/.next/standalone/.next/server/chunks/lib_rpc_15zyqaz.js +0 -3
  495. package/.next/standalone/.next/server/chunks/lib_rpc_18zpjtq.js +0 -3
  496. package/.next/standalone/.next/server/chunks/lib_rpc_1a8xkiz.js +0 -3
  497. package/.next/standalone/.next/server/chunks/lib_rpc_1e_55xo.js +0 -3
  498. package/.next/standalone/.next/server/chunks/lib_rpc_1oxfd1_.js +0 -3
  499. package/.next/standalone/.next/server/chunks/lib_rpc_1s0lod0.js +0 -3
  500. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08oxiqh._.js +0 -3
  501. package/.next/standalone/.next/server/chunks/ssr/_03l7vge._.js +0 -3
  502. package/.next/standalone/.next/server/chunks/ssr/_08g31gm._.js +0 -3
  503. package/.next/standalone/.next/server/chunks/ssr/_0gk_ofx._.js +0 -3
  504. package/.next/standalone/.next/server/chunks/ssr/_0nwsf93._.js +0 -3
  505. package/.next/standalone/.next/server/chunks/ssr/_0vho1lb._.js +0 -3
  506. package/.next/standalone/.next/server/chunks/ssr/_1-658fq._.js +0 -3
  507. package/.next/standalone/.next/server/chunks/ssr/_1bc46kz._.js +0 -3
  508. package/.next/standalone/.next/server/chunks/ssr/_2105jz8._.js +0 -3
  509. package/.next/standalone/.next/server/chunks/ssr/app_components_settings_SectorPicker_1c-29qw.js +0 -3
  510. package/.next/standalone/.next/static/chunks/02pq76u7y4v9-.js +0 -22
  511. package/.next/standalone/.next/static/chunks/0cmq0exd4d0h9.js +0 -1
  512. package/.next/standalone/.next/static/chunks/0i02yxppzds1o.js +0 -1
  513. package/.next/standalone/.next/static/chunks/0m_p4k-q2qh--.js +0 -1
  514. package/.next/standalone/.next/static/chunks/0oyslj62jkubd.js +0 -1
  515. package/.next/standalone/.next/static/chunks/0vo-l8hxneba6.css +0 -3
  516. package/.next/standalone/.next/static/chunks/0yh8z2tbrc5i_.js +0 -1
  517. package/.next/standalone/.next/static/chunks/17-rhp8sg4m1b.js +0 -44
  518. package/.next/standalone/.next/static/chunks/172e10mi-tpo_.js +0 -34
  519. package/.next/standalone/.next/static/chunks/1ei5vbe2uw6fl.js +0 -1
  520. package/.next/standalone/.next/static/chunks/1f7_pl_wq7lh3.js +0 -11
  521. package/.next/standalone/.next/static/chunks/1jzbe7zdx9vbj.js +0 -2
  522. package/.next/standalone/.next/static/chunks/1m3017gp790pw.js +0 -45
  523. package/.next/standalone/.next/static/chunks/1t20e8u0st7ep.js +0 -1
  524. package/.next/standalone/.next/static/chunks/2-ch9rm27eril.js +0 -11
  525. package/.next/standalone/.next/static/chunks/2ldv-v3s9yaif.js +0 -1
  526. package/.next/standalone/.next/static/chunks/3bp284gjf7dyq.js +0 -1
  527. package/.next/standalone/.next/static/chunks/3k1f6clbn3gj3.js +0 -7
  528. package/.next/standalone/.next/static/chunks/3l4t8je6iguzc.js +0 -1
  529. package/.next/standalone/.next/static/chunks/3tplzuc9jk-tl.js +0 -1
  530. package/.next/standalone/.next/static/chunks/42m0bqr8pbovz.js +0 -11
  531. package/.next/static/chunks/02pq76u7y4v9-.js +0 -22
  532. package/.next/static/chunks/0cmq0exd4d0h9.js +0 -1
  533. package/.next/static/chunks/0i02yxppzds1o.js +0 -1
  534. package/.next/static/chunks/0m_p4k-q2qh--.js +0 -1
  535. package/.next/static/chunks/0oyslj62jkubd.js +0 -1
  536. package/.next/static/chunks/0vo-l8hxneba6.css +0 -3
  537. package/.next/static/chunks/0yh8z2tbrc5i_.js +0 -1
  538. package/.next/static/chunks/17-rhp8sg4m1b.js +0 -44
  539. package/.next/static/chunks/172e10mi-tpo_.js +0 -34
  540. package/.next/static/chunks/1ei5vbe2uw6fl.js +0 -1
  541. package/.next/static/chunks/1f7_pl_wq7lh3.js +0 -11
  542. package/.next/static/chunks/1jzbe7zdx9vbj.js +0 -2
  543. package/.next/static/chunks/1m3017gp790pw.js +0 -45
  544. package/.next/static/chunks/1t20e8u0st7ep.js +0 -1
  545. package/.next/static/chunks/2-ch9rm27eril.js +0 -11
  546. package/.next/static/chunks/2ldv-v3s9yaif.js +0 -1
  547. package/.next/static/chunks/3bp284gjf7dyq.js +0 -1
  548. package/.next/static/chunks/3k1f6clbn3gj3.js +0 -7
  549. package/.next/static/chunks/3l4t8je6iguzc.js +0 -1
  550. package/.next/static/chunks/3tplzuc9jk-tl.js +0 -1
  551. package/.next/static/chunks/42m0bqr8pbovz.js +0 -11
  552. /package/.next/standalone/.next/static/{dQUvvVSXqbkEFC3-CNcwT → ZVpLBK-2vpllv2HxOFs2d}/_buildManifest.js +0 -0
  553. /package/.next/standalone/.next/static/{dQUvvVSXqbkEFC3-CNcwT → ZVpLBK-2vpllv2HxOFs2d}/_clientMiddlewareManifest.js +0 -0
  554. /package/.next/standalone/.next/static/{dQUvvVSXqbkEFC3-CNcwT → ZVpLBK-2vpllv2HxOFs2d}/_ssgManifest.js +0 -0
  555. /package/.next/static/{dQUvvVSXqbkEFC3-CNcwT → ZVpLBK-2vpllv2HxOFs2d}/_buildManifest.js +0 -0
  556. /package/.next/static/{dQUvvVSXqbkEFC3-CNcwT → ZVpLBK-2vpllv2HxOFs2d}/_clientMiddlewareManifest.js +0 -0
  557. /package/.next/static/{dQUvvVSXqbkEFC3-CNcwT → ZVpLBK-2vpllv2HxOFs2d}/_ssgManifest.js +0 -0
package/SECURITY.md CHANGED
@@ -12,21 +12,25 @@ 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
- - 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
15
+ - Thirteen API routes are gated (`admin-delete`, `admin-update`, `delete-cluster`,
16
+ `setup-profile`, `scraper`, `update`, Settings → Your data's
17
+ `data/export`, `data/import`, `data/restart`, `data/reveal`, `data/backup`,
18
+ `data/restore`, and `data/csv`). The first four can irreversibly destroy or rewrite data,
19
+ `scraper` starts processes, and `update` changes the code that runs next. `data/export` hands over the whole
20
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
21
+ server, `data/reveal` starts a process (Finder, or the Linux file
22
+ browser, on the data folder's own path or a backup in it), `data/backup`
23
+ writes the whole network to a file in `backups/`, `data/restore`
24
+ replaces the network with one of those backups, named as `backups/` lists
25
+ it, never by a path from the request, and `data/csv` keeps, hands over and
26
+ removes a LinkedIn CSV import kept in the data folder. While the server is bound to
23
27
  127.0.0.1 they need no token — anyone who can reach it can already open the
24
28
  database file directly, so a token there adds friction rather than safety. If
25
29
  it is bound to any other address, every caller must send `ADMIN_TOKEN` as a
26
30
  bearer, including this machine and the app's own buttons, and the routes
27
31
  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.
32
+ (`GET /api/data`: its path, sizes and backups) changes nothing and stays
33
+ open, like every other read.
30
34
  - A localhost-bound service is still reachable by any web page you visit — the
31
35
  binding keeps other machines out, not your own browser. Every mutating API
32
36
  request is therefore refused when the browser reports it came from another
@@ -113,8 +117,10 @@ This app is designed to run **on your own machine, against your own network**.
113
117
 
114
118
  Everything stays local:
115
119
 
116
- - A CSV import is parsed in the browser and held for that tab only. It is never
117
- uploaded and never written to a database.
120
+ - A CSV import is parsed in the browser. Only what the map needs about each
121
+ person (no email addresses) goes to the app on this computer, which keeps it
122
+ in the data folder as `csv-network.json` until you remove it, apart from the
123
+ database. It is never uploaded anywhere.
118
124
  - Scanned data and cached avatars are written to your machine and are gitignored.
119
125
  The app shows photos only from those files, so looking at your network never
120
126
  contacts LinkedIn.
package/lib/backups.js ADDED
@@ -0,0 +1,458 @@
1
+ // Backups: copies of the whole network kept in backups/, so the app keeps
2
+ // working without anyone looking after it (Blake, 2026-10-03: proper backups
3
+ // of his connections and photos).
4
+ //
5
+ // No new format. A backup is a .sixdegrees file, the same one Settings →
6
+ // Your data exports (lib/data-export.js): the database (the settings in it),
7
+ // the photos of the people in it and the scanner's files, each file with its
8
+ // SHA-256. So a backup is checked by the same importer that would restore it
9
+ // (validateImport), and restoring one is an import of it (stageImport), with
10
+ // the same Restart now and the same copy of what was there kept first.
11
+ //
12
+ // Never the Social tab's files (BACKUP_SOCIAL). Its messages are kept only
13
+ // while Keep my messages is on, and Forget it deletes them and the numbers
14
+ // (lib/social-store.js): a copy made every day and kept for a week, or for
15
+ // good, would keep what Forget it promised to delete. An export still carries
16
+ // them when its box is ticked: someone chose that copy. Because a backup
17
+ // brings none, restoring one leaves this computer's Social files where they
18
+ // are (applyPendingImport steps them aside only for a copy that brings its own).
19
+ //
20
+ // Written aside as a hidden .partial, synced, checked, and only then renamed
21
+ // to its name (the vacuumCopy pattern, lib/data-import.js): a file with a
22
+ // backup's name is always a whole one that passed the importer's checks. A
23
+ // copy that fails its check is never kept under a backup's name, so it can
24
+ // never push a good one out of the rotation.
25
+ //
26
+ // When they are made: before a new version first opens the data
27
+ // (lib/db-client.js backupOnNewVersion), once a day, checked when the server
28
+ // starts and after each scan (no timers: nothing runs that nobody started),
29
+ // and when someone clicks Back up now. Which are kept is KEEP below.
30
+
31
+ import { DatabaseSync } from 'node:sqlite';
32
+ import {
33
+ rmSync, renameSync, mkdirSync, lstatSync, copyFileSync, mkdtempSync,
34
+ } from 'node:fs';
35
+ import path from 'node:path';
36
+ import { buildExport, countPeople, EXPORT_EXTENSION } from './data-export.js';
37
+ import { validateImport, stageImport, ImportError } from './data-import.js';
38
+ import {
39
+ listBackups, namesInFolder, IMPORT_BACKUP_PREFIX, UPLOAD_WORK_PREFIX, sweepLeftovers,
40
+ } from './data-folder.js';
41
+ import { runningNow } from './scan-state.js';
42
+ import { durable } from './durable.js';
43
+
44
+ /** The kinds makeBackup makes. A copy kept before an import is made by the import itself (applyPendingImport). */
45
+ export const MADE_KINDS = ['daily', 'before-update', 'manual'];
46
+
47
+ /**
48
+ * How many of each kind are kept, newest first. One made by hand (Back up
49
+ * now, or anything someone put in backups/ themselves) is never deleted.
50
+ */
51
+ export const KEEP = Object.freeze({ daily: 7, 'before-update': 3, 'before-import': 3 });
52
+
53
+ /**
54
+ * A copy kept before an import (or a restore) is the only way back from it, so
55
+ * it stays for at least this long however many imports follow.
56
+ */
57
+ export const IMPORT_FLOOR_MS = 30 * 24 * 3600 * 1000;
58
+
59
+ /** Whether a backup carries the Social tab's files: never (see the top of this file). */
60
+ export const BACKUP_SOCIAL = false;
61
+
62
+ /** A daily backup is made when the newest is at least this old. */
63
+ export const DAILY_EVERY_MS = 24 * 3600 * 1000;
64
+
65
+ /** backups/ names before this feature: the database alone, before a new version (lib/db-client.js). */
66
+ export const AUTO_BACKUP_PREFIX = 'auto-before-';
67
+
68
+ // A half-written backup a stopped server left: hidden, so no listing shows it,
69
+ // and swept once it is an hour old (never one a running backup is writing).
70
+ const PARTIAL = /^\..+\.partial(?:-journal)?$/;
71
+ const PARTIAL_AGE_MS = 3600 * 1000;
72
+
73
+ const STAMP = /(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z/;
74
+ const MADE = /^(daily|before-update|manual)-(.+)-(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z)\.sixdegrees$/;
75
+ const SIDE = /^(.+\.sqlite)-(?:wal|shm|journal)$/;
76
+ const FILES = /^(.+)-files$/;
77
+
78
+ /** The time in a name, as every backup's name carries it (2026-10-03T14-05-09-123Z), or null. */
79
+ export function stampTime(name) {
80
+ const m = STAMP.exec(String(name));
81
+ if (!m) return null;
82
+ const t = Date.parse(`${m[1]}T${m[2]}:${m[3]}:${m[4]}.${m[5]}Z`);
83
+ return Number.isFinite(t) ? t : null;
84
+ }
85
+
86
+ const stampOf = (now) => now.toISOString().replace(/[:.]/g, '-');
87
+
88
+ /** "daily-0.7.0-2026-10-03T14-05-09-123Z.sixdegrees" */
89
+ export function backupName(kind, version, now = new Date()) {
90
+ return `${kind}-${version}-${stampOf(now)}${EXPORT_EXTENSION}`;
91
+ }
92
+
93
+ /** What one name in backups/ is: its kind, its format, and whether it can be restored. */
94
+ function describe(entry, autoPrefix) {
95
+ const { name } = entry;
96
+ const out = {
97
+ name,
98
+ kind: 'manual',
99
+ format: null, // 'sixdegrees' (photos and files inside) or 'database' (the database alone)
100
+ version: null,
101
+ restorable: false,
102
+ byHand: true, // put in backups/ by someone, not made by Six Degrees
103
+ withFiles: null, // the folder of photos and files kept beside a database copy
104
+ parts: [], // names that go with it: that folder, a -wal or -shm
105
+ bytes: entry.bytes,
106
+ modifiedAt: entry.modifiedAt,
107
+ at: stampTime(name) ?? entry.modifiedAt,
108
+ folder: entry.folder,
109
+ };
110
+ if (entry.folder) return out;
111
+ const made = MADE.exec(name);
112
+ if (made) {
113
+ Object.assign(out, { kind: made[1], version: made[2], format: 'sixdegrees', byHand: false });
114
+ } else if (name.startsWith(autoPrefix) && name.endsWith('.sqlite')) {
115
+ const rest = name.slice(autoPrefix.length, -'.sqlite'.length);
116
+ const at = STAMP.exec(rest);
117
+ Object.assign(out, {
118
+ kind: 'before-update', format: 'database', byHand: false,
119
+ version: at ? rest.slice(0, at.index).replace(/-$/, '') || null : null,
120
+ });
121
+ } else if (name.startsWith(IMPORT_BACKUP_PREFIX) && name.endsWith('.sqlite')) {
122
+ Object.assign(out, { kind: 'before-import', format: 'database', byHand: false });
123
+ } else if (name.endsWith(EXPORT_EXTENSION)) {
124
+ out.format = 'sixdegrees';
125
+ } else if (name.endsWith('.sqlite')) {
126
+ out.format = 'database';
127
+ }
128
+ out.restorable = out.format !== null;
129
+ return out;
130
+ }
131
+
132
+ /**
133
+ * Every backup in `folder`, newest first, as the Settings page lists them: a
134
+ * database copy with its -wal, -shm and its folder of photos and files is one
135
+ * backup. Names come from the folder's own listing (lib/data-folder.js
136
+ * listBackups), so nothing here is ever a path someone sent.
137
+ */
138
+ export function describeBackups(folder, { autoPrefix = AUTO_BACKUP_PREFIX } = {}) {
139
+ const raw = listBackups(folder, { autoPrefix });
140
+ const names = new Set(raw.map((b) => b.name));
141
+ const owners = new Map();
142
+ const extras = [];
143
+ for (const b of raw) {
144
+ const side = SIDE.exec(b.name);
145
+ if (!b.folder && side && names.has(side[1])) { extras.push([side[1], b]); continue; }
146
+ const files = FILES.exec(b.name);
147
+ if (b.folder && files && names.has(`${files[1]}.sqlite`)) { extras.push([`${files[1]}.sqlite`, b]); continue; }
148
+ owners.set(b.name, describe(b, autoPrefix));
149
+ }
150
+ for (const [owner, b] of extras) {
151
+ const o = owners.get(owner);
152
+ o.bytes += b.bytes || 0;
153
+ o.parts.push(b.name);
154
+ if (b.folder) o.withFiles = b.name;
155
+ }
156
+ return [...owners.values()].sort((a, b) => b.at - a.at || b.name.localeCompare(a.name));
157
+ }
158
+
159
+ /** Delete one backup and whatever goes with it. Only names from the listing. */
160
+ function removeBackup(folder, entry) {
161
+ for (const name of [...entry.parts, entry.name]) {
162
+ rmSync(path.join(folder, name), { recursive: true, force: true });
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Keep KEEP of each kind, newest first; never one made by hand; never a copy
168
+ * from before an import that is under 30 days old. Also sweeps half-written
169
+ * backups a stopped server left. @returns the names deleted.
170
+ */
171
+ export function rotateBackups(folder, { now = Date.now(), keep = KEEP, floorMs = IMPORT_FLOOR_MS, autoPrefix = AUTO_BACKUP_PREFIX } = {}) {
172
+ const at = now instanceof Date ? now.getTime() : now;
173
+ const all = describeBackups(folder, { autoPrefix });
174
+ const removed = [];
175
+ for (const [kind, n] of Object.entries(keep)) {
176
+ for (const entry of all.filter((e) => e.kind === kind && !e.byHand).slice(n)) {
177
+ if (kind === 'before-import' && at - entry.at < floorMs) continue;
178
+ removeBackup(folder, entry);
179
+ removed.push(entry.name);
180
+ }
181
+ }
182
+ for (const name of namesInFolder(folder)) {
183
+ if (!PARTIAL.test(name)) continue;
184
+ try {
185
+ if (at - lstatSync(path.join(folder, name)).mtimeMs >= PARTIAL_AGE_MS) rmSync(path.join(folder, name), { force: true });
186
+ } catch { /* gone already */ }
187
+ }
188
+ if (removed.length) durable.syncFolder(folder);
189
+ return removed;
190
+ }
191
+
192
+ // ── checking ─────────────────────────────────────────────────────────────────
193
+
194
+ // Checked once per file per server: a check reads every page and every photo,
195
+ // and the page asks often. The same file (name, size and time) gives the same
196
+ // answer; a file that changes on the disk is checked again. One map per
197
+ // process however the bundler splits route modules, like lib/scan-state.js.
198
+ const CHECKED = Symbol.for('six-degrees.backups-checked');
199
+ const FAILED = Symbol.for('six-degrees.backup-failed');
200
+ const checkedFiles = () => (globalThis[CHECKED] ||= new Map());
201
+
202
+ /** The importer's refusal, without the words that only fit an import. */
203
+ export function plainProblem(message) {
204
+ return String(message || 'it could not be read')
205
+ .replace(/,? so nothing was imported\..*$/s, '.')
206
+ .replace(/\s+Export it again on the other computer\.?$/, '');
207
+ }
208
+
209
+ /**
210
+ * Would this backup restore? The importer's own checks (validateImport): SQLite's
211
+ * integrity check, nothing an export never holds, every row count and every
212
+ * file's checksum against its manifest.
213
+ * @returns {{ ok: true, people, photos, exportedAt, fromVersion } | { ok: false, problem }}
214
+ */
215
+ export function checkBackup(file, { applySchema, schemaSql, appVersion }) {
216
+ try {
217
+ const info = validateImport(file, { applySchema, schemaSql, appVersion });
218
+ return { ok: true, ...info };
219
+ } catch (err) {
220
+ const problem = plainProblem(err instanceof ImportError ? err.message : `It could not be read (${err.message}).`);
221
+ return { ok: false, problem: problem.replace(/^This file/, 'This backup') };
222
+ }
223
+ }
224
+
225
+ function fileKey(file) {
226
+ try {
227
+ const s = lstatSync(file);
228
+ return s.isFile() ? `${file}|${s.size}|${s.mtimeMs}` : null;
229
+ } catch {
230
+ return null;
231
+ }
232
+ }
233
+
234
+ /** checkBackup, remembered for the same file. */
235
+ export function checkBackupOnce(file, opts) {
236
+ const key = fileKey(file);
237
+ if (!key) return { ok: false, problem: 'It is no longer there.' };
238
+ const memo = checkedFiles();
239
+ if (!memo.has(key)) memo.set(key, checkBackup(file, opts));
240
+ return memo.get(key);
241
+ }
242
+
243
+ /** The last backup that couldn't be made in this server, for the page; cleared by the next that works. */
244
+ export function backupFailure() {
245
+ return globalThis[FAILED] || null;
246
+ }
247
+
248
+ export function noteBackupFailure(kind, err, now = new Date()) {
249
+ globalThis[FAILED] = { kind, at: now.toISOString(), message: plainProblem(err?.message || String(err)) };
250
+ }
251
+
252
+ // ── making ───────────────────────────────────────────────────────────────────
253
+
254
+ /**
255
+ * Make a backup of the open database `db` and the data folder `dataDir` in
256
+ * `backupsDir`: the whole network as a .sixdegrees file, with every photo of
257
+ * someone in it and the scanner's files (not the Social tab's: BACKUP_SOCIAL).
258
+ *
259
+ * `appVersion` is the version making it (in its name); `dataVersion` the one
260
+ * whose data it holds (in its manifest), which differs only before an update:
261
+ * that copy holds the old version's data, so the old version can restore it.
262
+ *
263
+ * @returns {{ name, file, bytes, people, photos, kind, verified: true }}
264
+ * @throws when it can't be made or fails its check; nothing is left under a backup's name then.
265
+ */
266
+ export function makeBackup(kind, {
267
+ db, dataDir, backupsDir, appVersion, dataVersion = appVersion, schemaSql, applySchema, now = new Date(), rotate = true,
268
+ }) {
269
+ if (!MADE_KINDS.includes(kind)) throw new Error(`There is no kind of backup called '${kind}'.`);
270
+ mkdirSync(backupsDir, { recursive: true });
271
+ const name = backupName(kind, appVersion, now);
272
+ const file = path.join(backupsDir, name);
273
+ const partial = path.join(backupsDir, `.${name}.partial`);
274
+ const tidy = () => {
275
+ rmSync(partial, { force: true });
276
+ rmSync(`${partial}-journal`, { force: true });
277
+ };
278
+ tidy();
279
+ let made;
280
+ try {
281
+ made = buildExport(db, {
282
+ dir: dataDir, outFile: partial, includePhotos: true, includeSocial: BACKUP_SOCIAL, appVersion: dataVersion, schemaSql, now,
283
+ });
284
+ // SQLite doesn't sync what VACUUM INTO writes (lib/durable.js).
285
+ durable.syncFile(partial);
286
+ const check = checkBackup(partial, { applySchema, schemaSql, appVersion });
287
+ if (!check.ok) throw new Error(`the copy failed its check: ${check.problem}`);
288
+ renameSync(partial, file);
289
+ durable.syncFolder(backupsDir);
290
+ } catch (err) {
291
+ tidy();
292
+ throw err;
293
+ }
294
+ const key = fileKey(file);
295
+ if (key) checkedFiles().set(key, { ok: true, people: made.people, photos: made.photos });
296
+ globalThis[FAILED] = null;
297
+ if (rotate) {
298
+ // A backup that was made stays made, even if an old one can't be deleted.
299
+ try { rotateBackups(backupsDir, { now }); } catch { /* tried again after the next one */ }
300
+ }
301
+ return { name, file, kind, bytes: made.bytes, people: made.people, photos: made.photos, verified: true };
302
+ }
303
+
304
+ /** When was the newest daily backup made? null when there is none. */
305
+ export function newestDaily(backupsDir) {
306
+ return describeBackups(backupsDir).find((e) => e.kind === 'daily')?.at ?? null;
307
+ }
308
+
309
+ /**
310
+ * Is a daily backup due? When there is none, or the newest is a day old. One
311
+ * stamped more than a day ahead of now (the clock was changed) doesn't hold
312
+ * the next one back forever.
313
+ */
314
+ export function dailyDue(newest, now = Date.now()) {
315
+ if (newest == null) return true;
316
+ return now - newest >= DAILY_EVERY_MS || newest - now >= DAILY_EVERY_MS;
317
+ }
318
+
319
+ /**
320
+ * The daily backup, if one is due and there is a network to back up. Called
321
+ * when the server opens the database and after each scan. @returns the backup, or null.
322
+ */
323
+ export function dailyBackupIfDue(opts) {
324
+ const now = opts.now || new Date();
325
+ if (!dailyDue(newestDaily(opts.backupsDir), now.getTime())) return null;
326
+ if (countPeople(opts.db) === 0) return null;
327
+ try {
328
+ return makeBackup('daily', { ...opts, now });
329
+ } catch (err) {
330
+ noteBackupFailure('daily', err, now);
331
+ throw err;
332
+ }
333
+ }
334
+
335
+ // ── what the page shows ──────────────────────────────────────────────────────
336
+
337
+ /**
338
+ * The backups for the Settings page, newest first, and the newest restorable
339
+ * .sixdegrees backup checked (once per file per server): "Last backup: <time>,
340
+ * verified", or what is wrong with it (TRAPS §7: a damaged copy says so).
341
+ */
342
+ export function backupReport(backupsDir, { applySchema, schemaSql, appVersion }) {
343
+ const backups = describeBackups(backupsDir);
344
+ const newest = backups.find((e) => e.format === 'sixdegrees' && !e.byHand) || null;
345
+ let last = null;
346
+ if (newest) {
347
+ const check = checkBackupOnce(path.join(backupsDir, newest.name), { applySchema, schemaSql, appVersion });
348
+ last = {
349
+ name: newest.name,
350
+ kind: newest.kind,
351
+ at: new Date(newest.at).toISOString(),
352
+ verified: check.ok,
353
+ people: check.ok ? check.people ?? null : null,
354
+ problem: check.ok ? null : check.problem,
355
+ };
356
+ }
357
+ return { backups, last, failed: backupFailure(), keep: KEEP, floorDays: IMPORT_FLOOR_MS / (24 * 3600 * 1000) };
358
+ }
359
+
360
+ // ── restoring ────────────────────────────────────────────────────────────────
361
+
362
+ /**
363
+ * The checks before a restore, as an import makes them (lib/data-import.js
364
+ * importPreflight): nothing running, no import waiting, and a yes that names
365
+ * how many people it replaces. @returns an ImportError, or null to go ahead.
366
+ */
367
+ export function restorePreflight({ scanRunning, pending, currentPeople = 0, confirmedPeople }) {
368
+ if (scanRunning) {
369
+ return new ImportError(`${runningNow(scanRunning)}. Let it finish, or stop it on the Scan page, then restore.`, { status: 409 });
370
+ }
371
+ if (pending) {
372
+ return new ImportError('An import or a restore is already waiting to finish. Restart Six Degrees to finish it, or cancel it first.', { status: 409 });
373
+ }
374
+ if (currentPeople > 0 && confirmedPeople !== currentPeople) {
375
+ return new ImportError(
376
+ `This copy has ${currentPeople.toLocaleString('en-US')} people. Confirm that the backup replaces them.`,
377
+ { status: 409, needsConfirm: true, people: currentPeople },
378
+ );
379
+ }
380
+ return null;
381
+ }
382
+
383
+ /**
384
+ * The backup called `name`, only if it is one of the names backups/ holds now
385
+ * (lib/data-folder.js listBackups) and, unless `restorable` is false (Show in
386
+ * Finder), can be restored. Never a path: a name with a folder in it, "..", or
387
+ * anything the folder doesn't list is refused.
388
+ */
389
+ export function findBackup(backupsDir, name, { restorable = true } = {}) {
390
+ if (typeof name !== 'string' || !name || name !== path.basename(name) || name === '.' || name === '..'
391
+ || /[\\/\0]/.test(name)) {
392
+ return null;
393
+ }
394
+ if (!listBackups(backupsDir).some((b) => b.name === name)) return null;
395
+ return describeBackups(backupsDir).find((e) => e.name === name && (e.restorable || !restorable)) || null;
396
+ }
397
+
398
+ /**
399
+ * Stage a backup to replace the network at the next start: the same as an
400
+ * import (stageImport), from a copy of the backup in a private working folder,
401
+ * so the backup itself is never moved or changed. A database-only copy (from
402
+ * before this feature, or kept before an import) is made into a .sixdegrees
403
+ * file first, with the photos and files kept beside it, or, for one that has
404
+ * none, the photos and files here now.
405
+ * @returns what the page shows while it waits (READY).
406
+ * @throws ImportError, fit to show.
407
+ */
408
+ export function restoreBackup(name, {
409
+ dir, backupsDir, applySchema, schemaSql, appVersion, replacedPeople = 0, now = new Date(),
410
+ }) {
411
+ const entry = findBackup(backupsDir, name);
412
+ if (!entry) throw new ImportError('There is no backup by that name in the backups folder.', { status: 404 });
413
+ sweepLeftovers(dir);
414
+ const work = mkdtempSync(path.join(dir, UPLOAD_WORK_PREFIX));
415
+ try {
416
+ const upload = path.join(work, 'upload.sixdegrees');
417
+ const source = path.join(backupsDir, entry.name);
418
+ if (entry.format === 'sixdegrees') {
419
+ copyFileSync(source, upload);
420
+ } else {
421
+ // Read from a private copy: SQLite may write beside a database it opens,
422
+ // and nothing is ever written into backups/ by a restore.
423
+ const copy = path.join(work, 'backup.sqlite');
424
+ copyFileSync(source, copy);
425
+ let db;
426
+ try {
427
+ db = new DatabaseSync(copy);
428
+ } catch (err) {
429
+ throw new ImportError(`This backup couldn't be opened (${err.message}).`);
430
+ }
431
+ try {
432
+ // A copy kept before an import has the files that import stepped
433
+ // aside beside it, the Social tab's among them only if the import
434
+ // brought its own: undoing it puts them back. One with none beside it
435
+ // takes the photos and scanner files here now, and leaves the Social
436
+ // tab's where they are.
437
+ buildExport(db, {
438
+ dir: entry.withFiles ? path.join(backupsDir, entry.withFiles) : dir,
439
+ outFile: upload, includePhotos: true, includeSocial: Boolean(entry.withFiles), appVersion, schemaSql, now,
440
+ });
441
+ } catch (err) {
442
+ throw new ImportError(`This backup couldn't be read (${err.message}).`);
443
+ } finally {
444
+ db.close();
445
+ }
446
+ rmSync(copy, { force: true });
447
+ }
448
+ return stageImport(upload, {
449
+ dir, applySchema, schemaSql, appVersion, replacedPeople, now, restoredFrom: entry.name,
450
+ });
451
+ } catch (err) {
452
+ rmSync(work, { recursive: true, force: true });
453
+ if (err instanceof ImportError) {
454
+ throw new ImportError(plainProblem(err.message).replace(/^This file/, 'This backup'), { status: err.status, ...err.extra });
455
+ }
456
+ throw err;
457
+ }
458
+ }
@@ -296,3 +296,35 @@ function spread(wanted, sep) {
296
296
  seq.forEach((i, k) => { out[i] = placed[k]; });
297
297
  return out;
298
298
  }
299
+
300
+ /**
301
+ * How much is going on in each circle, for the order Bridge Chains rings them in:
302
+ * the busiest on the inner ring (Blake, 2026-10-03: "the inner most ring to have
303
+ * the clusters with the most amount of notifications ... a cluster that has been
304
+ * formed after scanning or if someone added back and is available to scan").
305
+ * Every notification about the circle counts, an unread one twice; so does each
306
+ * person in it who added you back and is ready to scan, and each cluster of
307
+ * their own that has formed from it since.
308
+ *
309
+ * @param {Array<{id: any}>} circles
310
+ * @param {{ notes?: Array<{circle: string|null, seen?: boolean}>, ready?: Map, chained?: Map }} facts
311
+ * @returns {Map<any, {score: number, notes: number, unread: number, ready: number, chains: number}>}
312
+ */
313
+ export function circleActivity(circles, { notes = [], ready = new Map(), chained = new Map() } = {}) {
314
+ const byCircle = new Map();
315
+ for (const n of notes) {
316
+ if (n?.circle == null) continue;
317
+ const c = byCircle.get(n.circle) || { notes: 0, unread: 0 };
318
+ c.notes += 1;
319
+ if (!n.seen) c.unread += 1;
320
+ byCircle.set(n.circle, c);
321
+ }
322
+ const out = new Map();
323
+ for (const b of circles) {
324
+ const n = byCircle.get(String(b.id)) || { notes: 0, unread: 0 };
325
+ const r = ready.get(b.id) || 0;
326
+ const ch = chained.get(b.id) || 0;
327
+ out.set(b.id, { score: n.notes + n.unread + r + ch, notes: n.notes, unread: n.unread, ready: r, chains: ch });
328
+ }
329
+ return out;
330
+ }
@@ -0,0 +1,131 @@
1
+ // A LinkedIn Connections.csv import, kept in the data folder (csv-network.json)
2
+ // so it is still there after the window closes and after the app restarts.
3
+ //
4
+ // Kept beside the database, never merged into it. The 1.0 list (2026-10-03)
5
+ // chose this over adding the rows to linkedin_connections as 1st degree:
6
+ // merging means matching CSV people to rows the scanner wrote by profile link,
7
+ // under a profile the CSV knows nothing about, and that is how people were
8
+ // duplicated, hidden and deleted before (TRAPS §19, §25, §36). Here the map
9
+ // reads the file instead of the database while it's open, as it always did
10
+ // from the window, and deleting the file is the whole of removing it.
11
+ //
12
+ // Only what the export says about each person is kept, packed as lib/csv.js
13
+ // packConnections makes it: id, name, position, company, profile link and the
14
+ // date you connected. Scores and tiers are worked out again on every load, so
15
+ // they follow the scoring model. The Email Address column is never read, so it
16
+ // can't be here.
17
+ //
18
+ // The file is read as untrusted: an import from another computer brings one
19
+ // (lib/data-folder.js NETWORK_FILES), and a hand edit or a bad disk can change
20
+ // it. A file that doesn't hold what this writes is never shown as a network;
21
+ // the page says it couldn't be read instead (TRAPS §7).
22
+
23
+ import { readFileSync, lstatSync, rmSync } from 'node:fs';
24
+ import path from 'node:path';
25
+ import { durable } from './durable.js';
26
+ import { CSV_NETWORK_FILE } from './data-folder.js';
27
+
28
+ export { CSV_NETWORK_FILE };
29
+
30
+ /** Bump when the file's layout changes in a way this version couldn't read. */
31
+ export const CSV_FORMAT = 1;
32
+
33
+ /** Each packed row, in order (lib/csv.js packConnections). */
34
+ export const CSV_COLUMNS = ['id', 'name', 'role', 'company', 'profile_url', 'connected_date'];
35
+
36
+ /**
37
+ * LinkedIn allows 30,000 connections. This leaves room, and still bounds what
38
+ * one request can make the app keep.
39
+ */
40
+ export const MAX_CSV_PEOPLE = 50000;
41
+ const MAX_FIELD = 2000;
42
+ const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
43
+
44
+ /** A refusal whose message is fit to show inside a sentence. Nothing was written. */
45
+ export class CsvStoreError extends Error {}
46
+
47
+ const text = (v, { empty = true } = {}) => typeof v === 'string' && v.length <= MAX_FIELD && (empty || v.trim() !== '');
48
+
49
+ /** Why `rows` aren't a packed import this app would make, or null when they are. */
50
+ export function csvRowsProblem(rows) {
51
+ if (!Array.isArray(rows)) return 'it has no list of people';
52
+ if (!rows.length) return 'it has nobody in it';
53
+ if (rows.length > MAX_CSV_PEOPLE) return `it has ${rows.length.toLocaleString('en-US')} people, more than the ${MAX_CSV_PEOPLE.toLocaleString('en-US')} it can hold`;
54
+ const ids = new Set();
55
+ for (let i = 0; i < rows.length; i++) {
56
+ const r = rows[i];
57
+ const which = `person ${i + 1}`;
58
+ if (!Array.isArray(r) || r.length !== CSV_COLUMNS.length) return `${which} isn't laid out the way the import keeps people`;
59
+ const [id, name, role, company, profileUrl, connected] = r;
60
+ if (!text(id, { empty: false }) || !text(name, { empty: false })) return `${which} has a name or id that is missing or too long`;
61
+ if (!text(role) || !text(company)) return `${which} has a position or company that isn't text, or is too long`;
62
+ if (profileUrl !== null && !text(profileUrl)) return `${which} has a profile link that isn't text, or is too long`;
63
+ if (connected !== null && !(typeof connected === 'string' && ISO_DATE.test(connected))) return `${which} has a date that isn't one`;
64
+ if (ids.has(id)) return `two people share the id ${JSON.stringify(id.slice(0, 40))}`;
65
+ ids.add(id);
66
+ }
67
+ return null;
68
+ }
69
+
70
+ /** Why `doc` isn't a kept import this app wrote, or null when it is. */
71
+ export function csvNetworkProblem(doc) {
72
+ if (!doc || typeof doc !== 'object' || Array.isArray(doc)) return "it isn't what Six Degrees writes";
73
+ if (doc.format !== CSV_FORMAT) {
74
+ return Number.isInteger(doc.format) && doc.format > CSV_FORMAT
75
+ ? 'it was written by a newer Six Degrees than this one'
76
+ : "it isn't what Six Degrees writes";
77
+ }
78
+ if (typeof doc.importedAt !== 'string' || Number.isNaN(Date.parse(doc.importedAt))) return "it doesn't say when it was imported";
79
+ return csvRowsProblem(doc.connections);
80
+ }
81
+
82
+ /**
83
+ * The kept import in `dir`: `{ csv: { connections, importedAt, people }, problem: null }`,
84
+ * `{ csv: null, problem: null }` when there is none, or `{ csv: null, problem }`
85
+ * when a file is there but can't be used. Never throws.
86
+ */
87
+ export function readCsvNetwork(dir) {
88
+ const file = path.join(dir, CSV_NETWORK_FILE);
89
+ let st;
90
+ try {
91
+ st = lstatSync(file);
92
+ } catch (err) {
93
+ if (err.code === 'ENOENT') return { csv: null, problem: null };
94
+ return { csv: null, problem: `it couldn't be opened (${err.code || err.message})` };
95
+ }
96
+ // A link is never followed, as nowhere else in the data folder is.
97
+ if (!st.isFile()) return { csv: null, problem: "it isn't a plain file" };
98
+ let doc;
99
+ try {
100
+ doc = JSON.parse(readFileSync(file, 'utf8'));
101
+ } catch (err) {
102
+ return { csv: null, problem: err instanceof SyntaxError ? "it isn't readable JSON" : `it couldn't be read (${err.code || err.message})` };
103
+ }
104
+ const problem = csvNetworkProblem(doc);
105
+ if (problem) return { csv: null, problem };
106
+ return { csv: { connections: doc.connections, importedAt: doc.importedAt, people: doc.connections.length }, problem: null };
107
+ }
108
+
109
+ /**
110
+ * Keep `connections` (packed rows) as the import, in place of any kept before.
111
+ * Written whole or not at all, and on the disk before this returns
112
+ * (lib/durable.js): a temporary file, synced, then renamed over the old one.
113
+ * @throws {CsvStoreError} when the rows aren't an import this app would make.
114
+ */
115
+ export function writeCsvNetwork(dir, connections, { now = new Date() } = {}) {
116
+ const problem = csvRowsProblem(connections);
117
+ if (problem) throw new CsvStoreError(problem);
118
+ const doc = { format: CSV_FORMAT, source: 'csv', importedAt: now.toISOString(), columns: CSV_COLUMNS, connections };
119
+ durable.writeFileDurably(path.join(dir, CSV_NETWORK_FILE), JSON.stringify(doc));
120
+ return { people: connections.length, importedAt: doc.importedAt };
121
+ }
122
+
123
+ /** Remove the kept import. Returns whether there was one. */
124
+ export function removeCsvNetwork(dir) {
125
+ const file = path.join(dir, CSV_NETWORK_FILE);
126
+ let had = true;
127
+ try { lstatSync(file); } catch { had = false; }
128
+ rmSync(file, { force: true });
129
+ if (had) durable.syncFolder(dir);
130
+ return had;
131
+ }